├── .editorconfig ├── .gitattributes ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── composer.json ├── phpstan.neon.dist ├── phpunit.xml ├── src ├── BarChart.php ├── BubbleChart.php ├── Chart.php ├── ChartInterface.php ├── Config │ ├── Config.php │ ├── ConfigInterface.php │ ├── Data.php │ ├── Dataset.php │ └── Options.php ├── DoughnutChart.php ├── LineChart.php ├── PieChart.php ├── PolarAreaChart.php ├── RadarChart.php └── ScatterChart.php └── tests ├── ChartTest.php └── TestCase.php /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | indent_style = space 8 | indent_size = 4 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | 14 | [*.{yml,yaml}] 15 | indent_size = 2 16 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | 3 | *.md diff=markdown 4 | *.php diff=php 5 | 6 | /.github export-ignore 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | /.idea 3 | /.vscode 4 | .DS_Store 5 | *.cache 6 | composer.phar 7 | Thumbs.db 8 | composer.lock 9 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, caste, color, religion, or sexual 10 | identity and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the overall 26 | community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or advances of 31 | any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email address, 35 | without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. 49 | 50 | ## Scope 51 | 52 | This Code of Conduct applies within all community spaces, and also applies when 53 | an individual is officially representing the community in public spaces. 54 | Examples of representing our community include using an official email address, 55 | posting via an official social media account, or acting as an appointed 56 | representative at an online or offline event. 57 | 58 | ## Enforcement 59 | 60 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 61 | reported to the community leaders responsible for enforcement at 62 | [anatoliybabushka@gmail.com](mailto:anatoliybabushka@gmail.com). 63 | All complaints will be reviewed and investigated promptly and fairly. 64 | 65 | All community leaders are obligated to respect the privacy and security of the 66 | reporter of any incident. 67 | 68 | ## Enforcement Guidelines 69 | 70 | Community leaders will follow these Community Impact Guidelines in determining 71 | the consequences for any action they deem in violation of this Code of Conduct: 72 | 73 | ### 1. Correction 74 | 75 | **Community Impact**: Use of inappropriate language or other behavior deemed 76 | unprofessional or unwelcome in the community. 77 | 78 | **Consequence**: A private, written warning from community leaders, providing 79 | clarity around the nature of the violation and an explanation of why the 80 | behavior was inappropriate. A public apology may be requested. 81 | 82 | ### 2. Warning 83 | 84 | **Community Impact**: A violation through a single incident or series of 85 | actions. 86 | 87 | **Consequence**: A warning with consequences for continued behavior. No 88 | interaction with the people involved, including unsolicited interaction with 89 | those enforcing the Code of Conduct, for a specified period of time. This 90 | includes avoiding interactions in community spaces as well as external channels 91 | like social media. Violating these terms may lead to a temporary or permanent 92 | ban. 93 | 94 | ### 3. Temporary Ban 95 | 96 | **Community Impact**: A serious violation of community standards, including 97 | sustained inappropriate behavior. 98 | 99 | **Consequence**: A temporary ban from any sort of interaction or public 100 | communication with the community for a specified period of time. No public or 101 | private interaction with the people involved, including unsolicited interaction 102 | with those enforcing the Code of Conduct, is allowed during this period. 103 | Violating these terms may lead to a permanent ban. 104 | 105 | ### 4. Permanent Ban 106 | 107 | **Community Impact**: Demonstrating a pattern of violation of community 108 | standards, including sustained inappropriate behavior, harassment of an 109 | individual, or aggression toward or disparagement of classes of individuals. 110 | 111 | **Consequence**: A permanent ban from any sort of public interaction within the 112 | community. 113 | 114 | ## Attribution 115 | 116 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 117 | version 2.1, available at 118 | [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. 119 | 120 | Community Impact Guidelines were inspired by 121 | [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. 122 | 123 | For answers to common questions about this code of conduct, see the FAQ at 124 | [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at 125 | [https://www.contributor-covenant.org/translations][translations]. 126 | 127 | [homepage]: https://www.contributor-covenant.org 128 | [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html 129 | [Mozilla CoC]: https://github.com/mozilla/diversity 130 | [FAQ]: https://www.contributor-covenant.org/faq 131 | [translations]: https://www.contributor-covenant.org/translations 132 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to ChartJS PHP 2 | 3 | Thank you for considering contributing to the ChartJS PHP project! We welcome contributions from the community to help improve and enhance the project. To contribute, please follow these guidelines: 4 | 5 | 1. Fork the repository and create a new branch for your contribution. 6 | 2. Make your changes and ensure they adhere to the project's coding standards. 7 | 3. Write tests to cover your changes and ensure they pass. 8 | 4. Submit a pull request with a clear description of your changes and the problem they solve. 9 | 5. Participate in the review process and address any feedback or comments. 10 | 11 | By contributing to ChartJS PHP, you agree to abide by the [Code of Conduct](CODE_OF_CONDUCT.md) of the project. 12 | 13 | We appreciate your contributions and look forward to working with you to make ChartJS PHP even better! 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Anatoliy Babushka 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ChartJS-PHP 2 | 3 | [![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg)](CODE_OF_CONDUCT.md) 4 | [![Tests](https://github.com/bbsnly/chartjs-php/actions/workflows/php.yml/badge.svg)](https://github.com/bbsnly/chartjs-php/actions) 5 | [![codecov](https://codecov.io/gh/bbsnly/chartjs-php/graph/badge.svg?token=MTIBNP8BDT)](https://codecov.io/gh/bbsnly/chartjs-php) 6 | [![Total Downloads](https://poser.pugx.org/bbsnly/chartjs-php/d/total.svg)](https://packagist.org/packages/bbsnly/chartjs-php) 7 | [![Latest Stable Version](https://poser.pugx.org/bbsnly/chartjs-php/v/stable.svg)](https://packagist.org/packages/bbsnly/chartjs-php) 8 | [![License](https://poser.pugx.org/bbsnly/chartjs-php/license.svg)](https://packagist.org/packages/bbsnly/chartjs-php) 9 | 10 | This package transforms how you create [ChartJS](https://www.chartjs.org/ "ChartJS") elements by bringing them directly into PHP. 11 | 12 | ChartJS-PHP eliminates the complexity of JavaScript when working with [ChartJS](https://www.chartjs.org/ "ChartJS") charts. While [ChartJS](https://www.chartjs.org/ "ChartJS") traditionally requires JavaScript implementation, our PHP solution delivers the same powerful charts through clean, efficient PHP code. By generating [ChartJS](https://www.chartjs.org/ "ChartJS") elements directly in PHP, you write less code, maintain cleaner codebases, and deliver faster results. This is the definitive solution for PHP developers building data visualizations, dashboards, or any application requiring dynamic charts. 13 | 14 | **Note: Include the ChartJS library in your project as specified in their [official documentation]().** 15 | 16 | ## Installation 17 | 18 | Installing ChartJS-PHP is straightforward with [Composer](https://getcomposer.org/). Run this command in your project directory: 19 | 20 | ```shell 21 | composer require bbsnly/chartjs-php 22 | ``` 23 | 24 | Minimum Requirements: 25 | 26 | - PHP version: 8.1 or higher 27 | - ChartJS version: 2.0 or higher 28 | 29 | ## Usage 30 | 31 | Creating charts with ChartJS-PHP is simple and intuitive. Start by instantiating the `Chart` class, define your data, and render your chart. The library handles all the complexity for you. 32 | 33 | Choose from our specialized chart classes for even faster development: `BarChart`, `BubbleChart`, `DoughnutChart`, `LineChart`, `PieChart`, `PolarAreaChart`, `RadarChart`, and `ScatterChart`. 34 | 35 | Here's how to create a line chart: 36 | 37 | ```php 38 | use Bbsnly\ChartJs\Chart; 39 | use Bbsnly\ChartJs\Config\Data; 40 | use Bbsnly\ChartJs\Config\Dataset; 41 | use Bbsnly\ChartJs\Config\Options; 42 | 43 | $chart = new Chart; 44 | $chart->type = 'line'; 45 | 46 | $data = new Data(); 47 | $data->labels = ['Red', 'Green', 'Blue']; 48 | 49 | $dataset = new Dataset(); 50 | $dataset->data = [5, 10, 20]; 51 | $data->datasets[] = $dataset; 52 | 53 | $chart->data($data); 54 | 55 | $options = new Options(); 56 | $options->responsive = true; 57 | $chart->options($options); 58 | 59 | $chart->get(); // Returns the array of chart data 60 | $chart->toJson(); // Returns the JSON representation of the chart data 61 | $chart->toHtml('my_chart'); // Returns the HTML and JavaScript code for the chart 62 | ``` 63 | 64 | --- 65 | 66 | In the example below we will use the `toHtml` method to generate the HTML and JavaScript code for the chart. 67 | 68 | ```html 69 | 70 | 71 |
72 | toHtml('my_chart'); ?> 73 |
74 | ``` 75 | 76 | --- 77 | 78 | In the example below we will use the `toJson` method to generate the JSON representation of the chart data. 79 | 80 | ```html 81 |
82 | 83 |
84 | 85 | 86 | 87 | 92 | ``` 93 | 94 | ## Tests 95 | 96 | Run the test suite with: 97 | 98 | ```shell 99 | composer test 100 | ``` 101 | 102 | ## Contributing 103 | 104 | Read our [Contributing](CONTRIBUTING.md) guidelines and start improving ChartJS-PHP today. 105 | 106 | ## License 107 | 108 | The ChartJS PHP is open-sourced software licensed under the [MIT license](http://opensource.org/licenses/MIT). 109 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bbsnly/chartjs-php", 3 | "description": "PHP wrapper for Chart.js library", 4 | "keywords": [ 5 | "chartjs", 6 | "php", 7 | "charts", 8 | "graphs", 9 | "data-visualization", 10 | "javascript", 11 | "chart-library", 12 | "statistics", 13 | "analytics", 14 | "php-library", 15 | "php-wrapper", 16 | "chart-generation" 17 | ], 18 | "homepage": "https://github.com/bbsnly/chartjs-php", 19 | "type": "package", 20 | "license": "MIT", 21 | "authors": [ 22 | { 23 | "name": "Anatoliy Babushka", 24 | "email": "anatoliybabushka@gmail.com" 25 | } 26 | ], 27 | "require": { 28 | "php": ">=8.1" 29 | }, 30 | "autoload": { 31 | "psr-4": { 32 | "Bbsnly\\ChartJs\\": "src/" 33 | } 34 | }, 35 | "autoload-dev": { 36 | "psr-4": { 37 | "Tests\\": "tests/" 38 | } 39 | }, 40 | "require-dev": { 41 | "phpstan/phpstan": "^2.0", 42 | "phpunit/phpunit": "^10.0" 43 | }, 44 | "config": { 45 | "optimize-autoloader": true, 46 | "platform": { 47 | "php": "8.1" 48 | }, 49 | "preferred-install": "dist", 50 | "sort-packages": true 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /phpstan.neon.dist: -------------------------------------------------------------------------------- 1 | parameters: 2 | level: 1 3 | paths: 4 | - src 5 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | ./tests 9 | 10 | 11 | 12 | 13 | src 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/BarChart.php: -------------------------------------------------------------------------------- 1 | type = 'bar'; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/BubbleChart.php: -------------------------------------------------------------------------------- 1 | type = 'bubble'; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Chart.php: -------------------------------------------------------------------------------- 1 | type = null; 19 | 20 | $this->options = new Options(); 21 | 22 | $this->data = new Data(); 23 | } 24 | 25 | /** 26 | * Get the chart as an array 27 | * 28 | * @return array 29 | */ 30 | public function get(): array 31 | { 32 | return $this->toArray(); 33 | } 34 | 35 | /** 36 | * Convert the chart to an array 37 | * 38 | * @return array 39 | */ 40 | public function toArray(): array 41 | { 42 | return [ 43 | 'type' => $this->type, 44 | 'data' => $this->data->toArray(), 45 | 'options' => $this->options->toArray() 46 | ]; 47 | } 48 | 49 | /** 50 | * Convert the chart to JSON 51 | * 52 | * @return string 53 | * @throws \JsonException 54 | */ 55 | public function toJson(): string 56 | { 57 | return json_encode($this->toArray(), JSON_THROW_ON_ERROR | JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT); 58 | } 59 | 60 | /** 61 | * Set the chart options 62 | * 63 | * @param Options $options 64 | * @return self 65 | */ 66 | public function options(Options $options): self 67 | { 68 | $this->options = $options; 69 | 70 | return $this; 71 | } 72 | 73 | /** 74 | * Set the chart data 75 | * 76 | * @param Data $data 77 | * @return self 78 | */ 79 | public function data(Data $data): self 80 | { 81 | $this->data = $data; 82 | 83 | return $this; 84 | } 85 | 86 | /** 87 | * Helper to generate beginAtZero configuration 88 | * 89 | * @return self 90 | */ 91 | public function beginAtZero(): self 92 | { 93 | $this->options->scales([ 94 | 'yAxes' => [ 95 | [ 96 | 'ticks' => [ 97 | 'beginAtZero' => true 98 | ] 99 | ] 100 | ] 101 | ]); 102 | 103 | return $this; 104 | } 105 | 106 | /** 107 | * Generate the HTML representation of the chart 108 | * 109 | * @param string $element 110 | * @param Chart $chart 111 | * @return string 112 | */ 113 | public function toHtml(string $element, Chart $chart = null): string 114 | { 115 | if ($chart === null) { 116 | $chart = $this; 117 | } 118 | 119 | $uniqueSuffix = '_' . uniqid(); 120 | $elementId = htmlspecialchars($element . $uniqueSuffix, ENT_QUOTES, 'UTF-8'); 121 | $chartId = 'chart' . $uniqueSuffix; 122 | $instancesVar = 'bbsnlyChartJSInstances'; // Namespace the global variable 123 | 124 | return ' 125 | '; 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/ChartInterface.php: -------------------------------------------------------------------------------- 1 | attributes = $attributes; 12 | } 13 | 14 | /** 15 | * Get the value of a property. 16 | * 17 | * @param string $name 18 | * @return mixed 19 | */ 20 | public function &__get($name) 21 | { 22 | return $this->attributes[$name]; 23 | } 24 | 25 | /** 26 | * Set the value of a property. 27 | * 28 | * @param string $name 29 | * @param mixed $value 30 | * @return $this 31 | */ 32 | public function &__set($name, $value) 33 | { 34 | $this->attributes[$name] = $value; 35 | 36 | return $this; 37 | } 38 | 39 | /** 40 | * Dynamically set the value of a property. 41 | * 42 | * @param string $name 43 | * @param mixed $value 44 | * @return $this 45 | */ 46 | public function __call($name, $value) 47 | { 48 | return $this->__set($name, reset($value)); 49 | } 50 | 51 | /** 52 | * Convert the object to an array. 53 | * 54 | * @return array 55 | */ 56 | public function toArray(): array 57 | { 58 | array_walk_recursive($this->attributes, function (&$item) { 59 | if (is_object($item)) { 60 | $item = $item->toArray(); 61 | } 62 | }); 63 | 64 | return $this->attributes; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Config/ConfigInterface.php: -------------------------------------------------------------------------------- 1 | type = 'doughnut'; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/LineChart.php: -------------------------------------------------------------------------------- 1 | type = 'line'; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/PieChart.php: -------------------------------------------------------------------------------- 1 | type = 'pie'; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/PolarAreaChart.php: -------------------------------------------------------------------------------- 1 | type = 'polarArea'; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/RadarChart.php: -------------------------------------------------------------------------------- 1 | type = 'radar'; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/ScatterChart.php: -------------------------------------------------------------------------------- 1 | type = 'scatter'; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tests/ChartTest.php: -------------------------------------------------------------------------------- 1 | chart = new Chart; 25 | } 26 | 27 | /** 28 | * Test if the chart instance can be created successfully. 29 | * 30 | * We assert that the created instance is an instance of the Chart class. 31 | */ 32 | public function test_can_create_it() 33 | { 34 | $this->assertInstanceOf(Chart::class, $this->chart); 35 | } 36 | 37 | /** 38 | * Test if chart type is correctly set using helper class BarChart. 39 | */ 40 | public function test_can_create_bar_chart() 41 | { 42 | $this->chart = new BarChart; 43 | $this->assertEquals('bar', $this->chart->type); 44 | } 45 | 46 | /** 47 | * Test if chart type is correctly set using helper class BubbleChart. 48 | */ 49 | public function test_can_create_bubble_chart() 50 | { 51 | $this->chart = new BubbleChart; 52 | $this->assertEquals('bubble', $this->chart->type); 53 | } 54 | 55 | /** 56 | * Test if chart type is correctly set using helper class DoughnutChart. 57 | */ 58 | public function test_can_create_doughnut_chart() 59 | { 60 | $this->chart = new DoughnutChart; 61 | $this->assertEquals('doughnut', $this->chart->type); 62 | } 63 | 64 | /** 65 | * Test if chart type is correctly set using helper class LineChart. 66 | */ 67 | public function test_can_create_line_chart() 68 | { 69 | $this->chart = new LineChart; 70 | $this->assertEquals('line', $this->chart->type); 71 | } 72 | 73 | /** 74 | * Test if chart type is correctly set using helper class PieChart. 75 | */ 76 | public function test_can_create_pie_chart() 77 | { 78 | $this->chart = new PieChart; 79 | $this->assertEquals('pie', $this->chart->type); 80 | } 81 | 82 | /** 83 | * Test if chart type is correctly set using helper class PolarAreaChart. 84 | */ 85 | public function test_can_create_polar_area_chart() 86 | { 87 | $this->chart = new PolarAreaChart; 88 | $this->assertEquals('polarArea', $this->chart->type); 89 | } 90 | 91 | /** 92 | * Test if chart type is correctly set using helper class RadarChart. 93 | */ 94 | public function test_can_create_radar_chart() 95 | { 96 | $this->chart = new RadarChart; 97 | $this->assertEquals('radar', $this->chart->type); 98 | } 99 | 100 | /** 101 | * Test if chart type is correctly set using helper class ScatterChart. 102 | */ 103 | public function test_can_create_scatter_chart() 104 | { 105 | $this->chart = new ScatterChart; 106 | $this->assertEquals('scatter', $this->chart->type); 107 | } 108 | 109 | /** 110 | * Test if the chart is responsive. 111 | * 112 | * We set the chart type to 'line' and assert that the chart options 113 | * include the 'responsive' property set to true. 114 | */ 115 | public function test_it_is_responsive() 116 | { 117 | $expected = [ 118 | 'type' => 'line', 119 | 'data' => [], 120 | 'options' => [ 121 | 'responsive' => true 122 | ] 123 | ]; 124 | 125 | $this->chart->type = 'line'; 126 | 127 | $options = (new Options)->responsive(true); 128 | $this->chart->options($options); 129 | 130 | $this->assertEquals($expected, $this->chart->get()); 131 | } 132 | 133 | /** 134 | * Test if the chart is animated. 135 | * 136 | * We set the chart type to 'line' and assert that the chart 137 | * options include the 'responsive' property set to true and the 138 | * 'responsiveAnimationDuration' property set to 10. 139 | */ 140 | public function test_it_is_animated() 141 | { 142 | $expected = [ 143 | 'type' => 'line', 144 | 'data' => [], 145 | 'options' => [ 146 | 'responsive' => true, 147 | 'responsiveAnimationDuration' => 10 148 | ] 149 | ]; 150 | 151 | $this->chart->type = 'line'; 152 | 153 | $options = (new Options) 154 | ->responsive(true) 155 | ->responsiveAnimationDuration(10); 156 | 157 | $this->chart->options($options); 158 | 159 | $this->assertEquals($expected, $this->chart->get()); 160 | } 161 | 162 | /** 163 | * Test if the chart has basic datasets in JSON format. 164 | * 165 | * We create a Data object with two datasets, each containing an array of data values. 166 | * We set the chart data to the created Data object and assert that the 167 | * chart data is encoded to the expected JSON format. 168 | */ 169 | public function test_it_has_basic_datasets_json() 170 | { 171 | $data = new Data([ 172 | 'datasets' => [ 173 | [ 174 | 'data' => [10, 20, 30] 175 | ], 176 | [ 177 | 'data' => [30, 20, 10] 178 | ] 179 | ] 180 | ]); 181 | 182 | $this->chart->data($data); 183 | 184 | $expected = json_encode([ 185 | 'type' => null, 186 | 'data' => [ 187 | 'datasets' => [ 188 | (object)[ 189 | 'data' => [10, 20, 30] 190 | ], 191 | (object)[ 192 | 'data' => [30, 20, 10] 193 | ] 194 | ] 195 | ], 196 | 'options' => [] 197 | ], true); 198 | 199 | $this->assertEquals($expected, $this->chart->toJson()); 200 | } 201 | 202 | /** 203 | * Test if the chart has basic data with labels. 204 | * 205 | * We create a Data object and two Dataset objects. 206 | * We set the data values and labels for the datasets and set the chart 207 | * data to the created Data object. 208 | * We assert that the chart data includes the datasets, labels, and 209 | * other properties in the expected format. 210 | */ 211 | public function test_it_has_basic_data_with_labels() 212 | { 213 | $data = new Data; 214 | 215 | $datasets = [ 216 | (new Dataset)->data([10, 20, 30]), 217 | (new Dataset)->data([30, 20, 10]) 218 | ]; 219 | 220 | $data->datasets($datasets)->labels(['Red', 'Green', 'Blue']); 221 | 222 | $this->chart->data($data); 223 | 224 | $expected = [ 225 | 'type' => null, 226 | 'data' => [ 227 | 'datasets' => [ 228 | [ 229 | 'data' => [10, 20, 30], 230 | ], 231 | [ 232 | 'data' => [30, 20, 10] 233 | ] 234 | ], 235 | 'labels' => ['Red', 'Green', 'Blue'] 236 | ], 237 | 'options' => [] 238 | ]; 239 | 240 | $this->assertEquals($expected, $this->chart->get()); 241 | } 242 | 243 | /** 244 | * Test if the chart has basic data with labels and colors. 245 | * 246 | * We create a Data object and two Dataset objects. 247 | * We set the data values, labels, and background colors for the datasets 248 | * and set the chart data to the created Data object. 249 | * We assert that the chart data includes the datasets, labels, 250 | * background colors, and other properties in the expected format. 251 | */ 252 | public function test_it_has_basic_data_with_labels_and_colors() 253 | { 254 | $data = new Data; 255 | 256 | $datasets = [ 257 | (new Dataset)->data([10, 20, 30])->backgroundColor(['red', 'green', 'blue']), 258 | (new Dataset)->data([30, 20, 10])->backgroundColor(['red', 'green', 'blue']) 259 | ]; 260 | 261 | $data->datasets($datasets)->labels(['Red', 'Green', 'Blue']); 262 | 263 | $this->chart->data($data); 264 | 265 | $expected = [ 266 | 'type' => null, 267 | 'data' => [ 268 | 'datasets' => [ 269 | [ 270 | 'data' => [10, 20, 30], 271 | 'backgroundColor' => ['red', 'green', 'blue'] 272 | ], 273 | [ 274 | 'data' => [30, 20, 10], 275 | 'backgroundColor' => ['red', 'green', 'blue'] 276 | ] 277 | ], 278 | 'labels' => ['Red', 'Green', 'Blue'] 279 | ], 280 | 'options' => [] 281 | ]; 282 | 283 | $this->assertEquals($expected, $this->chart->get()); 284 | } 285 | 286 | /** 287 | * Test if the chart can be configured to start from zero using a helper method. 288 | * 289 | * We create a Data object and a Dataset object. 290 | * We set the data values and background colors for the dataset and set 291 | * the chart data to the created Data object. 292 | * We call the 'beginAtZero' method on the chart instance to configure 293 | * the y-axis ticks to start from zero. 294 | * We assert that the chart data includes the datasets, labels, 295 | * background colors, and other properties in the expected format, 296 | * including the 'beginAtZero' configuration for the y-axis ticks. 297 | */ 298 | public function test_can_set_start_from_zero_using_helper() 299 | { 300 | $data = new Data; 301 | 302 | $datasets = [ 303 | (new Dataset)->data([10, 20, 30])->backgroundColor(['red', 'green', 'blue']) 304 | ]; 305 | 306 | $data->datasets($datasets)->labels(['Red', 'Green', 'Blue']); 307 | 308 | $this->chart->data($data); 309 | 310 | $this->chart->beginAtZero(); 311 | 312 | $expected = [ 313 | 'type' => null, 314 | 'data' => [ 315 | 'datasets' => [ 316 | [ 317 | 'data' => [10, 20, 30], 318 | 'backgroundColor' => ['red', 'green', 'blue'] 319 | ] 320 | ], 321 | 'labels' => ['Red', 'Green', 'Blue'] 322 | ], 323 | 'options' => [ 324 | 'scales' => [ 325 | 'yAxes' => [ 326 | [ 327 | 'ticks' => [ 328 | 'beginAtZero' => true 329 | ] 330 | ] 331 | ] 332 | ] 333 | ] 334 | ]; 335 | 336 | $this->assertEquals($expected, $this->chart->get()); 337 | } 338 | 339 | /** 340 | * Test if the chart can create mixed chart types. 341 | * 342 | * We set the chart type to 'bar' and create a Data object. 343 | * We create two Dataset objects, one for the bar chart and one for the line chart. 344 | * We set the data values, labels, and chart type for each dataset and set 345 | * the chart data to the created Data object. 346 | * We assert that the chart data includes the datasets, labels, chart type, 347 | * and other properties in the expected format. 348 | */ 349 | public function test_can_create_mixed_chart_types() 350 | { 351 | $this->chart->type = 'bar'; 352 | 353 | $data = new Data; 354 | 355 | $datasets = [ 356 | (new Dataset)->data([10, 20, 30, 40])->label('Bar Dataset'), 357 | (new Dataset)->data([50, 50, 50, 50])->label('Line Dataset')->type('line'), 358 | ]; 359 | 360 | $data->datasets($datasets)->labels(['January', 'February', 'March', 'April']); 361 | 362 | $this->chart->data($data); 363 | 364 | $expected = [ 365 | 'type' => 'bar', 366 | 'data' => [ 367 | 'datasets' => [ 368 | [ 369 | 'data' => [10, 20, 30, 40], 370 | 'label' => 'Bar Dataset' 371 | ], 372 | [ 373 | 'data' => [50, 50, 50, 50], 374 | 'label' => 'Line Dataset', 375 | 'type' => 'line' 376 | ] 377 | ], 378 | 'labels' => ['January', 'February', 'March', 'April'] 379 | ], 380 | 'options' => [] 381 | ]; 382 | 383 | $this->assertEquals($expected, $this->chart->get()); 384 | } 385 | 386 | /** 387 | * Test if the chart dataset can be set as an array without using a method. 388 | */ 389 | public function test_can_set_dataset_as_an_array() 390 | { 391 | $this->chart->type = 'bar'; 392 | 393 | $data = new Data; 394 | $dataset = new Dataset(); 395 | $dataset->data = [5, 10, 20]; 396 | $data->datasets[] = $dataset->data; 397 | 398 | $this->chart->data($data); 399 | 400 | $expected = [ 401 | 'type' => 'bar', 402 | 'data' => [ 403 | 'datasets' => [ 404 | [5, 10, 20], 405 | ], 406 | ], 407 | 'options' => [] 408 | ]; 409 | 410 | $this->assertEquals($expected, $this->chart->get()); 411 | } 412 | 413 | /** 414 | * Test if invalid JSON throws JsonException 415 | */ 416 | public function test_invalid_json_throws_exception() 417 | { 418 | $this->expectException(\JsonException::class); 419 | 420 | // Create an invalid UTF-8 sequence to force JSON encoding error 421 | $data = new Data(); 422 | $dataset = new Dataset(); 423 | $dataset->data = ["\xFF"]; // Invalid UTF-8 sequence 424 | $data->datasets([$dataset]); 425 | 426 | $this->chart->data($data); 427 | $this->chart->toJson(); 428 | } 429 | 430 | /** 431 | * Test if HTML special characters are properly escaped 432 | */ 433 | public function test_html_special_chars_are_escaped() 434 | { 435 | $maliciousId = '">'; 436 | $html = $this->chart->toHtml($maliciousId); 437 | 438 | // Update assertion to include unique suffix 439 | $this->assertStringContains('id=""><script>alert("xss")</script>_', $html); 440 | $this->assertStringNotContains($maliciousId, $html); 441 | } 442 | 443 | /** 444 | * Test if chart cleanup code is present 445 | */ 446 | public function test_chart_cleanup_code_is_present() 447 | { 448 | $html = $this->chart->toHtml('test-chart'); 449 | $chartId = 'chart_' . substr($html, strpos($html, 'chart_') + 6, 13); // Extract the dynamic chart ID 450 | 451 | $this->assertStringContains('window.addEventListener("unload"', $html); 452 | $this->assertStringContains($chartId . '.destroy()', $html); 453 | } 454 | 455 | /** 456 | * Test if JSON output properly escapes HTML characters 457 | */ 458 | public function test_json_escapes_html_characters() 459 | { 460 | $data = new Data; 461 | $datasets = [ 462 | (new Dataset)->data([1])->label('') 463 | ]; 464 | $data->datasets($datasets); 465 | $this->chart->data($data); 466 | 467 | $json = $this->chart->toJson(); 468 | 469 | $this->assertStringNotContains('