Chart.js在线性图表上拖动点

时间:2017-05-03 10:52:33

标签: javascript charts chart.js

我有一个使用Chart.js库构建的简单线性图表。

我希望允许用户在图表上拖动点以动态更改它的数据。我绑了chartjs-plugin-draggable,但它只适用于注释。我需要这样的图形:

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

但是在项目中使用新的图形库并不是一个好的解决方案:(

我也试着玩点活动。

更新

使用angular我创建了这样的东西。 enter image description here

也许如果没有办法添加拖放到点数,那么将会有一个黑客将“滑块”放在点位置上的图表上的绝对位置。我也没有找到任何信息:(

3 个答案:

答案 0 :(得分:4)

更新:我之前的回答被删除了,因为它只是一个解决问题的插件的链接,但是这里有解释它的作用:

如何实现所需行为的一般程序是

  1. 在给定图表上拦截mousedown(并检查它是否是拖动手势)
  2. 使用getElementAtEvent函数
  3. 检查mousedown是否在数据点上
  4. 在mousemove上,使用axis.getValueForPixel函数将新的Y-Pixel值转换为数据坐标
  5. 使用chart.update(0)
  6. 同步更新图表数据

    正如本Chart.js issue所指出的那样。

    为了拦截mousedown,mousemove和mouseup事件(拖动手势),需要创建所述事件的事件侦听器。为了简化监听器的创建,在这种情况下可以使用d3 library,如下所示:

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

    在mousedown(此处为'start'事件)上,可以调用函数(getElement),将最接近的图表元素提取到指针位置并获取Y-Scale的ID

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

    在mousemove('drag')上,应该根据指针的当前Y-Pixel值更新图表数据。因此,我们可以创建一个updateData函数来获取图表数据数组中点击的数据点的位置以及像这样的相应数据集

    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)
    }
    

    那就是它!如果您需要在拖动后存储结果值,您还可以指定callback这样的函数

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

    以上是上述代码的working fiddle。该功能也是Chart.js Plugin dragData的核心,在许多情况下可能更容易实现。

答案 1 :(得分:2)

以下是我通过将事件屏幕坐标包装在更多"泛型"中来固定使用上述优秀d3示例的触摸屏或鼠标事件x,y坐标的方法。 x,y对象。

(可能d3类似于处理这两种类型的事件但需要大量阅读才能找到...)

//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;
}

..以下是我将如何在上面的d3示例中使用它来存储初始抓点Y.并且适用于鼠标和触摸。

检查 Fiddle

这里我是如何解决使用d3并希望在移动设备或触摸屏上拖动文档的问题。不知何故,对于d3事件订阅,所有Chart区域事件都已被阻止冒泡DOM。

无法确定d3是否可以配置为在不触摸它的情况下传递画布事件。因此,在抗议活动中,我刚刚删除了d3,因为除了订阅活动之外没有太多参与。

不是Javascript大师这是一些有趣的代码,以旧的方式订阅事件。为了防止图表触摸仅在绘制图表点时拖动屏幕,每个处理程序只需返回 true 并调用 event.preventDefault()以保持事发给你自己。

//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);
        });

    }
};

上面的处理程序与现有的Chart.js对象一样启动:

chartEventHandler.init(chartAcTune);

beginDrag(evt) duringDrag(evt) endDrag(evt)具有与上述d3示例相同的基本功能。只想在使用事件时返回true,而不是为了文档平移和类似事件而使用它。

使用触摸屏在此Fiddle中尝试使用它。除非您触摸选择图表点,否则图表的其余部分对触摸/鼠标事件将是透明的,并允许平移页面。

答案 2 :(得分:0)

万一有人在寻找不需要使用插件的解决方案,那么在原始chart.js上这样做很简单。

这是一个简单的工作示例-只需单击并拖动数据点

// 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>