Skip to content

Commit 6d88255

Browse files
committed
make bar, box & violin plot method use d3 enter/exit/update pattern
- so that we no longer have the clear all their <g.trace> on every Cartesian.plot call !!! - Note that other svg traces (e.g. heatmap & contours) use trace.uid to append/update/clear their SVG layers weren't the focus of this commit.
1 parent 7b3154d commit 6d88255

File tree

3 files changed

+324
-291
lines changed

3 files changed

+324
-291
lines changed

src/traces/bar/plot.js

+119-116
Original file line numberDiff line numberDiff line change
@@ -37,136 +37,139 @@ module.exports = function plot(gd, plotinfo, cdbar) {
3737

3838
var bartraces = plotinfo.plot.select('.barlayer')
3939
.selectAll('g.trace.bars')
40-
.data(cdbar);
40+
.data(cdbar, function(d) { return d[0].trace.uid; });
4141

4242
bartraces.enter().append('g')
43-
.attr('class', 'trace bars');
43+
.attr('class', 'trace bars')
44+
.append('g')
45+
.attr('class', 'points');
4446

45-
if(!plotinfo.isRangePlot) {
46-
bartraces.each(function(d) {
47-
d[0].node3 = d3.select(this);
48-
});
49-
}
47+
bartraces.exit().remove();
5048

51-
bartraces.append('g')
52-
.attr('class', 'points')
53-
.each(function(d) {
54-
var sel = d3.select(this);
55-
var t = d[0].t;
56-
var trace = d[0].trace;
57-
var poffset = t.poffset;
58-
var poffsetIsArray = Array.isArray(poffset);
59-
60-
sel.selectAll('g.point')
61-
.data(Lib.identity)
62-
.enter().append('g').classed('point', true)
63-
.each(function(di, i) {
64-
// now display the bar
65-
// clipped xf/yf (2nd arg true): non-positive
66-
// log values go off-screen by plotwidth
67-
// so you see them continue if you drag the plot
68-
var p0 = di.p + ((poffsetIsArray) ? poffset[i] : poffset),
69-
p1 = p0 + di.w,
70-
s0 = di.b,
71-
s1 = s0 + di.s;
72-
73-
var x0, x1, y0, y1;
74-
if(trace.orientation === 'h') {
75-
y0 = ya.c2p(p0, true);
76-
y1 = ya.c2p(p1, true);
77-
x0 = xa.c2p(s0, true);
78-
x1 = xa.c2p(s1, true);
79-
80-
// for selections
81-
di.ct = [x1, (y0 + y1) / 2];
82-
}
83-
else {
84-
x0 = xa.c2p(p0, true);
85-
x1 = xa.c2p(p1, true);
86-
y0 = ya.c2p(s0, true);
87-
y1 = ya.c2p(s1, true);
88-
89-
// for selections
90-
di.ct = [(x0 + x1) / 2, y1];
91-
}
92-
93-
if(!isNumeric(x0) || !isNumeric(x1) ||
94-
!isNumeric(y0) || !isNumeric(y1) ||
95-
x0 === x1 || y0 === y1) {
96-
d3.select(this).remove();
97-
return;
98-
}
99-
100-
var lw = (di.mlw + 1 || trace.marker.line.width + 1 ||
101-
(di.trace ? di.trace.marker.line.width : 0) + 1) - 1,
102-
offset = d3.round((lw / 2) % 1, 2);
103-
104-
function roundWithLine(v) {
105-
// if there are explicit gaps, don't round,
106-
// it can make the gaps look crappy
107-
return (fullLayout.bargap === 0 && fullLayout.bargroupgap === 0) ?
108-
d3.round(Math.round(v) - offset, 2) : v;
109-
}
110-
111-
function expandToVisible(v, vc) {
112-
// if it's not in danger of disappearing entirely,
113-
// round more precisely
114-
return Math.abs(v - vc) >= 2 ? roundWithLine(v) :
115-
// but if it's very thin, expand it so it's
116-
// necessarily visible, even if it might overlap
117-
// its neighbor
118-
(v > vc ? Math.ceil(v) : Math.floor(v));
119-
}
120-
121-
if(!gd._context.staticPlot) {
122-
// if bars are not fully opaque or they have a line
123-
// around them, round to integer pixels, mainly for
124-
// safari so we prevent overlaps from its expansive
125-
// pixelation. if the bars ARE fully opaque and have
126-
// no line, expand to a full pixel to make sure we
127-
// can see them
128-
var op = Color.opacity(di.mc || trace.marker.color),
129-
fixpx = (op < 1 || lw > 0.01) ?
130-
roundWithLine : expandToVisible;
131-
x0 = fixpx(x0, x1);
132-
x1 = fixpx(x1, x0);
133-
y0 = fixpx(y0, y1);
134-
y1 = fixpx(y1, y0);
135-
}
136-
137-
// append bar path and text
138-
var bar = d3.select(this);
139-
140-
bar.append('path')
141-
.style('vector-effect', 'non-scaling-stroke')
142-
.attr('d',
143-
'M' + x0 + ',' + y0 + 'V' + y1 + 'H' + x1 + 'V' + y0 + 'Z')
144-
.call(Drawing.setClipUrl, plotinfo.layerClipId);
145-
146-
appendBarText(gd, bar, d, i, x0, x1, y0, y1);
147-
148-
if(plotinfo.layerClipId) {
149-
Drawing.hideOutsideRangePoint(d[i], bar.select('text'), xa, ya, trace.xcalendar, trace.ycalendar);
150-
}
151-
});
152-
});
153-
154-
// error bars are on the top
155-
Registry.getComponentMethod('errorbars', 'plot')(bartraces, plotinfo);
49+
bartraces.order();
15650

157-
// lastly, clip points groups of `cliponaxis !== false` traces
158-
// on `plotinfo._hasClipOnAxisFalse === true` subplots
15951
bartraces.each(function(d) {
52+
var cd0 = d[0];
53+
var t = cd0.t;
54+
var trace = cd0.trace;
55+
var sel = d3.select(this);
56+
57+
if(!plotinfo.isRangePlot) cd0.node3 = sel;
58+
59+
var poffset = t.poffset;
60+
var poffsetIsArray = Array.isArray(poffset);
61+
62+
var bars = sel.select('g.points').selectAll('g.point').data(Lib.identity);
63+
64+
bars.enter().append('g')
65+
.classed('point', true);
66+
67+
bars.exit().remove();
68+
69+
bars.each(function(di, i) {
70+
var bar = d3.select(this);
71+
72+
// now display the bar
73+
// clipped xf/yf (2nd arg true): non-positive
74+
// log values go off-screen by plotwidth
75+
// so you see them continue if you drag the plot
76+
var p0 = di.p + ((poffsetIsArray) ? poffset[i] : poffset),
77+
p1 = p0 + di.w,
78+
s0 = di.b,
79+
s1 = s0 + di.s;
80+
81+
var x0, x1, y0, y1;
82+
if(trace.orientation === 'h') {
83+
y0 = ya.c2p(p0, true);
84+
y1 = ya.c2p(p1, true);
85+
x0 = xa.c2p(s0, true);
86+
x1 = xa.c2p(s1, true);
87+
88+
// for selections
89+
di.ct = [x1, (y0 + y1) / 2];
90+
}
91+
else {
92+
x0 = xa.c2p(p0, true);
93+
x1 = xa.c2p(p1, true);
94+
y0 = ya.c2p(s0, true);
95+
y1 = ya.c2p(s1, true);
96+
97+
// for selections
98+
di.ct = [(x0 + x1) / 2, y1];
99+
}
100+
101+
if(!isNumeric(x0) || !isNumeric(x1) ||
102+
!isNumeric(y0) || !isNumeric(y1) ||
103+
x0 === x1 || y0 === y1) {
104+
bar.remove();
105+
return;
106+
}
107+
108+
var lw = (di.mlw + 1 || trace.marker.line.width + 1 ||
109+
(di.trace ? di.trace.marker.line.width : 0) + 1) - 1,
110+
offset = d3.round((lw / 2) % 1, 2);
111+
112+
function roundWithLine(v) {
113+
// if there are explicit gaps, don't round,
114+
// it can make the gaps look crappy
115+
return (fullLayout.bargap === 0 && fullLayout.bargroupgap === 0) ?
116+
d3.round(Math.round(v) - offset, 2) : v;
117+
}
118+
119+
function expandToVisible(v, vc) {
120+
// if it's not in danger of disappearing entirely,
121+
// round more precisely
122+
return Math.abs(v - vc) >= 2 ? roundWithLine(v) :
123+
// but if it's very thin, expand it so it's
124+
// necessarily visible, even if it might overlap
125+
// its neighbor
126+
(v > vc ? Math.ceil(v) : Math.floor(v));
127+
}
128+
129+
if(!gd._context.staticPlot) {
130+
// if bars are not fully opaque or they have a line
131+
// around them, round to integer pixels, mainly for
132+
// safari so we prevent overlaps from its expansive
133+
// pixelation. if the bars ARE fully opaque and have
134+
// no line, expand to a full pixel to make sure we
135+
// can see them
136+
var op = Color.opacity(di.mc || trace.marker.color),
137+
fixpx = (op < 1 || lw > 0.01) ?
138+
roundWithLine : expandToVisible;
139+
x0 = fixpx(x0, x1);
140+
x1 = fixpx(x1, x0);
141+
y0 = fixpx(y0, y1);
142+
y1 = fixpx(y1, y0);
143+
}
144+
145+
Lib.ensureSingle(bar, 'path')
146+
.style('vector-effect', 'non-scaling-stroke')
147+
.attr('d',
148+
'M' + x0 + ',' + y0 + 'V' + y1 + 'H' + x1 + 'V' + y0 + 'Z')
149+
.call(Drawing.setClipUrl, plotinfo.layerClipId);
150+
151+
appendBarText(gd, bar, d, i, x0, x1, y0, y1);
152+
153+
if(plotinfo.layerClipId) {
154+
Drawing.hideOutsideRangePoint(d[i], bar.select('text'), xa, ya, trace.xcalendar, trace.ycalendar);
155+
}
156+
});
157+
158+
// lastly, clip points groups of `cliponaxis !== false` traces
159+
// on `plotinfo._hasClipOnAxisFalse === true` subplots
160160
var hasClipOnAxisFalse = d[0].trace.cliponaxis === false;
161-
Drawing.setClipUrl(d3.select(this), hasClipOnAxisFalse ? null : plotinfo.layerClipId);
161+
Drawing.setClipUrl(sel, hasClipOnAxisFalse ? null : plotinfo.layerClipId);
162162
});
163+
164+
// error bars are on the top
165+
Registry.getComponentMethod('errorbars', 'plot')(bartraces, plotinfo);
163166
};
164167

165168
function appendBarText(gd, bar, calcTrace, i, x0, x1, y0, y1) {
166169
var textPosition;
167170

168171
function appendTextNode(bar, text, textFont) {
169-
var textSelection = bar.append('text')
172+
var textSelection = Lib.ensureSingle(bar, 'text')
170173
.text(text)
171174
.attr({
172175
'class': 'bartext bartext-' + textPosition,

0 commit comments

Comments
 (0)