Chart.js drag points on linear chart

chartjs-plugin-draggable
chart js annotation
chart js animation examples
dynamic charts javascript
chart js multiple lines
chart js table
chartjs draggable annotation
chart.js time series example

I have a simple linear chart built with Chart.js library.

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

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

But use new graph library in project is not good solution :(

Also i tried to play with dot event's.

UPDATE:

With angular i created something like this.

Maybe if there is no way to add drag&drop to points, there will be a hack to put "sliders" with absolute position on graph on points positions. I didn't find any info too :(

Update: My previous answer got deleted because it only featured a link to a plugin solving the issue, however here comes the explanation to what it does:

The general procedure on how to achieve the desired behaviour is to

  1. Intercept a mousedown (and check if it's a dragging gesture) on a given chart
  2. Check if the mousedown was over a data point using the getElementAtEvent function
  3. On mousemove, translate the new Y-Pixel value into a data coordinate using the axis.getValueForPixel function
  4. Synchronously update the chart data using chart.update(0)

as pointed out in this Chart.js issue.

In order to intercept the mousedown, mousemove and mouseup events (the dragging gesture), event listeners for said events need to be created. In order to simplify the creation of the listeners one may use the d3 library in this case as follows:

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

On mousedown (the 'start' event here), a function (getElement) may be called thatfetches the closest chart element to the pointers location and gets the ID of the Y-Scale

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

On mousemove ('drag'), the chart data is supposed to be updated according to the current Y-Pixel value of the pointer. We can therefore create an updateData function that gets the position of the clicked data point in the charts data array and the according 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 that's it! If you need to store the resulting value after dragging, you may also specify a callback function 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 fiddle of the above code. The functionality is also the core of the Chart.js Plugin dragData, which may be easier to implement in many cases.

chrispahm/chartjs-plugin-dragdata: Draggable data points , Configuration. To make (line, bubble, bar and radar chart) data points draggable, simply add dragData: true to the config section of� You can drag data points to Update the value in chart on the fly. This feature can be useful in building applications that allow users to change values directly to see its effects on other parameters. The Library provides several methods to get and set values for different properties.

Here is how I fixed using both touchscreen or mouse event x,y coordinates for the excellent d3 example above by wrapping event screen coordinates in a more "generic" x,y object.

(Probably d3 has something similar to handle both types of events but 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 here is how I would use it in the above d3 example to store the initial grab point Y. And works for both mouse and touch.

Check the Fiddle


Here how I solved the problem with using d3 and wanting to drag the document on mobile or touch screens. Somehow with the d3 event subscription all Chart area events where already blocked from bubbling up the DOM.

Was not able to figure out if d3 could be configured to pass canvas events on without touching them. So in a protest I just eliminated d3 as it was not much involved other than subscribing events.

Not being a Javascript master this is some fun code that subscribes the events the old way. To prevent chart touches from dragging the screen only when a chart point is grabed each of the handlers just have to return true and the event.preventDefault() is called to keep the event to your self.

//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 is initiated like this with an existing Chart.js object:

chartEventHandler.init(chartAcTune);

The beginDrag(evt), duringDrag(evt) and endDrag(evt) have the same basic function as in the d3 example above. Just returns true when wanting to consume the event and not pasing it on for document panning and similar.


Try it in this Fiddle using a touch screen. Unless you touch close to select a chart point the rest of the chart will be transparent to touch/mouse events and allow panning the page.

Popular Extensions � Chart.js documentation, LinearGauge.js - Adds a linear gauge chart type. chartjs-plugin-draggable - Makes select chart elements draggable with the mouse. chartjs-plugin- stacked100� Resize Column on drag. In this tutorial we describe how to change dataPoint value by dragging and reposition them. Here are the steps: Step1: Create a basic chart with required options. And call chart.render() method to render the chart.

In case anyone is looking for a solution that doesn't require the use of plugins, it's pretty straightforward to do it in vanilla chart.js.

Here's a simple working example - just click and drag a data point

// some data to be plotted
var x_data = [1500,1600,1700,1750,1800,1850,1900,1950,1999,2050];
var y_data_1 = [86,114,106,106,107,111,133,221,783,2478];
var y_data_2 = [2000,700,200,100,100,100,100,50,25,0];

// globals
var activePoint = null;
var canvas = null;

// draw a line chart on the canvas context
window.onload = function () {

    // Draw a line chart with two data sets
    var ctx = document.getElementById("canvas").getContext("2d");
    canvas = document.getElementById("canvas");
    window.myChart = Chart.Line(ctx, {
        data: {
            labels: x_data,
            datasets: [
                {
                    data: y_data_1,
                    label: "Data 1",
                    borderColor: "#3e95cd",
                    fill: false
                },
                {
                    data: y_data_2,
                    label: "Data 2",
                    borderColor: "#cd953e",
                    fill: false
                }
            ]
        },
        options: {
            animation: {
                duration: 0
            },
            tooltips: {
                mode: 'nearest'
            }
        }
    });

    // set pointer event handlers for canvas element
    canvas.onpointerdown = down_handler;
    canvas.onpointerup = up_handler;
    canvas.onpointermove = null;
};

function down_handler(event) {
    // check for data point near event location
    const points = window.myChart.getElementAtEvent(event, {intersect: false});
    if (points.length > 0) {
        // grab nearest point, start dragging
        activePoint = points[0];
        canvas.onpointermove = move_handler;
    };
};

function up_handler(event) {
    // release grabbed point, stop dragging
    activePoint = null;
    canvas.onpointermove = null;
};

function move_handler(event)
{
    // locate grabbed point in chart data
    if (activePoint != null) {
        var data = activePoint._chart.data;
        var datasetIndex = activePoint._datasetIndex;

        // read mouse position
        const helpers = Chart.helpers;
        var position = helpers.getRelativePosition(event, myChart);

        // convert mouse position to chart y axis value 
        var chartArea = window.myChart.chartArea;
        var yAxis = window.myChart.scales["y-axis-0"];
        var yValue = map(position.y, chartArea.bottom, chartArea.top, yAxis.min, yAxis.max);

        // update y value of active data point
        data.datasets[datasetIndex].data[activePoint._index] = yValue;
        window.myChart.update();
    };
};

// map value to other coordinate system
function map(value, start1, stop1, start2, stop2) {
    return start2 + (stop2 - start2) * ((value - start1) / (stop1 - start1))
};
body {
  font-family: Helvetica Neue, Arial, sans-serif;
  text-align: center;
}

.wrapper {
  max-width: 800px;
  margin: 50px auto;
}

h1 {
  font-weight: 200;
  font-size: 3em;
  margin: 0 0 0.1em 0;
}

h2 {
  font-weight: 200;
  font-size: 0.9em;
  margin: 0 0 50px;
  color: #555;
}

a {
  margin-top: 50px;
  display: block;
  color: #3e95cd;
}
<!DOCTYPE html>
<html>

  <!-- HEAD element: load the stylesheet and the chart.js library -->
  <head>
    <title>Draggable Points</title>
    <script src="https://cdn.jsdelivr.net/npm/chart.js@2.9.3/dist/Chart.min.js"></script>
    <link rel="stylesheet" type="text/css" href="style.css">
  </head>

  <!-- BODY element: create a canvas and render a chart on it -->
  <body>

    <!-- canvas element in a container -->
    <div class="wrapper">
      <canvas id="canvas" width="1600" height="900"></canvas>
    </div>

    <!-- call external script to create and render a chart on the canvas -->
    <script src="script.js"></script>
  </body>

</html>

Chart.js API � Chart.js documentation, These are available on all charts created with Chart.js, but for the examples, let's use a line chart we've made. // For example: var myLineChart getElementAtEvent(e); // => returns the first element at the event point. To get an item that was� To make (line, bubble, bar and radar chart) data points draggable, simply add dragData: true to the config section of the chart instance. If you (additionally to the y-axis) would like to drag data along the x-axis, you may also add dragX: true. To round the values dragged to, simply add dragDataRound: 0 to the config section of the chart instance.

JavaScript Charts & Graphs with Draggable Data Points, JavaScript Charts & Graphs with Draggable Data Points. Allows user to edit and update data directly in the Chart. Can be implemented in Line, Bar, Area, etc. Linear Cartesian Axis. The linear scale is use to chart numerical data. It can be placed on either the x or y axis. The scatter chart type automatically configures a line chart to use one of these scales for the x axis. As the name suggests, linear interpolation is used to determine where a value lies on the axis. Tick Configuration Options

Make Data Points on Chart Draggable, Make dataPoints on Chart Draggable, Create Draggable Charts in JavaScript. Chart.defaults.line.spanGaps = true; Data Structure. The data property of a dataset for a line chart can be passed in two formats. number[] data: [20, 10] When the data array is an array of numbers, the x axis is generally a category. The points are placed onto the axis using their position in the array.

Learn to create Drag-able Charts for Web & Mobile, Drag-able Charts are used to visually manipulate the data on a chart in jQuery. Creating your First Chart. Your First Chart � Rendering Different Charts the multi -series column, the multi-series line, and the multi-series area charts. In this chart, however, we have made only selected data points of all datasets drag-able . Most chart properties can be scripted; the dataset properties for each chart type tell you the exact list (e.g. see here for line chart). Here is how you might use scriptable options on a line chart (based on the example in the docs). On this chart negative data points are shown in red, and positive ones in alternating blue/green:

Comments
  • There is an initial small grab offset error in y-direction and affected by browser window scroll position.
  • Solution. If I use: chartInstance.scales[scale].getValueForPixel(e.layerY), the Y-coordinate becomes compensated for chart scroll location.
  • Here is my contribution with a chart where the second "old" data series rejects editing and new values are changed in integer steps 0..100. fiddle here: fiddle
  • Thanks for your contribution @flodis! In the plugin we eventually ran into the "offset" issue, too but solved it slightly different using e.clientY-chartInstance.canvas.getBoundingClientRect().y. Regarding the touch screens, I also don't have a solution yet.
  • Thank You @christoph-pahmeyer . You started it. I have added a touch and mouse event parser separately below with a Fiddle. Wonder how to pass "unused" touch events back to browser for panning the window when touching far from points?
  • And here is a great link developers.google.com/web/tools/chrome-devtools how to debug and emulate touch screens in Google Chrome browser when developer system has mouse only.