mirror of
https://github.com/tencentmusic/supersonic.git
synced 2025-12-10 19:51:00 +00:00
[improvement][semantic-fe] Optimizing the canvas functionality for better performance and user experience. (#517)
* [improvement][semantic-fe] Add model alias setting & Add view permission restrictions to the model permission management tab. [improvement][semantic-fe] Add permission control to the action buttons for the main domain; apply high sensitivity filtering to the authorization of metrics/dimensions. [improvement][semantic-fe] Optimize the editing mode in the dimension/metric/datasource components to use the modelId stored in the database for data, instead of relying on the data from the state manager. * [improvement][semantic-fe] Add time granularity setting in the data source configuration. * [improvement][semantic-fe] Dictionary import for dimension values supported in Q&A visibility * [improvement][semantic-fe] Modification of data source creation prompt wording" * [improvement][semantic-fe] metric market experience optimization * [improvement][semantic-fe] enhance the analysis of metric trends * [improvement][semantic-fe] optimize the presentation of metric trend permissions * [improvement][semantic-fe] add metric trend download functionality * [improvement][semantic-fe] fix the dimension initialization issue in metric correlation * [improvement][semantic-fe] Fix the issue of database changes not taking effect when creating based on an SQL data source. * [improvement][semantic-fe] Optimizing pagination logic and some CSS styles * [improvement][semantic-fe] Fixing the API for the indicator list by changing "current" to "pageNum" * [improvement][semantic-fe] Fixing the default value setting for the indicator list * [improvement][semantic-fe] Adding batch operations for indicators/dimensions/models * [improvement][semantic-fe] Replacing the single status update API for indicators/dimensions with a batch update API * [improvement][semantic-fe] Redesigning the indicator homepage to incorporate trend charts and table functionality for indicators * [improvement][semantic-fe] Optimizing the logic for setting dimension values and editing data sources, and adding system settings functionality * [improvement][semantic-fe] Upgrading antd version to 5.x, extracting the batch operation button component, optimizing the interaction for system settings, and expanding the configuration generation types for list-to-select component. * [improvement][semantic-fe] Adding the ability to filter dimensions based on whether they are tags or not. * [improvement][semantic-fe] Adding the ability to edit relationships between models in the canvas. * [improvement][semantic-fe] Updating the datePicker component to use dayjs instead. * [improvement][semantic-fe] Fixing the issue with passing the model ID for dimensions in the indicator market. * [improvement][semantic-fe] Fixing the abnormal state of the popup when creating a model. * [improvement][semantic-fe] Adding permission logic for bulk operations in the indicator market. * [improvement][semantic-fe] Adding the ability to download and transpose data. * [improvement][semantic-fe] Fixing the initialization issue with the date selection component in the indicator details page when switching time granularity. * [improvement][semantic-fe] Fixing the logic error in the dimension value setting. * [improvement][semantic-fe] Fixing the synchronization issue with the question and answer settings information. * [improvement][semantic-fe] Optimizing the canvas functionality for better performance and user experience.
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
ControlOutlined,
|
||||
ZoomInOutlined,
|
||||
ZoomOutOutlined,
|
||||
SearchOutlined,
|
||||
OneToOneOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { FloatButton, Tooltip, Input } from 'antd';
|
||||
import GraphLegendVisibleModeItem from '../GraphLegendVisibleModeItem';
|
||||
import { SemanticNodeType } from '../../../enum';
|
||||
import styles from './style.less';
|
||||
|
||||
const { Search } = Input;
|
||||
|
||||
type Props = {
|
||||
graph: any;
|
||||
onSearch?: (text: string) => void;
|
||||
onShowTypeChange?: (nodeType: SemanticNodeType) => void;
|
||||
onZoomIn?: () => void;
|
||||
onZoomOut?: () => void;
|
||||
onAutoZoom?: () => void;
|
||||
};
|
||||
|
||||
function zoomGraph(graph, ratio: number) {
|
||||
const width = graph.get('width');
|
||||
const height = graph.get('height');
|
||||
const centerX = width / 2;
|
||||
const centerY = height / 2;
|
||||
graph.zoom(ratio, { x: centerX, y: centerY });
|
||||
}
|
||||
|
||||
const ControlToolBar: React.FC<Props> = ({
|
||||
graph,
|
||||
onSearch,
|
||||
onShowTypeChange,
|
||||
onZoomIn,
|
||||
onZoomOut,
|
||||
onAutoZoom,
|
||||
}) => {
|
||||
const sensitivity = 0.1; // 设置缩放灵敏度,值越小,缩放越不敏感,默认值为 1
|
||||
const zoomOutRatio = 1 - sensitivity;
|
||||
const zoomInRatio = 1 + sensitivity;
|
||||
|
||||
return (
|
||||
<div className={styles.graphControlContent}>
|
||||
<FloatButton.Group
|
||||
shape="square"
|
||||
style={{ left: 25, top: 25, height: 310, position: 'absolute' }}
|
||||
>
|
||||
<Tooltip
|
||||
overlayClassName={styles.overlayClassName}
|
||||
title={
|
||||
<Search
|
||||
placeholder="请输入指标/维度名称"
|
||||
allowClear
|
||||
onSearch={(text: string) => {
|
||||
onSearch?.(text);
|
||||
}}
|
||||
style={{ width: 250 }}
|
||||
/>
|
||||
}
|
||||
placement="right"
|
||||
>
|
||||
<FloatButton icon={<SearchOutlined />} description="搜索" />
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip
|
||||
overlayClassName={styles.overlayClassName}
|
||||
title={
|
||||
<GraphLegendVisibleModeItem
|
||||
onChange={(nodeType: SemanticNodeType) => {
|
||||
onShowTypeChange?.(nodeType);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
placement="right"
|
||||
>
|
||||
<FloatButton icon={<ControlOutlined />} description="模式" />
|
||||
</Tooltip>
|
||||
|
||||
<FloatButton
|
||||
icon={<ZoomInOutlined />}
|
||||
description="放大"
|
||||
onClick={() => {
|
||||
zoomGraph(graph, zoomInRatio);
|
||||
onZoomIn?.();
|
||||
}}
|
||||
/>
|
||||
<FloatButton
|
||||
icon={<ZoomOutOutlined />}
|
||||
description="缩小"
|
||||
onClick={() => {
|
||||
zoomGraph(graph, zoomOutRatio);
|
||||
onZoomOut?.();
|
||||
}}
|
||||
/>
|
||||
<FloatButton
|
||||
icon={<OneToOneOutlined />}
|
||||
description="重置"
|
||||
onClick={() => {
|
||||
graph.fitView();
|
||||
onAutoZoom?.();
|
||||
}}
|
||||
/>
|
||||
</FloatButton.Group>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ControlToolBar;
|
||||
@@ -0,0 +1,39 @@
|
||||
.graphControlContent {
|
||||
:global {
|
||||
.ant-float-btn-group {
|
||||
width: 55px;
|
||||
}
|
||||
.ant-float-btn {
|
||||
width: 100%;
|
||||
}
|
||||
.ant-float-btn .ant-float-btn-body .ant-float-btn-content .ant-float-btn-icon {
|
||||
font-size: 24px;
|
||||
width: 26px;
|
||||
}
|
||||
.ant-float-btn-default .ant-float-btn-body .ant-float-btn-content .ant-float-btn-description {
|
||||
margin-top: 5px;
|
||||
}
|
||||
.ant-float-btn-group-square-shadow .ant-float-btn-square .ant-float-btn-body {
|
||||
width: inherit;
|
||||
height: inherit;
|
||||
padding: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.overlayClassName {
|
||||
background-color: #fff;
|
||||
max-width: unset;
|
||||
:global {
|
||||
.ant-tooltip-content {
|
||||
.ant-tooltip-inner {
|
||||
background-color: #fff;
|
||||
}
|
||||
}
|
||||
.ant-tooltip-arrow {
|
||||
&::before {
|
||||
background-color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Segmented } from 'antd';
|
||||
|
||||
import React from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { SemanticNodeType } from '../../enum';
|
||||
import styles from '../style.less';
|
||||
|
||||
@@ -11,12 +11,18 @@ type Props = {
|
||||
};
|
||||
|
||||
const GraphLegendVisibleModeItem: React.FC<Props> = ({ value, onChange }) => {
|
||||
const [nodeType, setNodeType] = useState<SemanticNodeType | undefined>();
|
||||
|
||||
useEffect(() => {
|
||||
setNodeType(value);
|
||||
}, [value]);
|
||||
|
||||
return (
|
||||
<div className={styles.graphLegendVisibleModeItem}>
|
||||
<Segmented
|
||||
size="small"
|
||||
block={true}
|
||||
value={value}
|
||||
value={nodeType}
|
||||
onChange={(changeValue) => {
|
||||
onChange?.(changeValue as SemanticNodeType);
|
||||
}}
|
||||
|
||||
@@ -35,6 +35,7 @@ import GraphToolBar from './components/GraphToolBar';
|
||||
import GraphLegend from './components/GraphLegend';
|
||||
import GraphLegendVisibleModeItem from './components/GraphLegendVisibleModeItem';
|
||||
import ModelRelationFormDrawer from './components/ModelRelationFormDrawer';
|
||||
import ControlToolBar from './components/ControlToolBar';
|
||||
|
||||
type Props = {
|
||||
domainManger: StateType;
|
||||
@@ -181,10 +182,12 @@ const DomainManger: React.FC<Props> = ({ domainManger, dispatch }) => {
|
||||
if (target) {
|
||||
const { config } = target;
|
||||
const parseConfig = jsonParse(config, []);
|
||||
setRelationConfig(target);
|
||||
parseConfig.forEach((item) => {
|
||||
graphRef?.current?.addItem('edge', item);
|
||||
});
|
||||
if (Array.isArray(parseConfig)) {
|
||||
setRelationConfig(target);
|
||||
parseConfig.forEach((item) => {
|
||||
graphRef?.current?.addItem('edge', item);
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
message.error(msg);
|
||||
@@ -478,11 +481,11 @@ const DomainManger: React.FC<Props> = ({ domainManger, dispatch }) => {
|
||||
|
||||
if (!graph && graphData) {
|
||||
const graphNodeList = flatGraphDataNode(graphData.children);
|
||||
// const graphConfigKey = graphNodeList.length > 20 ? 'dendrogram' : 'mindmap';
|
||||
const graphConfigKey = 'dendrogram';
|
||||
const graphConfigKey = graphNodeList.length > 20 ? 'dendrogram' : 'mindmap';
|
||||
// const graphConfigKey = 'mindmap';
|
||||
|
||||
// getLegendDataFilterFunctions();
|
||||
const toolbar = initToolBar({ onSearch: handleSeachNode, onClick: handleToolBarClick });
|
||||
// const toolbar = initToolBar({ onSearch: handleSeachNode, onClick: handleToolBarClick });
|
||||
const tooltip = initTooltips();
|
||||
const contextMenu = initContextMenu({
|
||||
onMenuClick: handleContextMenuClick,
|
||||
@@ -532,7 +535,8 @@ const DomainManger: React.FC<Props> = ({ domainManger, dispatch }) => {
|
||||
.getContainer()
|
||||
.findAll((ele) => ele.get('name') === 'anchor-point');
|
||||
anchorPoints.forEach((point) => {
|
||||
if (value || point.get('links') > 0) point.show();
|
||||
// if (value || point.get('links') > 0) point.show();
|
||||
if (value) point.show();
|
||||
else point.hide();
|
||||
});
|
||||
}
|
||||
@@ -655,6 +659,7 @@ const DomainManger: React.FC<Props> = ({ domainManger, dispatch }) => {
|
||||
return true;
|
||||
},
|
||||
},
|
||||
'scroll-canvas',
|
||||
'drag-canvas',
|
||||
// config the shouldBegin and shouldEnd to make sure the create-edge is began and ended at anchor-point circles
|
||||
{
|
||||
@@ -679,10 +684,10 @@ const DomainManger: React.FC<Props> = ({ domainManger, dispatch }) => {
|
||||
return true;
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'zoom-canvas',
|
||||
sensitivity: 0.3, // 设置缩放灵敏度,值越小,缩放越不敏感,默认值为 1
|
||||
},
|
||||
// {
|
||||
// type: 'zoom-canvas',
|
||||
// sensitivity: 0.3, // 设置缩放灵敏度,值越小,缩放越不敏感,默认值为 1
|
||||
// },
|
||||
{
|
||||
type: 'activate-relations',
|
||||
trigger: 'mouseenter', // 触发方式,可以是 'mouseenter' 或 'click'
|
||||
@@ -693,7 +698,7 @@ const DomainManger: React.FC<Props> = ({ domainManger, dispatch }) => {
|
||||
layout: {
|
||||
...graphConfigMap[graphConfigKey].layout,
|
||||
},
|
||||
plugins: [tooltip, toolbar, contextMenu],
|
||||
plugins: [tooltip, contextMenu],
|
||||
|
||||
defaultNode: {
|
||||
type: 'rect-node',
|
||||
@@ -751,8 +756,13 @@ const DomainManger: React.FC<Props> = ({ domainManger, dispatch }) => {
|
||||
label: '模型关系编辑',
|
||||
style: {
|
||||
stroke: '#296df3',
|
||||
endArrow: true,
|
||||
},
|
||||
});
|
||||
const sourceNode = e.edge.get('sourceNode');
|
||||
const targetnode = e.edge.get('targetNode');
|
||||
graphRef.current.setItemState(sourceNode, 'showAnchors', false);
|
||||
graphRef.current.setItemState(targetnode, 'showAnchors', false);
|
||||
}
|
||||
|
||||
// update the curveOffset for parallel edges
|
||||
@@ -804,6 +814,7 @@ const DomainManger: React.FC<Props> = ({ domainManger, dispatch }) => {
|
||||
label: '模型关系编辑',
|
||||
style: {
|
||||
stroke: '#296df3',
|
||||
endArrow: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -858,12 +869,12 @@ const DomainManger: React.FC<Props> = ({ domainManger, dispatch }) => {
|
||||
graphRef.current.data(graphData);
|
||||
graphRef.current.render();
|
||||
|
||||
const nodeCount = graphRef.current.getNodes().length;
|
||||
if (nodeCount < 10) {
|
||||
lessNodeZoomRealAndMoveCenter();
|
||||
} else {
|
||||
graphRef.current.fitView([80, 80]);
|
||||
}
|
||||
// const nodeCount = graphRef.current.getNodes().length;
|
||||
// if (nodeCount < 10) {
|
||||
lessNodeZoomRealAndMoveCenter();
|
||||
// } else {
|
||||
// graphRef.current.fitView([80, 80]);
|
||||
// }
|
||||
|
||||
graphRef.current.on('node:click', (evt: any) => {
|
||||
const item = evt.item; // 被操作的节点 item
|
||||
@@ -934,7 +945,8 @@ const DomainManger: React.FC<Props> = ({ domainManger, dispatch }) => {
|
||||
graphRef.current.changeData(graphRootData);
|
||||
const rootNode = graphRef.current.findById('root');
|
||||
graphRef.current.hideItem(rootNode);
|
||||
graphRef.current.fitView();
|
||||
// graphRef.current.fitView();
|
||||
lessNodeZoomRealAndMoveCenter();
|
||||
};
|
||||
|
||||
const saveModelRelationEdges = () => {
|
||||
@@ -948,7 +960,7 @@ const DomainManger: React.FC<Props> = ({ domainManger, dispatch }) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<GraphLegend
|
||||
{/* <GraphLegend
|
||||
legendOptions={legendDataRef.current}
|
||||
defaultCheckAll={true}
|
||||
onChange={(nodeIds: string[]) => {
|
||||
@@ -967,7 +979,22 @@ const DomainManger: React.FC<Props> = ({ domainManger, dispatch }) => {
|
||||
refreshGraphData(rootGraphData);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
)} */}
|
||||
|
||||
<ControlToolBar
|
||||
graph={graphRef.current}
|
||||
onSearch={(text) => {
|
||||
handleSeachNode(text);
|
||||
}}
|
||||
onShowTypeChange={(showType) => {
|
||||
graphShowTypeRef.current = showType;
|
||||
setGraphShowTypeState(showType);
|
||||
const rootGraphData = changeGraphData(dataSourceRef.current);
|
||||
refreshGraphData(rootGraphData);
|
||||
}}
|
||||
onZoomIn={() => {}}
|
||||
onZoomOut={() => {}}
|
||||
/>
|
||||
|
||||
<GraphToolBar
|
||||
onClick={({ eventName }: { eventName: string }) => {
|
||||
|
||||
@@ -45,10 +45,5 @@
|
||||
.graphLegendVisibleModeItem {
|
||||
padding: 3px;
|
||||
background: #fff;
|
||||
position: absolute;
|
||||
top: 58px;
|
||||
left: 20px;
|
||||
z-index: 1;
|
||||
border: 1px solid #eee;
|
||||
min-width: 190px;
|
||||
}
|
||||
@@ -89,7 +89,7 @@ const MetricInfoCreateForm: React.FC<CreateFormProps> = ({
|
||||
|
||||
const queryClassMeasureList = async () => {
|
||||
// const { code, data } = await getMeasureListByModelId(modelId);
|
||||
const { code, data } = await getModelDetail({ modelId });
|
||||
const { code, data } = await getModelDetail({ modelId: modelId || metricItem?.modelId });
|
||||
if (code === 200) {
|
||||
if (Array.isArray(data?.modelDetail?.measures)) {
|
||||
setClassMeasureList(data.modelDetail.measures);
|
||||
|
||||
Reference in New Issue
Block a user