35 Commits
1.0.0 ... 1.5.2

Author SHA1 Message Date
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
Andrés Santibáñez
0aedf541a9 Updated README and CHANGELOG 2020-11-03 08:02:36 -05:00
Andrés Santibáñez
316bb5a976 Removed markers support for multi line chart 2020-11-03 07:59:45 -05:00
Andrés Santibáñez
05c9f5c605 Added DataLabels support for charts 2020-11-03 07:44:32 -05:00
Andrés Santibáñez
63fe7a1533 Extracted title property to trait 2020-11-03 07:20:46 -05:00
Andrés Santibáñez
22b72c1980 Extracted multi line chart Blade view 2020-11-03 07:16:51 -05:00
Andrés Santibáñez
5e7cabfd16 Extracted animated property to trait 2020-11-03 07:10:30 -05:00
Andrés Santibáñez
71e2168b6c Added MultiLine support for Line Chart 2020-11-03 07:07:02 -05:00
Andrés Santibáñez
9b74d45a86 Updated README 2020-11-02 22:01:02 -05:00
Andrés Santibáñez
19f3ce70ae Updated README 2020-11-02 21:51:15 -05:00
Andrés Santibáñez
4c55e19486 Updated README 2020-10-25 01:01:39 -05:00
Andrés Santibáñez
1b7f928f94 Updated CHANGELOG 2020-10-25 00:57:26 -05:00
Andrés Santibáñez
28fa9421ad Added axis configuration to Line Chart 2020-10-25 00:55:08 -05:00
Andrés Santibáñez
09f8b354f7 Added more expressive api for legend config 2020-10-25 00:49:10 -05:00
Andrés Santibáñez
8586357077 Updated gitignore 2020-10-25 00:41:11 -05:00
Andrés Santibáñez
9df03c36a8 Added HasLegend trait
- Refactored component to use BaseChartModel and traits
2020-10-25 00:40:06 -05:00
Andrés Santibáñez
8126b95593 Added Stroke configuration 2020-10-25 00:18:20 -05:00
Andrés Santibáñez
472dfd8a4a Removed unused function 2020-10-25 00:17:34 -05:00
Andrés Santibáñez
865c1fe13f Fixed layout 2020-10-24 23:54:18 -05:00
asantibanez
e8003a33bd Updated docs 2020-10-20 10:46:59 -05:00
asantibanez
1d0755e249 Added Area Chart component and model 2020-10-20 10:43:04 -05:00
27 changed files with 1209 additions and 166 deletions

1
.gitignore vendored
View File

@@ -4,3 +4,4 @@ composer.lock
docs docs
vendor vendor
coverage coverage
.DS_STORE

View File

@@ -1,5 +1,41 @@
# 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
- Added Multi Line Chart
- Added Data Labels configuration for all charts
- Refactored title and animation to traits
## 1.2.0 - 2020-10-25
- Fixed column chart growing infinitely
- Improved customization API
- Added Legend configuration
- Refactored configuration traits
## 1.1.0 - 2020-10-20
- Added Area Chart
## 1.0.0 - 2020-10-19 ## 1.0.0 - 2020-10-19
- Initial Release - Initial Release

View File

@@ -1,11 +1,15 @@
# Livewire Charts [![GitHub release](https://img.shields.io/github/release/asantibanez/livewire-charts.svg)](https://github.com/asantibanez/livewire-charts/releases/)
Neat Livewire Charts for your Laravel projects. ![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
![preview](https://github.com/asantibanez/livewire-charts/raw/master/preview.gif) ![preview](https://github.com/asantibanez/livewire-charts/raw/master/preview.gif)
## Demo
https://github.com/asantibanez/livewire-charts-demo
## Installation ## Installation
You can install the package via composer: You can install the package via composer:
@@ -23,7 +27,7 @@ This package requires the following packages/libraries to work:
Please follow each package/library instructions on how to set them properly in your project. Please follow each package/library instructions on how to set them properly in your project.
> Note: At the moment, `Apex Charts` is only supported for drawing charts. >Note: At the moment, `Apex Charts` is only supported for drawing charts.
## Usage ## Usage
@@ -31,11 +35,13 @@ Livewire Charts supports out of the box the following types of charts
- Line Chart (`LivewireLineChart` component) - Line Chart (`LivewireLineChart` component)
- Pie Chart (`LivewirePieChart` component) - Pie Chart (`LivewirePieChart` component)
- Column Chart (`LivewireColumnChart` component) - Column Chart (`LivewireColumnChart` 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.
- `LivewireLineChart` uses `LineChartModel` to set up data points, markers, events and other ui customizations. - `LivewireLineChart` uses `LineChartModel` to set up data points, markers, events and other ui customizations.
- `LivewirePieChart` uses `PieChartModel` to set up data slices, events and other ui customizations. - `LivewirePieChart` uses `PieChartModel` to set up data slices, events and other ui customizations.
- `LivewireColumnChart` uses `ColumnChartModel` to set up data columns, events and other ui customizations. - `LivewireColumnChart` uses `ColumnChartModel` to set up data columns, events and other ui customizations.
- `LivewireAreaChart` uses `AreaChartModel` to set up data points, events and other ui customizations.
For example, to render a column chart, we can create an instance of `ColumnChartModel` and add some data to it For example, to render a column chart, we can create an instance of `ColumnChartModel` and add some data to it
```php ```php
@@ -48,7 +54,7 @@ $columnChartModel =
; ;
``` ```
> Note: Chart model methods are chainable 💪 >Note: Chart model methods are chainable 💪
With `$columnChartModel` at hand, we pass it to our `LivewireColumnChart` component in our Blade template. With `$columnChartModel` at hand, we pass it to our `LivewireColumnChart` component in our Blade template.
@@ -62,8 +68,21 @@ 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)
> 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
@@ -123,23 +142,40 @@ The parent component renders the charts as follows
For each chart, a specific model class needs to be used. Here, it is detailed the api available For each chart, a specific model class needs to be used. Here, it is detailed the api available
for each type of chart. for each type of chart.
#### LivewireLineChart #### All
| Method | Description | | Method | Description |
|--------|-------------| |--------|-------------|
| setTitle(string $title) | Sets chart title | | setTitle(string $title) | Sets chart title |
| setAnimated(boolean $animated) | Enables/disables animation | | setAnimated(boolean $animated) | Enables/disables animation |
| addPoint(string $title, double $value, array $extras = []) | Adds a point to the line chart. `$extras` is forwarded on click event | | setDataLabelsEnabled(boolean $enabled) | Enables/disables data labels |
| addMarker(string $title, double $value) | Adds a marker point to the line chart. Markers are used to emphasize particular points in the chart | | withDataLabels() | Enables data labels |
| withoutDataLabels() | Disables data labels |
| withLegends() | Shows legends |
| withoutLegends() | 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
| Method | Description |
|--------|-------------|
| multiLine() | Adds multiline support for line chart |
| singleLine() | Adds single support for line chart |
| addPoint(string $title, double $value, array $extras = []) | Adds a point to a single line chart. `$extras` is forwarded on click event |
| addSeriesPoint(string $seriesName, string $title, double $value, array $extras = []) | Adds a point to series to a multi line chart. `$extras` is forwarded on click event |
| addMarker(string $title, double $value) | Adds a marker point to the line chart. Markers are used to emphasize particular points in the chart (singleLine only) |
| withOnPointClickEvent(string $eventName) | Event Name that will be fired when a point/marker of the chart is clicked | | withOnPointClickEvent(string $eventName) | Event Name that will be fired when a point/marker of the chart is clicked |
#### LivewireColumnChart #### LivewireColumnChart
| Method | Description | | Method | Description |
|--------|-------------| |--------|-------------|
| setTitle(string $title) | Sets chart title |
| setAnimated(boolean $animated) | Enables/disables animation |
| 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 |
@@ -147,12 +183,32 @@ for each type of chart.
| Method | Description | | Method | Description |
|--------|-------------| |--------|-------------|
| setTitle(string $title) | Sets chart title |
| setAnimated(boolean $animated) | Enables/disables animation |
| setOpacity(int $opacity) | Sets slices' opacity | | setOpacity(int $opacity) | Sets slices' opacity |
| addSlice(string $title, double $value, string $color, array $extras = []) | Adds a slice to the chart with the specified color. `$extras` is forwarded on click event | | addSlice(string $title, double $value, string $color, array $extras = []) | Adds a slice to the chart with the specified color. `$extras` is forwarded on click event |
| withOnSliceClickEvent(string $eventName) | Event Name that will be fired when a column of the chart is clicked | | withOnSliceClickEvent(string $eventName) | Event Name that will be fired when a column of the chart is clicked |
#### LivewireAreaChart
| Method | Description |
|--------|-------------|
| addPoint(string $title, double $value, array $extras = []) | Adds a point to the area chart. `$extras` is forwarded on click event |
| withOnPointClickEvent(string $eventName) | Event Name that will be fired when a point of the chart is clicked |
| setXAxisVisible(boolean $visible) | Shows/hides xAxis labels |
| setYAxisVisible(boolean $visible) | Shows/hides yAxis labels |
## Troubleshooting
Chart components must be placed inside a container with fixed height. This is because the chart will use all the given
vertical space. A fixed height is needed to render properly.
```blade
<div style="height: 32rem;">
<livewire:livewire-column-chart .../>
</div>
```
>Note: if a fixed height is not given, the chart will grow vertically infinitely. That's not what we want, right?
## Testing ## Testing
``` bash ``` bash

View File

@@ -0,0 +1,98 @@
<div
style="width: 100%; height: 100%;"
x-data="{ ...areaChart() }"
x-init="drawChart(@this)"
>
<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');
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();
}
}
}
</script>
</div>

View File

@@ -1,8 +1,7 @@
<div <div
class="w-full h-full relative" style="width: 100%; height: 100%;"
x-data="{ ...columnChart() }" x-data="{ ...columnChart() }"
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>
@@ -12,52 +11,52 @@
chart: null, chart: null,
drawChart(component) { drawChart(component) {
this.renderApex(component)
},
renderApex(component) {
if (this.chart) { if (this.chart) {
this.chart.destroy() 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 = { const options = {
series: [{ data: component.get('columnChartModel.data').map(item => item.value)}], series: [{ data: data.map(item => item.value)}],
chart: { chart: {
type: 'bar', type: 'bar',
height: '100%', height: '100%',
toolbar: { ...sparkline,
show: false
},
animations: { toolbar: { show: false },
enabled: component.get('columnChartModel.animated') || false,
}, animations: { enabled: animated },
events: { events: {
dataPointSelection: function(event, chartContext, config) { dataPointSelection: function(event, chartContext, config) {
const onColumnClickEventName = component.get('columnChartModel.onColumnClickEventName')
if (!onColumnClickEventName) { if (!onColumnClickEventName) {
return return
} }
const { dataPointIndex } = config const { dataPointIndex } = config
const column = component.get('columnChartModel.data')[dataPointIndex] const column = data[dataPointIndex]
component.call('onColumnClick', column) component.call('onColumnClick', column)
}, },
} }
}, },
colors: component.get('columnChartModel.data').map(item => item.color), colors: data.map(item => item.color),
labels: { labels: {
style: { style: {
colors: component.get('columnChartModel.data').map(item => item.color), colors: data.map(item => item.color),
}, },
}, },
legend: component.get('columnChartModel.legend') || {},
plotOptions: { plotOptions: {
bar: { bar: {
horizontal: false, horizontal: false,
@@ -66,12 +65,10 @@
}, },
}, },
dataLabels: { dataLabels: dataLabels,
enabled: false,
},
xaxis: { xaxis: {
categories: component.get('columnChartModel.data').map(item => item.title), categories: data.map(item => item.title),
}, },
yaxis: { yaxis: {

View File

@@ -1,8 +1,7 @@
<div <div
class="w-full h-full" style="width: 100%; height: 100%;"
x-data="{ ...lineChart() }" x-data="{ ...lineChart() }"
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>
@@ -12,56 +11,69 @@
chart: null, chart: null,
drawChart(component) { drawChart(component) {
this.renderApex(component)
},
renderApex(component) {
if (this.chart) { if (this.chart) {
this.chart.destroy() 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 = { const options = {
series: [{ series: series,
name: component.get('lineChartModel.title'),
data: component.get('lineChartModel.data').map(item => item.value),
}],
chart: { chart: {
type: 'line', type: 'line',
height: '100%', height: '100%',
zoom: {
enabled: false ...sparkline,
},
toolbar: { zoom: { enabled: false },
show: false
}, toolbar: { show: false },
animations: {
enabled: component.get('lineChartModel.animated') || false, animations: { enabled: animated },
},
events: { events: {
markerClick: function(event, chartContext, { dataPointIndex }) { markerClick: function(event, chartContext, { dataPointIndex }) {
const onPointClickEventName = component.get('lineChartModel.onPointClickEventName')
if (!onPointClickEventName) { if (!onPointClickEventName) {
return return
} }
const point = component.get('lineChartModel.data')[dataPointIndex] const point = data[dataPointIndex]
component.call('onPointClick', point) component.call('onPointClick', point)
} }
} }
}, },
dataLabels: {
enabled: false dataLabels: dataLabels,
},
stroke: { stroke: component.get('lineChartModel.stroke') || {},
curve: 'straight'
},
title: { title: {
text: component.get('lineChartModel.title'), text: title,
align: 'center' align: 'center'
}, },
xaxis: { xaxis: {
categories: component.get('lineChartModel.data').map(item => item.title), labels: component.get('lineChartModel.xAxis.labels'),
categories: categories,
}, },
yaxis: component.get('lineChartModel.yAxis') || {},
annotations: { annotations: {
points: component.get('lineChartModel.markers').map(item => { points: component.get('lineChartModel.markers').map(item => {
return { return {

View File

@@ -0,0 +1,95 @@
<div
style="width: 100%; height: 100%;"
x-data="{ ...multiColumnChart() }"
x-init="drawChart(@this)"
>
<div wire:ignore x-ref="container"></div>
<script>
function 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();
}
}
}
</script>
</div>

View File

@@ -0,0 +1,87 @@
<div
style="width: 100%; height: 100%;"
x-data="{ ...multiLineChart() }"
x-init="drawChart(@this)"
>
<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 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();
}
}
}
</script>
</div>

View File

@@ -1,8 +1,7 @@
<div <div
class="w-full h-full" style="width: 100%; height: 100%;"
x-data="{ ...pieChart() }" x-data="{ ...pieChart() }"
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>
@@ -12,54 +11,53 @@
chart: null, chart: null,
drawChart(component) { drawChart(component) {
this.renderApex(component)
},
renderApex(component) {
if (this.chart) { if (this.chart) {
this.chart.destroy() 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 = { const options = {
series: component.get('pieChartModel.data').map(item => item.value), series: data.map(item => item.value),
chart: { chart: {
height: '100%', height: '100%',
type: 'pie', type: 'pie',
animations: { ...sparkline,
enabled: component.get('pieChartModel.animated') || false,
}, animations: { enabled: animated },
events: { events: {
dataPointSelection: function(event, chartContext, config) { dataPointSelection: function(event, chartContext, config) {
const onSliceClickEventName = component.get('pieChartModel.onSliceClickEventName')
if (!onSliceClickEventName) { if (!onSliceClickEventName) {
return return
} }
const { dataPointIndex } = config const { dataPointIndex } = config
const slice = component.get('pieChartModel.data')[dataPointIndex] const slice = data[dataPointIndex]
component.call('onSliceClick', slice) component.call('onSliceClick', slice)
}, },
} }
}, },
labels: component.get('pieChartModel.data').map(item => item.title), labels: data.map(item => item.title),
dataLabels: { dataLabels: dataLabels,
enabled: false,
},
colors: component.get('pieChartModel.data').map(item => item.color), colors: data.map(item => item.color),
fill: { fill: {
opacity: component.get('pieChartModel.opacity'), opacity: component.get('pieChartModel.opacity'),
}, },
title: { title: {
text: component.get('pieChartModel.title'), text: title,
align: 'center', align: 'center',
}, },
@@ -73,6 +71,8 @@
} }
} }
], ],
legend: component.get('pieChartModel.legend') || {},
}; };
this.chart = new ApexCharts(this.$refs.container, options); this.chart = new ApexCharts(this.$refs.container, options);

View File

@@ -0,0 +1,36 @@
<?php
namespace Asantibanez\LivewireCharts\Charts;
use Asantibanez\LivewireCharts\Models\AreaChartModel;
use Livewire\Component;
/**
* Class LivewireAreaChart
* @package Asantibanez\LivewireCharts\Charts
*/
class LivewireAreaChart extends Component
{
public $areaChartModel;
public function mount(AreaChartModel $areaChartModel)
{
$this->areaChartModel = $areaChartModel->toArray();
}
public function onPointClick($point)
{
$onPointClickEventName = data_get($this->areaChartModel, 'onPointClickEventName', null);
if ($onPointClickEventName === null) {
return;
}
$this->emit($onPointClickEventName, $point);
}
public function render()
{
return view('livewire-charts::livewire-area-chart');
}
}

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

@@ -31,6 +31,10 @@ class LivewireLineChart extends Component
public function render() public function render()
{ {
if ($this->lineChartModel['isMultiLine']) {
return view('livewire-charts::livewire-multi-line-chart');
}
return view('livewire-charts::livewire-line-chart'); return view('livewire-charts::livewire-line-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

@@ -2,6 +2,7 @@
namespace Asantibanez\LivewireCharts; namespace Asantibanez\LivewireCharts;
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;
@@ -26,6 +27,7 @@ class LivewireChartsServiceProvider extends ServiceProvider
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);
} }
/** /**
@@ -33,6 +35,6 @@ class LivewireChartsServiceProvider extends ServiceProvider
*/ */
public function register() public function register()
{ {
// $this->app->bind('livewirecharts', LivewireCharts::class);
} }
} }

View File

@@ -0,0 +1,73 @@
<?php
namespace Asantibanez\LivewireCharts\Models;
/**
* Class AreaChartModel
* @package Asantibanez\LivewireCharts\Models
*/
class AreaChartModel extends BaseChartModel
{
public $color;
public $data;
public $onPointClickEventName;
public function __construct()
{
parent::__construct();
$this->color = '#90cdf4';
$this->onPointClickEventName = null;
$this->data = collect();
}
public function setColor($color)
{
$this->color = $color;
return $this;
}
public function addPoint($title, $value, $extras = [])
{
$this->data->push([
'title' => $title,
'value' => $value,
'extras' => $extras,
]);
return $this;
}
public function withOnPointClickEvent($onPointClickEventName)
{
$this->onPointClickEventName = $onPointClickEventName;
return $this;
}
public function toArray()
{
return array_merge(parent::toArray(), [
'color' => $this->color,
'onPointClickEventName' => $this->onPointClickEventName,
'data' => $this->data->toArray(),
]);
}
public function fromArray($array)
{
parent::fromArray($array);
$this->color = data_get($array, 'color', '#90cdf4');
$this->onPointClickEventName = data_get($array, 'onPointClickEventName', null);
$this->data = collect(data_get($array, 'data', []));
}
}

View File

@@ -0,0 +1,59 @@
<?php
namespace Asantibanez\LivewireCharts\Models;
/**
* Class BaseChartModel
* @package Asantibanez\LivewireCharts\Models
*/
class BaseChartModel
{
use HasTitle;
use HasAnimation;
use HasAxis;
use HasStroke;
use HasLegend;
use HasDataLabels;
use HasSparkline;
public function __construct()
{
$this->initTitle();
$this->initAnimation();
$this->initAxis();
$this->initStroke();
$this->initLegend();
$this->initDataLabels();
$this->initSparkline();
}
public function reactiveKey()
{
return md5(json_encode($this->toArray()));
}
public function toArray()
{
return array_merge(
$this->titleToArray(),
$this->animationToArray(),
$this->axisToArray(),
$this->strokeToArray(),
$this->legendToArray(),
$this->dataLabelsToArray(),
$this->sparklineToArray()
);
}
public function fromArray($array)
{
$this->titleFromArray($array);
$this->animationFromArray($array);
$this->axisFromArray($array);
$this->strokeFromArray($array);
$this->legendFromArray($array);
$this->dataLabelsFromArray($array);
$this->sparklineFromArray($array);
}
}

View File

@@ -3,47 +3,56 @@
namespace Asantibanez\LivewireCharts\Models; namespace Asantibanez\LivewireCharts\Models;
use Ramsey\Uuid\Uuid;
/** /**
* Class ColumnChartModel * Class ColumnChartModel
* @package Asantibanez\LivewireCharts\Models * @package Asantibanez\LivewireCharts\Models
* @property boolean $isMultiColumn
* @property boolean $isStacked
*/ */
class ColumnChartModel class ColumnChartModel extends BaseChartModel
{ {
public $title;
public $animated;
public $opacity; public $opacity;
public $isMultiColumn;
public $isStacked;
public $onColumnClickEventName; public $onColumnClickEventName;
public $data; public $data;
public function __construct() public function __construct()
{ {
$this->title = ''; parent::__construct();
$this->animated = false;
$this->onColumnClickEventName = null; $this->onColumnClickEventName = null;
$this->opacity = 0.5; $this->opacity = 0.5;
$this->isMultiColumn = false;
$this->isStacked = false;
$this->data = collect(); $this->data = collect();
} }
public function setTitle($title) public function multiColumn()
{ {
$this->title = $title; $this->isMultiColumn = true;
return $this; return $this;
} }
public function setAnimated($animated) public function singleColumn()
{ {
$this->animated = $animated; $this->isMultiColumn = false;
return $this;
}
public function stacked()
{
$this->isStacked = true;
return $this; return $this;
} }
@@ -74,33 +83,45 @@ class ColumnChartModel
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 [ return array_merge(parent::toArray(), [
'title' => $this->title,
'animated' => $this->animated,
'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(),
]; ]);
} }
public function fromArray($array) public function fromArray($array)
{ {
$this->title = data_get($array, 'title', ''); parent::fromArray($array);
$this->animated = data_get($array, 'animated', false);
$this->onColumnClickEventName = data_get($array, 'onColumnClickEventName', null); $this->onColumnClickEventName = data_get($array, 'onColumnClickEventName', null);
$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', []));
} }
public function reactiveKey()
{
return md5(json_encode($this->toArray()));
}
} }

View File

@@ -0,0 +1,39 @@
<?php
namespace Asantibanez\LivewireCharts\Models;
trait HasAnimation
{
private $animated;
public function setAnimated($animated)
{
$this->animated = $animated;
return $this;
}
protected function initAnimation()
{
$this->animated = $this->defaultAnimated();
}
private function defaultAnimated()
{
return false;
}
protected function animationFromArray($array)
{
$this->animated = data_get($array, 'animated', $this->defaultAnimated());
}
protected function animationToArray()
{
return [
'animated' => $this->animated,
];
}
}

71
src/Models/HasAxis.php Normal file
View File

@@ -0,0 +1,71 @@
<?php
namespace Asantibanez\LivewireCharts\Models;
trait HasAxis
{
private $xAxis;
private $yAxis;
protected function initAxis()
{
$this->xAxis = $this->defaultXAxis();
$this->yAxis = $this->defaultYAxis();
}
private function defaultXAxis()
{
return [
'categories' => [],
'labels' => [
'show' => true,
],
];
}
private function defaultYAxis()
{
return [
'show' => true,
];
}
public function setXAxisVisible($visible)
{
data_set($this->xAxis, 'labels.show', $visible);
return $this;
}
public function setXAxisCategories($categories = [])
{
data_set($this->xAxis, 'categories', $categories);
return $this;
}
public function setYAxisVisible($visible)
{
data_set($this->yAxis, 'show', $visible);
return $this;
}
protected function axisFromArray($array)
{
$this->xAxis = data_get($array, 'xAxis', $this->defaultXAxis());
$this->yAxis = data_get($array, 'yAxis', $this->defaultYAxis());
}
protected function axisToArray()
{
return [
'xAxis' => $this->xAxis,
'yAxis' => $this->yAxis,
];
}
}

View File

@@ -0,0 +1,55 @@
<?php
namespace Asantibanez\LivewireCharts\Models;
trait HasDataLabels
{
private $dataLabels;
public function setDataLabelsEnabled($enabled)
{
data_set($this->dataLabels, 'enabled', $enabled);
return $this;
}
public function withDataLabels()
{
data_set($this->dataLabels, 'enabled', true);
return $this;
}
public function withoutDataLabels()
{
data_set($this->dataLabels, 'enabled', false);
return $this;
}
protected function initDataLabels()
{
$this->dataLabels = $this->defaultDataLabels();
}
private function defaultDataLabels()
{
return [
'enabled' => false,
];
}
protected function dataLabelsFromArray($array)
{
$this->dataLabels = data_get($array, 'dataLabels', $this->defaultDataLabels());
}
protected function dataLabelsToArray()
{
return [
'dataLabels' => $this->dataLabels,
];
}
}

102
src/Models/HasLegend.php Normal file
View File

@@ -0,0 +1,102 @@
<?php
namespace Asantibanez\LivewireCharts\Models;
trait HasLegend
{
private $legend;
public function initLegend()
{
$this->legend = $this->defaultLegend();
}
private function defaultLegend()
{
return [
'show' => true,
'position' => 'bottom',
'horizontalAlign' => 'center',
];
}
public function setLegendVisibility($visible)
{
data_set($this->legend, 'show', $visible);
return $this;
}
public function setLegendPosition($position)
{
data_set($this->legend, 'position', $position);
return $this;
}
public function setLegendHorizontalAlign($horizontalAlign)
{
data_set($this->legend, 'horizontalAlign', $horizontalAlign);
return $this;
}
public function withoutLegend()
{
return $this->setLegendVisibility(false);
}
public function withLegend()
{
return $this->setLegendVisibility(true);
}
public function legendPositionTop()
{
return $this->setLegendPosition('top');
}
public function legendPositionLeft()
{
return $this->setLegendPosition('left');
}
public function legendPositionRight()
{
return $this->setLegendPosition('right');
}
public function legendPositionBottom()
{
return $this->setLegendPosition('bottom');
}
public function legendHorizontallyAlignedLeft()
{
return $this->setLegendHorizontalAlign('left');
}
public function legendHorizontallyAlignedCenter()
{
return $this->setLegendHorizontalAlign('center');
}
public function legendHorizontallyAlignedRight()
{
return $this->setLegendHorizontalAlign('right');
}
protected function legendToArray()
{
return [
'legend' => $this->legend,
];
}
protected function legendFromArray($array)
{
$this->legend = data_get($array, 'legend', $this->defaultLegend());
}
}

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,
];
}
}

48
src/Models/HasStroke.php Normal file
View File

@@ -0,0 +1,48 @@
<?php
namespace Asantibanez\LivewireCharts\Models;
trait HasStroke
{
private $stroke;
public function initStroke()
{
$this->stroke = $this->defaultStroke();
}
private function defaultStroke()
{
return [
'curve' => 'straight',
];
}
public function setSmoothCurve()
{
data_set($this->stroke, 'curve', 'smooth');
return $this;
}
public function setStraightCurve()
{
data_set($this->stroke, 'curve', 'straight');
return $this;
}
protected function strokeToArray()
{
return [
'stroke' => $this->stroke,
];
}
protected function strokeFromArray($array)
{
$this->stroke = data_get($array, 'stroke', $this->defaultStroke());
}
}

39
src/Models/HasTitle.php Normal file
View File

@@ -0,0 +1,39 @@
<?php
namespace Asantibanez\LivewireCharts\Models;
trait HasTitle
{
private $title;
public function setTitle($title)
{
$this->title = $title;
return $this;
}
protected function initTitle()
{
$this->title = $this->defaultTitle();
}
private function defaultTitle()
{
return '';
}
protected function titleFromArray($array)
{
$this->title = data_get($array, 'title', $this->defaultTitle());
}
protected function titleToArray()
{
return [
'title' => $this->title,
];
}
}

View File

@@ -3,15 +3,16 @@
namespace Asantibanez\LivewireCharts\Models; namespace Asantibanez\LivewireCharts\Models;
use Illuminate\Support\Collection;
/** /**
* Class LineChartModel * Class LineChartModel
* @package Asantibanez\LivewireCharts\Models * @package Asantibanez\LivewireCharts\Models
* @property boolean $isMultiLine
*/ */
class LineChartModel class LineChartModel extends BaseChartModel
{ {
public $title; public $isMultiLine;
public $animated;
public $data; public $data;
@@ -21,9 +22,9 @@ class LineChartModel
public function __construct() public function __construct()
{ {
$this->title = ''; parent::__construct();
$this->animated = false; $this->isMultiLine = false;
$this->onPointClickEventName = null; $this->onPointClickEventName = null;
@@ -32,16 +33,20 @@ class LineChartModel
$this->markers = collect(); $this->markers = collect();
} }
public function setTitle($title) public function multiLine()
{ {
$this->title = $title; $this->isMultiLine = true;
$this->data = collect();
return $this; return $this;
} }
public function setAnimated($animated) public function singleLine()
{ {
$this->animated = $animated; $this->isMultiLine = false;
$this->data = collect();
return $this; return $this;
} }
@@ -57,6 +62,22 @@ class LineChartModel
return $this; return $this;
} }
public function addSeriesPoint($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 addMarker($title, public function addMarker($title,
$value, $value,
$strokeColor = 'green', $strokeColor = 'green',
@@ -85,20 +106,19 @@ class LineChartModel
public function toArray() public function toArray()
{ {
return [ return array_merge(parent::toArray(), [
'title' => $this->title, 'isMultiLine' => $this->isMultiLine,
'animated' => $this->animated,
'onPointClickEventName' => $this->onPointClickEventName, 'onPointClickEventName' => $this->onPointClickEventName,
'data' => $this->data->toArray(), 'data' => $this->data->toArray(),
'markers' => $this->markers->toArray(), 'markers' => $this->markers->toArray(),
]; ]);
} }
public function fromArray($array) public function fromArray($array)
{ {
$this->title = data_get($array, 'title', ''); parent::fromArray($array);
$this->animated = data_get($array, 'animated', false); $this->isMultiLine = data_get($array, 'isMultiLine', false);
$this->onPointClickEventName = data_get($array, 'onPointClickEventName', null); $this->onPointClickEventName = data_get($array, 'onPointClickEventName', null);
@@ -106,9 +126,4 @@ class LineChartModel
$this->markers = collect(data_get($array, 'markers', [])); $this->markers = collect(data_get($array, 'markers', []));
} }
public function reactiveKey()
{
return md5(json_encode($this->toArray()));
}
} }

View File

@@ -7,12 +7,8 @@ namespace Asantibanez\LivewireCharts\Models;
* Class PieChartModel * Class PieChartModel
* @package Asantibanez\LivewireCharts\Models * @package Asantibanez\LivewireCharts\Models
*/ */
class PieChartModel class PieChartModel extends BaseChartModel
{ {
public $title;
public $animated;
public $onSliceClickEventName; public $onSliceClickEventName;
public $opacity; public $opacity;
@@ -21,9 +17,7 @@ class PieChartModel
public function __construct() public function __construct()
{ {
$this->title = ''; parent::__construct();
$this->animated = false;
$this->onSliceClickEventName = null; $this->onSliceClickEventName = null;
@@ -32,20 +26,6 @@ class PieChartModel
$this->data = collect(); $this->data = collect();
} }
public function setTitle($title)
{
$this->title = $title;
return $this;
}
public function setAnimated($animated)
{
$this->animated = $animated;
return $this;
}
public function setOpacity($opacity) public function setOpacity($opacity)
{ {
$this->opacity = $opacity; $this->opacity = $opacity;
@@ -74,20 +54,16 @@ class PieChartModel
public function toArray() public function toArray()
{ {
return [ return array_merge(parent::toArray(), [
'title' => $this->title,
'animated' => $this->animated,
'onSliceClickEventName' => $this->onSliceClickEventName, 'onSliceClickEventName' => $this->onSliceClickEventName,
'opacity' => $this->opacity, 'opacity' => $this->opacity,
'data' => $this->data->toArray(), 'data' => $this->data->toArray(),
]; ]);
} }
public function fromArray($array) public function fromArray($array)
{ {
$this->title = data_get($array, 'title', ''); parent::fromArray($array);
$this->animated = data_get($array, 'animated', false);
$this->onSliceClickEventName = data_get($array, 'onSliceClickEventName', null); $this->onSliceClickEventName = data_get($array, 'onSliceClickEventName', null);
@@ -95,9 +71,4 @@ class PieChartModel
$this->data = collect(data_get($array, 'data', [])); $this->data = collect(data_get($array, 'data', []));
} }
public function reactiveKey()
{
return md5(json_encode($this->toArray()));
}
} }