From b00ec104f8ea98902b4d784bfc6a0ee0b5461827 Mon Sep 17 00:00:00 2001 From: Aaron Date: Mon, 2 Sep 2024 10:02:43 +0800 Subject: [PATCH] feat: add demo about why do cats (#6259) * test: add demo of why do cats * docs: add site demo * docs: add demo ref --------- Co-authored-by: antv --- package.json | 4 + .../g6/__tests__/demos/case-why-do-cats.ts | 189 ++++++++++++++++++ packages/g6/__tests__/demos/index.ts | 1 + packages/g6/src/runtime/graph.ts | 1 + packages/site/.dumi/global.ts | 4 + .../scene-case/default/demo/meta.json | 8 + .../scene-case/default/demo/why-do-cats.js | 174 ++++++++++++++++ 7 files changed, 381 insertions(+) create mode 100644 packages/g6/__tests__/demos/case-why-do-cats.ts create mode 100644 packages/site/examples/scene-case/default/demo/why-do-cats.js diff --git a/package.json b/package.json index fbb6e11a0d1..dd07585899b 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,8 @@ ] }, "devDependencies": { + "@antv/g-canvas": "^2.0.10", + "@antv/g-plugin-rough-canvas-renderer": "^2.0.12", "@babel/core": "^7.25.2", "@babel/plugin-transform-typescript": "^7.25.2", "@changesets/cli": "^2.27.7", @@ -42,12 +44,14 @@ "@rollup/plugin-typescript": "^11.1.6", "@swc/core": "^1.7.6", "@swc/jest": "^0.2.36", + "@types/d3-hierarchy": "^3.1.7", "@types/jest": "^29.5.12", "@types/jsdom": "^21.1.7", "@types/node": "^20.14.14", "@typescript-eslint/eslint-plugin": "^6.21.0", "@typescript-eslint/parser": "^6.21.0", "chalk": "^4.1.2", + "d3-hierarchy": "^3.1.2", "eslint": "^8.57.0", "eslint-plugin-jsdoc": "^46.10.1", "husky": "^8.0.3", diff --git a/packages/g6/__tests__/demos/case-why-do-cats.ts b/packages/g6/__tests__/demos/case-why-do-cats.ts new file mode 100644 index 00000000000..56438b01949 --- /dev/null +++ b/packages/g6/__tests__/demos/case-why-do-cats.ts @@ -0,0 +1,189 @@ +import { Renderer as CanvasRenderer } from '@antv/g-canvas'; +import { Plugin as PluginRoughCanvasRenderer } from '@antv/g-plugin-rough-canvas-renderer'; +import type { ComboData, GraphData, NodeData } from '@antv/g6'; +import { BaseLayout, ExtensionCategory, Graph, register } from '@antv/g6'; +import { hierarchy, pack } from 'd3-hierarchy'; + +export const caseWhyDoCats: TestCase = async (context) => { + const style = document.createElement('style'); + style.innerHTML = ` + @font-face { + font-family: 'handwriting'; + src: url('https://mass-office.alipay.com/huamei_koqzbu/afts/file/sgUeRbI3d-IAAAAAAAAAABAADnV5AQBr/font.woff2') + format('woff2'); +}`; + document.head.appendChild(style); + + function getColor(id: string) { + const colors = [ + '#8dd3c7', + '#bebada', + '#fb8072', + '#80b1d3', + '#fdb462', + '#b3de69', + '#fccde5', + '#d9d9d9', + '#bc80bd', + '#ccebc5', + '#ffed6f', + ]; + const index = parseInt(id); + return colors[index % colors.length]; + } + + type RowDatum = { + animal: string; + id: string; + id_num: string; + index_value: string; + leaf: string; + parentId: string; + remainder: string; + start_sentence: string; + sum_index_value: string; + text: string; + }; + + const rawData: RowDatum[] = await fetch('https://assets.antv.antgroup.com/g6/cat-hierarchy.json').then((res) => + res.json(), + ); + + const topics = [ + 'cat.like', + 'cat.hate', + 'cat.love', + 'cat.not.like', + 'cat.afraid_of', + 'cat.want.to', + 'cat.scared.of', + 'cat.not.want_to', + ]; + + const graphData = rawData.reduce( + (acc, row) => { + const { id } = row; + topics.forEach((topic) => { + if (id.startsWith(topic)) { + if (id === topic) { + acc.nodes.push({ ...row, depth: 1 }); + } else { + acc.nodes.push({ ...row, depth: 2, actualParentId: topic }); + } + } + }); + + return acc; + }, + { nodes: [], edges: [], combos: [] } as Required, + ); + + class BubbleLayout extends BaseLayout { + id = 'bubble-layout'; + + public async execute(model: GraphData, options?: any): Promise { + const { nodes = [] } = model; + + const { width = 0, height = 0 } = { ...this.options, ...options }; + + const root = hierarchy({ id: 'root' }, (datum) => { + const { id } = datum; + if (id === 'root') return nodes.filter((node) => node.depth === 1); + else if (datum.depth === 2) return []; + else return nodes.filter((node) => node.actualParentId === id); + }); + + root.sum((d: any) => (+d.index_value || 0.01) ** 0.5 * 100); + + pack() + .size([width, height]) + .padding((node) => { + return node.depth === 0 ? 20 : 2; + })(root); + + const result: Required = { nodes: [], edges: [], combos: [] }; + + root.descendants().forEach((node) => { + const { + data: { id }, + x, + y, + // @ts-expect-error r is exist + r, + } = node; + + if (node.depth >= 1) result.nodes.push({ id, style: { x, y, size: r * 2 } }); + }); + + return result; + } + } + + register(ExtensionCategory.LAYOUT, 'bubble-layout', BubbleLayout); + + const graph = new Graph({ + ...context, + animation: false, + data: graphData, + renderer: (layer) => { + const renderer = new CanvasRenderer(); + if (layer === 'main') { + renderer.registerPlugin(new PluginRoughCanvasRenderer()); + } + return renderer; + }, + node: { + style: (d) => { + const id_num = d.id_num as string; + const color = getColor(id_num); + + if (d.depth === 1) { + return { + fill: 'none', + stroke: color, + labelFontFamily: 'handwriting', + labelFontSize: 20, + labelText: d.id.replace('cat.', '').replace(/\.|_/g, ' '), + labelTextTransform: 'capitalize', + lineWidth: 1, + zIndex: -1, + }; + } + + const text = d.text as string; + const diameter = d.style!.size as number; + + return { + fill: color, + fillOpacity: 0.7, + stroke: color, + fillStyle: 'cross-hatch', + hachureGap: 1.5, + iconFontFamily: 'handwriting', + iconFontSize: (diameter / text.length) * 2, + iconText: diameter > 20 ? d.text : '', + iconFontWeight: 'bold', + iconStroke: color, + iconLineWidth: 2, + lineWidth: (diameter || 20) ** 0.5 / 5, + }; + }, + }, + layout: { + type: 'bubble-layout', + }, + plugins: [ + { + type: 'tooltip', + getContent: (event: any, items: NodeData[]) => { + return `${items[0].id.replace(/\.|_/g, ' ')}`; + }, + }, + ], + behaviors: [{ type: 'drag-canvas', enable: true }, 'zoom-canvas'], + }); + + await graph.render(); + + return graph; +}; diff --git a/packages/g6/__tests__/demos/index.ts b/packages/g6/__tests__/demos/index.ts index 71dda06dc8b..8c88fae237e 100644 --- a/packages/g6/__tests__/demos/index.ts +++ b/packages/g6/__tests__/demos/index.ts @@ -25,6 +25,7 @@ export { caseDecisionTree } from './case-decision-tree'; export { caseIndentedTree } from './case-indented-tree'; export { caseMindmap } from './case-mindmap'; export { caseOrgChart } from './case-org-chart'; +export { caseWhyDoCats } from './case-why-do-cats'; export { commonGraph } from './common-graph'; export { controllerViewport } from './controller-viewport'; export { demoAutosizeElementLabel } from './demo-autosize-element-label'; diff --git a/packages/g6/src/runtime/graph.ts b/packages/g6/src/runtime/graph.ts index c65d72f82f5..c492ce88a9c 100644 --- a/packages/g6/src/runtime/graph.ts +++ b/packages/g6/src/runtime/graph.ts @@ -1019,6 +1019,7 @@ export class Graph extends EventEmitter { if (container instanceof Canvas) { this.context.canvas = container; if (cursor) container.setCursor(cursor); + if (renderer) container.setRenderer(renderer); await container.ready; } else { const $container = isString(container) ? document.getElementById(container!) : container; diff --git a/packages/site/.dumi/global.ts b/packages/site/.dumi/global.ts index d3e05f20d89..6176573da8d 100644 --- a/packages/site/.dumi/global.ts +++ b/packages/site/.dumi/global.ts @@ -1,5 +1,9 @@ // @ts-nocheck if (window) { + window.d3Hierarchy = require('d3-hierarchy'); + window.gCanvas = require('@antv/g-canvas'); + window.gPluginRoughCanvasRenderer = require('@antv/g-plugin-rough-canvas-renderer'); + window.g6 = require('@antv/g6'); window.g6Extension3d = require('@antv/g6-extension-3d'); window.g6ExtensionReact = require('@antv/g6-extension-react'); diff --git a/packages/site/examples/scene-case/default/demo/meta.json b/packages/site/examples/scene-case/default/demo/meta.json index e29d85c0585..1b25184bd9d 100644 --- a/packages/site/examples/scene-case/default/demo/meta.json +++ b/packages/site/examples/scene-case/default/demo/meta.json @@ -59,6 +59,14 @@ "en": "Sub Graph" }, "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*2HzDTrQZ910AAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "why-do-cats.js", + "title": { + "zh": "猫咪喜好", + "en": "Why Do Cats?" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*ug4vTJA7QbMAAAAAAAAAAAAADmJ7AQ/original" } ] } diff --git a/packages/site/examples/scene-case/default/demo/why-do-cats.js b/packages/site/examples/scene-case/default/demo/why-do-cats.js new file mode 100644 index 00000000000..59795255aa4 --- /dev/null +++ b/packages/site/examples/scene-case/default/demo/why-do-cats.js @@ -0,0 +1,174 @@ +// ref: https://whydocatsanddogs.com/cats +import { Renderer as CanvasRenderer } from '@antv/g-canvas'; +import { Plugin as PluginRoughCanvasRenderer } from '@antv/g-plugin-rough-canvas-renderer'; +import { BaseLayout, ExtensionCategory, Graph, register } from '@antv/g6'; +import { hierarchy, pack } from 'd3-hierarchy'; + +const style = document.createElement('style'); +style.innerHTML = ` +@font-face { +font-family: 'handwriting'; +src: url('https://mass-office.alipay.com/huamei_koqzbu/afts/file/sgUeRbI3d-IAAAAAAAAAABAADnV5AQBr/font.woff2') + format('woff2'); +}`; +document.head.appendChild(style); + +function getColor(id) { + const colors = [ + '#8dd3c7', + '#bebada', + '#fb8072', + '#80b1d3', + '#fdb462', + '#b3de69', + '#fccde5', + '#d9d9d9', + '#bc80bd', + '#ccebc5', + '#ffed6f', + ]; + const index = parseInt(id); + return colors[index % colors.length]; +} + +const topics = [ + 'cat.like', + 'cat.hate', + 'cat.love', + 'cat.not.like', + 'cat.afraid_of', + 'cat.want.to', + 'cat.scared.of', + 'cat.not.want_to', +]; + +class BubbleLayout extends BaseLayout { + id = 'bubble-layout'; + + async execute(model, options) { + const { nodes = [] } = model; + + const { width = 0, height = 0 } = { ...this.options, ...options }; + + const root = hierarchy({ id: 'root' }, (datum) => { + const { id } = datum; + if (id === 'root') return nodes.filter((node) => node.depth === 1); + else if (datum.depth === 2) return []; + else return nodes.filter((node) => node.actualParentId === id); + }); + + root.sum((d) => (+d.index_value || 0.01) ** 0.5 * 100); + + pack() + .size([width, height]) + .padding((node) => { + return node.depth === 0 ? 20 : 2; + })(root); + + const result = { nodes: [] }; + + root.descendants().forEach((node) => { + const { + data: { id }, + x, + y, + // @ts-expect-error r is exist + r, + } = node; + + if (node.depth >= 1) result.nodes.push({ id, style: { x, y, size: r * 2 } }); + }); + + return result; + } +} + +register(ExtensionCategory.LAYOUT, 'bubble-layout', BubbleLayout); + +fetch('https://assets.antv.antgroup.com/g6/cat-hierarchy.json') + .then((res) => res.json()) + .then((rawData) => { + const graphData = rawData.reduce( + (acc, row) => { + const { id } = row; + topics.forEach((topic) => { + if (id.startsWith(topic)) { + if (id === topic) { + acc.nodes.push({ ...row, depth: 1 }); + } else { + acc.nodes.push({ ...row, depth: 2, actualParentId: topic }); + } + } + }); + + return acc; + }, + { nodes: [] }, + ); + + const graph = new Graph({ + container: 'container', + animation: false, + data: graphData, + renderer: (layer) => { + const renderer = new CanvasRenderer(); + if (layer === 'main') { + renderer.registerPlugin(new PluginRoughCanvasRenderer()); + } + return renderer; + }, + node: { + style: (d) => { + const { id, depth, id_num } = d; + const color = getColor(id_num); + + if (depth === 1) { + return { + fill: 'none', + stroke: color, + labelFontFamily: 'handwriting', + labelFontSize: 20, + labelText: id.replace('cat.', '').replace(/\.|_/g, ' '), + labelTextTransform: 'capitalize', + lineWidth: 1, + zIndex: -1, + }; + } + + const { + text, + style: { size: diameter }, + } = d; + + return { + fill: color, + fillOpacity: 0.7, + stroke: color, + fillStyle: 'cross-hatch', + hachureGap: 1.5, + iconFontFamily: 'handwriting', + iconFontSize: (diameter / text.length) * 2, + iconText: diameter > 20 ? text : '', + iconFontWeight: 'bold', + iconStroke: color, + iconLineWidth: 2, + lineWidth: (diameter || 20) ** 0.5 / 5, + }; + }, + }, + layout: { + type: 'bubble-layout', + }, + plugins: [ + { + type: 'tooltip', + getContent: (event, items) => { + return `${items[0].id.replace(/\.|_/g, ' ')}`; + }, + }, + ], + behaviors: [{ type: 'drag-canvas', enable: true }, 'zoom-canvas'], + }); + + graph.render(); + });