Skip to content
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