Chart.js drag points on line chart

I have a simple line chart plotted with Chart.js .

And I want to allow the user to drag points on the chart to change the data dynamically. I have attached chartjs-plugin-draggable , but it only works for me with annotations. I need a graph exactly like this:

https://www.rgraph.net/canvas/docs/adjusting-line.html

But using the new graph library in the project is not very good :(

Also I tried to play with the point event.

UPDATE

With angular I have created something like this. enter image description here

Maybe, if it is not possible to add drag & drop to points, it will break the "sliders" with an absolute position on the chart at the points' positions. I didn't find any information either :(

+3


source to share


2 answers


Update: My previous answer got deleted because it only showed the link to the plugin, solving the problem, however, here's what it does:

The general procedure on how to achieve the desired behavior is

  • Intercept the medallion (and check if it is a drag and drop gesture) on a given diagram
  • Check if mousedown was over data point with function getElementAtEvent

  • On mousemove, cast the new Y-Pixel value to the data coordinate using the function axis.getValueForPixel

  • Refresh chart data synchronously with chart.update(0)

as pointed out in this Chart.js issue .

To intercept the mousedown, mousemove, and mouseup (drag-and-drop) events, you must create event listeners for the specified events. To make it easier to create listeners, in this case the d3 library can be used :

d3.select(chartInstance.chart.canvas).call(
  d3.drag().container(chartInstance.chart.canvas)
    .on('start', getElement)
    .on('drag', updateData)
    .on('end', callback)
);

      

In mousedown (event 'start'

here) function () can be called getElement

, which types the closest chart element to the location of the pointers and gets the Y-scale ID



function getElement () {
    var e = d3.event.sourceEvent
    element = chartInstance.getElementAtEvent(e)[0]
    scale = element['_yScale'].id
}

      

In mousemove ( 'drag'

), the chart data should be updated according to the current Y-Pixel value of the pointer. So we can create a function updateData

that gets the position of the clicked data point in the chart dataset and the corresponding dataset like this

function updateData () {
  var e = d3.event.sourceEvent
  var datasetIndex = element['_datasetIndex']
  var index = element['_index']
  var value = chartInstance.scales[scale].getValueForPixel(e.clientY)
  chartInstance.data.datasets[datasetIndex].data[index] = value
  chartInstance.update(0)
}

      

And this! If you need to keep the resulting value after dragging, you can also provide a function callback

like this

function callback () {
  var datasetIndex = element['_datasetIndex']
  var index = element['_index']
  var value = chartInstance.data.datasets[datasetIndex].data[index]
  // e.g. store value in database
}

      

Here is a working script for the above code. The functionality is also at the core of the Chart.js Plugin dragData , which may be easier to implement in many cases.

+2


source


This is how I fixed the use of the x, y coordinate for the touchscreen or mouse for the great d3 example above, by wrapping the event screen coordinates in a more "generic" x, y object.

(Probably d3 has something similar to handling both types of events, but a lot of reading to find out ..)

//Get an class of {points: [{x, y},], type: event.type} clicked or touched
function getEventPoints(event)
{
    var retval = {point: [], type: event.type};

    //Get x,y of mouse point or touch event
    if (event.type.startsWith("touch")) {
        //Return x,y of one or more touches
        //Note 'changedTouches' has missing iterators and can not be iterated with forEach 
        for (var i = 0; i < event.changedTouches.length; i++) {
            var touch = event.changedTouches.item(i);
            retval.point.push({ x: touch.clientX, y: touch.clientY })
        }
    }
    else if (event.type.startsWith("mouse")) {
        //Return x,y of mouse event
        retval.point.push({ x: event.layerX, y: event.layerY })
    }

    return retval;
}

      

.. and this is how I would use it in the above example d3 to store the starting grip point Y. And works for both mouse and touch.

Check Fiddle


This is how I solved the problem using d3 and want to drag and drop document to mobile or touch screens. Somehow with subscribing d3 all events of the chart area where already blocked from bubble up the DOM.

Couldn't figure out if d3 could be configured to send canvas events without touching them. Therefore, in protest, I simply excluded d3, since it was not used not only for subscribing to events.



Not being a Javascript master, this is some fun code that signs events the old way. To prevent the chart from dragging while dragging the screen only when the chart point is found, each of the handlers should simply return true, and event.preventDefault () is called to store the event in itself.

//ChartJs event handler attaching events to chart canvas
const chartEventHandler = {

    //Call init with a ChartJs Chart instance to apply mouse and touch events to its canvas.
    init(chartInstance) {

        //Event handler for event types subscribed
        var evtHandler =
        function myeventHandler(evt) {
            var cancel = false; 
            switch (evt.type) {
            case "mousedown":
            case "touchstart":
                cancel = beginDrag(evt);
                break;
            case "mousemove":
            case "touchmove":
                cancel = duringDrag(evt);
                break;
            case "mouseup":
            case "touchend":
                cancel = endDrag(evt);
                break;
            default:
                //handleDefault(evt);
            }

            if (cancel) {
                //Prevent the event e from bubbling up the DOM
                if (evt.cancelable) {
                    if (evt.preventDefault) {
                        evt.preventDefault();
                    }
                    if (evt.cancelBubble != null) {
                        evt.cancelBubble = true;
                    }
                }                
            }
        };

        //Events to subscribe to
        var events = ['mousedown', 'touchstart', 'mousemove', 'touchmove', 'mouseup', 'touchend'];

        //Subscribe events
        events.forEach(function (evtName) {
            chartInstance.canvas.addEventListener(evtName, evtHandler);
        });

    }
};

      

The handler above runs like this with an existing Chart.js object:

chartEventHandler.init(chartAcTune);

      

The initial values โ€‹โ€‹(evt), inDrag (evt) and endDrag (evt) have the same basic function as in the d3 example above. Just returns true when it wants to consume the event, not pasing it for document panning and the like.


Try in this Fiddle with a touch screen. If you don't touch close to select a point on the graph, the rest of the graph will be transparent to touch / mouse events and allow the page to pan.

0


source







All Articles