chartjs-plugin-trendline 3.2.0 → 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.
Files changed (43) hide show
  1. package/.github/copilot-instructions.md +40 -40
  2. package/.github/workflows/release.yml +64 -61
  3. package/.github/workflows/tests.yml +26 -26
  4. package/.prettierrc +5 -5
  5. package/CLAUDE.md +44 -44
  6. package/GEMINI.md +40 -40
  7. package/LICENSE +21 -21
  8. package/MIGRATION.md +126 -126
  9. package/README.md +166 -166
  10. package/babel.config.js +3 -3
  11. package/changelog.md +39 -39
  12. package/dist/chartjs-plugin-trendline.cjs +884 -885
  13. package/dist/chartjs-plugin-trendline.esm.js +882 -883
  14. package/dist/chartjs-plugin-trendline.js +890 -891
  15. package/dist/chartjs-plugin-trendline.min.js +8 -8
  16. package/dist/chartjs-plugin-trendline.min.js.map +1 -1
  17. package/example/barChart.html +165 -165
  18. package/example/barChartWithNullValues.html +168 -168
  19. package/example/barChart_label.html +174 -174
  20. package/example/exponentialChart.html +244 -244
  21. package/example/lineChart.html +210 -210
  22. package/example/lineChartProjection.html +261 -261
  23. package/example/lineChartTypeTime.html +190 -190
  24. package/example/scatterChart.html +136 -136
  25. package/example/scatterProjection.html +141 -141
  26. package/example/test-null-handling.html +59 -59
  27. package/index.html +215 -215
  28. package/jest.config.js +4 -4
  29. package/package.json +45 -40
  30. package/rollup.config.js +54 -54
  31. package/src/components/label.js +56 -56
  32. package/src/components/label.test.js +129 -129
  33. package/src/components/trendline.js +375 -375
  34. package/src/components/trendline.test.js +789 -789
  35. package/src/core/plugin.js +78 -79
  36. package/src/core/plugin.test.js +307 -0
  37. package/src/index.js +12 -12
  38. package/src/utils/drawing.js +125 -125
  39. package/src/utils/drawing.test.js +308 -308
  40. package/src/utils/exponentialFitter.js +146 -146
  41. package/src/utils/exponentialFitter.test.js +362 -362
  42. package/src/utils/lineFitter.js +86 -86
  43. package/src/utils/lineFitter.test.js +340 -340
@@ -1,57 +1,57 @@
1
- /**
2
- * Adds a label to the trendline at the calculated angle.
3
- * @param {CanvasRenderingContext2D} ctx - The canvas rendering context.
4
- * @param {string} label - The label text to add.
5
- * @param {number} x1 - The starting x-coordinate of the trendline.
6
- * @param {number} y1 - The starting y-coordinate of the trendline.
7
- * @param {number} x2 - The ending x-coordinate of the trendline.
8
- * @param {number} y2 - The ending y-coordinate of the trendline.
9
- * @param {number} angle - The angle (in radians) of the trendline.
10
- * @param {string} labelColor - The color of the label text.
11
- * @param {string} family - The font family for the label text.
12
- * @param {number} size - The font size for the label text.
13
- * @param {number} offset - The offset of the label from the trendline
14
- */
15
- export const addTrendlineLabel = (
16
- ctx,
17
- label,
18
- x1,
19
- y1,
20
- x2,
21
- y2,
22
- angle,
23
- labelColor,
24
- family,
25
- size,
26
- offset
27
- ) => {
28
- // Set the label font and color
29
- ctx.font = `${size}px ${family}`;
30
- ctx.fillStyle = labelColor;
31
-
32
- // Label width
33
- const labelWidth = ctx.measureText(label).width;
34
-
35
- // Calculate the center of the trendline
36
- const labelX = (x1 + x2) / 2;
37
- const labelY = (y1 + y2) / 2;
38
-
39
- // Save the current state of the canvas
40
- ctx.save();
41
-
42
- // Translate to the label position
43
- ctx.translate(labelX, labelY);
44
-
45
- // Rotate the context to align with the trendline
46
- ctx.rotate(angle);
47
-
48
- // Adjust for the length of the label and rotation
49
- const adjustedX = -labelWidth / 2; // Center the label horizontally
50
- const adjustedY = offset; // Adjust Y to compensate for the height
51
-
52
- // Draw the label
53
- ctx.fillText(label, adjustedX, adjustedY);
54
-
55
- // Restore the canvas state
56
- ctx.restore();
1
+ /**
2
+ * Adds a label to the trendline at the calculated angle.
3
+ * @param {CanvasRenderingContext2D} ctx - The canvas rendering context.
4
+ * @param {string} label - The label text to add.
5
+ * @param {number} x1 - The starting x-coordinate of the trendline.
6
+ * @param {number} y1 - The starting y-coordinate of the trendline.
7
+ * @param {number} x2 - The ending x-coordinate of the trendline.
8
+ * @param {number} y2 - The ending y-coordinate of the trendline.
9
+ * @param {number} angle - The angle (in radians) of the trendline.
10
+ * @param {string} labelColor - The color of the label text.
11
+ * @param {string} family - The font family for the label text.
12
+ * @param {number} size - The font size for the label text.
13
+ * @param {number} offset - The offset of the label from the trendline
14
+ */
15
+ export const addTrendlineLabel = (
16
+ ctx,
17
+ label,
18
+ x1,
19
+ y1,
20
+ x2,
21
+ y2,
22
+ angle,
23
+ labelColor,
24
+ family,
25
+ size,
26
+ offset
27
+ ) => {
28
+ // Set the label font and color
29
+ ctx.font = `${size}px ${family}`;
30
+ ctx.fillStyle = labelColor;
31
+
32
+ // Label width
33
+ const labelWidth = ctx.measureText(label).width;
34
+
35
+ // Calculate the center of the trendline
36
+ const labelX = (x1 + x2) / 2;
37
+ const labelY = (y1 + y2) / 2;
38
+
39
+ // Save the current state of the canvas
40
+ ctx.save();
41
+
42
+ // Translate to the label position
43
+ ctx.translate(labelX, labelY);
44
+
45
+ // Rotate the context to align with the trendline
46
+ ctx.rotate(angle);
47
+
48
+ // Adjust for the length of the label and rotation
49
+ const adjustedX = -labelWidth / 2; // Center the label horizontally
50
+ const adjustedY = offset; // Adjust Y to compensate for the height
51
+
52
+ // Draw the label
53
+ ctx.fillText(label, adjustedX, adjustedY);
54
+
55
+ // Restore the canvas state
56
+ ctx.restore();
57
57
  };
@@ -1,129 +1,129 @@
1
- import { addTrendlineLabel } from './label.js';
2
- import 'jest-canvas-mock'; // Ensures canvas context is mocked
3
-
4
- describe('addTrendlineLabel', () => {
5
- let mockCtx;
6
-
7
- beforeEach(() => {
8
- // Reset the mock before each test
9
- mockCtx = {
10
- save: jest.fn(),
11
- translate: jest.fn(),
12
- rotate: jest.fn(),
13
- fillText: jest.fn(),
14
- measureText: jest.fn(() => ({ width: 100 })), // Mock measureText to return a fixed width
15
- font: '',
16
- fillStyle: '',
17
- restore: jest.fn(),
18
- };
19
- });
20
-
21
- // Define mock data directly as it's used in the function signature
22
- const mockLabel = 'Test Label';
23
- const mockX1 = 10;
24
- const mockY1 = 20;
25
- const mockX2 = 110;
26
- const mockY2 = 120;
27
- const mockAngle = Math.PI / 4; // 45 degrees
28
- const mockLabelColor = 'red';
29
- const mockFamily = 'Arial';
30
- const mockSize = 12;
31
- const mockOffset = 5;
32
-
33
-
34
- test('should correctly set label text and style', () => {
35
- addTrendlineLabel(
36
- mockCtx,
37
- mockLabel,
38
- mockX1, mockY1, mockX2, mockY2,
39
- mockAngle,
40
- mockLabelColor,
41
- mockFamily,
42
- mockSize,
43
- mockOffset
44
- );
45
-
46
- expect(mockCtx.fillText).toHaveBeenCalledWith(mockLabel, expect.any(Number), expect.any(Number));
47
- expect(mockCtx.measureText).toHaveBeenCalledWith(mockLabel);
48
- expect(mockCtx.font).toBe(`${mockSize}px ${mockFamily}`);
49
- expect(mockCtx.fillStyle).toBe(mockLabelColor);
50
- });
51
-
52
- test('should correctly apply transformations and positioning', () => {
53
- addTrendlineLabel(
54
- mockCtx,
55
- mockLabel,
56
- mockX1, mockY1, mockX2, mockY2,
57
- mockAngle,
58
- mockLabelColor,
59
- mockFamily,
60
- mockSize,
61
- mockOffset
62
- );
63
-
64
- const midX = (mockX1 + mockX2) / 2;
65
- const midY = (mockY1 + mockY2) / 2;
66
- expect(mockCtx.translate).toHaveBeenCalledWith(midX, midY);
67
- expect(mockCtx.rotate).toHaveBeenCalledWith(mockAngle);
68
-
69
- const labelWidth = mockCtx.measureText(mockLabel).width; // From mockCtx setup
70
- const adjustedX = -labelWidth / 2;
71
- // The original implementation uses offset directly, not negated
72
- const adjustedY = mockOffset;
73
- expect(mockCtx.fillText).toHaveBeenCalledWith(mockLabel, adjustedX, adjustedY);
74
- });
75
-
76
- test('should save and restore canvas state', () => {
77
- addTrendlineLabel(
78
- mockCtx,
79
- mockLabel,
80
- mockX1, mockY1, mockX2, mockY2,
81
- mockAngle,
82
- mockLabelColor,
83
- mockFamily,
84
- mockSize,
85
- mockOffset
86
- );
87
-
88
- expect(mockCtx.save).toHaveBeenCalledTimes(1);
89
- expect(mockCtx.restore).toHaveBeenCalledTimes(1);
90
- // We can infer call order by checking if save was called and restore was called.
91
- // For more specific ordering, a more complex mock or spy setup would be needed,
92
- // or checking the order of calls on the mock object if the testing library supports it.
93
- // For now, checking they were called is sufficient given the function's structure.
94
- });
95
-
96
- test('should handle zero offset correctly', () => {
97
- const currentOffset = 0;
98
- addTrendlineLabel(
99
- mockCtx,
100
- mockLabel,
101
- mockX1, mockY1, mockX2, mockY2,
102
- mockAngle,
103
- mockLabelColor,
104
- mockFamily,
105
- mockSize,
106
- currentOffset // Using zero offset
107
- );
108
-
109
- const labelWidth = mockCtx.measureText(mockLabel).width;
110
- const adjustedX = -labelWidth / 2;
111
- const adjustedY = currentOffset; // y is the offset itself
112
- expect(mockCtx.fillText).toHaveBeenCalledWith(mockLabel, adjustedX, adjustedY);
113
- });
114
-
115
- test('should handle different angle correctly', () => {
116
- const currentAngle = Math.PI / 2; // 90 degrees
117
- addTrendlineLabel(
118
- mockCtx,
119
- mockLabel,
120
- mockX1, mockY1, mockX2, mockY2,
121
- currentAngle, // Using different angle
122
- mockLabelColor,
123
- mockFamily,
124
- mockSize,
125
- mockOffset
126
- );
127
- expect(mockCtx.rotate).toHaveBeenCalledWith(currentAngle);
128
- });
129
- });
1
+ import { addTrendlineLabel } from './label.js';
2
+ import 'jest-canvas-mock'; // Ensures canvas context is mocked
3
+
4
+ describe('addTrendlineLabel', () => {
5
+ let mockCtx;
6
+
7
+ beforeEach(() => {
8
+ // Reset the mock before each test
9
+ mockCtx = {
10
+ save: jest.fn(),
11
+ translate: jest.fn(),
12
+ rotate: jest.fn(),
13
+ fillText: jest.fn(),
14
+ measureText: jest.fn(() => ({ width: 100 })), // Mock measureText to return a fixed width
15
+ font: '',
16
+ fillStyle: '',
17
+ restore: jest.fn(),
18
+ };
19
+ });
20
+
21
+ // Define mock data directly as it's used in the function signature
22
+ const mockLabel = 'Test Label';
23
+ const mockX1 = 10;
24
+ const mockY1 = 20;
25
+ const mockX2 = 110;
26
+ const mockY2 = 120;
27
+ const mockAngle = Math.PI / 4; // 45 degrees
28
+ const mockLabelColor = 'red';
29
+ const mockFamily = 'Arial';
30
+ const mockSize = 12;
31
+ const mockOffset = 5;
32
+
33
+
34
+ test('should correctly set label text and style', () => {
35
+ addTrendlineLabel(
36
+ mockCtx,
37
+ mockLabel,
38
+ mockX1, mockY1, mockX2, mockY2,
39
+ mockAngle,
40
+ mockLabelColor,
41
+ mockFamily,
42
+ mockSize,
43
+ mockOffset
44
+ );
45
+
46
+ expect(mockCtx.fillText).toHaveBeenCalledWith(mockLabel, expect.any(Number), expect.any(Number));
47
+ expect(mockCtx.measureText).toHaveBeenCalledWith(mockLabel);
48
+ expect(mockCtx.font).toBe(`${mockSize}px ${mockFamily}`);
49
+ expect(mockCtx.fillStyle).toBe(mockLabelColor);
50
+ });
51
+
52
+ test('should correctly apply transformations and positioning', () => {
53
+ addTrendlineLabel(
54
+ mockCtx,
55
+ mockLabel,
56
+ mockX1, mockY1, mockX2, mockY2,
57
+ mockAngle,
58
+ mockLabelColor,
59
+ mockFamily,
60
+ mockSize,
61
+ mockOffset
62
+ );
63
+
64
+ const midX = (mockX1 + mockX2) / 2;
65
+ const midY = (mockY1 + mockY2) / 2;
66
+ expect(mockCtx.translate).toHaveBeenCalledWith(midX, midY);
67
+ expect(mockCtx.rotate).toHaveBeenCalledWith(mockAngle);
68
+
69
+ const labelWidth = mockCtx.measureText(mockLabel).width; // From mockCtx setup
70
+ const adjustedX = -labelWidth / 2;
71
+ // The original implementation uses offset directly, not negated
72
+ const adjustedY = mockOffset;
73
+ expect(mockCtx.fillText).toHaveBeenCalledWith(mockLabel, adjustedX, adjustedY);
74
+ });
75
+
76
+ test('should save and restore canvas state', () => {
77
+ addTrendlineLabel(
78
+ mockCtx,
79
+ mockLabel,
80
+ mockX1, mockY1, mockX2, mockY2,
81
+ mockAngle,
82
+ mockLabelColor,
83
+ mockFamily,
84
+ mockSize,
85
+ mockOffset
86
+ );
87
+
88
+ expect(mockCtx.save).toHaveBeenCalledTimes(1);
89
+ expect(mockCtx.restore).toHaveBeenCalledTimes(1);
90
+ // We can infer call order by checking if save was called and restore was called.
91
+ // For more specific ordering, a more complex mock or spy setup would be needed,
92
+ // or checking the order of calls on the mock object if the testing library supports it.
93
+ // For now, checking they were called is sufficient given the function's structure.
94
+ });
95
+
96
+ test('should handle zero offset correctly', () => {
97
+ const currentOffset = 0;
98
+ addTrendlineLabel(
99
+ mockCtx,
100
+ mockLabel,
101
+ mockX1, mockY1, mockX2, mockY2,
102
+ mockAngle,
103
+ mockLabelColor,
104
+ mockFamily,
105
+ mockSize,
106
+ currentOffset // Using zero offset
107
+ );
108
+
109
+ const labelWidth = mockCtx.measureText(mockLabel).width;
110
+ const adjustedX = -labelWidth / 2;
111
+ const adjustedY = currentOffset; // y is the offset itself
112
+ expect(mockCtx.fillText).toHaveBeenCalledWith(mockLabel, adjustedX, adjustedY);
113
+ });
114
+
115
+ test('should handle different angle correctly', () => {
116
+ const currentAngle = Math.PI / 2; // 90 degrees
117
+ addTrendlineLabel(
118
+ mockCtx,
119
+ mockLabel,
120
+ mockX1, mockY1, mockX2, mockY2,
121
+ currentAngle, // Using different angle
122
+ mockLabelColor,
123
+ mockFamily,
124
+ mockSize,
125
+ mockOffset
126
+ );
127
+ expect(mockCtx.rotate).toHaveBeenCalledWith(currentAngle);
128
+ });
129
+ });