chartjs-plugin-trendline 3.0.1 → 3.0.3
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/.github/copilot-instructions.md +1 -1
- package/CLAUDE.md +45 -0
- package/GEMINI.md +40 -0
- package/README.md +4 -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/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/utils/drawing.js +15 -4
- package/src/utils/drawing.test.js +51 -0
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Project Overview
|
|
6
|
+
|
|
7
|
+
This is a Chart.js plugin that adds linear trendline support to charts. It fits regression models to datasets and draws trendlines over bar, line, and scatter charts.
|
|
8
|
+
|
|
9
|
+
## Development Commands
|
|
10
|
+
|
|
11
|
+
- `pnpm run build` - Build the minified plugin using webpack
|
|
12
|
+
- `pnpm test` - Run Jest tests for all components
|
|
13
|
+
|
|
14
|
+
## Architecture
|
|
15
|
+
|
|
16
|
+
The plugin follows a modular structure:
|
|
17
|
+
|
|
18
|
+
- **Entry Point**: `src/index.js` - Auto-registers plugin globally and exports for manual registration
|
|
19
|
+
- **Core**: `src/core/plugin.js` - Main plugin lifecycle integration with Chart.js hooks (`afterDatasetsDraw`, `beforeInit`)
|
|
20
|
+
- **Components**: `src/components/` - Rendering logic for trendlines and labels
|
|
21
|
+
- **Utilities**: `src/utils/` - Math calculations (`lineFitter.js`) and drawing helpers (`drawing.js`)
|
|
22
|
+
|
|
23
|
+
## Key Implementation Details
|
|
24
|
+
|
|
25
|
+
- Plugin integrates with Chart.js v4+ lifecycle using `afterDatasetsDraw` hook
|
|
26
|
+
- Trendlines are drawn after datasets to appear on top
|
|
27
|
+
- Dataset ordering is handled via `order` property (0-order datasets draw last)
|
|
28
|
+
- Configuration is added to datasets via `trendlineLinear` property
|
|
29
|
+
- Supports projection mode, custom styling, and labels with legends
|
|
30
|
+
|
|
31
|
+
## Testing
|
|
32
|
+
|
|
33
|
+
All components have corresponding `.test.js` files using Jest. Uses `jest-canvas-mock` for Canvas API mocking.
|
|
34
|
+
|
|
35
|
+
## Coding Standards
|
|
36
|
+
|
|
37
|
+
- Use modern JavaScript (ES6+)
|
|
38
|
+
- 2-space indentation
|
|
39
|
+
- JSDoc for public methods (Google style)
|
|
40
|
+
- Descriptive naming without abbreviations
|
|
41
|
+
- Ensure Chart.js v4+ compatibility
|
|
42
|
+
|
|
43
|
+
## Chart Type Support
|
|
44
|
+
|
|
45
|
+
Currently supports: bar, line, scatter charts
|
package/GEMINI.md
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Chart.js Plugin Trendline — Agent Instructions
|
|
2
|
+
|
|
3
|
+
This plugin adds trendline support to Chart.js charts. It fits a linear regression model to datasets and draws trendlines over bar or line charts.
|
|
4
|
+
|
|
5
|
+
## Project Structure
|
|
6
|
+
|
|
7
|
+
- Root:
|
|
8
|
+
- `package.json`, `webpack.config.js`, `example/*.html`
|
|
9
|
+
- Source (`src/`):
|
|
10
|
+
- `index.js` — Entry point
|
|
11
|
+
- `components/` — Rendering logic (`label.js`, `trendline.js`)
|
|
12
|
+
- `core/` — Plugin definition (`plugin.js`)
|
|
13
|
+
- `utils/` — Math and drawing helpers (`drawing.js`, `lineFitter.js`)
|
|
14
|
+
|
|
15
|
+
## Coding Guidelines
|
|
16
|
+
|
|
17
|
+
1. Use modern JavaScript (ES6+)
|
|
18
|
+
2. Indent with 2 spaces
|
|
19
|
+
3. Use JSDoc for public methods (Google style)
|
|
20
|
+
4. Ensure compatibility with Chart.js v4+
|
|
21
|
+
5. Name files/functions descriptively (no abbreviations)
|
|
22
|
+
|
|
23
|
+
## File Notes
|
|
24
|
+
|
|
25
|
+
- `plugin.js`: Main plugin lifecycle integration
|
|
26
|
+
- `trendline.js`: Handles drawing trendlines
|
|
27
|
+
- `lineFitter.js`: Performs regression math
|
|
28
|
+
- `example/*.html`: Demo charts — manually verify after changes
|
|
29
|
+
|
|
30
|
+
## Workflow Tips
|
|
31
|
+
|
|
32
|
+
1. Changes to `plugin.js` should align with Chart.js plugin lifecycle (e.g., `afterDatasetsDraw`)
|
|
33
|
+
2. If editing `trendline.js`, test with multiple chart types
|
|
34
|
+
3. Validate rendering via example HTMLs after code updates
|
|
35
|
+
4. Run linter and formatter before commit (see `.eslintrc.js` if present)
|
|
36
|
+
|
|
37
|
+
## Restrictions
|
|
38
|
+
|
|
39
|
+
- Do not modify build config unless changing build behavior
|
|
40
|
+
- Do not auto-generate example files
|
package/README.md
CHANGED
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
This plugin draws an linear trendline 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
|
|
@@ -1 +1 @@
|
|
|
1
|
-
(()=>{"use strict";class e{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()}}const t={id:"chartjs-plugin-trendline",afterDatasetsDraw:t=>{const i=t.ctx,{xScale:n,yScale:l}=(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}})(t);t.data.datasets.map(((e,t)=>({dataset:e,index:t}))).filter((e=>e.dataset.trendlineLinear)).sort(((e,t)=>{const i=e.dataset.order??0,n=t.dataset.order??0;return 0===i&&0!==n?1:0===n&&0!==i?-1:i-n})).forEach((({dataset:s,index:a})=>{if((s.alwaysShowTrendline||t.isDatasetVisible(a))&&s.data.length>1){((t,i,n,l,s)=>{const a=n.yAxisID||"y",o=t.controller.chart.scales[a]||s,r=n.borderColor||"rgba(169,169,169, .6)",{colorMin:d=r,colorMax:c=r,width:x=n.borderWidth||3,lineStyle:h="solid",fillColor:u=!1}=n.trendlineLinear||{};let f=(n.trendlineLinear||{}).trendoffset||0;const{color:y=r,text:g="Trendline",display:
|
|
1
|
+
(()=>{"use strict";class e{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()}}const t={id:"chartjs-plugin-trendline",afterDatasetsDraw:t=>{const i=t.ctx,{xScale:n,yScale:l}=(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}})(t);t.data.datasets.map(((e,t)=>({dataset:e,index:t}))).filter((e=>e.dataset.trendlineLinear)).sort(((e,t)=>{const i=e.dataset.order??0,n=t.dataset.order??0;return 0===i&&0!==n?1:0===n&&0!==i?-1:i-n})).forEach((({dataset:s,index:a})=>{if((s.alwaysShowTrendline||t.isDatasetVisible(a))&&s.data.length>1){((t,i,n,l,s)=>{const a=n.yAxisID||"y",o=t.controller.chart.scales[a]||s,r=n.borderColor||"rgba(169,169,169, .6)",{colorMin:d=r,colorMax:c=r,width:x=n.borderWidth||3,lineStyle:h="solid",fillColor:u=!1}=n.trendlineLinear||{};let f=(n.trendlineLinear||{}).trendoffset||0;const{color:y=r,text:g="Trendline",display:m=!0,displayValue:p=!0,offset:b=10,percentage:F=!1}=n.trendlineLinear&&n.trendlineLinear.label||{},{family:w="'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",size:L=12}=n.trendlineLinear&&n.trendlineLinear.label&&n.trendlineLinear.label.font||{},N=t.controller.chart.options,P="object"==typeof N.parsing?N.parsing:void 0,V=n.trendlineLinear?.xAxisKey||P?.xAxisKey||"x",M=n.trendlineLinear?.yAxisKey||P?.yAxisKey||"y";let S=new e;Math.abs(f)>=n.data.length&&(f=0);let D=0;if(f>0){const e=n.data.slice(f).findIndex((e=>null!=e));D=-1!==e?f+e:n.data.length}else{const e=n.data.findIndex((e=>null!=e));D=-1!==e?e:n.data.length}let T,C,v,A,k=D<n.data.length&&"object"==typeof n.data[D];if(n.data.forEach(((e,i)=>{if(null!=e&&!(f>0&&i<D||f<0&&i>=n.data.length+f))if(["time","timeseries"].includes(l.options.type)&&k){let t=null!=e[V]?e[V]:e.t;const i=e[M];null==t||void 0===t||null==i||isNaN(i)||S.add(new Date(t).getTime(),i)}else if(k){const t=e[V],i=e[M],n=null!=t&&!isNaN(t),l=null!=i&&!isNaN(i);n&&l&&S.add(t,i)}else if(["time","timeseries"].includes(l.options.type)&&!k){const n=t.controller.chart.data.labels;if(n&&n[i]&&null!=e&&!isNaN(e)){const t=new Date(n[i]).getTime();isNaN(t)||S.add(t,e)}}else null==e||isNaN(e)||S.add(i,e)})),S.count<2)return;const I=t.controller.chart.chartArea;if(n.trendlineLinear.projection){const e=S.slope(),t=S.intercept();let i=[];if(Math.abs(e)>1e-6){const n=o.getValueForPixel(I.top),l=(n-t)/e;i.push({x:l,y:n});const s=o.getValueForPixel(I.bottom),a=(s-t)/e;i.push({x:a,y:s})}else i.push({x:l.getValueForPixel(I.left),y:t}),i.push({x:l.getValueForPixel(I.right),y:t});const n=l.getValueForPixel(I.left),s=S.f(n);i.push({x:n,y:s});const a=l.getValueForPixel(I.right),r=S.f(a);i.push({x:a,y:r});const d=l.getValueForPixel(I.left),c=l.getValueForPixel(I.right),x=[o.getValueForPixel(I.top),o.getValueForPixel(I.bottom)].filter((e=>isFinite(e))),h=x.length>0?Math.min(...x):-1/0,u=x.length>0?Math.max(...x):1/0;let f=i.filter((e=>isFinite(e.x)&&isFinite(e.y)&&e.x>=d&&e.x<=c&&e.y>=h&&e.y<=u));f=f.filter(((e,t,i)=>t===i.findIndex((t=>Math.abs(t.x-e.x)<1e-4&&Math.abs(t.y-e.y)<1e-4)))),f.length>=2?(f.sort(((e,t)=>e.x-t.x||e.y-t.y)),T=l.getPixelForValue(f[0].x),C=o.getPixelForValue(f[0].y),v=l.getPixelForValue(f[f.length-1].x),A=o.getPixelForValue(f[f.length-1].y)):(T=NaN,C=NaN,v=NaN,A=NaN)}else{const e=S.f(S.minx),t=S.f(S.maxx);T=l.getPixelForValue(S.minx),C=o.getPixelForValue(e),v=l.getPixelForValue(S.maxx),A=o.getPixelForValue(t)}let j=null;if(isFinite(T)&&isFinite(C)&&isFinite(v)&&isFinite(A)&&(j=function(e,t,i,n,l){let s=i-e,a=n-t,o=0,r=1;const d=[-s,s,-a,a],c=[e-l.left,l.right-e,t-l.top,l.bottom-t];for(let e=0;e<4;e++)if(0===d[e]){if(c[e]<0)return null}else{const t=c[e]/d[e];if(d[e]<0){if(t>r)return null;o=Math.max(o,t)}else{if(t<o)return null;r=Math.min(r,t)}}return o>r?null:{x1:e+o*s,y1:t+o*a,x2:e+r*s,y2:t+r*a}}(T,C,v,A,I)),j)if(T=j.x1,C=j.y1,v=j.x2,A=j.y2,Math.abs(T-v)<.5&&Math.abs(C-A)<.5);else{i.lineWidth=x,((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([])}})(i,h),(({ctx:e,x1:t,y1:i,x2:n,y2:l,colorMin:s,colorMax:a})=>{if(isFinite(t)&&isFinite(i)&&isFinite(n)&&isFinite(l)){e.beginPath(),e.moveTo(t,i),e.lineTo(n,l);try{const o=n-t,r=l-i,d=Math.sqrt(o*o+r*r);if(d<.01)console.warn("Gradient vector too small, using solid color:",{x1:t,y1:i,x2:n,y2:l,length:d}),e.strokeStyle=s;else{let o=e.createLinearGradient(t,i,n,l);o.addColorStop(0,s),o.addColorStop(1,a),e.strokeStyle=o}}catch(t){console.warn("Gradient creation failed, using solid color:",t),e.strokeStyle=s}e.stroke(),e.closePath()}else console.warn("Cannot draw trendline: coordinates contain non-finite values",{x1:t,y1:i,x2:n,y2:l})})({ctx:i,x1:T,y1:C,x2:v,y2:A,colorMin:d,colorMax:c}),u&&((e,t,i,n,l,s,a)=>{isFinite(t)&&isFinite(i)&&isFinite(n)&&isFinite(l)&&isFinite(s)?(e.beginPath(),e.moveTo(t,i),e.lineTo(n,l),e.lineTo(n,s),e.lineTo(t,s),e.lineTo(t,i),e.closePath(),e.fillStyle=a,e.fill()):console.warn("Cannot fill below trendline: coordinates contain non-finite values",{x1:t,y1:i,x2:n,y2:l,drawBottom:s})})(i,T,C,v,A,I.bottom,u);const e=Math.atan2(A-C,v-T),t=S.slope();n.trendlineLinear.label&&!1!==m&&((e,t,i,n,l,s,a,o,r,d,c)=>{e.font=`${d}px ${r}`,e.fillStyle=o;const x=e.measureText(t).width,h=(i+l)/2,u=(n+s)/2;e.save(),e.translate(h,u),e.rotate(a);const f=-x/2,y=c;e.fillText(t,f,y),e.restore()})(i,p?`${g} (Slope: ${F?(100*t).toFixed(2)+"%":t.toFixed(2)})`:g,T,C,v,A,e,y,w,L,b)}})(t.getDatasetMeta(a),i,s,n,l)}})),i.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 l=n(e),s=t.trendlineLinear.legend;return s&&!1!==s.display&&l.push({text:s.text||i+" (Trendline)",strokeStyle:s.color||t.borderColor||"rgba(169,169,169, .6)",fillStyle:s.fillStyle||"transparent",lineCap:s.lineCap||"butt",lineDash:s.lineDash||[],lineWidth:s.width||1}),l}}}))}};"undefined"!=typeof window&&window.Chart&&(window.Chart.hasOwnProperty("register")?window.Chart.register(t):window.Chart.plugins.register(t))})();
|
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>
|