chartjs-plugin-trendline 3.1.1 → 3.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,307 @@
1
+ import { pluginTrendlineLinear } from './plugin.js';
2
+
3
+ describe('pluginTrendlineLinear.beforeInit - legend generation', () => {
4
+ let chartInstance;
5
+ let originalGenerateLabels;
6
+
7
+ beforeEach(() => {
8
+ originalGenerateLabels = jest.fn(() => [{ text: 'Dataset 1' }]);
9
+ chartInstance = {
10
+ data: { datasets: [] },
11
+ legend: {
12
+ options: {
13
+ labels: {
14
+ generateLabels: originalGenerateLabels,
15
+ },
16
+ },
17
+ },
18
+ };
19
+ });
20
+
21
+ /**
22
+ * Bug 1: Legend item must appear even when trendlineConfig.label is absent,
23
+ * as long as trendlineConfig.legend is configured.
24
+ */
25
+ it('adds a legend item when only trendlineConfig.legend is present (no label key)', () => {
26
+ chartInstance.data.datasets = [
27
+ {
28
+ label: 'My Dataset',
29
+ borderColor: '#2196F3',
30
+ trendlineLinear: {
31
+ // No `label` key - this was the bug trigger
32
+ legend: {
33
+ text: 'Trend',
34
+ strokeStyle: '#ff6b6b',
35
+ },
36
+ },
37
+ },
38
+ ];
39
+
40
+ pluginTrendlineLinear.beforeInit(chartInstance);
41
+
42
+ const labels = chartInstance.legend.options.labels.generateLabels(chartInstance);
43
+ const trendlineLabel = labels.find((l) => l.text === 'Trend');
44
+ expect(trendlineLabel).toBeDefined();
45
+ });
46
+
47
+ it('does not add a legend item when neither label nor legend key is present', () => {
48
+ chartInstance.data.datasets = [
49
+ {
50
+ label: 'My Dataset',
51
+ trendlineLinear: {
52
+ colorMin: 'red',
53
+ },
54
+ },
55
+ ];
56
+
57
+ pluginTrendlineLinear.beforeInit(chartInstance);
58
+
59
+ // generateLabels should not have been replaced
60
+ expect(chartInstance.legend.options.labels.generateLabels).toBe(originalGenerateLabels);
61
+ });
62
+
63
+ it('still adds a legend item when only trendlineConfig.label is present (backwards compat)', () => {
64
+ chartInstance.data.datasets = [
65
+ {
66
+ label: 'My Dataset',
67
+ trendlineLinear: {
68
+ label: { display: false },
69
+ legend: { text: 'Trend' },
70
+ },
71
+ },
72
+ ];
73
+
74
+ pluginTrendlineLinear.beforeInit(chartInstance);
75
+
76
+ const labels = chartInstance.legend.options.labels.generateLabels(chartInstance);
77
+ expect(labels.some((l) => l.text === 'Trend')).toBe(true);
78
+ });
79
+
80
+ /**
81
+ * Bug 2: legendConfig.strokeStyle should be respected.
82
+ */
83
+ it('uses legendConfig.strokeStyle for the legend item strokeStyle', () => {
84
+ chartInstance.data.datasets = [
85
+ {
86
+ borderColor: '#2196F3',
87
+ trendlineLinear: {
88
+ legend: {
89
+ text: 'Trend',
90
+ strokeStyle: '#ff6b6b',
91
+ },
92
+ },
93
+ },
94
+ ];
95
+
96
+ pluginTrendlineLinear.beforeInit(chartInstance);
97
+
98
+ const labels = chartInstance.legend.options.labels.generateLabels(chartInstance);
99
+ const trendlineLabel = labels.find((l) => l.text === 'Trend');
100
+ expect(trendlineLabel.strokeStyle).toBe('#ff6b6b');
101
+ });
102
+
103
+ it('falls back to legendConfig.color for strokeStyle when strokeStyle is absent', () => {
104
+ chartInstance.data.datasets = [
105
+ {
106
+ borderColor: '#2196F3',
107
+ trendlineLinear: {
108
+ legend: {
109
+ text: 'Trend',
110
+ color: '#aabbcc',
111
+ },
112
+ },
113
+ },
114
+ ];
115
+
116
+ pluginTrendlineLinear.beforeInit(chartInstance);
117
+
118
+ const labels = chartInstance.legend.options.labels.generateLabels(chartInstance);
119
+ const trendlineLabel = labels.find((l) => l.text === 'Trend');
120
+ expect(trendlineLabel.strokeStyle).toBe('#aabbcc');
121
+ });
122
+
123
+ it('falls back to dataset.borderColor for strokeStyle when neither strokeStyle nor color is set', () => {
124
+ chartInstance.data.datasets = [
125
+ {
126
+ borderColor: '#2196F3',
127
+ trendlineLinear: {
128
+ legend: { text: 'Trend' },
129
+ },
130
+ },
131
+ ];
132
+
133
+ pluginTrendlineLinear.beforeInit(chartInstance);
134
+
135
+ const labels = chartInstance.legend.options.labels.generateLabels(chartInstance);
136
+ const trendlineLabel = labels.find((l) => l.text === 'Trend');
137
+ expect(trendlineLabel.strokeStyle).toBe('#2196F3');
138
+ });
139
+
140
+ it('falls back to default gray strokeStyle when no color source is available', () => {
141
+ chartInstance.data.datasets = [
142
+ {
143
+ trendlineLinear: {
144
+ legend: { text: 'Trend' },
145
+ },
146
+ },
147
+ ];
148
+
149
+ pluginTrendlineLinear.beforeInit(chartInstance);
150
+
151
+ const labels = chartInstance.legend.options.labels.generateLabels(chartInstance);
152
+ const trendlineLabel = labels.find((l) => l.text === 'Trend');
153
+ expect(trendlineLabel.strokeStyle).toBe('rgba(169,169,169, .6)');
154
+ });
155
+
156
+ /**
157
+ * Bug 3: legendConfig.lineWidth should control the legend item line width.
158
+ */
159
+ it('uses legendConfig.lineWidth for the legend item lineWidth', () => {
160
+ chartInstance.data.datasets = [
161
+ {
162
+ trendlineLinear: {
163
+ legend: {
164
+ text: 'Trend',
165
+ lineWidth: 3,
166
+ },
167
+ },
168
+ },
169
+ ];
170
+
171
+ pluginTrendlineLinear.beforeInit(chartInstance);
172
+
173
+ const labels = chartInstance.legend.options.labels.generateLabels(chartInstance);
174
+ const trendlineLabel = labels.find((l) => l.text === 'Trend');
175
+ expect(trendlineLabel.lineWidth).toBe(3);
176
+ });
177
+
178
+ it('allows legendConfig.lineWidth of 0 to hide the legend border', () => {
179
+ chartInstance.data.datasets = [
180
+ {
181
+ trendlineLinear: {
182
+ legend: {
183
+ text: 'Trend',
184
+ lineWidth: 0,
185
+ },
186
+ },
187
+ },
188
+ ];
189
+
190
+ pluginTrendlineLinear.beforeInit(chartInstance);
191
+
192
+ const labels = chartInstance.legend.options.labels.generateLabels(chartInstance);
193
+ const trendlineLabel = labels.find((l) => l.text === 'Trend');
194
+ expect(trendlineLabel.lineWidth).toBe(0);
195
+ });
196
+
197
+ it('falls back to legendConfig.width for lineWidth when lineWidth is absent', () => {
198
+ chartInstance.data.datasets = [
199
+ {
200
+ trendlineLinear: {
201
+ legend: {
202
+ text: 'Trend',
203
+ width: 2,
204
+ },
205
+ },
206
+ },
207
+ ];
208
+
209
+ pluginTrendlineLinear.beforeInit(chartInstance);
210
+
211
+ const labels = chartInstance.legend.options.labels.generateLabels(chartInstance);
212
+ const trendlineLabel = labels.find((l) => l.text === 'Trend');
213
+ expect(trendlineLabel.lineWidth).toBe(2);
214
+ });
215
+
216
+ it('defaults lineWidth to 1 when neither lineWidth nor width is set', () => {
217
+ chartInstance.data.datasets = [
218
+ {
219
+ trendlineLinear: {
220
+ legend: { text: 'Trend' },
221
+ },
222
+ },
223
+ ];
224
+
225
+ pluginTrendlineLinear.beforeInit(chartInstance);
226
+
227
+ const labels = chartInstance.legend.options.labels.generateLabels(chartInstance);
228
+ const trendlineLabel = labels.find((l) => l.text === 'Trend');
229
+ expect(trendlineLabel.lineWidth).toBe(1);
230
+ });
231
+
232
+ /**
233
+ * Legend text fallback behaviour.
234
+ */
235
+ it('uses dataset.label as text fallback when legendConfig.text is absent', () => {
236
+ chartInstance.data.datasets = [
237
+ {
238
+ label: 'Balance',
239
+ trendlineLinear: {
240
+ legend: { strokeStyle: '#ff0000' },
241
+ },
242
+ },
243
+ ];
244
+
245
+ pluginTrendlineLinear.beforeInit(chartInstance);
246
+
247
+ const labels = chartInstance.legend.options.labels.generateLabels(chartInstance);
248
+ const trendlineLabel = labels.find((l) => l.text === 'Balance');
249
+ expect(trendlineLabel).toBeDefined();
250
+ });
251
+
252
+ it('falls back to "Trendline" text when neither legendConfig.text nor dataset.label is set', () => {
253
+ chartInstance.data.datasets = [
254
+ {
255
+ trendlineLinear: {
256
+ legend: { strokeStyle: '#ff0000' },
257
+ },
258
+ },
259
+ ];
260
+
261
+ pluginTrendlineLinear.beforeInit(chartInstance);
262
+
263
+ const labels = chartInstance.legend.options.labels.generateLabels(chartInstance);
264
+ const trendlineLabel = labels.find((l) => l.text === 'Trendline');
265
+ expect(trendlineLabel).toBeDefined();
266
+ });
267
+
268
+ it('does not add legend item when legendConfig.display is false', () => {
269
+ chartInstance.data.datasets = [
270
+ {
271
+ label: 'My Dataset',
272
+ trendlineLinear: {
273
+ legend: { text: 'Trend', display: false },
274
+ },
275
+ },
276
+ ];
277
+
278
+ pluginTrendlineLinear.beforeInit(chartInstance);
279
+
280
+ const labels = chartInstance.legend.options.labels.generateLabels(chartInstance);
281
+ expect(labels.some((l) => l.text === 'Trend')).toBe(false);
282
+ });
283
+
284
+ it('works with trendlineExponential config', () => {
285
+ chartInstance.data.datasets = [
286
+ {
287
+ label: 'Exp Dataset',
288
+ borderColor: '#00ff00',
289
+ trendlineExponential: {
290
+ legend: {
291
+ text: 'Exp Trend',
292
+ strokeStyle: '#ff0000',
293
+ lineWidth: 2,
294
+ },
295
+ },
296
+ },
297
+ ];
298
+
299
+ pluginTrendlineLinear.beforeInit(chartInstance);
300
+
301
+ const labels = chartInstance.legend.options.labels.generateLabels(chartInstance);
302
+ const trendlineLabel = labels.find((l) => l.text === 'Exp Trend');
303
+ expect(trendlineLabel).toBeDefined();
304
+ expect(trendlineLabel.strokeStyle).toBe('#ff0000');
305
+ expect(trendlineLabel.lineWidth).toBe(2);
306
+ });
307
+ });
package/src/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { pluginTrendlineLinear } from './core/plugin';
1
+ import { pluginTrendlineLinear } from './core/plugin.js';
2
2
 
3
3
  // If we're in the browser and have access to the global Chart obj, register plugin automatically
4
4
  if (typeof window !== 'undefined' && window.Chart) {
@@ -1,4 +1,4 @@
1
- import { getScales, setLineStyle, drawTrendline, fillBelowTrendline } from './drawing';
1
+ import { getScales, setLineStyle, drawTrendline, fillBelowTrendline } from './drawing.js';
2
2
 
3
3
  describe('getScales', () => {
4
4
  it('should correctly identify x and y scales', () => {
@@ -1,4 +1,4 @@
1
- import { ExponentialFitter } from './exponentialFitter';
1
+ import { ExponentialFitter } from './exponentialFitter.js';
2
2
 
3
3
  describe('ExponentialFitter', () => {
4
4
  describe('constructor', () => {
@@ -1,4 +1,4 @@
1
- import { LineFitter } from './lineFitter';
1
+ import { LineFitter } from './lineFitter.js';
2
2
 
3
3
  describe('LineFitter', () => {
4
4
  test('constructor should initialize values correctly', () => {
@@ -1,11 +0,0 @@
1
- /*!
2
- * chartjs-plugin-trendline.js
3
- * Version: 3.0.0
4
- *
5
- * Copyright 2025 Marcus Alsterfjord
6
- * Released under the MIT license
7
- * https://github.com/Makanz/chartjs-plugin-trendline/blob/master/README.md
8
- *
9
- * Modified by @vesal: accept xy-data from scatter,
10
- * Modified by @Megaemce: add label and basic legend to trendline, add JSDoc,
11
- */
package/webpack.config.js DELETED
@@ -1,9 +0,0 @@
1
- const path = require('path');
2
-
3
- module.exports = {
4
- entry: './src/index.js',
5
- output: {
6
- filename: 'chartjs-plugin-trendline.min.js',
7
- path: path.resolve(__dirname, 'dist'),
8
- },
9
- };