Hybrid Line and Bar chart - Line Now drawing

This commit is contained in:
Tanner Linsley 2015-06-15 15:27:56 -06:00
parent b0ece8b516
commit 257bdb2dbb
9 changed files with 5085 additions and 6277 deletions

10776
Chart.js vendored

File diff suppressed because it is too large Load Diff

38
Chart.min.js vendored

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,63 @@
<!doctype html>
<html>
<head>
<title>Bar Chart</title>
<script src="../node_modules/jquery/dist/jquery.min.js"></script>
<script src="../Chart.js"></script>
</head>
<body>
<div style="width: 50%">
<canvas id="canvas" height="450" width="600"></canvas>
</div>
<button id="randomizeData">Randomize Data</button>
<script>
var randomScalingFactor = function() {
return (Math.random() > 0.5 ? 1.0 : -1.0) * Math.round(Math.random() * 100);
};
var randomColorFactor = function() {
return Math.round(Math.random() * 255);
};
var barChartData = {
labels: ["January", "February", "March", "April", "May", "June", "July"],
datasets: [{
type: 'line',
label: 'Dataset 1',
backgroundColor: "rgba(220,220,220,0.5)",
data: [randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor()]
}, {
label: 'Dataset 2',
backgroundColor: "rgba(151,187,205,0.5)",
data: [randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor()]
}, {
label: 'Dataset 3',
backgroundColor: "rgba(151,187,205,0.5)",
data: [randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor()]
}]
};
window.onload = function() {
var ctx = document.getElementById("canvas").getContext("2d");
window.myBar = new Chart(ctx, {
type: 'bar',
data: barChartData,
options: {
responsive: true,
}
});
};
$('#randomizeData').click(function() {
$.each(barChartData.datasets, function(i, dataset) {
dataset.backgroundColor = 'rgba(' + randomColorFactor() + ',' + randomColorFactor() + ',' + randomColorFactor() + ',.7)';
dataset.data = [randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor()];
});
window.myBar.update();
});
</script>
</body>
</html>

View File

@ -93,7 +93,6 @@
backgroundColor: rectangle.custom && rectangle.custom.backgroundColor ? rectangle.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.getDataset().backgroundColor, index, this.chart.options.elements.rectangle.backgroundColor),
borderColor: rectangle.custom && rectangle.custom.borderColor ? rectangle.custom.borderColor : helpers.getValueAtIndexOrDefault(this.getDataset().borderColor, index, this.chart.options.elements.rectangle.borderColor),
borderWidth: rectangle.custom && rectangle.custom.borderWidth ? rectangle.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.getDataset().borderWidth, index, this.chart.options.elements.rectangle.borderWidth),
},
});
rectangle.pivot();
@ -122,16 +121,6 @@
// }, this);
// },
// eachRectangle: function(callback) {
// helpers.each(this.chart.data.datasets, function(dataset, datasetIndex) {
// helpers.each(dataset.metaData, function(element, index) {
// if (element instanceof Chart.Rectangle) {
// callback(element, index, dataset, datasetIndex);
// }
// }, this);
// }, this);
// },
// addLine: function addLine(dataset, datasetIndex) {
// if (dataset) {
// dataset.metaDataset = new Chart.Line({
@ -322,7 +311,6 @@
// },
// setElementHoverStyle: function setElementHoverStyle(element) {
// if (element instanceof Chart.Point) {
// this.setPointHoverStyle(element);
@ -341,7 +329,7 @@
// point._model.borderWidth = point.custom && point.custom.hoverBorderWidth ? point.custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, point._model.borderWidth);
// },
// setRectangleHoverStyle: function(rectangle) {
// setHoverStyle: function(rectangle) {
// var dataset = this.chart.data.datasets[rectangle._datasetIndex];
// var index = rectangle._index;
@ -388,42 +376,42 @@
// }, this);
// },
// getElementsAtEvent: function(e) {
// var elementsArray = [],
// eventPosition = helpers.getRelativePosition(e),
// datasetIterator = function(dataset) {
// elementsArray.push(dataset.metaData[elementIndex]);
// },
// elementIndex;
getElementsAtEvent: function(e) {
var elementsArray = [],
eventPosition = helpers.getRelativePosition(e),
datasetIterator = function(dataset) {
elementsArray.push(dataset.metaData[elementIndex]);
},
elementIndex;
// for (var datasetIndex = 0; datasetIndex < this.chart.data.datasets.length; datasetIndex++) {
// for (elementIndex = 0; elementIndex < this.chart.data.datasets[datasetIndex].metaData.length; elementIndex++) {
// if (this.chart.data.datasets[datasetIndex].metaData[elementIndex].inGroupRange(eventPosition.x, eventPosition.y)) {
// helpers.each(this.chart.data.datasets, datasetIterator);
// }
// }
// }
for (var datasetIndex = 0; datasetIndex < this.chart.data.datasets.length; datasetIndex++) {
for (elementIndex = 0; elementIndex < this.chart.data.datasets[datasetIndex].metaData.length; elementIndex++) {
if (this.chart.data.datasets[datasetIndex].metaData[elementIndex].inGroupRange(eventPosition.x, eventPosition.y)) {
helpers.each(this.chart.data.datasets, datasetIterator);
}
}
}
// return elementsArray.length ? elementsArray : [];
// },
return elementsArray.length ? elementsArray : [];
},
// // Get the single element that was clicked on
// // @return : An object containing the dataset index and element index of the matching element. Also contains the rectangle that was drawn
// getElementAtEvent: function(e) {
// var element = [];
// var eventPosition = helpers.getRelativePosition(e);
getElementAtEvent: function(e) {
var element = [];
var eventPosition = helpers.getRelativePosition(e);
// for (var datasetIndex = 0; datasetIndex < this.chart.data.datasets.length; ++datasetIndex) {
// for (var elementIndex = 0; elementIndex < this.chart.data.datasets[datasetIndex].metaData.length; ++elementIndex) {
// if (this.chart.data.datasets[datasetIndex].metaData[elementIndex].inRange(eventPosition.x, eventPosition.y)) {
// element.push(this.chart.data.datasets[datasetIndex].metaData[elementIndex]);
// return element;
// }
// }
// }
for (var datasetIndex = 0; datasetIndex < this.chart.data.datasets.length; ++datasetIndex) {
for (var elementIndex = 0; elementIndex < this.chart.data.datasets[datasetIndex].metaData.length; ++elementIndex) {
if (this.chart.data.datasets[datasetIndex].metaData[elementIndex].inRange(eventPosition.x, eventPosition.y)) {
element.push(this.chart.data.datasets[datasetIndex].metaData[elementIndex]);
return element;
}
}
}
// return [];
// },
return [];
},
});

View File

@ -0,0 +1,371 @@
(function() {
"use strict";
var root = this,
Chart = root.Chart,
helpers = Chart.helpers;
Chart.controllers.line = function(chart, datasetIndex) {
this.initialize.call(this, chart, datasetIndex);
};
helpers.extend(Chart.controllers.line.prototype, {
initialize: function(chart, datasetIndex) {
this.chart = chart;
this.index = datasetIndex;
this.linkScales();
this.addElements();
},
linkScales: function() {
if (!this.getDataset().xAxisID) {
this.getDataset().xAxisID = this.chart.options.scales.xAxes[0].id;
}
if (!this.getDataset().yAxisID) {
this.getDataset().yAxisID = this.chart.options.scales.yAxes[0].id;
}
},
getDataset: function() {
return this.chart.data.datasets[this.index];
},
getScaleForId: function(scaleID) {
return this.chart.scales[scaleID];
},
addElements: function() {
this.getDataset().metaData = this.getDataset().metaData || [];
this.getDataset().metaDataset = this.getDataset().metaDataset || new Chart.elements.Line({
_chart: this.chart.chart,
_datasetIndex: this.index,
_points: this.getDataset().metaData,
});
helpers.each(this.getDataset().data, function(value, index) {
this.getDataset().metaData[index] = this.getDataset().metaData[index] || new Chart.elements.Point({
_chart: this.chart.chart,
_datasetIndex: this.index,
_index: index,
});
}, this);
},
reset: function() {
this.update(true);
},
update: function(reset) {
var line = this.getDataset().metaDataset;
var points = this.getDataset().metaData;
var yScale = this.getScaleForId(this.getDataset().yAxisID);
var xScale = this.getScaleForId(this.getDataset().xAxisID);
var scaleBase;
if (yScale.min < 0 && yScale.max < 0) {
scaleBase = yScale.getPixelForValue(yScale.max);
} else if (yScale.min > 0 && yScale.max > 0) {
scaleBase = yScale.getPixelForValue(yScale.min);
} else {
scaleBase = yScale.getPixelForValue(0);
}
// Update Line
helpers.extend(line, {
// Utility
_scale: yScale,
_datasetIndex: this.index,
// Data
_children: points,
// Model
_model: {
// Appearance
tension: line.custom && line.custom.tension ? line.custom.tension : (this.getDataset().tension || this.chart.options.elements.line.tension),
backgroundColor: line.custom && line.custom.backgroundColor ? line.custom.backgroundColor : (this.getDataset().backgroundColor || this.chart.options.elements.line.backgroundColor),
borderWidth: line.custom && line.custom.borderWidth ? line.custom.borderWidth : (this.getDataset().borderWidth || this.chart.options.elements.line.borderWidth),
borderColor: line.custom && line.custom.borderColor ? line.custom.borderColor : (this.getDataset().borderColor || this.chart.options.elements.line.borderColor),
fill: line.custom && line.custom.fill ? line.custom.fill : (this.getDataset().fill !== undefined ? this.getDataset().fill : this.chart.options.elements.line.fill),
skipNull: this.getDataset().skipNull !== undefined ? this.getDataset().skipNull : this.chart.options.elements.line.skipNull,
drawNull: this.getDataset().drawNull !== undefined ? this.getDataset().drawNull : this.chart.options.elements.line.drawNull,
// Scale
scaleTop: yScale.top,
scaleBottom: yScale.bottom,
scaleZero: scaleBase,
},
});
line.pivot();
// Update Points
helpers.each(points, function(point, index) {
helpers.extend(point, {
// Utility
_chart: this.chart.chart,
_xScale: xScale,
_yScale: yScale,
_datasetIndex: this.index,
_index: index,
// Desired view properties
_model: {
x: xScale.getPointPixelForValue(this.getDataset().data[index], index, this.index),
y: yScale.getPointPixelForValue(this.getDataset().data[index], index, this.index),
// Appearance
tension: point.custom && point.custom.tension ? point.custom.tension : this.chart.options.elements.line.tension,
radius: point.custom && point.custom.radius ? point.custom.radius : helpers.getValueAtIndexOrDefault(this.getDataset().radius, index, this.chart.options.elements.point.radius),
backgroundColor: point.custom && point.custom.backgroundColor ? point.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.getDataset().pointBackgroundColor, index, this.chart.options.elements.point.backgroundColor),
borderColor: point.custom && point.custom.borderColor ? point.custom.borderColor : helpers.getValueAtIndexOrDefault(this.getDataset().pointBorderColor, index, this.chart.options.elements.point.borderColor),
borderWidth: point.custom && point.custom.borderWidth ? point.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.getDataset().pointBorderWidth, index, this.chart.options.elements.point.borderWidth),
skip: this.getDataset().data[index] === null,
// Tooltip
hitRadius: point.custom && point.custom.hitRadius ? point.custom.hitRadius : helpers.getValueAtIndexOrDefault(this.getDataset().hitRadius, index, this.chart.options.elements.point.hitRadius),
},
});
}, this);
// Update bezier control points
helpers.each(this.getDataset().metaData, function(point, index) {
var controlPoints = helpers.splineCurve(
helpers.previousItem(this.getDataset().metaData, index)._model,
point._model,
helpers.nextItem(this.getDataset().metaData, index)._model,
point._model.tension
);
point._model.controlPointPreviousX = controlPoints.previous.x;
point._model.controlPointNextX = controlPoints.next.x;
// Prevent the bezier going outside of the bounds of the graph
// Cap puter bezier handles to the upper/lower scale bounds
if (controlPoints.next.y > this.chart.chartArea.bottom) {
point._model.controlPointNextY = this.chart.chartArea.bottom;
} else if (controlPoints.next.y < this.chart.chartArea.top) {
point._model.controlPointNextY = this.chart.chartArea.top;
} else {
point._model.controlPointNextY = controlPoints.next.y;
}
// Cap inner bezier handles to the upper/lower scale bounds
if (controlPoints.previous.y > this.chart.chartArea.bottom) {
point._model.controlPointPreviousY = this.chart.chartArea.bottom;
} else if (controlPoints.previous.y < this.chart.chartArea.top) {
point._model.controlPointPreviousY = this.chart.chartArea.top;
} else {
point._model.controlPointPreviousY = controlPoints.previous.y;
}
// Now pivot the point for animation
point.pivot();
}, this);
},
draw: function(ease) {
var easingDecimal = ease || 1;
// Transition Point Locations
helpers.each(this.getDataset().metaData, function(point, index) {
point.transition(easingDecimal);
}, this);
// Transition and Draw the line
this.getDataset().metaDataset.transition(easingDecimal).draw();
// Draw the points
helpers.each(this.getDataset().metaData, function(point) {
point.draw();
});
},
// eachLine: function eachLine(callback) {
// helpers.each(this.chart.data.datasets, function(dataset, datasetIndex) {
// if (dataset.metaDataset && dataset.metaDataset instanceof Chart.Line) {
// callback.call(this, dataset, datasetIndex);
// }
// }, this);
// },
// addPoint: function addPoint(dataset, datasetIndex, index) {
// if (dataset) {
// dataset.metaData = dataset.metaData || new Array(this.chart.data.datasets[datasetIndex].data.length);
// if (index < dataset.metaData.length) {
// dataset.metaData[index] = new Chart.Point({
// _datasetIndex: datasetIndex,
// _index: index,
// _chart: this.chart.chart,
// _model: {
// x: 0,
// y: 0,
// },
// });
// }
// }
// },
// resetElements: function resetElements() {
// helpers.each(this.chart.data.datasets, function(dataset, datasetIndex) {
// // All elements must be the same type for the given dataset so we are fine to check just the first one
// if (dataset.metaData[0] instanceof Chart.Point) {
// // Have points. Update all of them
// this.resetDatasetPoints(dataset, datasetIndex);
// } else if (dataset.metaData[0] instanceof Chart.Rectangle) {
// // Have rectangles (lines)
// this.resetDatasetRectangles(dataset, datasetIndex);
// }
// }, this);
// },
// resetDatasetPoints: function resetDatasetPoints(dataset, datasetIndex) {
// helpers.each(dataset.metaData, function(point, index) {
// var xScale = this.getScaleForId(this.chart.data.datasets[datasetIndex].xAxisID);
// var yScale = this.getScaleForId(this.chart.data.datasets[datasetIndex].yAxisID);
// var yScalePoint;
// if (yScale.min < 0 && yScale.max < 0) {
// // all less than 0. use the top
// yScalePoint = yScale.getPixelForValue(yScale.max);
// } else if (yScale.min > 0 && yScale.max > 0) {
// yScalePoint = yScale.getPixelForValue(yScale.min);
// } else {
// yScalePoint = yScale.getPixelForValue(0);
// }
// helpers.extend(point, {
// // Utility
// _chart: this.chart.chart, //WTF
// _xScale: xScale,
// _yScale: yScale,
// _datasetIndex: datasetIndex,
// _index: index,
// // Desired view properties
// _model: {
// x: xScale.getPointPixelForValue(this.chart.data.datasets[datasetIndex].data[index], index, datasetIndex),
// y: yScalePoint,
// },
// });
// this.updatePointElementAppearance(point, datasetIndex, index);
// }, this);
// this.updateBezierControlPoints(dataset);
// },
// resetDatasetRectangles: function resetDatasetRectangles(dataset, datasetIndex) {
// },
// resetElementAppearance: function(element, datasetIndex, index) {
// if (element instanceof Chart.Point) {
// this.updatePointElementAppearance(element, datasetIndex, index);
// } else if (element instanceof Chart.Rectangle) {
// this.updateRectangleElementAppearance(element, datasetIndex, index);
// }
// },
// updateElements: function updateElements() {
// // Update the lines
// this.updateLines();
// helpers.each(this.chart.data.datasets, function(dataset, datasetIndex) {
// // All elements must be the same type for the given dataset so we are fine to check just the first one
// if (dataset.metaData[0] instanceof Chart.Point) {
// // Have points. Update all of them
// this.updatePoints(dataset, datasetIndex);
// } else if (dataset.metaData[0] instanceof Chart.Rectangle) {
// // Have rectangles (lines)
// this.updateRectangles(dataset, datasetIndex);
// }
// }, this);
// },
// setElementHoverStyle: function setElementHoverStyle(element) {
// if (element instanceof Chart.Point) {
// this.setPointHoverStyle(element);
// } else if (element instanceof Chart.Rectangle) {
// this.setRectangleHoverStyle(element);
// }
// },
// setPointHoverStyle: function setPointHoverStyle(point) {
// var dataset = this.chart.data.datasets[point._datasetIndex];
// var index = point._index;
// point._model.radius = point.custom && point.custom.radius ? point.custom.radius : helpers.getValueAtIndexOrDefault(dataset.pointHoverRadius, index, this.chart.options.elements.point.hoverRadius);
// point._model.backgroundColor = point.custom && point.custom.hoverBackgroundColor ? point.custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.color(point._model.backgroundColor).saturate(0.5).darken(0.1).rgbString());
// point._model.borderColor = point.custom && point.custom.hoverBorderColor ? point.custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.color(point._model.borderColor).saturate(0.5).darken(0.1).rgbString());
// point._model.borderWidth = point.custom && point.custom.hoverBorderWidth ? point.custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, point._model.borderWidth);
// },
// setHoverStyle: function(rectangle) {
// var dataset = this.chart.data.datasets[rectangle._datasetIndex];
// var index = rectangle._index;
// rectangle._model.backgroundColor = rectangle.custom && rectangle.custom.hoverBackgroundColor ? rectangle.custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.hoverBackgroundColor, index, helpers.color(rectangle._model.backgroundColor).saturate(0.5).darken(0.1).rgbString());
// rectangle._model.borderColor = rectangle.custom && rectangle.custom.hoverBorderColor ? rectangle.custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.hoverBorderColor, index, helpers.color(rectangle._model.borderColor).saturate(0.5).darken(0.1).rgbString());
// rectangle._model.borderWidth = rectangle.custom && rectangle.custom.hoverBorderWidth ? rectangle.custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, rectangle._model.borderWidth);
// },
getElementsAtEvent: function(e) {
var elementsArray = [],
eventPosition = helpers.getRelativePosition(e),
datasetIterator = function(dataset) {
elementsArray.push(dataset.metaData[elementIndex]);
},
elementIndex;
for (var datasetIndex = 0; datasetIndex < this.chart.data.datasets.length; datasetIndex++) {
for (elementIndex = 0; elementIndex < this.chart.data.datasets[datasetIndex].metaData.length; elementIndex++) {
if (this.chart.data.datasets[datasetIndex].metaData[elementIndex].inGroupRange(eventPosition.x, eventPosition.y)) {
helpers.each(this.chart.data.datasets, datasetIterator);
}
}
}
return elementsArray.length ? elementsArray : [];
},
// // Get the single element that was clicked on
// // @return : An object containing the dataset index and element index of the matching element. Also contains the rectangle that was drawn
getElementAtEvent: function(e) {
var element = [];
var eventPosition = helpers.getRelativePosition(e);
for (var datasetIndex = 0; datasetIndex < this.chart.data.datasets.length; ++datasetIndex) {
for (var elementIndex = 0; elementIndex < this.chart.data.datasets[datasetIndex].metaData.length; ++elementIndex) {
if (this.chart.data.datasets[datasetIndex].metaData[elementIndex].inRange(eventPosition.x, eventPosition.y)) {
element.push(this.chart.data.datasets[datasetIndex].metaData[elementIndex]);
return element;
}
}
}
return [];
},
});
}).call(this);

View File

@ -191,6 +191,12 @@
this.tooltip.transition(easingDecimal).draw();
},
eachValue: function eachValue(callback) {
helpers.each(this.data.datasets, function(dataset, datasetIndex) {
helpers.each(dataset.data, callback, this, datasetIndex);

View File

@ -327,6 +327,18 @@
}
};
},
nextItem = helpers.nextItem = function(collection, index, loop) {
if (loop) {
return collection[index + 1] || collection[0];
}
return collection[index + 1] || collection[collection.length - 1];
},
previousItem = helpers.previousItem = function(collection, index, loop) {
if (loop) {
return collection[index - 1] || collection[collection.length - 1];
}
return collection[index - 1] || collection[0];
},
calculateOrderOfMagnitude = helpers.calculateOrderOfMagnitude = function(val) {
return Math.floor(Math.log(val) / Math.LN10);
},

View File

@ -28,7 +28,7 @@
};
Chart.Line = Chart.Element.extend({
Chart.elements.Line = Chart.Element.extend({
draw: function() {
var vm = this._view;
@ -38,8 +38,8 @@
// Draw the background first (so the border is always on top)
helpers.each(this._children, function(point, index) {
var previous = this.previousPoint(point, this._children, index);
var next = this.nextPoint(point, this._children, index);
var previous = helpers.previousItem(this._children, index);
var next = helpers.nextItem(this._children, index);
// First point only
if (index === 0) {
@ -112,8 +112,8 @@
ctx.beginPath();
helpers.each(this._children, function(point, index) {
var previous = this.previousPoint(point, this._children, index);
var next = this.nextPoint(point, this._children, index);
var previous = helpers.previousItem(this._children, index);
var next = helpers.nextItem(this._children, index);
// First point only
if (index === 0) {
@ -173,18 +173,6 @@
ctx.stroke();
},
nextPoint: function(point, collection, index) {
if (this.loop) {
return collection[index + 1] || collection[0];
}
return collection[index + 1] || collection[collection.length - 1];
},
previousPoint: function(point, collection, index) {
if (this.loop) {
return collection[index - 1] || collection[collection.length - 1];
}
return collection[index - 1] || collection[0];
},
});
}).call(this);

View File

@ -29,7 +29,7 @@
};
Chart.Point = Chart.Element.extend({
Chart.elements.Point = Chart.Element.extend({
inRange: function(mouseX, mouseY) {
var vm = this._view;
var hoverRange = vm.hitRadius + vm.radius;