Skip to content

Commit f90690f

Browse files
authored
Merge pull request #4831 from plotly/rangebreaks-improve-ticks
Fix rangebreaks overlapping and tick positions
2 parents 5b8b1db + 9241578 commit f90690f

19 files changed

+633
-43
lines changed

src/plots/cartesian/axes.js

+59-35
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,15 @@ var autorange = require('./autorange');
5656
axes.getAutoRange = autorange.getAutoRange;
5757
axes.findExtremes = autorange.findExtremes;
5858

59+
var epsilon = 0.0001;
60+
function expandRange(range) {
61+
var delta = (range[1] - range[0]) * epsilon;
62+
return [
63+
range[0] - delta,
64+
range[1] + delta
65+
];
66+
}
67+
5968
/*
6069
* find the list of possible axes to reference with an xref or yref attribute
6170
* and coerce it to that list
@@ -566,8 +575,9 @@ axes.calcTicks = function calcTicks(ax) {
566575
ax._tmin = axes.tickFirst(ax);
567576

568577
// add a tiny bit so we get ticks which may have rounded out
569-
var startTick = rng[0] * 1.0001 - rng[1] * 0.0001;
570-
var endTick = rng[1] * 1.0001 - rng[0] * 0.0001;
578+
var exRng = expandRange(rng);
579+
var startTick = exRng[0];
580+
var endTick = exRng[1];
571581
// check for reversed axis
572582
var axrev = (rng[1] < rng[0]);
573583

@@ -612,39 +622,42 @@ axes.calcTicks = function calcTicks(ax) {
612622

613623
if(ax.rangebreaks) {
614624
// replace ticks inside breaks that would get a tick
615-
if(ax.tickmode === 'auto') {
616-
for(var t = 0; t < tickVals.length; t++) {
617-
var value = tickVals[t].value;
618-
if(ax.maskBreaks(value) === BADNUM) {
619-
// find which break we are in
620-
for(var k = 0; k < ax._rangebreaks.length; k++) {
621-
var brk = ax._rangebreaks[k];
622-
if(value >= brk.min && value < brk.max) {
623-
tickVals[t].value = brk.max; // replace with break end
624-
break;
625-
}
626-
}
627-
}
628-
}
629-
}
630-
631-
// reduce ticks
625+
// and reduce ticks
632626
var len = tickVals.length;
633-
if(len > 2) {
634-
var tf2 = 2 * (ax.tickfont ? ax.tickfont.size : 12);
627+
if(len) {
628+
var tf = 0;
629+
if(ax.tickmode === 'auto') {
630+
tf =
631+
(ax._id.charAt(0) === 'y' ? 2 : 6) *
632+
(ax.tickfont ? ax.tickfont.size : 12);
633+
}
635634

636635
var newTickVals = [];
637636
var prevPos;
638637

639638
var dir = axrev ? 1 : -1;
640639
var first = axrev ? 0 : len - 1;
641640
var last = axrev ? len - 1 : 0;
642-
for(var q = first; dir * q <= dir * last; q += dir) { // apply reverse loop to pick greater values in breaks first
643-
var pos = ax.c2p(tickVals[q].value);
641+
for(var q = first; dir * q <= dir * last; q += dir) {
642+
var tickVal = tickVals[q];
643+
if(ax.maskBreaks(tickVal.value) === BADNUM) {
644+
tickVal.value = moveOutsideBreak(tickVal.value, ax);
645+
646+
if(ax._rl && (
647+
ax._rl[0] === tickVal.value ||
648+
ax._rl[1] === tickVal.value
649+
)) continue;
650+
}
644651

645-
if(prevPos === undefined || Math.abs(pos - prevPos) > tf2) {
652+
var pos = ax.c2p(tickVal.value);
653+
654+
if(pos === prevPos) {
655+
if(newTickVals[newTickVals.length - 1].value < tickVal.value) {
656+
newTickVals[newTickVals.length - 1] = tickVal;
657+
}
658+
} else if(prevPos === undefined || Math.abs(pos - prevPos) > tf) {
646659
prevPos = pos;
647-
newTickVals.push(tickVals[q]);
660+
newTickVals.push(tickVal);
648661
}
649662
}
650663
tickVals = newTickVals.reverse();
@@ -691,10 +704,9 @@ function arrayTicks(ax) {
691704
var text = ax.ticktext;
692705
var ticksOut = new Array(vals.length);
693706
var rng = Lib.simpleMap(ax.range, ax.r2l);
694-
var r0expanded = rng[0] * 1.0001 - rng[1] * 0.0001;
695-
var r1expanded = rng[1] * 1.0001 - rng[0] * 0.0001;
696-
var tickMin = Math.min(r0expanded, r1expanded);
697-
var tickMax = Math.max(r0expanded, r1expanded);
707+
var exRng = expandRange(rng);
708+
var tickMin = Math.min(exRng[0], exRng[1]);
709+
var tickMax = Math.max(exRng[0], exRng[1]);
698710
var j = 0;
699711

700712
// without a text array, just format the given values as any other ticks
@@ -785,8 +797,7 @@ axes.autoTicks = function(ax, roughDTick) {
785797
roughDTick /= ONEAVGMONTH;
786798
ax.dtick = 'M' + roundDTick(roughDTick, 1, roundBase24);
787799
} else if(roughX2 > ONEDAY) {
788-
ax.dtick = roundDTick(roughDTick, ONEDAY, ax._hasDayOfWeekBreaks ? [1, 7, 14] : roundDays);
789-
800+
ax.dtick = roundDTick(roughDTick, ONEDAY, ax._hasDayOfWeekBreaks ? [1, 2, 7, 14] : roundDays);
790801
// get week ticks on sunday
791802
// this will also move the base tick off 2000-01-01 if dtick is
792803
// 2 or 3 days... but that's a weird enough case that we'll ignore it.
@@ -934,18 +945,20 @@ axes.tickIncrement = function(x, dtick, axrev, calendar) {
934945
if(tType === 'M') return Lib.incrementMonth(x, dtSigned, calendar);
935946

936947
// Log scales: Linear, Digits
937-
else if(tType === 'L') return Math.log(Math.pow(10, x) + dtSigned) / Math.LN10;
948+
if(tType === 'L') return Math.log(Math.pow(10, x) + dtSigned) / Math.LN10;
938949

939950
// log10 of 2,5,10, or all digits (logs just have to be
940951
// close enough to round)
941-
else if(tType === 'D') {
952+
if(tType === 'D') {
942953
var tickset = (dtick === 'D2') ? roundLog2 : roundLog1;
943954
var x2 = x + axSign * 0.01;
944955
var frac = Lib.roundUp(Lib.mod(x2, 1), tickset, axrev);
945956

946957
return Math.floor(x2) +
947958
Math.log(d3.round(Math.pow(10, frac), 1)) / Math.LN10;
948-
} else throw 'unrecognized dtick ' + String(dtick);
959+
}
960+
961+
throw 'unrecognized dtick ' + String(dtick);
949962
};
950963

951964
// calculate the first tick on an axis
@@ -956,7 +969,7 @@ axes.tickFirst = function(ax) {
956969
var sRound = axrev ? Math.floor : Math.ceil;
957970
// add a tiny extra bit to make sure we get ticks
958971
// that may have been rounded out
959-
var r0 = rng[0] * 1.0001 - rng[1] * 0.0001;
972+
var r0 = expandRange(rng)[0];
960973
var dtick = ax.dtick;
961974
var tick0 = r2l(ax.tick0);
962975

@@ -3158,3 +3171,14 @@ function swapAxisAttrs(layout, key, xFullAxes, yFullAxes, dfltTitle) {
31583171
function isAngular(ax) {
31593172
return ax._id === 'angularaxis';
31603173
}
3174+
3175+
function moveOutsideBreak(v, ax) {
3176+
var len = ax._rangebreaks.length;
3177+
for(var k = 0; k < len; k++) {
3178+
var brk = ax._rangebreaks[k];
3179+
if(v >= brk.min && v < brk.max) {
3180+
return brk.max;
3181+
}
3182+
}
3183+
return v;
3184+
}

src/plots/cartesian/set_convert.js

+1-3
Original file line numberDiff line numberDiff line change
@@ -686,9 +686,7 @@ module.exports = function setConvert(ax, fullLayout) {
686686
var isNewBreak = true;
687687
for(var j = 0; j < rangebreaksOut.length; j++) {
688688
var brkj = rangebreaksOut[j];
689-
if(min > brkj.max || max < brkj.min) {
690-
// potentially a new break
691-
} else {
689+
if(min < brkj.max && max >= brkj.min) {
692690
if(min < brkj.min) {
693691
brkj.min = min;
694692
}
-1.21 KB
Loading
32.6 KB
Loading
Loading
-2.74 KB
Loading
Loading
65.8 KB
Loading
-3.51 KB
Loading
Loading
Loading
-458 Bytes
Loading
Loading

test/image/baselines/axes_breaks.png

68 Bytes
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
{
2+
"data": [
3+
{
4+
"type": "scatter",
5+
"mode": "markers",
6+
"x": [
7+
"2020-04-06T09:00:00",
8+
"2020-04-06T10:00:00",
9+
"2020-04-06T11:00:00",
10+
"2020-04-06T12:00:00",
11+
"2020-04-06T13:00:00",
12+
"2020-04-06T14:00:00",
13+
"2020-04-06T15:00:00",
14+
"2020-04-06T16:00:00",
15+
"2020-04-07T09:00:00",
16+
"2020-04-07T10:00:00",
17+
"2020-04-07T11:00:00",
18+
"2020-04-07T12:00:00",
19+
"2020-04-07T13:00:00",
20+
"2020-04-07T14:00:00",
21+
"2020-04-07T15:00:00",
22+
"2020-04-07T16:00:00",
23+
"2020-04-08T09:00:00",
24+
"2020-04-08T10:00:00",
25+
"2020-04-08T11:00:00",
26+
"2020-04-08T12:00:00",
27+
"2020-04-08T13:00:00",
28+
"2020-04-08T14:00:00",
29+
"2020-04-08T15:00:00",
30+
"2020-04-08T16:00:00",
31+
"2020-04-09T09:00:00",
32+
"2020-04-09T10:00:00",
33+
"2020-04-09T11:00:00",
34+
"2020-04-09T12:00:00",
35+
"2020-04-09T13:00:00",
36+
"2020-04-09T14:00:00",
37+
"2020-04-09T15:00:00",
38+
"2020-04-09T16:00:00",
39+
"2020-04-10T09:00:00",
40+
"2020-04-10T10:00:00",
41+
"2020-04-10T11:00:00",
42+
"2020-04-10T12:00:00",
43+
"2020-04-10T13:00:00",
44+
"2020-04-10T14:00:00",
45+
"2020-04-10T15:00:00",
46+
"2020-04-10T16:00:00"
47+
],
48+
"y": [
49+
0.19742877314456364,
50+
0.1509714558226325,
51+
0.3730270552929804,
52+
0.7394093812216096,
53+
1.2149308862244954,
54+
1.5707342286171064,
55+
1.0824483128021085,
56+
0.9424263772804724,
57+
1.1724169397045303,
58+
0.8440466169659708,
59+
0.8650832231701001,
60+
0.41942121150935374,
61+
0.11941773640575382,
62+
-0.3620604691336322,
63+
-0.06836276577621159,
64+
-0.34443807771583146,
65+
-0.49908639701892876,
66+
-0.07100510355333789,
67+
0.13340929837019488,
68+
-0.33475177209849727,
69+
-0.6700576156005845,
70+
-0.548579214100821,
71+
-0.4713506254966534,
72+
-0.7334578041221448,
73+
-0.299243806197351,
74+
-0.18527785023145504,
75+
-0.14964504720649674,
76+
-0.05973507085192575,
77+
0.17038695866484388,
78+
-0.01766804585555426,
79+
-0.11944698363946238,
80+
-0.40960323466434023,
81+
-0.7234102287840041,
82+
-0.2790378388000705,
83+
-0.039487043750782935,
84+
-0.04902823513321586,
85+
-0.3216136071598926,
86+
-0.5672571253894997,
87+
-1.009227965065624,
88+
-1.0748113395075032
89+
]
90+
}
91+
],
92+
"layout": {
93+
"width": 800,
94+
"height": 600,
95+
"xaxis": {
96+
"rangebreaks": [
97+
{
98+
"bounds": [
99+
17,
100+
9
101+
],
102+
"pattern": "hour"
103+
}
104+
],
105+
"title": {
106+
"text": "time"
107+
}
108+
},
109+
"yaxis": {
110+
"title": {
111+
"text": "values"
112+
}
113+
}
114+
}
115+
}

0 commit comments

Comments
 (0)