使用 Vue 和 SVG 快速绘制曲线图(带动画效果)
图表
当接到类似以上需求时,你的第一想法是不是跟我一样,使用 Canvas 来绘制,啥都不说就开始撸代码。如果你是用 Vue 之类的 MVVM 框架,那意味着你得提供一个结点供 Canvas 着陆,同时让 Canvas 能够响应数据变动。
写起代码来应该是长这样的:
<template>
<canvas ref="chart">
</canvas>
</template>
<script>
/* eslint-disable */
import Chart from 'utils/chart'
export default {
props: {
duration: {
type: Number,
default: 2000
},
data: {
type: Object,
default: []
}
},
watch: {
'data' (val, oldVal) {
this.redraw()
}
},
mounted () {
this.chart = Chart.init(this.$refs.chart)
this.redraw()
},
methods: {
redraw () {
this.chart.draw({
duration: this.duratioin
})
}
}
}
</script>
类似的做法之前写过很多,比如上一篇文章里面绘制六芒星的方式,但是这种做法成本比较大,首先你得从‘头’开始写代码(创建 Canvas,计算所有坐标点,绘制所有可视内容),同时要求你熟练掌握 Canvas API,并且能够在两种不同的开发思想下来回切换代码,总体上成本较高,所以当设计师给我这样的设计稿时,我是拒绝的!(直接放个数字不行吗,搞这么麻烦)
当然这种为自己偷懒而找的理由最终都会被驳回,因为在你身经百战的 Leader 眼里,这些小 Case 都是不经入目的。
“有工作量吗?” —— Leader
“没有没有。” —— 我
需求接都接了,一个字,干!
做是肯定要做的了,那么应对这种需求,有没有更顺滑的方式?尝试下用 SVG 吧。
SVG 是什么就不说了。SVG 很很突出的一个特性是,用文本编辑器打开就能看到源代码,编辑保存就能修改图片!!!
<svg width="100" height="100">
<circle cx="50" cy="50" r="40" stroke="green" stroke-width="4" fill="yellow" />
</svg>
SVG 的属性很多,但还好一眼就能看出什么意思。了解一番之后,设计稿直接切图导出,拿到类似这样的东西:
<svg width="622px" height="245px" viewBox="0 0 622 245" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<circle id="path-1" cx="10" cy="16" r="10"></circle>
<filter x="-37.5%" y="-37.5%" width="175.0%" height="175.0%" filterUnits="objectBoundingBox" id="filter-2">
<feOffset dx="0" dy="0" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
<feGaussianBlur stdDeviation="2.5" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
</filter>
<!--省略-->
</defs>
<g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g transform="translate(0.000000, -1.000000)">
<g id="曲线" transform="translate(18.000000, 33.000000)">
<!-- 这是一条曲线 -->
<path d="M10.6640625,19.6210938 C39.8348416,119.651983 77.4858832,169.520472 123.617188,169.226563 C231.858912,168.536938 285.925216,10.7569151 351.429688,10.1484375 C405.739364,9.64394951 415.953125,132.3125 464.898438,132.3125 C493.393229,132.3125 531.016927,113.764323 577.769531,76.6679688" stroke="#47E0FF" stroke-width="6" stroke-linecap="round"></path>
<!-- 这是一个点 -->
<g id="Oval-6">
<use fill="black" fill-opacity="1" filter="url(#filter-2)" xlink:href="#path-1"></use>
<use fill="#47E0FF" fill-rule="evenodd" xlink:href="#path-1"></use>
</g>
<!--省略-->
</g>
</g>
</g>
</svg>
密密麻麻一堆像乱码的东西,但是能看出来,defs 里面定义了一些图形,通过 use 被引用,然后 path 就是那条曲线。
defs 里面我们不需要关心,设计师帮我们画好了。我们关心的是点的位置,曲线的位置,锚点。其他的都可以不用管。
点的位置,可以通过控制 g 元素的 translate 进行偏移也好写。那么线是怎么控制的?path 这里只有一个 d 属性。
<path d="M10.6640625,19.6210938 C39.8348416,119.651983 77.4858832,169.520472 123.617188,169.226563 C231.858912,168.536938 285.925216,10.7569151 351.429688,10.1484375 C405.739364,9.64394951 415.953125,132.3125 464.898438,132.3125 C493.393229,132.3125 531.016927,113.764323 577.769531,76.6679688" stroke="#47E0FF" stroke-width="6" stroke-linecap="round"></path>
看代码容易蒙圈,对照下指令表就清晰多了。
SVG Path 指令列表
/Users/helkyle/projects/w3ctrain
图片来自SVG 研究之路 (4) - Path 基礎篇
所以 d 属性的值就是一堆指令和点的有机组合。
设计师用画笔绘制曲线的时候也不是一像素一像素绘制的,而是先定一个起点(M),选择点模式(这里用的是二阶贝塞尔曲线 C),选中下一个点,然后确定两个控制点,然后第二个点为起始点,继续描绘。
链接了生成规则之后,通过 Vue 的 computed 自动生成 d,轻而易举。
path () {
let steps = []
this.valueArr.forEach((curr, index) => {
if (index === 0) {
// 移动到起点
steps.push('M' + curr.x + ',' + curr.y)
}
if (index !== this.valueArr.length - 1) {
let next = this.valueArr[index + 1]
// 两个控制点坐标
var ctrl1 = {
x: (curr.x + next.x) * 0.5,
y: curr.y
}
var ctrl2 = {
x: ctrl1.x,
y: next.y
}
steps.push('C' + ctrl1.x + ',' + ctrl1.y)
steps.push(ctrl2.x + ',' + ctrl2.y)
steps.push(next.x + ',' + next.y)
}
})
return steps.join(' ')
}
为了让曲线看上去比较均匀,自然,我们选择让两个控制点的 x 值为起始点和结束点的 x 值的中间值,y 值分别还是起始点和结束点的 y 值。
cubic-bezier
通过工具更容易看出来怎么选择控制点的位置 cubic-bezier
代码调整一下,再结合 Tween 来实现渐进动画。
doAnimation () {
animation.progress = 0
new TWEEN.Tween(animation)
.delay(1000)
.to({progress: 1}, this.duration, TWEEN.Easing.Quadratic.Out)
.onUpdate(this.onUpdate)
.start()
},
onUpdate () {
this.valueArr.forEach((item) => {
item.y = item.startY + (item.targetY - item.startY) * this.animation.progress
})
}
效果如下:
line-chart-animation
通过 Vue 的数据-视图绑定,我们只需要修改 data 数组的值,和 progress 动画进度,就可以实现图表数据更新和曲线动画了。又一次,我们的关注点回归到我们无比熟悉的数据层,Niceeeeeeeee!
设计稿出自伟大的 Ray.John
codepen地址
如需转载,请注明出处: w3ctrain.com/2017/09/19/…
上一篇: 正弦曲线怎么画