Skip to content

Commit 2102b0f

Browse files
committed
push subplot backgrounds to the way back unless they're inset plots
1 parent aeca8ae commit 2102b0f

File tree

10 files changed

+160
-61
lines changed

10 files changed

+160
-61
lines changed

src/components/images/draw.js

+39-12
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,28 @@ var xmlnsNamespaces = require('../../constants/xmlns_namespaces');
1616
module.exports = function draw(gd) {
1717
var fullLayout = gd._fullLayout,
1818
imageDataAbove = [],
19-
imageDataSubplot = [],
20-
imageDataBelow = [];
19+
imageDataSubplot = {},
20+
imageDataBelow = [],
21+
subplot,
22+
i;
2123

2224
// Sort into top, subplot, and bottom layers
23-
for(var i = 0; i < fullLayout.images.length; i++) {
25+
for(i = 0; i < fullLayout.images.length; i++) {
2426
var img = fullLayout.images[i];
2527

2628
if(img.visible) {
2729
if(img.layer === 'below' && img.xref !== 'paper' && img.yref !== 'paper') {
28-
imageDataSubplot.push(img);
30+
subplot = img.xref + img.yref;
31+
32+
var plotinfo = fullLayout._plots[subplot];
33+
if(plotinfo.mainplot) {
34+
subplot = plotinfo.mainplot.id;
35+
}
36+
37+
if(!imageDataSubplot[subplot]) {
38+
imageDataSubplot[subplot] = [];
39+
}
40+
imageDataSubplot[subplot].push(img);
2941
} else if(img.layer === 'above') {
3042
imageDataAbove.push(img);
3143
} else {
@@ -151,29 +163,44 @@ module.exports = function draw(gd) {
151163

152164
var imagesBelow = fullLayout._imageLowerLayer.selectAll('image')
153165
.data(imageDataBelow),
154-
imagesSubplot = fullLayout._imageSubplotLayer.selectAll('image')
155-
.data(imageDataSubplot),
156166
imagesAbove = fullLayout._imageUpperLayer.selectAll('image')
157167
.data(imageDataAbove);
158168

159169
imagesBelow.enter().append('image');
160-
imagesSubplot.enter().append('image');
161170
imagesAbove.enter().append('image');
162171

163172
imagesBelow.exit().remove();
164-
imagesSubplot.exit().remove();
165173
imagesAbove.exit().remove();
166174

167175
imagesBelow.each(function(d) {
168176
setImage.bind(this)(d);
169177
applyAttributes.bind(this)(d);
170178
});
171-
imagesSubplot.each(function(d) {
172-
setImage.bind(this)(d);
173-
applyAttributes.bind(this)(d);
174-
});
175179
imagesAbove.each(function(d) {
176180
setImage.bind(this)(d);
177181
applyAttributes.bind(this)(d);
178182
});
183+
184+
var allSubplots = Object.keys(fullLayout._plots);
185+
for(i = 0; i < allSubplots.length; i++) {
186+
subplot = allSubplots[i];
187+
var subplotObj = fullLayout._plots[subplot];
188+
189+
// filter out overlaid plots (which havd their images on the main plot)
190+
// and gl2d plots (which don't support below images, at least not yet)
191+
if(!subplotObj.imagelayer) continue;
192+
193+
var imagesOnSubplot = subplotObj.imagelayer.selectAll('image')
194+
// even if there are no images on this subplot, we need to run
195+
// enter and exit in case there were previously
196+
.data(imageDataSubplot[subplot] || []);
197+
198+
imagesOnSubplot.enter().append('image');
199+
imagesOnSubplot.exit().remove();
200+
201+
imagesOnSubplot.each(function(d) {
202+
setImage.bind(this)(d);
203+
applyAttributes.bind(this)(d);
204+
});
205+
}
179206
};

src/components/rangeslider/draw.js

-4
Original file line numberDiff line numberDiff line change
@@ -400,10 +400,6 @@ function drawRangePlot(rangeSlider, gd, axisOpts, opts) {
400400
}
401401

402402
Cartesian.rangePlot(gd, plotinfo, filterRangePlotCalcData(calcData, id));
403-
404-
// no need for the bg layer,
405-
// drawBg handles coloring the background
406-
if(isMainPlot) plotinfo.bg.remove();
407403
});
408404
}
409405

src/components/shapes/draw.js

+5-27
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ function draw(gd) {
4242
// Remove previous shapes before drawing new in shapes in fullLayout.shapes
4343
fullLayout._shapeUpperLayer.selectAll('path').remove();
4444
fullLayout._shapeLowerLayer.selectAll('path').remove();
45-
fullLayout._shapeSubplotLayer.selectAll('path').remove();
45+
fullLayout._shapeSubplotLayers.selectAll('path').remove();
4646

4747
for(var i = 0; i < fullLayout.shapes.length; i++) {
4848
if(fullLayout.shapes[i].visible) {
@@ -55,8 +55,6 @@ function draw(gd) {
5555
}
5656

5757
function drawOne(gd, index) {
58-
var i, n;
59-
6058
// remove the existing shape if there is one.
6159
// because indices can change, we need to look in all shape layers
6260
gd._fullLayout._paper
@@ -73,25 +71,14 @@ function drawOne(gd, index) {
7371
if(options.layer !== 'below') {
7472
drawShape(gd._fullLayout._shapeUpperLayer);
7573
}
76-
else if(options.xref === 'paper' && options.yref === 'paper') {
74+
else if(options.xref === 'paper' || options.yref === 'paper') {
7775
drawShape(gd._fullLayout._shapeLowerLayer);
7876
}
7977
else {
80-
var plots = gd._fullLayout._plots || {},
81-
subplots = Object.keys(plots),
82-
plotinfo;
83-
84-
for(i = 0, n = subplots.length; i < n; i++) {
85-
plotinfo = plots[subplots[i]];
78+
var plotinfo = gd._fullLayout._plots[options.xref + options.yref],
79+
mainPlot = plotinfo.mainplot || plotinfo;
8680

87-
if(isShapeInSubplot(gd, options, plotinfo)) {
88-
drawShape(plotinfo.shapelayer);
89-
90-
// make sure we only draw the shape once.
91-
// See https://github.com/plotly/plotly.js/issues/1452
92-
break;
93-
}
94-
}
81+
drawShape(mainPlot.shapelayer);
9582
}
9683

9784
function drawShape(shapeLayer) {
@@ -276,15 +263,6 @@ function setupDragElement(gd, shapePath, shapeOptions, index) {
276263
}
277264
}
278265

279-
function isShapeInSubplot(gd, shape, plotinfo) {
280-
var xa = Axes.getFromId(gd, plotinfo.id, 'x')._id,
281-
ya = Axes.getFromId(gd, plotinfo.id, 'y')._id,
282-
isBelow = shape.layer === 'below',
283-
inSuplotAxis = (xa === shape.xref || ya === shape.yref),
284-
isNotAnOverlaidSubplot = !!plotinfo.shapelayer;
285-
return isBelow && inSuplotAxis && isNotAnOverlaidSubplot;
286-
}
287-
288266
function getPathString(gd, options) {
289267
var type = options.type,
290268
xa = Axes.getFromId(gd, options.xref),

src/plot_api/plot_api.js

+12-4
Original file line numberDiff line numberDiff line change
@@ -308,8 +308,7 @@ Plotly.plot = function(gd, data, layout, config) {
308308

309309
// keep reference to shape layers in subplots
310310
var layerSubplot = fullLayout._paper.selectAll('.layer-subplot');
311-
fullLayout._imageSubplotLayer = layerSubplot.selectAll('.imagelayer');
312-
fullLayout._shapeSubplotLayer = layerSubplot.selectAll('.shapelayer');
311+
fullLayout._shapeSubplotLayers = layerSubplot.selectAll('.shapelayer');
313312

314313
// styling separate from drawing
315314
Plots.style(gd);
@@ -2855,11 +2854,20 @@ function makePlotFramework(gd) {
28552854
fullLayout._topdefs = fullLayout._toppaper.append('defs')
28562855
.attr('id', 'topdefs-' + fullLayout._uid);
28572856

2857+
fullLayout._bgLayer = fullLayout._paper.append('g')
2858+
.classed('bglayer', true);
2859+
28582860
fullLayout._draggers = fullLayout._paper.append('g')
28592861
.classed('draglayer', true);
28602862

2861-
// lower shape layer
2862-
// (only for shapes to be drawn below the whole plot)
2863+
// lower shape/image layer - note that this is behind
2864+
// all subplots data/grids but above the backgrounds
2865+
// except inset subplots, whose backgrounds are drawn
2866+
// inside their own group so that they appear above
2867+
// the data for the main subplot
2868+
// lower shapes and images which are fully referenced to
2869+
// a subplot still get drawn within the subplot's group
2870+
// so they will work correctly on insets
28632871
var layerBelow = fullLayout._paper.append('g')
28642872
.classed('layer-below', true);
28652873
fullLayout._imageLowerLayer = layerBelow.append('g')

src/plot_api/subroutines.js

+84-2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
'use strict';
1111

12+
var d3 = require('d3');
1213
var Plotly = require('../plotly');
1314
var Registry = require('../registry');
1415
var Plots = require('../plots/plots');
@@ -24,6 +25,21 @@ exports.layoutStyles = function(gd) {
2425
return Lib.syncOrAsync([Plots.doAutoMargin, exports.lsInner], gd);
2526
};
2627

28+
function overlappingDomain(xDomain, yDomain, domains) {
29+
for(var i = 0; i < domains.length; i++) {
30+
var existingX = domains[i][0],
31+
existingY = domains[i][1];
32+
33+
if(existingX[0] >= xDomain[1] || existingX[1] <= xDomain[0]) {
34+
continue;
35+
}
36+
if(existingY[0] < yDomain[1] && existingY[1] > yDomain[0]) {
37+
return true;
38+
}
39+
}
40+
return false;
41+
}
42+
2743
exports.lsInner = function(gd) {
2844
var fullLayout = gd._fullLayout,
2945
gs = fullLayout._size,
@@ -43,8 +59,73 @@ exports.lsInner = function(gd) {
4359

4460
gd._context.setBackground(gd, fullLayout.paper_bgcolor);
4561

62+
var subplotSelection = fullLayout._paper.selectAll('g.subplot');
63+
64+
// figure out which backgrounds we need to draw, and in which layers
65+
// to put them
66+
var lowerBackgroundIDs = [];
67+
var lowerDomains = [];
68+
subplotSelection.each(function(subplot) {
69+
var plotinfo = fullLayout._plots[subplot];
70+
71+
if(plotinfo.mainplot) {
72+
// mainplot is a reference to the main plot this one is overlaid on
73+
// so if it exists, this is an overlaid plot and we don't need to
74+
// give it its own background
75+
if(plotinfo.bg) {
76+
plotinfo.bg.remove();
77+
}
78+
plotinfo.bg = undefined;
79+
return;
80+
}
81+
82+
var xa = Plotly.Axes.getFromId(gd, subplot, 'x'),
83+
ya = Plotly.Axes.getFromId(gd, subplot, 'y'),
84+
xDomain = xa.domain,
85+
yDomain = ya.domain,
86+
plotgroupBgData = [];
87+
88+
if(overlappingDomain(xDomain, yDomain, lowerDomains)) {
89+
plotgroupBgData = [0];
90+
}
91+
else {
92+
lowerBackgroundIDs.push(subplot);
93+
lowerDomains.push([xDomain, yDomain]);
94+
}
95+
96+
// create the plot group backgrounds now, since
97+
// they're all independent selections
98+
var plotgroupBg = plotinfo.plotgroup.selectAll('.bg')
99+
.data(plotgroupBgData);
100+
101+
plotgroupBg.enter().append('rect')
102+
.classed('bg', true);
103+
104+
plotgroupBg.exit().remove();
105+
106+
plotgroupBg.each(function() {
107+
plotinfo.bg = plotgroupBg;
108+
var pgNode = plotinfo.plotgroup.node();
109+
pgNode.insertBefore(this, pgNode.childNodes[0]);
110+
});
111+
});
112+
113+
// now create all the lower-layer backgrounds at once now that
114+
// we have the list of subplots that need them
115+
var lowerBackgrounds = fullLayout._bgLayer.selectAll('.bg')
116+
.data(lowerBackgroundIDs);
117+
118+
lowerBackgrounds.enter().append('rect')
119+
.classed('bg', true);
120+
121+
lowerBackgrounds.exit().remove();
122+
123+
lowerBackgrounds.each(function(subplot) {
124+
fullLayout._plots[subplot].bg = d3.select(this);
125+
});
126+
46127
var freefinished = [];
47-
fullLayout._paper.selectAll('g.subplot').each(function(subplot) {
128+
subplotSelection.each(function(subplot) {
48129
var plotinfo = fullLayout._plots[subplot],
49130
xa = Plotly.Axes.getFromId(gd, subplot, 'x'),
50131
ya = Plotly.Axes.getFromId(gd, subplot, 'y');
@@ -58,7 +139,8 @@ exports.lsInner = function(gd) {
58139
.call(Drawing.setRect,
59140
xa._offset - gs.p, ya._offset - gs.p,
60141
xa._length + 2 * gs.p, ya._length + 2 * gs.p)
61-
.call(Color.fill, fullLayout.plot_bgcolor);
142+
.call(Color.fill, fullLayout.plot_bgcolor)
143+
.style('stroke-width', 0);
62144
}
63145

64146
// Clip so that data only shows up on the plot area.

src/plots/cartesian/index.js

-3
Original file line numberDiff line numberDiff line change
@@ -300,9 +300,6 @@ function makeSubplotLayer(plotinfo) {
300300
}
301301

302302
if(!plotinfo.mainplot) {
303-
plotinfo.bg = joinLayer(plotgroup, 'rect', 'bg');
304-
plotinfo.bg.style('stroke-width', 0);
305-
306303
var backLayer = joinLayer(plotgroup, 'g', 'layer-subplot');
307304
plotinfo.shapelayer = joinLayer(backLayer, 'g', 'shapelayer');
308305
plotinfo.imagelayer = joinLayer(backLayer, 'g', 'imagelayer');

test/image/baselines/dendrogram.png

52 Bytes
Loading
269 Bytes
Loading

test/image/mocks/layout_image.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
}
1212
],
1313
"layout": {
14+
"plot_bgcolor": "rgba(0,0,0,0)",
1415
"xaxis2": {
1516
"anchor": "y2"
1617
},
@@ -19,7 +20,8 @@
1920
},
2021
"yaxis2": {
2122
"domain": [0.55, 1],
22-
"type": "log"
23+
"type": "log",
24+
"anchor": "x2"
2325
},
2426
"images": [
2527
{

test/jasmine/tests/layout_images_test.js

+17-8
Original file line numberDiff line numberDiff line change
@@ -90,17 +90,28 @@ describe('Layout images', function() {
9090

9191
afterEach(destroyGraphDiv);
9292

93-
it('should draw images on the right layers', function() {
93+
function checkLayers(upper, lower, subplot) {
94+
var upperLayer = gd._fullLayout._imageUpperLayer;
95+
expect(upperLayer.size()).toBe(1);
96+
expect(upperLayer.selectAll('image').size()).toBe(upper);
97+
98+
var lowerLayer = gd._fullLayout._imageLowerLayer;
99+
expect(lowerLayer.size()).toBe(1);
100+
expect(lowerLayer.selectAll('image').size()).toBe(lower);
101+
102+
var subplotLayer = gd._fullLayout._plots.xy.imagelayer;
103+
expect(subplotLayer.size()).toBe(1);
104+
expect(subplotLayer.selectAll('image').size()).toBe(subplot);
105+
}
94106

95-
var layer;
107+
it('should draw images on the right layers', function() {
96108

97109
Plotly.plot(gd, data, { images: [{
98110
source: 'imageabove',
99111
layer: 'above'
100112
}]});
101113

102-
layer = gd._fullLayout._imageUpperLayer;
103-
expect(layer.length).toBe(1);
114+
checkLayers(1, 0, 0);
104115

105116
destroyGraphDiv();
106117
gd = createGraphDiv();
@@ -109,8 +120,7 @@ describe('Layout images', function() {
109120
layer: 'below'
110121
}]});
111122

112-
layer = gd._fullLayout._imageLowerLayer;
113-
expect(layer.length).toBe(1);
123+
checkLayers(0, 1, 0);
114124

115125
destroyGraphDiv();
116126
gd = createGraphDiv();
@@ -121,8 +131,7 @@ describe('Layout images', function() {
121131
yref: 'y'
122132
}]});
123133

124-
layer = gd._fullLayout._imageSubplotLayer;
125-
expect(layer.length).toBe(1);
134+
checkLayers(0, 0, 1);
126135
});
127136

128137
describe('with anchors and sizing', function() {

0 commit comments

Comments
 (0)