From 66be1546e2e07c1c92d8f5cec44c089b7f4ff679 Mon Sep 17 00:00:00 2001 From: pomelo-nwu Date: Wed, 15 Nov 2023 15:21:47 +0800 Subject: [PATCH] feat: update graphin --- packages/graphin/.umirc.ts | 17 ---- packages/graphin/package.json | 2 +- packages/graphin/src/ExtendGraph.tsx | 20 +++- packages/graphin/src/Graphin.tsx | 13 +-- packages/graphin/src/icon/loader.ts | 74 ++++++++++++++ .../graphin/src/icon/registerFontFamily.ts | 24 +++++ .../graphin/src/icon/registerIconFonts.ts | 61 ++++++++++++ packages/graphin/src/index.tsx | 27 +----- .../src/styling/edge-style-transform.ts | 64 ++++++++++--- .../src/styling/node-style-transform.ts | 96 +++++++++++++++---- packages/graphin/src/theme/edge-style.ts | 9 ++ packages/graphin/src/theme/node-style.ts | 7 +- packages/graphin/src/utils/mock.ts | 23 +++++ 13 files changed, 341 insertions(+), 96 deletions(-) delete mode 100644 packages/graphin/.umirc.ts create mode 100644 packages/graphin/src/icon/loader.ts create mode 100644 packages/graphin/src/icon/registerFontFamily.ts create mode 100644 packages/graphin/src/icon/registerIconFonts.ts diff --git a/packages/graphin/.umirc.ts b/packages/graphin/.umirc.ts deleted file mode 100644 index 03e3acf2a..000000000 --- a/packages/graphin/.umirc.ts +++ /dev/null @@ -1,17 +0,0 @@ -export default { - resolve: { includes: ['demo'] }, - externals: { - react: 'window.React', - 'react-dom': 'window.ReactDOM', - '@antv/g6': 'window.G6V5', - antd: 'window.antd', - }, - links: [{ href: 'https://gw.alipayobjects.com/os/lib/antd/4.24.14/dist/antd.css', rel: 'stylesheet' }], - scripts: [ - 'https://gw.alipayobjects.com/os/lib/react/17.0.2/umd/react.development.js', - 'https://gw.alipayobjects.com/os/lib/react-dom/17.0.2/umd/react-dom.development.js', - // 'https://gw.alipayobjects.com/os/lib/antv/g6/5.0.0-beta.20/dist/g6.min.js', - "http://127.0.0.1:9001/g6.min.js", - 'https://gw.alipayobjects.com/os/lib/antd/4.24.14/dist/antd.min.js', - ], -}; diff --git a/packages/graphin/package.json b/packages/graphin/package.json index fc7300d8a..8c91f0bf6 100644 --- a/packages/graphin/package.json +++ b/packages/graphin/package.json @@ -32,7 +32,7 @@ "postpublish": "tnpm sync @antv/graphin" }, "dependencies": { - "@antv/g6": "5.0.0-beta.5", + "@antv/g6": "5.0.0-beta.27", "lodash-es": "^4.17.21" }, "peerDependencies": { diff --git a/packages/graphin/src/ExtendGraph.tsx b/packages/graphin/src/ExtendGraph.tsx index f770588c3..2b242e8f0 100644 --- a/packages/graphin/src/ExtendGraph.tsx +++ b/packages/graphin/src/ExtendGraph.tsx @@ -2,21 +2,32 @@ import { Extensions, Graph as G6Graph, extend } from '@antv/g6'; const handler = (data, options = {}, graphCore) => { const { combos } = data; + console.log('node graphin....', data); + const nodes = (data.nodes || []).map(item => { - const { id, data } = item; + const { id, data, type, ...others } = item; + return { id: id, - data: data, + data: { + __type: type, + ...data, + }, + ...others, }; }); const edges = (data.edges || []).map((item, index) => { - const { source, target, id, data } = item; + const { source, target, id, type, data, ...others } = item; return { id: id || `edge-${index}`, source, target, - data, + data: { + __type: type, + ...data, + }, + ...others, }; }); @@ -43,6 +54,7 @@ export default extend(G6Graph, { }, nodes: { 'sphere-node': Extensions.SphereNode, + 'donut-node': Extensions.DonutNode, }, layouts: { diff --git a/packages/graphin/src/Graphin.tsx b/packages/graphin/src/Graphin.tsx index d38884346..ecda157c1 100644 --- a/packages/graphin/src/Graphin.tsx +++ b/packages/graphin/src/Graphin.tsx @@ -57,15 +57,6 @@ const Graphin: React.FunctionComponent = forwardRef((props, ref) = console.log('%c GRAPHIN DATA CHANGE....', 'color:yellow', data); console.time('GRAPHIN_CHANGE_DATA_COST'); //@ts-ignore - // if (dataRef.current.nodes !== data.nodes.length) { - // graph.updatePlugin({ - // key: 'lod-controller', - // type: 'lod-controller', - // //@ts-ignore - // disableLod: data.nodes.length < LOD_NODE_NUM_THRESHOLD, - // }); - // } - //@ts-ignore graph && graph.changeData(data, 'mergeReplace', false); //@ts-ignore if ( @@ -116,7 +107,7 @@ const Graphin: React.FunctionComponent = forwardRef((props, ref) = //@ts-ignore edge, // behaviors - modes = { default: [], lasso: [] }, + modes = { default: ['zoom-canvas', 'drag-canvas'], lasso: [] }, } = options; const ContainerDOM = document.getElementById(container); @@ -152,7 +143,7 @@ const Graphin: React.FunctionComponent = forwardRef((props, ref) = { type: 'lod-controller', //@ts-ignore - disableLod: data.nodes.length < LOD_NODE_NUM_THRESHOLD, + disableLod: true, //data.nodes.length < LOD_NODE_NUM_THRESHOLD, }, ], nodeState: { diff --git a/packages/graphin/src/icon/loader.ts b/packages/graphin/src/icon/loader.ts new file mode 100644 index 000000000..202690fc9 --- /dev/null +++ b/packages/graphin/src/icon/loader.ts @@ -0,0 +1,74 @@ +/** + * @file Icon loader + */ + +/** + * @description 获取图标字体资源的方式,离线方式需要将字体资源放到本地,供私有化部署使用 + */ +type Mode = 'online' | 'offline'; + +export type FontJson = { + id: string; + name: string; + font_family: string; + css_prefix_text: string; + description: string; + glyphs: { + icon_id: string; + name: string; + font_class: string; + unicode: string; + unicode_decimal: number; + }[]; +}; + +/** + * @description 加载 unicode json 资源 + */ +export async function loadFontJson(id: string, mode: Mode = 'online', url?: string): Promise { + if (mode === 'online') { + const _url = url || `https://at.alicdn.com/t/a/${id}.json`; + const json: FontJson = await fetch(_url).then(res => res.json()); + return json; + } + /** + * @description TODO offline + * @example 私有化部署示例 + * + * import font from './path/to/font.json'; + * return font; + */ + return { id: '', name: '', font_family: '', css_prefix_text: '', description: '', glyphs: [] }; +} + +/** + * @description 加载 unicode 字体 + */ +export async function loadUnicodeFont(id: string, mode: Mode = 'online') { + if (mode === 'online') { + const fontList = [ + { + fontUrl: `//at.alicdn.com/t/a/${id}.woff2`, + format: 'woff2', + }, + { + fontUrl: `//at.alicdn.com/t/a/${id}.woff`, + format: 'woff', + }, + { + fontUrl: `//at.alicdn.com/t/a/${id}.ttf`, + format: 'truetype', + }, + ]; + const load = async (fontFamily: string, fontUrl: string) => { + const font = new FontFace(fontFamily, `url(${fontUrl})`); + await font.load(); + //@ts-ignore + document.fonts.add(font); + }; + + await Promise.all(fontList.map(({ fontUrl }) => load('iconfont', fontUrl))); + } else { + // TODO: offline + } +} diff --git a/packages/graphin/src/icon/registerFontFamily.ts b/packages/graphin/src/icon/registerFontFamily.ts new file mode 100644 index 000000000..2d7093867 --- /dev/null +++ b/packages/graphin/src/icon/registerFontFamily.ts @@ -0,0 +1,24 @@ +export const registerFontFamily = iconLoader => { + /** 注册 font icon */ + const iconFont = iconLoader(); + const { glyphs, fontFamily } = iconFont; + const icons = glyphs.map(item => { + return { + name: item.name, + unicode: String.fromCodePoint(item.unicode_decimal), + }; + }); + + return new Proxy(icons, { + get: (target, propKey: string) => { + const matchIcon = target.find(icon => { + return icon.name === propKey; + }); + if (!matchIcon) { + console.error(`%c fontFamily:${fontFamily},does not found ${propKey} icon`); + return ''; + } + return matchIcon?.unicode; + }, + }); +}; diff --git a/packages/graphin/src/icon/registerIconFonts.ts b/packages/graphin/src/icon/registerIconFonts.ts new file mode 100644 index 000000000..ccdf61f10 --- /dev/null +++ b/packages/graphin/src/icon/registerIconFonts.ts @@ -0,0 +1,61 @@ +// 资源地址:https://www.iconfont.cn/manage/index?spm=a313x.7781069.1998910419.20&manage_type=myprojects&projectId=3381398&keyword=&project_type=&page= + +import { createFromIconfontCN } from '@ant-design/icons'; +import { loadFontJson, loadUnicodeFont, type FontJson } from './loader'; +import { registerFontFamily } from './registerFontFamily'; + +export const fontFamily = 'iconfont'; + +// --- 注册 font icon --- + +let icons = registerFontFamily(() => ({ fontFamily, glyphs: [] })); +let glyphs: FontJson['glyphs'] = []; + +async function loadFontsJson(ids: string[]) { + const fonts = await Promise.all(ids.map(id => loadFontJson(id))); + // 合并所有字体 + const _glyphs = fonts.reduce((acc, curr) => { + acc.push(...curr.glyphs); + return acc; + }, [] as FontJson['glyphs']); + + glyphs = _glyphs; + icons = registerFontFamily(() => ({ + fontFamily, + glyphs: _glyphs.map(item => { + return { + ...item, + name: item.font_class, //统一为font class + }; + }), + })); + return icons; +} + +// --- 注册 antd iconfont --- +const registeredIds = new Set(); +const builtInIconFontId = 'font_3381398_29c1c449r6f'; +const getIconfontScriptUrl = (id: string) => `//at.alicdn.com/t/a/${id}.js`; + +async function loadUnicodeFonts(ids: string[]) { + await Promise.all(ids.map(id => loadUnicodeFont(id))); +} + +export async function registerIconFonts(ids: string[] = [builtInIconFontId]) { + const unregisteredIds = ids.filter(id => !registeredIds.has(id)); + + if (!unregisteredIds.length) return icons; + + // register + createFromIconfontCN({ + scriptUrl: unregisteredIds.map(getIconfontScriptUrl), + }); + + const [ICONS] = await Promise.all([loadFontsJson(unregisteredIds), loadUnicodeFonts(unregisteredIds)]); + unregisteredIds.forEach(id => registeredIds.add(id)); + return ICONS; +} + +export function getFontIcons() { + return icons; +} diff --git a/packages/graphin/src/index.tsx b/packages/graphin/src/index.tsx index daee87f56..dbd8d4502 100644 --- a/packages/graphin/src/index.tsx +++ b/packages/graphin/src/index.tsx @@ -23,31 +23,8 @@ export const registerNode = () => {}; //@ts-ignore export const registerEdge = () => {}; -//@ts-ignore -export const registerFontFamily = iconLoader => { - /** 注册 font icon */ - const iconFont = iconLoader(); - const { glyphs, fontFamily } = iconFont; - const icons = glyphs.map(item => { - return { - name: item.name, - unicode: String.fromCodePoint(item.unicode_decimal), - }; - }); - - return new Proxy(icons, { - get: (target, propKey: string) => { - const matchIcon = target.find(icon => { - return icon.name === propKey; - }); - if (!matchIcon) { - console.error(`%c fontFamily:${fontFamily},does not found ${propKey} icon`); - return ''; - } - return matchIcon?.unicode; - }, - }); -}; +export { registerFontFamily } from './icon/registerFontFamily'; +export { getFontIcons, registerIconFonts } from './icon/registerIconFonts'; export { default as Behaviors, registerBehavior, useBehaviorHook } from './behaviors'; diff --git a/packages/graphin/src/styling/edge-style-transform.ts b/packages/graphin/src/styling/edge-style-transform.ts index 9bb39b697..d9c2975d1 100644 --- a/packages/graphin/src/styling/edge-style-transform.ts +++ b/packages/graphin/src/styling/edge-style-transform.ts @@ -1,15 +1,52 @@ -const transGraphinStyle = style => { - const { keyshape = {}, halo = {}, icon = {}, label = {}, badges = [] } = style || {}; +import { merge } from 'lodash-es'; +import getEdgeStyleByTheme from '../theme/edge-style'; +const { style: defaultStyle } = getEdgeStyleByTheme({ + primaryEdgeColor: '#ddd', + edgeSize: 1, + mode: 'light', +}); + +const transGraphinStyle = (style, otherStyles) => { + const { keyshape, halo, label } = merge({}, defaultStyle, style || {}) as typeof defaultStyle; + const { background } = label; + //@ts-ignore  用户指定的优先级最高 + const { poly, loop } = keyshape; + //@ts-ignore + const { isMultiple, type } = otherStyles; + //@ts-ignore + const { loopCfg, curveOffset } = otherStyles.keyShape || {}; return { - type: 'line-edge', + type: type || 'line-edge', keyShape: { - opacity: 0.6, // 边主图形的透明度 - stroke: 'grey', // 边主图形描边颜色 + opacity: keyshape.strokeOpacity, // 边主图形的透明度 + stroke: keyshape.stroke, // 边主图形描边颜色 + lineAppendWidth: keyshape.lineAppendWidth, + lineWidth: keyshape.lineWidth, + endArrow: { + path: '', + }, + ...(curveOffset ? { curveOffset: (poly && poly.distance) || curveOffset } : {}), + ...(loopCfg ? { loopCfg: loop || loopCfg } : {}), + + // ...(curveOffset ? { curveOffset } : {}), + // ...(loopCfg ? { loopCfg } : {}), }, // 边上的标签文本配置 labelShape: { + text: label.value, + position: label.position, + fill: label.fill, + fontSize: label.fontSize, + textAlign: label.textAlign, autoRotate: true, // 边上的标签文本根据边的方向旋转 + maxLines: '400%', + }, + labelBackgroundShape: { + radius: background.radius, + fill: background.fill, + stroke: background.stroke, + opacity: background.opacity, }, // 边的动画配置 // animates: { @@ -29,18 +66,19 @@ const transGraphinStyle = style => { }; export const edgeStyleTransform = edge => { - const { style, type, id, data, source, target } = edge; - const IS_GRAPHIN = (style && type === 'graphin-line') || !type; - // console.log('edge', edge); + const { id, source, target, data } = edge; + const { __type, style } = data; + const IS_GRAPHIN = (__type && __type === 'graphin-line') || !__type; + if (IS_GRAPHIN) { + const { isMultiple, keyShape, type } = data; + const displayData = transGraphinStyle(style, { isMultiple, keyShape, type }); + console.log('edge', edge, IS_GRAPHIN, displayData); return { source, target, - id: id || `${source}-${target}-${Math.random()}`, - data: { - ...data, - ...transGraphinStyle(style), - }, + id: id, + data: displayData, }; } return edge; diff --git a/packages/graphin/src/styling/node-style-transform.ts b/packages/graphin/src/styling/node-style-transform.ts index 6b731a061..4106261b0 100644 --- a/packages/graphin/src/styling/node-style-transform.ts +++ b/packages/graphin/src/styling/node-style-transform.ts @@ -1,48 +1,104 @@ +import { merge } from 'lodash-es'; +import getNodeStyleByTheme from '../theme/node-style'; +const { style: defaultStyle } = getNodeStyleByTheme({ + primaryColor: '#873af4', + nodeSize: 26, + mode: 'light', +}); + const transGraphinStyle = style => { - const { keyshape = {}, halo = {}, icon = {}, label = {}, badges = [] } = style || {}; + const { keyshape, halo, icon, label, badges } = merge({}, defaultStyle, style || {}) as typeof defaultStyle; + let iconShape: any = { + visible: false, + text: '', + }; + if (icon.type === 'image') { + iconShape = { + fill: 'transparent', + r: [keyshape.size, keyshape.size], + clip: { r: keyshape.size / 2 }, + img: icon.value, + visible: true, + }; + } + if (icon.type === 'font') { + iconShape = { + fontSize: keyshape.size / 2, + fontFamily: 'iconfont', + text: icon.value || '', + //@ts-ignore + fill: icon.fill || keyshape.fill, + visible: true, + }; + } + if (icon.type === 'text') { + iconShape = { + fontSize: keyshape.size / 2, + fill: '#fff', + text: icon.value, + visible: true, + }; + } + console.log('iconShape', iconShape, icon, style); return { type: 'circle-node', labelShape: { - text: label.value || '', - position: label.position || 'bottom', + text: label.value, + position: label.position, + fill: label.fill, + fillOpacity: label.fillOpacity, + fontSize: label.fontSize, + offset: label.offset, + maxWidth: '500%', }, keyShape: { - r: keyshape.size || 10, - fill: keyshape.fill || 'red', - stroke: keyshape.stroke || 'red', - strokeOpacity: keyshape.strokeOpacity || 1, + r: keyshape.size / 2, + fill: keyshape.fill, + stroke: keyshape.stroke, + strokeOpacity: keyshape.strokeOpacity, + opacity: keyshape.opacity, + fillOpacity: keyshape.fillOpacity, + lineWidth: keyshape.lineWidth, }, + iconShape, animates: { update: [ { fields: ['x', 'y'], shapeId: 'group', }, - // { - // fields: ['opacity'], - // shapeId: 'haloShape', - // }, - // { - // fields: ['lineWidth'], - // shapeId: 'keyShape', - // }, ], }, }; }; export const nodeStyleTransform = node => { - const { style, type, id, data } = node; + const { id, data, style } = node; - const IS_GRAPHIN = (style && type === 'graphin-circle') || !type; + const { x = 0, y = 0, z = 0 } = data; - if (IS_GRAPHIN) { + if (style) { + const { type } = style; + const IS_GRAPHIN = !type || type === 'graphin-circle'; + if (IS_GRAPHIN) { + return { + id, + data: { + x, + y, + z, + ...transGraphinStyle(style), + }, + }; + } return { id, data: { - // ...data, - ...transGraphinStyle(style), + x, + y, + z, + ...style, }, }; } diff --git a/packages/graphin/src/theme/edge-style.ts b/packages/graphin/src/theme/edge-style.ts index 5aebff38a..207cfc537 100644 --- a/packages/graphin/src/theme/edge-style.ts +++ b/packages/graphin/src/theme/edge-style.ts @@ -56,6 +56,15 @@ const getEdgeStyleByTheme = (inputTheme: EdgeTheme) => { fill: Color.label, fontSize: 12, textAlign: 'center', + background: { + // 设置背景的填充色 + fill: 'transparent', + // 设置圆角 + radius: 2, + // 设置border,即 stroke + stroke: 'transparent', + opacity: 1, + }, }, animate: {}, }, diff --git a/packages/graphin/src/theme/node-style.ts b/packages/graphin/src/theme/node-style.ts index a6e7d1962..e4de622dc 100644 --- a/packages/graphin/src/theme/node-style.ts +++ b/packages/graphin/src/theme/node-style.ts @@ -68,12 +68,9 @@ const getNodeStyleByTheme = (inputNodeTheme: NodeTheme) => { fillOpacity: 1, }, icon: { - type: 'text', + type: '', value: '', - size: nodeSize / 2, - fill: Color.icon, - fillOpacity: 1, - offset: [0, 0], + visible: false, }, badges: [], halo: { diff --git a/packages/graphin/src/utils/mock.ts b/packages/graphin/src/utils/mock.ts index 64becaa48..6de064e70 100644 --- a/packages/graphin/src/utils/mock.ts +++ b/packages/graphin/src/utils/mock.ts @@ -300,6 +300,29 @@ export class Mock { combos: this.combosData, }; }; + g6 = (): GraphinData => { + return { + // @ts-ignore + nodes: this.nodes.map(node => { + return { + ...node, + id: node.id, + type: 'circle-node', + comboId: node.comboId, + data: {}, + }; + }), + edges: this.edges.map((edge, index) => { + return { + id: `edge-${index}`, + source: edge.source, + target: edge.target, + data: {}, + }; + }), + combos: this.combosData, + }; + }; graphinTree = (): GraphinTreeData => { const tree = this.treeData.getRoot();