数据可视化案例总结
tip:项目用的Vue 其实React也差不多
补一个项目的bug,当数据中断后继续绘制会出现直线 如果不想要就判断断电 简单快速的方案,判断是否断点:先算出所有相邻两点差值的最小值min,如果某相邻两点的差值大于这个最小值min,则说明有断点,补一个null即可,从而不是连直线
Echarts
案例一
- 为
Echarts
创建一个具备高宽的DOM容器
<div :id="'myChart' + idkey " :style="{width: '300px', height: '200px'}"></div>
id必须唯一,这样设置由于项目中有多个图表 单一的图表可以自己随便设置
- 准备好初始化的内容 设置
Echarts
样式
option: {
// 直角坐标系 grid 中的 x 轴,一般情况下单个 grid 组件最多只能放上下两个 x 轴,多于两个 x 轴需要通过配置 offset 属性防止同个位置多个 x 轴的重叠。
xAxis: {
data: this.charttimes,// 类目数据
show: false,// 是否显示 x 轴
// 坐标轴刻度标签的相关设置
axisLabel: {
// 刻度标签的内容格式器,支持字符串模板和回调函数两种形式
formatter: function (value, idx) {// 函数参数分别为刻度数值(类目),刻度的索引
let newDate = new Date(Number(value))
return [newDate.getMonth() + 1, newDate.getDate()].join('/')
},
interval: 500// 坐标轴刻度标签的显示间隔,在类目轴中有效 设置为 1,表示『隔一个标签显示一个标签』,如果值为 2,表示隔两个标签显示一个标签,以此类推
},
boundaryGap: ['10%', '10%']// 坐标轴两边留白策略,类目轴和非类目轴的设置和表现不一样
},
// 类似xAxis
yAxis: {
show: false,
boundaryGap: ['30%', '30%']
},
series: {
showSymbol: false,// 是否显示 symbol, 如果 false 则只有在 tooltip hover 的时候显示
hoverAnimation: false,// 是否开启 hover 在拐点标志上的提示动画效果
data: this.chartusages,// 系列中的数据内容数组。数组项通常为具体的数据项
type: 'line',// 图表类型
lineStyle: {
color: '#8C8C8C',
width: 1
}
},
// 提示框组件
tooltip: {
trigger: 'axis',// 触发类型 坐标轴触发
formatter: function (datas) {// 提示框浮层内容格式器,支持字符串模板和回调函数两种形式
let newDate = new Date(Number(datas[0].name))
let dateFormatter = date2str(newDate, 'YYYY-MM-DD HH:mm:ss')
return dateFormatter + '</br>' + datas[0].value + '%'
},
// 坐标轴指示器配置项
axisPointer: {
lineStyle: {
color: '#3752ff'
},
shadowStyle: {
color: '#3752ff'
}
}
}
}
一些项目中用到的配置都在上面 也有一些详细的解释 这里再提一下 提示框是指当鼠标移到数据部分会有一个框详细展示该点的信息 那么指示器就是一条线 上面配置就是一个颜色差不多深蓝色 平行于y轴的直线 随着鼠标的移动跟着动
- 画图
drawEChart () {
获取DOM容器
let myChart = echarts.init(document.getElementById(`myChart${this.idkey}`))
this.option.xAxis.data = this.charttimes// 设置x轴数据 也就是说x轴应该显示啥 这就写啥
this.option.series.data = this.chartusages // 设置图的数据 没数据拿啥画?
myChart.setOption(this.option)// 画图
}
案例二
- 创建容器
....这个代码就不写了
- 准备配置
// 组件封装 数据传过来的 遍历一下 懒得改 凑合看
let series = this.ydata.map((data, idx) => ({
name: this.names[idx],//系列名称
data: data,//数据
type: 'line',
symbol: 'circle',
showSymbol: false,
/** 控制某点变色点时候不要直接设置颜色 */
lineStyle: this.visualMap ? {} : {
color: this.colors[idx]
},
itemStyle: {
color: this.colors[idx],
borderColor: this.colors[idx]
},
...(this.area ? { areaStyle: typeof this.area === 'object' ? this.area : {
color: new LinearGradient(0, 0, 0, 1, [{
offset: 0,
color: '#65BDF6'
}, {
offset: 1,
color: this.colors[idx]
}])
}} : {})
}))
let that = this
/** 控制某些点变颜色 */
// visualMap 是视觉映射组件,用于进行『视觉编码』,也就是将数据映射到视觉元素(视觉通道)
let visualMap = {
type: 'piecewise',// 定义为分段型
show: false,
dimension: 0,
pieces: this.pieces
}
Object.assign(this, {
line: {
title: {
text: this.title
},
visualMap: that.visualMap ? visualMap : { show: false },
tooltip: {
trigger: 'axis',
// textStyle: {
// color: '#0068FF'
// },
formatter: function (param) {
return `${msec2str(Number(param[0].name), 'YYYY-MM-DD HH:mm:ss')}<br/>${param[0].value}`
}
},
legend: {
data: this.names
},
// 直角坐标系内绘图网格,单个 grid 内最多可以放置上下两个 X 轴,左右两个 Y 轴
grid: {
top: 40,
bottom: 10,
right: 25,
left: 10,
containLabel: true
},
xAxis: {
data: this.xdata,
type: 'category',
axisTick: {
alignWithLabel: true
},
axisLabel: {
interval: that.xinterval || 59,
showMaxLabel: true,
formatter: function (value, idx) {
// return idx === 0 ? msec2str(Number(value), 'YYYY-MM-DD') : msec2str(Number(value), 'MM-DD HH:mm')
return msec2str(Number(value), that.xformatter || 'HH:00')
}
}
},
yAxis: {
type: 'value'
},
series: series
}
})
return {}
- 画图也就不用我多说 (其实是我懒)
Dygraph
- 创建一个DOM容器(代码跟前面的基本类似)
<div class="chart"></div>
- 准备好配置文件
plot () {
// 有图就先删掉
if (this.chart) {
this.chart.destroy()
this.chart = null
}
// 就是我们上面准备的容器
this.chartEl.innerHTML = ''
// 获取数据
let data = await getData()
if (data.values.length === 0) {
this.chartEl.innerHTML = '<div class="chart-no-data">没有数据</div>'
return
}
// 数据格式转换(不好意思 后端返的数据没法直接用 我先转换一下 各位看官可以跳过)
let values = data.values.map(el => {
let [a, b, c, , ...rest] = el
return [a, b, c, ...rest]
})
let scoreValues = data.values.map(el => {
// eslint-disable-next-line
let [a, , , b, ...rest] = el
return [a, b]
})
this.scoreValues = scoreValues
// 上界和下界
let lowers = []
let uppers = []
for (let v of values) {
lowers.push([v[0], v[3]])
uppers.push([v[0], v[4]])
v[0] = msec2date(v[0])
v[2] = v[2] ? v[1] : null
}
// tolerance update
if (Object.keys(this.statistic).length > 0) {
this.trimThreshold('upper', values, 4)
this.trimThreshold('lower', values, 3)
this.trimThreshold('upper', uppers, 1)
this.trimThreshold('lower', lowers, 1)
}
// 样式指定
let majorColor = '#136DFB'
let minorColor = '#CCCCCC'
let boundColor = '#408BFF'
let anomalyColor = '#FF0000'
let basebandColor = '#DCE9FE'
let dylabels = [ 'x', '值', '异常' ]
let series = {// 定义每系列选项。其键与y轴标签名称匹配,值是字典本身,包含特定于该系列的选项。
'值': {
color: majorColor,
strokeWidth: 1,
drawPoints: false
},
'异常': {
color: anomalyColor,
strokeWidth: 2,
drawPoints: true,
pointSize: 2,
highlightCircleSize: 4
},
'分数': {
color: majorColor,
strokeWidth: 0,
drawPoints: false,
pointSize: 0,
highlightCircleSize: 0
}
}
dylabels = dylabels.concat([ '下界', '上界' ])
series['上界'] = {
color: boundColor,
strokeWidth: 0,
drawPoints: false,
pointSize: 0,
highlightCircleSize: 0
}
series['下界'] = {
color: boundColor,
strokeWidth: 0,
drawPoints: false,
pointSize: 0,
highlightCircleSize: 0
}
// 是否填充
if (this.filling) {
let st = this.timerange.start
if (dateLater(values[0][0], st)) {
values.unshift([st, ...Array(values[0].length - 1).fill(null)])
}
let ed = this.timerange.end
if (dateLater(ed, values[values.length - 1][0])) {
values.push([ed, ...Array(values[0].length - 1).fill(null)])
}
}
// 初始化dygraph
this.chart = new Dygraph(this.chartEl, values, {
labels: dylabels,//每个数据系列的名称,包括独立(X)系列
connectSeparatedPoints: false,
digitsAfterDecimal: 4,
legend: 'follow',//何时显示图例。默认情况下,它仅在用户将鼠标悬停在图表上时显示
interactionModel: Dygraph.defaultInteractionModel,
labelsKMB: true,// 在y轴上显示千/千万/千亿的K / M / B.
showRangeSelector: true,
rangeSelectorHeight: 30,//范围选择器小部件的高度
rangeSelectorPlotStrokeColor: majorColor,
rangeSelectorPlotFillColor: minorColor,
rangeSelectorPlotLineWidth: 1,
rangeSelectorForegroundStrokeColor: minorColor,
rangeSelectorForegroundLineWidth: 1,
rangeSelectorBackgroundStrokeColor: minorColor,
rangeSelectorBackgroundLineWidth: 1,
axisLineColor: minorColor,
gridLineColor: minorColor,
series: series,
axes: {//定义每轴选项
x: {
axisLabelFormatter: this.axisLabelFormatter
},
y: {
axisLabelWidth: 35
}
},
//设置后,每次用户通过鼠标悬停在图表外停止突出显示任何点时,都会调用此回调
underlayCallback: (canvas, area, g) => {
canvas.strokeStyle = basebandColor
canvas.fillStyle = basebandColor
let lowPoints = []
let highPoints = []
let drawBasebandBackgroundColor = () => {
// 绘制基带 图中红色那一块
// lowPoints对应下基带, hightPoints对应上基带
// 至少有一边多于1个点才画: 才能画出线/区域
if (lowPoints.length > 1 || highPoints.length > 1) {
canvas.save()
canvas.beginPath()
if (lowPoints.length > 1) {
canvas.moveTo(...lowPoints[0])
// 从左到右
for (let i = 1; i < lowPoints.length; i++) {
canvas.lineTo(...lowPoints[i])
}
}
if (highPoints.length > 1) {
if (lowPoints.length > 1) {
canvas.lineTo(...highPoints[highPoints.length - 1])
} else {
canvas.moveTo(...highPoints[highPoints.length - 1])
}
// 从右到左
for (let i = highPoints.length - 2; i >= 0; i--) {
canvas.lineTo(...highPoints[i])
}
if (lowPoints.length > 1) {
canvas.lineTo(...lowPoints[0])
}
}
// 如果可以形成区域, 就填充
if (lowPoints.length > 1 && highPoints.length > 1) {
canvas.closePath()
canvas.fill()
}
canvas.stroke()
canvas.restore()
lowPoints = []
highPoints = []
}
}
for (let j = 0; j < uppers.length; j++) {
// 基带数据发生以下情况的变化, 就画出当前待绘制的这一段
if (
(lowPoints.length === highPoints.length && lowPoints.length > 0 && (lowers[j][1] === null || uppers[j][1] === null)) || // 2 -> 1/0
(lowPoints.length > 0 && highPoints.length === 0 && lowers[j][1] === null) || // 1 -> 0
(lowPoints.length === 0 && highPoints.length > 0 && uppers[j][1] === null) || // 1 -> 0
(lowPoints.length > 0 && highPoints.length === 0 && lowers[j][1] !== null && uppers[j][1] !== null) || // 1 -> 2
(lowPoints.length === 0 && highPoints.length > 0 && lowers[j][1] !== null && uppers[j][1] !== null) // 1 -> 2
) {
drawBasebandBackgroundColor()
}
let xx = 0
if (lowers[j][1] !== null) {
let x = g.toDomXCoord(lowers[j][0])
let y = g.toDomYCoord(lowers[j][1])
lowPoints.push([x, y])
xx = x
}
if (uppers[j][1] !== null) {
let x = g.toDomXCoord(uppers[j][0])
let y = g.toDomYCoord(uppers[j][1])
highPoints.push([x, y])
xx = x
}
if (xx >= area.x + area.w) {
break
}
}
drawBasebandBackgroundColor()
}
})
}
- 开始画图
this.chartEl = this.$el.getElementsByClassName('chart')[0]
this.plot()
D3
d3业务写的有点复杂 下次写
总结
大概平时项目就用过这三种可视化库,每个都有自己的特点 具体用什么还得看项目 欢迎各位跟我一起讨论数据可视化