<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>
<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'
- }
+ 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;
- });
+ $.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);
+ console.log(config.data);
- window.onload = function() {
- var ctx = document.getElementById("canvas").getContext("2d");
- window.myLine = new Chart(ctx, config);
+ window.onload = function() {
+ var ctx = document.getElementById("canvas").getContext("2d");
+ window.myLine = new Chart(ctx, config);
- updateLegend();
- };
+ updateLegend();
+ };
- function updateLegend() {
- $legendContainer = $('#legendContainer');
- $legendContainer.empty();
- $legendContainer.append(window.myLine.generateLegend());
- }
+ 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();
+ });
- $('#randomizeData').click(function() {
- $.each(config.data.datasets, function(i, dataset) {
- dataset.data = dataset.data.map(function() {
- return randomScalingFactor();
});
+ window.myLine.update();
+ updateLegend();
});
- 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: [],
- };
+ $('#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());
+ }
- for (var index = 0; index < config.data.labels.length; ++index) {
- newDataset.data.push(randomScalingFactor());
- }
+ window.myLine.addDataset(newDataset);
+ updateLegend();
+ });
- window.myLine.addDataset(newDataset);
- updateLegend();
- });
+ $('#addData').click(function() {
+ if (config.data.datasets.length > 0) {
+ config.data.labels.push('dataset #' + config.data.labels.length);
- $('#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);
+ }
- for (var index = 0; index < config.data.datasets.length; ++index) {
- window.myLine.addData(randomScalingFactor(), index);
+ updateLegend();
}
+ });
+ $('#removeDataset').click(function() {
+ window.myLine.removeDataset(0);
updateLegend();
- }
- });
+ });
- $('#removeDataset').click(function() {
- window.myLine.removeDataset(0);
- updateLegend();
- });
+ $('#removeData').click(function() {
+ config.data.labels.splice(-1, 1); // remove the label first
- $('#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);
+ });
- config.data.datasets.forEach(function(dataset, datasetIndex) {
- window.myLine.removeData(datasetIndex, -1);
+ updateLegend();
});
-
- updateLegend();
- });
</script>
</body>
type: "category",
// Specific to Bar Controller
- categoryPercentage: 0.8,
- barPercentage: 0.9,
+ categoryPercentage: 0.75,
+ barPercentage: 0.5,
// grid line settings
gridLines: {
this.updateElement(rectangle, index, true, numBars);
this.getDataset().metaData.splice(index, 0, rectangle);
},
+
removeElement: function(index) {
this.getDataset().metaData.splice(index, 1);
},
},
update: function(reset) {
+
var numBars = this.getBarCount();
var numData = this.getDataset().data.length;
},
- 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) {
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;
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() {
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);
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);
}
}
}
(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);
};
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;
+
+ },
+ 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;
+ }
},
- calculateTickRotation: function(maxHeight, margins) {
+ 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);
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) {
this.paddingRight = this.options.ticks.fontSize / 2;
- if (sinRotation * originalLabelWidth > maxHeight) {
+ if (sinRotation * originalLabelWidth > this.maxHeight) {
// go back one step
this.labelRotation--;
break;
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
- 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;
+ afterCalculateTickRotation: helpers.noop,
- 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));
+ 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()) {
+ 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;
+ },
+ afterFit: helpers.noop,
+
+
+
+
+
+
+ // Shared Methods
+ isHorizontal: function() {
+ return this.options.position == "top" || this.options.position == "bottom";
},
- 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
+
+ // 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);
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;
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;
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)));
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) {
}
}, 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);
}
}
}
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
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) {
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;
chartWidth -= (2 * xPadding);
chartHeight -= (2 * yPadding);
-
// Step 2
var verticalScaleWidth = (width - chartWidth) / (leftScales.length + rightScales.length);
var minimumScaleSizes = [];
var verticalScaleMinSizeFunction = function(scaleInstance) {
- var minSize = scaleInstance.fit(verticalScaleWidth, chartHeight);
+ var minSize = scaleInstance.update(verticalScaleWidth, chartHeight);
minimumScaleSizes.push({
horizontal: false,
minSize: minSize,
};
var horizontalScaleMinSizeFunction = function(scaleInstance) {
- var minSize = scaleInstance.fit(chartWidth, horizontalScaleHeight);
+ var minSize = scaleInstance.update(chartWidth, horizontalScaleHeight);
minimumScaleSizes.push({
horizontal: true,
minSize: minSize,
});
if (wrapper) {
- scaleInstance.fit(wrapper.minSize.width, maxChartHeight);
+ scaleInstance.update(wrapper.minSize.width, maxChartHeight);
}
};
};
if (wrapper) {
- scaleInstance.fit(maxChartWidth, wrapper.minSize.height, scaleMargin);
+ scaleInstance.update(maxChartWidth, wrapper.minSize.height, scaleMargin);
}
};
};
if (wrapper) {
- scaleInstance.fit(wrapper.minSize.width, maxChartHeight, scaleMargin);
+ scaleInstance.update(wrapper.minSize.width, maxChartHeight, scaleMargin);
}
});
};
if (wrapper) {
- scaleInstance.fit(wrapper.minSize.width, maxChartHeight, scaleMargin);
+ scaleInstance.update(wrapper.minSize.width, maxChartHeight, scaleMargin);
}
});
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);
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
-
- var 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)));
- }
+ // 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)));
+ }
- // Make sure we always have at least 2 ticks
- maxTicks = Math.max(2, maxTicks);
-
- // 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);
+
+ // 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;
+ 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") {
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;
},
// Functions needed for line charts
- calculateRange: function() {
- this.min = null;
- this.max = null;
-
- 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) {
-
- var value = this.getRightValue(rawValue);
-
- 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);
-
- 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);
-
- 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.min === this.max) {
- this.min--;
- this.max++;
- }
- },
-
- getPointPixelForValue: function(rawValue, index, datasetIndex) {
- var value = this.getRightValue(rawValue);
-
- 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];
- }
- }
-
- if (value < 0) {
- return this.getPixelForValue(offsetNeg + value);
- } else {
- return this.getPixelForValue(offsetPos + value);
- }
- } else {
- return this.getPixelForValue(value);
- }
- },
+ // calculateRange: function() {
+ // this.min = null;
+ // this.max = null;
+
+ // 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) {
+
+ // var value = this.getRightValue(rawValue);
+
+ // 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);
+
+ // 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);
+
+ // 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.min === this.max) {
+ // this.min--;
+ // this.max++;
+ // }
+ // },
+
+ // getPointPixelForValue: function(rawValue, index, datasetIndex) {
+ // var value = this.getRightValue(rawValue);
+
+ // 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];
+ // }
+ // }
+
+ // 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;
-
- if (this.options.stacked) {
-
- 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;
- }
- }
- }
-
- return this.getPixelForValue(base);
- }
-
- 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);
- }
-
- return base;
-
- },
- calculateBarY: function(datasetIndex, index) {
- var value = this.data.datasets[datasetIndex].data[index];
-
- if (this.options.stacked) {
-
- 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;
- }
- }
-
- if (value < 0) {
- return this.getPixelForValue(sumNeg + value);
- } else {
- return this.getPixelForValue(sumPos + value);
- }
-
- return this.getPixelForValue(value);
- }
-
- return this.getPixelForValue(value);
- },
+ // calculateBarBase: function(datasetIndex, index) {
+ // var base = 0;
+
+ // if (this.options.stacked) {
+
+ // 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;
+ // }
+ // }
+ // }
+
+ // return this.getPixelForValue(base);
+ // }
+
+ // 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);
+ // }
+
+ // return base;
+
+ // },
+ // calculateBarY: function(datasetIndex, index) {
+ // var value = this.data.datasets[datasetIndex].data[index];
+
+ // if (this.options.stacked) {
+
+ // 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;
+ // }
+ // }
+
+ // if (value < 0) {
+ // return this.getPixelForValue(sumNeg + value);
+ // } else {
+ // return this.getPixelForValue(sumPos + 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);
-
- var 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
- // |
- // -|
- // |
- // |
- // -|
- // |
- // |
- // -|
-
-
- // Width
- if (this.isHorizontal()) {
- minSize.width = maxWidth; // fill all the width
- } else {
- 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
- }
-
- 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.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);
-
- 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;
-
- // 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);
-
- 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;
- }
-
- 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;
-
- 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;
- },
+ // fit: function() {
+
+ // 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
+ // // |
+ // // -|
+ // // |
+ // // |
+ // // -|
+ // // |
+ // // |
+ // // -|
+
+
+ // // 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()) {
+ // 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;
+
+
+ // 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 = 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 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 = this.maxWidth - this.minSize.width;
+ // var largestTextWidth = helpers.longestText(this.ctx, labelFont, this.ticks);
+
+ // 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;
+ // }
+ // }
+
+ // 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.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) {
-
- var setContextLineSettings;
- var hasZero;
-
- // 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;
-
- 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;
- }
-
- xValue += helpers.aliasPixel(this.ctx.lineWidth);
-
- // Draw the label area
- this.ctx.beginPath();
-
- 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);
- }
-
- // 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
-
- 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";
- }
-
- 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) {
-
- // 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);
-
- 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);
-
- // Draw the label area
- this.ctx.beginPath();
-
- 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);
- }
-
- this.ctx.stroke();
- }, this);
- }
-
- if (this.options.ticks.show) {
- // Draw the ticks
-
- 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";
- }
- }
-
- 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);
- }
- }
- }
- }
+ // draw: function(chartArea) {
+ // if (this.options.display) {
+
+ // var setContextLineSettings;
+ // var hasZero;
+
+ // // 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;
+
+ // 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;
+ // }
+
+ // xValue += helpers.aliasPixel(this.ctx.lineWidth);
+
+ // // Draw the label area
+ // this.ctx.beginPath();
+
+ // 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);
+ // }
+
+ // // 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
+
+ // 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";
+ // }
+
+ // 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) {
+
+ // // 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);
+
+ // 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);
+
+ // // Draw the label area
+ // this.ctx.beginPath();
+
+ // 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);
+ // }
+
+ // this.ctx.stroke();
+ // }, this);
+ // }
+
+ // if (this.options.ticks.show) {
+ // // Draw the ticks
+
+ // 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";
+ // }
+ // }
+
+ // 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);
+ // }
+ // }
+ // }
+ // }
});
Chart.scaleService.registerScaleType("linear", LinearScale, defaultConfig);
};
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({
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) {
// 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);
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;
// 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
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)
)
);
}
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);
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