Stuck on shared vertical scale draw logic

Trying to make all scales share the same draw function for both
horizontal and vertical.  For some reason the vertical linear scale on
line.html has a width that is too small
This commit is contained in:
Tanner Linsley 2015-09-23 21:52:31 -06:00
parent 9b9ebca246
commit c7107677d2
8 changed files with 825 additions and 753 deletions

View File

@ -6,9 +6,9 @@
<script src="../Chart.js"></script>
<script src="../node_modules/jquery/dist/jquery.min.js"></script>
<style>
canvas {
-webkit-box-shadow: 0 0 20px 0 rgba(0, 0, 0, .5);
}
canvas {
-webkit-box-shadow: 0 0 20px 0 rgba(0, 0, 0, .5);
}
</style>
</head>
@ -26,135 +26,134 @@
<div>
<h3>Legend</h3>
<div id="legendContainer">
</div>
</div>
<script>
var randomScalingFactor = function() {
return Math.round(Math.random() * 100 * (Math.random() > 0.5 ? -1 : 1));
};
var randomColorFactor = function() {
return Math.round(Math.random() * 255);
};
var randomColor = function(opacity) {
return 'rgba(' + randomColorFactor() + ',' + randomColorFactor() + ',' + randomColorFactor() + ',' + (opacity || '.3') + ')';
};
var config = {
type: 'line',
data: {
labels: ["January", "February", "March", "April", "May", "June", "July"],
datasets: [{
label: "My First dataset",
data: [randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor()],
fill: false,
borderDash: [5, 5],
}, {
label: "My Second dataset",
data: [randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor()],
}]
},
options: {
responsive: true,
scales: {
xAxes: [{
display: true,
scaleLabel: {
show: true,
labelString: 'Month'
}
}],
yAxes: [{
display: true,
scaleLabel: {
show: true,
labelString: 'Value'
}
}]
}
}
};
$.each(config.data.datasets, function(i, dataset) {
dataset.borderColor = randomColor(0.4);
dataset.backgroundColor = randomColor(0.5);
dataset.pointBorderColor = randomColor(0.7);
dataset.pointBackgroundColor = randomColor(0.5);
dataset.pointBorderWidth = 1;
});
console.log(config.data);
window.onload = function() {
var ctx = document.getElementById("canvas").getContext("2d");
window.myLine = new Chart(ctx, config);
updateLegend();
};
function updateLegend() {
$legendContainer = $('#legendContainer');
$legendContainer.empty();
$legendContainer.append(window.myLine.generateLegend());
}
$('#randomizeData').click(function() {
$.each(config.data.datasets, function(i, dataset) {
dataset.data = dataset.data.map(function() {
return randomScalingFactor();
});
});
window.myLine.update();
updateLegend();
});
$('#addDataset').click(function() {
var newDataset = {
label: 'Dataset ' + config.data.datasets.length,
borderColor: randomColor(0.4),
backgroundColor: randomColor(0.5),
pointBorderColor: randomColor(0.7),
pointBackgroundColor: randomColor(0.5),
pointBorderWidth: 1,
data: [],
var randomScalingFactor = function() {
return Math.round(Math.random() * 100 * (Math.random() > 0.5 ? -1 : 1));
};
var randomColorFactor = function() {
return Math.round(Math.random() * 255);
};
var randomColor = function(opacity) {
return 'rgba(' + randomColorFactor() + ',' + randomColorFactor() + ',' + randomColorFactor() + ',' + (opacity || '.3') + ')';
};
for (var index = 0; index < config.data.labels.length; ++index) {
newDataset.data.push(randomScalingFactor());
}
window.myLine.addDataset(newDataset);
updateLegend();
});
$('#addData').click(function() {
if (config.data.datasets.length > 0) {
config.data.labels.push('dataset #' + config.data.labels.length);
for (var index = 0; index < config.data.datasets.length; ++index) {
window.myLine.addData(randomScalingFactor(), index);
var config = {
type: 'line',
data: {
labels: ["January", "February", "March", "April", "May", "June", "July"],
datasets: [{
label: "My First dataset",
data: [randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor()],
fill: false,
borderDash: [5, 5],
}, {
label: "My Second dataset",
data: [randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor()],
}]
},
options: {
responsive: true,
scales: {
xAxes: [{
display: true,
scaleLabel: {
show: true,
labelString: 'Month'
}
}],
yAxes: [{
display: true,
scaleLabel: {
show: true,
labelString: 'Value'
}
}]
}
}
};
updateLegend();
}
});
$('#removeDataset').click(function() {
window.myLine.removeDataset(0);
updateLegend();
});
$('#removeData').click(function() {
config.data.labels.splice(-1, 1); // remove the label first
config.data.datasets.forEach(function(dataset, datasetIndex) {
window.myLine.removeData(datasetIndex, -1);
$.each(config.data.datasets, function(i, dataset) {
dataset.borderColor = randomColor(0.4);
dataset.backgroundColor = randomColor(0.5);
dataset.pointBorderColor = randomColor(0.7);
dataset.pointBackgroundColor = randomColor(0.5);
dataset.pointBorderWidth = 1;
});
updateLegend();
});
console.log(config.data);
window.onload = function() {
var ctx = document.getElementById("canvas").getContext("2d");
window.myLine = new Chart(ctx, config);
updateLegend();
};
function updateLegend() {
$legendContainer = $('#legendContainer');
$legendContainer.empty();
$legendContainer.append(window.myLine.generateLegend());
}
$('#randomizeData').click(function() {
$.each(config.data.datasets, function(i, dataset) {
dataset.data = dataset.data.map(function() {
return randomScalingFactor();
});
});
window.myLine.update();
updateLegend();
});
$('#addDataset').click(function() {
var newDataset = {
label: 'Dataset ' + config.data.datasets.length,
borderColor: randomColor(0.4),
backgroundColor: randomColor(0.5),
pointBorderColor: randomColor(0.7),
pointBackgroundColor: randomColor(0.5),
pointBorderWidth: 1,
data: [],
};
for (var index = 0; index < config.data.labels.length; ++index) {
newDataset.data.push(randomScalingFactor());
}
window.myLine.addDataset(newDataset);
updateLegend();
});
$('#addData').click(function() {
if (config.data.datasets.length > 0) {
config.data.labels.push('dataset #' + config.data.labels.length);
for (var index = 0; index < config.data.datasets.length; ++index) {
window.myLine.addData(randomScalingFactor(), index);
}
updateLegend();
}
});
$('#removeDataset').click(function() {
window.myLine.removeDataset(0);
updateLegend();
});
$('#removeData').click(function() {
config.data.labels.splice(-1, 1); // remove the label first
config.data.datasets.forEach(function(dataset, datasetIndex) {
window.myLine.removeData(datasetIndex, -1);
});
updateLegend();
});
</script>
</body>

View File

@ -16,8 +16,8 @@
type: "category",
// Specific to Bar Controller
categoryPercentage: 0.8,
barPercentage: 0.9,
categoryPercentage: 0.75,
barPercentage: 0.5,
// grid line settings
gridLines: {
@ -101,6 +101,7 @@
this.updateElement(rectangle, index, true, numBars);
this.getDataset().metaData.splice(index, 0, rectangle);
},
removeElement: function(index) {
this.getDataset().metaData.splice(index, 1);
},
@ -110,6 +111,7 @@
},
update: function(reset) {
var numBars = this.getBarCount();
var numData = this.getDataset().data.length;
@ -218,37 +220,63 @@
},
calculateBarWidth: function() {
getRuler: function() {
var xScale = this.getScaleForID(this.getDataset().xAxisID);
var yScale = this.getScaleForID(this.getDataset().yAxisID);
var datasetCount = this.chart.data.datasets.length;
var tickWidth = xScale.getSmallestDataDistance();
console.log(tickWidth);
var categoryWidth = tickWidth * xScale.options.categoryPercentage;
var categorySpacing = (tickWidth - (tickWidth * xScale.options.categoryPercentage)) / 2;
var fullBarWidth = categoryWidth / datasetCount;
var barWidth = fullBarWidth * xScale.options.barPercentage;
var barSpacing = fullBarWidth - (fullBarWidth * xScale.options.barPercentage);
return {
datasetCount: datasetCount,
tickWidth: tickWidth,
categoryWidth: categoryWidth,
categorySpacing: categorySpacing,
fullBarWidth: fullBarWidth,
barWidth: barWidth,
barSpacing: barSpacing,
};
},
calculateBarWidth: function() {
var xScale = this.getScaleForID(this.getDataset().xAxisID);
var ruler = this.getRuler();
if (xScale.options.stacked) {
return xScale.ruler.categoryWidth;
return ruler.categoryWidth;
}
return xScale.ruler.barWidth;
return ruler.barWidth;
},
calculateBarX: function(datasetIndex, elementIndex) {
var xScale = this.getScaleForID(this.getDataset().xAxisID);
var yScale = this.getScaleForID(this.getDataset().yAxisID);
var xScale = this.getScaleForID(this.getDataset().xAxisID);
var leftTick = xScale.getPixelFromTickIndex(elementIndex);
var ruler = this.getRuler();
if (yScale.options.stacked) {
return leftTick + (xScale.ruler.categoryWidth / 2) + xScale.ruler.categorySpacing;
return ruler.leftTick + (ruler.categoryWidth / 2) + ruler.categorySpacing;
}
return leftTick +
(xScale.ruler.barWidth / 2) +
xScale.ruler.categorySpacing +
(xScale.ruler.barWidth * datasetIndex) +
(xScale.ruler.barSpacing / 2) +
(xScale.ruler.barSpacing * datasetIndex);
(ruler.barWidth / 2) +
ruler.categorySpacing +
(ruler.barWidth * datasetIndex) +
(ruler.barSpacing / 2) +
(ruler.barSpacing * datasetIndex);
},
calculateBarY: function(datasetIndex, index) {

View File

@ -125,9 +125,7 @@
this.stop();
var canvas = this.chart.canvas;
var newWidth = helpers.getMaximumWidth(this.chart.canvas);
var newHeight = (this.options.maintainAspectRatio && isNaN(this.chart.aspectRatio) === false && isFinite(this.chart.aspectRatio) && this.chart.aspectRatio !== 0)
? newWidth / this.chart.aspectRatio
: helpers.getMaximumHeight(this.chart.canvas);
var newHeight = (this.options.maintainAspectRatio && isNaN(this.chart.aspectRatio) === false && isFinite(this.chart.aspectRatio) && this.chart.aspectRatio !== 0) ? newWidth / this.chart.aspectRatio : helpers.getMaximumHeight(this.chart.canvas);
canvas.width = this.chart.width = newWidth;
canvas.height = this.chart.height = newHeight;
@ -207,7 +205,7 @@
this.scale = scale;
}
Chart.scaleService.fitScalesForChart(this, this.chart.width, this.chart.height);
Chart.scaleService.update(this, this.chart.width, this.chart.height);
},
buildOrUpdateControllers: function() {
@ -230,7 +228,7 @@
update: function update(animationDuration, lazy) {
// This will loop through any data and do the appropriate element update for the type
Chart.scaleService.fitScalesForChart(this, this.chart.width, this.chart.height);
Chart.scaleService.update(this, this.chart.width, this.chart.height);
helpers.each(this.data.datasets, function(dataset, datasetIndex) {
dataset.controller.update();
}, this);
@ -333,9 +331,9 @@
for (var datasetIndex = 0; datasetIndex < this.data.datasets.length; datasetIndex++) {
for (var elementIndex = 0; elementIndex < this.data.datasets[datasetIndex].metaData.length; elementIndex++) {
if (this.data.datasets[datasetIndex].metaData[elementIndex].inLabelRange(eventPosition.x, eventPosition.y)) {
helpers.each(this.data.datasets[datasetIndex].metaData, function(element, index) {
elementsArray.push(element);
}, this);
helpers.each(this.data.datasets[datasetIndex].metaData, function(element, index) {
elementsArray.push(element);
}, this);
}
}
}
@ -490,7 +488,7 @@
(this.lastActive.length && this.active.length && changed)) {
this.stop();
// We only need to render at this point. Updating will cause scales to be recomputed generating flicker & using more
// memory than necessary.
this.render(this.options.hover.animationDuration, true);

View File

@ -34,10 +34,69 @@
};
Chart.Scale = Chart.Element.extend({
isHorizontal: function() {
return this.options.position == "top" || this.options.position == "bottom";
// These methods are ordered by lifecyle. Utilities then follow.
// Any function defined here is inherited by all scale types.
// Any function can be extended by the scale type
beforeUpdate: helpers.noop,
update: function(maxWidth, maxHeight, margins) {
// Update Lifecycle - Probably don't want to ever extend or overwrite this function ;)
this.beforeUpdate();
// Absorb the master measurements
this.maxWidth = maxWidth;
this.maxHeight = maxHeight;
this.margins = margins;
// Dimensions
this.beforeSetDimensions();
this.setDimensions();
this.afterSetDimensions();
// Ticks
this.beforeBuildTicks();
this.buildTicks();
this.afterBuildTicks();
// Tick Rotation
this.beforeCalculateTickRotation();
this.calculateTickRotation();
this.afterCalculateTickRotation();
// Fit
this.beforeFit();
this.fit();
this.afterFit();
//
this.afterUpdate();
return this.minSize;
},
calculateTickRotation: function(maxHeight, margins) {
afterUpdate: helpers.noop,
//
beforeSetDimensions: helpers.noop,
setDimensions: function() {
// Set the unconstrained dimension before label rotation
if (this.isHorizontal()) {
this.width = this.maxWidth;
} else {
this.height = this.maxHeight;
}
},
afterSetDimensions: helpers.noop,
//
beforeBuildTicks: helpers.noop,
buildTicks: helpers.noop,
afterBuildTicks: helpers.noop,
//
beforeCalculateTickRotation: helpers.noop,
calculateTickRotation: function() {
//Get the width of each grid by calculating the difference
//between x offsets between 0 and 1.
var labelFont = helpers.fontString(this.options.ticks.fontSize, this.options.ticks.fontStyle, this.options.ticks.fontFamily);
@ -61,10 +120,10 @@
this.labelWidth = originalLabelWidth;
//Allow 3 pixels x2 padding either side for label readability
// Allow 3 pixels x2 padding either side for label readability
// only the index matters for a dataset scale, but we want a consistent interface between scales
var tickWidth = this.ruler.tick - 6;
var tickWidth = this.getPixelForTick(1) - this.getPixelForTick(0) - 6;
//Max label rotation can be set or default to 90 - also act as a loop counter
while (this.labelWidth > tickWidth && this.labelRotation <= this.options.ticks.maxRotation) {
@ -81,7 +140,7 @@
this.paddingRight = this.options.ticks.fontSize / 2;
if (sinRotation * originalLabelWidth > maxHeight) {
if (sinRotation * originalLabelWidth > this.maxHeight) {
// go back one step
this.labelRotation--;
break;
@ -98,36 +157,66 @@
this.paddingLeft = 0;
}
if (margins) {
this.paddingLeft -= margins.left;
this.paddingRight -= margins.right;
if (this.margins) {
this.paddingLeft -= this.margins.left;
this.paddingRight -= this.margins.right;
this.paddingLeft = Math.max(this.paddingLeft, 0);
this.paddingRight = Math.max(this.paddingRight, 0);
}
},
getPixelForValue: function(value, index, datasetIndex, includeOffset) {
// This must be called after fit has been run so that
// this.left, this.top, this.right, and this.bottom have been defined
afterCalculateTickRotation: helpers.noop,
//
beforeFit: helpers.noop,
fit: function() {
this.minSize = {
width: 0,
height: 0,
};
var labelFont = helpers.fontString(this.options.ticks.fontSize, this.options.ticks.fontStyle, this.options.ticks.fontFamily);
var longestLabelWidth = helpers.longestText(this.ctx, labelFont, this.ticks);
// Width
if (this.isHorizontal()) {
var isRotated = (this.labelRotation > 0);
var innerWidth = this.width - (this.paddingLeft + this.paddingRight);
var valueWidth = innerWidth / Math.max((this.data.labels.length - ((this.options.gridLines.offsetGridLines) ? 0 : 1)), 1);
var valueOffset = (valueWidth * index) + this.paddingLeft;
if (this.options.gridLines.offsetGridLines && includeOffset) {
valueOffset += (valueWidth / 2);
}
return this.left + Math.round(valueOffset);
} else {
return this.top + (index * (this.height / this.labels.length));
this.minSize.width = this.maxWidth;
} else if (this.options.display) {
var labelWidth = this.options.ticks.show ? longestLabelWidth + 6 : 0;
this.minSize.width = Math.min(labelWidth, this.maxWidth);
}
// Height
if (this.isHorizontal() && this.options.display) {
var labelHeight = (Math.sin(helpers.toRadians(this.labelRotation)) * longestLabelWidth) + 1.5 * this.options.ticks.fontSize;
this.minSize.height = Math.min(this.options.ticks.show ? labelHeight : 0, this.maxHeight);
} else if (this.options.display) {
this.minSize.height = this.maxHeight;
}
this.width = this.minSize.width;
this.height = this.minSize.height;
},
getPixelFromTickIndex: function(index, includeOffset) {
// This must be called after fit has been run so that
// this.left, this.top, this.right, and this.bottom have been defined
afterFit: helpers.noop,
// Shared Methods
isHorizontal: function() {
return this.options.position == "top" || this.options.position == "bottom";
},
// Used to get data value locations. Value can either be an index or a numerical value
getPixelForValue: helpers.noop,
// Used for tick location, should
getPixelForTick: function(index, includeOffset) {
if (this.isHorizontal()) {
var innerWidth = this.width - (this.paddingLeft + this.paddingRight);
var tickWidth = innerWidth / Math.max((this.ticks.length - ((this.options.gridLines.offsetGridLines) ? 0 : 1)), 1);
@ -141,9 +230,9 @@
return this.top + (index * (this.height / this.ticks.length));
}
},
getPixelFromDecimal: function(decimal, includeOffset) {
// This must be called after fit has been run so that
// this.left, this.top, this.right, and this.bottom have been defined
// Utility for getting the pixel location of a percentage of scale
getPixelForDecimal: function(decimal, includeOffset) {
if (this.isHorizontal()) {
var innerWidth = this.width - (this.paddingLeft + this.paddingRight);
var valueOffset = (innerWidth * decimal) + this.paddingLeft;
@ -153,57 +242,15 @@
return this.top + (decimal * (this.height / this.ticks.length));
}
},
// Fit this axis to the given size
// @param {number} maxWidth : the max width the axis can be
// @param {number} maxHeight: the max height the axis can be
// @return {object} minSize : the minimum size needed to draw the axis
fit: function(maxWidth, maxHeight, margins) {
// Set the unconstrained dimension before label rotation
if (this.isHorizontal()) {
this.width = maxWidth;
} else {
this.height = maxHeight;
}
this.buildTicks();
this.buildRuler();
this.calculateTickRotation(maxHeight, margins);
var minSize = {
width: 0,
height: 0,
};
var labelFont = helpers.fontString(this.options.ticks.fontSize, this.options.ticks.fontStyle, this.options.ticks.fontFamily);
var longestLabelWidth = helpers.longestText(this.ctx, labelFont, this.ticks);
// Width
if (this.isHorizontal()) {
minSize.width = maxWidth;
} else if (this.options.display) {
var labelWidth = this.options.ticks.show ? longestLabelWidth + 6 : 0;
minSize.width = Math.min(labelWidth, maxWidth);
}
// Height
if (this.isHorizontal() && this.options.display) {
var labelHeight = (Math.sin(helpers.toRadians(this.labelRotation)) * longestLabelWidth) + 1.5 * this.options.ticks.fontSize;
minSize.height = Math.min(this.options.ticks.show ? labelHeight : 0, maxHeight);
} else if (this.options.display) {
minSize.height = maxHeight;
}
this.width = minSize.width;
this.height = minSize.height;
return minSize;
},
// Actualy draw the scale on the canvas
// @param {rectangle} chartArea : the area of the chart to draw full grid lines on
draw: function(chartArea) {
if (this.options.display) {
var setContextLineSettings;
var isRotated;
var skipRatio;
// Make sure we draw text in the correct color
this.ctx.fillStyle = this.options.ticks.fontColor;
@ -212,8 +259,8 @@
setContextLineSettings = true;
var yTickStart = this.options.position == "bottom" ? this.top : this.bottom - 10;
var yTickEnd = this.options.position == "bottom" ? this.top + 10 : this.bottom;
var isRotated = this.labelRotation !== 0;
var skipRatio = false;
isRotated = this.labelRotation !== 0;
skipRatio = false;
if ((this.options.ticks.fontSize + 4) * this.ticks.length > (this.width - (this.paddingLeft + this.paddingRight))) {
skipRatio = 1 + Math.floor(((this.options.ticks.fontSize + 4) * this.ticks.length) / (this.width - (this.paddingLeft + this.paddingRight)));
@ -224,8 +271,8 @@
if ((skipRatio > 1 && index % skipRatio > 0) || (label === undefined || label === null)) {
return;
}
var xLineValue = this.getPixelFromTickIndex(index); // xvalues for grid lines
var xLabelValue = this.getPixelFromTickIndex(index, this.options.gridLines.offsetGridLines); // x values for ticks (need to consider offsetLabel option)
var xLineValue = this.getPixelForTick(index); // xvalues for grid lines
var xLabelValue = this.getPixelForTick(index, this.options.gridLines.offsetGridLines); // x values for ticks (need to consider offsetLabel option)
if (this.options.gridLines.show) {
if (index === 0) {
@ -271,12 +318,67 @@
}
}, this);
} else {
// TODO Vertical
if (this.options.gridLines.show) {}
setContextLineSettings = true;
var xTickStart = this.options.position == "left" ? this.right : this.left - 10;
var xTickEnd = this.options.position == "left" ? this.right + 10 : this.left;
isRotated = this.labelRotation !== 0;
//skipRatio = false;
if (this.options.ticks.show) {
// Draw the ticks
}
// if ((this.options.ticks.fontSize + 4) * this.ticks.length > (this.width - (this.paddingLeft + this.paddingRight))) {
// skipRatio = 1 + Math.floor(((this.options.ticks.fontSize + 4) * this.ticks.length) / (this.width - (this.paddingLeft + this.paddingRight)));
// }
helpers.each(this.ticks, function(label, index) {
// Blank ticks
// if ((skipRatio > 1 && index % skipRatio > 0) || (label === undefined || label === null)) {
// return;
// }
var yLineValue = this.getPixelForTick(index); // xvalues for grid lines
var yLabelValue = this.getPixelForTick(index, this.options.gridLines.offsetGridLines); // x values for ticks (need to consider offsetLabel option)
if (this.options.gridLines.show) {
if (index === 0) {
// Draw the first index specially
this.ctx.lineWidth = this.options.gridLines.zeroLineWidth;
this.ctx.strokeStyle = this.options.gridLines.zeroLineColor;
setContextLineSettings = true; // reset next time
} else if (setContextLineSettings) {
this.ctx.lineWidth = this.options.gridLines.lineWidth;
this.ctx.strokeStyle = this.options.gridLines.color;
setContextLineSettings = false;
}
yLineValue += helpers.aliasPixel(this.ctx.lineWidth);
// Draw the label area
this.ctx.beginPath();
if (this.options.gridLines.drawTicks) {
this.ctx.moveTo(xTickStart, yLineValue);
this.ctx.lineTo(xTickEnd, yLineValue);
}
// Draw the chart area
if (this.options.gridLines.drawOnChartArea) {
this.ctx.moveTo(chartArea.left, yLineValue);
this.ctx.lineTo(chartArea.right, yLineValue);
}
// Need to stroke in the loop because we are potentially changing line widths & colours
this.ctx.stroke();
}
if (this.options.ticks.show) {
this.ctx.save();
this.ctx.translate(yLabelValue, (isRotated) ? this.top + 12 : this.top + 8);
this.ctx.rotate(helpers.toRadians(this.labelRotation) * -1);
this.ctx.font = this.font;
this.ctx.textAlign = (isRotated) ? "right" : "center";
this.ctx.textBaseline = (isRotated) ? "middle" : "top";
this.ctx.fillText(label, 0, 0);
this.ctx.restore();
}
}, this);
}
}
}

View File

@ -6,7 +6,7 @@
helpers = Chart.helpers;
// The scale service is used to resize charts along with all of their axes. We make this as
// a service where scales are registered with their respective charts so that changing the
// a service where scales are registered with their respective charts so that changing the
// scales does not require
Chart.scaleService = {
// Scale registration object. Extensions can register new scale types (such as log or DB scales) and then
@ -14,6 +14,7 @@
constructors: {},
// Use a registration function so that we can move to an ES6 map when we no longer need to support
// old browsers
// Scale config defaults
defaults: {},
registerScaleType: function(type, scaleConstructor, defaults) {
@ -27,7 +28,7 @@
return this.defaults.hasOwnProperty(type) ? this.defaults[type] : {};
},
// The interesting function
fitScalesForChart: function(chartInstance, width, height) {
update: function(chartInstance, width, height) {
var xPadding = width > 30 ? 5 : 2;
var yPadding = height > 30 ? 5 : 2;
@ -78,7 +79,6 @@
chartWidth -= (2 * xPadding);
chartHeight -= (2 * yPadding);
// Step 2
var verticalScaleWidth = (width - chartWidth) / (leftScales.length + rightScales.length);
@ -89,7 +89,7 @@
var minimumScaleSizes = [];
var verticalScaleMinSizeFunction = function(scaleInstance) {
var minSize = scaleInstance.fit(verticalScaleWidth, chartHeight);
var minSize = scaleInstance.update(verticalScaleWidth, chartHeight);
minimumScaleSizes.push({
horizontal: false,
minSize: minSize,
@ -98,7 +98,7 @@
};
var horizontalScaleMinSizeFunction = function(scaleInstance) {
var minSize = scaleInstance.fit(chartWidth, horizontalScaleHeight);
var minSize = scaleInstance.update(chartWidth, horizontalScaleHeight);
minimumScaleSizes.push({
horizontal: true,
minSize: minSize,
@ -136,7 +136,7 @@
});
if (wrapper) {
scaleInstance.fit(wrapper.minSize.width, maxChartHeight);
scaleInstance.update(wrapper.minSize.width, maxChartHeight);
}
};
@ -153,7 +153,7 @@
};
if (wrapper) {
scaleInstance.fit(maxChartWidth, wrapper.minSize.height, scaleMargin);
scaleInstance.update(maxChartWidth, wrapper.minSize.height, scaleMargin);
}
};
@ -198,7 +198,7 @@
};
if (wrapper) {
scaleInstance.fit(wrapper.minSize.width, maxChartHeight, scaleMargin);
scaleInstance.update(wrapper.minSize.width, maxChartHeight, scaleMargin);
}
});
@ -215,7 +215,7 @@
};
if (wrapper) {
scaleInstance.fit(wrapper.minSize.width, maxChartHeight, scaleMargin);
scaleInstance.update(wrapper.minSize.width, maxChartHeight, scaleMargin);
}
});

View File

@ -12,30 +12,30 @@
var DatasetScale = Chart.Scale.extend({
buildTicks: function(index) {
this.ticks = [];
this.ticks = this.data.labels;
},
if (this.options.ticks.userCallback) {
this.data.labels.forEach(function(labelString, index) {
this.ticks.push(this.options.ticks.userCallback(labelString, index));
}, this);
// Used to get data value locations. Value can either be an index or a numerical value
getPixelForValue: function(value, index, datasetIndex, includeOffset) {
if (this.isHorizontal()) {
var innerWidth = this.width - (this.paddingLeft + this.paddingRight);
var valueWidth = innerWidth / Math.max((this.data.labels.length - ((this.options.gridLines.offsetGridLines) ? 0 : 1)), 1);
var toZero = this.max - this.min;
var newVal = value - toZero;
var decimal = newVal / (this.max - toZero);
var valueOffset = (valueWidth * decimal) + this.paddingLeft;
if (this.options.gridLines.offsetGridLines && includeOffset) {
valueOffset += (valueWidth / 2);
}
return this.left + Math.round(valueOffset);
} else {
this.ticks = this.data.labels;
return this.top + (index * (this.height / this.labels.length));
}
},
buildRuler: function() {
var datasetCount = this.data.datasets.length;
this.ruler = {};
this.ruler.tickWidth = this.getPixelFromTickIndex(1) - this.getPixelFromTickIndex(0) + 3; // TODO: Why is 2 needed here to make it take the full width?
this.ruler.categoryWidth = this.ruler.tickWidth * this.options.categoryPercentage;
this.ruler.categorySpacing = (this.ruler.tickWidth - (this.ruler.tickWidth * this.options.categoryPercentage)) / 2;
this.ruler.allBarsWidth = ((this.ruler.tickWidth - (this.ruler.categorySpacing * 2)) / datasetCount);
this.ruler.barWidth = this.ruler.allBarsWidth * this.options.barPercentage;
this.ruler.barSpacing = this.ruler.allBarsWidth - (this.ruler.allBarsWidth * this.options.barPercentage);
},
});
Chart.scaleService.registerScaleType("category", DatasetScale, defaultConfig);
}).call(this);

View File

@ -6,109 +6,74 @@
helpers = Chart.helpers;
var defaultConfig = {
display: true,
position: "left",
// grid line settings
gridLines: {
show: true,
color: "rgba(0, 0, 0, 0.1)",
lineWidth: 1,
drawOnChartArea: true,
drawTicks: true, // draw ticks extending towards the label
zeroLineWidth: 1,
zeroLineColor: "rgba(0,0,0,0.25)",
},
// scale numbers
reverse: false,
beginAtZero: false,
override: null,
// label settings
ticks: {
show: true,
mirror: false,
padding: 10,
template: "<%=value.toLocaleString()%>",
fontSize: 12,
fontStyle: "normal",
fontColor: "#666",
fontFamily: "Helvetica Neue"
}
};
var LinearScale = Chart.Scale.extend({
generateTicks: function(width, height) {
buildTicks: function() {
// We need to decide how many ticks we are going to have. Each tick draws a grid line.
// There are two possibilities. The first is that the user has manually overridden the scale
// calculations in which case the job is easy. The other case is that we have to do it ourselves
//
// We assume at this point that the scale object has been updated with the following values
// by the chart.
// min: this is the minimum value of the scale
// max: this is the maximum value of the scale
// options: contains the options for the scale. This is referenced from the user settings
// rather than being cloned. This ensures that updates always propogate to a redraw
//
// min: this is the minimum value of the scale
// max: this is the maximum value of the scale
// options: contains the options for the scale. This is referenced from the user settings
// rather than being cloned. This ensures that updates always propogate to a redraw
// 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
this.ticks = [];
this.min = this.maxWidth;
this.max = this.maxHeight;
if (this.options.override) {
// The user has specified the manual override. We use <= instead of < so that
// we get the final line
for (var i = 0; i <= this.options.override.steps; ++i) {
var value = this.options.override.start + (i * this.options.override.stepWidth);
this.ticks.push(value);
}
// 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
// We also limit the maximum number of ticks to 11 which gives a nice 10 squares on
// the graph
var maxTicks;
if (this.isHorizontal()) {
maxTicks = Math.min(11, Math.ceil(this.width / 50));
} else {
// 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
// We also limit the maximum number of ticks to 11 which gives a nice 10 squares on
// the graph
// The factor of 2 used to scale the font size has been experimentally determined.
maxTicks = Math.min(11, Math.ceil(this.height / (2 * this.options.ticks.fontSize)));
}
var maxTicks;
// Make sure we always have at least 2 ticks
maxTicks = Math.max(2, maxTicks);
if (this.isHorizontal()) {
maxTicks = Math.min(11, Math.ceil(width / 50));
} else {
// The factor of 2 used to scale the font size has been experimentally determined.
maxTicks = Math.min(11, Math.ceil(height / (2 * this.options.ticks.fontSize)));
// 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
// for details.
// 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
// axis, they can manually override it
if (this.options.beginAtZero) {
var minSign = helpers.sign(this.min);
var maxSign = helpers.sign(this.max);
if (minSign < 0 && maxSign < 0) {
// move the top up to 0
this.max = 0;
} else if (minSign > 0 && maxSign > 0) {
// move the botttom down to 0
this.min = 0;
}
}
// Make sure we always have at least 2 ticks
maxTicks = Math.max(2, maxTicks);
var niceRange = helpers.niceNum(this.max - this.min, false);
var spacing = helpers.niceNum(niceRange / (maxTicks - 1), true);
var niceMin = Math.floor(this.min / spacing) * spacing;
var niceMax = Math.ceil(this.max / spacing) * spacing;
// 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
// for details.
// 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
// axis, they can manually override it
if (this.options.beginAtZero) {
var minSign = helpers.sign(this.min);
var maxSign = helpers.sign(this.max);
if (minSign < 0 && maxSign < 0) {
// move the top up to 0
this.max = 0;
} else if (minSign > 0 && maxSign > 0) {
// move the botttom down to 0
this.min = 0;
}
}
var niceRange = helpers.niceNum(this.max - this.min, false);
var spacing = helpers.niceNum(niceRange / (maxTicks - 1), true);
var niceMin = Math.floor(this.min / spacing) * spacing;
var niceMax = Math.ceil(this.max / spacing) * spacing;
// Put the values into the ticks array
for (var j = niceMin; j <= niceMax; j += spacing) {
this.ticks.push(j);
}
// Put the values into the ticks array
for (var j = niceMin; j <= niceMax; j += spacing) {
this.ticks.push(j);
}
if (this.options.position == "left" || this.options.position == "right") {
@ -131,11 +96,15 @@
this.end = this.max;
}
},
// Utils
// Get the correct value. If the value type is object get the x or y based on whether we are horizontal or not
getRightValue: function(rawValue) {
return (typeof(rawValue) === "object" && rawValue !== null) ? (this.isHorizontal() ? rawValue.x : rawValue.y) : rawValue;
},
getPixelForValue: function(value) {
getPixelForValue: function(value, index, datasetIndex, includeOffset) {
// This must be called after fit has been run so that
// this.left, this.top, this.right, and this.bottom have been defined
var pixel;
@ -155,428 +124,425 @@
},
// Functions needed for line charts
calculateRange: function() {
this.min = null;
this.max = null;
// calculateRange: function() {
// this.min = null;
// this.max = null;
var positiveValues = [];
var negativeValues = [];
// var positiveValues = [];
// var negativeValues = [];
if (this.options.stacked) {
helpers.each(this.data.datasets, function(dataset) {
if (this.isHorizontal() ? dataset.xAxisID === this.id : dataset.yAxisID === this.id) {
helpers.each(dataset.data, function(rawValue, index) {
// if (this.options.stacked) {
// helpers.each(this.data.datasets, function(dataset) {
// if (this.isHorizontal() ? dataset.xAxisID === this.id : dataset.yAxisID === this.id) {
// helpers.each(dataset.data, function(rawValue, index) {
var value = this.getRightValue(rawValue);
// var value = this.getRightValue(rawValue);
positiveValues[index] = positiveValues[index] || 0;
negativeValues[index] = negativeValues[index] || 0;
// positiveValues[index] = positiveValues[index] || 0;
// negativeValues[index] = negativeValues[index] || 0;
if (this.options.relativePoints) {
positiveValues[index] = 100;
} else {
if (value < 0) {
negativeValues[index] += value;
} else {
positiveValues[index] += value;
}
}
}, this);
}
}, this);
// if (this.options.relativePoints) {
// positiveValues[index] = 100;
// } else {
// if (value < 0) {
// negativeValues[index] += value;
// } else {
// positiveValues[index] += value;
// }
// }
// }, this);
// }
// }, this);
var values = positiveValues.concat(negativeValues);
this.min = helpers.min(values);
this.max = helpers.max(values);
// var values = positiveValues.concat(negativeValues);
// this.min = helpers.min(values);
// this.max = helpers.max(values);
} else {
helpers.each(this.data.datasets, function(dataset) {
if (this.isHorizontal() ? dataset.xAxisID === this.id : dataset.yAxisID === this.id) {
helpers.each(dataset.data, function(rawValue, index) {
var value = this.getRightValue(rawValue);
// } else {
// helpers.each(this.data.datasets, function(dataset) {
// if (this.isHorizontal() ? dataset.xAxisID === this.id : dataset.yAxisID === this.id) {
// helpers.each(dataset.data, function(rawValue, index) {
// var value = this.getRightValue(rawValue);
if (this.min === null) {
this.min = value;
} else if (value < this.min) {
this.min = value;
}
// if (this.min === null) {
// this.min = value;
// } else if (value < this.min) {
// this.min = value;
// }
if (this.max === null) {
this.max = value;
} else if (value > this.max) {
this.max = value;
}
}, this);
}
}, this);
}
// if (this.max === null) {
// this.max = value;
// } else if (value > this.max) {
// this.max = value;
// }
// }, this);
// }
// }, this);
// }
if (this.min === this.max) {
this.min--;
this.max++;
}
},
// if (this.min === this.max) {
// this.min--;
// this.max++;
// }
// },
getPointPixelForValue: function(rawValue, index, datasetIndex) {
var value = this.getRightValue(rawValue);
// getPointPixelForValue: function(rawValue, index, datasetIndex) {
// var value = this.getRightValue(rawValue);
if (this.options.stacked) {
var offsetPos = 0;
var offsetNeg = 0;
// if (this.options.stacked) {
// var offsetPos = 0;
// var offsetNeg = 0;
for (var i = this.data.datasets.length - 1; i > datasetIndex; --i) {
if (this.data.datasets[i].data[index] < 0) {
offsetNeg += this.data.datasets[i].data[index];
} else {
offsetPos += this.data.datasets[i].data[index];
}
}
// for (var i = this.data.datasets.length - 1; i > datasetIndex; --i) {
// if (this.data.datasets[i].data[index] < 0) {
// offsetNeg += this.data.datasets[i].data[index];
// } else {
// offsetPos += this.data.datasets[i].data[index];
// }
// }
if (value < 0) {
return this.getPixelForValue(offsetNeg + value);
} else {
return this.getPixelForValue(offsetPos + value);
}
} else {
return this.getPixelForValue(value);
}
},
// if (value < 0) {
// return this.getPixelForValue(offsetNeg + value);
// } else {
// return this.getPixelForValue(offsetPos + value);
// }
// } else {
// return this.getPixelForValue(value);
// }
// },
// Functions needed for bar charts
calculateBarBase: function(datasetIndex, index) {
var base = 0;
// calculateBarBase: function(datasetIndex, index) {
// var base = 0;
if (this.options.stacked) {
// if (this.options.stacked) {
var value = this.data.datasets[datasetIndex].data[index];
// var value = this.data.datasets[datasetIndex].data[index];
if (value < 0) {
for (var i = 0; i < datasetIndex; i++) {
if (this.data.datasets[i].yAxisID === this.id) {
base += this.data.datasets[i].data[index] < 0 ? this.data.datasets[i].data[index] : 0;
}
}
} else {
for (var j = 0; j < datasetIndex; j++) {
if (this.data.datasets[j].yAxisID === this.id) {
base += this.data.datasets[j].data[index] > 0 ? this.data.datasets[j].data[index] : 0;
}
}
}
// if (value < 0) {
// for (var i = 0; i < datasetIndex; i++) {
// if (this.data.datasets[i].yAxisID === this.id) {
// base += this.data.datasets[i].data[index] < 0 ? this.data.datasets[i].data[index] : 0;
// }
// }
// } else {
// for (var j = 0; j < datasetIndex; j++) {
// if (this.data.datasets[j].yAxisID === this.id) {
// base += this.data.datasets[j].data[index] > 0 ? this.data.datasets[j].data[index] : 0;
// }
// }
// }
return this.getPixelForValue(base);
}
// return this.getPixelForValue(base);
// }
base = this.getPixelForValue(this.min);
// base = this.getPixelForValue(this.min);
if (this.beginAtZero || ((this.min <= 0 && this.max >= 0) || (this.min >= 0 && this.max <= 0))) {
base = this.getPixelForValue(0);
base += this.options.gridLines.lineWidth;
} else if (this.min < 0 && this.max < 0) {
// All values are negative. Use the top as the base
base = this.getPixelForValue(this.max);
}
// if (this.beginAtZero || ((this.min <= 0 && this.max >= 0) || (this.min >= 0 && this.max <= 0))) {
// base = this.getPixelForValue(0);
// base += this.options.gridLines.lineWidth;
// } else if (this.min < 0 && this.max < 0) {
// // All values are negative. Use the top as the base
// base = this.getPixelForValue(this.max);
// }
return base;
// return base;
},
calculateBarY: function(datasetIndex, index) {
var value = this.data.datasets[datasetIndex].data[index];
// },
// calculateBarY: function(datasetIndex, index) {
// var value = this.data.datasets[datasetIndex].data[index];
if (this.options.stacked) {
// if (this.options.stacked) {
var sumPos = 0,
sumNeg = 0;
// var sumPos = 0,
// sumNeg = 0;
for (var i = 0; i < datasetIndex; i++) {
if (this.data.datasets[i].data[index] < 0) {
sumNeg += this.data.datasets[i].data[index] || 0;
} else {
sumPos += this.data.datasets[i].data[index] || 0;
}
}
// for (var i = 0; i < datasetIndex; i++) {
// if (this.data.datasets[i].data[index] < 0) {
// sumNeg += this.data.datasets[i].data[index] || 0;
// } else {
// sumPos += this.data.datasets[i].data[index] || 0;
// }
// }
if (value < 0) {
return this.getPixelForValue(sumNeg + value);
} else {
return this.getPixelForValue(sumPos + value);
}
// if (value < 0) {
// return this.getPixelForValue(sumNeg + value);
// } else {
// return this.getPixelForValue(sumPos + value);
// }
return this.getPixelForValue(value);
}
// return this.getPixelForValue(value);
// }
return this.getPixelForValue(value);
},
// return this.getPixelForValue(value);
// },
// Fit this axis to the given size
// @param {number} maxWidth : the max width the axis can be
// @param {number} maxHeight: the max height the axis can be
// @return {object} minSize : the minimum size needed to draw the axis
fit: function(maxWidth, maxHeight, margins) {
this.calculateRange();
this.generateTicks(maxWidth, maxHeight);
// fit: function() {
var minSize = {
width: 0,
height: 0,
};
// this.minSize = {
// width: 0,
// height: 0,
// };
// In a horizontal axis, we need some room for the scale to be drawn
//
// -----------------------------------------------------
// | | | | |
//
// In a vertical axis, we need some room for the scale to be drawn.
// The actual grid lines will be drawn on the chart area, however, we need to show
// ticks where the axis actually is.
// We will allocate 25px for this width
// |
// -|
// |
// |
// -|
// |
// |
// -|
// // In a horizontal axis, we need some room for the scale to be drawn
// //
// // -----------------------------------------------------
// // | | | | |
// //
// // In a vertical axis, we need some room for the scale to be drawn.
// // The actual grid lines will be drawn on the chart area, however, we need to show
// // ticks where the axis actually is.
// // We will allocate 25px for this width
// // |
// // -|
// // |
// // |
// // -|
// // |
// // |
// // -|
// Width
if (this.isHorizontal()) {
minSize.width = maxWidth; // fill all the width
} else {
minSize.width = this.options.gridLines.show && this.options.display ? 10 : 0;
}
// // Width
// if (this.isHorizontal()) {
// this.minSize.width = this.maxWidth; // fill all the width
// } else {
// this.minSize.width = this.options.gridLines.show && this.options.display ? 10 : 0;
// }
// height
if (this.isHorizontal()) {
minSize.height = this.options.gridLines.show && this.options.display ? 10 : 0;
} else {
minSize.height = maxHeight; // fill all the height
}
// // height
// if (this.isHorizontal()) {
// this.minSize.height = this.options.gridLines.show && this.options.display ? 10 : 0;
// } else {
// this.minSize.height = this.maxHeight; // fill all the height
// }
this.paddingLeft = 0;
this.paddingRight = 0;
this.paddingTop = 0;
this.paddingBottom = 0;
// this.paddingLeft = 0;
// this.paddingRight = 0;
// this.paddingTop = 0;
// this.paddingBottom = 0;
if (this.options.ticks.show && this.options.display) {
// Don't bother fitting the ticks if we are not showing them
var labelFont = helpers.fontString(this.options.ticks.fontSize,
this.options.ticks.fontStyle, this.options.ticks.fontFamily);
// if (this.options.ticks.show && this.options.display) {
// // Don't bother fitting the ticks if we are not showing them
// var labelFont = helpers.fontString(this.options.ticks.fontSize,
// this.options.ticks.fontStyle, this.options.ticks.fontFamily);
if (this.isHorizontal()) {
// A horizontal axis is more constrained by the height.
var maxLabelHeight = maxHeight - minSize.height;
var labelHeight = 1.5 * this.options.ticks.fontSize;
minSize.height = Math.min(maxHeight, minSize.height + labelHeight);
// if (this.isHorizontal()) {
// // A horizontal axis is more constrained by the height.
// var maxLabelHeight = this.maxHeight - this.minSize.height;
// var labelHeight = 1.5 * this.options.ticks.fontSize;
// this.minSize.height = Math.min(this.maxHeight, this.minSize.height + labelHeight);
var labelFont = helpers.fontString(this.options.ticks.fontSize, this.options.ticks.fontStyle, this.options.ticks.fontFamily);
this.ctx.font = labelFont;
// var labelFont = helpers.fontString(this.options.ticks.fontSize, this.options.ticks.fontStyle, this.options.ticks.fontFamily);
// this.ctx.font = labelFont;
var firstLabelWidth = this.ctx.measureText(this.ticks[0]).width;
var lastLabelWidth = this.ctx.measureText(this.ticks[this.ticks.length - 1]).width;
// var firstLabelWidth = this.ctx.measureText(this.ticks[0]).width;
// var lastLabelWidth = this.ctx.measureText(this.ticks[this.ticks.length - 1]).width;
// Ensure that our ticks are always inside the canvas
this.paddingLeft = firstLabelWidth / 2;
this.paddingRight = lastLabelWidth / 2;
} else {
// A vertical axis is more constrained by the width. Labels are the dominant factor
// here, so get that length first
var maxLabelWidth = maxWidth - minSize.width;
var largestTextWidth = helpers.longestText(this.ctx, labelFont, this.ticks);
// // Ensure that our ticks are always inside the canvas
// this.paddingLeft = firstLabelWidth / 2;
// this.paddingRight = lastLabelWidth / 2;
// } else {
// // A vertical axis is more constrained by the width. Labels are the dominant factor
// // here, so get that length first
// var maxLabelWidth = this.maxWidth - this.minSize.width;
// var largestTextWidth = helpers.longestText(this.ctx, labelFont, this.ticks);
if (largestTextWidth < maxLabelWidth) {
// We don't need all the room
minSize.width += largestTextWidth;
minSize.width += 3; // extra padding
} else {
// Expand to max size
minSize.width = maxWidth;
}
// if (largestTextWidth < maxLabelWidth) {
// // We don't need all the room
// this.minSize.width += largestTextWidth;
// this.minSize.width += 3; // extra padding
// } else {
// // Expand to max size
// this.minSize.width = this.maxWidth;
// }
this.paddingTop = this.options.ticks.fontSize / 2;
this.paddingBottom = this.options.ticks.fontSize / 2;
}
}
// this.paddingTop = this.options.ticks.fontSize / 2;
// this.paddingBottom = this.options.ticks.fontSize / 2;
// }
// }
if (margins) {
this.paddingLeft -= margins.left;
this.paddingTop -= margins.top;
this.paddingRight -= margins.right;
this.paddingBottom -= margins.bottom;
// if (this.margins) {
// this.paddingLeft -= this.margins.left;
// this.paddingTop -= this.margins.top;
// this.paddingRight -= this.margins.right;
// this.paddingBottom -= this.margins.bottom;
this.paddingLeft = Math.max(this.paddingLeft, 0);
this.paddingTop = Math.max(this.paddingTop, 0);
this.paddingRight = Math.max(this.paddingRight, 0);
this.paddingBottom = Math.max(this.paddingBottom, 0);
}
// this.paddingLeft = Math.max(this.paddingLeft, 0);
// this.paddingTop = Math.max(this.paddingTop, 0);
// this.paddingRight = Math.max(this.paddingRight, 0);
// this.paddingBottom = Math.max(this.paddingBottom, 0);
// }
this.width = minSize.width;
this.height = minSize.height;
return minSize;
},
// this.width = this.minSize.width;
// this.height = this.minSize.height;
// },
// Actualy draw the scale on the canvas
// @param {rectangle} chartArea : the area of the chart to draw full grid lines on
draw: function(chartArea) {
if (this.options.display) {
// draw: function(chartArea) {
// if (this.options.display) {
var setContextLineSettings;
var hasZero;
// var setContextLineSettings;
// var hasZero;
// Make sure we draw text in the correct color
this.ctx.fillStyle = this.options.ticks.fontColor;
// // Make sure we draw text in the correct color
// this.ctx.fillStyle = this.options.ticks.fontColor;
if (this.isHorizontal()) {
if (this.options.gridLines.show) {
// Draw the horizontal line
setContextLineSettings = true;
hasZero = helpers.findNextWhere(this.ticks, function(tick) {
return tick === 0;
}) !== undefined;
var yTickStart = this.options.position == "bottom" ? this.top : this.bottom - 5;
var yTickEnd = this.options.position == "bottom" ? this.top + 5 : this.bottom;
// if (this.isHorizontal()) {
// if (this.options.gridLines.show) {
// // Draw the horizontal line
// setContextLineSettings = true;
// hasZero = helpers.findNextWhere(this.ticks, function(tick) {
// return tick === 0;
// }) !== undefined;
// var yTickStart = this.options.position == "bottom" ? this.top : this.bottom - 5;
// var yTickEnd = this.options.position == "bottom" ? this.top + 5 : this.bottom;
helpers.each(this.ticks, function(tick, index) {
// Grid lines are vertical
var xValue = this.getPixelForValue(tick);
// helpers.each(this.ticks, function(tick, index) {
// // Grid lines are vertical
// var xValue = this.getPixelForValue(tick);
if (tick === 0 || (!hasZero && index === 0)) {
// Draw the 0 point specially or the left if there is no 0
this.ctx.lineWidth = this.options.gridLines.zeroLineWidth;
this.ctx.strokeStyle = this.options.gridLines.zeroLineColor;
setContextLineSettings = true; // reset next time
} else if (setContextLineSettings) {
this.ctx.lineWidth = this.options.gridLines.lineWidth;
this.ctx.strokeStyle = this.options.gridLines.color;
setContextLineSettings = false;
}
// if (tick === 0 || (!hasZero && index === 0)) {
// // Draw the 0 point specially or the left if there is no 0
// this.ctx.lineWidth = this.options.gridLines.zeroLineWidth;
// this.ctx.strokeStyle = this.options.gridLines.zeroLineColor;
// setContextLineSettings = true; // reset next time
// } else if (setContextLineSettings) {
// this.ctx.lineWidth = this.options.gridLines.lineWidth;
// this.ctx.strokeStyle = this.options.gridLines.color;
// setContextLineSettings = false;
// }
xValue += helpers.aliasPixel(this.ctx.lineWidth);
// xValue += helpers.aliasPixel(this.ctx.lineWidth);
// Draw the label area
this.ctx.beginPath();
// // Draw the label area
// this.ctx.beginPath();
if (this.options.gridLines.drawTicks) {
this.ctx.moveTo(xValue, yTickStart);
this.ctx.lineTo(xValue, yTickEnd);
}
// if (this.options.gridLines.drawTicks) {
// this.ctx.moveTo(xValue, yTickStart);
// this.ctx.lineTo(xValue, yTickEnd);
// }
// Draw the chart area
if (this.options.gridLines.drawOnChartArea) {
this.ctx.moveTo(xValue, chartArea.top);
this.ctx.lineTo(xValue, chartArea.bottom);
}
// // Draw the chart area
// if (this.options.gridLines.drawOnChartArea) {
// this.ctx.moveTo(xValue, chartArea.top);
// this.ctx.lineTo(xValue, chartArea.bottom);
// }
// Need to stroke in the loop because we are potentially changing line widths & colours
this.ctx.stroke();
}, this);
}
// // Need to stroke in the loop because we are potentially changing line widths & colours
// this.ctx.stroke();
// }, this);
// }
if (this.options.ticks.show) {
// Draw the ticks
// if (this.options.ticks.show) {
// // Draw the ticks
var labelStartY;
// var labelStartY;
if (this.options.position == "top") {
labelStartY = this.bottom - 10;
this.ctx.textBaseline = "bottom";
} else {
// bottom side
labelStartY = this.top + 10;
this.ctx.textBaseline = "top";
}
// if (this.options.position == "top") {
// labelStartY = this.bottom - 10;
// this.ctx.textBaseline = "bottom";
// } else {
// // bottom side
// labelStartY = this.top + 10;
// this.ctx.textBaseline = "top";
// }
this.ctx.textAlign = "center";
this.ctx.font = helpers.fontString(this.options.ticks.fontSize, this.options.ticks.fontStyle, this.options.ticks.fontFamily);
// this.ctx.textAlign = "center";
// this.ctx.font = helpers.fontString(this.options.ticks.fontSize, this.options.ticks.fontStyle, this.options.ticks.fontFamily);
helpers.each(this.ticks, function(label, index) {
var xValue = this.getPixelForValue(this.ticks[index]);
this.ctx.fillText(label, xValue, labelStartY);
}, this);
}
} else {
// Vertical
if (this.options.gridLines.show) {
// helpers.each(this.ticks, function(label, index) {
// var xValue = this.getPixelForValue(this.ticks[index]);
// this.ctx.fillText(label, xValue, labelStartY);
// }, this);
// }
// } else {
// // Vertical
// if (this.options.gridLines.show) {
// Draw the vertical line
setContextLineSettings = true;
hasZero = helpers.findNextWhere(this.ticks, function(tick) {
return tick === 0;
}) !== undefined;
var xTickStart = this.options.position == "right" ? this.left : this.right - 5;
var xTickEnd = this.options.position == "right" ? this.left + 5 : this.right;
// // Draw the vertical line
// setContextLineSettings = true;
// hasZero = helpers.findNextWhere(this.ticks, function(tick) {
// return tick === 0;
// }) !== undefined;
// var xTickStart = this.options.position == "right" ? this.left : this.right - 5;
// var xTickEnd = this.options.position == "right" ? this.left + 5 : this.right;
helpers.each(this.ticks, function(tick, index) {
// Grid lines are horizontal
var yValue = this.getPixelForValue(tick);
// helpers.each(this.ticks, function(tick, index) {
// // Grid lines are horizontal
// var yValue = this.getPixelForValue(tick);
if (tick === 0 || (!hasZero && index === 0)) {
// Draw the 0 point specially or the bottom if there is no 0
this.ctx.lineWidth = this.options.gridLines.zeroLineWidth;
this.ctx.strokeStyle = this.options.gridLines.zeroLineColor;
setContextLineSettings = true; // reset next time
} else if (setContextLineSettings) {
this.ctx.lineWidth = this.options.gridLines.lineWidth;
this.ctx.strokeStyle = this.options.gridLines.color;
setContextLineSettings = false; // use boolean to indicate that we only want to do this once
}
// if (tick === 0 || (!hasZero && index === 0)) {
// // Draw the 0 point specially or the bottom if there is no 0
// this.ctx.lineWidth = this.options.gridLines.zeroLineWidth;
// this.ctx.strokeStyle = this.options.gridLines.zeroLineColor;
// setContextLineSettings = true; // reset next time
// } else if (setContextLineSettings) {
// this.ctx.lineWidth = this.options.gridLines.lineWidth;
// this.ctx.strokeStyle = this.options.gridLines.color;
// setContextLineSettings = false; // use boolean to indicate that we only want to do this once
// }
yValue += helpers.aliasPixel(this.ctx.lineWidth);
// yValue += helpers.aliasPixel(this.ctx.lineWidth);
// Draw the label area
this.ctx.beginPath();
// // Draw the label area
// this.ctx.beginPath();
if (this.options.gridLines.drawTicks) {
this.ctx.moveTo(xTickStart, yValue);
this.ctx.lineTo(xTickEnd, yValue);
}
// if (this.options.gridLines.drawTicks) {
// this.ctx.moveTo(xTickStart, yValue);
// this.ctx.lineTo(xTickEnd, yValue);
// }
// Draw the chart area
if (this.options.gridLines.drawOnChartArea) {
this.ctx.moveTo(chartArea.left, yValue);
this.ctx.lineTo(chartArea.right, yValue);
}
// // Draw the chart area
// if (this.options.gridLines.drawOnChartArea) {
// this.ctx.moveTo(chartArea.left, yValue);
// this.ctx.lineTo(chartArea.right, yValue);
// }
this.ctx.stroke();
}, this);
}
// this.ctx.stroke();
// }, this);
// }
if (this.options.ticks.show) {
// Draw the ticks
// if (this.options.ticks.show) {
// // Draw the ticks
var labelStartX;
// var labelStartX;
if (this.options.position == "left") {
if (this.options.ticks.mirror) {
labelStartX = this.right + this.options.ticks.padding;
this.ctx.textAlign = "left";
} else {
labelStartX = this.right - this.options.ticks.padding;
this.ctx.textAlign = "right";
}
} else {
// right side
if (this.options.ticks.mirror) {
labelStartX = this.left - this.options.ticks.padding;
this.ctx.textAlign = "right";
} else {
labelStartX = this.left + this.options.ticks.padding;
this.ctx.textAlign = "left";
}
}
// if (this.options.position == "left") {
// if (this.options.ticks.mirror) {
// labelStartX = this.right + this.options.ticks.padding;
// this.ctx.textAlign = "left";
// } else {
// labelStartX = this.right - this.options.ticks.padding;
// this.ctx.textAlign = "right";
// }
// } else {
// // right side
// if (this.options.ticks.mirror) {
// labelStartX = this.left - this.options.ticks.padding;
// this.ctx.textAlign = "right";
// } else {
// labelStartX = this.left + this.options.ticks.padding;
// this.ctx.textAlign = "left";
// }
// }
this.ctx.textBaseline = "middle";
this.ctx.font = helpers.fontString(this.options.ticks.fontSize, this.options.ticks.fontStyle, this.options.ticks.fontFamily);
// this.ctx.textBaseline = "middle";
// this.ctx.font = helpers.fontString(this.options.ticks.fontSize, this.options.ticks.fontStyle, this.options.ticks.fontFamily);
helpers.each(this.ticks, function(label, index) {
var yValue = this.getPixelForValue(this.ticks[index]);
this.ctx.fillText(label, labelStartX, yValue);
}, this);
}
}
}
}
// helpers.each(this.ticks, function(label, index) {
// var yValue = this.getPixelForValue(this.ticks[index]);
// this.ctx.fillText(label, labelStartX, yValue);
// }, this);
// }
// }
// }
// }
});
Chart.scaleService.registerScaleType("linear", LinearScale, defaultConfig);

View File

@ -58,41 +58,14 @@
};
var defaultConfig = {
display: true,
position: "bottom",
// grid line settings
gridLines: {
show: true,
color: "rgba(0, 0, 0, 0.1)",
lineWidth: 1,
drawOnChartArea: true,
drawTicks: true, // draw ticks extending towards the label
},
tick: {
time: {
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.
round: false, // none, or override with week, month, year, etc.
displayFormat: false, // defaults to unit's corresponding unitFormat below or override using pattern string from http://momentjs.com/docs/#/displaying/format/
},
// scale numbers
reverse: false,
override: null,
// label settings
ticks: {
show: true,
mirror: false,
padding: 10,
template: "<%=value.toLocaleString()%>",
fontSize: 12,
fontStyle: "normal",
fontColor: "#666",
fontFamily: "Helvetica Neue",
maxRotation: 45,
}
};
var TimeScale = Chart.Scale.extend({
@ -106,11 +79,11 @@
return label;
}
// Custom parsing (return an instance of moment)
if (typeof this.options.tick.format !== 'string' && this.options.tick.format.call) {
return this.options.tick.format(label);
if (typeof this.options.time.format !== 'string' && this.options.time.format.call) {
return this.options.time.format(label);
}
// Moment format parsing
return moment(label, this.options.tick.format);
return moment(label, this.options.time.format);
},
generateTicks: function(index) {
@ -120,8 +93,8 @@
// Parse each label into a moment
this.data.labels.forEach(function(label, index) {
var labelMoment = this.parseTime(label);
if (this.options.tick.round) {
labelMoment.startOf(this.options.tick.round);
if (this.options.time.round) {
labelMoment.startOf(this.options.time.round);
}
this.labelMoments.push(labelMoment);
}, this);
@ -131,15 +104,15 @@
this.lastTick = moment.max.call(this, this.labelMoments).clone();
// Set unit override if applicable
if (this.options.tick.unit) {
this.tickUnit = this.options.tick.unit || 'day';
if (this.options.time.unit) {
this.tickUnit = this.options.time.unit || 'day';
this.displayFormat = time.unit.day.display;
this.tickRange = Math.ceil(this.lastTick.diff(this.firstTick, this.tickUnit, true));
} else {
// Determine the smallest needed unit of the time
var innerWidth = this.width - (this.paddingLeft + this.paddingRight);
var labelCapacity = innerWidth / this.options.ticks.fontSize + 4;
var buffer = this.options.tick.round ? 0 : 2;
var buffer = this.options.time.round ? 0 : 2;
this.tickRange = Math.ceil(this.lastTick.diff(this.firstTick, true) + buffer);
var done;
@ -167,8 +140,8 @@
// Tick displayFormat override
if (this.options.tick.displayFormat) {
this.displayFormat = this.options.tick.displayFormat;
if (this.options.time.displayFormat) {
this.displayFormat = this.options.time.displayFormat;
}
// For every unit in between the first and last moment, create a moment and add it to the ticks tick
@ -177,7 +150,7 @@
this.ticks.push(
this.options.ticks.userCallback(this.firstTick.clone()
.add(i, this.tickUnit)
.format(this.options.tick.displayFormat ? this.options.tick.displayFormat : time.unit[this.tickUnit].display)
.format(this.options.time.displayFormat ? this.options.time.displayFormat : time.unit[this.tickUnit].display)
)
);
}
@ -185,14 +158,20 @@
for (i = 0; i <= this.tickRange; i++) {
this.ticks.push(this.firstTick.clone()
.add(i, this.tickUnit)
.format(this.options.tick.displayFormat ? this.options.tick.displayFormat : time.unit[this.tickUnit].display)
.format(this.options.time.displayFormat ? this.options.time.displayFormat : time.unit[this.tickUnit].display)
);
}
}
},
getPixelForValue: function(value, decimal, datasetIndex, includeOffset) {
getSmallestDataDistance: function() {
return this.smallestLabelSeparation;
},
getPixelForValue: function(value, datasetIndex, includeOffset) {
// This must be called after fit has been run so that
// this.left, this.top, this.right, and this.bottom have been defined
var decimal = 0.5;
if (this.isHorizontal()) {
var innerWidth = this.width - (this.paddingLeft + this.paddingRight);
var valueWidth = innerWidth / Math.max(this.ticks.length - 1, 1);
@ -203,46 +182,46 @@
return this.top + (decimal * (this.height / this.ticks.length));
}
},
getPointPixelForValue: function(value, index, datasetIndex) {
// getPointPixelForValue: function(value, index, datasetIndex) {
var offset = this.labelMoments[index].diff(this.firstTick, this.tickUnit, true);
return this.getPixelForValue(value, offset / this.tickRange, datasetIndex);
},
// var offset = this.labelMoments[index].diff(this.firstTick, this.tickUnit, true);
// return this.getPixelForValue(value, offset / this.tickRange, datasetIndex);
// },
// Functions needed for bar charts
calculateBaseWidth: function() {
// // Functions needed for bar charts
// calculateBaseWidth: function() {
var base = this.getPixelForValue(null, this.smallestLabelSeparation / this.tickRange, 0, true) - this.getPixelForValue(null, 0, 0, true);
var spacing = 2 * this.options.categorySpacing;
if (base < spacing * 2) {
var mod = Math.min((spacing * 2) / base, 1.5);
base = (base / 2) * mod;
return base;
}
return base - spacing;
},
calculateBarWidth: function(barDatasetCount) {
//The padding between datasets is to the right of each bar, providing that there are more than 1 dataset
var baseWidth = this.calculateBaseWidth() - ((barDatasetCount - 1) * this.options.spacing);
// var base = this.getPixelForValue(null, this.smallestLabelSeparation / this.tickRange, 0, true) - this.getPixelForValue(null, 0, 0, true);
// var spacing = 2 * this.options.categorySpacing;
// if (base < spacing * 2) {
// var mod = Math.min((spacing * 2) / base, 1.5);
// base = (base / 2) * mod;
// return base;
// }
// return base - spacing;
// },
// calculateBarWidth: function(barDatasetCount) {
// //The padding between datasets is to the right of each bar, providing that there are more than 1 dataset
// var baseWidth = this.calculateBaseWidth() - ((barDatasetCount - 1) * this.options.spacing);
if (this.options.stacked) {
return Math.max(baseWidth, 1);
}
return Math.max((baseWidth / barDatasetCount), 1);
},
calculateBarX: function(barDatasetCount, datasetIndex, elementIndex) {
// if (this.options.stacked) {
// return Math.max(baseWidth, 1);
// }
// return Math.max((baseWidth / barDatasetCount), 1);
// },
// calculateBarX: function(barDatasetCount, datasetIndex, elementIndex) {
var xWidth = this.calculateBaseWidth(),
offset = this.labelMoments[elementIndex].diff(this.firstTick, this.tickUnit, true),
xAbsolute = this.getPixelForValue(null, offset / this.tickRange, datasetIndex, true) - (xWidth / 2),
barWidth = this.calculateBarWidth(barDatasetCount);
// var xWidth = this.calculateBaseWidth(),
// offset = this.labelMoments[elementIndex].diff(this.firstTick, this.tickUnit, true),
// xAbsolute = this.getPixelForValue(null, offset / this.tickRange, datasetIndex, true) - (xWidth / 2),
// barWidth = this.calculateBarWidth(barDatasetCount);
if (this.options.stacked) {
return xAbsolute + barWidth / 2;
}
// if (this.options.stacked) {
// return xAbsolute + barWidth / 2;
// }
return xAbsolute + (barWidth * datasetIndex) + (datasetIndex * this.options.spacing) + barWidth / 2;
},
// return xAbsolute + (barWidth * datasetIndex) + (datasetIndex * this.options.spacing) + barWidth / 2;
// },
// calculateTickRotation: function(maxHeight, margins) {
// //Get the width of each grid by calculating the difference