前端图表可视化的应用实践总结

2019 年 9 月 3 日 IMWeb前端社区

本文由 IMWeb 首发于 IMWeb 社区网站 imweb.io。点击阅读原文查看 IMWeb 社区更多精彩文章。


需求简介

腾讯企鹅辅导在学生上课结束后推送“学习报告”,是课程所提供的一项重要服务。家长在“学习报告”中能查看孩子上课时间及互动情况,答题及掌握知识点,作业考试分数,班级排名等诸多数据,继而让学生家长及时掌握孩子的学习情况。

此次改版升级是针对旧学习报告的的数据和展示进行的一次优化:增加考试模块、知识点采用更简单的表达形式、在视觉交互上更加年轻活泼、并运用了更多数据图表可视化在其数据展示中。

考试模块-数据图表可视化的应用

1.数据可视化组件库的选择及应用

在考试模块中,需要展示学生成绩变化趋势的曲线图,而这需要用到第三方的可视化组件库,继而快速回忆起比较知名的几款:国外的HighChart,百度家的Echart,阿里的AntV(移动端F2)等。当然也希望腾讯有一天也能有同样知名好用的的可视化组件库。

在选择可视化组件库时,我们主要考虑以下几点:1.能够良好支持移动端且轻量。2.支持React。3.具备足够自由的可定制化配置样式的能力。

其中第三点尤其重要,因为这里要准确还原交互视觉(不得不说我们交互和视觉给的要求很高)。根据经验,纵使强大的可视化组件库配置非常繁多,但往往可配置的内容太多,根本找不到用什么配置项达到目的,而且一些配置相互影响,变化很多。

最终我们发现并使用了Recharts。它是一个使用React和D3构建的Redefined图表库。具备以下特性:

  • 支持React组件,声明式的标签,写图表和写 HTML 一样简单。

  • 原生SVG支持,依赖于轻量级的 D3 子模块构建 SVG 元素。

  • 接口式的 API,解决各种个性化的需求。

以下是部分需求代码,展示了其用法和特性:

  
  
    
  1. <ResponsiveContainer width="100%" height={200}>

  2. <LineChart data={data}>

  3. <CartesianGrid horizontal={false} strokeDasharray="2 3" />

  4. <Line type={lineStyle} dataKey="avgScore" stroke="#CCCCCC" fill="#CCCCCC"

  5. label={

  6. <CustomizedLabel direction="down" data={data}

  7. relateKey="actualScore"/>

  8. }

  9. isAnimationActive={false} />

  10. <Line

  11. type={lineStyle} dataKey="actualScore" stroke="#08CB6A" fill="#08CB6A"

  12. label={<CustomizedLabel data={data} relateKey="avgScore" />}

  13. isAnimationActive={false} />

  14. <Legend

  15. align="left" verticalAlign="top" iconSize={4}

  16. iconType="rect" height={36}

  17. formatter={(value) => {

  18. return { actualScore: '我的成绩', avgScore: '班级平均分' }[value];

  19. }}

  20. wrapperStyle={{

  21. left: -13,

  22. fontSize: 12,

  23. }} />

  24. <XAxis

  25. dataKey="name" padding={{ left: padding, right: padding }} axisLine={false} tickLine={false} />

  26. <YAxis domain={[-8, 108]} hide />

  27. </LineChart>

  28. </ResponsiveContainer>

除了样式配置项外,还提供了诸如“strokeDasharray”贴近原生SVG的配置项。对于熟悉SVG的同学就能能很准确写图形样式了。

2. 如何画好一根曲线[贝塞尔曲线]

说道贝塞尔曲线,前端的同学很容易想到的是CSS transition中的cubic-bezier,一般是起始点和两个控制点 来生成两点间的一条曲线,也就是常用三阶贝塞尔曲线。关于贝塞尔曲线就不再赘述了,其原理和SVG中Path中贝塞尔曲线的使用,可查阅下面两篇文章。贝塞尔曲线原理
SVG Path 曲线

OK,根据需求,我们考试成绩已经确定两个点了,那么这根曲线到底具备怎样的“性格”,弯一点还是平滑一点?但是这需要和视觉的同学反复调整得出一个让她满意的“参数”。当然如果要做到完全满意,可能还要针对不同情况计算不同的参数。

下面代码为:通过D3 shape(可视化的图形基元),除了终点,两个控制点的x值通过参数设置。将其实例作为props 的type值传入Recharts中的 <Line/>中,即可得到想要的曲线。

  
  
    
  1. BezierLineShape.prototype = {

  2. lineStart() {

  3. this._x0 = this._x1 = this._y0 = this._y1 = this._t0 = NaN;

  4. this._point = 0;

  5. console.log('lineStart', this._line, this._point);

  6. },

  7. lineEnd() {

  8. console.log('lineEnd', this._line, this._point);

  9. },

  10. point(x, y) {

  11. console.log('point', x, y, this._line);

  12. (x = +x), (y = +y);

  13. if (x === this._x1 && y === this._y1) {

  14. return;

  15. }

  16. switch (this._point) {

  17. case 0: {

  18. this._point = 1;

  19. this._x1 = x;

  20. this._y1 = y;

  21. this._context.moveTo(x, y);

  22. break;

  23. }

  24. case 1: {

  25. const mint = (x - this._x1) * 0.35;//此为控制点位置参数

  26. const x1 = this._x1 + mint;

  27. const y1 = this._y1;

  28. const x2 = x - mint;

  29. const y2 = y;

  30. this._x1 = x;

  31. this._y1 = y;

  32. console.log('bezierCurveTo', x1, y1, x2, y2, x, y);

  33. this._context.bezierCurveTo(x1, y1, x2, y2, x, y);

  34. break;

  35. }

  36. default:

  37. break;

  38. }

  39. },

  40. };

最后效果图:



基于SVG做的客制化修改

Scalable Vector Graphics,意思为可缩放的矢量图形,它基于XML,是一种开放标准的矢量图形语言。recharts提供基于react组件的写法,去写可定制化svg图形。比如下面:用组件svg 来定制的Label的位置样式。

  
  
    
  1. export default class CustomizedLabel extends React.PureComponent {

  2. static defaultProps = {

  3. direction: 'up', //

  4. stroke: '#777',

  5. };

  6. render() {

  7. const { x, y, stroke, value, direction, index, relateKey, data } = this.props;

  8. let settedDirect = direction;

  9. try {

  10. const relateValue = data[index][relateKey];

  11. if (value > relateValue) {

  12. settedDirect = 'up';

  13. } else if (value < relateValue) {

  14. settedDirect = 'down';

  15. }

  16. } catch (e) {

  17. // BJ_Report

  18. }

  19. const dy = settedDirect === 'up' ? -10 : 18;

  20. return (

  21. <text x={x} y={y} dy={dy} fill={stroke} fontSize={14} textAnchor="middle">

  22. {value}

  23. </text>

  24. );

  25. }

  26. }

学习回顾-轮播柱状图结合实现

在学习回顾模块,用户可以左右滑动/点击柱状图,来切换不同课程信息展示。很显然可以通过一个轮播组件来实现,但是这个模块还具备柱状图的展示。要选择一个兼具轮播和图表的组件,还要保证两者的功能和样式都可按需求定制。显然这并不容易,即便存在这样组件也要花上不少时间去寻找和筛选。

这时就要权衡,到底是在一个轮播组件添加图表,还是改造图表组件为轮播。这里我选择基于轮播组件来写里面的柱状图的这个方案。原因是:这里的柱状图并不复杂,可以用dom+css样式来实现,并且正好实现样式定制化的需求。虽然图表组件(比如antV的F2)也提供类似滑动图表的功能,但是由于轮播不是它主要特性,诸如多item展示以及居中item选中等特性,改起来也不容易。

确定在轮播组件实现柱状图方案后,发现在实现仍有难点:第一个item的左边和最后一个item的右边仍有虚线轴。最开始想到通过添加空item来实现,但实际需求是在滑至第一个和最后一个是不允许继续滑动的,所以不能直接添加空item。

那怎么办呢?我们都知道轮播都是由视窗加container组成,通过计算定位container的位置来轮播。我不能在container里面直接添加DOM元素,否则会影响轮播组件的计算。但是我们可以在container前后添加伪元素,这样就不会妨碍轮播定位的计算了。

  
  
    
  1. .time-chart-item:nth-of-type(1):after {

  2. content: '';

  3. width: pxToRem(116);

  4. height: pxToRem(144);

  5. display: block;

  6. position: absolute;

  7. background: url(./img/dashline.png) pxToRem(29) 0 no-repeat,

  8. url(./img/dashline.png) pxToRem(29+58) 0 no-repeat;

  9. top: pxToRem(28);

  10. left: pxToRem(-116);

  11. }

这里有个平时很少用到的background的都多背景方法,由于左右两边最多有两根虚线展示,backgound设置两个虚线图片即可。

本次上课-如何用CSS mask实现状态条

当看到视觉稿 学生在线时间状态条的时候,一眼看去ok完全没有难度,不就一个简单的状态条吗,只不过不连续罢了。写个div,overflow-hidden,只需计算绿色块的width值和left值即可,撸起袖子就是干,十分钟搞定。

可是设计走查时,却逃不过视觉设计同学的火眼金睛:“这里的绿色应该是覆盖灰色边框上面的!” 接下来为了满足视觉同学的要求可花费了不少功夫。因为在线状态条及其相关计算已经写好,最开始没有使用图表组件,因为我觉得这很简单,不需要杀鸡用牛刀,直接CSS可以实现。写来改写代码,为了让绿色在线条覆盖背景border,我将绿色状态条覆盖在上层,但这又出现另外一个问题。绿色条块左右两侧由于不被父级overflowhiden遮住,在值未达到极值时,无法做到圆角转直线的效果。

传统的办法

在外面再套一层div,position设置为relative,设置圆角和overflow hidden,绿色块相对于这一层div定位,如果溢出就会被裁剪。

css遮罩

css 有一个 -webkit-mask 属性。它所提供类似于遮罩的能力,让原本CSS无法实现的shape通过图片也能做到。看了下面这个图就清楚了。

那么怎么应用-webkit-mask来实现不连续的状态条呢?其实只需要一个非透明的极小的png图,计算好宽度以及位置,再进行样式设置即可。

这里的-webkit-mask和所有background的多背景图使用是一样的,需要注意的是,这里的第一个参数值不要把它误会成是的x值,而是图片的x%与容器x%的重合点,这里很容易出错。以下是计算的代码和生成的css样式:

  
  
    
  1. const maskArray = [];

  2. InClassState.map((item) => {

  3. const { start_inclass: start, end_inclass: end } = item;

  4. const left = (start - lessonBeginTime) / allTime;

  5. const width = (end - start) / allTime;

  6. const maskLeft = left / (1 - width);

  7. maskArray.push(`url(${maskimg}) no-repeat ${maskLeft.toFixed(2) * 100}% 0/

  8. ${width * 100}% 100%`);

  9. });

  10. style.WebkitMask = maskArray.join(',');

  
  
    
  1. -webkit-mask:

  2. url(//fudao.qq.com/block_0bb81cb….png) 0% 0px / 60% 100% no-repeat,

  3. url(//fudao.qq.com/block_0bb81cb….png) 76% 0px / 15% 100% no-repeat,

  4. url(//fudao.qq.com/block_0bb81cb….png) 106% 0px / 15% 100% no-repeat;

只需要通过一个元素。

关注我们

IMWeb 团队隶属腾讯公司,是国内最专业的前端团队之一。

我们专注前端领域多年,负责过 QQ 资料、QQ 注册、QQ 群等亿级业务。目前聚焦于在线教育领域,精心打磨 腾讯课堂、企鹅辅导 及 ABCMouse 三大产品。

社区官网

http://imweb.io/

加入我们

https://hr.tencent.com/position_detail.php?id=45616


扫码关注 IMWeb前端社区 公众号,获取最新前端好文

微博、掘金、Github、知乎可搜索 IMWebIMWeb团队 关注我们。


👇点击阅读原文获取更多参考资料


登录查看更多
0

相关内容

【实用书】学习用Python编写代码进行数据分析,103页pdf
专知会员服务
190+阅读 · 2020年6月29日
【实用书】Python技术手册,第三版767页pdf
专知会员服务
229+阅读 · 2020年5月21日
【经典书】Python数据数据分析第二版,541页pdf
专知会员服务
189+阅读 · 2020年3月12日
【书籍】深度学习框架:PyTorch入门与实践(附代码)
专知会员服务
160+阅读 · 2019年10月28日
手把手教你用Python实现“坦克大战”,附详细代码!
机器学习算法与Python学习
11+阅读 · 2019年6月8日
硬核实践经验 - 企鹅辅导 RN 迁移及优化总结
IMWeb前端社区
5+阅读 · 2019年5月6日
Python奇淫技巧,5个数据可视化工具
机器学习算法与Python学习
7+阅读 · 2019年4月12日
Python数据可视化2018:为什么这么多的库?
Python程序员
4+阅读 · 2019年1月2日
2018年7月份GitHub开源项目排行榜
算法与数据结构
15+阅读 · 2018年8月3日
Python 爬虫实践:《战狼2》豆瓣影评分析
数据库开发
5+阅读 · 2018年3月19日
荐书丨深度学习框架PyTorch:入门与实践
程序人生
11+阅读 · 2018年1月19日
用 Scikit-Learn 和 Pandas 学习线性回归
Python开发者
9+阅读 · 2017年9月26日
Arxiv
5+阅读 · 2019年4月21日
Music Transformer
Arxiv
5+阅读 · 2018年12月12日
Arxiv
8+阅读 · 2018年5月21日
Arxiv
5+阅读 · 2018年5月1日
Arxiv
10+阅读 · 2018年3月23日
Arxiv
5+阅读 · 2018年1月30日
Arxiv
5+阅读 · 2017年7月23日
VIP会员
相关VIP内容
【实用书】学习用Python编写代码进行数据分析,103页pdf
专知会员服务
190+阅读 · 2020年6月29日
【实用书】Python技术手册,第三版767页pdf
专知会员服务
229+阅读 · 2020年5月21日
【经典书】Python数据数据分析第二版,541页pdf
专知会员服务
189+阅读 · 2020年3月12日
【书籍】深度学习框架:PyTorch入门与实践(附代码)
专知会员服务
160+阅读 · 2019年10月28日
相关资讯
手把手教你用Python实现“坦克大战”,附详细代码!
机器学习算法与Python学习
11+阅读 · 2019年6月8日
硬核实践经验 - 企鹅辅导 RN 迁移及优化总结
IMWeb前端社区
5+阅读 · 2019年5月6日
Python奇淫技巧,5个数据可视化工具
机器学习算法与Python学习
7+阅读 · 2019年4月12日
Python数据可视化2018:为什么这么多的库?
Python程序员
4+阅读 · 2019年1月2日
2018年7月份GitHub开源项目排行榜
算法与数据结构
15+阅读 · 2018年8月3日
Python 爬虫实践:《战狼2》豆瓣影评分析
数据库开发
5+阅读 · 2018年3月19日
荐书丨深度学习框架PyTorch:入门与实践
程序人生
11+阅读 · 2018年1月19日
用 Scikit-Learn 和 Pandas 学习线性回归
Python开发者
9+阅读 · 2017年9月26日
相关论文
Arxiv
5+阅读 · 2019年4月21日
Music Transformer
Arxiv
5+阅读 · 2018年12月12日
Arxiv
8+阅读 · 2018年5月21日
Arxiv
5+阅读 · 2018年5月1日
Arxiv
10+阅读 · 2018年3月23日
Arxiv
5+阅读 · 2018年1月30日
Arxiv
5+阅读 · 2017年7月23日
Top
微信扫码咨询专知VIP会员