Skip to content

scrollable dropdown menus #1214

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

Conversation

n-riesco
Copy link
Contributor

@n-riesco n-riesco commented Dec 1, 2016

This PR addresses one of the points in #810 :

  • provides a helper class ScrollBox (that could be reused to provide scroll boxes elsewhere)

  • implements scrollable dropdown menus:

    • scrollbars are added only if needed
    • user can scroll by dragging a scrollbar
    • user can scroll by dragging the scrollbox (menu buttons skip the click triggered by dragend)

TODO

  • Add jasmine tests

EXAMPLE

Here's an example to test the functionality:

// Adapted from https://github.com/plotly/plotly.js/pull/770#issuecomment-234669383

var direction = 'down',
    data = [],
    layout = {
        updatemenus: [{
            direction: direction,
            buttons: [{
                method: 'restyle',
                args: ['line.color', 'red'],
                label: 'red'
            }, {
                method: 'restyle',
                args: ['line.color', 'blue'],
                label: 'blue'
            }, {
                method: 'restyle',
                args: ['line.color', 'green'],
                label: 'green'
            }]
        }, {
            y: 0.7,
            direction: direction,
            buttons: []
        }]
    };

for (var i = 0, n = 20; i < n; i++) {
    data.push({
        y: Array.apply(null, Array(10)).map(() => Math.random()),
        line: {
            shape: 'spline',
            color: 'red'
        },
        visible: i === 0,
        name: 'Data set ' + i,
    });

    var visible = Array.apply(null, Array(n)).map((_, j) => i === j);

    layout.updatemenus[1].buttons.push({
        method: 'restyle',
        args: ['visible', visible],
        label: 'Data set ' + i
    });
}

Plotly.plot('graph', data, layout);

* Don't `Lib.setTranslate` the button container and update the call to
  `Lib.setTranslate` for each button accordingly.

* This change will let us use `Lib.setTranslate` on the button container
  to implement a scroll box.
@etpinard
Copy link
Contributor

etpinard commented Dec 1, 2016

Some suggestions:

  • It would be nice to add a wheelevent scroll handler, similar to how long vertical legends can be scrolled
  • When clicking on the header, I think the set of dropped buttons should be centred on the active button

hbarL = boxL,
hbarT = (boxB + hbarH < fullHeight) ? boxB : fullHeight - hbarH;

var hbar = this.container.selectAll('rect.scrollbar-horizontal').data(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this ever going to be implemented?

We made the decision to not allow horizontal scrolling legends. I doubt that dropdown buttons should scroll horizontally in any scenario.

I'd say we can get away with 🔪 this logic.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we cut horizontal scrollbars, dropdown menus with direction: 'left' | 'right' will be limited to fit within the plot area.

For legends, we could make Scrollbox provide the option to disable the horizontal scrollbar.

@etpinard
Copy link
Contributor

@n-riesco are you available to finish off #1214 (comment) in the short term?

@etpinard etpinard added this to the 1.23.0 milestone Jan 30, 2017
@etpinard
Copy link
Contributor

@n-riesco Looking good. This will make a bunch of users happy 🎉

I'm wondering if you noticed that menus that have a scrollbox are a little jumpy on click:

gifrecord_2017-01-30_170812

I can help you debug this tomorrow if you like.

@n-riesco
Copy link
Contributor Author

@etpinard I hadn't noticed. I guess that it's caused by removing the clip path before the buttons.

I haven't tested it, but the fix may be as simple as removing this line.

* Changed `ScrollBox#setTranslate(translateX, translateY)` to take
  pixels.

* Previously, invoking `ScrollBox#setTranslate(xf, yf)` would require to
  access `ScrollBox#_box` to compute `xf` and `yf`.

* Now, it's possible to drag the buttons in the scrollbox beyond the
  scrollbox.
if(scrollBox._vbar) {
var translateY = 0;
for(i = 0; i < active; i++) {
translateY += menuOpts.heights[i] + constants.gapButton;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks off for direction: 'up' when the updatemenu y isn't the default value:

var direction = 'up',
    data = [],
    layout = {
        updatemenus: [{
            y: 0,
            direction: direction,
            buttons: []
        }]
    };

for (var i = 0, n = 20; i < n; i++) {
    data.push({
        y: Array.apply(null, Array(10)).map(() => Math.random()),
        line: {
            shape: 'spline',
            color: 'red'
        },
        visible: i === 0,
        name: 'Data set ' + i,
    });

    var visible = Array.apply(null, Array(n)).map((_, j) => i === j);

    layout.updatemenus[0].buttons.push({
        method: 'restyle',
        args: ['visible', visible],
        label: 'Data set ' + i
    });
}

Plotly.plot('graph', data, layout);

gives (after clicking on the header)

image

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh! The position of the scrollbox is wrong! This happens in the up direction, when the scrollbox doesn't fit within the chart height. Something similar may happen in the left direction.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one hasn't been addressed yet, correct?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking great now!


vbar.enter().append('rect')
.classed('scrollbar-vertical', true)
.call(Color.fill, ScrollBox.barColor);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be ok to chain

.attr('opacity', '0')
.transition()
.attr('opacity', '1')

here and for hbar to make the scroll bar appear at the same time as the buttons?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Working great now 👍

*
* @method
*/
ScrollBox.prototype.disable = function disable() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like 9e3ac1b made this method obsolete 🔪

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment ⬆️ is now obsolete

@@ -246,6 +246,8 @@ ScrollBox.prototype.enable = function enable() {
.on('.drag', null)
.call(onBarDrag);
}

this.container.on('wheel', this._onBoxWheel.bind(this));
Copy link
Contributor

@etpinard etpinard Jan 31, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently, the wheel handler isn't called when one tries to scroll with the cursor over the gap between the buttons (you can try increasing the gapButton constant to make this problem easier to detect).

I think the easiest solution would be to make Scrollbox add a background <rect> as big as the clipped <rect> in the container and attach the wheel handler on it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Working like a charm now. Thanks!

@@ -253,11 +254,19 @@ function drawButtons(gd, gHeader, gButton, menuOpts) {

exit.transition()
.attr('opacity', '0')
.remove();
.remove()
.each('end', function() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gifrecord_2017-01-31_143701

This makes the scrollbox disappear after the button exit transition is completed (you can chain .delay(1000) to the transition to make the problem easier to detect).

I would prefer making the scrollbox disappear at the same time as the buttons.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good now 👍

var fullLayout = gd._fullLayout,
scrollBoxId = 'updatemenus' + fullLayout._uid + menuOpts._index,
scrollBoxPosition = {
l: menuOpts.lx + menuOpts.borderwidth + x0 + menuOpts.pad.l,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice job taking into account borderwidth 🎉

* Previously, the scrollbox was created every time the dropmenu was
  unfolded.

* Now, the scrollbox is created right after creating the dropdown
  container.

* Moved the logic to fold and unfold the dropdown menu to functions
  foldDropdownMenu and unfoldDropdownMenu.

* Moved all the logic to compute the position and size of the scrollbox
  to function unfoldDropdownMenu.

* Moved logic to disable the scrollbox to function foldDropdownMenu.

* This refactor will help introduce a background <rect> element in the
  dropdown container before any dropdown buttons.

* It will also help introduce a transition to show and hide the
  scrollbars.
* Added a <rect> background to the scrollbox, so that events on the
  gaps between buttons aren't dropped.
* Fixes issues caused by the relayout tests, where `updatemenus[1]`
  ended up without a buttons field.

* A transition has been added to show and hide the scroll bars.
* Ensure scrollbox size is recomputed every time, drawButtons is called.
* Account for the direction of the scrollbox to determine to which side
  the scrollbox will be anchored.
@etpinard
Copy link
Contributor

etpinard commented Feb 3, 2017

Great @n-riesco looks like all the functionality is 👌

Ping me once you fixed the merge conflict and are happy with your test cases.

Awesome work 👍

* Fixed regexp in Lib.getTranslate to accept negative values.
* Conflicts:
    src/components/updatemenus/draw.js
    src/components/updatemenus/scrollbox.js
    src/lib/index.js
    test/jasmine/tests/cartesian_test.js
    test/jasmine/tests/updatemenus_test.js
@n-riesco n-riesco changed the title [WIP] scrollable dropdown menus scrollable dropdown menus Feb 3, 2017
@n-riesco
Copy link
Contributor Author

n-riesco commented Feb 3, 2017

@etpinard Ready for review!

This PR also fixes Drawing.getTranslate (the previous regexp drops - signs).

@@ -800,3 +801,406 @@ describe('update menus interaction with other components:', function() {
.then(done);
});
});


describe('update menus interaction with scrollbox:', function() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💯 / 💯 tests. Thanks!

src/lib/index.js Outdated
@@ -453,7 +453,7 @@ lib.addStyleRule = function(selector, styleString) {

lib.getTranslate = function(element) {

var re = /.*\btranslate\((\d*\.?\d*)[^\d]*(\d*\.?\d*)[^\d].*/,
var re = /.*\btranslate\((-?\d*\.?\d*)[^-\d]*(-?\d*\.?\d*)[^\d].*/,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice catch 🔬

Would you mind adding a test case in drawing_test.js to 🔒 this down?

(No rush on this by the way. If you don't have the time to do this by Monday night, I'll merge your PR as is and open up an issue about this).

@etpinard
Copy link
Contributor

etpinard commented Feb 3, 2017

#1214 (comment) is non-blocking.

This PR will be merged and released as part of 1.23.0 on Feb 6.

@etpinard
Copy link
Contributor

etpinard commented Feb 6, 2017

Merging. Thanks @n-riesco 🎉

@etpinard etpinard merged commit 3fad104 into plotly:master Feb 6, 2017
@ayh9kim
Copy link

ayh9kim commented Feb 7, 2017

Will this work for Ropensci/plotly automatically?

@etpinard etpinard mentioned this pull request Feb 8, 2017
@alexcjohnson alexcjohnson mentioned this pull request Jul 6, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature something new
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants