Skip to content

Commit fc76610

Browse files
benmccannetimberg
authored andcommitted
Add ticks.sampleSize option (#6508)
1 parent e9f3418 commit fc76610

File tree

3 files changed

+79
-31
lines changed

3 files changed

+79
-31
lines changed

docs/axes/cartesian/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ The following options are common to all cartesian axes but do not apply to other
2828
| ---- | ---- | ------- | -----------
2929
| `min` | `number` | | User defined minimum value for the scale, overrides minimum value from data.
3030
| `max` | `number` | | User defined maximum value for the scale, overrides maximum value from data.
31+
| `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.
3132
| `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.
3233
| `autoSkipPadding` | `number` | `0` | Padding between the ticks on the horizontal axis when `autoSkip` is enabled.
3334
| `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*

docs/general/performance.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Performance
2+
3+
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:
4+
5+
* Set `animation: { duration: 0 }` to disable [animations](../configuration/animations.md).
6+
* For large datasets:
7+
* 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
8+
* Set the [`ticks.sampleSize`](../axes/cartesian/README.md#tick-configuration) option in order to render axes more quickly

src/core/core.scale.js

Lines changed: 70 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,19 @@ defaults._set('scale', {
6767
}
6868
});
6969

70+
/** Returns a new array containing numItems from arr */
71+
function sample(arr, numItems) {
72+
var result = [];
73+
var increment = arr.length / numItems;
74+
var i = 0;
75+
var len = arr.length;
76+
77+
for (; i < len; i += increment) {
78+
result.push(arr[Math.floor(i)]);
79+
}
80+
return result;
81+
}
82+
7083
function getPixelForGridLine(scale, index, offsetGridLines) {
7184
var length = scale.getTicks().length;
7285
var validIndex = Math.min(index, length - 1);
@@ -266,7 +279,8 @@ var Scale = Element.extend({
266279
update: function(maxWidth, maxHeight, margins) {
267280
var me = this;
268281
var tickOpts = me.options.ticks;
269-
var i, ilen, labels, label, ticks, tick;
282+
var sampleSize = tickOpts.sampleSize;
283+
var i, ilen, labels, ticks;
270284

271285
// Update Lifecycle - Probably don't want to ever extend or overwrite this function ;)
272286
me.beforeUpdate();
@@ -281,6 +295,8 @@ var Scale = Element.extend({
281295
bottom: 0
282296
}, margins);
283297

298+
me._ticks = null;
299+
me.ticks = null;
284300
me._labelSizes = null;
285301
me._maxLabelLines = 0;
286302
me.longestLabelWidth = 0;
@@ -314,35 +330,23 @@ var Scale = Element.extend({
314330
// Allow modification of ticks in callback.
315331
ticks = me.afterBuildTicks(ticks) || ticks;
316332

317-
me.beforeTickToLabelConversion();
318-
319-
// New implementations should return the formatted tick labels but for BACKWARD
320-
// COMPAT, we still support no return (`this.ticks` internally changed by calling
321-
// this method and supposed to contain only string values).
322-
labels = me.convertTicksToLabels(ticks) || me.ticks;
323-
324-
me.afterTickToLabelConversion();
325-
326-
me.ticks = labels; // BACKWARD COMPATIBILITY
327-
328-
// IMPORTANT: below this point, we consider that `this.ticks` will NEVER change!
329-
330-
// BACKWARD COMPAT: synchronize `_ticks` with labels (so potentially `this.ticks`)
331-
for (i = 0, ilen = labels.length; i < ilen; ++i) {
332-
label = labels[i];
333-
tick = ticks[i];
334-
if (!tick) {
335-
ticks.push(tick = {
336-
label: label,
333+
// Ensure ticks contains ticks in new tick format
334+
if ((!ticks || !ticks.length) && me.ticks) {
335+
ticks = [];
336+
for (i = 0, ilen = me.ticks.length; i < ilen; ++i) {
337+
ticks.push({
338+
value: me.ticks[i],
337339
major: false
338340
});
339-
} else {
340-
tick.label = label;
341341
}
342342
}
343343

344344
me._ticks = ticks;
345345

346+
// Compute tick rotation and fit using a sampled subset of labels
347+
// We generally don't need to compute the size of every single label for determining scale size
348+
labels = me._convertTicksToLabels(sampleSize ? sample(ticks, sampleSize) : ticks);
349+
346350
// _configure is called twice, once here, once from core.controller.updateLayout.
347351
// Here we haven't been positioned yet, but dimensions are correct.
348352
// Variables set in _configure are needed for calculateTickRotation, and
@@ -353,19 +357,28 @@ var Scale = Element.extend({
353357
me.beforeCalculateTickRotation();
354358
me.calculateTickRotation();
355359
me.afterCalculateTickRotation();
356-
// Fit
360+
357361
me.beforeFit();
358362
me.fit();
359363
me.afterFit();
364+
360365
// Auto-skip
361-
me._ticksToDraw = tickOpts.display && tickOpts.autoSkip ? me._autoSkip(me._ticks) : me._ticks;
366+
me._ticksToDraw = tickOpts.display && tickOpts.autoSkip ? me._autoSkip(ticks) : ticks;
367+
368+
if (sampleSize) {
369+
// Generate labels using all non-skipped ticks
370+
labels = me._convertTicksToLabels(me._ticksToDraw);
371+
}
372+
373+
me.ticks = labels; // BACKWARD COMPATIBILITY
374+
375+
// IMPORTANT: after this point, we consider that `this.ticks` will NEVER change!
362376

363377
me.afterUpdate();
364378

365379
// TODO(v3): remove minSize as a public property and return value from all layout boxes. It is unused
366380
// make maxWidth and maxHeight private
367381
return me.minSize;
368-
369382
},
370383

371384
/**
@@ -673,6 +686,31 @@ var Scale = Element.extend({
673686
return rawValue;
674687
},
675688

689+
_convertTicksToLabels: function(ticks) {
690+
var me = this;
691+
var labels, i, ilen;
692+
693+
me.ticks = ticks.map(function(tick) {
694+
return tick.value;
695+
});
696+
697+
me.beforeTickToLabelConversion();
698+
699+
// New implementations should return the formatted tick labels but for BACKWARD
700+
// COMPAT, we still support no return (`this.ticks` internally changed by calling
701+
// this method and supposed to contain only string values).
702+
labels = me.convertTicksToLabels(ticks) || me.ticks;
703+
704+
me.afterTickToLabelConversion();
705+
706+
// BACKWARD COMPAT: synchronize `_ticks` with labels (so potentially `this.ticks`)
707+
for (i = 0, ilen = ticks.length; i < ilen; ++i) {
708+
ticks[i].label = labels[i];
709+
}
710+
711+
return labels;
712+
},
713+
676714
/**
677715
* @private
678716
*/
@@ -835,11 +873,12 @@ var Scale = Element.extend({
835873
for (i = 0; i < tickCount; i++) {
836874
tick = ticks[i];
837875

838-
if (skipRatio > 1 && i % skipRatio > 0) {
839-
// leave tick in place but make sure it's not displayed (#4635)
876+
if (skipRatio <= 1 || i % skipRatio === 0) {
877+
tick._index = i;
878+
result.push(tick);
879+
} else {
840880
delete tick.label;
841881
}
842-
result.push(tick);
843882
}
844883
return result;
845884
},
@@ -966,7 +1005,7 @@ var Scale = Element.extend({
9661005
borderDashOffset = gridLines.borderDashOffset || 0.0;
9671006
}
9681007

969-
lineValue = getPixelForGridLine(me, i, offsetGridLines);
1008+
lineValue = getPixelForGridLine(me, tick._index || i, offsetGridLines);
9701009

9711010
// Skip if the pixel is out of the range
9721011
if (lineValue === undefined) {
@@ -1044,7 +1083,7 @@ var Scale = Element.extend({
10441083
continue;
10451084
}
10461085

1047-
pixel = me.getPixelForTick(i) + optionTicks.labelOffset;
1086+
pixel = me.getPixelForTick(tick._index || i) + optionTicks.labelOffset;
10481087
font = tick.major ? fonts.major : fonts.minor;
10491088
lineHeight = font.lineHeight;
10501089
lineCount = isArray(label) ? label.length : 1;

0 commit comments

Comments
 (0)