Skip to content

Commit b4a4004

Browse files
authored
Merge pull request #4937 from plotly/fix4933-rangebreaks-ohlc-box
Fix misalignment of candlestick, ohlc and box plots when using rangebreaks
2 parents c21b3a5 + 5900b03 commit b4a4004

File tree

6 files changed

+201
-16
lines changed

6 files changed

+201
-16
lines changed

src/traces/box/plot.js

+40-14
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,11 @@ function plot(gd, plotinfo, cdbox, boxLayer) {
5252
}
5353

5454
function plotBoxAndWhiskers(sel, axes, trace, t) {
55-
var posAxis = axes.pos;
55+
var isHorizontal = trace.orientation === 'h';
5656
var valAxis = axes.val;
57+
var posAxis = axes.pos;
58+
var posHasRangeBreaks = !!posAxis.rangebreaks;
59+
5760
var bPos = t.bPos;
5861
var wdPos = t.wdPos || 0;
5962
var bPosPxOffset = t.bPosPxOffset || 0;
@@ -87,11 +90,15 @@ function plotBoxAndWhiskers(sel, axes, trace, t) {
8790
if(d.empty) return 'M0,0Z';
8891

8992
var lcenter = posAxis.c2l(d.pos + bPos, true);
90-
var posc = posAxis.l2p(lcenter) + bPosPxOffset;
93+
9194
var pos0 = posAxis.l2p(lcenter - bdPos0) + bPosPxOffset;
9295
var pos1 = posAxis.l2p(lcenter + bdPos1) + bPosPxOffset;
93-
var posw0 = posAxis.l2p(lcenter - wdPos) + bPosPxOffset;
94-
var posw1 = posAxis.l2p(lcenter + wdPos) + bPosPxOffset;
96+
var posc = posHasRangeBreaks ? (pos0 + pos1) / 2 : posAxis.l2p(lcenter) + bPosPxOffset;
97+
98+
var r = trace.whiskerwidth;
99+
var posw0 = posHasRangeBreaks ? pos0 * r + (1 - r) * posc : posAxis.l2p(lcenter - wdPos) + bPosPxOffset;
100+
var posw1 = posHasRangeBreaks ? pos1 * r + (1 - r) * posc : posAxis.l2p(lcenter + wdPos) + bPosPxOffset;
101+
95102
var posm0 = posAxis.l2p(lcenter - bdPos0 * nw) + bPosPxOffset;
96103
var posm1 = posAxis.l2p(lcenter + bdPos1 * nw) + bPosPxOffset;
97104
var q1 = valAxis.c2p(d.q1, true);
@@ -115,30 +122,45 @@ function plotBoxAndWhiskers(sel, axes, trace, t) {
115122
var ln = valAxis.c2p(d.ln, true);
116123
var un = valAxis.c2p(d.un, true);
117124

118-
if(trace.orientation === 'h') {
125+
if(isHorizontal) {
119126
d3.select(this).attr('d',
120127
'M' + m + ',' + posm0 + 'V' + posm1 + // median line
121128
'M' + q1 + ',' + pos0 + 'V' + pos1 + // left edge
122-
(notched ? 'H' + ln + 'L' + m + ',' + posm1 + 'L' + un + ',' + pos1 : '') + // top notched edge
129+
(notched ?
130+
'H' + ln + 'L' + m + ',' + posm1 + 'L' + un + ',' + pos1 :
131+
''
132+
) + // top notched edge
123133
'H' + q3 + // end of the top edge
124134
'V' + pos0 + // right edge
125135
(notched ? 'H' + un + 'L' + m + ',' + posm0 + 'L' + ln + ',' + pos0 : '') + // bottom notched edge
126136
'Z' + // end of the box
127137
'M' + q1 + ',' + posc + 'H' + lf + 'M' + q3 + ',' + posc + 'H' + uf + // whiskers
128-
((whiskerWidth === 0) ? '' : // whisker caps
129-
'M' + lf + ',' + posw0 + 'V' + posw1 + 'M' + uf + ',' + posw0 + 'V' + posw1));
138+
(whiskerWidth === 0 ?
139+
'' : // whisker caps
140+
'M' + lf + ',' + posw0 + 'V' + posw1 + 'M' + uf + ',' + posw0 + 'V' + posw1
141+
)
142+
);
130143
} else {
131144
d3.select(this).attr('d',
132145
'M' + posm0 + ',' + m + 'H' + posm1 + // median line
133146
'M' + pos0 + ',' + q1 + 'H' + pos1 + // top of the box
134-
(notched ? 'V' + ln + 'L' + posm1 + ',' + m + 'L' + pos1 + ',' + un : '') + // notched right edge
147+
(notched ?
148+
'V' + ln + 'L' + posm1 + ',' + m + 'L' + pos1 + ',' + un :
149+
''
150+
) + // notched right edge
135151
'V' + q3 + // end of the right edge
136152
'H' + pos0 + // bottom of the box
137-
(notched ? 'V' + un + 'L' + posm0 + ',' + m + 'L' + pos0 + ',' + ln : '') + // notched left edge
153+
(notched ?
154+
'V' + un + 'L' + posm0 + ',' + m + 'L' + pos0 + ',' + ln :
155+
''
156+
) + // notched left edge
138157
'Z' + // end of the box
139158
'M' + posc + ',' + q1 + 'V' + lf + 'M' + posc + ',' + q3 + 'V' + uf + // whiskers
140-
((whiskerWidth === 0) ? '' : // whisker caps
141-
'M' + posw0 + ',' + lf + 'H' + posw1 + 'M' + posw0 + ',' + uf + 'H' + posw1));
159+
(whiskerWidth === 0 ?
160+
'' : // whisker caps
161+
'M' + posw0 + ',' + lf + 'H' + posw1 + 'M' + posw0 + ',' + uf + 'H' + posw1
162+
)
163+
);
142164
}
143165
});
144166
}
@@ -254,8 +276,10 @@ function plotPoints(sel, axes, trace, t) {
254276
}
255277

256278
function plotBoxMean(sel, axes, trace, t) {
257-
var posAxis = axes.pos;
258279
var valAxis = axes.val;
280+
var posAxis = axes.pos;
281+
var posHasRangeBreaks = !!posAxis.rangebreaks;
282+
259283
var bPos = t.bPos;
260284
var bPosPxOffset = t.bPosPxOffset || 0;
261285

@@ -289,9 +313,11 @@ function plotBoxMean(sel, axes, trace, t) {
289313

290314
paths.each(function(d) {
291315
var lcenter = posAxis.c2l(d.pos + bPos, true);
292-
var posc = posAxis.l2p(lcenter) + bPosPxOffset;
316+
293317
var pos0 = posAxis.l2p(lcenter - bdPos0) + bPosPxOffset;
294318
var pos1 = posAxis.l2p(lcenter + bdPos1) + bPosPxOffset;
319+
var posc = posHasRangeBreaks ? (pos0 + pos1) / 2 : posAxis.l2p(lcenter) + bPosPxOffset;
320+
295321
var m = valAxis.c2p(d.mean, true);
296322
var sl = valAxis.c2p(d.mean - d.sd, true);
297323
var sh = valAxis.c2p(d.mean + d.sd, true);

src/traces/ohlc/plot.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@ var d3 = require('d3');
1313
var Lib = require('../../lib');
1414

1515
module.exports = function plot(gd, plotinfo, cdOHLC, ohlcLayer) {
16-
var xa = plotinfo.xaxis;
1716
var ya = plotinfo.yaxis;
17+
var xa = plotinfo.xaxis;
18+
var posHasRangeBreaks = !!xa.rangebreaks;
1819

1920
Lib.makeTraceGroups(ohlcLayer, cdOHLC, 'trace ohlc').each(function(cd) {
2021
var plotGroup = d3.select(this);
@@ -38,9 +39,9 @@ module.exports = function plot(gd, plotinfo, cdOHLC, ohlcLayer) {
3839
paths.attr('d', function(d) {
3940
if(d.empty) return 'M0,0Z';
4041

41-
var x = xa.c2p(d.pos, true);
4242
var xo = xa.c2p(d.pos - tickLen, true);
4343
var xc = xa.c2p(d.pos + tickLen, true);
44+
var x = posHasRangeBreaks ? (xo + xc) / 2 : xa.c2p(d.pos, true);
4445

4546
var yo = ya.c2p(d.o, true);
4647
var yh = ya.c2p(d.h, true);
Loading
-3 Bytes
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
{
2+
"data": [
3+
{
4+
"name": "candlestick",
5+
"type": "candlestick",
6+
"x": [
7+
"2017-01-09 00:00",
8+
"2017-01-16 00:00"
9+
],
10+
"high": [
11+
3,
12+
4
13+
],
14+
"open": [
15+
2,
16+
3
17+
],
18+
"close": [
19+
1,
20+
2
21+
],
22+
"low": [
23+
0,
24+
1
25+
]
26+
},
27+
{
28+
"name": "box",
29+
"type": "box",
30+
"yaxis": "y2",
31+
"x": [
32+
"2017-01-09 00:00",
33+
"2017-01-16 00:00"
34+
],
35+
"upperfence": [
36+
4,
37+
5
38+
],
39+
"q3": [
40+
3,
41+
4
42+
],
43+
"median": [
44+
2,
45+
3
46+
],
47+
"q1": [
48+
1,
49+
2
50+
],
51+
"lowerfence": [
52+
0,
53+
1
54+
]
55+
},
56+
{
57+
"name": "box",
58+
"type": "box",
59+
"yaxis": "y3",
60+
"whiskerwidth": 1,
61+
"x": [
62+
"2017-01-09 00:00",
63+
"2017-01-16 00:00"
64+
],
65+
"upperfence": [
66+
4,
67+
5
68+
],
69+
"q3": [
70+
3,
71+
4
72+
],
73+
"median": [
74+
2,
75+
3
76+
],
77+
"q1": [
78+
1,
79+
2
80+
],
81+
"lowerfence": [
82+
0,
83+
1
84+
]
85+
},
86+
{
87+
"name": "ohlc",
88+
"type": "ohlc",
89+
"yaxis": "y4",
90+
"x": [
91+
"2017-01-09 00:00",
92+
"2017-01-16 00:00"
93+
],
94+
"high": [
95+
3,
96+
4
97+
],
98+
"open": [
99+
2,
100+
3
101+
],
102+
"close": [
103+
1,
104+
2
105+
],
106+
"low": [
107+
0,
108+
1
109+
]
110+
}
111+
],
112+
"layout": {
113+
"width": 600,
114+
"height": 800,
115+
"showlegend": false,
116+
"xaxis": {
117+
"rangebreaks": [
118+
{
119+
"pattern": "day of week",
120+
"bounds": [
121+
6,
122+
1
123+
]
124+
}
125+
]
126+
},
127+
"yaxis": {
128+
"anchor": "x",
129+
"domain": [
130+
0.225,
131+
0.375
132+
]
133+
},
134+
"yaxis2": {
135+
"anchor": "x",
136+
"domain": [
137+
0.425,
138+
0.575
139+
]
140+
},
141+
"yaxis3": {
142+
"anchor": "x",
143+
"domain": [
144+
0.625,
145+
0.775
146+
]
147+
},
148+
"yaxis4": {
149+
"anchor": "x",
150+
"domain": [
151+
0.825,
152+
1
153+
]
154+
}
155+
}
156+
}

test/jasmine/tests/mock_test.js

+2
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ var list = [
6262
'axes_breaks-heatmap2d',
6363
'axes_breaks-histogram2d',
6464
'axes_breaks-night_autorange-reversed',
65+
'axes_breaks-ohlc_candlestick_box',
6566
'axes_breaks-overlap',
6667
'axes_breaks-rangeslider',
6768
'axes_breaks-reversed-without-pattern',
@@ -1103,6 +1104,7 @@ figs['axes_breaks-heatmap1d'] = require('@mocks/axes_breaks-heatmap1d');
11031104
figs['axes_breaks-heatmap2d'] = require('@mocks/axes_breaks-heatmap2d');
11041105
figs['axes_breaks-histogram2d'] = require('@mocks/axes_breaks-histogram2d');
11051106
figs['axes_breaks-night_autorange-reversed'] = require('@mocks/axes_breaks-night_autorange-reversed');
1107+
figs['axes_breaks-ohlc_candlestick_box'] = require('@mocks/axes_breaks-ohlc_candlestick_box');
11061108
figs['axes_breaks-overlap'] = require('@mocks/axes_breaks-overlap');
11071109
figs['axes_breaks-rangeslider'] = require('@mocks/axes_breaks-rangeslider');
11081110
figs['axes_breaks-reversed-without-pattern'] = require('@mocks/axes_breaks-reversed-without-pattern');

0 commit comments

Comments
 (0)