chartjs-plugin-trendline 2.1.6 → 2.1.8
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/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -34,7 +34,7 @@ To configure the trendline plugin you simply add a new config options to your da
|
|
|
34
34
|
```javascript
|
|
35
35
|
{
|
|
36
36
|
trendlineLinear: {
|
|
37
|
-
colorMin: Color
|
|
37
|
+
colorMin: Color,
|
|
38
38
|
colorMax: Color,
|
|
39
39
|
lineStyle: string, // "dotted" | "solid" | "dashed" | "dashdot"
|
|
40
40
|
width: number,
|
|
@@ -81,3 +81,7 @@ For bugs and feature requests, [please create an issue](https://github.com/Makan
|
|
|
81
81
|
## License
|
|
82
82
|
|
|
83
83
|
chartjs-plugin-trendline.js is available under the [MIT license](http://opensource.org/licenses/MIT).
|
|
84
|
+
|
|
85
|
+
## Star History
|
|
86
|
+
|
|
87
|
+
[](https://star-history.com/#Makanz/chartjs-plugin-trendline&Date)
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
/*! For license information please see chartjs-plugin-trendline.min.js.LICENSE.txt */
|
|
2
|
-
(()=>{var e={
|
|
1
|
+
/*! For license information please see chartjs-plugin-trendline.min.js.LICENSE.txt */
|
|
2
|
+
(()=>{var e={339:(e,t)=>{const i={id:"chartjs-plugin-trendline",afterDatasetsDraw:e=>{const t=e.ctx,{xScale:i,yScale:a}=n(e);e.data.datasets.forEach(((n,l)=>{const o=n.alwaysShowTrendline||e.isDatasetVisible(l);if(n.trendlineLinear&&o&&n.data.length>1){const o=e.getDatasetMeta(l);s(o,t,n,i,a)}})),t.setLineDash([])},beforeInit:e=>{e.data.datasets.forEach((t=>{if(t.trendlineLinear&&t.trendlineLinear.label){const i=t.trendlineLinear.label,n=e.legend.options.labels.generateLabels;e.legend.options.labels.generateLabels=function(e){const s=n(e),a=t.trendlineLinear.legend;return a&&!1!==a.display&&s.push({text:a.text||i+" (Trendline)",strokeStyle:a.color||t.borderColor||"rgba(169,169,169, .6)",fillStyle:a.fillStyle||"transparent",lineCap:a.lineCap||"butt",lineDash:a.lineDash||[],lineWidth:a.width||1}),s}}}))}},n=e=>{let t,i;for(const n of Object.values(e.scales))if(n.isHorizontal()?t=n:i=n,t&&i)break;return{xScale:t,yScale:i}},s=(e,t,i,n,s)=>{const x=i.yAxisID||"y",h=e.controller.chart.scales[x]||s,u=i.borderColor||"rgba(169,169,169, .6)",{colorMin:y=u,colorMax:f=u,width:m=i.borderWidth||3,lineStyle:p="solid",fillColor:b=!1}=i.trendlineLinear||{},{color:w=u,text:g="Trendline",display:F=!0,displayValue:L=!0,offset:S=10,percentage:v=!1}=i.trendlineLinear.label||{},{family:C="'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",size:D=12}=i.trendlineLinear.label?.font||{},T=e.controller.chart.options,N="object"==typeof T.parsing?T.parsing:void 0,P=i.trendlineLinear?.xAxisKey||N?.xAxisKey||"x",A=i.trendlineLinear?.yAxisKey||N?.yAxisKey||"y";let M=new c,V=i.data.findIndex((e=>null!=e)),k=i.data.length-1,j=e.data[V]?.[P],E=e.data[k]?.[P],W="object"==typeof i.data[V];i.data.forEach(((e,t)=>{if(null!=e)if(["time","timeseries"].includes(n.options.type)){let i=null!=e[P]?e[P]:e.t;void 0!==i?M.add(new Date(i).getTime(),e[A]):M.add(t,e)}else W?isNaN(e.x)||isNaN(e.y)?isNaN(e.x)?isNaN(e.y)||M.add(t,e.y):M.add(t,e.x):M.add(e.x,e.y):M.add(t,e)}));let I,K,$=isFinite(j)?j:n.getPixelForValue(M.minx),B=h.getPixelForValue(M.f(M.minx));if(i.trendlineLinear.projection&&M.scale()<0){let e=M.fo();e<M.minx&&(e=M.maxx),I=n.getPixelForValue(e),K=h.getPixelForValue(M.f(e))}else I=isFinite(E)?E:n.getPixelForValue(M.maxx),K=h.getPixelForValue(M.f(M.maxx));const H=e.controller.chart.chartArea.bottom,z=e.controller.chart.width;if(isFinite($)&&isFinite(B)&&isFinite(I)&&isFinite(K)){l({x1:$,y1:B,x2:I,y2:K,drawBottom:H,chartWidth:z}),t.lineWidth=m,o(t,p),r({ctx:t,x1:$,y1:B,x2:I,y2:K,colorMin:y,colorMax:f}),b&&d(t,$,B,I,K,H,b);const e=Math.atan2(K-B,I-$),n=(B-K)/(I-$);if(i.trendlineLinear.label&&!1!==F){const i=L?`${g} (Slope: ${v?(100*n).toFixed(2)+"%":n.toFixed(2)})`:g;a(t,i,$,B,I,K,e,w,C,D,S)}}},a=(e,t,i,n,s,a,l,o,r,d,c)=>{e.font=`${d}px ${r}`,e.fillStyle=o;const x=e.measureText(t).width,h=(i+s)/2,u=(n+a)/2;e.save(),e.translate(h,u),e.rotate(l);const y=-x/2,f=c;e.fillText(t,y,f),e.restore()},l=({x1:e,y1:t,x2:i,y2:n,drawBottom:s,chartWidth:a})=>{if(t>s){t=s}else if(n>s){let e=n-s,l=n-t;n=s,i=a-(i-(a-a*(e/l)))}},o=(e,t)=>{switch(t){case"dotted":e.setLineDash([2,2]);break;case"dashed":e.setLineDash([8,3]);break;case"dashdot":e.setLineDash([8,3,2,3]);break;default:e.setLineDash([])}},r=({ctx:e,x1:t,y1:i,x2:n,y2:s,colorMin:a,colorMax:l})=>{if(isFinite(t)&&isFinite(i)&&isFinite(n)&&isFinite(s)){e.beginPath(),e.moveTo(t,i),e.lineTo(n,s);try{let o=e.createLinearGradient(t,i,n,s);o.addColorStop(0,a),o.addColorStop(1,l),e.strokeStyle=o}catch(t){console.warn("Gradient creation failed, using solid color:",t),e.strokeStyle=a}e.stroke(),e.closePath()}else console.warn("Cannot draw trendline: coordinates contain non-finite values",{x1:t,y1:i,x2:n,y2:s})},d=(e,t,i,n,s,a,l)=>{isFinite(t)&&isFinite(i)&&isFinite(n)&&isFinite(s)&&isFinite(a)?(e.beginPath(),e.moveTo(t,i),e.lineTo(n,s),e.lineTo(n,a),e.lineTo(t,a),e.lineTo(t,i),e.closePath(),e.fillStyle=l,e.fill()):console.warn("Cannot fill below trendline: coordinates contain non-finite values",{x1:t,y1:i,x2:n,y2:s,drawBottom:a})};class c{constructor(){this.count=0,this.sumx=0,this.sumy=0,this.sumx2=0,this.sumxy=0,this.minx=Number.MAX_VALUE,this.maxx=Number.MIN_VALUE}add(e,t){this.sumx+=e,this.sumy+=t,this.sumx2+=e*e,this.sumxy+=e*t,e<this.minx&&(this.minx=e),e>this.maxx&&(this.maxx=e),this.count++}slope(){const e=this.count*this.sumx2-this.sumx*this.sumx;return(this.count*this.sumxy-this.sumx*this.sumy)/e}intercept(){return(this.sumy-this.slope()*this.sumx)/this.count}f(e){return this.slope()*e+this.intercept()}fo(){return-this.intercept()/this.slope()}scale(){return this.slope()}}"undefined"!=typeof window&&window.Chart&&(window.Chart.hasOwnProperty("register")?window.Chart.register(i):window.Chart.plugins.register(i));try{e.exports=i}catch(e){}}},t={};!function i(n){var s=t[n];if(void 0!==s)return s.exports;var a=t[n]={exports:{}};return e[n](a,a.exports,i),a.exports}(339)})();
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
/*!
|
|
2
|
-
* chartjs-plugin-trendline.js
|
|
3
|
-
* Version: 2.1.
|
|
4
|
-
*
|
|
5
|
-
* Copyright
|
|
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
|
-
*/
|
|
1
|
+
/*!
|
|
2
|
+
* chartjs-plugin-trendline.js
|
|
3
|
+
* Version: 2.1.8
|
|
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
|
+
*/
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Chart.js Example</title>
|
|
7
|
+
<!-- Include Chart.js and the Trendline plugin -->
|
|
8
|
+
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
9
|
+
<script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns@3.0.0/dist/chartjs-adapter-date-fns.bundle.min.js"></script>
|
|
10
|
+
<script src="../src/chartjs-plugin-trendline.js"></script>
|
|
11
|
+
</head>
|
|
12
|
+
<body>
|
|
13
|
+
|
|
14
|
+
<!-- Canvas element for rendering the chart -->
|
|
15
|
+
<canvas id="chartjs" width="400" height="200"></canvas>
|
|
16
|
+
|
|
17
|
+
<script type="text/javascript">
|
|
18
|
+
// Define colors
|
|
19
|
+
const barColor1 = '#258cda'; // Blue color for incidents
|
|
20
|
+
const borderColor1 = '#258cda'; // Same as backgroundColor for incidents
|
|
21
|
+
const barColor2 = '#cccccc'; // Gray color for outage
|
|
22
|
+
const borderColor2 = '#808080'; // Darker gray for borderColor of outage
|
|
23
|
+
|
|
24
|
+
// Get the context of the canvas element we want to select
|
|
25
|
+
const ctx = document.getElementById("chartjs").getContext('2d');
|
|
26
|
+
|
|
27
|
+
const chartDynamics = [
|
|
28
|
+
{
|
|
29
|
+
datepost: "2025-03-01T00:00:00",
|
|
30
|
+
quantity: 75,
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
datepost: "2025-03-02T00:00:00",
|
|
34
|
+
quantity: 64,
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
datepost: "2025-03-03T00:00:00",
|
|
38
|
+
quantity: 52,
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
datepost: "2025-03-04T00:00:00",
|
|
42
|
+
quantity: 23,
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
datepost: "2025-03-05T00:00:00",
|
|
46
|
+
quantity: 44,
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
datepost: "2025-03-06T00:00:00",
|
|
50
|
+
quantity: 38,
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
datepost: "2025-03-07T00:00:00",
|
|
54
|
+
quantity: 44,
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
datepost: "2025-03-08T00:00:00",
|
|
58
|
+
quantity: 41,
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
datepost: "2025-03-09T00:00:00",
|
|
62
|
+
quantity: 78,
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
datepost: "2025-03-10T00:00:00",
|
|
66
|
+
quantity: 31,
|
|
67
|
+
},
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
// Create a new Chart instance
|
|
71
|
+
const myChart1 = new Chart(ctx, {
|
|
72
|
+
type: "line",
|
|
73
|
+
data: {
|
|
74
|
+
labels: chartDynamics.map((o) => o.datepost),
|
|
75
|
+
datasets: [
|
|
76
|
+
{
|
|
77
|
+
label: "total",
|
|
78
|
+
data: chartDynamics.map((o) => o.quantity),
|
|
79
|
+
borderWidth: 1,
|
|
80
|
+
pointStyle: false,
|
|
81
|
+
trendlineLinear: { lineStyle: "dotted", width: 2 },
|
|
82
|
+
},
|
|
83
|
+
],
|
|
84
|
+
},
|
|
85
|
+
options: {
|
|
86
|
+
maintainAspectRatio: false,
|
|
87
|
+
scales: {
|
|
88
|
+
x: { type: "time", time: { tooltipFormat: "d", minUnit: "day" } },
|
|
89
|
+
},
|
|
90
|
+
interaction: { mode: "nearest", intersect: false },
|
|
91
|
+
plugins: {
|
|
92
|
+
legend: { display: false },
|
|
93
|
+
autocolors: { enabled: false },
|
|
94
|
+
datalabels: { display: false },
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
</script>
|
|
100
|
+
|
|
101
|
+
</body>
|
|
102
|
+
</html>
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/*!
|
|
2
2
|
* chartjs-plugin-trendline.js
|
|
3
|
-
* Version: 2.1.
|
|
3
|
+
* Version: 2.1.8
|
|
4
4
|
*
|
|
5
|
-
* Copyright
|
|
5
|
+
* Copyright 2025 Marcus Alsterfjord
|
|
6
6
|
* Released under the MIT license
|
|
7
7
|
* https://github.com/Makanz/chartjs-plugin-trendline/blob/master/README.md
|
|
8
8
|
*
|
|
@@ -107,6 +107,9 @@ const getScales = (chartInstance) => {
|
|
|
107
107
|
* @param {Scale} yScale - The y-axis scale object.
|
|
108
108
|
*/
|
|
109
109
|
const addFitter = (datasetMeta, ctx, dataset, xScale, yScale) => {
|
|
110
|
+
const yAxisID = dataset.yAxisID || 'y'; // Default to 'y' if no yAxisID is specified
|
|
111
|
+
const yScaleToUse = datasetMeta.controller.chart.scales[yAxisID] || yScale;
|
|
112
|
+
|
|
110
113
|
const defaultColor = dataset.borderColor || 'rgba(169,169,169, .6)';
|
|
111
114
|
const {
|
|
112
115
|
colorMin = defaultColor,
|
|
@@ -149,6 +152,7 @@ const addFitter = (datasetMeta, ctx, dataset, xScale, yScale) => {
|
|
|
149
152
|
let endPos = datasetMeta.data[lastIndex]?.[xAxisKey];
|
|
150
153
|
let xy = typeof dataset.data[firstIndex] === 'object';
|
|
151
154
|
|
|
155
|
+
// Collect data points for the fitter
|
|
152
156
|
dataset.data.forEach((data, index) => {
|
|
153
157
|
if (data == null) return;
|
|
154
158
|
|
|
@@ -172,9 +176,11 @@ const addFitter = (datasetMeta, ctx, dataset, xScale, yScale) => {
|
|
|
172
176
|
}
|
|
173
177
|
});
|
|
174
178
|
|
|
175
|
-
|
|
176
|
-
let
|
|
177
|
-
|
|
179
|
+
// Calculate the pixel coordinates for the trendline
|
|
180
|
+
let x1 = isFinite(startPos)
|
|
181
|
+
? startPos
|
|
182
|
+
: xScale.getPixelForValue(fitter.minx);
|
|
183
|
+
let y1 = yScaleToUse.getPixelForValue(fitter.f(fitter.minx));
|
|
178
184
|
let x2, y2;
|
|
179
185
|
|
|
180
186
|
// Projection logic for trendline
|
|
@@ -182,60 +188,60 @@ const addFitter = (datasetMeta, ctx, dataset, xScale, yScale) => {
|
|
|
182
188
|
let x2value = fitter.fo();
|
|
183
189
|
if (x2value < fitter.minx) x2value = fitter.maxx;
|
|
184
190
|
x2 = xScale.getPixelForValue(x2value);
|
|
185
|
-
y2 =
|
|
191
|
+
y2 = yScaleToUse.getPixelForValue(fitter.f(x2value));
|
|
186
192
|
} else {
|
|
187
|
-
x2 = xScale.getPixelForValue(fitter.maxx);
|
|
188
|
-
y2 =
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
if (isFinite(startPos) || isFinite(endPos)) {
|
|
192
|
-
x1 = startPos;
|
|
193
|
-
x2 = endPos;
|
|
193
|
+
x2 = isFinite(endPos) ? endPos : xScale.getPixelForValue(fitter.maxx);
|
|
194
|
+
y2 = yScaleToUse.getPixelForValue(fitter.f(fitter.maxx));
|
|
194
195
|
}
|
|
195
196
|
|
|
196
197
|
const drawBottom = datasetMeta.controller.chart.chartArea.bottom;
|
|
197
198
|
const chartWidth = datasetMeta.controller.chart.width;
|
|
198
199
|
|
|
199
|
-
|
|
200
|
+
// Only adjust line for overflow if coordinates are valid
|
|
201
|
+
if (isFinite(x1) && isFinite(y1) && isFinite(x2) && isFinite(y2)) {
|
|
202
|
+
adjustLineForOverflow({ x1, y1, x2, y2, drawBottom, chartWidth });
|
|
200
203
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
+
// Set line width and styles
|
|
205
|
+
ctx.lineWidth = lineWidth;
|
|
206
|
+
setLineStyle(ctx, lineStyle);
|
|
204
207
|
|
|
205
|
-
|
|
206
|
-
|
|
208
|
+
// Draw the trendline
|
|
209
|
+
drawTrendline({ ctx, x1, y1, x2, y2, colorMin, colorMax });
|
|
207
210
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
211
|
+
// Optionally fill below the trendline
|
|
212
|
+
if (fillColor) {
|
|
213
|
+
fillBelowTrendline(ctx, x1, y1, x2, y2, drawBottom, fillColor);
|
|
214
|
+
}
|
|
212
215
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
216
|
+
// Calculate the angle of the trendline
|
|
217
|
+
const angle = Math.atan2(y2 - y1, x2 - x1);
|
|
218
|
+
|
|
219
|
+
// Calculate the slope of the trendline (value of trend)
|
|
220
|
+
const slope = (y1 - y2) / (x2 - x1);
|
|
221
|
+
|
|
222
|
+
// Add the label to the trendline if it's populated and not set to hidden
|
|
223
|
+
if (dataset.trendlineLinear.label && display !== false) {
|
|
224
|
+
const trendText = displayValue
|
|
225
|
+
? `${text} (Slope: ${
|
|
226
|
+
percentage
|
|
227
|
+
? (slope * 100).toFixed(2) + '%'
|
|
228
|
+
: slope.toFixed(2)
|
|
229
|
+
})`
|
|
230
|
+
: text;
|
|
231
|
+
addTrendlineLabel(
|
|
232
|
+
ctx,
|
|
233
|
+
trendText,
|
|
234
|
+
x1,
|
|
235
|
+
y1,
|
|
236
|
+
x2,
|
|
237
|
+
y2,
|
|
238
|
+
angle,
|
|
239
|
+
color,
|
|
240
|
+
family,
|
|
241
|
+
size,
|
|
242
|
+
offset
|
|
243
|
+
);
|
|
244
|
+
}
|
|
239
245
|
}
|
|
240
246
|
};
|
|
241
247
|
|
|
@@ -362,15 +368,30 @@ const setLineStyle = (ctx, lineStyle) => {
|
|
|
362
368
|
* @param {string} params.colorMax - The ending color of the trendline gradient.
|
|
363
369
|
*/
|
|
364
370
|
const drawTrendline = ({ ctx, x1, y1, x2, y2, colorMin, colorMax }) => {
|
|
371
|
+
// Ensure all values are finite numbers
|
|
372
|
+
if (!isFinite(x1) || !isFinite(y1) || !isFinite(x2) || !isFinite(y2)) {
|
|
373
|
+
console.warn(
|
|
374
|
+
'Cannot draw trendline: coordinates contain non-finite values',
|
|
375
|
+
{ x1, y1, x2, y2 }
|
|
376
|
+
);
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
|
|
365
380
|
ctx.beginPath();
|
|
366
381
|
ctx.moveTo(x1, y1);
|
|
367
382
|
ctx.lineTo(x2, y2);
|
|
368
383
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
384
|
+
try {
|
|
385
|
+
let gradient = ctx.createLinearGradient(x1, y1, x2, y2);
|
|
386
|
+
gradient.addColorStop(0, colorMin);
|
|
387
|
+
gradient.addColorStop(1, colorMax);
|
|
388
|
+
ctx.strokeStyle = gradient;
|
|
389
|
+
} catch (e) {
|
|
390
|
+
// Fallback to solid color if gradient creation fails
|
|
391
|
+
console.warn('Gradient creation failed, using solid color:', e);
|
|
392
|
+
ctx.strokeStyle = colorMin;
|
|
393
|
+
}
|
|
372
394
|
|
|
373
|
-
ctx.strokeStyle = gradient;
|
|
374
395
|
ctx.stroke();
|
|
375
396
|
ctx.closePath();
|
|
376
397
|
};
|
|
@@ -386,6 +407,21 @@ const drawTrendline = ({ ctx, x1, y1, x2, y2, colorMin, colorMax }) => {
|
|
|
386
407
|
* @param {string} fillColor - The color to fill below the trendline.
|
|
387
408
|
*/
|
|
388
409
|
const fillBelowTrendline = (ctx, x1, y1, x2, y2, drawBottom, fillColor) => {
|
|
410
|
+
// Ensure all values are finite numbers
|
|
411
|
+
if (
|
|
412
|
+
!isFinite(x1) ||
|
|
413
|
+
!isFinite(y1) ||
|
|
414
|
+
!isFinite(x2) ||
|
|
415
|
+
!isFinite(y2) ||
|
|
416
|
+
!isFinite(drawBottom)
|
|
417
|
+
) {
|
|
418
|
+
console.warn(
|
|
419
|
+
'Cannot fill below trendline: coordinates contain non-finite values',
|
|
420
|
+
{ x1, y1, x2, y2, drawBottom }
|
|
421
|
+
);
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
|
|
389
425
|
ctx.beginPath();
|
|
390
426
|
ctx.moveTo(x1, y1);
|
|
391
427
|
ctx.lineTo(x2, y2);
|