├── .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 | [](CODE_OF_CONDUCT.md)
4 | [](https://github.com/bbsnly/chartjs-php/actions)
5 | [](https://codecov.io/gh/bbsnly/chartjs-php)
6 | [](https://packagist.org/packages/bbsnly/chartjs-php)
7 | [](https://packagist.org/packages/bbsnly/chartjs-php)
8 | [](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 | = $chart->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('