Skip to content

Commit e5ff05b

Browse files
authored
Merge pull request #956 from plotly/scatter-mode-exiting
Multiple scatter plot fixes
2 parents 87a83e9 + d810765 commit e5ff05b

File tree

3 files changed

+219
-100
lines changed

3 files changed

+219
-100
lines changed

Diff for: src/components/drawing/index.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,8 @@ drawing.translatePoint = function(d, sel, xa, ya) {
5353

5454
if(isNumeric(x) && isNumeric(y)) {
5555
// for multiline text this works better
56-
if(this.nodeName === 'text') {
57-
sel.node().attr('x', x).attr('y', y);
56+
if(sel.node().nodeName === 'text') {
57+
sel.attr('x', x).attr('y', y);
5858
} else {
5959
sel.attr('transform', 'translate(' + x + ',' + y + ')');
6060
}

Diff for: src/traces/scatter/plot.js

+118-98
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ module.exports = function plot(gd, plotinfo, cdscatter, transitionOpts, makeOnCo
3333

3434
selection = scatterlayer.selectAll('g.trace');
3535

36-
join = selection.data(cdscatter, function(d) {return d[0].trace.uid;});
36+
join = selection.data(cdscatter, function(d) { return d[0].trace.uid; });
3737

3838
// Append new traces:
3939
join.enter().append('g')
@@ -197,11 +197,19 @@ function plotOne(gd, idx, plotinfo, cdscatter, cdscatterAll, element, transition
197197
// revpath is fullpath reversed, for fill-to-next
198198
revpath = '',
199199
// functions for converting a point array to a path
200-
pathfn, revpathbase, revpathfn;
200+
pathfn, revpathbase, revpathfn,
201+
// variables used before and after the data join
202+
pt0, lastSegment, pt1, thisPolygons;
203+
204+
// initialize line join data / method
205+
var segments = [],
206+
lineSegments = [],
207+
makeUpdate = Lib.noop;
201208

202209
ownFillEl3 = trace._ownFill;
203210

204211
if(subTypes.hasLines(trace) || trace.fill !== 'none') {
212+
205213
if(tonext) {
206214
// This tells .style which trace to use for fill information:
207215
tonext.datum(cdscatter);
@@ -237,7 +245,7 @@ function plotOne(gd, idx, plotinfo, cdscatter, cdscatterAll, element, transition
237245
return revpathbase(pts.reverse());
238246
};
239247

240-
var segments = linePoints(cdscatter, {
248+
segments = linePoints(cdscatter, {
241249
xaxis: xa,
242250
yaxis: ya,
243251
connectGaps: trace.connectgaps,
@@ -250,24 +258,22 @@ function plotOne(gd, idx, plotinfo, cdscatter, cdscatterAll, element, transition
250258
// polygons for hover on fill
251259
// TODO: can we skip this if hoveron!=fills? That would mean we
252260
// need to redraw when you change hoveron...
253-
var thisPolygons = trace._polygons = new Array(segments.length);
261+
thisPolygons = trace._polygons = new Array(segments.length);
254262
for(i = 0; i < segments.length; i++) {
255263
trace._polygons[i] = polygonTester(segments[i]);
256264
}
257265

258-
var pt0, lastSegment, pt1;
259-
260266
if(segments.length) {
261267
pt0 = segments[0][0];
262268
lastSegment = segments[segments.length - 1];
263269
pt1 = lastSegment[lastSegment.length - 1];
264270
}
265271

266-
var lineSegments = segments.filter(function(s) {
272+
lineSegments = segments.filter(function(s) {
267273
return s.length > 1;
268274
});
269275

270-
var makeUpdate = function(isEnter) {
276+
makeUpdate = function(isEnter) {
271277
return function(pts) {
272278
thispath = pathfn(pts);
273279
thisrevpath = revpathfn(pts);
@@ -303,66 +309,66 @@ function plotOne(gd, idx, plotinfo, cdscatter, cdscatterAll, element, transition
303309
}
304310
};
305311
};
312+
}
306313

307-
var lineJoin = tr.selectAll('.js-line').data(lineSegments);
314+
var lineJoin = tr.selectAll('.js-line').data(lineSegments);
308315

309-
transition(lineJoin.exit())
310-
.style('opacity', 0)
311-
.remove();
316+
transition(lineJoin.exit())
317+
.style('opacity', 0)
318+
.remove();
312319

313-
lineJoin.each(makeUpdate(false));
320+
lineJoin.each(makeUpdate(false));
314321

315-
lineJoin.enter().append('path')
316-
.classed('js-line', true)
317-
.style('vector-effect', 'non-scaling-stroke')
318-
.call(Drawing.lineGroupStyle)
319-
.each(makeUpdate(true));
322+
lineJoin.enter().append('path')
323+
.classed('js-line', true)
324+
.style('vector-effect', 'non-scaling-stroke')
325+
.call(Drawing.lineGroupStyle)
326+
.each(makeUpdate(true));
320327

321-
if(segments.length) {
322-
if(ownFillEl3) {
323-
if(pt0 && pt1) {
324-
if(ownFillDir) {
325-
if(ownFillDir === 'y') {
326-
pt0[1] = pt1[1] = ya.c2p(0, true);
327-
}
328-
else if(ownFillDir === 'x') {
329-
pt0[0] = pt1[0] = xa.c2p(0, true);
330-
}
331-
332-
// fill to zero: full trace path, plus extension of
333-
// the endpoints to the appropriate axis
334-
// For the sake of animations, wrap the points around so that
335-
// the points on the axes are the first two points. Otherwise
336-
// animations get a little crazy if the number of points changes.
337-
transition(ownFillEl3).attr('d', 'M' + pt1 + 'L' + pt0 + 'L' + fullpath.substr(1));
338-
} else {
339-
// fill to self: just join the path to itself
340-
transition(ownFillEl3).attr('d', fullpath + 'Z');
328+
if(segments.length) {
329+
if(ownFillEl3) {
330+
if(pt0 && pt1) {
331+
if(ownFillDir) {
332+
if(ownFillDir === 'y') {
333+
pt0[1] = pt1[1] = ya.c2p(0, true);
334+
}
335+
else if(ownFillDir === 'x') {
336+
pt0[0] = pt1[0] = xa.c2p(0, true);
341337
}
338+
339+
// fill to zero: full trace path, plus extension of
340+
// the endpoints to the appropriate axis
341+
// For the sake of animations, wrap the points around so that
342+
// the points on the axes are the first two points. Otherwise
343+
// animations get a little crazy if the number of points changes.
344+
transition(ownFillEl3).attr('d', 'M' + pt1 + 'L' + pt0 + 'L' + fullpath.substr(1));
345+
} else {
346+
// fill to self: just join the path to itself
347+
transition(ownFillEl3).attr('d', fullpath + 'Z');
342348
}
343349
}
344-
else if(trace.fill.substr(0, 6) === 'tonext' && fullpath && prevRevpath) {
345-
// fill to next: full trace path, plus the previous path reversed
346-
if(trace.fill === 'tonext') {
347-
// tonext: for use by concentric shapes, like manually constructed
348-
// contours, we just add the two paths closed on themselves.
349-
// This makes strange results if one path is *not* entirely
350-
// inside the other, but then that is a strange usage.
351-
transition(tonext).attr('d', fullpath + 'Z' + prevRevpath + 'Z');
352-
}
353-
else {
354-
// tonextx/y: for now just connect endpoints with lines. This is
355-
// the correct behavior if the endpoints are at the same value of
356-
// y/x, but if they *aren't*, we should ideally do more complicated
357-
// things depending on whether the new endpoint projects onto the
358-
// existing curve or off the end of it
359-
transition(tonext).attr('d', fullpath + 'L' + prevRevpath.substr(1) + 'Z');
360-
}
361-
trace._polygons = trace._polygons.concat(prevPolygons);
350+
}
351+
else if(trace.fill.substr(0, 6) === 'tonext' && fullpath && prevRevpath) {
352+
// fill to next: full trace path, plus the previous path reversed
353+
if(trace.fill === 'tonext') {
354+
// tonext: for use by concentric shapes, like manually constructed
355+
// contours, we just add the two paths closed on themselves.
356+
// This makes strange results if one path is *not* entirely
357+
// inside the other, but then that is a strange usage.
358+
transition(tonext).attr('d', fullpath + 'Z' + prevRevpath + 'Z');
359+
}
360+
else {
361+
// tonextx/y: for now just connect endpoints with lines. This is
362+
// the correct behavior if the endpoints are at the same value of
363+
// y/x, but if they *aren't*, we should ideally do more complicated
364+
// things depending on whether the new endpoint projects onto the
365+
// existing curve or off the end of it
366+
transition(tonext).attr('d', fullpath + 'L' + prevRevpath.substr(1) + 'Z');
362367
}
363-
trace._prevRevpath = revpath;
364-
trace._prevPolygons = thisPolygons;
368+
trace._polygons = trace._polygons.concat(prevPolygons);
365369
}
370+
trace._prevRevpath = revpath;
371+
trace._prevPolygons = thisPolygons;
366372
}
367373

368374

@@ -381,64 +387,78 @@ function plotOne(gd, idx, plotinfo, cdscatter, cdscatterAll, element, transition
381387
}
382388
}
383389

390+
function hideFilter() {
391+
return false;
392+
}
393+
384394
function makePoints(d) {
385395
var join, selection;
396+
386397
var trace = d[0].trace,
387398
s = d3.select(this),
388399
showMarkers = subTypes.hasMarkers(trace),
389400
showText = subTypes.hasText(trace);
390401

391-
if((!showMarkers && !showText) || trace.visible !== true) s.remove();
392-
else {
393-
if(showMarkers) {
394-
selection = s.selectAll('path.point');
402+
var keyFunc = getKeyFunc(trace),
403+
markerFilter = hideFilter,
404+
textFilter = hideFilter;
395405

396-
join = selection
397-
.data(trace.marker.maxdisplayed ? visFilter : Lib.identity, getKeyFunc(trace));
406+
if(showMarkers) {
407+
markerFilter = trace.marker.maxdisplayed ? visFilter : Lib.identity;
408+
}
398409

399-
var enter = join.enter().append('path')
400-
.classed('point', true);
410+
if(showText) {
411+
textFilter = trace.marker.maxdisplayed ? visFilter : Lib.identity;
412+
}
401413

402-
enter.call(Drawing.pointStyle, trace)
403-
.call(Drawing.translatePoints, xa, ya, trace);
414+
// marker points
404415

405-
if(hasTransition) {
406-
enter.style('opacity', 0).transition()
407-
.style('opacity', 1);
408-
}
416+
selection = s.selectAll('path.point');
409417

410-
join.each(function(d) {
411-
var sel = transition(d3.select(this));
412-
Drawing.translatePoint(d, sel, xa, ya);
413-
Drawing.singlePointStyle(d, sel, trace);
414-
});
418+
join = selection.data(markerFilter, keyFunc);
415419

416-
if(hasTransition) {
417-
join.exit().transition()
418-
.style('opacity', 0)
419-
.remove();
420-
} else {
421-
join.exit().remove();
422-
}
423-
}
424-
if(showText) {
425-
selection = s.selectAll('g');
420+
var enter = join.enter().append('path')
421+
.classed('point', true);
426422

427-
join = selection
428-
.data(trace.marker.maxdisplayed ? visFilter : Lib.identity);
423+
enter.call(Drawing.pointStyle, trace)
424+
.call(Drawing.translatePoints, xa, ya, trace);
429425

430-
// each text needs to go in its own 'g' in case
431-
// it gets converted to mathjax
432-
join.enter().append('g')
433-
.append('text')
434-
.call(Drawing.translatePoints, xa, ya);
426+
if(hasTransition) {
427+
enter.style('opacity', 0).transition()
428+
.style('opacity', 1);
429+
}
435430

436-
selection
437-
.call(Drawing.translatePoints, xa, ya);
431+
join.each(function(d) {
432+
var sel = transition(d3.select(this));
433+
Drawing.translatePoint(d, sel, xa, ya);
434+
Drawing.singlePointStyle(d, sel, trace);
435+
});
438436

439-
join.exit().remove();
440-
}
437+
if(hasTransition) {
438+
join.exit().transition()
439+
.style('opacity', 0)
440+
.remove();
441+
} else {
442+
join.exit().remove();
441443
}
444+
445+
// text points
446+
447+
selection = s.selectAll('g');
448+
449+
join = selection.data(textFilter, keyFunc);
450+
451+
// each text needs to go in its own 'g' in case
452+
// it gets converted to mathjax
453+
join.enter().append('g')
454+
.append('text');
455+
456+
join.each(function(d) {
457+
var sel = d3.select(this).select('text');
458+
Drawing.translatePoint(d, sel, xa, ya);
459+
});
460+
461+
join.exit().remove();
442462
}
443463

444464
// NB: selectAll is evaluated on instantiation:

0 commit comments

Comments
 (0)