20 Commits
1.3.0 ... 2.0.0

Author SHA1 Message Date
Andrés Santibáñez
3d89d210b5 Added build 2020-11-30 12:48:14 -05:00
Andrés Santibáñez
f72c5294d9 Updated README 2020-11-30 12:45:44 -05:00
Andrés Santibáñez
76b64536d1 Added build system and assets for js 2020-11-30 12:13:03 -05:00
Andrés Santibáñez
fd4961d7ae Merge pull request #9 from AlexHupe/patch-1
Typo in README.md
2020-11-30 09:54:17 -05:00
Alexander Hupe
67bd18bc48 Typo in README.md 2020-11-19 09:35:05 +01:00
Andrés Santibáñez
940109ef03 Fixed infinite growing charts 2020-11-09 10:53:42 -05:00
Andrés Santibáñez
0457a9a007 Updated CHANGELOG 2020-11-09 10:18:23 -05:00
Andrés Santibáñez
2c7317c3b8 Removed unused directives 2020-11-09 10:17:29 -05:00
Andrés Santibáñez
98df89eca4 Updated README 2020-11-09 08:49:16 -05:00
Andrés Santibáñez
de8ed5c844 Added release badge :) 2020-11-09 08:43:36 -05:00
Andrés Santibáñez
81b502fd10 Increased package version 2020-11-09 08:39:24 -05:00
Andrés Santibáñez
01176c001e Updated CHANGELOG and README 2020-11-09 08:38:15 -05:00
Andrés Santibáñez
8e9aed484e Added sparkline feature to charts 2020-11-09 08:29:43 -05:00
Andrés Santibáñez
f5b40ec73d Added LivewireCharts facade 2020-11-08 21:53:26 -05:00
Andrés Santibáñez
dbbbf8eca2 Updated README and CHANGELOG 2020-11-07 19:50:52 -05:00
Andrés Santibáñez
a002d2f041 Added support for multi-column stacked charts 2020-11-07 19:40:23 -05:00
Andrés Santibáñez
e11c5ca375 Improved code 2020-11-07 19:21:19 -05:00
Andrés Santibáñez
0af61a2997 Added support for multi column chart 2020-11-07 19:21:13 -05:00
Andrés Santibáñez
cb3e4180a1 Added support for custom categories in Area Chart 2020-11-06 23:59:34 -05:00
Andrés Santibáñez
1e7ac1bffb Added ability to add custom categories to xAxis 2020-11-06 23:50:51 -05:00
30 changed files with 22931 additions and 446 deletions

1
.gitignore vendored
View File

@@ -5,3 +5,4 @@ docs
vendor vendor
coverage coverage
.DS_STORE .DS_STORE
node_modules

View File

@@ -1,5 +1,24 @@
# Changelog # Changelog
## 1.5.2 - 2020-11-09
- Fixed infinite growing charts
## 1.5.1 - 2020-11-09
- Removed unused Alpine directives
## 1.5.0 - 2020-11-09
- Added `LivewireCharts` facade to create any chart model
- Added `sparklined` Apex Chart feature to all charts (https://apexcharts.com/javascript-chart-demos/sparklines/basic/)
## 1.4.0 - 2020-11-07
- Added Multi Column Chart
- Added Stacked Multi Column Chart
- Added support for custom X Axis categories
## 1.3.0 - 2020-11-03 ## 1.3.0 - 2020-11-03
- Added Multi Line Chart - Added Multi Line Chart

View File

@@ -1,3 +1,5 @@
[![GitHub release](https://img.shields.io/github/release/asantibanez/livewire-charts.svg)](https://github.com/asantibanez/livewire-charts/releases/)
![Livewire Charts](https://banners.beyondco.de/Livewire%20Charts.png?theme=light&packageName=asantibanez%2Flivewire-charts&pattern=graphPaper&style=style_1&description=Neat+Livewire+Charts+for+your+Laravel+projects&md=1&fontSize=100px&images=chart-square-bar) ![Livewire Charts](https://banners.beyondco.de/Livewire%20Charts.png?theme=light&packageName=asantibanez%2Flivewire-charts&pattern=graphPaper&style=style_1&description=Neat+Livewire+Charts+for+your+Laravel+projects&md=1&fontSize=100px&images=chart-square-bar)
## Preview ## Preview
@@ -16,6 +18,10 @@ You can install the package via composer:
composer require asantibanez/livewire-charts composer require asantibanez/livewire-charts
``` ```
Next, you must export the package public scripts. To do this run `php artisan vendor:publish`
and export `livewire-charts:public`. This command will export a `vendor/livewire-charts` folder under the
`public` directory of your app.
## Requirements ## Requirements
This package requires the following packages/libraries to work: This package requires the following packages/libraries to work:
@@ -30,9 +36,9 @@ Please follow each package/library instructions on how to set them properly in y
## Usage ## Usage
Livewire Charts supports out of the box the following types of charts Livewire Charts supports out of the box the following types of charts
- Line Chart (`LivewireLineChart` component) - Line/Multi Line Chart (`LivewireLineChart` component)
- Pie Chart (`LivewirePieChart` component) - Pie Chart (`LivewirePieChart` component)
- Column Chart (`LivewireColumnChart` component) - Column/Multi Line Chart (`LivewireColumnChart` component)
- Area Chart (`LivewireAreaChart` component) - Area Chart (`LivewireAreaChart` component)
Each one comes with its own "model" class that allows you to define the chart's data and render behavior. Each one comes with its own "model" class that allows you to define the chart's data and render behavior.
@@ -62,6 +68,13 @@ With `$columnChartModel` at hand, we pass it to our `LivewireColumnChart` compon
/> />
``` ```
Next, include the `@livewireChartsScripts` directive next to your other app scripts
```html
<livewire:scripts />
@livewireChartsScripts
```
And that's it! You have a beautiful rendered chart in seconds. 👌 And that's it! You have a beautiful rendered chart in seconds. 👌
![column chart example](https://github.com/asantibanez/livewire-charts/raw/master/column-chart-example.png) ![column chart example](https://github.com/asantibanez/livewire-charts/raw/master/column-chart-example.png)
@@ -69,6 +82,19 @@ And that's it! You have a beautiful rendered chart in seconds. 👌
>Note: You can use these charts inside other Livewire components too. Just render them in your Blade template and you >Note: You can use these charts inside other Livewire components too. Just render them in your Blade template and you
are good to go. 👍 are good to go. 👍
## LivewireCharts facade
Chart models can also be created using the `LivewireCharts` facade.
```php
LivewireCharts::lineChartModel();
LivewireCharts::multiLineChartModel();
LivewireCharts::columnChartModel();
LivewireCharts::multiColumnChartModel();
LivewireCharts::pieChartModel();
LivewireCharts::areaChartModel();
```
## Enabling Interactions ## Enabling Interactions
To enable click events, you must use the `with[XXX]ClickEvent($eventName)` method present in every model class and To enable click events, you must use the `with[XXX]ClickEvent($eventName)` method present in every model class and
@@ -136,8 +162,11 @@ for each type of chart.
| setDataLabelsEnabled(boolean $enabled) | Enables/disables data labels | | setDataLabelsEnabled(boolean $enabled) | Enables/disables data labels |
| withDataLabels() | Enables data labels | | withDataLabels() | Enables data labels |
| withoutDataLabels() | Disables data labels | | withoutDataLabels() | Disables data labels |
| withLegends() | Shows legends | | withLegend() | Shows legends |
| withoutLegends() | Hides legends | | withoutLegend() | Hides legends |
| setXAxisCategories(array $categories) | Enables custom categories for chart X Axis |
| sparklined() | Enables Apex Charts sparkline feature |
| setSparklineEnabled(boolean $isEnabled) | Enables/Disables Apex Charts sparkline feature |
#### LivewireLineChart #### LivewireLineChart
@@ -155,6 +184,9 @@ for each type of chart.
| Method | Description | | Method | Description |
|--------|-------------| |--------|-------------|
| setOpacity(int $opacity) | Sets columns' opacity | | setOpacity(int $opacity) | Sets columns' opacity |
| multiColumn() | Sets chart to display multiple column series |
| singleColumn() | Sets chart to display a single column series |
| stacked() | Sets chart to display column series stacked |
| addColumn(string $title, double $value, string $color, array $extras = []) | Adds a column to the chart with the specified color. `$extras` is forwarded on click event | | addColumn(string $title, double $value, string $color, array $extras = []) | Adds a column to the chart with the specified color. `$extras` is forwarded on click event |
| onColumnClickEventName(string $eventName) | Event Name that will be fired when a column of the chart is clicked | | onColumnClickEventName(string $eventName) | Event Name that will be fired when a column of the chart is clicked |
@@ -175,6 +207,15 @@ for each type of chart.
| setXAxisVisible(boolean $visible) | Shows/hides xAxis labels | | setXAxisVisible(boolean $visible) | Shows/hides xAxis labels |
| setYAxisVisible(boolean $visible) | Shows/hides yAxis labels | | setYAxisVisible(boolean $visible) | Shows/hides yAxis labels |
## Advanced Usage - Integrating Scripts
The directive `@livewireChartsScripts` adds a `script` tag that includes `public/vendor/livewire-charts/app.js`.
If you want to include this script in your build system, you can export the package scripts
with `php artisan vendor:publish` and selecting `livewire-charts:scripts`. This will export `js/vendor/livewire-charts`
inside your resources folder. You can then adjust imports as you see fit in your project.
>Note: You must remove @livewireChartsScripts directive so it doesn't clash with the scripts in your build system.
## Troubleshooting ## Troubleshooting
Chart components must be placed inside a container with fixed height. This is because the chart will use all the given Chart components must be placed inside a container with fixed height. This is because the chart will use all the given

3
mix-manifest.json Normal file
View File

@@ -0,0 +1,3 @@
{
"/public/app.js": "/public/app.js"
}

22043
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

16
package.json Normal file
View File

@@ -0,0 +1,16 @@
{
"scripts": {
"dev": "npm run development",
"development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
"watch": "npm run development -- --watch",
"watch-poll": "npm run watch -- --watch-poll",
"prod": "npm run production",
"production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js"
},
"devDependencies": {
"apexcharts": "^3.22.2",
"cross-env": "^7.0.2",
"laravel-mix": "^5.0.9",
"vue-template-compiler": "^2.6.12"
}
}

2
public/app.js Normal file

File diff suppressed because one or more lines are too long

13
public/app.js.LICENSE.txt Normal file
View File

@@ -0,0 +1,13 @@
/*!
* ApexCharts v3.22.2
* (c) 2018-2020 Juned Chhipa
* Released under the MIT License.
*/
/*! svg.draggable.js - v2.2.2 - 2019-01-08
* https://github.com/svgdotjs/svg.draggable.js
* Copyright (c) 2019 Wout Fierens; Licensed MIT */
/*! svg.filter.js - v2.0.2 - 2016-02-24
* https://github.com/wout/svg.filter.js
* Copyright (c) 2016 Wout Fierens; Licensed MIT */

15
resources/js/app.js Normal file
View File

@@ -0,0 +1,15 @@
import ApexCharts from 'apexcharts'
import areaChart from "./areaChart"
import columnChart from "./columnChart"
import multiColumnChart from "./multiColumnChart";
import lineChart from "./lineChart"
import multiLineChart from "./multiLineChart"
import pieChart from "./pieChart"
window.ApexCharts = ApexCharts
window.livewireChartsAreaChart = areaChart
window.livewireChartsColumnChart = columnChart
window.livewireChartsLineChart = lineChart
window.livewireChartsMultiLineChart = multiLineChart
window.livewireChartsPieChart = pieChart
window.livewireChartsMultiColumnChart = multiColumnChart

90
resources/js/areaChart.js Normal file
View File

@@ -0,0 +1,90 @@
const areaChart = () => {
return {
chart: null,
drawChart(component) {
if (this.chart) {
this.chart.destroy()
}
const title = component.get('areaChartModel.title');
const animated = component.get('areaChartModel.animated') || false;
const dataLabels = component.get('areaChartModel.dataLabels') || {};
const onPointClickEventName = component.get('areaChartModel.onPointClickEventName')
const data = component.get('areaChartModel.data');
const sparkline = component.get('areaChartModel.sparkline');
const categories = component.get('areaChartModel.xAxis.categories').length > 0
? component.get('areaChartModel.xAxis.categories')
: data.map(item => item.title)
;
var options = {
series: [{
name: title,
data: data.map(item => item.value)
}],
chart: {
type: 'area',
height: '100%',
...sparkline,
zoom: { enabled: false },
toolbar: { show: false },
animations: { enabled: animated },
events: {
markerClick: function(event, chartContext, { dataPointIndex }) {
if (!onPointClickEventName) {
return
}
const point = data[dataPointIndex]
component.call('onPointClick', point)
}
}
},
dataLabels: dataLabels,
colors: [component.get('areaChartModel.color') || '#2E93fA'],
stroke: {
curve: 'straight'
},
title: {
text: title,
align: 'center'
},
labels: data.map(item => item.title),
xaxis: {
labels: component.get('areaChartModel.xAxis.labels'),
categories: categories,
},
yaxis: component.get('areaChartModel.yAxis') || {},
grid: {
padding: {
left: 0,
top: 0,
right: 0,
bottom: 0,
}
},
};
this.chart = new ApexCharts(this.$refs.container, options);
this.chart.render();
}
}
}
export default areaChart

View File

@@ -0,0 +1,84 @@
const columnChart = () => {
return {
chart: null,
drawChart(component) {
if (this.chart) {
this.chart.destroy()
}
const animated = component.get('columnChartModel.animated') || false;
const onColumnClickEventName = component.get('columnChartModel.onColumnClickEventName')
const dataLabels = component.get('columnChartModel.dataLabels') || {};
const data = component.get('columnChartModel.data');
const sparkline = component.get('columnChartModel.sparkline');
const options = {
series: [{ data: data.map(item => item.value)}],
chart: {
type: 'bar',
height: '100%',
...sparkline,
toolbar: { show: false },
animations: { enabled: animated },
events: {
dataPointSelection: function(event, chartContext, config) {
if (!onColumnClickEventName) {
return
}
const { dataPointIndex } = config
const column = data[dataPointIndex]
component.call('onColumnClick', column)
},
}
},
colors: data.map(item => item.color),
labels: {
style: {
colors: data.map(item => item.color),
},
},
legend: component.get('columnChartModel.legend') || {},
plotOptions: {
bar: {
horizontal: false,
columnWidth: '40%',
distributed: true,
},
},
dataLabels: dataLabels,
xaxis: {
categories: data.map(item => item.title),
},
yaxis: {
title: {
text: component.get('columnChartModel.title'),
}
},
fill: {
opacity: component.get('columnChartModel.opacity') || 0.5
},
};
this.chart = new ApexCharts(this.$refs.container, options);
this.chart.render();
}
}
}
export default columnChart

102
resources/js/lineChart.js Normal file
View File

@@ -0,0 +1,102 @@
const lineChart = () => {
return {
chart: null,
drawChart(component) {
if (this.chart) {
this.chart.destroy()
}
const title = component.get('lineChartModel.title');
const animated = component.get('lineChartModel.animated') || false;
const dataLabels = component.get('lineChartModel.dataLabels') || {};
const data = component.get('lineChartModel.data');
const onPointClickEventName = component.get('lineChartModel.onPointClickEventName');
const sparkline = component.get('lineChartModel.sparkline');
const series = [{
name: title,
data: data.map(item => item.value),
}]
const categories = component.get('lineChartModel.xAxis.categories').length > 0
? component.get('lineChartModel.xAxis.categories')
: data.map(item => item.title)
const options = {
series: series,
chart: {
type: 'line',
height: '100%',
...sparkline,
zoom: { enabled: false },
toolbar: { show: false },
animations: { enabled: animated },
events: {
markerClick: function(event, chartContext, { dataPointIndex }) {
if (!onPointClickEventName) {
return
}
const point = data[dataPointIndex]
component.call('onPointClick', point)
}
}
},
dataLabels: dataLabels,
stroke: component.get('lineChartModel.stroke') || {},
title: {
text: title,
align: 'center'
},
xaxis: {
labels: component.get('lineChartModel.xAxis.labels'),
categories: categories,
},
yaxis: component.get('lineChartModel.yAxis') || {},
annotations: {
points: component.get('lineChartModel.markers').map(item => {
return {
x: item.title,
y: item.value,
marker: {
size: 6,
fillColor: '#fff',
strokeColor: item.strokeColor,
radius: 2,
},
label: {
offsetY: 0,
style: {
color: item.textColor,
background: item.textBackgroundColor,
},
text: item.text || '',
}
}
}
)
},
};
this.chart = new ApexCharts(this.$refs.container, options);
this.chart.render();
}
}
}
export default lineChart

View File

@@ -0,0 +1,87 @@
const multiColumnChart = () => {
return {
chart: null,
drawChart(component) {
if (this.chart) {
this.chart.destroy()
}
const stacked = component.get('columnChartModel.isStacked');
const animated = component.get('columnChartModel.animated');
const onColumnClickEventName = component.get('columnChartModel.onColumnClickEventName')
const dataLabels = component.get('columnChartModel.dataLabels');
const data = component.get('columnChartModel.data');
const sparkline = component.get('columnChartModel.sparkline');
const series = Object.keys(data)
.map(seriesName => ({
name: seriesName,
data: data[seriesName].map(item => item.value)
}))
const categories = component.get('columnChartModel.xAxis.categories').length > 0
? component.get('columnChartModel.xAxis.categories')
: data[series[0].name].map(item => item.title)
;
const options = {
series: series,
chart: {
type: 'bar',
height: '100%',
stacked: stacked,
...sparkline,
toolbar: { show: false },
animations: { enabled: animated },
events: {
dataPointSelection: function(event, chartContext, {seriesIndex, dataPointIndex}) {
if (!onColumnClickEventName) {
return
}
const column = data[series[seriesIndex].name][dataPointIndex]
component.call('onColumnClick', column)
},
}
},
legend: component.get('columnChartModel.legend'),
plotOptions: {
bar: {
horizontal: false,
columnWidth: '50%',
},
},
dataLabels: dataLabels,
xaxis: {
categories: categories,
},
yaxis: {
title: {
text: component.get('columnChartModel.title'),
}
},
fill: {
opacity: component.get('columnChartModel.opacity'),
},
};
this.chart = new ApexCharts(this.$refs.container, options);
this.chart.render();
}
}
}
export default multiColumnChart

View File

@@ -0,0 +1,79 @@
const multiLineChart = () => {
return {
chart: null,
drawChart(component) {
if (this.chart) {
this.chart.destroy()
}
const title = component.get('lineChartModel.title');
const animated = component.get('lineChartModel.animated') || false;
const dataLabels = component.get('lineChartModel.dataLabels') || {};
const data = component.get('lineChartModel.data');
const onPointClickEventName = component.get('lineChartModel.onPointClickEventName');
const sparkline = component.get('lineChartModel.sparkline');
const series = Object.keys(data).map(key => {
return {
name: key,
data: data[key].map(item => item.value),
}
})
const categories = component.get('lineChartModel.xAxis.categories').length > 0
? component.get('lineChartModel.xAxis.categories')
: data[series[0].name].map(item => item.title)
const options = {
series: series,
chart: {
type: 'line',
height: '100%',
...sparkline,
zoom: { enabled: false },
toolbar: { show: false },
animations: { enabled: animated },
events: {
markerClick: function(event, chartContext, { seriesIndex, dataPointIndex }) {
if (!onPointClickEventName) {
return
}
const point = data[series[seriesIndex].name][dataPointIndex]
component.call('onPointClick', point)
}
}
},
dataLabels: dataLabels,
stroke: component.get('lineChartModel.stroke') || {},
title: {
text: title,
align: 'center'
},
xaxis: {
labels: component.get('lineChartModel.xAxis.labels'),
categories: categories,
},
yaxis: component.get('lineChartModel.yAxis') || {},
};
this.chart = new ApexCharts(this.$refs.container, options);
this.chart.render();
}
}
}
export default multiLineChart

77
resources/js/pieChart.js Normal file
View File

@@ -0,0 +1,77 @@
const pieChart = () => {
return {
chart: null,
drawChart(component) {
if (this.chart) {
this.chart.destroy()
}
const title = component.get('pieChartModel.title');
const animated = component.get('pieChartModel.animated') || false
const dataLabels = component.get('pieChartModel.dataLabels') || {}
const onSliceClickEventName = component.get('pieChartModel.onSliceClickEventName')
const data = component.get('pieChartModel.data')
const sparkline = component.get('pieChartModel.sparkline')
const options = {
series: data.map(item => item.value),
chart: {
height: '100%',
type: 'pie',
...sparkline,
animations: { enabled: animated },
events: {
dataPointSelection: function(event, chartContext, config) {
if (!onSliceClickEventName) {
return
}
const { dataPointIndex } = config
const slice = data[dataPointIndex]
component.call('onSliceClick', slice)
},
}
},
labels: data.map(item => item.title),
dataLabels: dataLabels,
colors: data.map(item => item.color),
fill: {
opacity: component.get('pieChartModel.opacity'),
},
title: {
text: title,
align: 'center',
},
responsive: [
{
breakpoint: 600,
options: {
legend: {
position: 'bottom'
}
}
}
],
legend: component.get('pieChartModel.legend') || {},
};
this.chart = new ApexCharts(this.$refs.container, options);
this.chart.render();
}
}
}
export default pieChart

View File

@@ -1,90 +1,8 @@
<div <div
class="w-full h-full" style="width: 100%; height: 100%;"
x-data="{ ...areaChart() }" x-data="{ ...livewireChartsAreaChart() }"
x-init="drawChart(@this)" x-init="drawChart(@this)"
x-on:draw-column-chart.window="drawChart(@this)"
> >
<div wire:ignore x-ref="container"></div> <div wire:ignore x-ref="container"></div>
<script>
function areaChart() {
return {
chart: null,
drawChart(component) {
if (this.chart) {
this.chart.destroy()
}
const title = component.get('areaChartModel.title');
const animated = component.get('areaChartModel.animated') || false;
const dataLabels = component.get('areaChartModel.dataLabels') || {};
const onPointClickEventName = component.get('areaChartModel.onPointClickEventName')
const data = component.get('areaChartModel.data');
var options = {
series: [{
name: title,
data: data.map(item => item.value)
}],
chart: {
type: 'area',
height: '100%',
zoom: { enabled: false },
toolbar: { show: false },
animations: {
enabled: animated,
},
events: {
markerClick: function(event, chartContext, { dataPointIndex }) {
if (!onPointClickEventName) {
return
}
const point = data[dataPointIndex]
component.call('onPointClick', point)
}
}
},
dataLabels: dataLabels,
colors: [component.get('areaChartModel.color') || '#2E93fA'],
stroke: {
curve: 'straight'
},
title: {
text: title,
align: 'center'
},
labels: data.map(item => item.title),
xaxis: component.get('areaChartModel.xAxis') || {},
yaxis: component.get('areaChartModel.yAxis') || {},
grid: {
padding: {
left: 0,
top: 0,
right: 0,
bottom: 0,
}
},
};
this.chart = new ApexCharts(this.$refs.container, options);
this.chart.render();
}
}
}
</script>
</div> </div>

View File

@@ -1,94 +1,8 @@
<div <div
class="w-full h-full" style="width: 100%; height: 100%;"
x-data="{ ...columnChart() }" x-data="{ ...livewireChartsColumnChart() }"
x-init="drawChart(@this)" x-init="drawChart(@this)"
x-on:draw-column-chart.window="drawChart(@this)"
> >
<div wire:ignore x-ref="container"></div> <div wire:ignore x-ref="container"></div>
<script>
function columnChart() {
return {
chart: null,
drawChart(component) {
if (this.chart) {
this.chart.destroy()
}
const animated = component.get('columnChartModel.animated') || false;
const onColumnClickEventName = component.get('columnChartModel.onColumnClickEventName')
const dataLabels = component.get('columnChartModel.dataLabels') || {};
const data = component.get('columnChartModel.data');
const options = {
series: [{ data: data.map(item => item.value)}],
chart: {
type: 'bar',
height: '100%',
toolbar: {
show: false
},
animations: {
enabled: animated,
},
events: {
dataPointSelection: function(event, chartContext, config) {
if (!onColumnClickEventName) {
return
}
const { dataPointIndex } = config
const column = data[dataPointIndex]
component.call('onColumnClick', column)
},
}
},
colors: data.map(item => item.color),
labels: {
style: {
colors: data.map(item => item.color),
},
},
legend: component.get('columnChartModel.legend') || {},
plotOptions: {
bar: {
horizontal: false,
columnWidth: '40%',
distributed: true,
},
},
dataLabels: dataLabels,
xaxis: {
categories: data.map(item => item.title),
},
yaxis: {
title: {
text: component.get('columnChartModel.title'),
}
},
fill: {
opacity: component.get('columnChartModel.opacity') || 0.5
},
};
this.chart = new ApexCharts(this.$refs.container, options);
this.chart.render();
}
}
}
</script>
</div> </div>

View File

@@ -1,106 +1,8 @@
<div <div
class="w-full h-full" style="width: 100%; height: 100%;"
x-data="{ ...lineChart() }" x-data="{ ...livewireChartsLineChart() }"
x-init="drawChart(@this)" x-init="drawChart(@this)"
> >
<div wire:ignore x-ref="container"></div> <div wire:ignore x-ref="container"></div>
<script>
function lineChart() {
return {
chart: null,
drawChart(component) {
if (this.chart) {
this.chart.destroy()
}
const title = component.get('lineChartModel.title');
const animated = component.get('lineChartModel.animated') || false;
const dataLabels = component.get('lineChartModel.dataLabels') || {};
const data = component.get('lineChartModel.data');
const onPointClickEventName = component.get('lineChartModel.onPointClickEventName');
const series = [{
name: title,
data: data.map(item => item.value),
}]
const categories = data.map(item => item.title)
const options = {
series: series,
chart: {
type: 'line',
height: '100%',
zoom: {
enabled: false
},
toolbar: {
show: false
},
animations: {
enabled: animated,
},
events: {
markerClick: function(event, chartContext, { dataPointIndex }) {
if (!onPointClickEventName) {
return
}
const point = data[dataPointIndex]
component.call('onPointClick', point)
}
}
},
dataLabels: dataLabels,
stroke: component.get('lineChartModel.stroke') || {},
title: {
text: title,
align: 'center'
},
xaxis: {
...component.get('lineChartModel.xAxis') || {},
categories: categories,
},
yaxis: component.get('lineChartModel.yAxis') || {},
annotations: {
points: component.get('lineChartModel.markers').map(item => {
return {
x: item.title,
y: item.value,
marker: {
size: 6,
fillColor: '#fff',
strokeColor: item.strokeColor,
radius: 2,
},
label: {
offsetY: 0,
style: {
color: item.textColor,
background: item.textBackgroundColor,
},
text: item.text || '',
}
}
}
)
},
};
this.chart = new ApexCharts(this.$refs.container, options);
this.chart.render();
}
}
}
</script>
</div> </div>

View File

@@ -0,0 +1,8 @@
<div
style="width: 100%; height: 100%;"
x-data="{ ...livewireChartsMultiColumnChart() }"
x-init="drawChart(@this)"
>
<div wire:ignore x-ref="container"></div>
</div>

View File

@@ -1,85 +1,8 @@
<div <div
class="w-full h-full" style="width: 100%; height: 100%;"
x-data="{ ...multiLineChart() }" x-data="{ ...livewireChartsMultiLineChart() }"
x-init="drawChart(@this)" x-init="drawChart(@this)"
> >
<div wire:ignore x-ref="container"></div> <div wire:ignore x-ref="container"></div>
<script>
function multiLineChart() {
return {
chart: null,
drawChart(component) {
if (this.chart) {
this.chart.destroy()
}
const title = component.get('lineChartModel.title');
const animated = component.get('lineChartModel.animated') || false;
const dataLabels = component.get('lineChartModel.dataLabels') || {};
const data = component.get('lineChartModel.data');
const onPointClickEventName = component.get('lineChartModel.onPointClickEventName');
const series = Object.keys(data).map(key => {
return {
name: key,
data: data[key].map(item => item.value),
}
})
const categories = []
const options = {
series: series,
chart: {
type: 'line',
height: '100%',
zoom: {
enabled: false
},
toolbar: {
show: false
},
animations: {
enabled: animated,
},
events: {
markerClick: function(event, chartContext, { seriesIndex, dataPointIndex }) {
if (!onPointClickEventName) {
return
}
const seriesName = Object.keys(data)[Object.keys(data).findIndex((el, index) => index === seriesIndex)]
const point = data[seriesName][dataPointIndex]
component.call('onPointClick', point)
}
}
},
dataLabels: dataLabels,
stroke: component.get('lineChartModel.stroke') || {},
title: {
text: title,
align: 'center'
},
xaxis: {
...component.get('lineChartModel.xAxis') || {},
categories: categories,
},
yaxis: component.get('lineChartModel.yAxis') || {},
};
this.chart = new ApexCharts(this.$refs.container, options);
this.chart.render();
}
}
}
</script>
</div> </div>

View File

@@ -1,84 +1,8 @@
<div <div
class="w-full h-full" style="width: 100%; height: 100%;"
x-data="{ ...pieChart() }" x-data="{ ...livewireChartsPieChart() }"
x-init="drawChart(@this)" x-init="drawChart(@this)"
> >
<div wire:ignore x-ref="container"></div> <div wire:ignore x-ref="container"></div>
<script>
function pieChart() {
return {
chart: null,
drawChart(component) {
if (this.chart) {
this.chart.destroy()
}
const title = component.get('pieChartModel.title');
const animated = component.get('pieChartModel.animated') || false
const dataLabels = component.get('pieChartModel.dataLabels') || {}
const onSliceClickEventName = component.get('pieChartModel.onSliceClickEventName')
const data = component.get('pieChartModel.data')
const options = {
series: data.map(item => item.value),
chart: {
height: '100%',
type: 'pie',
animations: {
enabled: animated,
},
events: {
dataPointSelection: function(event, chartContext, config) {
if (!onSliceClickEventName) {
return
}
const { dataPointIndex } = config
const slice = data[dataPointIndex]
component.call('onSliceClick', slice)
},
}
},
labels: data.map(item => item.title),
dataLabels: dataLabels,
colors: data.map(item => item.color),
fill: {
opacity: component.get('pieChartModel.opacity'),
},
title: {
text: title,
align: 'center',
},
responsive: [
{
breakpoint: 600,
options: {
legend: {
position: 'bottom'
}
}
}
],
legend: component.get('pieChartModel.legend') || {},
};
this.chart = new ApexCharts(this.$refs.container, options);
this.chart.render();
}
}
}
</script>
</div> </div>

View File

@@ -31,6 +31,10 @@ class LivewireColumnChart extends Component
public function render() public function render()
{ {
if ($this->columnChartModel['isMultiColumn']) {
return view('livewire-charts::livewire-multi-column-chart');
}
return view('livewire-charts::livewire-column-chart'); return view('livewire-charts::livewire-column-chart');
} }
} }

View File

@@ -0,0 +1,27 @@
<?php
namespace Asantibanez\LivewireCharts\Facades;
use Asantibanez\LivewireCharts\Models\AreaChartModel;
use Asantibanez\LivewireCharts\Models\ColumnChartModel;
use Asantibanez\LivewireCharts\Models\LineChartModel;
use Asantibanez\LivewireCharts\Models\PieChartModel;
use Illuminate\Support\Facades\Facade;
/**
* Class LivewireCharts
* @package Asantibanez\LivewireCharts\Facades
* @method static LineChartModel lineChartModel()
* @method static LineChartModel multiLineChartModel()
* @method static ColumnChartModel columnChartModel()
* @method static ColumnChartModel multiColumnChartModel()
* @method static AreaChartModel areaChartModel()
* @method static PieChartModel pieChartModel()
*/
class LivewireCharts extends Facade
{
protected static function getFacadeAccessor()
{
return 'livewirecharts';
}
}

47
src/LivewireCharts.php Normal file
View File

@@ -0,0 +1,47 @@
<?php
namespace Asantibanez\LivewireCharts;
use Asantibanez\LivewireCharts\Models\AreaChartModel;
use Asantibanez\LivewireCharts\Models\ColumnChartModel;
use Asantibanez\LivewireCharts\Models\LineChartModel;
use Asantibanez\LivewireCharts\Models\PieChartModel;
class LivewireCharts
{
public function lineChartModel()
{
return (new LineChartModel)
->singleLine();
}
public function multiLineChartModel()
{
return (new LineChartModel)
->multiLine();
}
public function columnChartModel()
{
return (new ColumnChartModel)
->singleColumn();
}
public function multiColumnChartModel()
{
return (new ColumnChartModel)
->multiColumn();
}
public function areaChartModel()
{
return new AreaChartModel;
}
public function pieChartModel()
{
return new PieChartModel;
}
}

View File

@@ -6,35 +6,65 @@ use Asantibanez\LivewireCharts\Charts\LivewireAreaChart;
use Asantibanez\LivewireCharts\Charts\LivewireColumnChart; use Asantibanez\LivewireCharts\Charts\LivewireColumnChart;
use Asantibanez\LivewireCharts\Charts\LivewireLineChart; use Asantibanez\LivewireCharts\Charts\LivewireLineChart;
use Asantibanez\LivewireCharts\Charts\LivewirePieChart; use Asantibanez\LivewireCharts\Charts\LivewirePieChart;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\ServiceProvider; use Illuminate\Support\ServiceProvider;
use Livewire\Livewire; use Livewire\Livewire;
class LivewireChartsServiceProvider extends ServiceProvider class LivewireChartsServiceProvider extends ServiceProvider
{ {
/**
* Bootstrap the application services.
*/
public function boot() public function boot()
{ {
$this->loadViewsFrom(__DIR__.'/../resources/views', 'livewire-charts'); $this->registerViews();
$this->registerPublishables();
$this->registerComponents();
$this->registerDirectives();
}
public function register()
{
$this->app->bind('livewirecharts', LivewireCharts::class);
}
private function registerViews()
{
$this->loadViewsFrom(__DIR__.'/../resources/views', 'livewire-charts');
}
private function registerPublishables()
{
if ($this->app->runningInConsole()) { if ($this->app->runningInConsole()) {
$this->publishes([ $this->publishes([
__DIR__.'/../resources/views' => resource_path('views/vendor/livewire-charts'), __DIR__.'/../resources/views' => resource_path('views/vendor/livewire-charts'),
], 'livewire-charts-views'); ], 'livewire-charts:views');
}
$this->publishes([
__DIR__.'/../resources/js' => resource_path('js/vendor/livewire-charts'),
], 'livewire-charts:scripts');
$this->publishes([
__DIR__.'/../public' => public_path('vendor/livewire-charts'),
], 'livewire-charts:public');
}
}
private function registerComponents()
{
Livewire::component('livewire-line-chart', LivewireLineChart::class); Livewire::component('livewire-line-chart', LivewireLineChart::class);
Livewire::component('livewire-column-chart', LivewireColumnChart::class); Livewire::component('livewire-column-chart', LivewireColumnChart::class);
Livewire::component('livewire-pie-chart', LivewirePieChart::class); Livewire::component('livewire-pie-chart', LivewirePieChart::class);
Livewire::component('livewire-area-chart', LivewireAreaChart::class); Livewire::component('livewire-area-chart', LivewireAreaChart::class);
} }
/** private function registerDirectives()
* Register the application services.
*/
public function register()
{ {
// Blade::directive('livewireChartsScripts', function () {
$scriptsUrl = asset('/vendor/livewire-charts/app.js');
return <<<EOF
<script src="$scriptsUrl"></script>
EOF;
});
} }
} }

View File

@@ -15,6 +15,7 @@ class BaseChartModel
use HasStroke; use HasStroke;
use HasLegend; use HasLegend;
use HasDataLabels; use HasDataLabels;
use HasSparkline;
public function __construct() public function __construct()
{ {
@@ -24,6 +25,7 @@ class BaseChartModel
$this->initStroke(); $this->initStroke();
$this->initLegend(); $this->initLegend();
$this->initDataLabels(); $this->initDataLabels();
$this->initSparkline();
} }
public function reactiveKey() public function reactiveKey()
@@ -39,7 +41,8 @@ class BaseChartModel
$this->axisToArray(), $this->axisToArray(),
$this->strokeToArray(), $this->strokeToArray(),
$this->legendToArray(), $this->legendToArray(),
$this->dataLabelsToArray() $this->dataLabelsToArray(),
$this->sparklineToArray()
); );
} }
@@ -51,5 +54,6 @@ class BaseChartModel
$this->strokeFromArray($array); $this->strokeFromArray($array);
$this->legendFromArray($array); $this->legendFromArray($array);
$this->dataLabelsFromArray($array); $this->dataLabelsFromArray($array);
$this->sparklineFromArray($array);
} }
} }

View File

@@ -6,11 +6,17 @@ namespace Asantibanez\LivewireCharts\Models;
/** /**
* Class ColumnChartModel * Class ColumnChartModel
* @package Asantibanez\LivewireCharts\Models * @package Asantibanez\LivewireCharts\Models
* @property boolean $isMultiColumn
* @property boolean $isStacked
*/ */
class ColumnChartModel extends BaseChartModel class ColumnChartModel extends BaseChartModel
{ {
public $opacity; public $opacity;
public $isMultiColumn;
public $isStacked;
public $onColumnClickEventName; public $onColumnClickEventName;
public $data; public $data;
@@ -23,9 +29,34 @@ class ColumnChartModel extends BaseChartModel
$this->opacity = 0.5; $this->opacity = 0.5;
$this->isMultiColumn = false;
$this->isStacked = false;
$this->data = collect(); $this->data = collect();
} }
public function multiColumn()
{
$this->isMultiColumn = true;
return $this;
}
public function singleColumn()
{
$this->isMultiColumn = false;
return $this;
}
public function stacked()
{
$this->isStacked = true;
return $this;
}
public function setOpacity($opacity) public function setOpacity($opacity)
{ {
$this->opacity = $opacity; $this->opacity = $opacity;
@@ -52,11 +83,29 @@ class ColumnChartModel extends BaseChartModel
return $this; return $this;
} }
public function addSeriesColumn($seriesName, $title, $value, $extras = [])
{
$series = $this->data->get($seriesName, collect());
$series->push([
'seriesName' => $seriesName,
'title' => $title,
'value' => $value,
'extras' => $extras,
]);
$this->data->put($seriesName, $series);
return $this;
}
public function toArray() public function toArray()
{ {
return array_merge(parent::toArray(), [ return array_merge(parent::toArray(), [
'onColumnClickEventName' => $this->onColumnClickEventName, 'onColumnClickEventName' => $this->onColumnClickEventName,
'opacity' => $this->opacity, 'opacity' => $this->opacity,
'isMultiColumn' => $this->isMultiColumn,
'isStacked' => $this->isStacked,
'data' => $this->data->toArray(), 'data' => $this->data->toArray(),
]); ]);
} }
@@ -69,6 +118,10 @@ class ColumnChartModel extends BaseChartModel
$this->opacity = data_get($array, 'opacity', 0.5); $this->opacity = data_get($array, 'opacity', 0.5);
$this->isMultiColumn = data_get($array, 'isMultiColumn', false);
$this->isStacked = data_get($array, 'isStacked', false);
$this->data = collect(data_get($array, 'data', [])); $this->data = collect(data_get($array, 'data', []));
} }
} }

View File

@@ -10,7 +10,7 @@ trait HasAxis
private $yAxis; private $yAxis;
public function initAxis() protected function initAxis()
{ {
$this->xAxis = $this->defaultXAxis(); $this->xAxis = $this->defaultXAxis();
$this->yAxis = $this->defaultYAxis(); $this->yAxis = $this->defaultYAxis();
@@ -19,6 +19,7 @@ trait HasAxis
private function defaultXAxis() private function defaultXAxis()
{ {
return [ return [
'categories' => [],
'labels' => [ 'labels' => [
'show' => true, 'show' => true,
], ],
@@ -39,6 +40,13 @@ trait HasAxis
return $this; return $this;
} }
public function setXAxisCategories($categories = [])
{
data_set($this->xAxis, 'categories', $categories);
return $this;
}
public function setYAxisVisible($visible) public function setYAxisVisible($visible)
{ {
data_set($this->yAxis, 'show', $visible); data_set($this->yAxis, 'show', $visible);

View File

@@ -0,0 +1,48 @@
<?php
namespace Asantibanez\LivewireCharts\Models;
trait HasSparkline
{
private $sparkline;
public function setSparklineEnabled($isEnabled)
{
data_set($this->sparkline, 'sparkline.enabled', $isEnabled);
return $this;
}
public function sparklined()
{
return $this->setSparklineEnabled(true);
}
protected function initSparkline()
{
$this->sparkline = $this->defaultSparkline();
}
private function defaultSparkline()
{
return [
'sparkline' => [
'enabled' => false,
],
];
}
protected function sparklineFromArray($array)
{
$this->sparkline = data_get($array, 'sparkline', $this->defaultSparkline());
}
protected function sparklineToArray()
{
return [
'sparkline' => $this->sparkline,
];
}
}

3
webpack.mix.js Normal file
View File

@@ -0,0 +1,3 @@
let mix = require('laravel-mix')
mix.js('resources/js/app.js', 'public/')