Skip to content

Commit d437234

Browse files
committed
Add sampling to limit time computing label sizes
1 parent 6e3e4a1 commit d437234

File tree

3 files changed

+71
-32
lines changed

3 files changed

+71
-32
lines changed

src/controllers/controller.bar.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,13 @@ defaults._set('bar', {
3333
*/
3434
function computeMinSampleSize(scale, pixels) {
3535
var min = scale.isHorizontal() ? scale.width : scale.height;
36-
var ticks = scale.getTicks();
3736
var prev, curr, i, ilen;
3837

3938
for (i = 1, ilen = pixels.length; i < ilen; ++i) {
4039
min = Math.min(min, Math.abs(pixels[i] - pixels[i - 1]));
4140
}
4241

43-
for (i = 0, ilen = ticks.length; i < ilen; ++i) {
42+
for (i = 0, ilen = scale.getTicks().length; i < ilen; ++i) {
4443
curr = scale.getPixelForTick(i);
4544
min = i > 0 ? Math.min(min, curr - prev) : min;
4645
prev = curr;

src/core/core.scale.js

Lines changed: 69 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -65,11 +65,30 @@ defaults._set('scale', {
6565
}
6666
});
6767

68+
function sample(arr, size) {
69+
var shuffled = arr.slice(0);
70+
var i = arr.length;
71+
var min = i - size;
72+
var tmp, index;
73+
74+
if (size >= min) {
75+
return arr;
76+
}
77+
78+
while (i-- > min) {
79+
index = Math.floor((i + 1) * Math.random());
80+
tmp = shuffled[index];
81+
shuffled[index] = shuffled[i];
82+
shuffled[i] = tmp;
83+
}
84+
return shuffled.slice(min);
85+
}
86+
6887
function getPixelForGridLine(scale, index, offsetGridLines) {
6988
var lineValue = scale.getPixelForTick(index);
7089

7190
if (offsetGridLines) {
72-
if (scale.getTicks().length === 1) {
91+
if (scale._ticks.length === 1) {
7392
lineValue -= scale.isHorizontal() ?
7493
Math.max(lineValue - scale.left, scale.right - lineValue) :
7594
Math.max(lineValue - scale.top, scale.bottom - lineValue);
@@ -304,43 +323,39 @@ var Scale = Element.extend({
304323
ticks = [];
305324
for (i = 0, ilen = me.ticks.length; i < ilen; ++i) {
306325
ticks.push({
307-
index: i,
326+
_index: i,
308327
value: me.ticks[i],
309328
major: false
310329
});
311330
}
312331
}
313332
me._numTicks = ticks.length;
314333

315-
me.beforeTickToLabelConversion();
316-
317-
// New implementations should return the formatted tick labels but for BACKWARD
318-
// COMPAT, we still support no return (`this.ticks` internally changed by calling
319-
// this method and supposed to contain only string values).
320-
labels = me.convertTicksToLabels(ticks) || me.ticks;
321-
322-
me.afterTickToLabelConversion();
323-
324-
// IMPORTANT: below this point, we consider that `this.ticks` will NEVER change!
325-
me.ticks = labels; // BACKWARD COMPATIBILITY
326-
327-
// BACKWARD COMPAT: synchronize `_ticks` with labels (so potentially `this.ticks`)
328-
for (i = 0, ilen = labels.length; i < ilen; ++i) {
329-
ticks[i].label = labels[i];
330-
}
334+
// Compute tick rotation and fit using a sampled sub-set of labels
335+
// We generally don't need to compute the size of every single label for determining scale size
336+
me._ticks = sample(ticks, tickOpts.sampleSize || ticks.length);
331337

332-
me._ticks = ticks;
338+
labels = me._convertTicksToLabels(me._ticks);
333339

334-
// Tick Rotation
335340
me.beforeCalculateTickRotation();
336341
me.calculateTickRotation();
337342
me.afterCalculateTickRotation();
338-
// Fit
343+
339344
me.beforeFit();
340345
me.fit();
341346
me.afterFit();
347+
342348
// Auto-skip
343-
me._ticks = tickOpts.display && tickOpts.autoSkip ? me._autoSkip(me.getTicks()) : me.getTicks();
349+
me._ticks = ticks = tickOpts.display && tickOpts.autoSkip ? me._autoSkip(ticks) : ticks;
350+
351+
if (tickOpts.sampleSize) {
352+
// Generate labels using all non-skipped ticks
353+
labels = me._convertTicksToLabels(ticks);
354+
}
355+
356+
me.ticks = labels; // BACKWARD COMPATIBILITY
357+
358+
// IMPORTANT: after this point, we consider that `this.ticks` will NEVER change!
344359

345360
me.afterUpdate();
346361

@@ -524,7 +539,7 @@ var Scale = Element.extend({
524539
minSize.height = Math.min(me.maxHeight, minSize.height + labelHeight + tickPadding);
525540

526541
var offsetLeft = me.getPixelForTick(0) - me.left;
527-
var offsetRight = me.right - me.getPixelForTick(me._numTicks - 1);
542+
var offsetRight = me.right - me.getPixelForTick(me._ticks.length - 1);
528543
var paddingLeft, paddingRight;
529544

530545
// Ensure that our ticks are always inside the canvas. When rotated, ticks are right aligned
@@ -621,6 +636,31 @@ var Scale = Element.extend({
621636
return rawValue;
622637
},
623638

639+
_convertTicksToLabels: function(ticks) {
640+
var me = this;
641+
var labels, i, ilen;
642+
643+
me.ticks = ticks.map(function(tick) {
644+
return tick.value;
645+
});
646+
647+
me.beforeTickToLabelConversion();
648+
649+
// New implementations should return the formatted tick labels but for BACKWARD
650+
// COMPAT, we still support no return (`this.ticks` internally changed by calling
651+
// this method and supposed to contain only string values).
652+
labels = me.convertTicksToLabels(ticks) || me.ticks;
653+
654+
me.afterTickToLabelConversion();
655+
656+
// BACKWARD COMPAT: synchronize `_ticks` with labels (so potentially `this.ticks`)
657+
for (i = 0, ilen = labels.length; i < ilen; ++i) {
658+
ticks[i].label = labels[i];
659+
}
660+
661+
return labels;
662+
},
663+
624664
/**
625665
* @private
626666
*/
@@ -705,7 +745,7 @@ var Scale = Element.extend({
705745
getPixelForTick: function(index) {
706746
var me = this;
707747
var offset = me.options.offset;
708-
var numTicks = me._numTicks;
748+
var numTicks = me._ticks.length;
709749
if (index < 0 || index > numTicks - 1) {
710750
return null;
711751
}
@@ -894,8 +934,8 @@ var Scale = Element.extend({
894934
tickEnd = me.left + tl;
895935
}
896936

897-
helpers.each(ticks, function(tick) {
898-
var index = tick.index;
937+
helpers.each(ticks, function(tick, i) {
938+
var index = tick._index;
899939
var label = tick.label;
900940
var tickFont = tick.major ? tickFonts.major : tickFonts.minor;
901941
var lineHeight = tickFont.lineHeight;
@@ -916,7 +956,7 @@ var Scale = Element.extend({
916956
// Common properties
917957
var tx1, ty1, tx2, ty2, x1, y1, x2, y2, labelX, labelY, textOffset, textAlign;
918958
var labelCount = helpers.isArray(label) ? label.length : 1;
919-
var lineValue = getPixelForGridLine(me, index, gridLines.offsetGridLines);
959+
var lineValue = getPixelForGridLine(me, i, gridLines.offsetGridLines);
920960

921961
if (isHorizontal) {
922962
var labelYOffset = tl + tickPadding;
@@ -928,7 +968,7 @@ var Scale = Element.extend({
928968
tx1 = tx2 = x1 = x2 = alignPixel(chart, lineValue, lineWidth);
929969
ty1 = tickStart;
930970
ty2 = tickEnd;
931-
labelX = me.getPixelForTick(index) + labelOffset; // x values for optionTicks (need to consider offsetLabel option)
971+
labelX = me.getPixelForTick(i) + labelOffset; // x values for optionTicks (need to consider offsetLabel option)
932972

933973
if (position === 'top') {
934974
y1 = alignPixel(chart, chartArea.top, axisWidth) + axisWidth / 2;
@@ -953,7 +993,7 @@ var Scale = Element.extend({
953993
tx1 = tickStart;
954994
tx2 = tickEnd;
955995
ty1 = ty2 = y1 = y2 = alignPixel(chart, lineValue, lineWidth);
956-
labelY = me.getPixelForTick(index) + labelOffset;
996+
labelY = me.getPixelForTick(i) + labelOffset;
957997
textOffset = (1 - labelCount) * lineHeight / 2;
958998

959999
if (position === 'left') {

src/scales/scale.time.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -430,7 +430,7 @@ function ticksFromTimestamps(scale, values, majorUnit) {
430430
map[value] = i;
431431

432432
ticks.push({
433-
index: i,
433+
_index: i,
434434
value: value,
435435
major: false
436436
});

0 commit comments

Comments
 (0)