Skip to content

[ChartJs] chart.js v4 support #610

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 29 additions & 1 deletion .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -235,19 +235,47 @@ jobs:
working-directory: src/Notify
run: php vendor/bin/simple-phpunit

tests-js:
tests-js-low-deps:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master

- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT

- uses: actions/cache@v2
id: yarn-cache
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-low-yarn-${{ hashFiles('**/package.json') }}
restore-keys: |
${{ runner.os }}-low-yarn-

- name: Force Lowest Dependencies
run: node ./src/Chartjs/assets/scripts/force-lowest-dependencies.js

- run: yarn

- run: yarn test

tests-js-high-deps:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master

- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT

- uses: actions/cache@v2
id: yarn-cache
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/package.json') }}
restore-keys: |
${{ runner.os }}-yarn-

- run: yarn

- run: yarn test
1 change: 1 addition & 0 deletions src/Chartjs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## Unreleased

- Add `assets/src` to `.gitattributes` to exclude them from the installation
- Support added for Chart.js version 4

## 2.6.0

Expand Down
16 changes: 12 additions & 4 deletions src/Chartjs/assets/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,30 @@
"types": "dist/controller.d.ts",
"symfony": {
"controllers": {
"chart": {
"main": "dist/controller.js",
"chart_v3": {
"main": "dist/chart_v3_controller.js",
"name": "symfony--ux-chartjs--chart",
"webpackMode": "eager",
"fetch": "eager",
"enabled": true
},
"chart_v4": {
"main": "dist/chart_v4_controller.js",
"name": "symfony--ux-chartjs--chart",
"webpackMode": "eager",
"fetch": "eager",
"enabled": false
}
}
},
"peerDependencies": {
"@hotwired/stimulus": "^3.0.0",
"chart.js": "^3.4.1"
"chart.js": "^3.4.1 <3.9 || ^4.0"
},
"devDependencies": {
"@hotwired/stimulus": "^3.0.0",
"@types/chart.js": "^2.9.34",
"chart.js": "^3.4.1 <3.9",
"chart.js": "^3.4.1 <3.9 || ^4.0",
"jest-canvas-mock": "^2.3.0",
"resize-observer-polyfill": "^1.5.1"
}
Expand Down
144 changes: 144 additions & 0 deletions src/Chartjs/assets/scripts/force-lowest-dependencies.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
/*
* This file is part of the Symfony Webpack Encore package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

'use strict';

const fs = require('fs');
const childProcess = require('child_process');

const packageFilePath = 'src/Chartjs/assets/package.json';
const chartJsLowVersion = '3.4.1';

/**
* @param {string} dependency
* @param {string} range
* @return {Promise}
*/
function getLowestVersion(dependency, range) {
return new Promise((resolve, reject) => {
if (range.startsWith('file:')) {
resolve([dependency, range]);
}

childProcess.exec(
`npm view "${dependency}@${range}" version`,
{ encoding: 'utf-8' },
(error, stdout) => {
if (error) {
reject(`Could not retrieve versions list for "${dependency}@${range}"`);
return;
}

const versions = stdout
.split('\n')
.filter(line => line);

if (versions.length === 0) {
reject(`Could not find a lowest version for "${dependency}@${range}"`);
return;
}

const parts = versions[0].split(' ');

// If there is only one version available that version
// is directly printed as the output of npm view.
if (parts.length === 1) {
resolve([dependency, parts[0]]);
return;
}

// If multiple versions are available then it outputs
// multiple lines matching the following format:
// <package>@<version> '<version>'
if (parts.length === 2) {
resolve([dependency, parts[1].replace(/'/g, '')]);
return;
}

reject(`Unexpected response for "${dependency}@${range}": ${versions[0]}`);
}
);
});
}

fs.readFile(packageFilePath, (error, data) => {
if (error) {
throw error;
}

const packageInfo = JSON.parse(data);

const dependencyPromises = [];
if (packageInfo.dependencies) {
for (const dependency in packageInfo.dependencies) {
dependencyPromises.push(getLowestVersion(
dependency,
packageInfo.dependencies[dependency]
));
}
}

const devDependencyPromises = [];
if (packageInfo.devDependencies) {
for (const devDependency in packageInfo.devDependencies) {
devDependencyPromises.push(getLowestVersion(
devDependency,
packageInfo.devDependencies[devDependency]
));
}
}

const dependenciesUpdate = Promise.all(dependencyPromises).then(versions => {
versions.forEach(version => {
packageInfo.dependencies[version[0]] = version[1];
});
});

const devDependenciesUpdate = Promise.all(devDependencyPromises).then(versions => {
versions.forEach(version => {
packageInfo.devDependencies[version[0]] = version[1];
});
});

// Once all the lowest versions have been resolved, update the
// package.json file accordingly.
Promise
.all([dependenciesUpdate, devDependenciesUpdate])
.then(() => new Promise((resolve, reject) => {
fs.writeFile(packageFilePath, JSON.stringify(packageInfo, null, 2), (error) => {
if (error) {
reject(error);
return;
}

resolve();
});
}))
.then(() => {
console.log('Manually forcing chart.js to lowest version');
packageInfo.devDependencies['chart.js'] = chartJsLowVersion;
})
.then(() => {
console.log('Updated package.json file with lowest dependency versions: ');

console.log('Dependencies:');
for (const dependency in packageInfo.dependencies) {
console.log(` - ${dependency}: ${packageInfo.dependencies[dependency]}`);
}

console.log('Dev dependencies:');
for (const dependency in packageInfo.devDependencies) {
console.log(` - ${dependency}: ${packageInfo.devDependencies[dependency]}`);
}
})
.catch(error => {
console.error(error);
process.exit(1); // eslint-disable-line
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,8 @@
'use strict';

import { Controller } from '@hotwired/stimulus';
import Chart from 'chart.js/auto';

export default class extends Controller {
export default abstract class AbstractChartController extends Controller {
declare readonly viewValue: any;

static values = {
Expand All @@ -35,12 +34,17 @@ export default class extends Controller {
if (!canvasContext) {
throw new Error('Could not getContext() from Element');
}
const chart = new Chart(canvasContext, payload);
const chart = this.createChart(canvasContext, payload);

this._dispatchEvent('chartjs:connect', { chart });
}

_dispatchEvent(name: string, payload: any) {
this.element.dispatchEvent(new CustomEvent(name, { detail: payload }));
}

/**
* To support v3 and v4 of chart.js this help function is added, could be refactored when support for v3 is dropped
*/
abstract createChart(canvasContext: any, payload: any): any;
}
20 changes: 20 additions & 0 deletions src/Chartjs/assets/src/chart_v3_controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

'use strict';

import AbstractChartController from './abstract_controller';
import Chart from 'chart.js/auto';
import { ChartConfiguration, ChartItem } from 'chart.js';

export default class extends AbstractChartController {
createChart(canvasContext: ChartItem, payload: ChartConfiguration): any {
return new Chart(canvasContext, payload);
}
}
20 changes: 20 additions & 0 deletions src/Chartjs/assets/src/chart_v4_controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

'use strict';

import AbstractChartController from './abstract_controller';
import Chart from 'chart.js/auto/auto.cjs';
import { ChartConfiguration, ChartItem } from 'chart.js';

export default class extends AbstractChartController {
createChart(canvasContext: ChartItem, payload: ChartConfiguration): any {
return new Chart(canvasContext, payload);
}
}
26 changes: 26 additions & 0 deletions src/Chartjs/assets/test/chart_check_controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

'use strict';

import { Controller } from '@hotwired/stimulus'

// Controller used to check the actual controller was properly booted
export class CheckController extends Controller {
connect() {
this.element.addEventListener('chartjs:pre-connect', () => {
this.element.classList.add('pre-connected');
});

this.element.addEventListener('chartjs:connect', (event) => {
this.element.classList.add('connected');
this.element.chart = event.detail.chart;
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,11 @@

'use strict';

import { Application, Controller } from '@hotwired/stimulus';
import { Application } from '@hotwired/stimulus';
import { CheckController } from './chart_check_controller';
import { getByTestId, waitFor } from '@testing-library/dom';
import { clearDOM, mountDOM } from '@symfony/stimulus-testing';
import ChartjsController from '../src/controller';

// Controller used to check the actual controller was properly booted
class CheckController extends Controller {
connect() {
this.element.addEventListener('chartjs:pre-connect', () => {
this.element.classList.add('pre-connected');
});

this.element.addEventListener('chartjs:connect', (event) => {
this.element.classList.add('connected');
this.element.chart = event.detail.chart;
});
}
}
import ChartjsController from '../src/chart_v3_controller';

const startStimulus = () => {
const application = Application.start();
Expand Down
Loading