Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ticks.sampleSize option #6508

Merged
merged 1 commit into from Oct 9, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/axes/cartesian/README.md
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
@@ -0,0 +1,8 @@
# Performance
etimberg marked this conversation as resolved.
Show resolved Hide resolved

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
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) {
etimberg marked this conversation as resolved.
Show resolved Hide resolved
// 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;
});
etimberg marked this conversation as resolved.
Show resolved Hide resolved

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;
etimberg marked this conversation as resolved.
Show resolved Hide resolved
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