import { IParam, IChartControllerParams, ICtrlActionResult, } from '@/core/interface'; import { IChartAbility } from '@/core/interface/widgets/ability'; import { IChartController } from '@/core/interface/widgets/controller'; import { ChartActionType } from '@/core/interface/widgets/event'; import { IChartStore } from '@/core/interface/widgets/store'; import { ControlVOBase } from '@/core/modules'; import ChartService from '@/core/modules/ctrl-service/chart-service'; import { createUUID, deepCopy, deepObjectMerge } from '@/core/utils'; import { init } from 'echarts'; import { compareAsc, format as formatFns, getMonth, getQuarter, getUnixTime, getWeek, getYear, max, min, parseISO, } from 'date-fns'; import { MDCtrlController } from './md-ctrl-controller'; /** * 图表控制器 * * @export * @class ChartController * @extends {(MDCtrlController)} * @implements {IChartController} * @template A */ export class ChartController extends MDCtrlController implements IChartController { /** * Creates an instance of ChartController. * @param {IChartControllerParams} params * @memberof ChartController */ public constructor( params: IChartControllerParams ) { super(params); this.ctrlInit(params); } /** * 图表部件服务 * * @type {ChartService} * @memberof GridController */ declare ctrlService: ChartService; /** * echarts图表对象 * * @memberof ChartController */ public myChart: any; /** * 前端组装的序列模型 * * @memberof ChartController */ public seriesModel: IParam = {}; /** * 初始图表所需参数 * * @memberof ChartController */ public chartOption: IParam = {}; /** * 图表自定义参数集合 * * @memberof ChartController */ public chartUserParams: IParam = {}; /** * 图表基础动态模型 * * @memberof ChartController */ public chartBaseOPtion: IParam = {}; /** * 图表值属性 * * @memberof ChartController */ public chartValueName = ''; /** * 图表分类属性 * * @memberof ChartController */ public chartCatalogName = ''; /** * 处理部件初始化 * * @protected * @param {IChartControllerParams} params * @memberof ChartController */ protected processCtrlInit( params: IChartControllerParams ) { super.processCtrlInit(params); Object.assign(this.store, { isNoData: false, chartId: createUUID(), chartRenderOption: {}, }); this.initSeriesModel(); this.initChartOption(); this.initChartUserParams(); this.initChartBaseOPtion(); } /** * 图表数据加载 * * @param {IParam} [opts={}] * @param {boolean} [pageReset=false] * @return {*} {Promise} * @memberof ChartController */ public async load(opts: IParam = {}): Promise { const res = await super.load(opts); const _this = this; this.transformToBasicChartSetData(this.store.data, (codelist: any) => { _this.drawCharts(); }); return res; } /** * 处理加载数据变更 * * @protected * @param {IParam[]} data * @memberof ChartController */ protected handleLoadDataChange(data: IParam[]): void { this.store.data = data; } /** * 绘制图表 * * @returns {*} * @memberof ChartController */ public drawCharts() { if (!this.myChart) { const element: any = document.getElementById(this.store.chartId); if (element) { this.myChart = init(element); } } //判断刷新时dom是否存在 if ( this.myChart && !Object.is(this.myChart._dom.offsetHeight, 0) && !Object.is(this.myChart._dom.offsetWidth, 0) ) { const _chartOption = this.handleChartOPtion(); this.store.chartRenderOption = { ..._chartOption }; if (this.myChart) { this.myChart.setOption(_chartOption); this.onChartEvents(); this.handleDefaultSelect(); this.myChart.resize(); } } } /** * 图表事件监听 * * @memberof ChartController */ public onChartEvents() { this.myChart?.on('click', (e: any) => { this.onChartClick(e); }); this.myChart?.on('selectchanged', (e: any) => { if (this.selectFirstDefault) { if (e && e.fromActionPayload) { const _event = { seriesId: e.fromActionPayload.seriesId, data: e.fromActionPayload.curData, name: e.fromActionPayload.seriesId, }; this.onChartClick(_event); } } }); } /** * 图表单击事件 * * @memberof ChartControlBase */ public onChartClick(event: any) { if (!event || !event.name) { return; } const data: any = event.data; Object.assign(data, { _chartName: event.seriesId }); this.store.selections = [data]; this.emit('selectionChange', [data]); } /** * 处理默认选中 * * @memberof ChartController */ public handleDefaultSelect() { if (this.selectFirstDefault) { const options = this.handleChartOPtion(); const selectSeriesId = this.model.serieses?.[0]?.name || null; const curData = options?.dataset[0]?.source?.[0] || undefined; if (selectSeriesId && curData) { this.myChart?.dispatchAction({ type: 'select', seriesId: selectSeriesId.toLowerCase(), dataIndex: 0, curData: curData, }); } } } /** * 处理图表参数 * * @memberof ChartController */ public handleChartOPtion() { const _chartOption: any = deepCopy(this.chartOption); if (Object.keys(this.seriesModel).length > 0) { const tempDataSourceMap: Map = new Map(); for (let i = 0; i < Object.keys(this.seriesModel).length; i++) { Object.values(this.seriesModel).forEach((seriesvalue: any) => { if (seriesvalue.seriesIndex === i) { tempDataSourceMap.set(seriesvalue.name, seriesvalue.data); } }); } if (tempDataSourceMap.size > 0) { tempDataSourceMap.forEach((item: any) => { _chartOption.dataset.push({ source: item }); }); } Object.keys(this.seriesModel).forEach((seriesName: string) => { if (_chartOption && _chartOption.series.length > 0) { _chartOption.series.forEach((item: any) => { if ( this.seriesModel[seriesName].ecxObject && Object.is(seriesName, item.id) ) { item = deepObjectMerge( item, this.seriesModel[seriesName].ecxObject ); } if ( this.seriesModel[seriesName].baseOption && Object.keys(this.seriesModel[seriesName].baseOption).length > 0 && Object.is(seriesName, item.id) ) { item = deepObjectMerge( item, this.seriesModel[seriesName].baseOption ); } if ( this.seriesModel[seriesName].ecObject && Object.is(seriesName, item.id) ) { item = deepObjectMerge( item, this.seriesModel[seriesName].ecObject ); } }); } //设置多序列 const tempSeries: any = this.seriesModel[seriesName]; const returnIndex: number = _chartOption.series.findIndex( (item: any) => { return Object.is(item.id, seriesName); } ); if (tempSeries && Object.is(tempSeries.type, 'gauge')) { _chartOption.series.splice(returnIndex, 1); const maxValue: number = this.calcSourceMaxValue( _chartOption.dataset[0].source ); const temSeries = { type: 'gauge', title: { fontSize: 14, }, progress: { show: true, overlap: false, roundCap: true, }, max: maxValue, detail: { width: 40, height: 14, fontSize: 14, color: '#fff', backgroundColor: 'auto', borderRadius: 3, formatter: '{value}', }, data: this.transformToChartSeriesData( _chartOption.dataset[0].source, 'gauge' ), }; _chartOption.series.push(temSeries); } else if (tempSeries && Object.is(tempSeries.type, 'radar')) { const maxValue: number = this.calcSourceMaxValue( _chartOption.dataset[0].source ); _chartOption.radar.indicator?.forEach((item: any) => { item.max = item.max ? item.max : maxValue; }); _chartOption.series[returnIndex].data = this.transformToChartSeriesData( _chartOption.dataset[0].source, 'radar', _chartOption.radar.indicator ); } else if (tempSeries && Object.is(tempSeries.type, 'candlestick')) { _chartOption.series[returnIndex].data = this.transformToChartSeriesData( _chartOption.dataset[0].source, 'candlestick' ); } else if ( tempSeries && tempSeries.seriesIdField && tempSeries.seriesValues.length > 0 ) { const series = _chartOption.series[returnIndex]; _chartOption.series.splice(returnIndex, 1); delete series.id; tempSeries.seriesValues.forEach((seriesvalueItem: any) => { const tempSeriesTemp: any = deepCopy(tempSeries.seriesTemp); Object.assign(tempSeriesTemp, series); tempSeriesTemp.name = tempSeries.seriesMap[seriesvalueItem]; tempSeriesTemp.datasetIndex = tempSeries.seriesIndex; tempSeriesTemp.encode = { x: tempSeries.categorField, y: `${seriesvalueItem}`, }; _chartOption.series.push(tempSeriesTemp); }); } }); } if (Object.keys(this.chartBaseOPtion).length > 0) { Object.assign(_chartOption, this.chartBaseOPtion); } if (Object.keys(this.chartUserParams).length > 0) { Object.assign(_chartOption, this.chartUserParams); } return _chartOption; } /** * * 计算数据集最大数 * * @param {Array} data 传入数据 * @memberof ChartController */ public calcSourceMaxValue(source: any[]) { const data: any[] = []; source.forEach((item: any) => { if (item.data) { data.push(item.data); } else { const itemData = []; for (const key in item) { if (!isNaN(item[key])) { itemData.push(item[key]); } } data.push(Math.max(...itemData)); } }); return Math.max(...data); } /** * * 1.整合数据集数据到data中,不走数据集 * * @param {Array} data 传入数据 * @param {Array} series chart类型 * @memberof ChartControlBase */ public transformToChartSeriesData( source: any[], series: string, indicator?: any[] ) { if (Object.is(series, 'gauge')) { const seriesData: any[] = []; const offsetLength: number = 100 / (source.length - 1); source.forEach((sourceItem: any, index: number) => { const data = { name: sourceItem[this.chartCatalogName], value: sourceItem[this.chartValueName], title: { offsetCenter: [index * offsetLength - 50 + '%', '80%'], }, detail: { offsetCenter: [index * offsetLength - 50 + '%', '95%'], }, }; seriesData.push(data); }); return seriesData; } else if (Object.is(series, 'radar')) { if (!indicator || indicator.length == 0) { console.log( App.ts( 'widget.chart.noindicator', '雷达图indicator不存在,无法转换数据集!' ) ); return; } const seriesData: any[] = []; source.forEach((sourceItem: any) => { const name = sourceItem.type; const data: any[] = []; indicator.forEach((item: any) => { data.push(sourceItem[item.name]); }); seriesData.push({ name, value: data }); }); return seriesData; } else if (Object.is(series, 'candlestick')) { const seriesData: any[] = []; source.forEach((sourceItem: any) => { const name = sourceItem.type; seriesData.push({ name, value: sourceItem[this.chartValueName] }); }); return seriesData; } } /** * 获取图表所需代码表 * * @memberof ChartController */ public getChartAllCodeList(): Promise { return new Promise((resolve: any, reject: any) => { const codeListMap: Map = new Map(); if (Object.values(this.seriesModel).length > 0) { let tempFlag = true; Object.values(this.seriesModel).forEach((singleSeries: any) => { if ( singleSeries.dataSetFields && singleSeries.dataSetFields.length > 0 ) { const promiseArray: Array = []; const promiseKeyArray: Array = []; singleSeries.dataSetFields.forEach( (singleDataSetField: any, index: any) => { if (singleDataSetField.codelist) { tempFlag = false; if (!codeListMap.get(singleDataSetField.codelist.codeName)) { promiseArray.push( this.getCodeList(singleDataSetField.codelist) ); promiseKeyArray.push(singleDataSetField.codelist.codeName); Promise.all(promiseArray).then((result: any) => { if (result && result.length > 0) { result.forEach((codeList: any) => { const tempCodeListMap: Map = new Map(); if (codeList.length > 0) { codeList.forEach((codeListItem: any) => { tempCodeListMap.set( codeListItem.value, codeListItem.text ); }); } codeListMap.set( singleDataSetField.codelist.codeName, tempCodeListMap ); }); resolve(codeListMap); } }); } } } ); } }); if (tempFlag) { resolve(codeListMap); } } else { resolve(codeListMap); } }); } /** * 获取代码表 * * @param {*} codeListObject 代码表对象 * @return {*} {Promise} * @memberof ChartController */ public getCodeList(codeListObject: any): Promise { return new Promise((resolve: any, reject: any) => { App.getAppCodeListHelper() .getCodeListItems( codeListObject.codeListTag, this.store.context, this.store.viewParams ) .then((res: any) => { resolve(res.data); }) .catch((error: any) => { console.log( `----${codeListObject.codeName}----${App.ts( 'common.generic.codenotexist', '代码表不存在' )}` ); }); }); } /** * 实体数据集转化为图表数据集 * * 1.获取图表所有代码表值 * 2.查询集合映射图表数据集 * 3.补全图表数据集 * 4.图表数据集分组求和 * 5.排序图表数据集 * * @param {*} data 实体数据集 * @param {Function} callback 回调 * @memberof ChartController */ public async transformToBasicChartSetData(data: any, callback: Function) { if (!data || !Array.isArray(data) || data.length === 0) { this.store.isNoData = true; if (this.myChart) { this.myChart.dispose(); this.myChart = undefined; } return; } this.store.isNoData = false; //获取代码表值 const allCodeList: any = await this.getChartAllCodeList(); if (Object.values(this.seriesModel).length > 0) { Object.values(this.seriesModel).forEach( (singleSeries: any, index: number) => { // 值属性为srfcount设置{srfcount:1}到data const valueField = singleSeries.dataSetFields.find( (datasetField: any) => { return datasetField.name === singleSeries.valueField; } ); if ( valueField && valueField.name && Object.is(valueField.name, 'srfcount') ) { data.forEach((singleData: any) => { Object.assign(singleData, { srfcount: 1 }); }); } // 分组属性 const groupField = singleSeries.dataSetFields.find( (datasetField: any) => { return datasetField.name === singleSeries.categorField; } ); const tempChartSetData: Array = []; let tempSeriesValues: Map = new Map(); data.forEach((item: any) => { const tempChartSetDataItem: any = {}; // 序列属性不存在 if (!singleSeries.seriesIdField) { Object.assign(tempChartSetDataItem, { name: singleSeries.name }); if ( singleSeries.dataSetFields && singleSeries.dataSetFields.length > 0 ) { singleSeries.dataSetFields.forEach( (singleDataSetField: any) => { this.handleSingleDataSetField( item, singleDataSetField, allCodeList, tempChartSetDataItem, groupField ); } ); } } else { // 序列属性存在时 // 序列代码表存在时,翻译tempSeriesValues的键值对 if (singleSeries.seriesCodeList) { const seriesCodeList: Map = allCodeList.get( singleSeries.seriesCodeList.tag ); const tempSeriesValueItem = tempSeriesValues.get( seriesCodeList.get(item[singleSeries.seriesIdField]) ); if (!tempSeriesValueItem) { tempSeriesValues.set( seriesCodeList.get(item[singleSeries.seriesIdField]), seriesCodeList.get(item[singleSeries.seriesNameField]) ); } } else { const tempSeriesValueItem = tempSeriesValues.get( item[singleSeries.seriesIdField] ); if (!tempSeriesValueItem) { tempSeriesValues.set( item[singleSeries.seriesIdField], item[singleSeries.seriesNameField] ); } } Object.assign(tempChartSetDataItem, { name: item[singleSeries.seriesIdField], }); if ( singleSeries.dataSetFields && singleSeries.dataSetFields.length > 0 ) { singleSeries.dataSetFields.forEach( (singleDataSetField: any) => { this.handleSingleDataSetField( item, singleDataSetField, allCodeList, tempChartSetDataItem, groupField ); } ); } } tempChartSetData.push(tempChartSetDataItem); }); // 补全数据集合 this.completeDataSet(tempChartSetData, singleSeries, allCodeList); // 序列代码表存在时,补全序列 if (singleSeries.seriesCodeList) { const seriesCodeList: Map = allCodeList.get( singleSeries.seriesCodeList.tag ); tempSeriesValues = new Map(); seriesCodeList?.forEach((item: any) => { tempSeriesValues.set(item, item); }); } singleSeries.seriesValues = [...tempSeriesValues.keys()]; const tempSeriesMapObj: any = {}; tempSeriesValues.forEach((value: any, key: any) => { tempSeriesMapObj[key] = value; }); singleSeries.seriesMap = tempSeriesMapObj; const callbackFunction: any = index === Object.values(this.seriesModel).length - 1 ? callback : null; // 往图表序列里塞data this.transformToChartSeriesDataSet( tempChartSetData, singleSeries, callbackFunction, allCodeList ); } ); } } /** * 处理单个属性 * * @param {*} input 输入值 * @param {*} field 属性值 * @param {*} allCodeList 所有代码表 * @param {*} result 结果值 * @param {*} groupField 分组属性 * * @memberof ChartController */ public handleSingleDataSetField( input: any, field: any, allCodeList: any, result: any, groupField: any ) { const tempFieldObj: any = {}; //存在代码表的情况(自动转化值) if (field.codelist) { //获取代码表 const curCodeList: Map = allCodeList.get( field.codelist.codeName ); tempFieldObj[field.name] = curCodeList.get(input[field.name]); tempFieldObj[field.name + '_srfvalue'] = input[field.name]; } else { // 不存在代码表的情况 if (groupField && Object.is(groupField.name, field.name)) { if (parseISO(input[field.name])) { const date = parseISO(input[field.name]); if (Object.is(groupField.groupMode, 'YEAR')) { tempFieldObj[field.name] = getYear(date) + ''; } else if (Object.is(groupField.groupMode, 'QUARTER')) { tempFieldObj[field.name] = getYear(date) + '-' + getQuarter(date); } else if (Object.is(groupField.groupMode, 'MONTH')) { tempFieldObj[field.name] = getYear(date) + '-' + getMonth(date); } else if (Object.is(groupField.groupMode, 'YEARWEEK')) { tempFieldObj[field.name] = getYear(date) + '-' + getWeek(date); } else if (Object.is(groupField.groupMode, 'DAY')) { tempFieldObj[field.name] = formatFns(date, 'yyyy-MM-dd'); } else { tempFieldObj[field.name] = input[field.name]; } } else { tempFieldObj[field.name] = input[field.name]; } } else { if (!field.isCodeItem) { tempFieldObj[field.name] = input[field.name]; } } } Object.assign(result, tempFieldObj); } /** * 补全数据集 * * @param {Array} data 传入数据 * @param {Array} item 单个序列 * @param {Array} allCodeList 所有的代码表 * * @memberof ChartController */ public completeDataSet(data: any, item: any, allCodeList: any) { // 分组属性 const groupField = item.dataSetFields.find((datasetField: any) => { return datasetField.name === item.categorField; }); if (!groupField || Object.is(groupField.groupMode, '')) { return; } //分组模式为代码表(补值) if (Object.is(groupField.groupMode, 'CODELIST')) { this.completeCodeList(data, item, allCodeList); } //分组模式为年/季度/月份(最大值,最小值,分组,补值) if ( Object.is(groupField.groupMode, 'YEAR') || Object.is(groupField.groupMode, 'QUARTER') || Object.is(groupField.groupMode, 'MONTH') || Object.is(groupField.groupMode, 'YEARWEEK') || Object.is(groupField.groupMode, 'DAY') ) { this.handleTimeData(data, item, allCodeList, groupField); } } /** * 补全代码表 * * @param {Array} data 传入数据 * @param {Array} item 单个序列 * @param {Array} allCodeList 所有的代码表 * * @memberof ChartController */ public completeCodeList(data: any, item: any, allCodeList: any) { const groupField = item.dataSetFields.find((datasetField: any) => { return datasetField.name === item.categorField; }); if (!groupField.codelist) { return; } const valueField = item.dataSetFields.find((datasetField: any) => { return datasetField.name === item.valueField; }); const curCodeList: Map = allCodeList.get( groupField.codelist.codeName ); // 对分类实现分组 const tempGroupData: Map = new Map(); data.forEach((item: any) => { const tempGroupItem: any = tempGroupData.get( item[groupField.name + '_srfvalue'] ); if (!tempGroupItem) { tempGroupData.set(item[groupField.name + '_srfvalue'], item); } }); if (curCodeList.size !== tempGroupData.size) { curCodeList.forEach((text: any, value: any) => { if (!tempGroupData.get(value)) { const copyTemp: any = deepCopy(data[0]); const curObj: any = {}; curObj[groupField.name + '_srfvalue'] = value; curObj[groupField.name] = text; curObj[valueField.name] = 0; Object.assign(copyTemp, curObj); data.push(copyTemp); } }); } } /** * 补全时间类型数据集 * * @param {Array} data 传入数据 * @param {Array} item 单个序列 * @param {Array} allCodeList 所有的代码表 * @param {Array} groupField 分组属性 * * @memberof ChartController */ public handleTimeData( data: any, item: any, allCodeList: any, groupField: any ) { const valueField = item.dataSetFields.find((datasetField: any) => { return datasetField.name === item.valueField; }); const groupMode: string = groupField.groupMode; // 排序数据,找到最大值、最小值 const tempTimeArray: Array = []; if (data && data.length > 0) { data.forEach((dataItem: any) => { // 判断时间类型是否为空,为空不处理 if (dataItem[groupField.name]) { tempTimeArray.push(parseISO(dataItem[groupField.name])); } }); } const maxTime: Date = max(tempTimeArray); const minTime: Date = min(tempTimeArray); const timeFragmentArray: Array = []; const tempGroupData: Map = new Map(); // 时间分段 //groupMode为"YEAR" if (Object.is(groupMode, 'YEAR')) { timeFragmentArray.push(getYear(minTime) + ''); } //groupMode为"QUARTER" if (Object.is(groupMode, 'QUARTER')) { timeFragmentArray.push(getYear(minTime) + '-' + getQuarter(minTime)); } //groupMode为"MONTH" if (Object.is(groupMode, 'MONTH')) { timeFragmentArray.push(getYear(minTime) + '-' + getMonth(minTime)); } //groupMode为"YEARWEEK" if (Object.is(groupMode, 'YEARWEEK')) { timeFragmentArray.push(getYear(minTime) + '-' + getWeek(minTime)); } //groupMode为"DAY" if (Object.is(groupMode, 'DAY')) { timeFragmentArray.push(formatFns(minTime, 'yyyy-MM-dd')); } data.forEach((item: any) => { const tempKeyStr: string = item[groupField.name]; const tempGroupItem: any = tempGroupData.get(tempKeyStr); if (!tempGroupItem) { tempGroupData.set(tempKeyStr, item); } }); timeFragmentArray.forEach((timeFragment: string) => { if (!tempGroupData.get(timeFragment)) { const copyTemp: any = deepCopy(data[0]); const curObj: any = {}; curObj[groupField.name] = timeFragment; curObj[valueField.name] = 0; Object.assign(copyTemp, curObj); data.push(copyTemp); } }); } /** * 获取最大值最小值 * * @param {Array} tempTimeArray 传入数据 * * @memberof ChartController */ public getRangeData(tempTimeArray: Array) { tempTimeArray.sort((a: Date, b: Date) => { // 若 a 大于 b ,返回 1;反之返回 -1,如果相等则返回 0 return compareAsc(a, b); }); return tempTimeArray; } /** * 构建图表序列数据集合 * * 1.分组求和 * 2.排序求和数组 * * @param {Array} data 传入数据 * @param {Array} item 单个序列 * @param {Array} callback 回调 * @param {*} allCodeList 所有代码表 * * @memberof ChartController */ public transformToChartSeriesDataSet( data: any, item: any, callback: Function, allCodeList: any ): any { if (item.seriesIdField) { // 多序列 const groupField = item.dataSetFields.filter((datasetField: any) => { return datasetField.name === item.categorField; }); const tempGroupField: Array = groupField.map((item: any) => { return item.name; }); const seriesField = item.dataSetFields.filter((datasetField: any) => { return datasetField.name === item.seriesIdField; }); const tempSeriesField: Array = seriesField.map((item: any) => { return item.name; }); const valueField = item.dataSetFields.filter((datasetField: any) => { return datasetField.name === item.valueField; }); const tempValueField: Array = valueField.map((item: any) => { return item.name; }); item.data = this.groupAndAdd( tempGroupField, tempSeriesField, tempValueField, data, item, groupField, allCodeList ); } else { //单序列 const groupField = item.dataSetFields.filter((datasetField: any) => { return datasetField.name === item.categorField; }); const tempGroupField: Array = groupField.map((item: any) => { return item.name; }); const valueField = item.dataSetFields.filter((datasetField: any) => { return datasetField.name === item.valueField; }); const tempValueField: Array = valueField.map((item: any) => { return item.name; }); item.data = this.groupAndAdd( tempGroupField, [], tempValueField, data, item, groupField, allCodeList ); } if (callback && callback instanceof Function) { callback(allCodeList); } } /** * 分组和求和 * * @param {Array} groupField 分组属性 * @param {Array} seriesField 序列属性 * @param {Array} valueField 值属性 * @param {*} data 传入数据 * @param {*} item 单个序列 * @param {*} groupFieldModel 分组属性模型 * @param {*} allCodeList 所有代码表 * @return {*} * @memberof ChartController */ public groupAndAdd( groupField: Array, seriesField: Array, valueField: Array, data: any, item: any, groupFieldModel: any, allCodeList: any ) { const tempMap: Map = new Map(); // let groupMode: string = groupFieldModel[0].groupMode; let groupKeyStr = ''; data.forEach((item: any) => { const tempGroupField: string = groupField[0]; groupKeyStr = item[tempGroupField]; const tempMapItem: any = tempMap.get(groupKeyStr); if (tempMapItem) { tempMapItem.push(item); tempMap.set(groupKeyStr, tempMapItem); } else { tempMap.set(groupKeyStr, [item]); } }); // 处理多序列 if (seriesField.length > 0 && tempMap.size > 0) { const tempSeriesField: string = seriesField[0]; tempMap.forEach((item: any, key: string) => { const tempItemMap: Map = new Map(); item.forEach((singleItem: any) => { const seriesValueArray: any = tempItemMap.get( singleItem[tempSeriesField] ); if (seriesValueArray) { seriesValueArray.push(singleItem); tempItemMap.set(singleItem[tempSeriesField], seriesValueArray); } else { tempItemMap.set(singleItem[tempSeriesField], [singleItem]); } }); tempMap.set(key, tempItemMap); }); } let returnArray: Array = []; if (seriesField.length == 0) { //单序列 tempMap.forEach((tempItem: any) => { if (tempItem.length > 0) { const curObject: any = {}; const valueResult: any = {}; let categorResult: any; tempItem.forEach((singleItem: any) => { categorResult = singleItem[groupField[0]]; valueResult[valueField[0]] = valueResult[valueField[0]] ? singleItem[valueField[0]] ? valueResult[valueField[0]] + singleItem[valueField[0]] : valueResult[valueField[0]] : singleItem[valueField[0]]; item.dataSetFields.forEach((dataSetField: any) => { // 只读不合并数据(扩展值属性4) if (dataSetField.isReadOnly) { valueResult[dataSetField.name] = singleItem[dataSetField.name]; } else { if ( !Object.is(dataSetField.name, groupField[0]) && !Object.is(dataSetField.name, valueField[0]) ) { valueResult[dataSetField.name] = valueResult[ dataSetField.name ] ? valueResult[dataSetField.name] + singleItem[dataSetField.name] : singleItem[dataSetField.name]; } } }); }); Object.defineProperty(curObject, groupField[0], { value: categorResult, writable: true, enumerable: true, configurable: true, }); for (const value in valueResult) { Object.defineProperty(curObject, value, { value: valueResult[value], writable: true, enumerable: true, configurable: true, }); } returnArray.push(curObject); } }); } else { // 多序列 const seriesValuesArray: Array = item.seriesValues; tempMap.forEach((groupItem: any, groupKey: string) => { //求和 const curObject: any = {}; Object.defineProperty(curObject, groupField[0], { value: groupKey, writable: true, enumerable: true, configurable: true, }); seriesValuesArray.forEach((seriesValueItem: any) => { Object.defineProperty(curObject, seriesValueItem, { value: 0, writable: true, enumerable: true, configurable: true, }); }); groupItem.forEach((seriesItem: any, seriesKey: string) => { let seriesNum = 0; seriesItem.forEach((dataItem: any) => { seriesNum += dataItem[valueField[0]]; }); curObject[seriesKey] = seriesNum; }); returnArray.push(curObject); }); } // 补全空白分类 if (returnArray.length > 0) { const emptyText = groupFieldModel[0] && groupFieldModel[0].codeList ? groupFieldModel[0].codeList.emptytext : App.ts("widget.chart.undefined","未定义"); returnArray.forEach((item: any) => { if (!item[groupField[0]]) { item[groupField[0]] = emptyText; } }); } returnArray = this.sortReturnArray( returnArray, groupFieldModel, allCodeList ); // 雷达图数据格式处理 if (Object.is(item.type, 'radar') && returnArray.length > 0) { const tempReturnArray: Array = []; const seriesValues: Array = item.seriesValues; if (seriesValues && seriesValues.length > 0) { seriesValues.forEach((singleSeriesName: any) => { const singleSeriesObj: any = {}; returnArray.forEach((item: any) => { Object.assign(singleSeriesObj, { [item[groupField[0]]]: item[singleSeriesName], }); }); Object.assign(singleSeriesObj, { type: singleSeriesName }); tempReturnArray.push(singleSeriesObj); }); } returnArray = tempReturnArray; } return returnArray; } /** * 排序数组 * * @param {Array} arr 传入数组 * @param {*} groupField 分组属性 * @param {*} allCodeList 所有代码表 * * @memberof ChartController */ public sortReturnArray(arr: Array, groupField: any, allCodeList: any) { let returnArray: Array = []; // 分组属性有代码表的情况(最后执行) if (groupField[0]) { if (groupField[0].codelist) { const curCodeList: Map = allCodeList.get( groupField[0].codelist.codeName ); curCodeList.forEach((codelist: any) => { arr.forEach((item: any) => { if (Object.is(item[groupField[0].name], codelist)) { returnArray.push(item); item.hasused = true; } }); }); arr.forEach((item: any, index: number) => { if (!item.hasused) { returnArray.push(item); } }); returnArray.forEach((item: any) => { delete item.hasused; }); } else { // 分组为年份 if (Object.is(groupField[0].groupMode, 'YEAR')) { returnArray = arr.sort((a: any, b: any) => { return ( Number(a[groupField[0].name]) - Number(b[groupField[0].name]) ); }); } else if (Object.is(groupField[0].groupMode, 'QUARTER')) { returnArray = this.handleSortGroupData( arr, groupField, '季度' as string ); } else if (Object.is(groupField[0].groupMode, 'MONTH')) { returnArray = this.handleSortGroupData( arr, groupField, '月' as string ); } else if (Object.is(groupField[0].groupMode, 'YEARWEEK')) { returnArray = this.handleSortGroupData( arr, groupField, '周' as string ); } else if (Object.is(groupField[0].groupMode, 'DAY')) { returnArray = arr.sort((a: any, b: any) => { if ( parseISO(a[groupField[0].name]) && parseISO(b[groupField[0].name]) ) { return ( getUnixTime(parseISO(a[groupField[0].name])) - getUnixTime(parseISO(b[groupField[0].name])) ); } else { return ( getUnixTime(a[groupField[0].name]) - getUnixTime(b[groupField[0].name]) ); } }); } else { const groupFieldName: string = groupField[0].name; let isConvert = true; arr.forEach((item: any) => { if (isNaN(item[groupFieldName])) { isConvert = false; } }); if (isConvert) { returnArray = arr.sort((a: any, b: any) => { return a[groupFieldName] - b[groupFieldName]; }); } else { returnArray = arr; } } } } else { console.warn(App.ts("widget.chart.nogroupfield","没有分组属性")); } return returnArray; } /** * 排序分组模式下的数据 * * @param {Array} arr 传入数据 * @param {Array} groupField 分组属性 * @param {Array} label label标签 * * @memberof ChartController */ public handleSortGroupData(arr: Array, groupField: any, label: string) { arr.forEach((item: any) => { const sortFieldValue: Array = item[groupField[0].name].split('-'); Object.assign(item, { sortField: Number(sortFieldValue[0]) * 10000 + Number(sortFieldValue[1]), }); if (Object.is(label, '月')) { item[groupField[0].name] = sortFieldValue[0] + ('年' as string) + (Number(sortFieldValue[1]) + 1) + label; } else { item[groupField[0].name] = sortFieldValue[0] + ('年' as string) + sortFieldValue[1] + label; } }); arr.sort((a: any, b: any) => { return Number(a.sortField) - Number(b.sortField); }); arr.forEach((item: any) => { delete item.sortField; }); return arr; } /** * 初始化seriesModel * * @memberof ChartController */ public async initSeriesModel() { if (!this.model.serieses) { return; } for (let index = 0; index < this.model.serieses.length; index++) { const series = this.model.serieses[index]; if (series) { this.seriesModel[series.name.toLowerCase()] = this.getSeriesModelParam(series); } } } /** * 获取SeriesModel参数 * * @param {*} series 序列模型 * @memberof ChartController */ public getSeriesModelParam(series: any): Promise { this.chartValueName = series.valueField; this.chartCatalogName = series.catalogField; // 构造dataSetFields属性 const opts: any = { name: series.name, categorField: series.catalogField, valueField: series.valueField, seriesValues: [], seriesIndex: series.index | 0, data: [], seriesMap: {}, categorCodeList: series.categorCodeList, dataSetFields: this.getDataSetFields(series), ecxObject: { label: { show: true, position: 'top', }, labelLine: { length: 10, lineStyle: { width: 1, type: 'solid', }, }, itemStyle: { borderWidth: 1, }, emphasis: { label: { show: true, fontSize: 20, }, }, }, seriesCodeList: series.seriesCodeList, seriesNameField: series.seriesField, seriesIdField: series.seriesField, ecObject: {}, seriesTemp: { type: series.eChartsType, }, type: series.eChartsType, seriesLayoutBy: series.seriesLayoutBy, baseOption: {}, }; // 饼图引导线默认配置 if (Object.is(series.eChartsType, 'pie')) { Object.assign(opts.ecxObject.label, { position: 'outside', formatter: `{b}: {d}%({@${opts.valueField}})`, }); Object.assign(opts.ecxObject.labelLine, { show: true, }); } // 漏斗图默认配置 if (Object.is(series.eChartsType, 'funnel')) { Object.assign(opts.ecxObject.label, { position: 'outside', formatter: `{b}: {d}%({@${opts.valueField}})`, }); } // 地图默认配置(暂时自定义) if (Object.is(series.eChartsType, 'custom')) { Object.assign(opts.ecxObject.label, { formatter: `{b}: {@${opts.valueField}}`, }); opts.ecxObject.tooltip = { trigger: 'item', formatter: '{b}: {c}', }; } // 处理自定义ECX参数 this.fillUserParam(series, opts.ecxObject, 'ECX'); this.fillUserParam(series, opts.ecObject, 'EC'); Object.assign( opts, series.baseOptionJOString ? new Function('return {' + series.baseOptionJOString + '}')() : {} ); return opts; } /** * 临时获取seriesDataSetField 模型 * * @param {*} series 序列模型 * @return {*} * @memberof ChartController */ public getDataSetFields(series: any) { const seriesData: any = []; const dataSet = this.model.dataSets?.find((item: any) => { return item.id === series.chartDataSetId; }) || null; if (!dataSet && !dataSet.fields) { return null; } for (let index = 0; index < dataSet.fields?.length; index++) { const dataFile: any = dataSet.fields[index]; const data: any = {}; if (dataFile.codelist) { const codeList = dataFile.codelist; Object.assign(data, { codelist: codeList }); // 设置代码项原值 if (Object.is(dataFile?.name, series?.catalogField)) { const tempCodeItem = { name: `${dataFile.name?.toLowerCase()}_srfvalue`, isGroupField: dataFile.groupField, groupMode: dataFile.groupMode, isReadOnly: true, isCodeItem: true, }; seriesData.push(tempCodeItem); } } data['isGroupField'] = dataFile.groupField; data['name'] = dataFile.name?.toLowerCase(); data['groupMode'] = dataFile.groupMode; // 只读不合并数据(扩展值属性4) if ( series.extValue4Field && Object.is(dataFile.name, series.extValue4Field) ) { data['isReadOnly'] = true; } seriesData.push(data); } return seriesData; } /** * 是否为json字符串 * * @param {*} str 字符串 * @return {*} {boolean} * @memberof ChartController */ public isJson(str: any): boolean { try { JSON.parse(str); return true; } catch (error) { return false; } } /** * 解析字符串函数 * * @param {*} data * @return {*} {boolean} * @memberof ChartController */ public deepJsonParseFun(data: any): any { switch (typeof data) { case 'string': case 'number': case 'boolean': case 'undefined': case 'function': case 'symbol': return data; } for (const key in data) { const item = data[key]; let res: any; if (item.isFun && item.functionBody) { res = new Function(item.arg, item.functionBody); } else { res = this.deepJsonParseFun(item); } data[key] = res; } return data; } /** * 是否为数组字符串 * * @param {string} str 字符串 * @return {*} * @memberof ChartController */ public isArray(str: string) { try { eval(str); return true; } catch (error) { return false; } } /** * 处理用户自定义参数 * * @param {*} param 模型对象 * @param {*} opts 图表参数 * @param {string} tag 模式标识 * @return {*} * @memberof ChartController */ public fillUserParam(param: any, opts: any, tag: string) { if (!param.userParams) { return; } const userParam = param.userParams; try { switch (tag) { case 'ECX': if (userParam['ECX.label']) { opts['label'] = userParam['ECX.label']; } if (userParam['ECX.labelLine']) { opts['labelLine'] = userParam['ECX.labelLine']; } if (userParam['ECX.itemStyle']) { opts['itemStyle'] = userParam['ECX.itemStyle']; } if (userParam['ECX.emphasis']) { opts['emphasis'] = userParam['ECX.emphasis']; } for (const key in userParam) { if (Object.prototype.hasOwnProperty.call(userParam, key)) { if (key.indexOf('ECX.') != -1) { const value = userParam[key]; opts[key.replace('ECX.', '')] = value; } } } break; case 'EC': for (const key in userParam) { if (Object.prototype.hasOwnProperty.call(userParam, key)) { if (key.indexOf('EC.') != -1) { const value = userParam[key]; opts[key.replace('EC.', '')] = this.isJson(value) ? this.deepJsonParseFun(JSON.parse(value)) : this.isArray(value) ? eval(value) : value; } } } break; default: break; } } catch (error) { console.warn(App.ts("widget.chart.customparamerror","自定义参数格式不正确") + error); } } /** * 填充chartOption * * @memberof ChartController */ public initChartOption() { const series: any = []; // 填充series const indicator: any = []; this.model.serieses?.forEach((_series: any) => { series.push(this.fillSeries(_series, indicator)); }); // 填充xAxis const xAxis: any = []; this.model.chartXAxises.forEach((_xAxis: any) => { xAxis.push(this.fillAxis(_xAxis)); }); // 填充yAxis const yAxis: any = []; this.model.chartYAxises?.forEach((_yAxis: any) => { yAxis.push(this.fillAxis(_yAxis)); }); // 填充grid const grid: any = []; this.model.chartGrids?.forEach((_grid: any) => { grid.push({ ...(_grid.baseOptionJOString ? new Function('return {' + _grid.baseOptionJOString + '}')() : {}), }); }); // chartOption参数 const opt = { tooltip: { show: true }, dataset: [], series: series, xAxis: xAxis, yAxis: yAxis, // grid: grid, }; this.fillTitleOption(opt); this.fillLegendOption(opt); // TODO 地图暂不支持 // this.registerMap(); // 合并chartOption Object.assign(this.chartOption, opt); // 雷达图特殊参数 if (indicator.length > 0) { Object.assign(this.chartOption, { radar: { indicator } }); } } /** * 数组元素小写 * * @param {*} arr 数组 * @returns * @memberof ChartController */ public arrayToLowerCase(arr: any) { if (!arr || arr.length == 0) { return []; } for (let index = 0; index < arr.length; index++) { arr[index] = arr[index].toLowerCase(); } return arr; } /** * 填充 series * * @param {*} series 序列模型 * @param {*} [indicator={}] 雷达图参数 * @return {*} * @memberof ChartController */ public fillSeries(series: any, indicator: any = {}) { const encode: any = {}; let customType = ''; const assginCodeList = (codeList: any) => { codeList.codeItems?.forEach((_item: any) => { const item: any = { name: _item.text, max: _item.userParams?.MAXVALUE ? _item.userParams.MAXVALUE : null, }; indicator.push(item); if (_item.codeItems?.length > 0) { assginCodeList(_item); } }); }; switch (series.eChartsType) { case 'line': case 'bar': const cSCartesian2DEncode = series.chartSeriesEncode; encode.x = this.arrayToLowerCase(cSCartesian2DEncode.getX); encode.y = this.arrayToLowerCase(cSCartesian2DEncode.getY); break; case 'pie': case 'funnel': const CSNoneEncode = series.chartSeriesEncode; encode.itemName = CSNoneEncode.category?.toLowerCase(); encode.value = CSNoneEncode.value?.toLowerCase(); break; case 'radar': encode.itemName = 'type'; const catalogCodeList = series.categorCodeList; if (catalogCodeList) { assginCodeList(catalogCodeList); } break; case 'scatter': break; case 'gauge': break; case 'candlestick': const candlestickEncode = series.chartSeriesEncode; encode.x = this.arrayToLowerCase(candlestickEncode.getX); encode.y = this.arrayToLowerCase(candlestickEncode.getY); break; case 'custom': customType = 'map'; break; default: break; } return { id: series.name?.toLowerCase(), name: App.ts(series.capPSLanguageRes, series.caption), type: customType ? customType : series.eChartsType, xAxisIndex: series.xAxisIndex | 0, yAxisIndex: series.yAxisIndex | 0, datasetIndex: series.chartDataSetId | 0, encode: Object.keys(encode).length > 0 ? encode : null, areaStyle: series.seriesType === 'area' ? {} : null, ...(series.baseOptionJOString ? new Function('return {' + series.baseOptionJOString + '}')() : {}), }; } /** * 填充 axis * * @param {IPSChartGridXAxis} axis 坐标模型 * @return {*} {*} * @memberof ChartController */ public fillAxis(axis: any): any { const _axis: any = { position: axis.position, type: axis.eChartsType, name: App.ts(axis.capPSLanguageRes, axis.caption), }; const dataShowMode = (axis as any).dataShowMode; if (dataShowMode === 1) { // 纵向显示 Object.assign(_axis, { axisLabel: { formatter: (value: any) => { if (value.length > 4) { return value.substr(0, 4).split('').join('\n') + '\n...'; } else { return value.split('').join('\n'); } }, }, }); } else if (dataShowMode === 2) { // 横向显示 } else if (dataShowMode === 3) { // 斜向显示 Object.assign(_axis, { axisLabel: { rotate: 45, formatter: (value: any) => { if (value.length > 4) { return value.substr(0, 4) + '...'; } else { return value; } }, }, }); } // 填充用户自定义参数 this.fillUserParam(axis, _axis, 'EC'); if (axis.minValue) { _axis['min'] = axis.minValue; } if (axis.maxValue) { _axis['max'] = axis.maxValue; } return _axis; } /** * 填充标题配置 * * @param opts 图表配置 * @memberof ChartController */ public fillTitleOption(opts: any) { if (this.model.chartTitle) { const _titleModel = this.model.chartTitle; const title: any = { show: _titleModel?.showTitle, text: App.ts(_titleModel?.titlePSLanguageRes, _titleModel?.title), subtext: App.ts( _titleModel?.subTitlePSLanguageRes, _titleModel?.subTitle ), }; if (_titleModel?.titlePos) { switch (_titleModel?.titlePos) { case 'LEFT': Object.assign(title, { left: 'left', }); break; case 'RIGHT': Object.assign(title, { left: 'right', }); break; case 'BOTTOM': Object.assign(title, { left: 'center', top: 'bottom', }); break; } } Object.assign(opts, { title }); } } /** * 填充图例配置 * * @param opts 图表配置 * @memberof ChartController */ public fillLegendOption(opts: any) { if (this.model.chartLegend) { const legendModel: any = this.model.chartLegend; const legend: any = { show: legendModel?.showLegend, }; if (legendModel?.legendPos) { switch (legendModel.legendPos) { case 'LEFT': Object.assign(legend, { left: 'left', top: 'middle', orient: 'vertical', }); break; case 'RIGHT': Object.assign(legend, { left: 'right', top: 'middle', orient: 'vertical', }); break; case 'BOTTOM': Object.assign(legend, { top: 'bottom', }); break; } } Object.assign(opts, { legend }); } } /** * 初始化chartUserParams * * @memberof ChartController */ public initChartUserParams() { this.fillUserParam(this.model, this.chartUserParams, 'EC'); } /** * 初始化图表基础动态模型 * * @memberof ChartController */ public initChartBaseOPtion() { if (this.model.baseOptionJOString) { this.chartBaseOPtion = new Function( 'return {' + this.model.baseOptionJOString + '}' )(); } } /** * 更新图表大小 * * @memberof ChartController */ public updateSize() { if ( this.myChart && this.myChart.resize && this.myChart.resize instanceof Function ) { this.myChart.resize(); } } /** * 获取能力 * * @template A * @return {*} {A} * @memberof ChartController */ getAbility(): IChartAbility { return { ...super.getAbility(), updateSize: this.updateSize.bind(this), }; } }