From: Tanner Linsley Date: Thu, 24 Sep 2015 03:52:31 +0000 (-0600) Subject: Stuck on shared vertical scale draw logic X-Git-Tag: 2.0.0-alpha4~12^2~25 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=c7107677d2aa690f677868436b8bf3f8f10f60fa;p=thirdparty%2FChart.js.git Stuck on shared vertical scale draw logic Trying to make all scales share the same draw function for both horizontal and vertical. For some reason the vertical linear scale on line.html has a width that is too small --- 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; + + }, + 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); @@ -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 - 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); @@ -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 - - 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") { @@ -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; - - 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); 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