chartjs-plugin-trendline 2.1.3 → 2.1.5
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 +29 -7
- package/dist/chartjs-plugin-trendline.min.js +1 -1
- package/dist/chartjs-plugin-trendline.min.js.LICENSE.txt +3 -2
- package/example/barChart.html +50 -0
- package/example/lineChart.html +98 -63
- package/package.json +1 -1
- package/src/chartjs-plugin-trendline.js +323 -97
- package/example/barChart_not_working.html +0 -79
- package/example/barChart_working.html +0 -72
package/README.md
CHANGED
|
@@ -34,13 +34,34 @@ 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:
|
|
38
|
-
colorMax:
|
|
39
|
-
lineStyle: "dotted|solid"
|
|
40
|
-
width:
|
|
41
|
-
xAxisKey:
|
|
42
|
-
yAxisKey:
|
|
43
|
-
projection:
|
|
37
|
+
colorMin: Color
|
|
38
|
+
colorMax: Color,
|
|
39
|
+
lineStyle: string, // "dotted" | "solid" | "dashed" | "dashdot"
|
|
40
|
+
width: number,
|
|
41
|
+
xAxisKey: string, // optional
|
|
42
|
+
yAxisKey: string, // optional
|
|
43
|
+
projection: boolean, // optional
|
|
44
|
+
// optional
|
|
45
|
+
label: {
|
|
46
|
+
color: Color,
|
|
47
|
+
text: string,
|
|
48
|
+
display: boolean,
|
|
49
|
+
displayValue: boolean,
|
|
50
|
+
offset: number,
|
|
51
|
+
font: {
|
|
52
|
+
family: string,
|
|
53
|
+
size: number,
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
// optional
|
|
57
|
+
legend: {
|
|
58
|
+
text: string,
|
|
59
|
+
strokeStyle: Color,
|
|
60
|
+
fillStyle: Color,
|
|
61
|
+
lineCap: string,
|
|
62
|
+
lineDash: number[],
|
|
63
|
+
lineWidth: number,
|
|
64
|
+
}
|
|
44
65
|
}
|
|
45
66
|
}
|
|
46
67
|
```
|
|
@@ -49,6 +70,7 @@ To configure the trendline plugin you simply add a new config options to your da
|
|
|
49
70
|
|
|
50
71
|
- bar
|
|
51
72
|
- line
|
|
73
|
+
- scatter
|
|
52
74
|
|
|
53
75
|
## Contributing
|
|
54
76
|
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/*! For license information please see chartjs-plugin-trendline.min.js.LICENSE.txt */
|
|
2
|
-
(()=>{var
|
|
2
|
+
(()=>{var e={339:(e,t)=>{const i={id:"chartjs-plugin-trendline",afterDatasetsDraw:e=>{const t=e.ctx,{xScale:i,yScale:n}=a(e);e.data.datasets.forEach(((a,l)=>{const r=a.alwaysShowTrendline||e.isDatasetVisible(l);if(a.trendlineLinear&&r&&a.data.length>1){const r=e.getDatasetMeta(l);s(r,t,a,i,n)}})),t.setLineDash([])},beforeInit:e=>{e.data.datasets.forEach((t=>{if(t.trendlineLinear&&t.trendlineLinear.label){const i=t.trendlineLinear.label,a=e.legend.options.labels.generateLabels;e.legend.options.labels.generateLabels=function(e){const s=a(e),n=t.trendlineLinear.legend;return n&&!1!==n.display&&s.push({text:n.text||i+" (Trendline)",strokeStyle:n.color||t.borderColor||"rgba(169,169,169, .6)",fillStyle:n.fillStyle||"transparent",lineCap:n.lineCap||"butt",lineDash:n.lineDash||[],lineWidth:n.width||1}),s}}}))}},a=e=>{let t,i;for(const a of Object.values(e.scales))if(a.isHorizontal()?t=a:i=a,t&&i)break;return{xScale:t,yScale:i}},s=(e,t,i,a,s)=>{const x=i.borderColor||"rgba(169,169,169, .6)",{colorMin:c=x,colorMax:u=x,width:y=i.borderWidth||3,lineStyle:f="solid",fillColor:m=!1}=i.trendlineLinear||{},{color:p=x,text:b="Trendline",display:g=!0,displayValue:w=!0,offset:L=10}=i.trendlineLinear.label||{},{family:S="'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",size:v=12}=i.trendlineLinear.label?.font||{},D=e.controller.chart.options,N="object"==typeof D.parsing?D.parsing:void 0,T=i.trendlineLinear?.xAxisKey||N?.xAxisKey||"x",C=i.trendlineLinear?.yAxisKey||N?.yAxisKey||"y";let P=new h,M=i.data.findIndex((e=>null!=e)),V=i.data.length-1,A=e.data[M]?.[T],F=e.data[V]?.[T],k="object"==typeof i.data[M];i.data.forEach(((e,t)=>{if(null!=e)if(["time","timeseries"].includes(a.options.type)){let i=null!=e[T]?e[T]:e.t;void 0!==i?P.add(new Date(i).getTime(),e[C]):P.add(t,e)}else k?isNaN(e.x)||isNaN(e.y)?isNaN(e.x)?isNaN(e.y)||P.add(t,e.y):P.add(t,e.x):P.add(e.x,e.y):P.add(t,e)}));let j,E,W=a.getPixelForValue(P.minx),K=s.getPixelForValue(P.f(P.minx));if(i.trendlineLinear.projection&&P.scale()<0){let e=P.fo();e<P.minx&&(e=P.maxx),j=a.getPixelForValue(e),E=s.getPixelForValue(P.f(e))}else j=a.getPixelForValue(P.maxx),E=s.getPixelForValue(P.f(P.maxx));(isFinite(A)||isFinite(F))&&(W=A,j=F);const $=e.controller.chart.chartArea.bottom,H=e.controller.chart.width;l({x1:W,y1:K,x2:j,y2:E,drawBottom:$,chartWidth:H}),t.lineWidth=y,r(t,f),o({ctx:t,x1:W,y1:K,x2:j,y2:E,colorMin:c,colorMax:u}),m&&d(t,W,K,j,E,$,m);const I=Math.atan2(E-K,j-W),z=(K-E)/(j-W);if(i.trendlineLinear.label&&!1!==g){const e=w?`${b} (Slope: ${z.toFixed(2)})`:b;n(t,e,W,K,j,E,I,p,S,v,L)}},n=(e,t,i,a,s,n,l,r,o,d,h)=>{e.font=`${d}px ${o}`,e.fillStyle=r;const x=(i+s)/2,c=(a+n)/2;e.save(),e.translate(x,c),e.rotate(l),e.fillText(t,0,-h),e.restore()},l=({x1:e,y1:t,x2:i,y2:a,drawBottom:s,chartWidth:n})=>{if(t>s){t=s}else if(a>s){let e=a-s,l=a-t;a=s,i=n-(i-(n-n*(e/l)))}},r=(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([])}},o=({ctx:e,x1:t,y1:i,x2:a,y2:s,colorMin:n,colorMax:l})=>{e.beginPath(),e.moveTo(t,i),e.lineTo(a,s);let r=e.createLinearGradient(t,i,a,s);r.addColorStop(0,n),r.addColorStop(1,l),e.strokeStyle=r,e.stroke(),e.closePath()},d=(e,t,i,a,s,n,l)=>{e.beginPath(),e.moveTo(t,i),e.lineTo(a,s),e.lineTo(a,n),e.lineTo(t,n),e.lineTo(t,i),e.closePath(),e.fillStyle=l,e.fill()};class h{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(a){var s=t[a];if(void 0!==s)return s.exports;var n=t[a]={exports:{}};return e[a](n,n.exports,i),n.exports}(339)})();
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/*!
|
|
2
2
|
* chartjs-plugin-trendline.js
|
|
3
|
-
* Version: 2.1.
|
|
3
|
+
* Version: 2.1.5
|
|
4
4
|
*
|
|
5
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
|
+
* Modified by @vesal: accept xy-data from scatter,
|
|
10
|
+
* Modified by @Megaemce: add label and basic legend to trendline, add JSDoc,
|
|
10
11
|
*/
|
|
@@ -0,0 +1,50 @@
|
|
|
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
|
+
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
|
7
|
+
<title>BarChart Example</title>
|
|
8
|
+
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.3/dist/chart.umd.js"></script>
|
|
9
|
+
<script src="./../src/chartjs-plugin-trendline.js"></script>
|
|
10
|
+
<script>
|
|
11
|
+
document.addEventListener("DOMContentLoaded", function(event) {
|
|
12
|
+
// Bar chart
|
|
13
|
+
new Chart(document.getElementById("bar-chart"), {
|
|
14
|
+
type: 'bar',
|
|
15
|
+
data: {
|
|
16
|
+
labels: ["Africa", "Asia", "Europe", "Latin America", "North America"],
|
|
17
|
+
datasets: [
|
|
18
|
+
{
|
|
19
|
+
label: "Population (millions)",
|
|
20
|
+
backgroundColor: ["#3e95cd", "#8e5ea2","#3cba9f","#e8c3b9","#c45850"],
|
|
21
|
+
data: [2478,5267,734,784,433],
|
|
22
|
+
trendlineLinear: {
|
|
23
|
+
colorMin: "rgba(255,105,180, .8)",
|
|
24
|
+
lineStyle: "dotted",
|
|
25
|
+
width: 2
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
]
|
|
29
|
+
},
|
|
30
|
+
options: {
|
|
31
|
+
legend: { display: false },
|
|
32
|
+
title: {
|
|
33
|
+
display: true,
|
|
34
|
+
text: 'Predicted world population (millions) in 2050'
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
</script>
|
|
40
|
+
</head>
|
|
41
|
+
<body>
|
|
42
|
+
<h1>Bar Chart</h1>
|
|
43
|
+
|
|
44
|
+
<div style="width: 800px;">
|
|
45
|
+
<canvas id="bar-chart"></canvas>
|
|
46
|
+
</div>
|
|
47
|
+
|
|
48
|
+
<p>Using example code from <a href="http://tobiasahlin.com/blog/chartjs-charts-to-get-you-started/" target="_blank">tobiasahlin.com.</a></p>
|
|
49
|
+
</body>
|
|
50
|
+
</html>
|
package/example/lineChart.html
CHANGED
|
@@ -1,73 +1,108 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
2
|
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
document.addEventListener(
|
|
12
|
-
new Chart(document.getElementById(
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
|
|
7
|
+
<title>LineChart Example with Labeled Trendlines</title>
|
|
8
|
+
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.3/dist/chart.umd.js"></script>
|
|
9
|
+
<script src="./../src/chartjs-plugin-trendline.js"></script>
|
|
10
|
+
<script>
|
|
11
|
+
document.addEventListener('DOMContentLoaded', function (event) {
|
|
12
|
+
new Chart(document.getElementById('line-chart'), {
|
|
13
|
+
type: 'line',
|
|
14
|
+
data: {
|
|
15
|
+
labels: [
|
|
16
|
+
1500, 1600, 1700, 1750, 1800, 1850, 1900, 1950,
|
|
17
|
+
1999, 2050,
|
|
18
|
+
],
|
|
19
|
+
datasets: [
|
|
20
|
+
{
|
|
21
|
+
data: [
|
|
22
|
+
86, 114, 106, 106, 107, 111, 133, 221, 783,
|
|
23
|
+
2478,
|
|
24
|
+
],
|
|
25
|
+
label: 'Africa',
|
|
26
|
+
borderColor: '#3e95cd',
|
|
20
27
|
fill: false,
|
|
21
28
|
trendlineLinear: {
|
|
22
|
-
colorMin:
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
26
|
-
},
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
29
|
+
colorMin: '#3e95cd',
|
|
30
|
+
width: 1,
|
|
31
|
+
lineStyle: 'solid',
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
data: [
|
|
36
|
+
282, 350, 411, 502, 635, 809, 947, 1402,
|
|
37
|
+
3700, 5267,
|
|
38
|
+
],
|
|
39
|
+
label: 'Asia',
|
|
40
|
+
borderColor: '#8e5ea2',
|
|
30
41
|
fill: false,
|
|
31
42
|
trendlineLinear: {
|
|
32
|
-
colorMin:
|
|
33
|
-
colorMax:
|
|
34
|
-
lineStyle:
|
|
35
|
-
width: 1
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
43
|
+
colorMin: 'red',
|
|
44
|
+
colorMax: 'green',
|
|
45
|
+
lineStyle: 'dashed',
|
|
46
|
+
width: 1,
|
|
47
|
+
label: {
|
|
48
|
+
font: {
|
|
49
|
+
size: 12,
|
|
50
|
+
},
|
|
51
|
+
color: 'red',
|
|
52
|
+
text: 'Asia',
|
|
53
|
+
displayValue: false,
|
|
54
|
+
offset: 10,
|
|
55
|
+
},
|
|
56
|
+
legend: {
|
|
57
|
+
color: 'red',
|
|
58
|
+
text: 'Asian trendline',
|
|
59
|
+
display: true,
|
|
60
|
+
lineDash: [8, 3],
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
data: [
|
|
66
|
+
168, 170, 178, 190, 203, 276, 408, 547, 675,
|
|
67
|
+
734,
|
|
68
|
+
],
|
|
69
|
+
label: 'Europe',
|
|
70
|
+
borderColor: '#3cba9f',
|
|
71
|
+
fill: false,
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
data: [
|
|
75
|
+
40, 20, 10, 16, 24, 38, 74, 167, 508, 784,
|
|
76
|
+
],
|
|
77
|
+
label: 'Latin America',
|
|
78
|
+
borderColor: '#e8c3b9',
|
|
79
|
+
fill: false,
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
data: [6, 3, 2, 2, 7, 26, 82, 172, 312, 433],
|
|
83
|
+
label: 'North America',
|
|
84
|
+
borderColor: '#c45850',
|
|
85
|
+
fill: false,
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
},
|
|
89
|
+
options: {
|
|
90
|
+
plugins: {
|
|
56
91
|
title: {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
92
|
+
display: true,
|
|
93
|
+
text: 'World population per region (in millions)',
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
},
|
|
62
97
|
});
|
|
98
|
+
});
|
|
63
99
|
</script>
|
|
64
|
-
</head>
|
|
65
|
-
<body>
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
<div style="width: 800px;">
|
|
69
|
-
<canvas id="line-chart"></canvas>
|
|
70
|
-
</div>
|
|
100
|
+
</head>
|
|
101
|
+
<body>
|
|
102
|
+
<h1>Line Chart with Labeled Trendlines</h1>
|
|
71
103
|
|
|
72
|
-
|
|
73
|
-
|
|
104
|
+
<div style="width: 800px">
|
|
105
|
+
<canvas id="line-chart"></canvas>
|
|
106
|
+
</div>
|
|
107
|
+
</body>
|
|
108
|
+
</html>
|
package/package.json
CHANGED
|
@@ -1,24 +1,29 @@
|
|
|
1
1
|
/*!
|
|
2
2
|
* chartjs-plugin-trendline.js
|
|
3
|
-
* Version: 2.1.
|
|
3
|
+
* Version: 2.1.5
|
|
4
4
|
*
|
|
5
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
|
+
* Modified by @vesal: accept xy-data from scatter,
|
|
10
|
+
* Modified by @Megaemce: add label and basic legend to trendline, add JSDoc,
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Chart.js plugin to draw linear trendlines on datasets.
|
|
10
15
|
*/
|
|
11
16
|
const pluginTrendlineLinear = {
|
|
12
17
|
id: 'chartjs-plugin-trendline',
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Hook that is called after datasets are drawn.
|
|
21
|
+
* Adds trendlines to the datasets that have `trendlineLinear` configured.
|
|
22
|
+
* @param {Chart} chartInstance - The chart instance where datasets are drawn.
|
|
23
|
+
*/
|
|
13
24
|
afterDatasetsDraw: (chartInstance) => {
|
|
14
|
-
let yScale;
|
|
15
|
-
let xScale;
|
|
16
|
-
for (let axis in chartInstance.scales) {
|
|
17
|
-
if (axis[0] == 'x') xScale = chartInstance.scales[axis];
|
|
18
|
-
else yScale = chartInstance.scales[axis];
|
|
19
|
-
if (xScale && yScale) break;
|
|
20
|
-
}
|
|
21
25
|
const ctx = chartInstance.ctx;
|
|
26
|
+
const { xScale, yScale } = getScales(chartInstance);
|
|
22
27
|
|
|
23
28
|
chartInstance.data.datasets.forEach((dataset, index) => {
|
|
24
29
|
const showTrendline =
|
|
@@ -31,27 +36,98 @@ const pluginTrendlineLinear = {
|
|
|
31
36
|
dataset.data.length > 1
|
|
32
37
|
) {
|
|
33
38
|
const datasetMeta = chartInstance.getDatasetMeta(index);
|
|
34
|
-
addFitter(
|
|
35
|
-
datasetMeta,
|
|
36
|
-
ctx,
|
|
37
|
-
dataset,
|
|
38
|
-
xScale,
|
|
39
|
-
chartInstance.scales[datasetMeta.yAxisID]
|
|
40
|
-
);
|
|
39
|
+
addFitter(datasetMeta, ctx, dataset, xScale, yScale);
|
|
41
40
|
}
|
|
42
41
|
});
|
|
43
42
|
|
|
43
|
+
// Reset to solid line after drawing trendline
|
|
44
44
|
ctx.setLineDash([]);
|
|
45
45
|
},
|
|
46
|
+
|
|
47
|
+
beforeInit: (chartInstance) => {
|
|
48
|
+
const datasets = chartInstance.data.datasets;
|
|
49
|
+
|
|
50
|
+
datasets.forEach((dataset) => {
|
|
51
|
+
if (dataset.trendlineLinear && dataset.trendlineLinear.label) {
|
|
52
|
+
const label = dataset.trendlineLinear.label;
|
|
53
|
+
|
|
54
|
+
// Access chartInstance to update legend labels
|
|
55
|
+
const originalGenerateLabels =
|
|
56
|
+
chartInstance.legend.options.labels.generateLabels;
|
|
57
|
+
|
|
58
|
+
chartInstance.legend.options.labels.generateLabels = function (
|
|
59
|
+
chart
|
|
60
|
+
) {
|
|
61
|
+
const defaultLabels = originalGenerateLabels(chart);
|
|
62
|
+
|
|
63
|
+
const legendConfig = dataset.trendlineLinear.legend;
|
|
64
|
+
|
|
65
|
+
// Display the legend is it's populated and not set to hidden
|
|
66
|
+
if (legendConfig && legendConfig.display !== false) {
|
|
67
|
+
defaultLabels.push({
|
|
68
|
+
text: legendConfig.text || label + ' (Trendline)',
|
|
69
|
+
strokeStyle:
|
|
70
|
+
legendConfig.color ||
|
|
71
|
+
dataset.borderColor ||
|
|
72
|
+
'rgba(169,169,169, .6)',
|
|
73
|
+
fillStyle: legendConfig.fillStyle || 'transparent',
|
|
74
|
+
lineCap: legendConfig.lineCap || 'butt',
|
|
75
|
+
lineDash: legendConfig.lineDash || [],
|
|
76
|
+
lineWidth: legendConfig.width || 1,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
return defaultLabels;
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
},
|
|
46
84
|
};
|
|
47
85
|
|
|
86
|
+
/**
|
|
87
|
+
* Retrieves the x and y scales from the chart instance.
|
|
88
|
+
* @param {Chart} chartInstance - The chart instance.
|
|
89
|
+
* @returns {Object} - The xScale and yScale of the chart.
|
|
90
|
+
*/
|
|
91
|
+
const getScales = (chartInstance) => {
|
|
92
|
+
let xScale, yScale;
|
|
93
|
+
for (const scale of Object.values(chartInstance.scales)) {
|
|
94
|
+
if (scale.isHorizontal()) xScale = scale;
|
|
95
|
+
else yScale = scale;
|
|
96
|
+
if (xScale && yScale) break;
|
|
97
|
+
}
|
|
98
|
+
return { xScale, yScale };
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Adds a trendline (fitter) to the dataset on the chart and optionally labels it with trend value.
|
|
103
|
+
* @param {Object} datasetMeta - Metadata about the dataset.
|
|
104
|
+
* @param {CanvasRenderingContext2D} ctx - The canvas rendering context.
|
|
105
|
+
* @param {Object} dataset - The dataset configuration from the chart.
|
|
106
|
+
* @param {Scale} xScale - The x-axis scale object.
|
|
107
|
+
* @param {Scale} yScale - The y-axis scale object.
|
|
108
|
+
*/
|
|
48
109
|
const addFitter = (datasetMeta, ctx, dataset, xScale, yScale) => {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
110
|
+
const defaultColor = dataset.borderColor || 'rgba(169,169,169, .6)';
|
|
111
|
+
const {
|
|
112
|
+
colorMin = defaultColor,
|
|
113
|
+
colorMax = defaultColor,
|
|
114
|
+
width: lineWidth = dataset.borderWidth || 3,
|
|
115
|
+
lineStyle = 'solid',
|
|
116
|
+
fillColor = false,
|
|
117
|
+
} = dataset.trendlineLinear || {};
|
|
118
|
+
|
|
119
|
+
const {
|
|
120
|
+
color = defaultColor,
|
|
121
|
+
text = 'Trendline',
|
|
122
|
+
display = true,
|
|
123
|
+
displayValue = true,
|
|
124
|
+
offset = 10,
|
|
125
|
+
} = dataset.trendlineLinear.label || {};
|
|
126
|
+
|
|
127
|
+
const {
|
|
128
|
+
family = "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
|
|
129
|
+
size = 12,
|
|
130
|
+
} = dataset.trendlineLinear.label?.font || {};
|
|
55
131
|
|
|
56
132
|
const chartOptions = datasetMeta.controller.chart.options;
|
|
57
133
|
const parsingOptions =
|
|
@@ -59,17 +135,17 @@ const addFitter = (datasetMeta, ctx, dataset, xScale, yScale) => {
|
|
|
59
135
|
? chartOptions.parsing
|
|
60
136
|
: undefined;
|
|
61
137
|
const xAxisKey =
|
|
62
|
-
dataset.trendlineLinear
|
|
138
|
+
dataset.trendlineLinear?.xAxisKey || parsingOptions?.xAxisKey || 'x';
|
|
63
139
|
const yAxisKey =
|
|
64
|
-
dataset.trendlineLinear
|
|
140
|
+
dataset.trendlineLinear?.yAxisKey || parsingOptions?.yAxisKey || 'y';
|
|
65
141
|
|
|
66
142
|
let fitter = new LineFitter();
|
|
67
|
-
let firstIndex = dataset.data.findIndex(
|
|
68
|
-
|
|
69
|
-
|
|
143
|
+
let firstIndex = dataset.data.findIndex(
|
|
144
|
+
(d) => d !== undefined && d !== null
|
|
145
|
+
);
|
|
70
146
|
let lastIndex = dataset.data.length - 1;
|
|
71
|
-
let startPos = datasetMeta.data[firstIndex][xAxisKey];
|
|
72
|
-
let endPos = datasetMeta.data[lastIndex][xAxisKey];
|
|
147
|
+
let startPos = datasetMeta.data[firstIndex]?.[xAxisKey];
|
|
148
|
+
let endPos = datasetMeta.data[lastIndex]?.[xAxisKey];
|
|
73
149
|
let xy = typeof dataset.data[firstIndex] === 'object';
|
|
74
150
|
|
|
75
151
|
dataset.data.forEach((data, index) => {
|
|
@@ -98,33 +174,131 @@ const addFitter = (datasetMeta, ctx, dataset, xScale, yScale) => {
|
|
|
98
174
|
let x1 = xScale.getPixelForValue(fitter.minx);
|
|
99
175
|
let y1 = yScale.getPixelForValue(fitter.f(fitter.minx));
|
|
100
176
|
|
|
101
|
-
let x2;
|
|
102
|
-
let y2;
|
|
177
|
+
let x2, y2;
|
|
103
178
|
|
|
104
|
-
//
|
|
179
|
+
// Projection logic for trendline
|
|
105
180
|
if (dataset.trendlineLinear.projection && fitter.scale() < 0) {
|
|
106
|
-
// X
|
|
107
181
|
let x2value = fitter.fo();
|
|
108
182
|
if (x2value < fitter.minx) x2value = fitter.maxx;
|
|
109
183
|
x2 = xScale.getPixelForValue(x2value);
|
|
110
|
-
|
|
111
|
-
// Y
|
|
112
184
|
y2 = yScale.getPixelForValue(fitter.f(x2value));
|
|
113
185
|
} else {
|
|
114
186
|
x2 = xScale.getPixelForValue(fitter.maxx);
|
|
115
187
|
y2 = yScale.getPixelForValue(fitter.f(fitter.maxx));
|
|
116
188
|
}
|
|
117
189
|
|
|
118
|
-
if(isFinite(startPos) || isFinite(endPos)) {
|
|
190
|
+
if (isFinite(startPos) || isFinite(endPos)) {
|
|
119
191
|
x1 = startPos;
|
|
120
192
|
x2 = endPos;
|
|
121
193
|
}
|
|
122
194
|
|
|
123
|
-
|
|
124
|
-
|
|
195
|
+
const drawBottom = datasetMeta.controller.chart.chartArea.bottom;
|
|
196
|
+
const chartWidth = datasetMeta.controller.chart.width;
|
|
125
197
|
|
|
198
|
+
adjustLineForOverflow({ x1, y1, x2, y2, drawBottom, chartWidth });
|
|
199
|
+
|
|
200
|
+
// Set line width and styles
|
|
201
|
+
ctx.lineWidth = lineWidth;
|
|
202
|
+
setLineStyle(ctx, lineStyle);
|
|
203
|
+
|
|
204
|
+
// Draw the trendline
|
|
205
|
+
drawTrendline({ ctx, x1, y1, x2, y2, colorMin, colorMax });
|
|
206
|
+
|
|
207
|
+
// Optionally fill below the trendline
|
|
208
|
+
if (fillColor) {
|
|
209
|
+
fillBelowTrendline(ctx, x1, y1, x2, y2, drawBottom, fillColor);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Calculate the angle of the trendline
|
|
213
|
+
const angle = Math.atan2(y2 - y1, x2 - x1);
|
|
214
|
+
|
|
215
|
+
// Calculate the slope of the trendline (value of trend)
|
|
216
|
+
const slope = (y1 - y2) / (x2 - x1);
|
|
217
|
+
|
|
218
|
+
// Add the label to the trendline if it's populated and not set to hidden
|
|
219
|
+
if (dataset.trendlineLinear.label && display !== false) {
|
|
220
|
+
const trendText = displayValue
|
|
221
|
+
? `${text} (Slope: ${slope.toFixed(2)})`
|
|
222
|
+
: text;
|
|
223
|
+
addTrendlineLabel(
|
|
224
|
+
ctx,
|
|
225
|
+
trendText,
|
|
226
|
+
x1,
|
|
227
|
+
y1,
|
|
228
|
+
x2,
|
|
229
|
+
y2,
|
|
230
|
+
angle,
|
|
231
|
+
color,
|
|
232
|
+
family,
|
|
233
|
+
size,
|
|
234
|
+
offset
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Adds a label to the trendline at the calculated angle.
|
|
241
|
+
* @param {CanvasRenderingContext2D} ctx - The canvas rendering context.
|
|
242
|
+
* @param {string} label - The label text to add.
|
|
243
|
+
* @param {number} x1 - The starting x-coordinate of the trendline.
|
|
244
|
+
* @param {number} y1 - The starting y-coordinate of the trendline.
|
|
245
|
+
* @param {number} x2 - The ending x-coordinate of the trendline.
|
|
246
|
+
* @param {number} y2 - The ending y-coordinate of the trendline.
|
|
247
|
+
* @param {number} angle - The angle (in radians) of the trendline.
|
|
248
|
+
* @param {string} labelColor - The color of the label text.
|
|
249
|
+
* @param {string} family - The font family for the label text.
|
|
250
|
+
* @param {number} size - The font size for the label text.
|
|
251
|
+
* @param {number} offset - The offset of the label from the trendline
|
|
252
|
+
*/
|
|
253
|
+
const addTrendlineLabel = (
|
|
254
|
+
ctx,
|
|
255
|
+
label,
|
|
256
|
+
x1,
|
|
257
|
+
y1,
|
|
258
|
+
x2,
|
|
259
|
+
y2,
|
|
260
|
+
angle,
|
|
261
|
+
labelColor,
|
|
262
|
+
family,
|
|
263
|
+
size,
|
|
264
|
+
offset
|
|
265
|
+
) => {
|
|
266
|
+
// Set the label font and color
|
|
267
|
+
ctx.font = `${size}px ${family}`;
|
|
268
|
+
ctx.fillStyle = labelColor;
|
|
269
|
+
|
|
270
|
+
// Calculate label position (middle of the trendline)
|
|
271
|
+
const labelX = (x1 + x2) / 2;
|
|
272
|
+
const labelY = (y1 + y2) / 2;
|
|
273
|
+
|
|
274
|
+
// Save the current state of the canvas
|
|
275
|
+
ctx.save();
|
|
276
|
+
|
|
277
|
+
// Translate to the label position
|
|
278
|
+
ctx.translate(labelX, labelY);
|
|
279
|
+
|
|
280
|
+
// Rotate the context to align with the trendline
|
|
281
|
+
ctx.rotate(angle);
|
|
282
|
+
|
|
283
|
+
// Draw the label
|
|
284
|
+
ctx.fillText(label, 0, -offset);
|
|
285
|
+
|
|
286
|
+
// Restore the canvas state
|
|
287
|
+
ctx.restore();
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Adjusts the line if it overflows below the chart bottom.
|
|
292
|
+
* @param {Object} params - The line parameters.
|
|
293
|
+
* @param {number} params.x1 - Starting x-coordinate of the trendline.
|
|
294
|
+
* @param {number} params.y1 - Starting y-coordinate of the trendline.
|
|
295
|
+
* @param {number} params.x2 - Ending x-coordinate of the trendline.
|
|
296
|
+
* @param {number} params.y2 - Ending y-coordinate of the trendline.
|
|
297
|
+
* @param {number} params.drawBottom - Bottom boundary of the chart.
|
|
298
|
+
* @param {number} params.chartWidth - Width of the chart.
|
|
299
|
+
*/
|
|
300
|
+
const adjustLineForOverflow = ({ x1, y1, x2, y2, drawBottom, chartWidth }) => {
|
|
126
301
|
if (y1 > drawBottom) {
|
|
127
|
-
// Left side is below zero
|
|
128
302
|
let diff = y1 - drawBottom;
|
|
129
303
|
let lineHeight = y1 - y2;
|
|
130
304
|
let overlapPercentage = diff / lineHeight;
|
|
@@ -133,7 +307,6 @@ const addFitter = (datasetMeta, ctx, dataset, xScale, yScale) => {
|
|
|
133
307
|
y1 = drawBottom;
|
|
134
308
|
x1 = x1 + addition;
|
|
135
309
|
} else if (y2 > drawBottom) {
|
|
136
|
-
// right side is below zero
|
|
137
310
|
let diff = y2 - drawBottom;
|
|
138
311
|
let lineHeight = y2 - y1;
|
|
139
312
|
let overlapPercentage = diff / lineHeight;
|
|
@@ -142,95 +315,148 @@ const addFitter = (datasetMeta, ctx, dataset, xScale, yScale) => {
|
|
|
142
315
|
y2 = drawBottom;
|
|
143
316
|
x2 = chartWidth - (x2 - subtraction);
|
|
144
317
|
}
|
|
318
|
+
};
|
|
145
319
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
320
|
+
/**
|
|
321
|
+
* Sets the line style (dashed, dotted, solid) for the canvas context.
|
|
322
|
+
* @param {CanvasRenderingContext2D} ctx - The canvas rendering context.
|
|
323
|
+
* @param {string} lineStyle - The style of the line ('dotted', 'dashed', 'solid', etc.).
|
|
324
|
+
*/
|
|
325
|
+
const setLineStyle = (ctx, lineStyle) => {
|
|
326
|
+
switch (lineStyle) {
|
|
327
|
+
case 'dotted':
|
|
328
|
+
ctx.setLineDash([2, 2]);
|
|
329
|
+
break;
|
|
330
|
+
case 'dashed':
|
|
331
|
+
ctx.setLineDash([8, 3]);
|
|
332
|
+
break;
|
|
333
|
+
case 'dashdot':
|
|
334
|
+
ctx.setLineDash([8, 3, 2, 3]);
|
|
335
|
+
break;
|
|
336
|
+
case 'solid':
|
|
337
|
+
default:
|
|
338
|
+
ctx.setLineDash([]);
|
|
339
|
+
break;
|
|
152
340
|
}
|
|
341
|
+
};
|
|
153
342
|
|
|
343
|
+
/**
|
|
344
|
+
* Draws the trendline on the canvas context.
|
|
345
|
+
* @param {Object} params - The trendline parameters.
|
|
346
|
+
* @param {CanvasRenderingContext2D} params.ctx - The canvas rendering context.
|
|
347
|
+
* @param {number} params.x1 - Starting x-coordinate of the trendline.
|
|
348
|
+
* @param {number} params.y1 - Starting y-coordinate of the trendline.
|
|
349
|
+
* @param {number} params.x2 - Ending x-coordinate of the trendline.
|
|
350
|
+
* @param {number} params.y2 - Ending y-coordinate of the trendline.
|
|
351
|
+
* @param {string} params.colorMin - The starting color of the trendline gradient.
|
|
352
|
+
* @param {string} params.colorMax - The ending color of the trendline gradient.
|
|
353
|
+
*/
|
|
354
|
+
const drawTrendline = ({ ctx, x1, y1, x2, y2, colorMin, colorMax }) => {
|
|
154
355
|
ctx.beginPath();
|
|
155
356
|
ctx.moveTo(x1, y1);
|
|
156
357
|
ctx.lineTo(x2, y2);
|
|
157
358
|
|
|
158
359
|
let gradient = ctx.createLinearGradient(x1, y1, x2, y2);
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
gradient.addColorStop(1, colorMin);
|
|
162
|
-
} else {
|
|
163
|
-
gradient.addColorStop(0, colorMin);
|
|
164
|
-
gradient.addColorStop(1, colorMax);
|
|
165
|
-
}
|
|
360
|
+
gradient.addColorStop(0, colorMin);
|
|
361
|
+
gradient.addColorStop(1, colorMax);
|
|
166
362
|
|
|
167
363
|
ctx.strokeStyle = gradient;
|
|
168
|
-
|
|
169
364
|
ctx.stroke();
|
|
170
365
|
ctx.closePath();
|
|
366
|
+
};
|
|
171
367
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
368
|
+
/**
|
|
369
|
+
* Fills the area below the trendline with the specified color.
|
|
370
|
+
* @param {CanvasRenderingContext2D} ctx - The canvas rendering context.
|
|
371
|
+
* @param {number} x1 - Starting x-coordinate of the trendline.
|
|
372
|
+
* @param {number} y1 - Starting y-coordinate of the trendline.
|
|
373
|
+
* @param {number} x2 - Ending x-coordinate of the trendline.
|
|
374
|
+
* @param {number} y2 - Ending y-coordinate of the trendline.
|
|
375
|
+
* @param {number} drawBottom - The bottom boundary of the chart.
|
|
376
|
+
* @param {string} fillColor - The color to fill below the trendline.
|
|
377
|
+
*/
|
|
378
|
+
const fillBelowTrendline = (ctx, x1, y1, x2, y2, drawBottom, fillColor) => {
|
|
379
|
+
ctx.beginPath();
|
|
380
|
+
ctx.moveTo(x1, y1);
|
|
381
|
+
ctx.lineTo(x2, y2);
|
|
382
|
+
ctx.lineTo(x2, drawBottom);
|
|
383
|
+
ctx.lineTo(x1, drawBottom);
|
|
384
|
+
ctx.lineTo(x1, y1);
|
|
385
|
+
ctx.closePath();
|
|
386
|
+
|
|
387
|
+
ctx.fillStyle = fillColor;
|
|
388
|
+
ctx.fill();
|
|
182
389
|
};
|
|
183
390
|
|
|
391
|
+
/**
|
|
392
|
+
* A class that fits a line to a series of points using least squares.
|
|
393
|
+
*/
|
|
184
394
|
class LineFitter {
|
|
185
395
|
constructor() {
|
|
186
396
|
this.count = 0;
|
|
187
|
-
this.
|
|
188
|
-
this.
|
|
189
|
-
this.
|
|
190
|
-
this.
|
|
191
|
-
this.minx =
|
|
192
|
-
this.maxx =
|
|
193
|
-
this.maxy = -1e100;
|
|
397
|
+
this.sumx = 0;
|
|
398
|
+
this.sumy = 0;
|
|
399
|
+
this.sumx2 = 0;
|
|
400
|
+
this.sumxy = 0;
|
|
401
|
+
this.minx = Number.MAX_VALUE;
|
|
402
|
+
this.maxx = Number.MIN_VALUE;
|
|
194
403
|
}
|
|
195
404
|
|
|
405
|
+
/**
|
|
406
|
+
* Adds a point to the line fitter.
|
|
407
|
+
* @param {number} x - The x-coordinate of the point.
|
|
408
|
+
* @param {number} y - The y-coordinate of the point.
|
|
409
|
+
*/
|
|
196
410
|
add(x, y) {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
this.
|
|
201
|
-
this.sumX += x;
|
|
202
|
-
this.sumX2 += x * x;
|
|
203
|
-
this.sumXY += x * y;
|
|
204
|
-
this.sumY += y;
|
|
411
|
+
this.sumx += x;
|
|
412
|
+
this.sumy += y;
|
|
413
|
+
this.sumx2 += x * x;
|
|
414
|
+
this.sumxy += x * y;
|
|
205
415
|
if (x < this.minx) this.minx = x;
|
|
206
416
|
if (x > this.maxx) this.maxx = x;
|
|
207
|
-
|
|
417
|
+
this.count++;
|
|
208
418
|
}
|
|
209
419
|
|
|
210
|
-
|
|
211
|
-
|
|
420
|
+
/**
|
|
421
|
+
* Calculates the slope of the fitted line.
|
|
422
|
+
* @returns {number} - The slope of the line.
|
|
423
|
+
*/
|
|
424
|
+
slope() {
|
|
425
|
+
const denominator = this.count * this.sumx2 - this.sumx * this.sumx;
|
|
426
|
+
return (this.count * this.sumxy - this.sumx * this.sumy) / denominator;
|
|
427
|
+
}
|
|
212
428
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
429
|
+
/**
|
|
430
|
+
* Calculates the y-intercept of the fitted line.
|
|
431
|
+
* @returns {number} - The y-intercept of the line.
|
|
432
|
+
*/
|
|
433
|
+
intercept() {
|
|
434
|
+
return (this.sumy - this.slope() * this.sumx) / this.count;
|
|
217
435
|
}
|
|
218
436
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
437
|
+
/**
|
|
438
|
+
* Returns the fitted value (y) for a given x.
|
|
439
|
+
* @param {number} x - The x-coordinate.
|
|
440
|
+
* @returns {number} - The corresponding y-coordinate on the fitted line.
|
|
441
|
+
*/
|
|
442
|
+
f(x) {
|
|
443
|
+
return this.slope() * x + this.intercept();
|
|
444
|
+
}
|
|
223
445
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
446
|
+
/**
|
|
447
|
+
* Calculates the projection of the line for the future value.
|
|
448
|
+
* @returns {number} - The future value based on the fitted line.
|
|
449
|
+
*/
|
|
450
|
+
fo() {
|
|
451
|
+
return -this.intercept() / this.slope();
|
|
227
452
|
}
|
|
228
453
|
|
|
454
|
+
/**
|
|
455
|
+
* Returns the scale (variance) of the fitted line.
|
|
456
|
+
* @returns {number} - The scale of the fitted line.
|
|
457
|
+
*/
|
|
229
458
|
scale() {
|
|
230
|
-
|
|
231
|
-
let scale = (this.count * this.sumXY - this.sumX * this.sumY) / det;
|
|
232
|
-
|
|
233
|
-
return scale;
|
|
459
|
+
return this.slope();
|
|
234
460
|
}
|
|
235
461
|
}
|
|
236
462
|
|
|
@@ -1,79 +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
|
-
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
|
7
|
-
<title>BarChart Example</title>
|
|
8
|
-
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.3/dist/chart.umd.js"></script>
|
|
9
|
-
<script src="./../src/chartjs-plugin-trendline.js"></script>
|
|
10
|
-
<script>
|
|
11
|
-
|
|
12
|
-
const datasets = [{
|
|
13
|
-
data: [
|
|
14
|
-
{ x: '202309', y: 144214.8 },
|
|
15
|
-
{ x: '202310', y: 144514.8 },
|
|
16
|
-
{ x: '202311', y: 150626 },
|
|
17
|
-
{ x: '202312', y: 204523 },
|
|
18
|
-
{ x: '202401', y: 125402 },
|
|
19
|
-
{ x: '202402', y: 225232 },
|
|
20
|
-
{ x: '202403', y: 2786435 },
|
|
21
|
-
{ x: '202404', y: 584833 },
|
|
22
|
-
],
|
|
23
|
-
label: 'Usage Not Working!',
|
|
24
|
-
yAxisID: 'y',
|
|
25
|
-
lineTension: 0,
|
|
26
|
-
borderWidth: 2,
|
|
27
|
-
trendlineLinear: {
|
|
28
|
-
colorMin: '#ff0000',
|
|
29
|
-
colorMax: '#00ff00',
|
|
30
|
-
lineStyle: 'dotted',
|
|
31
|
-
width: 2,
|
|
32
|
-
yAxisKey: 'y',
|
|
33
|
-
xAxisKey: 'x',
|
|
34
|
-
projection: true,
|
|
35
|
-
},
|
|
36
|
-
}, ]
|
|
37
|
-
const chartConfig = {
|
|
38
|
-
type: 'line',
|
|
39
|
-
data: {
|
|
40
|
-
datasets: datasets
|
|
41
|
-
},
|
|
42
|
-
options: {
|
|
43
|
-
scales: {
|
|
44
|
-
x: {
|
|
45
|
-
ticks: {
|
|
46
|
-
padding: 8
|
|
47
|
-
},
|
|
48
|
-
},
|
|
49
|
-
y: {
|
|
50
|
-
type: 'linear',
|
|
51
|
-
display: true,
|
|
52
|
-
position: 'left',
|
|
53
|
-
title: {
|
|
54
|
-
display: true,
|
|
55
|
-
text: 'Usage',
|
|
56
|
-
padding: 8,
|
|
57
|
-
},
|
|
58
|
-
},
|
|
59
|
-
},
|
|
60
|
-
},
|
|
61
|
-
plugins: [pluginTrendlineLinear],
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
document.addEventListener("DOMContentLoaded", function(event) {
|
|
65
|
-
// Bar chart
|
|
66
|
-
new Chart(document.getElementById("bar-chart"), chartConfig);
|
|
67
|
-
});
|
|
68
|
-
</script>
|
|
69
|
-
</head>
|
|
70
|
-
<body>
|
|
71
|
-
<h1>Bar Chart</h1>
|
|
72
|
-
|
|
73
|
-
<div style="width: 800px;">
|
|
74
|
-
<canvas id="bar-chart"></canvas>
|
|
75
|
-
</div>
|
|
76
|
-
|
|
77
|
-
<p>Using example code from <a href="http://tobiasahlin.com/blog/chartjs-charts-to-get-you-started/" target="_blank">tobiasahlin.com.</a></p>
|
|
78
|
-
</body>
|
|
79
|
-
</html>
|
|
@@ -1,72 +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
|
-
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
|
7
|
-
<title>BarChart Example</title>
|
|
8
|
-
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.3/dist/chart.umd.js"></script>
|
|
9
|
-
<script src="./../src/chartjs-plugin-trendline.js"></script>
|
|
10
|
-
<script>
|
|
11
|
-
|
|
12
|
-
const datasets = [{
|
|
13
|
-
data: [144214.8, 144514.8, 150626, 204523, 125402, 225232, 2786435, 584833],
|
|
14
|
-
label: 'Usage',
|
|
15
|
-
yAxisID: 'y',
|
|
16
|
-
lineTension: 0,
|
|
17
|
-
borderWidth: 2,
|
|
18
|
-
trendlineLinear: {
|
|
19
|
-
colorMin: '#ff0000',
|
|
20
|
-
colorMax: '#00ff00',
|
|
21
|
-
lineStyle: 'dotted',
|
|
22
|
-
width: 2,
|
|
23
|
-
yAxisKey: 'y',
|
|
24
|
-
xAxisKey: 'x',
|
|
25
|
-
projection: true,
|
|
26
|
-
},
|
|
27
|
-
}, ]
|
|
28
|
-
|
|
29
|
-
const chartConfig = {
|
|
30
|
-
type: 'line',
|
|
31
|
-
data: {
|
|
32
|
-
datasets: datasets,
|
|
33
|
-
labels: ['202309', '202310', '202311', '202401', '202402', '202403', '202404', '20240'],
|
|
34
|
-
},
|
|
35
|
-
options: {
|
|
36
|
-
scales: {
|
|
37
|
-
x: {
|
|
38
|
-
ticks: {
|
|
39
|
-
padding: 8
|
|
40
|
-
},
|
|
41
|
-
},
|
|
42
|
-
y: {
|
|
43
|
-
type: 'linear',
|
|
44
|
-
display: true,
|
|
45
|
-
position: 'left',
|
|
46
|
-
title: {
|
|
47
|
-
display: true,
|
|
48
|
-
text: 'Usage',
|
|
49
|
-
padding: 8,
|
|
50
|
-
},
|
|
51
|
-
},
|
|
52
|
-
},
|
|
53
|
-
},
|
|
54
|
-
plugins: [pluginTrendlineLinear],
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
document.addEventListener("DOMContentLoaded", function(event) {
|
|
58
|
-
// Bar chart
|
|
59
|
-
new Chart(document.getElementById("bar-chart"), chartConfig);
|
|
60
|
-
});
|
|
61
|
-
</script>
|
|
62
|
-
</head>
|
|
63
|
-
<body>
|
|
64
|
-
<h1>Bar Chart</h1>
|
|
65
|
-
|
|
66
|
-
<div style="width: 800px;">
|
|
67
|
-
<canvas id="bar-chart"></canvas>
|
|
68
|
-
</div>
|
|
69
|
-
|
|
70
|
-
<p>Using example code from <a href="http://tobiasahlin.com/blog/chartjs-charts-to-get-you-started/" target="_blank">tobiasahlin.com.</a></p>
|
|
71
|
-
</body>
|
|
72
|
-
</html>
|