技术拆解-时间轴折线图
http://jira002.iwencai.com:8080/browse/DATAV-2055
时间计划:
10 月内上线
10.23 提供折线图组件给 Ifind
需求:
1、动态折线图
https://flourish.studio/2019/07/15/line-chart-race/
曲线
圆角
阴影
渐变
时间点 TIP
Y 轴自动伸缩
一种方案是直接画好整个 path,然后遮住一部分,横向平移
https://observablehq.com/@miguelsanchezgonzalez/dynamic-x-and-y-ranges-on-pan
一种是实时变更画图
另外初始要不要画满?ECharts 自定义能力是优先的,无法满足需求。
把 render 函数和 update 函数设计好,dirty[]数组,每个对象调用自己的 render 方法
可以通过 path+scale(X、Y)的方式来实现;实际上 X 是不用 Scale 的,只需要 ScaleY 即可,而且是初始就设定固定值;做得好一点就可以动态设定,比如下一个数据的高度超过了当前的高度这种情况。
应该将最大最小值以参数形式传入,这样可以适应多条曲线的情况。
核心函数:dataToPath(data, max, min)
风险点:
(1)性能:能否增量绘制
(2)线条的圆角
2、多线叠加(如何避免重叠问题?)
3、支持大数据量(精确到天)
4、支持导出动态图
1、项目和柱状图的独立开
因为二者的坐标轴不一样,数据也不一样;
不对,数据可以通用的,比如多条线的情况
坐标轴也可以部分通用
求同存异;扩展自定义能力
2、动画的控制配置项可以通用
这样的话,我只需要将作图部分解耦出来就可以了
另外把比例尺摸透,这个在动画过程中很关键的吧
开发流程
原理分析
通过数据的 Max 和 Min 值,以及页面显示区域的高度,确定每个单位数值对应的页面大小(这里可以用一个插值器);
然后就可以算出一系列的点的 x、y 值;
然后就可以得到 svg 的 path 元素的 d 属性的值了。
生成假数据
render 函数
数据驱动实现动画
requestAnimationFrame+render()
功能分解
(TODO,大活)坐标轴
折线图
自己计算 path 的 d 属性
1 | |
采用 d3.line
这种方式更好。
https://observablehq.com/@d3/line-chart
获取当前鼠标位置的 x 数据
https://observablehq.com/@d3/learn-d3-interaction
折线图动画
直接过渡
The first relies on D3’s transitions, creating an initial graphic and then starting a transition to modify it (interpolating the stroke-dasharray).
这种是直接使用 transition 来实现,一般用于点比较多的情况,这种情况虽然动画是直接过渡的,但是因为点多,过渡快,所以看起来动画还是很连贯的。
平滑过渡动画
当折线图点比较少的时候,如果直接更换数据,就会很突兀,需要一个平滑过渡的效果。
将线条拆分为多段进行绘制
将数据分为多个不同的 path 进行绘制,这样就可以针对某一段的数据进行动画设置了,而不是针对整条线进行动画设置。
这样的问题是可能导致页面的 path 元素太多。不过似乎也不是问题,因为采用这种方案,一般就是页面点太少,动画不够连贯的情况。
还有一个办法,就是永远只把线条拆分为 2 段:上一次之前绘制的所有线条+本次绘制的线条。然后控制本次绘制的线条的动画即可。
关于 stroke-dasharray 和 stroke-dashoffset 的问题:
也可以采用 2 段线条的机制,这个可能更容易一些。
通过 d3.interpolate 处理时间动画
参考这个文章:https://observablehq.com/@d3/learn-d3-animation
需要学习的概念:
d3.interpolate:https://www.wenjiangs.com/doc/d3-interpolate
1 | |
遮罩层
直接把图形全部画出来,然后通过一个遮罩层来实现假的动画效果。
这个方案局限性太大了,暂不考虑。
自己用 path 硬画一个 d 属性出来填充
和下面的阴影图类似。
圆点
标注 TIP
看了 D3 的示例,是通过给每根线画一个隐藏的矩形来处理事件的,鼠标移动到这个矩形上之后,再显示 tooltip。
tooltip 如果用 div 来实现的话,定位要用 top 和 left,不要用 transform
1 | |
圆点与标注 TIP 的连线
圆点与标注 TIP 的连线动画
区域阴影
动态:
https://observablehq.com/@eliezerisrael/animated-area-graph
自己用 path 硬画一个 d 属性出来填充
最终我采用了这个方案,感觉还是最基础的最可控。
大致逻辑如下:
- 获取当前页面所有点的数据 data
- 通过前 N-1 个数据,计算前 N-1 个点的坐标
- 通过 transition.ease(func)的 func,取得动画中每一帧的相对时间(0-1 之间),然后根据第 N-1 和第 N 个点的 x、y 坐标,算到当前动画帧的 x、y 坐标
- 通过上述数据构建 path 的 d 属性的字符串
- 在 ease 动画的每一帧中,重绘阴影区域
样式美化
配置化
参考效果:
这个才是我需要的:
https://gist.github.com/ckinmind/caa2a0181c314cd6138997565093c84d
https://lovekobe24.github.io/DymanicLineChartWithD3/
https://app.flourish.studio/@flourish/horserace
只展示固定数量的数据:
https://blog.csdn.net/m0_37777005/article/details/96994716
资料整合:
http://www.voidcn.com/article/p-vgmwytsc-bwq.html
D3 Dynamic Line Chart:
https://bl.ocks.org/OpenViz/306df0edf02426a576204046c5b38520
区域图(用 Path 画个区域,然后.attr(“fill”, “steelblue”)填充颜色即可):
https://observablehq.com/@d3/area-chart
(TBR)动态折线图:
https://jonsadka.com/blog/how-to-create-live-updating-and-flexible-d3-line-charts-using-pseudo-data
(TBR)折线图动画:
https://www.d3-graph-gallery.com/graph/line_change_data.html
(TBR)selection.joini()动态增加数据:
https://observablehq.com/@d3/selection-join
一些感想
设计流程
- 页面上总共有哪些元素?
- 每个元素涉及的交互有哪些?
- 哪些元素是初始化就画完了(init 方法),哪些是后续随着动画才开始画图的(update 方法)?
让一个类/方法做的事情越少越好
比如数据中的日期字符串的处理,其实就不应该放到组件内部去消化,这样会增加组件代码复杂度,并降低组件的可扩展性。
我老是将一堆东西画一起
比如一开始将 line、circle、text 都放在 Line 这个类里了;后面发现分开其实好得多。
鼠标事件穿透:pointer-events
绘图元素组件应该有固定的数据结构
不该在 tooltip 里面做数据处理,tooltip 只应该接收数据,纯做展示
且 tooltip 的数据结构一定是固定的,不能每个组件传入不同的数据结构
通过设计基类来实现扩展性
比如柱状图和折线图数据结构不一样,要差异化处理数据;应该有各种基类
回调函数中不要传入对象
如果用户在自定义代码中修改了你这个对象,可能导致你画图出问题。