├── .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 | [![Codacy Badge](https://app.codacy.com/project/badge/Grade/14ef51b25e954d0f86373fc6dac30e8c)](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 | [![StyleCI](https://github.styleci.io/repos/85484767/shield?branch=master)](https://github.styleci.io/repos/85484767) 5 | [![License](https://poser.pugx.org/laravel-enso/charts/license)](https://packagist.org/packages/laravel-enso/charts) 6 | [![Total Downloads](https://poser.pugx.org/laravel-enso/charts/downloads)](https://packagist.org/packages/laravel-enso/charts) 7 | [![Latest Stable Version](https://poser.pugx.org/laravel-enso/charts/version)](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 | [![Watch the demo](https://laravel-enso.github.io/charts/screenshots/bulma_cap002_thumb.png)](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 | --------------------------------------------------------------------------------