web前端图表可视化应用实践

需求简介

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

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

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

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

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

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

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

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

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

  • 支持React组件,声明式的标签,写图表和写 HTML 一样简单。
  • 原生SVG支持,依赖于轻量级的 D3 子模块构建 SVG 元素。
  • 接口式的 API,解决各种个性化的需求。

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

<ResponsiveContainerwidth="100%"height={200}>

<LineChartdata={data}>

<CartesianGridhorizontal={false}strokeDasharray="2 3" />

<Linetype={lineStyle}dataKey="avgScore"stroke="#CCCCCC"fill="#CCCCCC"

label={

<CustomizedLabeldirection="down"data={data}

relateKey="actualScore"/>

}

isAnimationActive={false} />

<Line

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

label={<CustomizedLabeldata={data}relateKey="avgScore" />}

isAnimationActive={false} />

<Legend

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

iconType="rect"height={36}

formatter={(value) => {

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

}}

wrapperStyle={{

left: -13,

fontSize: 12,

}} />

<XAxis

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

<YAxisdomain={[-8,108]} hide />

</LineChart>

</ResponsiveContainer>

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

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

说道贝塞尔曲线,前端的同学很容易想到的是CSS transition中的cubic-bezier,一般是起始点和两个控制点 来生成两点间的一条曲线,也就是常用三阶贝塞尔曲线。

关于贝塞尔曲线就不再赘述了,其原理和SVG中Path中贝塞尔曲线的使用,可查阅下面两篇文章。

贝塞尔曲线原理

SVG Path 曲线

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

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

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

BezierLineShape.prototype = {

lineStart() {

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

this._point = 0;

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

},

lineEnd() {

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

},

point(x, y) {

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

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

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

return;

}

switch (this._point) {

case0: {

this._point = 1;

this._x1 = x;

this._y1 = y;

this._context.moveTo(x, y);

break;

}

case1: {

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

const x1 = this._x1 + mint;

const y1 = this._y1;

const x2 = x - mint;

const y2 = y;

this._x1 = x;

this._y1 = y;

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

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

break;

}

default:

break;

}

},

};

最后效果图:

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

基于SVG做的客制化修改

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

exportdefaultclassCustomizedLabelextendsReact.PureComponent{

static defaultProps = {

direction: 'up', //

stroke: '#777',

};

render() {

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

let settedDirect = direction;

try {

const relateValue = data[index][relateKey];

if (value > relateValue) {

settedDirect = 'up';

} elseif (value < relateValue) {

settedDirect = 'down';

}

} catch (e) {

// BJ_Report

}

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

return (

<textx={x}y={y}dy={dy}fill={stroke}fontSize={14}textAnchor="middle">

{value}

</text>

);

}

}

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

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

在学习回顾模块,用户可以左右滑动/点击柱状图,来切换不同课程信息展示。

很显然可以通过一个轮播组件来实现,但是这个模块还具备柱状图的展示。要选择一个兼具轮播和图表的组件,还要保证两者的功能和样式都可按需求定制。显然这并不容易,即便存在这样组件也要花上不少时间去寻找和筛选。

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

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

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

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

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

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

content: '';

width:pxToRem(116);

height:pxToRem(144);

display: block;

position: absolute;

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

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

top:pxToRem(28);

left:pxToRem(-116);

}

这里有个平时很少用到的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样式:

const maskArray = [];

InClassState.map((item) => {

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

const left = (start - lessonBeginTime) / allTime;

const width = (end - start) / allTime;

const maskLeft = left / (1 - width);

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

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

});

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

-webkit-mask:

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

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

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

只需要通过一个元素。

以上是 web前端图表可视化应用实践 的全部内容, 来源链接: utcz.com/a/115505.html

回到顶部