Skip to content

Commit

Permalink
Make autoskip aware of major ticks
Browse files Browse the repository at this point in the history
  • Loading branch information
benmccann committed Oct 9, 2019
1 parent fc76610 commit 28e3435
Show file tree
Hide file tree
Showing 4 changed files with 206 additions and 137 deletions.
42 changes: 39 additions & 3 deletions samples/scales/time/financial.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<head>
<title>Line Chart</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.1/moment.min.js"></script>
<script src="../../../dist/Chart.min.js"></script>
<script src="../../../dist/Chart.js"></script>
<script src="../../utils.js"></script>
<style>
canvas {
Expand Down Expand Up @@ -76,7 +76,7 @@
var now = moment();
var data = [];
var lessThanDay = unitLessThanDay();
for (; data.length < 60 && date.isBefore(now); date = date.clone().add(1, unit).startOf(unit)) {
for (; data.length < 600 && date.isBefore(now); date = date.clone().add(1, unit).startOf(unit)) {
if (outsideMarketHours(date)) {
if (!lessThanDay || !beforeNineThirty(date)) {
date = date.clone().add(date.isoWeekday() >= 5 ? 8 - date.isoWeekday() : 1, 'day');
Expand Down Expand Up @@ -112,13 +112,49 @@
}]
},
options: {
animation: {
duration: 0
},
scales: {
xAxes: [{
type: 'time',
distribution: 'series',
ticks: {
major: {
enabled: true
},
source: 'data',
autoSkip: true
autoSkip: true,
autoSkipPadding: 75,
maxRotation: 0,
sampleSize: 100
},
afterBuildTicks: function(scale, ticks) {
var unit = scale._unit;
var majorUnit = scale._majorUnit;
var i, ilen, tick, ts, val, lastMajorVal;
for (i = 0, ilen = ticks.length; i < ilen; i++) {
tick = ticks[i];
ts = tick.value;
val = moment(ts);
if (val.get(majorUnit) === lastMajorVal) {
tick.major = false;
continue;
}
lastMajorVal = val.get(majorUnit);
if (unit === 'hour' && val.hour() === 9) {
tick.major = true;
continue;
} else if (unit === 'day' && val.date() <= 3 && val.isoWeekday() === 1) {
tick.major = true;
continue;
} else if (unit === 'month' && val.month() === 0) {
tick.major = true;
continue;
}
tick.major = majorUnit ? ts === +scale._adapter.startOf(ts, majorUnit) : false;
}
return ticks;
}
}],
yAxes: [{
Expand Down
135 changes: 105 additions & 30 deletions src/core/core.scale.js
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,82 @@ function parseTickFontOptions(options) {
return {minor: minor, major: major};
}

function calculateSpacing(majorIndices, ticks, axisLength, ticksLimit) {
var evenMajorSpacing = majorIndices.length > 1 ? majorIndices.reduce(function(acc, val, idx, arr) {
var diff = idx === 0 ? acc : arr[idx] - arr[idx - 1];
return acc && acc === diff ? diff : false;
}, majorIndices[1] - majorIndices[0]) : false;
var spacing = (ticks.length - 1) / ticksLimit;
var factors, factor, i, ilen;

// If the major ticks are evenly spaced apart, place the minor ticks
// so that they divide the major ticks into even chunks
if (evenMajorSpacing) {
factors = helpers.math._factorize(evenMajorSpacing);
for (i = 0, ilen = factors.length - 1; i < ilen; i++) {
factor = factors[i];
if (factor > spacing) {
return factor;
}
}
}
return Math.max(spacing, 1);
}

function getMajorIndices(ticks) {
var result = [];
var i, ilen;
for (i = 0, ilen = ticks.length; i < ilen; i++) {
if (ticks[i].major) {
result.push(i);
}
}
return result;
}

function skipMajors(ticks, majorIndices, spacing) {
var ticksToKeep = {};
var i, tick;

spacing = Math.ceil(spacing);
for (i = 0; i < majorIndices.length; i += spacing) {
ticksToKeep[majorIndices[i]] = 1;
}
for (i = 0; i < ticks.length; i++) {
tick = ticks[i];
if (ticksToKeep[i]) {
tick._index = i;
} else {
delete tick.label;
}
}
}

function skip(ticks, spacing, majorStart, majorEnd) {
var ticksToKeep = {};
var start = valueOrDefault(majorStart, 0);
var end = Math.min(valueOrDefault(majorEnd, ticks.length), ticks.length);
var length, i, tick;

spacing = Math.ceil(spacing);
if (majorEnd) {
length = majorEnd - majorStart;
spacing = length / Math.floor(length / spacing);
}
for (i = 0, tick = start; tick < end; i++) {
tick = Math.round(start + i * spacing);
ticksToKeep[tick] = 1;
}
for (i = Math.max(start, 0); i < end; i++) {
tick = ticks[i];
if (ticksToKeep[i]) {
tick._index = i;
} else {
delete tick.label;
}
}
}

var Scale = Element.extend({

zeroLineIndex: 0,
Expand Down Expand Up @@ -363,7 +439,7 @@ var Scale = Element.extend({
me.afterFit();

// Auto-skip
me._ticksToDraw = tickOpts.display && tickOpts.autoSkip ? me._autoSkip(ticks) : ticks;
me._ticksToDraw = tickOpts.display && (tickOpts.autoSkip || tickOpts.source === 'auto') ? me._autoSkip(ticks) : ticks;

if (sampleSize) {
// Generate labels using all non-skipped ticks
Expand Down Expand Up @@ -847,40 +923,40 @@ var Scale = Element.extend({
*/
_autoSkip: function(ticks) {
var me = this;
var optionTicks = me.options.ticks;
var tickCount = ticks.length;
var skipRatio = false;
var maxTicks = optionTicks.maxTicksLimit;

// Total space needed to display all ticks. First and last ticks are
// drawn as their center at end of axis, so tickCount-1
var ticksLength = me._tickSize() * (tickCount - 1);

var tickOpts = me.options.ticks;
var axisLength = me._length;
var result = [];
var i, tick;

if (ticksLength > axisLength) {
skipRatio = 1 + Math.floor(ticksLength / axisLength);
var ticksLimit = tickOpts.maxTicksLimit || axisLength / me._tickSize() + 1;
var majorIndices = tickOpts.major.enabled ? getMajorIndices(ticks) : [];
var numMajorIndices = majorIndices.length;
var first = majorIndices[0];
var last = majorIndices[numMajorIndices - 1];
var i, ilen, spacing, avgMajorSpacing;

function nonSkipped(ticksToFilter) {
return ticksToFilter.filter(function(item) {
return typeof item._index !== 'undefined';
});
}

// if they defined a max number of optionTicks,
// increase skipRatio until that number is met
if (tickCount > maxTicks) {
skipRatio = Math.max(skipRatio, 1 + Math.floor(tickCount / maxTicks));
// If there are too many major ticks to display them all
if (numMajorIndices > ticksLimit) {
skipMajors(ticks, majorIndices, numMajorIndices / ticksLimit);
return nonSkipped(ticks);
}

for (i = 0; i < tickCount; i++) {
tick = ticks[i];
spacing = calculateSpacing(majorIndices, ticks, axisLength, ticksLimit);

if (skipRatio <= 1 || i % skipRatio === 0) {
tick._index = i;
result.push(tick);
} else {
delete tick.label;
if (numMajorIndices > 0) {
for (i = 0, ilen = numMajorIndices - 1; i < ilen; i++) {
skip(ticks, spacing, majorIndices[i], majorIndices[i + 1]);
}
avgMajorSpacing = numMajorIndices > 1 ? (last - first) / (numMajorIndices - 1) : null;
skip(ticks, spacing, helpers.isNullOrUndef(avgMajorSpacing) ? 0 : first - avgMajorSpacing, first);
skip(ticks, spacing, last, helpers.isNullOrUndef(avgMajorSpacing) ? ticks.length : last + avgMajorSpacing);
return nonSkipped(ticks);
}
return result;
skip(ticks, spacing);
return nonSkipped(ticks);
},

/**
Expand Down Expand Up @@ -954,7 +1030,7 @@ var Scale = Element.extend({
var alignBorderValue = function(pixel) {
return alignPixel(chart, pixel, axisWidth);
};
var borderValue, i, tick, label, lineValue, alignedLineValue;
var borderValue, i, tick, lineValue, alignedLineValue;
var tx1, ty1, tx2, ty2, x1, y1, x2, y2, lineWidth, lineColor, borderDash, borderDashOffset;

if (position === 'top') {
Expand Down Expand Up @@ -985,10 +1061,9 @@ var Scale = Element.extend({

for (i = 0; i < ticksLength; ++i) {
tick = ticks[i] || {};
label = tick.label;

// autoskipper skipped this tick (#4635)
if (isNullOrUndef(label) && i < ticks.length) {
if (isNullOrUndef(tick.label) && i < ticks.length) {
continue;
}

Expand Down
Loading

0 comments on commit 28e3435

Please sign in to comment.