Skip to content

Commit bbb31f4

Browse files
authored
Merge pull request #1871 from plotly/axis-layer-above-below
Implement `axis.layer` with 'above traces' and 'below traces' values
2 parents 0496144 + 6f494c6 commit bbb31f4

25 files changed

+343
-108
lines changed

devtools/test_dashboard/index.html

-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121

2222
<script type="text/javascript" src="../../dist/extras/mathjax/MathJax.js?config=TeX-AMS-MML_SVG"></script>
2323
<script id="source" type="text/javascript" src="../../build/plotly.js"></script>
24-
<script type="text/javascript" src="../../test/image/strict-d3.js" charset="utf-8"></script>
2524
<script type="text/javascript" src="../../build/test_dashboard-bundle.js"></script>
2625
</body>
2726
</html>

src/plot_api/subroutines.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -180,8 +180,8 @@ exports.lsInner = function(gd) {
180180

181181
Drawing.setClipUrl(plotinfo.plot, plotClipId);
182182

183-
for(i = 0; i < cartesianConstants.layers.length; i++) {
184-
var layer = cartesianConstants.layers[i];
183+
for(i = 0; i < cartesianConstants.traceLayerClasses.length; i++) {
184+
var layer = cartesianConstants.traceLayerClasses[i];
185185
if(layer !== 'scatterlayer') {
186186
plotinfo.plot.selectAll('g.' + layer).call(Drawing.setClipUrl, layerClipId);
187187
}

src/plots/cartesian/constants.js

+8-3
Original file line numberDiff line numberDiff line change
@@ -51,19 +51,24 @@ module.exports = {
5151
DFLTRANGEX: [-1, 6],
5252
DFLTRANGEY: [-1, 4],
5353

54-
// Layers to keep plot types in the right order.
54+
// Layers to keep trace types in the right order.
5555
// from back to front:
5656
// 1. heatmaps, 2D histos and contour maps
5757
// 2. bars / 1D histos
5858
// 3. errorbars for bars and scatter
5959
// 4. scatter
6060
// 5. box plots
61-
layers: [
61+
traceLayerClasses: [
6262
'imagelayer',
6363
'maplayer',
6464
'barlayer',
6565
'carpetlayer',
6666
'boxlayer',
6767
'scatterlayer'
68-
]
68+
],
69+
70+
layerValue2layerClass: {
71+
'above traces': 'above',
72+
'below traces': 'below'
73+
}
6974
};

src/plots/cartesian/dragbox.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -743,7 +743,7 @@ module.exports = function dragBox(gd, plotinfo, x, y, w, h, ns, ew) {
743743
var plotDx = xa2._offset - clipDx / xScaleFactor2,
744744
plotDy = ya2._offset - clipDy / yScaleFactor2;
745745

746-
fullLayout._defs.selectAll('#' + subplot.clipId)
746+
fullLayout._defs.select('#' + subplot.clipId + '> rect')
747747
.call(Drawing.setTranslate, clipDx, clipDy)
748748
.call(Drawing.setScale, xScaleFactor2, yScaleFactor2);
749749

src/plots/cartesian/index.js

+43-12
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,8 @@ function makeSubplotData(gd) {
296296
function makeSubplotLayer(plotinfo) {
297297
var plotgroup = plotinfo.plotgroup;
298298
var id = plotinfo.id;
299+
var xLayer = constants.layerValue2layerClass[plotinfo.xaxis.layer];
300+
var yLayer = constants.layerValue2layerClass[plotinfo.yaxis.layer];
299301

300302
if(!plotinfo.mainplot) {
301303
var backLayer = joinLayer(plotgroup, 'g', 'layer-subplot');
@@ -308,19 +310,36 @@ function makeSubplotLayer(plotinfo) {
308310
plotinfo.zerolinelayer = joinLayer(plotgroup, 'g', 'zerolinelayer');
309311
plotinfo.overzero = joinLayer(plotgroup, 'g', 'overzero');
310312

313+
joinLayer(plotgroup, 'path', 'xlines-below');
314+
joinLayer(plotgroup, 'path', 'ylines-below');
315+
plotinfo.overlinesBelow = joinLayer(plotgroup, 'g', 'overlines-below');
316+
317+
joinLayer(plotgroup, 'g', 'xaxislayer-below');
318+
joinLayer(plotgroup, 'g', 'yaxislayer-below');
319+
plotinfo.overaxesBelow = joinLayer(plotgroup, 'g', 'overaxes-below');
320+
311321
plotinfo.plot = joinLayer(plotgroup, 'g', 'plot');
312322
plotinfo.overplot = joinLayer(plotgroup, 'g', 'overplot');
313323

314-
plotinfo.xlines = joinLayer(plotgroup, 'path', 'xlines');
315-
plotinfo.ylines = joinLayer(plotgroup, 'path', 'ylines');
316-
plotinfo.overlines = joinLayer(plotgroup, 'g', 'overlines');
324+
joinLayer(plotgroup, 'path', 'xlines-above');
325+
joinLayer(plotgroup, 'path', 'ylines-above');
326+
plotinfo.overlinesAbove = joinLayer(plotgroup, 'g', 'overlines-above');
327+
328+
joinLayer(plotgroup, 'g', 'xaxislayer-above');
329+
joinLayer(plotgroup, 'g', 'yaxislayer-above');
330+
plotinfo.overaxesAbove = joinLayer(plotgroup, 'g', 'overaxes-above');
317331

318-
plotinfo.xaxislayer = joinLayer(plotgroup, 'g', 'xaxislayer');
319-
plotinfo.yaxislayer = joinLayer(plotgroup, 'g', 'yaxislayer');
320-
plotinfo.overaxes = joinLayer(plotgroup, 'g', 'overaxes');
332+
// set refs to correct layers as determined by 'axis.layer'
333+
plotinfo.xlines = plotgroup.select('.xlines-' + xLayer);
334+
plotinfo.ylines = plotgroup.select('.ylines-' + yLayer);
335+
plotinfo.xaxislayer = plotgroup.select('.xaxislayer-' + xLayer);
336+
plotinfo.yaxislayer = plotgroup.select('.yaxislayer-' + yLayer);
321337
}
322338
else {
323339
var mainplotinfo = plotinfo.mainplotinfo;
340+
var mainplotgroup = mainplotinfo.plotgroup;
341+
var xId = id + '-x';
342+
var yId = id + '-y';
324343

325344
// now make the components of overlaid subplots
326345
// overlays don't have backgrounds, and append all
@@ -330,17 +349,29 @@ function makeSubplotLayer(plotinfo) {
330349
plotinfo.gridlayer = joinLayer(mainplotinfo.overgrid, 'g', id);
331350
plotinfo.zerolinelayer = joinLayer(mainplotinfo.overzero, 'g', id);
332351

352+
joinLayer(mainplotinfo.overlinesBelow, 'path', xId);
353+
joinLayer(mainplotinfo.overlinesBelow, 'path', yId);
354+
joinLayer(mainplotinfo.overaxesBelow, 'g', xId);
355+
joinLayer(mainplotinfo.overaxesBelow, 'g', yId);
356+
333357
plotinfo.plot = joinLayer(mainplotinfo.overplot, 'g', id);
334-
plotinfo.xlines = joinLayer(mainplotinfo.overlines, 'path', id + '-x');
335-
plotinfo.ylines = joinLayer(mainplotinfo.overlines, 'path', id + '-y');
336-
plotinfo.xaxislayer = joinLayer(mainplotinfo.overaxes, 'g', id + '-x');
337-
plotinfo.yaxislayer = joinLayer(mainplotinfo.overaxes, 'g', id + '-y');
358+
359+
joinLayer(mainplotinfo.overlinesAbove, 'path', xId);
360+
joinLayer(mainplotinfo.overlinesAbove, 'path', yId);
361+
joinLayer(mainplotinfo.overaxesAbove, 'g', xId);
362+
joinLayer(mainplotinfo.overaxesAbove, 'g', yId);
363+
364+
// set refs to correct layers as determined by 'abovetraces'
365+
plotinfo.xlines = mainplotgroup.select('.overlines-' + xLayer).select('.' + xId);
366+
plotinfo.ylines = mainplotgroup.select('.overlines-' + yLayer).select('.' + yId);
367+
plotinfo.xaxislayer = mainplotgroup.select('.overaxes-' + xLayer).select('.' + xId);
368+
plotinfo.yaxislayer = mainplotgroup.select('.overaxes-' + yLayer).select('.' + yId);
338369
}
339370

340371
// common attributes for all subplots, overlays or not
341372

342-
for(var i = 0; i < constants.layers.length; i++) {
343-
joinLayer(plotinfo.plot, 'g', constants.layers[i]);
373+
for(var i = 0; i < constants.traceLayerClasses.length; i++) {
374+
joinLayer(plotinfo.plot, 'g', constants.traceLayerClasses[i]);
344375
}
345376

346377
plotinfo.xlines

src/plots/cartesian/layout_attributes.js

+14
Original file line numberDiff line numberDiff line change
@@ -573,6 +573,20 @@ module.exports = {
573573
'If *false*, this axis does not overlay any same-letter axes.'
574574
].join(' ')
575575
},
576+
layer: {
577+
valType: 'enumerated',
578+
values: ['above traces', 'below traces'],
579+
dflt: 'above traces',
580+
role: 'info',
581+
description: [
582+
'Sets the layer on which this axis is displayed.',
583+
'If *above traces*, this axis is displayed above all the subplot\'s traces',
584+
'If *below traces*, this axis is displayed below all the subplot\'s traces,',
585+
'but above the grid lines.',
586+
'Useful when used together with scatter-like traces with `cliponaxis`',
587+
'set to *false* to show markers and/or text nodes above this axis.'
588+
].join(' ')
589+
},
576590
domain: {
577591
valType: 'info_array',
578592
role: 'info',

src/plots/cartesian/position_defaults.js

+2
Original file line numberDiff line numberDiff line change
@@ -59,5 +59,7 @@ module.exports = function handlePositionDefaults(containerIn, containerOut, coer
5959
Lib.noneOrAll(containerIn.domain, containerOut.domain, [0, 1]);
6060
}
6161

62+
coerce('layer');
63+
6264
return containerOut;
6365
};

src/plots/cartesian/transition_axes.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ module.exports = function transitionAxes(gd, newLayout, transitionOpts, makeOnCo
137137
var xa2 = subplot.xaxis;
138138
var ya2 = subplot.yaxis;
139139

140-
fullLayout._defs.selectAll('#' + subplot.clipId)
140+
fullLayout._defs.select('#' + subplot.clipId + '> rect')
141141
.call(Drawing.setTranslate, 0, 0)
142142
.call(Drawing.setScale, 1, 1);
143143

@@ -221,7 +221,7 @@ module.exports = function transitionAxes(gd, newLayout, transitionOpts, makeOnCo
221221
var plotDx = xa2._offset - fracDx,
222222
plotDy = ya2._offset - fracDy;
223223

224-
fullLayout._defs.selectAll('#' + subplot.clipId)
224+
fullLayout._defs.select('#' + subplot.clipId + '> rect')
225225
.call(Drawing.setTranslate, clipDx, clipDy)
226226
.call(Drawing.setScale, 1 / xScaleFactor, 1 / yScaleFactor);
227227

src/plots/plots.js

+18-7
Original file line numberDiff line numberDiff line change
@@ -638,24 +638,35 @@ plots.linkSubplots = function(newFullData, newFullLayout, oldFullData, oldFullLa
638638
var ids = Plotly.Axes.getSubplots(mockGd);
639639

640640
for(var i = 0; i < ids.length; i++) {
641-
var id = ids[i],
642-
oldSubplot = oldSubplots[id],
643-
plotinfo;
641+
var id = ids[i];
642+
var oldSubplot = oldSubplots[id];
643+
var xaxis = Plotly.Axes.getFromId(mockGd, id, 'x');
644+
var yaxis = Plotly.Axes.getFromId(mockGd, id, 'y');
645+
var plotinfo;
644646

645647
if(oldSubplot) {
646648
plotinfo = newSubplots[id] = oldSubplot;
647649

648650
if(plotinfo._scene2d) {
649651
plotinfo._scene2d.updateRefs(newFullLayout);
650652
}
651-
}
652-
else {
653+
654+
if(plotinfo.xaxis.layer !== xaxis.layer) {
655+
plotinfo.xlines.attr('d', null);
656+
plotinfo.xaxislayer.selectAll('*').remove();
657+
}
658+
659+
if(plotinfo.yaxis.layer !== yaxis.layer) {
660+
plotinfo.ylines.attr('d', null);
661+
plotinfo.yaxislayer.selectAll('*').remove();
662+
}
663+
} else {
653664
plotinfo = newSubplots[id] = {};
654665
plotinfo.id = id;
655666
}
656667

657-
plotinfo.xaxis = Plotly.Axes.getFromId(mockGd, id, 'x');
658-
plotinfo.yaxis = Plotly.Axes.getFromId(mockGd, id, 'y');
668+
plotinfo.xaxis = xaxis;
669+
plotinfo.yaxis = yaxis;
659670

660671
// By default, we clip at the subplot level,
661672
// but if one trace on a given subplot has *cliponaxis* set to false,

src/plots/ternary/ternary.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,7 @@ proto.adjustLayout = function(ternaryLayout, graphSize) {
292292
_this.plotContainer.selectAll('.scatterlayer,.maplayer')
293293
.attr('transform', plotTransform);
294294

295-
_this.clipDefRelative.attr('transform', null);
295+
_this.clipDefRelative.select('path').attr('transform', null);
296296

297297
// TODO: shift axes to accommodate linewidth*sin(30) tick mark angle
298298

@@ -619,7 +619,7 @@ proto.initInteractions = function() {
619619
.attr('transform', plotTransform);
620620

621621
var plotTransform2 = 'translate(' + -dx + ',' + -dy + ')';
622-
_this.clipDefRelative.attr('transform', plotTransform2);
622+
_this.clipDefRelative.select('path').attr('transform', plotTransform2);
623623

624624
// move the ticks
625625
_this.aaxis.range = [mins.a, _this.sum - mins.b - mins.c];

src/traces/scatter/attributes.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,9 @@ module.exports = {
186186
editType: 'doplot',
187187
description: [
188188
'Determines whether or not markers and text nodes',
189-
'are clipped about the subplot axes.'
189+
'are clipped about the subplot axes.',
190+
'To show markers and text nodes above axis lines and tick labels,',
191+
'make sure to set `xaxis.layer` and `yaxis.layer` to *below traces*.'
190192
].join(' ')
191193
},
192194

src/traces/scatter/plot.js

+10-4
Original file line numberDiff line numberDiff line change
@@ -404,8 +404,7 @@ function plotOne(gd, idx, plotinfo, cdscatter, cdscatterAll, element, transition
404404
var trace = d[0].trace,
405405
s = d3.select(this),
406406
showMarkers = subTypes.hasMarkers(trace),
407-
showText = subTypes.hasText(trace),
408-
hasClipOnAxisFalse = trace.cliponaxis === false;
407+
showText = subTypes.hasText(trace);
409408

410409
var keyFunc = getKeyFunc(trace),
411410
markerFilter = hideFilter,
@@ -450,7 +449,7 @@ function plotOne(gd, idx, plotinfo, cdscatter, cdscatterAll, element, transition
450449
if(hasNode) {
451450
Drawing.singlePointStyle(d, sel, trace, markerScale, lineScale, gd);
452451

453-
if(hasClipOnAxisFalse) {
452+
if(plotinfo.layerClipId) {
454453
Drawing.hideOutsideRangePoint(d, sel, xa, ya);
455454
}
456455

@@ -486,7 +485,7 @@ function plotOne(gd, idx, plotinfo, cdscatter, cdscatterAll, element, transition
486485
hasNode = Drawing.translatePoint(d, sel, xa, ya);
487486

488487
if(hasNode) {
489-
if(hasClipOnAxisFalse) {
488+
if(plotinfo.layerClipId) {
490489
Drawing.hideOutsideRangePoint(d, g, xa, ya);
491490
}
492491
} else {
@@ -525,6 +524,13 @@ function plotOne(gd, idx, plotinfo, cdscatter, cdscatterAll, element, transition
525524
.each(makePoints);
526525

527526
join.exit().remove();
527+
528+
// lastly, clip points groups of `cliponaxis !== false` traces
529+
// on `plotinfo._hasClipOnAxisFalse === true` subplots
530+
join.each(function(d) {
531+
var hasClipOnAxisFalse = d[0].trace.cliponaxis === false;
532+
Drawing.setClipUrl(d3.select(this), hasClipOnAxisFalse ? null : plotinfo.layerClipId);
533+
});
528534
}
529535

530536
function selectMarkers(gd, idx, plotinfo, cdscatter, cdscatterAll) {

tasks/cibundle.js

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ var _bundle = require('./util/browserify_wrapper');
1616
_bundle(constants.pathToPlotlyIndex, constants.pathToPlotlyBuild, {
1717
standalone: 'Plotly',
1818
pathToMinBundle: constants.pathToPlotlyDistMin,
19+
debug: true
1920
});
2021

2122
// Browserify the geo assets

tasks/util/browserify_wrapper.js

+9-4
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ var UglifyJS = require('uglify-js');
77
var constants = require('./constants');
88
var compressAttributes = require('./compress_attributes');
99
var patchMinified = require('./patch_minified');
10+
var strictD3 = require('./strict_d3');
1011

1112
/** Convenience browserify wrapper
1213
*
@@ -32,16 +33,20 @@ module.exports = function _bundle(pathToIndex, pathToBundle, opts) {
3233
opts = opts || {};
3334

3435
// do we output a minified file?
35-
var pathToMinBundle = opts.pathToMinBundle,
36-
outputMinified = !!pathToMinBundle && !opts.debug;
36+
var pathToMinBundle = opts.pathToMinBundle;
37+
var outputMinified = !!pathToMinBundle;
3738

3839
var browserifyOpts = {};
3940
browserifyOpts.standalone = opts.standalone;
4041
browserifyOpts.debug = opts.debug;
4142
browserifyOpts.transform = outputMinified ? [compressAttributes] : [];
4243

43-
var b = browserify(pathToIndex, browserifyOpts),
44-
bundleWriteStream = fs.createWriteStream(pathToBundle);
44+
if(opts.debug) {
45+
browserifyOpts.transform.push(strictD3);
46+
}
47+
48+
var b = browserify(pathToIndex, browserifyOpts);
49+
var bundleWriteStream = fs.createWriteStream(pathToBundle);
4550

4651
bundleWriteStream.on('finish', function() {
4752
logger(pathToBundle);

tasks/util/constants.js

+1
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ module.exports = {
5858
pathToTestDashboardBundle: path.join(pathToBuild, 'test_dashboard-bundle.js'),
5959
pathToImageViewerBundle: path.join(pathToBuild, 'image_viewer-bundle.js'),
6060

61+
pathToImageTest: pathToImageTest,
6162
pathToTestImageMocks: path.join(pathToImageTest, 'mocks/'),
6263
pathToTestImageBaselines: path.join(pathToImageTest, 'baselines/'),
6364
pathToTestImages: path.join(pathToBuild, 'test_images/'),

tasks/util/strict_d3.js

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
var path = require('path');
2+
var transformTools = require('browserify-transform-tools');
3+
var constants = require('./constants');
4+
5+
var pathToStrictD3Module = path.join(
6+
constants.pathToImageTest,
7+
'strict-d3.js'
8+
);
9+
10+
/**
11+
* Transform `require('d3')` expressions to `require(/path/to/strict-d3.js)`
12+
*/
13+
14+
module.exports = transformTools.makeRequireTransform('requireTransform',
15+
{ evaluateArguments: true, jsFilesOnly: true },
16+
function(args, opts, cb) {
17+
var pathIn = args[0];
18+
var pathOut;
19+
20+
if(pathIn === 'd3' && opts.file !== pathToStrictD3Module) {
21+
pathOut = 'require(\'' + pathToStrictD3Module + '\')';
22+
}
23+
24+
if(pathOut) return cb(null, pathOut);
25+
else return cb();
26+
});

tasks/util/watchified_bundle.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ var prettySize = require('prettysize');
77
var constants = require('./constants');
88
var common = require('./common');
99
var compressAttributes = require('./compress_attributes');
10+
var strictD3 = require('./strict_d3');
1011

1112
/**
1213
* Make a plotly.js browserify bundle function watched by watchify.
@@ -22,7 +23,7 @@ module.exports = function makeWatchifiedBundle(onFirstBundleCallback) {
2223
var b = browserify(constants.pathToPlotlyIndex, {
2324
debug: true,
2425
standalone: 'Plotly',
25-
transform: [compressAttributes],
26+
transform: [strictD3, compressAttributes],
2627
cache: {},
2728
packageCache: {},
2829
plugin: [watchify]
13 KB
Loading

0 commit comments

Comments
 (0)