背景

在实现拖拽曲线的功能基础上,官方例子存在部分问题:连续拖拽掉点视图更新导致拖拽不平滑超出坐标轴报错等问题。因此,在进行多次调试后,通过以下方式来解决对应的问题。

问题一:连续拖拽掉点

定义变量preIndex记录上次改变的点,preSeconds记录上次改变的时间戳。若两次改变的时间戳在0.1s内,则认为中间的值均为改变值

  1. const index = myChart.convertFromPixel('grid', [dx.offsetX, dx.offsetY])[0];
  2. if (preIndex) {
  3. // 快速划过时,存在点丢失的情况
  4. if (Math.abs(preIndex - index) > 1 && moment().millisecond() - preSeconds < 100) {
  5. const max = preIndex - index > 0 ? preIndex : index;
  6. const min = preIndex - index < 0 ? preIndex : index;
  7. for (let i = min; i < max + 1; i++) {
  8. fun(i);
  9. }
  10. preIndex = index;
  11. preSeconds = moment().millisecond();
  12. return;
  13. }
  14. }
  15. preSeconds = moment().millisecond();
  16. preIndex = index;

问题二:视图更新导致拖拽不平滑

视图更新(setOption)会引起页面的回流,导致页面性能变低,因此,通过防抖的方法,控制setOption的频率来实现页面不卡顿,而实现平滑拖拽。

  1. const fun = (index) => {
  2. // 通用方法
  3. if (dataChart[index] && myChart.convertFromPixel('grid', [dx.offsetX, dx.offsetY])) {
  4. const value = myChart.convertFromPixel('grid', [dx.offsetX, dx.offsetY])[1].toFixed(2);
  5. if (max) {
  6. dataChart[index][1] = max < value ? max : value;
  7. } else {
  8. dataChart[index][1] = value;
  9. }
  10. }
  11. clearTimeout(clock);
  12. clock = setTimeout(() => {
  13. myChart.setOption({
  14. series: [
  15. {
  16. id,
  17. type: 'line',
  18. data: dataChart,
  19. },
  20. ],
  21. });
  22. }, 0);
  23. };

总结

主要由于Echarts提供的例子在点数少的时候,卡顿不会明显,但由于系统大部分采用一天96点展示的方法,导致一个折线图会展示大部分节点,对浏览器GUI线程造成较大压力,所以通过防抖的方法,可以很好的解决以上办法。

封装方法

  1. /*
  2. * @File: src/
  3. * @Author: hunter
  4. * @Date: 2020-09-10 09:31:10
  5. * @LastEditors: hunter
  6. * @LastEditTime: 2020-09-16 20:59:25
  7. */
  8. import { getMetaPointList } from '@/utils/getMetaPointList';
  9. import echarts from 'echarts';
  10. import moment, { Moment } from 'moment';
  11. const tvMeta = {
  12. startDateTime: '2020-01-01 00:00:00',
  13. delta: 15,
  14. deltaUnit: 1,
  15. size: 96,
  16. };
  17. const dateArr = getMetaPointList(tvMeta)?.map((p: Moment) => p.format('HH:mm'));
  18. let preIndex;
  19. let preSeconds = moment().millisecond();
  20. let clock;
  21. // 拖拽图表
  22. // 拖拽
  23. const updatePosition = ({ myChart, dataChart }: any) => {
  24. myChart &&
  25. myChart.setOption({
  26. graphic: echarts.util.map(dataChart, function (item) {
  27. return {
  28. position: myChart.convertToPixel('grid', item),
  29. };
  30. }),
  31. });
  32. };
  33. const onPointDragging = ({ myChart, dataChart, dx, id, max }) => {
  34. const fun = (index) => {
  35. // 通用方法
  36. if (dataChart[index] && myChart.convertFromPixel('grid', [dx.offsetX, dx.offsetY])) {
  37. const value = myChart.convertFromPixel('grid', [dx.offsetX, dx.offsetY])[1].toFixed(2);
  38. if (max) {
  39. dataChart[index][1] = max < value ? max : value;
  40. } else {
  41. dataChart[index][1] = value;
  42. }
  43. }
  44. clearTimeout(clock);
  45. clock = setTimeout(() => {
  46. myChart.setOption({
  47. series: [
  48. {
  49. id,
  50. type: 'line',
  51. data: dataChart,
  52. },
  53. ],
  54. });
  55. }, 0);
  56. };
  57. const index = myChart.convertFromPixel('grid', [dx.offsetX, dx.offsetY])[0];
  58. if (preIndex) {
  59. // 快速划过时,存在点丢失的情况
  60. if (Math.abs(preIndex - index) > 1 && moment().millisecond() - preSeconds < 100) {
  61. const max = preIndex - index > 0 ? preIndex : index;
  62. const min = preIndex - index < 0 ? preIndex : index;
  63. for (let i = min; i < max + 1; i++) {
  64. fun(i);
  65. }
  66. preIndex = index;
  67. preSeconds = moment().millisecond();
  68. return;
  69. }
  70. }
  71. preSeconds = moment().millisecond();
  72. preIndex = index;
  73. if (dataChart.length > index && index >= 0) {
  74. // 普通的重定位
  75. fun(index);
  76. }
  77. };
  78. // echart
  79. export const echartsAnimate = ({ myChart, saveData, id, max }: any) => {
  80. let dataChart;
  81. myChart &&
  82. myChart.getOption().series.map((item) => {
  83. if (item.id === id) {
  84. dataChart = item.data.map((item, index) => {
  85. return [dateArr[index], item];
  86. });
  87. }
  88. });
  89. setTimeout(() => {
  90. myChart &&
  91. myChart.setOption({
  92. graphic: echarts.util.map(dataChart, function (item, dataIndex) {
  93. return {
  94. type: 'circle',
  95. position: myChart.convertToPixel('grid', item),
  96. shape: {
  97. cx: 0,
  98. cy: 0,
  99. r: 16,
  100. },
  101. invisible: true, // 调试设置为false
  102. draggable: true,
  103. ondrag: echarts.util.curry((dataIndex, dx) => {
  104. onPointDragging({ myChart, dataChart, dx, id, max });
  105. }, dataIndex),
  106. ondragend: echarts.util.curry((dataIndex, dx) => {
  107. preIndex = undefined;
  108. saveData(dataChart);
  109. }, dataIndex),
  110. z: 100,
  111. };
  112. }),
  113. });
  114. }, 0);
  115. window.addEventListener('resize', updatePosition);
  116. myChart.on('dataZoom', () => {
  117. updatePosition({ myChart, dataChart });
  118. });
  119. };

使用例子

  1. <ReactEcharts
  2. style={{
  3. height: '400px',
  4. border: '1px solid rgb(204 204 204 / 0.5)',
  5. paddingTop: '10px',
  6. borderRadius: '6px',
  7. }}
  8. option={getConfig()}
  9. ref={(e) => {
  10. // 拖拽图表的核心
  11. if (e) {
  12. const myChart = e.getEchartsInstance();
  13. echartsAnimate({ myChart, saveData, id: 'test' });
  14. }
  15. }}
  16. />