Skip to content

Commit 96657aa

Browse files
committed
Add layout.shapes.layer
* Added functionality to show layer below traces. * The axis references of a 'below'-shape determine the subplots under which a shape is shown. * If both axis references of a 'below'-shape are set to 'paper', then the shape is shown below all the subplots. * Updated `test/image/mocks/shapes.json` to exercise the new functionality. * Updated `test/jasmine/tests/shapes_test.js` to account for the new shape layers.
1 parent 52be69a commit 96657aa

File tree

5 files changed

+211
-46
lines changed

5 files changed

+211
-46
lines changed

src/components/shapes/attributes.js

+8
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,14 @@ module.exports = {
3838
].join(' ')
3939
},
4040

41+
layer: {
42+
valType: 'enumerated',
43+
values: ['below', 'above'],
44+
dflt: 'above',
45+
role: 'info',
46+
description: 'Specifies whether shapes are drawn below or above traces.'
47+
},
48+
4149
xref: extendFlat({}, annAttrs.xref, {
4250
description: [
4351
'Sets the shape\'s x coordinate axis.',

src/components/shapes/index.js

+72-17
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ function handleShapeDefaults(shapeIn, fullLayout) {
3434
attr, dflt);
3535
}
3636

37+
coerce('layer');
3738
coerce('opacity');
3839
coerce('fillcolor');
3940
coerce('line.color');
@@ -167,7 +168,8 @@ function updateAllShapes(gd, opt, value) {
167168
}
168169

169170
function deleteShape(gd, index) {
170-
gd._fullLayout._shapelayer.selectAll('[data-index="' + index + '"]')
171+
getShapeLayer(gd, index)
172+
.selectAll('[data-index="' + index + '"]')
171173
.remove();
172174

173175
gd._fullLayout.shapes.splice(index, 1);
@@ -177,9 +179,9 @@ function deleteShape(gd, index) {
177179
for(var i = index; i < gd._fullLayout.shapes.length; i++) {
178180
// redraw all shapes past the removed one,
179181
// so they bind to the right events
180-
gd._fullLayout._shapelayer
181-
.selectAll('[data-index="' + (i+1) + '"]')
182-
.attr('data-index', String(i));
182+
getShapeLayer(gd, i)
183+
.selectAll('[data-index="' + (i + 1) + '"]')
184+
.attr('data-index', i);
183185
shapes.draw(gd, i);
184186
}
185187
}
@@ -197,10 +199,13 @@ function insertShape(gd, index, newShape) {
197199
gd.layout.shapes = [rule];
198200
}
199201

202+
// there is no need to call shapes.draw(gd, index),
203+
// because updateShape() is called from within shapes.draw()
204+
200205
for(var i = gd._fullLayout.shapes.length - 1; i > index; i--) {
201-
gd._fullLayout._shapelayer
206+
getShapeLayer(gd, i)
202207
.selectAll('[data-index="' + (i - 1) + '"]')
203-
.attr('data-index', String(i));
208+
.attr('data-index', i);
204209
shapes.draw(gd, i);
205210
}
206211
}
@@ -209,7 +214,8 @@ function updateShape(gd, index, opt, value) {
209214
var i;
210215

211216
// remove the existing shape if there is one
212-
gd._fullLayout._shapelayer.selectAll('[data-index="' + index + '"]')
217+
getShapeLayer(gd, index)
218+
.selectAll('[data-index="' + index + '"]')
213219
.remove();
214220

215221
// remember a few things about what was already there,
@@ -284,24 +290,73 @@ function updateShape(gd, index, opt, value) {
284290
gd._fullLayout.shapes[index] = options;
285291

286292
var attrs = {
287-
'data-index': String(index),
293+
'data-index': index,
288294
'fill-rule': 'evenodd',
289295
d: shapePath(gd, options)
290296
},
291297
clipAxes = (options.xref + options.yref).replace(/paper/g, '');
292298

293299
var lineColor = options.line.width ? options.line.color : 'rgba(0,0,0,0)';
294300

295-
var path = gd._fullLayout._shapelayer.append('path')
296-
.attr(attrs)
297-
.style('opacity', options.opacity)
298-
.call(Plotly.Color.stroke, lineColor)
299-
.call(Plotly.Color.fill, options.fillcolor)
300-
.call(Plotly.Drawing.dashLine, options.line.dash, options.line.width);
301+
if(options.layer !== 'below') {
302+
drawShape(gd._fullLayout._shapeUpperLayer);
303+
}
304+
else if(options.xref === 'paper' && options.yref === 'paper') {
305+
drawShape(gd._fullLayout._shapeLowerLayer);
306+
} else {
307+
forEachSubplot(gd, function(plotinfo) {
308+
if(isShapeInSubplot(gd, options, plotinfo.id)) {
309+
drawShape(plotinfo.shapelayer);
310+
}
311+
});
312+
}
313+
314+
return;
315+
316+
function drawShape(shapeLayer) {
317+
var path = shapeLayer.append('path')
318+
.attr(attrs)
319+
.style('opacity', options.opacity)
320+
.call(Plotly.Color.stroke, lineColor)
321+
.call(Plotly.Color.fill, options.fillcolor)
322+
.call(Plotly.Drawing.dashLine, options.line.dash,
323+
options.line.width);
324+
325+
if(clipAxes) {
326+
path.call(Plotly.Drawing.setClipUrl,
327+
'clip' + gd._fullLayout._uid + clipAxes);
328+
}
329+
}
330+
}
331+
332+
function getShapeLayer(gd, index) {
333+
var shape = gd._fullLayout.shapes[index],
334+
shapeLayer = gd._fullLayout._shapeUpperLayer;
335+
336+
if(!shape) {
337+
console.log('getShapeLayer: undefined shape: index', index);
338+
}
339+
else if(shape.layer === 'below') {
340+
shapeLayer = (shape.xref === 'paper' && shape.yref === 'paper') ?
341+
gd._fullLayout._shapeLowerLayer :
342+
gd._fullLayout._subplotShapeLayer;
343+
}
344+
345+
return shapeLayer;
346+
}
347+
348+
function isShapeInSubplot(gd, shape, subplot) {
349+
var xa = Plotly.Axes.getFromId(gd, subplot, 'x')._id,
350+
ya = Plotly.Axes.getFromId(gd, subplot, 'y')._id;
351+
return shape.layer === 'below' && (xa === shape.xref || ya === shape.yref);
352+
}
353+
354+
function forEachSubplot(gd, fn) {
355+
var plots = gd._fullLayout._plots || {},
356+
subplots = Object.getOwnPropertyNames(plots);
301357

302-
if(clipAxes) {
303-
path.call(Plotly.Drawing.setClipUrl,
304-
'clip' + gd._fullLayout._uid + clipAxes);
358+
for(var i = 0, n = subplots.length; i < n; i++) {
359+
fn(plots[subplots[i]]);
305360
}
306361
}
307362

src/plot_api/plot_api.js

+21-2
Original file line numberDiff line numberDiff line change
@@ -2615,16 +2615,27 @@ function makePlotFramework(gd) {
26152615
fullLayout._draggers = fullLayout._paper.append('g')
26162616
.classed('draglayer', true);
26172617

2618+
// lower shape layer
2619+
// (only for shapes to be drawn below the whole plot)
2620+
fullLayout._shapeLowerLayer = fullLayout._paper.append('g')
2621+
.classed('shapelayer shapelayer-below', true);
2622+
26182623
var subplots = Plotly.Axes.getSubplots(gd);
26192624
if(subplots.join('') !== Object.keys(gd._fullLayout._plots || {}).join('')) {
26202625
makeSubplots(gd, subplots);
26212626
}
26222627

26232628
if(fullLayout._hasCartesian) makeCartesianPlotFramwork(gd, subplots);
26242629

2625-
// single ternary, shape and pie layers for the whole plot
2630+
// single ternary layer for the whole plot
26262631
fullLayout._ternarylayer = fullLayout._paper.append('g').classed('ternarylayer', true);
2627-
fullLayout._shapelayer = fullLayout._paper.append('g').classed('shapelayer', true);
2632+
2633+
// upper shape layer
2634+
// (only for shapes to be drawn above the whole plot, including subplots)
2635+
fullLayout._shapeUpperLayer = fullLayout._paper.append('g')
2636+
.classed('shapelayer shapelayer-above', true);
2637+
2638+
// single pie layer for the whole plot
26282639
fullLayout._pielayer = fullLayout._paper.append('g').classed('pielayer', true);
26292640

26302641
// fill in image server scrape-svg
@@ -2752,6 +2763,10 @@ function makeCartesianPlotFramwork(gd, subplots) {
27522763
// the plot and containers for overlays
27532764
plotinfo.bg = plotgroup.append('rect')
27542765
.style('stroke-width', 0);
2766+
// shape layer
2767+
// (only for shapes to be drawn below a subplot)
2768+
plotinfo.shapelayer = plotgroup.append('g')
2769+
.classed('shapelayer shapelayer-subplot', true);
27552770
plotinfo.gridlayer = plotgroup.append('g');
27562771
plotinfo.overgrid = plotgroup.append('g');
27572772
plotinfo.zerolinelayer = plotgroup.append('g');
@@ -2800,6 +2815,10 @@ function makeCartesianPlotFramwork(gd, subplots) {
28002815
.style('fill', 'none')
28012816
.classed('crisp', true);
28022817
});
2818+
2819+
// shape layers in subplots
2820+
fullLayout._subplotShapeLayer = fullLayout._paper
2821+
.selectAll('.shapelayer-subplot');
28032822
}
28042823

28052824
// layoutStyles: styling for plot layout elements

test/image/mocks/shapes.json

+4-4
Original file line numberDiff line numberDiff line change
@@ -21,18 +21,18 @@
2121
"margin": {"l":20,"r":20,"top":10,"bottom":10,"pad":0},
2222
"showlegend":false,
2323
"shapes":[
24-
{"xref":"paper","yref":"paper","x0":0,"x1":0.1,"y0":0,"y1":0.1},
24+
{"layer":"below","xref":"paper","yref":"paper","x0":0,"x1":0.1,"y0":0,"y1":0.1},
2525
{"xref":"paper","yref":"paper","path":"M0,0.2V0.3H0.05L0,0.4Q0.1,0.4 0.1,0.3T0.15,0.3C0.1,0.4 0.2,0.4 0.2,0.3S0.15,0.3 0.15,0.2Z","fillcolor":"#4c0"},
2626
{"xref":"paper","yref":"paper","type":"circle","x0":0.23,"x1":0.3,"y0":0.2,"y1":0.4},
2727
{"xref":"paper","yref":"paper","type":"line","x0":0.2,"x1":0.3,"y0":0,"y1":0.1},
28-
{"x0":0.1,"x1":0.4,"y0":1.5,"y1":20,"opacity":0.5,"fillcolor":"#f00","line":{"width":8,"color":"#008","dash":"dashdot"}},
28+
{"layer":"below","x0":0.1,"x1":0.4,"y0":1.5,"y1":20,"opacity":0.5,"fillcolor":"#f00","line":{"width":8,"color":"#008","dash":"dashdot"}},
2929
{"path":"M0.5,3C0.5,9 0.9,9 0.9,3C0.9,1 0.5,1 0.5,3ZM0.6,4C0.6,5 0.66,5 0.66,4ZM0.74,4C0.74,5 0.8,5 0.8,4ZM0.6,3C0.63,2 0.77,2 0.8,3Z","fillcolor":"#fd2","line":{"width":1,"color":"black"}},
30-
{"xref":"x2","yref":"y2","type":"circle","x0":"2000-01-01 02","x1":"2000-01-01 08:30:33.456","y0":0.1,"y1":0.9,"fillcolor":"rgba(0,0,0,0.5)","line":{"color":"rgba(0,255,0,0.5)", "width":5}},
30+
{"layer":"below","xref":"x2","yref":"y2","type":"circle","x0":"2000-01-01 02","x1":"2000-01-01 08:30:33.456","y0":0.1,"y1":0.9,"fillcolor":"rgba(0,0,0,0.5)","line":{"color":"rgba(0,255,0,0.5)", "width":5}},
3131
{"xref":"x2","yref":"y2","path":"M2000-01-01_11:20:45.6,0.2Q2000-01-01_10:00,0.85 2000-01-01_21,0.8Q2000-01-01_22:20,0.15 2000-01-01_11:20:45.6,0.2Z","fillcolor":"rgb(151,73,58)"},
3232
{"yref":"paper","type":"line","x0":0.1,"x1":0.4,"y0":0,"y1":0.4,"line":{"color":"#009","dash":"dot","width":1}},
3333
{"yref":"paper","path":"M0.5,0H1.1L0.8,0.4Z","line":{"width":0},"fillcolor":"#ccd3ff"},
3434
{"xref":"paper","x0":0.1,"x1":0.2,"y0":-1,"y1":3,"fillcolor":"#ccc"},
35-
{"xref":"paper","path":"M0.05,4C0.4,12 -0.1,12 0.25,4Z","fillcolor":"#a66"}
35+
{"layer":"above","xref":"paper","path":"M0.05,4C0.4,12 -0.1,12 0.25,4Z","fillcolor":"#a66"}
3636
]
3737
}
3838
}

0 commit comments

Comments
 (0)