Merge pull request #1794 from nnnick/update-scale-lifecycle

Update Scale Lifcycle
This commit is contained in:
Evert Timberg
2015-12-19 14:29:49 -05:00
10 changed files with 146 additions and 83 deletions

View File

@@ -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.

View File

@@ -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() {},

View File

@@ -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);

View File

@@ -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() {

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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';

View File

@@ -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

View File

@@ -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