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

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;