From d43723441634484a1866631e577800a76efd0474 Mon Sep 17 00:00:00 2001 From: Ben McCann <322311+benmccann@users.noreply.github.com> Date: Thu, 27 Jun 2019 19:34:47 -0700 Subject: [PATCH] Add sampling to limit time computing label sizes --- src/controllers/controller.bar.js | 3 +- src/core/core.scale.js | 98 ++++++++++++++++++++++--------- src/scales/scale.time.js | 2 +- 3 files changed, 71 insertions(+), 32 deletions(-) diff --git a/src/controllers/controller.bar.js b/src/controllers/controller.bar.js index aa8b06a2cbc..a0e2d6e6a63 100644 --- a/src/controllers/controller.bar.js +++ b/src/controllers/controller.bar.js @@ -33,14 +33,13 @@ defaults._set('bar', { */ function computeMinSampleSize(scale, pixels) { var min = scale.isHorizontal() ? scale.width : scale.height; - var ticks = scale.getTicks(); var prev, curr, i, ilen; for (i = 1, ilen = pixels.length; i < ilen; ++i) { min = Math.min(min, Math.abs(pixels[i] - pixels[i - 1])); } - for (i = 0, ilen = ticks.length; i < ilen; ++i) { + for (i = 0, ilen = scale.getTicks().length; i < ilen; ++i) { curr = scale.getPixelForTick(i); min = i > 0 ? Math.min(min, curr - prev) : min; prev = curr; diff --git a/src/core/core.scale.js b/src/core/core.scale.js index 9fcec487ee3..b2e7dd36414 100644 --- a/src/core/core.scale.js +++ b/src/core/core.scale.js @@ -65,11 +65,30 @@ defaults._set('scale', { } }); +function sample(arr, size) { + var shuffled = arr.slice(0); + var i = arr.length; + var min = i - size; + var tmp, index; + + if (size >= min) { + return arr; + } + + while (i-- > min) { + index = Math.floor((i + 1) * Math.random()); + tmp = shuffled[index]; + shuffled[index] = shuffled[i]; + shuffled[i] = tmp; + } + return shuffled.slice(min); +} + function getPixelForGridLine(scale, index, offsetGridLines) { var lineValue = scale.getPixelForTick(index); if (offsetGridLines) { - if (scale.getTicks().length === 1) { + if (scale._ticks.length === 1) { lineValue -= scale.isHorizontal() ? Math.max(lineValue - scale.left, scale.right - lineValue) : Math.max(lineValue - scale.top, scale.bottom - lineValue); @@ -304,7 +323,7 @@ var Scale = Element.extend({ ticks = []; for (i = 0, ilen = me.ticks.length; i < ilen; ++i) { ticks.push({ - index: i, + _index: i, value: me.ticks[i], major: false }); @@ -312,35 +331,31 @@ var Scale = Element.extend({ } me._numTicks = ticks.length; - 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(); - - // IMPORTANT: below this point, we consider that `this.ticks` will NEVER change! - me.ticks = labels; // BACKWARD COMPATIBILITY - - // BACKWARD COMPAT: synchronize `_ticks` with labels (so potentially `this.ticks`) - for (i = 0, ilen = labels.length; i < ilen; ++i) { - ticks[i].label = labels[i]; - } + // Compute tick rotation and fit using a sampled sub-set of labels + // We generally don't need to compute the size of every single label for determining scale size + me._ticks = sample(ticks, tickOpts.sampleSize || ticks.length); - me._ticks = ticks; + labels = me._convertTicksToLabels(me._ticks); - // Tick Rotation me.beforeCalculateTickRotation(); me.calculateTickRotation(); me.afterCalculateTickRotation(); - // Fit + me.beforeFit(); me.fit(); me.afterFit(); + // Auto-skip - me._ticks = tickOpts.display && tickOpts.autoSkip ? me._autoSkip(me.getTicks()) : me.getTicks(); + me._ticks = ticks = tickOpts.display && tickOpts.autoSkip ? me._autoSkip(ticks) : ticks; + + if (tickOpts.sampleSize) { + // Generate labels using all non-skipped ticks + labels = me._convertTicksToLabels(ticks); + } + + me.ticks = labels; // BACKWARD COMPATIBILITY + + // IMPORTANT: after this point, we consider that `this.ticks` will NEVER change! me.afterUpdate(); @@ -524,7 +539,7 @@ var Scale = Element.extend({ minSize.height = Math.min(me.maxHeight, minSize.height + labelHeight + tickPadding); var offsetLeft = me.getPixelForTick(0) - me.left; - var offsetRight = me.right - me.getPixelForTick(me._numTicks - 1); + var offsetRight = me.right - me.getPixelForTick(me._ticks.length - 1); var paddingLeft, paddingRight; // Ensure that our ticks are always inside the canvas. When rotated, ticks are right aligned @@ -621,6 +636,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 = labels.length; i < ilen; ++i) { + ticks[i].label = labels[i]; + } + + return labels; + }, + /** * @private */ @@ -705,7 +745,7 @@ var Scale = Element.extend({ getPixelForTick: function(index) { var me = this; var offset = me.options.offset; - var numTicks = me._numTicks; + var numTicks = me._ticks.length; if (index < 0 || index > numTicks - 1) { return null; } @@ -894,8 +934,8 @@ var Scale = Element.extend({ tickEnd = me.left + tl; } - helpers.each(ticks, function(tick) { - var index = tick.index; + helpers.each(ticks, function(tick, i) { + var index = tick._index; var label = tick.label; var tickFont = tick.major ? tickFonts.major : tickFonts.minor; var lineHeight = tickFont.lineHeight; @@ -916,7 +956,7 @@ var Scale = Element.extend({ // Common properties var tx1, ty1, tx2, ty2, x1, y1, x2, y2, labelX, labelY, textOffset, textAlign; var labelCount = helpers.isArray(label) ? label.length : 1; - var lineValue = getPixelForGridLine(me, index, gridLines.offsetGridLines); + var lineValue = getPixelForGridLine(me, i, gridLines.offsetGridLines); if (isHorizontal) { var labelYOffset = tl + tickPadding; @@ -928,7 +968,7 @@ var Scale = Element.extend({ tx1 = tx2 = x1 = x2 = alignPixel(chart, lineValue, lineWidth); ty1 = tickStart; ty2 = tickEnd; - labelX = me.getPixelForTick(index) + labelOffset; // x values for optionTicks (need to consider offsetLabel option) + labelX = me.getPixelForTick(i) + labelOffset; // x values for optionTicks (need to consider offsetLabel option) if (position === 'top') { y1 = alignPixel(chart, chartArea.top, axisWidth) + axisWidth / 2; @@ -953,7 +993,7 @@ var Scale = Element.extend({ tx1 = tickStart; tx2 = tickEnd; ty1 = ty2 = y1 = y2 = alignPixel(chart, lineValue, lineWidth); - labelY = me.getPixelForTick(index) + labelOffset; + labelY = me.getPixelForTick(i) + labelOffset; textOffset = (1 - labelCount) * lineHeight / 2; if (position === 'left') { diff --git a/src/scales/scale.time.js b/src/scales/scale.time.js index f9814a955cd..e861469d10b 100644 --- a/src/scales/scale.time.js +++ b/src/scales/scale.time.js @@ -430,7 +430,7 @@ function ticksFromTimestamps(scale, values, majorUnit) { map[value] = i; ticks.push({ - index: i, + _index: i, value: value, major: false });