[improvement][semantic-fe] Added an editing component to set filtering rules for Q&A. Now, the SQL editor will be accompanied by a list for display and control, to resolve ambiguity when using comma-separated values.

[improvement][semantic-fe] Improved validation logic and prompt copywriting for data source/dimension/metric editing and creation.
[improvement][semantic-fe] Improved user experience for visual modeling. Now, when using the legend to control the display/hide of data sources and their associated metric dimensions, the canvas will be re-layout based on the activated data source in the legend.

Co-authored-by: tristanliu <tristanliu@tencent.com>
This commit is contained in:
tristanliu
2023-07-21 15:30:38 +08:00
committed by GitHub
parent 6492316e23
commit 078a81038f
39 changed files with 1541 additions and 1161 deletions

View File

@@ -1,45 +1,90 @@
import G6 from '@antv/g6';
import G6, { Graph } from '@antv/g6';
import { createDom } from '@antv/dom-util';
import { RefreshGraphData } from '../../data';
const searchIconSvgPath = `<path d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z" />`;
const searchNode = (graph) => {
// const searchNode = (graph) => {
// const toolBarSearchInput = document.getElementById('toolBarSearchInput') as HTMLInputElement;
// const searchText = toolBarSearchInput.value.trim();
// let lastFoundNode = null;
// graph.getNodes().forEach((node) => {
// const model = node.getModel();
// const isFound = searchText && model.label.includes(searchText);
// if (isFound) {
// graph.setItemState(node, 'active', true);
// lastFoundNode = node;
// } else {
// graph.setItemState(node, 'active', false);
// }
// });
// if (lastFoundNode) {
// // 将视图移动到找到的节点位置
// graph.focusItem(lastFoundNode, true, {
// duration: 300,
// easing: 'easeCubic',
// });
// }
// };
interface Node {
label: string;
children?: Node[];
}
function findNodesByLabel(query: string, nodes: Node[]): Node[] {
const result: Node[] = [];
for (const node of nodes) {
let match = false;
let children: Node[] = [];
// 如果节点的label包含查询字符串我们将其标记为匹配
if (node.label.includes(query)) {
match = true;
}
// 我们还需要在子节点中进行搜索
if (node.children) {
children = findNodesByLabel(query, node.children);
if (children.length > 0) {
match = true;
}
}
// 如果节点匹配或者其子节点匹配,我们将其添加到结果中
if (match) {
result.push({ ...node, children });
}
}
return result;
}
const searchNode = (graph: Graph, refreshGraphData?: RefreshGraphData) => {
const toolBarSearchInput = document.getElementById('toolBarSearchInput') as HTMLInputElement;
const searchText = toolBarSearchInput.value.trim();
let lastFoundNode = null;
graph.getNodes().forEach((node) => {
const model = node.getModel();
const isFound = searchText && model.label.includes(searchText);
if (isFound) {
graph.setItemState(node, 'active', true);
lastFoundNode = node;
} else {
graph.setItemState(node, 'active', false);
}
const graphData = graph.get('initGraphData');
const filterChildrenData = findNodesByLabel(searchText, graphData.children);
refreshGraphData?.({
...graphData,
children: filterChildrenData,
});
if (lastFoundNode) {
// 将视图移动到找到的节点位置
graph.focusItem(lastFoundNode, true, {
duration: 300,
easing: 'easeCubic',
});
}
};
const generatorSearchInputDom = (graph) => {
const generatorSearchInputDom = (graph: Graph, refreshGraphData: RefreshGraphData) => {
const domString =
'<input placeholder="请输入指标/维度名称" class="ant-input" id="toolBarSearchInput" type="text" value="" />';
const searchInputDom = createDom(domString);
searchInputDom.addEventListener('keydown', (event) => {
if (event.key === 'Enter') {
searchNode(graph);
searchNode(graph, refreshGraphData);
}
});
return searchInputDom;
};
const generatorSearchBtnDom = (graph) => {
const generatorSearchBtnDom = (graph: Graph) => {
const domString = `<button
id="toolBarSearchBtn"
type="button"
@@ -66,11 +111,11 @@ const generatorSearchBtnDom = (graph) => {
return searchBtnDom;
};
const searchInputDOM = (graph) => {
const searchInputDom = generatorSearchInputDom(graph);
const searchInputDOM = (graph: Graph, refreshGraphData: RefreshGraphData) => {
const searchInputDom = generatorSearchInputDom(graph, refreshGraphData);
const searchBtnDom = generatorSearchBtnDom(graph);
const searchInput = `
<div id="searchInputContent" class="g6-component-toolbar-search-input" style="position: absolute;top: 38px;width: 190px;left: 0;display:none">
<div id="searchInputContent" class="g6-component-toolbar-search-input" style="position: absolute;top: 38px;width: 190px;left: 0;">
<span class="ant-input-group-wrapper ant-input-search" >
<span class="ant-input-wrapper ant-input-group" id="toolBarSearchWrapper">
<span class="ant-input-group-addon"></span>
@@ -84,7 +129,7 @@ const searchInputDOM = (graph) => {
return searchDom;
};
const initToolBar = () => {
const initToolBar = ({ refreshGraphData }: { refreshGraphData: RefreshGraphData }) => {
const toolBarInstance = new G6.ToolBar();
const config = toolBarInstance._cfgs;
const defaultContentDomString = config.getContent();
@@ -108,12 +153,12 @@ const initToolBar = () => {
</svg>
</li>`;
defaultContentDom.insertAdjacentHTML('afterbegin', searchBtnDom);
let searchInputContentVisible = false;
let searchInputContentVisible = true;
const toolbar = new G6.ToolBar({
position: { x: 10, y: 10 },
className: 'semantic-graph-toolbar',
getContent: (graph) => {
const searchInput = searchInputDOM(graph);
const searchInput = searchInputDOM(graph as Graph, refreshGraphData);
const content = `<div class="g6-component-toolbar-content">${defaultContentDom.outerHTML}</div>`;
const contentDom = createDom(content);
contentDom.appendChild(searchInput);