diff --git a/samples/line.html b/samples/line.html index 192702888..c0f73445e 100644 --- a/samples/line.html +++ b/samples/line.html @@ -6,9 +6,9 @@ @@ -26,135 +26,134 @@

Legend

-
diff --git a/src/controllers/controller.bar.js b/src/controllers/controller.bar.js index 80b0ff9d6..c9b80ddd4 100644 --- a/src/controllers/controller.bar.js +++ b/src/controllers/controller.bar.js @@ -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) { diff --git a/src/core/core.controller.js b/src/core/core.controller.js index 614b0579d..b87dc5121 100644 --- a/src/core/core.controller.js +++ b/src/core/core.controller.js @@ -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); diff --git a/src/core/core.scale.js b/src/core/core.scale.js index b9fdbbb47..88bc715c7 100644 --- a/src/core/core.scale.js +++ b/src/core/core.scale.js @@ -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); } } } diff --git a/src/core/core.scaleService.js b/src/core/core.scaleService.js index 75b76ff66..4b8c74483 100644 --- a/src/core/core.scaleService.js +++ b/src/core/core.scaleService.js @@ -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); } }); diff --git a/src/scales/scale.category.js b/src/scales/scale.category.js index 6a305193b..52334ee80 100644 --- a/src/scales/scale.category.js +++ b/src/scales/scale.category.js @@ -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); diff --git a/src/scales/scale.linear.js b/src/scales/scale.linear.js index 5d3263d88..7209a6605 100644 --- a/src/scales/scale.linear.js +++ b/src/scales/scale.linear.js @@ -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); diff --git a/src/scales/scale.time.js b/src/scales/scale.time.js index 5952f15ce..26cb67739 100644 --- a/src/scales/scale.time.js +++ b/src/scales/scale.time.js @@ -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