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