Skip to content

Table scroll #3327

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Dec 12, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/traces/table/data_preparation_helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ module.exports = function calc(gd, trace) {
var maxLineWidth = Math.max(arrayMax(trace.header.line.width), arrayMax(trace.cells.line.width));

var calcdata = {
key: trace.index,
// include staticPlot in the key so if it changes we delete and redraw
key: trace.uid + gd._context.staticPlot,
translateX: domain.x[0] * gd._fullLayout._size.w,
translateY: gd._fullLayout._size.h * (1 - domain.y[1]),
size: gd._fullLayout._size,
Expand Down
57 changes: 39 additions & 18 deletions src/traces/table/plot.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ var splitData = require('./data_split_helpers');
var Color = require('../../components/color');

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

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

tableControlView.enter()
var cvEnter = tableControlView.enter()
.append('g')
.classed(c.cn.tableControlView, true)
.style('box-sizing', 'content-box')
.on('mousemove', function(d) {tableControlView.filter(function(dd) {return d === dd;}).call(renderScrollbarKit, gd);})
.on('mousewheel', function(d) {
if(d.scrollbarState.wheeling) return;
d.scrollbarState.wheeling = true;
d3.event.stopPropagation();
d3.event.preventDefault();
makeDragRow(gd, tableControlView, null, d.scrollY + d3.event.deltaY)(d);
d.scrollbarState.wheeling = false;
})
.call(renderScrollbarKit, gd, true);
.style('box-sizing', 'content-box');
if(dynamic) {
cvEnter
.on('mousemove', function(d) {
tableControlView
.filter(function(dd) {return d === dd;})
.call(renderScrollbarKit, gd);
})
.on('mousewheel', function(d) {
if(d.scrollbarState.wheeling) return;
d.scrollbarState.wheeling = true;
var newY = d.scrollY + d3.event.deltaY;
var noChange = makeDragRow(gd, tableControlView, null, newY)(d);
if(!noChange) {
d3.event.stopPropagation();
d3.event.preventDefault();
}
d.scrollbarState.wheeling = false;
})
.call(renderScrollbarKit, gd, true);
}

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

yColumn.exit().remove();

yColumn
.attr('transform', function(d) {return 'translate(' + d.x + ' 0)';})
.call(d3.behavior.drag()
yColumn.attr('transform', function(d) {return 'translate(' + d.x + ' 0)';});

if(dynamic) {
yColumn.call(d3.behavior.drag()
.origin(function(d) {
var movedColumn = d3.select(this);
easeColumn(movedColumn, d, -c.uplift);
Expand Down Expand Up @@ -137,6 +149,7 @@ module.exports = function plot(gd, wrappedTraceHolders) {
columnMoved(gd, p, p.columns.map(function(dd) {return dd.xIndex;}));
})
);
}

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

cellsColumnBlock
.call(d3.behavior.drag()
if(dynamic) {
cellsColumnBlock.call(d3.behavior.drag()
.origin(function(d) {
d3.event.stopPropagation();
return d;
Expand All @@ -169,6 +182,7 @@ module.exports = function plot(gd, wrappedTraceHolders) {
// fixme emit plotly notification
})
);
}

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

function makeDragRow(gd, allTableControlView, optionalMultiplier, optionalPosition) {
return function dragRow(eventD) {
// may come from whicever DOM event target: drag, wheel, bar... eventD corresponds to event target
// may come from whichever DOM event target: drag, wheel, bar... eventD corresponds to event target
var d = eventD.calcdata ? eventD.calcdata : eventD;
var tableControlView = allTableControlView.filter(function(dd) {return d.key === dd.key;});
var multiplier = optionalMultiplier || d.scrollbarState.dragMultiplier;

var initialScrollY = d.scrollY;

d.scrollY = optionalPosition === void(0) ? d.scrollY + multiplier * d3.event.dy : optionalPosition;
var cellsColumnBlock = tableControlView.selectAll('.' + c.cn.yColumn).selectAll('.' + c.cn.columnBlock).filter(cellsBlock);
updateBlockYPosition(gd, cellsColumnBlock, tableControlView);

// return false if we've "used" the scroll, ie it did something,
// so the event shouldn't bubble (if appropriate)
return d.scrollY === initialScrollY;
};
}

Expand Down
7 changes: 5 additions & 2 deletions test/jasmine/assets/mouse_event.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,11 @@ module.exports = function(type, x, y, opts) {
var el = (opts && opts.element) || document.elementFromPoint(x, y),
ev;

if(type === 'scroll') {
ev = new window.WheelEvent('wheel', Lib.extendFlat({}, fullOpts, opts));
if(type === 'scroll' || type === 'mousewheel') {
// somehow table needs this to be mouswheel but others need wheel.
// yet they all work the same in the browser?
type = (type === 'scroll') ? 'wheel' : 'mousewheel';
ev = new window.WheelEvent(type, Lib.extendFlat({}, fullOpts, opts));
} else {
ev = new window.MouseEvent(type, fullOpts);
}
Expand Down
102 changes: 100 additions & 2 deletions test/jasmine/tests/table_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ var createGraphDiv = require('../assets/create_graph_div');
var destroyGraphDiv = require('../assets/destroy_graph_div');
var failTest = require('../assets/fail_test');
var supplyAllDefaults = require('../assets/supply_defaults');
var mouseEvent = require('../assets/mouse_event');

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

Expand Down Expand Up @@ -385,8 +386,7 @@ describe('table', function() {
});

describe('more restyling tests with scenegraph queries', function() {
var mockCopy,
gd;
var mockCopy, gd;

beforeEach(function(done) {
mockCopy = Lib.extendDeep({}, mock2);
Expand Down Expand Up @@ -425,4 +425,102 @@ describe('table', function() {
.then(done);
});
});

describe('scroll effects', function() {
var mockCopy, gd, gdWheelEventCount;

beforeEach(function(done) {
mockCopy = Lib.extendDeep({}, mockMulti);
gd = createGraphDiv();
gdWheelEventCount = 0;
Plotly.plot(gd, mockCopy.data, mockCopy.layout)
.then(function() {
gd.addEventListener('mousewheel', function(evt) {
gdWheelEventCount++;
// make sure we don't *really* scroll the page.
// that would be annoying!
evt.stopPropagation();
evt.preventDefault();
});
})
.then(done);
});

function assertBubbledEvents(cnt) {
expect(gdWheelEventCount).toBe(cnt);
gdWheelEventCount = 0;
}

function getCenter(el) {
var bBox = el.getBoundingClientRect();
// actually above center, since these bboxes are bigger than the
// visible table area
return {x: bBox.left + bBox.width / 2, y: bBox.top + bBox.height / 4};
}

function scroll(pos, dy) {
mouseEvent('mousemove', pos.x, pos.y);
mouseEvent('mousewheel', pos.x, pos.y, {deltaX: 0, deltaY: dy});
}

it('bubbles scroll events iff they did not scroll a table', function() {
var allTableControls = document.querySelectorAll('.' + cn.tableControlView);
var smallCenter = getCenter(allTableControls[0]);
var bigCenter = getCenter(allTableControls[1]);

// table with no scroll bars - events always bubble
scroll(smallCenter, -20);
assertBubbledEvents(1);
scroll(smallCenter, 20);
assertBubbledEvents(1);

// table with scrollbars - events bubble if we don't use them
scroll(bigCenter, -20); // up from the top - bubbles
assertBubbledEvents(1);
scroll(bigCenter, 20); // down from the top - scrolled, so no bubble
assertBubbledEvents(0);
scroll(bigCenter, -40); // back to the top, with extra dy discarded
assertBubbledEvents(0);
scroll(bigCenter, -20); // now it bubbles
assertBubbledEvents(1);
scroll(bigCenter, 200000); // all the way to the bottom
assertBubbledEvents(0);
scroll(bigCenter, 20); // now it bubbles from the bottom..
assertBubbledEvents(1);
});

it('does not scroll any tables with staticPlot', function(done) {
var allTableControls = document.querySelectorAll('.' + cn.tableControlView);
var bigCenter = getCenter(allTableControls[1]);

var mock = Lib.extendDeep({}, mockMulti);

// make sure initially with staticPlot: false, scrolling works
// (copied from previous test)
scroll(bigCenter, 20);
assertBubbledEvents(0);
scroll(bigCenter, -40);
assertBubbledEvents(0);

Plotly.react(gd, mock.data, mock.layout, {staticPlot: true})
.then(function() {
// now the same scrolls bubble
scroll(bigCenter, 20);
assertBubbledEvents(1);
scroll(bigCenter, -40);
assertBubbledEvents(1);

return Plotly.react(gd, mock.data, mock.layout, {staticPlot: false});
})
.then(function() {
// scroll works again!
scroll(bigCenter, 20);
assertBubbledEvents(0);
scroll(bigCenter, -40);
assertBubbledEvents(0);
})
.catch(failTest)
.then(done);
});
});
});