小例子:自己实现拖拽
小例子:自己实现拖拽
介绍一个实现拖拽的小例子。这个例子是在原生 echarts 基础上做了些小小扩展,带有一定的交互性。通过这个例子,我们可以了解到,如何使用 echarts 提供的 API 实现定制化的富有交互的功能。 https://echarts.baidu.com/gallery/view.html?c=line-draggable&edit=1&reset=1
这个例子主要做到了这样一件事,用鼠标可以拖拽曲线的点,从而改变曲线的形状。例子很简单,但是有了这个基础我们还可以做更多的事情,比如在图中可视化得编辑。所以我们从这个简单的例子开始。
echarts 本身没有提供封装好的『拖拽改变图表』功能,因为现在认为这个功能并不足够有通用性。那么这个功能就留给开发者用 API 实现,这也有助于开发者按自己的需要个性定制。
(一)实现基本的拖拽功能
在这个例子中,基础的图表是一个 折线图 (series-line)。参见如下配置:
1. var symbolSize = 20;3. // 这个 data 变量在这里单独声明,在后面也会用到。4. var data = [[15, 0], [-50, 10], [-56.5, 20], [-46.5, 30], [-22.1, 40]];6. myChart.setOption({7. xAxis: {8. min: -100,9. max: 80,10. type: 'value',11. axisLine: {onZero: false}12. },13. yAxis: {14. min: -30,15. max: 60,16. type: 'value',17. axisLine: {onZero: false}18. },19. series: [20. {21. id: 'a',22. type: 'line',23. smooth: true,24. symbolSize: symbolSize, // 为了方便拖拽,把 symbolSize 尺寸设大了。25. data: data26. }27. ]28. });
既然折线中原生的点没有拖拽功能,我们就为它加上拖拽功能:用 graphic 组件,在每个点上面,覆盖一个隐藏的可拖拽的圆点。
1. myChart.setOption({2. // 声明一个 graphic component,里面有若干个 type 为 'circle' 的 graphic elements。3. // 这里使用了 echarts.util.map 这个帮助方法,其行为和 Array.prototype.map 一样,但是兼容 es5 以下的环境。4. // 用 map 方法遍历 data 的每项,为每项生成一个圆点。5. graphic: echarts.util.map(data, function (dataItem, dataIndex) {6. return {7. // 'circle' 表示这个 graphic element 的类型是圆点。8. type: 'circle',10. shape: {11. // 圆点的半径。12. r: symbolSize / 213. },14. // 用 transform 的方式对圆点进行定位。position: [x, y] 表示将圆点平移到 [x, y] 位置。15. // 这里使用了 convertToPixel 这个 API 来得到每个圆点的位置,下面介绍。16. position: myChart.convertToPixel('grid', dataItem),18. // 这个属性让圆点不可见(但是不影响他响应鼠标事件)。19. invisible: true,20. // 这个属性让圆点可以被拖拽。21. draggable: true,22. // 把 z 值设得比较大,表示这个圆点在最上方,能覆盖住已有的折线图的圆点。23. z: 100,24. // 此圆点的拖拽的响应事件,在拖拽过程中会不断被触发。下面介绍详情。25. // 这里使用了 echarts.util.curry 这个帮助方法,意思是生成一个与 onPointDragging26. // 功能一样的新的函数,只不过第一个参数永远为此时传入的 dataIndex 的值。27. ondrag: echarts.util.curry(onPointDragging, dataIndex)28. };29. })30. });
上面的代码中,使用 convertToPixel 这个 API,进行了从 data 到『像素坐标』的转换,从而得到了每个圆点应该在的位置,从而能绘制这些圆点。myChart.convertToPixel('grid', dataItem) 这句话中,第一个参数 'grid' 表示 dataItem 在 grid 这个组件中(即直角坐标系)中进行转换。所谓『像素坐标』,就是以 echarts 容器 dom element 的左上角为零点的以像素为单位的坐标系中的坐标。
注意这件事需要在第一次 setOption 后再进行,也就是说,须在坐标系(grid)初始化后才能调用 myChart.convertToPixel('grid', dataItem)。
有了这段代码后,就有了诸个能拖拽的点。接下来要为每个点,加上拖拽响应的事件:
1. // 拖拽某个圆点的过程中会不断调用此函数。2. // 此函数中会根据拖拽后的新位置,改变 data 中的值,并用新的 data 值,重绘折线图,从而使折线图同步于被拖拽的隐藏圆点。3. function onPointDragging(dataIndex) {4. // 这里的 data 就是本文最初的代码块中声明的 data,在这里会被更新。5. // 这里的 this 就是被拖拽的圆点。this.position 就是圆点当前的位置。6. data[dataIndex] = myChart.convertFromPixel('grid', this.position);7. // 用更新后的 data,重绘折线图。8. myChart.setOption({9. series: [{10. id: 'a',11. data: data12. }]13. });14. }
上面的代码中,使用了 convertFromPixel 这个 API。它是 convertToPixel 的逆向过程。myChart.convertFromPixel('grid', this.position) 表示把当前像素坐标转换成 grid 组件中直角坐标系的 dataItem 值。
最后,为了使 dom 尺寸改变时,图中的元素能自适应得变化,加上这些代码:
1. window.addEventListener('resize', function () {2. // 对每个拖拽圆点重新计算位置,并用 setOption 更新。3. myChart.setOption({4. graphic: echarts.util.map(data, function (item, dataIndex) {5. return {6. position: myChart.convertToPixel('grid', item)7. };8. })9. });10. });
(二)添加 tooltip 组件
到此,拖拽的基本功能就完成了。但是想要更进一步得实时看到拖拽过程中,被拖拽的点的 data 值的变化状况,我们可以使用 tooltip 组件来实时显示这个值。但是,tooltip 有其默认的『显示』『隐藏』触发规则,在我们拖拽的场景中并不适用,所以我们还要手动定制 tooltip 的『显示』『隐藏』行为。
在上述代码中分别添加如下定义:
1. myChart.setOption({2. ...,3. tooltip: {4. // 表示不使用默认的『显示』『隐藏』触发规则。5. triggerOn: 'none',6. formatter: function (params) {7. return 'X: ' + params.data[0].toFixed(2) + '<br>Y: ' + params.data[1].toFixed(2);8. }9. }10. });
1. myChart.setOption({2. graphic: echarts.util.map(data, function (item, dataIndex) {3. return {4. type: 'circle',5. ...,6. // 在 mouseover 的时候显示,在 mouseout 的时候隐藏。7. onmousemove: echarts.util.curry(showTooltip, dataIndex),8. onmouseout: echarts.util.curry(hideTooltip, dataIndex),9. };10. })11. });13. function showTooltip(dataIndex) {14. myChart.dispatchAction({15. type: 'showTip',16. seriesIndex: 0,17. dataIndex: dataIndex18. });19. }21. function hideTooltip(dataIndex) {22. myChart.dispatchAction({23. type: 'hideTip'24. });25. }
这里使用了 dispatchAction 来显示隐藏 tooltip。用到了 showTip、hideTip。
(三)全部代码
总结一下,全部的代码如下:
1. <!DOCTYPE html>2. <html>3. <head>4. <meta charset="utf-8">5. <script src="http://echarts.baidu.com/dist/echarts.min.js"></script>6. </head>7. <body>8. <div id="main" style="width: 600px;height:400px;"></div>9. <script type="text/javascript">11. var symbolSize = 20;12. var data = [[15, 0], [-50, 10], [-56.5, 20], [-46.5, 30], [-22.1, 40]];14. var myChart = echarts.init(document.getElementById('main'));16. myChart.setOption({17. tooltip: {18. triggerOn: 'none',19. formatter: function (params) {20. return 'X: ' + params.data[0].toFixed(2) + '<br>Y: ' + params.data[1].toFixed(2);21. }22. },23. xAxis: {24. min: -100,25. max: 80,26. type: 'value',27. axisLine: {onZero: false}28. },29. yAxis: {30. min: -30,31. max: 60,32. type: 'value',33. axisLine: {onZero: false}34. },35. series: [36. {37. id: 'a',38. type: 'line',39. smooth: true,40. symbolSize: symbolSize,41. data: data42. }43. ],44. });46. myChart.setOption({47. graphic: echarts.util.map(data, function (item, dataIndex) {48. return {49. type: 'circle',50. position: myChart.convertToPixel('grid', item),51. shape: {52. r: symbolSize / 253. },54. invisible: true,55. draggable: true,56. ondrag: echarts.util.curry(onPointDragging, dataIndex),57. onmousemove: echarts.util.curry(showTooltip, dataIndex),58. onmouseout: echarts.util.curry(hideTooltip, dataIndex),59. z: 10060. };61. })62. });64. window.addEventListener('resize', function () {65. myChart.setOption({66. graphic: echarts.util.map(data, function (item, dataIndex) {67. return {68. position: myChart.convertToPixel('grid', item)69. };70. })71. });72. });74. function showTooltip(dataIndex) {75. myChart.dispatchAction({76. type: 'showTip',77. seriesIndex: 0,78. dataIndex: dataIndex79. });80. }82. function hideTooltip(dataIndex) {83. myChart.dispatchAction({84. type: 'hideTip'85. });86. }88. function onPointDragging(dataIndex, dx, dy) {89. data[dataIndex] = myChart.convertFromPixel('grid', this.position);90. myChart.setOption({91. series: [{92. id: 'a',93. data: data94. }]95. });96. }98. </script>99. </body>100. </html>
有了这些基础,就可以定制更多的功能了。可以加 dataZoom 组件,可以制作一个直角坐标系上的绘图板等等。可以发挥想象力。
