UNPKG

chart.js-fork

Version:

Simple HTML5 charts using the canvas element.

379 lines (311 loc) 15.9 kB
(function() { "use strict"; var root = this, Chart = root.Chart, helpers = Chart.helpers; var defaultConfig = { //Function - Whether the current x-axis label should be filtered out, takes in current label, //index and full label array, returns true to filter out the label returns false to keep the label labelsFilter : function(label, index, labels){return false}, ///Boolean - Whether grid lines are shown across the chart scaleShowGridLines: true, //String - Colour of the grid lines scaleGridLineColor: "rgba(0,0,0,.05)", //Number - Width of the grid lines scaleGridLineWidth: 1, //Boolean - Whether to show horizontal lines (except X axis) scaleShowHorizontalLines: true, //Boolean - Whether to show vertical lines (except Y axis) scaleShowVerticalLines: true, //Boolean - Whether the line is curved between points bezierCurve: true, //Number - Tension of the bezier curve between points bezierCurveTension: 0.4, //Boolean - Whether to show a dot for each point pointDot: true, //Number - Radius of each point dot in pixels pointDotRadius: 4, //Number - Pixel width of point dot stroke pointDotStrokeWidth: 1, //Number - amount extra to add to the radius to cater for hit detection outside the drawn point pointHitDetectionRadius: 20, //Boolean - Whether to show a stroke for datasets datasetStroke: true, //Number - Pixel width of dataset stroke datasetStrokeWidth: 2, //Boolean - Whether to fill the dataset with a colour datasetFill: true, //String - A legend template legendTemplate: "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<datasets.length; i++){%><li><span style=\"background-color:<%=datasets[i].strokeColor%>\"></span><%if(datasets[i].label){%><%=datasets[i].label%><%}%></li><%}%></ul>", //Boolean - Whether to horizontally center the label and point dot inside the grid offsetGridLines: false, //Array - specific yAxis details yAxes: [], //Boolean - set default yAxis on the left of chart scalePositionLeft: true, }; Chart.Type.extend({ name: "Line", defaults: defaultConfig, initialize: function(data) { //Declare the extension of the default point, to cater for the options passed in to the constructor this.PointClass = Chart.Point.extend({ offsetGridLines: this.options.offsetGridLines, strokeWidth: this.options.pointDotStrokeWidth, radius: this.options.pointDotRadius, display: this.options.pointDot, hitDetectionRadius: this.options.pointHitDetectionRadius, ctx: this.chart.ctx, inRange: function(mouseX) { return (Math.pow(mouseX - this.x, 2) < Math.pow(this.radius + this.hitDetectionRadius, 2)); } }); this.datasets = []; this.yAxes = data.yAxes; //Set up tooltip events on the chart if (this.options.showTooltips) { helpers.bindEvents(this, this.options.tooltipEvents, function(evt) { var activePoints = (evt.type !== 'mouseout') ? this.getPointsAtEvent(evt) : []; this.eachPoints(function(point) { point.restore(['fillColor', 'strokeColor']); }); helpers.each(activePoints, function(activePoint) { activePoint.fillColor = activePoint.highlightFill; activePoint.strokeColor = activePoint.highlightStroke; }); this.showTooltip(activePoints); }); } //Iterate through each of the datasets, and build this into a property of the chart helpers.each(data.datasets, function(dataset) { var datasetObject = { label: dataset.label || null, fillColor: dataset.fillColor, strokeColor: dataset.strokeColor, pointColor: dataset.pointColor, pointStrokeColor: dataset.pointStrokeColor, points: [], yAxesGroup: dataset.yAxesGroup, values: dataset.data }; this.datasets.push(datasetObject); helpers.each(dataset.data, function(dataPoint, index) { //Add a new point for each piece of data, passing any required data to draw. datasetObject.points.push(new this.PointClass({ value: dataPoint, label: data.labels[index], datasetLabel: dataset.label, strokeColor: dataset.pointStrokeColor, fillColor: dataset.pointColor, highlightFill: dataset.pointHighlightFill || dataset.pointColor, highlightStroke: dataset.pointHighlightStroke || dataset.pointStrokeColor, yAxesGroup: dataset.yAxesGroup })); }, this); }, this); this.buildScale(data.labels); this.eachPoints(function(point, index) { helpers.extend(point, { x: this.scale.calculateX(index), y: this.scale.endPoint }); point.save(); }, this); this.render(); }, update: function() { this.scale.update(); // Reset any highlight colours before updating. helpers.each(this.activeElements, function(activeElement) { activeElement.restore(['fillColor', 'strokeColor']); }); this.eachPoints(function(point) { point.save(); }); this.render(); }, eachPoints: function(callback) { helpers.each(this.datasets, function(dataset) { helpers.each(dataset.points, callback, this); }, this); }, getPointsAtEvent: function(e) { var pointsArray = [], eventPosition = helpers.getRelativePosition(e); helpers.each(this.datasets, function(dataset) { helpers.each(dataset.points, function(point) { if (point.inRange(eventPosition.x, eventPosition.y)) pointsArray.push(point); }); }, this); return pointsArray; }, buildScale: function(labels) { var self = this; var scaleOptions = { labelsFilter: this.options.labelsFilter, templateString: this.options.scaleLabel, height: this.chart.height, width: this.chart.width, ctx: this.chart.ctx, textColor: this.options.scaleFontColor, offsetGridLines: this.options.offsetGridLines, fontSize: this.options.scaleFontSize, fontStyle: this.options.scaleFontStyle, fontFamily: this.options.scaleFontFamily, valuesCount: labels.length, beginAtZero: this.options.scaleBeginAtZero, integersOnly: this.options.scaleIntegersOnly, datasets: this.datasets, xLabels: labels, font: helpers.fontString(this.options.scaleFontSize, this.options.scaleFontStyle, this.options.scaleFontFamily), lineWidth: this.options.scaleLineWidth, lineColor: this.options.scaleLineColor, showHorizontalLines: this.options.scaleShowHorizontalLines, showVerticalLines: this.options.scaleShowVerticalLines, gridLineWidth: (this.options.scaleShowGridLines) ? this.options.scaleGridLineWidth : 0, gridLineColor: (this.options.scaleShowGridLines) ? this.options.scaleGridLineColor : "rgba(0,0,0,0)", padding: (this.options.showScale) ? 0 : this.options.pointDotRadius + this.options.pointDotStrokeWidth, showLabels: this.options.scaleShowLabels, display: this.options.showScale, yAxes: this.yAxes, positionLeft: this.options.scalePositionLeft }; if (this.options.scaleOverride) { helpers.extend(scaleOptions, { calculateYRange: helpers.noop, steps: this.options.scaleSteps, stepValue: this.options.scaleStepWidth, min: this.options.scaleStartValue, max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth) }); } this.scale = new Chart.Scale(scaleOptions); }, addData: function(valuesArray, label) { //Map the values array for each of the datasets helpers.each(valuesArray, function(value, datasetIndex) { //Add a new point for each piece of data, passing any required data to draw. this.datasets[datasetIndex].points.push(new this.PointClass({ value: value, label: label, x: this.scale.calculateX(this.scale.valuesCount + 1), y: this.scale.endPoint, strokeColor: this.datasets[datasetIndex].pointStrokeColor, fillColor: this.datasets[datasetIndex].pointColor, yAxesGroup: this.datasets[datasetIndex].yAxesGroup })); }, this); this.scale.addXLabel(label); //Then re-render the chart. this.update(); }, removeData: function() { this.scale.removeXLabel(); //Then re-render the chart. helpers.each(this.datasets, function(dataset) { dataset.points.shift(); }, this); this.update(); }, reflow: function() { var newScaleProps = helpers.extend({ height: this.chart.height, width: this.chart.width }); this.scale.update(newScaleProps); }, draw: function(ease) { var easingDecimal = ease || 1; this.clear(); var ctx = this.chart.ctx; // Some helper methods for getting the next/prev points var hasValue = function(item) { return item.value !== null; }, nextPoint = function(point, collection, index) { return helpers.findNextWhere(collection, hasValue, index) || point; }, previousPoint = function(point, collection, index) { return helpers.findPreviousWhere(collection, hasValue, index) || point; }; this.scale.draw(easingDecimal); helpers.each(this.datasets, function(dataset) { var pointsWithValues = helpers.where(dataset.points, hasValue); //Transition each point first so that the line and point drawing isn't out of sync //We can use this extra loop to calculate the control points of this dataset also in this loop helpers.each(dataset.points, function(point, index) { if (point.hasValue()) { point.transition({ y: this.scale.calculateY(point), x: this.scale.calculateX(index) }, easingDecimal); } }, this); // Control points need to be calculated in a seperate loop, because we need to know the current x/y of the point // This would cause issues when there is no animation, because the y of the next point would be 0, so beziers would be skewed if (this.options.bezierCurve) { helpers.each(pointsWithValues, function(point, index) { var tension = (index > 0 && index < pointsWithValues.length - 1) ? this.options.bezierCurveTension : 0; point.controlPoints = helpers.splineCurve( previousPoint(point, pointsWithValues, index), point, nextPoint(point, pointsWithValues, index), tension ); // Prevent the bezier going outside of the bounds of the graph // Cap puter bezier handles to the upper/lower scale bounds if (point.controlPoints.outer.y > this.scale.endPoint) { point.controlPoints.outer.y = this.scale.endPoint; } else if (point.controlPoints.outer.y < this.scale.startPoint) { point.controlPoints.outer.y = this.scale.startPoint; } // Cap inner bezier handles to the upper/lower scale bounds if (point.controlPoints.inner.y > this.scale.endPoint) { point.controlPoints.inner.y = this.scale.endPoint; } else if (point.controlPoints.inner.y < this.scale.startPoint) { point.controlPoints.inner.y = this.scale.startPoint; } }, this); } //Draw the line between all the points ctx.lineWidth = this.options.datasetStrokeWidth; ctx.strokeStyle = dataset.strokeColor; ctx.beginPath(); helpers.each(pointsWithValues, function(point, index) { if (index === 0) { ctx.moveTo(point.x, point.y); } else { if (this.options.bezierCurve) { var previous = previousPoint(point, pointsWithValues, index); ctx.bezierCurveTo( previous.controlPoints.outer.x, previous.controlPoints.outer.y, point.controlPoints.inner.x, point.controlPoints.inner.y, point.x, point.y ); } else { ctx.lineTo(point.x, point.y); } } }, this); ctx.stroke(); if (this.options.datasetFill && pointsWithValues.length > 0) { //Round off the line by going to the base of the chart, back to the start, then fill. ctx.lineTo(pointsWithValues[pointsWithValues.length - 1].x, this.scale.endPoint); ctx.lineTo(pointsWithValues[0].x, this.scale.endPoint); ctx.fillStyle = dataset.fillColor; ctx.closePath(); ctx.fill(); } //Now draw the points over the line //A little inefficient double looping, but better than the line //lagging behind the point positions helpers.each(pointsWithValues, function(point) { point.draw(); }); }, this); } }); }).call(this);