图下示:
Grid Chart 啊啊啊啊
设计稿到手一脸懵逼,
但是!
甲方爸爸的需求怎么能轻易放弃呢~哼哼哈嘿
Chart.js- --想了3秒钟,还是默默放弃🤦♀️🤦♀️🤦♀️
D3 ----搜啊搜啊搜,就是木有咱们这样滴表格系统😭😭😭
给跪了~~~~~~
向大佬低头
用到的技术
Angular5+
Angular- Material
SCSS
父子信息传递 @Input()
mock数据
ngClass三元判断
*ngFor + index索引的运用
*ngStyle
ngOnInit运用
如何遍历数字次数
一、配置(angular + material)
ng new app-chart --style=scss//创建angular项目
ng g c chart //创建chart组件
npm install --save @angular/material @angular/cdk //安装angular-material
npm install --save @angular/animations //安装material对应的animations
二、思路
- 项目中我们从后台获取的数据mockData是readonly模式,我们前台通过JSON.parse+ JSON.stringfy对新变量newMockData进行操作
- 一共50个小格子,按照百分比划分分对应格子数,百分比都是整数那好说,不是整数就单独拎出来小数判断,小数最大的进一位,其他的舍弃小数,
- angular如果想遍历数字,那也要通过转换成数组的形式
- 比如数字是8,表示我们想遍历8次,可以通过for循环push 进去一个空数据8个值,这样就达到效果了(具体见ts文件的setBar方法)
- 通过ngClass进行三元判断给奇数相添加class,从而实现奇数时stick在下面,偶数在上面
- 如果占比小于一个格子的话,至少我们也要保证显示一个格子,在setBar里Math.floor如果是获取对应数据占比格子数是0就进1位,
- 如果传来的数据里有num=0的情况,在一开始获取mockData的时候就进行filter过滤掉(setbar)
-
如果所有的数据num都是0,就改成了默认全灰
grey
三、项目html + scss + ts代码
- 挂载子组件并传递数据
// app.component.html
<app-chart [mockData]="mockData"></app-chart>
- 父组件mock数据
// app.component.ts
mockData = {
propertion: [
{
name: 'Review required', // 每个bar的文本信息
color: '#F1A636', //各自对应的color和background-color
bars: [], // 计算,用来存放判断后最终分得的格子数
num: 30, // 各自的数量
total: 120, //总数量
},
{
name: 'Issue confirmed',
color: '#398F90',
bars: [],
num: 25,
total: 120,
},
{
name: 'No issue',
color: '#8DA656',
bars: [],
num: 65,
total: 120,
},
]
};
- 子组件input获取数据
// chart.component.ts
import { Component, OnInit, Input } from '@angular/core';
@Input() mockData;
- 子组件html
<div class="chart clearfix">
<!-- 当数据不为空 -->
<div class="not-empty" *ngIf="!isEmpty">
<!-- 奇数时添加class="odd" stick在下面 -->
<div class="item" [ngClass]="{'odd': index %2 !==0}" *ngFor="let item of newMockData; index as index">
<div class="stick" [ngStyle]="{'background-color':item.color}">
<div class="item-text" [ngStyle]="{'color':item.color}">{{item.name}} </div>
<div class="item-cent" [ngStyle]="{'color':item.color}">{{item.cent }}%
<span class="item-showCent">({{item.num }}/{{item.total}})</span>
</div>
</div>
<div class="bars clearfix">
<!-- 根据各自分得的小格子数,遍历显示在页面 -->
<div class="bar" *ngFor="let i of item.bars" [ngStyle]="{'background-color':item.color}"></div>
</div>
</div>
</div>
<div class="empty" *ngIf="isEmpty">
<div class="empty-bars bar" *ngFor="let i of emptyArray" [class.empty-bg]="isEmpty">
</div>
</div>
</div>
- 子组件scss样式
.chart {
display: inline-block;
height: 40px;
position: relative;
right: -40px;
top: 70px;
font-family: 'Roboto', Helvetica, Arial, sans-serif;
.clearfix:after {
content: '';
display: block;
visibility: hidden;
line-height: 0;
font-size: 0;
height: 0;
clear: none;
}
.bar {
width: 5px;
height: 40px;
float: left;
margin-right: 5px;
}
.item {
display: inline-block;
}
.item-text {
position: absolute;
top: 2px;
left: 10px;
white-space: nowrap; // 禁止字体换行
font-size: 18px;
}
.item-cent {
position: absolute;
top: 25px;
left: 10px;
white-space: nowrap;
font-size: 18px;
font-weight: 700;
}
.item-showCent {
font-weight: 300;
}
.stick {
width: 5px;
height: 60px;
position: absolute;
top: -59px;
}
.odd .stick {
top: 40px;
}
}
// 数据全空时的小格子颜色
.empty-bg {
background-color: rgba(40, 14, 0, 0.1);
}
- 子组件ts文件
export class ChartComponent implements OnInit {
@Input() mockData;
constructor() { }
totalBars = 50; // 一共50个小格子
newMockData: any;
realData = [];
isEmpty = false;
emptyArray = [];
ngOnInit() {
// this.mockData.propertion[0].num = 100;
this.cloneMockData();
this.setCent();
this.setBar();
}
cloneMockData() {
// 如果传来的参数为0就直接filter掉不进入后续数据操作
this.newMockData = JSON.parse(
JSON.stringify(this.mockData),
).propertion.filter(el => {
return el.num !== 0;
});
console.log('newmock data: ', this.newMockData);
}
// 获取每个值的百分比
setCent() {
this.newMockData.propertion.forEach(ele => {
ele.cent = ((ele.num / ele.total) * 100).toFixed(0);
});
}
setBar() {
// 如果数据是空的,就显示灰色条条
if (this.newMockData.length === 0) {
this.isEmpty = true;
for (let i = 0; i < this.totalBars; i++) {
this.emptyArray.push(1);
}
return;
} else {
// [10.5, 20.3, 19.2]
const barsAssign = this.newMockData.map(ele => {
return (ele.num / ele.total) * this.totalBars;
});
let bars = []; // [10, 20, 19];
const points = []; // [0.5, 0.3, 0.2];
let sum = 0;
barsAssign.forEach(ele => {
let integer = Math.floor(ele);
points.push(ele - integer);
if (integer === 0) {
integer = 1;
}
bars.push(integer);
sum += integer;
});
while (sum < this.totalBars) {
// 获取最大的小数位在points的索引值;进1位,
const maxPoint = Math.max(...points);
const index = points.indexOf(maxPoint);
bars[index] = bars[index] + 1; // [11, 20, 19];
points[index] = 0; // 最大points对应的bars的加1之后,就置为0,下次就不会再加。
sum++;
}
// angular想遍历数字 ===》要通过转成数组方式
bars = bars.map(ele => {
const temp = [];
for (let i = 0; i < ele; i++) {
temp.push(1);
}
return temp;
});
// 填充newMockData数据里的bar作为格子数
bars.forEach((bar, i) => {
this.newMockData[i].bars = bar;
});
console.log(this.newMockData);
}
}
最终效果
四、复用性
-
基本上只要后台给多少数据,都能按照这种模式自动往后添加表格
reusability
github帐号:slwzero
github项目链接戳此处鸭
网友评论
setBar() {
// [10.5, 20.3, 19.2]
const barsAssign = this.newMockData.propertion.map(ele => {
return ele.num / ele.total * this.totalBars;
});
let bars = []; // [10, 20, 19];
const points = []; // [0.5, 0.3, 0.2];
let sum = 0;
barsAssign.forEach(ele => {
const integer = Math.floor(ele);
points.push(ele - integer);
bars.push(integer);
sum += integer;
});
while (sum < this.totalBars) {
// 获取最大的小数位在points的索引值;进1位,
const maxPoint = Math.max(...points);
const index = points.indexOf(maxPoint);
bars[index] = bars[index] + 1; // [11, 20, 19];
sum++;
}
// angular想遍历数字 ===》要通过转成数组方式
bars = bars.map(ele => {
const temp = [];
for (let i = 0; i < ele; i++) {
temp.push(1);
}
return temp;
});
// 填充newMockData数据里的bar作为格子数
bars.forEach((bar, i) => {
this.newMockData.propertion[i].bars = bar;
});
}