WebUI领导驾驶舱
1、效果预览
“领导驾驶舱”可视化界面当下比较流行,因此以“档案数据管理为例”画了画界面,先附上效果,技术栈:angular + G2 + L7
需要装几个包:
//G2 Chart
npm install @antv/g2 --save
// L7 依赖
npm install --save @antv/l7
// 第三方底图依赖
npm install --save @antv/l7-maps;
参考:
GitHub:https://github.com/liuhuawu/data_visualization_cockpit
GeoJson:http://datav.aliyun.com/tools/atlas/#&lat=30.316551722910077&lng=106.68898666525287&zoom=3.5
附上代码:
archives1.component.html
<!-- archives1.component.html -->
<div class="head">
<!-- header头部 -->
<h1>档案管理数据驾驶舱</h1>
<div class="weather">
<span id="showTime">{{now|date:'yyyy-MM-dd'}} {{now|date:'HH:mm:ss'}}</span>
</div>
</div>
<!-- 中部内容 -->
<div class="mainbox">
<ul class="clearfix">
<li>
<div class="boxall">
<div class="alltitle">2020各分院项目归档情况</div>
<div id="container1"></div>
<div class="boxfoot"></div>
</div>
<div class="boxall" style="height: 22rem;">
<div class="alltitle">2020各分院项目小类</div>
<div id="container2"></div>
<div class="boxfoot"></div>
</div>
</li>
<li>
<div class="bar">
<div class="barbox">
<ul class="clearfix">
<li>690</li>
<li>610</li>
</ul>
</div>
<div class="barbox2">
<ul class="clearfix">
<li style="color: white;">2020年总项目数</li>
<li style="color: white;">2020年已归档数</li>
</ul>
</div>
</div>
<div class="map">
<!-- <div class="map1"><img style="width: 80%;" src="../../../../assets/archives1/lbx.png"></div>
<div class="map2"><img style="width: 75%;" src="../../../../assets/archives1/jt.png"></div>
<div class="map3"><img style="width: 70%;" src="../../../../assets/archives1/map.png"></div> -->
<div id="map4"></div>
</div>
</li>
<li>
<div class="boxall">
<div class="alltitle">2020各分院年产值(百万)</div>
<div id="container3"></div>
<div class="boxfoot"></div>
</div>
<div class="boxall" style="height: 22rem;">
<div class="alltitle">2020各月份归档/调晒情况</div>
<div id="container4"></div>
<div class="boxfoot"></div>
</div>
</li>
</ul>
</div>
archives1.component.css
@font-face {
font-family: electronicFont;
src: url(../../../../font/DS-DIGIT.TTF)
}
@keyframes myfirst2 {
from {
transform: rotate(0deg);
}
to {
transform: rotate(359deg);
}
}
@keyframes myfirst {
from {
transform: rotate(0deg);
}
to {
transform: rotate(-359deg);
}
}
.head {
height: 4rem;
background: url(../../../../assets/archives1/head_bg.png) no-repeat center center;
background-size: 100% 100%;
position: relative;
z-index: 100;
}
.head h1 {
color: #fff;
text-align: center;
font-size: 1.5rem;
line-height: 3rem;
}
.weather {
position: absolute;
right: 1rem;
top: 0;
line-height: .75rem;
}
.weather span {
color: rgba(255, 255, 255, .7);
font-size: 1rem;
padding-right: .5rem;
line-height: 2.6rem;
}
.mainbox {
width: 100%;
height: 100%;
}
li {
list-style-type: none;
}
table {}
ol,
ul,
p,
h1,
h2,
h3,
h4,
h5,
h6 {
padding: 0;
margin: 0
}
.mainbox>ul {
/* list-style: none; */
}
.mainbox>ul>li {
float: left;
width: 30%;
padding: 0 .4rem;
}
.boxall {
height: 14rem;
border: 1px solid rgba(25, 186, 139, .17);
padding: 0 .2rem .4rem .15rem;
background: rgba(255, 255, 255, .04) url(../../../../assets/archives1/line.png);
background-size: 100% auto;
position: relative;
margin-bottom: 2rem;
z-index: 10;
}
.alltitle {
padding: .4rem 0rem;
font-size: 0.9rem;
color: #fff;
text-align: center;
line-height: 1.6rem;
}
.allnav {
height: calc(100% - 30px);
}
.bar {
height: 8rem;
background: rgba(101, 132, 226, .1);
padding: .5rem;
margin: 0rem .5rem;
}
.barbox {
border: 1px solid rgba(25, 186, 139, .17);
position: relative;
height: 60%;
margin: 1rem 2rem 0rem 2rem;
}
.barbox2 {
margin: 0.4rem 2rem 0rem 2rem;
}
.barbox>ul,
.barbox2>ul {
list-style: none;
}
.barbox li {
font-size: 4rem;
color: #ffeb7b;
padding: .05rem 0;
font-family: electronicFont;
font-weight: bold;
}
.barbox li,
.barbox2 li {
float: left;
width: 50%;
text-align: center;
position: relative;
z-index: 100;
}
.mainbox>ul>li:nth-child(1) {
width: 30%;
padding: 0;
}
.mainbox>ul>li:nth-child(2) {
width: 40%;
padding: 0;
}
.mainbox>ul>li:nth-child(3) {
width: 30%;
padding: 0;
}
.barbox:before,
.barbox:after {
position: absolute;
width: 2rem;
height: .5rem;
content: "";
}
.barbox:before {
border-left: 2px solid #02a6b5;
left: 0;
border-top: 2px solid #02a6b5;
}
.barbox:after {
border-right: 2px solid #02a6b5;
right: 0;
bottom: 0;
border-bottom: 2px solid #02a6b5;
}
.barbox li:first-child:before {
position: absolute;
content: "";
height: 50%;
width: 1px;
background: rgba(255, 255, 255, .2);
right: 0;
top: 25%;
}
.boxall:before,
.boxall:after {
position: absolute;
width: .5rem;
height: .5rem;
content: "";
border-top: 2px solid #02a6b5;
top: 0;
}
.boxall:before,
.boxfoot:before {
border-left: 2px solid #02a6b5;
left: 0;
}
.boxall:after,
.boxfoot:after {
border-right: 2px solid #02a6b5;
right: 0;
}
.map {
position: relative;
height: 30rem;
z-index: 9;
}
.map1,
.map2,
.map3 {
position: absolute;
opacity: .5
}
.map1 {
text-align: center;
vertical-align: middle;
width: 100%;
height: 100%;
z-index: 2;
animation: myfirst2 15s infinite linear;
}
.map2 {
text-align: center;
vertical-align: middle;
width: 100%;
height: 100%;
z-index: 3;
animation: myfirst 10s infinite linear;
}
.map3 {
text-align: center;
vertical-align: middle;
width: 100%;
height: 100%;
z-index: 1;
}
.map1 img,
.map2 img,
.map3 img {
left: 50%;
top: 50%;
position: absolute;
transform: translate(-50%, -50%);
}
.clearfix:after,
.clearfix:before {
display: table;
content: " "
}
.clearfix:after {
clear: both
}
.boxfoot:before {
border-left: 2px solid #02a6b5;
left: 0;
}
.boxfoot:after {
border-right: 2px solid #02a6b5;
right: 0;
}
.boxfoot {
position: absolute;
bottom: 0;
width: 100%;
left: 0;
}
.boxfoot:before,
.boxfoot:after {
position: absolute;
width: 0.5rem;
height: 0.5rem;
content: "";
border-bottom: 2px solid #02a6b5;
bottom: 0;
}
archives1.component.ts
import { Component, OnInit } from '@angular/core';
import { Chart } from '@antv/g2';
import { DataSet } from '@antv/data-set/build/data-set';
//地图相关
import { Scene } from '@antv/l7';
import { ProvinceLayer } from '@antv/l7-district';
import { CityLayer } from '@antv/l7-district';
import { Mapbox } from '@antv/l7-maps';
@Component({
selector: 'app-archives1',
templateUrl: './archives1.component.html',
styleUrls: ['./archives1.component.css']
})
export class Archives1Component implements OnInit {
now: Date;
//全局变量
data1 = [
{ country: '未归档', year: '市政', value: 163 },
{ country: '未归档', year: '建筑', value: 203 },
{ country: '未归档', year: '规划', value: 276 },
{ country: '未归档', year: '交通', value: 408 },
{ country: '未归档', year: '园林', value: 547 },
{ country: '未归档', year: '水务', value: 729 },
{ country: '已归档', year: '市政', value: 502 },
{ country: '已归档', year: '建筑', value: 635 },
{ country: '已归档', year: '规划', value: 809 },
{ country: '已归档', year: '交通', value: 947 },
{ country: '已归档', year: '园林', value: 1402 },
{ country: '已归档', year: '水务', value: 3634 },
];
data2 = [
{ name: '研究', type: '市政', value: 11 },
{ name: '咨询', type: '建筑', value: 10 },
{ name: '其他', type: '规划', value: 10 },
{ name: '专项', type: '交通', value: 14 },
{ name: '咨询', type: '园林', value: 7 },
{ name: '研究', type: '水务', value: 7 },
{ name: '其他', type: '市政', value: 14 },
{ name: '研究', type: '规划', value: 3 },
{ name: '其他', type: '水务', value: 3 },
{ name: '研究', type: '园林', value: 11 },
{ name: '咨询', type: '园林', value: 5 },
{ name: '研究', type: '建筑', value: 5 },
];
data3 = [
{ type: '市政', value: 34 },
{ type: '建筑', value: 85 },
{ type: '规划', value: 103 },
{ type: '交通', value: 142 },
{ type: '园林', value: 251 },
{ type: '水务', value: 367 },
];
data4 = [
{ month: '1月', city: '归档', temperature: 7 },
{ month: '1月', city: '调晒', temperature: 5 },
{ month: '2月', city: '归档', temperature: 7 },
{ month: '2月', city: '调晒', temperature: 4 },
{ month: '3月', city: '归档', temperature: 9 },
{ month: '3月', city: '调晒', temperature: 5 },
{ month: '4月', city: '归档', temperature: 14 },
{ month: '4月', city: '调晒', temperature: 8 },
{ month: '5月', city: '归档', temperature: 18 },
{ month: '5月', city: '调晒', temperature: 11 },
{ month: '6月', city: '归档', temperature: 21 },
{ month: '6月', city: '调晒', temperature: 15 },
{ month: '7月', city: '归档', temperature: 25 },
{ month: '7月', city: '调晒', temperature: 17 },
{ month: '8月', city: '归档', temperature: 26 },
{ month: '8月', city: '调晒', temperature: 16 },
{ month: '9月', city: '归档', temperature: 23 },
{ month: '9月', city: '调晒', temperature: 14 },
{ month: '10月', city: '归档', temperature: 18 },
{ month: '10月', city: '调晒', temperature: 10 },
{ month: '11月', city: '归档', temperature: 13 },
{ month: '11月', city: '调晒', temperature: 6 },
{ month: '12月', city: '归档', temperature: 9 },
{ month: '12月', city: '调晒', temperature: 5 },
];
colors = ['#1A4397', '#2555B7', '#3165D1', '#467BE8', '#6296FE', '#7EA6F9'];
initChar1(): void {
// 计算每个柱子的占比
const ds = new DataSet();
const dv = ds
.createView()
.source(this.data1)
.transform({
type: 'percent',
field: 'value', // 统计销量
dimension: 'country', // 每年的占比
groupBy: ['year'], // 以不同产品类别为分组
as: 'percent',
});
const chart = new Chart({
container: 'container1',
autoFit: true,
height: 180,
});
chart.data(dv.rows);
chart.scale({
percent: {
min: 0,
formatter(val) {
return (val * 100).toFixed(2) + '%';
},
}
});
chart.tooltip({
shared: true,
showMarkers: false,
});
chart
.interval()
.position('year*percent')
.color('country')
.adjust('stack');
chart.interaction('active-region');
chart.render();
}
initChar2(): void {
const ds = new DataSet();
const dv = ds.createView();
dv.source(this.data2).transform({
type: 'percent',
field: 'value',
dimension: 'type',
as: 'percent',
});
const colorMap = {
市政: '#5B8FF9',
建筑: '#61DDAA',
规划: '#65789B',
交通: '#F6BD16',
园林: '#6F5EF9',
水务: '#78D3F8',
};
const chart = new Chart({
container: 'container2',
autoFit: true,
height: 350,
});
chart.data(dv.rows);
chart.legend(false);
chart.coordinate('theta', {
radius: 0.5,
innerRadius: 0.3,
});
chart.tooltip({
showMarkers: false
});
chart
.interval()
.adjust('stack')
.position('percent')
.color('type', (val) => colorMap[val])
.style({
stroke: 'white',
lineWidth: 1,
})
.label('type', {
offset: -5,
style: {
fill: 'white',
shadowBlur: 2,
shadowColor: 'rgba(0, 0, 0, .45)',
},
});
const ds2 = new DataSet();
const dv2 = ds2.createView();
dv2.source(this.data2).transform({
type: 'percent',
field: 'value',
dimension: 'name',
as: 'percent',
});
const outterView = chart.createView();
outterView.data(dv2.rows);
outterView.coordinate('theta', {
innerRadius: 0.5 / 0.8,
radius: 0.8,
});
outterView
.interval()
.adjust('stack')
.position('percent')
.color('type*name', (type, name) => colorMap[type])
.style({
stroke: 'white',
lineWidth: 1,
})
.label('name', {
offset: -10,
style: {
fill: 'white',
shadowBlur: 2,
shadowColor: 'rgba(0, 0, 0, .45)',
},
});
chart.interaction('element-active')
chart.render();
}
initChar3(): void {
const chart = new Chart({
container: 'container3',
autoFit: true,
height: 180,
});
chart.data(this.data3);
chart.scale({
value: {
max: 400,
min: 0,
alias: '产值(百万)',
},
});
chart.axis('type', {
title: null,
tickLine: null,
line: null,
});
chart.axis('value', {
label: null,
title: {
offset: 30,
style: {
fontSize: 12,
fontWeight: 300,
},
},
});
chart.legend(false);
chart.coordinate().transpose();
chart
.interval()
.position('type*value')
.size(16)
.label('value', {
style: {
fill: '#8d8d8d',
},
offset: 10,
});
chart.interaction('element-active');
chart.render();
}
initChar4(): void {
const chart = new Chart({
container: 'container4',
autoFit: true,
height: 300,
});
chart.data(this.data4);
chart.scale({
month: {
range: [0, 1],
},
temperature: {
nice: true,
},
});
chart.tooltip({
showCrosshairs: true,
shared: true,
});
chart.axis('temperature', {
label: {
formatter: (val) => {
return val + '个';
},
},
});
chart
.line()
.position('month*temperature')
.color('city')
.shape('smooth');
chart
.point()
.position('month*temperature')
.color('city')
.shape('circle')
.style({
stroke: '#fff',
lineWidth: 1,
});
chart.render();
}
async initMap() {
const response = await fetch(
'https://geo.datav.aliyun.com/areas_v2/bound/440000_full.json'
);
const result = await response.json();
console.log(result.features[0].properties);
const data = [];
for (let i = 0; i < result.features.length; i++) {
console.log(result.features[i].properties);
let ft = {
code: result.features[i].properties.adcode,
name: result.features[i].properties.name,
pop: result.features[i].properties.adcode
};
data.push(ft);
}
console.log(data);
const scene = new Scene({
id: 'map4',
map: new Mapbox({
center: [113.5502, 22.27],
pitch: 0,
style: 'blank',
zoom: 3,
minZoom: 3,
maxZoom: 10
})
});
scene.on('loaded', () => {
new ProvinceLayer(scene, {
data,
joinBy: ['adcode', 'code'],
adcode: ['440000'],
depth: 2,
label: {
field: 'NAME_CHN',
textAllowOverlap: false
},
fill: {
color: {
field: 'pop',
values: this.colors
}
},
popup: {
enable: true,
Html: props => {
return `<span>${props.NAME_CHN}:</span><span>${props.pop}</span>`;
}
}
});
});
}
setTime(): void {
this.now = new Date();
setInterval(() => {
this.now = new Date();
}, 1000);
}
constructor() { }
ngOnInit(): void {
this.initChar1();
this.initChar2();
this.initChar3();
this.initChar4();
this.initMap();
this.setTime();
}
}