dev_add_show-pie (#2241)

Great, thanks!
This commit is contained in:
WDEP
2025-05-05 15:47:18 +08:00
committed by GitHub
parent d0a67af684
commit 763def2de0
8 changed files with 291 additions and 6 deletions

View File

@@ -81,6 +81,7 @@ export enum MsgContentTypeEnum {
METRIC_TREND = 'METRIC_TREND',
METRIC_BAR = 'METRIC_BAR',
MARKDOWN = 'MARKDOWN',
METRIC_PIE = 'METRIC_PIE',
}
export enum ChatContextTypeQueryTypeEnum {

View File

@@ -123,9 +123,11 @@ const ExecuteItem: React.FC<Props> = ({
)}
</div>
<div>
{[MsgContentTypeEnum.METRIC_TREND, MsgContentTypeEnum.METRIC_BAR].includes(
msgContentType as MsgContentTypeEnum
) && (
{[
MsgContentTypeEnum.METRIC_TREND,
MsgContentTypeEnum.METRIC_BAR,
MsgContentTypeEnum.METRIC_PIE,
].includes(msgContentType as MsgContentTypeEnum) && (
<Switch
checkedChildren="表格"
unCheckedChildren="表格"

View File

@@ -23,7 +23,7 @@ import { useExportByEcharts } from '../../../hooks';
type Props = {
data: MsgDataType;
question: string;
question?: string;
triggerResize?: boolean;
loading: boolean;
metricField: ColumnType;
@@ -32,7 +32,7 @@ type Props = {
const BarChart: React.FC<Props> = ({
data,
question,
question="",
triggerResize,
loading,
metricField,

View File

@@ -0,0 +1,123 @@
import { PREFIX_CLS, THEME_COLOR_LIST } from '../../../common/constants';
import { MsgDataType } from '../../../common/type';
import { formatByDecimalPlaces, getFormattedValue } from '../../../utils/utils';
import type { ECharts } from 'echarts';
import * as echarts from 'echarts';
import { useEffect, useRef } from 'react';
import { ColumnType } from '../../../common/type';
type Props = {
data: MsgDataType;
metricField: ColumnType;
categoryField: ColumnType;
triggerResize?: boolean;
};
const PieChart: React.FC<Props> = ({
data,
metricField,
categoryField,
triggerResize,
}) => {
const chartRef = useRef<any>();
const instanceRef = useRef<ECharts>();
const { queryResults } = data;
const categoryColumnName = categoryField?.bizName || '';
const metricColumnName = metricField?.bizName || '';
const renderChart = () => {
let instanceObj: any;
if (!instanceRef.current) {
instanceObj = echarts.init(chartRef.current);
instanceRef.current = instanceObj;
} else {
instanceObj = instanceRef.current;
}
const data = queryResults || [];
const seriesData = data.map((item, index) => {
const value = item[metricColumnName];
const name = item[categoryColumnName] !== undefined ? item[categoryColumnName] : '未知';
return {
name,
value,
itemStyle: {
color: THEME_COLOR_LIST[index % THEME_COLOR_LIST.length],
},
};
});
instanceObj.setOption({
tooltip: {
trigger: 'item',
formatter: function (params: any) {
const value = params.value;
return `${params.name}: ${
metricField.dataFormatType === 'percent'
? `${formatByDecimalPlaces(
metricField.dataFormat?.needMultiply100 ? +value * 100 : value,
metricField.dataFormat?.decimalPlaces || 2
)}%`
: getFormattedValue(value)
}`;
},
},
legend: {
orient: 'vertical',
left: 'left',
type: 'scroll',
data: seriesData.map(item => item.name),
selectedMode: true,
textStyle: {
color: '#666',
},
},
series: [
{
name: '占比',
type: 'pie',
radius: ['40%', '70%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 2,
},
label: {
show: false,
position: 'center',
},
emphasis: {
label: {
show: true,
fontSize: '14',
fontWeight: 'bold',
},
},
labelLine: {
show: false,
},
data: seriesData,
},
],
});
instanceObj.resize();
};
useEffect(() => {
if (queryResults && queryResults.length > 0) {
renderChart();
}
}, [queryResults, metricField, categoryField]);
useEffect(() => {
if (triggerResize && instanceRef.current) {
instanceRef.current.resize();
}
}, [triggerResize]);
return <div className={`${PREFIX_CLS}-pie-chart`} ref={chartRef} />;
};
export default PieChart;

View File

@@ -0,0 +1,88 @@
import { PREFIX_CLS } from '../../../common/constants';
import { MsgDataType } from '../../../common/type';
import { useRef, useState } from 'react';
import NoPermissionChart from '../NoPermissionChart';
import { ColumnType } from '../../../common/type';
import { Spin, Select } from 'antd';
import PieChart from './PieChart';
import Bar from '../Bar';
type Props = {
data: MsgDataType;
question: string;
triggerResize?: boolean;
loading: boolean;
metricField: ColumnType;
categoryField: ColumnType;
onApplyAuth?: (model: string) => void;
};
const metricChartSelectOptions = [
{
value: 'pie',
label: '饼图',
},
{
value: 'bar',
label: '柱状图',
},
];
const Pie: React.FC<Props> = ({
data,
question,
triggerResize,
loading,
metricField,
categoryField,
onApplyAuth,
}) => {
const [chartType, setChartType] = useState('pie');
const { entityInfo } = data;
if (metricField && !metricField?.authorized) {
return (
<NoPermissionChart
model={entityInfo?.dataSetInfo?.name || ''}
chartType="pieChart"
onApplyAuth={onApplyAuth}
/>
);
}
const prefixCls = `${PREFIX_CLS}-pie`;
return (
<div className={prefixCls}>
<div className={`${prefixCls}-metric-fields ${prefixCls}-metric-field-single`}>
{question}
</div>
<div className={`${prefixCls}-select-options`}>
<Select
defaultValue="pie"
bordered={false}
options={metricChartSelectOptions}
onChange={(value: string) => setChartType(value)}
/>
</div>
{chartType === 'pie' ? (
<PieChart
data={data}
metricField={metricField}
categoryField={categoryField}
triggerResize={triggerResize}
/>
) : (
<Bar
data={data}
triggerResize={triggerResize}
loading={loading}
metricField={metricField}
onApplyAuth={onApplyAuth}
/>
)}
</div>
);
};
export default Pie;

View File

@@ -0,0 +1,43 @@
@import '../../../styles/index.less';
@pie-prefix-cls: ~'@{prefix-cls}-pie';
.@{pie-prefix-cls} {
&-select-options {
display: flex;
justify-content: flex-end;
}
&-chart {
width: 100%;
height: 400px;
min-height: 400px;
}
&-metric-fields {
display: flex;
align-items: baseline;
flex-wrap: wrap;
column-gap: 8px;
row-gap: 12px;
}
&-metric-field-single {
padding-left: 0;
font-weight: 500;
cursor: default;
font-size: 15px;
color: var(--text-color);
&:hover {
color: var(--text-color);
}
}
&-indicator-name {
font-size: 14px;
color: var(--text-color);
font-weight: 500;
margin-top: 2px;
}
}

View File

@@ -12,6 +12,7 @@ import Text from './Text';
import DrillDownDimensions from '../DrillDownDimensions';
import MetricOptions from '../MetricOptions';
import { isMobile } from '../../utils/utils';
import Pie from './Pie';
type Props = {
queryId?: number;
@@ -120,6 +121,16 @@ const ChatMsg: React.FC<Props> = ({
return MsgContentTypeEnum.METRIC_TREND;
}
const isMetricPie =
metricFields.length > 0 &&
metricFields?.length === 1 &&
(isMobile ? dataSource?.length <= 5 : dataSource?.length <= 10) &&
dataSource.every(item => item[metricFields[0].bizName] > 0);
if (isMetricPie) {
return MsgContentTypeEnum.METRIC_PIE;
}
const isMetricBar =
categoryField?.length > 0 &&
metricFields?.length === 1 &&
@@ -148,7 +159,7 @@ const ChatMsg: React.FC<Props> = ({
[queryColumns.length > 5 ? 'width' : 'minWidth']: queryColumns.length * 150,
};
}
if (type === MsgContentTypeEnum.METRIC_TREND) {
if (type === MsgContentTypeEnum.METRIC_TREND || type === MsgContentTypeEnum.METRIC_PIE) {
return { width: 'calc(100vw - 410px)' };
}
};
@@ -213,6 +224,21 @@ const ChatMsg: React.FC<Props> = ({
metricField={metricFields[0]}
/>
);
case MsgContentTypeEnum.METRIC_PIE:
const categoryField = columns.find(item => item.showType === 'CATEGORY');
if (!categoryField) {
return null;
}
return (
<Pie
data={{ ...data, queryColumns: columns, queryResults: dataSource }}
question={question}
triggerResize={triggerResize}
loading={loading}
metricField={metricFields[0]}
categoryField={categoryField}
/>
);
case MsgContentTypeEnum.MARKDOWN:
return (
<div style={{ maxHeight: 800 }}>

View File

@@ -16,6 +16,8 @@
@import "../components/ChatMsg/MetricTrend/style.less";
@import "../components/ChatMsg/Pie/style.less";
@import "../components/ChatMsg/ApplyAuth/style.less";
@import "../components/ChatMsg/NoPermissionChart/style.less";