Skip to content

Commit 067ee11

Browse files
authored
Merge pull request #6918 from plotly/trace-zindex
Add z-index stacking attribute to cartesian SVG traces
2 parents 51ef6c1 + efcd49f commit 067ee11

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+732
-48
lines changed

draftlogs/6918_add.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- Add a new attribute `zorder` to SVG based Cartesian traces (not to WebGL traces). Traces with higher `zorder` values are drawn in front of traces with lower `zorder` values. This feature was anonymously sponsored: thank you to our sponsor!

src/plots/cartesian/index.js

Lines changed: 55 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -130,21 +130,27 @@ exports.plot = function(gd, traces, transitionOpts, makeOnCompleteCallback) {
130130
var calcdata = gd.calcdata;
131131
var i;
132132

133+
// Traces is a list of trace indices to (re)plot. If it's not provided,
134+
// then it's a complete replot so we create a new list and add all trace indices
135+
// which are in calcdata.
136+
133137
if(!Array.isArray(traces)) {
134138
// If traces is not provided, then it's a complete replot and missing
135139
// traces are removed
136140
traces = [];
137141
for(i = 0; i < calcdata.length; i++) traces.push(i);
138142
}
139143

144+
// For each subplot
140145
for(i = 0; i < subplots.length; i++) {
141146
var subplot = subplots[i];
142147
var subplotInfo = fullLayout._plots[subplot];
143148

144-
// Get all calcdata for this subplot:
149+
// Get all calcdata (traces) for this subplot:
145150
var cdSubplot = [];
146151
var pcd;
147152

153+
// For each trace
148154
for(var j = 0; j < calcdata.length; j++) {
149155
var cd = calcdata[j];
150156
var trace = cd[0].trace;
@@ -178,7 +184,7 @@ exports.plot = function(gd, traces, transitionOpts, makeOnCompleteCallback) {
178184
pcd = cd;
179185
}
180186
}
181-
187+
// Plot the traces for this subplot
182188
plotOne(gd, subplotInfo, cdSubplot, transitionOpts, makeOnCompleteCallback);
183189
}
184190
};
@@ -189,41 +195,60 @@ function plotOne(gd, plotinfo, cdSubplot, transitionOpts, makeOnCompleteCallback
189195
var modules = fullLayout._modules;
190196
var _module, cdModuleAndOthers, cdModule;
191197

198+
// Separate traces by zorder and plot each zorder group separately
199+
// TODO: Performance
200+
var traceZorderGroups = {};
201+
for(var t = 0; t < cdSubplot.length; t++) {
202+
var trace = cdSubplot[t][0].trace;
203+
var zi = trace.zorder || 0;
204+
if(!traceZorderGroups[zi]) traceZorderGroups[zi] = [];
205+
traceZorderGroups[zi].push(cdSubplot[t]);
206+
}
207+
192208
var layerData = [];
193209
var zoomScaleQueryParts = [];
194210

195-
for(var i = 0; i < modules.length; i++) {
196-
_module = modules[i];
197-
var name = _module.name;
198-
var categories = Registry.modules[name].categories;
199-
200-
if(categories.svg) {
201-
var className = (_module.layerName || name + 'layer');
202-
var plotMethod = _module.plot;
203-
204-
// plot all visible traces of this type on this subplot at once
205-
cdModuleAndOthers = getModuleCalcData(cdSubplot, plotMethod);
206-
cdModule = cdModuleAndOthers[0];
207-
// don't need to search the found traces again - in fact we need to NOT
208-
// so that if two modules share the same plotter we don't double-plot
209-
cdSubplot = cdModuleAndOthers[1];
210-
211-
if(cdModule.length) {
212-
layerData.push({
213-
i: traceLayerClasses.indexOf(className),
214-
className: className,
215-
plotMethod: plotMethod,
216-
cdModule: cdModule
217-
});
218-
}
211+
// Plot each zorder group in ascending order
212+
var zindices = Object.keys(traceZorderGroups)
213+
.map(Number)
214+
.sort(Lib.sorterAsc);
215+
for(var z = 0; z < zindices.length; z++) {
216+
var zorder = zindices[z];
217+
// For each "module" (trace type)
218+
for(var i = 0; i < modules.length; i++) {
219+
_module = modules[i];
220+
var name = _module.name;
221+
var categories = Registry.modules[name].categories;
222+
223+
if(categories.svg) {
224+
var className = (_module.layerName || name + 'layer') + (z ? Number(z) + 1 : '');
225+
var plotMethod = _module.plot;
226+
227+
// plot all visible traces of this type on this subplot at once
228+
cdModuleAndOthers = getModuleCalcData(cdSubplot, plotMethod, zorder);
229+
cdModule = cdModuleAndOthers[0];
230+
// don't need to search the found traces again - in fact we need to NOT
231+
// so that if two modules share the same plotter we don't double-plot
232+
cdSubplot = cdModuleAndOthers[1];
233+
234+
if(cdModule.length) {
235+
layerData.push({
236+
i: traceLayerClasses.indexOf(className),
237+
zorder: z,
238+
className: className,
239+
plotMethod: plotMethod,
240+
cdModule: cdModule
241+
});
242+
}
219243

220-
if(categories.zoomScale) {
221-
zoomScaleQueryParts.push('.' + className);
244+
if(categories.zoomScale) {
245+
zoomScaleQueryParts.push('.' + className);
246+
}
222247
}
223248
}
224249
}
225-
226-
layerData.sort(function(a, b) { return a.i - b.i; });
250+
// Sort the layers primarily by z, then by i
251+
layerData.sort(function(a, b) { return (a.zorder || 0) - (b.zorder || 0) || a.i - b.i; });
227252

228253
var layers = plotinfo.plot.selectAll('g.mlayer')
229254
.data(layerData, function(d) { return d.className; });
@@ -434,7 +459,6 @@ function makeSubplotData(gd) {
434459
}
435460
subplotData[i] = d;
436461
}
437-
438462
return subplotData;
439463
}
440464

src/plots/get_data.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,10 @@ exports.getSubplotCalcData = function(calcData, type, subplotId) {
3939
* @param {array} calcdata: as in gd.calcdata
4040
* @param {object|string|fn} arg1:
4141
* the plotting module, or its name, or its plot method
42-
*
42+
* @param {int} arg2: (optional) zorder to filter on
4343
* @return {array[array]} [foundCalcdata, remainingCalcdata]
4444
*/
45-
exports.getModuleCalcData = function(calcdata, arg1) {
45+
exports.getModuleCalcData = function(calcdata, arg1, arg2) {
4646
var moduleCalcData = [];
4747
var remainingCalcData = [];
4848

@@ -57,10 +57,12 @@ exports.getModuleCalcData = function(calcdata, arg1) {
5757
if(!plotMethod) {
5858
return [moduleCalcData, calcdata];
5959
}
60+
var zorder = arg2;
6061

6162
for(var i = 0; i < calcdata.length; i++) {
6263
var cd = calcdata[i];
6364
var trace = cd[0].trace;
65+
var filterByZ = (trace.zorder !== undefined);
6466
// N.B.
6567
// - 'legendonly' traces do not make it past here
6668
// - skip over 'visible' traces that got trimmed completely during calc transforms
@@ -70,7 +72,7 @@ exports.getModuleCalcData = function(calcdata, arg1) {
7072
// would suggest), but by 'module plot method' so that if some traces
7173
// share the same module plot method (e.g. bar and histogram), we
7274
// only call it one!
73-
if(trace._module && trace._module.plot === plotMethod) {
75+
if(trace._module && trace._module.plot === plotMethod && (!filterByZ || trace.zorder === zorder)) {
7476
moduleCalcData.push(cd);
7577
} else {
7678
remainingCalcData.push(cd);

src/traces/bar/attributes.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@ module.exports = {
226226
textfont: scatterAttrs.unselected.textfont,
227227
editType: 'style'
228228
},
229+
zorder: scatterAttrs.zorder,
229230

230231
_deprecated: {
231232
bardir: {

src/traces/bar/defaults.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
2929
coerce('xhoverformat');
3030
coerce('yhoverformat');
3131

32+
coerce('zorder');
33+
3234
coerce('orientation', (traceOut.x && !traceOut.y) ? 'h' : 'v');
3335
coerce('base');
3436
coerce('offset');

src/traces/bar/style.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ var attributeOutsideTextFont = attributes.outsidetextfont;
1414
var helpers = require('./helpers');
1515

1616
function style(gd) {
17-
var s = d3.select(gd).selectAll('g.barlayer').selectAll('g.trace');
17+
var s = d3.select(gd).selectAll('g[class^="barlayer"]').selectAll('g.trace');
1818
resizeText(gd, s, 'bar');
1919

2020
var barcount = s.size();

src/traces/box/attributes.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -451,5 +451,6 @@ module.exports = {
451451
'Do the hover effects highlight individual boxes ',
452452
'or sample points or both?'
453453
].join(' ')
454-
}
454+
},
455+
zorder: scatterAttrs.zorder
455456
};

src/traces/box/defaults.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
6767
if(notched) coerce('notchwidth');
6868

6969
handlePointsDefaults(traceIn, traceOut, coerce, {prefix: 'box'});
70+
coerce('zorder');
7071
}
7172

7273
function handleSampleDefaults(traceIn, traceOut, coerce, layout) {

src/traces/candlestick/attributes.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,5 @@ module.exports = {
5353
whiskerwidth: extendFlat({}, boxAttrs.whiskerwidth, { dflt: 0 }),
5454

5555
hoverlabel: OHLCattrs.hoverlabel,
56+
zorder: boxAttrs.zorder
5657
};

src/traces/candlestick/defaults.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
3131
coerce('whiskerwidth');
3232

3333
layout._requestRangeslider[traceOut.xaxis] = true;
34+
coerce('zorder');
3435
};
3536

3637
function handleDirection(traceIn, traceOut, coerce, direction) {

0 commit comments

Comments
 (0)