chartjs-plugin-trendline 2.1.7 → 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.
|
@@ -1,2 +1,2 @@
|
|
|
1
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:
|
|
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)})();
|
|
@@ -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,6 +1,6 @@
|
|
|
1
1
|
/*!
|
|
2
2
|
* chartjs-plugin-trendline.js
|
|
3
|
-
* Version: 2.1.
|
|
3
|
+
* Version: 2.1.8
|
|
4
4
|
*
|
|
5
5
|
* Copyright 2025 Marcus Alsterfjord
|
|
6
6
|
* Released under the MIT license
|
|
@@ -148,6 +148,8 @@ const addFitter = (datasetMeta, ctx, dataset, xScale, yScale) => {
|
|
|
148
148
|
(d) => d !== undefined && d !== null
|
|
149
149
|
);
|
|
150
150
|
let lastIndex = dataset.data.length - 1;
|
|
151
|
+
let startPos = datasetMeta.data[firstIndex]?.[xAxisKey];
|
|
152
|
+
let endPos = datasetMeta.data[lastIndex]?.[xAxisKey];
|
|
151
153
|
let xy = typeof dataset.data[firstIndex] === 'object';
|
|
152
154
|
|
|
153
155
|
// Collect data points for the fitter
|
|
@@ -175,7 +177,9 @@ const addFitter = (datasetMeta, ctx, dataset, xScale, yScale) => {
|
|
|
175
177
|
});
|
|
176
178
|
|
|
177
179
|
// Calculate the pixel coordinates for the trendline
|
|
178
|
-
let x1 =
|
|
180
|
+
let x1 = isFinite(startPos)
|
|
181
|
+
? startPos
|
|
182
|
+
: xScale.getPixelForValue(fitter.minx);
|
|
179
183
|
let y1 = yScaleToUse.getPixelForValue(fitter.f(fitter.minx));
|
|
180
184
|
let x2, y2;
|
|
181
185
|
|
|
@@ -186,13 +190,10 @@ const addFitter = (datasetMeta, ctx, dataset, xScale, yScale) => {
|
|
|
186
190
|
x2 = xScale.getPixelForValue(x2value);
|
|
187
191
|
y2 = yScaleToUse.getPixelForValue(fitter.f(x2value));
|
|
188
192
|
} else {
|
|
189
|
-
x2 = xScale.getPixelForValue(fitter.maxx);
|
|
193
|
+
x2 = isFinite(endPos) ? endPos : xScale.getPixelForValue(fitter.maxx);
|
|
190
194
|
y2 = yScaleToUse.getPixelForValue(fitter.f(fitter.maxx));
|
|
191
195
|
}
|
|
192
196
|
|
|
193
|
-
// Do not use startPos and endPos directly, as they may be undefined
|
|
194
|
-
// This was causing the vertical line issue
|
|
195
|
-
|
|
196
197
|
const drawBottom = datasetMeta.controller.chart.chartArea.bottom;
|
|
197
198
|
const chartWidth = datasetMeta.controller.chart.width;
|
|
198
199
|
|
package/example/issue.html
DELETED
|
@@ -1,113 +0,0 @@
|
|
|
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="../src/chartjs-plugin-trendline.js"></script>
|
|
10
|
-
</head>
|
|
11
|
-
<body>
|
|
12
|
-
|
|
13
|
-
<!-- Canvas element for rendering the chart -->
|
|
14
|
-
<canvas id="chartjs" width="400" height="200"></canvas>
|
|
15
|
-
|
|
16
|
-
<script type="text/javascript">
|
|
17
|
-
// Define colors
|
|
18
|
-
const barColor1 = '#258cda'; // Blue color for incidents
|
|
19
|
-
const borderColor1 = '#258cda'; // Same as backgroundColor for incidents
|
|
20
|
-
const barColor2 = '#cccccc'; // Gray color for outage
|
|
21
|
-
const borderColor2 = '#808080'; // Darker gray for borderColor of outage
|
|
22
|
-
|
|
23
|
-
// Get the context of the canvas element we want to select
|
|
24
|
-
const ctx = document.getElementById("chartjs").getContext('2d');
|
|
25
|
-
|
|
26
|
-
// Create a new Chart instance
|
|
27
|
-
const myChart1 = new Chart(ctx, {
|
|
28
|
-
type: 'bar', // Specify chart type (e.g., bar)
|
|
29
|
-
|
|
30
|
-
data: {
|
|
31
|
-
labels: ["July 2024","August 2024","September 2024","October 2024","November 2024","December 2024","January 2025","February 2025","March 2025","April 2025","May 2025","June 2025"], // Example labels for x-axis
|
|
32
|
-
datasets: [
|
|
33
|
-
{
|
|
34
|
-
label: '# of Incidents',
|
|
35
|
-
yAxisID: 'y1',
|
|
36
|
-
fill: false,
|
|
37
|
-
backgroundColor: barColor1,
|
|
38
|
-
borderColor: borderColor1,
|
|
39
|
-
borderWidth: 3,
|
|
40
|
-
tension: 0.3,
|
|
41
|
-
data: [7,6,13,6,4,3,4,4,0,0,0,0], // Example data for incidents
|
|
42
|
-
trendlineLinear: {
|
|
43
|
-
lineStyle: 'dotted',
|
|
44
|
-
width: 2
|
|
45
|
-
},
|
|
46
|
-
},
|
|
47
|
-
{
|
|
48
|
-
label: 'Total Outage',
|
|
49
|
-
yAxisID: 'y2',
|
|
50
|
-
fill: true,
|
|
51
|
-
backgroundColor: barColor2,
|
|
52
|
-
borderColor: borderColor2,
|
|
53
|
-
borderWidth: 3,
|
|
54
|
-
tension: 0.3,
|
|
55
|
-
data: [112,201,316,16,16,13,11,81,20,300,250,200], // Example data for outage in hours
|
|
56
|
-
trendlineLinear: {
|
|
57
|
-
lineStyle: 'dotted',
|
|
58
|
-
width: 2
|
|
59
|
-
},
|
|
60
|
-
}
|
|
61
|
-
],
|
|
62
|
-
},
|
|
63
|
-
|
|
64
|
-
options: {
|
|
65
|
-
plugins: {
|
|
66
|
-
legend: {
|
|
67
|
-
position: 'bottom', // Position of the legend
|
|
68
|
-
},
|
|
69
|
-
},
|
|
70
|
-
tooltips: {
|
|
71
|
-
displayColors: true, // Display color in tooltip
|
|
72
|
-
mode: 'index', // Tooltip mode
|
|
73
|
-
},
|
|
74
|
-
scales: {
|
|
75
|
-
x: { // X-axis configuration
|
|
76
|
-
ticks: {
|
|
77
|
-
autoSkip: true,
|
|
78
|
-
maxTicksLimit: 12,
|
|
79
|
-
maxRotation: 45,
|
|
80
|
-
minRotation: 45,
|
|
81
|
-
},
|
|
82
|
-
},
|
|
83
|
-
y1: { // Left Y-axis for incidents
|
|
84
|
-
ticks: {
|
|
85
|
-
beginAtZero: true,
|
|
86
|
-
},
|
|
87
|
-
title: {
|
|
88
|
-
display: true,
|
|
89
|
-
text: 'Incidents',
|
|
90
|
-
},
|
|
91
|
-
type: 'linear',
|
|
92
|
-
position: 'left',
|
|
93
|
-
},
|
|
94
|
-
y2: { // Right Y-axis for outage in hours
|
|
95
|
-
ticks: {
|
|
96
|
-
beginAtZero: true,
|
|
97
|
-
},
|
|
98
|
-
title: {
|
|
99
|
-
display: true,
|
|
100
|
-
text: 'Outage in Hours',
|
|
101
|
-
},
|
|
102
|
-
type: 'linear',
|
|
103
|
-
position: 'right',
|
|
104
|
-
}
|
|
105
|
-
},
|
|
106
|
-
responsive: true, // Make the chart responsive
|
|
107
|
-
maintainAspectRatio: true, // Maintain aspect ratio
|
|
108
|
-
}
|
|
109
|
-
});
|
|
110
|
-
</script>
|
|
111
|
-
|
|
112
|
-
</body>
|
|
113
|
-
</html>
|