From 2a8fda75d73f0e1cd02c7032105f0e1a8688a8e5 Mon Sep 17 00:00:00 2001 From: Jack Date: Tue, 26 May 2020 17:49:34 +0100 Subject: [PATCH] added a trendline plugin --- public/js/chartjs-plugin-trendline.js | 123 ++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 public/js/chartjs-plugin-trendline.js diff --git a/public/js/chartjs-plugin-trendline.js b/public/js/chartjs-plugin-trendline.js new file mode 100644 index 000000000..243660729 --- /dev/null +++ b/public/js/chartjs-plugin-trendline.js @@ -0,0 +1,123 @@ +/*! + * 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 = { + beforeDraw: 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([2, 3]); } + 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; + } +};