前言
這段時間一直在做視覺化,在我的專案中有一部分是電力巡檢的資料視覺化。其中的資料看板比較簡單,我將其單獨抽離出來形成一個demo,為保密demo中資料非真實資料。先看效果。
具體效果
連結相關
- 瀏覽連結:http://xisite.top/original/data-board/index.html#/
- 專案連結(覺得有用的記得star哦):https://gitee.com/xi1213/data-board
實現目標
- 可根據專案切換不同看板資料。
- 資料的展現形式包括:折線圖,柱狀圖、餅圖、環圖、進度圖、輪播圖。
- 包含一個可控制的3d球體,球面打點具體資料。
具體實現
資料切換
沒啥技術含量,demo資料我是寫死,要使用的可以直接拿去替換為自己的介面資料。
projectPtions: [
{
value: '1',
label: '四川專案',
sphereData: [
{
position: [102, 30],
pointName: "成都",
value: 31889355
},
{
position: [102, 27],//經度,緯度
pointName: "西昌",
value: 13578453
},
{
position: [107, 31],//經度,緯度
pointName: "達州",
value: 7854541453
},
],
msg: {
distance: 12245,//巡檢距離
towerNum: 85345,//杆塔數量
defectNum: 208//缺陷數量
},
lineData: [
[140, 232, 101, 264, 90, 340, 250],
[120, 282, 111, 234, 220, 340, 310],
[320, 132, 201, 334, 190, 130, 220]
],//折線圖資料
pieData: [234, 124, 156, 178],//餅圖資料
ringData: [100, 120, 104, 140, 160],//環圖資料
histData: {
xAxisData: ['成都', '南充', '宜賓', '西昌', '眉山', '樂山', '攀枝花'],
seriesData: [635, 880, 1165, 135, 342, 342, 524]
},//柱狀圖資料
proData: [29, 67, 90],//進度圖資料
},
{
value: '2',
label: '西藏專案',
sphereData: [
{
position: [91.11, 29.97],//經度,緯度
pointName: "拉薩",
value: 78453
},
{
position: [80, 32],//經度,緯度
pointName: "阿里",
value: 13578453
},
{
position: [88, 29],//經度,緯度
pointName: "日喀則",
value: 7854541453
},
],
msg: {
distance: 20018,//巡檢距離
towerNum: 87624,//杆塔數量
defectNum: 126189//缺陷數量
},
lineData: [
[14, 22, 100, 164, 200, 140, 250],
[120, 22, 111, 24, 220, 240, 310],
[10, 132, 201, 334, 190, 30, 220]
],//折線圖資料
pieData: [134, 154, 156, 198],//餅圖資料
ringData: [120, 180, 114, 120, 110],//環圖資料
histData: {
xAxisData: ['拉薩', '日喀則', '昌都', '林芝', '山南', '那曲', '阿里'],
seriesData: [100, 280, 467, 956, 345, 111, 61]
},//柱狀圖資料
proData: [69, 37, 50],//進度圖資料
},
{
value: '3',
label: '浙江專案',
sphereData: [
{
position: [119, 27],//經度,緯度
pointName: "溫州",
value: 78453
},
{
position: [120, 29],//經度,緯度
pointName: "寧波",
value: 13578453
},
{
position: [120, 30],//經度,緯度
pointName: "嘉興",
value: 7854541453
},
],
msg: {
distance: 18722,//巡檢距離
towerNum: 122334,//杆塔數量
defectNum: 127895//缺陷數量
},
lineData: [
[104, 122, 200, 164, 20, 140, 250],
[220, 22, 111, 24, 120, 40, 10],
[130, 32, 201, 34, 190, 30, 200]
],//折線圖資料
pieData: [134, 174, 156, 108],//餅圖資料
ringData: [190, 110, 174, 130, 110],//環圖資料
histData: {
xAxisData: ['杭州', '寧波', '溫州', '嘉興', '湖州', '金華', '舟山'],
seriesData: [1035, 100, 565, 435, 142, 842, 124]
},//柱狀圖資料
proData: [89, 37, 60],//進度圖資料
},
],
陣列中的每一個物件代表一個專案。
切換專案時直接使用element的el-select切換即可。由於圖表元件是區分了元件的,每次切換資料時需要根據不同資料重繪圖表。
折線圖
圖中可以看到一共只有九個圖表。比較簡單,直接使用echarts配置即可。這是折線圖。
可能會感覺奇怪,折線圖咋會這樣呢?那是因為在配置中設定了areaStyle與smooth,使折線圖變成了平滑的堆疊面積圖,本質還是折線圖。areaStyle中的color可以接受echarts.graphic.LinearGradient,使其具有漸變的顏色,LinearGradient的前四個引數分別為漸變色的起始點與終止點的x值與y值,後面的值為顏色值。
let option = {
color: [],
title: {
text: '專案執行情況',
top: "5%",
left: 'center',
textStyle: {
color: "#fff"
}
},
tooltip: {
trigger: 'axis',
axisPointer: {
label: {
backgroundColor: '#6a7985'
}
}
},
grid: {
top: "20%",
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: [
{
type: 'category',
data: [],
axisLabel: {
color: "#fff",
},
axisLine: {
lineStyle: {
color: this.dataVColor[1]
}
}
}
],
yAxis: [
{
type: 'value',
axisLabel: {
color: "#fff",
},
axisLine: {
lineStyle: {
color: this.dataVColor[1]
}
},
splitLine: {
show: true,//網格設定
lineStyle: {
color: "#70707033",
width: 1,
type: "dotted",//虛線
},
},
}
],
series: []
};
option.xAxis[0].data = chartData.xAxisData;
chartData.seriesData.forEach(s => {
option.color.unshift(this.dataVColor[1]);//注意顏色新增的順序
option.series.push(
{
animationDuration: 3000,//動畫時間
animationEasing: "cubicInOut",//動畫型別
name: s.name,
type: 'line',
smooth: true,
stack: 'Total',
lineStyle: {
width: 1
},
showSymbol: false,
areaStyle: {
opacity: 0.8,
//使用線性漸變顏色(x1,y1,x2,y2,漸變陣列)
color: new echarts.graphic.LinearGradient(1, 1, 1, 0, [
{
offset: 0,
color: this.dataVColor[0]
},
{
offset: 1,
color: "#fff"
}
])
},
emphasis: {
focus: 'series'
},
data: s.data
}
)
});
await (option && this.lineChart.setOption(option));//設定資料
餅圖
餅圖我一樣在itemStyle的color中設定了漸變色。餅圖的尺寸是透過series中的radius來控制的,位置是center來控制的。
let option = {
title: {
text: '任務型別佔比',
top: "5%",
left: 'center',
textStyle: {
color: "#fff"
}
},
tooltip: {
trigger: 'item'
},
series: [
{
type: 'pie',
animationDuration: 3000,
radius:"60%",
animationEasing: "cubicInOut",
center: ["50%", "60%"],//餅圖位置
label: {
color: "#fff"
},
emphasis: {
label: {
show: true,
fontSize: '20',
fontWeight: 'bold'
}
},
data: [],
}
]
};
chartData.seriesData.forEach(s => {
option.series[0].data.push(
{
value: s.value,
name: s.name,
itemStyle: {
color: new echarts.graphic.LinearGradient(1, 1, 1, 0, [
{
offset: 0,
color: this.dataVColor[0]
},
{
offset: 1,
color: "#fff"
}
])
}
}
)
});
await (option && this.pieChart.setOption(option));//設定資料
環圖
環圖其實就是餅圖的變形。將series中的radius設定為兩個元素的陣列即可,數值為內外環的半徑比。
let option = {
title: {
text: '缺陷型別',
top: "5%",
left: 'center',
textStyle: {
color: "#fff"
}
},
tooltip: {
trigger: 'item'
},
series: [
{
type: 'pie',
animationDuration: 3000,
animationEasing: "cubicInOut",
radius: ['30%', '60%'],//內外環半徑比
center: ["50%", "60%"],//餅圖位置
label: {
color: "#fff"
},
emphasis: {
label: {
show: true,
fontSize: '20',
fontWeight: 'bold'
}
},
data: []
}
]
};
chartData.seriesData.forEach(s => {
option.series[0].data.push(
{
value: s.value,
name: s.name,
itemStyle: {
color: new echarts.graphic.LinearGradient(1, 1, 1, 0, [
{
offset: 0,
color: this.dataVColor[0]
},
{
offset: 1,
color: "#fff"
}
])
}
}
)
});
await (option && this.ringChart.setOption(option));//設定資料
柱狀圖
柱狀圖也一樣設定了漸變色。每個柱子後面的陰影是透過series中的showBackground設定的。
let option = {
title: {
text: '缺陷分佈',
top: "5%",
left: 'center',
textStyle: {
color: "#fff"
}
},
tooltip: {
trigger: 'item'
},
grid: {
left: '3%',
top: "20%",
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
data: [],
axisLabel: {
color: "#fff",
interval: 0,
rotate: 20,
},
},
yAxis: {
type: 'value',
axisLabel: {
color: "#fff",
},
splitLine: {
show: true,//網格設定
lineStyle: {
color: "#70707033",
width: 1,
type: "dotted",//虛線
},
},
},
series: [
{
type: 'bar',
animationDuration: 3000,
animationEasing: "cubicInOut",
showBackground: true,
label: {
color: "#fff"
},
data: [],
color: new echarts.graphic.LinearGradient(0, 0, 1, 1, [
{
offset: 1,
color: this.dataVColor[0]
},
{
offset: 0,
color: "#ffffff"
}
])
}
]
};
option.xAxis.data = chartData.xAxisData;
chartData.seriesData.forEach(s => {
option.series[0].data.push(s);
});
await (option && this.histogramChart.setOption(option));//設定資料
關係圖
本來想用3d力導向圖外掛3d-force-graph的,但後面發現echarts自己也有類似的功能graph,直接設定series的layout即可,它有三種值:none(無任何佈局),circular(環形佈局)、force(力引導佈局)。我用了circular,只有點,沒有連線。
let option = {
title: {
text: '巡檢工作待辦',
top: "1%",
left: 'center',
textStyle: {
color: "#fff"
}
},
// tooltip: {
// trigger: 'item'
// },
series: [{
type: 'graph',
layout: 'circular',//環形佈局
scaleLimit: {
min: .5,//縮放限制
max: 2
},
zoom: .7,
roam: false,
label: {
normal: {
color: "#fff",
show: true,
position: 'inside',
fontSize: 14,
fontStyle: '900',
}
},
data: []
}]
};
chartData.seriesData.forEach(s => {
option.series[0].data.push(
{
name: s.name,
value: s.value,
symbolSize: Math.round((s.value / maxSymbolSize) * 100),//尺寸
draggable: true,//允許拖拽
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 1, 1, [
{
offset: 0,
color: this.dataVColor[0]
},
{
offset: 1,
color: "#fff"
}
])
}
}
)
});
await (option && this.atlasChart.setOption(option));//設定資料
輪播圖
這是直接用了element的走馬燈元件,自己新增圖片即可。
<el-carousel :height="carouselHeight" indicator-position="outside" arrow="never" :autoplay="true"
:interval="2000">
<el-carousel-item v-for="item in defectImgList" :key="item.name">
<img :src="item.img" fit="fill">
</el-carousel-item>
</el-carousel>
進度儀表圖
這是具體配置:
let option = {
title: {
text: '\n{a|' + chartData.name + '}',
x: 'center',
y: '65%',
bottom: "0",
textStyle: {
color: "#ffffff",
rich: {
a: {
fontSize: 15,
fontWeight: 900
},
}
}
},
series: [
{
type: 'gauge',
radius: '86%',//儀表盤半徑
center: ['50%', '45%'],//儀表盤位置
splitNumber: 5,
animationDuration: 3000,
animationEasing: "cubicInOut",
axisLine: {
lineStyle: {
width: 15,
color: [
[1, new echarts.graphic.LinearGradient(1, 1, 0, 1, [
{
offset: 0,
color: this.dataVColor[1]
},
{
offset: 1,
color: "#aaa"
}
])]
]
}
},
//指標
pointer: {
width: 3,
length: '70%',
},
//小刻度
axisTick: {
length: 5,
lineStyle: {
color: '#fff',
width: 1
}
},
//大刻度
splitLine: {
show: false,
length: 10,
lineStyle: {
color: '#fff',
width: 2
}
},
//刻度標籤
axisLabel: {
color: '#fff',
distance: 5,
fontSize: 8,
fontWeight: 900,
},
detail: {
valueAnimation: false,
formatter: '{value}%',
color: '#fff',
fontSize: 15,
fontWeight: 900,
padding: [30, 0, 0, 0]
},
data: [
{
value: chartData.value
}
]
}
]
};
await (option && this.progressChart.setOption(option));//設定資料
圖表資訊框動畫
圖表資訊框自己顯示輪播,其實是利用的echartsAutoTooltip.js這個東西,東西不大,這是他的原始碼:
export const autoToolTip = (chart, chartOption, options) => {
var defaultOptions = {
interval: 2000,
loopSeries: false,
seriesIndex: 0,
updateData: null,
};
if (!chart || !chartOption) {
return {};
}
var dataIndex = 0; // 資料索引,初始化為-1,是為了判斷是否是第一次執行
var seriesIndex = 0; // 系列索引
var timeTicket = 0;
var seriesLen = chartOption.series.length; // 系列個數
var dataLen = 0; // 某個系列資料個數
var chartType; // 系列型別
var first = true;
// 不迴圈series時seriesIndex指定顯示tooltip的系列,不指定預設為0,指定多個則預設為第一個
// 迴圈series時seriesIndex指定迴圈的series,不指定則從0開始迴圈所有series,指定單個則相當於不迴圈,指定多個
// 要不要新增開始series索引和開始的data索引?
if (options) {
options.interval = options.interval || defaultOptions.interval;
options.loopSeries = options.loopSeries || defaultOptions.loopSeries;
options.seriesIndex = options.seriesIndex || defaultOptions.seriesIndex;
options.updateData = options.updateData || defaultOptions.updateData;
} else {
options = defaultOptions;
}
// 如果設定的seriesIndex無效,則預設為0
if (options.seriesIndex < 0 || options.seriesIndex >= seriesLen) {
seriesIndex = 0;
} else {
seriesIndex = options.seriesIndex;
}
function autoShowTip() {
function showTip() {
// 判斷是否更新資料
if (
dataIndex === 0 &&
!first &&
typeof options.updateData === "function"
) {
options.updateData();
chart.setOption(chartOption);
}
var series = chartOption.series;
chartType = series[seriesIndex].type; // 系列型別
dataLen = series[seriesIndex].data.length; // 某個系列的資料個數
var tipParams = { seriesIndex: seriesIndex };
switch (chartType) {
case "map":
case "pie":
case "chord":
tipParams.name = series[seriesIndex].data[dataIndex].name;
break;
case "radar": // 雷達圖
tipParams.seriesIndex = seriesIndex;
tipParams.dataIndex = dataIndex;
break;
default:
tipParams.dataIndex = dataIndex;
break;
}
if (
chartType === "pie" ||//餅圖
chartType === "radar" ||
chartType === "map" ||
chartType === "scatter" ||
chartType === "line" ||//折線圖
chartType === "bar" ||//柱狀圖
chartType === "graph"
) {
// 取消之前高亮的圖形
chart.dispatchAction({
type: "downplay",
seriesIndex: options.loopSeries
? seriesIndex === 0
? seriesLen - 1
: seriesIndex - 1
: seriesIndex,
dataIndex: dataIndex === 0 ? dataLen - 1 : dataIndex - 1,
});
// 高亮當前圖形
chart.dispatchAction({
type: "highlight",
seriesIndex: seriesIndex,
dataIndex: dataIndex,
});
}
// 顯示 tooltip
tipParams.type = "showTip";
chart.dispatchAction(tipParams);
dataIndex = (dataIndex + 1) % dataLen;
if (options.loopSeries && dataIndex === 0 && !first) {
// 資料索引歸0表示當前系列資料已經迴圈完
seriesIndex = (seriesIndex + 1) % seriesLen;
}
first = false;
}
showTip();
timeTicket = setInterval(showTip, options.interval);
}
// 關閉輪播
function stopAutoShow() {
if (timeTicket) {
clearInterval(timeTicket);
timeTicket = 0;
if (
chartType === "pie" ||
chartType === "radar" ||
chartType === "map" ||
chartType === "scatter" ||
chartType === "line" ||
chartType === "bar" ||
chartType === "graph"
) {
// 取消高亮的圖形
chart.dispatchAction({
type: "downplay",
seriesIndex: options.loopSeries
? seriesIndex === 0
? seriesLen - 1
: seriesIndex - 1
: seriesIndex,
dataIndex: dataIndex === 0 ? dataLen - 1 : dataIndex - 1,
});
}
}
}
var zRender = chart.getZr();
function zRenderMouseMove(param) {
if (param.event) {
// 阻止canvas上的滑鼠移動事件冒泡
param.event.cancelBubble = true;
}
stopAutoShow();
}
// 離開echarts圖時恢復自動輪播
function zRenderGlobalOut() {
if (!timeTicket) {
autoShowTip();
}
}
// 滑鼠在echarts圖上時停止輪播
chart.on("mousemove", stopAutoShow);
zRender.on("mousemove", zRenderMouseMove);
zRender.on("globalout", zRenderGlobalOut);
autoShowTip();
return {
clearLoop: function () {
if (timeTicket) {
clearInterval(timeTicket);
timeTicket = 0;
}
chart.off("mousemove", stopAutoShow);
zRender.off("mousemove", zRenderMouseMove);
zRender.off("globalout", zRenderGlobalOut);
},
};
};
球體實現
球體是用了three.js來實現的,具體可以看我之前的疫情視覺化文章(https://www.cnblogs.com/xi12/p/16690119.html),實現原理是一樣,直接建立宇宙、繪製球體、球面打點,一氣呵成。
數值動畫
這幾個數值是有遞增動畫的,我專案整體風格使用的dataV(http://datav.jiaminghi.com/guide/)實現的,dataV裡面也有數值增加動畫。但我沒用那個,可以利用vue的資料響應式很方便即可實現。
<!--數字增加動畫元件-->
<template>
<span class="num-span" :data-time="time" :data-value="value">{{ addNum }}</span>
</template>
<script>
export default {
props: {
//動畫時間
time: {
type: Number,
default: 2
},
//停止時的值
value: {
type: Number,
default: 0
},
//千位的逗號
thousandSign: {
type: Boolean,
default: () => false
}
},
data() {
return {
oldValue: 0,
addNum: 0,//響應式的數值
};
},
watch: {
value(val) {
this.oldValue = 0;
this.addNum = 0;//響應式的數值
this.startAnimation();//值改變時開始動畫
}
},
mounted() {
this.startAnimation();
},
methods: {
startAnimation() {
let value = this.value - this.oldValue;
let step = (value * 10) / (this.time * 100);
let current = 0;
let start = this.oldValue;
//定時器
let t = setInterval(() => {
start += step;
if (start > value) {
clearInterval(t);
start = value;
t = null;
}
if (current === start) {
return;
}
current = Math.floor(start);//取整
this.oldValue = current;
if (this.thousandSign) {
this.addNum = current.toString().replace(/(\d)(?=(?:\d{3}[+]?)+$)/g, '$1,');//新增千位符
} else {
this.addNum = current.toString();//無千位符
}
}, 10)
}
},
};
</script>
<style scoped lang='scss'>
.num-span {
/*開啟gpu加速*/
transform: translateZ(0);
}
</style>
特效背景
我比較懶,背景可不是我自己寫的,我直接一個iframe,把別人程式碼一扔,他就出來了⊙﹏⊙∥。原始碼在我專案的這個路徑下:
背景顏色可以透過Victor.js檔案下的Victor方法裡的diffuse值調節。
結語
感覺視覺化專案難度不大(當然這只是對於我這種只會用輪子的懶人加縫合怪來說),無非就是熟練利用echarts配置,但麻煩的是效果需要自己仔細調節。