小例子:自己實現拖拽
介紹一個實現拖拽的小例子。這個例子是在原生 echarts 基礎上做了些小小擴展,帶有一定的交互性。通過這個例子,我們可以瞭解到,如何使用 echarts 提供的 API 實現定製化的富交互的功能。
這個例子主要做到了這樣一件事,用鼠標可以拖拽曲線的點,從而改變曲線的形狀。例子很簡單,但是有了這個基礎我們還可以做更多的事情,比如在圖中可視化得編輯。所以我們從這個簡單的例子開始。
echarts 本身沒有提供封裝好的『拖拽改變圖表』功能,因爲現在認爲這個功能並不足夠有通用性。那麼這個功能就留給開發者用 API 實現,這也有助於開發者按自己的需要個性定製。
(一)實現基本的拖拽功能
在這個例子中,基礎的圖表是一個 折線圖 (series-line)。參見如下配置:
var symbolSize = 20;
// 這個 data 變量在這裏單獨聲明,在後面也會用到。
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,
symbolSize: symbolSize, // 爲了方便拖拽,把 symbolSize 尺寸設大了。
data: data
}
]
});
既然折線中原生的點沒有拖拽功能,我們就爲它加上拖拽功能:用 graphic 組件,在每個點上面,覆蓋一個隱藏的可拖拽的圓點。
myChart.setOption({
// 聲明一個 graphic component,裏面有若干個 type 爲 'circle' 的 graphic elements。
// 這裏使用了 echarts.util.map 這個幫助方法,其行爲和 Array.prototype.map 一樣,但是兼容 es5 以下的環境。
// 用 map 方法遍歷 data 的每項,爲每項生成一個圓點。
graphic: echarts.util.map(data, function (dataItem, dataIndex) {
return {
// 'circle' 表示這個 graphic element 的類型是圓點。
type: 'circle',
shape: {
// 圓點的半徑。
r: symbolSize / 2
},
// 用 transform 的方式對圓點進行定位。position: [x, y] 表示將圓點平移到 [x, y] 位置。
// 這裏使用了 convertToPixel 這個 API 來得到每個圓點的位置,下面介紹。
position: myChart.convertToPixel('grid', dataItem),
// 這個屬性讓圓點不可見(但是不影響他響應鼠標事件)。
invisible: true,
// 這個屬性讓圓點可以被拖拽。
draggable: true,
// 把 z 值設得比較大,表示這個圓點在最上方,能覆蓋住已有的折線圖的圓點。
z: 100,
// 此圓點的拖拽的響應事件,在拖拽過程中會不斷被觸發。下面介紹詳情。
// 這裏使用了 echarts.util.curry 這個幫助方法,意思是生成一個與 onPointDragging
// 功能一樣的新的函數,只不過第一個參數永遠爲此時傳入的 dataIndex 的值。
ondrag: echarts.util.curry(onPointDragging, dataIndex)
};
})
});
上面的代碼中,使用 convertToPixel 這個 API,進行了從 data 到『像素座標』的轉換,從而得到了每個圓點應該在的位置,從而能繪製這些圓點。myChart.convertToPixel('grid', dataItem)
這句話中,第一個參數 'grid'
表示 dataItem
在 grid 這個組件中(即直角座標系)中進行轉換。所謂『像素座標』,就是以 echarts 容器 dom element 的左上角爲零點的以像素爲單位的座標系中的座標。
注意這件事需要在第一次 setOption 後再進行,也就是說,須在座標系(grid)初始化後才能調用 myChart.convertToPixel('grid', dataItem)
。
有了這段代碼後,就有了諸個能拖拽的點。接下來要爲每個點,加上拖拽響應的事件:
// 拖拽某個圓點的過程中會不斷調用此函數。
// 此函數中會根據拖拽後的新位置,改變 data 中的值,並用新的 data 值,重繪折線圖,從而使折線圖同步於被拖拽的隱藏圓點。
function onPointDragging(dataIndex) {
// 這裏的 data 就是本文最初的代碼塊中聲明的 data,在這裏會被更新。
// 這裏的 this 就是被拖拽的圓點。this.position 就是圓點當前的位置。
data[dataIndex] = myChart.convertFromPixel('grid', this.position);
// 用更新後的 data,重繪折線圖。
myChart.setOption({
series: [{
id: 'a',
data: data
}]
});
}
上面的代碼中,使用了 convertFromPixel 這個 API。它是 convertToPixel 的逆向過程。myChart.convertFromPixel('grid', this.position)
表示把當前像素座標轉換成 grid 組件中直角座標系的 dataItem 值。
最後,爲了使 dom 尺寸改變時,圖中的元素能自適應得變化,加上這些代碼:
window.addEventListener('resize', function () {
// 對每個拖拽圓點重新計算位置,並用 setOption 更新。
myChart.setOption({
graphic: echarts.util.map(data, function (item, dataIndex) {
return {
position: myChart.convertToPixel('grid', item)
};
})
});
});
(二)添加 tooltip 組件
到此,拖拽的基本功能就完成了。但是想要更進一步得實時看到拖拽過程中,被拖拽的點的 data 值的變化狀況,我們可以使用 tooltip 組件來實時顯示這個值。但是,tooltip 有其默認的『顯示』『隱藏』觸發規則,在我們拖拽的場景中並不適用,所以我們還要手動定製 tooltip 的『顯示』『隱藏』行爲。
在上述代碼中分別添加如下定義:
myChart.setOption({
...,
tooltip: {
// 表示不使用默認的『顯示』『隱藏』觸發規則。
triggerOn: 'none',
formatter: function (params) {
return 'X: ' + params.data[0].toFixed(2) + '<br>Y: ' + params.data[1].toFixed(2);
}
}
});
myChart.setOption({
graphic: echarts.util.map(data, function (item, dataIndex) {
return {
type: 'circle',
...,
// 在 mouseover 的時候顯示,在 mouseout 的時候隱藏。
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'
});
}
這裏使用了 dispatchAction 來顯示隱藏 tooltip。用到了 showTip、hideTip。
(三)全部代碼
總結一下,全部的代碼如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="dist/echarts.min.js"></script>
</head>
<body>
<div id="main" style="width: 600px;height:400px;"></div>
<script type="text/javascript">
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
}]
});
}
</script>
</body>
</html>
有了這些基礎,就可以定製更多的功能了。可以加 dataZoom 組件,可以製作一個直角座標系上的繪圖板等等。可以發揮想象力。