Skip to content

Commit

Permalink
Add ticks.sampleSize option
Browse files Browse the repository at this point in the history
  • Loading branch information
benmccann committed Sep 19, 2019
1 parent 995efa5 commit a8f9603
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 31 deletions.
1 change: 1 addition & 0 deletions docs/axes/cartesian/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ The following options are common to all cartesian axes but do not apply to other
| ---- | ---- | ------- | -----------
| `min` | `number` | | User defined minimum value for the scale, overrides minimum value from data.
| `max` | `number` | | User defined maximum value for the scale, overrides maximum value from data.
| `sampleSize` | `number` | `ticks.length` | The number of ticks to examine when deciding how many labels will fit. Setting a smaller value will be faster, but may be less accurate when there is large variability in label length.
| `autoSkip` | `boolean` | `true` | If true, automatically calculates how many labels can be shown and hides labels accordingly. Labels will be rotated up to `maxRotation` before skipping any. Turn `autoSkip` off to show all labels no matter what.
| `autoSkipPadding` | `number` | `0` | Padding between the ticks on the horizontal axis when `autoSkip` is enabled.
| `labelOffset` | `number` | `0` | Distance in pixels to offset the label from the centre point of the tick (in the x direction for the x axis, and the y direction for the y axis). *Note: this can cause labels at the edges to be cropped by the edge of the canvas*
Expand Down
8 changes: 8 additions & 0 deletions docs/general/performance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Performance

Chart.js charts are rendered on `canvas` elements, which makes rendering quite fast. For large datasets or performance sensitive applications, you may wish to consider the tips below:

* Set `animation: { duration: 0 }` to disable [animations](../configuration/animations.md).
* For large datasets:
* You may wish to sample your data before providing it to Chart.js. E.g. if you have a data point for each day, you may find it more performant to pass in a data point for each week instead
* Set the [`ticks.sampleSize`](../axes/cartesian/README.md#tick-configuration) option in order to render axes more quickly
101 changes: 70 additions & 31 deletions src/core/core.scale.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,19 @@ defaults._set('scale', {
}
});

/** Returns a new array containing numItems from arr */
function sample(arr, numItems) {
var result = [];
var increment = arr.length / numItems;
var i = 0;
var len = arr.length;

for (; i < len; i += increment) {
result.push(arr[Math.floor(i)]);
}
return result;
}

function getPixelForGridLine(scale, index, offsetGridLines) {
var length = scale.getTicks().length;
var validIndex = Math.min(index, length - 1);
Expand Down Expand Up @@ -263,7 +276,8 @@ var Scale = Element.extend({
update: function(maxWidth, maxHeight, margins) {
var me = this;
var tickOpts = me.options.ticks;
var i, ilen, labels, label, ticks, tick;
var sampleSize = tickOpts.sampleSize;
var i, ilen, labels, ticks;

// Update Lifecycle - Probably don't want to ever extend or overwrite this function ;)
me.beforeUpdate();
Expand All @@ -278,6 +292,8 @@ var Scale = Element.extend({
bottom: 0
}, margins);

me._ticks = null;
me.ticks = null;
me._labelSizes = null;
me._maxLabelLines = 0;
me.longestLabelWidth = 0;
Expand Down Expand Up @@ -311,35 +327,23 @@ var Scale = Element.extend({
// Allow modification of ticks in callback.
ticks = me.afterBuildTicks(ticks) || ticks;

me.beforeTickToLabelConversion();

// New implementations should return the formatted tick labels but for BACKWARD
// COMPAT, we still support no return (`this.ticks` internally changed by calling
// this method and supposed to contain only string values).
labels = me.convertTicksToLabels(ticks) || me.ticks;

me.afterTickToLabelConversion();

me.ticks = labels; // BACKWARD COMPATIBILITY

// IMPORTANT: below this point, we consider that `this.ticks` will NEVER change!

// BACKWARD COMPAT: synchronize `_ticks` with labels (so potentially `this.ticks`)
for (i = 0, ilen = labels.length; i < ilen; ++i) {
label = labels[i];
tick = ticks[i];
if (!tick) {
ticks.push(tick = {
label: label,
// Ensure ticks contains ticks in new tick format
if ((!ticks || !ticks.length) && me.ticks) {
ticks = [];
for (i = 0, ilen = me.ticks.length; i < ilen; ++i) {
ticks.push({
value: me.ticks[i],
major: false
});
} else {
tick.label = label;
}
}

me._ticks = ticks;

// Compute tick rotation and fit using a sampled subset of labels
// We generally don't need to compute the size of every single label for determining scale size
labels = me._convertTicksToLabels(sampleSize ? sample(ticks, sampleSize) : ticks);

// _configure is called twice, once here, once from core.controller.updateLayout.
// Here we haven't been positioned yet, but dimensions are correct.
// Variables set in _configure are needed for calculateTickRotation, and
Expand All @@ -350,19 +354,28 @@ var Scale = Element.extend({
me.beforeCalculateTickRotation();
me.calculateTickRotation();
me.afterCalculateTickRotation();
// Fit

me.beforeFit();
me.fit();
me.afterFit();

// Auto-skip
me._ticksToDraw = tickOpts.display && tickOpts.autoSkip ? me._autoSkip(me._ticks) : me._ticks;
me._ticksToDraw = tickOpts.display && tickOpts.autoSkip ? me._autoSkip(ticks) : ticks;

if (sampleSize) {
// Generate labels using all non-skipped ticks
labels = me._convertTicksToLabels(me._ticksToDraw);
}

me.ticks = labels; // BACKWARD COMPATIBILITY

// IMPORTANT: after this point, we consider that `this.ticks` will NEVER change!

me.afterUpdate();

// TODO(v3): remove minSize as a public property and return value from all layout boxes. It is unused
// make maxWidth and maxHeight private
return me.minSize;

},

/**
Expand Down Expand Up @@ -670,6 +683,31 @@ var Scale = Element.extend({
return rawValue;
},

_convertTicksToLabels: function(ticks) {
var me = this;
var labels, i, ilen;

me.ticks = ticks.map(function(tick) {
return tick.value;
});

me.beforeTickToLabelConversion();

// New implementations should return the formatted tick labels but for BACKWARD
// COMPAT, we still support no return (`this.ticks` internally changed by calling
// this method and supposed to contain only string values).
labels = me.convertTicksToLabels(ticks) || me.ticks;

me.afterTickToLabelConversion();

// BACKWARD COMPAT: synchronize `_ticks` with labels (so potentially `this.ticks`)
for (i = 0, ilen = ticks.length; i < ilen; ++i) {
ticks[i].label = labels[i];
}

return labels;
},

/**
* @private
*/
Expand Down Expand Up @@ -832,11 +870,12 @@ var Scale = Element.extend({
for (i = 0; i < tickCount; i++) {
tick = ticks[i];

if (skipRatio > 1 && i % skipRatio > 0) {
// leave tick in place but make sure it's not displayed (#4635)
if (skipRatio <= 1 || i % skipRatio === 0) {
tick._index = i;
result.push(tick);
} else {
delete tick.label;
}
result.push(tick);
}
return result;
},
Expand Down Expand Up @@ -963,7 +1002,7 @@ var Scale = Element.extend({
borderDashOffset = gridLines.borderDashOffset || 0.0;
}

lineValue = getPixelForGridLine(me, i, offsetGridLines);
lineValue = getPixelForGridLine(me, tick._index || i, offsetGridLines);

// Skip if the pixel is out of the range
if (lineValue === undefined) {
Expand Down Expand Up @@ -1041,7 +1080,7 @@ var Scale = Element.extend({
continue;
}

pixel = me.getPixelForTick(i) + optionTicks.labelOffset;
pixel = me.getPixelForTick(tick._index || i) + optionTicks.labelOffset;
font = tick.major ? fonts.major : fonts.minor;
lineHeight = font.lineHeight;
lineCount = isArray(label) ? label.length : 1;
Expand Down

0 comments on commit a8f9603

Please sign in to comment.