├── .styleci.yml
├── src
├── Factories
│ ├── Pie.php
│ ├── Polar.php
│ ├── Doughnut.php
│ ├── Radar.php
│ ├── Circles.php
│ ├── Bar.php
│ ├── Line.php
│ ├── Bubble.php
│ └── Chart.php
├── Enums
│ └── Charts.php
└── AppServiceProvider.php
├── .github
└── issue_template.md
├── codesize.xml
├── LICENSE
├── composer.json
├── config
└── charts.php
└── README.md
/.styleci.yml:
--------------------------------------------------------------------------------
1 | risky: true
2 |
3 | preset: laravel
4 |
5 | enabled:
6 | - strict
7 | - unalign_double_arrow
8 |
9 | disabled:
10 | - short_array_syntax
11 |
12 | finder:
13 | exclude:
14 | - "public"
15 | - "resources"
16 | - "tests"
17 | name:
18 | - "*.php"
19 |
--------------------------------------------------------------------------------
/src/Factories/Pie.php:
--------------------------------------------------------------------------------
1 | type(Charts::Pie)
14 | ->ratio(1);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/Enums/Charts.php:
--------------------------------------------------------------------------------
1 | type(Charts::PolarArea)
14 | ->ratio(1);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/Factories/Doughnut.php:
--------------------------------------------------------------------------------
1 | type(Charts::Doughnut)
14 | ->ratio(1);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/AppServiceProvider.php:
--------------------------------------------------------------------------------
1 | mergeConfigFrom(__DIR__.'/../config/charts.php', 'enso.charts');
12 |
13 | $this->publishes([
14 | __DIR__.'/../config' => config_path('enso'),
15 | ], ['charts-config', 'enso-config']);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/.github/issue_template.md:
--------------------------------------------------------------------------------
1 |
2 | This is a **bug | feature request**.
3 |
4 |
5 | ### Prerequisites
6 | * [ ] Are you running the latest version?
7 | * [ ] Are you reporting to the correct repository?
8 | * [ ] Did you check the documentation?
9 | * [ ] Did you perform a cursory search?
10 |
11 | ### Description
12 |
13 |
14 | ### Steps to Reproduce
15 |
20 |
21 | ### Expected behavior
22 |
23 |
24 | ### Actual behavior
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/codesize.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 | custom rules
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 laravel-enso
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "laravel-enso/charts",
3 | "description": "Server-side data builder for Chart.js, with a VueJS component for the frontend.",
4 | "keywords": [
5 | "laravel-enso",
6 | "charts",
7 | "laravel",
8 | "chartjs"
9 | ],
10 | "homepage": "https://github.com/laravel-enso/charts",
11 | "type": "library",
12 | "license": "MIT",
13 | "authors": [
14 | {
15 | "name": "Adrian Ocneanu",
16 | "email": "aocneanu@gmail.com",
17 | "homepage": "https://laravel-enso.com",
18 | "role": "Developer"
19 | }
20 | ],
21 | "require": {
22 | "laravel/framework": "^7.0|^8.0|^9.0|^10.0|^11.0",
23 | "laravel-enso/helpers": "^3.0",
24 | "php": "^8.0"
25 | },
26 | "autoload": {
27 | "psr-4": {
28 | "LaravelEnso\\Charts\\": "src/"
29 | }
30 | },
31 | "extra": {
32 | "laravel": {
33 | "providers": [
34 | "LaravelEnso\\Charts\\AppServiceProvider"
35 | ],
36 | "aliases": []
37 | }
38 | }
39 | }
--------------------------------------------------------------------------------
/config/charts.php:
--------------------------------------------------------------------------------
1 | 0.25,
5 | 'options' => [
6 | 'plugins' => [
7 | 'datalabels' => [
8 | 'anchor' => 'end',
9 | 'align' => 'top',
10 | 'borderRadius' => 3,
11 | 'padding' => 2,
12 | 'color' => 'white',
13 | 'font' => [
14 | 'weight' => 'bold',
15 | ],
16 | ],
17 | 'tooltip' => [
18 | 'enabled' => false,
19 | ],
20 | ],
21 | ],
22 | 'colors' => [
23 | 'Green' => '#008000',
24 | 'Red' => '#FF0000',
25 | 'Blue' => '#1E90FF',
26 | 'Purple' => '#800080',
27 | 'Orange' => '#FFA500',
28 | 'Brown' => '#A52A2A',
29 | 'OrangeRed' => '#FF4500',
30 | 'Teal' => '#008080',
31 | 'Coral' => '#FF7F50',
32 | 'SlateBlue' => '#6A5ACD',
33 | 'Maroon' => '#800000',
34 | 'SlateGrey' => '#708090',
35 | 'DeepPink' => '#FF1493',
36 | 'LightGreen' => '#90EE90',
37 | 'SteelBlue' => '#4682B4',
38 | 'Magenta' => '#FF00FF',
39 | 'Tan' => '#D2B48C',
40 | 'Silver' => '#C0C0C0',
41 | 'Black' => '#000000',
42 | ],
43 | ];
44 |
--------------------------------------------------------------------------------
/src/Factories/Radar.php:
--------------------------------------------------------------------------------
1 | type(Charts::Radar)
15 | ->ratio(1);
16 | }
17 |
18 | public function response(): array
19 | {
20 | return [
21 | 'data' => [
22 | 'labels' => $this->labels,
23 | 'datasets' => $this->data,
24 | ],
25 | 'options' => $this->options,
26 | 'title' => $this->title,
27 | 'type' => $this->type,
28 | ];
29 | }
30 |
31 | protected function build(): void
32 | {
33 | Collection::wrap($this->datasets)
34 | ->each(fn ($dataset, $label) => $this->data[] = [
35 | 'label' => $label,
36 | 'borderColor' => $this->color(),
37 | 'backgroundColor' => $this->hex2rgba($this->color()),
38 | 'pointBorderColor' => '#fff',
39 | 'data' => $dataset,
40 | 'datalabels' => empty($this->datalabels) ? [
41 | 'backgroundColor' => $this->color(),
42 | ] : $this->datalabels,
43 | ]);
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/Factories/Circles.php:
--------------------------------------------------------------------------------
1 | [
13 | 'labels' => $this->labels,
14 | 'datasets' => $this->data,
15 | ],
16 | 'options' => $this->options,
17 | 'title' => $this->title,
18 | 'type' => $this->type,
19 | ];
20 | }
21 |
22 | protected function build(): void
23 | {
24 | $colors = Collection::wrap($this->colors())
25 | ->slice(0, count($this->labels));
26 |
27 | $this->data = is_array($this->datasets[0])
28 | ? $this->stackedDatasets($colors)
29 | : [[
30 | 'data' => $this->datasets,
31 | 'backgroundColor' => $colors,
32 | 'datalabels' => ['backgroundColor' => $colors],
33 | ]];
34 | }
35 |
36 | private function stackedDatasets(Collection $colors): Collection
37 | {
38 | return Collection::wrap($this->datasets)
39 | ->map(fn ($dataset) => [
40 | 'data' => $dataset,
41 | 'backgroundColor' => $colors,
42 | 'datalabels' => empty($this->datalabels) ? [
43 | 'backgroundColor' => $colors,
44 | ] : $this->datalabels,
45 | ]);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/Factories/Bar.php:
--------------------------------------------------------------------------------
1 | type('bar')
14 | ->ratio(1.6);
15 | }
16 |
17 | public function horizontal(): self
18 | {
19 | $this->option('indexAxis', 'y');
20 |
21 | return $this;
22 | }
23 |
24 | public function stackedScales(): self
25 | {
26 | $this->xAxisConfig(['stacked' => true])
27 | ->yAxisConfig(['stacked' => true]);
28 |
29 | return $this;
30 | }
31 |
32 | protected function response(): array
33 | {
34 | return [
35 | 'data' => [
36 | 'labels' => $this->labels,
37 | 'datasets' => $this->data,
38 | ],
39 | 'options' => $this->options,
40 | 'title' => $this->title,
41 | 'type' => $this->type,
42 | ];
43 | }
44 |
45 | protected function build(): void
46 | {
47 | Collection::wrap($this->datasets)
48 | ->each(fn ($dataset, $label) => $this->data[] = [
49 | 'label' => $label,
50 | 'backgroundColor' => $this->color(),
51 | 'data' => $dataset,
52 | 'datalabels' => empty($this->datalabels) ? [
53 | 'backgroundColor' => $this->color(),
54 | ] : $this->datalabels,
55 | ]);
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/Factories/Line.php:
--------------------------------------------------------------------------------
1 | fill = false;
17 |
18 | $this->type(Charts::Line)
19 | ->ratio(1.6);
20 | }
21 |
22 | public function response(): array
23 | {
24 | return [
25 | 'data' => [
26 | 'labels' => $this->labels,
27 | 'datasets' => $this->data,
28 | ],
29 | 'options' => $this->options,
30 | 'title' => $this->title,
31 | 'type' => $this->type,
32 | ];
33 | }
34 |
35 | public function fill(): self
36 | {
37 | $this->fill = true;
38 |
39 | return $this;
40 | }
41 |
42 | protected function build(): void
43 | {
44 | Collection::wrap($this->datasets)
45 | ->each(fn ($dataset, $label) => $this->data[] = [
46 | 'fill' => $this->fill,
47 | 'lineTension' => 0.3,
48 | 'pointHoverRadius' => 5,
49 | 'pointHitRadius' => 5,
50 | 'label' => $label,
51 | 'borderColor' => $this->color(),
52 | 'backgroundColor' => $this->hex2rgba($this->color()),
53 | 'data' => $dataset,
54 | 'datalabels' => empty($this->datalabels) ? [
55 | 'backgroundColor' => $this->color(),
56 | ] : $this->datalabels,
57 | ]);
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Charts
2 |
3 | [](https://www.codacy.com/gh/laravel-enso/charts?utm_source=github.com&utm_medium=referral&utm_content=laravel-enso/charts&utm_campaign=Badge_Grade)
4 | [](https://github.styleci.io/repos/85484767)
5 | [](https://packagist.org/packages/laravel-enso/charts)
6 | [](https://packagist.org/packages/laravel-enso/charts)
7 | [](https://packagist.org/packages/laravel-enso/charts)
8 |
9 | Server-side data builder for charts.
10 |
11 | This package can work independently of the [Enso](https://github.com/laravel-enso/Enso) ecosystem.
12 |
13 | The front end implementation that utilizes this api is present in the [enso-ui/charts](https://github.com/enso-ui/charts) package.
14 |
15 | For live examples and demos, you may visit [laravel-enso.com](https://www.laravel-enso.com)
16 |
17 | [](https://laravel-enso.github.io/charts/videos/bulma_demo_01.webm)
18 |
19 | click on the photo to view a short demo in compatible browsers
20 |
21 | ### Installation, Configuration & Usage
22 |
23 | Be sure to check out the full documentation for this package available at [docs.laravel-enso.com](https://docs.laravel-enso.com/backend/charts.html)
24 |
25 | ### Contributions
26 |
27 | are welcome. Pull requests are great, but issues are good too.
28 |
29 | ### License
30 |
31 | This package is released under the MIT license.
32 |
--------------------------------------------------------------------------------
/src/Factories/Bubble.php:
--------------------------------------------------------------------------------
1 | autoRadius = true;
23 | $this->radiusLimit = 25;
24 |
25 | $this->type(Charts::Bubble)
26 | ->ratio(1.6);
27 | }
28 |
29 | public function response(): array
30 | {
31 | return [
32 | 'data' => ['datasets' => $this->data],
33 | 'options' => $this->options,
34 | 'title' => $this->title,
35 | 'type' => $this->type,
36 | ];
37 | }
38 |
39 | public function disableAutoRadius(): self
40 | {
41 | $this->autoRadius = false;
42 |
43 | return $this;
44 | }
45 |
46 | protected function build(): void
47 | {
48 | $this->when($this->autoRadius, fn ($chart) => $chart
49 | ->maxRadius()
50 | ->computeRadius())
51 | ->mapDatasetsLabels()
52 | ->data();
53 | }
54 |
55 | private function maxRadius(): self
56 | {
57 | $this->maxRadius = Collection::wrap($this->datasets)
58 | ->map(fn ($dataset) => max(array_column($dataset, 2)))
59 | ->max();
60 |
61 | return $this;
62 | }
63 |
64 | private function computeRadius(): self
65 | {
66 | $this->datasets = Collection::wrap($this->datasets)
67 | ->map(fn ($dataset) => Collection::wrap($dataset)
68 | ->map(fn ($bubble) => $this->bubbleRadius($bubble)))
69 | ->toArray();
70 |
71 | return $this;
72 | }
73 |
74 | private function bubbleRadius(array $bubble): array
75 | {
76 | $bubble[2] = Decimals::ceil(
77 | Decimals::div($this->radiusLimit * $bubble[2], $this->maxRadius)
78 | );
79 |
80 | return $bubble;
81 | }
82 |
83 | private function mapDatasetsLabels(): self
84 | {
85 | $this->datasets = array_combine(
86 | array_values($this->labels),
87 | array_values($this->datasets)
88 | );
89 |
90 | return $this;
91 | }
92 |
93 | private function data(): void
94 | {
95 | Collection::wrap($this->datasets)
96 | ->each(fn ($dataset, $label) => $this->data[] = [
97 | 'label' => $label,
98 | 'borderColor' => $this->color(),
99 | 'backgroundColor' => $this->hex2rgba($this->color()),
100 | 'hoverBackgroundColor' => $this->hex2rgba($this->color(), 0.6),
101 | 'data' => $this->dataset($dataset),
102 | 'datalabels' => empty($this->datalabels) ? [
103 | 'backgroundColor' => $this->color(),
104 | ] : $this->datalabels,
105 | ]);
106 | }
107 |
108 | private function dataset($dataset): Collection
109 | {
110 | return Collection::wrap($dataset)
111 | ->map(fn ($values) => [
112 | 'x' => $values[0],
113 | 'y' => $values[1],
114 | 'r' => $values[2],
115 | ]);
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/src/Factories/Chart.php:
--------------------------------------------------------------------------------
1 | axes = ['xAxes' => ['x' => []], 'yAxes' => ['y' => []]];
26 | $this->data = [];
27 | $this->datasetConfig = [];
28 | $this->options = Config::get('enso.charts.options');
29 | $this->labels = [];
30 | $this->datalabels = [];
31 | $this->gridlines = false;
32 | $this->autoYMin = false;
33 |
34 | $this->colors();
35 | }
36 |
37 | public function get(): array
38 | {
39 | $this->scales()->build();
40 |
41 | $this->customize();
42 |
43 | return $this->response();
44 | }
45 |
46 | public function autoYMin(): self
47 | {
48 | $this->autoYMin = true;
49 |
50 | return $this;
51 | }
52 |
53 | public function gridlines(): self
54 | {
55 | $this->gridlines = true;
56 |
57 | return $this;
58 | }
59 |
60 | public function datalabels(array $config): self
61 | {
62 | $this->datalabels = $config;
63 |
64 | return $this;
65 | }
66 |
67 | public function colorsConfig(array $colors): self
68 | {
69 | $this->colors = $colors;
70 |
71 | return $this;
72 | }
73 |
74 | public function datasetConfig(string $dataset, array $config): self
75 | {
76 | $this->datasetConfig[$dataset] = $config;
77 |
78 | return $this;
79 | }
80 |
81 | public function xAxisConfig(array $config, ?string $dataset = 'x'): self
82 | {
83 | $this->axes['xAxes'][$dataset] = $config;
84 |
85 | return $this;
86 | }
87 |
88 | public function yAxisConfig(array $config, ?string $dataset = 'y'): self
89 | {
90 | $this->axes['yAxes'][$dataset] = $config;
91 |
92 | return $this;
93 | }
94 |
95 | public function title(string $title): self
96 | {
97 | $this->title = $title;
98 |
99 | return $this;
100 | }
101 |
102 | public function labels(array $labels): self
103 | {
104 | $this->labels = $labels;
105 |
106 | return $this;
107 | }
108 |
109 | public function datasets(array $datasets): self
110 | {
111 | $this->datasets = $datasets;
112 |
113 | return $this;
114 | }
115 |
116 | public function ratio(float $ratio): self
117 | {
118 | $this->options['aspectRatio'] = $ratio;
119 |
120 | return $this;
121 | }
122 |
123 | public function option(string $option, $value): self
124 | {
125 | $this->options[$option] = $value;
126 |
127 | return $this;
128 | }
129 |
130 | public function plugin(string $plugin, $config)
131 | {
132 | $this->options['plugins'][$plugin] = $config;
133 |
134 | return $this;
135 | }
136 |
137 | public function shortNumbers(int $precision = 2): self
138 | {
139 | $this->option('shortNumbers', true)
140 | ->option('precision', $precision);
141 |
142 | return $this;
143 | }
144 |
145 | abstract protected function build(): void;
146 |
147 | abstract protected function response(): array;
148 |
149 | protected function type(string $type): self
150 | {
151 | $this->type = $type;
152 |
153 | return $this;
154 | }
155 |
156 | protected function hex2rgba(string $color): string
157 | {
158 | $color = substr($color, 1);
159 |
160 | $hex = [
161 | "{$color[0]}{$color[1]}",
162 | "{$color[2]}{$color[3]}",
163 | "{$color[4]}{$color[5]}",
164 | ];
165 |
166 | $rgb = array_map('hexdec', $hex);
167 | $rgba = implode(',', $rgb);
168 | $opacity = Config::get('enso.charts.fillBackgroundOpacity');
169 |
170 | return "rgba({$rgba},{$opacity})";
171 | }
172 |
173 | protected function color(?string $index = null): string
174 | {
175 | $index ??= count($this->data);
176 |
177 | return $this->colors[$index];
178 | }
179 |
180 | protected function colors(): array
181 | {
182 | return $this->colors ??= array_values(Config::get('enso.charts.colors'));
183 | }
184 |
185 | protected function scales(): self
186 | {
187 | foreach ($this->axes['xAxes'] as $axis => $config) {
188 | $this->options['scales'][$axis] = array_merge_recursive(
189 | $config,
190 | $this->defaultXConfig()
191 | );
192 | }
193 | foreach ($this->axes['yAxes'] as $axis => $config) {
194 | $this->options['scales'][$axis] = array_merge_recursive(
195 | $config,
196 | $this->defaultYConfig()
197 | );
198 | }
199 |
200 | return $this;
201 | }
202 |
203 | private function defaultXConfig(): array
204 | {
205 | return [
206 | 'ticks' => [
207 | 'autoSkip' => false,
208 | 'maxRotation' => 90,
209 | ],
210 | 'grid' => ['drawOnChartArea' => $this->gridlines],
211 | ];
212 | }
213 |
214 | private function defaultYConfig(): array
215 | {
216 | $config = ['grid' => ['drawOnChartArea' => $this->gridlines]];
217 |
218 | if (! $this->autoYMin) {
219 | $config['ticks']['min'] = 0;
220 | }
221 |
222 | return $config;
223 | }
224 |
225 | private function customize(): void
226 | {
227 | foreach ($this->datasetConfig as $label => $config) {
228 | $index = Collection::wrap($this->data)
229 | ->search(fn ($dataset) => $dataset['label'] === $label);
230 | $this->data[$index] = array_merge($this->data[$index], $config);
231 | }
232 | }
233 | }
234 |
--------------------------------------------------------------------------------