Skip to content

Commit a4763d7

Browse files
committed
bug #870 [ChartJs] Adding a better way to register Chartjs plugins (weaverryan)
This PR was squashed before being merged into the 2.x branch. Discussion ---------- [ChartJs] Adding a better way to register Chartjs plugins | Q | A | ------------- | --- | Bug fix? | yes | New feature? | yes, but in order to fix a bug | Tickets | Fix #867 Fix #754 Fix #598 Fix #167 | License | MIT The previous method, in npm (and likely in the native browser via importmaps) the Chart would always be imported into the Chartjs UX controller before your userland code would run to register the plugin. Cheers! Commits ------- ab5f111 [ChartJs] Adding a better way to register Chartjs plugins
2 parents da66d2c + ab5f111 commit a4763d7

File tree

4 files changed

+108
-8
lines changed

4 files changed

+108
-8
lines changed

src/Chartjs/assets/dist/controller.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,30 @@
11
import { Controller } from '@hotwired/stimulus';
22
import Chart from 'chart.js/auto';
33

4+
let isChartInitialized = false;
45
class default_1 extends Controller {
56
constructor() {
67
super(...arguments);
78
this.chart = null;
89
}
910
connect() {
11+
if (!isChartInitialized) {
12+
isChartInitialized = true;
13+
this.dispatchEvent('init', {
14+
Chart,
15+
});
16+
}
1017
if (!(this.element instanceof HTMLCanvasElement)) {
1118
throw new Error('Invalid element');
1219
}
1320
const payload = this.viewValue;
1421
if (Array.isArray(payload.options) && 0 === payload.options.length) {
1522
payload.options = {};
1623
}
17-
this.dispatchEvent('pre-connect', { options: payload.options });
24+
this.dispatchEvent('pre-connect', {
25+
options: payload.options,
26+
config: payload,
27+
});
1828
const canvasContext = this.element.getContext('2d');
1929
if (!canvasContext) {
2030
throw new Error('Could not getContext() from Element');

src/Chartjs/assets/src/controller.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
import { Controller } from '@hotwired/stimulus';
1313
import Chart from 'chart.js/auto';
1414

15+
let isChartInitialized = false;
16+
1517
export default class extends Controller {
1618
declare readonly viewValue: any;
1719

@@ -22,6 +24,13 @@ export default class extends Controller {
2224
private chart: Chart | null = null;
2325

2426
connect() {
27+
if (!isChartInitialized) {
28+
isChartInitialized = true;
29+
this.dispatchEvent('init', {
30+
Chart,
31+
});
32+
}
33+
2534
if (!(this.element instanceof HTMLCanvasElement)) {
2635
throw new Error('Invalid element');
2736
}
@@ -31,7 +40,10 @@ export default class extends Controller {
3140
payload.options = {};
3241
}
3342

34-
this.dispatchEvent('pre-connect', { options: payload.options });
43+
this.dispatchEvent('pre-connect', {
44+
options: payload.options,
45+
config: payload,
46+
});
3547

3648
const canvasContext = this.element.getContext('2d');
3749
if (!canvasContext) {

src/Chartjs/assets/test/controller.test.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,18 @@ import { Application } from '@hotwired/stimulus';
1313
import { waitFor } from '@testing-library/dom';
1414
import ChartjsController from '../src/controller';
1515

16+
// Kept track of globally, but just used in one test.
17+
// This is because, by the time that test has run, it is likely that the
18+
// chartjs:init event has already been dispatched. So, we capture it out here.
19+
let initCallCount = 0;
20+
1621
const startChartTest = async (canvasHtml: string): Promise<{ canvas: HTMLCanvasElement, chart: Chart }> => {
1722
let chart: Chart | null = null;
1823

24+
document.body.addEventListener('chartjs:init', () => {
25+
initCallCount++;
26+
});
27+
1928
document.body.addEventListener('chartjs:pre-connect', () => {
2029
document.body.classList.add('pre-connected');
2130
});
@@ -95,4 +104,51 @@ describe('ChartjsController', () => {
95104
expect(chart.options.showLines).toBe(true);
96105
});
97106
});
107+
108+
it('dispatches the events correctly', async () => {
109+
let preConnectCallCount = 0;
110+
let preConnectDetail: any = null;
111+
112+
document.body.addEventListener('chartjs:pre-connect', (event: any) => {
113+
preConnectCallCount++;
114+
preConnectDetail = event.detail;
115+
});
116+
117+
await startChartTest(`
118+
<canvas
119+
data-testid="canvas"
120+
data-controller="chartjs"
121+
data-chartjs-view-value="&#x7B;&quot;type&quot;&#x3A;&quot;line&quot;,&quot;data&quot;&#x3A;&#x7B;&quot;labels&quot;&#x3A;&#x5B;&quot;January&quot;,&quot;February&quot;,&quot;March&quot;,&quot;April&quot;,&quot;May&quot;,&quot;June&quot;,&quot;July&quot;&#x5D;,&quot;datasets&quot;&#x3A;&#x5B;&#x7B;&quot;label&quot;&#x3A;&quot;My&#x20;First&#x20;dataset&quot;,&quot;backgroundColor&quot;&#x3A;&quot;rgb&#x28;255,&#x20;99,&#x20;132&#x29;&quot;,&quot;borderColor&quot;&#x3A;&quot;rgb&#x28;255,&#x20;99,&#x20;132&#x29;&quot;,&quot;data&quot;&#x3A;&#x5B;0,10,5,2,20,30,45&#x5D;&#x7D;&#x5D;&#x7D;,&quot;options&quot;&#x3A;&#x7B;&quot;showLines&quot;&#x3A;false&#x7D;&#x7D;"
122+
></canvas>
123+
`);
124+
expect(initCallCount).toBe(1);
125+
expect(preConnectCallCount).toBe(1);
126+
expect(preConnectDetail.options.showLines).toBe(false);
127+
expect(preConnectDetail.config.type).toBe('line');
128+
expect(preConnectDetail.config.data.datasets[0].data).toEqual([0, 10, 5, 2, 20, 30, 45]);
129+
130+
// add a second chart!
131+
const canvas = document.createElement('canvas');
132+
canvas.dataset.controller = 'chartjs';
133+
canvas.dataset.chartjsViewValue = JSON.stringify({
134+
type: 'line',
135+
data: {
136+
labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
137+
datasets: [{
138+
label: 'My First dataset',
139+
backgroundColor: 'rgb(255, 99, 132)',
140+
borderColor: 'rgb(255, 99, 132)',
141+
data: [0, 10, 5, 2, 20, 30, 45],
142+
}],
143+
},
144+
options: {
145+
showLines: false,
146+
},
147+
});
148+
document.body.appendChild(canvas);
149+
150+
await waitFor(() => expect(preConnectCallCount).toBe(2));
151+
// still only initialized once
152+
expect(initCallCount).toBe(1);
153+
});
98154
});

src/Chartjs/doc/index.rst

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -98,18 +98,20 @@ First, install the plugin:
9898
$ npm install chartjs-plugin-zoom -D
9999
100100
# or use yarn
101-
$ yarn add chartjs-plugin-zoom -dev
101+
$ yarn add chartjs-plugin-zoom --dev
102102
103103
Then register the plugin globally. This can be done in your ``app.js`` file:
104104

105105
.. code-block:: javascript
106106
107107
// assets/app.js
108-
109-
import { Chart } from 'chart.js';
110108
import zoomPlugin from 'chartjs-plugin-zoom';
111109
112-
Chart.register(zoomPlugin);
110+
// register globally for all charts
111+
document.addEventListener('chartjs:init', function (event) {
112+
const Chart = event.detail.Chart;
113+
Chart.register(zoomPlugin);
114+
});
113115
114116
// ...
115117
@@ -177,10 +179,11 @@ custom Stimulus controller:
177179
178180
_onPreConnect(event) {
179181
// The chart is not yet created
180-
console.log(event.detail.options); // You can access the chart options using the event details
182+
// You can access the config that will be passed to "new Chart()"
183+
console.log(event.detail.config);
181184
182185
// For instance you can format Y axis
183-
event.detail.options.scales = {
186+
event.detail.config.options.scales = {
184187
yAxes: [
185188
{
186189
ticks: {
@@ -213,6 +216,24 @@ Then in your render call, add your controller as an HTML attribute:
213216
214217
{{ render_chart(chart, {'data-controller': 'mychart'}) }}
215218
219+
There is also a ``chartjs:init`` event that is called just *one* time before your
220+
first chart is rendered. That's an ideal place to `register plugins globally <Using Plugins>`_
221+
or make other changes to any "static"/global part of Chart.js. For example,
222+
to add a global `Tooltip positioner`_:
223+
224+
.. code-block:: javascript
225+
226+
// assets/app.js
227+
228+
// register globally for all charts
229+
document.addEventListener('chartjs:init', function (event) {
230+
const Chart = event.detail.Chart;
231+
const Tooltip = Chart.registry.plugins.get('tooltip');
232+
Tooltip.positioners.bottom = function(items) {
233+
/* ... */
234+
};
235+
});
236+
216237
Backward Compatibility promise
217238
------------------------------
218239

@@ -228,3 +249,4 @@ the Symfony framework: https://symfony.com/doc/current/contributing/code/bc.html
228249
.. _`a lot of plugins`: https://github.com/chartjs/awesome#plugins
229250
.. _`zoom plugin`: https://www.chartjs.org/chartjs-plugin-zoom/latest/
230251
.. _`zoom plugin documentation`: https://www.chartjs.org/chartjs-plugin-zoom/latest/guide/integration.html
252+
.. _`Tooltip positioner`: https://www.chartjs.org/docs/latest/samples/tooltip/position.html

0 commit comments

Comments
 (0)