mirror of
https://github.com/tencentmusic/supersonic.git
synced 2025-12-10 19:51:00 +00:00
@@ -81,6 +81,7 @@ export enum MsgContentTypeEnum {
|
||||
METRIC_TREND = 'METRIC_TREND',
|
||||
METRIC_BAR = 'METRIC_BAR',
|
||||
MARKDOWN = 'MARKDOWN',
|
||||
METRIC_PIE = 'METRIC_PIE',
|
||||
}
|
||||
|
||||
export enum ChatContextTypeQueryTypeEnum {
|
||||
|
||||
@@ -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="表格"
|
||||
|
||||
@@ -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,
|
||||
|
||||
123
webapp/packages/chat-sdk/src/components/ChatMsg/Pie/PieChart.tsx
Normal file
123
webapp/packages/chat-sdk/src/components/ChatMsg/Pie/PieChart.tsx
Normal 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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 }}>
|
||||
|
||||
@@ -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";
|
||||
|
||||
Reference in New Issue
Block a user