Skip to content

improve labels on axes with rangebreaks #5187

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

Merged
merged 10 commits into from
Oct 9, 2020
175 changes: 85 additions & 90 deletions src/plots/cartesian/axes.js
Original file line number Diff line number Diff line change
Expand Up @@ -589,6 +589,11 @@ axes.calcTicks = function calcTicks(ax, opts) {
var endTick = exRng[1];
// check for reversed axis
var axrev = (rng[1] < rng[0]);
var minRange = Math.min(rng[0], rng[1]);
var maxRange = Math.max(rng[0], rng[1]);
var dtick = ax.dtick;
var tickformat = axes.getTickFormat(ax);
var isPeriod = ax.ticklabelmode === 'period';

// No visible ticks? Quit.
// I've only seen this on category axes with all categories off the edge.
Expand All @@ -600,7 +605,77 @@ axes.calcTicks = function calcTicks(ax, opts) {
Math.min(ax._categories.length - 0.5, endTick);
}

var isDLog = (ax.type === 'log') && !(isNumeric(ax.dtick) || ax.dtick.charAt(0) === 'L');
var isDLog = (ax.type === 'log') && !(isNumeric(dtick) || dtick.charAt(0) === 'L');

var definedDelta;
if(isPeriod && tickformat) {
if(
!(/%[fLQsSMX]/.test(tickformat))
// %f: microseconds as a decimal number [000000, 999999]
// %L: milliseconds as a decimal number [000, 999]
// %Q: milliseconds since UNIX epoch
// %s: seconds since UNIX epoch
// %S: second as a decimal number [00,61]
// %M: minute as a decimal number [00,59]
// %X: the locale’s time, such as %-I:%M:%S %p
) {
if(
/%[HI]/.test(tickformat)
// %H: hour (24-hour clock) as a decimal number [00,23]
// %I: hour (12-hour clock) as a decimal number [01,12]
) {
definedDelta = ONEHOUR;
if(!dtick) dtick = ONEHOUR;
} else if(
/%p/.test(tickformat) // %p: either AM or PM
) {
definedDelta = HALFDAY;
if(!dtick) dtick = HALFDAY;
} else if(
/%[Aadejuwx]/.test(tickformat)
// %A: full weekday name
// %a: abbreviated weekday name
// %d: zero-padded day of the month as a decimal number [01,31]
// %e: space-padded day of the month as a decimal number [ 1,31]
// %j: day of the year as a decimal number [001,366]
// %u: Monday-based (ISO 8601) weekday as a decimal number [1,7]
// %w: Sunday-based weekday as a decimal number [0,6]
// %x: the locale’s date, such as %-m/%-d/%Y
) {
definedDelta = ONEDAY;
if(!dtick) dtick = ONEDAY;
} else if(
/%[UVW]/.test(tickformat)
// %U: Sunday-based week of the year as a decimal number [00,53]
// %V: ISO 8601 week of the year as a decimal number [01, 53]
// %W: Monday-based week of the year as a decimal number [00,53]
) {
definedDelta = ONEWEEK;
if(!dtick) dtick = ONEWEEK;
} else if(
/%[Bbm]/.test(tickformat)
// %B: full month name
// %b: abbreviated month name
// %m: month as a decimal number [01,12]
) {
definedDelta = ONEAVGMONTH;
if(!dtick) dtick = ONEMAXMONTH;
} else if(
/%[q]/.test(tickformat)
// %q: quarter of the year as a decimal number [1,4]
) {
definedDelta = ONEAVGQUARTER;
if(!dtick) dtick = ONEMAXQUARTER;
} else if(
/%[Yy]/.test(tickformat)
// %Y: year with century as a decimal number, such as 1999
// %y: year without century as a decimal number [00,99]
) {
definedDelta = ONEAVGYEAR;
if(!dtick) dtick = ONEMAXYEAR;
}
}
}

var tickVals;
function generateTicks() {
Expand All @@ -609,7 +684,7 @@ axes.calcTicks = function calcTicks(ax, opts) {
tickVals = [];
for(var x = ax._tmin;
(axrev) ? (x >= endTick) : (x <= endTick);
x = axes.tickIncrement(x, ax.dtick, axrev, ax.calendar)) {
x = axes.tickIncrement(x, dtick, axrev, ax.calendar)) {
// prevent infinite loops - no more than one tick per pixel,
// and make sure each value is different from the previous
if(tickVals.length > maxTicks || x === xPrevious) break;
Expand All @@ -629,13 +704,12 @@ axes.calcTicks = function calcTicks(ax, opts) {

generateTicks();

var isPeriod = ax.ticklabelmode === 'period';
var addedPreTick0Label = false;
if(isPeriod && tickVals[0]) {
// add one label to show pre tick0 period
tickVals.unshift({
minor: false,
value: axes.tickIncrement(tickVals[0].value, ax.dtick, !axrev, ax.caldendar)
value: axes.tickIncrement(tickVals[0].value, dtick, !axrev, ax.caldendar)
});
addedPreTick0Label = true;
}
Expand Down Expand Up @@ -701,68 +775,8 @@ axes.calcTicks = function calcTicks(ax, opts) {
ax._prevDateHead = '';
ax._inCalcTicks = true;

var minRange = Math.min(rng[0], rng[1]);
var maxRange = Math.max(rng[0], rng[1]);

var definedDelta;
var tickformat = axes.getTickFormat(ax);
if(isPeriod && tickformat) {
if(
!(/%[fLQsSMX]/.test(tickformat))
// %f: microseconds as a decimal number [000000, 999999]
// %L: milliseconds as a decimal number [000, 999]
// %Q: milliseconds since UNIX epoch
// %s: seconds since UNIX epoch
// %S: second as a decimal number [00,61]
// %M: minute as a decimal number [00,59]
// %X: the locale’s time, such as %-I:%M:%S %p
) {
if(
/%[HI]/.test(tickformat)
// %H: hour (24-hour clock) as a decimal number [00,23]
// %I: hour (12-hour clock) as a decimal number [01,12]
) definedDelta = ONEHOUR;
else if(
/%p/.test(tickformat) // %p: either AM or PM
) definedDelta = HALFDAY;
else if(
/%[Aadejuwx]/.test(tickformat)
// %A: full weekday name
// %a: abbreviated weekday name
// %d: zero-padded day of the month as a decimal number [01,31]
// %e: space-padded day of the month as a decimal number [ 1,31]
// %j: day of the year as a decimal number [001,366]
// %u: Monday-based (ISO 8601) weekday as a decimal number [1,7]
// %w: Sunday-based weekday as a decimal number [0,6]
// %x: the locale’s date, such as %-m/%-d/%Y
) definedDelta = ONEDAY;
else if(
/%[UVW]/.test(tickformat)
// %U: Sunday-based week of the year as a decimal number [00,53]
// %V: ISO 8601 week of the year as a decimal number [01, 53]
// %W: Monday-based week of the year as a decimal number [00,53]
) definedDelta = ONEWEEK;
else if(
/%[Bbm]/.test(tickformat)
// %B: full month name
// %b: abbreviated month name
// %m: month as a decimal number [01,12]
) definedDelta = ONEAVGMONTH;
else if(
/%[q]/.test(tickformat)
// %q: quarter of the year as a decimal number [1,4]
) definedDelta = ONEAVGQUARTER;
else if(
/%[Yy]/.test(tickformat)
// %Y: year with century as a decimal number, such as 1999
// %y: year without century as a decimal number [00,99]
) definedDelta = ONEAVGYEAR;
}
}

var ticksOut = [];
var i;
var prevText;
for(i = 0; i < tickVals.length; i++) {
var _minor = tickVals[i].minor;
var _value = tickVals[i].value;
Expand All @@ -774,9 +788,6 @@ axes.calcTicks = function calcTicks(ax, opts) {
_minor // noSuffixPrefix
);

if(isPeriod && prevText === t.text) continue;
prevText = t.text;

ticksOut.push(t);
}

Expand Down Expand Up @@ -825,6 +836,7 @@ axes.calcTicks = function calcTicks(ax, opts) {
}
} else if(definedDelta === ONEWEEK && delta >= ONEWEEK) {
periodLength = ONEWEEK;
if(ax._hasDayOfWeekBreaks) periodLength -= 2 * ONEDAY; // hack to center labels | TODO: improve this based on the actual number of days
} else if(delta >= ONEDAY) {
periodLength = ONEDAY;
} else if(definedDelta === HALFDAY && delta >= HALFDAY) {
Expand All @@ -833,28 +845,11 @@ axes.calcTicks = function calcTicks(ax, opts) {
periodLength = ONEHOUR;
}

if(periodLength && ax.rangebreaks) {
var nFirstHalf = 0;
var nSecondHalf = 0;
var nAll = 2 * 3 * 7; // number of samples
for(var c = 0; c < nAll; c++) {
var r = c / nAll;
if(ax.maskBreaks(A * (1 - r) + B * r) !== BADNUM) {
if(r < 0.5) {
nFirstHalf++;
} else {
nSecondHalf++;
}
}
}

if(nSecondHalf) {
periodLength *= (nFirstHalf + nSecondHalf) / nAll;
}
}
// ensure new label positions remain between ticks
v += Math.min(periodLength, actualDelta) / 2;

if(periodLength <= actualDelta) { // i.e. to ensure new label positions remain between ticks
v += periodLength / 2;
if(periodLength && ax.rangebreaks) {
v = moveOutsideBreak(v, ax, true);
}

ticksOut[i].periodX = v;
Expand Down Expand Up @@ -3386,12 +3381,12 @@ function isAngular(ax) {
return ax._id === 'angularaxis';
}

function moveOutsideBreak(v, ax) {
function moveOutsideBreak(v, ax, isStart) {
var len = ax._rangebreaks.length;
for(var k = 0; k < len; k++) {
var brk = ax._rangebreaks[k];
if(v >= brk.min && v < brk.max) {
return brk.max;
return isStart ? brk.min : brk.max;
}
}
return v;
Expand Down
Binary file modified test/image/baselines/date_axes_period_breaks_automargin.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified test/image/baselines/period_positioning3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified test/image/baselines/period_positioning4.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
46 changes: 23 additions & 23 deletions test/jasmine/tests/axes_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5522,7 +5522,7 @@ describe('Test axes', function() {
})
.then(function() {
_assert('', [
['2019-12-31 04:00', '2020-01-08 12:00', '2020-01-15 12:00', '2020-01-22 12:00', '2020-01-29 12:00'],
['2019-12-31 12:00', '2020-01-08 12:00', '2020-01-15 12:00', '2020-01-22 12:00', '2020-01-29 12:00'],
['2020-01-01 12:00', '2020-01-08 12:00', '2020-01-15 12:00', '2020-01-22 12:00', '2020-01-29 12:00'],
['2020-01-01 12:00', '2020-01-08 12:00', '2020-01-15 12:00', '2020-01-22 12:00', '2020-01-29 12:00']
][i], [
Expand Down Expand Up @@ -5630,28 +5630,28 @@ describe('Test axes', function() {
},
{
formatter: '%p',
positions: ['2019-12-31 21:00', '2020-01-01 06:00', '2020-01-01 18:00', '2020-01-02 06:00'],
labels: [' ', 'Wed-AM', 'Wed-PM', ' ']
positions: ['2019-12-31 22:30', '2020-01-01 01:30', '2020-01-01 04:30', '2020-01-01 07:30', '2020-01-01 10:30', '2020-01-01 13:30', '2020-01-01 16:30', '2020-01-01 19:30', '2020-01-01 22:30', '2020-01-02 01:30'],
labels: [' ', 'Wed-AM', 'Wed-AM', 'Wed-AM', 'Wed-AM', 'Wed-PM', 'Wed-PM', 'Wed-PM', 'Wed-PM', ' ']
},
{
formatter: '%M',
positions: ['2019-12-31 21:00', '2020-01-01 12:00', '2020-01-02 12:00'],
labels: [' ', 'Wed-00', ' ']
positions: ['2019-12-31 21:00', '2020-01-01', '2020-01-01 03:00', '2020-01-01 06:00', '2020-01-01 09:00', '2020-01-01 12:00', '2020-01-01 15:00', '2020-01-01 18:00', '2020-01-01 21:00', '2020-01-02'],
labels: [' ', 'Wed-00', 'Wed-00', 'Wed-00', 'Wed-00', 'Wed-00', 'Wed-00', 'Wed-00', 'Wed-00', 'Thu-00']
},
{
formatter: '%S',
positions: ['2019-12-31 21:00', '2020-01-01 12:00', '2020-01-02 12:00'],
labels: [' ', 'Wed-00', ' ']
positions: ['2019-12-31 21:00', '2020-01-01', '2020-01-01 03:00', '2020-01-01 06:00', '2020-01-01 09:00', '2020-01-01 12:00', '2020-01-01 15:00', '2020-01-01 18:00', '2020-01-01 21:00', '2020-01-02'],
labels: [' ', 'Wed-00', 'Wed-00', 'Wed-00', 'Wed-00', 'Wed-00', 'Wed-00', 'Wed-00', 'Wed-00', 'Thu-00']
},
{
formatter: '%L',
positions: ['2019-12-31 21:00', '2020-01-01 12:00', '2020-01-02 12:00'],
labels: [' ', 'Wed-000', ' ']
positions: ['2019-12-31 21:00', '2020-01-01', '2020-01-01 03:00', '2020-01-01 06:00', '2020-01-01 09:00', '2020-01-01 12:00', '2020-01-01 15:00', '2020-01-01 18:00', '2020-01-01 21:00', '2020-01-02'],
labels: [' ', 'Wed-000', 'Wed-000', 'Wed-000', 'Wed-000', 'Wed-000', 'Wed-000', 'Wed-000', 'Wed-000', 'Thu-000']
},
{
formatter: '%f',
positions: ['2019-12-31 21:00', '2020-01-01 12:00', '2020-01-02 12:00'],
labels: [' ', 'Wed-0', ' ']
positions: ['2019-12-31 21:00', '2020-01-01', '2020-01-01 03:00', '2020-01-01 06:00', '2020-01-01 09:00', '2020-01-01 12:00', '2020-01-01 15:00', '2020-01-01 18:00', '2020-01-01 21:00', '2020-01-02'],
labels: [' ', 'Wed-0', 'Wed-0', 'Wed-0', 'Wed-0', 'Wed-0', 'Wed-0', 'Wed-0', 'Wed-0', 'Thu-0']
}
].forEach(function(t) {
it('should respect time tickformat that includes ' + t.formatter, function(done) {
Expand Down Expand Up @@ -5921,38 +5921,38 @@ describe('Test axes', function() {
[
{
range: ['2020-12-14 08:00', '2022-12-14 08:00'],
positions: ['2020-12-06 10:26:47.1429', '2021-03-07 09:50:21.4286', '2021-06-06 16:26:47.1429', '2021-09-06 16:26:47.1429', '2021-12-07 09:50:21.4286', '2022-03-06 16:26:47.1429', '2022-06-06 16:26:47.1429', '2022-09-07 01:08:34.2857', '2022-12-07 01:08:34.2857'],
labels: [' ', 'Mar 2021', 'Jun 2021', 'Sep 2021', 'Dec 2021', 'Mar 2022', 'Jun 2022', 'Sep 2022', 'Dec 2022']
positions: ['2020-12-15 18:00', '2021-03-16 11:15', '2021-06-16 11:15', '2021-09-16 11:15', '2021-12-16 11:15', '2022-03-16 11:15', '2022-06-16 11:15', '2022-09-16 11:15', '2022-12-16 11:15'],
labels: ['Dec 2020', 'Mar 2021', 'Jun 2021', 'Sep 2021', 'Dec 2021', 'Mar 2022', 'Jun 2022', 'Sep 2022', ' ']
},
{
range: ['2020-12-14 08:00', '2021-08-14 08:00'],
positions: ['2020-12-06 04:17:08.5714', '2020-12-27 22:00', '2021-01-24 22:00', '2021-02-21 22:00', '2021-03-21 22:00', '2021-04-18 22:00', '2021-05-16 22:00', '2021-06-13 22:00', '2021-07-11 22:00', '2021-08-08 22:00'],
labels: [' ', 'Dec 21<br>2020', 'Jan 18<br>2021', 'Feb 15', 'Mar 15', 'Apr 12', 'May 10', 'Jun 7', 'Jul 5', 'Aug 2']
positions: ['2020-12-06 12:00', '2021-01-04 06:00', '2021-02-01 06:00', '2021-03-01 06:00', '2021-03-29 06:00', '2021-04-26 06:00', '2021-05-24 06:00', '2021-06-21 06:00', '2021-07-19 06:00', '2021-08-16 06:00'],
labels: [' ', 'Dec 21<br>2020', 'Jan 18<br>2021', 'Feb 15', 'Mar 15', 'Apr 12', 'May 10', 'Jun 7', 'Jul 5', ' ']
},
{
range: ['2020-12-14 08:00', '2021-04-14 08:00'],
positions: ['2020-12-13 03:42:51.4286', '2020-12-21 11:42:51.4286', '2021-01-04 11:42:51.4286', '2021-01-18 11:42:51.4286', '2021-02-01 11:42:51.4286', '2021-02-15 11:42:51.4286', '2021-03-01 11:42:51.4286', '2021-03-15 11:42:51.4286', '2021-03-29 11:42:51.4286', '2021-04-12 11:42:51.4286'],
positions: ['2020-12-13 12:00', '2020-12-21 18:00', '2021-01-04 18:00', '2021-01-18 18:00', '2021-02-01 18:00', '2021-02-15 18:00', '2021-03-01 18:00', '2021-03-15 18:00', '2021-03-29 18:00', '2021-04-12 18:00'],
labels: [' ', 'Dec 21<br>2020', 'Jan 4<br>2021', 'Jan 18', 'Feb 1', 'Feb 15', 'Mar 1', 'Mar 15', 'Mar 29', 'Apr 12']
},
{
range: ['2020-12-14 08:00', '2021-02-14 08:00'],
positions: ['2020-12-13 03:42:51.4286', '2020-12-21 10:17:08.5714', '2020-12-28 10:17:08.5714', '2021-01-04 10:17:08.5714', '2021-01-11 10:17:08.5714', '2021-01-18 10:17:08.5714', '2021-01-25 10:17:08.5714', '2021-02-01 10:17:08.5714', '2021-02-08 11:42:51.4286', '2021-02-14 13:42:51.4286'],
positions: ['2020-12-13 12:00', '2020-12-21 18:00', '2020-12-28 18:00', '2021-01-04 18:00', '2021-01-11 18:00', '2021-01-18 18:00', '2021-01-25 18:00', '2021-02-01 18:00', '2021-02-08 18:00', '2021-02-14 20:00'],
labels: [' ', 'Dec 21<br>2020', 'Dec 28', 'Jan 4<br>2021', 'Jan 11', 'Jan 18', 'Jan 25', 'Feb 1', 'Feb 8', ' ']
},
{
range: ['2020-12-14 08:00', '2021-01-14 08:00'],
positions: ['2020-12-14 05:08:34.2857', '2020-12-16 12:17:08.5714', '2020-12-18 09:08:34.2857', '2020-12-22 12:17:08.5714', '2020-12-24 18:00', '2020-12-28 12:17:08.5714', '2020-12-30 12:17:08.5714', '2021-01-01 09:08:34.2857', '2021-01-05 12:17:08.5714', '2021-01-07 18:00', '2021-01-11 12:17:08.5714', '2021-01-13 12:17:08.5714'],
labels: [' ', 'Dec 16<br>2020', 'Dec 18', 'Dec 22', 'Dec 24', 'Dec 28', 'Dec 30', 'Jan 1<br>2021', 'Jan 5', 'Jan 7', 'Jan 11', 'Jan 13']
positions: ['2020-12-14 12:00', '2020-12-16 18:00', '2020-12-18 18:00', '2020-12-22 18:00', '2020-12-24 18:00', '2020-12-28 18:00', '2020-12-30 18:00', '2021-01-01 18:00', '2021-01-05 18:00', '2021-01-07 18:00', '2021-01-11 18:00', '2021-01-13 18:00'],
labels: ['Dec 14<br>2020', 'Dec 16', 'Dec 18', 'Dec 22', 'Dec 24', 'Dec 28', 'Dec 30', 'Jan 1<br>2021', 'Jan 5', 'Jan 7', 'Jan 11', 'Jan 13']
},
{
range: ['2020-12-14 08:00', '2021-01-01 08:00'],
positions: ['2020-12-14 05:08:34.2857', '2020-12-16 12:17:08.5714', '2020-12-18 09:08:34.2857', '2020-12-22 12:17:08.5714', '2020-12-24 18:00', '2020-12-28 12:17:08.5714', '2020-12-30 12:17:08.5714', '2021-01-01 12:17:08.5714'],
labels: [' ', 'Dec 16<br>2020', 'Dec 18', 'Dec 22', 'Dec 24', 'Dec 28', 'Dec 30', ' ']
positions: ['2020-12-14 12:00', '2020-12-16 18:00', '2020-12-18 18:00', '2020-12-22 18:00', '2020-12-24 18:00', '2020-12-28 18:00', '2020-12-30 18:00', '2021-01-01 18:00'],
labels: ['Dec 14<br>2020', 'Dec 16', 'Dec 18', 'Dec 22', 'Dec 24', 'Dec 28', 'Dec 30', ' ']
},
{
range: ['2020-12-14 08:00', '2020-12-22 08:00'],
positions: ['2020-12-14 04:51:25.7143', '2020-12-15 18:00', '2020-12-16 18:00', '2020-12-17 18:00', '2020-12-18 18:00', '2020-12-21 18:00', '2020-12-22 18:00'],
labels: [' ', '06:00<br>Dec 15, 2020', '06:00<br>Dec 16, 2020', '06:00<br>Dec 17, 2020', '06:00<br>Dec 18, 2020', '06:00<br>Dec 21, 2020', ' ']
positions: ['2020-12-14 12:00', '2020-12-15 18:00', '2020-12-16 18:00', '2020-12-17 18:00', '2020-12-18 18:00', '2020-12-21 18:00', '2020-12-22 18:00'],
labels: ['00:00<br>Dec 14, 2020', '06:00<br>Dec 15, 2020', '06:00<br>Dec 16, 2020', '06:00<br>Dec 17, 2020', '06:00<br>Dec 18, 2020', '06:00<br>Dec 21, 2020', ' ']
},
{
range: ['2020-12-14 08:00', '2020-12-18 08:00'],
Expand Down