Files
supersonic/webapp/packages/chat-sdk/src/components/ChatMsg/MetricTrend/MetricTrendChart.tsx

239 lines
6.9 KiB
TypeScript

import { CHART_SECONDARY_COLOR, CLS_PREFIX, THEME_COLOR_LIST } from '../../../common/constants';
import {
formatByDecimalPlaces,
getFormattedValue,
getMinMaxDate,
groupByColumn,
normalizeTrendData,
} from '../../../utils/utils';
import type { ECharts } from 'echarts';
import * as echarts from 'echarts';
import React, { useContext, useEffect, useRef, useState } from 'react';
import moment from 'moment';
import { ColumnType } from '../../../common/type';
import NoPermissionChart from '../NoPermissionChart';
import classNames from 'classnames';
import { isArray } from 'lodash';
import { useExportByEcharts } from '../../../hooks';
import { ChartItemContext } from '../../ChatItem';
type Props = {
model?: string;
dateColumnName: string;
categoryColumnName: string;
metricField: ColumnType;
resultList: any[];
triggerResize?: boolean;
onApplyAuth?: (model: string) => void;
chartType?: string;
};
const MetricTrendChart: React.FC<Props> = ({
model,
dateColumnName,
categoryColumnName,
metricField,
resultList,
triggerResize,
onApplyAuth,
chartType,
}) => {
const chartRef = useRef<any>();
const instanceRef = useRef<ECharts>();
const renderChart = () => {
let instanceObj: any;
if (!instanceRef.current) {
instanceObj = echarts.init(chartRef.current);
instanceRef.current = instanceObj;
} else {
instanceObj = instanceRef.current;
instanceObj.clear();
}
const valueColumnName = metricField.nameEn;
const dataSource = resultList.map((item: any) => {
return {
...item,
[dateColumnName]: Array.isArray(item[dateColumnName])
? moment(item[dateColumnName].join('')).format('MM-DD')
: item[dateColumnName],
};
});
const groupDataValue = groupByColumn(dataSource, categoryColumnName);
const [startDate, endDate] = getMinMaxDate(dataSource, dateColumnName);
const groupData = Object.keys(groupDataValue).reduce((result: any, key) => {
result[key] =
startDate &&
endDate &&
(dateColumnName.includes('date') || dateColumnName.includes('month'))
? normalizeTrendData(
groupDataValue[key],
dateColumnName,
valueColumnName,
startDate,
endDate,
dateColumnName.includes('month') ? 'months' : 'days'
)
: groupDataValue[key];
return result;
}, {});
const sortedGroupKeys = Object.keys(groupData).sort((a, b) => {
return (
groupData[b][groupData[b].length - 1][valueColumnName] -
groupData[a][groupData[a].length - 1][valueColumnName]
);
});
const xData = groupData[sortedGroupKeys[0]]?.map((item: any) => {
const date = isArray(item[dateColumnName])
? item[dateColumnName].join('-')
: `${item[dateColumnName]}`;
return date.length === 10 ? moment(date).format('MM-DD') : date;
});
instanceObj.setOption({
legend: categoryColumnName && {
left: 0,
top: 0,
icon: 'rect',
itemWidth: 15,
itemHeight: 5,
type: 'scroll',
},
xAxis: {
type: 'category',
axisTick: {
alignWithLabel: true,
lineStyle: {
color: CHART_SECONDARY_COLOR,
},
},
axisLine: {
lineStyle: {
color: CHART_SECONDARY_COLOR,
},
},
axisLabel: {
showMaxLabel: true,
color: '#999',
},
data: xData,
},
yAxis: {
type: 'value',
splitLine: {
lineStyle: {
opacity: 0.3,
},
},
axisLabel: {
formatter: function (value: any) {
return value === 0
? 0
: metricField.dataFormatType === 'percent'
? `${formatByDecimalPlaces(value, metricField.dataFormat?.decimalPlaces || 2)}%`
: getFormattedValue(value);
},
},
},
tooltip: {
trigger: 'axis',
formatter: function (params: any[]) {
const param = params[0];
const valueLabels = params
.sort((a, b) => b.value - a.value)
.map(
(item: any) =>
`<div style="margin-top: 3px;">${
item.marker
} <span style="display: inline-block; width: 70px; margin-right: 12px;">${
item.seriesName
}</span><span style="display: inline-block; width: 90px; text-align: right; font-weight: 500;">${
item.value === ''
? '-'
: metricField.dataFormatType === 'percent' ||
metricField.dataFormatType === 'decimal'
? `${formatByDecimalPlaces(
item.value,
metricField.dataFormat?.decimalPlaces || 2
)}${metricField.dataFormatType === 'percent' ? '%' : ''}`
: getFormattedValue(item.value)
}</span></div>`
)
.join('');
return `${param.name}<br />${valueLabels}`;
},
},
grid: {
left: '1%',
right: '4%',
bottom: '3%',
top: categoryColumnName ? 45 : 20,
containLabel: true,
},
series: sortedGroupKeys.slice(0, 20).map((category, index) => {
const data = groupData[category];
return {
type: chartType,
name: categoryColumnName ? category : metricField.name,
symbol: 'circle',
showSymbol: data.length === 1,
smooth: true,
data: data.map((item: any) => {
const value = item[valueColumnName];
return (metricField.dataFormatType === 'percent' ||
metricField.dataFormatType === 'decimal') &&
metricField.dataFormat?.needMultiply100
? value * 100
: value;
}),
color: THEME_COLOR_LIST[index],
};
}),
});
instanceObj.resize();
};
const { downloadChartAsImage } = useExportByEcharts({
instanceRef,
question: metricField.name,
});
const { register } = useContext(ChartItemContext);
register('downloadChartAsImage', downloadChartAsImage);
useEffect(() => {
if (metricField.authorized) {
renderChart();
}
}, [resultList, metricField, chartType]);
useEffect(() => {
if (triggerResize && instanceRef.current) {
instanceRef.current.resize();
}
}, [triggerResize]);
const prefixCls = `${CLS_PREFIX}-metric-trend`;
const flowTrendChartClass = classNames(`${prefixCls}-flow-trend-chart`, {
[`${prefixCls}-flow-trend-chart-single`]: !categoryColumnName,
});
return (
<div>
{!metricField.authorized ? (
<NoPermissionChart model={model || ''} onApplyAuth={onApplyAuth} />
) : (
<div className={flowTrendChartClass} ref={chartRef} />
)}
</div>
);
};
export default MetricTrendChart;