Skip to content

Commit 39c2f06

Browse files
authored
Merge pull request #6593 from plotly/colorbar-positioning
Add `xref` and `yref` to colorbars
2 parents 44c97be + 39277d9 commit 39c2f06

9 files changed

+1042
-254
lines changed

draftlogs/6593_add.md

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- Add `colorbar.xref` and `colorbar.yref` to enable container-referenced positioning for plot colorbars [[#6593](https://github.com/plotly/plotly.js/pull/6593)], with thanks to [Gamma Technologies](https://www.gtisoft.com/) for sponsoring the related development.

src/components/colorbar/attributes.js

+36-10
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,25 @@ module.exports = overrideAll({
5757
},
5858
x: {
5959
valType: 'number',
60-
min: -2,
61-
max: 3,
6260
description: [
63-
'Sets the x position of the color bar (in plot fraction).',
64-
'Defaults to 1.02 when `orientation` is *v* and',
65-
'0.5 when `orientation` is *h*.'
61+
'Sets the x position with respect to `xref` of the color bar (in plot fraction).',
62+
'When `xref` is *paper*, defaults to 1.02 when `orientation` is *v* and',
63+
'0.5 when `orientation` is *h*.',
64+
'When `xref` is *container*, defaults to *1* when `orientation` is *v* and',
65+
'0.5 when `orientation` is *h*.',
66+
'Must be between *0* and *1* if `xref` is *container*',
67+
'and between *-2* and *3* if `xref` is *paper*.'
68+
].join(' ')
69+
},
70+
xref: {
71+
valType: 'enumerated',
72+
dflt: 'paper',
73+
values: ['container', 'paper'],
74+
editType: 'layoutstyle',
75+
description: [
76+
'Sets the container `x` refers to.',
77+
'*container* spans the entire `width` of the plot.',
78+
'*paper* refers to the width of the plotting area only.'
6679
].join(' ')
6780
},
6881
xanchor: {
@@ -84,14 +97,27 @@ module.exports = overrideAll({
8497
},
8598
y: {
8699
valType: 'number',
87-
min: -2,
88-
max: 3,
89100
description: [
90-
'Sets the y position of the color bar (in plot fraction).',
91-
'Defaults to 0.5 when `orientation` is *v* and',
92-
'1.02 when `orientation` is *h*.'
101+
'Sets the y position with respect to `yref` of the color bar (in plot fraction).',
102+
'When `yref` is *paper*, defaults to 0.5 when `orientation` is *v* and',
103+
'1.02 when `orientation` is *h*.',
104+
'When `yref` is *container*, defaults to 0.5 when `orientation` is *v* and',
105+
'1 when `orientation` is *h*.',
106+
'Must be between *0* and *1* if `yref` is *container*',
107+
'and between *-2* and *3* if `yref` is *paper*.'
93108
].join(' ')
94109
},
110+
yref: {
111+
valType: 'enumerated',
112+
dflt: 'paper',
113+
values: ['container', 'paper'],
114+
editType: 'layoutstyle',
115+
description: [
116+
'Sets the container `y` refers to.',
117+
'*container* spans the entire `height` of the plot.',
118+
'*paper* refers to the height of the plotting area only.'
119+
].join(' '),
120+
},
95121
yanchor: {
96122
valType: 'enumerated',
97123
values: ['top', 'middle', 'bottom'],

src/components/colorbar/defaults.js

+41-4
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,48 @@ module.exports = function colorbarDefaults(containerIn, containerOut, layout) {
3737
isVertical ? h : w
3838
);
3939

40-
coerce('x', isVertical ? 1.02 : 0.5);
41-
coerce('xanchor', isVertical ? 'left' : 'center');
40+
var yref = coerce('yref');
41+
var xref = coerce('xref');
42+
43+
var isPaperY = yref === 'paper';
44+
var isPaperX = xref === 'paper';
45+
46+
var defaultX, defaultY, defaultYAnchor;
47+
var defaultXAnchor = 'left';
48+
49+
if(isVertical) {
50+
defaultYAnchor = 'middle';
51+
defaultXAnchor = isPaperX ? 'left' : 'right';
52+
defaultX = isPaperX ? 1.02 : 1;
53+
defaultY = 0.5;
54+
} else {
55+
defaultYAnchor = isPaperY ? 'bottom' : 'top';
56+
defaultXAnchor = 'center';
57+
defaultX = 0.5;
58+
defaultY = isPaperY ? 1.02 : 1;
59+
}
60+
61+
Lib.coerce(colorbarIn, colorbarOut, {
62+
x: {
63+
valType: 'number',
64+
min: isPaperX ? -2 : 0,
65+
max: isPaperX ? 3 : 1,
66+
dflt: defaultX,
67+
}
68+
}, 'x');
69+
70+
Lib.coerce(colorbarIn, colorbarOut, {
71+
y: {
72+
valType: 'number',
73+
min: isPaperY ? -2 : 0,
74+
max: isPaperY ? 3 : 1,
75+
dflt: defaultY,
76+
}
77+
}, 'y');
78+
79+
coerce('xanchor', defaultXAnchor);
4280
coerce('xpad');
43-
coerce('y', isVertical ? 0.5 : 1.02);
44-
coerce('yanchor', isVertical ? 'middle' : 'bottom');
81+
coerce('yanchor', defaultYAnchor);
4582
coerce('ypad');
4683
Lib.noneOrAll(colorbarIn, colorbarOut, ['x', 'y']);
4784

src/components/colorbar/draw.js

+55-18
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,9 @@ function drawColorBar(g, opts, gd) {
182182
var optsX = opts.x;
183183
var optsY = isVertical ? opts.y : 1 - opts.y;
184184

185+
var isPaperY = opts.yref === 'paper';
186+
var isPaperX = opts.xref === 'paper';
187+
185188
var fullLayout = gd._fullLayout;
186189
var gs = fullLayout._size;
187190

@@ -216,11 +219,14 @@ function drawColorBar(g, opts, gd) {
216219
var lenPx = Math.round(len * (lenmode === 'fraction' ? (isVertical ? gs.h : gs.w) : 1));
217220
var lenFrac = lenPx / (isVertical ? gs.h : gs.w);
218221

222+
var posW = isPaperX ? gs.w : gd._fullLayout.width;
223+
var posH = isPaperY ? gs.h : gd._fullLayout.height;
224+
219225
// x positioning: do it initially just for left anchor,
220226
// then fix at the end (since we don't know the width yet)
221227
var uPx = Math.round(isVertical ?
222-
optsX * gs.w + xpad :
223-
optsY * gs.h + ypad
228+
optsX * posW + xpad :
229+
optsY * posH + ypad
224230
);
225231

226232
var xRatio = {center: 0.5, right: 1}[xanchor] || 0;
@@ -237,8 +243,8 @@ function drawColorBar(g, opts, gd) {
237243
optsX - xRatio * lenFrac;
238244

239245
var vPx = Math.round(isVertical ?
240-
gs.h * (1 - vFrac) :
241-
gs.w * vFrac
246+
posH * (1 - vFrac) :
247+
posW * vFrac
242248
);
243249

244250
// stash a few things for makeEditable
@@ -351,18 +357,18 @@ function drawColorBar(g, opts, gd) {
351357
var x, y;
352358

353359
if(titleSide === 'top') {
354-
x = xpad + gs.l + gs.w * optsX;
355-
y = ypad + gs.t + gs.h * (1 - vFrac - lenFrac) + 3 + titleFontSize * 0.75;
360+
x = xpad + gs.l + posW * optsX;
361+
y = ypad + gs.t + posH * (1 - vFrac - lenFrac) + 3 + titleFontSize * 0.75;
356362
}
357363

358364
if(titleSide === 'bottom') {
359-
x = xpad + gs.l + gs.w * optsX;
360-
y = ypad + gs.t + gs.h * (1 - vFrac) - 3 - titleFontSize * 0.25;
365+
x = xpad + gs.l + posW * optsX;
366+
y = ypad + gs.t + posH * (1 - vFrac) - 3 - titleFontSize * 0.25;
361367
}
362368

363369
if(titleSide === 'right') {
364-
y = ypad + gs.t + gs.h * optsY + 3 + titleFontSize * 0.75;
365-
x = xpad + gs.l + gs.w * vFrac;
370+
y = ypad + gs.t + posH * optsY + 3 + titleFontSize * 0.75;
371+
x = xpad + gs.l + posW * vFrac;
366372
}
367373

368374
drawTitle(ax._id + 'title', {
@@ -382,14 +388,14 @@ function drawColorBar(g, opts, gd) {
382388

383389
if(titleSide === 'right') {
384390
y = mid;
385-
x = gs.l + gs.w * pos + 10 + titleFontSize * (
391+
x = gs.l + posW * pos + 10 + titleFontSize * (
386392
ax.showticklabels ? 1 : 0.5
387393
);
388394
} else {
389395
x = mid;
390396

391397
if(titleSide === 'bottom') {
392-
y = gs.t + gs.h * pos + 10 + (
398+
y = gs.t + posH * pos + 10 + (
393399
ticklabelposition.indexOf('inside') === -1 ?
394400
ax.tickfont.size :
395401
0
@@ -402,7 +408,7 @@ function drawColorBar(g, opts, gd) {
402408

403409
if(titleSide === 'top') {
404410
var nlines = title.text.split('<br>').length;
405-
y = gs.t + gs.h * pos + 10 - thickPx - LINE_SPACING * titleFontSize * nlines;
411+
y = gs.t + posH * pos + 10 - thickPx - LINE_SPACING * titleFontSize * nlines;
406412
}
407413
}
408414

@@ -668,9 +674,13 @@ function drawColorBar(g, opts, gd) {
668674

669675
var extraW = borderwidth + outlinewidth;
670676

677+
// TODO - are these the correct positions?
678+
var lx = (isVertical ? uPx : vPx) - extraW / 2 - (isVertical ? xpad : 0);
679+
var ly = (isVertical ? vPx : uPx) - (isVertical ? lenPx : ypad + moveY - hColorbarMoveTitle);
680+
671681
g.select('.' + cn.cbbg)
672-
.attr('x', (isVertical ? uPx : vPx) - extraW / 2 - (isVertical ? xpad : 0))
673-
.attr('y', (isVertical ? vPx : uPx) - (isVertical ? lenPx : ypad + moveY - hColorbarMoveTitle))
682+
.attr('x', lx)
683+
.attr('y', ly)
674684
.attr(isVertical ? 'width' : 'height', Math.max(outerThickness - hColorbarMoveTitle, 2))
675685
.attr(isVertical ? 'height' : 'width', Math.max(lenPx + extraW, 2))
676686
.call(Color.fill, bgcolor)
@@ -693,9 +703,14 @@ function drawColorBar(g, opts, gd) {
693703
'stroke-width': outlinewidth
694704
});
695705

706+
var xShift = ((isVertical ? xRatio * outerThickness : 0));
707+
var yShift = ((isVertical ? 0 : (1 - yRatio) * outerThickness - moveY));
708+
xShift = isPaperX ? gs.l - xShift : -xShift;
709+
yShift = isPaperY ? gs.t - yShift : -yShift;
710+
696711
g.attr('transform', strTranslate(
697-
gs.l - (isVertical ? xRatio * outerThickness : 0),
698-
gs.t - (isVertical ? 0 : (1 - yRatio) * outerThickness - moveY)
712+
xShift,
713+
yShift
699714
));
700715

701716
if(!isVertical && (
@@ -802,8 +817,30 @@ function drawColorBar(g, opts, gd) {
802817
marginOpts.yb = optsY + thickness * bFrac;
803818
}
804819
}
820+
var sideY = opts.y < 0.5 ? 'b' : 't';
821+
var sideX = opts.x < 0.5 ? 'l' : 'r';
822+
823+
gd._fullLayout._reservedMargin[opts._id] = {};
824+
var possibleReservedMargins = {
825+
r: (fullLayout.width - lx - xShift),
826+
l: lx + marginOpts.r,
827+
b: (fullLayout.height - ly - yShift),
828+
t: ly + marginOpts.b
829+
};
805830

806-
Plots.autoMargin(gd, opts._id, marginOpts);
831+
if(isPaperX && isPaperY) {
832+
Plots.autoMargin(gd, opts._id, marginOpts);
833+
} else if(isPaperX) {
834+
gd._fullLayout._reservedMargin[opts._id][sideY] = possibleReservedMargins[sideY];
835+
} else if(isPaperY) {
836+
gd._fullLayout._reservedMargin[opts._id][sideX] = possibleReservedMargins[sideX];
837+
} else {
838+
if(isVertical) {
839+
gd._fullLayout._reservedMargin[opts._id][sideX] = possibleReservedMargins[sideX];
840+
} else {
841+
gd._fullLayout._reservedMargin[opts._id][sideY] = possibleReservedMargins[sideY];
842+
}
843+
}
807844
}
808845

809846
return Lib.syncOrAsync([
Loading
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
{
2+
"data": [
3+
{
4+
"colorbar": {
5+
"orientation": "h",
6+
"yref": "container",
7+
"y": 0,
8+
"yanchor": "bottom",
9+
"thickness": 10,
10+
"ticks": "outside",
11+
"bgcolor": "rgba(255,255,0,0.5)",
12+
"borderwidth": 4,
13+
"bordercolor": "gray",
14+
"title": {
15+
"text": "Colorbar<br>title",
16+
"font": {
17+
"size": 16
18+
}
19+
}
20+
},
21+
"z": [
22+
[
23+
1,
24+
3,
25+
5
26+
],
27+
[
28+
4,
29+
7,
30+
10
31+
],
32+
[
33+
7,
34+
11,
35+
14
36+
]
37+
],
38+
"type": "heatmap"
39+
}
40+
],
41+
"layout": {
42+
"margin": {"l": 0, "r": 0, "t": 0, "b": 0},
43+
"height": 300,
44+
"width": 400,
45+
"xaxis": {"automargin": true, "title": {"text": "X-axis title"}},
46+
"title": {"text": "Colorbar with `yref='container'` | horizontal", "automargin": true}
47+
}
48+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
{
2+
"data": [
3+
{
4+
"colorbar": {
5+
"orientation": "v",
6+
"xref": "container",
7+
"thickness": 10,
8+
"ticks": "outside",
9+
"bgcolor": "rgba(255,255,0,0.5)",
10+
"borderwidth": 4,
11+
"bordercolor": "gray",
12+
"title": {
13+
"side": "top",
14+
"text": "Colorbar<br>title",
15+
"font": {
16+
"size": 16
17+
}
18+
}
19+
},
20+
"z": [
21+
[
22+
1,
23+
3,
24+
5
25+
],
26+
[
27+
4,
28+
7,
29+
10
30+
],
31+
[
32+
7,
33+
11,
34+
14
35+
]
36+
],
37+
"type": "heatmap"
38+
}
39+
],
40+
"layout": {
41+
"margin": {"l": 0, "r": 0, "t": 0, "b": 0},
42+
"height": 300,
43+
"width": 400,
44+
"yaxis": {"automargin": true, "side": "right", "title": {"text": "Y-axis title"}},
45+
"title": {"text": "Colorbar with `xref='container' | vertical`", "automargin": true}
46+
}
47+
}

0 commit comments

Comments
 (0)