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,147 +1,147 @@
1
- /**
2
- * A class that fits an exponential curve to a series of points using least squares.
3
- * Fits y = a * e^(b*x) by transforming to ln(y) = ln(a) + b*x
4
- */
5
- export class ExponentialFitter {
6
- constructor() {
7
- this.count = 0;
8
- this.sumx = 0;
9
- this.sumlny = 0;
10
- this.sumx2 = 0;
11
- this.sumxlny = 0;
12
- this.minx = Number.MAX_VALUE;
13
- this.maxx = Number.MIN_VALUE;
14
- this.hasValidData = true;
15
- this.dataPoints = []; // Store data points for correlation calculation
16
- this._cachedGrowthRate = null;
17
- this._cachedCoefficient = null;
18
- this._cachedCorrelation = null;
19
- this._cacheValid = false;
20
- }
21
-
22
- /**
23
- * Adds a point to the exponential fitter.
24
- * @param {number} x - The x-coordinate of the point.
25
- * @param {number} y - The y-coordinate of the point.
26
- */
27
- add(x, y) {
28
- if (y <= 0) {
29
- this.hasValidData = false;
30
- return;
31
- }
32
-
33
- const lny = Math.log(y);
34
- if (!isFinite(lny)) {
35
- this.hasValidData = false;
36
- return;
37
- }
38
-
39
- this.sumx += x;
40
- this.sumlny += lny;
41
- this.sumx2 += x * x;
42
- this.sumxlny += x * lny;
43
- if (x < this.minx) this.minx = x;
44
- if (x > this.maxx) this.maxx = x;
45
- this.dataPoints.push({x, y, lny}); // Store actual data points
46
- this.count++;
47
- this._cacheValid = false;
48
- }
49
-
50
- /**
51
- * Calculates the exponential growth rate (b in y = a * e^(b*x)).
52
- * @returns {number} - The exponential growth rate.
53
- */
54
- growthRate() {
55
- if (!this.hasValidData || this.count < 2) return 0;
56
- if (!this._cacheValid) {
57
- this._computeCoefficients();
58
- }
59
- return this._cachedGrowthRate;
60
- }
61
-
62
- /**
63
- * Calculates the exponential coefficient (a in y = a * e^(b*x)).
64
- * @returns {number} - The exponential coefficient.
65
- */
66
- coefficient() {
67
- if (!this.hasValidData || this.count < 2) return 1;
68
- if (!this._cacheValid) {
69
- this._computeCoefficients();
70
- }
71
- return this._cachedCoefficient;
72
- }
73
-
74
- /**
75
- * Returns the fitted exponential value (y) for a given x.
76
- * @param {number} x - The x-coordinate.
77
- * @returns {number} - The corresponding y-coordinate on the fitted exponential curve.
78
- */
79
- f(x) {
80
- if (!this.hasValidData || this.count < 2) return 0;
81
- if (!this._cacheValid) {
82
- this._computeCoefficients();
83
- }
84
-
85
- // Check for potential overflow before calculation
86
- if (Math.abs(this._cachedGrowthRate * x) > 500) return 0; // Safer limit to prevent overflow
87
-
88
- const result = this._cachedCoefficient * Math.exp(this._cachedGrowthRate * x);
89
- return isFinite(result) ? result : 0;
90
- }
91
-
92
- /**
93
- * Calculates the correlation coefficient (R-squared) for the exponential fit.
94
- * @returns {number} - The correlation coefficient (0-1).
95
- */
96
- correlation() {
97
- if (!this.hasValidData || this.count < 2) return 0;
98
- if (!this._cacheValid) {
99
- this._computeCoefficients();
100
- }
101
- return this._cachedCorrelation;
102
- }
103
-
104
- /**
105
- * Returns the scale (growth rate) of the fitted exponential curve.
106
- * @returns {number} - The growth rate of the exponential curve.
107
- */
108
- scale() {
109
- return this.growthRate();
110
- }
111
-
112
- _computeCoefficients() {
113
- if (!this.hasValidData || this.count < 2) {
114
- this._cachedGrowthRate = 0;
115
- this._cachedCoefficient = 1;
116
- this._cachedCorrelation = 0;
117
- this._cacheValid = true;
118
- return;
119
- }
120
-
121
- const denominator = this.count * this.sumx2 - this.sumx * this.sumx;
122
- if (Math.abs(denominator) < 1e-10) {
123
- this._cachedGrowthRate = 0;
124
- this._cachedCoefficient = 1;
125
- this._cachedCorrelation = 0;
126
- this._cacheValid = true;
127
- return;
128
- }
129
-
130
- this._cachedGrowthRate = (this.count * this.sumxlny - this.sumx * this.sumlny) / denominator;
131
- const lnA = (this.sumlny - this._cachedGrowthRate * this.sumx) / this.count;
132
- this._cachedCoefficient = Math.exp(lnA);
133
-
134
- const meanLnY = this.sumlny / this.count;
135
- let ssTotal = 0;
136
- let ssRes = 0;
137
-
138
- for (const point of this.dataPoints) {
139
- const predictedLnY = lnA + this._cachedGrowthRate * point.x;
140
- ssTotal += Math.pow(point.lny - meanLnY, 2);
141
- ssRes += Math.pow(point.lny - predictedLnY, 2);
142
- }
143
-
144
- this._cachedCorrelation = ssTotal === 0 ? 1 : Math.max(0, 1 - (ssRes / ssTotal));
145
- this._cacheValid = true;
146
- }
1
+ /**
2
+ * A class that fits an exponential curve to a series of points using least squares.
3
+ * Fits y = a * e^(b*x) by transforming to ln(y) = ln(a) + b*x
4
+ */
5
+ export class ExponentialFitter {
6
+ constructor() {
7
+ this.count = 0;
8
+ this.sumx = 0;
9
+ this.sumlny = 0;
10
+ this.sumx2 = 0;
11
+ this.sumxlny = 0;
12
+ this.minx = Number.MAX_VALUE;
13
+ this.maxx = Number.MIN_VALUE;
14
+ this.hasValidData = true;
15
+ this.dataPoints = []; // Store data points for correlation calculation
16
+ this._cachedGrowthRate = null;
17
+ this._cachedCoefficient = null;
18
+ this._cachedCorrelation = null;
19
+ this._cacheValid = false;
20
+ }
21
+
22
+ /**
23
+ * Adds a point to the exponential fitter.
24
+ * @param {number} x - The x-coordinate of the point.
25
+ * @param {number} y - The y-coordinate of the point.
26
+ */
27
+ add(x, y) {
28
+ if (y <= 0) {
29
+ this.hasValidData = false;
30
+ return;
31
+ }
32
+
33
+ const lny = Math.log(y);
34
+ if (!isFinite(lny)) {
35
+ this.hasValidData = false;
36
+ return;
37
+ }
38
+
39
+ this.sumx += x;
40
+ this.sumlny += lny;
41
+ this.sumx2 += x * x;
42
+ this.sumxlny += x * lny;
43
+ if (x < this.minx) this.minx = x;
44
+ if (x > this.maxx) this.maxx = x;
45
+ this.dataPoints.push({x, y, lny}); // Store actual data points
46
+ this.count++;
47
+ this._cacheValid = false;
48
+ }
49
+
50
+ /**
51
+ * Calculates the exponential growth rate (b in y = a * e^(b*x)).
52
+ * @returns {number} - The exponential growth rate.
53
+ */
54
+ growthRate() {
55
+ if (!this.hasValidData || this.count < 2) return 0;
56
+ if (!this._cacheValid) {
57
+ this._computeCoefficients();
58
+ }
59
+ return this._cachedGrowthRate;
60
+ }
61
+
62
+ /**
63
+ * Calculates the exponential coefficient (a in y = a * e^(b*x)).
64
+ * @returns {number} - The exponential coefficient.
65
+ */
66
+ coefficient() {
67
+ if (!this.hasValidData || this.count < 2) return 1;
68
+ if (!this._cacheValid) {
69
+ this._computeCoefficients();
70
+ }
71
+ return this._cachedCoefficient;
72
+ }
73
+
74
+ /**
75
+ * Returns the fitted exponential value (y) for a given x.
76
+ * @param {number} x - The x-coordinate.
77
+ * @returns {number} - The corresponding y-coordinate on the fitted exponential curve.
78
+ */
79
+ f(x) {
80
+ if (!this.hasValidData || this.count < 2) return 0;
81
+ if (!this._cacheValid) {
82
+ this._computeCoefficients();
83
+ }
84
+
85
+ // Check for potential overflow before calculation
86
+ if (Math.abs(this._cachedGrowthRate * x) > 500) return 0; // Safer limit to prevent overflow
87
+
88
+ const result = this._cachedCoefficient * Math.exp(this._cachedGrowthRate * x);
89
+ return isFinite(result) ? result : 0;
90
+ }
91
+
92
+ /**
93
+ * Calculates the correlation coefficient (R-squared) for the exponential fit.
94
+ * @returns {number} - The correlation coefficient (0-1).
95
+ */
96
+ correlation() {
97
+ if (!this.hasValidData || this.count < 2) return 0;
98
+ if (!this._cacheValid) {
99
+ this._computeCoefficients();
100
+ }
101
+ return this._cachedCorrelation;
102
+ }
103
+
104
+ /**
105
+ * Returns the scale (growth rate) of the fitted exponential curve.
106
+ * @returns {number} - The growth rate of the exponential curve.
107
+ */
108
+ scale() {
109
+ return this.growthRate();
110
+ }
111
+
112
+ _computeCoefficients() {
113
+ if (!this.hasValidData || this.count < 2) {
114
+ this._cachedGrowthRate = 0;
115
+ this._cachedCoefficient = 1;
116
+ this._cachedCorrelation = 0;
117
+ this._cacheValid = true;
118
+ return;
119
+ }
120
+
121
+ const denominator = this.count * this.sumx2 - this.sumx * this.sumx;
122
+ if (Math.abs(denominator) < 1e-10) {
123
+ this._cachedGrowthRate = 0;
124
+ this._cachedCoefficient = 1;
125
+ this._cachedCorrelation = 0;
126
+ this._cacheValid = true;
127
+ return;
128
+ }
129
+
130
+ this._cachedGrowthRate = (this.count * this.sumxlny - this.sumx * this.sumlny) / denominator;
131
+ const lnA = (this.sumlny - this._cachedGrowthRate * this.sumx) / this.count;
132
+ this._cachedCoefficient = Math.exp(lnA);
133
+
134
+ const meanLnY = this.sumlny / this.count;
135
+ let ssTotal = 0;
136
+ let ssRes = 0;
137
+
138
+ for (const point of this.dataPoints) {
139
+ const predictedLnY = lnA + this._cachedGrowthRate * point.x;
140
+ ssTotal += Math.pow(point.lny - meanLnY, 2);
141
+ ssRes += Math.pow(point.lny - predictedLnY, 2);
142
+ }
143
+
144
+ this._cachedCorrelation = ssTotal === 0 ? 1 : Math.max(0, 1 - (ssRes / ssTotal));
145
+ this._cacheValid = true;
146
+ }
147
147
  }