Skip to content

Implement scatter cliponaxis: false #1824

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 10 commits into from
11 changes: 7 additions & 4 deletions src/components/drawing/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,17 +62,20 @@ drawing.setRect = function(s, x, y, w, h) {
* @param {sel} sel : d3 selction of node to translate
* @param {object} xa : corresponding full xaxis object
* @param {object} ya : corresponding full yaxis object
* @param {object} trace : corresponding full trace object
*
* @return {boolean} :
* true if selection got translated
* false if selection could not get translated
*/
drawing.translatePoint = function(d, sel, xa, ya) {
drawing.translatePoint = function(d, sel, xa, ya, trace) {
// put xp and yp into d if pixel scaling is already done
var x = d.xp || xa.c2p(d.x),
y = d.yp || ya.c2p(d.y);
var x = d.xp = xa.c2p(d.x);
var y = d.yp = ya.c2p(d.y);

if(isNumeric(x) && isNumeric(y) && sel.node()) {
if(isNumeric(x) && isNumeric(y) && sel.node() &&
(trace.cliponaxis !== false || xa.isPtWithinRange(d) && ya.isPtWithinRange(d))
) {
// for multiline text this works better
if(sel.node().nodeName === 'text') {
sel.attr('x', x).attr('y', y);
Expand Down
41 changes: 23 additions & 18 deletions src/components/errorbars/plot.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,24 @@ var isNumeric = require('fast-isnumeric');

var subTypes = require('../../traces/scatter/subtypes');

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

var xa = plotinfo.xaxis,
ya = plotinfo.yaxis;
var xa = plotinfo.xaxis;
var ya = plotinfo.yaxis;

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

traces.each(function(d) {
var trace = d[0].trace,
// || {} is in case the trace (specifically scatterternary)
// doesn't support error bars at all, but does go through
// the scatter.plot mechanics, which calls ErrorBars.plot
// internally
xObj = trace.error_x || {},
yObj = trace.error_y || {};
var tr = d3.select(this);
var trace = d[0].trace;

// || {} is in case the trace (specifically scatterternary)
// doesn't support error bars at all, but does go through
// the scatter.plot mechanics, which calls ErrorBars.plot
// internally
var xObj = trace.error_x || {};
var yObj = trace.error_y || {};

var keyFunc;

Expand All @@ -44,8 +46,8 @@ module.exports = function plot(traces, plotinfo, transitionOpts) {

if(!yObj.visible && !xObj.visible) return;

var errorbars = d3.select(this).selectAll('g.errorbar')
.data(d, keyFunc);
var errorbars = tr.selectAll('g.errorbar')
.data(trace.cliponaxis === clipOnAxis ? d : [], keyFunc);

errorbars.exit().remove();

Expand All @@ -66,7 +68,7 @@ module.exports = function plot(traces, plotinfo, transitionOpts) {

if(sparse && !d.vis) return;

var path;
var path, yerror, xerror;

if(yObj.visible && isNumeric(coords.x) &&
isNumeric(coords.yh) &&
Expand All @@ -77,11 +79,9 @@ module.exports = function plot(traces, plotinfo, transitionOpts) {
coords.yh + 'h' + (2 * yw) + // hat
'm-' + yw + ',0V' + coords.ys; // bar


if(!coords.noYS) path += 'm-' + yw + ',0h' + (2 * yw); // shoe

var yerror = errorbar.select('path.yerror');

yerror = errorbar.select('path.yerror');
isNew = !yerror.size();

if(isNew) {
Expand All @@ -108,8 +108,7 @@ module.exports = function plot(traces, plotinfo, transitionOpts) {

if(!coords.noXS) path += 'm0,-' + xw + 'v' + (2 * xw); // shoe

var xerror = errorbar.select('path.xerror');

xerror = errorbar.select('path.xerror');
isNew = !xerror.size();

if(isNew) {
Expand All @@ -124,6 +123,12 @@ module.exports = function plot(traces, plotinfo, transitionOpts) {

xerror.attr('d', path);
}

if(trace.cliponaxis === false &&
!(xa.isPtWithinRange(d) && ya.isPtWithinRange(d))) {
yerror.remove();
xerror.remove();
}
});
});
};
Expand Down
9 changes: 7 additions & 2 deletions src/plot_api/plot_api.js
Original file line number Diff line number Diff line change
Expand Up @@ -1635,8 +1635,13 @@ function _restyle(gd, aobj, _traces) {
}

// some attributes declare an 'editType' flaglist
if(valObject.editType === 'docalc') {
flags.docalc = true;
switch(valObject.editType) {
case 'docalc':
flags.docalc = true;
break;
case 'doplot':
flags.doplot = true;
break;
}

// all the other ones, just modify that one attribute
Expand Down
7 changes: 5 additions & 2 deletions src/plot_api/subroutines.js
Original file line number Diff line number Diff line change
Expand Up @@ -164,9 +164,12 @@ exports.lsInner = function(gd) {
'height': ya._length
});

plotinfo.plot
.call(Drawing.setTranslate, xa._offset, ya._offset)
.call(Drawing.setClipUrl, plotinfo.clipId);

plotinfo.plot.call(Drawing.setTranslate, xa._offset, ya._offset);
plotinfo.plot.call(Drawing.setClipUrl, plotinfo.clipId);
plotinfo.plotnoclip
.call(Drawing.setTranslate, xa._offset, ya._offset);

var xlw = Drawing.crispRound(gd, xa.linewidth, 1),
ylw = Drawing.crispRound(gd, ya.linewidth, 1),
Expand Down
23 changes: 15 additions & 8 deletions src/plots/cartesian/dragbox.js
Original file line number Diff line number Diff line change
Expand Up @@ -749,16 +749,23 @@ module.exports = function dragBox(gd, plotinfo, x, y, w, h, ns, ew) {

subplot.plot
.call(Drawing.setTranslate, plotDx, plotDy)
.call(Drawing.setScale, 1 / xScaleFactor2, 1 / yScaleFactor2)
.call(Drawing.setScale, 1 / xScaleFactor2, 1 / yScaleFactor2);

// This is specifically directed at scatter traces, applying an inverse
// scale to individual points to counteract the scale of the trace
// as a whole:
.select('.scatterlayer').selectAll('.points').selectAll('.point')
.call(Drawing.setPointGroupScale, xScaleFactor2, yScaleFactor2);
subplot.plotnoclip
.call(Drawing.setTranslate, plotDx, plotDy)
.call(Drawing.setScale, 1 / xScaleFactor2, 1 / yScaleFactor2);

var points = subplot.plotgroup
.selectAll('.scatterlayer')
.selectAll('.points');

// This is specifically directed at scatter traces, applying an inverse
// scale to individual points to counteract the scale of the trace
// as a whole:
points.selectAll('.point')
.call(Drawing.setPointGroupScale, xScaleFactor2, yScaleFactor2);

subplot.plot.select('.scatterlayer')
.selectAll('.points').selectAll('.textpoint')
points.selectAll('.textpoint')
.call(Drawing.setTextPointsScale, xScaleFactor2, yScaleFactor2);
}
}
Expand Down
12 changes: 11 additions & 1 deletion src/plots/cartesian/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,12 @@ exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout)
.selectAll('g.trace')
.remove();
}

if(subplotInfo.plotnoclip) {
subplotInfo.plotnoclip.select('g.scatterlayer')
.selectAll('g.trace')
.remove();
}
}

oldFullLayout._infolayer.selectAll('g.rangeslider-container')
Expand Down Expand Up @@ -334,6 +340,9 @@ function makeSubplotLayer(plotinfo) {
plotinfo.xaxislayer = joinLayer(plotgroup, 'g', 'xaxislayer');
plotinfo.yaxislayer = joinLayer(plotgroup, 'g', 'yaxislayer');
plotinfo.overaxes = joinLayer(plotgroup, 'g', 'overaxes');

plotinfo.plotnoclip = joinLayer(plotgroup, 'g', 'plotnoclip');
plotinfo.overplotnoclip = joinLayer(plotgroup, 'g', 'overplotnoclip');
}
else {
var mainplotinfo = plotinfo.mainplotinfo;
Expand All @@ -345,16 +354,17 @@ function makeSubplotLayer(plotinfo) {

plotinfo.gridlayer = joinLayer(mainplotinfo.overgrid, 'g', id);
plotinfo.zerolinelayer = joinLayer(mainplotinfo.overzero, 'g', id);

plotinfo.plot = joinLayer(mainplotinfo.overplot, 'g', id);
plotinfo.xlines = joinLayer(mainplotinfo.overlines, 'path', id);
plotinfo.ylines = joinLayer(mainplotinfo.overlines, 'path', id);
plotinfo.xaxislayer = joinLayer(mainplotinfo.overaxes, 'g', id);
plotinfo.yaxislayer = joinLayer(mainplotinfo.overaxes, 'g', id);
plotinfo.plotnoclip = joinLayer(mainplotinfo.overplotnoclip, 'g', id);
}

// common attributes for all subplots, overlays or not
plotinfo.plot.call(joinPlotLayers);
joinLayer(plotinfo.plotnoclip, 'g', 'scatterlayer');

plotinfo.xlines
.style('fill', 'none')
Expand Down
18 changes: 15 additions & 3 deletions src/plots/cartesian/set_convert.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ function fromLog(v) {
module.exports = function setConvert(ax, fullLayout) {
fullLayout = fullLayout || {};

var axLetter = (ax._id || 'x').charAt(0);

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

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

// set scaling to pixels
ax.setScale = function(usePrivateRange) {
var gs = fullLayout._size,
axLetter = ax._id.charAt(0);
var gs = fullLayout._size;

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

if(axLetter === 'x') {
ax.isPtWithinRange = function(d) {
var x = d.x;
return x >= ax.range[0] && x <= ax.range[1];
};
} else {
ax.isPtWithinRange = function(d) {
var y = d.y;
return y >= ax.range[0] && y <= ax.range[1];
};
}

// for autoranging: arrays of objects:
// {val: axis value, pad: pixel padding}
// on the low and high sides
Expand Down
22 changes: 15 additions & 7 deletions src/plots/cartesian/transition_axes.js
Original file line number Diff line number Diff line change
Expand Up @@ -143,15 +143,23 @@ module.exports = function transitionAxes(gd, newLayout, transitionOpts, makeOnCo

subplot.plot
.call(Drawing.setTranslate, xa2._offset, ya2._offset)
.call(Drawing.setScale, 1, 1)
.call(Drawing.setScale, 1, 1);

// This is specifically directed at scatter traces, applying an inverse
// scale to individual points to counteract the scale of the trace
// as a whole:
.select('.scatterlayer').selectAll('.points').selectAll('.point')
.call(Drawing.setPointGroupScale, 1, 1);
subplot.plotnoclip
.call(Drawing.setTranslate, xa2._offset, ya2._offset)
.call(Drawing.setScale, 1, 1);

var points = subplot.plotgroup
.selectAll('.scatterlayer')
.selectAll('.points');

// This is specifically directed at scatter traces, applying an inverse
// scale to individual points to counteract the scale of the trace
// as a whole:
points.selectAll('.point')
.call(Drawing.setPointGroupScale, 1, 1);

subplot.plot.select('.scatterlayer').selectAll('.points').selectAll('.textpoint')
points.selectAll('.textpoint')
.call(Drawing.setTextPointsScale, 1, 1);
}

Expand Down
15 changes: 14 additions & 1 deletion src/plots/ternary/ternary.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,8 @@ proto.makeFramework = function() {
'grids',
'frontplot',
'zoom',
'aaxis', 'baxis', 'caxis', 'axlines'
'aaxis', 'baxis', 'caxis', 'axlines',
'frontplotnoclip'
];
var toplevel = _this.plotContainer.selectAll('g.toplevel')
.data(plotLayers);
Expand All @@ -113,6 +114,7 @@ proto.makeFramework = function() {
d3.select(this).classed(d, true);
});
}
else if(d === 'frontplotnoclip') s.append('g').classed('scatterlayer', true);
});

var grids = _this.plotContainer.select('.grids').selectAll('g.grid')
Expand Down Expand Up @@ -180,6 +182,16 @@ proto.adjustLayout = function(ternaryLayout, graphSize) {
};
setConvert(_this.xaxis, _this.graphDiv._fullLayout);
_this.xaxis.setScale();
_this.xaxis.isPtWithinRange = function(d) {
return (
d.a >= _this.aaxis.range[0] &&
d.a <= _this.aaxis.range[1] &&
d.b >= _this.baxis.range[1] &&
d.b <= _this.baxis.range[0] &&
d.c >= _this.caxis.range[1] &&
d.c <= _this.caxis.range[0]
);
};

_this.yaxis = {
type: 'linear',
Expand All @@ -192,6 +204,7 @@ proto.adjustLayout = function(ternaryLayout, graphSize) {
};
setConvert(_this.yaxis, _this.graphDiv._fullLayout);
_this.yaxis.setScale();
_this.yaxis.isPtWithinRange = function() { return true; };

// set up the modified axes for tick drawing
var yDomain0 = _this.yaxis.domain[0];
Expand Down
2 changes: 1 addition & 1 deletion src/traces/box/plot.js
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ module.exports = function plot(gd, plotinfo, cdbox) {
});
})
.enter().append('path')
.call(Drawing.translatePoints, xa, ya);
.call(Drawing.translatePoints, xa, ya, trace);
}
// draw mean (and stdev diamond) if desired
if(trace.boxmean) {
Expand Down
12 changes: 12 additions & 0 deletions src/traces/scatter/attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ module.exports = {
].join(' ')
}
},

connectgaps: {
valType: 'boolean',
dflt: false,
Expand All @@ -178,6 +179,17 @@ module.exports = {
'in the provided data arrays are connected.'
].join(' ')
},
cliponaxis: {
valType: 'boolean',
dflt: true,
role: 'info',
editType: 'doplot',
description: [
'Determines whether or not markers, text nodes and errobars',
'are clipped about the subplot axes.'
].join(' ')
},

fill: {
valType: 'enumerated',
values: ['none', 'tozeroy', 'tozerox', 'tonexty', 'tonextx', 'toself', 'tonext'],
Expand Down
2 changes: 2 additions & 0 deletions src/traces/scatter/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,6 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout

errorBarsSupplyDefaults(traceIn, traceOut, defaultColor, {axis: 'y'});
errorBarsSupplyDefaults(traceIn, traceOut, defaultColor, {axis: 'x', inherit: 'y'});

coerce('cliponaxis');
};
Loading