From: Evert Timberg Date: Thu, 17 Dec 2015 14:21:04 +0000 (-0500) Subject: Split out data limits step of scales into it's own step. Wire up callbacks from the... X-Git-Tag: 2.0.0-beta2~15^2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=refs%2Fpull%2F1794%2Fhead;p=thirdparty%2FChart.js.git Split out data limits step of scales into it's own step. Wire up callbacks from the options to those in the scale life-cycle. Updated the docs accordingly --- diff --git a/docs/01-Scales.md b/docs/01-Scales.md index 5a4fcc63e..06f406a43 100644 --- a/docs/01-Scales.md +++ b/docs/01-Scales.md @@ -16,6 +16,20 @@ Name | Type | Default | Description --- |:---:| --- | --- type | String | Chart specific. | Type of scale being employed. Custom scales can be created and registered with a string key. Options: ["category"](#scales-category-scale), ["linear"](#scales-linear-scale), ["logarithmic"](#scales-logarithmic-scale), ["time"](#scales-time-scale), ["radialLinear"](#scales-radial-linear-scale) display | Boolean | true | If true, show the scale including gridlines, ticks, and labels. Overrides *gridLines.display*, *scaleLabel.display*, and *ticks.display*. +beforeUpdate | Function | undefined | Callback called before the update process starts. Passed a single argument, the scale instance. +beforeSetDimensions | Function | undefined | Callback that runs before dimensions are set. Passed a single argument, the scale instance. +afterSetDimensions | Function | undefined | Callback that runs after dimensions are set. Passed a single argument, the scale instance. +beforeDataLimits | Function | undefined | Callback that runs before data limits are determined. Passed a single argument, the scale instance. +afterDataLimits | Function | undefined | Callback that runs after data limits are determined. Passed a single argument, the scale instance. +beforeBuildTicks | Function | undefined | Callback that runs before ticks are created. Passed a single argument, the scale instance. +afterBuildTicks | Function | undefined | Callback that runs after ticks are created. Useful for filtering ticks. Passed a single argument, the scale instance. +beforeTickToLabelConversion | Function | undefined | Callback that runs before ticks are converted into strings. Passed a single argument, the scale instance. +afterTickToLabelConversion | Function | undefined | Callback that runs after ticks are converted into strings. Passed a single argument, the scale instance. +beforeCalculateTickRotation | Function | undefined | Callback that runs before tick rotation is determined. Passed a single argument, the scale instance. +afterCalculateTickRotation | Function | undefined | Callback that runs after tick rotation is determined. Passed a single argument, the scale instance. +beforeFit | Function | undefined | Callback that runs before the scale fits to the canvas. Passed a single argument, the scale instance. +afterFit | Function | undefined | Callback that runs after the scale fits to the canvas. Passed a single argument, the scale instance. +afterUpdate | Function | undefined | Callback that runs at the end of the update process. Passed a single argument, the scale instance. **gridLines** | Array | - | Options for the grid lines that run perpendicular to the axis. *gridLines*.display | Boolean | true | *gridLines*.color | Color | "rgba(0, 0, 0, 0.1)" | Color of the grid lines. diff --git a/docs/07-Advanced.md b/docs/07-Advanced.md index d040d4584..f2ab317ad 100644 --- a/docs/07-Advanced.md +++ b/docs/07-Advanced.md @@ -229,6 +229,9 @@ To work with Chart.js, custom scale types must implement the following interface ```javascript { + // Determines the data limits. Should set this.min and this.max to be the data max/min + determineDataLimits: function() {}, + // Generate tick marks. this.chart is the chart instance. The data object can be accessed as this.chart.data // buildTicks() should create a ticks array on the scale instance, if you intend to use any of the implementations from the base class buildTicks: function() {}, diff --git a/src/core/core.helpers.js b/src/core/core.helpers.js index 177397a20..19c242b84 100644 --- a/src/core/core.helpers.js +++ b/src/core/core.helpers.js @@ -848,4 +848,10 @@ isDatasetVisible = helpers.isDatasetVisible = function(dataset) { return !dataset.hidden; }; + + helpers.callCallback = function(fn, args, _tArg) { + if (fn && typeof fn.call === 'function') { + fn.apply(_tArg, args); + } + } }).call(this); diff --git a/src/core/core.scale.js b/src/core/core.scale.js index 6f15033e6..f8b634230 100644 --- a/src/core/core.scale.js +++ b/src/core/core.scale.js @@ -58,7 +58,9 @@ // Any function defined here is inherited by all scale types. // Any function can be extended by the scale type - beforeUpdate: helpers.noop, + beforeUpdate: function() { + helpers.callCallback(this.options.beforeUpdate, [this]); + }, update: function(maxWidth, maxHeight, margins) { // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;) @@ -78,6 +80,12 @@ this.beforeSetDimensions(); this.setDimensions(); this.afterSetDimensions(); + + // Data min/max + this.beforeDataLimits(); + this.determineDataLimits(); + this.afterDataLimits(); + // Ticks this.beforeBuildTicks(); this.buildTicks(); @@ -101,11 +109,15 @@ return this.minSize; }, - afterUpdate: helpers.noop, + afterUpdate: function() { + helpers.callCallback(this.options.afterUpdate, [this]); + }, // - beforeSetDimensions: helpers.noop, + beforeSetDimensions: function() { + helpers.callCallback(this.options.beforeSetDimensions, [this]); + }, setDimensions: function() { // Set the unconstrained dimension before label rotation if (this.isHorizontal()) { @@ -127,15 +139,31 @@ this.paddingRight = 0; this.paddingBottom = 0; }, - afterSetDimensions: helpers.noop, + afterSetDimensions: function() { + helpers.callCallback(this.options.afterSetDimensions, [this]); + }, - // + // Data limits + beforeDataLimits: function() { + helpers.callCallback(this.options.beforeDataLimits, [this]); + }, + determineDataLimits: helpers.noop, + afterDataLimits: function() { + helpers.callCallback(this.options.afterDataLimits, [this]); + }, - beforeBuildTicks: helpers.noop, + // + beforeBuildTicks: function() { + helpers.callCallback(this.options.beforeBuildTicks, [this]); + }, buildTicks: helpers.noop, - afterBuildTicks: helpers.noop, + afterBuildTicks: function() { + helpers.callCallback(this.options.afterBuildTicks, [this]); + }, - beforeTickToLabelConversion: helpers.noop, + beforeTickToLabelConversion: function() { + helpers.callCallback(this.options.beforeTickToLabelConversion, [this]); + }, convertTicksToLabels: function() { // Convert ticks to strings this.ticks = this.ticks.map(function(numericalTick, index, ticks) { @@ -146,11 +174,15 @@ }, this); }, - afterTickToLabelConversion: helpers.noop, + afterTickToLabelConversion: function() { + helpers.callCallback(this.options.afterTickToLabelConversion, [this]); + }, // - beforeCalculateTickRotation: helpers.noop, + beforeCalculateTickRotation: function() { + helpers.callCallback(this.options.beforeCalculateTickRotation, [this]); + }, calculateTickRotation: function() { //Get the width of each grid by calculating the difference //between x offsets between 0 and 1. @@ -218,11 +250,15 @@ this.paddingRight = Math.max(this.paddingRight, 0); } }, - afterCalculateTickRotation: helpers.noop, + afterCalculateTickRotation: function() { + helpers.callCallback(this.options.afterCalculateTickRotation, [this]); + }, // - beforeFit: helpers.noop, + beforeFit: function() { + helpers.callCallback(this.options.beforeFit, [this]); + }, fit: function() { this.minSize = { @@ -319,7 +355,9 @@ this.height = this.minSize.height; }, - afterFit: helpers.noop, + afterFit: function() { + helpers.callCallback(this.options.afterFit, [this]); + }, // Shared Methods isHorizontal: function() { diff --git a/src/scales/scale.linear.js b/src/scales/scale.linear.js index e21d10225..d453b2c7e 100644 --- a/src/scales/scale.linear.js +++ b/src/scales/scale.linear.js @@ -36,8 +36,7 @@ }; var LinearScale = Chart.Scale.extend({ - buildTicks: function() { - + determineDataLimits: function() { // First Calculate the range this.min = null; this.max = null; @@ -114,32 +113,6 @@ }, this); } - // Then calulate the ticks - this.ticks = []; - - // 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(this.options.ticks.maxTicksLimit ? this.options.ticks.maxTicksLimit : 11, - Math.ceil(this.width / 50)); - } else { - // The factor of 2 used to scale the font size has been experimentally determined. - maxTicks = Math.min(this.options.ticks.maxTicksLimit ? this.options.ticks.maxTicksLimit : 11, - 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 @@ -172,6 +145,34 @@ this.min--; this.max++; } + }, + buildTicks: function() { + + // Then calulate the ticks + this.ticks = []; + + // 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(this.options.ticks.maxTicksLimit ? this.options.ticks.maxTicksLimit : 11, + Math.ceil(this.width / 50)); + } else { + // The factor of 2 used to scale the font size has been experimentally determined. + maxTicks = Math.min(this.options.ticks.maxTicksLimit ? this.options.ticks.maxTicksLimit : 11, + 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. var niceRange = helpers.niceNum(this.max - this.min, false); var spacing = helpers.niceNum(niceRange / (maxTicks - 1), true); diff --git a/src/scales/scale.logarithmic.js b/src/scales/scale.logarithmic.js index 0de930ae6..10b406c18 100644 --- a/src/scales/scale.logarithmic.js +++ b/src/scales/scale.logarithmic.js @@ -23,10 +23,8 @@ }; var LogarithmicScale = Chart.Scale.extend({ - buildTicks: function() { - - // Calculate Range (we may break this out into it's own lifecycle function) - + determineDataLimits: function() { + // Calculate Range this.min = null; this.max = null; @@ -102,8 +100,8 @@ this.max = 10; } } - - + }, + buildTicks: function() { // 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.tickValues = []; @@ -129,19 +127,6 @@ tickVal = significand * Math.pow(10, exp); } - /*var minExponent = Math.floor(helpers.log10(this.min)); - var maxExponent = Math.ceil(helpers.log10(this.max)); - - for (var exponent = minExponent; exponent < maxExponent; ++exponent) { - for (var i = 1; i < 10; ++i) { - if (this.options.ticks.min) { - - } else { - this.tickValues.push(i * Math.pow(10, exponent)); - } - } - }*/ - var lastTick = this.options.ticks.max !== undefined ? this.options.ticks.max : tickVal; this.tickValues.push(lastTick); diff --git a/src/scales/scale.radialLinear.js b/src/scales/scale.radialLinear.js index d9f327cf5..77eb9a790 100644 --- a/src/scales/scale.radialLinear.js +++ b/src/scales/scale.radialLinear.js @@ -63,7 +63,7 @@ var minSize = helpers.min([this.height, this.width]); this.drawingArea = (this.options.display) ? (minSize / 2) - (this.options.ticks.fontSize / 2 + this.options.ticks.backdropPaddingY) : (minSize / 2); }, - buildTicks: function() { + determineDataLimits: function() { this.min = null; this.max = null; @@ -95,20 +95,6 @@ this.max++; } - this.ticks = []; - - // 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 = Math.min(this.options.ticks.maxTicksLimit ? this.options.ticks.maxTicksLimit : 11, - Math.ceil(this.drawingArea / (1.5 * this.options.ticks.fontSize))); - maxTicks = Math.max(2, maxTicks); // Make sure we always have at least 2 ticks - - // To get a "nice" value for the tick spacing, we will use the appropriately named - // "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 @@ -124,6 +110,23 @@ this.min = 0; } } + }, + buildTicks: function() { + + + this.ticks = []; + + // 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 = Math.min(this.options.ticks.maxTicksLimit ? this.options.ticks.maxTicksLimit : 11, + Math.ceil(this.drawingArea / (1.5 * this.options.ticks.fontSize))); + maxTicks = Math.max(2, maxTicks); // Make sure we always have at least 2 ticks + + // To get a "nice" value for the tick spacing, we will use the appropriately named + // "nice number" algorithm. See http://stackoverflow.com/questions/8506881/nice-label-algorithm-for-charts-with-minimum-ticks + // for details. var niceRange = helpers.niceNum(this.max - this.min, false); var spacing = helpers.niceNum(niceRange / (maxTicks - 1), true); diff --git a/src/scales/scale.time.js b/src/scales/scale.time.js index 8c957beb0..a1ce6a35e 100644 --- a/src/scales/scale.time.js +++ b/src/scales/scale.time.js @@ -71,8 +71,9 @@ getLabelMoment: function(datasetIndex, index) { return this.labelMoments[datasetIndex][index]; }, - - buildLabelMoments: function() { + determineDataLimits: function() { + this.labelMoments = []; + // Only parse these once. If the dataset does not have data as x,y pairs, we will use // these var scaleLabelMoments = []; @@ -128,15 +129,11 @@ this.firstTick = this.firstTick.clone(); this.lastTick = this.lastTick.clone(); }, - buildTicks: function(index) { this.ticks = []; - this.labelMoments = []; this.unitScale = 1; // How much we scale the unit by, ie 2 means 2x unit per step - this.buildLabelMoments(); - // Set unit override if applicable if (this.options.time.unit) { this.tickUnit = this.options.time.unit || 'day'; diff --git a/test/scale.linear.tests.js b/test/scale.linear.tests.js index ef1b2cbb9..65b7444da 100644 --- a/test/scale.linear.tests.js +++ b/test/scale.linear.tests.js @@ -82,7 +82,7 @@ describe('Linear Scale', function() { scale.width = 50; scale.height = 400; - scale.buildTicks(); + scale.determineDataLimits(); expect(scale.min).toBe(-100); expect(scale.max).toBe(150); }); @@ -121,7 +121,7 @@ describe('Linear Scale', function() { scale.width = 50; scale.height = 400; - scale.buildTicks(); + scale.determineDataLimits(); expect(scale.min).toBe(-100); expect(scale.max).toBe(150); }); @@ -161,6 +161,7 @@ describe('Linear Scale', function() { scale.width = 50; scale.height = 400; + scale.determineDataLimits(); scale.buildTicks(); expect(scale.min).toBe(-100); expect(scale.max).toBe(80); @@ -204,6 +205,7 @@ describe('Linear Scale', function() { verticalScale.width = 50; verticalScale.height = 400; + verticalScale.determineDataLimits(); verticalScale.buildTicks(); expect(verticalScale.min).toBe(0); expect(verticalScale.max).toBe(100); @@ -223,6 +225,7 @@ describe('Linear Scale', function() { horizontalScale.width = 400; horizontalScale.height = 50; + horizontalScale.determineDataLimits(); horizontalScale.buildTicks(); expect(horizontalScale.min).toBe(-20); expect(horizontalScale.max).toBe(100); @@ -267,6 +270,7 @@ describe('Linear Scale', function() { scale.width = 50; scale.height = 400; + scale.determineDataLimits(); scale.buildTicks(); expect(scale.min).toBe(-150); expect(scale.max).toBe(200); @@ -309,6 +313,7 @@ describe('Linear Scale', function() { scale.width = 50; scale.height = 400; + scale.determineDataLimits(); scale.buildTicks(); expect(scale.min).toBe(-150); expect(scale.max).toBe(200); @@ -336,6 +341,7 @@ describe('Linear Scale', function() { scale.width = 50; scale.height = 400; + scale.determineDataLimits(); scale.buildTicks(); expect(scale.min).toBe(-1); expect(scale.max).toBe(1); @@ -369,6 +375,7 @@ describe('Linear Scale', function() { scale.width = 50; scale.height = 400; + scale.determineDataLimits(); scale.buildTicks(); expect(scale.min).toBe(-10); expect(scale.max).toBe(10); @@ -402,6 +409,7 @@ describe('Linear Scale', function() { scale.width = 50; scale.height = 400; + scale.determineDataLimits(); scale.buildTicks(); expect(scale.min).toBe(-1010); expect(scale.max).toBe(1010); @@ -436,18 +444,22 @@ describe('Linear Scale', function() { scale.width = 50; scale.height = 400; + scale.determineDataLimits(); scale.buildTicks(); expect(scale.ticks).toEqual([50, 45, 40, 35, 30, 25, 20]); config.ticks.beginAtZero = true; + scale.determineDataLimits(); scale.buildTicks(); expect(scale.ticks).toEqual([50, 45, 40, 35, 30, 25, 20, 15, 10, 5, 0]); mockData.datasets[0].data = [-20, -30, -40, -50]; + scale.determineDataLimits(); scale.buildTicks(); expect(scale.ticks).toEqual([0, -5, -10, -15, -20, -25, -30, -35, -40, -45, -50]); config.ticks.beginAtZero = false; + scale.determineDataLimits(); scale.buildTicks(); expect(scale.ticks).toEqual([-20, -25, -30, -35, -40, -45, -50]); }); @@ -477,6 +489,7 @@ describe('Linear Scale', function() { scale.width = 50; scale.height = 400; + scale.determineDataLimits(); scale.buildTicks(); // Counts down because the lines are drawn top to bottom @@ -511,6 +524,7 @@ describe('Linear Scale', function() { scale.width = 50; scale.height = 400; + scale.determineDataLimits(); scale.buildTicks(); // Reverse mode makes this count up diff --git a/test/scale.logarithmic.tests.js b/test/scale.logarithmic.tests.js index 1f05388c6..2a9400852 100644 --- a/test/scale.logarithmic.tests.js +++ b/test/scale.logarithmic.tests.js @@ -389,6 +389,7 @@ describe('Logarithmic Scale tests', function() { scale.width = 50; scale.height = 400; + scale.determineDataLimits(); scale.buildTicks(); // Counts down because the lines are drawn top to bottom @@ -424,6 +425,7 @@ describe('Logarithmic Scale tests', function() { scale.width = 50; scale.height = 400; + scale.determineDataLimits(); scale.buildTicks(); // Counts down because the lines are drawn top to bottom