diff --git a/src/Chartjs/assets/dist/controller.js b/src/Chartjs/assets/dist/controller.js index 76f0db9fe7c..c865bf75b77 100644 --- a/src/Chartjs/assets/dist/controller.js +++ b/src/Chartjs/assets/dist/controller.js @@ -1,12 +1,19 @@ import { Controller } from '@hotwired/stimulus'; import Chart from 'chart.js/auto'; +let isChartInitialized = false; class default_1 extends Controller { constructor() { super(...arguments); this.chart = null; } connect() { + if (!isChartInitialized) { + isChartInitialized = true; + this.dispatchEvent('init', { + Chart, + }); + } if (!(this.element instanceof HTMLCanvasElement)) { throw new Error('Invalid element'); } @@ -14,7 +21,10 @@ class default_1 extends Controller { if (Array.isArray(payload.options) && 0 === payload.options.length) { payload.options = {}; } - this.dispatchEvent('pre-connect', { options: payload.options }); + this.dispatchEvent('pre-connect', { + options: payload.options, + config: payload, + }); const canvasContext = this.element.getContext('2d'); if (!canvasContext) { throw new Error('Could not getContext() from Element'); diff --git a/src/Chartjs/assets/src/controller.ts b/src/Chartjs/assets/src/controller.ts index 12e10e8e316..8b9ce1c1403 100644 --- a/src/Chartjs/assets/src/controller.ts +++ b/src/Chartjs/assets/src/controller.ts @@ -12,6 +12,8 @@ import { Controller } from '@hotwired/stimulus'; import Chart from 'chart.js/auto'; +let isChartInitialized = false; + export default class extends Controller { declare readonly viewValue: any; @@ -22,6 +24,13 @@ export default class extends Controller { private chart: Chart | null = null; connect() { + if (!isChartInitialized) { + isChartInitialized = true; + this.dispatchEvent('init', { + Chart, + }); + } + if (!(this.element instanceof HTMLCanvasElement)) { throw new Error('Invalid element'); } @@ -31,7 +40,10 @@ export default class extends Controller { payload.options = {}; } - this.dispatchEvent('pre-connect', { options: payload.options }); + this.dispatchEvent('pre-connect', { + options: payload.options, + config: payload, + }); const canvasContext = this.element.getContext('2d'); if (!canvasContext) { diff --git a/src/Chartjs/assets/test/controller.test.ts b/src/Chartjs/assets/test/controller.test.ts index ed26b40d148..8c7512b0118 100644 --- a/src/Chartjs/assets/test/controller.test.ts +++ b/src/Chartjs/assets/test/controller.test.ts @@ -13,9 +13,18 @@ import { Application } from '@hotwired/stimulus'; import { waitFor } from '@testing-library/dom'; import ChartjsController from '../src/controller'; +// Kept track of globally, but just used in one test. +// This is because, by the time that test has run, it is likely that the +// chartjs:init event has already been dispatched. So, we capture it out here. +let initCallCount = 0; + const startChartTest = async (canvasHtml: string): Promise<{ canvas: HTMLCanvasElement, chart: Chart }> => { let chart: Chart | null = null; + document.body.addEventListener('chartjs:init', () => { + initCallCount++; + }); + document.body.addEventListener('chartjs:pre-connect', () => { document.body.classList.add('pre-connected'); }); @@ -95,4 +104,51 @@ describe('ChartjsController', () => { expect(chart.options.showLines).toBe(true); }); }); + + it('dispatches the events correctly', async () => { + let preConnectCallCount = 0; + let preConnectDetail: any = null; + + document.body.addEventListener('chartjs:pre-connect', (event: any) => { + preConnectCallCount++; + preConnectDetail = event.detail; + }); + + await startChartTest(` + + `); + expect(initCallCount).toBe(1); + expect(preConnectCallCount).toBe(1); + expect(preConnectDetail.options.showLines).toBe(false); + expect(preConnectDetail.config.type).toBe('line'); + expect(preConnectDetail.config.data.datasets[0].data).toEqual([0, 10, 5, 2, 20, 30, 45]); + + // add a second chart! + const canvas = document.createElement('canvas'); + canvas.dataset.controller = 'chartjs'; + canvas.dataset.chartjsViewValue = JSON.stringify({ + type: 'line', + data: { + labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'], + datasets: [{ + label: 'My First dataset', + backgroundColor: 'rgb(255, 99, 132)', + borderColor: 'rgb(255, 99, 132)', + data: [0, 10, 5, 2, 20, 30, 45], + }], + }, + options: { + showLines: false, + }, + }); + document.body.appendChild(canvas); + + await waitFor(() => expect(preConnectCallCount).toBe(2)); + // still only initialized once + expect(initCallCount).toBe(1); + }); }); diff --git a/src/Chartjs/doc/index.rst b/src/Chartjs/doc/index.rst index 56ded2a3f18..5baa79a125c 100644 --- a/src/Chartjs/doc/index.rst +++ b/src/Chartjs/doc/index.rst @@ -98,18 +98,20 @@ First, install the plugin: $ npm install chartjs-plugin-zoom -D # or use yarn - $ yarn add chartjs-plugin-zoom -dev + $ yarn add chartjs-plugin-zoom --dev Then register the plugin globally. This can be done in your ``app.js`` file: .. code-block:: javascript // assets/app.js - - import { Chart } from 'chart.js'; import zoomPlugin from 'chartjs-plugin-zoom'; - Chart.register(zoomPlugin); + // register globally for all charts + document.addEventListener('chartjs:init', function (event) { + const Chart = event.detail.Chart; + Chart.register(zoomPlugin); + }); // ... @@ -177,10 +179,11 @@ custom Stimulus controller: _onPreConnect(event) { // The chart is not yet created - console.log(event.detail.options); // You can access the chart options using the event details + // You can access the config that will be passed to "new Chart()" + console.log(event.detail.config); // For instance you can format Y axis - event.detail.options.scales = { + event.detail.config.options.scales = { yAxes: [ { ticks: { @@ -213,6 +216,24 @@ Then in your render call, add your controller as an HTML attribute: {{ render_chart(chart, {'data-controller': 'mychart'}) }} +There is also a ``chartjs:init`` event that is called just *one* time before your +first chart is rendered. That's an ideal place to `register plugins globally `_ +or make other changes to any "static"/global part of Chart.js. For example, +to add a global `Tooltip positioner`_: + +.. code-block:: javascript + + // assets/app.js + + // register globally for all charts + document.addEventListener('chartjs:init', function (event) { + const Chart = event.detail.Chart; + const Tooltip = Chart.registry.plugins.get('tooltip'); + Tooltip.positioners.bottom = function(items) { + /* ... */ + }; + }); + Backward Compatibility promise ------------------------------ @@ -228,3 +249,4 @@ the Symfony framework: https://symfony.com/doc/current/contributing/code/bc.html .. _`a lot of plugins`: https://github.com/chartjs/awesome#plugins .. _`zoom plugin`: https://www.chartjs.org/chartjs-plugin-zoom/latest/ .. _`zoom plugin documentation`: https://www.chartjs.org/chartjs-plugin-zoom/latest/guide/integration.html +.. _`Tooltip positioner`: https://www.chartjs.org/docs/latest/samples/tooltip/position.html