1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
|
/*!
* chartjs-plugin-trendline.js
* Version: 0.1.3
*
* Copyright 2017 Marcus Alsterfjord
* Released under the MIT license
* https://github.com/Makanz/chartjs-plugin-trendline/blob/master/README.md
*
* Mod by: vesal: accept also xy-data so works with scatter
*/
var pluginTrendlineLinear = {
afterDatasetsDraw: function (chartInstance) {
var yScale;
var xScale;
for (var axis in chartInstance.scales) {
if (axis[0] == "x") xScale = chartInstance.scales[axis];
else yScale = chartInstance.scales[axis];
if (xScale && yScale) break;
}
var ctx = chartInstance.chart.ctx;
chartInstance.data.datasets.forEach(function (dataset, index) {
if (dataset.trendlineLinear && chartInstance.isDatasetVisible(index)) {
var datasetMeta = chartInstance.getDatasetMeta(index);
addFitter(datasetMeta, ctx, dataset, xScale, yScale);
}
});
ctx.setLineDash([]);
},
};
function addFitter(datasetMeta, ctx, dataset, xScale, yScale) {
if (
datasetMeta.data == [] ||
datasetMeta.data == null ||
datasetMeta.data.length == 0
)
return;
var style = dataset.trendlineLinear.style || dataset.borderColor;
var lineWidth = dataset.trendlineLinear.width || dataset.borderWidth;
var lineStyle = dataset.trendlineLinear.lineStyle || "solid";
style = style !== undefined ? style : "rgba(169,169,169, .6)";
lineWidth = lineWidth !== undefined ? lineWidth : 3;
var fitter = new LineFitter();
var lastIndex = dataset.data.length - 1;
var startPos = datasetMeta.data[0]._model.x;
var endPos = datasetMeta.data[lastIndex]._model.x;
var xy = false;
if (dataset.data && typeof dataset.data[0] === "object") xy = true;
dataset.data.forEach(function (data, index) {
if (data == null) return;
if (xy) fitter.add(data.x, data.y);
else fitter.add(index, data);
});
var x1 = xScale.getPixelForValue(fitter.minx);
var x2 = xScale.getPixelForValue(fitter.maxx);
var y1 = yScale.getPixelForValue(fitter.f(fitter.minx));
var y2 = yScale.getPixelForValue(fitter.f(fitter.maxx));
if (!xy) {
x1 = startPos;
x2 = endPos;
}
var drawBottom = datasetMeta.controller.chart.chartArea.bottom;
var chartWidth = datasetMeta.controller.chart.width;
if (y1 > drawBottom) {
// Left side is below zero
var diff = y1 - drawBottom;
var lineHeight = y1 - y2;
var overlapPercentage = diff / lineHeight;
var addition = chartWidth * overlapPercentage;
y1 = drawBottom;
x1 = x1 + addition;
} else if (y2 > drawBottom) {
// right side is below zero
var diff = y2 - drawBottom;
var lineHeight = y2 - y1;
var overlapPercentage = diff / lineHeight;
var subtraction = chartWidth - chartWidth * overlapPercentage;
y2 = drawBottom;
x2 = chartWidth - (x2 - subtraction);
}
ctx.lineWidth = lineWidth;
if (lineStyle === "dotted") {
ctx.setLineDash([6, 6]);
}
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.strokeStyle = style;
ctx.stroke();
}
Chart.plugins.register(pluginTrendlineLinear);
function LineFitter() {
this.count = 0;
this.sumX = 0;
this.sumX2 = 0;
this.sumXY = 0;
this.sumY = 0;
this.minx = 1e100;
this.maxx = -1e100;
}
LineFitter.prototype = {
add: function (x, y) {
this.count++;
this.sumX += x;
this.sumX2 += x * x;
this.sumXY += x * y;
this.sumY += y;
if (x < this.minx) this.minx = x;
if (x > this.maxx) this.maxx = x;
},
f: function (x) {
var det = this.count * this.sumX2 - this.sumX * this.sumX;
var offset = (this.sumX2 * this.sumY - this.sumX * this.sumXY) / det;
var scale = (this.count * this.sumXY - this.sumX * this.sumY) / det;
return offset + x * scale;
},
};
|