Skip to content

Commit 5a9e953

Browse files
committed
Merge branch 'master' into legend-positioning
2 parents e53e6bc + 39c2f06 commit 5a9e953

23 files changed

+1183
-332
lines changed

draftlogs/6574_change.md

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- Improve heatmap rendering performance when `zsmooth` is set to false [[#6574](https://github.com/plotly/plotly.js/pull/6574)], with thanks to @lvlte for the contribution!

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([

src/constants/pixelated_image.js

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
'use strict';
2+
3+
// Pixelated image rendering
4+
// The actual CSS declaration is prepended with fallbacks for older browsers.
5+
// NB. IE's `-ms-interpolation-mode` works only with <img> not with SVG <image>
6+
// https://developer.mozilla.org/en-US/docs/Web/CSS/image-rendering
7+
// https://caniuse.com/?search=image-rendering
8+
// http://phrogz.net/tmp/canvas_image_zoom.html
9+
10+
exports.CSS_DECLARATIONS = [
11+
['image-rendering', 'optimizeSpeed'],
12+
['image-rendering', '-moz-crisp-edges'],
13+
['image-rendering', '-o-crisp-edges'],
14+
['image-rendering', '-webkit-optimize-contrast'],
15+
['image-rendering', 'optimize-contrast'],
16+
['image-rendering', 'crisp-edges'],
17+
['image-rendering', 'pixelated']
18+
];
19+
20+
exports.STYLE = exports.CSS_DECLARATIONS.map(function(d) {
21+
return d.join(': ') + '; ';
22+
}).join('');

src/lib/supports_pixelated_image.js

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
'use strict';
2+
3+
var constants = require('../constants/pixelated_image');
4+
var Drawing = require('../components/drawing');
5+
var Lib = require('../lib');
6+
7+
var _supportsPixelated = null;
8+
9+
/**
10+
* Check browser support for pixelated image rendering
11+
*
12+
* @return {boolean}
13+
*/
14+
function supportsPixelatedImage() {
15+
if(_supportsPixelated !== null) { // only run the feature detection once
16+
return _supportsPixelated;
17+
}
18+
if(Lib.isIE()) {
19+
_supportsPixelated = false;
20+
} else {
21+
var declarations = Array.from(constants.CSS_DECLARATIONS).reverse();
22+
var supports = window.CSS && window.CSS.supports || window.supportsCSS;
23+
if(typeof supports === 'function') {
24+
_supportsPixelated = declarations.some(function(d) {
25+
return supports.apply(null, d);
26+
});
27+
} else {
28+
var image3 = Drawing.tester.append('image');
29+
var cStyles = window.getComputedStyle(image3.node());
30+
image3.attr('style', constants.STYLE);
31+
_supportsPixelated = declarations.some(function(d) {
32+
var value = d[1];
33+
return cStyles.imageRendering === value ||
34+
cStyles.imageRendering === value.toLowerCase();
35+
});
36+
image3.remove();
37+
}
38+
}
39+
return _supportsPixelated;
40+
}
41+
42+
module.exports = supportsPixelatedImage;

src/traces/heatmap/calc.js

+22-23
Original file line numberDiff line numberDiff line change
@@ -90,32 +90,31 @@ module.exports = function calc(gd, trace) {
9090
Lib.warn('cannot use zsmooth: "fast": ' + msg);
9191
}
9292

93-
// check whether we really can smooth (ie all boxes are about the same size)
94-
if(zsmooth === 'fast') {
95-
if(xa.type === 'log' || ya.type === 'log') {
96-
noZsmooth('log axis found');
97-
} else if(!isHist) {
98-
if(x.length) {
99-
var avgdx = (x[x.length - 1] - x[0]) / (x.length - 1);
100-
var maxErrX = Math.abs(avgdx / 100);
101-
for(i = 0; i < x.length - 1; i++) {
102-
if(Math.abs(x[i + 1] - x[i] - avgdx) > maxErrX) {
103-
noZsmooth('x scale is not linear');
104-
break;
105-
}
106-
}
107-
}
108-
if(y.length && zsmooth === 'fast') {
109-
var avgdy = (y[y.length - 1] - y[0]) / (y.length - 1);
110-
var maxErrY = Math.abs(avgdy / 100);
111-
for(i = 0; i < y.length - 1; i++) {
112-
if(Math.abs(y[i + 1] - y[i] - avgdy) > maxErrY) {
113-
noZsmooth('y scale is not linear');
114-
break;
115-
}
93+
function scaleIsLinear(s) {
94+
if(s.length > 1) {
95+
var avgdx = (s[s.length - 1] - s[0]) / (s.length - 1);
96+
var maxErrX = Math.abs(avgdx / 100);
97+
for(i = 0; i < s.length - 1; i++) {
98+
if(Math.abs(s[i + 1] - s[i] - avgdx) > maxErrX) {
99+
return false;
116100
}
117101
}
118102
}
103+
return true;
104+
}
105+
106+
// Check whether all brick are uniform
107+
trace._islinear = false;
108+
if(xa.type === 'log' || ya.type === 'log') {
109+
if(zsmooth === 'fast') {
110+
noZsmooth('log axis found');
111+
}
112+
} else if(!scaleIsLinear(x)) {
113+
if(zsmooth === 'fast') noZsmooth('x scale is not linear');
114+
} else if(!scaleIsLinear(y)) {
115+
if(zsmooth === 'fast') noZsmooth('y scale is not linear');
116+
} else {
117+
trace._islinear = true;
119118
}
120119

121120
// create arrays of brick boundaries, to be used by autorange and heatmap.plot

0 commit comments

Comments
 (0)