Skip to content

Commit 1dbaea5

Browse files
authored
Merge pull request #1861 from plotly/cliponaxis-false-take2
[take 2] Implement scatter `cliponaxis: false`
2 parents edf52ca + bbb31f4 commit 1dbaea5

Some content is hidden

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

42 files changed

+960
-212
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/components/drawing/index.js

+22-5
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,8 @@ drawing.setRect = function(s, x, y, w, h) {
6868
* false if selection could not get translated
6969
*/
7070
drawing.translatePoint = function(d, sel, xa, ya) {
71-
// put xp and yp into d if pixel scaling is already done
72-
var x = d.xp || xa.c2p(d.x),
73-
y = d.yp || ya.c2p(d.y);
71+
var x = xa.c2p(d.x);
72+
var y = ya.c2p(d.y);
7473

7574
if(isNumeric(x) && isNumeric(y) && sel.node()) {
7675
// for multiline text this works better
@@ -86,10 +85,28 @@ drawing.translatePoint = function(d, sel, xa, ya) {
8685
return true;
8786
};
8887

89-
drawing.translatePoints = function(s, xa, ya, trace) {
88+
drawing.translatePoints = function(s, xa, ya) {
9089
s.each(function(d) {
9190
var sel = d3.select(this);
92-
drawing.translatePoint(d, sel, xa, ya, trace);
91+
drawing.translatePoint(d, sel, xa, ya);
92+
});
93+
};
94+
95+
drawing.hideOutsideRangePoint = function(d, sel, xa, ya) {
96+
sel.attr(
97+
'display',
98+
xa.isPtWithinRange(d) && ya.isPtWithinRange(d) ? null : 'none'
99+
);
100+
};
101+
102+
drawing.hideOutsideRangePoints = function(points, subplot) {
103+
if(!subplot._hasClipOnAxisFalse) return;
104+
105+
var xa = subplot.xaxis;
106+
var ya = subplot.yaxis;
107+
108+
points.each(function(d) {
109+
drawing.hideOutsideRangePoint(d, d3.select(this), xa, ya);
93110
});
94111
};
95112

src/components/errorbars/plot.js

+5-2
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,14 @@
1212
var d3 = require('d3');
1313
var isNumeric = require('fast-isnumeric');
1414

15+
var Drawing = require('../drawing');
1516
var subTypes = require('../../traces/scatter/subtypes');
1617

1718
module.exports = function plot(traces, plotinfo, transitionOpts) {
1819
var isNew;
1920

20-
var xa = plotinfo.xaxis,
21-
ya = plotinfo.yaxis;
21+
var xa = plotinfo.xaxis;
22+
var ya = plotinfo.yaxis;
2223

2324
var hasAnimation = transitionOpts && transitionOpts.duration > 0;
2425

@@ -60,6 +61,8 @@ module.exports = function plot(traces, plotinfo, transitionOpts) {
6061
.style('opacity', 1);
6162
}
6263

64+
Drawing.setClipUrl(errorbars, plotinfo.layerClipId);
65+
6366
errorbars.each(function(d) {
6467
var errorbar = d3.select(this);
6568
var coords = errorCoords(d, xa, ya);

src/plot_api/subroutines.js

+25-2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ var Drawing = require('../components/drawing');
2020
var Titles = require('../components/titles');
2121
var ModeBar = require('../components/modebar');
2222
var initInteractions = require('../plots/cartesian/graph_interact');
23+
var cartesianConstants = require('../plots/cartesian/constants');
2324

2425
exports.layoutStyles = function(gd) {
2526
return Lib.syncOrAsync([Plots.doAutoMargin, exports.lsInner], gd);
@@ -164,9 +165,31 @@ exports.lsInner = function(gd) {
164165
'height': ya._length
165166
});
166167

167-
168168
plotinfo.plot.call(Drawing.setTranslate, xa._offset, ya._offset);
169-
plotinfo.plot.call(Drawing.setClipUrl, plotinfo.clipId);
169+
170+
var plotClipId;
171+
var layerClipId;
172+
173+
if(plotinfo._hasClipOnAxisFalse) {
174+
plotClipId = null;
175+
layerClipId = plotinfo.clipId;
176+
} else {
177+
plotClipId = plotinfo.clipId;
178+
layerClipId = null;
179+
}
180+
181+
Drawing.setClipUrl(plotinfo.plot, plotClipId);
182+
183+
for(i = 0; i < cartesianConstants.traceLayerClasses.length; i++) {
184+
var layer = cartesianConstants.traceLayerClasses[i];
185+
if(layer !== 'scatterlayer') {
186+
plotinfo.plot.selectAll('g.' + layer).call(Drawing.setClipUrl, layerClipId);
187+
}
188+
}
189+
190+
// stash layer clipId value (null or same as clipId)
191+
// to DRY up Drawing.setClipUrl calls downstream
192+
plotinfo.layerClipId = layerClipId;
170193

171194
var xlw = Drawing.crispRound(gd, xa.linewidth, 1),
172195
ylw = Drawing.crispRound(gd, ya.linewidth, 1),

src/plots/cartesian/constants.js

+22-1
Original file line numberDiff line numberDiff line change
@@ -49,5 +49,26 @@ module.exports = {
4949

5050
// last resort axis ranges for x and y axes if we have no data
5151
DFLTRANGEX: [-1, 6],
52-
DFLTRANGEY: [-1, 4]
52+
DFLTRANGEY: [-1, 4],
53+
54+
// Layers to keep trace types in the right order.
55+
// from back to front:
56+
// 1. heatmaps, 2D histos and contour maps
57+
// 2. bars / 1D histos
58+
// 3. errorbars for bars and scatter
59+
// 4. scatter
60+
// 5. box plots
61+
traceLayerClasses: [
62+
'imagelayer',
63+
'maplayer',
64+
'barlayer',
65+
'carpetlayer',
66+
'boxlayer',
67+
'scatterlayer'
68+
],
69+
70+
layerValue2layerClass: {
71+
'above traces': 'above',
72+
'below traces': 'below'
73+
}
5374
};

src/plots/cartesian/dragbox.js

+15-12
Original file line numberDiff line numberDiff line change
@@ -743,23 +743,26 @@ 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

750+
var scatterPoints = subplot.plot.select('.scatterlayer').selectAll('.points');
751+
750752
subplot.plot
751753
.call(Drawing.setTranslate, plotDx, plotDy)
752-
.call(Drawing.setScale, 1 / xScaleFactor2, 1 / yScaleFactor2)
753-
754-
// This is specifically directed at scatter traces, applying an inverse
755-
// scale to individual points to counteract the scale of the trace
756-
// as a whole:
757-
.select('.scatterlayer').selectAll('.points').selectAll('.point')
758-
.call(Drawing.setPointGroupScale, xScaleFactor2, yScaleFactor2);
759-
760-
subplot.plot.select('.scatterlayer')
761-
.selectAll('.points').selectAll('.textpoint')
762-
.call(Drawing.setTextPointsScale, xScaleFactor2, yScaleFactor2);
754+
.call(Drawing.setScale, 1 / xScaleFactor2, 1 / yScaleFactor2);
755+
756+
// This is specifically directed at scatter traces, applying an inverse
757+
// scale to individual points to counteract the scale of the trace
758+
// as a whole:
759+
scatterPoints.selectAll('.point')
760+
.call(Drawing.setPointGroupScale, xScaleFactor2, yScaleFactor2)
761+
.call(Drawing.hideOutsideRangePoints, subplot);
762+
763+
scatterPoints.selectAll('.textpoint')
764+
.call(Drawing.setTextPointsScale, xScaleFactor2, yScaleFactor2)
765+
.call(Drawing.hideOutsideRangePoints, subplot);
763766
}
764767
}
765768

src/plots/cartesian/index.js

+47-29
Original file line numberDiff line numberDiff line change
@@ -289,24 +289,10 @@ function makeSubplotData(gd) {
289289
}
290290

291291
function makeSubplotLayer(plotinfo) {
292-
var plotgroup = plotinfo.plotgroup,
293-
id = plotinfo.id;
294-
295-
// Layers to keep plot types in the right order.
296-
// from back to front:
297-
// 1. heatmaps, 2D histos and contour maps
298-
// 2. bars / 1D histos
299-
// 3. errorbars for bars and scatter
300-
// 4. scatter
301-
// 5. box plots
302-
function joinPlotLayers(parent) {
303-
joinLayer(parent, 'g', 'imagelayer');
304-
joinLayer(parent, 'g', 'maplayer');
305-
joinLayer(parent, 'g', 'barlayer');
306-
joinLayer(parent, 'g', 'carpetlayer');
307-
joinLayer(parent, 'g', 'boxlayer');
308-
joinLayer(parent, 'g', 'scatterlayer');
309-
}
292+
var plotgroup = plotinfo.plotgroup;
293+
var id = plotinfo.id;
294+
var xLayer = constants.layerValue2layerClass[plotinfo.xaxis.layer];
295+
var yLayer = constants.layerValue2layerClass[plotinfo.yaxis.layer];
310296

311297
if(!plotinfo.mainplot) {
312298
var backLayer = joinLayer(plotgroup, 'g', 'layer-subplot');
@@ -319,19 +305,36 @@ function makeSubplotLayer(plotinfo) {
319305
plotinfo.zerolinelayer = joinLayer(plotgroup, 'g', 'zerolinelayer');
320306
plotinfo.overzero = joinLayer(plotgroup, 'g', 'overzero');
321307

308+
joinLayer(plotgroup, 'path', 'xlines-below');
309+
joinLayer(plotgroup, 'path', 'ylines-below');
310+
plotinfo.overlinesBelow = joinLayer(plotgroup, 'g', 'overlines-below');
311+
312+
joinLayer(plotgroup, 'g', 'xaxislayer-below');
313+
joinLayer(plotgroup, 'g', 'yaxislayer-below');
314+
plotinfo.overaxesBelow = joinLayer(plotgroup, 'g', 'overaxes-below');
315+
322316
plotinfo.plot = joinLayer(plotgroup, 'g', 'plot');
323317
plotinfo.overplot = joinLayer(plotgroup, 'g', 'overplot');
324318

325-
plotinfo.xlines = joinLayer(plotgroup, 'path', 'xlines');
326-
plotinfo.ylines = joinLayer(plotgroup, 'path', 'ylines');
327-
plotinfo.overlines = joinLayer(plotgroup, 'g', 'overlines');
319+
joinLayer(plotgroup, 'path', 'xlines-above');
320+
joinLayer(plotgroup, 'path', 'ylines-above');
321+
plotinfo.overlinesAbove = joinLayer(plotgroup, 'g', 'overlines-above');
328322

329-
plotinfo.xaxislayer = joinLayer(plotgroup, 'g', 'xaxislayer');
330-
plotinfo.yaxislayer = joinLayer(plotgroup, 'g', 'yaxislayer');
331-
plotinfo.overaxes = joinLayer(plotgroup, 'g', 'overaxes');
323+
joinLayer(plotgroup, 'g', 'xaxislayer-above');
324+
joinLayer(plotgroup, 'g', 'yaxislayer-above');
325+
plotinfo.overaxesAbove = joinLayer(plotgroup, 'g', 'overaxes-above');
326+
327+
// set refs to correct layers as determined by 'axis.layer'
328+
plotinfo.xlines = plotgroup.select('.xlines-' + xLayer);
329+
plotinfo.ylines = plotgroup.select('.ylines-' + yLayer);
330+
plotinfo.xaxislayer = plotgroup.select('.xaxislayer-' + xLayer);
331+
plotinfo.yaxislayer = plotgroup.select('.yaxislayer-' + yLayer);
332332
}
333333
else {
334334
var mainplotinfo = plotinfo.mainplotinfo;
335+
var mainplotgroup = mainplotinfo.plotgroup;
336+
var xId = id + '-x';
337+
var yId = id + '-y';
335338

336339
// now make the components of overlaid subplots
337340
// overlays don't have backgrounds, and append all
@@ -341,15 +344,30 @@ function makeSubplotLayer(plotinfo) {
341344
plotinfo.gridlayer = joinLayer(mainplotinfo.overgrid, 'g', id);
342345
plotinfo.zerolinelayer = joinLayer(mainplotinfo.overzero, 'g', id);
343346

347+
joinLayer(mainplotinfo.overlinesBelow, 'path', xId);
348+
joinLayer(mainplotinfo.overlinesBelow, 'path', yId);
349+
joinLayer(mainplotinfo.overaxesBelow, 'g', xId);
350+
joinLayer(mainplotinfo.overaxesBelow, 'g', yId);
351+
344352
plotinfo.plot = joinLayer(mainplotinfo.overplot, 'g', id);
345-
plotinfo.xlines = joinLayer(mainplotinfo.overlines, 'path', id + '-x');
346-
plotinfo.ylines = joinLayer(mainplotinfo.overlines, 'path', id + '-y');
347-
plotinfo.xaxislayer = joinLayer(mainplotinfo.overaxes, 'g', id + '-x');
348-
plotinfo.yaxislayer = joinLayer(mainplotinfo.overaxes, 'g', id + '-y');
353+
354+
joinLayer(mainplotinfo.overlinesAbove, 'path', xId);
355+
joinLayer(mainplotinfo.overlinesAbove, 'path', yId);
356+
joinLayer(mainplotinfo.overaxesAbove, 'g', xId);
357+
joinLayer(mainplotinfo.overaxesAbove, 'g', yId);
358+
359+
// set refs to correct layers as determined by 'abovetraces'
360+
plotinfo.xlines = mainplotgroup.select('.overlines-' + xLayer).select('.' + xId);
361+
plotinfo.ylines = mainplotgroup.select('.overlines-' + yLayer).select('.' + yId);
362+
plotinfo.xaxislayer = mainplotgroup.select('.overaxes-' + xLayer).select('.' + xId);
363+
plotinfo.yaxislayer = mainplotgroup.select('.overaxes-' + yLayer).select('.' + yId);
349364
}
350365

351366
// common attributes for all subplots, overlays or not
352-
plotinfo.plot.call(joinPlotLayers);
367+
368+
for(var i = 0; i < constants.traceLayerClasses.length; i++) {
369+
joinLayer(plotinfo.plot, 'g', constants.traceLayerClasses[i]);
370+
}
353371

354372
plotinfo.xlines
355373
.style('fill', 'none')

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/set_convert.js

+15-3
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ function fromLog(v) {
5858
module.exports = function setConvert(ax, fullLayout) {
5959
fullLayout = fullLayout || {};
6060

61+
var axLetter = (ax._id || 'x').charAt(0);
62+
6163
// clipMult: how many axis lengths past the edge do we render?
6264
// for panning, 1-2 would suffice, but for zooming more is nice.
6365
// also, clipping can affect the direction of lines off the edge...
@@ -277,7 +279,6 @@ module.exports = function setConvert(ax, fullLayout) {
277279
ax.cleanRange = function(rangeAttr) {
278280
if(!rangeAttr) rangeAttr = 'range';
279281
var range = Lib.nestedProperty(ax, rangeAttr).get(),
280-
axLetter = (ax._id || 'x').charAt(0),
281282
i, dflt;
282283

283284
if(ax.type === 'date') dflt = Lib.dfltRange(ax.calendar);
@@ -341,8 +342,7 @@ module.exports = function setConvert(ax, fullLayout) {
341342

342343
// set scaling to pixels
343344
ax.setScale = function(usePrivateRange) {
344-
var gs = fullLayout._size,
345-
axLetter = ax._id.charAt(0);
345+
var gs = fullLayout._size;
346346

347347
// TODO cleaner way to handle this case
348348
if(!ax._categories) ax._categories = [];
@@ -435,6 +435,18 @@ module.exports = function setConvert(ax, fullLayout) {
435435
);
436436
};
437437

438+
if(axLetter === 'x') {
439+
ax.isPtWithinRange = function(d) {
440+
var x = d.x;
441+
return x >= ax.range[0] && x <= ax.range[1];
442+
};
443+
} else {
444+
ax.isPtWithinRange = function(d) {
445+
var y = d.y;
446+
return y >= ax.range[0] && y <= ax.range[1];
447+
};
448+
}
449+
438450
// for autoranging: arrays of objects:
439451
// {val: axis value, pad: pixel padding}
440452
// on the low and high sides

0 commit comments

Comments
 (0)