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

[take 2] Implement scatter cliponaxis: false #1861

Merged
merged 21 commits into from
Jul 13, 2017
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
c28b26a
lint in drawing/
etpinard Jul 7, 2017
8f72d1f
implement cliponaxis
etpinard Jul 7, 2017
1909034
hide outside range points on drag and transitions
etpinard Jul 7, 2017
4b5c4bb
implement `cliponaxis: false` for scatterternary
etpinard Jul 7, 2017
d46cae7
merge custom shared assertions into single assets/ module
etpinard Jul 7, 2017
7228d5d
add cliponaxis false mock
etpinard Jul 7, 2017
fe1f889
showcase new cliponaxis logic in ternary_markers
etpinard Jul 7, 2017
89ee533
add scatter and scatterternary cliponaxis tests
etpinard Jul 7, 2017
e4d7889
fix panning for ternary lines under `cliponaxis: false`
etpinard Jul 7, 2017
091473b
fix lint
etpinard Jul 10, 2017
9becb7c
hide points using `display: 'none'` instead of visibility attr
etpinard Jul 11, 2017
62ab845
remove obsolete code from old d.{xp|yp} optimization attempt
etpinard Jul 11, 2017
0496144
stash layer clipId value (null or same as clipId)
etpinard Jul 11, 2017
7b62b73
handle cliponaxis false/true traces coexisting on same subplot
etpinard Jul 11, 2017
02b9fbc
rename 'layers' cartesian constant -> 'traceLayerClasses'
etpinard Jul 11, 2017
1c85de6
add 'layer' axis attribute
etpinard Jul 11, 2017
e44aa4d
improve cliponaxis_false mock
etpinard Jul 11, 2017
e84701f
replace strict-d3 IIFE with browserify 'require' transform
etpinard Jul 12, 2017
e234827
fixes #1873 - apply transform on <clipPath> child
etpinard Jul 12, 2017
6f494c6
talk about 'cliponaxis' in axis 'layer' description and vice-versa
etpinard Jul 12, 2017
bbb31f4
Merge pull request #1871 from plotly/axis-layer-above-below
etpinard Jul 13, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 22 additions & 4 deletions src/components/drawing/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ drawing.setRect = function(s, x, y, w, h) {
*/
drawing.translatePoint = function(d, sel, xa, ya) {
// 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);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wow, || is 4 years old there... but it doesn't look to me like .xp and .yp are used anywhere except https://github.com/plotly/plotly.js/blob/master/src/traces/scatter/plot.js#L489 which is a near copy of this routine. Take it out? Must have been a perf optimization that we did long ago but is now mostly gone.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done in 62ab845


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

drawing.translatePoints = function(s, xa, ya, trace) {
drawing.translatePoints = function(s, xa, ya) {
s.each(function(d) {
var sel = d3.select(this);
drawing.translatePoint(d, sel, xa, ya, trace);
drawing.translatePoint(d, sel, xa, ya);
});
};

drawing.hideOutsideRangePoint = function(d, sel, xa, ya) {
sel.attr(
'visibility',
xa.isPtWithinRange(d) && ya.isPtWithinRange(d) ? null : 'hidden'
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

possibly better to use display: 'none' instead? visibility: 'hidden' still includes the element in the rendering pipeline (so children could be made visible for example, as we've seen!), which is probably slower and also means it still contributes to the bounding box (potential confusion for illustrator? I suppose we could also add something to our static plot pipeline to remove visibility: 'hidden' and display: 'none' elements completely...)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

possibly better to use display: 'none' instead?

Would be nice to benchmark these things, but you make a good argument for display. Good point above illustrator not liking visibility 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done in 9becb7c

);
};

drawing.hideOutsideRangePoints = function(points, subplot) {
if(!subplot._hasClipOnAxisFalse) return;

var xa = subplot.xaxis;
var ya = subplot.yaxis;

points.each(function(d) {
drawing.hideOutsideRangePoint(d, d3.select(this), xa, ya);
});
};

Expand Down
10 changes: 8 additions & 2 deletions src/components/errorbars/plot.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,14 @@
var d3 = require('d3');
var isNumeric = require('fast-isnumeric');

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

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

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

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

Expand Down Expand Up @@ -60,6 +61,11 @@ module.exports = function plot(traces, plotinfo, transitionOpts) {
.style('opacity', 1);
}

errorbars.call(
Drawing.setClipUrl,
plotinfo._hasClipOnAxisFalse ? plotinfo.clipId : null
);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

perhaps it would simplify things if when we calculate _hasClipOnAxisFalse we stash plotClipId and layerClipId (each of which is either clipId or null)?

Also a debatable style point, I've started avoiding .call when it's not part of a method chain - so this would just be Drawing.setClipUrl(errorbars, clipId)


errorbars.each(function(d) {
var errorbar = d3.select(this);
var coords = errorCoords(d, xa, ya);
Expand Down
23 changes: 21 additions & 2 deletions src/plot_api/subroutines.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ var Drawing = require('../components/drawing');
var Titles = require('../components/titles');
var ModeBar = require('../components/modebar');
var initInteractions = require('../plots/cartesian/graph_interact');
var cartesianConstants = require('../plots/cartesian/constants');

exports.layoutStyles = function(gd) {
return Lib.syncOrAsync([Plots.doAutoMargin, exports.lsInner], gd);
Expand Down Expand Up @@ -164,9 +165,27 @@ exports.lsInner = function(gd) {
'height': ya._length
});


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

var plotClipId;
var layerClipId;

if(plotinfo._hasClipOnAxisFalse) {
plotClipId = null;
layerClipId = plotinfo.clipId;
} else {
plotClipId = plotinfo.clipId;
layerClipId = null;
}

plotinfo.plot.call(Drawing.setClipUrl, plotClipId);

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

var xlw = Drawing.crispRound(gd, xa.linewidth, 1),
ylw = Drawing.crispRound(gd, ya.linewidth, 1),
Expand Down
18 changes: 17 additions & 1 deletion src/plots/cartesian/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,5 +49,21 @@ module.exports = {

// last resort axis ranges for x and y axes if we have no data
DFLTRANGEX: [-1, 6],
DFLTRANGEY: [-1, 4]
DFLTRANGEY: [-1, 4],

// Layers to keep plot types in the right order.
// from back to front:
// 1. heatmaps, 2D histos and contour maps
// 2. bars / 1D histos
// 3. errorbars for bars and scatter
// 4. scatter
// 5. box plots
layers: [
'imagelayer',
'maplayer',
'barlayer',
'carpetlayer',
'boxlayer',
'scatterlayer'
]
};
25 changes: 14 additions & 11 deletions src/plots/cartesian/dragbox.js
Original file line number Diff line number Diff line change
Expand Up @@ -747,19 +747,22 @@ module.exports = function dragBox(gd, plotinfo, x, y, w, h, ns, ew) {
.call(Drawing.setTranslate, clipDx, clipDy)
.call(Drawing.setScale, xScaleFactor2, yScaleFactor2);

var scatterPoints = subplot.plot.select('.scatterlayer').selectAll('.points');

subplot.plot
.call(Drawing.setTranslate, plotDx, plotDy)
.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.plot.select('.scatterlayer')
.selectAll('.points').selectAll('.textpoint')
.call(Drawing.setTextPointsScale, xScaleFactor2, 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:
scatterPoints.selectAll('.point')
.call(Drawing.setPointGroupScale, xScaleFactor2, yScaleFactor2)
.call(Drawing.hideOutsideRangePoints, subplot);

scatterPoints.selectAll('.textpoint')
.call(Drawing.setTextPointsScale, xScaleFactor2, yScaleFactor2)
.call(Drawing.hideOutsideRangePoints, subplot);
}
}

Expand Down
25 changes: 6 additions & 19 deletions src/plots/cartesian/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -294,24 +294,8 @@ function makeSubplotData(gd) {
}

function makeSubplotLayer(plotinfo) {
var plotgroup = plotinfo.plotgroup,
id = plotinfo.id;

// Layers to keep plot types in the right order.
// from back to front:
// 1. heatmaps, 2D histos and contour maps
// 2. bars / 1D histos
// 3. errorbars for bars and scatter
// 4. scatter
// 5. box plots
function joinPlotLayers(parent) {
joinLayer(parent, 'g', 'imagelayer');
joinLayer(parent, 'g', 'maplayer');
joinLayer(parent, 'g', 'barlayer');
joinLayer(parent, 'g', 'carpetlayer');
joinLayer(parent, 'g', 'boxlayer');
joinLayer(parent, 'g', 'scatterlayer');
}
var plotgroup = plotinfo.plotgroup;
var id = plotinfo.id;

if(!plotinfo.mainplot) {
var backLayer = joinLayer(plotgroup, 'g', 'layer-subplot');
Expand Down Expand Up @@ -354,7 +338,10 @@ function makeSubplotLayer(plotinfo) {
}

// common attributes for all subplots, overlays or not
plotinfo.plot.call(joinPlotLayers);

for(var i = 0; i < constants.layers.length; i++) {
joinLayer(plotinfo.plot, 'g', constants.layers[i]);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎉

}

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
20 changes: 12 additions & 8 deletions src/plots/cartesian/transition_axes.js
Original file line number Diff line number Diff line change
Expand Up @@ -143,16 +143,20 @@ 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);
var scatterPoints = subplot.plot.select('.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:
scatterPoints.selectAll('.point')
.call(Drawing.setPointGroupScale, 1, 1)
.call(Drawing.hideOutsideRangePoints, subplot);

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

function updateSubplot(subplot, progress) {
Expand Down
19 changes: 19 additions & 0 deletions src/plots/plots.js
Original file line number Diff line number Diff line change
Expand Up @@ -656,6 +656,25 @@ plots.linkSubplots = function(newFullData, newFullLayout, oldFullData, oldFullLa

plotinfo.xaxis = Plotly.Axes.getFromId(mockGd, id, 'x');
plotinfo.yaxis = Plotly.Axes.getFromId(mockGd, id, 'y');

// By default, we clip at the subplot level,
// but if one trace on a given subplot has *cliponaxis* set to false,
// we need to clip at the trace module layer level;
// find this out here, once of for all.
plotinfo._hasClipOnAxisFalse = false;

for(var j = 0; j < newFullData.length; j++) {
var trace = newFullData[j];

if(
trace.xaxis === plotinfo.xaxis._id &&
trace.yaxis === plotinfo.yaxis._id &&
trace.cliponaxis === false
) {
plotinfo._hasClipOnAxisFalse = true;
break;
}
}
}
};

Expand Down
1 change: 1 addition & 0 deletions src/plots/ternary/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout)
if(!newFullLayout[oldTernaryKey] && !!oldTernary) {
oldTernary.plotContainer.remove();
oldTernary.clipDef.remove();
oldTernary.clipDefRelative.remove();
}
}

Expand Down
Loading