Clean up code climate style issues and formatting.

This commit is contained in:
etimberg 2016-02-14 17:06:00 -05:00
parent 756208413b
commit 01b88f19df
32 changed files with 5700 additions and 5694 deletions

View File

@ -2,10 +2,10 @@
module.exports = function(Chart) { module.exports = function(Chart) {
Chart.Bar = function(context, config) { Chart.Bar = function(context, config) {
config.type = 'bar'; config.type = 'bar';
return new Chart(context, config); return new Chart(context, config);
}; };
}; };

View File

@ -2,9 +2,9 @@
module.exports = function(Chart) { module.exports = function(Chart) {
Chart.Bubble = function(context, config) { Chart.Bubble = function(context, config) {
config.type = 'bubble'; config.type = 'bubble';
return new Chart(context, config); return new Chart(context, config);
}; };
}; };

View File

@ -2,10 +2,10 @@
module.exports = function(Chart) { module.exports = function(Chart) {
Chart.Doughnut = function(context, config) { Chart.Doughnut = function(context, config) {
config.type = 'doughnut'; config.type = 'doughnut';
return new Chart(context, config); return new Chart(context, config);
}; };
}; };

View File

@ -2,10 +2,10 @@
module.exports = function(Chart) { module.exports = function(Chart) {
Chart.Line = function(context, config) { Chart.Line = function(context, config) {
config.type = 'line'; config.type = 'line';
return new Chart(context, config); return new Chart(context, config);
}; };
}; };

View File

@ -2,10 +2,10 @@
module.exports = function(Chart) { module.exports = function(Chart) {
Chart.PolarArea = function(context, config) { Chart.PolarArea = function(context, config) {
config.type = 'polarArea'; config.type = 'polarArea';
return new Chart(context, config); return new Chart(context, config);
}; };
}; };

View File

@ -2,17 +2,17 @@
module.exports = function(Chart) { module.exports = function(Chart) {
var helpers = Chart.helpers; var helpers = Chart.helpers;
var defaultConfig = { var defaultConfig = {
aspectRatio: 1, aspectRatio: 1
}; };
Chart.Radar = function(context, config) { Chart.Radar = function(context, config) {
config.options = helpers.configMerge(defaultConfig, config.options); config.options = helpers.configMerge(defaultConfig, config.options);
config.type = 'radar'; config.type = 'radar';
return new Chart(context, config); return new Chart(context, config);
}; };
}; };

View File

@ -2,46 +2,46 @@
module.exports = function(Chart) { module.exports = function(Chart) {
var defaultConfig = { var defaultConfig = {
hover: { hover: {
mode: 'single', mode: 'single'
}, },
scales: { scales: {
xAxes: [{ xAxes: [{
type: "linear", // scatter should not use a category axis type: "linear", // scatter should not use a category axis
position: "bottom", position: "bottom",
id: "x-axis-1", // need an ID so datasets can reference the scale id: "x-axis-1" // need an ID so datasets can reference the scale
}], }],
yAxes: [{ yAxes: [{
type: "linear", type: "linear",
position: "left", position: "left",
id: "y-axis-1", id: "y-axis-1"
}], }]
}, },
tooltips: { tooltips: {
callbacks: { callbacks: {
title: function(tooltipItems, data) { title: function(tooltipItems, data) {
// Title doesn't make sense for scatter since we format the data as a point // Title doesn't make sense for scatter since we format the data as a point
return ''; return '';
}, },
label: function(tooltipItem, data) { label: function(tooltipItem, data) {
return '(' + tooltipItem.xLabel + ', ' + tooltipItem.yLabel + ')'; return '(' + tooltipItem.xLabel + ', ' + tooltipItem.yLabel + ')';
} }
} }
}, }
}; };
// Register the default config for this type // Register the default config for this type
Chart.defaults.scatter = defaultConfig; Chart.defaults.scatter = defaultConfig;
// Scatter charts use line controllers // Scatter charts use line controllers
Chart.controllers.scatter = Chart.controllers.line; Chart.controllers.scatter = Chart.controllers.line;
Chart.Scatter = function(context, config) { Chart.Scatter = function(context, config) {
config.type = 'scatter'; config.type = 'scatter';
return new Chart(context, config); return new Chart(context, config);
}; };
}; };

View File

@ -2,304 +2,304 @@
module.exports = function(Chart) { module.exports = function(Chart) {
var helpers = Chart.helpers; var helpers = Chart.helpers;
Chart.defaults.bar = { Chart.defaults.bar = {
hover: { hover: {
mode: "label" mode: "label"
}, },
scales: { scales: {
xAxes: [{ xAxes: [{
type: "category", type: "category",
// Specific to Bar Controller // Specific to Bar Controller
categoryPercentage: 0.8, categoryPercentage: 0.8,
barPercentage: 0.9, barPercentage: 0.9,
// grid line settings // grid line settings
gridLines: { gridLines: {
offsetGridLines: true, offsetGridLines: true
}, }
}], }],
yAxes: [{ yAxes: [{
type: "linear", type: "linear"
}], }]
}, }
}; };
Chart.controllers.bar = Chart.DatasetController.extend({ Chart.controllers.bar = Chart.DatasetController.extend({
initialize: function(chart, datasetIndex) { initialize: function(chart, datasetIndex) {
Chart.DatasetController.prototype.initialize.call(this, chart, datasetIndex); Chart.DatasetController.prototype.initialize.call(this, chart, datasetIndex);
// Use this to indicate that this is a bar dataset. // Use this to indicate that this is a bar dataset.
this.getDataset().bar = true; this.getDataset().bar = true;
}, },
// Get the number of datasets that display bars. We use this to correctly calculate the bar width // Get the number of datasets that display bars. We use this to correctly calculate the bar width
getBarCount: function getBarCount() { getBarCount: function getBarCount() {
var barCount = 0; var barCount = 0;
helpers.each(this.chart.data.datasets, function(dataset) { helpers.each(this.chart.data.datasets, function(dataset) {
if (helpers.isDatasetVisible(dataset) && dataset.bar) { if (helpers.isDatasetVisible(dataset) && dataset.bar) {
++barCount; ++barCount;
} }
}); });
return barCount; return barCount;
}, },
addElements: function() { addElements: function() {
this.getDataset().metaData = this.getDataset().metaData || []; this.getDataset().metaData = this.getDataset().metaData || [];
helpers.each(this.getDataset().data, function(value, index) { helpers.each(this.getDataset().data, function(value, index) {
this.getDataset().metaData[index] = this.getDataset().metaData[index] || new Chart.elements.Rectangle({ this.getDataset().metaData[index] = this.getDataset().metaData[index] || new Chart.elements.Rectangle({
_chart: this.chart.chart, _chart: this.chart.chart,
_datasetIndex: this.index, _datasetIndex: this.index,
_index: index, _index: index
}); });
}, this); }, this);
}, },
addElementAndReset: function(index) { addElementAndReset: function(index) {
this.getDataset().metaData = this.getDataset().metaData || []; this.getDataset().metaData = this.getDataset().metaData || [];
var rectangle = new Chart.elements.Rectangle({ var rectangle = new Chart.elements.Rectangle({
_chart: this.chart.chart, _chart: this.chart.chart,
_datasetIndex: this.index, _datasetIndex: this.index,
_index: index, _index: index
}); });
var numBars = this.getBarCount(); var numBars = this.getBarCount();
this.updateElement(rectangle, index, true, numBars); this.updateElement(rectangle, index, true, numBars);
this.getDataset().metaData.splice(index, 0, rectangle); this.getDataset().metaData.splice(index, 0, rectangle);
}, },
update: function update(reset) { update: function update(reset) {
var numBars = this.getBarCount(); var numBars = this.getBarCount();
helpers.each(this.getDataset().metaData, function(rectangle, index) { helpers.each(this.getDataset().metaData, function(rectangle, index) {
this.updateElement(rectangle, index, reset, numBars); this.updateElement(rectangle, index, reset, numBars);
}, this); }, this);
}, },
updateElement: function updateElement(rectangle, index, reset, numBars) { updateElement: function updateElement(rectangle, index, reset, numBars) {
var xScale = this.getScaleForId(this.getDataset().xAxisID); var xScale = this.getScaleForId(this.getDataset().xAxisID);
var yScale = this.getScaleForId(this.getDataset().yAxisID); var yScale = this.getScaleForId(this.getDataset().yAxisID);
var yScalePoint; var yScalePoint;
if (yScale.min < 0 && yScale.max < 0) { if (yScale.min < 0 && yScale.max < 0) {
// all less than 0. use the top // all less than 0. use the top
yScalePoint = yScale.getPixelForValue(yScale.max); yScalePoint = yScale.getPixelForValue(yScale.max);
} else if (yScale.min > 0 && yScale.max > 0) { } else if (yScale.min > 0 && yScale.max > 0) {
yScalePoint = yScale.getPixelForValue(yScale.min); yScalePoint = yScale.getPixelForValue(yScale.min);
} else { } else {
yScalePoint = yScale.getPixelForValue(0); yScalePoint = yScale.getPixelForValue(0);
} }
helpers.extend(rectangle, { helpers.extend(rectangle, {
// Utility // Utility
_chart: this.chart.chart, _chart: this.chart.chart,
_xScale: xScale, _xScale: xScale,
_yScale: yScale, _yScale: yScale,
_datasetIndex: this.index, _datasetIndex: this.index,
_index: index, _index: index,
// Desired view properties // Desired view properties
_model: { _model: {
x: this.calculateBarX(index, this.index), x: this.calculateBarX(index, this.index),
y: reset ? yScalePoint : this.calculateBarY(index, this.index), y: reset ? yScalePoint : this.calculateBarY(index, this.index),
// Tooltip // Tooltip
label: this.chart.data.labels[index], label: this.chart.data.labels[index],
datasetLabel: this.getDataset().label, datasetLabel: this.getDataset().label,
// Appearance // Appearance
base: this.calculateBarBase(this.index, index), base: this.calculateBarBase(this.index, index),
width: this.calculateBarWidth(numBars), width: this.calculateBarWidth(numBars),
backgroundColor: rectangle.custom && rectangle.custom.backgroundColor ? rectangle.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.getDataset().backgroundColor, index, this.chart.options.elements.rectangle.backgroundColor), 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), 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), borderWidth: rectangle.custom && rectangle.custom.borderWidth ? rectangle.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.getDataset().borderWidth, index, this.chart.options.elements.rectangle.borderWidth)
}, }
}); });
rectangle.pivot(); rectangle.pivot();
}, },
calculateBarBase: function(datasetIndex, index) { calculateBarBase: function(datasetIndex, index) {
var xScale = this.getScaleForId(this.getDataset().xAxisID); var xScale = this.getScaleForId(this.getDataset().xAxisID);
var yScale = this.getScaleForId(this.getDataset().yAxisID); var yScale = this.getScaleForId(this.getDataset().yAxisID);
var base = 0; var base = 0;
if (yScale.options.stacked) { if (yScale.options.stacked) {
var value = this.chart.data.datasets[datasetIndex].data[index]; var value = this.chart.data.datasets[datasetIndex].data[index];
if (value < 0) { if (value < 0) {
for (var i = 0; i < datasetIndex; i++) { for (var i = 0; i < datasetIndex; i++) {
var negDS = this.chart.data.datasets[i]; var negDS = this.chart.data.datasets[i];
if (helpers.isDatasetVisible(negDS) && negDS.yAxisID === yScale.id && negDS.bar) { if (helpers.isDatasetVisible(negDS) && negDS.yAxisID === yScale.id && negDS.bar) {
base += negDS.data[index] < 0 ? negDS.data[index] : 0; base += negDS.data[index] < 0 ? negDS.data[index] : 0;
} }
} }
} else { } else {
for (var j = 0; j < datasetIndex; j++) { for (var j = 0; j < datasetIndex; j++) {
var posDS = this.chart.data.datasets[j]; var posDS = this.chart.data.datasets[j];
if (helpers.isDatasetVisible(posDS) && posDS.yAxisID === yScale.id && posDS.bar) { if (helpers.isDatasetVisible(posDS) && posDS.yAxisID === yScale.id && posDS.bar) {
base += posDS.data[index] > 0 ? posDS.data[index] : 0; base += posDS.data[index] > 0 ? posDS.data[index] : 0;
} }
} }
} }
return yScale.getPixelForValue(base); return yScale.getPixelForValue(base);
} }
base = yScale.getPixelForValue(yScale.min); base = yScale.getPixelForValue(yScale.min);
if (yScale.beginAtZero || ((yScale.min <= 0 && yScale.max >= 0) || (yScale.min >= 0 && yScale.max <= 0))) { if (yScale.beginAtZero || ((yScale.min <= 0 && yScale.max >= 0) || (yScale.min >= 0 && yScale.max <= 0))) {
base = yScale.getPixelForValue(0, 0); base = yScale.getPixelForValue(0, 0);
//base += yScale.options.gridLines.lineWidth; //base += yScale.options.gridLines.lineWidth;
} else if (yScale.min < 0 && yScale.max < 0) { } else if (yScale.min < 0 && yScale.max < 0) {
// All values are negative. Use the top as the base // All values are negative. Use the top as the base
base = yScale.getPixelForValue(yScale.max); base = yScale.getPixelForValue(yScale.max);
} }
return base; return base;
}, },
getRuler: function() { getRuler: function() {
var xScale = this.getScaleForId(this.getDataset().xAxisID); var xScale = this.getScaleForId(this.getDataset().xAxisID);
var yScale = this.getScaleForId(this.getDataset().yAxisID); var yScale = this.getScaleForId(this.getDataset().yAxisID);
var datasetCount = this.getBarCount(); var datasetCount = this.getBarCount();
var tickWidth = (function() { var tickWidth = (function() {
var min = xScale.getPixelForTick(1) - xScale.getPixelForTick(0); var min = xScale.getPixelForTick(1) - xScale.getPixelForTick(0);
for (var i = 2; i < this.getDataset().data.length; i++) { for (var i = 2; i < this.getDataset().data.length; i++) {
min = Math.min(xScale.getPixelForTick(i) - xScale.getPixelForTick(i - 1), min); min = Math.min(xScale.getPixelForTick(i) - xScale.getPixelForTick(i - 1), min);
} }
return min; return min;
}).call(this); }).call(this);
var categoryWidth = tickWidth * xScale.options.categoryPercentage; var categoryWidth = tickWidth * xScale.options.categoryPercentage;
var categorySpacing = (tickWidth - (tickWidth * xScale.options.categoryPercentage)) / 2; var categorySpacing = (tickWidth - (tickWidth * xScale.options.categoryPercentage)) / 2;
var fullBarWidth = categoryWidth / datasetCount; var fullBarWidth = categoryWidth / datasetCount;
var barWidth = fullBarWidth * xScale.options.barPercentage; var barWidth = fullBarWidth * xScale.options.barPercentage;
var barSpacing = fullBarWidth - (fullBarWidth * xScale.options.barPercentage); var barSpacing = fullBarWidth - (fullBarWidth * xScale.options.barPercentage);
return { return {
datasetCount: datasetCount, datasetCount: datasetCount,
tickWidth: tickWidth, tickWidth: tickWidth,
categoryWidth: categoryWidth, categoryWidth: categoryWidth,
categorySpacing: categorySpacing, categorySpacing: categorySpacing,
fullBarWidth: fullBarWidth, fullBarWidth: fullBarWidth,
barWidth: barWidth, barWidth: barWidth,
barSpacing: barSpacing, barSpacing: barSpacing
}; };
}, },
calculateBarWidth: function() { calculateBarWidth: function() {
var xScale = this.getScaleForId(this.getDataset().xAxisID); var xScale = this.getScaleForId(this.getDataset().xAxisID);
var ruler = this.getRuler(); var ruler = this.getRuler();
return xScale.options.stacked ? ruler.categoryWidth : ruler.barWidth; return xScale.options.stacked ? ruler.categoryWidth : ruler.barWidth;
}, },
// Get bar index from the given dataset index accounting for the fact that not all bars are visible // Get bar index from the given dataset index accounting for the fact that not all bars are visible
getBarIndex: function(datasetIndex) { getBarIndex: function(datasetIndex) {
var barIndex = 0; var barIndex = 0;
for (var j = 0; j < datasetIndex; ++j) { for (var j = 0; j < datasetIndex; ++j) {
if (helpers.isDatasetVisible(this.chart.data.datasets[j]) && this.chart.data.datasets[j].bar) { if (helpers.isDatasetVisible(this.chart.data.datasets[j]) && this.chart.data.datasets[j].bar) {
++barIndex; ++barIndex;
} }
} }
return barIndex; return barIndex;
}, },
calculateBarX: function(index, datasetIndex) { calculateBarX: function(index, datasetIndex) {
var yScale = this.getScaleForId(this.getDataset().yAxisID); var yScale = this.getScaleForId(this.getDataset().yAxisID);
var xScale = this.getScaleForId(this.getDataset().xAxisID); var xScale = this.getScaleForId(this.getDataset().xAxisID);
var barIndex = this.getBarIndex(datasetIndex); var barIndex = this.getBarIndex(datasetIndex);
var ruler = this.getRuler(); var ruler = this.getRuler();
var leftTick = xScale.getPixelForValue(null, index, datasetIndex, this.chart.isCombo); var leftTick = xScale.getPixelForValue(null, index, datasetIndex, this.chart.isCombo);
leftTick -= this.chart.isCombo ? (ruler.tickWidth / 2) : 0; leftTick -= this.chart.isCombo ? (ruler.tickWidth / 2) : 0;
if (xScale.options.stacked) { if (xScale.options.stacked) {
return leftTick + (ruler.categoryWidth / 2) + ruler.categorySpacing; return leftTick + (ruler.categoryWidth / 2) + ruler.categorySpacing;
} }
return leftTick + return leftTick +
(ruler.barWidth / 2) + (ruler.barWidth / 2) +
ruler.categorySpacing + ruler.categorySpacing +
(ruler.barWidth * barIndex) + (ruler.barWidth * barIndex) +
(ruler.barSpacing / 2) + (ruler.barSpacing / 2) +
(ruler.barSpacing * barIndex); (ruler.barSpacing * barIndex);
}, },
calculateBarY: function(index, datasetIndex) { calculateBarY: function(index, datasetIndex) {
var xScale = this.getScaleForId(this.getDataset().xAxisID); var xScale = this.getScaleForId(this.getDataset().xAxisID);
var yScale = this.getScaleForId(this.getDataset().yAxisID); var yScale = this.getScaleForId(this.getDataset().yAxisID);
var value = this.getDataset().data[index]; var value = this.getDataset().data[index];
if (yScale.options.stacked) { if (yScale.options.stacked) {
var sumPos = 0, var sumPos = 0,
sumNeg = 0; sumNeg = 0;
for (var i = 0; i < datasetIndex; i++) { for (var i = 0; i < datasetIndex; i++) {
var ds = this.chart.data.datasets[i]; var ds = this.chart.data.datasets[i];
if (helpers.isDatasetVisible(ds) && ds.bar && ds.yAxisID === yScale.id) { if (helpers.isDatasetVisible(ds) && ds.bar && ds.yAxisID === yScale.id) {
if (ds.data[index] < 0) { if (ds.data[index] < 0) {
sumNeg += ds.data[index] || 0; sumNeg += ds.data[index] || 0;
} else { } else {
sumPos += ds.data[index] || 0; sumPos += ds.data[index] || 0;
} }
} }
} }
if (value < 0) { if (value < 0) {
return yScale.getPixelForValue(sumNeg + value); return yScale.getPixelForValue(sumNeg + value);
} else { } else {
return yScale.getPixelForValue(sumPos + value); return yScale.getPixelForValue(sumPos + value);
} }
return yScale.getPixelForValue(value); return yScale.getPixelForValue(value);
} }
return yScale.getPixelForValue(value); return yScale.getPixelForValue(value);
}, },
draw: function(ease) { draw: function(ease) {
var easingDecimal = ease || 1; var easingDecimal = ease || 1;
helpers.each(this.getDataset().metaData, function(rectangle, index) { helpers.each(this.getDataset().metaData, function(rectangle, index) {
var d = this.getDataset().data[index]; var d = this.getDataset().data[index];
if (d !== null && d !== undefined && !isNaN(d)) { if (d !== null && d !== undefined && !isNaN(d)) {
rectangle.transition(easingDecimal).draw(); rectangle.transition(easingDecimal).draw();
} }
}, this); }, this);
}, },
setHoverStyle: function(rectangle) { setHoverStyle: function(rectangle) {
var dataset = this.chart.data.datasets[rectangle._datasetIndex]; var dataset = this.chart.data.datasets[rectangle._datasetIndex];
var index = rectangle._index; 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.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.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.hoverBorderWidth, index, rectangle._model.borderWidth); rectangle._model.borderWidth = rectangle.custom && rectangle.custom.hoverBorderWidth ? rectangle.custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.hoverBorderWidth, index, rectangle._model.borderWidth);
}, },
removeHoverStyle: function(rectangle) { removeHoverStyle: function(rectangle) {
var dataset = this.chart.data.datasets[rectangle._datasetIndex]; var dataset = this.chart.data.datasets[rectangle._datasetIndex];
var index = rectangle._index; var index = rectangle._index;
rectangle._model.backgroundColor = rectangle.custom && rectangle.custom.backgroundColor ? rectangle.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.getDataset().backgroundColor, index, this.chart.options.elements.rectangle.backgroundColor); rectangle._model.backgroundColor = rectangle.custom && rectangle.custom.backgroundColor ? rectangle.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.getDataset().backgroundColor, index, this.chart.options.elements.rectangle.backgroundColor);
rectangle._model.borderColor = rectangle.custom && rectangle.custom.borderColor ? rectangle.custom.borderColor : helpers.getValueAtIndexOrDefault(this.getDataset().borderColor, index, this.chart.options.elements.rectangle.borderColor); rectangle._model.borderColor = rectangle.custom && rectangle.custom.borderColor ? rectangle.custom.borderColor : helpers.getValueAtIndexOrDefault(this.getDataset().borderColor, index, this.chart.options.elements.rectangle.borderColor);
rectangle._model.borderWidth = rectangle.custom && rectangle.custom.borderWidth ? rectangle.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.getDataset().borderWidth, index, this.chart.options.elements.rectangle.borderWidth); rectangle._model.borderWidth = rectangle.custom && rectangle.custom.borderWidth ? rectangle.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.getDataset().borderWidth, index, this.chart.options.elements.rectangle.borderWidth);
} }
}); });
}; };

View File

@ -13,13 +13,13 @@ module.exports = function(Chart) {
xAxes: [{ xAxes: [{
type: "linear", // bubble should probably use a linear scale by default type: "linear", // bubble should probably use a linear scale by default
position: "bottom", position: "bottom",
id: "x-axis-0", // need an ID so datasets can reference the scale id: "x-axis-0" // need an ID so datasets can reference the scale
}], }],
yAxes: [{ yAxes: [{
type: "linear", type: "linear",
position: "left", position: "left",
id: "y-axis-0", id: "y-axis-0"
}], }]
}, },
tooltips: { tooltips: {
@ -34,7 +34,7 @@ module.exports = function(Chart) {
return datasetLabel + ': (' + dataPoint.x + ', ' + dataPoint.y + ', ' + dataPoint.r + ')'; return datasetLabel + ': (' + dataPoint.x + ', ' + dataPoint.y + ', ' + dataPoint.r + ')';
} }
} }
}, }
}; };
@ -47,7 +47,7 @@ module.exports = function(Chart) {
this.getDataset().metaData[index] = this.getDataset().metaData[index] || new Chart.elements.Point({ this.getDataset().metaData[index] = this.getDataset().metaData[index] || new Chart.elements.Point({
_chart: this.chart.chart, _chart: this.chart.chart,
_datasetIndex: this.index, _datasetIndex: this.index,
_index: index, _index: index
}); });
}, this); }, this);
}, },
@ -56,7 +56,7 @@ module.exports = function(Chart) {
var point = new Chart.elements.Point({ var point = new Chart.elements.Point({
_chart: this.chart.chart, _chart: this.chart.chart,
_datasetIndex: this.index, _datasetIndex: this.index,
_index: index, _index: index
}); });
// Reset the point // Reset the point
@ -120,8 +120,8 @@ module.exports = function(Chart) {
borderWidth: point.custom && point.custom.borderWidth ? point.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.getDataset().borderWidth, index, this.chart.options.elements.point.borderWidth), borderWidth: point.custom && point.custom.borderWidth ? point.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.getDataset().borderWidth, index, this.chart.options.elements.point.borderWidth),
// Tooltip // Tooltip
hitRadius: point.custom && point.custom.hitRadius ? point.custom.hitRadius : helpers.getValueAtIndexOrDefault(this.getDataset().hitRadius, index, this.chart.options.elements.point.hitRadius), hitRadius: point.custom && point.custom.hitRadius ? point.custom.hitRadius : helpers.getValueAtIndexOrDefault(this.getDataset().hitRadius, index, this.chart.options.elements.point.hitRadius)
}, }
}); });
point._model.skip = point.custom && point.custom.skip ? point.custom.skip : (isNaN(point._model.x) || isNaN(point._model.y)); point._model.skip = point.custom && point.custom.skip ? point.custom.skip : (isNaN(point._model.x) || isNaN(point._model.y));

View File

@ -9,7 +9,7 @@ module.exports = function(Chart) {
//Boolean - Whether we animate the rotation of the Doughnut //Boolean - Whether we animate the rotation of the Doughnut
animateRotate: true, animateRotate: true,
//Boolean - Whether we animate scaling the Doughnut from the centre //Boolean - Whether we animate scaling the Doughnut from the centre
animateScale: false, animateScale: false
}, },
aspectRatio: 1, aspectRatio: 1,
hover: { hover: {
@ -70,7 +70,9 @@ module.exports = function(Chart) {
// Need to override these to give a nice default // Need to override these to give a nice default
tooltips: { tooltips: {
callbacks: { callbacks: {
title: function() { return '';}, title: function() {
return '';
},
label: function(tooltipItem, data) { label: function(tooltipItem, data) {
return data.labels[tooltipItem.index] + ': ' + data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index]; return data.labels[tooltipItem.index] + ': ' + data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index];
} }
@ -95,7 +97,7 @@ module.exports = function(Chart) {
this.getDataset().metaData[index] = this.getDataset().metaData[index] || new Chart.elements.Arc({ this.getDataset().metaData[index] = this.getDataset().metaData[index] || new Chart.elements.Arc({
_chart: this.chart.chart, _chart: this.chart.chart,
_datasetIndex: this.index, _datasetIndex: this.index,
_index: index, _index: index
}); });
}, this); }, this);
}, },
@ -104,7 +106,7 @@ module.exports = function(Chart) {
var arc = new Chart.elements.Arc({ var arc = new Chart.elements.Arc({
_chart: this.chart.chart, _chart: this.chart.chart,
_datasetIndex: this.index, _datasetIndex: this.index,
_index: index, _index: index
}); });
if (colorForNewElement && helpers.isArray(this.getDataset().backgroundColor)) { if (colorForNewElement && helpers.isArray(this.getDataset().backgroundColor)) {
@ -119,7 +121,9 @@ module.exports = function(Chart) {
}, },
getVisibleDatasetCount: function getVisibleDatasetCount() { getVisibleDatasetCount: function getVisibleDatasetCount() {
return helpers.where(this.chart.data.datasets, function(ds) { return helpers.isDatasetVisible(ds); }).length; return helpers.where(this.chart.data.datasets, function(ds) {
return helpers.isDatasetVisible(ds);
}).length;
}, },
// Get index of the dataset in relation to the visible datasets. This allows determining the inner and outer radius correctly // Get index of the dataset in relation to the visible datasets. This allows determining the inner and outer radius correctly
@ -190,7 +194,7 @@ module.exports = function(Chart) {
borderColor: arc.custom && arc.custom.borderColor ? arc.custom.borderColor : helpers.getValueAtIndexOrDefault(this.getDataset().borderColor, index, this.chart.options.elements.arc.borderColor), borderColor: arc.custom && arc.custom.borderColor ? arc.custom.borderColor : helpers.getValueAtIndexOrDefault(this.getDataset().borderColor, index, this.chart.options.elements.arc.borderColor),
label: helpers.getValueAtIndexOrDefault(this.getDataset().label, index, this.chart.data.labels[index]) label: helpers.getValueAtIndexOrDefault(this.getDataset().label, index, this.chart.data.labels[index])
}, }
}); });
if (!reset) { if (!reset) {
@ -244,7 +248,6 @@ module.exports = function(Chart) {
} else { } else {
return 0; return 0;
} }
}, }
}); });
}; };

View File

@ -2,289 +2,289 @@
module.exports = function(Chart) { module.exports = function(Chart) {
var helpers = Chart.helpers; var helpers = Chart.helpers;
Chart.defaults.line = { Chart.defaults.line = {
showLines: true, showLines: true,
hover: { hover: {
mode: "label" mode: "label"
}, },
scales: { scales: {
xAxes: [{ xAxes: [{
type: "category", type: "category",
id: 'x-axis-0' id: 'x-axis-0'
}], }],
yAxes: [{ yAxes: [{
type: "linear", type: "linear",
id: 'y-axis-0' id: 'y-axis-0'
}], }]
}, }
}; };
Chart.controllers.line = Chart.DatasetController.extend({ Chart.controllers.line = Chart.DatasetController.extend({
addElements: function() { addElements: function() {
this.getDataset().metaData = this.getDataset().metaData || []; this.getDataset().metaData = this.getDataset().metaData || [];
this.getDataset().metaDataset = this.getDataset().metaDataset || new Chart.elements.Line({ this.getDataset().metaDataset = this.getDataset().metaDataset || new Chart.elements.Line({
_chart: this.chart.chart, _chart: this.chart.chart,
_datasetIndex: this.index, _datasetIndex: this.index,
_points: this.getDataset().metaData, _points: this.getDataset().metaData
}); });
helpers.each(this.getDataset().data, function(value, index) { helpers.each(this.getDataset().data, function(value, index) {
this.getDataset().metaData[index] = this.getDataset().metaData[index] || new Chart.elements.Point({ this.getDataset().metaData[index] = this.getDataset().metaData[index] || new Chart.elements.Point({
_chart: this.chart.chart, _chart: this.chart.chart,
_datasetIndex: this.index, _datasetIndex: this.index,
_index: index, _index: index
}); });
}, this); }, this);
}, },
addElementAndReset: function(index) { addElementAndReset: function(index) {
this.getDataset().metaData = this.getDataset().metaData || []; this.getDataset().metaData = this.getDataset().metaData || [];
var point = new Chart.elements.Point({ var point = new Chart.elements.Point({
_chart: this.chart.chart, _chart: this.chart.chart,
_datasetIndex: this.index, _datasetIndex: this.index,
_index: index, _index: index
}); });
// Reset the point // Reset the point
this.updateElement(point, index, true); this.updateElement(point, index, true);
// Add to the points array // Add to the points array
this.getDataset().metaData.splice(index, 0, point); this.getDataset().metaData.splice(index, 0, point);
// Make sure bezier control points are updated // Make sure bezier control points are updated
if (this.chart.options.showLines && this.chart.options.elements.line.tension !== 0) if (this.chart.options.showLines && this.chart.options.elements.line.tension !== 0)
this.updateBezierControlPoints(); this.updateBezierControlPoints();
}, },
update: function update(reset) { update: function update(reset) {
var line = this.getDataset().metaDataset; var line = this.getDataset().metaDataset;
var points = this.getDataset().metaData; var points = this.getDataset().metaData;
var yScale = this.getScaleForId(this.getDataset().yAxisID); var yScale = this.getScaleForId(this.getDataset().yAxisID);
var xScale = this.getScaleForId(this.getDataset().xAxisID); var xScale = this.getScaleForId(this.getDataset().xAxisID);
var scaleBase; var scaleBase;
if (yScale.min < 0 && yScale.max < 0) { if (yScale.min < 0 && yScale.max < 0) {
scaleBase = yScale.getPixelForValue(yScale.max); scaleBase = yScale.getPixelForValue(yScale.max);
} else if (yScale.min > 0 && yScale.max > 0) { } else if (yScale.min > 0 && yScale.max > 0) {
scaleBase = yScale.getPixelForValue(yScale.min); scaleBase = yScale.getPixelForValue(yScale.min);
} else { } else {
scaleBase = yScale.getPixelForValue(0); scaleBase = yScale.getPixelForValue(0);
} }
// Update Line // Update Line
if (this.chart.options.showLines) { if (this.chart.options.showLines) {
// Utility // Utility
line._scale = yScale; line._scale = yScale;
line._datasetIndex = this.index; line._datasetIndex = this.index;
// Data // Data
line._children = points; line._children = points;
// Model // Model
line._model = { line._model = {
// Appearance // Appearance
tension: line.custom && line.custom.tension ? line.custom.tension : helpers.getValueOrDefault(this.getDataset().tension, this.chart.options.elements.line.tension), tension: line.custom && line.custom.tension ? line.custom.tension : helpers.getValueOrDefault(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), 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), 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), borderColor: line.custom && line.custom.borderColor ? line.custom.borderColor : (this.getDataset().borderColor || this.chart.options.elements.line.borderColor),
borderCapStyle: line.custom && line.custom.borderCapStyle ? line.custom.borderCapStyle : (this.getDataset().borderCapStyle || this.chart.options.elements.line.borderCapStyle), borderCapStyle: line.custom && line.custom.borderCapStyle ? line.custom.borderCapStyle : (this.getDataset().borderCapStyle || this.chart.options.elements.line.borderCapStyle),
borderDash: line.custom && line.custom.borderDash ? line.custom.borderDash : (this.getDataset().borderDash || this.chart.options.elements.line.borderDash), borderDash: line.custom && line.custom.borderDash ? line.custom.borderDash : (this.getDataset().borderDash || this.chart.options.elements.line.borderDash),
borderDashOffset: line.custom && line.custom.borderDashOffset ? line.custom.borderDashOffset : (this.getDataset().borderDashOffset || this.chart.options.elements.line.borderDashOffset), borderDashOffset: line.custom && line.custom.borderDashOffset ? line.custom.borderDashOffset : (this.getDataset().borderDashOffset || this.chart.options.elements.line.borderDashOffset),
borderJoinStyle: line.custom && line.custom.borderJoinStyle ? line.custom.borderJoinStyle : (this.getDataset().borderJoinStyle || this.chart.options.elements.line.borderJoinStyle), borderJoinStyle: line.custom && line.custom.borderJoinStyle ? line.custom.borderJoinStyle : (this.getDataset().borderJoinStyle || this.chart.options.elements.line.borderJoinStyle),
fill: line.custom && line.custom.fill ? line.custom.fill : (this.getDataset().fill !== undefined ? this.getDataset().fill : this.chart.options.elements.line.fill), fill: line.custom && line.custom.fill ? line.custom.fill : (this.getDataset().fill !== undefined ? this.getDataset().fill : this.chart.options.elements.line.fill),
// Scale // Scale
scaleTop: yScale.top, scaleTop: yScale.top,
scaleBottom: yScale.bottom, scaleBottom: yScale.bottom,
scaleZero: scaleBase scaleZero: scaleBase
}; };
line.pivot(); line.pivot();
} }
// Update Points // Update Points
helpers.each(points, function(point, index) { helpers.each(points, function(point, index) {
this.updateElement(point, index, reset); this.updateElement(point, index, reset);
}, this); }, this);
if (this.chart.options.showLines && this.chart.options.elements.line.tension !== 0) if (this.chart.options.showLines && this.chart.options.elements.line.tension !== 0)
this.updateBezierControlPoints(); this.updateBezierControlPoints();
}, },
getPointBackgroundColor: function(point, index) { getPointBackgroundColor: function(point, index) {
var backgroundColor = this.chart.options.elements.point.backgroundColor; var backgroundColor = this.chart.options.elements.point.backgroundColor;
var dataset = this.getDataset(); var dataset = this.getDataset();
if (point.custom && point.custom.backgroundColor) { if (point.custom && point.custom.backgroundColor) {
backgroundColor = point.custom.backgroundColor; backgroundColor = point.custom.backgroundColor;
} else if (dataset.pointBackgroundColor) { } else if (dataset.pointBackgroundColor) {
backgroundColor = helpers.getValueAtIndexOrDefault(dataset.pointBackgroundColor, index, backgroundColor); backgroundColor = helpers.getValueAtIndexOrDefault(dataset.pointBackgroundColor, index, backgroundColor);
} else if (dataset.backgroundColor) { } else if (dataset.backgroundColor) {
backgroundColor = dataset.backgroundColor; backgroundColor = dataset.backgroundColor;
} }
return backgroundColor; return backgroundColor;
}, },
getPointBorderColor: function(point, index) { getPointBorderColor: function(point, index) {
var borderColor = this.chart.options.elements.point.borderColor; var borderColor = this.chart.options.elements.point.borderColor;
var dataset = this.getDataset(); var dataset = this.getDataset();
if (point.custom && point.custom.borderColor) { if (point.custom && point.custom.borderColor) {
borderColor = point.custom.borderColor; borderColor = point.custom.borderColor;
} else if (dataset.pointBorderColor) { } else if (dataset.pointBorderColor) {
borderColor = helpers.getValueAtIndexOrDefault(this.getDataset().pointBorderColor, index, borderColor); borderColor = helpers.getValueAtIndexOrDefault(this.getDataset().pointBorderColor, index, borderColor);
} else if (dataset.borderColor) { } else if (dataset.borderColor) {
borderColor = dataset.borderColor; borderColor = dataset.borderColor;
} }
return borderColor; return borderColor;
}, },
getPointBorderWidth: function(point, index) { getPointBorderWidth: function(point, index) {
var borderWidth = this.chart.options.elements.point.borderWidth; var borderWidth = this.chart.options.elements.point.borderWidth;
var dataset = this.getDataset(); var dataset = this.getDataset();
if (point.custom && point.custom.borderWidth !== undefined) { if (point.custom && point.custom.borderWidth !== undefined) {
borderWidth = point.custom.borderWidth; borderWidth = point.custom.borderWidth;
} else if (dataset.pointBorderWidth !== undefined) { } else if (dataset.pointBorderWidth !== undefined) {
borderWidth = helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, borderWidth); borderWidth = helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, borderWidth);
} else if (dataset.borderWidth !== undefined) { } else if (dataset.borderWidth !== undefined) {
borderWidth = dataset.borderWidth; borderWidth = dataset.borderWidth;
} }
return borderWidth; return borderWidth;
}, },
updateElement: function(point, index, reset) { updateElement: function(point, index, reset) {
var yScale = this.getScaleForId(this.getDataset().yAxisID); var yScale = this.getScaleForId(this.getDataset().yAxisID);
var xScale = this.getScaleForId(this.getDataset().xAxisID); var xScale = this.getScaleForId(this.getDataset().xAxisID);
var scaleBase; var scaleBase;
if (yScale.min < 0 && yScale.max < 0) { if (yScale.min < 0 && yScale.max < 0) {
scaleBase = yScale.getPixelForValue(yScale.max); scaleBase = yScale.getPixelForValue(yScale.max);
} else if (yScale.min > 0 && yScale.max > 0) { } else if (yScale.min > 0 && yScale.max > 0) {
scaleBase = yScale.getPixelForValue(yScale.min); scaleBase = yScale.getPixelForValue(yScale.min);
} else { } else {
scaleBase = yScale.getPixelForValue(0); scaleBase = yScale.getPixelForValue(0);
} }
// Utility // Utility
point._chart = this.chart.chart; point._chart = this.chart.chart;
point._xScale = xScale; point._xScale = xScale;
point._yScale = yScale; point._yScale = yScale;
point._datasetIndex = this.index; point._datasetIndex = this.index;
point._index = index; point._index = index;
// Desired view properties // Desired view properties
point._model = { point._model = {
x: xScale.getPixelForValue(this.getDataset().data[index], index, this.index, this.chart.isCombo), x: xScale.getPixelForValue(this.getDataset().data[index], index, this.index, this.chart.isCombo),
y: reset ? scaleBase : this.calculatePointY(this.getDataset().data[index], index, this.index, this.chart.isCombo), y: reset ? scaleBase : this.calculatePointY(this.getDataset().data[index], index, this.index, this.chart.isCombo),
// Appearance // Appearance
tension: point.custom && point.custom.tension ? point.custom.tension : helpers.getValueOrDefault(this.getDataset().tension, this.chart.options.elements.line.tension), tension: point.custom && point.custom.tension ? point.custom.tension : helpers.getValueOrDefault(this.getDataset().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), radius: point.custom && point.custom.radius ? point.custom.radius : helpers.getValueAtIndexOrDefault(this.getDataset().radius, index, this.chart.options.elements.point.radius),
pointStyle: point.custom && point.custom.pointStyle ? point.custom.pointStyle : helpers.getValueAtIndexOrDefault(this.getDataset().pointStyle, index, this.chart.options.elements.point.pointStyle), pointStyle: point.custom && point.custom.pointStyle ? point.custom.pointStyle : helpers.getValueAtIndexOrDefault(this.getDataset().pointStyle, index, this.chart.options.elements.point.pointStyle),
backgroundColor: this.getPointBackgroundColor(point, index), backgroundColor: this.getPointBackgroundColor(point, index),
borderColor: this.getPointBorderColor(point, index), borderColor: this.getPointBorderColor(point, index),
borderWidth: this.getPointBorderWidth(point, index), borderWidth: this.getPointBorderWidth(point, index),
// Tooltip // Tooltip
hitRadius: point.custom && point.custom.hitRadius ? point.custom.hitRadius : helpers.getValueAtIndexOrDefault(this.getDataset().hitRadius, index, this.chart.options.elements.point.hitRadius) hitRadius: point.custom && point.custom.hitRadius ? point.custom.hitRadius : helpers.getValueAtIndexOrDefault(this.getDataset().hitRadius, index, this.chart.options.elements.point.hitRadius)
}; };
point._model.skip = point.custom && point.custom.skip ? point.custom.skip : (isNaN(point._model.x) || isNaN(point._model.y)); point._model.skip = point.custom && point.custom.skip ? point.custom.skip : (isNaN(point._model.x) || isNaN(point._model.y));
}, },
calculatePointY: function(value, index, datasetIndex, isCombo) { calculatePointY: function(value, index, datasetIndex, isCombo) {
var xScale = this.getScaleForId(this.getDataset().xAxisID); var xScale = this.getScaleForId(this.getDataset().xAxisID);
var yScale = this.getScaleForId(this.getDataset().yAxisID); var yScale = this.getScaleForId(this.getDataset().yAxisID);
if (yScale.options.stacked) { if (yScale.options.stacked) {
var sumPos = 0, var sumPos = 0,
sumNeg = 0; sumNeg = 0;
for (var i = this.chart.data.datasets.length - 1; i > datasetIndex; i--) { for (var i = this.chart.data.datasets.length - 1; i > datasetIndex; i--) {
var ds = this.chart.data.datasets[i]; var ds = this.chart.data.datasets[i];
if (ds.type === 'line' && helpers.isDatasetVisible(ds)) { if (ds.type === 'line' && helpers.isDatasetVisible(ds)) {
if (ds.data[index] < 0) { if (ds.data[index] < 0) {
sumNeg += ds.data[index] || 0; sumNeg += ds.data[index] || 0;
} else { } else {
sumPos += ds.data[index] || 0; sumPos += ds.data[index] || 0;
} }
} }
} }
if (value < 0) { if (value < 0) {
return yScale.getPixelForValue(sumNeg + value); return yScale.getPixelForValue(sumNeg + value);
} else { } else {
return yScale.getPixelForValue(sumPos + value); return yScale.getPixelForValue(sumPos + value);
} }
} }
return yScale.getPixelForValue(value); return yScale.getPixelForValue(value);
}, },
updateBezierControlPoints: function() { updateBezierControlPoints: function() {
// Update bezier control points // Update bezier control points
helpers.each(this.getDataset().metaData, function(point, index) { helpers.each(this.getDataset().metaData, function(point, index) {
var controlPoints = helpers.splineCurve( var controlPoints = helpers.splineCurve(
helpers.previousItem(this.getDataset().metaData, index)._model, helpers.previousItem(this.getDataset().metaData, index)._model,
point._model, point._model,
helpers.nextItem(this.getDataset().metaData, index)._model, helpers.nextItem(this.getDataset().metaData, index)._model,
point._model.tension point._model.tension
); );
// Prevent the bezier going outside of the bounds of the graph // Prevent the bezier going outside of the bounds of the graph
point._model.controlPointPreviousX = Math.max(Math.min(controlPoints.previous.x, this.chart.chartArea.right), this.chart.chartArea.left); point._model.controlPointPreviousX = Math.max(Math.min(controlPoints.previous.x, this.chart.chartArea.right), this.chart.chartArea.left);
point._model.controlPointPreviousY = Math.max(Math.min(controlPoints.previous.y, this.chart.chartArea.bottom), this.chart.chartArea.top); point._model.controlPointPreviousY = Math.max(Math.min(controlPoints.previous.y, this.chart.chartArea.bottom), this.chart.chartArea.top);
point._model.controlPointNextX = Math.max(Math.min(controlPoints.next.x, this.chart.chartArea.right), this.chart.chartArea.left); point._model.controlPointNextX = Math.max(Math.min(controlPoints.next.x, this.chart.chartArea.right), this.chart.chartArea.left);
point._model.controlPointNextY = Math.max(Math.min(controlPoints.next.y, this.chart.chartArea.bottom), this.chart.chartArea.top); point._model.controlPointNextY = Math.max(Math.min(controlPoints.next.y, this.chart.chartArea.bottom), this.chart.chartArea.top);
// Now pivot the point for animation // Now pivot the point for animation
point.pivot(); point.pivot();
}, this); }, this);
}, },
draw: function(ease) { draw: function(ease) {
var easingDecimal = ease || 1; var easingDecimal = ease || 1;
// Transition Point Locations // Transition Point Locations
helpers.each(this.getDataset().metaData, function(point) { helpers.each(this.getDataset().metaData, function(point) {
point.transition(easingDecimal); point.transition(easingDecimal);
}); });
// Transition and Draw the line // Transition and Draw the line
if (this.chart.options.showLines) if (this.chart.options.showLines)
this.getDataset().metaDataset.transition(easingDecimal).draw(); this.getDataset().metaDataset.transition(easingDecimal).draw();
// Draw the points // Draw the points
helpers.each(this.getDataset().metaData, function(point) { helpers.each(this.getDataset().metaData, function(point) {
point.draw(); point.draw();
}); });
}, },
setHoverStyle: function(point) { setHoverStyle: function(point) {
// Point // Point
var dataset = this.chart.data.datasets[point._datasetIndex]; var dataset = this.chart.data.datasets[point._datasetIndex];
var index = point._index; var index = point._index;
point._model.radius = point.custom && point.custom.hoverRadius ? point.custom.hoverRadius : helpers.getValueAtIndexOrDefault(dataset.pointHoverRadius, index, this.chart.options.elements.point.hoverRadius); point._model.radius = point.custom && point.custom.hoverRadius ? point.custom.hoverRadius : 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.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.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.pointHoverBorderWidth, index, point._model.borderWidth); point._model.borderWidth = point.custom && point.custom.hoverBorderWidth ? point.custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.pointHoverBorderWidth, index, point._model.borderWidth);
}, },
removeHoverStyle: function(point) { removeHoverStyle: function(point) {
var dataset = this.chart.data.datasets[point._datasetIndex]; var dataset = this.chart.data.datasets[point._datasetIndex];
var index = point._index; var index = point._index;
point._model.radius = point.custom && point.custom.radius ? point.custom.radius : helpers.getValueAtIndexOrDefault(this.getDataset().radius, index, this.chart.options.elements.point.radius); point._model.radius = point.custom && point.custom.radius ? point.custom.radius : helpers.getValueAtIndexOrDefault(this.getDataset().radius, index, this.chart.options.elements.point.radius);
point._model.backgroundColor = this.getPointBackgroundColor(point, index); point._model.backgroundColor = this.getPointBackgroundColor(point, index);
point._model.borderColor = this.getPointBorderColor(point, index); point._model.borderColor = this.getPointBorderColor(point, index);
point._model.borderWidth = this.getPointBorderWidth(point, index); point._model.borderWidth = this.getPointBorderWidth(point, index);
} }
}); });
}; };

View File

@ -8,7 +8,7 @@ module.exports = function(Chart) {
scale: { scale: {
type: "radialLinear", type: "radialLinear",
lineArc: true, // so that lines are circular lineArc: true // so that lines are circular
}, },
//Boolean - Whether to animate the rotation of the chart //Boolean - Whether to animate the rotation of the chart
@ -68,7 +68,9 @@ module.exports = function(Chart) {
// Need to override these to give a nice default // Need to override these to give a nice default
tooltips: { tooltips: {
callbacks: { callbacks: {
title: function() { return ''; }, title: function() {
return '';
},
label: function(tooltipItem, data) { label: function(tooltipItem, data) {
return data.labels[tooltipItem.index] + ': ' + tooltipItem.yLabel; return data.labels[tooltipItem.index] + ': ' + tooltipItem.yLabel;
} }
@ -86,7 +88,7 @@ module.exports = function(Chart) {
this.getDataset().metaData[index] = this.getDataset().metaData[index] || new Chart.elements.Arc({ this.getDataset().metaData[index] = this.getDataset().metaData[index] || new Chart.elements.Arc({
_chart: this.chart.chart, _chart: this.chart.chart,
_datasetIndex: this.index, _datasetIndex: this.index,
_index: index, _index: index
}); });
}, this); }, this);
}, },
@ -95,7 +97,7 @@ module.exports = function(Chart) {
var arc = new Chart.elements.Arc({ var arc = new Chart.elements.Arc({
_chart: this.chart.chart, _chart: this.chart.chart,
_datasetIndex: this.index, _datasetIndex: this.index,
_index: index, _index: index
}); });
// Reset the point // Reset the point
@ -105,7 +107,9 @@ module.exports = function(Chart) {
this.getDataset().metaData.splice(index, 0, arc); this.getDataset().metaData.splice(index, 0, arc);
}, },
getVisibleDatasetCount: function getVisibleDatasetCount() { getVisibleDatasetCount: function getVisibleDatasetCount() {
return helpers.where(this.chart.data.datasets, function(ds) { return helpers.isDatasetVisible(ds); }).length; return helpers.where(this.chart.data.datasets, function(ds) {
return helpers.isDatasetVisible(ds);
}).length;
}, },
update: function update(reset) { update: function update(reset) {
@ -180,7 +184,7 @@ module.exports = function(Chart) {
borderColor: arc.custom && arc.custom.borderColor ? arc.custom.borderColor : helpers.getValueAtIndexOrDefault(this.getDataset().borderColor, index, this.chart.options.elements.arc.borderColor), borderColor: arc.custom && arc.custom.borderColor ? arc.custom.borderColor : helpers.getValueAtIndexOrDefault(this.getDataset().borderColor, index, this.chart.options.elements.arc.borderColor),
label: helpers.getValueAtIndexOrDefault(this.chart.data.labels, index, this.chart.data.labels[index]) label: helpers.getValueAtIndexOrDefault(this.chart.data.labels, index, this.chart.data.labels[index])
}, }
}); });
arc.pivot(); arc.pivot();

View File

@ -2,204 +2,204 @@
module.exports = function(Chart) { module.exports = function(Chart) {
var helpers = Chart.helpers; var helpers = Chart.helpers;
Chart.defaults.radar = { Chart.defaults.radar = {
scale: { scale: {
type: "radialLinear", type: "radialLinear"
}, },
elements: { elements: {
line: { line: {
tension: 0, // no bezier in radar tension: 0 // no bezier in radar
} }
}, }
}; };
Chart.controllers.radar = Chart.DatasetController.extend({ Chart.controllers.radar = Chart.DatasetController.extend({
linkScales: function() { linkScales: function() {
// No need. Single scale only // No need. Single scale only
}, },
addElements: function() { addElements: function() {
this.getDataset().metaData = this.getDataset().metaData || []; this.getDataset().metaData = this.getDataset().metaData || [];
this.getDataset().metaDataset = this.getDataset().metaDataset || new Chart.elements.Line({ this.getDataset().metaDataset = this.getDataset().metaDataset || new Chart.elements.Line({
_chart: this.chart.chart, _chart: this.chart.chart,
_datasetIndex: this.index, _datasetIndex: this.index,
_points: this.getDataset().metaData, _points: this.getDataset().metaData,
_loop: true _loop: true
}); });
helpers.each(this.getDataset().data, function(value, index) { helpers.each(this.getDataset().data, function(value, index) {
this.getDataset().metaData[index] = this.getDataset().metaData[index] || new Chart.elements.Point({ this.getDataset().metaData[index] = this.getDataset().metaData[index] || new Chart.elements.Point({
_chart: this.chart.chart, _chart: this.chart.chart,
_datasetIndex: this.index, _datasetIndex: this.index,
_index: index, _index: index,
_model: { _model: {
x: 0, //xScale.getPixelForValue(null, index, true), x: 0, //xScale.getPixelForValue(null, index, true),
y: 0, //this.chartArea.bottom, y: 0 //this.chartArea.bottom,
}, }
}); });
}, this); }, this);
}, },
addElementAndReset: function(index) { addElementAndReset: function(index) {
this.getDataset().metaData = this.getDataset().metaData || []; this.getDataset().metaData = this.getDataset().metaData || [];
var point = new Chart.elements.Point({ var point = new Chart.elements.Point({
_chart: this.chart.chart, _chart: this.chart.chart,
_datasetIndex: this.index, _datasetIndex: this.index,
_index: index, _index: index
}); });
// Reset the point // Reset the point
this.updateElement(point, index, true); this.updateElement(point, index, true);
// Add to the points array // Add to the points array
this.getDataset().metaData.splice(index, 0, point); this.getDataset().metaData.splice(index, 0, point);
// Make sure bezier control points are updated // Make sure bezier control points are updated
this.updateBezierControlPoints(); this.updateBezierControlPoints();
}, },
update: function update(reset) { update: function update(reset) {
var line = this.getDataset().metaDataset; var line = this.getDataset().metaDataset;
var points = this.getDataset().metaData; var points = this.getDataset().metaData;
var scale = this.chart.scale; var scale = this.chart.scale;
var scaleBase; var scaleBase;
if (scale.min < 0 && scale.max < 0) { if (scale.min < 0 && scale.max < 0) {
scaleBase = scale.getPointPositionForValue(0, scale.max); scaleBase = scale.getPointPositionForValue(0, scale.max);
} else if (scale.min > 0 && scale.max > 0) { } else if (scale.min > 0 && scale.max > 0) {
scaleBase = scale.getPointPositionForValue(0, scale.min); scaleBase = scale.getPointPositionForValue(0, scale.min);
} else { } else {
scaleBase = scale.getPointPositionForValue(0, 0); scaleBase = scale.getPointPositionForValue(0, 0);
} }
helpers.extend(this.getDataset().metaDataset, { helpers.extend(this.getDataset().metaDataset, {
// Utility // Utility
_datasetIndex: this.index, _datasetIndex: this.index,
// Data // Data
_children: this.getDataset().metaData, _children: this.getDataset().metaData,
// Model // Model
_model: { _model: {
// Appearance // Appearance
tension: line.custom && line.custom.tension ? line.custom.tension : helpers.getValueOrDefault(this.getDataset().tension, this.chart.options.elements.line.tension), tension: line.custom && line.custom.tension ? line.custom.tension : helpers.getValueOrDefault(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), 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), 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), 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), fill: line.custom && line.custom.fill ? line.custom.fill : (this.getDataset().fill !== undefined ? this.getDataset().fill : this.chart.options.elements.line.fill),
borderCapStyle: line.custom && line.custom.borderCapStyle ? line.custom.borderCapStyle : (this.getDataset().borderCapStyle || this.chart.options.elements.line.borderCapStyle), borderCapStyle: line.custom && line.custom.borderCapStyle ? line.custom.borderCapStyle : (this.getDataset().borderCapStyle || this.chart.options.elements.line.borderCapStyle),
borderDash: line.custom && line.custom.borderDash ? line.custom.borderDash : (this.getDataset().borderDash || this.chart.options.elements.line.borderDash), borderDash: line.custom && line.custom.borderDash ? line.custom.borderDash : (this.getDataset().borderDash || this.chart.options.elements.line.borderDash),
borderDashOffset: line.custom && line.custom.borderDashOffset ? line.custom.borderDashOffset : (this.getDataset().borderDashOffset || this.chart.options.elements.line.borderDashOffset), borderDashOffset: line.custom && line.custom.borderDashOffset ? line.custom.borderDashOffset : (this.getDataset().borderDashOffset || this.chart.options.elements.line.borderDashOffset),
borderJoinStyle: line.custom && line.custom.borderJoinStyle ? line.custom.borderJoinStyle : (this.getDataset().borderJoinStyle || this.chart.options.elements.line.borderJoinStyle), borderJoinStyle: line.custom && line.custom.borderJoinStyle ? line.custom.borderJoinStyle : (this.getDataset().borderJoinStyle || this.chart.options.elements.line.borderJoinStyle),
// Scale // Scale
scaleTop: scale.top, scaleTop: scale.top,
scaleBottom: scale.bottom, scaleBottom: scale.bottom,
scaleZero: scaleBase, scaleZero: scaleBase
}, }
}); });
this.getDataset().metaDataset.pivot(); this.getDataset().metaDataset.pivot();
// Update Points // Update Points
helpers.each(points, function(point, index) { helpers.each(points, function(point, index) {
this.updateElement(point, index, reset); this.updateElement(point, index, reset);
}, this); }, this);
// Update bezier control points // Update bezier control points
this.updateBezierControlPoints(); this.updateBezierControlPoints();
}, },
updateElement: function(point, index, reset) { updateElement: function(point, index, reset) {
var pointPosition = this.chart.scale.getPointPositionForValue(index, this.getDataset().data[index]); var pointPosition = this.chart.scale.getPointPositionForValue(index, this.getDataset().data[index]);
helpers.extend(point, { helpers.extend(point, {
// Utility // Utility
_datasetIndex: this.index, _datasetIndex: this.index,
_index: index, _index: index,
_scale: this.chart.scale, _scale: this.chart.scale,
// Desired view properties // Desired view properties
_model: { _model: {
x: reset ? this.chart.scale.xCenter : pointPosition.x, // value not used in dataset scale, but we want a consistent API between scales x: reset ? this.chart.scale.xCenter : pointPosition.x, // value not used in dataset scale, but we want a consistent API between scales
y: reset ? this.chart.scale.yCenter : pointPosition.y, y: reset ? this.chart.scale.yCenter : pointPosition.y,
// Appearance // Appearance
tension: point.custom && point.custom.tension ? point.custom.tension : helpers.getValueOrDefault(this.getDataset().tension, this.chart.options.elements.line.tension), tension: point.custom && point.custom.tension ? point.custom.tension : helpers.getValueOrDefault(this.getDataset().tension, this.chart.options.elements.line.tension),
radius: point.custom && point.custom.radius ? point.custom.radius : helpers.getValueAtIndexOrDefault(this.getDataset().pointRadius, index, this.chart.options.elements.point.radius), radius: point.custom && point.custom.radius ? point.custom.radius : helpers.getValueAtIndexOrDefault(this.getDataset().pointRadius, 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), 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), 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), borderWidth: point.custom && point.custom.borderWidth ? point.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.getDataset().pointBorderWidth, index, this.chart.options.elements.point.borderWidth),
pointStyle: point.custom && point.custom.pointStyle ? point.custom.pointStyle : helpers.getValueAtIndexOrDefault(this.getDataset().pointStyle, index, this.chart.options.elements.point.pointStyle), pointStyle: point.custom && point.custom.pointStyle ? point.custom.pointStyle : helpers.getValueAtIndexOrDefault(this.getDataset().pointStyle, index, this.chart.options.elements.point.pointStyle),
// Tooltip // Tooltip
hitRadius: point.custom && point.custom.hitRadius ? point.custom.hitRadius : helpers.getValueAtIndexOrDefault(this.getDataset().hitRadius, index, this.chart.options.elements.point.hitRadius), hitRadius: point.custom && point.custom.hitRadius ? point.custom.hitRadius : helpers.getValueAtIndexOrDefault(this.getDataset().hitRadius, index, this.chart.options.elements.point.hitRadius)
}, }
}); });
point._model.skip = point.custom && point.custom.skip ? point.custom.skip : (isNaN(point._model.x) || isNaN(point._model.y)); point._model.skip = point.custom && point.custom.skip ? point.custom.skip : (isNaN(point._model.x) || isNaN(point._model.y));
}, },
updateBezierControlPoints: function() { updateBezierControlPoints: function() {
helpers.each(this.getDataset().metaData, function(point, index) { helpers.each(this.getDataset().metaData, function(point, index) {
var controlPoints = helpers.splineCurve( var controlPoints = helpers.splineCurve(
helpers.previousItem(this.getDataset().metaData, index, true)._model, helpers.previousItem(this.getDataset().metaData, index, true)._model,
point._model, point._model,
helpers.nextItem(this.getDataset().metaData, index, true)._model, helpers.nextItem(this.getDataset().metaData, index, true)._model,
point._model.tension point._model.tension
); );
// Prevent the bezier going outside of the bounds of the graph // Prevent the bezier going outside of the bounds of the graph
point._model.controlPointPreviousX = Math.max(Math.min(controlPoints.previous.x, this.chart.chartArea.right), this.chart.chartArea.left); point._model.controlPointPreviousX = Math.max(Math.min(controlPoints.previous.x, this.chart.chartArea.right), this.chart.chartArea.left);
point._model.controlPointPreviousY = Math.max(Math.min(controlPoints.previous.y, this.chart.chartArea.bottom), this.chart.chartArea.top); point._model.controlPointPreviousY = Math.max(Math.min(controlPoints.previous.y, this.chart.chartArea.bottom), this.chart.chartArea.top);
point._model.controlPointNextX = Math.max(Math.min(controlPoints.next.x, this.chart.chartArea.right), this.chart.chartArea.left); point._model.controlPointNextX = Math.max(Math.min(controlPoints.next.x, this.chart.chartArea.right), this.chart.chartArea.left);
point._model.controlPointNextY = Math.max(Math.min(controlPoints.next.y, this.chart.chartArea.bottom), this.chart.chartArea.top); point._model.controlPointNextY = Math.max(Math.min(controlPoints.next.y, this.chart.chartArea.bottom), this.chart.chartArea.top);
// Now pivot the point for animation // Now pivot the point for animation
point.pivot(); point.pivot();
}, this); }, this);
}, },
draw: function(ease) { draw: function(ease) {
var easingDecimal = ease || 1; var easingDecimal = ease || 1;
// Transition Point Locations // Transition Point Locations
helpers.each(this.getDataset().metaData, function(point, index) { helpers.each(this.getDataset().metaData, function(point, index) {
point.transition(easingDecimal); point.transition(easingDecimal);
}); });
// Transition and Draw the line // Transition and Draw the line
this.getDataset().metaDataset.transition(easingDecimal).draw(); this.getDataset().metaDataset.transition(easingDecimal).draw();
// Draw the points // Draw the points
helpers.each(this.getDataset().metaData, function(point) { helpers.each(this.getDataset().metaData, function(point) {
point.draw(); point.draw();
}); });
}, },
setHoverStyle: function(point) { setHoverStyle: function(point) {
// Point // Point
var dataset = this.chart.data.datasets[point._datasetIndex]; var dataset = this.chart.data.datasets[point._datasetIndex];
var index = point._index; var index = point._index;
point._model.radius = point.custom && point.custom.hoverRadius ? point.custom.hoverRadius : helpers.getValueAtIndexOrDefault(dataset.pointHoverRadius, index, this.chart.options.elements.point.hoverRadius); point._model.radius = point.custom && point.custom.hoverRadius ? point.custom.hoverRadius : 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.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.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.pointHoverBorderWidth, index, point._model.borderWidth); point._model.borderWidth = point.custom && point.custom.hoverBorderWidth ? point.custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.pointHoverBorderWidth, index, point._model.borderWidth);
}, },
removeHoverStyle: function(point) { removeHoverStyle: function(point) {
var dataset = this.chart.data.datasets[point._datasetIndex]; var dataset = this.chart.data.datasets[point._datasetIndex];
var index = point._index; var index = point._index;
point._model.radius = point.custom && point.custom.radius ? point.custom.radius : helpers.getValueAtIndexOrDefault(this.getDataset().radius, index, this.chart.options.elements.point.radius); point._model.radius = point.custom && point.custom.radius ? point.custom.radius : helpers.getValueAtIndexOrDefault(this.getDataset().radius, index, this.chart.options.elements.point.radius);
point._model.backgroundColor = point.custom && point.custom.backgroundColor ? point.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.getDataset().pointBackgroundColor, index, this.chart.options.elements.point.backgroundColor); point._model.backgroundColor = point.custom && point.custom.backgroundColor ? point.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.getDataset().pointBackgroundColor, index, this.chart.options.elements.point.backgroundColor);
point._model.borderColor = point.custom && point.custom.borderColor ? point.custom.borderColor : helpers.getValueAtIndexOrDefault(this.getDataset().pointBorderColor, index, this.chart.options.elements.point.borderColor); point._model.borderColor = point.custom && point.custom.borderColor ? point.custom.borderColor : helpers.getValueAtIndexOrDefault(this.getDataset().pointBorderColor, index, this.chart.options.elements.point.borderColor);
point._model.borderWidth = point.custom && point.custom.borderWidth ? point.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.getDataset().pointBorderWidth, index, this.chart.options.elements.point.borderWidth); point._model.borderWidth = point.custom && point.custom.borderWidth ? point.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.getDataset().pointBorderWidth, index, this.chart.options.elements.point.borderWidth);
} }
}); });
}; };

View File

@ -3,116 +3,116 @@
module.exports = function(Chart) { module.exports = function(Chart) {
var helpers = Chart.helpers; var helpers = Chart.helpers;
Chart.defaults.global.animation = { Chart.defaults.global.animation = {
duration: 1000, duration: 1000,
easing: "easeOutQuart", easing: "easeOutQuart",
onProgress: helpers.noop, onProgress: helpers.noop,
onComplete: helpers.noop, onComplete: helpers.noop
}; };
Chart.Animation = Chart.Element.extend({ Chart.Animation = Chart.Element.extend({
currentStep: null, // the current animation step currentStep: null, // the current animation step
numSteps: 60, // default number of steps numSteps: 60, // default number of steps
easing: "", // the easing to use for this animation easing: "", // the easing to use for this animation
render: null, // render function used by the animation service render: null, // render function used by the animation service
onAnimationProgress: null, // user specified callback to fire on each step of the animation onAnimationProgress: null, // user specified callback to fire on each step of the animation
onAnimationComplete: null, // user specified callback to fire when the animation finishes onAnimationComplete: null // user specified callback to fire when the animation finishes
}); });
Chart.animationService = { Chart.animationService = {
frameDuration: 17, frameDuration: 17,
animations: [], animations: [],
dropFrames: 0, dropFrames: 0,
addAnimation: function(chartInstance, animationObject, duration, lazy) { addAnimation: function(chartInstance, animationObject, duration, lazy) {
if (!lazy) { if (!lazy) {
chartInstance.animating = true; chartInstance.animating = true;
} }
for (var index = 0; index < this.animations.length; ++index) { for (var index = 0; index < this.animations.length; ++index) {
if (this.animations[index].chartInstance === chartInstance) { if (this.animations[index].chartInstance === chartInstance) {
// replacing an in progress animation // replacing an in progress animation
this.animations[index].animationObject = animationObject; this.animations[index].animationObject = animationObject;
return; return;
} }
} }
this.animations.push({ this.animations.push({
chartInstance: chartInstance, chartInstance: chartInstance,
animationObject: animationObject animationObject: animationObject
}); });
// If there are no animations queued, manually kickstart a digest, for lack of a better word // If there are no animations queued, manually kickstart a digest, for lack of a better word
if (this.animations.length == 1) { if (this.animations.length === 1) {
helpers.requestAnimFrame.call(window, this.digestWrapper); helpers.requestAnimFrame.call(window, this.digestWrapper);
} }
}, },
// Cancel the animation for a given chart instance // Cancel the animation for a given chart instance
cancelAnimation: function(chartInstance) { cancelAnimation: function(chartInstance) {
var index = helpers.findNextWhere(this.animations, function(animationWrapper) { var index = helpers.findNextWhere(this.animations, function(animationWrapper) {
return animationWrapper.chartInstance === chartInstance; return animationWrapper.chartInstance === chartInstance;
}); });
if (index) { if (index) {
this.animations.splice(index, 1); this.animations.splice(index, 1);
chartInstance.animating = false; chartInstance.animating = false;
} }
}, },
// calls startDigest with the proper context // calls startDigest with the proper context
digestWrapper: function() { digestWrapper: function() {
Chart.animationService.startDigest.call(Chart.animationService); Chart.animationService.startDigest.call(Chart.animationService);
}, },
startDigest: function() { startDigest: function() {
var startTime = Date.now(); var startTime = Date.now();
var framesToDrop = 0; var framesToDrop = 0;
if (this.dropFrames > 1) { if (this.dropFrames > 1) {
framesToDrop = Math.floor(this.dropFrames); framesToDrop = Math.floor(this.dropFrames);
this.dropFrames = this.dropFrames % 1; this.dropFrames = this.dropFrames % 1;
} }
for (var i = 0; i < this.animations.length; i++) { for (var i = 0; i < this.animations.length; i++) {
if (this.animations[i].animationObject.currentStep === null) { if (this.animations[i].animationObject.currentStep === null) {
this.animations[i].animationObject.currentStep = 0; this.animations[i].animationObject.currentStep = 0;
} }
this.animations[i].animationObject.currentStep += 1 + framesToDrop; this.animations[i].animationObject.currentStep += 1 + framesToDrop;
if (this.animations[i].animationObject.currentStep > this.animations[i].animationObject.numSteps) { if (this.animations[i].animationObject.currentStep > this.animations[i].animationObject.numSteps) {
this.animations[i].animationObject.currentStep = this.animations[i].animationObject.numSteps; this.animations[i].animationObject.currentStep = this.animations[i].animationObject.numSteps;
} }
this.animations[i].animationObject.render(this.animations[i].chartInstance, this.animations[i].animationObject); this.animations[i].animationObject.render(this.animations[i].chartInstance, this.animations[i].animationObject);
if (this.animations[i].animationObject.onAnimationProgress && this.animations[i].animationObject.onAnimationProgress.call) { if (this.animations[i].animationObject.onAnimationProgress && this.animations[i].animationObject.onAnimationProgress.call) {
this.animations[i].animationObject.onAnimationProgress.call(this.animations[i].chartInstance, this.animations[i]); this.animations[i].animationObject.onAnimationProgress.call(this.animations[i].chartInstance, this.animations[i]);
} }
if (this.animations[i].animationObject.currentStep == this.animations[i].animationObject.numSteps) { if (this.animations[i].animationObject.currentStep === this.animations[i].animationObject.numSteps) {
if (this.animations[i].animationObject.onAnimationComplete && this.animations[i].animationObject.onAnimationComplete.call) { if (this.animations[i].animationObject.onAnimationComplete && this.animations[i].animationObject.onAnimationComplete.call) {
this.animations[i].animationObject.onAnimationComplete.call(this.animations[i].chartInstance, this.animations[i]); this.animations[i].animationObject.onAnimationComplete.call(this.animations[i].chartInstance, this.animations[i]);
} }
// executed the last frame. Remove the animation. // executed the last frame. Remove the animation.
this.animations[i].chartInstance.animating = false; this.animations[i].chartInstance.animating = false;
this.animations.splice(i, 1); this.animations.splice(i, 1);
// Keep the index in place to offset the splice // Keep the index in place to offset the splice
i--; i--;
} }
} }
var endTime = Date.now(); var endTime = Date.now();
var dropFrames = (endTime - startTime) / this.frameDuration; var dropFrames = (endTime - startTime) / this.frameDuration;
this.dropFrames += dropFrames; this.dropFrames += dropFrames;
// Do we have more stuff to animate? // Do we have more stuff to animate?
if (this.animations.length > 0) { if (this.animations.length > 0) {
helpers.requestAnimFrame.call(window, this.digestWrapper); helpers.requestAnimFrame.call(window, this.digestWrapper);
} }
} }
}; };
}; };

File diff suppressed because it is too large Load Diff

View File

@ -2,72 +2,72 @@
module.exports = function(Chart) { module.exports = function(Chart) {
var helpers = Chart.helpers; var helpers = Chart.helpers;
// Base class for all dataset controllers (line, bar, etc) // Base class for all dataset controllers (line, bar, etc)
Chart.DatasetController = function(chart, datasetIndex) { Chart.DatasetController = function(chart, datasetIndex) {
this.initialize.call(this, chart, datasetIndex); this.initialize.call(this, chart, datasetIndex);
}; };
helpers.extend(Chart.DatasetController.prototype, { helpers.extend(Chart.DatasetController.prototype, {
initialize: function(chart, datasetIndex) { initialize: function(chart, datasetIndex) {
this.chart = chart; this.chart = chart;
this.index = datasetIndex; this.index = datasetIndex;
this.linkScales(); this.linkScales();
this.addElements(); this.addElements();
}, },
updateIndex: function(datasetIndex) { updateIndex: function(datasetIndex) {
this.index = datasetIndex; this.index = datasetIndex;
}, },
linkScales: function() { linkScales: function() {
if (!this.getDataset().xAxisID) { if (!this.getDataset().xAxisID) {
this.getDataset().xAxisID = this.chart.options.scales.xAxes[0].id; this.getDataset().xAxisID = this.chart.options.scales.xAxes[0].id;
} }
if (!this.getDataset().yAxisID) { if (!this.getDataset().yAxisID) {
this.getDataset().yAxisID = this.chart.options.scales.yAxes[0].id; this.getDataset().yAxisID = this.chart.options.scales.yAxes[0].id;
} }
}, },
getDataset: function() { getDataset: function() {
return this.chart.data.datasets[this.index]; return this.chart.data.datasets[this.index];
}, },
getScaleForId: function(scaleID) { getScaleForId: function(scaleID) {
return this.chart.scales[scaleID]; return this.chart.scales[scaleID];
}, },
reset: function() { reset: function() {
this.update(true); this.update(true);
}, },
buildOrUpdateElements: function buildOrUpdateElements() { buildOrUpdateElements: function buildOrUpdateElements() {
// Handle the number of data points changing // Handle the number of data points changing
var numData = this.getDataset().data.length; var numData = this.getDataset().data.length;
var numMetaData = this.getDataset().metaData.length; var numMetaData = this.getDataset().metaData.length;
// Make sure that we handle number of datapoints changing // Make sure that we handle number of datapoints changing
if (numData < numMetaData) { if (numData < numMetaData) {
// Remove excess bars for data points that have been removed // Remove excess bars for data points that have been removed
this.getDataset().metaData.splice(numData, numMetaData - numData); this.getDataset().metaData.splice(numData, numMetaData - numData);
} else if (numData > numMetaData) { } else if (numData > numMetaData) {
// Add new elements // Add new elements
for (var index = numMetaData; index < numData; ++index) { for (var index = numMetaData; index < numData; ++index) {
this.addElementAndReset(index); this.addElementAndReset(index);
} }
} }
}, },
// Controllers should implement the following // Controllers should implement the following
addElements: helpers.noop, addElements: helpers.noop,
addElementAndReset: helpers.noop, addElementAndReset: helpers.noop,
draw: helpers.noop, draw: helpers.noop,
removeHoverStyle: helpers.noop, removeHoverStyle: helpers.noop,
setHoverStyle: helpers.noop, setHoverStyle: helpers.noop,
update: helpers.noop, update: helpers.noop
}); });
Chart.DatasetController.extend = helpers.inherits; Chart.DatasetController.extend = helpers.inherits;
}; };

File diff suppressed because it is too large Load Diff

View File

@ -2,102 +2,102 @@
module.exports = function() { module.exports = function() {
//Occupy the global variable of Chart, and create a simple base class //Occupy the global variable of Chart, and create a simple base class
var Chart = function(context, config) { var Chart = function(context, config) {
this.config = config; this.config = config;
// Support a jQuery'd canvas element // Support a jQuery'd canvas element
if (context.length && context[0].getContext) { if (context.length && context[0].getContext) {
context = context[0]; context = context[0];
} }
// Support a canvas domnode // Support a canvas domnode
if (context.getContext) { if (context.getContext) {
context = context.getContext("2d"); context = context.getContext("2d");
} }
this.ctx = context; this.ctx = context;
this.canvas = context.canvas; this.canvas = context.canvas;
// Figure out what the size of the chart will be. // Figure out what the size of the chart will be.
// If the canvas has a specified width and height, we use those else // If the canvas has a specified width and height, we use those else
// we look to see if the canvas node has a CSS width and height. // we look to see if the canvas node has a CSS width and height.
// If there is still no height, fill the parent container // If there is still no height, fill the parent container
this.width = context.canvas.width || parseInt(Chart.helpers.getStyle(context.canvas, 'width')) || Chart.helpers.getMaximumWidth(context.canvas); this.width = context.canvas.width || parseInt(Chart.helpers.getStyle(context.canvas, 'width')) || Chart.helpers.getMaximumWidth(context.canvas);
this.height = context.canvas.height || parseInt(Chart.helpers.getStyle(context.canvas, 'height')) || Chart.helpers.getMaximumHeight(context.canvas); this.height = context.canvas.height || parseInt(Chart.helpers.getStyle(context.canvas, 'height')) || Chart.helpers.getMaximumHeight(context.canvas);
this.aspectRatio = this.width / this.height; this.aspectRatio = this.width / this.height;
if (isNaN(this.aspectRatio) || isFinite(this.aspectRatio) === false) { if (isNaN(this.aspectRatio) || isFinite(this.aspectRatio) === false) {
// If the canvas has no size, try and figure out what the aspect ratio will be. // If the canvas has no size, try and figure out what the aspect ratio will be.
// Some charts prefer square canvases (pie, radar, etc). If that is specified, use that // Some charts prefer square canvases (pie, radar, etc). If that is specified, use that
// else use the canvas default ratio of 2 // else use the canvas default ratio of 2
this.aspectRatio = config.aspectRatio !== undefined ? config.aspectRatio : 2; this.aspectRatio = config.aspectRatio !== undefined ? config.aspectRatio : 2;
} }
// Store the original style of the element so we can set it back // Store the original style of the element so we can set it back
this.originalCanvasStyleWidth = context.canvas.style.width; this.originalCanvasStyleWidth = context.canvas.style.width;
this.originalCanvasStyleHeight = context.canvas.style.height; this.originalCanvasStyleHeight = context.canvas.style.height;
// High pixel density displays - multiply the size of the canvas height/width by the device pixel ratio, then scale. // High pixel density displays - multiply the size of the canvas height/width by the device pixel ratio, then scale.
Chart.helpers.retinaScale(this); Chart.helpers.retinaScale(this);
if (config) { if (config) {
this.controller = new Chart.Controller(this); this.controller = new Chart.Controller(this);
} }
// Always bind this so that if the responsive state changes we still work // Always bind this so that if the responsive state changes we still work
var _this = this; var _this = this;
Chart.helpers.addResizeListener(context.canvas.parentNode, function() { Chart.helpers.addResizeListener(context.canvas.parentNode, function() {
if (_this.controller && _this.controller.config.options.responsive) { if (_this.controller && _this.controller.config.options.responsive) {
_this.controller.resize(); _this.controller.resize();
} }
}); });
return this.controller ? this.controller : this; return this.controller ? this.controller : this;
}; };
//Globally expose the defaults to allow for user updating/changing //Globally expose the defaults to allow for user updating/changing
Chart.defaults = { Chart.defaults = {
global: { global: {
responsive: true, responsive: true,
responsiveAnimationDuration: 0, responsiveAnimationDuration: 0,
maintainAspectRatio: true, maintainAspectRatio: true,
events: ["mousemove", "mouseout", "click", "touchstart", "touchmove"], events: ["mousemove", "mouseout", "click", "touchstart", "touchmove"],
hover: { hover: {
onHover: null, onHover: null,
mode: 'single', mode: 'single',
animationDuration: 400, animationDuration: 400
}, },
onClick: null, onClick: null,
defaultColor: 'rgba(0,0,0,0.1)', defaultColor: 'rgba(0,0,0,0.1)',
defaultFontColor: '#666', defaultFontColor: '#666',
defaultFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif", defaultFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
defaultFontSize: 12, defaultFontSize: 12,
defaultFontStyle: 'normal', defaultFontStyle: 'normal',
showLines: true, showLines: true,
// Element defaults defined in element extensions // Element defaults defined in element extensions
elements: {}, elements: {},
// Legend callback string // Legend callback string
legendCallback: function(chart) { legendCallback: function(chart) {
var text = []; var text = [];
text.push('<ul class="' + chart.id + '-legend">'); text.push('<ul class="' + chart.id + '-legend">');
for (var i = 0; i < chart.data.datasets.length; i++) { for (var i = 0; i < chart.data.datasets.length; i++) {
text.push('<li><span style="background-color:' + chart.data.datasets[i].backgroundColor + '">'); text.push('<li><span style="background-color:' + chart.data.datasets[i].backgroundColor + '">');
if (chart.data.datasets[i].label) { if (chart.data.datasets[i].label) {
text.push(chart.data.datasets[i].label); text.push(chart.data.datasets[i].label);
} }
text.push('</span></li>'); text.push('</span></li>');
} }
text.push('</ul>'); text.push('</ul>');
return text.join(""); return text.join("");
} }
}, }
}; };
return Chart; return Chart;

View File

@ -2,322 +2,322 @@
module.exports = function(Chart) { module.exports = function(Chart) {
var helpers = Chart.helpers; var helpers = Chart.helpers;
// The layout service is very self explanatory. It's responsible for the layout within a chart. // The layout service is very self explanatory. It's responsible for the layout within a chart.
// Scales, Legends and Plugins all rely on the layout service and can easily register to be placed anywhere they need // Scales, Legends and Plugins all rely on the layout service and can easily register to be placed anywhere they need
// It is this service's responsibility of carrying out that layout. // It is this service's responsibility of carrying out that layout.
Chart.layoutService = { Chart.layoutService = {
defaults: {}, defaults: {},
// Register a box to a chartInstance. A box is simply a reference to an object that requires layout. eg. Scales, Legend, Plugins. // Register a box to a chartInstance. A box is simply a reference to an object that requires layout. eg. Scales, Legend, Plugins.
addBox: function(chartInstance, box) { addBox: function(chartInstance, box) {
if (!chartInstance.boxes) { if (!chartInstance.boxes) {
chartInstance.boxes = []; chartInstance.boxes = [];
} }
chartInstance.boxes.push(box); chartInstance.boxes.push(box);
}, },
removeBox: function(chartInstance, box) { removeBox: function(chartInstance, box) {
if (!chartInstance.boxes) { if (!chartInstance.boxes) {
return; return;
} }
chartInstance.boxes.splice(chartInstance.boxes.indexOf(box), 1); chartInstance.boxes.splice(chartInstance.boxes.indexOf(box), 1);
}, },
// The most important function // The most important function
update: function(chartInstance, width, height) { update: function(chartInstance, width, height) {
if (!chartInstance) { if (!chartInstance) {
return; return;
} }
var xPadding = width > 30 ? 5 : 2; var xPadding = width > 30 ? 5 : 2;
var yPadding = height > 30 ? 5 : 2; var yPadding = height > 30 ? 5 : 2;
var leftBoxes = helpers.where(chartInstance.boxes, function(box) { var leftBoxes = helpers.where(chartInstance.boxes, function(box) {
return box.options.position == "left"; return box.options.position === "left";
}); });
var rightBoxes = helpers.where(chartInstance.boxes, function(box) { var rightBoxes = helpers.where(chartInstance.boxes, function(box) {
return box.options.position == "right"; return box.options.position === "right";
}); });
var topBoxes = helpers.where(chartInstance.boxes, function(box) { var topBoxes = helpers.where(chartInstance.boxes, function(box) {
return box.options.position == "top"; return box.options.position === "top";
}); });
var bottomBoxes = helpers.where(chartInstance.boxes, function(box) { var bottomBoxes = helpers.where(chartInstance.boxes, function(box) {
return box.options.position == "bottom"; return box.options.position === "bottom";
}); });
// Boxes that overlay the chartarea such as the radialLinear scale // Boxes that overlay the chartarea such as the radialLinear scale
var chartAreaBoxes = helpers.where(chartInstance.boxes, function(box) { var chartAreaBoxes = helpers.where(chartInstance.boxes, function(box) {
return box.options.position == "chartArea"; return box.options.position === "chartArea";
}); });
function fullWidthSorter(a, b) { function fullWidthSorter(a, b) {
} }
// Ensure that full width boxes are at the very top / bottom // Ensure that full width boxes are at the very top / bottom
topBoxes.sort(function(a, b) { topBoxes.sort(function(a, b) {
return (b.options.fullWidth ? 1 : 0) - (a.options.fullWidth ? 1 : 0); return (b.options.fullWidth ? 1 : 0) - (a.options.fullWidth ? 1 : 0);
}); });
bottomBoxes.sort(function(a, b) { bottomBoxes.sort(function(a, b) {
return (a.options.fullWidth ? 1 : 0) - (b.options.fullWidth ? 1 : 0); return (a.options.fullWidth ? 1 : 0) - (b.options.fullWidth ? 1 : 0);
}); });
// Essentially we now have any number of boxes on each of the 4 sides. // Essentially we now have any number of boxes on each of the 4 sides.
// Our canvas looks like the following. // Our canvas looks like the following.
// The areas L1 and L2 are the left axes. R1 is the right axis, T1 is the top axis and // The areas L1 and L2 are the left axes. R1 is the right axis, T1 is the top axis and
// B1 is the bottom axis // B1 is the bottom axis
// There are also 4 quadrant-like locations (left to right instead of clockwise) reserved for chart overlays // There are also 4 quadrant-like locations (left to right instead of clockwise) reserved for chart overlays
// These locations are single-box locations only, when trying to register a chartArea location that is already taken, // These locations are single-box locations only, when trying to register a chartArea location that is already taken,
// an error will be thrown. // an error will be thrown.
// //
// |----------------------------------------------------| // |----------------------------------------------------|
// | T1 (Full Width) | // | T1 (Full Width) |
// |----------------------------------------------------| // |----------------------------------------------------|
// | | | T2 | | // | | | T2 | |
// | |----|-------------------------------------|----| // | |----|-------------------------------------|----|
// | | | C1 | | C2 | | // | | | C1 | | C2 | |
// | | |----| |----| | // | | |----| |----| |
// | | | | | // | | | | |
// | L1 | L2 | ChartArea (C0) | R1 | // | L1 | L2 | ChartArea (C0) | R1 |
// | | | | | // | | | | |
// | | |----| |----| | // | | |----| |----| |
// | | | C3 | | C4 | | // | | | C3 | | C4 | |
// | |----|-------------------------------------|----| // | |----|-------------------------------------|----|
// | | | B1 | | // | | | B1 | |
// |----------------------------------------------------| // |----------------------------------------------------|
// | B2 (Full Width) | // | B2 (Full Width) |
// |----------------------------------------------------| // |----------------------------------------------------|
// //
// What we do to find the best sizing, we do the following // What we do to find the best sizing, we do the following
// 1. Determine the minimum size of the chart area. // 1. Determine the minimum size of the chart area.
// 2. Split the remaining width equally between each vertical axis // 2. Split the remaining width equally between each vertical axis
// 3. Split the remaining height equally between each horizontal axis // 3. Split the remaining height equally between each horizontal axis
// 4. Give each layout the maximum size it can be. The layout will return it's minimum size // 4. Give each layout the maximum size it can be. The layout will return it's minimum size
// 5. Adjust the sizes of each axis based on it's minimum reported size. // 5. Adjust the sizes of each axis based on it's minimum reported size.
// 6. Refit each axis // 6. Refit each axis
// 7. Position each axis in the final location // 7. Position each axis in the final location
// 8. Tell the chart the final location of the chart area // 8. Tell the chart the final location of the chart area
// 9. Tell any axes that overlay the chart area the positions of the chart area // 9. Tell any axes that overlay the chart area the positions of the chart area
// Step 1 // Step 1
var chartWidth = width - (2 * xPadding); var chartWidth = width - (2 * xPadding);
var chartHeight = height - (2 * yPadding); var chartHeight = height - (2 * yPadding);
var chartAreaWidth = chartWidth / 2; // min 50% var chartAreaWidth = chartWidth / 2; // min 50%
var chartAreaHeight = chartHeight / 2; // min 50% var chartAreaHeight = chartHeight / 2; // min 50%
// Step 2 // Step 2
var verticalBoxWidth = (width - chartAreaWidth) / (leftBoxes.length + rightBoxes.length); var verticalBoxWidth = (width - chartAreaWidth) / (leftBoxes.length + rightBoxes.length);
// Step 3 // Step 3
var horizontalBoxHeight = (height - chartAreaHeight) / (topBoxes.length + bottomBoxes.length); var horizontalBoxHeight = (height - chartAreaHeight) / (topBoxes.length + bottomBoxes.length);
// Step 4 // Step 4
var maxChartAreaWidth = chartWidth; var maxChartAreaWidth = chartWidth;
var maxChartAreaHeight = chartHeight; var maxChartAreaHeight = chartHeight;
var minBoxSizes = []; var minBoxSizes = [];
helpers.each(leftBoxes.concat(rightBoxes, topBoxes, bottomBoxes), getMinimumBoxSize); helpers.each(leftBoxes.concat(rightBoxes, topBoxes, bottomBoxes), getMinimumBoxSize);
function getMinimumBoxSize(box) { function getMinimumBoxSize(box) {
var minSize; var minSize;
var isHorizontal = box.isHorizontal(); var isHorizontal = box.isHorizontal();
if (isHorizontal) { if (isHorizontal) {
minSize = box.update(box.options.fullWidth ? chartWidth : maxChartAreaWidth, horizontalBoxHeight); minSize = box.update(box.options.fullWidth ? chartWidth : maxChartAreaWidth, horizontalBoxHeight);
maxChartAreaHeight -= minSize.height; maxChartAreaHeight -= minSize.height;
} else { } else {
minSize = box.update(verticalBoxWidth, chartAreaHeight); minSize = box.update(verticalBoxWidth, chartAreaHeight);
maxChartAreaWidth -= minSize.width; maxChartAreaWidth -= minSize.width;
} }
minBoxSizes.push({ minBoxSizes.push({
horizontal: isHorizontal, horizontal: isHorizontal,
minSize: minSize, minSize: minSize,
box: box, box: box
}); });
} }
// At this point, maxChartAreaHeight and maxChartAreaWidth are the size the chart area could // At this point, maxChartAreaHeight and maxChartAreaWidth are the size the chart area could
// be if the axes are drawn at their minimum sizes. // be if the axes are drawn at their minimum sizes.
// Steps 5 & 6 // Steps 5 & 6
var totalLeftBoxesWidth = xPadding; var totalLeftBoxesWidth = xPadding;
var totalRightBoxesWidth = xPadding; var totalRightBoxesWidth = xPadding;
var totalTopBoxesHeight = yPadding; var totalTopBoxesHeight = yPadding;
var totalBottomBoxesHeight = yPadding; var totalBottomBoxesHeight = yPadding;
// Update, and calculate the left and right margins for the horizontal boxes // Update, and calculate the left and right margins for the horizontal boxes
helpers.each(leftBoxes.concat(rightBoxes), fitBox); helpers.each(leftBoxes.concat(rightBoxes), fitBox);
helpers.each(leftBoxes, function(box) { helpers.each(leftBoxes, function(box) {
totalLeftBoxesWidth += box.width; totalLeftBoxesWidth += box.width;
}); });
helpers.each(rightBoxes, function(box) { helpers.each(rightBoxes, function(box) {
totalRightBoxesWidth += box.width; totalRightBoxesWidth += box.width;
}); });
// Set the Left and Right margins for the horizontal boxes // Set the Left and Right margins for the horizontal boxes
helpers.each(topBoxes.concat(bottomBoxes), fitBox); helpers.each(topBoxes.concat(bottomBoxes), fitBox);
// Function to fit a box // Function to fit a box
function fitBox(box) { function fitBox(box) {
var minBoxSize = helpers.findNextWhere(minBoxSizes, function(minBoxSize) { var minBoxSize = helpers.findNextWhere(minBoxSizes, function(minBoxSize) {
return minBoxSize.box === box; return minBoxSize.box === box;
}); });
if (minBoxSize) { if (minBoxSize) {
if (box.isHorizontal()) { if (box.isHorizontal()) {
var scaleMargin = { var scaleMargin = {
left: totalLeftBoxesWidth, left: totalLeftBoxesWidth,
right: totalRightBoxesWidth, right: totalRightBoxesWidth,
top: 0, top: 0,
bottom: 0, bottom: 0
}; };
// Don't use min size here because of label rotation. When the labels are rotated, their rotation highly depends // Don't use min size here because of label rotation. When the labels are rotated, their rotation highly depends
// on the margin. Sometimes they need to increase in size slightly // on the margin. Sometimes they need to increase in size slightly
box.update(box.options.fullWidth ? chartWidth : maxChartAreaWidth, chartHeight / 2, scaleMargin); box.update(box.options.fullWidth ? chartWidth : maxChartAreaWidth, chartHeight / 2, scaleMargin);
} else { } else {
box.update(minBoxSize.minSize.width, maxChartAreaHeight); box.update(minBoxSize.minSize.width, maxChartAreaHeight);
} }
} }
} }
// Figure out how much margin is on the top and bottom of the vertical boxes // Figure out how much margin is on the top and bottom of the vertical boxes
helpers.each(topBoxes, function(box) { helpers.each(topBoxes, function(box) {
totalTopBoxesHeight += box.height; totalTopBoxesHeight += box.height;
}); });
helpers.each(bottomBoxes, function(box) { helpers.each(bottomBoxes, function(box) {
totalBottomBoxesHeight += box.height; totalBottomBoxesHeight += box.height;
}); });
// Let the left layout know the final margin // Let the left layout know the final margin
helpers.each(leftBoxes.concat(rightBoxes), finalFitVerticalBox); helpers.each(leftBoxes.concat(rightBoxes), finalFitVerticalBox);
function finalFitVerticalBox(box) { function finalFitVerticalBox(box) {
var minBoxSize = helpers.findNextWhere(minBoxSizes, function(minBoxSize) { var minBoxSize = helpers.findNextWhere(minBoxSizes, function(minBoxSize) {
return minBoxSize.box === box; return minBoxSize.box === box;
}); });
var scaleMargin = { var scaleMargin = {
left: 0, left: 0,
right: 0, right: 0,
top: totalTopBoxesHeight, top: totalTopBoxesHeight,
bottom: totalBottomBoxesHeight bottom: totalBottomBoxesHeight
}; };
if (minBoxSize) { if (minBoxSize) {
box.update(minBoxSize.minSize.width, maxChartAreaHeight, scaleMargin); box.update(minBoxSize.minSize.width, maxChartAreaHeight, scaleMargin);
} }
} }
// Recalculate because the size of each layout might have changed slightly due to the margins (label rotation for instance) // Recalculate because the size of each layout might have changed slightly due to the margins (label rotation for instance)
totalLeftBoxesWidth = xPadding; totalLeftBoxesWidth = xPadding;
totalRightBoxesWidth = xPadding; totalRightBoxesWidth = xPadding;
totalTopBoxesHeight = yPadding; totalTopBoxesHeight = yPadding;
totalBottomBoxesHeight = yPadding; totalBottomBoxesHeight = yPadding;
helpers.each(leftBoxes, function(box) { helpers.each(leftBoxes, function(box) {
totalLeftBoxesWidth += box.width; totalLeftBoxesWidth += box.width;
}); });
helpers.each(rightBoxes, function(box) { helpers.each(rightBoxes, function(box) {
totalRightBoxesWidth += box.width; totalRightBoxesWidth += box.width;
}); });
helpers.each(topBoxes, function(box) { helpers.each(topBoxes, function(box) {
totalTopBoxesHeight += box.height; totalTopBoxesHeight += box.height;
}); });
helpers.each(bottomBoxes, function(box) { helpers.each(bottomBoxes, function(box) {
totalBottomBoxesHeight += box.height; totalBottomBoxesHeight += box.height;
}); });
// Figure out if our chart area changed. This would occur if the dataset layout label rotation // Figure out if our chart area changed. This would occur if the dataset layout label rotation
// changed due to the application of the margins in step 6. Since we can only get bigger, this is safe to do // changed due to the application of the margins in step 6. Since we can only get bigger, this is safe to do
// without calling `fit` again // without calling `fit` again
var newMaxChartAreaHeight = height - totalTopBoxesHeight - totalBottomBoxesHeight; var newMaxChartAreaHeight = height - totalTopBoxesHeight - totalBottomBoxesHeight;
var newMaxChartAreaWidth = width - totalLeftBoxesWidth - totalRightBoxesWidth; var newMaxChartAreaWidth = width - totalLeftBoxesWidth - totalRightBoxesWidth;
if (newMaxChartAreaWidth !== maxChartAreaWidth || newMaxChartAreaHeight !== maxChartAreaHeight) { if (newMaxChartAreaWidth !== maxChartAreaWidth || newMaxChartAreaHeight !== maxChartAreaHeight) {
helpers.each(leftBoxes, function(box) { helpers.each(leftBoxes, function(box) {
box.height = newMaxChartAreaHeight; box.height = newMaxChartAreaHeight;
}); });
helpers.each(rightBoxes, function(box) { helpers.each(rightBoxes, function(box) {
box.height = newMaxChartAreaHeight; box.height = newMaxChartAreaHeight;
}); });
helpers.each(topBoxes, function(box) { helpers.each(topBoxes, function(box) {
box.width = newMaxChartAreaWidth; box.width = newMaxChartAreaWidth;
}); });
helpers.each(bottomBoxes, function(box) { helpers.each(bottomBoxes, function(box) {
box.width = newMaxChartAreaWidth; box.width = newMaxChartAreaWidth;
}); });
maxChartAreaHeight = newMaxChartAreaHeight; maxChartAreaHeight = newMaxChartAreaHeight;
maxChartAreaWidth = newMaxChartAreaWidth; maxChartAreaWidth = newMaxChartAreaWidth;
} }
// Step 7 - Position the boxes // Step 7 - Position the boxes
var left = xPadding; var left = xPadding;
var top = yPadding; var top = yPadding;
var right = 0; var right = 0;
var bottom = 0; var bottom = 0;
helpers.each(leftBoxes.concat(topBoxes), placeBox); helpers.each(leftBoxes.concat(topBoxes), placeBox);
// Account for chart width and height // Account for chart width and height
left += maxChartAreaWidth; left += maxChartAreaWidth;
top += maxChartAreaHeight; top += maxChartAreaHeight;
helpers.each(rightBoxes, placeBox); helpers.each(rightBoxes, placeBox);
helpers.each(bottomBoxes, placeBox); helpers.each(bottomBoxes, placeBox);
function placeBox(box) { function placeBox(box) {
if (box.isHorizontal()) { if (box.isHorizontal()) {
box.left = box.options.fullWidth ? xPadding : totalLeftBoxesWidth; box.left = box.options.fullWidth ? xPadding : totalLeftBoxesWidth;
box.right = box.options.fullWidth ? width - xPadding : totalLeftBoxesWidth + maxChartAreaWidth; box.right = box.options.fullWidth ? width - xPadding : totalLeftBoxesWidth + maxChartAreaWidth;
box.top = top; box.top = top;
box.bottom = top + box.height; box.bottom = top + box.height;
// Move to next point // Move to next point
top = box.bottom; top = box.bottom;
} else { } else {
box.left = left; box.left = left;
box.right = left + box.width; box.right = left + box.width;
box.top = totalTopBoxesHeight; box.top = totalTopBoxesHeight;
box.bottom = totalTopBoxesHeight + maxChartAreaHeight; box.bottom = totalTopBoxesHeight + maxChartAreaHeight;
// Move to next point // Move to next point
left = box.right; left = box.right;
} }
} }
// Step 8 // Step 8
chartInstance.chartArea = { chartInstance.chartArea = {
left: totalLeftBoxesWidth, left: totalLeftBoxesWidth,
top: totalTopBoxesHeight, top: totalTopBoxesHeight,
right: totalLeftBoxesWidth + maxChartAreaWidth, right: totalLeftBoxesWidth + maxChartAreaWidth,
bottom: totalTopBoxesHeight + maxChartAreaHeight, bottom: totalTopBoxesHeight + maxChartAreaHeight
}; };
// Step 9 // Step 9
helpers.each(chartAreaBoxes, function(box) { helpers.each(chartAreaBoxes, function(box) {
box.left = chartInstance.chartArea.left; box.left = chartInstance.chartArea.left;
box.top = chartInstance.chartArea.top; box.top = chartInstance.chartArea.top;
box.right = chartInstance.chartArea.right; box.right = chartInstance.chartArea.right;
box.bottom = chartInstance.chartArea.bottom; box.bottom = chartInstance.chartArea.bottom;
box.update(maxChartAreaWidth, maxChartAreaHeight); box.update(maxChartAreaWidth, maxChartAreaHeight);
}); });
} }
}; };
}; };

View File

@ -2,323 +2,323 @@
module.exports = function(Chart) { module.exports = function(Chart) {
var helpers = Chart.helpers; var helpers = Chart.helpers;
Chart.defaults.global.legend = { Chart.defaults.global.legend = {
display: true, display: true,
position: 'top', position: 'top',
fullWidth: true, // marks that this box should take the full width of the canvas (pushing down other boxes) fullWidth: true, // marks that this box should take the full width of the canvas (pushing down other boxes)
// a callback that will handle // a callback that will handle
onClick: function(e, legendItem) { onClick: function(e, legendItem) {
var dataset = this.chart.data.datasets[legendItem.datasetIndex]; var dataset = this.chart.data.datasets[legendItem.datasetIndex];
dataset.hidden = !dataset.hidden; dataset.hidden = !dataset.hidden;
// We hid a dataset ... rerender the chart // We hid a dataset ... rerender the chart
this.chart.update(); this.chart.update();
}, },
labels: { labels: {
boxWidth: 40, boxWidth: 40,
fontSize: Chart.defaults.global.defaultFontSize, fontSize: Chart.defaults.global.defaultFontSize,
fontStyle: Chart.defaults.global.defaultFontStyle, fontStyle: Chart.defaults.global.defaultFontStyle,
fontColor: Chart.defaults.global.defaultFontColor, fontColor: Chart.defaults.global.defaultFontColor,
fontFamily: Chart.defaults.global.defaultFontFamily, fontFamily: Chart.defaults.global.defaultFontFamily,
padding: 10, padding: 10,
// Generates labels shown in the legend // Generates labels shown in the legend
// Valid properties to return: // Valid properties to return:
// text : text to display // text : text to display
// fillStyle : fill of coloured box // fillStyle : fill of coloured box
// strokeStyle: stroke of coloured box // strokeStyle: stroke of coloured box
// hidden : if this legend item refers to a hidden item // hidden : if this legend item refers to a hidden item
// lineCap : cap style for line // lineCap : cap style for line
// lineDash // lineDash
// lineDashOffset : // lineDashOffset :
// lineJoin : // lineJoin :
// lineWidth : // lineWidth :
generateLabels: function(data) { generateLabels: function(data) {
return data.datasets.map(function(dataset, i) { return data.datasets.map(function(dataset, i) {
return { return {
text: dataset.label, text: dataset.label,
fillStyle: dataset.backgroundColor, fillStyle: dataset.backgroundColor,
hidden: dataset.hidden, hidden: dataset.hidden,
lineCap: dataset.borderCapStyle, lineCap: dataset.borderCapStyle,
lineDash: dataset.borderDash, lineDash: dataset.borderDash,
lineDashOffset: dataset.borderDashOffset, lineDashOffset: dataset.borderDashOffset,
lineJoin: dataset.borderJoinStyle, lineJoin: dataset.borderJoinStyle,
lineWidth: dataset.borderWidth, lineWidth: dataset.borderWidth,
strokeStyle: dataset.borderColor, strokeStyle: dataset.borderColor,
// Below is extra data used for toggling the datasets // Below is extra data used for toggling the datasets
datasetIndex: i datasetIndex: i
}; };
}, this); }, this);
} }
}, }
}; };
Chart.Legend = Chart.Element.extend({ Chart.Legend = Chart.Element.extend({
initialize: function(config) { initialize: function(config) {
helpers.extend(this, config); helpers.extend(this, config);
// Contains hit boxes for each dataset (in dataset order) // Contains hit boxes for each dataset (in dataset order)
this.legendHitBoxes = []; this.legendHitBoxes = [];
// Are we in doughnut mode which has a different data type // Are we in doughnut mode which has a different data type
this.doughnutMode = false; this.doughnutMode = false;
}, },
// These methods are ordered by lifecyle. Utilities then follow. // These methods are ordered by lifecyle. Utilities then follow.
// Any function defined here is inherited by all legend types. // Any function defined here is inherited by all legend types.
// Any function can be extended by the legend type // Any function can be extended by the legend type
beforeUpdate: helpers.noop, beforeUpdate: helpers.noop,
update: function(maxWidth, maxHeight, margins) { update: function(maxWidth, maxHeight, margins) {
// Update Lifecycle - Probably don't want to ever extend or overwrite this function ;) // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;)
this.beforeUpdate(); this.beforeUpdate();
// Absorb the master measurements // Absorb the master measurements
this.maxWidth = maxWidth; this.maxWidth = maxWidth;
this.maxHeight = maxHeight; this.maxHeight = maxHeight;
this.margins = margins; this.margins = margins;
// Dimensions // Dimensions
this.beforeSetDimensions(); this.beforeSetDimensions();
this.setDimensions(); this.setDimensions();
this.afterSetDimensions(); this.afterSetDimensions();
// Labels // Labels
this.beforeBuildLabels(); this.beforeBuildLabels();
this.buildLabels(); this.buildLabels();
this.afterBuildLabels(); this.afterBuildLabels();
// Fit // Fit
this.beforeFit(); this.beforeFit();
this.fit(); this.fit();
this.afterFit(); this.afterFit();
// //
this.afterUpdate(); this.afterUpdate();
return this.minSize; return this.minSize;
}, },
afterUpdate: helpers.noop, afterUpdate: helpers.noop,
// //
beforeSetDimensions: helpers.noop, beforeSetDimensions: helpers.noop,
setDimensions: function() { setDimensions: function() {
// Set the unconstrained dimension before label rotation // Set the unconstrained dimension before label rotation
if (this.isHorizontal()) { if (this.isHorizontal()) {
// Reset position before calculating rotation // Reset position before calculating rotation
this.width = this.maxWidth; this.width = this.maxWidth;
this.left = 0; this.left = 0;
this.right = this.width; this.right = this.width;
} else { } else {
this.height = this.maxHeight; this.height = this.maxHeight;
// Reset position before calculating rotation // Reset position before calculating rotation
this.top = 0; this.top = 0;
this.bottom = this.height; this.bottom = this.height;
} }
// Reset padding // Reset padding
this.paddingLeft = 0; this.paddingLeft = 0;
this.paddingTop = 0; this.paddingTop = 0;
this.paddingRight = 0; this.paddingRight = 0;
this.paddingBottom = 0; this.paddingBottom = 0;
// Reset minSize // Reset minSize
this.minSize = { this.minSize = {
width: 0, width: 0,
height: 0, height: 0
}; };
}, },
afterSetDimensions: helpers.noop, afterSetDimensions: helpers.noop,
// //
beforeBuildLabels: helpers.noop, beforeBuildLabels: helpers.noop,
buildLabels: function() { buildLabels: function() {
this.legendItems = this.options.labels.generateLabels.call(this, this.chart.data); this.legendItems = this.options.labels.generateLabels.call(this, this.chart.data);
}, },
afterBuildLabels: helpers.noop, afterBuildLabels: helpers.noop,
// //
beforeFit: helpers.noop, beforeFit: helpers.noop,
fit: function() { fit: function() {
var ctx = this.ctx; var ctx = this.ctx;
var labelFont = helpers.fontString(this.options.labels.fontSize, this.options.labels.fontStyle, this.options.labels.fontFamily); var labelFont = helpers.fontString(this.options.labels.fontSize, this.options.labels.fontStyle, this.options.labels.fontFamily);
// Reset hit boxes // Reset hit boxes
this.legendHitBoxes = []; this.legendHitBoxes = [];
// Width // Width
if (this.isHorizontal()) { if (this.isHorizontal()) {
this.minSize.width = this.maxWidth; // fill all the width this.minSize.width = this.maxWidth; // fill all the width
} else { } else {
this.minSize.width = this.options.display ? 10 : 0; this.minSize.width = this.options.display ? 10 : 0;
} }
// height // height
if (this.isHorizontal()) { if (this.isHorizontal()) {
this.minSize.height = this.options.display ? 10 : 0; this.minSize.height = this.options.display ? 10 : 0;
} else { } else {
this.minSize.height = this.maxHeight; // fill all the height this.minSize.height = this.maxHeight; // fill all the height
} }
// Increase sizes here // Increase sizes here
if (this.options.display) { if (this.options.display) {
if (this.isHorizontal()) { if (this.isHorizontal()) {
// Labels // Labels
// Width of each line of legend boxes. Labels wrap onto multiple lines when there are too many to fit on one // Width of each line of legend boxes. Labels wrap onto multiple lines when there are too many to fit on one
this.lineWidths = [0]; this.lineWidths = [0];
var totalHeight = this.legendItems.length ? this.options.labels.fontSize + (this.options.labels.padding) : 0; var totalHeight = this.legendItems.length ? this.options.labels.fontSize + (this.options.labels.padding) : 0;
ctx.textAlign = "left"; ctx.textAlign = "left";
ctx.textBaseline = 'top'; ctx.textBaseline = 'top';
ctx.font = labelFont; ctx.font = labelFont;
helpers.each(this.legendItems, function(legendItem, i) { helpers.each(this.legendItems, function(legendItem, i) {
var width = this.options.labels.boxWidth + (this.options.labels.fontSize / 2) + ctx.measureText(legendItem.text).width; var width = this.options.labels.boxWidth + (this.options.labels.fontSize / 2) + ctx.measureText(legendItem.text).width;
if (this.lineWidths[this.lineWidths.length - 1] + width >= this.width) { if (this.lineWidths[this.lineWidths.length - 1] + width >= this.width) {
totalHeight += this.options.labels.fontSize + (this.options.labels.padding); totalHeight += this.options.labels.fontSize + (this.options.labels.padding);
this.lineWidths[this.lineWidths.length] = this.left; this.lineWidths[this.lineWidths.length] = this.left;
} }
// Store the hitbox width and height here. Final position will be updated in `draw` // Store the hitbox width and height here. Final position will be updated in `draw`
this.legendHitBoxes[i] = { this.legendHitBoxes[i] = {
left: 0, left: 0,
top: 0, top: 0,
width: width, width: width,
height: this.options.labels.fontSize, height: this.options.labels.fontSize
}; };
this.lineWidths[this.lineWidths.length - 1] += width + this.options.labels.padding; this.lineWidths[this.lineWidths.length - 1] += width + this.options.labels.padding;
}, this); }, this);
this.minSize.height += totalHeight; this.minSize.height += totalHeight;
} else { } else {
// TODO vertical // TODO vertical
} }
} }
this.width = this.minSize.width; this.width = this.minSize.width;
this.height = this.minSize.height; this.height = this.minSize.height;
}, },
afterFit: helpers.noop, afterFit: helpers.noop,
// Shared Methods // Shared Methods
isHorizontal: function() { isHorizontal: function() {
return this.options.position == "top" || this.options.position == "bottom"; return this.options.position === "top" || this.options.position === "bottom";
}, },
// Actualy draw the legend on the canvas // Actualy draw the legend on the canvas
draw: function() { draw: function() {
if (this.options.display) { if (this.options.display) {
var ctx = this.ctx; var ctx = this.ctx;
var cursor = { var cursor = {
x: this.left + ((this.width - this.lineWidths[0]) / 2), x: this.left + ((this.width - this.lineWidths[0]) / 2),
y: this.top + this.options.labels.padding, y: this.top + this.options.labels.padding,
line: 0, line: 0
}; };
var labelFont = helpers.fontString(this.options.labels.fontSize, this.options.labels.fontStyle, this.options.labels.fontFamily); var labelFont = helpers.fontString(this.options.labels.fontSize, this.options.labels.fontStyle, this.options.labels.fontFamily);
// Horizontal // Horizontal
if (this.isHorizontal()) { if (this.isHorizontal()) {
// Labels // Labels
ctx.textAlign = "left"; ctx.textAlign = "left";
ctx.textBaseline = 'top'; ctx.textBaseline = 'top';
ctx.lineWidth = 0.5; ctx.lineWidth = 0.5;
ctx.strokeStyle = this.options.labels.fontColor; // for strikethrough effect ctx.strokeStyle = this.options.labels.fontColor; // for strikethrough effect
ctx.fillStyle = this.options.labels.fontColor; // render in correct colour ctx.fillStyle = this.options.labels.fontColor; // render in correct colour
ctx.font = labelFont; ctx.font = labelFont;
helpers.each(this.legendItems, function(legendItem, i) { helpers.each(this.legendItems, function(legendItem, i) {
var textWidth = ctx.measureText(legendItem.text).width; var textWidth = ctx.measureText(legendItem.text).width;
var width = this.options.labels.boxWidth + (this.options.labels.fontSize / 2) + textWidth; var width = this.options.labels.boxWidth + (this.options.labels.fontSize / 2) + textWidth;
if (cursor.x + width >= this.width) { if (cursor.x + width >= this.width) {
cursor.y += this.options.labels.fontSize + (this.options.labels.padding); cursor.y += this.options.labels.fontSize + (this.options.labels.padding);
cursor.line++; cursor.line++;
cursor.x = this.left + ((this.width - this.lineWidths[cursor.line]) / 2); cursor.x = this.left + ((this.width - this.lineWidths[cursor.line]) / 2);
} }
// Set the ctx for the box // Set the ctx for the box
ctx.save(); ctx.save();
var itemOrDefault = function(item, defaulVal) { var itemOrDefault = function(item, defaulVal) {
return item !== undefined ? item : defaulVal; return item !== undefined ? item : defaulVal;
}; };
ctx.fillStyle = itemOrDefault(legendItem.fillStyle, Chart.defaults.global.defaultColor); ctx.fillStyle = itemOrDefault(legendItem.fillStyle, Chart.defaults.global.defaultColor);
ctx.lineCap = itemOrDefault(legendItem.lineCap, Chart.defaults.global.elements.line.borderCapStyle); ctx.lineCap = itemOrDefault(legendItem.lineCap, Chart.defaults.global.elements.line.borderCapStyle);
ctx.lineDashOffset = itemOrDefault(legendItem.lineDashOffset, Chart.defaults.global.elements.line.borderDashOffset); ctx.lineDashOffset = itemOrDefault(legendItem.lineDashOffset, Chart.defaults.global.elements.line.borderDashOffset);
ctx.lineJoin = itemOrDefault(legendItem.lineJoin, Chart.defaults.global.elements.line.borderJoinStyle); ctx.lineJoin = itemOrDefault(legendItem.lineJoin, Chart.defaults.global.elements.line.borderJoinStyle);
ctx.lineWidth = itemOrDefault(legendItem.lineWidth, Chart.defaults.global.elements.line.borderWidth); ctx.lineWidth = itemOrDefault(legendItem.lineWidth, Chart.defaults.global.elements.line.borderWidth);
ctx.strokeStyle = itemOrDefault(legendItem.strokeStyle, Chart.defaults.global.defaultColor); ctx.strokeStyle = itemOrDefault(legendItem.strokeStyle, Chart.defaults.global.defaultColor);
if (ctx.setLineDash) { if (ctx.setLineDash) {
// IE 9 and 10 do not support line dash // IE 9 and 10 do not support line dash
ctx.setLineDash(itemOrDefault(legendItem.lineDash, Chart.defaults.global.elements.line.borderDash)); ctx.setLineDash(itemOrDefault(legendItem.lineDash, Chart.defaults.global.elements.line.borderDash));
} }
// Draw the box // Draw the box
ctx.strokeRect(cursor.x, cursor.y, this.options.labels.boxWidth, this.options.labels.fontSize); ctx.strokeRect(cursor.x, cursor.y, this.options.labels.boxWidth, this.options.labels.fontSize);
ctx.fillRect(cursor.x, cursor.y, this.options.labels.boxWidth, this.options.labels.fontSize); ctx.fillRect(cursor.x, cursor.y, this.options.labels.boxWidth, this.options.labels.fontSize);
ctx.restore(); ctx.restore();
this.legendHitBoxes[i].left = cursor.x; this.legendHitBoxes[i].left = cursor.x;
this.legendHitBoxes[i].top = cursor.y; this.legendHitBoxes[i].top = cursor.y;
// Fill the actual label // Fill the actual label
ctx.fillText(legendItem.text, this.options.labels.boxWidth + (this.options.labels.fontSize / 2) + cursor.x, cursor.y); ctx.fillText(legendItem.text, this.options.labels.boxWidth + (this.options.labels.fontSize / 2) + cursor.x, cursor.y);
if (legendItem.hidden) { if (legendItem.hidden) {
// Strikethrough the text if hidden // Strikethrough the text if hidden
ctx.beginPath(); ctx.beginPath();
ctx.lineWidth = 2; ctx.lineWidth = 2;
ctx.moveTo(this.options.labels.boxWidth + (this.options.labels.fontSize / 2) + cursor.x, cursor.y + (this.options.labels.fontSize / 2)); ctx.moveTo(this.options.labels.boxWidth + (this.options.labels.fontSize / 2) + cursor.x, cursor.y + (this.options.labels.fontSize / 2));
ctx.lineTo(this.options.labels.boxWidth + (this.options.labels.fontSize / 2) + cursor.x + textWidth, cursor.y + (this.options.labels.fontSize / 2)); ctx.lineTo(this.options.labels.boxWidth + (this.options.labels.fontSize / 2) + cursor.x + textWidth, cursor.y + (this.options.labels.fontSize / 2));
ctx.stroke(); ctx.stroke();
} }
cursor.x += width + (this.options.labels.padding); cursor.x += width + (this.options.labels.padding);
}, this); }, this);
} else { } else {
} }
} }
}, },
// Handle an event // Handle an event
handleEvent: function(e) { handleEvent: function(e) {
var position = helpers.getRelativePosition(e, this.chart.chart); var position = helpers.getRelativePosition(e, this.chart.chart);
if (position.x >= this.left && position.x <= this.right && position.y >= this.top && position.y <= this.bottom) { if (position.x >= this.left && position.x <= this.right && position.y >= this.top && position.y <= this.bottom) {
// See if we are touching one of the dataset boxes // See if we are touching one of the dataset boxes
for (var i = 0; i < this.legendHitBoxes.length; ++i) { for (var i = 0; i < this.legendHitBoxes.length; ++i) {
var hitBox = this.legendHitBoxes[i]; var hitBox = this.legendHitBoxes[i];
if (position.x >= hitBox.left && position.x <= hitBox.left + hitBox.width && position.y >= hitBox.top && position.y <= hitBox.top + hitBox.height) { if (position.x >= hitBox.left && position.x <= hitBox.left + hitBox.width && position.y >= hitBox.top && position.y <= hitBox.top + hitBox.height) {
// Touching an element // Touching an element
if (this.options.onClick) { if (this.options.onClick) {
this.options.onClick.call(this, e, this.legendItems[i]); this.options.onClick.call(this, e, this.legendItems[i]);
} }
break; break;
} }
} }
} }
} }
}); });
}; };

File diff suppressed because it is too large Load Diff

View File

@ -2,33 +2,33 @@
module.exports = function(Chart) { module.exports = function(Chart) {
var helpers = Chart.helpers; var helpers = Chart.helpers;
Chart.scaleService = { Chart.scaleService = {
// Scale registration object. Extensions can register new scale types (such as log or DB scales) and then // Scale registration object. Extensions can register new scale types (such as log or DB scales) and then
// use the new chart options to grab the correct scale // use the new chart options to grab the correct scale
constructors: {}, constructors: {},
// Use a registration function so that we can move to an ES6 map when we no longer need to support // Use a registration function so that we can move to an ES6 map when we no longer need to support
// old browsers // old browsers
// Scale config defaults // Scale config defaults
defaults: {}, defaults: {},
registerScaleType: function(type, scaleConstructor, defaults) { registerScaleType: function(type, scaleConstructor, defaults) {
this.constructors[type] = scaleConstructor; this.constructors[type] = scaleConstructor;
this.defaults[type] = helpers.clone(defaults); this.defaults[type] = helpers.clone(defaults);
}, },
getScaleConstructor: function(type) { getScaleConstructor: function(type) {
return this.constructors.hasOwnProperty(type) ? this.constructors[type] : undefined; return this.constructors.hasOwnProperty(type) ? this.constructors[type] : undefined;
}, },
getScaleDefaults: function(type) { getScaleDefaults: function(type) {
// Return the scale defaults merged with the global settings so that we always use the latest ones // Return the scale defaults merged with the global settings so that we always use the latest ones
return this.defaults.hasOwnProperty(type) ? helpers.scaleMerge(Chart.defaults.scale, this.defaults[type]) : {}; return this.defaults.hasOwnProperty(type) ? helpers.scaleMerge(Chart.defaults.scale, this.defaults[type]) : {};
}, },
addScalesToLayout: function(chartInstance) { addScalesToLayout: function(chartInstance) {
// Adds each scale to the chart.boxes array to be sized accordingly // Adds each scale to the chart.boxes array to be sized accordingly
helpers.each(chartInstance.scales, function(scale) { helpers.each(chartInstance.scales, function(scale) {
Chart.layoutService.addBox(chartInstance, scale); Chart.layoutService.addBox(chartInstance, scale);
}); });
}, }
}; };
}; };

View File

@ -2,192 +2,192 @@
module.exports = function(Chart) { module.exports = function(Chart) {
var helpers = Chart.helpers; var helpers = Chart.helpers;
Chart.defaults.global.title = { Chart.defaults.global.title = {
display: false, display: false,
position: 'top', position: 'top',
fullWidth: true, // marks that this box should take the full width of the canvas (pushing down other boxes) fullWidth: true, // marks that this box should take the full width of the canvas (pushing down other boxes)
fontColor: Chart.defaults.global.defaultFontColor, fontColor: Chart.defaults.global.defaultFontColor,
fontFamily: Chart.defaults.global.defaultFontFamily, fontFamily: Chart.defaults.global.defaultFontFamily,
fontSize: Chart.defaults.global.defaultFontSize, fontSize: Chart.defaults.global.defaultFontSize,
fontStyle: 'bold', fontStyle: 'bold',
padding: 10, padding: 10,
// actual title // actual title
text: '', text: ''
}; };
Chart.Title = Chart.Element.extend({ Chart.Title = Chart.Element.extend({
initialize: function(config) { initialize: function(config) {
helpers.extend(this, config); helpers.extend(this, config);
this.options = helpers.configMerge(Chart.defaults.global.title, config.options); this.options = helpers.configMerge(Chart.defaults.global.title, config.options);
// Contains hit boxes for each dataset (in dataset order) // Contains hit boxes for each dataset (in dataset order)
this.legendHitBoxes = []; this.legendHitBoxes = [];
}, },
// These methods are ordered by lifecyle. Utilities then follow. // These methods are ordered by lifecyle. Utilities then follow.
beforeUpdate: helpers.noop, beforeUpdate: helpers.noop,
update: function(maxWidth, maxHeight, margins) { update: function(maxWidth, maxHeight, margins) {
// Update Lifecycle - Probably don't want to ever extend or overwrite this function ;) // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;)
this.beforeUpdate(); this.beforeUpdate();
// Absorb the master measurements // Absorb the master measurements
this.maxWidth = maxWidth; this.maxWidth = maxWidth;
this.maxHeight = maxHeight; this.maxHeight = maxHeight;
this.margins = margins; this.margins = margins;
// Dimensions // Dimensions
this.beforeSetDimensions(); this.beforeSetDimensions();
this.setDimensions(); this.setDimensions();
this.afterSetDimensions(); this.afterSetDimensions();
// Labels // Labels
this.beforeBuildLabels(); this.beforeBuildLabels();
this.buildLabels(); this.buildLabels();
this.afterBuildLabels(); this.afterBuildLabels();
// Fit // Fit
this.beforeFit(); this.beforeFit();
this.fit(); this.fit();
this.afterFit(); this.afterFit();
// //
this.afterUpdate(); this.afterUpdate();
return this.minSize; return this.minSize;
}, },
afterUpdate: helpers.noop, afterUpdate: helpers.noop,
// //
beforeSetDimensions: helpers.noop, beforeSetDimensions: helpers.noop,
setDimensions: function() { setDimensions: function() {
// Set the unconstrained dimension before label rotation // Set the unconstrained dimension before label rotation
if (this.isHorizontal()) { if (this.isHorizontal()) {
// Reset position before calculating rotation // Reset position before calculating rotation
this.width = this.maxWidth; this.width = this.maxWidth;
this.left = 0; this.left = 0;
this.right = this.width; this.right = this.width;
} else { } else {
this.height = this.maxHeight; this.height = this.maxHeight;
// Reset position before calculating rotation // Reset position before calculating rotation
this.top = 0; this.top = 0;
this.bottom = this.height; this.bottom = this.height;
} }
// Reset padding // Reset padding
this.paddingLeft = 0; this.paddingLeft = 0;
this.paddingTop = 0; this.paddingTop = 0;
this.paddingRight = 0; this.paddingRight = 0;
this.paddingBottom = 0; this.paddingBottom = 0;
// Reset minSize // Reset minSize
this.minSize = { this.minSize = {
width: 0, width: 0,
height: 0, height: 0
}; };
}, },
afterSetDimensions: helpers.noop, afterSetDimensions: helpers.noop,
// //
beforeBuildLabels: helpers.noop, beforeBuildLabels: helpers.noop,
buildLabels: helpers.noop, buildLabels: helpers.noop,
afterBuildLabels: helpers.noop, afterBuildLabels: helpers.noop,
// //
beforeFit: helpers.noop, beforeFit: helpers.noop,
fit: function() { fit: function() {
var ctx = this.ctx; var ctx = this.ctx;
var titleFont = helpers.fontString(this.options.fontSize, this.options.fontStyle, this.options.fontFamily); var titleFont = helpers.fontString(this.options.fontSize, this.options.fontStyle, this.options.fontFamily);
// Width // Width
if (this.isHorizontal()) { if (this.isHorizontal()) {
this.minSize.width = this.maxWidth; // fill all the width this.minSize.width = this.maxWidth; // fill all the width
} else { } else {
this.minSize.width = 0; this.minSize.width = 0;
} }
// height // height
if (this.isHorizontal()) { if (this.isHorizontal()) {
this.minSize.height = 0; this.minSize.height = 0;
} else { } else {
this.minSize.height = this.maxHeight; // fill all the height this.minSize.height = this.maxHeight; // fill all the height
} }
// Increase sizes here // Increase sizes here
if (this.isHorizontal()) { if (this.isHorizontal()) {
// Title // Title
if (this.options.display) { if (this.options.display) {
this.minSize.height += this.options.fontSize + (this.options.padding * 2); this.minSize.height += this.options.fontSize + (this.options.padding * 2);
} }
} else { } else {
// TODO vertical // TODO vertical
} }
this.width = this.minSize.width; this.width = this.minSize.width;
this.height = this.minSize.height; this.height = this.minSize.height;
}, },
afterFit: helpers.noop, afterFit: helpers.noop,
// Shared Methods // Shared Methods
isHorizontal: function() { isHorizontal: function() {
return this.options.position == "top" || this.options.position == "bottom"; return this.options.position === "top" || this.options.position === "bottom";
}, },
// Actualy draw the title block on the canvas // Actualy draw the title block on the canvas
draw: function() { draw: function() {
if (this.options.display) { if (this.options.display) {
var ctx = this.ctx; var ctx = this.ctx;
var titleX, titleY; var titleX, titleY;
// Horizontal // Horizontal
if (this.isHorizontal()) { if (this.isHorizontal()) {
// Title // Title
if (this.options.display) { if (this.options.display) {
ctx.textAlign = "center"; ctx.textAlign = "center";
ctx.textBaseline = 'middle'; ctx.textBaseline = 'middle';
ctx.fillStyle = this.options.fontColor; // render in correct colour ctx.fillStyle = this.options.fontColor; // render in correct colour
ctx.font = helpers.fontString(this.options.fontSize, this.options.fontStyle, this.options.fontFamily); ctx.font = helpers.fontString(this.options.fontSize, this.options.fontStyle, this.options.fontFamily);
titleX = this.left + ((this.right - this.left) / 2); // midpoint of the width titleX = this.left + ((this.right - this.left) / 2); // midpoint of the width
titleY = this.top + ((this.bottom - this.top) / 2); // midpoint of the height titleY = this.top + ((this.bottom - this.top) / 2); // midpoint of the height
ctx.fillText(this.options.text, titleX, titleY); ctx.fillText(this.options.text, titleX, titleY);
} }
} else { } else {
// Title // Title
if (this.options.display) { if (this.options.display) {
titleX = this.options.position == 'left' ? this.left + (this.options.fontSize / 2) : this.right - (this.options.fontSize / 2); titleX = this.options.position === 'left' ? this.left + (this.options.fontSize / 2) : this.right - (this.options.fontSize / 2);
titleY = this.top + ((this.bottom - this.top) / 2); titleY = this.top + ((this.bottom - this.top) / 2);
var rotation = this.options.position == 'left' ? -0.5 * Math.PI : 0.5 * Math.PI; var rotation = this.options.position === 'left' ? -0.5 * Math.PI : 0.5 * Math.PI;
ctx.save(); ctx.save();
ctx.translate(titleX, titleY); ctx.translate(titleX, titleY);
ctx.rotate(rotation); ctx.rotate(rotation);
ctx.textAlign = "center"; ctx.textAlign = "center";
ctx.fillStyle = this.options.fontColor; // render in correct colour ctx.fillStyle = this.options.fontColor; // render in correct colour
ctx.font = helpers.fontString(this.options.fontSize, this.options.fontStyle, this.options.fontFamily); ctx.font = helpers.fontString(this.options.fontSize, this.options.fontStyle, this.options.fontFamily);
ctx.textBaseline = 'middle'; ctx.textBaseline = 'middle';
ctx.fillText(this.options.text, 0, 0); ctx.fillText(this.options.text, 0, 0);
ctx.restore(); ctx.restore();
} }
} }
} }
} }
}); });
}; };

File diff suppressed because it is too large Load Diff

View File

@ -2,160 +2,160 @@
module.exports = function(Chart) { module.exports = function(Chart) {
var helpers = Chart.helpers; var helpers = Chart.helpers;
Chart.defaults.global.elements.line = { Chart.defaults.global.elements.line = {
tension: 0.4, tension: 0.4,
backgroundColor: Chart.defaults.global.defaultColor, backgroundColor: Chart.defaults.global.defaultColor,
borderWidth: 3, borderWidth: 3,
borderColor: Chart.defaults.global.defaultColor, borderColor: Chart.defaults.global.defaultColor,
borderCapStyle: 'butt', borderCapStyle: 'butt',
borderDash: [], borderDash: [],
borderDashOffset: 0.0, borderDashOffset: 0.0,
borderJoinStyle: 'miter', borderJoinStyle: 'miter',
fill: true, // do we fill in the area between the line and its base axis fill: true // do we fill in the area between the line and its base axis
}; };
Chart.elements.Line = Chart.Element.extend({ Chart.elements.Line = Chart.Element.extend({
lineToNextPoint: function(previousPoint, point, nextPoint, skipHandler, previousSkipHandler) { lineToNextPoint: function(previousPoint, point, nextPoint, skipHandler, previousSkipHandler) {
var ctx = this._chart.ctx; var ctx = this._chart.ctx;
if (point._view.skip) { if (point._view.skip) {
skipHandler.call(this, previousPoint, point, nextPoint); skipHandler.call(this, previousPoint, point, nextPoint);
} else if (previousPoint._view.skip) { } else if (previousPoint._view.skip) {
previousSkipHandler.call(this, previousPoint, point, nextPoint); previousSkipHandler.call(this, previousPoint, point, nextPoint);
} else if (point._view.tension === 0) { } else if (point._view.tension === 0) {
ctx.lineTo(point._view.x, point._view.y); ctx.lineTo(point._view.x, point._view.y);
} else { } else {
// Line between points // Line between points
ctx.bezierCurveTo( ctx.bezierCurveTo(
previousPoint._view.controlPointNextX, previousPoint._view.controlPointNextX,
previousPoint._view.controlPointNextY, previousPoint._view.controlPointNextY,
point._view.controlPointPreviousX, point._view.controlPointPreviousX,
point._view.controlPointPreviousY, point._view.controlPointPreviousY,
point._view.x, point._view.x,
point._view.y point._view.y
); );
} }
}, },
draw: function() { draw: function() {
var _this = this; var _this = this;
var vm = this._view; var vm = this._view;
var ctx = this._chart.ctx; var ctx = this._chart.ctx;
var first = this._children[0]; var first = this._children[0];
var last = this._children[this._children.length - 1]; var last = this._children[this._children.length - 1];
function loopBackToStart(drawLineToCenter) { function loopBackToStart(drawLineToCenter) {
if (!first._view.skip && !last._view.skip) { if (!first._view.skip && !last._view.skip) {
// Draw a bezier line from last to first // Draw a bezier line from last to first
ctx.bezierCurveTo( ctx.bezierCurveTo(
last._view.controlPointNextX, last._view.controlPointNextX,
last._view.controlPointNextY, last._view.controlPointNextY,
first._view.controlPointPreviousX, first._view.controlPointPreviousX,
first._view.controlPointPreviousY, first._view.controlPointPreviousY,
first._view.x, first._view.x,
first._view.y first._view.y
); );
} else if (drawLineToCenter) { } else if (drawLineToCenter) {
// Go to center // Go to center
ctx.lineTo(_this._view.scaleZero.x, _this._view.scaleZero.y); ctx.lineTo(_this._view.scaleZero.x, _this._view.scaleZero.y);
} }
} }
ctx.save(); ctx.save();
// If we had points and want to fill this line, do so. // If we had points and want to fill this line, do so.
if (this._children.length > 0 && vm.fill) { if (this._children.length > 0 && vm.fill) {
// Draw the background first (so the border is always on top) // Draw the background first (so the border is always on top)
ctx.beginPath(); ctx.beginPath();
helpers.each(this._children, function(point, index) { helpers.each(this._children, function(point, index) {
var previous = helpers.previousItem(this._children, index); var previous = helpers.previousItem(this._children, index);
var next = helpers.nextItem(this._children, index); var next = helpers.nextItem(this._children, index);
// First point moves to it's starting position no matter what // First point moves to it's starting position no matter what
if (index === 0) { if (index === 0) {
if (this._loop) { if (this._loop) {
ctx.moveTo(vm.scaleZero.x, vm.scaleZero.y); ctx.moveTo(vm.scaleZero.x, vm.scaleZero.y);
} else { } else {
ctx.moveTo(point._view.x, vm.scaleZero); ctx.moveTo(point._view.x, vm.scaleZero);
} }
if (point._view.skip) { if (point._view.skip) {
if (!this._loop) { if (!this._loop) {
ctx.moveTo(next._view.x, this._view.scaleZero); ctx.moveTo(next._view.x, this._view.scaleZero);
} }
} else { } else {
ctx.lineTo(point._view.x, point._view.y); ctx.lineTo(point._view.x, point._view.y);
} }
} else { } else {
this.lineToNextPoint(previous, point, next, function(previousPoint, point, nextPoint) { this.lineToNextPoint(previous, point, next, function(previousPoint, point, nextPoint) {
if (this._loop) { if (this._loop) {
// Go to center // Go to center
ctx.lineTo(this._view.scaleZero.x, this._view.scaleZero.y); ctx.lineTo(this._view.scaleZero.x, this._view.scaleZero.y);
} else { } else {
ctx.lineTo(previousPoint._view.x, this._view.scaleZero); ctx.lineTo(previousPoint._view.x, this._view.scaleZero);
ctx.moveTo(nextPoint._view.x, this._view.scaleZero); ctx.moveTo(nextPoint._view.x, this._view.scaleZero);
} }
}, function(previousPoint, point) { }, function(previousPoint, point) {
// If we skipped the last point, draw a line to ourselves so that the fill is nice // If we skipped the last point, draw a line to ourselves so that the fill is nice
ctx.lineTo(point._view.x, point._view.y); ctx.lineTo(point._view.x, point._view.y);
}); });
} }
}, this); }, this);
// For radial scales, loop back around to the first point // For radial scales, loop back around to the first point
if (this._loop) { if (this._loop) {
loopBackToStart(true); loopBackToStart(true);
} else { } else {
//Round off the line by going to the base of the chart, back to the start, then fill. //Round off the line by going to the base of the chart, back to the start, then fill.
ctx.lineTo(this._children[this._children.length - 1]._view.x, vm.scaleZero); ctx.lineTo(this._children[this._children.length - 1]._view.x, vm.scaleZero);
ctx.lineTo(this._children[0]._view.x, vm.scaleZero); ctx.lineTo(this._children[0]._view.x, vm.scaleZero);
} }
ctx.fillStyle = vm.backgroundColor || Chart.defaults.global.defaultColor; ctx.fillStyle = vm.backgroundColor || Chart.defaults.global.defaultColor;
ctx.closePath(); ctx.closePath();
ctx.fill(); ctx.fill();
} }
// Now draw the line between all the points with any borders // Now draw the line between all the points with any borders
ctx.lineCap = vm.borderCapStyle || Chart.defaults.global.elements.line.borderCapStyle; ctx.lineCap = vm.borderCapStyle || Chart.defaults.global.elements.line.borderCapStyle;
// IE 9 and 10 do not support line dash // IE 9 and 10 do not support line dash
if (ctx.setLineDash) { if (ctx.setLineDash) {
ctx.setLineDash(vm.borderDash || Chart.defaults.global.elements.line.borderDash); ctx.setLineDash(vm.borderDash || Chart.defaults.global.elements.line.borderDash);
} }
ctx.lineDashOffset = vm.borderDashOffset || Chart.defaults.global.elements.line.borderDashOffset; ctx.lineDashOffset = vm.borderDashOffset || Chart.defaults.global.elements.line.borderDashOffset;
ctx.lineJoin = vm.borderJoinStyle || Chart.defaults.global.elements.line.borderJoinStyle; ctx.lineJoin = vm.borderJoinStyle || Chart.defaults.global.elements.line.borderJoinStyle;
ctx.lineWidth = vm.borderWidth || Chart.defaults.global.elements.line.borderWidth; ctx.lineWidth = vm.borderWidth || Chart.defaults.global.elements.line.borderWidth;
ctx.strokeStyle = vm.borderColor || Chart.defaults.global.defaultColor; ctx.strokeStyle = vm.borderColor || Chart.defaults.global.defaultColor;
ctx.beginPath(); ctx.beginPath();
helpers.each(this._children, function(point, index) { helpers.each(this._children, function(point, index) {
var previous = helpers.previousItem(this._children, index); var previous = helpers.previousItem(this._children, index);
var next = helpers.nextItem(this._children, index); var next = helpers.nextItem(this._children, index);
if (index === 0) { if (index === 0) {
ctx.moveTo(point._view.x, point._view.y); ctx.moveTo(point._view.x, point._view.y);
} else { } else {
this.lineToNextPoint(previous, point, next, function(previousPoint, point, nextPoint) { this.lineToNextPoint(previous, point, next, function(previousPoint, point, nextPoint) {
ctx.moveTo(nextPoint._view.x, nextPoint._view.y); ctx.moveTo(nextPoint._view.x, nextPoint._view.y);
}, function(previousPoint, point) { }, function(previousPoint, point) {
// If we skipped the last point, move up to our point preventing a line from being drawn // If we skipped the last point, move up to our point preventing a line from being drawn
ctx.moveTo(point._view.x, point._view.y); ctx.moveTo(point._view.x, point._view.y);
}); });
} }
}, this); }, this);
if (this._loop && this._children.length > 0) { if (this._loop && this._children.length > 0) {
loopBackToStart(); loopBackToStart();
} }
ctx.stroke(); ctx.stroke();
ctx.restore(); ctx.restore();
}, }
}); });
}; };

View File

@ -2,148 +2,147 @@
module.exports = function(Chart) { module.exports = function(Chart) {
var helpers = Chart.helpers; var helpers = Chart.helpers;
Chart.defaults.global.elements.point = { Chart.defaults.global.elements.point = {
radius: 3, radius: 3,
pointStyle: 'circle', pointStyle: 'circle',
backgroundColor: Chart.defaults.global.defaultColor, backgroundColor: Chart.defaults.global.defaultColor,
borderWidth: 1, borderWidth: 1,
borderColor: Chart.defaults.global.defaultColor, borderColor: Chart.defaults.global.defaultColor,
// Hover // Hover
hitRadius: 1, hitRadius: 1,
hoverRadius: 4, hoverRadius: 4,
hoverBorderWidth: 1, hoverBorderWidth: 1
}; };
Chart.elements.Point = Chart.Element.extend({ Chart.elements.Point = Chart.Element.extend({
inRange: function(mouseX, mouseY) { inRange: function(mouseX, mouseY) {
var vm = this._view; var vm = this._view;
if (vm) { if (vm) {
var hoverRange = vm.hitRadius + vm.radius; var hoverRange = vm.hitRadius + vm.radius;
return ((Math.pow(mouseX - vm.x, 2) + Math.pow(mouseY - vm.y, 2)) < Math.pow(hoverRange, 2)); return ((Math.pow(mouseX - vm.x, 2) + Math.pow(mouseY - vm.y, 2)) < Math.pow(hoverRange, 2));
} else { } else {
return false; return false;
} }
}, },
inLabelRange: function(mouseX) { inLabelRange: function(mouseX) {
var vm = this._view; var vm = this._view;
if (vm) { if (vm) {
return (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hitRadius, 2)); return (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hitRadius, 2));
} else { } else {
return false; return false;
} }
}, },
tooltipPosition: function() { tooltipPosition: function() {
var vm = this._view; var vm = this._view;
return { return {
x: vm.x, x: vm.x,
y: vm.y, y: vm.y,
padding: vm.radius + vm.borderWidth padding: vm.radius + vm.borderWidth
}; };
}, },
draw: function() { draw: function() {
var vm = this._view; var vm = this._view;
var ctx = this._chart.ctx; var ctx = this._chart.ctx;
if (vm.skip) { if (vm.skip) {
return; return;
} }
if (vm.radius > 0 || vm.borderWidth > 0) { if (vm.radius > 0 || vm.borderWidth > 0) {
ctx.strokeStyle = vm.borderColor || Chart.defaults.global.defaultColor; ctx.strokeStyle = vm.borderColor || Chart.defaults.global.defaultColor;
ctx.lineWidth = vm.borderWidth || Chart.defaults.global.elements.point.borderWidth; ctx.lineWidth = vm.borderWidth || Chart.defaults.global.elements.point.borderWidth;
ctx.fillStyle = vm.backgroundColor || Chart.defaults.global.defaultColor; ctx.fillStyle = vm.backgroundColor || Chart.defaults.global.defaultColor;
var radius = vm.radius || Chart.defaults.global.elements.point.radius; var radius = vm.radius || Chart.defaults.global.elements.point.radius;
var xOffset; var xOffset;
var yOffset; var yOffset;
switch (vm.pointStyle) { switch (vm.pointStyle) {
// Default includes circle // Default includes circle
default: default: ctx.beginPath();
ctx.beginPath(); ctx.arc(vm.x, vm.y, radius, 0, Math.PI * 2);
ctx.arc(vm.x, vm.y, radius, 0, Math.PI * 2); ctx.closePath();
ctx.closePath(); ctx.fill();
ctx.fill(); break;
break; case 'triangle':
case 'triangle': ctx.beginPath();
ctx.beginPath(); var edgeLength = 3 * radius / Math.sqrt(3);
var edgeLength = 3 * radius / Math.sqrt(3); var height = edgeLength * Math.sqrt(3) / 2;
var height = edgeLength * Math.sqrt(3) / 2; ctx.moveTo(vm.x - edgeLength / 2, vm.y + height / 3);
ctx.moveTo(vm.x - edgeLength / 2, vm.y + height / 3); ctx.lineTo(vm.x + edgeLength / 2, vm.y + height / 3);
ctx.lineTo(vm.x + edgeLength / 2, vm.y + height / 3); ctx.lineTo(vm.x, vm.y - 2 * height / 3);
ctx.lineTo(vm.x, vm.y - 2 * height / 3); ctx.closePath();
ctx.closePath(); ctx.fill();
ctx.fill(); break;
break; case 'rect':
case 'rect': ctx.fillRect(vm.x - 1 / Math.SQRT2 * radius, vm.y - 1 / Math.SQRT2 * radius, 2 / Math.SQRT2 * radius, 2 / Math.SQRT2 * radius);
ctx.fillRect(vm.x - 1 / Math.SQRT2 * radius, vm.y - 1 / Math.SQRT2 * radius, 2 / Math.SQRT2 * radius, 2 / Math.SQRT2 * radius); ctx.strokeRect(vm.x - 1 / Math.SQRT2 * radius, vm.y - 1 / Math.SQRT2 * radius, 2 / Math.SQRT2 * radius, 2 / Math.SQRT2 * radius);
ctx.strokeRect(vm.x - 1 / Math.SQRT2 * radius, vm.y - 1 / Math.SQRT2 * radius, 2 / Math.SQRT2 * radius, 2 / Math.SQRT2 * radius); break;
break; case 'rectRot':
case 'rectRot': ctx.translate(vm.x, vm.y);
ctx.translate(vm.x, vm.y); ctx.rotate(Math.PI / 4);
ctx.rotate(Math.PI / 4); ctx.fillRect(-1 / Math.SQRT2 * radius, -1 / Math.SQRT2 * radius, 2 / Math.SQRT2 * radius, 2 / Math.SQRT2 * radius);
ctx.fillRect(-1 / Math.SQRT2 * radius, -1 / Math.SQRT2 * radius, 2 / Math.SQRT2 * radius, 2 / Math.SQRT2 * radius); ctx.strokeRect(-1 / Math.SQRT2 * radius, -1 / Math.SQRT2 * radius, 2 / Math.SQRT2 * radius, 2 / Math.SQRT2 * radius);
ctx.strokeRect(-1 / Math.SQRT2 * radius, -1 / Math.SQRT2 * radius, 2 / Math.SQRT2 * radius, 2 / Math.SQRT2 * radius); ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.setTransform(1, 0, 0, 1, 0, 0); break;
break; case 'cross':
case 'cross': ctx.beginPath();
ctx.beginPath(); ctx.moveTo(vm.x, vm.y + radius);
ctx.moveTo(vm.x, vm.y + radius); ctx.lineTo(vm.x, vm.y - radius);
ctx.lineTo(vm.x, vm.y - radius); ctx.moveTo(vm.x - radius, vm.y);
ctx.moveTo(vm.x - radius, vm.y); ctx.lineTo(vm.x + radius, vm.y);
ctx.lineTo(vm.x + radius, vm.y); ctx.closePath();
ctx.closePath(); break;
break; case 'crossRot':
case 'crossRot': ctx.beginPath();
ctx.beginPath(); xOffset = Math.cos(Math.PI / 4) * radius;
xOffset = Math.cos(Math.PI / 4) * radius; yOffset = Math.sin(Math.PI / 4) * radius;
yOffset = Math.sin(Math.PI / 4) * radius; ctx.moveTo(vm.x - xOffset, vm.y - yOffset);
ctx.moveTo(vm.x - xOffset, vm.y - yOffset); ctx.lineTo(vm.x + xOffset, vm.y + yOffset);
ctx.lineTo(vm.x + xOffset, vm.y + yOffset); ctx.moveTo(vm.x - xOffset, vm.y + yOffset);
ctx.moveTo(vm.x - xOffset, vm.y + yOffset); ctx.lineTo(vm.x + xOffset, vm.y - yOffset);
ctx.lineTo(vm.x + xOffset, vm.y - yOffset); ctx.closePath();
ctx.closePath(); break;
break; case 'star':
case 'star': ctx.beginPath();
ctx.beginPath(); ctx.moveTo(vm.x, vm.y + radius);
ctx.moveTo(vm.x, vm.y + radius); ctx.lineTo(vm.x, vm.y - radius);
ctx.lineTo(vm.x, vm.y - radius); ctx.moveTo(vm.x - radius, vm.y);
ctx.moveTo(vm.x - radius, vm.y); ctx.lineTo(vm.x + radius, vm.y);
ctx.lineTo(vm.x + radius, vm.y); xOffset = Math.cos(Math.PI / 4) * radius;
xOffset = Math.cos(Math.PI / 4) * radius; yOffset = Math.sin(Math.PI / 4) * radius;
yOffset = Math.sin(Math.PI / 4) * radius; ctx.moveTo(vm.x - xOffset, vm.y - yOffset);
ctx.moveTo(vm.x - xOffset, vm.y - yOffset); ctx.lineTo(vm.x + xOffset, vm.y + yOffset);
ctx.lineTo(vm.x + xOffset, vm.y + yOffset); ctx.moveTo(vm.x - xOffset, vm.y + yOffset);
ctx.moveTo(vm.x - xOffset, vm.y + yOffset); ctx.lineTo(vm.x + xOffset, vm.y - yOffset);
ctx.lineTo(vm.x + xOffset, vm.y - yOffset); ctx.closePath();
ctx.closePath(); break;
break; case 'line':
case 'line': ctx.beginPath();
ctx.beginPath(); ctx.moveTo(vm.x - radius, vm.y);
ctx.moveTo(vm.x - radius, vm.y); ctx.lineTo(vm.x + radius, vm.y);
ctx.lineTo(vm.x + radius, vm.y); ctx.closePath();
ctx.closePath(); break;
break; case 'dash':
case 'dash': ctx.beginPath();
ctx.beginPath(); ctx.moveTo(vm.x, vm.y);
ctx.moveTo(vm.x, vm.y); ctx.lineTo(vm.x + radius, vm.y);
ctx.lineTo(vm.x + radius, vm.y); ctx.closePath();
ctx.closePath(); break;
break; }
}
ctx.stroke(); ctx.stroke();
} }
} }
}); });
}; };

View File

@ -2,85 +2,85 @@
module.exports = function(Chart) { module.exports = function(Chart) {
var helpers = Chart.helpers; var helpers = Chart.helpers;
Chart.defaults.global.elements.rectangle = { Chart.defaults.global.elements.rectangle = {
backgroundColor: Chart.defaults.global.defaultColor, backgroundColor: Chart.defaults.global.defaultColor,
borderWidth: 0, borderWidth: 0,
borderColor: Chart.defaults.global.defaultColor, borderColor: Chart.defaults.global.defaultColor
}; };
Chart.elements.Rectangle = Chart.Element.extend({ Chart.elements.Rectangle = Chart.Element.extend({
draw: function() { draw: function() {
var ctx = this._chart.ctx; var ctx = this._chart.ctx;
var vm = this._view; var vm = this._view;
var halfWidth = vm.width / 2, var halfWidth = vm.width / 2,
leftX = vm.x - halfWidth, leftX = vm.x - halfWidth,
rightX = vm.x + halfWidth, rightX = vm.x + halfWidth,
top = vm.base - (vm.base - vm.y), top = vm.base - (vm.base - vm.y),
halfStroke = vm.borderWidth / 2; halfStroke = vm.borderWidth / 2;
// Canvas doesn't allow us to stroke inside the width so we can // Canvas doesn't allow us to stroke inside the width so we can
// adjust the sizes to fit if we're setting a stroke on the line // adjust the sizes to fit if we're setting a stroke on the line
if (vm.borderWidth) { if (vm.borderWidth) {
leftX += halfStroke; leftX += halfStroke;
rightX -= halfStroke; rightX -= halfStroke;
top += halfStroke; top += halfStroke;
} }
ctx.beginPath(); ctx.beginPath();
ctx.fillStyle = vm.backgroundColor; ctx.fillStyle = vm.backgroundColor;
ctx.strokeStyle = vm.borderColor; ctx.strokeStyle = vm.borderColor;
ctx.lineWidth = vm.borderWidth; ctx.lineWidth = vm.borderWidth;
// It'd be nice to keep this class totally generic to any rectangle // It'd be nice to keep this class totally generic to any rectangle
// and simply specify which border to miss out. // and simply specify which border to miss out.
ctx.moveTo(leftX, vm.base); ctx.moveTo(leftX, vm.base);
ctx.lineTo(leftX, top); ctx.lineTo(leftX, top);
ctx.lineTo(rightX, top); ctx.lineTo(rightX, top);
ctx.lineTo(rightX, vm.base); ctx.lineTo(rightX, vm.base);
ctx.fill(); ctx.fill();
if (vm.borderWidth) { if (vm.borderWidth) {
ctx.stroke(); ctx.stroke();
} }
}, },
height: function() { height: function() {
var vm = this._view; var vm = this._view;
return vm.base - vm.y; return vm.base - vm.y;
}, },
inRange: function(mouseX, mouseY) { inRange: function(mouseX, mouseY) {
var vm = this._view; var vm = this._view;
var inRange = false; var inRange = false;
if (vm) { if (vm) {
if (vm.y < vm.base) { if (vm.y < vm.base) {
inRange = (mouseX >= vm.x - vm.width / 2 && mouseX <= vm.x + vm.width / 2) && (mouseY >= vm.y && mouseY <= vm.base); inRange = (mouseX >= vm.x - vm.width / 2 && mouseX <= vm.x + vm.width / 2) && (mouseY >= vm.y && mouseY <= vm.base);
} else { } else {
inRange = (mouseX >= vm.x - vm.width / 2 && mouseX <= vm.x + vm.width / 2) && (mouseY >= vm.base && mouseY <= vm.y); inRange = (mouseX >= vm.x - vm.width / 2 && mouseX <= vm.x + vm.width / 2) && (mouseY >= vm.base && mouseY <= vm.y);
} }
} }
return inRange; return inRange;
}, },
inLabelRange: function(mouseX) { inLabelRange: function(mouseX) {
var vm = this._view; var vm = this._view;
if (vm) { if (vm) {
return (mouseX >= vm.x - vm.width / 2 && mouseX <= vm.x + vm.width / 2); return (mouseX >= vm.x - vm.width / 2 && mouseX <= vm.x + vm.width / 2);
} else { } else {
return false; return false;
} }
}, },
tooltipPosition: function() { tooltipPosition: function() {
var vm = this._view; var vm = this._view;
return { return {
x: vm.x, x: vm.x,
y: vm.y y: vm.y
}; };
}, }
}); });
}; };

View File

@ -2,47 +2,47 @@
module.exports = function(Chart) { module.exports = function(Chart) {
var helpers = Chart.helpers; var helpers = Chart.helpers;
// Default config for a category scale // Default config for a category scale
var defaultConfig = { var defaultConfig = {
position: "bottom", position: "bottom"
}; };
var DatasetScale = Chart.Scale.extend({ var DatasetScale = Chart.Scale.extend({
buildTicks: function(index) { buildTicks: function(index) {
this.ticks = this.chart.data.labels; this.ticks = this.chart.data.labels;
}, },
getLabelForIndex: function(index, datasetIndex) { getLabelForIndex: function(index, datasetIndex) {
return this.ticks[index]; return this.ticks[index];
}, },
// Used to get data value locations. Value can either be an index or a numerical value // Used to get data value locations. Value can either be an index or a numerical value
getPixelForValue: function(value, index, datasetIndex, includeOffset) { getPixelForValue: function(value, index, datasetIndex, includeOffset) {
if (this.isHorizontal()) { if (this.isHorizontal()) {
var innerWidth = this.width - (this.paddingLeft + this.paddingRight); var innerWidth = this.width - (this.paddingLeft + this.paddingRight);
var valueWidth = innerWidth / Math.max((this.chart.data.labels.length - ((this.options.gridLines.offsetGridLines) ? 0 : 1)), 1); var valueWidth = innerWidth / Math.max((this.chart.data.labels.length - ((this.options.gridLines.offsetGridLines) ? 0 : 1)), 1);
var widthOffset = (valueWidth * index) + this.paddingLeft; var widthOffset = (valueWidth * index) + this.paddingLeft;
if (this.options.gridLines.offsetGridLines && includeOffset) { if (this.options.gridLines.offsetGridLines && includeOffset) {
widthOffset += (valueWidth / 2); widthOffset += (valueWidth / 2);
} }
return this.left + Math.round(widthOffset); return this.left + Math.round(widthOffset);
} else { } else {
var innerHeight = this.height - (this.paddingTop + this.paddingBottom); var innerHeight = this.height - (this.paddingTop + this.paddingBottom);
var valueHeight = innerHeight / Math.max((this.chart.data.labels.length - ((this.options.gridLines.offsetGridLines) ? 0 : 1)), 1); var valueHeight = innerHeight / Math.max((this.chart.data.labels.length - ((this.options.gridLines.offsetGridLines) ? 0 : 1)), 1);
var heightOffset = (valueHeight * index) + this.paddingTop; var heightOffset = (valueHeight * index) + this.paddingTop;
if (this.options.gridLines.offsetGridLines && includeOffset) { if (this.options.gridLines.offsetGridLines && includeOffset) {
heightOffset += (valueHeight / 2); heightOffset += (valueHeight / 2);
} }
return this.top + Math.round(heightOffset); return this.top + Math.round(heightOffset);
} }
}, }
}); });
Chart.scaleService.registerScaleType("category", DatasetScale, defaultConfig); Chart.scaleService.registerScaleType("category", DatasetScale, defaultConfig);
}; };

View File

@ -2,255 +2,255 @@
module.exports = function(Chart) { module.exports = function(Chart) {
var helpers = Chart.helpers; var helpers = Chart.helpers;
var defaultConfig = { var defaultConfig = {
position: "left", position: "left",
ticks: { ticks: {
callback: function(tickValue, index, ticks) { callback: function(tickValue, index, ticks) {
var delta = ticks[1] - ticks[0]; var delta = ticks[1] - ticks[0];
// If we have a number like 2.5 as the delta, figure out how many decimal places we need // If we have a number like 2.5 as the delta, figure out how many decimal places we need
if (Math.abs(delta) > 1) { if (Math.abs(delta) > 1) {
if (tickValue !== Math.floor(tickValue)) { if (tickValue !== Math.floor(tickValue)) {
// not an integer // not an integer
delta = tickValue - Math.floor(tickValue); delta = tickValue - Math.floor(tickValue);
} }
} }
var logDelta = helpers.log10(Math.abs(delta)); var logDelta = helpers.log10(Math.abs(delta));
var tickString = ''; var tickString = '';
if (tickValue !== 0) { if (tickValue !== 0) {
var numDecimal = -1 * Math.floor(logDelta); var numDecimal = -1 * Math.floor(logDelta);
numDecimal = Math.max(Math.min(numDecimal, 20), 0); // toFixed has a max of 20 decimal places numDecimal = Math.max(Math.min(numDecimal, 20), 0); // toFixed has a max of 20 decimal places
tickString = tickValue.toFixed(numDecimal); tickString = tickValue.toFixed(numDecimal);
} else { } else {
tickString = '0'; // never show decimal places for 0 tickString = '0'; // never show decimal places for 0
} }
return tickString; return tickString;
} }
} }
}; };
var LinearScale = Chart.Scale.extend({ var LinearScale = Chart.Scale.extend({
determineDataLimits: function() { determineDataLimits: function() {
// First Calculate the range // First Calculate the range
this.min = null; this.min = null;
this.max = null; this.max = null;
if (this.options.stacked) { if (this.options.stacked) {
var valuesPerType = {}; var valuesPerType = {};
var hasPositiveValues = false; var hasPositiveValues = false;
var hasNegativeValues = false; var hasNegativeValues = false;
helpers.each(this.chart.data.datasets, function(dataset) { helpers.each(this.chart.data.datasets, function(dataset) {
if (valuesPerType[dataset.type] === undefined) { if (valuesPerType[dataset.type] === undefined) {
valuesPerType[dataset.type] = { valuesPerType[dataset.type] = {
positiveValues: [], positiveValues: [],
negativeValues: [], negativeValues: []
}; };
} }
// Store these per type // Store these per type
var positiveValues = valuesPerType[dataset.type].positiveValues; var positiveValues = valuesPerType[dataset.type].positiveValues;
var negativeValues = valuesPerType[dataset.type].negativeValues; var negativeValues = valuesPerType[dataset.type].negativeValues;
if (helpers.isDatasetVisible(dataset) && (this.isHorizontal() ? dataset.xAxisID === this.id : dataset.yAxisID === this.id)) { if (helpers.isDatasetVisible(dataset) && (this.isHorizontal() ? dataset.xAxisID === this.id : dataset.yAxisID === this.id)) {
helpers.each(dataset.data, function(rawValue, index) { helpers.each(dataset.data, function(rawValue, index) {
var value = +this.getRightValue(rawValue); var value = +this.getRightValue(rawValue);
if (isNaN(value)) { if (isNaN(value)) {
return; return;
} }
positiveValues[index] = positiveValues[index] || 0; positiveValues[index] = positiveValues[index] || 0;
negativeValues[index] = negativeValues[index] || 0; negativeValues[index] = negativeValues[index] || 0;
if (this.options.relativePoints) { if (this.options.relativePoints) {
positiveValues[index] = 100; positiveValues[index] = 100;
} else { } else {
if (value < 0) { if (value < 0) {
hasNegativeValues = true; hasNegativeValues = true;
negativeValues[index] += value; negativeValues[index] += value;
} else { } else {
hasPositiveValues = true; hasPositiveValues = true;
positiveValues[index] += value; positiveValues[index] += value;
} }
} }
}, this); }, this);
} }
}, this); }, this);
helpers.each(valuesPerType, function(valuesForType) { helpers.each(valuesPerType, function(valuesForType) {
var values = valuesForType.positiveValues.concat(valuesForType.negativeValues); var values = valuesForType.positiveValues.concat(valuesForType.negativeValues);
var minVal = helpers.min(values); var minVal = helpers.min(values);
var maxVal = helpers.max(values); var maxVal = helpers.max(values);
this.min = this.min === null ? minVal : Math.min(this.min, minVal); this.min = this.min === null ? minVal : Math.min(this.min, minVal);
this.max = this.max === null ? maxVal : Math.max(this.max, maxVal); this.max = this.max === null ? maxVal : Math.max(this.max, maxVal);
}, this); }, this);
} else { } else {
helpers.each(this.chart.data.datasets, function(dataset) { helpers.each(this.chart.data.datasets, function(dataset) {
if (helpers.isDatasetVisible(dataset) && (this.isHorizontal() ? dataset.xAxisID === this.id : dataset.yAxisID === this.id)) { if (helpers.isDatasetVisible(dataset) && (this.isHorizontal() ? dataset.xAxisID === this.id : dataset.yAxisID === this.id)) {
helpers.each(dataset.data, function(rawValue, index) { helpers.each(dataset.data, function(rawValue, index) {
var value = +this.getRightValue(rawValue); var value = +this.getRightValue(rawValue);
if (isNaN(value)) { if (isNaN(value)) {
return; return;
} }
if (this.min === null) { if (this.min === null) {
this.min = value; this.min = value;
} else if (value < this.min) { } else if (value < this.min) {
this.min = value; this.min = value;
} }
if (this.max === null) { if (this.max === null) {
this.max = value; this.max = value;
} else if (value > this.max) { } else if (value > this.max) {
this.max = value; this.max = value;
} }
}, this); }, this);
} }
}, this); }, this);
} }
// If we are forcing it to begin at 0, but 0 will already be rendered on the chart, // If we are forcing it to begin at 0, but 0 will already be rendered on the chart,
// do nothing since that would make the chart weird. If the user really wants a weird chart // do nothing since that would make the chart weird. If the user really wants a weird chart
// axis, they can manually override it // axis, they can manually override it
if (this.options.ticks.beginAtZero) { if (this.options.ticks.beginAtZero) {
var minSign = helpers.sign(this.min); var minSign = helpers.sign(this.min);
var maxSign = helpers.sign(this.max); var maxSign = helpers.sign(this.max);
if (minSign < 0 && maxSign < 0) { if (minSign < 0 && maxSign < 0) {
// move the top up to 0 // move the top up to 0
this.max = 0; this.max = 0;
} else if (minSign > 0 && maxSign > 0) { } else if (minSign > 0 && maxSign > 0) {
// move the botttom down to 0 // move the botttom down to 0
this.min = 0; this.min = 0;
} }
} }
if (this.options.ticks.min !== undefined) { if (this.options.ticks.min !== undefined) {
this.min = this.options.ticks.min; this.min = this.options.ticks.min;
} else if (this.options.ticks.suggestedMin !== undefined) { } else if (this.options.ticks.suggestedMin !== undefined) {
this.min = Math.min(this.min, this.options.ticks.suggestedMin); this.min = Math.min(this.min, this.options.ticks.suggestedMin);
} }
if (this.options.ticks.max !== undefined) { if (this.options.ticks.max !== undefined) {
this.max = this.options.ticks.max; this.max = this.options.ticks.max;
} else if (this.options.ticks.suggestedMax !== undefined) { } else if (this.options.ticks.suggestedMax !== undefined) {
this.max = Math.max(this.max, this.options.ticks.suggestedMax); this.max = Math.max(this.max, this.options.ticks.suggestedMax);
} }
if (this.min === this.max) { if (this.min === this.max) {
this.min--; this.min--;
this.max++; this.max++;
} }
}, },
buildTicks: function() { buildTicks: function() {
// Then calulate the ticks // Then calulate the ticks
this.ticks = []; this.ticks = [];
// Figure out what the max number of ticks we can support it is based on the size of // Figure out what the max number of ticks we can support it is based on the size of
// the axis area. For now, we say that the minimum tick spacing in pixels must be 50 // the axis area. For now, we say that the minimum tick spacing in pixels must be 50
// We also limit the maximum number of ticks to 11 which gives a nice 10 squares on // We also limit the maximum number of ticks to 11 which gives a nice 10 squares on
// the graph // the graph
var maxTicks; var maxTicks;
if (this.isHorizontal()) { if (this.isHorizontal()) {
maxTicks = Math.min(this.options.ticks.maxTicksLimit ? this.options.ticks.maxTicksLimit : 11, maxTicks = Math.min(this.options.ticks.maxTicksLimit ? this.options.ticks.maxTicksLimit : 11,
Math.ceil(this.width / 50)); Math.ceil(this.width / 50));
} else { } else {
// The factor of 2 used to scale the font size has been experimentally determined. // The factor of 2 used to scale the font size has been experimentally determined.
maxTicks = Math.min(this.options.ticks.maxTicksLimit ? this.options.ticks.maxTicksLimit : 11, maxTicks = Math.min(this.options.ticks.maxTicksLimit ? this.options.ticks.maxTicksLimit : 11,
Math.ceil(this.height / (2 * this.options.ticks.fontSize))); Math.ceil(this.height / (2 * this.options.ticks.fontSize)));
} }
// Make sure we always have at least 2 ticks // Make sure we always have at least 2 ticks
maxTicks = Math.max(2, maxTicks); maxTicks = Math.max(2, maxTicks);
// To get a "nice" value for the tick spacing, we will use the appropriately named // To get a "nice" value for the tick spacing, we will use the appropriately named
// "nice number" algorithm. See http://stackoverflow.com/questions/8506881/nice-label-algorithm-for-charts-with-minimum-ticks // "nice number" algorithm. See http://stackoverflow.com/questions/8506881/nice-label-algorithm-for-charts-with-minimum-ticks
// for details. // for details.
var spacing; var spacing;
var fixedStepSizeSet = this.options.ticks.fixedStepSize && this.options.ticks.fixedStepSize > 0; var fixedStepSizeSet = this.options.ticks.fixedStepSize && this.options.ticks.fixedStepSize > 0;
if (fixedStepSizeSet) { if (fixedStepSizeSet) {
spacing = this.options.ticks.fixedStepSize; spacing = this.options.ticks.fixedStepSize;
} else { } else {
var niceRange = helpers.niceNum(this.max - this.min, false); var niceRange = helpers.niceNum(this.max - this.min, false);
spacing = helpers.niceNum(niceRange / (maxTicks - 1), true); spacing = helpers.niceNum(niceRange / (maxTicks - 1), true);
} }
var niceMin = Math.floor(this.min / spacing) * spacing; var niceMin = Math.floor(this.min / spacing) * spacing;
var niceMax = Math.ceil(this.max / spacing) * spacing; var niceMax = Math.ceil(this.max / spacing) * spacing;
var numSpaces = (niceMax - niceMin) / spacing; var numSpaces = (niceMax - niceMin) / spacing;
// If very close to our rounded value, use it. // If very close to our rounded value, use it.
if (helpers.almostEquals(numSpaces, Math.round(numSpaces), spacing / 1000)) { if (helpers.almostEquals(numSpaces, Math.round(numSpaces), spacing / 1000)) {
numSpaces = Math.round(numSpaces); numSpaces = Math.round(numSpaces);
} else { } else {
numSpaces = Math.ceil(numSpaces); numSpaces = Math.ceil(numSpaces);
} }
// Put the values into the ticks array // Put the values into the ticks array
this.ticks.push(this.options.ticks.min !== undefined ? this.options.ticks.min : niceMin); this.ticks.push(this.options.ticks.min !== undefined ? this.options.ticks.min : niceMin);
for (var j = 1; j < numSpaces; ++j) { for (var j = 1; j < numSpaces; ++j) {
this.ticks.push(niceMin + (j * spacing)); this.ticks.push(niceMin + (j * spacing));
} }
this.ticks.push(this.options.ticks.max !== undefined ? this.options.ticks.max : niceMax); this.ticks.push(this.options.ticks.max !== undefined ? this.options.ticks.max : niceMax);
if (this.options.position == "left" || this.options.position == "right") { if (this.options.position === "left" || this.options.position === "right") {
// We are in a vertical orientation. The top value is the highest. So reverse the array // We are in a vertical orientation. The top value is the highest. So reverse the array
this.ticks.reverse(); this.ticks.reverse();
} }
// At this point, we need to update our max and min given the tick values since we have expanded the // At this point, we need to update our max and min given the tick values since we have expanded the
// range of the scale // range of the scale
this.max = helpers.max(this.ticks); this.max = helpers.max(this.ticks);
this.min = helpers.min(this.ticks); this.min = helpers.min(this.ticks);
if (this.options.ticks.reverse) { if (this.options.ticks.reverse) {
this.ticks.reverse(); this.ticks.reverse();
this.start = this.max; this.start = this.max;
this.end = this.min; this.end = this.min;
} else { } else {
this.start = this.min; this.start = this.min;
this.end = this.max; this.end = this.max;
} }
this.ticksAsNumbers = this.ticks.slice(); // do after we potentially reverse the ticks this.ticksAsNumbers = this.ticks.slice(); // do after we potentially reverse the ticks
this.zeroLineIndex = this.ticks.indexOf(0); this.zeroLineIndex = this.ticks.indexOf(0);
}, },
getLabelForIndex: function(index, datasetIndex) { getLabelForIndex: function(index, datasetIndex) {
return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]); return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]);
}, },
// Utils // Utils
getPixelForValue: function(value, index, datasetIndex, includeOffset) { getPixelForValue: function(value, index, datasetIndex, includeOffset) {
// This must be called after fit has been run so that // This must be called after fit has been run so that
// this.left, this.top, this.right, and this.bottom have been defined // this.left, this.top, this.right, and this.bottom have been defined
var rightValue = +this.getRightValue(value); var rightValue = +this.getRightValue(value);
var pixel; var pixel;
var range = this.end - this.start; var range = this.end - this.start;
if (this.isHorizontal()) { if (this.isHorizontal()) {
var innerWidth = this.width - (this.paddingLeft + this.paddingRight); var innerWidth = this.width - (this.paddingLeft + this.paddingRight);
pixel = this.left + (innerWidth / range * (rightValue - this.start)); pixel = this.left + (innerWidth / range * (rightValue - this.start));
return Math.round(pixel + this.paddingLeft); return Math.round(pixel + this.paddingLeft);
} else { } else {
var innerHeight = this.height - (this.paddingTop + this.paddingBottom); var innerHeight = this.height - (this.paddingTop + this.paddingBottom);
pixel = (this.bottom - this.paddingBottom) - (innerHeight / range * (rightValue - this.start)); pixel = (this.bottom - this.paddingBottom) - (innerHeight / range * (rightValue - this.start));
return Math.round(pixel); return Math.round(pixel);
} }
}, },
getPixelForTick: function(index, includeOffset) { getPixelForTick: function(index, includeOffset) {
return this.getPixelForValue(this.ticksAsNumbers[index], null, null, includeOffset); return this.getPixelForValue(this.ticksAsNumbers[index], null, null, includeOffset);
}, }
}); });
Chart.scaleService.registerScaleType("linear", LinearScale, defaultConfig); Chart.scaleService.registerScaleType("linear", LinearScale, defaultConfig);
}; };

View File

@ -2,191 +2,191 @@
module.exports = function(Chart) { module.exports = function(Chart) {
var helpers = Chart.helpers; var helpers = Chart.helpers;
var defaultConfig = { var defaultConfig = {
position: "left", position: "left",
// label settings // label settings
ticks: { ticks: {
callback: function(value, index, arr) { callback: function(value, index, arr) {
var remain = value / (Math.pow(10, Math.floor(Chart.helpers.log10(value)))); var remain = value / (Math.pow(10, Math.floor(Chart.helpers.log10(value))));
if (remain === 1 || remain === 2 || remain === 5 || index === 0 || index === arr.length - 1) { if (remain === 1 || remain === 2 || remain === 5 || index === 0 || index === arr.length - 1) {
return value.toExponential(); return value.toExponential();
} else { } else {
return ''; return '';
} }
} }
} }
}; };
var LogarithmicScale = Chart.Scale.extend({ var LogarithmicScale = Chart.Scale.extend({
determineDataLimits: function() { determineDataLimits: function() {
// Calculate Range // Calculate Range
this.min = null; this.min = null;
this.max = null; this.max = null;
if (this.options.stacked) { if (this.options.stacked) {
var valuesPerType = {}; var valuesPerType = {};
helpers.each(this.chart.data.datasets, function(dataset) { helpers.each(this.chart.data.datasets, function(dataset) {
if (helpers.isDatasetVisible(dataset) && (this.isHorizontal() ? dataset.xAxisID === this.id : dataset.yAxisID === this.id)) { if (helpers.isDatasetVisible(dataset) && (this.isHorizontal() ? dataset.xAxisID === this.id : dataset.yAxisID === this.id)) {
if (valuesPerType[dataset.type] === undefined) { if (valuesPerType[dataset.type] === undefined) {
valuesPerType[dataset.type] = []; valuesPerType[dataset.type] = [];
} }
helpers.each(dataset.data, function(rawValue, index) { helpers.each(dataset.data, function(rawValue, index) {
var values = valuesPerType[dataset.type]; var values = valuesPerType[dataset.type];
var value = +this.getRightValue(rawValue); var value = +this.getRightValue(rawValue);
if (isNaN(value)) { if (isNaN(value)) {
return; return;
} }
values[index] = values[index] || 0; values[index] = values[index] || 0;
if (this.options.relativePoints) { if (this.options.relativePoints) {
values[index] = 100; values[index] = 100;
} else { } else {
// Don't need to split positive and negative since the log scale can't handle a 0 crossing // Don't need to split positive and negative since the log scale can't handle a 0 crossing
values[index] += value; values[index] += value;
} }
}, this); }, this);
} }
}, this); }, this);
helpers.each(valuesPerType, function(valuesForType) { helpers.each(valuesPerType, function(valuesForType) {
var minVal = helpers.min(valuesForType); var minVal = helpers.min(valuesForType);
var maxVal = helpers.max(valuesForType); var maxVal = helpers.max(valuesForType);
this.min = this.min === null ? minVal : Math.min(this.min, minVal); this.min = this.min === null ? minVal : Math.min(this.min, minVal);
this.max = this.max === null ? maxVal : Math.max(this.max, maxVal); this.max = this.max === null ? maxVal : Math.max(this.max, maxVal);
}, this); }, this);
} else { } else {
helpers.each(this.chart.data.datasets, function(dataset) { helpers.each(this.chart.data.datasets, function(dataset) {
if (helpers.isDatasetVisible(dataset) && (this.isHorizontal() ? dataset.xAxisID === this.id : dataset.yAxisID === this.id)) { if (helpers.isDatasetVisible(dataset) && (this.isHorizontal() ? dataset.xAxisID === this.id : dataset.yAxisID === this.id)) {
helpers.each(dataset.data, function(rawValue, index) { helpers.each(dataset.data, function(rawValue, index) {
var value = +this.getRightValue(rawValue); var value = +this.getRightValue(rawValue);
if (isNaN(value)) { if (isNaN(value)) {
return; return;
} }
if (this.min === null) { if (this.min === null) {
this.min = value; this.min = value;
} else if (value < this.min) { } else if (value < this.min) {
this.min = value; this.min = value;
} }
if (this.max === null) { if (this.max === null) {
this.max = value; this.max = value;
} else if (value > this.max) { } else if (value > this.max) {
this.max = value; this.max = value;
} }
}, this); }, this);
} }
}, this); }, this);
} }
this.min = this.options.ticks.min !== undefined ? this.options.ticks.min : this.min; this.min = this.options.ticks.min !== undefined ? this.options.ticks.min : this.min;
this.max = this.options.ticks.max !== undefined ? this.options.ticks.max : this.max; this.max = this.options.ticks.max !== undefined ? this.options.ticks.max : this.max;
if (this.min === this.max) { if (this.min === this.max) {
if (this.min !== 0 && this.min !== null) { if (this.min !== 0 && this.min !== null) {
this.min = Math.pow(10, Math.floor(helpers.log10(this.min)) - 1); this.min = Math.pow(10, Math.floor(helpers.log10(this.min)) - 1);
this.max = Math.pow(10, Math.floor(helpers.log10(this.max)) + 1); this.max = Math.pow(10, Math.floor(helpers.log10(this.max)) + 1);
} else { } else {
this.min = 1; this.min = 1;
this.max = 10; this.max = 10;
} }
} }
}, },
buildTicks: function() { buildTicks: function() {
// Reset the ticks array. Later on, we will draw a grid line at these positions // Reset the ticks array. Later on, we will draw a grid line at these positions
// The array simply contains the numerical value of the spots where ticks will be // The array simply contains the numerical value of the spots where ticks will be
this.tickValues = []; this.tickValues = [];
// Figure out what the max number of ticks we can support it is based on the size of // Figure out what the max number of ticks we can support it is based on the size of
// the axis area. For now, we say that the minimum tick spacing in pixels must be 50 // the axis area. For now, we say that the minimum tick spacing in pixels must be 50
// We also limit the maximum number of ticks to 11 which gives a nice 10 squares on // We also limit the maximum number of ticks to 11 which gives a nice 10 squares on
// the graph // the graph
var tickVal = this.options.ticks.min !== undefined ? this.options.ticks.min : Math.pow(10, Math.floor(helpers.log10(this.min))); var tickVal = this.options.ticks.min !== undefined ? this.options.ticks.min : Math.pow(10, Math.floor(helpers.log10(this.min)));
while (tickVal < this.max) { while (tickVal < this.max) {
this.tickValues.push(tickVal); this.tickValues.push(tickVal);
var exp = Math.floor(helpers.log10(tickVal)); var exp = Math.floor(helpers.log10(tickVal));
var significand = Math.floor(tickVal / Math.pow(10, exp)) + 1; var significand = Math.floor(tickVal / Math.pow(10, exp)) + 1;
if (significand === 10) { if (significand === 10) {
significand = 1; significand = 1;
++exp; ++exp;
} }
tickVal = significand * Math.pow(10, exp); tickVal = significand * Math.pow(10, exp);
} }
var lastTick = this.options.ticks.max !== undefined ? this.options.ticks.max : tickVal; var lastTick = this.options.ticks.max !== undefined ? this.options.ticks.max : tickVal;
this.tickValues.push(lastTick); this.tickValues.push(lastTick);
if (this.options.position == "left" || this.options.position == "right") { if (this.options.position === "left" || this.options.position === "right") {
// We are in a vertical orientation. The top value is the highest. So reverse the array // We are in a vertical orientation. The top value is the highest. So reverse the array
this.tickValues.reverse(); this.tickValues.reverse();
} }
// At this point, we need to update our max and min given the tick values since we have expanded the // At this point, we need to update our max and min given the tick values since we have expanded the
// range of the scale // range of the scale
this.max = helpers.max(this.tickValues); this.max = helpers.max(this.tickValues);
this.min = helpers.min(this.tickValues); this.min = helpers.min(this.tickValues);
if (this.options.ticks.reverse) { if (this.options.ticks.reverse) {
this.tickValues.reverse(); this.tickValues.reverse();
this.start = this.max; this.start = this.max;
this.end = this.min; this.end = this.min;
} else { } else {
this.start = this.min; this.start = this.min;
this.end = this.max; this.end = this.max;
} }
this.ticks = this.tickValues.slice(); this.ticks = this.tickValues.slice();
}, },
// Get the correct tooltip label // Get the correct tooltip label
getLabelForIndex: function(index, datasetIndex) { getLabelForIndex: function(index, datasetIndex) {
return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]); return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]);
}, },
getPixelForTick: function(index, includeOffset) { getPixelForTick: function(index, includeOffset) {
return this.getPixelForValue(this.tickValues[index], null, null, includeOffset); return this.getPixelForValue(this.tickValues[index], null, null, includeOffset);
}, },
getPixelForValue: function(value, index, datasetIndex, includeOffset) { getPixelForValue: function(value, index, datasetIndex, includeOffset) {
var pixel; var pixel;
var newVal = +this.getRightValue(value); var newVal = +this.getRightValue(value);
var range = helpers.log10(this.end) - helpers.log10(this.start); var range = helpers.log10(this.end) - helpers.log10(this.start);
if (this.isHorizontal()) { if (this.isHorizontal()) {
if (newVal === 0) { if (newVal === 0) {
pixel = this.left + this.paddingLeft; pixel = this.left + this.paddingLeft;
} else { } else {
var innerWidth = this.width - (this.paddingLeft + this.paddingRight); var innerWidth = this.width - (this.paddingLeft + this.paddingRight);
pixel = this.left + (innerWidth / range * (helpers.log10(newVal) - helpers.log10(this.start))); pixel = this.left + (innerWidth / range * (helpers.log10(newVal) - helpers.log10(this.start)));
pixel += this.paddingLeft; pixel += this.paddingLeft;
} }
} else { } else {
// Bottom - top since pixels increase downard on a screen // Bottom - top since pixels increase downard on a screen
if (newVal === 0) { if (newVal === 0) {
pixel = this.top + this.paddingTop; pixel = this.top + this.paddingTop;
} else { } else {
var innerHeight = this.height - (this.paddingTop + this.paddingBottom); var innerHeight = this.height - (this.paddingTop + this.paddingBottom);
pixel = (this.bottom - this.paddingBottom) - (innerHeight / range * (helpers.log10(newVal) - helpers.log10(this.start))); pixel = (this.bottom - this.paddingBottom) - (innerHeight / range * (helpers.log10(newVal) - helpers.log10(this.start)));
} }
} }
return pixel; return pixel;
}, }
}); });
Chart.scaleService.registerScaleType("logarithmic", LogarithmicScale, defaultConfig); Chart.scaleService.registerScaleType("logarithmic", LogarithmicScale, defaultConfig);
}; };

View File

@ -2,430 +2,430 @@
module.exports = function(Chart) { module.exports = function(Chart) {
var helpers = Chart.helpers; var helpers = Chart.helpers;
var defaultConfig = { var defaultConfig = {
display: true, display: true,
//Boolean - Whether to animate scaling the chart from the centre //Boolean - Whether to animate scaling the chart from the centre
animate: true, animate: true,
lineArc: false, lineArc: false,
position: "chartArea", position: "chartArea",
angleLines: { angleLines: {
display: true, display: true,
color: "rgba(0, 0, 0, 0.1)", color: "rgba(0, 0, 0, 0.1)",
lineWidth: 1 lineWidth: 1
}, },
// label settings // label settings
ticks: { ticks: {
//Boolean - Show a backdrop to the scale label //Boolean - Show a backdrop to the scale label
showLabelBackdrop: true, showLabelBackdrop: true,
//String - The colour of the label backdrop //String - The colour of the label backdrop
backdropColor: "rgba(255,255,255,0.75)", backdropColor: "rgba(255,255,255,0.75)",
//Number - The backdrop padding above & below the label in pixels //Number - The backdrop padding above & below the label in pixels
backdropPaddingY: 2, backdropPaddingY: 2,
//Number - The backdrop padding to the side of the label in pixels //Number - The backdrop padding to the side of the label in pixels
backdropPaddingX: 2, backdropPaddingX: 2
}, },
pointLabels: { pointLabels: {
//String - Point label font declaration //String - Point label font declaration
fontFamily: Chart.defaults.global.defaultFontFamily, fontFamily: Chart.defaults.global.defaultFontFamily,
//String - Point label font weight //String - Point label font weight
fontStyle: Chart.defaults.global.defaultFontStyle, fontStyle: Chart.defaults.global.defaultFontStyle,
//Number - Point label font size in pixels //Number - Point label font size in pixels
fontSize: 10, fontSize: 10,
//String - Point label font colour //String - Point label font colour
fontColor: Chart.defaults.global.defaultFontColor, fontColor: Chart.defaults.global.defaultFontColor,
//Function - Used to convert point labels //Function - Used to convert point labels
callback: function(label) { callback: function(label) {
return label; return label;
} }
}, }
}; };
var LinearRadialScale = Chart.Scale.extend({ var LinearRadialScale = Chart.Scale.extend({
getValueCount: function() { getValueCount: function() {
return this.chart.data.labels.length; return this.chart.data.labels.length;
}, },
setDimensions: function() { setDimensions: function() {
// Set the unconstrained dimension before label rotation // Set the unconstrained dimension before label rotation
this.width = this.maxWidth; this.width = this.maxWidth;
this.height = this.maxHeight; this.height = this.maxHeight;
this.xCenter = Math.round(this.width / 2); this.xCenter = Math.round(this.width / 2);
this.yCenter = Math.round(this.height / 2); this.yCenter = Math.round(this.height / 2);
var minSize = helpers.min([this.height, this.width]); var minSize = helpers.min([this.height, this.width]);
this.drawingArea = (this.options.display) ? (minSize / 2) - (this.options.ticks.fontSize / 2 + this.options.ticks.backdropPaddingY) : (minSize / 2); this.drawingArea = (this.options.display) ? (minSize / 2) - (this.options.ticks.fontSize / 2 + this.options.ticks.backdropPaddingY) : (minSize / 2);
}, },
determineDataLimits: function() { determineDataLimits: function() {
this.min = null; this.min = null;
this.max = null; this.max = null;
helpers.each(this.chart.data.datasets, function(dataset) { helpers.each(this.chart.data.datasets, function(dataset) {
if (helpers.isDatasetVisible(dataset)) { if (helpers.isDatasetVisible(dataset)) {
helpers.each(dataset.data, function(rawValue, index) { helpers.each(dataset.data, function(rawValue, index) {
var value = +this.getRightValue(rawValue); var value = +this.getRightValue(rawValue);
if (isNaN(value)) { if (isNaN(value)) {
return; return;
} }
if (this.min === null) { if (this.min === null) {
this.min = value; this.min = value;
} else if (value < this.min) { } else if (value < this.min) {
this.min = value; this.min = value;
} }
if (this.max === null) { if (this.max === null) {
this.max = value; this.max = value;
} else if (value > this.max) { } else if (value > this.max) {
this.max = value; this.max = value;
} }
}, this); }, this);
} }
}, this); }, this);
// If we are forcing it to begin at 0, but 0 will already be rendered on the chart, // If we are forcing it to begin at 0, but 0 will already be rendered on the chart,
// do nothing since that would make the chart weird. If the user really wants a weird chart // do nothing since that would make the chart weird. If the user really wants a weird chart
// axis, they can manually override it // axis, they can manually override it
if (this.options.ticks.beginAtZero) { if (this.options.ticks.beginAtZero) {
var minSign = helpers.sign(this.min); var minSign = helpers.sign(this.min);
var maxSign = helpers.sign(this.max); var maxSign = helpers.sign(this.max);
if (minSign < 0 && maxSign < 0) { if (minSign < 0 && maxSign < 0) {
// move the top up to 0 // move the top up to 0
this.max = 0; this.max = 0;
} else if (minSign > 0 && maxSign > 0) { } else if (minSign > 0 && maxSign > 0) {
// move the botttom down to 0 // move the botttom down to 0
this.min = 0; this.min = 0;
} }
} }
if (this.options.ticks.min !== undefined) { if (this.options.ticks.min !== undefined) {
this.min = this.options.ticks.min; this.min = this.options.ticks.min;
} else if (this.options.ticks.suggestedMin !== undefined) { } else if (this.options.ticks.suggestedMin !== undefined) {
this.min = Math.min(this.min, this.options.ticks.suggestedMin); this.min = Math.min(this.min, this.options.ticks.suggestedMin);
} }
if (this.options.ticks.max !== undefined) { if (this.options.ticks.max !== undefined) {
this.max = this.options.ticks.max; this.max = this.options.ticks.max;
} else if (this.options.ticks.suggestedMax !== undefined) { } else if (this.options.ticks.suggestedMax !== undefined) {
this.max = Math.max(this.max, this.options.ticks.suggestedMax); this.max = Math.max(this.max, this.options.ticks.suggestedMax);
} }
if (this.min === this.max) { if (this.min === this.max) {
this.min--; this.min--;
this.max++; this.max++;
} }
}, },
buildTicks: function() { buildTicks: function() {
this.ticks = []; this.ticks = [];
// Figure out what the max number of ticks we can support it is based on the size of // Figure out what the max number of ticks we can support it is based on the size of
// the axis area. For now, we say that the minimum tick spacing in pixels must be 50 // the axis area. For now, we say that the minimum tick spacing in pixels must be 50
// We also limit the maximum number of ticks to 11 which gives a nice 10 squares on // We also limit the maximum number of ticks to 11 which gives a nice 10 squares on
// the graph // the graph
var maxTicks = Math.min(this.options.ticks.maxTicksLimit ? this.options.ticks.maxTicksLimit : 11, var maxTicks = Math.min(this.options.ticks.maxTicksLimit ? this.options.ticks.maxTicksLimit : 11,
Math.ceil(this.drawingArea / (1.5 * this.options.ticks.fontSize))); Math.ceil(this.drawingArea / (1.5 * this.options.ticks.fontSize)));
maxTicks = Math.max(2, maxTicks); // Make sure we always have at least 2 ticks maxTicks = Math.max(2, maxTicks); // Make sure we always have at least 2 ticks
// To get a "nice" value for the tick spacing, we will use the appropriately named // To get a "nice" value for the tick spacing, we will use the appropriately named
// "nice number" algorithm. See http://stackoverflow.com/questions/8506881/nice-label-algorithm-for-charts-with-minimum-ticks // "nice number" algorithm. See http://stackoverflow.com/questions/8506881/nice-label-algorithm-for-charts-with-minimum-ticks
// for details. // for details.
var niceRange = helpers.niceNum(this.max - this.min, false); var niceRange = helpers.niceNum(this.max - this.min, false);
var spacing = helpers.niceNum(niceRange / (maxTicks - 1), true); var spacing = helpers.niceNum(niceRange / (maxTicks - 1), true);
var niceMin = Math.floor(this.min / spacing) * spacing; var niceMin = Math.floor(this.min / spacing) * spacing;
var niceMax = Math.ceil(this.max / spacing) * spacing; var niceMax = Math.ceil(this.max / spacing) * spacing;
var numSpaces = Math.ceil((niceMax - niceMin) / spacing); var numSpaces = Math.ceil((niceMax - niceMin) / spacing);
// Put the values into the ticks array // Put the values into the ticks array
this.ticks.push(this.options.ticks.min !== undefined ? this.options.ticks.min : niceMin); this.ticks.push(this.options.ticks.min !== undefined ? this.options.ticks.min : niceMin);
for (var j = 1; j < numSpaces; ++j) { for (var j = 1; j < numSpaces; ++j) {
this.ticks.push(niceMin + (j * spacing)); this.ticks.push(niceMin + (j * spacing));
} }
this.ticks.push(this.options.ticks.max !== undefined ? this.options.ticks.max : niceMax); this.ticks.push(this.options.ticks.max !== undefined ? this.options.ticks.max : niceMax);
// At this point, we need to update our max and min given the tick values since we have expanded the // At this point, we need to update our max and min given the tick values since we have expanded the
// range of the scale // range of the scale
this.max = helpers.max(this.ticks); this.max = helpers.max(this.ticks);
this.min = helpers.min(this.ticks); this.min = helpers.min(this.ticks);
if (this.options.ticks.reverse) { if (this.options.ticks.reverse) {
this.ticks.reverse(); this.ticks.reverse();
this.start = this.max; this.start = this.max;
this.end = this.min; this.end = this.min;
} else { } else {
this.start = this.min; this.start = this.min;
this.end = this.max; this.end = this.max;
} }
this.zeroLineIndex = this.ticks.indexOf(0); this.zeroLineIndex = this.ticks.indexOf(0);
}, },
convertTicksToLabels: function() { convertTicksToLabels: function() {
Chart.Scale.prototype.convertTicksToLabels.call(this); Chart.Scale.prototype.convertTicksToLabels.call(this);
// Point labels // Point labels
this.pointLabels = this.chart.data.labels.map(this.options.pointLabels.callback, this); this.pointLabels = this.chart.data.labels.map(this.options.pointLabels.callback, this);
}, },
getLabelForIndex: function(index, datasetIndex) { getLabelForIndex: function(index, datasetIndex) {
return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]); return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]);
}, },
fit: function() { fit: function() {
/* /*
* Right, this is really confusing and there is a lot of maths going on here * Right, this is really confusing and there is a lot of maths going on here
* The gist of the problem is here: https://gist.github.com/nnnick/696cc9c55f4b0beb8fe9 * The gist of the problem is here: https://gist.github.com/nnnick/696cc9c55f4b0beb8fe9
* *
* Reaction: https://dl.dropboxusercontent.com/u/34601363/toomuchscience.gif * Reaction: https://dl.dropboxusercontent.com/u/34601363/toomuchscience.gif
* *
* Solution: * Solution:
* *
* We assume the radius of the polygon is half the size of the canvas at first * We assume the radius of the polygon is half the size of the canvas at first
* at each index we check if the text overlaps. * at each index we check if the text overlaps.
* *
* Where it does, we store that angle and that index. * Where it does, we store that angle and that index.
* *
* After finding the largest index and angle we calculate how much we need to remove * After finding the largest index and angle we calculate how much we need to remove
* from the shape radius to move the point inwards by that x. * from the shape radius to move the point inwards by that x.
* *
* We average the left and right distances to get the maximum shape radius that can fit in the box * We average the left and right distances to get the maximum shape radius that can fit in the box
* along with labels. * along with labels.
* *
* Once we have that, we can find the centre point for the chart, by taking the x text protrusion * Once we have that, we can find the centre point for the chart, by taking the x text protrusion
* on each side, removing that from the size, halving it and adding the left x protrusion width. * on each side, removing that from the size, halving it and adding the left x protrusion width.
* *
* This will mean we have a shape fitted to the canvas, as large as it can be with the labels * This will mean we have a shape fitted to the canvas, as large as it can be with the labels
* and position it in the most space efficient manner * and position it in the most space efficient manner
* *
* https://dl.dropboxusercontent.com/u/34601363/yeahscience.gif * https://dl.dropboxusercontent.com/u/34601363/yeahscience.gif
*/ */
// Get maximum radius of the polygon. Either half the height (minus the text width) or half the width. // Get maximum radius of the polygon. Either half the height (minus the text width) or half the width.
// Use this to calculate the offset + change. - Make sure L/R protrusion is at least 0 to stop issues with centre points // Use this to calculate the offset + change. - Make sure L/R protrusion is at least 0 to stop issues with centre points
var largestPossibleRadius = helpers.min([(this.height / 2 - this.options.pointLabels.fontSize - 5), this.width / 2]), var largestPossibleRadius = helpers.min([(this.height / 2 - this.options.pointLabels.fontSize - 5), this.width / 2]),
pointPosition, pointPosition,
i, i,
textWidth, textWidth,
halfTextWidth, halfTextWidth,
furthestRight = this.width, furthestRight = this.width,
furthestRightIndex, furthestRightIndex,
furthestRightAngle, furthestRightAngle,
furthestLeft = 0, furthestLeft = 0,
furthestLeftIndex, furthestLeftIndex,
furthestLeftAngle, furthestLeftAngle,
xProtrusionLeft, xProtrusionLeft,
xProtrusionRight, xProtrusionRight,
radiusReductionRight, radiusReductionRight,
radiusReductionLeft, radiusReductionLeft,
maxWidthRadius; maxWidthRadius;
this.ctx.font = helpers.fontString(this.options.pointLabels.fontSize, this.options.pointLabels.fontStyle, this.options.pointLabels.fontFamily); this.ctx.font = helpers.fontString(this.options.pointLabels.fontSize, this.options.pointLabels.fontStyle, this.options.pointLabels.fontFamily);
for (i = 0; i < this.getValueCount(); i++) { for (i = 0; i < this.getValueCount(); i++) {
// 5px to space the text slightly out - similar to what we do in the draw function. // 5px to space the text slightly out - similar to what we do in the draw function.
pointPosition = this.getPointPosition(i, largestPossibleRadius); pointPosition = this.getPointPosition(i, largestPossibleRadius);
textWidth = this.ctx.measureText(this.pointLabels[i] ? this.pointLabels[i] : '').width + 5; textWidth = this.ctx.measureText(this.pointLabels[i] ? this.pointLabels[i] : '').width + 5;
if (i === 0 || i === this.getValueCount() / 2) { if (i === 0 || i === this.getValueCount() / 2) {
// If we're at index zero, or exactly the middle, we're at exactly the top/bottom // If we're at index zero, or exactly the middle, we're at exactly the top/bottom
// of the radar chart, so text will be aligned centrally, so we'll half it and compare // of the radar chart, so text will be aligned centrally, so we'll half it and compare
// w/left and right text sizes // w/left and right text sizes
halfTextWidth = textWidth / 2; halfTextWidth = textWidth / 2;
if (pointPosition.x + halfTextWidth > furthestRight) { if (pointPosition.x + halfTextWidth > furthestRight) {
furthestRight = pointPosition.x + halfTextWidth; furthestRight = pointPosition.x + halfTextWidth;
furthestRightIndex = i; furthestRightIndex = i;
} }
if (pointPosition.x - halfTextWidth < furthestLeft) { if (pointPosition.x - halfTextWidth < furthestLeft) {
furthestLeft = pointPosition.x - halfTextWidth; furthestLeft = pointPosition.x - halfTextWidth;
furthestLeftIndex = i; furthestLeftIndex = i;
} }
} else if (i < this.getValueCount() / 2) { } else if (i < this.getValueCount() / 2) {
// Less than half the values means we'll left align the text // Less than half the values means we'll left align the text
if (pointPosition.x + textWidth > furthestRight) { if (pointPosition.x + textWidth > furthestRight) {
furthestRight = pointPosition.x + textWidth; furthestRight = pointPosition.x + textWidth;
furthestRightIndex = i; furthestRightIndex = i;
} }
} else if (i > this.getValueCount() / 2) { } else if (i > this.getValueCount() / 2) {
// More than half the values means we'll right align the text // More than half the values means we'll right align the text
if (pointPosition.x - textWidth < furthestLeft) { if (pointPosition.x - textWidth < furthestLeft) {
furthestLeft = pointPosition.x - textWidth; furthestLeft = pointPosition.x - textWidth;
furthestLeftIndex = i; furthestLeftIndex = i;
} }
} }
} }
xProtrusionLeft = furthestLeft; xProtrusionLeft = furthestLeft;
xProtrusionRight = Math.ceil(furthestRight - this.width); xProtrusionRight = Math.ceil(furthestRight - this.width);
furthestRightAngle = this.getIndexAngle(furthestRightIndex); furthestRightAngle = this.getIndexAngle(furthestRightIndex);
furthestLeftAngle = this.getIndexAngle(furthestLeftIndex); furthestLeftAngle = this.getIndexAngle(furthestLeftIndex);
radiusReductionRight = xProtrusionRight / Math.sin(furthestRightAngle + Math.PI / 2); radiusReductionRight = xProtrusionRight / Math.sin(furthestRightAngle + Math.PI / 2);
radiusReductionLeft = xProtrusionLeft / Math.sin(furthestLeftAngle + Math.PI / 2); radiusReductionLeft = xProtrusionLeft / Math.sin(furthestLeftAngle + Math.PI / 2);
// Ensure we actually need to reduce the size of the chart // Ensure we actually need to reduce the size of the chart
radiusReductionRight = (helpers.isNumber(radiusReductionRight)) ? radiusReductionRight : 0; radiusReductionRight = (helpers.isNumber(radiusReductionRight)) ? radiusReductionRight : 0;
radiusReductionLeft = (helpers.isNumber(radiusReductionLeft)) ? radiusReductionLeft : 0; radiusReductionLeft = (helpers.isNumber(radiusReductionLeft)) ? radiusReductionLeft : 0;
this.drawingArea = Math.round(largestPossibleRadius - (radiusReductionLeft + radiusReductionRight) / 2); this.drawingArea = Math.round(largestPossibleRadius - (radiusReductionLeft + radiusReductionRight) / 2);
this.setCenterPoint(radiusReductionLeft, radiusReductionRight); this.setCenterPoint(radiusReductionLeft, radiusReductionRight);
}, },
setCenterPoint: function(leftMovement, rightMovement) { setCenterPoint: function(leftMovement, rightMovement) {
var maxRight = this.width - rightMovement - this.drawingArea, var maxRight = this.width - rightMovement - this.drawingArea,
maxLeft = leftMovement + this.drawingArea; maxLeft = leftMovement + this.drawingArea;
this.xCenter = Math.round(((maxLeft + maxRight) / 2) + this.left); this.xCenter = Math.round(((maxLeft + maxRight) / 2) + this.left);
// Always vertically in the centre as the text height doesn't change // Always vertically in the centre as the text height doesn't change
this.yCenter = Math.round((this.height / 2) + this.top); this.yCenter = Math.round((this.height / 2) + this.top);
}, },
getIndexAngle: function(index) { getIndexAngle: function(index) {
var angleMultiplier = (Math.PI * 2) / this.getValueCount(); var angleMultiplier = (Math.PI * 2) / this.getValueCount();
// Start from the top instead of right, so remove a quarter of the circle // Start from the top instead of right, so remove a quarter of the circle
return index * angleMultiplier - (Math.PI / 2); return index * angleMultiplier - (Math.PI / 2);
}, },
getDistanceFromCenterForValue: function(value) { getDistanceFromCenterForValue: function(value) {
if (value === null) { if (value === null) {
return 0; // null always in center return 0; // null always in center
} }
// Take into account half font size + the yPadding of the top value // Take into account half font size + the yPadding of the top value
var scalingFactor = this.drawingArea / (this.max - this.min); var scalingFactor = this.drawingArea / (this.max - this.min);
if (this.options.reverse) { if (this.options.reverse) {
return (this.max - value) * scalingFactor; return (this.max - value) * scalingFactor;
} else { } else {
return (value - this.min) * scalingFactor; return (value - this.min) * scalingFactor;
} }
}, },
getPointPosition: function(index, distanceFromCenter) { getPointPosition: function(index, distanceFromCenter) {
var thisAngle = this.getIndexAngle(index); var thisAngle = this.getIndexAngle(index);
return { return {
x: Math.round(Math.cos(thisAngle) * distanceFromCenter) + this.xCenter, x: Math.round(Math.cos(thisAngle) * distanceFromCenter) + this.xCenter,
y: Math.round(Math.sin(thisAngle) * distanceFromCenter) + this.yCenter y: Math.round(Math.sin(thisAngle) * distanceFromCenter) + this.yCenter
}; };
}, },
getPointPositionForValue: function(index, value) { getPointPositionForValue: function(index, value) {
return this.getPointPosition(index, this.getDistanceFromCenterForValue(value)); return this.getPointPosition(index, this.getDistanceFromCenterForValue(value));
}, },
draw: function() { draw: function() {
if (this.options.display) { if (this.options.display) {
var ctx = this.ctx; var ctx = this.ctx;
helpers.each(this.ticks, function(label, index) { helpers.each(this.ticks, function(label, index) {
// Don't draw a centre value (if it is minimum) // Don't draw a centre value (if it is minimum)
if (index > 0 || this.options.reverse) { if (index > 0 || this.options.reverse) {
var yCenterOffset = this.getDistanceFromCenterForValue(this.ticks[index]); var yCenterOffset = this.getDistanceFromCenterForValue(this.ticks[index]);
var yHeight = this.yCenter - yCenterOffset; var yHeight = this.yCenter - yCenterOffset;
// Draw circular lines around the scale // Draw circular lines around the scale
if (this.options.gridLines.display) { if (this.options.gridLines.display) {
ctx.strokeStyle = this.options.gridLines.color; ctx.strokeStyle = this.options.gridLines.color;
ctx.lineWidth = this.options.gridLines.lineWidth; ctx.lineWidth = this.options.gridLines.lineWidth;
if (this.options.lineArc) { if (this.options.lineArc) {
// Draw circular arcs between the points // Draw circular arcs between the points
ctx.beginPath(); ctx.beginPath();
ctx.arc(this.xCenter, this.yCenter, yCenterOffset, 0, Math.PI * 2); ctx.arc(this.xCenter, this.yCenter, yCenterOffset, 0, Math.PI * 2);
ctx.closePath(); ctx.closePath();
ctx.stroke(); ctx.stroke();
} else { } else {
// Draw straight lines connecting each index // Draw straight lines connecting each index
ctx.beginPath(); ctx.beginPath();
for (var i = 0; i < this.getValueCount(); i++) { for (var i = 0; i < this.getValueCount(); i++) {
var pointPosition = this.getPointPosition(i, this.getDistanceFromCenterForValue(this.ticks[index])); var pointPosition = this.getPointPosition(i, this.getDistanceFromCenterForValue(this.ticks[index]));
if (i === 0) { if (i === 0) {
ctx.moveTo(pointPosition.x, pointPosition.y); ctx.moveTo(pointPosition.x, pointPosition.y);
} else { } else {
ctx.lineTo(pointPosition.x, pointPosition.y); ctx.lineTo(pointPosition.x, pointPosition.y);
} }
} }
ctx.closePath(); ctx.closePath();
ctx.stroke(); ctx.stroke();
} }
} }
if (this.options.ticks.display) { if (this.options.ticks.display) {
ctx.font = helpers.fontString(this.options.ticks.fontSize, this.options.ticks.fontStyle, this.options.ticks.fontFamily); ctx.font = helpers.fontString(this.options.ticks.fontSize, this.options.ticks.fontStyle, this.options.ticks.fontFamily);
if (this.options.ticks.showLabelBackdrop) { if (this.options.ticks.showLabelBackdrop) {
var labelWidth = ctx.measureText(label).width; var labelWidth = ctx.measureText(label).width;
ctx.fillStyle = this.options.ticks.backdropColor; ctx.fillStyle = this.options.ticks.backdropColor;
ctx.fillRect( ctx.fillRect(
this.xCenter - labelWidth / 2 - this.options.ticks.backdropPaddingX, this.xCenter - labelWidth / 2 - this.options.ticks.backdropPaddingX,
yHeight - this.options.ticks.fontSize / 2 - this.options.ticks.backdropPaddingY, yHeight - this.options.ticks.fontSize / 2 - this.options.ticks.backdropPaddingY,
labelWidth + this.options.ticks.backdropPaddingX * 2, labelWidth + this.options.ticks.backdropPaddingX * 2,
this.options.ticks.fontSize + this.options.ticks.backdropPaddingY * 2 this.options.ticks.fontSize + this.options.ticks.backdropPaddingY * 2
); );
} }
ctx.textAlign = 'center'; ctx.textAlign = 'center';
ctx.textBaseline = "middle"; ctx.textBaseline = "middle";
ctx.fillStyle = this.options.ticks.fontColor; ctx.fillStyle = this.options.ticks.fontColor;
ctx.fillText(label, this.xCenter, yHeight); ctx.fillText(label, this.xCenter, yHeight);
} }
} }
}, this); }, this);
if (!this.options.lineArc) { if (!this.options.lineArc) {
ctx.lineWidth = this.options.angleLines.lineWidth; ctx.lineWidth = this.options.angleLines.lineWidth;
ctx.strokeStyle = this.options.angleLines.color; ctx.strokeStyle = this.options.angleLines.color;
for (var i = this.getValueCount() - 1; i >= 0; i--) { for (var i = this.getValueCount() - 1; i >= 0; i--) {
if (this.options.angleLines.display) { if (this.options.angleLines.display) {
var outerPosition = this.getPointPosition(i, this.getDistanceFromCenterForValue(this.options.reverse ? this.min : this.max)); var outerPosition = this.getPointPosition(i, this.getDistanceFromCenterForValue(this.options.reverse ? this.min : this.max));
ctx.beginPath(); ctx.beginPath();
ctx.moveTo(this.xCenter, this.yCenter); ctx.moveTo(this.xCenter, this.yCenter);
ctx.lineTo(outerPosition.x, outerPosition.y); ctx.lineTo(outerPosition.x, outerPosition.y);
ctx.stroke(); ctx.stroke();
ctx.closePath(); ctx.closePath();
} }
// Extra 3px out for some label spacing // Extra 3px out for some label spacing
var pointLabelPosition = this.getPointPosition(i, this.getDistanceFromCenterForValue(this.options.reverse ? this.min : this.max) + 5); var pointLabelPosition = this.getPointPosition(i, this.getDistanceFromCenterForValue(this.options.reverse ? this.min : this.max) + 5);
ctx.font = helpers.fontString(this.options.pointLabels.fontSize, this.options.pointLabels.fontStyle, this.options.pointLabels.fontFamily); ctx.font = helpers.fontString(this.options.pointLabels.fontSize, this.options.pointLabels.fontStyle, this.options.pointLabels.fontFamily);
ctx.fillStyle = this.options.pointLabels.fontColor; ctx.fillStyle = this.options.pointLabels.fontColor;
var labelsCount = this.pointLabels.length, var labelsCount = this.pointLabels.length,
halfLabelsCount = this.pointLabels.length / 2, halfLabelsCount = this.pointLabels.length / 2,
quarterLabelsCount = halfLabelsCount / 2, quarterLabelsCount = halfLabelsCount / 2,
upperHalf = (i < quarterLabelsCount || i > labelsCount - quarterLabelsCount), upperHalf = (i < quarterLabelsCount || i > labelsCount - quarterLabelsCount),
exactQuarter = (i === quarterLabelsCount || i === labelsCount - quarterLabelsCount); exactQuarter = (i === quarterLabelsCount || i === labelsCount - quarterLabelsCount);
if (i === 0) { if (i === 0) {
ctx.textAlign = 'center'; ctx.textAlign = 'center';
} else if (i === halfLabelsCount) { } else if (i === halfLabelsCount) {
ctx.textAlign = 'center'; ctx.textAlign = 'center';
} else if (i < halfLabelsCount) { } else if (i < halfLabelsCount) {
ctx.textAlign = 'left'; ctx.textAlign = 'left';
} else { } else {
ctx.textAlign = 'right'; ctx.textAlign = 'right';
} }
// Set the correct text baseline based on outer positioning // Set the correct text baseline based on outer positioning
if (exactQuarter) { if (exactQuarter) {
ctx.textBaseline = 'middle'; ctx.textBaseline = 'middle';
} else if (upperHalf) { } else if (upperHalf) {
ctx.textBaseline = 'bottom'; ctx.textBaseline = 'bottom';
} else { } else {
ctx.textBaseline = 'top'; ctx.textBaseline = 'top';
} }
ctx.fillText(this.pointLabels[i] ? this.pointLabels[i] : '', pointLabelPosition.x, pointLabelPosition.y); ctx.fillText(this.pointLabels[i] ? this.pointLabels[i] : '', pointLabelPosition.x, pointLabelPosition.y);
} }
} }
} }
} }
}); });
Chart.scaleService.registerScaleType("radialLinear", LinearRadialScale, defaultConfig); Chart.scaleService.registerScaleType("radialLinear", LinearRadialScale, defaultConfig);
}; };

View File

@ -6,302 +6,302 @@ moment = typeof(moment) === 'function' ? moment : window.moment;
module.exports = function(Chart) { module.exports = function(Chart) {
var helpers = Chart.helpers; var helpers = Chart.helpers;
if (!moment) { if (!moment) {
console.warn('Chart.js - Moment.js could not be found! You must include it before Chart.js to use the time scale. Download at http://momentjs.com/'); console.warn('Chart.js - Moment.js could not be found! You must include it before Chart.js to use the time scale. Download at http://momentjs.com/');
return; return;
} }
var time = { var time = {
units: [{ units: [{
name: 'millisecond', name: 'millisecond',
steps: [1, 2, 5, 10, 20, 50, 100, 250, 500] steps: [1, 2, 5, 10, 20, 50, 100, 250, 500]
}, { }, {
name: 'second', name: 'second',
steps: [1, 2, 5, 10, 30] steps: [1, 2, 5, 10, 30]
}, { }, {
name: 'minute', name: 'minute',
steps: [1, 2, 5, 10, 30] steps: [1, 2, 5, 10, 30]
}, { }, {
name: 'hour', name: 'hour',
steps: [1, 2, 3, 6, 12] steps: [1, 2, 3, 6, 12]
}, { }, {
name: 'day', name: 'day',
steps: [1, 2, 5] steps: [1, 2, 5]
}, { }, {
name: 'week', name: 'week',
maxStep: 4 maxStep: 4
}, { }, {
name: 'month', name: 'month',
maxStep: 3 maxStep: 3
}, { }, {
name: 'quarter', name: 'quarter',
maxStep: 4, maxStep: 4
}, { }, {
name: 'year', name: 'year',
maxStep: false maxStep: false
}, ], }]
}; };
var defaultConfig = { var defaultConfig = {
position: "bottom", position: "bottom",
time: { time: {
format: false, // false == date objects or use pattern string from http://momentjs.com/docs/#/parsing/string-format/ format: false, // false == date objects or use pattern string from http://momentjs.com/docs/#/parsing/string-format/
unit: false, // false == automatic or override with week, month, year, etc. unit: false, // false == automatic or override with week, month, year, etc.
round: false, // none, or override with week, month, year, etc. round: false, // none, or override with week, month, year, etc.
displayFormat: false, // DEPRECATED displayFormat: false, // DEPRECATED
// defaults to unit's corresponding unitFormat below or override using pattern string from http://momentjs.com/docs/#/displaying/format/ // defaults to unit's corresponding unitFormat below or override using pattern string from http://momentjs.com/docs/#/displaying/format/
displayFormats: { displayFormats: {
'millisecond': 'h:mm:ss.SSS a', // 11:20:01.123 AM, 'millisecond': 'h:mm:ss.SSS a', // 11:20:01.123 AM,
'second': 'h:mm:ss a', // 11:20:01 AM 'second': 'h:mm:ss a', // 11:20:01 AM
'minute': 'h:mm:ss a', // 11:20:01 AM 'minute': 'h:mm:ss a', // 11:20:01 AM
'hour': 'MMM D, hA', // Sept 4, 5PM 'hour': 'MMM D, hA', // Sept 4, 5PM
'day': 'll', // Sep 4 2015 'day': 'll', // Sep 4 2015
'week': 'll', // Week 46, or maybe "[W]WW - YYYY" ? 'week': 'll', // Week 46, or maybe "[W]WW - YYYY" ?
'month': 'MMM YYYY', // Sept 2015 'month': 'MMM YYYY', // Sept 2015
'quarter': '[Q]Q - YYYY', // Q3 'quarter': '[Q]Q - YYYY', // Q3
'year': 'YYYY', // 2015 'year': 'YYYY' // 2015
}, }
}, }
}; };
var TimeScale = Chart.Scale.extend({ var TimeScale = Chart.Scale.extend({
getLabelMoment: function(datasetIndex, index) { getLabelMoment: function(datasetIndex, index) {
return this.labelMoments[datasetIndex][index]; return this.labelMoments[datasetIndex][index];
}, },
determineDataLimits: function() { determineDataLimits: function() {
this.labelMoments = []; this.labelMoments = [];
// Only parse these once. If the dataset does not have data as x,y pairs, we will use // Only parse these once. If the dataset does not have data as x,y pairs, we will use
// these // these
var scaleLabelMoments = []; var scaleLabelMoments = [];
if (this.chart.data.labels && this.chart.data.labels.length > 0) { if (this.chart.data.labels && this.chart.data.labels.length > 0) {
helpers.each(this.chart.data.labels, function(label, index) { helpers.each(this.chart.data.labels, function(label, index) {
var labelMoment = this.parseTime(label); var labelMoment = this.parseTime(label);
if (this.options.time.round) { if (this.options.time.round) {
labelMoment.startOf(this.options.time.round); labelMoment.startOf(this.options.time.round);
} }
scaleLabelMoments.push(labelMoment); scaleLabelMoments.push(labelMoment);
}, this); }, this);
this.firstTick = moment.min.call(this, scaleLabelMoments); this.firstTick = moment.min.call(this, scaleLabelMoments);
this.lastTick = moment.max.call(this, scaleLabelMoments); this.lastTick = moment.max.call(this, scaleLabelMoments);
} else { } else {
this.firstTick = null; this.firstTick = null;
this.lastTick = null; this.lastTick = null;
} }
helpers.each(this.chart.data.datasets, function(dataset, datasetIndex) { helpers.each(this.chart.data.datasets, function(dataset, datasetIndex) {
var momentsForDataset = []; var momentsForDataset = [];
if (typeof dataset.data[0] === 'object') { if (typeof dataset.data[0] === 'object') {
helpers.each(dataset.data, function(value, index) { helpers.each(dataset.data, function(value, index) {
var labelMoment = this.parseTime(this.getRightValue(value)); var labelMoment = this.parseTime(this.getRightValue(value));
if (this.options.time.round) { if (this.options.time.round) {
labelMoment.startOf(this.options.time.round); labelMoment.startOf(this.options.time.round);
} }
momentsForDataset.push(labelMoment); momentsForDataset.push(labelMoment);
// May have gone outside the scale ranges, make sure we keep the first and last ticks updated // May have gone outside the scale ranges, make sure we keep the first and last ticks updated
this.firstTick = this.firstTick !== null ? moment.min(this.firstTick, labelMoment) : labelMoment; this.firstTick = this.firstTick !== null ? moment.min(this.firstTick, labelMoment) : labelMoment;
this.lastTick = this.lastTick !== null ? moment.max(this.lastTick, labelMoment) : labelMoment; this.lastTick = this.lastTick !== null ? moment.max(this.lastTick, labelMoment) : labelMoment;
}, this); }, this);
} else { } else {
// We have no labels. Use the ones from the scale // We have no labels. Use the ones from the scale
momentsForDataset = scaleLabelMoments; momentsForDataset = scaleLabelMoments;
} }
this.labelMoments.push(momentsForDataset); this.labelMoments.push(momentsForDataset);
}, this); }, this);
// Set these after we've done all the data // Set these after we've done all the data
if (this.options.time.min) { if (this.options.time.min) {
this.firstTick = this.parseTime(this.options.time.min); this.firstTick = this.parseTime(this.options.time.min);
} }
if (this.options.time.max) { if (this.options.time.max) {
this.lastTick = this.parseTime(this.options.time.max); this.lastTick = this.parseTime(this.options.time.max);
} }
// We will modify these, so clone for later // We will modify these, so clone for later
this.firstTick = (this.firstTick || moment()).clone(); this.firstTick = (this.firstTick || moment()).clone();
this.lastTick = (this.lastTick || moment()).clone(); this.lastTick = (this.lastTick || moment()).clone();
}, },
buildTicks: function(index) { buildTicks: function(index) {
this.ticks = []; this.ticks = [];
this.unitScale = 1; // How much we scale the unit by, ie 2 means 2x unit per step this.unitScale = 1; // How much we scale the unit by, ie 2 means 2x unit per step
// Set unit override if applicable // Set unit override if applicable
if (this.options.time.unit) { if (this.options.time.unit) {
this.tickUnit = this.options.time.unit || 'day'; this.tickUnit = this.options.time.unit || 'day';
this.displayFormat = this.options.time.displayFormats[this.tickUnit]; this.displayFormat = this.options.time.displayFormats[this.tickUnit];
this.tickRange = Math.ceil(this.lastTick.diff(this.firstTick, this.tickUnit, true)); this.tickRange = Math.ceil(this.lastTick.diff(this.firstTick, this.tickUnit, true));
} else { } else {
// Determine the smallest needed unit of the time // Determine the smallest needed unit of the time
var innerWidth = this.isHorizontal() ? this.width - (this.paddingLeft + this.paddingRight) : this.height - (this.paddingTop + this.paddingBottom); var innerWidth = this.isHorizontal() ? this.width - (this.paddingLeft + this.paddingRight) : this.height - (this.paddingTop + this.paddingBottom);
var labelCapacity = innerWidth / (this.options.ticks.fontSize + 10); var labelCapacity = innerWidth / (this.options.ticks.fontSize + 10);
var buffer = this.options.time.round ? 0 : 1; var buffer = this.options.time.round ? 0 : 1;
// Start as small as possible // Start as small as possible
this.tickUnit = 'millisecond'; this.tickUnit = 'millisecond';
this.tickRange = Math.ceil(this.lastTick.diff(this.firstTick, this.tickUnit, true) + buffer); this.tickRange = Math.ceil(this.lastTick.diff(this.firstTick, this.tickUnit, true) + buffer);
this.displayFormat = this.options.time.displayFormats[this.tickUnit]; this.displayFormat = this.options.time.displayFormats[this.tickUnit];
var unitDefinitionIndex = 0; var unitDefinitionIndex = 0;
var unitDefinition = time.units[unitDefinitionIndex]; var unitDefinition = time.units[unitDefinitionIndex];
// While we aren't ideal and we don't have units left // While we aren't ideal and we don't have units left
while (unitDefinitionIndex < time.units.length) { while (unitDefinitionIndex < time.units.length) {
// Can we scale this unit. If `false` we can scale infinitely // Can we scale this unit. If `false` we can scale infinitely
this.unitScale = 1; this.unitScale = 1;
if (helpers.isArray(unitDefinition.steps) && Math.ceil(this.tickRange / labelCapacity) < helpers.max(unitDefinition.steps)) { if (helpers.isArray(unitDefinition.steps) && Math.ceil(this.tickRange / labelCapacity) < helpers.max(unitDefinition.steps)) {
// Use one of the prefedined steps // Use one of the prefedined steps
for (var idx = 0; idx < unitDefinition.steps.length; ++idx) { for (var idx = 0; idx < unitDefinition.steps.length; ++idx) {
if (unitDefinition.steps[idx] > Math.ceil(this.tickRange / labelCapacity)) { if (unitDefinition.steps[idx] > Math.ceil(this.tickRange / labelCapacity)) {
this.unitScale = unitDefinition.steps[idx]; this.unitScale = unitDefinition.steps[idx];
break; break;
} }
} }
break; break;
} else if ((unitDefinition.maxStep === false) || (Math.ceil(this.tickRange / labelCapacity) < unitDefinition.maxStep)) { } else if ((unitDefinition.maxStep === false) || (Math.ceil(this.tickRange / labelCapacity) < unitDefinition.maxStep)) {
// We have a max step. Scale this unit // We have a max step. Scale this unit
this.unitScale = Math.ceil(this.tickRange / labelCapacity); this.unitScale = Math.ceil(this.tickRange / labelCapacity);
break; break;
} else { } else {
// Move to the next unit up // Move to the next unit up
++unitDefinitionIndex; ++unitDefinitionIndex;
unitDefinition = time.units[unitDefinitionIndex]; unitDefinition = time.units[unitDefinitionIndex];
this.tickUnit = unitDefinition.name; this.tickUnit = unitDefinition.name;
this.tickRange = Math.ceil(this.lastTick.diff(this.firstTick, this.tickUnit) + buffer); this.tickRange = Math.ceil(this.lastTick.diff(this.firstTick, this.tickUnit) + buffer);
this.displayFormat = this.options.time.displayFormats[unitDefinition.name]; this.displayFormat = this.options.time.displayFormats[unitDefinition.name];
} }
} }
} }
var roundedStart; var roundedStart;
// Only round the first tick if we have no hard minimum // Only round the first tick if we have no hard minimum
if (!this.options.time.min) { if (!this.options.time.min) {
this.firstTick.startOf(this.tickUnit); this.firstTick.startOf(this.tickUnit);
roundedStart = this.firstTick; roundedStart = this.firstTick;
} else { } else {
roundedStart = this.firstTick.clone().startOf(this.tickUnit); roundedStart = this.firstTick.clone().startOf(this.tickUnit);
} }
// Only round the last tick if we have no hard maximum // Only round the last tick if we have no hard maximum
if (!this.options.time.max) { if (!this.options.time.max) {
this.lastTick.endOf(this.tickUnit); this.lastTick.endOf(this.tickUnit);
} }
this.smallestLabelSeparation = this.width; this.smallestLabelSeparation = this.width;
helpers.each(this.chart.data.datasets, function(dataset, datasetIndex) { helpers.each(this.chart.data.datasets, function(dataset, datasetIndex) {
for (var i = 1; i < this.labelMoments[datasetIndex].length; i++) { for (var i = 1; i < this.labelMoments[datasetIndex].length; i++) {
this.smallestLabelSeparation = Math.min(this.smallestLabelSeparation, this.labelMoments[datasetIndex][i].diff(this.labelMoments[datasetIndex][i - 1], this.tickUnit, true)); this.smallestLabelSeparation = Math.min(this.smallestLabelSeparation, this.labelMoments[datasetIndex][i].diff(this.labelMoments[datasetIndex][i - 1], this.tickUnit, true));
} }
}, this); }, this);
// Tick displayFormat override // Tick displayFormat override
if (this.options.time.displayFormat) { if (this.options.time.displayFormat) {
this.displayFormat = this.options.time.displayFormat; this.displayFormat = this.options.time.displayFormat;
} }
// first tick. will have been rounded correctly if options.time.min is not specified // first tick. will have been rounded correctly if options.time.min is not specified
this.ticks.push(this.firstTick.clone()); this.ticks.push(this.firstTick.clone());
// For every unit in between the first and last moment, create a moment and add it to the ticks tick // For every unit in between the first and last moment, create a moment and add it to the ticks tick
for (var i = 1; i < this.tickRange; ++i) { for (var i = 1; i < this.tickRange; ++i) {
var newTick = roundedStart.clone().add(i, this.tickUnit); var newTick = roundedStart.clone().add(i, this.tickUnit);
// Are we greater than the max time // Are we greater than the max time
if (this.options.time.max && newTick.diff(this.lastTick, this.tickUnit, true) >= 0) { if (this.options.time.max && newTick.diff(this.lastTick, this.tickUnit, true) >= 0) {
break; break;
} }
if (i % this.unitScale === 0) { if (i % this.unitScale === 0) {
this.ticks.push(newTick); this.ticks.push(newTick);
} }
} }
// Always show the right tick // Always show the right tick
if (this.options.time.max) { if (this.options.time.max) {
this.ticks.push(this.lastTick.clone()); this.ticks.push(this.lastTick.clone());
} else if (this.ticks[this.ticks.length - 1].diff(this.lastTick, this.tickUnit, true) !== 0) { } else if (this.ticks[this.ticks.length - 1].diff(this.lastTick, this.tickUnit, true) !== 0) {
this.tickRange = Math.ceil(this.tickRange / this.unitScale) * this.unitScale; this.tickRange = Math.ceil(this.tickRange / this.unitScale) * this.unitScale;
this.ticks.push(this.firstTick.clone().add(this.tickRange, this.tickUnit)); this.ticks.push(this.firstTick.clone().add(this.tickRange, this.tickUnit));
this.lastTick = this.ticks[this.ticks.length - 1].clone(); this.lastTick = this.ticks[this.ticks.length - 1].clone();
} }
}, },
// Get tooltip label // Get tooltip label
getLabelForIndex: function(index, datasetIndex) { getLabelForIndex: function(index, datasetIndex) {
var label = this.chart.data.labels && index < this.chart.data.labels.length ? this.chart.data.labels[index] : ''; var label = this.chart.data.labels && index < this.chart.data.labels.length ? this.chart.data.labels[index] : '';
if (typeof this.chart.data.datasets[datasetIndex].data[0] === 'object') { if (typeof this.chart.data.datasets[datasetIndex].data[0] === 'object') {
label = this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]); label = this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]);
} }
// Format nicely // Format nicely
if (this.options.time.tooltipFormat) { if (this.options.time.tooltipFormat) {
label = this.parseTime(label).format(this.options.time.tooltipFormat); label = this.parseTime(label).format(this.options.time.tooltipFormat);
} }
return label; return label;
}, },
convertTicksToLabels: function() { convertTicksToLabels: function() {
this.ticks = this.ticks.map(function(tick, index, ticks) { this.ticks = this.ticks.map(function(tick, index, ticks) {
var formattedTick = tick.format(this.displayFormat); var formattedTick = tick.format(this.displayFormat);
if (this.options.ticks.userCallback) { if (this.options.ticks.userCallback) {
return this.options.ticks.userCallback(formattedTick, index, ticks); return this.options.ticks.userCallback(formattedTick, index, ticks);
} else { } else {
return formattedTick; return formattedTick;
} }
}, this); }, this);
}, },
getPixelForValue: function(value, index, datasetIndex, includeOffset) { getPixelForValue: function(value, index, datasetIndex, includeOffset) {
var labelMoment = this.getLabelMoment(datasetIndex, index); var labelMoment = this.getLabelMoment(datasetIndex, index);
var offset = labelMoment.diff(this.firstTick, this.tickUnit, true); var offset = labelMoment.diff(this.firstTick, this.tickUnit, true);
var decimal = offset / this.tickRange; var decimal = offset / this.tickRange;
if (this.isHorizontal()) { if (this.isHorizontal()) {
var innerWidth = this.width - (this.paddingLeft + this.paddingRight); var innerWidth = this.width - (this.paddingLeft + this.paddingRight);
var valueWidth = innerWidth / Math.max(this.ticks.length - 1, 1); var valueWidth = innerWidth / Math.max(this.ticks.length - 1, 1);
var valueOffset = (innerWidth * decimal) + this.paddingLeft; var valueOffset = (innerWidth * decimal) + this.paddingLeft;
return this.left + Math.round(valueOffset); return this.left + Math.round(valueOffset);
} else { } else {
var innerHeight = this.height - (this.paddingTop + this.paddingBottom); var innerHeight = this.height - (this.paddingTop + this.paddingBottom);
var valueHeight = innerHeight / Math.max(this.ticks.length - 1, 1); var valueHeight = innerHeight / Math.max(this.ticks.length - 1, 1);
var heightOffset = (innerHeight * decimal) + this.paddingTop; var heightOffset = (innerHeight * decimal) + this.paddingTop;
return this.top + Math.round(heightOffset); return this.top + Math.round(heightOffset);
} }
}, },
parseTime: function(label) { parseTime: function(label) {
// Date objects // Date objects
if (typeof label.getMonth === 'function' || typeof label == 'number') { if (typeof label.getMonth === 'function' || typeof label === 'number') {
return moment(label); return moment(label);
} }
// Moment support // Moment support
if (label.isValid && label.isValid()) { if (label.isValid && label.isValid()) {
return label; return label;
} }
// Custom parsing (return an instance of moment) // Custom parsing (return an instance of moment)
if (typeof this.options.time.format !== 'string' && this.options.time.format.call) { if (typeof this.options.time.format !== 'string' && this.options.time.format.call) {
return this.options.time.format(label); return this.options.time.format(label);
} }
// Moment format parsing // Moment format parsing
return moment(label, this.options.time.format); return moment(label, this.options.time.format);
}, }
}); });
Chart.scaleService.registerScaleType("time", TimeScale, defaultConfig); Chart.scaleService.registerScaleType("time", TimeScale, defaultConfig);
}; };