[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:
tristanliu
2023-12-15 19:12:34 +08:00
committed by GitHub
parent 6daaff8c30
commit ef8caea9d2
6 changed files with 208 additions and 30 deletions

View File

@@ -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;

View File

@@ -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;
}
}
}
}

View File

@@ -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);
}}

View File

@@ -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 }) => {

View File

@@ -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;
}

View File

@@ -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);