Skip to content

Commit 713c6fe

Browse files
authored
Merge pull request #3327 from plotly/table-scroll
Table scroll
2 parents 07d73df + 090a9f1 commit 713c6fe

File tree

4 files changed

+146
-23
lines changed

4 files changed

+146
-23
lines changed

src/traces/table/data_preparation_helper.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,8 @@ module.exports = function calc(gd, trace) {
5858
var maxLineWidth = Math.max(arrayMax(trace.header.line.width), arrayMax(trace.cells.line.width));
5959

6060
var calcdata = {
61-
key: trace.index,
61+
// include staticPlot in the key so if it changes we delete and redraw
62+
key: trace.uid + gd._context.staticPlot,
6263
translateX: domain.x[0] * gd._fullLayout._size.w,
6364
translateY: gd._fullLayout._size.h * (1 - domain.y[1]),
6465
size: gd._fullLayout._size,

src/traces/table/plot.js

+39-18
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ var splitData = require('./data_split_helpers');
2020
var Color = require('../../components/color');
2121

2222
module.exports = function plot(gd, wrappedTraceHolders) {
23+
var dynamic = !gd._context.staticPlot;
2324

2425
var table = gd._fullLayout._paper.selectAll('.' + c.cn.table)
2526
.data(wrappedTraceHolders.map(function(wrappedTraceHolder) {
@@ -51,20 +52,30 @@ module.exports = function plot(gd, wrappedTraceHolders) {
5152
var tableControlView = table.selectAll('.' + c.cn.tableControlView)
5253
.data(gup.repeat, gup.keyFun);
5354

54-
tableControlView.enter()
55+
var cvEnter = tableControlView.enter()
5556
.append('g')
5657
.classed(c.cn.tableControlView, true)
57-
.style('box-sizing', 'content-box')
58-
.on('mousemove', function(d) {tableControlView.filter(function(dd) {return d === dd;}).call(renderScrollbarKit, gd);})
59-
.on('mousewheel', function(d) {
60-
if(d.scrollbarState.wheeling) return;
61-
d.scrollbarState.wheeling = true;
62-
d3.event.stopPropagation();
63-
d3.event.preventDefault();
64-
makeDragRow(gd, tableControlView, null, d.scrollY + d3.event.deltaY)(d);
65-
d.scrollbarState.wheeling = false;
66-
})
67-
.call(renderScrollbarKit, gd, true);
58+
.style('box-sizing', 'content-box');
59+
if(dynamic) {
60+
cvEnter
61+
.on('mousemove', function(d) {
62+
tableControlView
63+
.filter(function(dd) {return d === dd;})
64+
.call(renderScrollbarKit, gd);
65+
})
66+
.on('mousewheel', function(d) {
67+
if(d.scrollbarState.wheeling) return;
68+
d.scrollbarState.wheeling = true;
69+
var newY = d.scrollY + d3.event.deltaY;
70+
var noChange = makeDragRow(gd, tableControlView, null, newY)(d);
71+
if(!noChange) {
72+
d3.event.stopPropagation();
73+
d3.event.preventDefault();
74+
}
75+
d.scrollbarState.wheeling = false;
76+
})
77+
.call(renderScrollbarKit, gd, true);
78+
}
6879

6980
tableControlView
7081
.attr('transform', function(d) {return 'translate(' + d.size.l + ' ' + d.size.t + ')';});
@@ -96,9 +107,10 @@ module.exports = function plot(gd, wrappedTraceHolders) {
96107

97108
yColumn.exit().remove();
98109

99-
yColumn
100-
.attr('transform', function(d) {return 'translate(' + d.x + ' 0)';})
101-
.call(d3.behavior.drag()
110+
yColumn.attr('transform', function(d) {return 'translate(' + d.x + ' 0)';});
111+
112+
if(dynamic) {
113+
yColumn.call(d3.behavior.drag()
102114
.origin(function(d) {
103115
var movedColumn = d3.select(this);
104116
easeColumn(movedColumn, d, -c.uplift);
@@ -137,6 +149,7 @@ module.exports = function plot(gd, wrappedTraceHolders) {
137149
columnMoved(gd, p, p.columns.map(function(dd) {return dd.xIndex;}));
138150
})
139151
);
152+
}
140153

141154
yColumn.each(function(d) {
142155
Drawing.setClipUrl(d3.select(this), columnBoundaryClipKey(gd, d), gd);
@@ -158,8 +171,8 @@ module.exports = function plot(gd, wrappedTraceHolders) {
158171
var headerColumnBlock = columnBlock.filter(headerBlock);
159172
var cellsColumnBlock = columnBlock.filter(cellsBlock);
160173

161-
cellsColumnBlock
162-
.call(d3.behavior.drag()
174+
if(dynamic) {
175+
cellsColumnBlock.call(d3.behavior.drag()
163176
.origin(function(d) {
164177
d3.event.stopPropagation();
165178
return d;
@@ -169,6 +182,7 @@ module.exports = function plot(gd, wrappedTraceHolders) {
169182
// fixme emit plotly notification
170183
})
171184
);
185+
}
172186

173187
// initial rendering: header is rendered first, as it may may have async LaTeX (show header first)
174188
// but blocks are _entered_ the way they are due to painter's algo (header on top)
@@ -711,13 +725,20 @@ function updateBlockYPosition(gd, cellsColumnBlock, tableControlView) {
711725

712726
function makeDragRow(gd, allTableControlView, optionalMultiplier, optionalPosition) {
713727
return function dragRow(eventD) {
714-
// may come from whicever DOM event target: drag, wheel, bar... eventD corresponds to event target
728+
// may come from whichever DOM event target: drag, wheel, bar... eventD corresponds to event target
715729
var d = eventD.calcdata ? eventD.calcdata : eventD;
716730
var tableControlView = allTableControlView.filter(function(dd) {return d.key === dd.key;});
717731
var multiplier = optionalMultiplier || d.scrollbarState.dragMultiplier;
732+
733+
var initialScrollY = d.scrollY;
734+
718735
d.scrollY = optionalPosition === void(0) ? d.scrollY + multiplier * d3.event.dy : optionalPosition;
719736
var cellsColumnBlock = tableControlView.selectAll('.' + c.cn.yColumn).selectAll('.' + c.cn.columnBlock).filter(cellsBlock);
720737
updateBlockYPosition(gd, cellsColumnBlock, tableControlView);
738+
739+
// return false if we've "used" the scroll, ie it did something,
740+
// so the event shouldn't bubble (if appropriate)
741+
return d.scrollY === initialScrollY;
721742
};
722743
}
723744

test/jasmine/assets/mouse_event.js

+5-2
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,11 @@ module.exports = function(type, x, y, opts) {
3636
var el = (opts && opts.element) || document.elementFromPoint(x, y),
3737
ev;
3838

39-
if(type === 'scroll') {
40-
ev = new window.WheelEvent('wheel', Lib.extendFlat({}, fullOpts, opts));
39+
if(type === 'scroll' || type === 'mousewheel') {
40+
// somehow table needs this to be mouswheel but others need wheel.
41+
// yet they all work the same in the browser?
42+
type = (type === 'scroll') ? 'wheel' : 'mousewheel';
43+
ev = new window.WheelEvent(type, Lib.extendFlat({}, fullOpts, opts));
4144
} else {
4245
ev = new window.MouseEvent(type, fullOpts);
4346
}

test/jasmine/tests/table_test.js

+100-2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ var createGraphDiv = require('../assets/create_graph_div');
99
var destroyGraphDiv = require('../assets/destroy_graph_div');
1010
var failTest = require('../assets/fail_test');
1111
var supplyAllDefaults = require('../assets/supply_defaults');
12+
var mouseEvent = require('../assets/mouse_event');
1213

1314
var mockMulti = require('@mocks/table_latex_multitrace_scatter.json');
1415

@@ -385,8 +386,7 @@ describe('table', function() {
385386
});
386387

387388
describe('more restyling tests with scenegraph queries', function() {
388-
var mockCopy,
389-
gd;
389+
var mockCopy, gd;
390390

391391
beforeEach(function(done) {
392392
mockCopy = Lib.extendDeep({}, mock2);
@@ -425,4 +425,102 @@ describe('table', function() {
425425
.then(done);
426426
});
427427
});
428+
429+
describe('scroll effects', function() {
430+
var mockCopy, gd, gdWheelEventCount;
431+
432+
beforeEach(function(done) {
433+
mockCopy = Lib.extendDeep({}, mockMulti);
434+
gd = createGraphDiv();
435+
gdWheelEventCount = 0;
436+
Plotly.plot(gd, mockCopy.data, mockCopy.layout)
437+
.then(function() {
438+
gd.addEventListener('mousewheel', function(evt) {
439+
gdWheelEventCount++;
440+
// make sure we don't *really* scroll the page.
441+
// that would be annoying!
442+
evt.stopPropagation();
443+
evt.preventDefault();
444+
});
445+
})
446+
.then(done);
447+
});
448+
449+
function assertBubbledEvents(cnt) {
450+
expect(gdWheelEventCount).toBe(cnt);
451+
gdWheelEventCount = 0;
452+
}
453+
454+
function getCenter(el) {
455+
var bBox = el.getBoundingClientRect();
456+
// actually above center, since these bboxes are bigger than the
457+
// visible table area
458+
return {x: bBox.left + bBox.width / 2, y: bBox.top + bBox.height / 4};
459+
}
460+
461+
function scroll(pos, dy) {
462+
mouseEvent('mousemove', pos.x, pos.y);
463+
mouseEvent('mousewheel', pos.x, pos.y, {deltaX: 0, deltaY: dy});
464+
}
465+
466+
it('bubbles scroll events iff they did not scroll a table', function() {
467+
var allTableControls = document.querySelectorAll('.' + cn.tableControlView);
468+
var smallCenter = getCenter(allTableControls[0]);
469+
var bigCenter = getCenter(allTableControls[1]);
470+
471+
// table with no scroll bars - events always bubble
472+
scroll(smallCenter, -20);
473+
assertBubbledEvents(1);
474+
scroll(smallCenter, 20);
475+
assertBubbledEvents(1);
476+
477+
// table with scrollbars - events bubble if we don't use them
478+
scroll(bigCenter, -20); // up from the top - bubbles
479+
assertBubbledEvents(1);
480+
scroll(bigCenter, 20); // down from the top - scrolled, so no bubble
481+
assertBubbledEvents(0);
482+
scroll(bigCenter, -40); // back to the top, with extra dy discarded
483+
assertBubbledEvents(0);
484+
scroll(bigCenter, -20); // now it bubbles
485+
assertBubbledEvents(1);
486+
scroll(bigCenter, 200000); // all the way to the bottom
487+
assertBubbledEvents(0);
488+
scroll(bigCenter, 20); // now it bubbles from the bottom..
489+
assertBubbledEvents(1);
490+
});
491+
492+
it('does not scroll any tables with staticPlot', function(done) {
493+
var allTableControls = document.querySelectorAll('.' + cn.tableControlView);
494+
var bigCenter = getCenter(allTableControls[1]);
495+
496+
var mock = Lib.extendDeep({}, mockMulti);
497+
498+
// make sure initially with staticPlot: false, scrolling works
499+
// (copied from previous test)
500+
scroll(bigCenter, 20);
501+
assertBubbledEvents(0);
502+
scroll(bigCenter, -40);
503+
assertBubbledEvents(0);
504+
505+
Plotly.react(gd, mock.data, mock.layout, {staticPlot: true})
506+
.then(function() {
507+
// now the same scrolls bubble
508+
scroll(bigCenter, 20);
509+
assertBubbledEvents(1);
510+
scroll(bigCenter, -40);
511+
assertBubbledEvents(1);
512+
513+
return Plotly.react(gd, mock.data, mock.layout, {staticPlot: false});
514+
})
515+
.then(function() {
516+
// scroll works again!
517+
scroll(bigCenter, 20);
518+
assertBubbledEvents(0);
519+
scroll(bigCenter, -40);
520+
assertBubbledEvents(0);
521+
})
522+
.catch(failTest)
523+
.then(done);
524+
});
525+
});
428526
});

0 commit comments

Comments
 (0)