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.
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 :(
source to share
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.
source to share
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.
source to share