From 4873ede6f7f17424eb4c25b745e0a912d08263c7 Mon Sep 17 00:00:00 2001 From: gongbei Date: Thu, 28 Nov 2024 15:27:58 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9EtreeLineShow=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E5=92=8CusePolyline=EF=BC=8C=E7=BB=99=E6=A0=91?= =?UTF-8?q?=E5=BD=A2=E8=A1=A8=E5=A4=B4=E6=89=A9=E5=B1=95=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E7=BA=BF=E6=80=A7=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../s2-core/src/common/interface/s2Options.ts | 4 + .../playground/components/CustomTree.tsx | 1 + packages/s2-react/src/hooks/index.ts | 1 + packages/s2-react/src/hooks/useLoading.ts | 6 + packages/s2-react/src/hooks/usePolyline.ts | 166 ++++++++++++++++++ packages/s2-react/src/hooks/useSpreadSheet.ts | 3 + 6 files changed, 181 insertions(+) create mode 100644 packages/s2-react/src/hooks/usePolyline.ts diff --git a/packages/s2-core/src/common/interface/s2Options.ts b/packages/s2-core/src/common/interface/s2Options.ts index 8639a59e2e..b462ea0927 100644 --- a/packages/s2-core/src/common/interface/s2Options.ts +++ b/packages/s2-core/src/common/interface/s2Options.ts @@ -331,6 +331,10 @@ export interface S2PivotSheetOptions { */ hierarchyType?: HierarchyType; + /** + * 是否配置线性样式,默认不配置 + */ + treeLineShow?: boolean; /** * 小计/总计配置 * @see https://s2.antv.antgroup.com/manual/basic/totals diff --git a/packages/s2-react/playground/components/CustomTree.tsx b/packages/s2-react/playground/components/CustomTree.tsx index a54ca56f5e..3c2e15332e 100644 --- a/packages/s2-react/playground/components/CustomTree.tsx +++ b/packages/s2-react/playground/components/CustomTree.tsx @@ -28,6 +28,7 @@ export const CustomTreeOptions: SheetComponentOptions = { width: 600, height: 480, hierarchyType: 'tree', + treeLineShow: true, transformCanvasConfig() { return { supportsCSSTransform: true, diff --git a/packages/s2-react/src/hooks/index.ts b/packages/s2-react/src/hooks/index.ts index aa0d029863..520a78e8d6 100644 --- a/packages/s2-react/src/hooks/index.ts +++ b/packages/s2-react/src/hooks/index.ts @@ -2,5 +2,6 @@ export * from './useEvents'; export * from './useLoading'; export * from './usePagination'; export * from './usePivotSheetUpdate'; +export * from './usePolyline'; export * from './useResize'; export * from './useSpreadSheet'; diff --git a/packages/s2-react/src/hooks/useLoading.ts b/packages/s2-react/src/hooks/useLoading.ts index 72d1418dc8..c4fc995f33 100644 --- a/packages/s2-react/src/hooks/useLoading.ts +++ b/packages/s2-react/src/hooks/useLoading.ts @@ -1,10 +1,12 @@ import { S2Event, SpreadSheet } from '@antv/s2'; import React from 'react'; +import { usePolyline } from './usePolyline'; export const useLoading = (s2: SpreadSheet, loadingFromProps?: boolean) => { const [loading, setLoading] = React.useState( loadingFromProps ?? false, ); + const { drawDottedLines } = usePolyline(); React.useEffect(() => { s2?.on(S2Event.LAYOUT_BEFORE_RENDER, () => { @@ -13,6 +15,10 @@ export const useLoading = (s2: SpreadSheet, loadingFromProps?: boolean) => { s2?.on(S2Event.LAYOUT_AFTER_RENDER, () => { setLoading(false); + drawDottedLines(s2); + }); + s2?.on(S2Event.GLOBAL_SCROLL, () => { + drawDottedLines(s2); }); }, [s2]); diff --git a/packages/s2-react/src/hooks/usePolyline.ts b/packages/s2-react/src/hooks/usePolyline.ts new file mode 100644 index 0000000000..8faeac69d6 --- /dev/null +++ b/packages/s2-react/src/hooks/usePolyline.ts @@ -0,0 +1,166 @@ +import { Polyline } from '@antv/g'; +import { Node, PivotSheet, SpreadSheet, TableSheet } from '@antv/s2'; +import { get } from 'lodash'; + +// 存储已绘制的连接线,用于销毁 +const dottedLines: Polyline[] = []; + +export const usePolyline = () => { + /** + * 销毁已存在的虚线 + */ + const destroyDottedLines = (lines: Polyline[] = []) => { + while (lines.length) { + lines?.pop()?.destroy(); + } + }; + + /** + * 获取列头的高度 + */ + const getColHeaderHeight = (chart: SpreadSheet | TableSheet | PivotSheet) => { + return chart.facet.getColNodes(0)?.[0]?.hierarchy?.height; + }; + + /** + * 获取滚动高度 + */ + const getOffsetHeight = (chart: SpreadSheet | TableSheet | PivotSheet) => { + return chart.facet.getScrollOffset().scrollY; + }; + /** + * 获取树级行头嵌套深度 + */ + const getRowHeaderTreeDepth = ( + chart: SpreadSheet | TableSheet | PivotSheet, + ) => { + return chart.facet.getRowNodes(0)?.[0]?.hierarchy?.maxLevel; + }; + /** + * 获取视口高度(列头 + 数值区域) + */ + const getViewportHeight = (chart: SpreadSheet | TableSheet | PivotSheet) => { + return chart.facet.panelBBox.viewportHeight + getColHeaderHeight(chart); + }; + + /** + * 获取 tree icon 的信息 + */ + const getTreeIconCfg = (node: Node) => { + if ( + get(node, 'belongsCell.treeIcon.cfg') && + !get(node, 'belongsCell.treeIcon.cfg.destroyed') + ) { + return get(node, 'belongsCell.treeIcon.cfg'); + } + + return { + x: 8 + node.level * 14, + y: node.y + node.height / 2 - 10 / 2, + width: 10, + height: 10, + }; + }; + + const drawDottedLines = (chart: SpreadSheet | TableSheet | PivotSheet) => { + if (chart?.options?.treeLineShow) { + destroyDottedLines(dottedLines); + const canvas = chart.container; + const colHeaderHeight = getColHeaderHeight(chart); + const viewportHeight = getViewportHeight(chart); + const offsetHeight = getOffsetHeight(chart); + + const rowHeaderTreeDepth = getRowHeaderTreeDepth(chart); + + for (let i = 0; i <= rowHeaderTreeDepth; i++) { + // 获取当前层级的节点 + const rowNodes = chart.facet.getRowNodes(i); + + rowNodes.forEach((rowNode) => { + // && + // 如果当前行头是叶子节点,那么就不需要画 + // rowNode.children.some((child) => _.get(child, 'belongsCell.treeIcon')) + if (rowNode.children.length) { + // 避免重复画线导致的颜色过深 + const childs: Node[] = []; + + rowNode.children.forEach((child) => { + const rowNodeTreeIconCfg = getTreeIconCfg(rowNode); + const childTreeIconCfg = getTreeIconCfg(child); + + if (rowNodeTreeIconCfg && childTreeIconCfg) { + const x1 = + rowNode.x + + rowNodeTreeIconCfg.x + + rowNodeTreeIconCfg.width / 2; + let y1 = + colHeaderHeight + + rowNodeTreeIconCfg.y + + rowNodeTreeIconCfg.height; + + if (childs?.length > 0) { + const preChild = childs?.pop()!; + const preChildTreeIconCfg = getTreeIconCfg(preChild); + + y1 = + colHeaderHeight + + preChildTreeIconCfg.y + + preChildTreeIconCfg.height / 2; + } + + childs.push(child); + const x2 = child.x + childTreeIconCfg.x; + const y2 = + colHeaderHeight + + childTreeIconCfg.y + + childTreeIconCfg.height / 2; + const points = [ + [ + x1, + Math.min( + Math.max(y1 - offsetHeight, colHeaderHeight), + viewportHeight, + ), + ], + [ + x1, + Math.min( + Math.max(y2 - offsetHeight, colHeaderHeight), + viewportHeight, + ), + ], + ]; + + if ( + y2 - offsetHeight >= colHeaderHeight && + y2 - offsetHeight <= viewportHeight + ) { + points.push([x2, y2 - offsetHeight]); + } + + const dottedLine = canvas.appendChild( + new Polyline({ + style: { + points: points as any, + stroke: '#000', + lineDash: [2, 3], + lineWidth: 1, + lineJoin: 'round', + zIndex: 999, + }, + }), + ); + + dottedLines.push(dottedLine); + } + }); + } + }); + } + } + }; + + return { + drawDottedLines, + }; +}; diff --git a/packages/s2-react/src/hooks/useSpreadSheet.ts b/packages/s2-react/src/hooks/useSpreadSheet.ts index 661bdad5fe..72a34d98fa 100644 --- a/packages/s2-react/src/hooks/useSpreadSheet.ts +++ b/packages/s2-react/src/hooks/useSpreadSheet.ts @@ -15,6 +15,7 @@ import { getSheetComponentOptions } from '../utils'; import { useEvents } from './useEvents'; import { useLoading } from './useLoading'; import { usePagination } from './usePagination'; +import { usePolyline } from './usePolyline'; import { useResize } from './useResize'; export function useSpreadSheet(props: SheetComponentProps) { @@ -50,6 +51,7 @@ export function useSpreadSheet(props: SheetComponentProps) { const { loading, setLoading } = useLoading(s2Ref.current!, props.loading); const pagination = usePagination(s2Ref.current!, props.options!); + const { drawDottedLines } = usePolyline(); useEvents(props, s2Ref.current!); @@ -82,6 +84,7 @@ export function useSpreadSheet(props: SheetComponentProps) { s2.setThemeCfg(props.themeCfg); await s2.render(); setLoading(false); + drawDottedLines(s2); s2Ref.current = s2; /**