Skip to content
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

Add Touch Support to RangeSlider #1098

Closed
cristiantx opened this issue Oct 28, 2016 · 10 comments
Closed

Add Touch Support to RangeSlider #1098

cristiantx opened this issue Oct 28, 2016 · 10 comments
Labels
community community contribution feature something new

Comments

@cristiantx
Copy link

Range Slider should support touch events so users are able to interact with that component as expected. Otherwise it feels broken on mobile devices.

This is related with #480, but not the same. I think this one is more important since the expected behaviour of the component is not achieved with this issue.

I've managed to add the events to the method in charge of this (https://github.com/plotly/plotly.js/blob/master/src/components/rangeslider/draw.js#L158) but seems like I need to do two taps for it to work. I suspect that the touchmove doesn't trigger just after the touchstart. Like mousemove and mousedown does.

My code:

function setupDragElement(rangeSlider, gd, axisOpts, opts) {
    var slideBox = rangeSlider.select('rect.' + constants.slideBoxClassName).node(),
        grabAreaMin = rangeSlider.select('rect.' + constants.grabAreaMinClassName).node(),
        grabAreaMax = rangeSlider.select('rect.' + constants.grabAreaMaxClassName).node();

    function mouseDownHandler() {
        var event = d3.event,
            target = event.target,
            startX = event.clientX || event.touches[0].clientX,
            offsetX = startX - rangeSlider.node().getBoundingClientRect().left,
            minVal = opts.d2p(axisOpts.range[0]),
            maxVal = opts.d2p(axisOpts.range[1]);

        var dragCover = dragElement.coverSlip();

        dragCover.addEventListener('mousemove', mouseMove);
        dragCover.addEventListener('touchmove', mouseMove);
        dragCover.addEventListener('mouseup', mouseUp);
        dragCover.addEventListener('touchend', mouseUp);

        function mouseMove(e) {
            var clientX = e.clientX || e.touches[0].clientX;
            var delta = +clientX - startX;
            var pixelMin, pixelMax, cursor;

            switch(target) {
                case slideBox:
                    cursor = 'ew-resize';
                    pixelMin = minVal + delta;
                    pixelMax = maxVal + delta;
                    break;

                case grabAreaMin:
                    cursor = 'col-resize';
                    pixelMin = minVal + delta;
                    pixelMax = maxVal;
                    break;

                case grabAreaMax:
                    cursor = 'col-resize';
                    pixelMin = minVal;
                    pixelMax = maxVal + delta;
                    break;

                default:
                    cursor = 'ew-resize';
                    pixelMin = offsetX;
                    pixelMax = offsetX + delta;
                    break;
            }

            if(pixelMax < pixelMin) {
                var tmp = pixelMax;
                pixelMax = pixelMin;
                pixelMin = tmp;
            }

            opts._pixelMin = pixelMin;
            opts._pixelMax = pixelMax;

            setCursor(d3.select(dragCover), cursor);
            setDataRange(rangeSlider, gd, axisOpts, opts);
        }

        function mouseUp() {
            dragCover.removeEventListener('mousemove', mouseMove);
            dragCover.removeEventListener('mouseup', mouseUp);
            dragCover.removeEventListener('touchmove', mouseMove);
            dragCover.removeEventListener('touchend', mouseUp);
            Lib.removeElement(dragCover);
        }

    }

    rangeSlider.on('mousedown', mouseDownHandler );
    rangeSlider.on('touchstart', mouseDownHandler );
}
@etpinard
Copy link
Contributor

Thanks very much for writing in

I suspect our range sliders will update painfully slowly on mobile, but hey, if you want to add this in. PR away 🚀

@etpinard etpinard added feature something new community community contribution labels Oct 28, 2016
@cristiantx
Copy link
Author

@etpinard On a not too related matter, I noticed that performance issue on mobile. I was wondering if the chart could update on touchend event instead of live. That would make the performance issue much less noticeable (I was taking a look to other libraries and seems like Highcharts for instance, do this).

But I've noticed that rangeslider draw update is pretty much bounded with the actual chart. Can you point me out (if you know) if there is something I can look into to try to make this change in behavior.

What I want, drag/resize range slider and update the chart only on touchend or mouseup, probably I will be making this behavior an option.

@etpinard
Copy link
Contributor

etpinard commented Nov 1, 2016

I was wondering if the chart could update on touchend event instead of live.

That sounds like a good idea.

But I've noticed that rangeslider draw update is pretty much bounded with the actual chart

I'm not sure what you mean here. Could you try to clarify?

@cristiantx
Copy link
Author

@etpinard Sure, on mousemove event, the method setDataRange is called, which is in charge of updating the chart to the new range. Code here

And this method calls this:

Plotly.relayout(gd, 'xaxis.range', [dataMin, dataMax]);

Which seems to relayout everything (slider range and actual chart). I couldn't find a way to isolate this two different draws.

So, the question in other words, is there a way to update live just the slider range? Or an idea/clue on where to start looking.

@etpinard
Copy link
Contributor

etpinard commented Nov 2, 2016

Which seems to relayout everything (slider range and actual chart). I couldn't find a way to isolate this two different draws.

That's correct.

I think the best way to solve this problem would be to make the ontouchmove handler not call setDataRange and instead call setPixelRange which will take care of updating the handle positions. Then setDataRange could be called inside a ontouchend handler.

@cristiantx
Copy link
Author

Thanks @etpinard! I will give it a try

@JasDev42
Copy link

JasDev42 commented Mar 7, 2018

Hey people,

I used the fix provided by @cristiantx but was facing the issue he mentioned.

So fix for touchmove and touchend is to use this instead of dragCover

Here is the updated code:

function setupDragElement(rangeSlider, gd, axisOpts, opts) {
    var slideBox = rangeSlider.select('rect.' + constants.slideBoxClassName).node(),
        grabAreaMin = rangeSlider.select('rect.' + constants.grabAreaMinClassName).node(),
        grabAreaMax = rangeSlider.select('rect.' + constants.grabAreaMaxClassName).node();

    function mouseDownHandler() {
      var event = d3.event,
            target = event.target,
            startX = event.clientX || event.touches[0].clientX,
            offsetX = startX - rangeSlider.node().getBoundingClientRect().left,
            minVal = opts.d2p(axisOpts._rl[0]),
            maxVal = opts.d2p(axisOpts._rl[1]);

        var dragCover = dragElement.coverSlip();

        this.addEventListener('touchmove', mouseMove);
        this.addEventListener('touchend', mouseUp);
        dragCover.addEventListener('mousemove', mouseMove);
        dragCover.addEventListener('mouseup', mouseUp);

        function mouseMove(e) {
            var clientX = e.clientX || e.touches[0].clientX;
            var delta = +clientX - startX;
            var pixelMin, pixelMax, cursor;

            switch(target) {
                case slideBox:
                    cursor = 'ew-resize';
                    pixelMin = minVal + delta;
                    pixelMax = maxVal + delta;
                    break;

                case grabAreaMin:
                    cursor = 'col-resize';
                    pixelMin = minVal + delta;
                    pixelMax = maxVal;
                    break;

                case grabAreaMax:
                    cursor = 'col-resize';
                    pixelMin = minVal;
                    pixelMax = maxVal + delta;
                    break;

                default:
                    cursor = 'ew-resize';
                    pixelMin = offsetX;
                    pixelMax = offsetX + delta;
                    break;
            }

            if(pixelMax < pixelMin) {
                var tmp = pixelMax;
                pixelMax = pixelMin;
                pixelMin = tmp;
            }

            opts._pixelMin = pixelMin;
            opts._pixelMax = pixelMax;

            setCursor(d3.select(dragCover), cursor);
            setDataRange(rangeSlider, gd, axisOpts, opts);
        }

        function mouseUp() {
            dragCover.removeEventListener('mousemove', mouseMove);
            dragCover.removeEventListener('mouseup', mouseUp);
            this.removeEventListener('touchmove', mouseMove);
            this.removeEventListener('touchend', mouseUp);
            Lib.removeElement(dragCover);
        }
    }

    rangeSlider.on('mousedown', mouseDownHandler);
    rangeSlider.on('touchstart', mouseDownHandler);
}

@Berezhnyk
Copy link

Any updates?

@forforeach
Copy link

Any news on this?

priyanomi added a commit to priyanomi/plotly.js that referenced this issue Jul 28, 2020
archmoj added a commit that referenced this issue Jul 31, 2020
@archmoj
Copy link
Contributor

archmoj commented Sep 9, 2020

Closed via #5025.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
community community contribution feature something new
Projects
None yet
Development

No branches or pull requests

6 participants