lineChart single hoverMode tooltips, fallbackColor global

This commit is contained in:
Tanner Linsley
2015-05-15 16:40:01 -06:00
parent f79ebdaa88
commit fb580a6e48
4 changed files with 400 additions and 342 deletions

View File

@@ -6,7 +6,7 @@
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
</head>
<body>
<div style="width:30%">
<div style="width:100%">
<div>
<canvas id="canvas" height="450" width="600"></canvas>
</div>
@@ -15,48 +15,45 @@
<script>
var randomScalingFactor = function(){ return Math.round(Math.random()*100)};
var randomColorFactor = function(){ return Math.round(Math.random()*255)};
var randomScalingFactor = function(){ return Math.round(Math.random()*100);};
var randomColor = function(opacity){
return 'rgba(' + Math.round(Math.random()*255) + ',' + Math.round(Math.random()*255) + ',' + Math.round(Math.random()*255) + ','+ (opacity || '.3') +')';
};
var lineChartData = {
labels : ["January","February","March","April","May","June","July"],
datasets : [
{
label: "My First dataset",
fillColor : "rgba(220,220,220,0.2)",
strokeColor : "rgba(220,220,220,1)",
pointColor : "rgba(220,220,220,1)",
pointStrokeColor : "#fff",
pointHighlightFill : "#fff",
pointHighlightStroke : "rgba(220,220,220,1)",
data : [randomScalingFactor(),randomScalingFactor(),randomScalingFactor(),randomScalingFactor(),randomScalingFactor(),randomScalingFactor(),randomScalingFactor()]
},
{
label: "My Second dataset",
fillColor : "rgba(151,187,205,0.2)",
strokeColor : "rgba(151,187,205,1)",
pointColor : "rgba(151,187,205,1)",
pointStrokeColor : "#fff",
pointHighlightFill : "#fff",
pointHighlightStroke : "rgba(151,187,205,1)",
data : [randomScalingFactor(),randomScalingFactor(),randomScalingFactor(),randomScalingFactor(),randomScalingFactor(),randomScalingFactor(),randomScalingFactor()]
}
]
};
}
$.each(lineChartData.datasets, function(i, dataset){
dataset.borderColor = randomColor(0.4);
dataset.backgroundColor = randomColor(0.1);
dataset.pointBorderColor = randomColor(0.7);
dataset.pointBackgroundColor = randomColor(0.5);
dataset.pointBorderWidth = 1;
});
console.log(lineChartData);
window.onload = function(){
var ctx = document.getElementById("canvas").getContext("2d");
window.myLine = new Chart(ctx).Line(lineChartData, {
responsive: true
});
}
};
$('#randomizeData').click(function(){
lineChartData.datasets[0].fillColor = 'rgba(' + randomColorFactor() + ',' + randomColorFactor() + ',' + randomColorFactor() + ',.3)';
lineChartData.datasets[0].data = [randomScalingFactor(),randomScalingFactor(),randomScalingFactor(),randomScalingFactor(),randomScalingFactor(),randomScalingFactor(),randomScalingFactor()];
lineChartData.datasets[1].fillColor = 'rgba(' + randomColorFactor() + ',' + randomColorFactor() + ',' + randomColorFactor() + ',.3)';
lineChartData.datasets[1].data = [randomScalingFactor(),randomScalingFactor(),randomScalingFactor(),randomScalingFactor(),randomScalingFactor(),randomScalingFactor(),randomScalingFactor()];
window.myLine.update();

View File

@@ -34,15 +34,6 @@
//Number - Spacing between data sets within X values
barDatasetSpacing : 1,
//String / Boolean - Hover mode for events.
hoverMode : 'single', // 'label', 'dataset', 'false'
//Function - Custom hover handler
onHover : null,
//Function - Custom hover handler
hoverAnimationDuration : 400,
//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].backgroundColor%>\"></span><%if(datasets[i].label){%><%=datasets[i].label%><%}%></li><%}%></ul>"
@@ -57,9 +48,9 @@
// Save data as a source for updating of values & methods
this.data = data;
//Expose options as a scope variable here so we can access it in the ScaleClass
var options = this.options;
// Custom Scale Methods and Options
this.ScaleClass = Chart.Scale.extend({
offsetGridLines : true,
calculateBarX : function(datasetCount, datasetIndex, elementIndex){
@@ -90,7 +81,7 @@
});
// Build Scale
this.buildScale(data.labels);
this.buildScale(this.data.labels);
//Create a new bar for each piece of data
helpers.each(this.data.datasets,function(dataset,datasetIndex){
@@ -158,13 +149,11 @@
case 'single':
this.lastActive[0].backgroundColor = this.data.datasets[this.lastActive[0]._datasetIndex].backgroundColor;
this.lastActive[0].borderColor = this.data.datasets[this.lastActive[0]._datasetIndex].borderColor;
this.lastActive[0].borderWidth = 0;
break;
case 'label':
for (var i = 0; i < this.lastActive.length; i++) {
this.lastActive[i].backgroundColor = this.data.datasets[this.lastActive[i]._datasetIndex].backgroundColor;
this.lastActive[i].borderColor = this.data.datasets[this.lastActive[i]._datasetIndex].borderColor;
this.lastActive[i].borderWidth = 0;
}
break;
case 'dataset':
@@ -235,6 +224,7 @@
(this.lastActive.length && this.active.length && changed)){
this.tooltip.pivot();
this.stop();
this.render(this.options.hoverAnimationDuration);
}
}
@@ -277,7 +267,6 @@
backgroundColor : this.data.datasets[datasetIndex].backgroundColor,
_datasetIndex: datasetIndex,
_index: index,
_start: undefined
});
}, this);
@@ -350,7 +339,7 @@
base : base
});
});
render();
this.render();
},
draw : function(ease){

View File

@@ -114,6 +114,15 @@
// Boolean - whether to maintain the starting aspect ratio or not when responsive, if set to false, will take up entire container
maintainAspectRatio: true,
//String / Boolean - Hover mode for events.
hoverMode : 'single', // 'label', 'dataset', 'false'
//Function - Custom hover handler
onHover : null,
//Function - Custom hover handler
hoverAnimationDuration : 400,
// Boolean - Determines whether to draw tooltips on the canvas or not - attaches events to touchmove & mousemove
showTooltips: true,
@@ -178,7 +187,10 @@
onAnimationProgress: function(){},
// Function - Will fire on animation completion.
onAnimationComplete: function(){}
onAnimationComplete: function(){},
// Color String - Used for undefined Colros
colorFallback: 'rgba(0,0,0,0.1)',
}
};
@@ -384,11 +396,11 @@
fa=t*d01/(d01+d12),// scaling factor for triangle Ta
fb=t*d12/(d01+d12);
return {
inner : {
next : {
x : MiddlePoint.x-fa*(AfterPoint.x-FirstPoint.x),
y : MiddlePoint.y-fa*(AfterPoint.y-FirstPoint.y)
},
outer : {
previous : {
x: MiddlePoint.x+fb*(AfterPoint.x-FirstPoint.x),
y : MiddlePoint.y+fb*(AfterPoint.y-FirstPoint.y)
}
@@ -976,7 +988,7 @@
},
eachElement : function(callback){
helpers.each(this.data.datasets,function(dataset, datasetIndex){
helpers.each(dataset.metaData, callback, this, datasetIndex);
helpers.each(dataset.metaData, callback, this, dataset.metaData, datasetIndex);
},this);
},
eachValue : function(callback){
@@ -984,6 +996,9 @@
helpers.each(dataset.data, callback, this, datasetIndex);
},this);
},
eachDataset : function(callback){
helpers.each(this.data.datasets, callback, this);
},
getElementsAtEvent : function(e){
var elementsArray = [],
eventPosition = helpers.getRelativePosition(e),
@@ -1112,29 +1127,30 @@
},
transition : function(ease){
if(!this._start){
if(!this._vm){
this.save();
}
this._start = clone(this._vm);
}
each(this,function(value, key){
// Only non-vm properties
if(key[0] === '_' || !this.hasOwnProperty(key)){
return;
// Only non-underscored properties
}
// Init if doesn't exist
if(!this._vm[key]){
else if(!this._vm[key]){
this._vm[key] = value || null;
return;
}
// No unnecessary computations
if(this[key] === this._vm[key]){
return;
else if(this[key] === this._vm[key]){
// It's the same! Woohoo!
}
// Color transitions if possible
if(typeof value === 'string'){
else if(typeof value === 'string'){
try{
var color = helpers.color(this._start[key]).mix(helpers.color(this[key]), ease);
this._vm[key] = color.rgbString();
@@ -1144,14 +1160,16 @@
}
// Number transitions
else if(typeof value === 'number'){
this._vm[key] = ((this[key] - this._start[key]) * ease) + this._start[key];
}
// Non-transitionals or strings
else{
// Everything else
this._vm[key] = value;
}
},this);
if(ease === 1){
delete this._start;
}
@@ -1172,56 +1190,92 @@
Chart.Point = Chart.Element.extend({
display: true,
inRange: function(chartX,chartY){
var hitDetectionRange = this.hitDetectionRadius + this.radius;
return ((Math.pow(chartX-this.x, 2)+Math.pow(chartY-this.y, 2)) < Math.pow(hitDetectionRange,2));
var vm = this._vm;
var hoverRange = vm.hoverRadius + vm.radius;
return ((Math.pow(chartX - vm.x, 2)+Math.pow(chartY - vm.y, 2)) < Math.pow(hoverRange,2));
},
tooltipPosition : function(){
var vm = this._vm;
return {
x : vm.x,
y : vm.y
};
},
draw : function(){
if (this.display){
var ctx = this.ctx;
var vm = this._vm;
var ctx = this._chart.ctx;
if (vm.radius > 0 || vm.borderWidth > 0){
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI*2);
ctx.arc(vm.x, vm.y, vm.radius, 0, Math.PI*2);
ctx.closePath();
ctx.strokeStyle = this.borderColor;
ctx.lineWidth = this.borderWidth;
ctx.strokeStyle = vm.borderColor || Chart.defaults.global.colorFallback;
ctx.lineWidth = vm.borderWidth || Chart.defaults.global.colorFallback;
ctx.fillStyle = this.backgroundColor;
ctx.fillStyle = vm.backgroundColor || Chart.defaults.global.colorFallback;
ctx.fill();
ctx.stroke();
}
//Quick debug for bezier curve splining
//Highlights control points and the line between them.
//Handy for dev - stripped in the min version.
// ctx.save();
// ctx.fillStyle = "black";
// ctx.strokeStyle = "black"
// ctx.beginPath();
// ctx.arc(this.controlPoints.inner.x,this.controlPoints.inner.y, 2, 0, Math.PI*2);
// ctx.fill();
// ctx.beginPath();
// ctx.arc(this.controlPoints.outer.x,this.controlPoints.outer.y, 2, 0, Math.PI*2);
// ctx.fill();
// ctx.moveTo(this.controlPoints.inner.x,this.controlPoints.inner.y);
// ctx.lineTo(this.x, this.y);
// ctx.lineTo(this.controlPoints.outer.x,this.controlPoints.outer.y);
// ctx.stroke();
// ctx.restore();
}
});
Chart.Line = Chart.Element.extend({
draw : function(){
var vm = this._vm;
var ctx = this._chart.ctx;
//Draw the line between all the points
ctx.lineWidth = vm.borderWidth || Chart.defaults.global.colorFallback;
ctx.strokeStyle = vm.borderColor || Chart.defaults.global.colorFallback;
ctx.beginPath();
helpers.each(vm._points, function(point, index){
if (index === 0){
ctx.moveTo(point._vm.x, point._vm.y);
}
else{
if(vm._tension > 0 || 1){
var previous = this.previousPoint(point, vm._points, index);
ctx.bezierCurveTo(
previous._vm.controlPointNextX,
previous._vm.controlPointNextY,
point._vm.controlPointPreviousX,
point._vm.controlPointPreviousY,
point._vm.x,
point._vm.y
);
}
else{
ctx.lineTo(point._vm.x,point._vm.y);
}
}
}, this);
ctx.stroke();
if (vm._points.length > 0){
//Round off the line by going to the base of the chart, back to the start, then fill.
ctx.lineTo(vm._points[vm._points.length - 1].x, vm.scaleBottom);
ctx.lineTo(vm._points[0].x, vm.scaleBottom);
ctx.fillStyle = vm.backgroundColor || Chart.defaults.global.colorFallback;
ctx.closePath();
ctx.fill();
}
},
previousPoint: function(point, collection, index){
return helpers.findPreviousWhere(collection, function(){return true;}, index) || point;
},
});
Chart.Arc = Chart.Element.extend({
inRange : function(chartX,chartY){

View File

@@ -22,35 +22,23 @@
//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,
tension : 0.4,
//Number - Radius of each point dot in pixels
pointDotRadius : 4,
pointRadius : 4,
//Number - Pixel width of point dot stroke
pointDotStrokeWidth : 1,
//Number - Pixel width of point dot border
pointBorderWidth : 1,
//Number - amount extra to add to the radius to cater for hit detection outside the drawn point
pointHitDetectionRadius : 20,
pointHoverRadius : 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,
//Number - Pixel width of dataset border
borderWidth : 2,
//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>",
legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<datasets.length; i++){%><li><span style=\"background-color:<%=datasets[i].borderColor%>\"></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
@@ -65,138 +53,291 @@
// Save data as a source for updating of values & methods
this.data = data;
//Declare the extension of the default point, to cater for the options passed in to the constructor
//Custom Point Defaults
this.PointClass = Chart.Point.extend({
_chart: this.chart,
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));
}
borderWidth : this.options.pointBorderWidth,
radius : this.options.pointRadius,
hoverRadius : this.options.pointHoverRadius,
});
this.datasets = [];
// Events
helpers.bindEvents(this, this.options.tooltipEvents, this.onHover);
//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);
// Build Scale
this.buildScale(this.data.labels);
//Create a new line and its points for each dataset and piece of data
helpers.each(this.data.datasets,function(dataset,datasetIndex){
dataset.metaDataset = new Chart.Line();
dataset.metaData = [];
helpers.each(dataset.data,function(dataPoint,index){
dataset.metaData.push(new this.PointClass());
},this);
},this);
// Set defaults for lines
this.eachDataset(function(dataset, datasetIndex){
dataset = helpers.merge(this.options, dataset);
helpers.extend(dataset.metaDataset, {
_points: dataset.metaData,
_datasetIndex: datasetIndex,
_chart: this.chart,
});
// Copy to view model
dataset.metaDataset.save();
}, this);
// Set defaults for points
this.eachElement(function(point, index, dataset, datasetIndex){
helpers.extend(point, {
x: this.scale.calculateX(index),
y: this.scale.endPoint,
_datasetIndex: datasetIndex,
_index: index,
_chart: this.chart
});
// Default bezier control points
helpers.extend(point, {
controlPointPreviousX: this.previousPoint(dataset, index).x,
controlPointPreviousY: this.nextPoint(dataset, index).y,
controlPointNextX: this.previousPoint(dataset, index).x,
controlPointNextY: this.nextPoint(dataset, index).y,
});
// Copy to view model
point.save();
}, this);
// Create tooltip instance exclusively for this chart with some defaults.
this.tooltip = new Chart.Tooltip({
_chart: this.chart,
_data: this.data,
_options: this.options,
}, this);
this.update();
},
nextPoint: function(collection, index){
return collection[index - 1] || collection[index];
},
previousPoint: function(collection, index){
return collection[index + 1] || collection[index];
},
onHover: function(e){
// If exiting chart
if(e.type == 'mouseout'){
return this;
}
//Iterate through each of the datasets, and build this into a property of the chart
helpers.each(data.datasets,function(dataset){
this.lastActive = this.lastActive || [];
var datasetObject = {
label : dataset.label || null,
fillColor : dataset.fillColor,
strokeColor : dataset.strokeColor,
pointColor : dataset.pointColor,
pointStrokeColor : dataset.pointStrokeColor,
points : []
};
// Find Active Elements
this.active = function(){
switch(this.options.hoverMode){
case 'single':
return this.getElementAtEvent(e);
case 'label':
return this.getElementsAtEvent(e);
case 'dataset':
return this.getDatasetAtEvent(e);
default:
return e;
}
}.call(this);
this.datasets.push(datasetObject);
// On Hover hook
if(this.options.onHover){
this.options.onHover.call(this, this.active);
}
// Remove styling for last active (even if it may still be active)
if(this.lastActive.length){
switch(this.options.hoverMode){
case 'single':
this.lastActive[0].backgroundColor = this.data.datasets[this.lastActive[0]._datasetIndex].pointBackgroundColor;
this.lastActive[0].borderColor = this.data.datasets[this.lastActive[0]._datasetIndex].pointBorderColor;
this.lastActive[0].borderWidth = this.data.datasets[this.lastActive[0]._datasetIndex].pointBorderWidth;
break;
case 'label':
for (var i = 0; i < this.lastActive.length; i++) {
this.lastActive[i].backgroundColor = this.data.datasets[this.lastActive[i]._datasetIndex].pointBackgroundColor;
this.lastActive[i].borderColor = this.data.datasets[this.lastActive[i]._datasetIndex].pointBorderColor;
this.lastActive[i].borderWidth = this.data.datasets[this.lastActive[0]._datasetIndex].pointBorderWidth;
}
break;
case 'dataset':
break;
default:
// Don't change anything
}
}
// Built in hover styling
if(this.active.length && this.options.hoverMode){
switch(this.options.hoverMode){
case 'single':
this.active[0].backgroundColor = this.data.datasets[this.active[0]._datasetIndex].hoverBackgroundColor || helpers.color(this.active[0].backgroundColor).saturate(0.5).darken(0.35).rgbString();
this.active[0].borderColor = this.data.datasets[this.active[0]._datasetIndex].hoverBorderColor || helpers.color(this.active[0].borderColor).saturate(0.5).darken(0.35).rgbString();
this.active[0].borderWidth = this.data.datasets[this.active[0]._datasetIndex].borderWidth + 10;
break;
case 'label':
for (var i = 0; i < this.active.length; i++) {
this.active[i].backgroundColor = this.data.datasets[this.active[i]._datasetIndex].hoverBackgroundColor || helpers.color(this.active[i].backgroundColor).saturate(0.5).darken(0.35).rgbString();
this.active[i].borderColor = this.data.datasets[this.active[i]._datasetIndex].hoverBorderColor || helpers.color(this.active[i].borderColor).saturate(0.5).darken(0.35).rgbString();
this.active[i].borderWidth = this.data.datasets[this.active[i]._datasetIndex].borderWidth + 2;
}
break;
case 'dataset':
break;
default:
// Don't change anything
}
}
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
}));
},this);
// Built in Tooltips
if(this.options.showTooltips){
this.buildScale(data.labels);
// The usual updates
this.tooltip.initialize();
this.eachPoints(function(point, index){
helpers.extend(point, {
x: this.scale.calculateX(index),
y: this.scale.endPoint
// Active
if(this.active.length){
helpers.extend(this.tooltip, {
opacity: 1,
_active: this.active,
});
point.save();
this.tooltip.update();
}
else{
// Inactive
helpers.extend(this.tooltip, {
opacity: 0,
});
}
}
// Hover animations
if(!this.animating){
var changed;
helpers.each(this.active, function(element, index){
if (element !== this.lastActive[index]){
changed = true;
}
}, this);
},this);
// If entering, leaving, or changing elements, animate the change via pivot
if ((!this.lastActive.length && this.active.length) ||
(this.lastActive.length && !this.active.length)||
(this.lastActive.length && this.active.length && changed)){
this.tooltip.pivot();
this.stop();
this.render(this.options.hoverAnimationDuration);
}
}
this.render();
// Remember Last Active
this.lastActive = this.active;
return this;
},
update : function(){
//Iterate through each of the datasets, and build this into a property of the chart
helpers.each(this.data.datasets,function(dataset,datasetIndex){
helpers.extend(this.datasets[datasetIndex], {
label : dataset.label || null,
fillColor : dataset.fillColor,
strokeColor : dataset.strokeColor,
pointColor : dataset.pointColor,
pointStrokeColor : dataset.pointStrokeColor,
});
helpers.each(dataset.data,function(dataPoint,index){
helpers.extend(this.datasets[datasetIndex].points[index], {
value : dataPoint,
label : this.data.labels[index],
datasetLabel: dataset.label,
strokeColor : dataset.pointStrokeColor,
fillColor : dataset.pointColor,
highlightFill : dataset.pointHighlightFill || dataset.pointColor,
highlightStroke : dataset.pointHighlightStroke || dataset.pointStrokeColor
});
},this);
},this);
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);
// Update the lines
this.eachDataset(function(dataset, datasetIndex){
helpers.extend(dataset.metaDataset, {
backgroundColor: dataset.backgroundColor || this.options.backgroundColor,
borderWidth: dataset.borderWidth || this.options.borderWidth,
borderColor: dataset.borderColor || this.options.borderColor,
tension: dataset.tension || this.options.tension,
scaleTop: this.scale.startPoint,
scaleBottom: this.scale.endPoint,
_points: dataset.metaData,
_datasetIndex: datasetIndex,
});
},this);
return pointsArray;
dataset.metaDataset.pivot();
});
// Update the points
this.eachElement(function(point, index, dataset, datasetIndex){
helpers.extend(point, {
x: this.scale.calculateX(index),
y: this.scale.calculateY(this.data.datasets[datasetIndex].data[index]),
value : this.data.datasets[datasetIndex].data[index],
label : this.data.labels[index],
datasetLabel: this.data.datasets[datasetIndex].label,
// Appearance
hoverBackgroundColor: this.data.datasets[datasetIndex].pointHoverBackgroundColor || this.options.pointHoverBackgroundColor,
hoverBorderColor : this.data.datasets[datasetIndex].pointHoverBorderColor || this.options.pointHoverBorderColor,
hoverRadius : this.data.datasets[datasetIndex].pointHoverRadius || this.options.pointHoverRadius,
radius: this.data.datasets[datasetIndex].pointRadius || this.options.pointRadius,
borderWidth: this.data.datasets[datasetIndex].pointBorderWidth || this.options.pointBorderWidth,
borderColor: this.data.datasets[datasetIndex].pointBorderColor || this.options.pointBorderColor,
backgroundColor: this.data.datasets[datasetIndex].pointBackgroundColor || this.options.pointBackgroundColor,
tension: this.data.datasets[datasetIndex].metaDataset.tension,
_datasetIndex: datasetIndex,
_index: index,
});
}, this);
// Update control points for the bezier curve
this.eachElement(function(point, index, dataset, datasetIndex){
var controlPoints = helpers.splineCurve(
this.previousPoint(dataset, index),
point,
this.nextPoint(dataset, index),
point.tension
);
point.controlPointPreviousX = controlPoints.previous.x;
point.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.scale.endPoint){
point.controlPointNextY = this.scale.endPoint;
}
else if (controlPoints.next.y < this.scale.startPoint){
point.controlPointNextY = this.scale.startPoint;
}
else{
point.controlPointNextY = controlPoints.next.y;
}
// Cap inner bezier handles to the upper/lower scale bounds
if (controlPoints.previous.y > this.scale.endPoint){
point.controlPointPreviousY = this.scale.endPoint;
}
else if (controlPoints.previous.y < this.scale.startPoint){
point.controlPointPreviousY = this.scale.startPoint;
}
else{
point.controlPointPreviousY = controlPoints.previous.y;
}
// Now pivot the point for animation
point.pivot();
}, this);
this.render();
},
buildScale : function(labels){
var self = this;
var dataTotal = function(){
var values = [];
self.eachPoints(function(point){
values.push(point.value);
self.eachValue(function(value){
values.push(value);
});
return values;
@@ -225,7 +366,7 @@
);
helpers.extend(this, updatedRanges);
},
xLabels : labels,
xLabels : this.data.labels,
font : helpers.fontString(this.options.scaleFontSize, this.options.scaleFontStyle, this.options.scaleFontFamily),
lineWidth : this.options.scaleLineWidth,
lineColor : this.options.scaleLineColor,
@@ -233,7 +374,7 @@
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,
padding: (this.options.showScale) ? 0 : this.options.pointRadius + this.options.pointBorderWidth,
showLabels : this.options.scaleShowLabels,
display : this.options.showScale
};
@@ -248,159 +389,36 @@
});
}
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,
datasetLabel: this.datasets[datasetIndex].label,
x: this.scale.calculateX(this.scale.valuesCount+1),
y: this.scale.endPoint,
strokeColor : this.datasets[datasetIndex].pointStrokeColor,
fillColor : this.datasets[datasetIndex].pointColor
}));
},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);
redraw : function(){
},
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);
this.eachDataset(function(dataset, datasetIndex){
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.value),
x : this.scale.calculateX(index)
}, easingDecimal);
}
// Transition Point Locations
helpers.each(dataset.metaData, function(point, index){
point.transition(easingDecimal);
},this);
// Transition and Draw the line
dataset.metaDataset.transition(easingDecimal).draw();
// 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){
// Draw the points
helpers.each(dataset.metaData,function(point){
point.draw();
});
},this);
// Finally draw the tooltip
this.tooltip.transition(easingDecimal).draw();
}
});