chartjs-plugin-trendline 3.0.2 â 3.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 +63 -2
- package/changelog.md +13 -0
- package/dist/chartjs-plugin-trendline.min.js +1 -1
- package/example/barChart.html +135 -23
- package/example/barChartWithNullValues.html +139 -24
- package/example/barChart_label.html +137 -23
- package/example/exponentialChart.html +245 -0
- package/example/lineChart.html +116 -16
- package/example/lineChartProjection.html +87 -17
- package/example/lineChartTypeTime.html +112 -23
- package/example/scatterChart.html +78 -11
- package/example/scatterProjection.html +88 -20
- package/example/test-null-handling.html +60 -0
- package/index.html +216 -0
- package/package.json +1 -1
- package/src/components/trendline.js +66 -41
- package/src/components/trendline.test.js +304 -0
- package/src/core/plugin.js +5 -4
- package/src/utils/drawing.js +15 -4
- package/src/utils/drawing.test.js +51 -0
- package/src/utils/exponentialFitter.js +114 -0
- package/src/utils/exponentialFitter.test.js +251 -0
package/README.md
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
# chartjs-plugin-trendline
|
|
2
2
|
|
|
3
|
-
This plugin draws
|
|
3
|
+
This plugin draws linear and exponential trendlines in your Chart.
|
|
4
4
|
It has been tested with Chart.js version 4.4.9.
|
|
5
5
|
|
|
6
|
+
## đ [View Live Examples](https://makanz.github.io/chartjs-plugin-trendline/)
|
|
7
|
+
|
|
8
|
+
See the plugin in action with interactive examples for different chart types.
|
|
9
|
+
|
|
6
10
|
## Installation
|
|
7
11
|
|
|
8
12
|
#### Load directly in the browser
|
|
@@ -31,6 +35,10 @@ ChartJS.plugins.register(chartTrendline);
|
|
|
31
35
|
|
|
32
36
|
To configure the trendline plugin you simply add a new config options to your dataset in your chart config.
|
|
33
37
|
|
|
38
|
+
### Linear Trendlines
|
|
39
|
+
|
|
40
|
+
For linear trendlines (straight lines), use the `trendlineLinear` configuration:
|
|
41
|
+
|
|
34
42
|
```javascript
|
|
35
43
|
{
|
|
36
44
|
trendlineLinear: {
|
|
@@ -47,7 +55,7 @@ To configure the trendline plugin you simply add a new config options to your da
|
|
|
47
55
|
color: Color,
|
|
48
56
|
text: string,
|
|
49
57
|
display: boolean,
|
|
50
|
-
displayValue: boolean,
|
|
58
|
+
displayValue: boolean, // shows slope value
|
|
51
59
|
offset: number,
|
|
52
60
|
percentage: boolean,
|
|
53
61
|
font: {
|
|
@@ -68,12 +76,65 @@ To configure the trendline plugin you simply add a new config options to your da
|
|
|
68
76
|
}
|
|
69
77
|
```
|
|
70
78
|
|
|
79
|
+
### Exponential Trendlines
|
|
80
|
+
|
|
81
|
+
For exponential trendlines (curves of the form y = a Ă e^(bĂx)), use the `trendlineExponential` configuration:
|
|
82
|
+
|
|
83
|
+
```javascript
|
|
84
|
+
{
|
|
85
|
+
trendlineExponential: {
|
|
86
|
+
colorMin: Color,
|
|
87
|
+
colorMax: Color,
|
|
88
|
+
lineStyle: string, // "dotted" | "solid" | "dashed" | "dashdot"
|
|
89
|
+
width: number,
|
|
90
|
+
xAxisKey: string, // optional
|
|
91
|
+
yAxisKey: string, // optional
|
|
92
|
+
projection: boolean, // optional
|
|
93
|
+
trendoffset: number, // optional, if > 0 skips first n elements, if < 0 uses last n elements
|
|
94
|
+
// optional
|
|
95
|
+
label: {
|
|
96
|
+
color: Color,
|
|
97
|
+
text: string,
|
|
98
|
+
display: boolean,
|
|
99
|
+
displayValue: boolean, // shows exponential parameters (a, b)
|
|
100
|
+
offset: number,
|
|
101
|
+
font: {
|
|
102
|
+
family: string,
|
|
103
|
+
size: number,
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
// optional
|
|
107
|
+
legend: {
|
|
108
|
+
text: string,
|
|
109
|
+
strokeStyle: Color,
|
|
110
|
+
fillStyle: Color,
|
|
111
|
+
lineCap: string,
|
|
112
|
+
lineDash: number[],
|
|
113
|
+
lineWidth: number,
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
**Note:** Exponential trendlines work best with positive y-values. The equation fitted is y = a Ă e^(bĂx), where:
|
|
120
|
+
- `a` is the coefficient (scaling factor)
|
|
121
|
+
- `b` is the growth rate (positive for growth, negative for decay)
|
|
122
|
+
|
|
123
|
+
## Examples
|
|
124
|
+
|
|
125
|
+
- [Linear Trendline Example](./example/lineChart.html)
|
|
126
|
+
- [Exponential Trendline Example](./example/exponentialChart.html)
|
|
127
|
+
- [Bar Chart with Trendline](./example/barChart.html)
|
|
128
|
+
- [Scatter Chart with Trendline](./example/scatterChart.html)
|
|
129
|
+
|
|
71
130
|
## Supported chart types
|
|
72
131
|
|
|
73
132
|
- bar
|
|
74
133
|
- line
|
|
75
134
|
- scatter
|
|
76
135
|
|
|
136
|
+
Both linear and exponential trendlines are supported for all chart types.
|
|
137
|
+
|
|
77
138
|
## Contributing
|
|
78
139
|
|
|
79
140
|
Pull requests and issues are always welcome.
|
package/changelog.md
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
# Changelog 3.1.0
|
|
2
|
+
|
|
3
|
+
### New Features
|
|
4
|
+
- Added exponential trendline support with `trendlineExponential` configuration
|
|
5
|
+
- Exponential trendlines fit curves of the form y = a Ă e^(bĂx)
|
|
6
|
+
- All existing styling options (colors, width, lineStyle, projection, etc.) work with exponential trendlines
|
|
7
|
+
- Added comprehensive test coverage for exponential functionality
|
|
8
|
+
- Added exponential trendline example (exponentialChart.html)
|
|
9
|
+
|
|
10
|
+
### Improvements
|
|
11
|
+
- Updated README with exponential trendline documentation
|
|
12
|
+
- Enhanced package description to mention exponential support
|
|
13
|
+
|
|
1
14
|
# Changelog 3.0.0
|
|
2
15
|
|
|
3
16
|
### Breaking Changes
|
|
@@ -1 +1 @@
|
|
|
1
|
-
(()=>{"use strict";class
|
|
1
|
+
(()=>{"use strict";class t{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(t,e){this.sumx+=t,this.sumy+=e,this.sumx2+=t*t,this.sumxy+=t*e,t<this.minx&&(this.minx=t),t>this.maxx&&(this.maxx=t),this.count++}slope(){const t=this.count*this.sumx2-this.sumx*this.sumx;return(this.count*this.sumxy-this.sumx*this.sumy)/t}intercept(){return(this.sumy-this.slope()*this.sumx)/this.count}f(t){return this.slope()*t+this.intercept()}fo(){return-this.intercept()/this.slope()}scale(){return this.slope()}}class e{constructor(){this.count=0,this.sumx=0,this.sumlny=0,this.sumx2=0,this.sumxlny=0,this.minx=Number.MAX_VALUE,this.maxx=Number.MIN_VALUE,this.hasValidData=!0,this.dataPoints=[]}add(t,e){if(e<=0)return void(this.hasValidData=!1);const i=Math.log(e);isFinite(i)?(this.sumx+=t,this.sumlny+=i,this.sumx2+=t*t,this.sumxlny+=t*i,t<this.minx&&(this.minx=t),t>this.maxx&&(this.maxx=t),this.dataPoints.push({x:t,y:e,lny:i}),this.count++):this.hasValidData=!1}growthRate(){if(!this.hasValidData||this.count<2)return 0;const t=this.count*this.sumx2-this.sumx*this.sumx;return Math.abs(t)<1e-10?0:(this.count*this.sumxlny-this.sumx*this.sumlny)/t}coefficient(){if(!this.hasValidData||this.count<2)return 1;const t=(this.sumlny-this.growthRate()*this.sumx)/this.count;return Math.exp(t)}f(t){if(!this.hasValidData||this.count<2)return 0;const e=this.coefficient(),i=this.growthRate();if(Math.abs(i*t)>500)return 0;const s=e*Math.exp(i*t);return isFinite(s)?s:0}correlation(){if(!this.hasValidData||this.count<2)return 0;const t=this.sumlny/this.count,e=Math.log(this.coefficient()),i=this.growthRate();let s=0,n=0;for(const a of this.dataPoints){const l=e+i*a.x;s+=Math.pow(a.lny-t,2),n+=Math.pow(a.lny-l,2)}return 0===s?1:Math.max(0,1-n/s)}scale(){return this.growthRate()}}const i={id:"chartjs-plugin-trendline",afterDatasetsDraw:i=>{const s=i.ctx,{xScale:n,yScale:a}=(t=>{let e,i;for(const s of Object.values(t.scales))if(s.isHorizontal()?e=s:i=s,e&&i)break;return{xScale:e,yScale:i}})(i);i.data.datasets.map(((t,e)=>({dataset:t,index:e}))).filter((t=>t.dataset.trendlineLinear||t.dataset.trendlineExponential)).sort(((t,e)=>{const i=t.dataset.order??0,s=e.dataset.order??0;return 0===i&&0!==s?1:0===s&&0!==i?-1:i-s})).forEach((({dataset:l,index:o})=>{if((l.alwaysShowTrendline||i.isDatasetVisible(o))&&l.data.length>1){((i,s,n,a,l)=>{const o=n.yAxisID||"y",r=i.controller.chart.scales[o]||l,h=!!n.trendlineExponential,c=n.trendlineExponential||n.trendlineLinear||{},u=n.borderColor||"rgba(169,169,169, .6)",{colorMin:x=u,colorMax:d=u,width:f=n.borderWidth||3,lineStyle:y="solid",fillColor:m=!1}=c;let g=c.trendoffset||0;const{color:p=u,text:b=(h?"Exponential Trendline":"Trendline"),display:F=!0,displayValue:w=!0,offset:V=10,percentage:M=!1}=c&&c.label||{},{family:P="'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",size:N=12}=c&&c.label&&c.label.font||{},D=i.controller.chart.options,S="object"==typeof D.parsing?D.parsing:void 0,L=c?.xAxisKey||S?.xAxisKey||"x",T=c?.yAxisKey||S?.yAxisKey||"y";let A=h?new e:new t;Math.abs(g)>=n.data.length&&(g=0);let C=0;if(g>0){const t=n.data.slice(g).findIndex((t=>null!=t));C=-1!==t?g+t:n.data.length}else{const t=n.data.findIndex((t=>null!=t));C=-1!==t?t:n.data.length}let v,E,k,I,$=C<n.data.length&&"object"==typeof n.data[C];if(n.data.forEach(((t,e)=>{if(null!=t&&!(g>0&&e<C||g<0&&e>=n.data.length+g))if(["time","timeseries"].includes(a.options.type)&&$){let e=null!=t[L]?t[L]:t.t;const i=t[T];null==e||void 0===e||null==i||isNaN(i)||A.add(new Date(e).getTime(),i)}else if($){const e=t[L],i=t[T],s=null!=e&&!isNaN(e),n=null!=i&&!isNaN(i);s&&n&&A.add(e,i)}else if(["time","timeseries"].includes(a.options.type)&&!$){const s=i.controller.chart.data.labels;if(s&&s[e]&&null!=t&&!isNaN(t)){const i=new Date(s[e]).getTime();isNaN(i)||A.add(i,t)}}else null==t||isNaN(t)||A.add(e,t)})),A.count<2)return;const R=i.controller.chart.chartArea;if(c.projection){let t=[];if(h){const e=a.getValueForPixel(R.left),i=A.f(e);t.push({x:e,y:i});const s=a.getValueForPixel(R.right),n=A.f(s);t.push({x:s,y:n})}else{const e=A.slope(),i=A.intercept();if(Math.abs(e)>1e-6){const s=r.getValueForPixel(R.top),n=(s-i)/e;t.push({x:n,y:s});const a=r.getValueForPixel(R.bottom),l=(a-i)/e;t.push({x:l,y:a})}else t.push({x:a.getValueForPixel(R.left),y:i}),t.push({x:a.getValueForPixel(R.right),y:i});const s=a.getValueForPixel(R.left),n=A.f(s);t.push({x:s,y:n});const l=a.getValueForPixel(R.right),o=A.f(l);t.push({x:l,y:o})}const e=a.getValueForPixel(R.left),i=a.getValueForPixel(R.right),s=[r.getValueForPixel(R.top),r.getValueForPixel(R.bottom)].filter((t=>isFinite(t))),n=s.length>0?Math.min(...s):-1/0,l=s.length>0?Math.max(...s):1/0;let o=t.filter((t=>isFinite(t.x)&&isFinite(t.y)&&t.x>=e&&t.x<=i&&t.y>=n&&t.y<=l));o=o.filter(((t,e,i)=>e===i.findIndex((e=>Math.abs(e.x-t.x)<1e-4&&Math.abs(e.y-t.y)<1e-4)))),o.length>=2?(o.sort(((t,e)=>t.x-e.x||t.y-e.y)),v=a.getPixelForValue(o[0].x),E=r.getPixelForValue(o[0].y),k=a.getPixelForValue(o[o.length-1].x),I=r.getPixelForValue(o[o.length-1].y)):(v=NaN,E=NaN,k=NaN,I=NaN)}else{const t=A.f(A.minx),e=A.f(A.maxx);v=a.getPixelForValue(A.minx),E=r.getPixelForValue(t),k=a.getPixelForValue(A.maxx),I=r.getPixelForValue(e)}let j=null;if(isFinite(v)&&isFinite(E)&&isFinite(k)&&isFinite(I)&&(j=function(t,e,i,s,n){let a=i-t,l=s-e,o=0,r=1;const h=[-a,a,-l,l],c=[t-n.left,n.right-t,e-n.top,n.bottom-e];for(let t=0;t<4;t++)if(0===h[t]){if(c[t]<0)return null}else{const e=c[t]/h[t];if(h[t]<0){if(e>r)return null;o=Math.max(o,e)}else{if(e<o)return null;r=Math.min(r,e)}}return o>r?null:{x1:t+o*a,y1:e+o*l,x2:t+r*a,y2:e+r*l}}(v,E,k,I,R)),j)if(v=j.x1,E=j.y1,k=j.x2,I=j.y2,Math.abs(v-k)<.5&&Math.abs(E-I)<.5);else{s.lineWidth=f,((t,e)=>{switch(e){case"dotted":t.setLineDash([2,2]);break;case"dashed":t.setLineDash([8,3]);break;case"dashdot":t.setLineDash([8,3,2,3]);break;default:t.setLineDash([])}})(s,y),(({ctx:t,x1:e,y1:i,x2:s,y2:n,colorMin:a,colorMax:l})=>{if(isFinite(e)&&isFinite(i)&&isFinite(s)&&isFinite(n)){t.beginPath(),t.moveTo(e,i),t.lineTo(s,n);try{const o=s-e,r=n-i,h=Math.sqrt(o*o+r*r);if(h<.01)console.warn("Gradient vector too small, using solid color:",{x1:e,y1:i,x2:s,y2:n,length:h}),t.strokeStyle=a;else{let o=t.createLinearGradient(e,i,s,n);o.addColorStop(0,a),o.addColorStop(1,l),t.strokeStyle=o}}catch(e){console.warn("Gradient creation failed, using solid color:",e),t.strokeStyle=a}t.stroke(),t.closePath()}else console.warn("Cannot draw trendline: coordinates contain non-finite values",{x1:e,y1:i,x2:s,y2:n})})({ctx:s,x1:v,y1:E,x2:k,y2:I,colorMin:x,colorMax:d}),m&&((t,e,i,s,n,a,l)=>{isFinite(e)&&isFinite(i)&&isFinite(s)&&isFinite(n)&&isFinite(a)?(t.beginPath(),t.moveTo(e,i),t.lineTo(s,n),t.lineTo(s,a),t.lineTo(e,a),t.lineTo(e,i),t.closePath(),t.fillStyle=l,t.fill()):console.warn("Cannot fill below trendline: coordinates contain non-finite values",{x1:e,y1:i,x2:s,y2:n,drawBottom:a})})(s,v,E,k,I,R.bottom,m);const t=Math.atan2(I-E,k-v);if(c.label&&!1!==F){let e=b;if(w)if(h){const t=A.coefficient(),i=A.growthRate();e=`${b} (a=${t.toFixed(2)}, b=${i.toFixed(2)})`}else{const t=A.slope();e=`${b} (Slope: ${M?(100*t).toFixed(2)+"%":t.toFixed(2)})`}((t,e,i,s,n,a,l,o,r,h,c)=>{t.font=`${h}px ${r}`,t.fillStyle=o;const u=t.measureText(e).width,x=(i+n)/2,d=(s+a)/2;t.save(),t.translate(x,d),t.rotate(l);const f=-u/2,y=c;t.fillText(e,f,y),t.restore()})(s,e,v,E,k,I,t,p,P,N,V)}}})(i.getDatasetMeta(o),s,l,n,a)}})),s.setLineDash([])},beforeInit:t=>{t.data.datasets.forEach((e=>{const i=e.trendlineLinear||e.trendlineExponential;if(i&&i.label){const s=i.label,n=t.legend.options.labels.generateLabels;t.legend.options.labels.generateLabels=function(t){const a=n(t),l=i.legend;return l&&!1!==l.display&&a.push({text:l.text||s+" (Trendline)",strokeStyle:l.color||e.borderColor||"rgba(169,169,169, .6)",fillStyle:l.fillStyle||"transparent",lineCap:l.lineCap||"butt",lineDash:l.lineDash||[],lineWidth:l.width||1}),a}}}))}};"undefined"!=typeof window&&window.Chart&&(window.Chart.hasOwnProperty("register")?window.Chart.register(i):window.Chart.plugins.register(i))})();
|
package/example/barChart.html
CHANGED
|
@@ -1,16 +1,116 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
2
|
<html lang="en">
|
|
3
|
-
|
|
4
3
|
<head>
|
|
5
4
|
<meta charset="UTF-8">
|
|
6
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
6
|
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
|
8
|
-
<title>
|
|
9
|
-
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.
|
|
10
|
-
<script src="
|
|
7
|
+
<title>Bar Chart - Chart.js Trendline Plugin</title>
|
|
8
|
+
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.5.0/dist/chart.umd.js"></script>
|
|
9
|
+
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-trendline/dist/chartjs-plugin-trendline.min.js"></script>
|
|
10
|
+
<style>
|
|
11
|
+
body {
|
|
12
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
13
|
+
margin: 0;
|
|
14
|
+
padding: 40px 20px;
|
|
15
|
+
background-color: #f8f9fa;
|
|
16
|
+
color: #333;
|
|
17
|
+
line-height: 1.6;
|
|
18
|
+
}
|
|
19
|
+
.container {
|
|
20
|
+
max-width: 1000px;
|
|
21
|
+
margin: 0 auto;
|
|
22
|
+
}
|
|
23
|
+
h1 {
|
|
24
|
+
text-align: center;
|
|
25
|
+
color: #2c3e50;
|
|
26
|
+
margin-bottom: 10px;
|
|
27
|
+
font-size: 2.5rem;
|
|
28
|
+
}
|
|
29
|
+
.subtitle {
|
|
30
|
+
text-align: center;
|
|
31
|
+
color: #7f8c8d;
|
|
32
|
+
margin-bottom: 40px;
|
|
33
|
+
font-size: 1.1rem;
|
|
34
|
+
}
|
|
35
|
+
.chart-container {
|
|
36
|
+
background: white;
|
|
37
|
+
padding: 30px;
|
|
38
|
+
border-radius: 8px;
|
|
39
|
+
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
|
40
|
+
margin-bottom: 30px;
|
|
41
|
+
}
|
|
42
|
+
.chart-wrapper {
|
|
43
|
+
position: relative;
|
|
44
|
+
height: 400px;
|
|
45
|
+
}
|
|
46
|
+
.info-section {
|
|
47
|
+
background: white;
|
|
48
|
+
padding: 25px;
|
|
49
|
+
border-radius: 8px;
|
|
50
|
+
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
|
51
|
+
margin-bottom: 20px;
|
|
52
|
+
}
|
|
53
|
+
.info-section h3 {
|
|
54
|
+
margin-top: 0;
|
|
55
|
+
color: #2c3e50;
|
|
56
|
+
border-bottom: 2px solid #3498db;
|
|
57
|
+
padding-bottom: 10px;
|
|
58
|
+
}
|
|
59
|
+
.nav-links {
|
|
60
|
+
text-align: center;
|
|
61
|
+
margin-top: 30px;
|
|
62
|
+
}
|
|
63
|
+
.nav-links a {
|
|
64
|
+
display: inline-block;
|
|
65
|
+
margin: 0 10px;
|
|
66
|
+
padding: 10px 20px;
|
|
67
|
+
background: #3498db;
|
|
68
|
+
color: white;
|
|
69
|
+
text-decoration: none;
|
|
70
|
+
border-radius: 4px;
|
|
71
|
+
font-weight: 500;
|
|
72
|
+
transition: background-color 0.2s;
|
|
73
|
+
}
|
|
74
|
+
.nav-links a:hover {
|
|
75
|
+
background: #2980b9;
|
|
76
|
+
}
|
|
77
|
+
code {
|
|
78
|
+
background: #f8f9fa;
|
|
79
|
+
padding: 2px 6px;
|
|
80
|
+
border-radius: 3px;
|
|
81
|
+
font-family: 'Monaco', 'Menlo', monospace;
|
|
82
|
+
}
|
|
83
|
+
</style>
|
|
84
|
+
</head>
|
|
85
|
+
<body>
|
|
86
|
+
<div class="container">
|
|
87
|
+
<h1>Bar Chart with Trendline</h1>
|
|
88
|
+
<p class="subtitle">Demonstrates linear trendline fitting on bar chart data</p>
|
|
89
|
+
|
|
90
|
+
<div class="chart-container">
|
|
91
|
+
<div class="chart-wrapper">
|
|
92
|
+
<canvas id="bar-chart"></canvas>
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
|
|
96
|
+
<div class="info-section">
|
|
97
|
+
<h3>Configuration Used</h3>
|
|
98
|
+
<p>This example shows a bar chart with a <code>dotted</code> trendline in pink color. The trendline is configured with:</p>
|
|
99
|
+
<ul>
|
|
100
|
+
<li><strong>colorMin:</strong> <code>"rgba(255,105,180, .8)"</code> - Pink semi-transparent color</li>
|
|
101
|
+
<li><strong>lineStyle:</strong> <code>"dotted"</code> - Dotted line pattern</li>
|
|
102
|
+
<li><strong>width:</strong> <code>2</code> - Line width of 2 pixels</li>
|
|
103
|
+
</ul>
|
|
104
|
+
</div>
|
|
105
|
+
|
|
106
|
+
<div class="nav-links">
|
|
107
|
+
<a href="../index.html">â Back to Examples</a>
|
|
108
|
+
<a href="barChart_label.html">Bar Chart with Label â</a>
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
|
|
11
112
|
<script>
|
|
12
|
-
document.addEventListener("DOMContentLoaded", function (
|
|
13
|
-
// Bar chart
|
|
113
|
+
document.addEventListener("DOMContentLoaded", function () {
|
|
14
114
|
new Chart(document.getElementById("bar-chart"), {
|
|
15
115
|
type: 'bar',
|
|
16
116
|
data: {
|
|
@@ -25,30 +125,42 @@
|
|
|
25
125
|
lineStyle: "dotted",
|
|
26
126
|
width: 2
|
|
27
127
|
}
|
|
28
|
-
}
|
|
128
|
+
}
|
|
29
129
|
]
|
|
30
130
|
},
|
|
31
131
|
options: {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
132
|
+
responsive: true,
|
|
133
|
+
maintainAspectRatio: false,
|
|
134
|
+
plugins: {
|
|
135
|
+
legend: {
|
|
136
|
+
display: false
|
|
137
|
+
},
|
|
138
|
+
title: {
|
|
139
|
+
display: true,
|
|
140
|
+
text: 'Predicted World Population (millions) in 2050',
|
|
141
|
+
font: {
|
|
142
|
+
size: 16
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
scales: {
|
|
147
|
+
y: {
|
|
148
|
+
beginAtZero: true,
|
|
149
|
+
title: {
|
|
150
|
+
display: true,
|
|
151
|
+
text: 'Population (millions)'
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
x: {
|
|
155
|
+
title: {
|
|
156
|
+
display: true,
|
|
157
|
+
text: 'Regions'
|
|
158
|
+
}
|
|
159
|
+
}
|
|
36
160
|
}
|
|
37
161
|
}
|
|
38
162
|
});
|
|
39
163
|
});
|
|
40
164
|
</script>
|
|
41
|
-
</head>
|
|
42
|
-
|
|
43
|
-
<body>
|
|
44
|
-
<h1>Bar Chart</h1>
|
|
45
|
-
|
|
46
|
-
<div style="width: 800px;">
|
|
47
|
-
<canvas id="bar-chart"></canvas>
|
|
48
|
-
</div>
|
|
49
|
-
|
|
50
|
-
<p>Using example code from <a href="http://tobiasahlin.com/blog/chartjs-charts-to-get-you-started/"
|
|
51
|
-
target="_blank">tobiasahlin.com.</a></p>
|
|
52
165
|
</body>
|
|
53
|
-
|
|
54
166
|
</html>
|
|
@@ -1,16 +1,119 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
2
|
<html lang="en">
|
|
3
|
-
|
|
4
3
|
<head>
|
|
5
4
|
<meta charset="UTF-8">
|
|
6
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
6
|
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
|
8
|
-
<title>
|
|
9
|
-
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.
|
|
10
|
-
<script src="
|
|
7
|
+
<title>Bar Chart with Null Values - Chart.js Trendline Plugin</title>
|
|
8
|
+
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.5.0/dist/chart.umd.js"></script>
|
|
9
|
+
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-trendline/dist/chartjs-plugin-trendline.min.js"></script>
|
|
10
|
+
<style>
|
|
11
|
+
body {
|
|
12
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
13
|
+
margin: 0;
|
|
14
|
+
padding: 40px 20px;
|
|
15
|
+
background-color: #f8f9fa;
|
|
16
|
+
color: #333;
|
|
17
|
+
line-height: 1.6;
|
|
18
|
+
}
|
|
19
|
+
.container {
|
|
20
|
+
max-width: 1000px;
|
|
21
|
+
margin: 0 auto;
|
|
22
|
+
}
|
|
23
|
+
h1 {
|
|
24
|
+
text-align: center;
|
|
25
|
+
color: #2c3e50;
|
|
26
|
+
margin-bottom: 10px;
|
|
27
|
+
font-size: 2.5rem;
|
|
28
|
+
}
|
|
29
|
+
.subtitle {
|
|
30
|
+
text-align: center;
|
|
31
|
+
color: #7f8c8d;
|
|
32
|
+
margin-bottom: 40px;
|
|
33
|
+
font-size: 1.1rem;
|
|
34
|
+
}
|
|
35
|
+
.chart-container {
|
|
36
|
+
background: white;
|
|
37
|
+
padding: 30px;
|
|
38
|
+
border-radius: 8px;
|
|
39
|
+
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
|
40
|
+
margin-bottom: 30px;
|
|
41
|
+
}
|
|
42
|
+
.chart-wrapper {
|
|
43
|
+
position: relative;
|
|
44
|
+
height: 400px;
|
|
45
|
+
}
|
|
46
|
+
.info-section {
|
|
47
|
+
background: white;
|
|
48
|
+
padding: 25px;
|
|
49
|
+
border-radius: 8px;
|
|
50
|
+
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
|
51
|
+
margin-bottom: 20px;
|
|
52
|
+
}
|
|
53
|
+
.info-section h3 {
|
|
54
|
+
margin-top: 0;
|
|
55
|
+
color: #2c3e50;
|
|
56
|
+
border-bottom: 2px solid #3498db;
|
|
57
|
+
padding-bottom: 10px;
|
|
58
|
+
}
|
|
59
|
+
.nav-links {
|
|
60
|
+
text-align: center;
|
|
61
|
+
margin-top: 30px;
|
|
62
|
+
}
|
|
63
|
+
.nav-links a {
|
|
64
|
+
display: inline-block;
|
|
65
|
+
margin: 0 10px;
|
|
66
|
+
padding: 10px 20px;
|
|
67
|
+
background: #3498db;
|
|
68
|
+
color: white;
|
|
69
|
+
text-decoration: none;
|
|
70
|
+
border-radius: 4px;
|
|
71
|
+
font-weight: 500;
|
|
72
|
+
transition: background-color 0.2s;
|
|
73
|
+
}
|
|
74
|
+
.nav-links a:hover {
|
|
75
|
+
background: #2980b9;
|
|
76
|
+
}
|
|
77
|
+
code {
|
|
78
|
+
background: #f8f9fa;
|
|
79
|
+
padding: 2px 6px;
|
|
80
|
+
border-radius: 3px;
|
|
81
|
+
font-family: 'Monaco', 'Menlo', monospace;
|
|
82
|
+
}
|
|
83
|
+
</style>
|
|
84
|
+
</head>
|
|
85
|
+
<body>
|
|
86
|
+
<div class="container">
|
|
87
|
+
<h1>Bar Chart with Null Values</h1>
|
|
88
|
+
<p class="subtitle">Demonstrates how trendlines handle missing data points (null/undefined)</p>
|
|
89
|
+
|
|
90
|
+
<div class="chart-container">
|
|
91
|
+
<div class="chart-wrapper">
|
|
92
|
+
<canvas id="bar-chart"></canvas>
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
|
|
96
|
+
<div class="info-section">
|
|
97
|
+
<h3>Configuration Used</h3>
|
|
98
|
+
<p>This example shows how the trendline plugin handles datasets containing <code>null</code> and <code>undefined</code> values. The trendline automatically excludes these values from calculations:</p>
|
|
99
|
+
<ul>
|
|
100
|
+
<li><strong>Data points:</strong> Includes <code>undefined</code> and <code>null</code> values that are ignored</li>
|
|
101
|
+
<li><strong>colorMin:</strong> <code>"rgba(255,105,180, .8)"</code> - Pink semi-transparent color</li>
|
|
102
|
+
<li><strong>lineStyle:</strong> <code>"dotted"</code> - Dotted line pattern</li>
|
|
103
|
+
<li><strong>width:</strong> <code>2</code> - Line width of 2 pixels</li>
|
|
104
|
+
</ul>
|
|
105
|
+
<p><strong>Note:</strong> The trendline calculation only uses valid numeric data points, automatically filtering out null/undefined values.</p>
|
|
106
|
+
</div>
|
|
107
|
+
|
|
108
|
+
<div class="nav-links">
|
|
109
|
+
<a href="../index.html">â Back to Examples</a>
|
|
110
|
+
<a href="barChart_label.html">â Bar Chart with Label</a>
|
|
111
|
+
<a href="lineChart.html">Line Chart â</a>
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
|
|
11
115
|
<script>
|
|
12
|
-
document.addEventListener("DOMContentLoaded", function (
|
|
13
|
-
// Bar chart
|
|
116
|
+
document.addEventListener("DOMContentLoaded", function () {
|
|
14
117
|
new Chart(document.getElementById("bar-chart"), {
|
|
15
118
|
type: 'bar',
|
|
16
119
|
data: {
|
|
@@ -18,37 +121,49 @@
|
|
|
18
121
|
datasets: [
|
|
19
122
|
{
|
|
20
123
|
label: "Population (millions)",
|
|
21
|
-
backgroundColor: ["#3e95cd", "#8e5ea2", "#3cba9f", "#e8c3b9", "#c45850"],
|
|
124
|
+
backgroundColor: ["#3e95cd", "#8e5ea2", "#3cba9f", "#e8c3b9", "#c45850", "#666666", "#ff6b6b"],
|
|
22
125
|
data: [2478, 5267, 734, 784, undefined, null, 433],
|
|
23
126
|
trendlineLinear: {
|
|
24
127
|
colorMin: "rgba(255,105,180, .8)",
|
|
25
128
|
lineStyle: "dotted",
|
|
26
129
|
width: 2
|
|
27
130
|
}
|
|
28
|
-
}
|
|
131
|
+
}
|
|
29
132
|
]
|
|
30
133
|
},
|
|
31
134
|
options: {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
135
|
+
responsive: true,
|
|
136
|
+
maintainAspectRatio: false,
|
|
137
|
+
plugins: {
|
|
138
|
+
legend: {
|
|
139
|
+
display: false
|
|
140
|
+
},
|
|
141
|
+
title: {
|
|
142
|
+
display: true,
|
|
143
|
+
text: 'Population Data with Missing Values',
|
|
144
|
+
font: {
|
|
145
|
+
size: 16
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
scales: {
|
|
150
|
+
y: {
|
|
151
|
+
beginAtZero: true,
|
|
152
|
+
title: {
|
|
153
|
+
display: true,
|
|
154
|
+
text: 'Population (millions)'
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
x: {
|
|
158
|
+
title: {
|
|
159
|
+
display: true,
|
|
160
|
+
text: 'Regions'
|
|
161
|
+
}
|
|
162
|
+
}
|
|
36
163
|
}
|
|
37
164
|
}
|
|
38
165
|
});
|
|
39
166
|
});
|
|
40
167
|
</script>
|
|
41
|
-
</head>
|
|
42
|
-
|
|
43
|
-
<body>
|
|
44
|
-
<h1>Bar Chart</h1>
|
|
45
|
-
|
|
46
|
-
<div style="width: 800px;">
|
|
47
|
-
<canvas id="bar-chart"></canvas>
|
|
48
|
-
</div>
|
|
49
|
-
|
|
50
|
-
<p>Using example code from <a href="http://tobiasahlin.com/blog/chartjs-charts-to-get-you-started/"
|
|
51
|
-
target="_blank">tobiasahlin.com.</a></p>
|
|
52
168
|
</body>
|
|
53
|
-
|
|
54
169
|
</html>
|