範例:實作拖曳

這是一個小範例,介紹如何在 Apache EChartsTM 中實作圖形元素的拖曳。從這個範例中,我們將看到如何基於 echarts API 製作具有豐富互動性的應用程式。

這個範例主要實作拖曳曲線上的點,並藉此修改曲線。雖然這是一個簡單的範例,但我們可以基於此做更多事情,例如視覺化編輯圖表。所以讓我們從這個簡單的範例開始。

實作基本拖曳

首先,我們建立一個基本的 折線圖(折線系列)

var symbolSize = 20;
var data = [
  [15, 0],
  [-50, 10],
  [-56.5, 20],
  [-46.5, 30],
  [-22.1, 40]
];

myChart.setOption({
  xAxis: {
    min: -100,
    max: 80,
    type: 'value',
    axisLine: { onZero: false }
  },
  yAxis: {
    min: -30,
    max: 60,
    type: 'value',
    axisLine: { onZero: false }
  },
  series: [
    {
      id: 'a',
      type: 'line',
      smooth: true,
      // Set a big symbolSize for dragging convenience.
      symbolSize: symbolSize,
      data: data
    }
  ]
});

由於折線中的符號不可拖曳,我們使用 圖形元件 將可拖曳的圓形元素分別新增到符號上,使其可拖曳。

myChart.setOption({
  // Declare a graphic component, which contains some graphic elements
  // with the type of 'circle'.
  // Here we have used the method `echarts.util.map`, which has the same
  // behavior as Array.prototype.map, and is compatible with ES5-.
  graphic: echarts.util.map(data, function(dataItem, dataIndex) {
    return {
      // 'circle' means this graphic element is a shape of circle.
      type: 'circle',

      shape: {
        // The radius of the circle.
        r: symbolSize / 2
      },
      // Transform is used to located the circle. position:
      // [x, y] means translate the circle to the position [x, y].
      // The API `convertToPixel` is used to get the position of
      // the circle, which will introduced later.
      position: myChart.convertToPixel('grid', dataItem),

      // Make the circle invisible (but mouse event works as normal).
      invisible: true,
      // Make the circle draggable.
      draggable: true,
      // Give a big z value, which makes the circle cover the symbol
      // in line series.
      z: 100,
      // This is the event handler of dragging, which will be triggered
      // repeatly while dragging. See more details below.
      // A util method `echarts.util.curry` is used here to generate a
      // new function the same as `onPointDragging`, except that the
      // first parameter is fixed to be the `dataIndex` here.
      ondrag: echarts.util.curry(onPointDragging, dataIndex)
    };
  })
});

在上面的程式碼中,API convertToPixel 用於將數據轉換為其「像素座標」,基於此,每個圖形元素都可以在畫布上呈現。「像素座標」一詞表示座標位於畫布像素中,其原點是畫布的左上角。在語句 myChart.convertToPixel('grid', dataItem) 中,第一個參數 'grid' 表示 dataItem 應該在第一個 網格元件(笛卡爾) 中轉換。

注意: convertToPixel 不應在第一次呼叫 setOption 之前呼叫。也就是說,它只能在初始化座標系統(網格/極座標/...)之後使用。

現在點已經可以拖曳了。然後我們將在拖曳時將事件偵聽器綁定到這些點。

// This function will be called repeatly while dragging.
// The mission of this function is to update `series.data` based on
// the new points updated by dragging, and to re-render the line
// series based on the new data, by which the graphic elements of the
// line series can be synchronized with dragging.
function onPointDragging(dataIndex) {
  // Here the `data` is declared in the code block in the beginning
  // of this article. The `this` refers to the dragged circle.
  // `this.position` is the current position of the circle.
  data[dataIndex] = myChart.convertFromPixel('grid', this.position);
  // Re-render the chart based on the updated `data`.
  myChart.setOption({
    series: [
      {
        id: 'a',
        data: data
      }
    ]
  });
}

在上面的程式碼中,使用了 API convertFromPixel,它是 convertToPixel 的反向過程。myChart.convertFromPixel('grid', this.position) 將像素座標轉換為 網格(笛卡爾) 中的數據項。

最後,新增這些程式碼,使圖形元素對畫布大小的變化做出回應。

window.addEventListener('resize', function() {
  // Re-calculate the position of each circle and update chart using `setOption`.
  myChart.setOption({
    graphic: echarts.util.map(data, function(item, dataIndex) {
      return {
        position: myChart.convertToPixel('grid', item)
      };
    })
  });
});

新增提示工具元件

現在基本功能已經由第 1 部分實作完成。如果我們需要在拖曳時即時顯示數據,我們可以使用 提示工具元件 來實現。儘管如此,提示工具元件具有其預設的「顯示/隱藏規則」,在本例中不適用。因此,我們需要為我們的案例自訂「顯示/隱藏規則」。

將這些程式碼片段新增到上面的程式碼區塊中

myChart.setOption({
  // ...,
  tooltip: {
    // Means disable default "show/hide rule".
    triggerOn: 'none',
    formatter: function(params) {
      return (
        'X: ' +
        params.data[0].toFixed(2) +
        '<br>Y: ' +
        params.data[1].toFixed(2)
      );
    }
  }
});
myChart.setOption({
  graphic: data.map(function(item, dataIndex) {
    return {
      type: 'circle',
      // ...,
      // Customize "show/hide rule", show when mouse over, hide when mouse out.
      onmousemove: echarts.util.curry(showTooltip, dataIndex),
      onmouseout: echarts.util.curry(hideTooltip, dataIndex)
    };
  })
});

function showTooltip(dataIndex) {
  myChart.dispatchAction({
    type: 'showTip',
    seriesIndex: 0,
    dataIndex: dataIndex
  });
}

function hideTooltip(dataIndex) {
  myChart.dispatchAction({
    type: 'hideTip'
  });
}

API dispatchAction 用於顯示/隱藏提示工具內容,其中調用了動作 showTiphideTip

完整程式碼

完整程式碼如下所示

import echarts from 'echarts';

var symbolSize = 20;
var data = [
  [15, 0],
  [-50, 10],
  [-56.5, 20],
  [-46.5, 30],
  [-22.1, 40]
];
var myChart = echarts.init(document.getElementById('main'));
myChart.setOption({
  tooltip: {
    triggerOn: 'none',
    formatter: function(params) {
      return (
        'X: ' +
        params.data[0].toFixed(2) +
        '<br />Y: ' +
        params.data[1].toFixed(2)
      );
    }
  },
  xAxis: { min: -100, max: 80, type: 'value', axisLine: { onZero: false } },
  yAxis: { min: -30, max: 60, type: 'value', axisLine: { onZero: false } },
  series: [
    { id: 'a', type: 'line', smooth: true, symbolSize: symbolSize, data: data }
  ]
});
myChart.setOption({
  graphic: echarts.util.map(data, function(item, dataIndex) {
    return {
      type: 'circle',
      position: myChart.convertToPixel('grid', item),
      shape: { r: symbolSize / 2 },
      invisible: true,
      draggable: true,
      ondrag: echarts.util.curry(onPointDragging, dataIndex),
      onmousemove: echarts.util.curry(showTooltip, dataIndex),
      onmouseout: echarts.util.curry(hideTooltip, dataIndex),
      z: 100
    };
  })
});
window.addEventListener('resize', function() {
  myChart.setOption({
    graphic: echarts.util.map(data, function(item, dataIndex) {
      return { position: myChart.convertToPixel('grid', item) };
    })
  });
});
function showTooltip(dataIndex) {
  myChart.dispatchAction({
    type: 'showTip',
    seriesIndex: 0,
    dataIndex: dataIndex
  });
}
function hideTooltip(dataIndex) {
  myChart.dispatchAction({ type: 'hideTip' });
}
function onPointDragging(dataIndex, dx, dy) {
  data[dataIndex] = myChart.convertFromPixel('grid', this.position);
  myChart.setOption({
    series: [
      {
        id: 'a',
        data: data
      }
    ]
  });
}

憑藉上面介紹的知識,可以實作更多功能。例如,可以新增 dataZoom 元件 以與笛卡爾座標系協作,或者我們可以在座標系統上製作繪圖板。發揮你的想像力吧~

貢獻者 在 GitHub 上編輯此頁面

Ovilia Oviliapissang pissang