import { ChangeDetectorRef, Component, ElementRef, NgZone, OnInit, ViewChild } from '@angular/core'; import { ModalHelper, _HttpClient } from '@delon/theme'; import { G2MiniAreaClickItem, G2MiniAreaData } from '@delon/chart/mini-area'; import { G2PieClickItem, G2PieComponent, G2PieData } from '@delon/chart/pie'; import { format } from 'date-fns'; import { DataService } from '../../services/data.service'; import { Chart, registerShape, Util } from '@antv/g2'; import { G2TimelineClickItem, G2TimelineData } from '@delon/chart/timeline'; import { CurrencyPipe } from '@angular/common'; import { LOGS } from '_mock'; import { G2CustomComponent } from '@delon/chart/custom'; import { G2BarData } from '@delon/chart/bar'; import { GeometryLabelCfg } from '@antv/g2/lib/interface'; @Component({ selector: 'app-datatable-dataindex', templateUrl: './dataindex.component.html', styleUrls: ['./dataindex.component.less'], providers: [CurrencyPipe] }) export class DatatableDataindexComponent implements OnInit { @ViewChild('g2custom', { static: false }) g2custom!: G2CustomComponent; @ViewChild('RegionalPerforman', { static: false }) RegionalPerforman!: G2CustomComponent; @ViewChild('BillDirectProportion', { static: false }) BillDirectProportion!: G2CustomComponent; @ViewChild('SaleProportion', { static: false }) SaleProportion!: G2CustomComponent; salesData: any[] = []; totalAdvanceDeposit: { totalAmount: string; list: G2MiniAreaData[] } = { totalAmount: '0', list: this.genData() }; totalPerformanceVolume: { totalAmount: string; list: G2MiniAreaData[] } = { totalAmount: '0', list: this.genData() }; totalFreight: { totalAmount: string; list: G2MiniAreaData[] } = { totalAmount: '0', list: this.genData() }; totalSurcharge: { totalAmount: string; list: G2MiniAreaData[] } = { totalAmount: '0', list: this.genData() }; billTypeDatas: any = [ { item: '货源单', count: 0, percent: 0 }, { item: '合同单', count: 0, percent: 0 } ]; regionalPerformanceCompletion: DataPerformanceTrendVO[] = []; constructor(private service: DataService, private currency: CurrencyPipe) {} ngOnInit(): void { this.initMiniAreaData(); this.initOthersData(); } private initMiniAreaData() { // 客户预存款总额 this.service.request(this.service.$api_total_advance_deposit).subscribe((res: DataTotalVO) => { if (res) { this.totalAdvanceDeposit = this.formatMiniAreaData(res); } }); // 业绩量总额 this.service.request(this.service.$api_total_performance_volume).subscribe((res: DataTotalVO) => { if (res) { this.totalPerformanceVolume = this.formatMiniAreaData(res); } }); // 司机应付总额 this.service.request(this.service.$api_total_freight).subscribe((res: DataTotalVO) => { if (res) { this.totalFreight = this.formatMiniAreaData(res); } }); // 业绩量总额 this.service.request(this.service.$api_total_surcharge).subscribe((res: DataTotalVO) => { if (res) { this.totalSurcharge = this.formatMiniAreaData(res); } }); } private initOthersData() { // 订单类型比例 this.service.request(this.service.$api_getBillTypeProportion).subscribe(res => { if (res) { this.billTypeDatas = this.formatCoordinateData(res); this.initBillChart(this.g2custom['el'].nativeElement as any); } }); // 大区业绩完成情况 this.service.request(this.service.$api_getBillAmount).subscribe((res: DataPerformanceTrendVO[]) => { if (res) { // this.regionalPerformanceCompletion = res.map(item => ({ ...item, time: new Date(item.time)?.getTime() })); // this.initRegionalPerformanceChart(this.RegionalPerforman['el'].nativeElement as any, this.regionalPerformanceCompletion); this.regionalPerformanceCompletion = this.formatBarData(res); this.initBiaxialChart(this.RegionalPerforman['el'].nativeElement as any, this.regionalPerformanceCompletion, { y1Title: '业绩量(万)', y2Title: '业绩完成率', y3Title: '同期业绩完成率' }); } }); // 订单类型比例 this.service.request(this.service.$api_getWayBillDirectProportion).subscribe(res => { if (res) { const billTypeDatas = this.formatCoordinateData(res.map((item: any) => ({ ...item, billType: item.wayBillType }))); this.initBillChart(this.BillDirectProportion['el'].nativeElement as any, billTypeDatas); } }); // 统计订单结算金额-趋势 this.service.request(this.service.$api_get_bill_payment_amount).subscribe(res => { if (res) { this.salesData = this.formatBarData(res); this.initBiaxialChart(this.SaleProportion['el'].nativeElement as any, this.salesData, { y1Title: '业绩量(万)', y2Title: '业绩完成率', y3Title: '同期业绩完成率' }); } }); } handleClick(data: G2MiniAreaClickItem): void { this.service.msgSrv.info(`${data.item.x} - ${data.item.y}`); } /** * 构建订单类型秘鲁图 * @param el */ private initBillChart(el: HTMLElement, datas?: any[]): void { const data = datas || this.billTypeDatas; const chart = new Chart({ container: el, autoFit: true, height: 400 }); // 新建一个 view 用来单独渲染Annotation const innerView = chart.createView(); chart.coordinate('theta', { radius: 0.65, innerRadius: 0.6 }); chart.data(data); chart.scale('percent', { formatter: val => { val = val * 100 + '%'; return val; } }); chart.tooltip(false); // 声明需要进行自定义图例字段: 'item' chart.legend('item', { position: 'bottom', // 配置图例显示位置 custom: true, // 关键字段,告诉 G2,要使用自定义的图例 items: data.map((obj: any, index: any) => { return { name: obj.item, // 对应 itemName value: obj.percent, // 对应 itemValue marker: { symbol: 'circle', // marker 的形状 style: { r: 5, // marker 图形半径 fill: index === 0 ? '#E60012' : '#F09896' // marker 颜色,使用默认颜色,同图形对应 } } // marker 配置 }; }), itemValue: { style: { fill: '#999' }, // 配置 itemValue 样式 formatter: (val: any) => `${val * 100}%` // 格式化 itemValue 内容 } }); chart .interval() .adjust('stack') .position('percent') .color('item', ['#E60012', '#F09896']) .style({ fillOpacity: 1, stroke: 'white', lineWidth: 8 }) .state({ active: { style: element => { const shape = element.shape; return { lineWidth: 1, stroke: 'white', strokeOpacity: shape.attr('fillOpacity') }; } } }) .label('percent', percent => { return { content: data => { return ` ${percent * 100}%`; }, style: { fontSize: 14 } }; }); innerView .annotation() .text({ position: ['50%', '50%'], content: data[0].item, style: { fontSize: 20, fill: '#8c8c8c', textAlign: 'center' }, offsetY: -20 }) .text({ position: ['50%', '50%'], content: data[0].count, style: { fontSize: 28, fill: '##000', textAlign: 'center' }, offsetY: 20 }); innerView.render(true); // 移除图例点击过滤交互 chart.removeInteraction('legend-filter'); chart.interaction('element-active'); chart.render(); // 监听 element 上状态的变化来动态更新 Annotation 信息 chart.on('element:statechange', (ev: any) => { const { state, stateStatus, element } = ev.gEvent.originalEvent; // 本示例只需要监听 active 的状态变化 if (state === 'active') { const data = element.getData(); if (stateStatus) { // 更新 Annotation updateAnnotation(data); } else { // 隐藏 Annotation // clearAnnotation(); } } }); // 绘制 annotation let lastItem: any; function updateAnnotation(data: any) { if (data.item !== lastItem) { innerView.annotation().clear(true); innerView .annotation() .text({ position: ['50%', '50%'], content: data.item, style: { fontSize: 20, fill: '#8c8c8c', textAlign: 'center' }, offsetY: -20 }) .text({ position: ['50%', '50%'], content: data.count, style: { fontSize: 28, fill: '##000', textAlign: 'center' }, offsetY: 20 }); innerView.render(true); lastItem = data.item; } } // 清空 annotation function clearAnnotation() { innerView.annotation().clear(true); innerView.render(true); lastItem = null; } } /** * 构建面积图 * @param el */ private initAreaMap(el: HTMLElement, datas: any[]): void { const chart = new Chart({ container: el, autoFit: true, height: 500 }); chart.data(datas); chart.scale('Data', { range: [0, 1], tickCount: 10, type: 'timeCat' }); chart.scale('sales', { nice: true }); chart.axis('Data', false); chart.axis('sales', false); chart.tooltip({ showCrosshairs: true }); // chart.annotation().dataMarker({ // position: ['2014-01', 1750], // top: true, // text: { // content: '因政策调整导致销量下滑', // style: { // fontSize: 13 // } // }, // line: { // length: 30 // } // }); chart.line().position('Data*sales'); chart.area().position('Data*sales'); chart.render(); } /** * 构建大区业绩完成情况柱折双轴图 * @param el * @param data */ private initRegionalPerformanceChart(el: HTMLElement, data: DataPerformanceTrendVO[]) { const chart = new Chart({ container: el, autoFit: true, height: 400 }); chart.data(data); chart.scale({ time: { alias: '日期', type: 'time' }, quantity: { alias: '金额', min: 0, sync: true, // 将 pv 字段数值同 time 字段数值进行同步 nice: true } }); chart.axis('quantity', { title: {} }); chart.tooltip({ showCrosshairs: true, shared: true }); // 声明需要进行自定义图例字段: 'item' chart.legend({ title: { text: '1111', spacing: 11 }, offsetY: 10, position: 'bottom', // 配置图例显示位置 custom: true, // 关键字段,告诉 G2,要使用自定义的图例 items: [ { name: '订单金额 (元)', // 对应 itemName value: '' // 对应 itemValue } ], itemValue: { style: { fill: '#999' } // 配置 itemValue 样式 } }); chart.line().position('time*quantity').color('#4FAAEB'); chart.render(); } /** * 构建业绩完成情况柱折双轴图 * @param el * @param data */ private initBiaxialChart( el: HTMLElement, data: any[], { y1Title, y2Title, y3Title }: { y1Title: string; y2Title: string; y3Title: string } ) { const chart = new Chart({ container: el, autoFit: true, height: 400 }); chart.data(data); // 设置坐标轴 chart.scale({ y1: { alias: y1Title, min: 0, max: 1000000 }, y2: { alias: y2Title, min: 0, max: 1, formatter: val => (val * 100).toFixed(0) + '%' }, y3: { alias: y3Title, min: 0, max: 1, formatter: val => (val * 100).toFixed(0) + '%' } }); // 设置 chart.legend({ custom: true, position: 'top-right', padding: [10, 0, 40, 0], items: [ { value: 'y1', name: y1Title, marker: { symbol: 'circle', style: { fill: '#E60012', r: 5, fontSize: 13 } } }, { value: 'y3', name: y3Title, marker: { symbol: 'circle', style: { fill: '#6CBFFF', r: 5, fontSize: 13 } } }, { value: 'y2', name: y2Title, marker: { symbol: 'circle', style: { fill: '#50D4AB', r: 5, fontSize: 13 } } } ] }); chart.axis('y2', { grid: null, title: null, label: { formatter: val => val } }); chart.axis('y3', false); chart.tooltip({ shared: true }); chart.interval().position('x*y1').color('#E60012'); chart .line() .position('x*y2') // .label('pre', val => ({ content: (val * 100).toFixed(0) + '%' })) .color('#6CBFFF') .size(3); chart.point().position('x*y2').color('#6CBFFF').size(3).shape('circle'); chart .line() .position('x*y3') // .label('pre2', val => ({ content: (val * 100).toFixed(0) + '%' })) .color('#50D4AB') .size(3); chart.point().position('x*y3').color('#50D4AB').size(3).shape('circle'); chart.interaction('active-region'); chart.removeInteraction('legend-filter'); // 自定义图例,移除默认的分类图例筛选交互 chart.render(); } private formatMiniAreaData(data: DataTotalVO) { return { totalAmount: `${this.currency.transform(data.totalAmount || 0)}万`, list: data.list?.map(item => ({ x: item.time, y: item.quantity })) }; } /** * 初始化饼图数据格式 * @param data * @returns */ private formatCoordinateData(data: DataBillTypeProportion[]): any[] { const total = data.map(item => item.quantity).reduce((pre, next) => pre + next); const rs: any[] = []; data.forEach(item => { rs.push({ item: (BILLTYPE as any)[item.billType], count: item.quantity, percent: Number((item.quantity / total).toFixed(2)) }); }); return rs; } private formatBarData(data: DataPerformanceTrendVO[]): any[] { return data.map(item => ({ x: item.time, y1: item.quantity, y2: Math.floor(Math.random() * 100) / 100, y3: Math.floor(Math.random() * 100) / 100 })); } private genData(): G2MiniAreaData[] { const beginDay = new Date().getTime(); const res: G2MiniAreaData[] = []; for (let i = 0; i < 20; i += 1) { res.push({ x: format(new Date(beginDay + 1000 * 60 * 60 * 24 * i), 'yyyy-MM-dd'), y: Math.floor(Math.random() * 1) }); } return res; } } interface DataTotalVO { totalAmount: number; list: DataPerformanceTrendVO[]; } interface DataPerformanceTrendVO { quantity: number; time: number; } interface DataBillTypeProportion { billType: BILLTYPE; quantity: number; } enum BILLTYPE { 'HY' = '货源单', 'HT' = '合同单', 'ZF' = '直付', 'CDZ' = '车队长' }