Skip to content

Commit 569b378

Browse files
committed
fix #808 - update scrollBar when changing other legend attributes
1 parent 246b77f commit 569b378

File tree

2 files changed

+107
-37
lines changed

2 files changed

+107
-37
lines changed

src/components/legend/draw.js

+22-24
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,10 @@ module.exports = function draw(gd) {
207207
// legend, background and border, scroll box and scroll bar
208208
Drawing.setTranslate(legend, lx, ly);
209209

210+
// to be safe, remove previous listeners
211+
scrollBar.on('.drag', null);
212+
legend.on('wheel', null);
213+
210214
if(opts._height <= legendHeight || gd._context.staticPlot) {
211215
// if scrollbar should not be shown.
212216
bg.attr({
@@ -226,6 +230,9 @@ module.exports = function draw(gd) {
226230
});
227231

228232
scrollBox.call(Drawing.setClipUrl, clipId);
233+
234+
Drawing.setRect(scrollBar, 0, 0, 0, 0);
235+
delete opts._scrollY;
229236
}
230237
else {
231238
var scrollBarHeight = Math.max(constants.scrollBarMinHeight,
@@ -234,9 +241,10 @@ module.exports = function draw(gd) {
234241
scrollBarHeight -
235242
2 * constants.scrollBarMargin;
236243
var scrollBoxYMax = opts._height - legendHeight;
244+
var scrollRatio = scrollBarYMax / scrollBoxYMax;
237245

238-
var scrollBarY = constants.scrollBarMargin;
239-
var scrollBoxY = scrollBox.attr('data-scroll') || 0;
246+
// scrollBoxY is 0 or a negative number
247+
var scrollBoxY = Math.max(opts._scrollY || 0, -scrollBoxYMax);
240248

241249
// increase the background and clip-path width
242250
// by the scrollbar width and margin
@@ -262,59 +270,49 @@ module.exports = function draw(gd) {
262270

263271
scrollBox.call(Drawing.setClipUrl, clipId);
264272

265-
if(firstRender) scrollHandler(scrollBarY, scrollBoxY, scrollBarHeight);
273+
scrollHandler(scrollBoxY, scrollBarHeight, scrollRatio);
266274

267-
legend.on('wheel', null); // to be safe, remove previous listeners
268275
legend.on('wheel', function() {
269276
scrollBoxY = Lib.constrain(
270-
scrollBox.attr('data-scroll') -
277+
opts._scrollY -
271278
d3.event.deltaY / scrollBarYMax * scrollBoxYMax,
272279
-scrollBoxYMax, 0);
273-
scrollBarY = constants.scrollBarMargin -
274-
scrollBoxY / scrollBoxYMax * scrollBarYMax;
275-
scrollHandler(scrollBarY, scrollBoxY, scrollBarHeight);
280+
scrollHandler(scrollBoxY, scrollBarHeight, scrollRatio);
276281
if(scrollBoxY !== 0 && scrollBoxY !== -scrollBoxYMax) {
277282
d3.event.preventDefault();
278283
}
279284
});
280285

281-
// to be safe, remove previous listeners
282-
scrollBar.on('.drag', null);
283-
scrollBox.on('.drag', null);
284-
285-
var eventY0, scrollBarY0;
286+
var eventY0, scrollBoxY0;
286287

287288
var drag = d3.behavior.drag()
288289
.on('dragstart', function() {
289290
eventY0 = d3.event.sourceEvent.clientY;
290-
scrollBarY0 = scrollBarY;
291+
scrollBoxY0 = scrollBoxY;
291292
})
292293
.on('drag', function() {
293294
var e = d3.event.sourceEvent;
294295
if(e.buttons === 2 || e.ctrlKey) return;
295296

296-
scrollBarY = Lib.constrain(
297-
e.clientY - eventY0 + scrollBarY0,
298-
constants.scrollBarMargin,
299-
constants.scrollBarMargin + scrollBarYMax);
300-
scrollBoxY = - (scrollBarY - constants.scrollBarMargin) /
301-
scrollBarYMax * scrollBoxYMax;
302-
scrollHandler(scrollBarY, scrollBoxY, scrollBarHeight);
297+
scrollBoxY = Lib.constrain(
298+
(eventY0 - e.clientY) / scrollRatio + scrollBoxY0,
299+
-scrollBoxYMax, 0);
300+
scrollHandler(scrollBoxY, scrollBarHeight, scrollRatio);
303301
});
304302

305303
scrollBar.call(drag);
306304
}
307305

308306

309-
function scrollHandler(scrollBarY, scrollBoxY, scrollBarHeight) {
307+
function scrollHandler(scrollBoxY, scrollBarHeight, scrollRatio) {
308+
opts._scrollY = gd._fullLayout.legend._scrollY = scrollBoxY;
310309
scrollBox
311-
.attr('data-scroll', scrollBoxY)
312310
.call(Drawing.setTranslate, 0, scrollBoxY);
313311

314312
scrollBar.call(
315313
Drawing.setRect,
316314
legendWidth,
317-
scrollBarY,
315+
constants.scrollBarMargin - scrollBoxY * scrollRatio,
318316
constants.scrollBarWidth,
319317
scrollBarHeight
320318
);

test/jasmine/tests/legend_scroll_test.js

+85-13
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ var DBLCLICKDELAY = require('@src/constants/interactions').DBLCLICKDELAY;
66
var d3 = require('d3');
77
var createGraph = require('../assets/create_graph_div');
88
var destroyGraph = require('../assets/destroy_graph_div');
9+
var failTest = require('../assets/fail_test');
910
var getBBox = require('../assets/get_bbox');
1011
var mouseEvent = require('../assets/mouse_event');
1112
var mock = require('../../image/mocks/legend_scroll.json');
@@ -48,6 +49,17 @@ describe('The legend', function() {
4849
return d3.select('g.legend').select('.legendtoggle').node();
4950
}
5051

52+
function getScroll(gd) {
53+
return gd._fullLayout.legend._scrollY;
54+
}
55+
56+
function hasScrollBar() {
57+
var scrollBar = getScrollBar();
58+
return scrollBar &&
59+
+scrollBar.getAttribute('width') > 0 &&
60+
+scrollBar.getAttribute('height') > 0;
61+
}
62+
5163
describe('when plotted with many traces', function() {
5264
var gd;
5365

@@ -85,31 +97,30 @@ describe('The legend', function() {
8597
var scrollBarYMax = legendHeight -
8698
scrollBar.getBoundingClientRect().height -
8799
2 * constants.scrollBarMargin;
88-
var initialDataScroll = scrollBox.getAttribute('data-scroll');
100+
var initialDataScroll = getScroll(gd);
89101
var wheelDeltaY = 100;
90-
var finalDataScroll = '' + Lib.constrain(initialDataScroll -
102+
var finalDataScroll = Lib.constrain(initialDataScroll -
91103
wheelDeltaY / scrollBarYMax * scrollBoxYMax,
92104
-scrollBoxYMax, 0);
93105

94106
legend.dispatchEvent(scrollTo(wheelDeltaY));
95107

96-
expect(scrollBox.getAttribute('data-scroll')).toBe(finalDataScroll);
108+
expect(getScroll(gd)).toBe(finalDataScroll);
97109
expect(scrollBox.getAttribute('transform')).toBe(
98110
'translate(0, ' + finalDataScroll + ')');
99111
});
100112

101113
function dragScroll(element, rightClick) {
102-
var scrollBox = getScrollBox();
103114
var scrollBar = getScrollBar();
104115
var scrollBarBB = scrollBar.getBoundingClientRect();
105116
var legendHeight = getLegendHeight(gd);
106117
var scrollBoxYMax = gd._fullLayout.legend._height - legendHeight;
107118
var scrollBarYMax = legendHeight -
108119
scrollBarBB.height -
109120
2 * constants.scrollBarMargin;
110-
var initialDataScroll = scrollBox.getAttribute('data-scroll');
121+
var initialDataScroll = getScroll(gd);
111122
var dy = 50;
112-
var finalDataScroll = '' + Lib.constrain(initialDataScroll -
123+
var finalDataScroll = Lib.constrain(initialDataScroll -
113124
dy / scrollBarYMax * scrollBoxYMax,
114125
-scrollBoxYMax, 0);
115126

@@ -138,7 +149,7 @@ describe('The legend', function() {
138149
var finalDataScroll = dragScroll(getScrollBar());
139150
var scrollBox = getScrollBox();
140151

141-
var dataScroll = scrollBox.getAttribute('data-scroll');
152+
var dataScroll = getScroll(gd);
142153
expect(dataScroll).toBeCloseTo(finalDataScroll, 3);
143154
expect(scrollBox.getAttribute('transform')).toBe(
144155
'translate(0, ' + dataScroll + ')');
@@ -148,7 +159,7 @@ describe('The legend', function() {
148159
var scrollBox = getScrollBox();
149160
var finalDataScroll = dragScroll(scrollBox);
150161

151-
var dataScroll = scrollBox.getAttribute('data-scroll');
162+
var dataScroll = getScroll(gd);
152163
expect(dataScroll).not.toBeCloseTo(finalDataScroll, 3);
153164
expect(scrollBox.getAttribute('transform')).toBe(
154165
'translate(0, ' + dataScroll + ')');
@@ -158,12 +169,73 @@ describe('The legend', function() {
158169
var finalDataScroll = dragScroll(getScrollBar(), true);
159170
var scrollBox = getScrollBox();
160171

161-
var dataScroll = scrollBox.getAttribute('data-scroll');
172+
var dataScroll = getScroll(gd);
162173
expect(dataScroll).not.toBeCloseTo(finalDataScroll, 3);
163174
expect(scrollBox.getAttribute('transform')).toBe(
164175
'translate(0, ' + dataScroll + ')');
165176
});
166177

178+
it('removes scroll bar and handlers when switching to horizontal', function(done) {
179+
expect(hasScrollBar()).toBe(true);
180+
181+
Plotly.relayout(gd, {'legend.orientation': 'h'})
182+
.then(function() {
183+
expect(hasScrollBar()).toBe(false);
184+
expect(getScroll(gd)).toBeUndefined();
185+
186+
getLegend().dispatchEvent(scrollTo(100));
187+
expect(hasScrollBar()).toBe(false);
188+
expect(getScroll(gd)).toBeUndefined();
189+
190+
return Plotly.relayout(gd, {'legend.orientation': 'v'});
191+
})
192+
.then(function() {
193+
expect(hasScrollBar()).toBe(true);
194+
expect(getScroll(gd)).toBe(0);
195+
196+
getLegend().dispatchEvent(scrollTo(100));
197+
expect(hasScrollBar()).toBe(true);
198+
expect(getScroll(gd)).not.toBe(0);
199+
})
200+
.catch(failTest)
201+
.then(done);
202+
});
203+
204+
it('updates scrollBar size/existence on deleteTraces', function(done) {
205+
expect(hasScrollBar()).toBe(true);
206+
var dataScroll = dragScroll(getScrollBar());
207+
var scrollBarHeight = getScrollBar().getBoundingClientRect().height;
208+
var scrollBarHeight1;
209+
210+
Plotly.deleteTraces(gd, [0])
211+
.then(function() {
212+
expect(getScroll(gd)).toBeCloseTo(dataScroll, 3);
213+
scrollBarHeight1 = getScrollBar().getBoundingClientRect().height;
214+
expect(scrollBarHeight1).toBeGreaterThan(scrollBarHeight);
215+
216+
// we haven't quite removed the scrollbar, but we should have clipped the scroll value
217+
return Plotly.deleteTraces(gd, [0, 1, 2, 3, 4, 5, 6, 7]);
218+
})
219+
.then(function() {
220+
expect(getScroll(gd)).toBeGreaterThan(dataScroll + 1);
221+
var scrollBarHeight2 = getScrollBar().getBoundingClientRect().height;
222+
expect(scrollBarHeight2).toBeGreaterThan(scrollBarHeight1);
223+
224+
// now no more scrollBar
225+
return Plotly.deleteTraces(gd, [0, 1]);
226+
})
227+
.then(function() {
228+
expect(hasScrollBar()).toBe(false);
229+
expect(getScroll(gd)).toBeUndefined();
230+
231+
getLegend().dispatchEvent(scrollTo(100));
232+
expect(hasScrollBar()).toBe(false);
233+
expect(getScroll(gd)).toBeUndefined();
234+
})
235+
.catch(failTest)
236+
.then(done);
237+
});
238+
167239
it('should keep the scrollbar position after a toggle event', function(done) {
168240
var legend = getLegend(),
169241
scrollBox = getScrollBox(),
@@ -172,12 +244,12 @@ describe('The legend', function() {
172244

173245
legend.dispatchEvent(scrollTo(wheelDeltaY));
174246

175-
var dataScroll = scrollBox.getAttribute('data-scroll');
247+
var dataScroll = getScroll(gd);
176248
toggle.dispatchEvent(new MouseEvent('mousedown'));
177249
toggle.dispatchEvent(new MouseEvent('mouseup'));
178250
setTimeout(function() {
179251
expect(+toggle.parentNode.style.opacity).toBeLessThan(1);
180-
expect(scrollBox.getAttribute('data-scroll')).toBe(dataScroll);
252+
expect(getScroll(gd)).toBe(dataScroll);
181253
expect(scrollBox.getAttribute('transform')).toBe(
182254
'translate(0, ' + dataScroll + ')');
183255
done();
@@ -210,12 +282,12 @@ describe('The legend', function() {
210282
expect(scrollBar.getAttribute('x')).toBe(scrollBarX);
211283
expect(scrollBar.getAttribute('y')).toBe(scrollBarY);
212284

213-
var dataScroll = scrollBox.getAttribute('data-scroll');
285+
var dataScroll = getScroll(gd);
214286
toggle.dispatchEvent(new MouseEvent('mousedown'));
215287
toggle.dispatchEvent(new MouseEvent('mouseup'));
216288
setTimeout(function() {
217289
expect(+toggle.parentNode.style.opacity).toBeLessThan(1);
218-
expect(scrollBox.getAttribute('data-scroll')).toBe(dataScroll);
290+
expect(getScroll(gd)).toBe(dataScroll);
219291
expect(scrollBox.getAttribute('transform')).toBe(
220292
'translate(0, ' + dataScroll + ')');
221293
expect(scrollBar.getAttribute('width')).toBeGreaterThan(0);

0 commit comments

Comments
 (0)