chartjs-plugin-trendline 2.0.4 → 2.1.0
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.
- package/README.md +4 -2
- package/dist/chartjs-plugin-trendline.min.js +1 -1
- package/dist/chartjs-plugin-trendline.min.js.LICENSE.txt +2 -2
- package/example/barChart.html +1 -1
- package/example/barChartWithNullValues.html +1 -1
- package/example/lineChart.html +1 -1
- package/example/lineChartProjection.html +1 -1
- package/package.json +1 -1
- package/src/chartjs-plugin-trendline.js +87 -75
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# chartjs-plugin-trendline
|
|
2
2
|
|
|
3
3
|
This plugin draws an linear trendline in your Chart.
|
|
4
|
-
It has been tested with Chart.js version 4.
|
|
4
|
+
It has been tested with Chart.js version 4.4.0.
|
|
5
5
|
|
|
6
6
|
## Installation
|
|
7
7
|
|
|
@@ -10,7 +10,7 @@ It has been tested with Chart.js version 4.3.0.
|
|
|
10
10
|
Load Chart.js first, then the plugin which will automatically register itself with Chart.js
|
|
11
11
|
|
|
12
12
|
```html
|
|
13
|
-
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.
|
|
13
|
+
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.min.js"></script>
|
|
14
14
|
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-trendline"></script>
|
|
15
15
|
```
|
|
16
16
|
|
|
@@ -38,6 +38,8 @@ To configure the trendline plugin you simply add a new config options to your da
|
|
|
38
38
|
colorMax: "green",
|
|
39
39
|
lineStyle: "dotted|solid",
|
|
40
40
|
width: 2,
|
|
41
|
+
xAxisKey: "time" (optional),
|
|
42
|
+
yAxisKey: "usage" (optional),
|
|
41
43
|
projection: true|false (optional)
|
|
42
44
|
}
|
|
43
45
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/*! For license information please see chartjs-plugin-trendline.min.js.LICENSE.txt */
|
|
2
|
-
(()=>{var t={460:(t,
|
|
2
|
+
(()=>{var t={460:(t,e)=>{const s={id:"chartjs-plugin-trendline",afterDatasetsDraw:t=>{let e,s;for(let i in t.scales)if("x"==i[0]?s=t.scales[i]:e=t.scales[i],s&&e)break;const a=t.ctx;t.data.datasets.forEach(((e,o)=>{const r=e.alwaysShowTrendline||t.isDatasetVisible(o);if(e.trendlineLinear&&r&&e.data.length>1){const r=t.getDatasetMeta(o);i(r,a,e,s,t.scales[r.yAxisID])}})),a.setLineDash([])}},i=(t,e,s,i,o)=>{let r=s.borderColor||"rgba(169,169,169, .6)",n=s.trendlineLinear.colorMin||r,l=s.trendlineLinear.colorMax||r,h=s.trendlineLinear.width||s.borderWidth,d=s.trendlineLinear.lineStyle||"solid",u=s.trendlineLinear.fillColor;const m="object"==typeof t.controller.chart.options.parsing?t.controller.chart.options.parsing:void 0,x=s.trendlineLinear.xAxisKey||m?m.xAxisKey:"x",c=s.trendlineLinear.yAxisKey||m?m.yAxisKey:"y";h=void 0!==h?h:3;let f=new a,X=s.data.findIndex((t=>null!=t)),p=s.data.length-1,y=t.data[X][x],g=t.data[p][x],w="object"==typeof s.data[X];s.data.forEach(((t,e)=>{if(null!=t)if(["time","timeseries"].includes(i.options.type)){let s=null!=t[x]?t[x]:t.t;void 0!==s?f.add(new Date(s).getTime(),t[c]):f.add(e,t)}else w?isNaN(t.x)||isNaN(t.y)?isNaN(t.x)?isNaN(t.y)||f.add(e,t.y):f.add(e,t.x):f.add(t.x,t.y):f.add(e,t)}));let Y,L,P=i.getPixelForValue(f.minx),b=o.getPixelForValue(f.f(f.minx));if(s.trendlineLinear.projection&&f.scale()<0){let t=f.fo();t<f.minx&&(t=f.maxx),Y=i.getPixelForValue(t),L=o.getPixelForValue(f.f(t))}else Y=i.getPixelForValue(f.maxx),L=o.getPixelForValue(f.f(f.maxx));w||(P=y,Y=g);let C=t.controller.chart.chartArea.bottom,v=t.controller.chart.width;if(b>C){let t=b-C,e=b-L;b=C,P+=v*(t/e)}else if(L>C){let t=L-C,e=L-b;L=C,Y=v-(Y-(v-v*(t/e)))}e.lineWidth=h,"dotted"===d?e.setLineDash([2,3]):e.setLineDash([]),e.beginPath(),e.moveTo(P,b),e.lineTo(Y,L);let D=e.createLinearGradient(P,b,Y,L);L<b?(D.addColorStop(0,l),D.addColorStop(1,n)):(D.addColorStop(0,n),D.addColorStop(1,l)),e.strokeStyle=D,e.stroke(),e.closePath(),u&&(e.fillStyle=u,e.beginPath(),e.moveTo(P,b),e.lineTo(Y,L),e.lineTo(Y,C),e.lineTo(P,C),e.closePath(),e.fill())};class a{constructor(){this.count=0,this.sumX=0,this.sumX2=0,this.sumXY=0,this.sumY=0,this.minx=1e100,this.maxx=-1e100,this.maxy=-1e100}add(t,e){t=parseFloat(t),e=parseFloat(e),this.count++,this.sumX+=t,this.sumX2+=t*t,this.sumXY+=t*e,this.sumY+=e,t<this.minx&&(this.minx=t),t>this.maxx&&(this.maxx=t),e>this.maxy&&(this.maxy=e)}f(t){t=parseFloat(t);let e=this.count*this.sumX2-this.sumX*this.sumX;return(this.sumX2*this.sumY-this.sumX*this.sumXY)/e+t*((this.count*this.sumXY-this.sumX*this.sumY)/e)}fo(){let t=this.count*this.sumX2-this.sumX*this.sumX;return-(this.sumX2*this.sumY-this.sumX*this.sumXY)/t/((this.count*this.sumXY-this.sumX*this.sumY)/t)}scale(){let t=this.count*this.sumX2-this.sumX*this.sumX;return(this.count*this.sumXY-this.sumX*this.sumY)/t}}"undefined"!=typeof window&&window.Chart&&(window.Chart.hasOwnProperty("register")?window.Chart.register(s):window.Chart.plugins.register(s));try{t.exports=s}catch(t){}}},e={};!function s(i){var a=e[i];if(void 0!==a)return a.exports;var o=e[i]={exports:{}};return t[i](o,o.exports,s),o.exports}(460)})();
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/*!
|
|
2
2
|
* chartjs-plugin-trendline.js
|
|
3
|
-
* Version: 2.0
|
|
3
|
+
* Version: 2.1.0
|
|
4
4
|
*
|
|
5
|
-
* Copyright
|
|
5
|
+
* Copyright 2024 Marcus Alsterfjord
|
|
6
6
|
* Released under the MIT license
|
|
7
7
|
* https://github.com/Makanz/chartjs-plugin-trendline/blob/master/README.md
|
|
8
8
|
*
|
package/example/barChart.html
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
|
7
7
|
<title>BarChart Example</title>
|
|
8
|
-
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.
|
|
8
|
+
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.js"></script>
|
|
9
9
|
<script src="./../src/chartjs-plugin-trendline.js"></script>
|
|
10
10
|
<script>
|
|
11
11
|
document.addEventListener("DOMContentLoaded", function(event) {
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
|
7
7
|
<title>BarChart Example</title>
|
|
8
|
-
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.
|
|
8
|
+
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.js"></script>
|
|
9
9
|
<script src="./../src/chartjs-plugin-trendline.js"></script>
|
|
10
10
|
<script>
|
|
11
11
|
document.addEventListener("DOMContentLoaded", function(event) {
|
package/example/lineChart.html
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
|
7
7
|
<title>LineChart Example</title>
|
|
8
|
-
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.
|
|
8
|
+
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.js"></script>
|
|
9
9
|
<script src="./../src/chartjs-plugin-trendline.js"></script>
|
|
10
10
|
<script>
|
|
11
11
|
document.addEventListener("DOMContentLoaded", function(event) {
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
|
7
7
|
<title>XYlineChart Projection Example</title>
|
|
8
|
-
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.
|
|
8
|
+
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.js"></script>
|
|
9
9
|
<script src="./../src/chartjs-plugin-trendline.js"></script>
|
|
10
10
|
<script>
|
|
11
11
|
document.addEventListener("DOMContentLoaded", function(event) {
|
package/package.json
CHANGED
|
@@ -1,27 +1,27 @@
|
|
|
1
1
|
/*!
|
|
2
2
|
* chartjs-plugin-trendline.js
|
|
3
|
-
* Version: 2.0
|
|
3
|
+
* Version: 2.1.0
|
|
4
4
|
*
|
|
5
|
-
* Copyright
|
|
5
|
+
* Copyright 2024 Marcus Alsterfjord
|
|
6
6
|
* Released under the MIT license
|
|
7
7
|
* https://github.com/Makanz/chartjs-plugin-trendline/blob/master/README.md
|
|
8
8
|
*
|
|
9
9
|
* Mod by: vesal: accept also xy-data so works with scatter
|
|
10
10
|
*/
|
|
11
|
-
|
|
11
|
+
const pluginTrendlineLinear = {
|
|
12
12
|
id: 'chartjs-plugin-trendline',
|
|
13
|
-
afterDatasetsDraw:
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
for (
|
|
13
|
+
afterDatasetsDraw: (chartInstance) => {
|
|
14
|
+
let yScale;
|
|
15
|
+
let xScale;
|
|
16
|
+
for (let axis in chartInstance.scales) {
|
|
17
17
|
if (axis[0] == 'x') xScale = chartInstance.scales[axis];
|
|
18
18
|
else yScale = chartInstance.scales[axis];
|
|
19
19
|
if (xScale && yScale) break;
|
|
20
20
|
}
|
|
21
|
-
|
|
21
|
+
const ctx = chartInstance.ctx;
|
|
22
22
|
|
|
23
|
-
chartInstance.data.datasets.forEach(
|
|
24
|
-
|
|
23
|
+
chartInstance.data.datasets.forEach((dataset, index) => {
|
|
24
|
+
const showTrendline =
|
|
25
25
|
dataset.alwaysShowTrendline ||
|
|
26
26
|
chartInstance.isDatasetVisible(index);
|
|
27
27
|
|
|
@@ -30,7 +30,7 @@ var pluginTrendlineLinear = {
|
|
|
30
30
|
showTrendline &&
|
|
31
31
|
dataset.data.length > 1
|
|
32
32
|
) {
|
|
33
|
-
|
|
33
|
+
const datasetMeta = chartInstance.getDatasetMeta(index);
|
|
34
34
|
addFitter(
|
|
35
35
|
datasetMeta,
|
|
36
36
|
ctx,
|
|
@@ -45,32 +45,37 @@ var pluginTrendlineLinear = {
|
|
|
45
45
|
},
|
|
46
46
|
};
|
|
47
47
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
48
|
+
const addFitter = (datasetMeta, ctx, dataset, xScale, yScale) => {
|
|
49
|
+
let defaultColor = dataset.borderColor || 'rgba(169,169,169, .6)';
|
|
50
|
+
let colorMin = dataset.trendlineLinear.colorMin || defaultColor;
|
|
51
|
+
let colorMax = dataset.trendlineLinear.colorMax || defaultColor;
|
|
52
|
+
let lineWidth = dataset.trendlineLinear.width || dataset.borderWidth;
|
|
53
|
+
let lineStyle = dataset.trendlineLinear.lineStyle || 'solid';
|
|
54
|
+
let fillColor = dataset.trendlineLinear.fillColor;
|
|
55
|
+
|
|
56
|
+
const parsing = typeof datasetMeta.controller.chart.options.parsing === "object" ?
|
|
57
|
+
datasetMeta.controller.chart.options.parsing : undefined;
|
|
58
|
+
const xAxisKey = dataset.trendlineLinear.xAxisKey || parsing ? parsing.xAxisKey : "x";
|
|
59
|
+
const yAxisKey = dataset.trendlineLinear.yAxisKey || parsing ? parsing.yAxisKey : "y";
|
|
55
60
|
|
|
56
61
|
lineWidth = lineWidth !== undefined ? lineWidth : 3;
|
|
57
62
|
|
|
58
|
-
|
|
59
|
-
|
|
63
|
+
let fitter = new LineFitter();
|
|
64
|
+
let firstIndex = dataset.data.findIndex((d) => {
|
|
60
65
|
return d !== undefined && d !== null;
|
|
61
66
|
});
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
67
|
+
let lastIndex = dataset.data.length - 1;
|
|
68
|
+
let startPos = datasetMeta.data[firstIndex][xAxisKey];
|
|
69
|
+
let endPos = datasetMeta.data[lastIndex][xAxisKey];
|
|
70
|
+
let xy = typeof dataset.data[firstIndex] === 'object';
|
|
66
71
|
|
|
67
|
-
dataset.data.forEach(
|
|
72
|
+
dataset.data.forEach((data, index) => {
|
|
68
73
|
if (data == null) return;
|
|
69
74
|
|
|
70
75
|
if (['time', 'timeseries'].includes(xScale.options.type)) {
|
|
71
|
-
|
|
76
|
+
let x = data[xAxisKey] != null ? data[xAxisKey] : data.t;
|
|
72
77
|
if (x !== undefined) {
|
|
73
|
-
fitter.add(new Date(x).getTime(), data
|
|
78
|
+
fitter.add(new Date(x).getTime(), data[yAxisKey]);
|
|
74
79
|
} else {
|
|
75
80
|
fitter.add(index, data);
|
|
76
81
|
}
|
|
@@ -87,16 +92,16 @@ function addFitter(datasetMeta, ctx, dataset, xScale, yScale) {
|
|
|
87
92
|
}
|
|
88
93
|
});
|
|
89
94
|
|
|
90
|
-
|
|
91
|
-
|
|
95
|
+
let x1 = xScale.getPixelForValue(fitter.minx);
|
|
96
|
+
let y1 = yScale.getPixelForValue(fitter.f(fitter.minx));
|
|
92
97
|
|
|
93
|
-
|
|
94
|
-
|
|
98
|
+
let x2;
|
|
99
|
+
let y2;
|
|
95
100
|
|
|
96
101
|
// Project only on x axes, do not project if trendline will never hit x axes
|
|
97
102
|
if (dataset.trendlineLinear.projection && fitter.scale() < 0) {
|
|
98
103
|
// X
|
|
99
|
-
|
|
104
|
+
let x2value = fitter.fo();
|
|
100
105
|
if (x2value < fitter.minx) x2value = fitter.maxx;
|
|
101
106
|
x2 = xScale.getPixelForValue(x2value);
|
|
102
107
|
|
|
@@ -112,38 +117,42 @@ function addFitter(datasetMeta, ctx, dataset, xScale, yScale) {
|
|
|
112
117
|
x2 = endPos;
|
|
113
118
|
}
|
|
114
119
|
|
|
115
|
-
|
|
116
|
-
|
|
120
|
+
let drawBottom = datasetMeta.controller.chart.chartArea.bottom;
|
|
121
|
+
let chartWidth = datasetMeta.controller.chart.width;
|
|
117
122
|
|
|
118
123
|
if (y1 > drawBottom) {
|
|
119
124
|
// Left side is below zero
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
125
|
+
let diff = y1 - drawBottom;
|
|
126
|
+
let lineHeight = y1 - y2;
|
|
127
|
+
let overlapPercentage = diff / lineHeight;
|
|
128
|
+
let addition = chartWidth * overlapPercentage;
|
|
124
129
|
|
|
125
130
|
y1 = drawBottom;
|
|
126
131
|
x1 = x1 + addition;
|
|
127
132
|
} else if (y2 > drawBottom) {
|
|
128
133
|
// right side is below zero
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
134
|
+
let diff = y2 - drawBottom;
|
|
135
|
+
let lineHeight = y2 - y1;
|
|
136
|
+
let overlapPercentage = diff / lineHeight;
|
|
137
|
+
let subtraction = chartWidth - chartWidth * overlapPercentage;
|
|
133
138
|
|
|
134
139
|
y2 = drawBottom;
|
|
135
140
|
x2 = chartWidth - (x2 - subtraction);
|
|
136
141
|
}
|
|
137
142
|
|
|
138
143
|
ctx.lineWidth = lineWidth;
|
|
144
|
+
|
|
139
145
|
if (lineStyle === 'dotted') {
|
|
140
|
-
ctx.setLineDash([2, 3]);
|
|
146
|
+
ctx.setLineDash([2, 3]); // Dotted
|
|
147
|
+
} else {
|
|
148
|
+
ctx.setLineDash([]); // Solid
|
|
141
149
|
}
|
|
150
|
+
|
|
142
151
|
ctx.beginPath();
|
|
143
152
|
ctx.moveTo(x1, y1);
|
|
144
153
|
ctx.lineTo(x2, y2);
|
|
145
154
|
|
|
146
|
-
|
|
155
|
+
let gradient = ctx.createLinearGradient(x1, y1, x2, y2);
|
|
147
156
|
if (y2 < y1) {
|
|
148
157
|
gradient.addColorStop(0, colorMax);
|
|
149
158
|
gradient.addColorStop(1, colorMin);
|
|
@@ -167,21 +176,21 @@ function addFitter(datasetMeta, ctx, dataset, xScale, yScale) {
|
|
|
167
176
|
ctx.closePath();
|
|
168
177
|
ctx.fill();
|
|
169
178
|
}
|
|
170
|
-
}
|
|
179
|
+
};
|
|
171
180
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
181
|
+
class LineFitter {
|
|
182
|
+
constructor() {
|
|
183
|
+
this.count = 0;
|
|
184
|
+
this.sumX = 0;
|
|
185
|
+
this.sumX2 = 0;
|
|
186
|
+
this.sumXY = 0;
|
|
187
|
+
this.sumY = 0;
|
|
188
|
+
this.minx = 1e100;
|
|
189
|
+
this.maxx = -1e100;
|
|
190
|
+
this.maxy = -1e100;
|
|
191
|
+
}
|
|
182
192
|
|
|
183
|
-
|
|
184
|
-
add: function (x, y) {
|
|
193
|
+
add(x, y) {
|
|
185
194
|
x = parseFloat(x);
|
|
186
195
|
y = parseFloat(y);
|
|
187
196
|
|
|
@@ -193,31 +202,34 @@ LineFitter.prototype = {
|
|
|
193
202
|
if (x < this.minx) this.minx = x;
|
|
194
203
|
if (x > this.maxx) this.maxx = x;
|
|
195
204
|
if (y > this.maxy) this.maxy = y;
|
|
196
|
-
}
|
|
197
|
-
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
f(x) {
|
|
198
208
|
x = parseFloat(x);
|
|
199
209
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
210
|
+
let det = this.count * this.sumX2 - this.sumX * this.sumX;
|
|
211
|
+
let offset = (this.sumX2 * this.sumY - this.sumX * this.sumXY) / det;
|
|
212
|
+
let scale = (this.count * this.sumXY - this.sumX * this.sumY) / det;
|
|
203
213
|
return offset + x * scale;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
fo() {
|
|
217
|
+
let det = this.count * this.sumX2 - this.sumX * this.sumX;
|
|
218
|
+
let offset = (this.sumX2 * this.sumY - this.sumX * this.sumXY) / det;
|
|
219
|
+
let scale = (this.count * this.sumXY - this.sumX * this.sumY) / det;
|
|
209
220
|
|
|
210
|
-
//
|
|
211
|
-
|
|
221
|
+
// Get x when y = 0
|
|
222
|
+
let xo = -offset / scale;
|
|
212
223
|
return xo;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
scale() {
|
|
227
|
+
let det = this.count * this.sumX2 - this.sumX * this.sumX;
|
|
228
|
+
let scale = (this.count * this.sumXY - this.sumX * this.sumY) / det;
|
|
217
229
|
|
|
218
230
|
return scale;
|
|
219
|
-
}
|
|
220
|
-
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
221
233
|
|
|
222
234
|
// If we're in the browser and have access to the global Chart obj, register plugin automatically
|
|
223
235
|
if (typeof window !== 'undefined' && window.Chart) {
|