Skip to content

Commit 19a3735

Browse files
committed
Merge pull request #584 from n-riesco/issue-578-do-not-reset-scrollbar-position-after-toggle
Issue 578 (keep legend scrollbar position after toggle)
2 parents 4c2e48b + 3ebb324 commit 19a3735

File tree

2 files changed

+92
-38
lines changed

2 files changed

+92
-38
lines changed

src/components/legend/draw.js

+41-38
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,6 @@ module.exports = function draw(gd) {
4343
return;
4444
}
4545

46-
if(typeof gd.firstRender === 'undefined') gd.firstRender = true;
47-
else if(gd.firstRender) gd.firstRender = false;
48-
4946
var legend = fullLayout._infolayer.selectAll('g.legend')
5047
.data([0]);
5148

@@ -122,7 +119,8 @@ module.exports = function draw(gd) {
122119
.call(setupTraceToggle, gd);
123120
});
124121

125-
if(gd.firstRender) {
122+
var firstRender = legend.enter().size() !== 0;
123+
if(firstRender) {
126124
computeLegendDimensions(gd, groups, traces);
127125
expandMargin(gd);
128126
}
@@ -198,60 +196,64 @@ module.exports = function draw(gd) {
198196
// legend, background and border, scroll box and scroll bar
199197
Lib.setTranslate(legend, lx, ly);
200198

201-
bg.attr({
202-
width: legendWidth - opts.borderwidth,
203-
height: legendHeight - opts.borderwidth,
204-
x: opts.borderwidth / 2,
205-
y: opts.borderwidth / 2
206-
});
199+
var scrollBarYMax = legendHeight -
200+
constants.scrollBarHeight -
201+
2 * constants.scrollBarMargin,
202+
scrollBoxYMax = opts.height - legendHeight,
203+
scrollBarY,
204+
scrollBoxY;
207205

208-
var scrollPosition = scrollBox.attr('data-scroll') || 0;
206+
if(opts.height <= legendHeight || gd._context.staticPlot) {
207+
// if scrollbar should not be shown.
208+
bg.attr({
209+
width: legendWidth - opts.borderwidth,
210+
height: legendHeight - opts.borderwidth,
211+
x: opts.borderwidth / 2,
212+
y: opts.borderwidth / 2
213+
});
209214

210-
Lib.setTranslate(scrollBox, 0, scrollPosition);
215+
Lib.setTranslate(scrollBox, 0, 0);
211216

212-
clipPath.select('rect').attr({
213-
width: legendWidth - 2 * opts.borderwidth,
214-
height: legendHeight - 2 * opts.borderwidth,
215-
x: opts.borderwidth - scrollPosition,
216-
y: opts.borderwidth
217-
});
218-
219-
scrollBox.call(Drawing.setClipUrl, clipId);
217+
clipPath.select('rect').attr({
218+
width: legendWidth - 2 * opts.borderwidth,
219+
height: legendHeight - 2 * opts.borderwidth,
220+
x: opts.borderwidth,
221+
y: opts.borderwidth
222+
});
220223

221-
// If scrollbar should be shown.
222-
if(opts.height - legendHeight > 0 && !gd._context.staticPlot) {
224+
scrollBox.call(Drawing.setClipUrl, clipId);
225+
}
226+
else {
227+
scrollBarY = constants.scrollBarMargin,
228+
scrollBoxY = scrollBox.attr('data-scroll') || 0;
223229

224230
// increase the background and clip-path width
225231
// by the scrollbar width and margin
226232
bg.attr({
227233
width: legendWidth -
228234
2 * opts.borderwidth +
229235
constants.scrollBarWidth +
230-
constants.scrollBarMargin
236+
constants.scrollBarMargin,
237+
height: legendHeight - opts.borderwidth,
238+
x: opts.borderwidth / 2,
239+
y: opts.borderwidth / 2
231240
});
232241

233242
clipPath.select('rect').attr({
234243
width: legendWidth -
235244
2 * opts.borderwidth +
236245
constants.scrollBarWidth +
237-
constants.scrollBarMargin
246+
constants.scrollBarMargin,
247+
height: legendHeight - 2 * opts.borderwidth,
248+
x: opts.borderwidth,
249+
y: opts.borderwidth - scrollBoxY
238250
});
239251

240-
if(gd.firstRender) {
241-
// Move scrollbar to starting position
242-
scrollHandler(constants.scrollBarMargin, 0);
243-
}
244-
245-
var scrollBarYMax = legendHeight -
246-
constants.scrollBarHeight -
247-
2 * constants.scrollBarMargin,
248-
scrollBoxYMax = opts.height - legendHeight,
249-
scrollBarY = constants.scrollBarMargin,
250-
scrollBoxY = 0;
252+
scrollBox.call(Drawing.setClipUrl, clipId);
251253

252-
scrollHandler(scrollBarY, scrollBoxY);
254+
if(firstRender) scrollHandler(scrollBarY, scrollBoxY);
253255

254-
legend.on('wheel', null);
256+
legend.on('wheel', null); // to be safe, remove previous listeners
255257
legend.on('wheel', function() {
256258
scrollBoxY = Lib.constrain(
257259
scrollBox.attr('data-scroll') -
@@ -263,8 +265,10 @@ module.exports = function draw(gd) {
263265
d3.event.preventDefault();
264266
});
265267

268+
// to be safe, remove previous listeners
266269
scrollBar.on('.drag', null);
267270
scrollBox.on('.drag', null);
271+
268272
var drag = d3.behavior.drag().on('drag', function() {
269273
scrollBarY = Lib.constrain(
270274
d3.event.y - constants.scrollBarHeight / 2,
@@ -277,7 +281,6 @@ module.exports = function draw(gd) {
277281

278282
scrollBar.call(drag);
279283
scrollBox.call(drag);
280-
281284
}
282285

283286

test/jasmine/tests/legend_scroll_test.js

+51
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,57 @@ describe('The legend', function() {
7979
'translate(0, ' + finalDataScroll + ')');
8080
});
8181

82+
it('should keep the scrollbar position after a toggle event', function() {
83+
var scrollBox = legend.getElementsByClassName('scrollbox')[0],
84+
toggle = legend.getElementsByClassName('legendtoggle')[0],
85+
wheelDeltaY = 100;
86+
87+
legend.dispatchEvent(scrollTo(wheelDeltaY));
88+
89+
var dataScroll = scrollBox.getAttribute('data-scroll');
90+
toggle.dispatchEvent(new MouseEvent('click'));
91+
expect(+toggle.parentNode.style.opacity).toBeLessThan(1);
92+
expect(scrollBox.getAttribute('data-scroll')).toBe(dataScroll);
93+
expect(scrollBox.getAttribute('transform')).toBe(
94+
'translate(0, ' + dataScroll + ')');
95+
});
96+
97+
it('should be restored and functional after relayout', function() {
98+
var wheelDeltaY = 100,
99+
legend = document.getElementsByClassName('legend')[0],
100+
scrollBox,
101+
scrollBar,
102+
scrollBarX,
103+
scrollBarY,
104+
toggle;
105+
106+
legend.dispatchEvent(scrollTo(wheelDeltaY));
107+
scrollBar = legend.getElementsByClassName('scrollbar')[0];
108+
scrollBarX = scrollBar.getAttribute('x'),
109+
scrollBarY = scrollBar.getAttribute('y');
110+
111+
Plotly.relayout(gd, 'showlegend', false);
112+
Plotly.relayout(gd, 'showlegend', true);
113+
114+
legend = document.getElementsByClassName('legend')[0];
115+
scrollBox = legend.getElementsByClassName('scrollbox')[0];
116+
scrollBar = legend.getElementsByClassName('scrollbar')[0];
117+
toggle = legend.getElementsByClassName('legendtoggle')[0];
118+
119+
legend.dispatchEvent(scrollTo(wheelDeltaY));
120+
expect(scrollBar.getAttribute('x')).toBe(scrollBarX);
121+
expect(scrollBar.getAttribute('y')).toBe(scrollBarY);
122+
123+
var dataScroll = scrollBox.getAttribute('data-scroll');
124+
toggle.dispatchEvent(new MouseEvent('click'));
125+
expect(+toggle.parentNode.style.opacity).toBeLessThan(1);
126+
expect(scrollBox.getAttribute('data-scroll')).toBe(dataScroll);
127+
expect(scrollBox.getAttribute('transform')).toBe(
128+
'translate(0, ' + dataScroll + ')');
129+
expect(scrollBar.getAttribute('width')).toBeGreaterThan(0);
130+
expect(scrollBar.getAttribute('height')).toBeGreaterThan(0);
131+
});
132+
82133
it('should constrain scrolling to the contents', function() {
83134
var scrollBox = legend.getElementsByClassName('scrollbox')[0];
84135

0 commit comments

Comments
 (0)