From 98f2b6766489a1fcdaf936c43ab60d0771dad164 Mon Sep 17 00:00:00 2001 From: baizn <576375879@qq.com> Date: Wed, 15 Jul 2020 14:47:48 +0800 Subject: [PATCH 01/77] feat: slider timebar component --- package.json | 6 +- src/index.ts | 5 +- src/plugins/index.ts | 4 +- src/plugins/timeBar/index.ts | 240 +++++++++++++++++++++++++++++ tests/unit/plugins/timebar-spec.ts | 91 +++++++++++ 5 files changed, 342 insertions(+), 4 deletions(-) create mode 100644 src/plugins/timeBar/index.ts create mode 100644 tests/unit/plugins/timebar-spec.ts diff --git a/package.json b/package.json index 2c48710847..3de41c8763 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "site:deploy": "npm run site:build && gh-pages -d public", "start": "npm run site:develop", "test": "jest", - "test-live": "DEBUG_MODE=1 jest --watch ./tests/unit/graph/tree-graph-spec.ts", + "test-live": "DEBUG_MODE=1 jest --watch ./tests/unit/plugins/timebar-spec.ts", "lint-staged:js": "eslint --ext .js,.jsx,.ts,.tsx", "watch": "father build -w", "cdn": "antv-bin upload -n @antv/g6" @@ -69,6 +69,8 @@ "**/*.{js,ts,tsx}": "npm run lint-staged:js" }, "dependencies": { + "@antv/color-util": "^2.0.5", + "@antv/component": "^0.6.1", "@antv/dom-util": "^2.0.1", "@antv/event-emitter": "~0.1.0", "@antv/g-base": "^0.4.1", @@ -125,4 +127,4 @@ "webpack-cli": "^3.3.10", "worker-loader": "^2.0.0" } -} \ No newline at end of file +} diff --git a/src/index.ts b/src/index.ts index 356c706dfd..75fdef24b9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -20,6 +20,7 @@ const Bundling = Plugins.Bundling; const Menu = Plugins.Menu; const ToolBar = Plugins.ToolBar const Tooltip = Plugins.Tooltip +const TimeBar = Plugins.TimeBar export { registerNode, @@ -38,7 +39,8 @@ export { registerBehavior, Algorithm, ToolBar, - Tooltip + Tooltip, + TimeBar }; export default { @@ -59,6 +61,7 @@ export default { Menu: Plugins.Menu, ToolBar: Plugins.ToolBar, Tooltip: Plugins.Tooltip, + TimeBar, Algorithm, Arrow, Marker diff --git a/src/plugins/index.ts b/src/plugins/index.ts index 94b4a89da4..c6c9d2e542 100644 --- a/src/plugins/index.ts +++ b/src/plugins/index.ts @@ -4,6 +4,7 @@ import Minimap from './minimap'; import Bundling from './bundling'; import ToolBar from './toolBar' import Tooltip from './tooltip' +import TimeBar from './timeBar' export default { Menu, @@ -11,5 +12,6 @@ export default { Minimap, Bundling, ToolBar, - Tooltip + Tooltip, + TimeBar }; diff --git a/src/plugins/timeBar/index.ts b/src/plugins/timeBar/index.ts new file mode 100644 index 0000000000..b12831bdcf --- /dev/null +++ b/src/plugins/timeBar/index.ts @@ -0,0 +1,240 @@ +import modifyCSS from '@antv/dom-util/lib/modify-css'; +import createDOM from '@antv/dom-util/lib/create-dom'; +import isString from '@antv/util/lib/is-string' +import { IGroup } from '@antv/g-base' +import { Canvas } from '@antv/g-canvas' +import { Slider } from '@antv/component' +import { ShapeStyle, GraphData } from '../../types'; +import Base, { IPluginBaseConfig } from '../base'; +import { IGraph } from '../../interface/graph'; + +interface Data { + date: string; + value: number; +} + +interface Callback { + originValue: number[]; + value: number[]; + target: IGroup; +} + +interface TrendConfig { + readonly data: Data[]; + // 样式 + readonly smooth?: boolean; + readonly isArea?: boolean; + readonly backgroundStyle?: ShapeStyle; + readonly lineStyle?: ShapeStyle; + readonly areaStyle?: ShapeStyle; +}; + +type TimeBarOption = Partial<{ + // position size + readonly x: number; + readonly y: number; + readonly width: number; + readonly height: number; + + readonly backgroundStyle: ShapeStyle; + readonly foregroundStyle: ShapeStyle; + readonly handlerStyle: ShapeStyle; + readonly textStyle: ShapeStyle; + // 允许滑动位置 + readonly minLimit: number; + readonly maxLimit: number; + // 初始位置 + readonly start: number; + readonly end: number; + // 滑块文本 + readonly minText: string; + readonly maxText: string; + + readonly trend: TrendConfig; +}>; + +interface TimeBarConfig extends IPluginBaseConfig { + width?: number; + height?: number; + timebar: TimeBarOption; + rangeChange?: (graph: IGraph, min: number, max: number) => void; +} + +export default class TimeBar extends Base { + private cacheGraphData: GraphData + + constructor(cfg?: TimeBarConfig) { + super(cfg); + } + + public getDefaultCfgs(): TimeBarConfig { + return { + width: 400, + height: 50, + rangeChange: null, + timebar: { + x: 10, + y: 10, + width: 400, + height: 26, + minLimit: 0.05, + maxLimit: 0.95, + start: 0.1, + end: 0.9, + } + }; + } + + public init() { + const timeBarConfig: TimeBarOption = this.get('timebar') + const { trend = {} as TrendConfig } = timeBarConfig + const { data = [] } = trend + + if (!data || data.length === 0) { + console.warn('TimeBar 中没有传入数据') + return + } + + const container = this.get('container') + + let timebar + if (!container) { + timebar = createDOM(`
`) + modifyCSS(timebar, { position: 'absolute' }); + document.body.appendChild(timebar) + } else if (isString(container)) { + timebar = createDOM(`
`) + modifyCSS(timebar, { position: 'absolute' }); + document.body.appendChild(timebar) + } else { + timebar = container + } + + this.set('timeBarContainer', timebar) + + this.initTimeBar(timebar) + } + + private initTimeBar(container: HTMLDivElement) { + const width = this.get('width') + const height = this.get('height') + const canvas = new Canvas({ + container, + width, + height, + }); + + const group = canvas.addGroup({ + id: 'timebar-plugin', + }) + + const timeBarConfig: TimeBarOption = this.get('timebar') + const { trend = {} as TrendConfig , ...option } = timeBarConfig + + const config = { + container: group, + minText: option.start, + maxText: option.end, + ...option + } + + // 是否显示 TimeBar 根据是否传入了数据来确定 + const { data = [], ...trendOption } = trend + + const trendData = data.map(d => d.value) + + config['trendCfg'] = { + ...trendOption, + data: trendData + } + + config.minText = data[0].date + config.maxText = data[data.length - 1].date + + this.set('trendData', data) + + console.log('配置项', config) + + const slider = new Slider(config) + + slider.init(); + slider.render() + + this.set('slider', slider) + + this.bindEvent() + } + + /** + * 当滑动时,最小值和最大值会变化,变化以后触发相应事件 + */ + private bindEvent() { + const graph: IGraph = this.get('graph') + const slider = this.get('slider') + const rangeChange = this.get('rangeChange') + const trendData: Data[] = this.get('trendData') + slider.on('valuechanged', (evt: Callback) => { + const { value } = evt + + const min = Math.round(trendData.length * value[0]) + let max = Math.round(trendData.length * value[1]) + max = max > trendData.length ? trendData.length : max + const minText = trendData[min].date + const maxText = trendData[max].date + + slider.set('minText', minText) + slider.set('maxText', maxText) + + if (rangeChange) { + rangeChange(graph, minText, maxText) + } else { + // 自动过滤数据,并渲染 graph + const graphData = graph.save() as GraphData + + if (!this.cacheGraphData) { + this.cacheGraphData = graphData + } + + // 过滤不在 min 和 max 范围内的节点 + const filterData = this.cacheGraphData.nodes.filter((d: any) => d.date >= minText && d.date <= maxText) + + const nodeIds = filterData.map(node => node.id) + + // 过滤 source 或 target 不在 min 和 max 范围内的边 + const fileterEdges = this.cacheGraphData.edges.filter(edge => nodeIds.includes(edge.source) && nodeIds.includes(edge.target)) + + graph.changeData({ + nodes: filterData, + edges: fileterEdges + }) + + } + }) + } + + public show() { + const slider = this.get('slider') + slider.show() + } + + public hide() { + const slider = this.get('slider') + slider.hide() + } + + public destroy() { + this.cacheGraphData = null + + const slider = this.get('slider') + + if (slider) { + slider.off('valuechanged') + slider.destroy() + } + + const timeBarContainer = this.get('timeBarContainer') + if (timeBarContainer) { + document.body.removeChild(timeBarContainer); + } + } +} diff --git a/tests/unit/plugins/timebar-spec.ts b/tests/unit/plugins/timebar-spec.ts new file mode 100644 index 0000000000..043a9b68a6 --- /dev/null +++ b/tests/unit/plugins/timebar-spec.ts @@ -0,0 +1,91 @@ +import G6 from '../../../src'; +const div = document.createElement('div'); +div.id = 'timebar-plugin'; +document.body.appendChild(div); + +const data = { + nodes: [ + { + id: 'node1', + label: 'node1', + x: 100, + y: 100 + }, + { + id: 'node2', + label: 'node2', + x: 150, + y: 300 + } + ], + edges: [ + { + source: 'node1', + target: 'node2' + } + ] +} + +for(let i = 0; i < 100; i++) { + const id = `node-${i}` + data.nodes.push({ + id, + label: `node${i}`, + date: `2020${i}`, + value: Math.round(Math.random() * 300) + }) + + const edgeId = i + 3 + data.edges.push({ + source: `node-${Math.round(Math.random() * 90)}`, + target: `node-${Math.round(Math.random() * 90)}` + }) +} + +describe('tooltip', () => { + it('tooltip with default', () => { + const timeBarData = [] + + for(let i = 0; i < 100; i++) { + timeBarData.push({ + date: `2020${i}`, + value: Math.round(Math.random() * 300) + }) + } + const timebar = new G6.TimeBar({ + timebar: { + trend: { + data: timeBarData, + isArea: false, + smooth: true, + } + } + }); + const tooltip = new G6.Tooltip() + + const graph = new G6.Graph({ + container: div, + width: 500, + height: 500, + plugins: [timebar, tooltip], + modes: { + default: ['drag-node', 'zoom-canvas', 'drag-canvas'] + }, + defaultEdge: { + style: { + lineAppendWidth: 20 + } + } + }); + + graph.data(data) + graph.render() + + const timebarPlugin = graph.get('plugins')[0] + console.log(timebarPlugin) + // expect(timebarPlugin.get('offset')).toBe(6) + // expect(timebarPlugin.get('tooltip').outerHTML).toBe(``) + + // graph.destroy() + }) +}); From 2a8a2e6851760e0e0d3e231190d9ed2eeb5c43c8 Mon Sep 17 00:00:00 2001 From: chenluli Date: Tue, 14 Jul 2020 20:25:37 +0800 Subject: [PATCH 02/77] docs: fix customBehavior demo --- .../customBehavior/demo/dragCanvasTwoFingers.js | 9 +++++---- examples/interaction/customBehavior/index.en.md | 1 + examples/interaction/customBehavior/index.zh.md | 3 ++- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/examples/interaction/customBehavior/demo/dragCanvasTwoFingers.js b/examples/interaction/customBehavior/demo/dragCanvasTwoFingers.js index 8ae733cadf..9e2fbb6674 100644 --- a/examples/interaction/customBehavior/demo/dragCanvasTwoFingers.js +++ b/examples/interaction/customBehavior/demo/dragCanvasTwoFingers.js @@ -1,7 +1,7 @@ import G6 from '@antv/g6'; /** - * This demo shows how to custom a behavior to allow drag canvas with two fingers on touchpad and wheel + * This demo shows how to custom a behavior to allow drag and zoom canvas with two fingers on touchpad and wheel * By Shiwu */ G6.registerBehavior('double-finger-drag-canvas', { @@ -14,7 +14,6 @@ G6.registerBehavior('double-finger-drag-canvas', { onWheel: function onWheel(ev) { if (ev.ctrlKey) { const canvas = graph.get('canvas'); - const pixelRatio = canvas.get('pixelRatio'); const point = canvas.getPointByClient(ev.clientX, ev.clientY); let ratio = graph.getZoom(); if (ev.wheelDelta > 0) { @@ -23,8 +22,8 @@ G6.registerBehavior('double-finger-drag-canvas', { ratio = ratio - ratio * 0.05; } graph.zoomTo(ratio, { - x: point.x / pixelRatio, - y: point.y / pixelRatio + x: point.x, + y: point.y, }); } else { const x = ev.deltaX || ev.movementX; @@ -50,6 +49,8 @@ const graph = new G6.Graph({ }, }); +graph.get('canvas').set('localRefresh', false); + fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/relations.json') .then(res => res.json()) .then(data => { diff --git a/examples/interaction/customBehavior/index.en.md b/examples/interaction/customBehavior/index.en.md index 2d310d6433..d1dfc5ddf6 100644 --- a/examples/interaction/customBehavior/index.en.md +++ b/examples/interaction/customBehavior/index.en.md @@ -8,3 +8,4 @@ Custom a behavior when the [built-in behaviors](/en/docs/manual/middle/states/de ## Usage Custom a behavior with `graph.registerBehavior`, refer to [Custom Behavior Doc](/en/docs/manual/advanced/custom-behavior). +The following demo shows how to custom a behavior to allow drag and zoom canvas with two fingers on touchpad and wheel. diff --git a/examples/interaction/customBehavior/index.zh.md b/examples/interaction/customBehavior/index.zh.md index 6b269ec6bb..8e90030a14 100644 --- a/examples/interaction/customBehavior/index.zh.md +++ b/examples/interaction/customBehavior/index.zh.md @@ -7,4 +7,5 @@ order: 10 ## 使用指南 -使用 `graph.registerBehavior` 自定义交互,教程参见 [自定义交互](/zh/docs/manual/advanced/custom-behavior)。 \ No newline at end of file +使用 `graph.registerBehavior` 自定义交互,教程参见 [自定义交互](/zh/docs/manual/advanced/custom-behavior)。 +下面示例演示了如何使用自定义交互机制实现使用鼠标滚轮或触摸板双指拖动和缩放画布。 From 3579f9f463a2b81f98cd5c151af74077f327a402 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BE=BD=E7=86=99?= Date: Tue, 14 Jul 2020 21:14:23 +0800 Subject: [PATCH 03/77] docs: uniform AntV navbar's order and naming --- gatsby-config.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gatsby-config.js b/gatsby-config.js index caa2dde49a..b2141b66ab 100644 --- a/gatsby-config.js +++ b/gatsby-config.js @@ -23,21 +23,21 @@ module.exports = { { slug: 'docs/manual/introduction', title: { - zh: '文档', - en: 'Docs', + zh: '使用文档', + en: 'Manual', }, }, { slug: 'docs/api/Graph', title: { - zh: 'API', + zh: 'API 文档', en: 'API', }, }, { slug: 'examples', title: { - zh: '图表演示', + zh: '图表示例', en: 'Examples', }, }, From 54c4a29807309fa18b69c66d2efb0b1de4ec514b Mon Sep 17 00:00:00 2001 From: chenluli Date: Tue, 21 Jul 2020 14:07:14 +0800 Subject: [PATCH 04/77] fix: drag new node without initial position --- package.json | 2 +- src/behavior/drag-node.ts | 6 ++--- src/graph/controller/item.ts | 3 +++ tests/unit/behavior/drag-node-spec.ts | 37 +++++++++++++++++++++++++++ 4 files changed, 44 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 3de41c8763..ff68c13f7e 100644 --- a/package.json +++ b/package.json @@ -127,4 +127,4 @@ "webpack-cli": "^3.3.10", "worker-loader": "^2.0.0" } -} +} \ No newline at end of file diff --git a/src/behavior/drag-node.ts b/src/behavior/drag-node.ts index df571603bf..0446e3bdd7 100644 --- a/src/behavior/drag-node.ts +++ b/src/behavior/drag-node.ts @@ -185,7 +185,7 @@ export default { }) } } - + // 拖动结束后,入栈 if (graph.get('enabledStack')) { graph.pushStack('update', clone(graph.save())) @@ -250,8 +250,8 @@ export default { const nodeId: string = item.get('id'); if (!this.point[nodeId]) { this.point[nodeId] = { - x: model.x, - y: model.y, + x: model.x || 0, + y: model.y || 0, }; } diff --git a/src/graph/controller/item.ts b/src/graph/controller/item.ts index 518b07c27b..c03e533388 100644 --- a/src/graph/controller/item.ts +++ b/src/graph/controller/item.ts @@ -120,6 +120,9 @@ export default class ItemController { group: parent.addGroup(), }); } else if (type === NODE) { + model.x = model.x || 0; + model.y = model.y || 0; + item = new Node({ model, styles, diff --git a/tests/unit/behavior/drag-node-spec.ts b/tests/unit/behavior/drag-node-spec.ts index 64ca31cfab..1b66ea3de5 100644 --- a/tests/unit/behavior/drag-node-spec.ts +++ b/tests/unit/behavior/drag-node-spec.ts @@ -510,6 +510,43 @@ describe('drag-node', () => { expect(matrix[7]).toEqual(50); graph.destroy(); }); + it('drag new added node without config position', () => { + const graph = new Graph({ + container: div, + width: 500, + height: 500, + modes: { + default: [ + { + type: 'drag-node', + delegateStyle: { + fillOpacity: 0.8, + }, + }, + ], + }, + defaultNode: { + type: 'rect', + size: [114, 54], + style: { + radius: 4, + }, + }, + }); + graph.render() + const node = graph.addItem('node', { + id: 'node1', + }) + + graph.emit('node:dragstart', { x: 0, y: 0, item: node }); + graph.emit('node:drag', { x: 120, y: 120, item: node }); + graph.emit('node:dragend', { x: 120, y: 120, item: node }); + const matrix = node.get('group').getMatrix(); + expect(matrix[0]).toEqual(1); + expect(matrix[6]).toEqual(120); + expect(matrix[7]).toEqual(120); + graph.destroy() + }) it('drag node not update edge', () => { const graph = new Graph({ container: div, From 0f8a9af6e564fa7924bfaab99e51f003568d0232 Mon Sep 17 00:00:00 2001 From: chenluli Date: Tue, 21 Jul 2020 10:28:59 +0800 Subject: [PATCH 05/77] docs: fix contextmenu demo --- examples/tool/contextMenu/demo/contextMenu.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/tool/contextMenu/demo/contextMenu.js b/examples/tool/contextMenu/demo/contextMenu.js index cb00f40254..63938445cb 100644 --- a/examples/tool/contextMenu/demo/contextMenu.js +++ b/examples/tool/contextMenu/demo/contextMenu.js @@ -123,8 +123,8 @@ graph.render(); graph.on('node:contextmenu', evt => { evt.preventDefault(); evt.stopPropagation(); - conextMenuContainer.style.left = `${evt.x + 20}px`; - conextMenuContainer.style.top = `${evt.y}px`; + conextMenuContainer.style.left = `${evt.canvasX + 20}px`; + conextMenuContainer.style.top = `${evt.canvasY}px`; }); graph.on('node:mouseleave', () => { From 8c4fe41b491f4e8863e8afc0ed2e6cf9debc2e7b Mon Sep 17 00:00:00 2001 From: baizn <576375879@qq.com> Date: Tue, 14 Jul 2020 15:38:02 +0800 Subject: [PATCH 06/77] feat: hide edge & label when drag canvas --- package.json | 4 +-- src/behavior/drag-canvas.ts | 52 +++++++++++++++++++++++++++++++ src/graph/controller/event.ts | 2 +- tests/unit/behavior/index-spec.ts | 2 +- 4 files changed, 56 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index ff68c13f7e..44c681a218 100644 --- a/package.json +++ b/package.json @@ -74,7 +74,7 @@ "@antv/dom-util": "^2.0.1", "@antv/event-emitter": "~0.1.0", "@antv/g-base": "^0.4.1", - "@antv/g-canvas": "^0.4.3", + "@antv/g-canvas": "^0.4.14", "@antv/g-math": "^0.1.1", "@antv/g-svg": "^0.4.1", "@antv/hierarchy": "^0.6.2", @@ -127,4 +127,4 @@ "webpack-cli": "^3.3.10", "worker-loader": "^2.0.0" } -} \ No newline at end of file +} diff --git a/src/behavior/drag-canvas.ts b/src/behavior/drag-canvas.ts index a7a2a7eaf3..2cadf3f048 100644 --- a/src/behavior/drag-canvas.ts +++ b/src/behavior/drag-canvas.ts @@ -1,5 +1,6 @@ import { G6Event, IG6GraphEvent } from '../types'; import { cloneEvent, isNaN } from '../util/base'; +import { IGraph } from '../interface/graph'; const { abs } = Math; const DRAG_OFFSET = 10; @@ -9,6 +10,7 @@ export default { getDefaultCfg(): object { return { direction: 'both', + enableOptimize: true }; }, getEvents(): { [key in G6Event]?: string } { @@ -69,6 +71,30 @@ export default { self.origin = { x: e.clientX, y: e.clientY }; self.dragging = false; + + if (this.enableOptimize) { + // 开始拖动时关闭局部渲染 + this.graph.get('canvas').set('localRefresh', false) + + // 拖动 canvas 过程中隐藏所有的边及label + const graph: IGraph = this.graph + const edges = graph.getEdges() + for (let i = 0, len = edges.length; i < len; i++) { + graph.hideItem(edges[i]) + } + + const nodes = graph.getNodes() + for (let j = 0, nodeLen = nodes.length; j < nodeLen; j++) { + const container = nodes[j].getContainer() + const children = container.get('children') + for (let child of children) { + const isKeyShape = child.get('isKeyShape') + if (!isKeyShape) { + child.set('visible', false) + } + } + } + } }, onMouseMove(e: IG6GraphEvent) { const { graph } = this; @@ -101,10 +127,36 @@ export default { }, onMouseUp(e: IG6GraphEvent) { const { graph } = this; + if (this.keydown || e.shape) { return; } + if (this.enableOptimize) { + // 拖动结束后显示所有的边 + const edges = graph.getEdges() + for (let i = 0, len = edges.length; i < len; i++) { + graph.showItem(edges[i]) + } + + const nodes = graph.getNodes() + for (let j = 0, nodeLen = nodes.length; j < nodeLen; j++) { + const container = nodes[j].getContainer() + const children = container.get('children') + for (let child of children) { + const isKeyShape = child.get('isKeyShape') + if (!isKeyShape) { + child.set('visible', true) + } + } + } + + setTimeout(() => { + // 拖动结束后开启局部渲染 + graph.get('canvas').set('localRefresh', true) + }, 16) + } + if (!this.dragging) { this.origin = null; return; diff --git a/src/graph/controller/event.ts b/src/graph/controller/event.ts index 374634a2cd..518f14f26f 100644 --- a/src/graph/controller/event.ts +++ b/src/graph/controller/event.ts @@ -195,7 +195,7 @@ export default class EventController { private handleMouseMove(evt: IG6GraphEvent, type: string) { const { graph, preItem } = this; const canvas: Canvas = graph.get('canvas'); - const item = evt.target === canvas ? null : evt.item; + const item = (evt.target as any) === canvas ? null : evt.item; evt = cloneEvent(evt) as IG6GraphEvent; diff --git a/tests/unit/behavior/index-spec.ts b/tests/unit/behavior/index-spec.ts index 47967fa39b..dc9237649d 100644 --- a/tests/unit/behavior/index-spec.ts +++ b/tests/unit/behavior/index-spec.ts @@ -99,7 +99,7 @@ describe('Default Behavior', () => { const dragCanvas: IBehavior = new DragCanvas(); const config = dragCanvas.getDefaultCfg(); - expect(config).toEqual({ direction: 'both' }); + expect(config).toEqual({ direction: 'both', enableOptimize: true }); const events = dragCanvas.getEvents(); const keys = Object.keys(events); From 920399e14d63a4ef37e6c08a47623902934b917c Mon Sep 17 00:00:00 2001 From: baizn <576375879@qq.com> Date: Tue, 14 Jul 2020 18:08:44 +0800 Subject: [PATCH 07/77] feat: add drag-canvas enableOptimize doc --- docs/manual/middle/states/defaultBehavior.en.md | 1 + docs/manual/middle/states/defaultBehavior.zh.md | 1 + 2 files changed, 2 insertions(+) diff --git a/docs/manual/middle/states/defaultBehavior.en.md b/docs/manual/middle/states/defaultBehavior.en.md index 7b56656732..6e97cd8c7d 100644 --- a/docs/manual/middle/states/defaultBehavior.en.md +++ b/docs/manual/middle/states/defaultBehavior.en.md @@ -100,6 +100,7 @@ const graph = new G6.Graph({ - Configurations: - `type: 'drag-canvas'`; - `direction`: The direction of dragging that is allowed. Options: `'x'`, `'y'`, `'both'`. `'both'` by default; + - `enableOptimize`: whether enable optimize, `true` by default, when drag canvas will auto hide all edges and the part of node that is not keyShape; - `shouldBegin(e)`: Whether allow the behavior happen on the current item (e.item). - Related timing events: - `canvas:dragstart`: Triggered when drag start. Listened by `graph.on('canvas:dragstart', e => {...})`; diff --git a/docs/manual/middle/states/defaultBehavior.zh.md b/docs/manual/middle/states/defaultBehavior.zh.md index 4aa84872bd..2475e9b1f2 100644 --- a/docs/manual/middle/states/defaultBehavior.zh.md +++ b/docs/manual/middle/states/defaultBehavior.zh.md @@ -100,6 +100,7 @@ const graph = new G6.Graph({ - 配置项: - `type: 'drag-canvas'`; - `direction`:允许拖拽方向,支持`'x'`,`'y'`,`'both'`,默认方向为 `'both'`; + - `enableOptimize`:是否开启优化,开启后拖动画布过程中隐藏所有的边及节点上非 keyShape 部分,默认开启; - `shouldBegin(e)`:是否允许触发该操作。 - 相关时机事件: - `canvas:dragstart`:画布拖拽开始时触发,使用 `graph.on('canvas:dragstart', e => {...})` 监听; From 3af1826b79ba8e9d690bc91d23b771a20d236e7a Mon Sep 17 00:00:00 2001 From: Yanyan-Wang Date: Mon, 13 Jul 2020 15:15:11 +0800 Subject: [PATCH 08/77] feat: fix node or edge size, fontSize, lineWidth while zooming graph with zoom-canvas. --- package.json | 2 +- src/behavior/collapse-expand.ts | 1 - src/behavior/zoom-canvas.ts | 120 +++++++++++++- src/layout/dagre.ts | 1 - src/shape/shapeBase.ts | 8 +- src/types/index.ts | 9 ++ .../Interaction/component/zoom-canvas-fix.tsx | 153 ++++++++++++++++++ stories/Interaction/interaction.stories.tsx | 3 + tests/unit/behavior/zoom-spec.ts | 25 ++- 9 files changed, 297 insertions(+), 25 deletions(-) create mode 100644 stories/Interaction/component/zoom-canvas-fix.tsx diff --git a/package.json b/package.json index 44c681a218..7e241be48a 100644 --- a/package.json +++ b/package.json @@ -127,4 +127,4 @@ "webpack-cli": "^3.3.10", "worker-loader": "^2.0.0" } -} +} \ No newline at end of file diff --git a/src/behavior/collapse-expand.ts b/src/behavior/collapse-expand.ts index 5af0d4a2af..8ef6574e5c 100644 --- a/src/behavior/collapse-expand.ts +++ b/src/behavior/collapse-expand.ts @@ -22,7 +22,6 @@ export default { // eslint-disable-next-line no-console console.warn("Behavior collapse-expand 的 trigger 参数不合法,请输入 'click' 或 'dblclick'"); } - console.log('triggertrigger', trigger); return { [`node:${trigger}`]: 'onNodeClick', }; diff --git a/src/behavior/zoom-canvas.ts b/src/behavior/zoom-canvas.ts index e78982c570..f0d7399966 100644 --- a/src/behavior/zoom-canvas.ts +++ b/src/behavior/zoom-canvas.ts @@ -1,4 +1,7 @@ import { G6Event, IG6GraphEvent } from '../types'; +import { mat3 } from '@antv/matrix-util'; +import { clone } from '@antv/util' + const DELTA = 0.05; @@ -9,16 +12,31 @@ export default { minZoom: undefined, maxZoom: undefined, enableOptimize: false, - optimizeZoom: 0.7 + optimizeZoom: 0.7, + fixSelectedItems: { + fixAll: false, + fixShape: false, + fixLineWidth: false, + fixLabel: false, + fixState: 'selected' + } }; }, getEvents(): { [key in G6Event]?: string } { + const { fixSelectedItems } = this; + + if (fixSelectedItems.fixAll) { + fixSelectedItems.fixShape = true; + fixSelectedItems.fixLineWidth = true; + fixSelectedItems.fixLabel = true; + } + return { wheel: 'onWheel', }; }, onWheel(e: IG6GraphEvent) { - const { graph } = this; + const { graph, fixSelectedItems } = this; if (!this.shouldUpdate.call(this, e)) { return; } @@ -26,20 +44,30 @@ export default { const canvas = graph.get('canvas'); const point = canvas.getPointByClient(e.clientX, e.clientY); const sensitivity = this.get('sensitivity'); - let ratio = graph.getZoom(); + const graphZoom = graph.getZoom(); + let ratio = graphZoom; + let zoom = graphZoom; // 兼容IE、Firefox及Chrome if (e.wheelDelta < 0) { + // ratio = 1 - DELTA * sensitivity; + // ratio = graphZoom - DELTA * sensitivity; + // zoom = graphZoom * ratio; ratio = 1 - DELTA * sensitivity; } else { - ratio = 1 + DELTA * sensitivity; + // ratio = 1 + DELTA * sensitivity; + // ratio = graphZoom + DELTA * sensitivity; + // zoom = graphZoom / ratio; + ratio = 1 / (1 - DELTA * sensitivity); } - const zoom = ratio * graph.getZoom(); + zoom = graphZoom * ratio; + // const zoom = ratio * graphZoom; const minZoom = this.get('minZoom') || graph.get('minZoom'); const maxZoom = this.get('maxZoom') || graph.get('maxZoom'); if (zoom > maxZoom || zoom < minZoom) { return; } + const enableOptimize = this.get('enableOptimize') if (enableOptimize) { const optimizeZoom = this.get('optimizeZoom') @@ -90,7 +118,87 @@ export default { } } - graph.zoom(ratio, { x: point.x, y: point.y }); + + // fix the items when zooming + if (graphZoom <= 1) {// + let fixNodes, fixEdges; + if (fixSelectedItems.fixShape || fixSelectedItems.fixLineWidth || fixSelectedItems.fixLabel) { + fixNodes = graph.findAllByState('node', fixSelectedItems.fixState); + fixEdges = graph.findAllByState('edge', fixSelectedItems.fixState); + + const scale = graphZoom / zoom; + fixNodes.forEach(node => { + const group = node.getContainer(); + const nodeModel = node.getModel(); + const itemStateStyle = node.getStateStyle(fixSelectedItems.fixState); + const shapeStateStyle = node.get('shapeFactory').getShape(nodeModel.shape || nodeModel.type).getStateStyle(fixSelectedItems.fixState, node)[fixSelectedItems.fixState]; + if (fixSelectedItems.fixAll) { + let groupMatrix = clone(group.getMatrix()); + if (!groupMatrix) groupMatrix = mat3.create(); + const model = node.getModel(); + const { x, y } = model; + mat3.translate(groupMatrix, groupMatrix, [-x, -y]); + mat3.scale(groupMatrix, groupMatrix, [scale, scale]); + mat3.translate(groupMatrix, groupMatrix, [x, y]); + group.setMatrix(groupMatrix); + } else { + const children = group.get('children') + children.map(shape => { + let fontSize, lineWidth; + if (fixSelectedItems.fixLabel) { + const shapeType = shape.get('type'); + if (shapeType === 'text') { + fontSize = shape.attr('fontSize') || 12; + const oriFontSize = itemStateStyle[shape.get('name')].fontSize || shapeStateStyle[shape.get('name')].fontSize || 12; + if (zoom <= 1) shape.attr('fontSize', oriFontSize / zoom);// * graphZoom / zoom + if (lineWidth) return; + } + } + if (fixSelectedItems.fixLineWidth) { + if (shape.get('isKeyShape')) { + lineWidth = shape.attr('lineWidth') || 0; + const oriLineWidth = itemStateStyle.lineWidth || shapeStateStyle.lineWidth || 0; + if (zoom <= 1) shape.attr('lineWidth', oriLineWidth / zoom);// * graphZoom / zoom + if (fontSize) return; + } + } + }); + } + }); + fixEdges.forEach(edge => { + const group = edge.getContainer(); + const children = group.get('children') + const nodeModel = edge.getModel(); + const itemStateStyle = edge.getStateStyle(fixSelectedItems.fixState); + const shapeStateStyle = edge.get('shapeFactory').getShape(nodeModel.shape || nodeModel.type).getStateStyle(fixSelectedItems.fixState, edge)[fixSelectedItems.fixState]; + children.map(shape => { + let fontSize, lineWidth; + if (fixSelectedItems.fixLabel) { + const shapeType = shape.get('type'); + if (shapeType === 'text') { + fontSize = shape.attr('fontSize') || 12; + const oriFontSize = itemStateStyle[shape.get('name')].fontSize || shapeStateStyle[shape.get('name')].fontSize || 12; + if (zoom <= 1) shape.attr('fontSize', oriFontSize / zoom); + if (lineWidth) return; + } + } + if (fixSelectedItems.fixLineWidth) { + if (shape.get('isKeyShape')) { + lineWidth = shape.attr('lineWidth') || 0; + const oriLineWidth = itemStateStyle.lineWidth || shapeStateStyle.lineWidth || 0; + if (zoom <= 1) shape.attr('lineWidth', oriLineWidth / zoom); + if (fontSize) return; + } + } + }); + }); + } + + + } + + // graph.zoom(ratio, { x: point.x, y: point.y }); + graph.zoomTo(zoom, { x: point.x, y: point.y }); graph.emit('wheelzoom', e); }, }; diff --git a/src/layout/dagre.ts b/src/layout/dagre.ts index 485ff003ff..806a32d161 100644 --- a/src/layout/dagre.ts +++ b/src/layout/dagre.ts @@ -159,7 +159,6 @@ export default class DagreLayout extends BaseLayout { } return 0; }); - console.log('sortedByCombo', sortedByCombo); sortedByCombo.forEach((node, i) => { node.x = minX + i * gap; }); diff --git a/src/shape/shapeBase.ts b/src/shape/shapeBase.ts index 4040302c25..b0cde579cf 100644 --- a/src/shape/shapeBase.ts +++ b/src/shape/shapeBase.ts @@ -51,7 +51,7 @@ export const shapeBase: ShapeOptions = { * @param group * @param keyShape */ - afterDraw(cfg?: ModelConfig, group?: GGroup, keyShape?: IShape) {}, + afterDraw(cfg?: ModelConfig, group?: GGroup, keyShape?: IShape) { }, drawShape(cfg?: ModelConfig, group?: GGroup): IShape { return null as any; }, @@ -272,7 +272,7 @@ export const shapeBase: ShapeOptions = { }, // update(cfg, item) // 默认不定义 - afterUpdate(cfg?: ModelConfig, item?: Item) {}, + afterUpdate(cfg?: ModelConfig, item?: Item) { }, /** * 设置节点的状态,主要是交互状态,业务状态请在 draw 方法中实现 * 单图形的节点仅考虑 selected、active 状态,有其他状态需求的用户自己复写这个方法 @@ -310,6 +310,8 @@ export const shapeBase: ShapeOptions = { const style = styles[key]; if (isPlainObject(style)) { const subShape = group.find((element) => element.get('name') === key); + + if (subShape) { subShape.attr(style); } @@ -425,7 +427,7 @@ export const shapeBase: ShapeOptions = { const model = item.getModel(); const type = item.getType(); const { stateStyles, style = {} } = this.getOptions(model); - + const modelStateStyle = model.stateStyles ? model.stateStyles[name] : stateStyles && stateStyles[name]; diff --git a/src/types/index.ts b/src/types/index.ts index 91485e32ba..db20e81d0f 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -65,6 +65,8 @@ export type ShapeStyle = Partial<{ cursor: string; position: string; fontSize: number; + + keepVisualSize: boolean }>; export interface IShapeBase extends ShapeBase { @@ -126,6 +128,13 @@ export interface ModeOption { includeEdges?: boolean; direction?: 'x' | 'y'; offset?: number; + fixSelectedItems?: Partial<{ + fixAll: boolean; + fixShape: boolean; + fixLineWidth: boolean; + fixLabel: boolean; + fixState: string + }>; shouldUpdate?: (e: IG6GraphEvent) => boolean; shouldBegin?: (e: IG6GraphEvent) => boolean; shouldEnd?: (e: IG6GraphEvent) => boolean; diff --git a/stories/Interaction/component/zoom-canvas-fix.tsx b/stories/Interaction/component/zoom-canvas-fix.tsx new file mode 100644 index 0000000000..4a128db7fe --- /dev/null +++ b/stories/Interaction/component/zoom-canvas-fix.tsx @@ -0,0 +1,153 @@ +import React, { useEffect } from 'react'; +import G6 from '../../../src'; +import { IGraph } from '../../../src/interface/graph'; + +let graph: IGraph = null; + +const data = { + nodes: [ + { + id: "1", + label: "node1" + }, + { + id: "2", + label: "node2" + }, + { + id: "3", + label: "node3" + }, + { + id: "4", + label: "node4" + }, + { + id: "5", + label: "node5" + }, + { + id: "6", + label: "node6" + }, + ], + edges: [ + { + source: "1", + target: "2", + label: 'edge' + }, + { + source: "1", + target: "3", + label: 'edge' + }, + { + source: "2", + target: "3", + label: 'edge' + }, + { + source: "3", + target: "4", + label: 'edge' + }, + { + source: "5", + target: "6", + label: 'edge' + }, + { + source: "1", + target: "5", + label: 'edge' + }, + ] +}; + + +const ZoomCanvasFix = () => { + const container = React.useRef(); + useEffect(() => { + if (!graph) { + graph = new G6.Graph({ + container: container.current as string | HTMLElement, + width: 800, + height: 500, + minZoom: 0.001, + modes: { + default: [ + 'drag-canvas', + { + type: 'zoom-canvas', + fixSelectedItems: { + fixState: 'selected', + fixLabel: true, + fixLineWidth: true + } + }] + }, + defaultEdge: { + style: { + lineWidth: 1 + } + }, + defaultNode: { + style: { + lineWidth: 1 + } + }, + nodeStateStyles: { + selected: { + lineWidth: 5, + stroke: '#f00', + 'text-shape': { + fontSize: 12 + } + } + }, + edgeStateStyles: { + selected: { + lineWidth: 5, + stroke: '#f00', + 'text-shape': { + fontSize: 12 + } + } + } + }); + graph.data(data); + graph.render(); + + // graph.setItemState(graph.getNodes()[0], 'selected', true); + // graph.setItemState(graph.getNodes()[1], 'selected', true); + // graph.setItemState(graph.getEdges()[0], 'selected', true); + + graph.on('node:click', e => { + const item = e.item; + graph.setItemState(item, 'selected', true); + }); + graph.on('edge:click', e => { + const item = e.item; + graph.setItemState(item, 'selected', true); + }); + + graph.on('canvas:click', e => { + graph.findAllByState('node', 'selected').forEach(node => { + graph.setItemState(node, 'selected', false); + }) + graph.findAllByState('edge', 'selected').forEach(edge => { + graph.setItemState(edge, 'selected', false); + }) + + }); + } + }); + return ( +
+
+
+
); +}; + +export default ZoomCanvasFix; diff --git a/stories/Interaction/interaction.stories.tsx b/stories/Interaction/interaction.stories.tsx index f55041d42c..7af70eab8a 100644 --- a/stories/Interaction/interaction.stories.tsx +++ b/stories/Interaction/interaction.stories.tsx @@ -6,6 +6,7 @@ import AltTab from './component/alt-tab'; import AddItem from './component/addItem'; import Tooltip from './component/tooltip'; import DragCanvas from './component/drag-canvas'; +import ZoomCanvasFix from './component/zoom-canvas-fix' export default { title: 'Interaction' }; @@ -23,4 +24,6 @@ storiesOf('Interaction', module) )).add('drag canvas', () => ( + )).add('zoom canvas fix items', () => ( + )); diff --git a/tests/unit/behavior/zoom-spec.ts b/tests/unit/behavior/zoom-spec.ts index 9467c686c1..3d86635ab9 100644 --- a/tests/unit/behavior/zoom-spec.ts +++ b/tests/unit/behavior/zoom-spec.ts @@ -19,8 +19,8 @@ function createWheelEvent(canvas, delta, x, y) { return e; } -function approximateEqual(a, b) { - return Math.abs(a - b) < 0.0001; +function approximateEqual(a, b, threshold = 0.02) { + return Math.abs(a - b) < threshold; } describe('zoom-canvas', () => { @@ -38,14 +38,14 @@ describe('zoom-canvas', () => { let matrix = graph.get('group').getMatrix(); expect(approximateEqual(matrix[0], 1.1)).toBe(true); expect(approximateEqual(matrix[4], 1.1)).toBe(true); - expect(approximateEqual(matrix[6], -10)).toBe(true); - expect(approximateEqual(matrix[7], -10)).toBe(true); + expect(approximateEqual(matrix[6], -11.1)).toBe(true); + expect(approximateEqual(matrix[7], -11.1)).toBe(true); graph.emit('wheel', e); matrix = graph.get('group').getMatrix(); - expect(approximateEqual(matrix[0], 1.21)).toBe(true); - expect(approximateEqual(matrix[4], 1.21)).toBe(true); - expect(approximateEqual(matrix[6], -21)).toBe(true); - expect(approximateEqual(matrix[7], -21)).toBe(true); + expect(approximateEqual(matrix[0], 1.23)).toBe(true); + expect(approximateEqual(matrix[4], 1.23)).toBe(true); + expect(approximateEqual(matrix[6], -23.45)).toBe(true); + expect(approximateEqual(matrix[7], -23.45)).toBe(true); graph.destroy(); }); it('event not prevented', () => { @@ -117,8 +117,8 @@ describe('zoom-canvas', () => { e = createWheelEvent(graph.get('canvas').get('el'), 100, 100, 100); graph.emit('wheel', e); matrix = graph.get('group').getMatrix(); - expect(matrix[0]).toEqual(0.45); - expect(matrix[4]).toEqual(0.45); + expect(matrix[0]).toEqual(0.5); + expect(matrix[4]).toEqual(0.5); }); it('unbind zoom', () => { const graph = new Graph({ @@ -162,11 +162,10 @@ describe('zoom-canvas', () => { let e = createWheelEvent(graph.get('canvas').get('el'), 100, 100, 100); graph.emit('wheel', e); let matrix = graph.get('group').getMatrix(); - console.log(matrix) expect(approximateEqual(matrix[0], 1.1)).toBe(true); expect(approximateEqual(matrix[4], 1.1)).toBe(true); - expect(approximateEqual(matrix[6], -10)).toBe(true); - expect(approximateEqual(matrix[7], -10)).toBe(true); + expect(approximateEqual(matrix[6], -11.1)).toBe(true); + expect(approximateEqual(matrix[7], -11.1)).toBe(true); const data = { nodes: [ From bc24b0449d463c7c6cc4d8478ae9e521b68cec42 Mon Sep 17 00:00:00 2001 From: Yanyan-Wang Date: Mon, 20 Jul 2020 11:51:40 +0800 Subject: [PATCH 09/77] chore: use for instead of map to improve the performance. --- src/behavior/zoom-canvas.ts | 85 +++++++++++++++++++++++-------------- 1 file changed, 54 insertions(+), 31 deletions(-) diff --git a/src/behavior/zoom-canvas.ts b/src/behavior/zoom-canvas.ts index f0d7399966..98c34a0cdb 100644 --- a/src/behavior/zoom-canvas.ts +++ b/src/behavior/zoom-canvas.ts @@ -73,61 +73,75 @@ export default { const optimizeZoom = this.get('optimizeZoom') const currentZoom = graph.getZoom() + const nodes = graph.getNodes() + const edges = graph.getEdges() + const nodesLength = nodes.length; + const edgesLength = edges.length; if (currentZoom < optimizeZoom) { - const nodes = graph.getNodes() - const edges = graph.getEdges() - nodes.map(node => { + for (let n = 0; n < nodesLength; n++) { + const node = nodes[n]; if (!node.destroyed) { - const children = node.getContainer().get('children') - children.map(shape => { + const children = node.getContainer().get('children'); + const childrenLength = children.length; + for (let c = 0; c < childrenLength; c++) { + const shape = children[c]; if (!shape.destoryed && !shape.get('isKeyShape')) { shape.hide() } - }) + } } - }) + } - edges.map(edge => { + for (let e = 0; e < edgesLength; e++) { + const edge = edges[e]; const children = edge.getContainer().get('children') - children.map(shape => { + const childrenLength = children.length; + for (let c = 0; c < childrenLength; c++) { + const shape = children[c]; if (!shape.get('isKeyShape')) { shape.hide() } - }) - }) + } + } } else { - const nodes = graph.getNodes() - const edges = graph.getEdges() - nodes.map(node => { + for (let n = 0; n < nodesLength; n++) { + const node = nodes[n]; const children = node.getContainer().get('children') - children.map(shape => { + const childrenLength = children.length; + for (let c = 0; c < childrenLength; c++) { + const shape = children[c]; if (!shape.get('visible')) { shape.show() } - }) - }) + } + } - edges.map(edge => { + for (let e = 0; e < edgesLength; e++) { + const edge = edges[e]; const children = edge.getContainer().get('children') - children.map(shape => { + const childrenLength = children.length; + for (let c = 0; c < childrenLength; c++) { + const shape = children[c]; if (!shape.get('visible')) { shape.show() } - }) - }) + } + } } } // fix the items when zooming - if (graphZoom <= 1) {// + if (graphZoom <= 1) { let fixNodes, fixEdges; if (fixSelectedItems.fixShape || fixSelectedItems.fixLineWidth || fixSelectedItems.fixLabel) { fixNodes = graph.findAllByState('node', fixSelectedItems.fixState); fixEdges = graph.findAllByState('edge', fixSelectedItems.fixState); const scale = graphZoom / zoom; - fixNodes.forEach(node => { + const fixNodesLength = fixNodes.length; + for (let fn = 0; fn < fixNodesLength; fn++) { + const node = fixNodes[fn]; const group = node.getContainer(); const nodeModel = node.getModel(); const itemStateStyle = node.getStateStyle(fixSelectedItems.fixState); @@ -142,8 +156,10 @@ export default { mat3.translate(groupMatrix, groupMatrix, [x, y]); group.setMatrix(groupMatrix); } else { - const children = group.get('children') - children.map(shape => { + const children = group.get('children'); + const childrenLength = children.length; + for (let c = 0; c < childrenLength; c++) { + const shape = children[c]; let fontSize, lineWidth; if (fixSelectedItems.fixLabel) { const shapeType = shape.get('type'); @@ -162,16 +178,23 @@ export default { if (fontSize) return; } } - }); + } } - }); - fixEdges.forEach(edge => { + } + + + const fixEdgesLength = fixEdges.length; + for (let fe = 0; fe < fixEdgesLength; fe++) { + const edge = fixEdges[fe]; const group = edge.getContainer(); const children = group.get('children') const nodeModel = edge.getModel(); const itemStateStyle = edge.getStateStyle(fixSelectedItems.fixState); const shapeStateStyle = edge.get('shapeFactory').getShape(nodeModel.shape || nodeModel.type).getStateStyle(fixSelectedItems.fixState, edge)[fixSelectedItems.fixState]; - children.map(shape => { + + const childrenLength = children.length; + for (let c = 0; c < childrenLength; c++) { + const shape = children[c]; let fontSize, lineWidth; if (fixSelectedItems.fixLabel) { const shapeType = shape.get('type'); @@ -190,8 +213,8 @@ export default { if (fontSize) return; } } - }); - }); + } + } } From c03dcf44069176138846509fa9928f89a5bb74eb Mon Sep 17 00:00:00 2001 From: Yanyan-Wang Date: Tue, 21 Jul 2020 14:16:50 +0800 Subject: [PATCH 10/77] fix: use for instead of map and change the return --- src/behavior/zoom-canvas.ts | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/src/behavior/zoom-canvas.ts b/src/behavior/zoom-canvas.ts index 98c34a0cdb..bbb3ec7b06 100644 --- a/src/behavior/zoom-canvas.ts +++ b/src/behavior/zoom-canvas.ts @@ -49,14 +49,8 @@ export default { let zoom = graphZoom; // 兼容IE、Firefox及Chrome if (e.wheelDelta < 0) { - // ratio = 1 - DELTA * sensitivity; - // ratio = graphZoom - DELTA * sensitivity; - // zoom = graphZoom * ratio; ratio = 1 - DELTA * sensitivity; } else { - // ratio = 1 + DELTA * sensitivity; - // ratio = graphZoom + DELTA * sensitivity; - // zoom = graphZoom / ratio; ratio = 1 / (1 - DELTA * sensitivity); } zoom = graphZoom * ratio; @@ -167,7 +161,7 @@ export default { fontSize = shape.attr('fontSize') || 12; const oriFontSize = itemStateStyle[shape.get('name')].fontSize || shapeStateStyle[shape.get('name')].fontSize || 12; if (zoom <= 1) shape.attr('fontSize', oriFontSize / zoom);// * graphZoom / zoom - if (lineWidth) return; + if (lineWidth) break; } } if (fixSelectedItems.fixLineWidth) { @@ -175,7 +169,7 @@ export default { lineWidth = shape.attr('lineWidth') || 0; const oriLineWidth = itemStateStyle.lineWidth || shapeStateStyle.lineWidth || 0; if (zoom <= 1) shape.attr('lineWidth', oriLineWidth / zoom);// * graphZoom / zoom - if (fontSize) return; + if (fontSize) break; } } } @@ -202,7 +196,7 @@ export default { fontSize = shape.attr('fontSize') || 12; const oriFontSize = itemStateStyle[shape.get('name')].fontSize || shapeStateStyle[shape.get('name')].fontSize || 12; if (zoom <= 1) shape.attr('fontSize', oriFontSize / zoom); - if (lineWidth) return; + if (lineWidth) break; } } if (fixSelectedItems.fixLineWidth) { @@ -210,7 +204,7 @@ export default { lineWidth = shape.attr('lineWidth') || 0; const oriLineWidth = itemStateStyle.lineWidth || shapeStateStyle.lineWidth || 0; if (zoom <= 1) shape.attr('lineWidth', oriLineWidth / zoom); - if (fontSize) return; + if (fontSize) break; } } } @@ -219,8 +213,6 @@ export default { } - - // graph.zoom(ratio, { x: point.x, y: point.y }); graph.zoomTo(zoom, { x: point.x, y: point.y }); graph.emit('wheelzoom', e); }, From 7d19279960ac8b027c2e1abdacefab234abf226b Mon Sep 17 00:00:00 2001 From: Yanyan-Wang Date: Tue, 21 Jul 2020 20:03:40 +0800 Subject: [PATCH 11/77] fix: fix all shapes by scale the node matrix. --- src/behavior/zoom-canvas.ts | 21 +++++++++---------- src/types/index.ts | 1 - .../Interaction/component/zoom-canvas-fix.tsx | 5 +++-- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/behavior/zoom-canvas.ts b/src/behavior/zoom-canvas.ts index bbb3ec7b06..1c2e5f899b 100644 --- a/src/behavior/zoom-canvas.ts +++ b/src/behavior/zoom-canvas.ts @@ -15,7 +15,6 @@ export default { optimizeZoom: 0.7, fixSelectedItems: { fixAll: false, - fixShape: false, fixLineWidth: false, fixLabel: false, fixState: 'selected' @@ -26,7 +25,6 @@ export default { const { fixSelectedItems } = this; if (fixSelectedItems.fixAll) { - fixSelectedItems.fixShape = true; fixSelectedItems.fixLineWidth = true; fixSelectedItems.fixLabel = true; } @@ -128,7 +126,7 @@ export default { // fix the items when zooming if (graphZoom <= 1) { let fixNodes, fixEdges; - if (fixSelectedItems.fixShape || fixSelectedItems.fixLineWidth || fixSelectedItems.fixLabel) { + if (fixSelectedItems.fixLineWidth || fixSelectedItems.fixLabel) { fixNodes = graph.findAllByState('node', fixSelectedItems.fixState); fixEdges = graph.findAllByState('edge', fixSelectedItems.fixState); @@ -141,14 +139,15 @@ export default { const itemStateStyle = node.getStateStyle(fixSelectedItems.fixState); const shapeStateStyle = node.get('shapeFactory').getShape(nodeModel.shape || nodeModel.type).getStateStyle(fixSelectedItems.fixState, node)[fixSelectedItems.fixState]; if (fixSelectedItems.fixAll) { - let groupMatrix = clone(group.getMatrix()); - if (!groupMatrix) groupMatrix = mat3.create(); - const model = node.getModel(); - const { x, y } = model; - mat3.translate(groupMatrix, groupMatrix, [-x, -y]); - mat3.scale(groupMatrix, groupMatrix, [scale, scale]); - mat3.translate(groupMatrix, groupMatrix, [x, y]); - group.setMatrix(groupMatrix); + if (zoom <= 1) { + let groupMatrix = clone(group.getMatrix()); + if (!groupMatrix) groupMatrix = mat3.create(); + const { x, y } = node.getModel(); + mat3.translate(groupMatrix, groupMatrix, [-x, -y]); + mat3.scale(groupMatrix, groupMatrix, [scale, scale]); + mat3.translate(groupMatrix, groupMatrix, [x, y]); + group.setMatrix(groupMatrix); + } } else { const children = group.get('children'); const childrenLength = children.length; diff --git a/src/types/index.ts b/src/types/index.ts index db20e81d0f..d95999e8a2 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -130,7 +130,6 @@ export interface ModeOption { offset?: number; fixSelectedItems?: Partial<{ fixAll: boolean; - fixShape: boolean; fixLineWidth: boolean; fixLabel: boolean; fixState: string diff --git a/stories/Interaction/component/zoom-canvas-fix.tsx b/stories/Interaction/component/zoom-canvas-fix.tsx index 4a128db7fe..c0c19aa20e 100644 --- a/stories/Interaction/component/zoom-canvas-fix.tsx +++ b/stories/Interaction/component/zoom-canvas-fix.tsx @@ -82,8 +82,9 @@ const ZoomCanvasFix = () => { type: 'zoom-canvas', fixSelectedItems: { fixState: 'selected', - fixLabel: true, - fixLineWidth: true + fixAll: true + // fixLabel: true, + // fixLineWidth: true } }] }, From 17fd3ab32dbcc2f37fe0b31f922a27883cdfedd8 Mon Sep 17 00:00:00 2001 From: Yanyan-Wang Date: Wed, 22 Jul 2020 09:54:18 +0800 Subject: [PATCH 12/77] fix: zoom-canvas with fixItem problems. docs: add zoom-canvas with fixItem demo. --- .../middle/states/defaultBehavior.en.md | 5 + .../middle/states/defaultBehavior.zh.md | 7 +- .../zoomCanvasFixItem/demo/fixItem.js | 140 +++++++++++++ .../zoomCanvasFixItem/demo/meta.json | 16 ++ .../interaction/zoomCanvasFixItem/index.en.md | 8 + .../interaction/zoomCanvasFixItem/index.zh.md | 9 + examples/item/customNode/demo/svgDom.js | 188 +++++++++--------- gatsby-browser.js | 4 +- src/behavior/zoom-canvas.ts | 19 +- .../Interaction/component/zoom-canvas-fix.tsx | 5 +- 10 files changed, 296 insertions(+), 105 deletions(-) create mode 100644 examples/interaction/zoomCanvasFixItem/demo/fixItem.js create mode 100644 examples/interaction/zoomCanvasFixItem/demo/meta.json create mode 100644 examples/interaction/zoomCanvasFixItem/index.en.md create mode 100644 examples/interaction/zoomCanvasFixItem/index.zh.md diff --git a/docs/manual/middle/states/defaultBehavior.en.md b/docs/manual/middle/states/defaultBehavior.en.md index 6e97cd8c7d..f19d6c921e 100644 --- a/docs/manual/middle/states/defaultBehavior.en.md +++ b/docs/manual/middle/states/defaultBehavior.en.md @@ -147,6 +147,11 @@ The canvas can be dragged along x direction only.
{...})`. diff --git a/docs/manual/middle/states/defaultBehavior.zh.md b/docs/manual/middle/states/defaultBehavior.zh.md index 2475e9b1f2..5893b536ca 100644 --- a/docs/manual/middle/states/defaultBehavior.zh.md +++ b/docs/manual/middle/states/defaultBehavior.zh.md @@ -146,7 +146,12 @@ const graph = new G6.Graph({ - `maxZoom`:最大缩放比例; - `enableOptimize`:是否开启性能优化,默认为 false,设置为 true 开启,开启后缩放比例小于 optimizeZoom 时自动隐藏非 keyShape; - `optimizeZoom`:当 enableOptimize 为 true 时起作用,默认值为 0.7,表示当缩放到哪个比例时开始隐藏非 keyShape; - - `shouldUpdate(e)`:是否允许发生缩放。 + - `shouldUpdate(e)`:是否允许发生缩放; + - `fixSelectedItems`:在缩小画布时是否固定选定元素的描边粗细、文本大小、整体大小等,`fixSelectedItems` 是一个对象,有以下变量: + - `fixSelectedItems.fixState`:将被固定的元素状态,被设置为该状态的节点将会在画布缩小时参与固定大小的计算,默认为 `'selected'`; + - `fixSelectedItems.fixAll`:固定元素的整体大小,优先级高于 `fixSelectedItems.fixLineWidth` 和 `fixSelectedItems.fixLabel`; + - `fixSelectedItems.fixLineWidth`:固定元素的 keyShape 的描边粗细; + - `fixSelectedItems.fixLabel`:固定元素的文本大小。 - 相关时机事件: - `wheelzoom(e)`:当缩放发生变化时被触发。使用 `graph.on('wheelzoom', e => {...})` 监听该时机事件。 diff --git a/examples/interaction/zoomCanvasFixItem/demo/fixItem.js b/examples/interaction/zoomCanvasFixItem/demo/fixItem.js new file mode 100644 index 0000000000..911b7ae2c6 --- /dev/null +++ b/examples/interaction/zoomCanvasFixItem/demo/fixItem.js @@ -0,0 +1,140 @@ +import G6 from '@antv/g6'; + +const data = { + nodes: [ + { id: 'node0', size: 50, label: '0', x: 326, y: 268 }, + { id: 'node1', size: 30, label: '1', x: 280, y: 384 }, + { id: 'node2', size: 30, label: '2', x: 234, y: 167 }, + { id: 'node3', size: 30, label: '3', x: 391, y: 368 }, + { id: 'node4', size: 30, label: '4', x: 444, y: 209 }, + { id: 'node5', size: 30, label: '5', x: 378, y: 157 }, + { id: 'node6', size: 15, label: '6', x: 229, y: 400 }, + { id: 'node7', size: 15, label: '7', x: 281, y: 440 }, + { id: 'node8', size: 15, label: '8', x: 188, y: 119 }, + { id: 'node9', size: 15, label: '9', x: 287, y: 157 }, + { id: 'node10', size: 15, label: '10', x: 185, y: 200 }, + { id: 'node11', size: 15, label: '11', x: 238, y: 110 }, + { id: 'node12', size: 15, label: '12', x: 239, y: 221 }, + { id: 'node13', size: 15, label: '13', x: 176, y: 160 }, + { id: 'node14', size: 15, label: '14', x: 389, y: 423 }, + { id: 'node15', size: 15, label: '15', x: 441, y: 341 }, + { id: 'node16', size: 15, label: '16', x: 442, y: 398 }, + ], + edges: [ + { source: 'node0', target: 'node1', label: '0-1' }, + { source: 'node0', target: 'node2', label: '0-2' }, + { source: 'node0', target: 'node3', label: '0-3' }, + { source: 'node0', target: 'node4', label: '0-4' }, + { source: 'node0', target: 'node5', label: '0-5' }, + { source: 'node1', target: 'node6', label: '1-6' }, + { source: 'node1', target: 'node7', label: '1-7' }, + { source: 'node2', target: 'node8', label: '2-8' }, + { source: 'node2', target: 'node9', label: '2-9' }, + { source: 'node2', target: 'node10', label: '2-10' }, + { source: 'node2', target: 'node11', label: '2-11' }, + { source: 'node2', target: 'node12', label: '2-12' }, + { source: 'node2', target: 'node13', label: '2-13' }, + { source: 'node3', target: 'node14', label: '3-14' }, + { source: 'node3', target: 'node15', label: '3-15' }, + { source: 'node3', target: 'node16', label: '3-16' }, + ], +}; + +const graphContainer = document.getElementById('container'); + +// Add a selector to DOM +const selector = document.createElement('select'); +selector.id = 'selector'; +const selection1 = document.createElement('option'); +selection1.value = 'fixAll'; +selection1.innerHTML = 'Fix all'; +const selection2 = document.createElement('option'); +selection2.value = 'fixLabel'; +selection2.innerHTML = 'Fix fontSize of label'; +const selection3 = document.createElement('option'); +selection3.value = 'fixLineWidth'; +selection3.innerHTML = 'Fix lineWidth of keyShape'; +selector.appendChild(selection1); +selector.appendChild(selection2); +selector.appendChild(selection3); +graphContainer.appendChild(selector); + +let fixSelectedItems = { + fixAll: true, + fixState: 'yourStateName' // 'selected' by default +}; + +const width = graphContainer.scrollWidth; +const height = (graphContainer.scrollHeight || 500) - 20; +const graph = new G6.Graph({ + container: 'container', + width, + height, + modes: { + default: [ + 'drag-canvas', + 'drag-node', + { + type: 'zoom-canvas', + fixSelectedItems + } + ], + }, + defaultNode: { + size: [10, 10], + style: { + lineWidth: 2, + fill: '#DEE9FF', + stroke: '#5B8FF9', + }, + }, + defaultEdge: { + size: 1, + style: { + stroke: '#e2e2e2', + lineAppendWidth: 2, + }, + }, + nodeStateStyles: { + yourStateName: { + stroke: '#f00', + lineWidth: 3 + }, + }, + edgeStateStyles: { + yourStateName: { + stroke: '#f00', + lineWidth: 3 + }, + }, +}); + +graph.on('node:click', e => { + graph.setItemState(e.item, 'yourStateName', true); +}); +graph.on('edge:click', e => { + graph.setItemState(e.item, 'yourStateName', true); +}); + +graph.on('canvas:click', e => { + graph.findAllByState('node', 'yourStateName').forEach(node => { + graph.setItemState(node, 'yourStateName', false); + }); + graph.findAllByState('edge', 'yourStateName').forEach(edge => { + graph.setItemState(edge, 'yourStateName', false); + }); +}); + + +graph.data(data); +graph.render(); + + +// Listen to the selector, change the mode when the selector is changed +selector.addEventListener('change', e => { + const value = e.target.value; + Object.keys(fixSelectedItems).forEach(key => { + if (key !== 'fixState') fixSelectedItems[key] = false; + }) + fixSelectedItems[value] = true; +}); diff --git a/examples/interaction/zoomCanvasFixItem/demo/meta.json b/examples/interaction/zoomCanvasFixItem/demo/meta.json new file mode 100644 index 0000000000..4247bedef4 --- /dev/null +++ b/examples/interaction/zoomCanvasFixItem/demo/meta.json @@ -0,0 +1,16 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "fixItem.js", + "title": { + "zh": "缩放画布时固定元素", + "en": "Fix Items while Zooming" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*A3iMQo8L_McAAAAAAAAAAABkARQnAQ" + } + ] +} \ No newline at end of file diff --git a/examples/interaction/zoomCanvasFixItem/index.en.md b/examples/interaction/zoomCanvasFixItem/index.en.md new file mode 100644 index 0000000000..3d419a0ccd --- /dev/null +++ b/examples/interaction/zoomCanvasFixItem/index.en.md @@ -0,0 +1,8 @@ +--- +title: Fix Items while Zooming +order: 11 +--- + +## Usage + +This demo fix the size, fontSize, lineWidth of selected items by configuring the built-in behavior `'zoom-canvas'`. See the configuration `fixSelectedItems` in [zoom-canvas](/en/docs/manual/middle/states/defaultBehavior#zoom-canvas) for detail. \ No newline at end of file diff --git a/examples/interaction/zoomCanvasFixItem/index.zh.md b/examples/interaction/zoomCanvasFixItem/index.zh.md new file mode 100644 index 0000000000..a280daa769 --- /dev/null +++ b/examples/interaction/zoomCanvasFixItem/index.zh.md @@ -0,0 +1,9 @@ +--- +title: 缩放画布时固定元素 +order: 11 +--- + + +## 使用指南 + +该示例演示了通过配置 G6 内置的 zoom-canvas 可以达到在缩小画布时固定选定的元素的大小、文本大小、描边粗细。相关参数 `fixSelectedItems` 详见[zoom-canvas](/zh/docs/manual/middle/states/defaultBehavior#zoom-canvas) 。 diff --git a/examples/item/customNode/demo/svgDom.js b/examples/item/customNode/demo/svgDom.js index 4a744c5c59..2c384d1a2d 100644 --- a/examples/item/customNode/demo/svgDom.js +++ b/examples/item/customNode/demo/svgDom.js @@ -9,105 +9,105 @@ import G6 from '@antv/g6'; /** * Register a node type with DOM */ -G6.registerNode('dom-node', { - draw: (cfg, group) => { - const stroke = cfg.style ? cfg.style.stroke || '#5B8FF9' : '#5B8FF9'; - const shape = group.addShape('dom', { - attrs: { - width: cfg.size[0], - height: cfg.size[1], - html: ` -
-
- -
- ${cfg.label} -
- ` - }, - draggable: true - }); - return shape; - } -}); +// G6.registerNode('dom-node', { +// draw: (cfg, group) => { +// const stroke = cfg.style ? cfg.style.stroke || '#5B8FF9' : '#5B8FF9'; +// const shape = group.addShape('dom', { +// attrs: { +// width: cfg.size[0], +// height: cfg.size[1], +// html: ` +//
+//
+// +//
+// ${cfg.label} +//
+// ` +// }, +// draggable: true +// }); +// return shape; +// } +// }); -/** 数据 */ -const data = { - nodes: [ - { - id: 'node1', - x: 10, - y: 100, - label: 'Homepage', - }, - { - id: 'node2', - x: 200, - y: 100, - label: 'Subpage', - }, - ], - edges: [ - { - source: 'node1', - target: 'node2', - }, - ], -}; +// /** 数据 */ +// const data = { +// nodes: [ +// { +// id: 'node1', +// x: 10, +// y: 100, +// label: 'Homepage', +// }, +// { +// id: 'node2', +// x: 200, +// y: 100, +// label: 'Subpage', +// }, +// ], +// edges: [ +// { +// source: 'node1', +// target: 'node2', +// }, +// ], +// }; -const width = document.getElementById('container').scrollWidth; -const height = document.getElementById('container').scrollHeight || 500; -const graph = new G6.Graph({ - container: 'container', - width, - height, - // translate the graph to align the canvas's center, support by v3.5.1 - fitCenter: true, - renderer: 'svg', - linkCenter: true, - defaultNode: { - type: 'dom-node', - size: [120, 40] - } -}); +// const width = document.getElementById('container').scrollWidth; +// const height = document.getElementById('container').scrollHeight || 500; +// const graph = new G6.Graph({ +// container: 'container', +// width, +// height, +// // translate the graph to align the canvas's center, support by v3.5.1 +// fitCenter: true, +// renderer: 'svg', +// linkCenter: true, +// defaultNode: { +// type: 'dom-node', +// size: [120, 40] +// } +// }); -graph.data(data); -graph.render(); +// graph.data(data); +// graph.render(); -// click listener for dom nodes to response the click by changing stroke color -const listener = (dom) => { - const nodeId = dom.id; - const node = graph.findById(nodeId); - let stroke = ''; - if (!node.hasState('selected')) { - stroke = '#f00'; - graph.setItemState(node, 'selected', true); - } else { - stroke = '#5B8FF9'; - graph.setItemState(node, 'selected', false); - } - graph.updateItem(nodeId, { - style: { - stroke - } - }); -} +// // click listener for dom nodes to response the click by changing stroke color +// const listener = (dom) => { +// const nodeId = dom.id; +// const node = graph.findById(nodeId); +// let stroke = ''; +// if (!node.hasState('selected')) { +// stroke = '#f00'; +// graph.setItemState(node, 'selected', true); +// } else { +// stroke = '#5B8FF9'; +// graph.setItemState(node, 'selected', false); +// } +// graph.updateItem(nodeId, { +// style: { +// stroke +// } +// }); +// } -const bindClickListener = () => { - const domNodes = document.getElementsByClassName('dom-node') - for (let i = 0; i < domNodes.length; i++) { - const dom = domNodes[i]; - dom.addEventListener('click', (e) => { - listener(dom); - }); - } -} +// const bindClickListener = () => { +// const domNodes = document.getElementsByClassName('dom-node') +// for (let i = 0; i < domNodes.length; i++) { +// const dom = domNodes[i]; +// dom.addEventListener('click', (e) => { +// listener(dom); +// }); +// } +// } -bindClickListener(); +// bindClickListener(); -// after update the item, all the DOMs will be re-rendered -// so the listeners should be rebinded to the new DOMs -graph.on('afterupdateitem', e => { - bindClickListener(); -}); +// // after update the item, all the DOMs will be re-rendered +// // so the listeners should be rebinded to the new DOMs +// graph.on('afterupdateitem', e => { +// bindClickListener(); +// }); diff --git a/gatsby-browser.js b/gatsby-browser.js index 98906e728f..d1b4d76a74 100644 --- a/gatsby-browser.js +++ b/gatsby-browser.js @@ -1,3 +1,3 @@ -// window.g6 = require('./src/index.ts'); // import the source for debugging -window.g6 = require('./dist/g6.min.js'); // import the package for webworker +window.g6 = require('./src/index.ts'); // import the source for debugging +// window.g6 = require('./dist/g6.min.js'); // import the package for webworker window.insertCss = require('insert-css'); diff --git a/src/behavior/zoom-canvas.ts b/src/behavior/zoom-canvas.ts index 1c2e5f899b..f0d6069e3b 100644 --- a/src/behavior/zoom-canvas.ts +++ b/src/behavior/zoom-canvas.ts @@ -24,6 +24,7 @@ export default { getEvents(): { [key in G6Event]?: string } { const { fixSelectedItems } = this; + if (!fixSelectedItems.fixState) fixSelectedItems.fixState = 'selected'; if (fixSelectedItems.fixAll) { fixSelectedItems.fixLineWidth = true; fixSelectedItems.fixLabel = true; @@ -122,14 +123,14 @@ export default { } } - // fix the items when zooming if (graphZoom <= 1) { let fixNodes, fixEdges; - if (fixSelectedItems.fixLineWidth || fixSelectedItems.fixLabel) { + if (fixSelectedItems.fixAll || fixSelectedItems.fixLineWidth || fixSelectedItems.fixLabel) { fixNodes = graph.findAllByState('node', fixSelectedItems.fixState); fixEdges = graph.findAllByState('edge', fixSelectedItems.fixState); + console.log('zooming ', fixNodes, fixSelectedItems.fixState); const scale = graphZoom / zoom; const fixNodesLength = fixNodes.length; for (let fn = 0; fn < fixNodesLength; fn++) { @@ -158,7 +159,11 @@ export default { const shapeType = shape.get('type'); if (shapeType === 'text') { fontSize = shape.attr('fontSize') || 12; - const oriFontSize = itemStateStyle[shape.get('name')].fontSize || shapeStateStyle[shape.get('name')].fontSize || 12; + const itemStyle = itemStateStyle[shape.get('name')]; + const shapeStyle = shapeStateStyle[shape.get('name')]; + const itemFontSize = itemStyle ? itemStyle.fontSize : 12; + const shapeFontSize = shapeStyle ? shapeStyle.fontSize : 12; + const oriFontSize = itemFontSize || shapeFontSize || 12; if (zoom <= 1) shape.attr('fontSize', oriFontSize / zoom);// * graphZoom / zoom if (lineWidth) break; } @@ -193,7 +198,11 @@ export default { const shapeType = shape.get('type'); if (shapeType === 'text') { fontSize = shape.attr('fontSize') || 12; - const oriFontSize = itemStateStyle[shape.get('name')].fontSize || shapeStateStyle[shape.get('name')].fontSize || 12; + const itemStyle = itemStateStyle[shape.get('name')]; + const shapeStyle = shapeStateStyle[shape.get('name')]; + const itemFontSize = itemStyle ? itemStyle.fontSize : 12; + const shapeFontSize = shapeStyle ? shapeStyle.fontSize : 12; + const oriFontSize = itemFontSize || shapeFontSize || 12; if (zoom <= 1) shape.attr('fontSize', oriFontSize / zoom); if (lineWidth) break; } @@ -201,7 +210,7 @@ export default { if (fixSelectedItems.fixLineWidth) { if (shape.get('isKeyShape')) { lineWidth = shape.attr('lineWidth') || 0; - const oriLineWidth = itemStateStyle.lineWidth || shapeStateStyle.lineWidth || 0; + const oriLineWidth = itemStateStyle.lineWidth || shapeStateStyle.lineWidth || 1; if (zoom <= 1) shape.attr('lineWidth', oriLineWidth / zoom); if (fontSize) break; } diff --git a/stories/Interaction/component/zoom-canvas-fix.tsx b/stories/Interaction/component/zoom-canvas-fix.tsx index c0c19aa20e..93e289236c 100644 --- a/stories/Interaction/component/zoom-canvas-fix.tsx +++ b/stories/Interaction/component/zoom-canvas-fix.tsx @@ -136,11 +136,10 @@ const ZoomCanvasFix = () => { graph.on('canvas:click', e => { graph.findAllByState('node', 'selected').forEach(node => { graph.setItemState(node, 'selected', false); - }) + }); graph.findAllByState('edge', 'selected').forEach(edge => { graph.setItemState(edge, 'selected', false); - }) - + }); }); } }); From 322a1c838058f00945c69301693b1f1f63696d4d Mon Sep 17 00:00:00 2001 From: Yanyan-Wang Date: Wed, 22 Jul 2020 11:02:49 +0800 Subject: [PATCH 13/77] docs: demo for hide items while dragging. fix: fix node while zooming problem. fix: set default enableOptimize of drag-canvas to be false. --- .../middle/states/defaultBehavior.en.md | 2 +- .../middle/states/defaultBehavior.zh.md | 2 +- .../dragCanvasHideItem/demo/hideItem.js | 90 +++++++++ .../dragCanvasHideItem/demo/meta.json | 16 ++ .../dragCanvasHideItem/index.en.md | 10 + .../dragCanvasHideItem/index.zh.md | 10 + .../zoomCanvasFixItem/demo/meta.json | 2 +- .../interaction/zoomCanvasFixItem/index.en.md | 4 +- .../interaction/zoomCanvasFixItem/index.zh.md | 7 +- examples/item/customNode/demo/svgDom.js | 188 +++++++++--------- src/behavior/drag-canvas.ts | 8 +- src/behavior/zoom-canvas.ts | 4 +- tests/unit/behavior/index-spec.ts | 2 +- 13 files changed, 237 insertions(+), 108 deletions(-) create mode 100644 examples/interaction/dragCanvasHideItem/demo/hideItem.js create mode 100644 examples/interaction/dragCanvasHideItem/demo/meta.json create mode 100644 examples/interaction/dragCanvasHideItem/index.en.md create mode 100644 examples/interaction/dragCanvasHideItem/index.zh.md diff --git a/docs/manual/middle/states/defaultBehavior.en.md b/docs/manual/middle/states/defaultBehavior.en.md index f19d6c921e..7bcdb23697 100644 --- a/docs/manual/middle/states/defaultBehavior.en.md +++ b/docs/manual/middle/states/defaultBehavior.en.md @@ -100,7 +100,7 @@ const graph = new G6.Graph({ - Configurations: - `type: 'drag-canvas'`; - `direction`: The direction of dragging that is allowed. Options: `'x'`, `'y'`, `'both'`. `'both'` by default; - - `enableOptimize`: whether enable optimize, `true` by default, when drag canvas will auto hide all edges and the part of node that is not keyShape; + - `enableOptimize`: whether enable optimization, `false` by default. `enableOptimize: true` means hiding all edges and the shapes beside keyShapes of nodes while dragging canvas; - `shouldBegin(e)`: Whether allow the behavior happen on the current item (e.item). - Related timing events: - `canvas:dragstart`: Triggered when drag start. Listened by `graph.on('canvas:dragstart', e => {...})`; diff --git a/docs/manual/middle/states/defaultBehavior.zh.md b/docs/manual/middle/states/defaultBehavior.zh.md index 5893b536ca..322b9f211a 100644 --- a/docs/manual/middle/states/defaultBehavior.zh.md +++ b/docs/manual/middle/states/defaultBehavior.zh.md @@ -100,7 +100,7 @@ const graph = new G6.Graph({ - 配置项: - `type: 'drag-canvas'`; - `direction`:允许拖拽方向,支持`'x'`,`'y'`,`'both'`,默认方向为 `'both'`; - - `enableOptimize`:是否开启优化,开启后拖动画布过程中隐藏所有的边及节点上非 keyShape 部分,默认开启; + - `enableOptimize`:是否开启优化,开启后拖动画布过程中隐藏所有的边及节点上非 keyShape 部分,默认关闭; - `shouldBegin(e)`:是否允许触发该操作。 - 相关时机事件: - `canvas:dragstart`:画布拖拽开始时触发,使用 `graph.on('canvas:dragstart', e => {...})` 监听; diff --git a/examples/interaction/dragCanvasHideItem/demo/hideItem.js b/examples/interaction/dragCanvasHideItem/demo/hideItem.js new file mode 100644 index 0000000000..8a1154f7da --- /dev/null +++ b/examples/interaction/dragCanvasHideItem/demo/hideItem.js @@ -0,0 +1,90 @@ +import G6 from '@antv/g6'; + +const data = { + nodes: [ + { id: 'node0', size: 50, label: '0', x: 326, y: 268 }, + { id: 'node1', size: 30, label: '1', x: 280, y: 384 }, + { id: 'node2', size: 30, label: '2', x: 234, y: 167 }, + { id: 'node3', size: 30, label: '3', x: 391, y: 368 }, + { id: 'node4', size: 30, label: '4', x: 444, y: 209 }, + { id: 'node5', size: 30, label: '5', x: 378, y: 157 }, + { id: 'node6', size: 15, label: '6', x: 229, y: 400 }, + { id: 'node7', size: 15, label: '7', x: 281, y: 440 }, + { id: 'node8', size: 15, label: '8', x: 188, y: 119 }, + { id: 'node9', size: 15, label: '9', x: 287, y: 157 }, + { id: 'node10', size: 15, label: '10', x: 185, y: 200 }, + { id: 'node11', size: 15, label: '11', x: 238, y: 110 }, + { id: 'node12', size: 15, label: '12', x: 239, y: 221 }, + { id: 'node13', size: 15, label: '13', x: 176, y: 160 }, + { id: 'node14', size: 15, label: '14', x: 389, y: 423 }, + { id: 'node15', size: 15, label: '15', x: 441, y: 341 }, + { id: 'node16', size: 15, label: '16', x: 442, y: 398 }, + ], + edges: [ + { source: 'node0', target: 'node1', label: '0-1' }, + { source: 'node0', target: 'node2', label: '0-2' }, + { source: 'node0', target: 'node3', label: '0-3' }, + { source: 'node0', target: 'node4', label: '0-4' }, + { source: 'node0', target: 'node5', label: '0-5' }, + { source: 'node1', target: 'node6', label: '1-6' }, + { source: 'node1', target: 'node7', label: '1-7' }, + { source: 'node2', target: 'node8', label: '2-8' }, + { source: 'node2', target: 'node9', label: '2-9' }, + { source: 'node2', target: 'node10', label: '2-10' }, + { source: 'node2', target: 'node11', label: '2-11' }, + { source: 'node2', target: 'node12', label: '2-12' }, + { source: 'node2', target: 'node13', label: '2-13' }, + { source: 'node3', target: 'node14', label: '3-14' }, + { source: 'node3', target: 'node15', label: '3-15' }, + { source: 'node3', target: 'node16', label: '3-16' }, + ], +}; + +const graphContainer = document.getElementById('container'); +const width = graphContainer.scrollWidth; +const height = graphContainer.scrollHeight || 500; + +const graph = new G6.Graph({ + container: 'container', + width, + height, + modes: { + default: [ + 'drag-node', + { + type: 'drag-canvas', + enableOptimize: true // enable the optimize to hide the shapes beside nodes' keyShape + }, + ], + }, + defaultNode: { + size: [10, 10], + style: { + lineWidth: 2, + fill: '#DEE9FF', + stroke: '#5B8FF9', + }, + }, + defaultEdge: { + size: 1, + style: { + stroke: '#e2e2e2', + lineAppendWidth: 2, + }, + }, + nodeStateStyles: { + yourStateName: { + stroke: '#f00', + lineWidth: 3 + }, + }, + edgeStateStyles: { + yourStateName: { + stroke: '#f00', + lineWidth: 3 + }, + }, +}); + +graph.data(data); +graph.render(); \ No newline at end of file diff --git a/examples/interaction/dragCanvasHideItem/demo/meta.json b/examples/interaction/dragCanvasHideItem/demo/meta.json new file mode 100644 index 0000000000..856eafa7fd --- /dev/null +++ b/examples/interaction/dragCanvasHideItem/demo/meta.json @@ -0,0 +1,16 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "hideItem.js", + "title": { + "zh": "拖拽画布时隐藏节点 keyShape 外所有图形", + "en": "Hide the Shapes beside keyShape of Nodes while Dragging" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*hld6SrA4qacAAAAAAAAAAABkARQnAQ" + } + ] +} \ No newline at end of file diff --git a/examples/interaction/dragCanvasHideItem/index.en.md b/examples/interaction/dragCanvasHideItem/index.en.md new file mode 100644 index 0000000000..15de7be327 --- /dev/null +++ b/examples/interaction/dragCanvasHideItem/index.en.md @@ -0,0 +1,10 @@ +--- +title: Hide Items while Dragging +order: 13 +--- + +The global rendering will be triggered frequently, which costs a lot. To improve the performance of drag-canvas, v3.5.11 supports a boolean type configuration `enableOptimize` for built-in behavior 'drag-canvas' to hide the shapes besides keyShape of nodes. + +## Usage + +This demo hide the shapes beside the keyShape of nodes by configuring the built-in behavesides `'drag-canvas'`. See the configuration `fixSelectedItems` in [zoom-canvas](/zh/docs/manual/middle/states/defaultBehavior#zoom-canvas) for detail. \ No newline at end of file diff --git a/examples/interaction/dragCanvasHideItem/index.zh.md b/examples/interaction/dragCanvasHideItem/index.zh.md new file mode 100644 index 0000000000..e3b649cc24 --- /dev/null +++ b/examples/interaction/dragCanvasHideItem/index.zh.md @@ -0,0 +1,10 @@ +--- +title: 拖拽画布时隐藏元素 +order: 13 +--- + +拖拽画布会触发非常频繁的全局渲染,为了提升性能,v3.5.11 推出了内置交互 'drag-canvas' 的布尔型配置项 `enableOptimize`,若 `enableOptimize` 为 `true`,则拖拽画布时,将隐藏除节点 keyShape 以外的所有图形。 + +## 使用指南 + +配置内置交互 'drag-canvas' 的布尔型配置项 `enableOptimize`。详见[drag-canvas](/zh/docs/manual/middle/states/defaultBehavior#drag-canvas)。 diff --git a/examples/interaction/zoomCanvasFixItem/demo/meta.json b/examples/interaction/zoomCanvasFixItem/demo/meta.json index 4247bedef4..eb225575db 100644 --- a/examples/interaction/zoomCanvasFixItem/demo/meta.json +++ b/examples/interaction/zoomCanvasFixItem/demo/meta.json @@ -10,7 +10,7 @@ "zh": "缩放画布时固定元素", "en": "Fix Items while Zooming" }, - "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*A3iMQo8L_McAAAAAAAAAAABkARQnAQ" + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*nmaeRqYi9CMAAAAAAAAAAABkARQnAQ" } ] } \ No newline at end of file diff --git a/examples/interaction/zoomCanvasFixItem/index.en.md b/examples/interaction/zoomCanvasFixItem/index.en.md index 3d419a0ccd..13357551c9 100644 --- a/examples/interaction/zoomCanvasFixItem/index.en.md +++ b/examples/interaction/zoomCanvasFixItem/index.en.md @@ -1,8 +1,10 @@ --- title: Fix Items while Zooming -order: 11 +order: 12 --- +To keep the selected items being highlighted, v3.5.11 supports a configuration `fixSelectedItems` for built-in behavior 'zoom-canvas'. + ## Usage This demo fix the size, fontSize, lineWidth of selected items by configuring the built-in behavior `'zoom-canvas'`. See the configuration `fixSelectedItems` in [zoom-canvas](/en/docs/manual/middle/states/defaultBehavior#zoom-canvas) for detail. \ No newline at end of file diff --git a/examples/interaction/zoomCanvasFixItem/index.zh.md b/examples/interaction/zoomCanvasFixItem/index.zh.md index a280daa769..379c35eee7 100644 --- a/examples/interaction/zoomCanvasFixItem/index.zh.md +++ b/examples/interaction/zoomCanvasFixItem/index.zh.md @@ -1,9 +1,10 @@ --- -title: 缩放画布时固定元素 -order: 11 +title: 缩放画布时固定选中元素 +order: 12 --- +为了在缩放画布时能够保持关注节点/边的高亮效果,v3.5.11 推出了内置交互 'zoom-canvas' 的配置项 `fixSelectedItems` 以达到缩放画布时固定选中节点的需求。 ## 使用指南 -该示例演示了通过配置 G6 内置的 zoom-canvas 可以达到在缩小画布时固定选定的元素的大小、文本大小、描边粗细。相关参数 `fixSelectedItems` 详见[zoom-canvas](/zh/docs/manual/middle/states/defaultBehavior#zoom-canvas) 。 +配置内置交互 'zoom-canvas' 的配置项 `fixSelectedItems`,`fixSelectedItems` 是一个对象,包括 `fixState`、`fixAll`、`fixLabel`、`fixLineWidth`,可达到在缩小画布时固定选定的元素的大小、文本大小、描边粗细。详见[zoom-canvas](/zh/docs/manual/middle/states/defaultBehavior#zoom-canvas)。 diff --git a/examples/item/customNode/demo/svgDom.js b/examples/item/customNode/demo/svgDom.js index 2c384d1a2d..4a744c5c59 100644 --- a/examples/item/customNode/demo/svgDom.js +++ b/examples/item/customNode/demo/svgDom.js @@ -9,105 +9,105 @@ import G6 from '@antv/g6'; /** * Register a node type with DOM */ -// G6.registerNode('dom-node', { -// draw: (cfg, group) => { -// const stroke = cfg.style ? cfg.style.stroke || '#5B8FF9' : '#5B8FF9'; -// const shape = group.addShape('dom', { -// attrs: { -// width: cfg.size[0], -// height: cfg.size[1], -// html: ` -//
-//
-// -//
-// ${cfg.label} -//
-// ` -// }, -// draggable: true -// }); -// return shape; -// } -// }); +G6.registerNode('dom-node', { + draw: (cfg, group) => { + const stroke = cfg.style ? cfg.style.stroke || '#5B8FF9' : '#5B8FF9'; + const shape = group.addShape('dom', { + attrs: { + width: cfg.size[0], + height: cfg.size[1], + html: ` +
+
+ +
+ ${cfg.label} +
+ ` + }, + draggable: true + }); + return shape; + } +}); -// /** 数据 */ -// const data = { -// nodes: [ -// { -// id: 'node1', -// x: 10, -// y: 100, -// label: 'Homepage', -// }, -// { -// id: 'node2', -// x: 200, -// y: 100, -// label: 'Subpage', -// }, -// ], -// edges: [ -// { -// source: 'node1', -// target: 'node2', -// }, -// ], -// }; +/** 数据 */ +const data = { + nodes: [ + { + id: 'node1', + x: 10, + y: 100, + label: 'Homepage', + }, + { + id: 'node2', + x: 200, + y: 100, + label: 'Subpage', + }, + ], + edges: [ + { + source: 'node1', + target: 'node2', + }, + ], +}; -// const width = document.getElementById('container').scrollWidth; -// const height = document.getElementById('container').scrollHeight || 500; -// const graph = new G6.Graph({ -// container: 'container', -// width, -// height, -// // translate the graph to align the canvas's center, support by v3.5.1 -// fitCenter: true, -// renderer: 'svg', -// linkCenter: true, -// defaultNode: { -// type: 'dom-node', -// size: [120, 40] -// } -// }); +const width = document.getElementById('container').scrollWidth; +const height = document.getElementById('container').scrollHeight || 500; +const graph = new G6.Graph({ + container: 'container', + width, + height, + // translate the graph to align the canvas's center, support by v3.5.1 + fitCenter: true, + renderer: 'svg', + linkCenter: true, + defaultNode: { + type: 'dom-node', + size: [120, 40] + } +}); -// graph.data(data); -// graph.render(); +graph.data(data); +graph.render(); -// // click listener for dom nodes to response the click by changing stroke color -// const listener = (dom) => { -// const nodeId = dom.id; -// const node = graph.findById(nodeId); -// let stroke = ''; -// if (!node.hasState('selected')) { -// stroke = '#f00'; -// graph.setItemState(node, 'selected', true); -// } else { -// stroke = '#5B8FF9'; -// graph.setItemState(node, 'selected', false); -// } -// graph.updateItem(nodeId, { -// style: { -// stroke -// } -// }); -// } +// click listener for dom nodes to response the click by changing stroke color +const listener = (dom) => { + const nodeId = dom.id; + const node = graph.findById(nodeId); + let stroke = ''; + if (!node.hasState('selected')) { + stroke = '#f00'; + graph.setItemState(node, 'selected', true); + } else { + stroke = '#5B8FF9'; + graph.setItemState(node, 'selected', false); + } + graph.updateItem(nodeId, { + style: { + stroke + } + }); +} -// const bindClickListener = () => { -// const domNodes = document.getElementsByClassName('dom-node') -// for (let i = 0; i < domNodes.length; i++) { -// const dom = domNodes[i]; -// dom.addEventListener('click', (e) => { -// listener(dom); -// }); -// } -// } +const bindClickListener = () => { + const domNodes = document.getElementsByClassName('dom-node') + for (let i = 0; i < domNodes.length; i++) { + const dom = domNodes[i]; + dom.addEventListener('click', (e) => { + listener(dom); + }); + } +} -// bindClickListener(); +bindClickListener(); -// // after update the item, all the DOMs will be re-rendered -// // so the listeners should be rebinded to the new DOMs -// graph.on('afterupdateitem', e => { -// bindClickListener(); -// }); +// after update the item, all the DOMs will be re-rendered +// so the listeners should be rebinded to the new DOMs +graph.on('afterupdateitem', e => { + bindClickListener(); +}); diff --git a/src/behavior/drag-canvas.ts b/src/behavior/drag-canvas.ts index 2cadf3f048..7f20495881 100644 --- a/src/behavior/drag-canvas.ts +++ b/src/behavior/drag-canvas.ts @@ -10,7 +10,7 @@ export default { getDefaultCfg(): object { return { direction: 'both', - enableOptimize: true + enableOptimize: false }; }, getEvents(): { [key in G6Event]?: string } { @@ -75,14 +75,14 @@ export default { if (this.enableOptimize) { // 开始拖动时关闭局部渲染 this.graph.get('canvas').set('localRefresh', false) - + // 拖动 canvas 过程中隐藏所有的边及label const graph: IGraph = this.graph const edges = graph.getEdges() for (let i = 0, len = edges.length; i < len; i++) { graph.hideItem(edges[i]) } - + const nodes = graph.getNodes() for (let j = 0, nodeLen = nodes.length; j < nodeLen; j++) { const container = nodes[j].getContainer() @@ -138,7 +138,7 @@ export default { for (let i = 0, len = edges.length; i < len; i++) { graph.showItem(edges[i]) } - + const nodes = graph.getNodes() for (let j = 0, nodeLen = nodes.length; j < nodeLen; j++) { const container = nodes[j].getContainer() diff --git a/src/behavior/zoom-canvas.ts b/src/behavior/zoom-canvas.ts index f0d6069e3b..fa9cc85c5c 100644 --- a/src/behavior/zoom-canvas.ts +++ b/src/behavior/zoom-canvas.ts @@ -194,7 +194,7 @@ export default { for (let c = 0; c < childrenLength; c++) { const shape = children[c]; let fontSize, lineWidth; - if (fixSelectedItems.fixLabel) { + if (fixSelectedItems.fixLabel || fixSelectedItems.fixAll) { const shapeType = shape.get('type'); if (shapeType === 'text') { fontSize = shape.attr('fontSize') || 12; @@ -207,7 +207,7 @@ export default { if (lineWidth) break; } } - if (fixSelectedItems.fixLineWidth) { + if (fixSelectedItems.fixLineWidth || fixSelectedItems.fixAll) { if (shape.get('isKeyShape')) { lineWidth = shape.attr('lineWidth') || 0; const oriLineWidth = itemStateStyle.lineWidth || shapeStateStyle.lineWidth || 1; diff --git a/tests/unit/behavior/index-spec.ts b/tests/unit/behavior/index-spec.ts index dc9237649d..efb0231128 100644 --- a/tests/unit/behavior/index-spec.ts +++ b/tests/unit/behavior/index-spec.ts @@ -99,7 +99,7 @@ describe('Default Behavior', () => { const dragCanvas: IBehavior = new DragCanvas(); const config = dragCanvas.getDefaultCfg(); - expect(config).toEqual({ direction: 'both', enableOptimize: true }); + expect(config).toEqual({ direction: 'both', enableOptimize: false }); const events = dragCanvas.getEvents(); const keys = Object.keys(events); From 9c7db822e9ff8e1956bf7ce07add9a19cc092f25 Mon Sep 17 00:00:00 2001 From: Yanyan-Wang Date: Wed, 22 Jul 2020 13:11:17 +0800 Subject: [PATCH 14/77] fix: delete unnecessary console. --- src/behavior/zoom-canvas.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/behavior/zoom-canvas.ts b/src/behavior/zoom-canvas.ts index fa9cc85c5c..0d50452f6c 100644 --- a/src/behavior/zoom-canvas.ts +++ b/src/behavior/zoom-canvas.ts @@ -130,7 +130,6 @@ export default { fixNodes = graph.findAllByState('node', fixSelectedItems.fixState); fixEdges = graph.findAllByState('edge', fixSelectedItems.fixState); - console.log('zooming ', fixNodes, fixSelectedItems.fixState); const scale = graphZoom / zoom; const fixNodesLength = fixNodes.length; for (let fn = 0; fn < fixNodesLength; fn++) { From 5eb51b1d88ff6934eadd0cc2f3a5de74fec840f4 Mon Sep 17 00:00:00 2001 From: baizn <576375879@qq.com> Date: Mon, 13 Jul 2020 20:23:44 +0800 Subject: [PATCH 15/77] feat: graph.priorityState & event mode --- CHANGELOG.md | 4 + package.json | 2 +- src/global.ts | 2 +- src/graph/controller/event.ts | 66 +++++++++-------- src/graph/controller/item.ts | 19 +++++ src/graph/graph.ts | 10 +++ src/interface/graph.ts | 7 ++ tests/unit/graph/controller/event-spec.ts | 89 +++++++++++++++++++++++ 8 files changed, 167 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7bf0ce4a28..62fad14d02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # ChangeLog +#### 3.5.11 +- feat: graph.priorityState api; +- feat: graph.on support name:event mode. + #### 3.5.10 - fix: fitView and fitCenter with animate in the initial state; - fix: dulplicated edges in nodeselectchange event of brush-select; diff --git a/package.json b/package.json index 7e241be48a..8b213267df 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@antv/g6", - "version": "3.5.10", + "version": "3.5.11", "description": "A Graph Visualization Framework in JavaScript", "keywords": [ "antv", diff --git a/src/global.ts b/src/global.ts index 718a166404..109ccd9014 100644 --- a/src/global.ts +++ b/src/global.ts @@ -1,5 +1,5 @@ export default { - version: '3.5.10', + version: '3.5.11', rootContainerClassName: 'root-container', nodeContainerClassName: 'node-container', edgeContainerClassName: 'edge-container', diff --git a/src/graph/controller/event.ts b/src/graph/controller/event.ts index 518f14f26f..0137251de2 100644 --- a/src/graph/controller/event.ts +++ b/src/graph/controller/event.ts @@ -10,32 +10,32 @@ import { IG6GraphEvent, Matrix, Item } from '../../types'; import { cloneEvent, isViewportChanged } from '../../util/base'; import { mat3 } from '@antv/matrix-util'; - type Fun = () => void; -const EVENTS = [ - 'click', - 'mousedown', - 'mouseup', - 'dblclick', - 'contextmenu', - 'mouseenter', - 'mouseout', - 'mouseover', - 'mousemove', - 'mouseleave', - 'dragstart', - 'dragend', - 'drag', - 'dragenter', - 'dragleave', - 'dragover', - 'dragout', - 'drop', - 'touchstart', - 'touchmove', - 'touchend', -]; +// const EVENTS = [ +// 'click', +// 'mousedown', +// 'mouseup', +// 'dblclick', +// 'contextmenu', +// 'mouseenter', +// 'mouseout', +// 'mouseover', +// 'mousemove', +// 'mouseleave', +// 'dragstart', +// 'dragend', +// 'drag', +// 'dragenter', +// 'dragleave', +// 'dragover', +// 'dragout', +// 'drop', +// 'touchstart', +// 'touchmove', +// 'touchend', +// ]; + export default class EventController { private graph: Graph; @@ -69,9 +69,11 @@ export default class EventController { const originHandler = wrapBehavior(this, 'onExtendEvents'); const wheelHandler = wrapBehavior(this, 'onWheelEvent'); - each(EVENTS, event => { - canvas.on(event, canvasHandler); - }); + // each(EVENTS, event => { + // canvas.on(event, canvasHandler); + // }); + + canvas.on('*', canvasHandler) this.canvasHandler = canvasHandler; extendEvents.push(addEventListener(el, 'DOMMouseScroll', wheelHandler)); @@ -157,6 +159,8 @@ export default class EventController { graph.emit(`${type}:${eventType}`, evt); + graph.emit(evt.name, evt); + if (eventType === 'dragstart') { this.dragging = true; } @@ -235,9 +239,11 @@ export default class EventController { const { graph, canvasHandler, extendEvents } = this; const canvas: Canvas = graph.get('canvas'); - each(EVENTS, event => { - canvas.off(event, canvasHandler); - }); + // each(EVENTS, event => { + // canvas.off(event, canvasHandler); + // }); + + canvas.off('*', canvasHandler); each(extendEvents, event => { event.remove(); diff --git a/src/graph/controller/item.ts b/src/graph/controller/item.ts index c03e533388..68ba8310b2 100644 --- a/src/graph/controller/item.ts +++ b/src/graph/controller/item.ts @@ -446,6 +446,25 @@ export default class ItemController { graph.emit('afteritemstatechange', { item, state: stateName, enabled: value }); } + /** + * 将指定状态的优先级提升为最高优先级 + * @param {Item} item 元素id或元素实例 + * @param state 状态名称 + */ + public priorityState(item: Item | string, state: string): void { + const { graph } = this; + + let currentItem = item + if (isString(item)) { + currentItem = graph.findById(item) + } + // 先取消已有的 state + this.setItemState(currentItem as Item, state, false) + + // 再设置state,则此时该优先级为最高 + this.setItemState(currentItem as Item, state, true) + } + /** * 清除所有指定的状态 * diff --git a/src/graph/graph.ts b/src/graph/graph.ts index 81901df5c9..94200e7864 100644 --- a/src/graph/graph.ts +++ b/src/graph/graph.ts @@ -1150,6 +1150,16 @@ export default class Graph extends EventEmitter implements IGraph { } } + /** + * 将指定状态的优先级提升为最高优先级 + * @param {Item} item 元素id或元素实例 + * @param state 状态名称 + */ + public priorityState(item: Item | string, state: string): void { + const itemController: ItemController = this.get('itemController') + itemController.priorityState(item, state); + } + /** * 设置视图初始化数据 * @param {GraphData} data 初始化数据 diff --git a/src/interface/graph.ts b/src/interface/graph.ts index 0f40836f85..202bf7e549 100644 --- a/src/interface/graph.ts +++ b/src/interface/graph.ts @@ -188,6 +188,13 @@ export interface IGraph extends EventEmitter { */ setItemState(item: Item | string, state: string, value: string | boolean): void; + /** + * 将指定状态的优先级提升为最高优先级 + * @param {Item} item 元素id或元素实例 + * @param state 状态名称 + */ + priorityState(item: Item | string, state: string): void; + /** * 设置视图初始化数据 * @param {GraphData} data 初始化数据 diff --git a/tests/unit/graph/controller/event-spec.ts b/tests/unit/graph/controller/event-spec.ts index 17f8c112b0..38c100ed56 100644 --- a/tests/unit/graph/controller/event-spec.ts +++ b/tests/unit/graph/controller/event-spec.ts @@ -256,3 +256,92 @@ describe('event', () => { expect(graph.destroyed).toBe(true); }); }); + +describe('event with name', () => { + it('default node', () => { + G6.registerNode('custom-node', { + drawShape(cfg, group) { + const keyShape = group.addShape('rect', { + attrs: { + width: 120, + height: 50, + stroke: 'red', + fill: '#ccc' + }, + name: 'custom-node-rect' + }) + + group.addShape('rect', { + attrs: { + width: 70, + height: 30, + stroke: 'green', + fill: 'green', + x: 20, + y: 10 + }, + name: 'custom-node-subrect' + }) + return keyShape + } + }, 'single-node') + + const graph = new G6.Graph({ + container: 'event-spec', + width: 500, + height: 400, + nodeStateStyles: { + selected: { + fill: 'red' + } + }, + defaultNode: { + type: 'custom-node', + linkPoint: { + show: true + } + } + }) + + const data = { + nodes: [ + { + id: 'node', + label: 'node', + x: 100, + y: 200 + }, + { + id: 'node1', + label: 'node1', + x: 300, + y: 200 + } + ] + } + + graph.data(data) + graph.render() + + graph.on('node:mouseenter', evt => { + graph.setItemState(evt.item, 'selected', true) + }) + + graph.on('node:mouseleave', evt => { + graph.setItemState(evt.item, 'selected', false) + }) + + graph.on('custom-node-rect:click', evt => { + graph.setItemState(evt.item, 'selected', true) + const name = evt.target.get('name') + expect(name).toEqual('custom-node-rect') + }) + + graph.on('custom-node-subrect:click', evt => { + const name = evt.target.get('name') + expect(name).toEqual('custom-node-subrect') + }) + + graph.destroy() + }) +}) From 62eb5df771e72d059bed93515d43bd2b2488f105 Mon Sep 17 00:00:00 2001 From: baizn <576375879@qq.com> Date: Wed, 22 Jul 2020 14:58:41 +0800 Subject: [PATCH 16/77] fix: update partialNode demo --- .../partialResponse/demo/partialNode.js | 38 ++++++++++--------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/examples/interaction/partialResponse/demo/partialNode.js b/examples/interaction/partialResponse/demo/partialNode.js index e96d38b386..a45d388636 100644 --- a/examples/interaction/partialResponse/demo/partialNode.js +++ b/examples/interaction/partialResponse/demo/partialNode.js @@ -4,7 +4,6 @@ import G6 from '@antv/g6'; * by 长哲 */ -const INNER_CIRCLE_CLASS = 'node-inner-circle'; const GRAPH_CONTAINER = 'container'; // 注册自定义节点 @@ -24,7 +23,7 @@ G6.registerNode( name: 'key-shape', }); // 绘制节点里面的小圆。点击这个小圆会显示tooltip - const innerCircle = group.addShape('circle', { + group.addShape('circle', { attrs: { x: 0, y: -30, @@ -34,8 +33,6 @@ G6.registerNode( }, name: 'circle-shape', }); - // 设置className属性 - innerCircle.set('className', INNER_CIRCLE_CLASS); return shape; }, }, @@ -99,6 +96,11 @@ const graph = new G6.Graph({ stroke: '#e2e2e2', }, }, + nodeStateStyles: { + selected: { + stroke: 'red' + } + } }); graph.data(data); @@ -107,18 +109,18 @@ graph.render(); // 节点上的点击事件 graph.on('node:click', function(event) { const { item } = event; - const shape = event.target; - - if (shape.get('className') === INNER_CIRCLE_CLASS) { - // 如果点击是发生在节点里面的小圆上,则更新对应的label - graph.updateItem(item, { - label: '点击了圆', - labelCfg: { - style: { - fill: '#003a8c', - fontSize: 16, - }, - }, - }); - } + graph.setItemState(item, 'selected', true) }); + +graph.on('circle-shape:click', evt => { + const { item } = evt + graph.updateItem(item, { + label: '点击了圆', + labelCfg: { + style: { + fill: '#003a8c', + fontSize: 16, + }, + }, + }); +}) From 960a62861b17796ca84e0075d9f1152efb4a162b Mon Sep 17 00:00:00 2001 From: Yanyan-Wang Date: Tue, 21 Jul 2020 18:52:16 +0800 Subject: [PATCH 17/77] fix: combo edge with uncorrect end points; fix: combo polyline edge with wrong path; fix: getViewCenter with padding problem. closes: #1817, #1809, #1775. --- CHANGELOG.md | 3 ++ src/graph/controller/item.ts | 32 ++++++------- src/graph/controller/view.ts | 4 +- src/item/combo.ts | 3 ++ src/item/node.ts | 13 +++++- src/shape/edges/polyline-util.ts | 50 ++++++++++++++++++--- src/shape/edges/polyline.ts | 4 ++ src/util/graphic.ts | 5 +++ stories/Combo/component/edges2.tsx | 72 ++++++++++++++++++++++++++---- 9 files changed, 152 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 62fad14d02..215ea18773 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,9 @@ #### 3.5.11 - feat: graph.priorityState api; - feat: graph.on support name:event mode. +- fix: combo edge with uncorrect end points; +- fix: combo polyline edge with wrong path; +- fix: getViewCenter with padding problem. #### 3.5.10 - fix: fitView and fitCenter with animate in the initial state; diff --git a/src/graph/controller/item.ts b/src/graph/controller/item.ts index 68ba8310b2..8bbeac127e 100644 --- a/src/graph/controller/item.ts +++ b/src/graph/controller/item.ts @@ -108,8 +108,14 @@ export default class ItemController { return; } - if ((source as Item).getType && (source as Item).getType() === 'combo') model.isComboEdge = true; - if ((target as Item).getType && (target as Item).getType() === 'combo') model.isComboEdge = true; + if ((source as Item).getType && (source as Item).getType() === 'combo') { + model.isComboEdge = true; + graph.updateCombo(source as ICombo); + } + if ((target as Item).getType && (target as Item).getType() === 'combo') { + model.isComboEdge = true; + graph.updateCombo(target as ICombo); + } item = new Edge({ model, @@ -245,7 +251,7 @@ export default class ItemController { if (type === NODE || type === COMBO) { const edges: IEdge[] = (item as INode).getEdges(); each(edges, (edge: IEdge) => { - graph.refreshItem(edge); + edge.refresh(); }); } graph.emit('afterupdateitem', { item, cfg }); @@ -276,7 +282,12 @@ export default class ItemController { x: comboBBox.x, y: comboBBox.y }); - + const combEdges = combo.getEdges() || []; + const length = combEdges.length; + for (let i = 0; i < length; i++) { + const edge = combEdges[i]; + edge && edge.refresh(); + } } /** @@ -534,18 +545,7 @@ export default class ItemController { }); const comboGroup = graph.get('comboGroup'); if (comboGroup) comboGroup.sort(); - // refresh the edges' source and target point position - setTimeout(() => { - if (graph.destroyed) return; - const edges: IEdge[] = graph.get('edges'); - const vedges: IEdge[] = graph.get('vedges'); - each(edges, (edge: IEdge) => { - edge.refresh(); - }); - each(vedges, (vedge: IEdge) => { - vedge.refresh(); - }); - }, 450); + } /** diff --git a/src/graph/controller/view.ts b/src/graph/controller/view.ts index 5b4aa3e575..f2c85d324c 100644 --- a/src/graph/controller/view.ts +++ b/src/graph/controller/view.ts @@ -27,15 +27,13 @@ export default class ViewController { const width: number = this.graph.get('width'); const height: number = graph.get('height'); return { - x: (width - padding[2] - padding[3]) / 2 + padding[3], + x: (width - padding[1] - padding[3]) / 2 + padding[3], y: (height - padding[0] - padding[2]) / 2 + padding[0], }; } public fitCenter() { const { graph } = this; - const width: number = graph.get('width'); - const height: number = graph.get('height'); const group: Group = graph.get('group'); group.resetMatrix(); const bbox = group.getCanvasBBox(); diff --git a/src/item/combo.ts b/src/item/combo.ts index 971f97d2b5..5c229e652f 100644 --- a/src/item/combo.ts +++ b/src/item/combo.ts @@ -80,6 +80,8 @@ export default class Combo extends Node implements ICombo { } else { bbox.width = bbox.maxX - bbox.minX; bbox.height = bbox.maxY - bbox.minY; + bbox.centerX = (bbox.minX + bbox.maxX) / 2; + bbox.centerY = (bbox.minY + bbox.maxY) / 2; } return bbox; } @@ -226,6 +228,7 @@ export default class Combo extends Node implements ICombo { return bbox; } + public clearCache() { this.set(CACHE_BBOX, null); // 清理缓存的 bbox this.set(CACHE_CANVAS_BBOX, null); diff --git a/src/item/node.ts b/src/item/node.ts index c4a61aee4c..8368cdca15 100644 --- a/src/item/node.ts +++ b/src/item/node.ts @@ -108,8 +108,17 @@ export default class Node extends Item implements INode { public getLinkPoint(point: IPoint): IPoint | null { const keyShape: IShapeBase = this.get('keyShape'); const type: string = keyShape.get('type'); - const bbox = this.getBBox(); - const { centerX, centerY } = bbox; + const itemType: string = this.get('type'); + let bbox, centerX, centerY; + if (itemType === 'combo') { + bbox = this.getKeyShape().getCanvasBBox(); + centerX = (bbox.maxX + bbox.minX) / 2; + centerY = (bbox.maxY + bbox.minY) / 2; + } else { + bbox = this.getBBox(); + centerX = bbox.centerX; + centerY = bbox.centerY; + } const anchorPoints = this.getAnchorPoints(); let intersectPoint: IPoint | null; switch (type) { diff --git a/src/shape/edges/polyline-util.ts b/src/shape/edges/polyline-util.ts index d5cbd05cee..cef6eece20 100644 --- a/src/shape/edges/polyline-util.ts +++ b/src/shape/edges/polyline-util.ts @@ -1,5 +1,5 @@ import { each } from '@antv/util'; -import { IShapeBase } from '../../types'; +import { INode, ICombo } from '../../interface/item'; import { Point } from '@antv/g-base/lib/types'; interface PolyPoint { @@ -203,6 +203,9 @@ export const getBBoxCrossPointsByPoint = (bbox: PBBox, point: PolyPoint): PolyPo export const distance = (p1: PolyPoint, p2: PolyPoint): number => Math.abs(p1.x - p2.x) + Math.abs(p1.y - p2.y); +/** +* 如果 points 中的一个节点 x 与 p 相等,则消耗 -2。y 同 +*/ export const _costByPoints = (p: PolyPoint, points: PolyPoint[]): number => { const offset = -2; let result = 0; @@ -218,6 +221,10 @@ export const _costByPoints = (p: PolyPoint, points: PolyPoint[]): number => { }); return result; }; + +/** +* ps 经过 p 到 pt 的距离,减去其他路过节点造成的消耗 +*/ export const heuristicCostEstimate = ( p: PolyPoint, ps: PolyPoint, @@ -238,6 +245,10 @@ export const reconstructPath = ( reconstructPath(pathPoints, pointById, cameFrom, cameFrom[currentId], iterator + 1); } }; + +/** +* 从 arr 中删去 item +*/ export const removeFrom = (arr: PolyPoint[], item: PolyPoint) => { const index = arr.indexOf(item); if (index > -1) { @@ -272,6 +283,9 @@ export const isSegmentCrossingBBox = (p1: PolyPoint, p2: PolyPoint, bbox: PBBox) isSegmentsIntersected(p1, p2, pc, pd) ); }; +/** + * 在 points 中找到满足 x 或 y 和 point 的 x 或 y 相等,且与 point 连线不经过 bbox1 与 bbox2 的点 + */ export const getNeighborPoints = ( points: PolyPoint[], point: PolyPoint, @@ -328,6 +342,7 @@ export const pathFinder = ( while (openSet.length) { let current: any; let lowestFScore = Infinity; + // 找到 openSet 中 fScore 最小的点 openSet.forEach((p: any) => { if (fScore[p.id] < lowestFScore) { lowestFScore = fScore[p.id]; @@ -335,6 +350,7 @@ export const pathFinder = ( } }); + // 若 openSet 中 fScore 最小的点就是终点 if (current === goal) { // ending condition const pathPoints: any = []; @@ -428,12 +444,34 @@ export const getPathWithBorderRadiusByPolyline = ( export const getPolylinePoints = ( start: PolyPoint, end: PolyPoint, - sNode: IShapeBase, - tNode: IShapeBase, + sNode: INode | ICombo, + tNode: INode | ICombo, offset: number, ): PolyPoint[] => { - const sBBox = sNode && sNode.getBBox() ? sNode.getBBox() : getBBoxFromPoint(start); - const tBBox = tNode && tNode.getBBox() ? tNode.getBBox() : getBBoxFromPoint(end); + let sBBox: PBBox, tBBox: PBBox; + + if (!sNode || !sNode.getType()) { + sBBox = getBBoxFromPoint(start); + } else if (sNode.getType() === 'combo') { + const sNodeKeyShape = sNode.getKeyShape(); + sBBox = sNodeKeyShape.getCanvasBBox() || getBBoxFromPoint(start) as PBBox; + sBBox.centerX = (sBBox.minX + sBBox.maxX) / 2; + sBBox.centerY = (sBBox.minY + sBBox.maxY) / 2; + } else { + sBBox = sNode.getBBox(); + } + + if (!tNode || !tNode.getType()) { + tBBox = getBBoxFromPoint(end); + } else if (tNode.getType() === 'combo') { + const tNodeKeyShape = tNode.getKeyShape(); + tBBox = tNodeKeyShape.getCanvasBBox() || getBBoxFromPoint(end) as PBBox; + tBBox.centerX = (tBBox.minX + tBBox.maxX) / 2; + tBBox.centerY = (tBBox.minY + tBBox.maxY) / 2; + } else { + tBBox = tNode && tNode.getBBox(); + } + if (isBBoxesOverlapping(sBBox, tBBox)) { // source and target nodes are overlapping return simplifyPolyline(getSimplePolyline(start, end)); @@ -478,6 +516,7 @@ export const getPolylinePoints = ( y: sPoint.y, }, ].forEach(p => { + // impossible!! if ( isPointOutsideBBox(p, sxBBox) && isPointOutsideBBox(p, txBBox) // && @@ -488,6 +527,7 @@ export const getPolylinePoints = ( }); connectPoints.unshift(sPoint); connectPoints.push(tPoint); + // filter out dulplicated points in connectPoints connectPoints = filterConnectPoints(connectPoints); // , sxBBox, txBBox, outerBBox const pathPoints = pathFinder(connectPoints, sPoint, tPoint, sBBox, tBBox, start, end); pathPoints.unshift(start); diff --git a/src/shape/edges/polyline.ts b/src/shape/edges/polyline.ts index c1ec8889a2..852db853b1 100644 --- a/src/shape/edges/polyline.ts +++ b/src/shape/edges/polyline.ts @@ -86,6 +86,7 @@ Shape.registerEdge( }, getPath(points: Point[], routeCfg?: any): Array> | string { const { source, target, offset, radius } = routeCfg as any; + // 指定了控制点 if (!offset || points.length > 2) { if (radius) { return getPathWithBorderRadiusByPolyline(points, radius); @@ -101,6 +102,8 @@ Shape.registerEdge( }); return pathArray; } + + // 未指定控制点 let polylinePoints: any; if (radius) { polylinePoints = simplifyPolyline( @@ -116,6 +119,7 @@ Shape.registerEdge( offset, ); const res = pointsToPolygon(polylinePoints); + console.log('path of polyline', points, source, target, polylinePoints, res) return res; }, }, diff --git a/src/util/graphic.ts b/src/util/graphic.ts index aa310cfe7c..7ec2edb5b1 100644 --- a/src/util/graphic.ts +++ b/src/util/graphic.ts @@ -652,6 +652,8 @@ export const getComboBBox = (children: ComboTree[], graph: IGraph): BBox => { y: undefined, width: undefined, height: undefined, + centerX: undefined, + centerY: undefined }; if (!children || children.length === 0) { @@ -673,6 +675,9 @@ export const getComboBBox = (children: ComboTree[], graph: IGraph): BBox => { comboBBox.width = comboBBox.maxX - comboBBox.minX; comboBBox.height = comboBBox.maxY - comboBBox.minY; + comboBBox.centerX = (comboBBox.minX + comboBBox.maxX) / 2; + comboBBox.centerY = (comboBBox.minY + comboBBox.maxY) / 2; + Object.keys(comboBBox).forEach(key => { if (comboBBox[key] === Infinity || comboBBox[key] === -Infinity) { comboBBox[key] = undefined; diff --git a/stories/Combo/component/edges2.tsx b/stories/Combo/component/edges2.tsx index 3b5acaf8f6..dff330cc25 100644 --- a/stories/Combo/component/edges2.tsx +++ b/stories/Combo/component/edges2.tsx @@ -20,7 +20,52 @@ const data: GraphData = { id: '4', comboId: '分组2', x: 400, - y: 100 + y: 150 + } + ], + edges: [ + { + source: '分组1', + target: '分组2', + } + ], + combos: [ + { + id: '分组1', + label: '分组1', + // collapsed: true + }, + { + id: '分组2', + label: '分组2', + //collapsed: true, + }, + { + id: '分组3', + label: '分组3', + x: 100, + y: 300 + //collapsed: true, + } + ] +}; + +const data2: GraphData = { + nodes: [{ + id: '1', + comboId: '分组1', + x: 100, + y: 100 + }, { + id: '2', + comboId: '分组2', + x: 200, + y: 100 + }, { + id: '4', + comboId: '分组2', + x: 400, + y: 150 } ], edges: [ @@ -33,7 +78,6 @@ const data: GraphData = { { id: '分组1', label: '分组1', - //collapsed: true }, { id: '分组2', @@ -42,7 +86,6 @@ const data: GraphData = { } ] }; - const Edges2 = () => { const container = React.useRef(); useEffect(() => { @@ -53,6 +96,7 @@ const Edges2 = () => { height: 800, groupByTypes: false, defaultEdge: { + type: 'polyline', style: { endArrow: true } @@ -61,13 +105,15 @@ const Edges2 = () => { type: 'rect', size: [50, 60], // The minimum size of the Combo padding: [20, 10, 10, 20], + // padding: [0, 0, 0, 0], style: { lineWidth: 3, + opacity: 0.1 }, - anchorPoints: [ - [0.5, 1], - [0.5, 0], - ], + // anchorPoints: [ + // [0.5, 1], + // [0.5, 0], + // ], labelCfg: { refY: 10, refX: 20, @@ -78,7 +124,7 @@ const Edges2 = () => { default: ['drag-canvas', 'drag-node', { type: 'drag-combo', - enableDelegate: true //拖动时禁止合并 + // enableDelegate: true //拖动时禁止合并 } ], }, @@ -89,6 +135,16 @@ const Edges2 = () => { graph.collapseExpandCombo(e.item); graph.refreshPositions(); }); + + graph.on('canvas:click', e => { + // graph.addItem('edge', { + // source: '分组1', + // target: '分组2', + // }); + // graph.updateCombos(); + // graph.changeData(data2); + graph.getEdges()[0].refresh() + }); } }); return
; From 212bfe0ec2f193a09160adc6f89aee9b3f564717 Mon Sep 17 00:00:00 2001 From: Yanyan-Wang Date: Tue, 21 Jul 2020 18:55:02 +0800 Subject: [PATCH 18/77] fix: update changelog --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 215ea18773..508c00ba07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,8 @@ - feat: graph.on support name:event mode. - fix: combo edge with uncorrect end points; - fix: combo polyline edge with wrong path; -- fix: getViewCenter with padding problem. +- fix: getViewCenter with padding problem; +- feat: allow user to configure the initial positions for empty combos. #### 3.5.10 - fix: fitView and fitCenter with animate in the initial state; From c191bbc22223157f64ece6dcd2db314c2b5c37a7 Mon Sep 17 00:00:00 2001 From: Yanyan-Wang Date: Tue, 21 Jul 2020 19:09:42 +0800 Subject: [PATCH 19/77] fix: Cannot read property getModel of null problem on contextmenu when the target is not an item. closes: #1815. --- CHANGELOG.md | 3 ++- src/plugins/menu/index.ts | 22 +++++++++++----------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 508c00ba07..564ede48f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,8 @@ - fix: combo edge with uncorrect end points; - fix: combo polyline edge with wrong path; - fix: getViewCenter with padding problem; -- feat: allow user to configure the initial positions for empty combos. +- feat: allow user to configure the initial positions for empty combos; +- fix: Cannot read property 'getModel' of null problem on contextmenu when the target is not an item. #### 3.5.10 - fix: fitView and fitCenter with animate in the initial state; diff --git a/src/plugins/menu/index.ts b/src/plugins/menu/index.ts index bda0816de7..d082682c8e 100644 --- a/src/plugins/menu/index.ts +++ b/src/plugins/menu/index.ts @@ -46,7 +46,7 @@ export default class Menu extends Base {
  • 菜单项2
  • ` - }, + }, // 菜单隐藏事件 onHide() { return true; @@ -71,7 +71,7 @@ export default class Menu extends Base { protected onMenuShow(e: IG6GraphEvent) { const self = this; - + const container = this.get('menu') const getContent = this.get('getContent') let menu = getContent(e) @@ -94,26 +94,26 @@ export default class Menu extends Base { const bbox = container.getBoundingClientRect(); - let x = e.item.getModel().x; - let y = e.item.getModel().y; + let x = e.item.getModel().x || e.x; + let y = e.item.getModel().y || e.y; // 若菜单超出画布范围,反向 if (x + bbox.width > width) { x = width - bbox.width; } - + if (y + bbox.height > height) { y = height - bbox.height; } - + const point = graph.getClientByPoint(x, y) e.canvasX = point.x; e.canvasY = point.y; modifyCSS(container, { - top: `${point.y}px`, - left: `${point.x}px`, - visibility: 'visible' + top: `${point.y}px`, + left: `${point.x}px`, + visibility: 'visible' }); const handler = (evt) => { @@ -133,7 +133,7 @@ export default class Menu extends Base { // 隐藏菜单后需要移除事件监听 document.body.removeEventListener('click', this.get('handler')); - + const handleMenuClick = this.get('handleMenuClick') if (handleMenuClick) { container.removeEventListener('click', handleMenuClick) @@ -148,7 +148,7 @@ export default class Menu extends Base { if (handleMenuClick) { menu.removeEventListener('click', handleMenuClick) } - + if (menu) { document.body.removeChild(menu); } From 309f9b217a49c8253be7ea25f08a76e0524674ec Mon Sep 17 00:00:00 2001 From: Yanyan-Wang Date: Tue, 21 Jul 2020 19:48:56 +0800 Subject: [PATCH 20/77] docs: update changelog --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 564ede48f6..5c85390855 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,8 @@ - fix: combo polyline edge with wrong path; - fix: getViewCenter with padding problem; - feat: allow user to configure the initial positions for empty combos; -- fix: Cannot read property 'getModel' of null problem on contextmenu when the target is not an item. +- fix: cannot read property 'getModel' of null problem on contextmenu when the target is not an item; +- feat: optimize by hiding edges and shapes which are not keyShape while dragging canvas. #### 3.5.10 - fix: fitView and fitCenter with animate in the initial state; From a9ec023aa924b866ebd1b162aa8525f25d651dae Mon Sep 17 00:00:00 2001 From: Yanyan-Wang Date: Wed, 22 Jul 2020 12:07:36 +0800 Subject: [PATCH 21/77] feat: fix the initial positions by equably distributing for layout to produce similar result. --- CHANGELOG.md | 5 +++-- gatsby-browser.js | 4 ++-- src/graph/controller/item.ts | 3 --- src/graph/controller/layout.ts | 16 +++++++++++++--- src/shape/edges/polyline.ts | 1 - 5 files changed, 18 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c85390855..967759bdc1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,9 +6,10 @@ - fix: combo edge with uncorrect end points; - fix: combo polyline edge with wrong path; - fix: getViewCenter with padding problem; -- feat: allow user to configure the initial positions for empty combos; - fix: cannot read property 'getModel' of null problem on contextmenu when the target is not an item; -- feat: optimize by hiding edges and shapes which are not keyShape while dragging canvas. +- feat: allow user to configure the initial positions for empty combos; +- feat: optimize by hiding edges and shapes which are not keyShape while dragging canvas; +- feat: fix the initial positions by equably distributing for layout to produce similar result. #### 3.5.10 - fix: fitView and fitCenter with animate in the initial state; diff --git a/gatsby-browser.js b/gatsby-browser.js index d1b4d76a74..98906e728f 100644 --- a/gatsby-browser.js +++ b/gatsby-browser.js @@ -1,3 +1,3 @@ -window.g6 = require('./src/index.ts'); // import the source for debugging -// window.g6 = require('./dist/g6.min.js'); // import the package for webworker +// window.g6 = require('./src/index.ts'); // import the source for debugging +window.g6 = require('./dist/g6.min.js'); // import the package for webworker window.insertCss = require('insert-css'); diff --git a/src/graph/controller/item.ts b/src/graph/controller/item.ts index 8bbeac127e..a454eac1a6 100644 --- a/src/graph/controller/item.ts +++ b/src/graph/controller/item.ts @@ -126,9 +126,6 @@ export default class ItemController { group: parent.addGroup(), }); } else if (type === NODE) { - model.x = model.x || 0; - model.y = model.y || 0; - item = new Node({ model, styles, diff --git a/src/graph/controller/layout.ts b/src/graph/controller/layout.ts index 332a6b7a3c..26f2d9221d 100644 --- a/src/graph/controller/layout.ts +++ b/src/graph/controller/layout.ts @@ -4,6 +4,7 @@ import { LAYOUT_MESSAGE } from '../../layout/worker/layoutConst'; import { isNaN } from '../../util/base'; import { IGraph } from '../../interface/graph'; +import { path2Absolute } from '@antv/path-util'; const helper = { // pollyfill @@ -463,14 +464,23 @@ export default class LayoutController { return false; } let allHavePos = true; - nodes.forEach(node => { + const width = graph.get('width') * 0.85; + const height = graph.get('height') * 0.85; + const nodeNum = nodes.length; + const horiNum = Math.sqrt(width * nodeNum / height); + const vertiNum = horiNum * height / width; + const horiGap = width / (horiNum - 1); + const vertiGap = height / (vertiNum - 1); + const beginX = center[0] - width / 2; + const beginY = center[1] - height / 2; + nodes.forEach((node, i) => { if (isNaN(node.x)) { allHavePos = false; - node.x = (Math.random() - 0.5) * 0.7 * graph.get('width') + center[0]; + node.x = i % horiNum * horiGap + beginX; } if (isNaN(node.y)) { allHavePos = false; - node.y = (Math.random() - 0.5) * 0.7 * graph.get('height') + center[1]; + node.y = i / horiNum * vertiGap + beginY; } }); return allHavePos; diff --git a/src/shape/edges/polyline.ts b/src/shape/edges/polyline.ts index 852db853b1..0557437ae0 100644 --- a/src/shape/edges/polyline.ts +++ b/src/shape/edges/polyline.ts @@ -119,7 +119,6 @@ Shape.registerEdge( offset, ); const res = pointsToPolygon(polylinePoints); - console.log('path of polyline', points, source, target, polylinePoints, res) return res; }, }, From 7e16737cde7cc77d1c3536861aa57b2888d52c91 Mon Sep 17 00:00:00 2001 From: Yanyan-Wang Date: Wed, 22 Jul 2020 18:14:37 +0800 Subject: [PATCH 22/77] docs: update the english version of comboTheory docs. --- .../middle/elements/combos/comboTheory.en.md | 70 ++++++++++--------- .../middle/elements/combos/comboTheory.zh.md | 13 ++-- 2 files changed, 43 insertions(+), 40 deletions(-) diff --git a/docs/manual/middle/elements/combos/comboTheory.en.md b/docs/manual/middle/elements/combos/comboTheory.en.md index ac805ca076..d2692c4f28 100644 --- a/docs/manual/middle/elements/combos/comboTheory.en.md +++ b/docs/manual/middle/elements/combos/comboTheory.en.md @@ -1,29 +1,31 @@ --- -title: Combo Theory +title: combo Theory order: 3 --- > The English version is in progress -## Combo 渲染视觉层级逻辑 +## The Rendering Logic of combo -当图中只有 Node 和 Edge 而不存在 Combo 时,所有 Edge 的「视觉层级 zIndex」默认低于所有的 Node。但增加嵌套的 Combo 后,元素间的视觉层级需要较复杂的规则定义,方能符合合理的逻辑。为了方便说明,我们用 z(X) 表示 X 元素的视觉层级值。 -- 规则一:单层 Combo 中各元素层级关系是 z(Node) > z(Edge) > z(Combo),如下所示: +When there are no combos but nodes and edges, the visual index (zIndex) of edges are lower than nodes by default. For a graph with combos, rules about visual index should be specified to achieve reasonable result. For convenience, z(X) indicates the visual index(zIndex) in the following. + +- Rule 1: For one unnested combo, z(Node) > z(Edge) > z(combo), as shown below img -> z(a) = z(b) > z(e0) > z(Combo A) +> z(a) = z(b) > z(e0) > z(combo A) -- 规则一补充:假设 Combo A 内部的子元素包括子 Combo 及节点,且节点间存在边,则:z(子 Combo) > z(Node) > z(Edge) > z(Combo A 本身),示例如下: +- Rule 1+: Suppose that combo A has sub combos and nodes, and there are edges between the nodes, z(sub combo) > z(Node) > z(Edge) > z(combo A it self), as shown below: img -> z(b1) = z(b2) > z(e2) > z(Combo B) > z(a1) = z(a2) > z(e1) > z(Combo A) +> z(b1) = z(b2) > z(e2) > z(combo B) > z(a1) = z(a2) > z(e1) > z(combo A) -- 规则二:通过规则一,可以得到所有 Combo 及 Node 的视觉层级值。若存在某条边 E 的两个端点 a 与 b 来自不同的 Combo,已知 z(a) 与 z(b),则 z(E) 为 max(z(a), z(b)) 所在 Combo 内边的层级,即: - - 当 z(a) > z(b) 时,z(E) 等于 a 所在 Combo 内边的层级; - - 当 z(a) <= z(b),z(E) 等于 b 所在 Combo 内边的层级。 -以下图为例,图中红色标注的边属于上述情况: +- Rule 2: We now abtain all the visual indexes of combos and nodes by rule 1. If there is an edge E with end nodes a and b from different combos, and we already know z(a) and z(b), the z(E) will be equal to the visual index of the edges in the combo which contains the end node with larger z(x). That is: + - When z(a) > z(b), z(E) is equal to the visual index of the edges in the combo which contains a; + - When z(a) <= z(b), z(E) is equal to the visual index of the edges in the combo which contains b. + +As shown in the figure below, The edges with red label matches Rule 2: img @@ -32,38 +34,38 @@ order: 3 > z(e6) = z(e1)=z(e3) -- 规则二补充:在上图的基础上,Combo B 收起后,如下左图;Combo A 收起后,如下右图。可以发现,在收缩一个 Combo 后,隐藏了与该 Combo 相关的节点及边,而增加了虚拟边来表示有外部元素连接到该 Combo 内的元素。 +- Rule 2+: The combo B of upper figure is collapsed as following figure. The related nodes and edges are hidden, and some vitual edges are added to represent the relationships between items inside and outside combo B. img img -## Combo 布局原理 -Combo 使用带有不重叠约束的力导型布局方法,Combo 布局分为以下三种情况: -1. 布局最细粒度所有元素; -2. 交互展开一个 Combo; -3. 交互收起一个 Combo。 +## combo Layout Theory +G6 provides a force-directed based layout for combo named 'comboForce'. There are three situations to be considered: +1. Layout all the items; +2. Expand a combo interactively; +3. Collapse a combo interactively. -力导向布局的原则:所有点对之间存在斥力 `Fr = k/r2`,边连接的点对之间存在引力 `Fa = ks * r`,其中 `r` 为两个节点之间的距离,`k` 与 `ks` 为系数。 +The principle of traditional force-directed layout: There are repulsive forces between all the node pairs as `Fr = k/r2`; There are attractive forces between the node pairs which have connections(edges) as `Fa = ks * r`. Where `r` is the distance between two nodes, `k` and `ks` are coefficient. To meet the requirement of combo layout, we add some additional strategies to make the nodes inside a combo more compact and avoid the combo overlappings. -#### 为边上的引力 Fa 添加系数 m = f(c) -- 「跨组边」—— 边两端节点来自不同的 Combos,减弱其引力大小,即 `m = f(c) < 1`。图中所有标出的边都为跨组边。跨越层数越多,减弱程度越高。如 `e46`、`e23`、`e12`、`e15` 跨越了一层,`e34`、`e13` 跨越了两层。因此 `f(c)` 是关于跨越层数(c)的函数,可以是 `m = 1/c` 等; -- 同组边」—— 边两端节点来自相同 Combo,则引力定义方式不变,即 `m = f(c) = 1`。 +#### Define a coefficient m = f(c) for the attractive force Fa on the edge +- 「Inter Edge」means an edge with two end nodes from different combos. All the edges in the below figure are inter edges. The attractive forces on them should be reduce the avoid this two combos overlapped. So the coefficient is `m = f(c) < 1`. Higher difference of the combos' depths, `m` should be smaller. E.g. the differences of `e46`, `e23`, `e12`, and `e15` are 1, and `e34`、`e13` are 2. So `f(c)` is a function about difference of the depths bewteen two end nodes' combos, e.g. `m = 1/c`; +- 「Intra Edge」means an edge with two end nodes form the same combo,the coefficient is `m = f(c) = 1`. -#### 增加 Combo 的中心力 -- 为方便描述,我们为 Combo X 定义层级的高低值 `P(X)`。如下图所示,A、B、C、D 四个组的层级高低:`P(A) > P(B) > P(C) > P(D)`; -- 每个 Combo 中有由分组内节点当前的平均位置中心发出的重力,该平均中心根据每次迭代的节点位置进行更新; -- `P(X)` 越小,其发出的重力 `G(X)` 越大。例如 `G(X) = 1/P(X)`; -- 有些节点可能受到多个重力。例如下图 6 号节点,受到了它上层红色 Combo C 的重力 `G(C)`,绿色 Combo B 的重力 G(B),黄色 Combo A 的重力 `G(A)`。`G(C) > G(B) > G(A)`。 +#### Gravity for combo +- For convenience, we say `P(X)` is the hierarchy depth of combo X. As shown in the figure below, `P(A) > P(B) > P(C) > P(D)`; +- Each combo has a gravity force G(X) for its succeeding nodes from their mean center. The mean center will be updated in each iteration; +- Smaller `P(X)`, larger `G(X)`. e.g. `G(X) = 1/P(X)`; +- Some nodes might be affected by multiple gravity forces. Such as the node #6 in the figure below, it is affected by the gravity forces `G(C)` from combo C with red stroke, `G(B)` from combo B with green stroke, and `G(A)` from combo A with yellow stroke, where `G(C) > G(B) > G(A)`. -#### 迭代中的重叠检测 -- 每次迭代检测节点之间是否存在重叠: -- 若两个节点之间存在重叠,则为二者间的斥力乘以一个放大系数 `R`,使之斥开。 -- 每次迭代(或每 `q` 次迭代)检测 Combo 之间是否存在重叠: -- 首先计算最小能够包围该组内元素的圆形或矩形(根据 Combo Type 决定); -- 计算至上而下遍历,检测每个 Combo 内层级相同的子 Combos 是否存在重叠; -- 若存在重叠则加大该 Combo 的重力。 +#### Overlapping detection +- Detect the overlappings between nodes in each iteration, and: + - If two node overlapped, magnify a coefficient `R` to the repulsive force between them to take them apart. +- Detect the overlappings between combos in each iteration (or each `q` iteraction in reduce the computation): + - First of all, compute the bounding box of the children (including nodes and sub combos); + - Then traverse the combo tree from top to bottom to find the overlapped combo pairs with same depth in a parent combo; + - Increase the gravity of the parent combo if two sub combos are overlapped. img -> 相同颜色的 border 代表了相同的层级,该图层级由高到低分别是:A > B > C > D +> The combos with same hierarchy depths are in the same color. The hierarchy depths on this graph is: A > B > C > D diff --git a/docs/manual/middle/elements/combos/comboTheory.zh.md b/docs/manual/middle/elements/combos/comboTheory.zh.md index c2d671ca1c..ffef8c30ef 100644 --- a/docs/manual/middle/elements/combos/comboTheory.zh.md +++ b/docs/manual/middle/elements/combos/comboTheory.zh.md @@ -21,6 +21,7 @@ order: 3 - 规则二:通过规则一,可以得到所有 Combo 及 Node 的视觉层级值。若存在某条边 E 的两个端点 a 与 b 来自不同的 Combo,已知 z(a) 与 z(b),则 z(E) 为 max(z(a), z(b)) 所在 Combo 内边的层级,即: - 当 z(a) > z(b) 时,z(E) 等于 a 所在 Combo 内边的层级; - 当 z(a) <= z(b),z(E) 等于 b 所在 Combo 内边的层级。 + 以下图为例,图中红色标注的边属于上述情况: img @@ -46,21 +47,21 @@ Combo 使用带有不重叠约束的力导型布局方法,Combo 布局分为 #### 为边上的引力 Fa 添加系数 m = f(c) - 「跨组边」—— 边两端节点来自不同的 Combos,减弱其引力大小,即 `m = f(c) < 1`。图中所有标出的边都为跨组边。跨越层数越多,减弱程度越高。如 `e46`、`e23`、`e12`、`e15` 跨越了一层,`e34`、`e13` 跨越了两层。因此 `f(c)` 是关于跨越层数(c)的函数,可以是 `m = 1/c` 等; -- 同组边」—— 边两端节点来自相同 Combo,则引力定义方式不变,即 `m = f(c) = 1`。 +- 「同组边」—— 边两端节点来自相同 Combo,则引力定义方式不变,即 `m = f(c) = 1`。 #### 增加 Combo 的中心力 - 为方便描述,我们为 Combo X 定义层级的高低值 `P(X)`。如下图所示,A、B、C、D 四个组的层级高低:`P(A) > P(B) > P(C) > P(D)`; - 每个 Combo 中有由分组内节点当前的平均位置中心发出的重力,该平均中心根据每次迭代的节点位置进行更新; - `P(X)` 越小,其发出的重力 `G(X)` 越大。例如 `G(X) = 1/P(X)`; -- 有些节点可能受到多个重力。例如下图 6 号节点,受到了它上层红色 Combo C 的重力 `G(C)`,绿色 Combo B 的重力 G(B),黄色 Combo A 的重力 `G(A)`。`G(C) > G(B) > G(A)`。 +- 有些节点可能受到多个重力。例如下图 6 号节点,受到了它上层红色 Combo C 的重力 `G(C)`,绿色 Combo B 的重力 `G(B)`,黄色 Combo A 的重力 `G(A)`。`G(C) > G(B) > G(A)`。 #### 迭代中的重叠检测 - 每次迭代检测节点之间是否存在重叠: -- 若两个节点之间存在重叠,则为二者间的斥力乘以一个放大系数 `R`,使之斥开。 + - 若两个节点之间存在重叠,则为二者间的斥力乘以一个放大系数 `R`,使之斥开。 - 每次迭代(或每 `q` 次迭代)检测 Combo 之间是否存在重叠: -- 首先计算最小能够包围该组内元素的圆形或矩形(根据 Combo Type 决定); -- 计算至上而下遍历,检测每个 Combo 内层级相同的子 Combos 是否存在重叠; -- 若存在重叠则加大该 Combo 的重力。 + - 首先计算最小能够包围该组内元素的圆形或矩形(根据 Combo Type 决定); + - 计算至上而下遍历,检测每个 Combo 内层级相同的子 Combos 是否存在重叠; + - 若存在重叠则加大该 Combo 的重力。 img From 936c461b933fcd19de049ff44b5504c86bfcc90e Mon Sep 17 00:00:00 2001 From: baizn <576375879@qq.com> Date: Wed, 15 Jul 2020 14:47:48 +0800 Subject: [PATCH 23/77] feat: slider timebar component --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8b213267df..7f0754b22c 100644 --- a/package.json +++ b/package.json @@ -127,4 +127,4 @@ "webpack-cli": "^3.3.10", "worker-loader": "^2.0.0" } -} \ No newline at end of file +} From 21955e019bb9390d250143d47cbe64d195862835 Mon Sep 17 00:00:00 2001 From: baizn <576375879@qq.com> Date: Fri, 24 Jul 2020 16:12:46 +0800 Subject: [PATCH 24/77] feat: add plugins docs & demo --- docs/api/Plugins.zh.md | 118 +++++++++++++++ examples/tool/contextMenu/demo/contextMenu.js | 29 ++-- examples/tool/timebar/demo/configTimebar.js | 97 +++++++++++++ examples/tool/timebar/demo/meta.json | 24 ++++ examples/tool/timebar/demo/timebar.js | 82 +++++++++++ examples/tool/timebar/index.zh.md | 11 ++ examples/tool/toolbar/demo/meta.json | 24 ++++ examples/tool/toolbar/demo/self-toolbar.js | 136 ++++++++++++++++++ examples/tool/toolbar/demo/toolbar.js | 100 +++++++++++++ examples/tool/toolbar/index.en.md | 16 +++ examples/tool/toolbar/index.zh.md | 16 +++ src/plugins/menu/index.ts | 55 ++++--- src/plugins/timeBar/index.ts | 27 ++-- src/plugins/tooltip/index.ts | 12 +- tests/unit/plugins/menu-spec.ts | 2 +- tests/unit/plugins/timebar-spec.ts | 103 +++++++++++-- tests/unit/plugins/tooltip-spec.ts | 7 +- 17 files changed, 806 insertions(+), 53 deletions(-) create mode 100644 examples/tool/timebar/demo/configTimebar.js create mode 100644 examples/tool/timebar/demo/meta.json create mode 100644 examples/tool/timebar/demo/timebar.js create mode 100644 examples/tool/timebar/index.zh.md create mode 100644 examples/tool/toolbar/demo/meta.json create mode 100644 examples/tool/toolbar/demo/self-toolbar.js create mode 100644 examples/tool/toolbar/demo/toolbar.js create mode 100644 examples/tool/toolbar/index.en.md create mode 100644 examples/tool/toolbar/index.zh.md diff --git a/docs/api/Plugins.zh.md b/docs/api/Plugins.zh.md index d237a51685..405b01b2ad 100644 --- a/docs/api/Plugins.zh.md +++ b/docs/api/Plugins.zh.md @@ -266,6 +266,124 @@ const graph = new G6.Graph({ }); ``` +## TimeBar + +目前 G6 内置的 TimeBar 主要有以下功能: +- 改变时间范围,过滤图上的数据; +- TimeBar 上展示指定字段随时间推移的变化趋势。 + +img + +**说明:** 目前的 TimeBar 功能还比较简单,不能用于较为复杂的时序分析。 + +### 配置项 + +| 名称 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| container | HTMLDivElement | null | TimeBar 容器,如果不设置,则默认创建 className 为 g6-component-timebar 的 DOM 容器 | +| width | number | 400 | TimeBar 容器宽度 | +| height | number | 400 | TimeBar 容器高度 | +| timebar | TimeBarOption | {} | TimeBar 样式配置项 | +| rangeChange | (graph: IGraph, min: number, max: number) => void | null | 改变时间范围后的回调函数 | + + +**TimeBarOption 配置项** + +``` +interface HandleStyle { + width: number; + height: number; + style: ShapeStyle; +} +``` + +| 名称 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| x | number | 0 | TimeBar 起始 x 坐标 | +| y | number | 0 | TimeBar 起始 y 坐标 | +| width | number | 400 | TimeBar 宽度 | +| height | number | 400 | TimeBar 高度 | +| backgroundStyle | ShapeStyle | {} | TimeBar 背景样式配置项 | +| foregroundStyle | ShapeStyle | {} | TimeBar 选中部分样式配置项 | +| handlerStyle | HandleStyle | null | 滑块样式设置 | +| textStyle | ShapeStyle | null | 文本样式 | +| minLimit | number | 0 | 允许滑块最左边(最小)位置,范围 0-1 | +| maxLimit | number | 1 | 允许滑块最右边(最大)位置,范围 0-1 | +| start | number | 0 | 滑块初始开始位置 | +| end | number | 1 | 滑块初始结束位置 | +| minText | string | null | 滑块最小值时显示的文本 | +| maxText | string | null | 滑块最大值时显示的文本 | +| trend | TrendConfig | null | 滑块上趋势图配置 | + +**TrendConfig 配置项** + +``` +interface Data { + date: string; + value: number; +} +``` + +| 名称 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| data | Data[] | [] | 滑块上的数据源 | +| smooth | boolean | false | 是否是平滑的曲线 | +| isArea | boolean | false | 是否显示面积图 | +| lineStyle | ShapeStyle | null | 折线的样式 | +| areaStyle | ShapeStyle | null | 面积的样式,只有当 isArea 为 true 时生效 | + +### 用法 + +#### 默认用法 +G6 内置的默认的 TimeBar 有默认的样式及交互功能。 + +``` +const timebar = new G6.TimeBar(); + +const graph = new G6.Graph({ + //... 其他配置项 + plugins: [timebar], // 配置 timebar 插件 +}); +``` + +##### 配置样式 +可以个性化定制 TimeBar 的样式,也可以自己定义时间范围改变后的处理方式。 + +``` +const timebar = new G6.TimeBar({ + width: 600, + timebar: { + width: 600, + backgroundStyle: { + fill: '#08979c', + opacity: 0.3 + }, + foregroundStyle: { + fill: '#40a9ff', + opacity: 0.4 + }, + trend: { + data: timeBarData, + isArea: false, + smooth: true, + lineStyle: { + stroke: '#9254de' + } + } + }, + rangeChange: (graph, min, max) => { + // 拿到 Graph 实例和 timebar 上范围,自己可以控制图上的渲染逻辑 + console.log(graph, min, max) + } +}); + +const graph = new G6.Graph({ + //... 其他配置项 + plugins: [timebar], // 配置 timebar 插件 +}); +``` + + ## ToolTip ToolTip 插件主要用于在节点和边上展示一些辅助信息,G6 4.0 以后,Tooltip 插件将会替换 Behavior 中的 tooltip。 diff --git a/examples/tool/contextMenu/demo/contextMenu.js b/examples/tool/contextMenu/demo/contextMenu.js index 63938445cb..a0a8508d1f 100644 --- a/examples/tool/contextMenu/demo/contextMenu.js +++ b/examples/tool/contextMenu/demo/contextMenu.js @@ -47,11 +47,30 @@ document.getElementById('container').appendChild(conextMenuContainer); const width = document.getElementById('container').scrollWidth; const height = document.getElementById('container').scrollHeight || 500; + +const contextMenu = new G6.Menu({ + getContent(graph) { + console.log('graph',graph) + return `
      +
    • 测试01
    • +
    • 测试02
    • +
    • 测试03
    • +
    • 测试04
    • +
    • 测试05
    • +
    `; + }, + handleMenuClick: (target, item) => { + console.log(target, item) + } +}); + const graph = new G6.Graph({ + // 使用 contextMenu plugins 时,需要将 container 设置为 position: relative; container: 'container', width, height, linkCenter: true, + plugins: [contextMenu], defaultNode: { size: [80, 40], type: 'rect', @@ -120,13 +139,3 @@ const data = { graph.data(data); graph.render(); -graph.on('node:contextmenu', evt => { - evt.preventDefault(); - evt.stopPropagation(); - conextMenuContainer.style.left = `${evt.canvasX + 20}px`; - conextMenuContainer.style.top = `${evt.canvasY}px`; -}); - -graph.on('node:mouseleave', () => { - conextMenuContainer.style.left = '-150px'; -}); diff --git a/examples/tool/timebar/demo/configTimebar.js b/examples/tool/timebar/demo/configTimebar.js new file mode 100644 index 0000000000..cdf1d487e6 --- /dev/null +++ b/examples/tool/timebar/demo/configTimebar.js @@ -0,0 +1,97 @@ +import G6 from '@antv/g6'; +import insertCss from 'insert-css'; + +insertCss(` + #g6-component-timebar { + top: 540px; + left: 10px; + } +`); + +const data = { + nodes: [], + edges: [], +}; + +for(let i = 0; i < 100; i++) { + const id = `node-${i}` + data.nodes.push({ + id, + label: `node${i}`, + date: `2020${i}`, + value: Math.round(Math.random() * 300) + }) + + data.edges.push({ + source: `node-${Math.round(Math.random() * 90)}`, + target: `node-${Math.round(Math.random() * 90)}` + }) +} + +const width = document.getElementById('container').scrollWidth; +const height = document.getElementById('container').scrollHeight || 500; + +const timeBarData = [] + +for(let i = 0; i < 100; i++) { + timeBarData.push({ + date: `2020${i}`, + value: Math.round(Math.random() * 300) + }) +} + +const timebar = new G6.TimeBar({ + width: 600, + timebar: { + width: 600, + backgroundStyle: { + fill: '#08979c', + opacity: 0.3 + }, + foregroundStyle: { + fill: '#40a9ff', + opacity: 0.4 + }, + trend: { + data: timeBarData, + isArea: false, + smooth: true, + lineStyle: { + stroke: '#9254de' + } + } + }, + rangeChange: (graph, min, max) => { + // 拿到 Graph 实例和 timebar 上范围,自己可以控制图上的渲染逻辑 + console.log(graph, min, max) + } +}); + +const graph = new G6.Graph({ + container: 'container', + width, + height, + linkCenter: true, + plugins: [timebar], + defaultNode: { + size: 40, + type: 'circle', + style: { + fill: '#DEE9FF', + stroke: '#5B8FF9', + }, + }, + defaultEdge: { + style: { + stroke: '#b5b5b5', + lineAppendWidth: 3, + }, + }, + modes: { + default: [ + 'drag-node' + ], + }, +}); +graph.data(data); +graph.render(); diff --git a/examples/tool/timebar/demo/meta.json b/examples/tool/timebar/demo/meta.json new file mode 100644 index 0000000000..10c0fa1e36 --- /dev/null +++ b/examples/tool/timebar/demo/meta.json @@ -0,0 +1,24 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "timebar.js", + "title": { + "zh": "时间轴", + "en": "ToolBar" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*xj85R7loNvMAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "configTimebar.js", + "title": { + "zh": "定义时间轴样式", + "en": "ToolBar" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*AG3AQ7PyHSMAAAAAAAAAAABkARQnAQ" + } + ] +} \ No newline at end of file diff --git a/examples/tool/timebar/demo/timebar.js b/examples/tool/timebar/demo/timebar.js new file mode 100644 index 0000000000..5124c38243 --- /dev/null +++ b/examples/tool/timebar/demo/timebar.js @@ -0,0 +1,82 @@ +import G6 from '@antv/g6'; +import insertCss from 'insert-css'; + +insertCss(` + #g6-component-timebar { + top: 540px; + left: 10px; + } +`); + +const data = { + nodes: [], + edges: [], +}; + +for(let i = 0; i < 100; i++) { + const id = `node-${i}` + data.nodes.push({ + id, + label: `node${i}`, + date: `2020${i}`, + value: Math.round(Math.random() * 300) + }) + + data.edges.push({ + source: `node-${Math.round(Math.random() * 90)}`, + target: `node-${Math.round(Math.random() * 90)}` + }) +} + +const width = document.getElementById('container').scrollWidth; +const height = document.getElementById('container').scrollHeight || 500; + +const timeBarData = [] + +for(let i = 0; i < 100; i++) { + timeBarData.push({ + date: `2020${i}`, + value: Math.round(Math.random() * 300) + }) +} + +const timebar = new G6.TimeBar({ + width: 600, + timebar: { + width: 600, + trend: { + data: timeBarData, + isArea: false, + smooth: true, + } + } +}); + +const graph = new G6.Graph({ + container: 'container', + width, + height, + linkCenter: true, + plugins: [timebar], + defaultNode: { + size: 40, + type: 'circle', + style: { + fill: '#DEE9FF', + stroke: '#5B8FF9', + }, + }, + defaultEdge: { + style: { + stroke: '#b5b5b5', + lineAppendWidth: 3, + }, + }, + modes: { + default: [ + 'drag-node' + ], + }, +}); +graph.data(data); +graph.render(); diff --git a/examples/tool/timebar/index.zh.md b/examples/tool/timebar/index.zh.md new file mode 100644 index 0000000000..3fc4eb0bd5 --- /dev/null +++ b/examples/tool/timebar/index.zh.md @@ -0,0 +1,11 @@ +--- +title: 时间轴 +order: 0 +--- + +G6 中内置的 TimeBar 组件。 + +## 使用指南 + +下面的代码演示展示了如何在图上使用 TimeBar。TimeBar 的样式可以使用 G6 内置的,也可以完全自定义。 + diff --git a/examples/tool/toolbar/demo/meta.json b/examples/tool/toolbar/demo/meta.json new file mode 100644 index 0000000000..83137b8327 --- /dev/null +++ b/examples/tool/toolbar/demo/meta.json @@ -0,0 +1,24 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "toolbar.js", + "title": { + "zh": "工具栏", + "en": "ToolBar" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*MhpmS68lZW0AAAAAAAAAAABkARQnAQ" + }, + { + "filename": "self-toolbar.js", + "title": { + "zh": "自定义工具栏", + "en": "ToolBar" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*ljOiTJAuQnIAAAAAAAAAAABkARQnAQ" + } + ] +} \ No newline at end of file diff --git a/examples/tool/toolbar/demo/self-toolbar.js b/examples/tool/toolbar/demo/self-toolbar.js new file mode 100644 index 0000000000..2c288827a6 --- /dev/null +++ b/examples/tool/toolbar/demo/self-toolbar.js @@ -0,0 +1,136 @@ +import G6 from '@antv/g6'; +import insertCss from 'insert-css'; + +insertCss(` + .g6-toolbar-ul { + position: absolute; + top: 20px; + border: 1px solid #e2e2e2; + border-radius: 4px; + font-size: 12px; + color: #545454; + background-color: rgba(255, 255, 255, 0.9); + padding: 10px 8px; + box-shadow: rgb(174, 174, 174) 0px 0px 10px; + width: 100px; + cursor: pointer; + } +`); + +const data = { + nodes: [ + { + id: '0', + label: 'node-0', + x: 100, + y: 100, + description: 'This is node-0.', + subdescription: 'This is subdescription of node-0.', + }, + { + id: '1', + label: 'node-1', + x: 250, + y: 100, + description: 'This is node-1.', + subdescription: 'This is subdescription of node-1.', + }, + { + id: '2', + label: 'node-2', + x: 150, + y: 310, + description: 'This is node-2.', + subdescription: 'This is subdescription of node-2.', + }, + { + id: '3', + label: 'node-3', + x: 320, + y: 310, + description: 'This is node-3.', + subdescription: 'This is subdescription of node-3.', + }, + ], + edges: [ + { + id: 'e0', + source: '0', + target: '1', + description: 'This is edge from node 0 to node 1.', + }, + { + id: 'e1', + source: '0', + target: '2', + description: 'This is edge from node 0 to node 2.', + }, + { + id: 'e2', + source: '0', + target: '3', + description: 'This is edge from node 0 to node 3.', + }, + ], +}; +const width = document.getElementById('container').scrollWidth; +const height = document.getElementById('container').scrollHeight || 500; + +const toolbar = new G6.ToolBar({ + // container: tc, + className: 'g6-toolbar-ul', + getContent: () => { + return ` +
      +
    • 增加节点
    • +
    • 撤销
    • +
    • 回退
    • +
    + ` + }, + handleClick: (code, graph) => { + if (code === 'add') { + graph.addItem('node', { + id: 'node2', + label: 'node2', + x: 300, + y: 150 + }) + } else if (code === 'undo') { + toolbar.undo() + } else if (code === 'redo') { + toolbar.redo() + } + } +}); + +const graph = new G6.Graph({ + container: 'container', + width, + height, + linkCenter: true, + // 设置为true,启用 redo & undo 栈功能 + enabledStack: true, + plugins: [toolbar], + defaultNode: { + size: [80, 40], + type: 'rect', + style: { + fill: '#DEE9FF', + stroke: '#5B8FF9', + }, + }, + defaultEdge: { + style: { + stroke: '#b5b5b5', + lineAppendWidth: 3, + }, + }, + modes: { + default: [ + 'drag-node' + ], + }, +}); +graph.data(data); +graph.render(); diff --git a/examples/tool/toolbar/demo/toolbar.js b/examples/tool/toolbar/demo/toolbar.js new file mode 100644 index 0000000000..e5ba0e607b --- /dev/null +++ b/examples/tool/toolbar/demo/toolbar.js @@ -0,0 +1,100 @@ +import G6 from '@antv/g6'; +import insertCss from 'insert-css'; + +insertCss(` + .g6-component-toolbar li { + list-style-type: none !important; + } +`); + +const data = { + nodes: [ + { + id: '0', + label: 'node-0', + x: 100, + y: 100, + description: 'This is node-0.', + subdescription: 'This is subdescription of node-0.', + }, + { + id: '1', + label: 'node-1', + x: 250, + y: 100, + description: 'This is node-1.', + subdescription: 'This is subdescription of node-1.', + }, + { + id: '2', + label: 'node-2', + x: 150, + y: 310, + description: 'This is node-2.', + subdescription: 'This is subdescription of node-2.', + }, + { + id: '3', + label: 'node-3', + x: 320, + y: 310, + description: 'This is node-3.', + subdescription: 'This is subdescription of node-3.', + }, + ], + edges: [ + { + id: 'e0', + source: '0', + target: '1', + description: 'This is edge from node 0 to node 1.', + }, + { + id: 'e1', + source: '0', + target: '2', + description: 'This is edge from node 0 to node 2.', + }, + { + id: 'e2', + source: '0', + target: '3', + description: 'This is edge from node 0 to node 3.', + }, + ], +}; +const width = document.getElementById('container').scrollWidth; +const height = document.getElementById('container').scrollHeight || 500; + +const toolbar = new G6.ToolBar(); + +const graph = new G6.Graph({ + container: 'container', + width, + height, + linkCenter: true, + plugins: [toolbar], + // 设置为true,启用 redo & undo 栈功能 + enabledStack: true, + defaultNode: { + size: [80, 40], + type: 'rect', + style: { + fill: '#DEE9FF', + stroke: '#5B8FF9', + }, + }, + defaultEdge: { + style: { + stroke: '#b5b5b5', + lineAppendWidth: 3, + }, + }, + modes: { + default: [ + 'drag-node' + ], + }, +}); +graph.data(data); +graph.render(); diff --git a/examples/tool/toolbar/index.en.md b/examples/tool/toolbar/index.en.md new file mode 100644 index 0000000000..d557bae8c2 --- /dev/null +++ b/examples/tool/toolbar/index.en.md @@ -0,0 +1,16 @@ +--- +title: ToolBar +order: 0 +--- + +G6 中内置的 ToolBar 组件。 + +## 使用指南 + +下面的代码演示展示了如何在图上使用 ToolBar。ToolBar 的样式可以使用 G6 内置的,也可以完全自定义 ToolBar 的内容,要修改内置 ToolBar 的样式,只需要修改 g6-component-toolbar 的样式: + +``` +.g6-component-toolbar { + // 自定义 CSS 内容 + } +``` diff --git a/examples/tool/toolbar/index.zh.md b/examples/tool/toolbar/index.zh.md new file mode 100644 index 0000000000..18af2e77a2 --- /dev/null +++ b/examples/tool/toolbar/index.zh.md @@ -0,0 +1,16 @@ +--- +title: 工具栏 +order: 0 +--- + +G6 中内置的 ToolBar 组件。 + +## 使用指南 + +下面的代码演示展示了如何在图上使用 ToolBar。ToolBar 的样式可以使用 G6 内置的,也可以完全自定义 ToolBar 的内容,要修改内置 ToolBar 的样式,只需要修改 g6-component-toolbar 的样式: + +``` +.g6-component-toolbar { + // 自定义 CSS 内容 + } +``` diff --git a/src/plugins/menu/index.ts b/src/plugins/menu/index.ts index d082682c8e..b716a830e0 100644 --- a/src/plugins/menu/index.ts +++ b/src/plugins/menu/index.ts @@ -65,25 +65,37 @@ export default class Menu extends Base { const className = this.get('className') const menu = createDOM(`
    `) modifyCSS(menu, { position: 'absolute', visibility: 'hidden' }); - document.body.appendChild(menu) + let container: HTMLDivElement | null = this.get('container'); + if (!container) { + container = this.get('graph').get('container'); + } + container.appendChild(menu) + this.set('menu', menu) } protected onMenuShow(e: IG6GraphEvent) { + e.preventDefault() + e.stopPropagation() + + if (!e.item) { + return + } + const self = this; - const container = this.get('menu') + const menuDom = this.get('menu') const getContent = this.get('getContent') let menu = getContent(e) if (isString(menu)) { - container.innerHTML = menu + menuDom.innerHTML = menu } else { - container.innerHTML = menu.outerHTML + menuDom.innerHTML = menu.outerHTML } const handleMenuClick = this.get('handleMenuClick') if (handleMenuClick) { - container.addEventListener('click', evt => { + menuDom.addEventListener('click', evt => { handleMenuClick(evt.target, e.item) }) } @@ -92,10 +104,11 @@ export default class Menu extends Base { const width: number = graph.get('width'); const height: number = graph.get('height'); - const bbox = container.getBoundingClientRect(); + const bbox = menuDom.getBoundingClientRect(); - let x = e.item.getModel().x || e.x; - let y = e.item.getModel().y || e.y; + + let x = e.canvasX//e.item.getModel().x || e.x; + let y = e.canvasY//e.item.getModel().y || e.y; // 若菜单超出画布范围,反向 if (x + bbox.width > width) { @@ -106,13 +119,13 @@ export default class Menu extends Base { y = height - bbox.height; } - const point = graph.getClientByPoint(x, y) - e.canvasX = point.x; - e.canvasY = point.y; + // const point = graph.getClientByPoint(x, y) + // e.canvasX = point.x; + // e.canvasY = point.y; - modifyCSS(container, { - top: `${point.y}px`, - left: `${point.x}px`, + modifyCSS(menuDom, { + top: `${y}px`, + left: `${x}px`, visibility: 'visible' }); @@ -126,9 +139,9 @@ export default class Menu extends Base { } private onMenuHide() { - const container = this.get('menu') - if (container) { - modifyCSS(container, { visibility: 'hidden' }); + const menuDom = this.get('menu') + if (menuDom) { + modifyCSS(menuDom, { visibility: 'hidden' }); } // 隐藏菜单后需要移除事件监听 @@ -136,7 +149,7 @@ export default class Menu extends Base { const handleMenuClick = this.get('handleMenuClick') if (handleMenuClick) { - container.removeEventListener('click', handleMenuClick) + menuDom.removeEventListener('click', handleMenuClick) } } @@ -150,7 +163,11 @@ export default class Menu extends Base { } if (menu) { - document.body.removeChild(menu); + let container: HTMLDivElement | null = this.get('container'); + if (!container) { + container = this.get('graph').get('container'); + } + container.removeChild(menu); } if (handler) { diff --git a/src/plugins/timeBar/index.ts b/src/plugins/timeBar/index.ts index b12831bdcf..39107e2a56 100644 --- a/src/plugins/timeBar/index.ts +++ b/src/plugins/timeBar/index.ts @@ -38,7 +38,12 @@ type TimeBarOption = Partial<{ readonly backgroundStyle: ShapeStyle; readonly foregroundStyle: ShapeStyle; - readonly handlerStyle: ShapeStyle; + // 滑块样式 + readonly handlerStyle: { + width: number; + height: number; + style: ShapeStyle; + }; readonly textStyle: ShapeStyle; // 允许滑动位置 readonly minLimit: number; @@ -95,20 +100,18 @@ export default class TimeBar extends Base { return } - const container = this.get('container') + const container: HTMLDivElement | null = this.get('container') + + const graphContainer = this.get('graph').get('container'); let timebar if (!container) { - timebar = createDOM(`
    `) + timebar = createDOM(`
    `) modifyCSS(timebar, { position: 'absolute' }); - document.body.appendChild(timebar) - } else if (isString(container)) { - timebar = createDOM(`
    `) - modifyCSS(timebar, { position: 'absolute' }); - document.body.appendChild(timebar) } else { timebar = container } + graphContainer.appendChild(timebar) this.set('timeBarContainer', timebar) @@ -153,8 +156,6 @@ export default class TimeBar extends Base { this.set('trendData', data) - console.log('配置项', config) - const slider = new Slider(config) slider.init(); @@ -234,7 +235,11 @@ export default class TimeBar extends Base { const timeBarContainer = this.get('timeBarContainer') if (timeBarContainer) { - document.body.removeChild(timeBarContainer); + let container: HTMLDivElement | null = this.get('container'); + if (!container) { + container = this.get('graph').get('container'); + } + container.removeChild(timeBarContainer); } } } diff --git a/src/plugins/tooltip/index.ts b/src/plugins/tooltip/index.ts index 934a8959a5..dfe8fab4f8 100644 --- a/src/plugins/tooltip/index.ts +++ b/src/plugins/tooltip/index.ts @@ -67,7 +67,11 @@ export default class Tooltip extends Base { const className = this.get('className') const tooltip = createDOM(`
    `) modifyCSS(tooltip, { position: 'absolute', visibility: 'hidden' }); - document.body.appendChild(tooltip) + let container: HTMLDivElement | null = this.get('container'); + if (!container) { + container = this.get('graph').get('container'); + } + container.appendChild(tooltip) this.set('tooltip', tooltip) } @@ -150,7 +154,11 @@ export default class Tooltip extends Base { const tooltip = this.get('tooltip') if (tooltip) { - document.body.removeChild(tooltip); + let container: HTMLDivElement | null = this.get('container'); + if (!container) { + container = this.get('graph').get('container'); + } + container.removeChild(tooltip); } } } diff --git a/tests/unit/plugins/menu-spec.ts b/tests/unit/plugins/menu-spec.ts index f940793ee1..1208035e9b 100644 --- a/tests/unit/plugins/menu-spec.ts +++ b/tests/unit/plugins/menu-spec.ts @@ -34,7 +34,7 @@ describe('menu', () => { graph.data(data) graph.render() - graph.destroy() + // graph.destroy() }) it('menu with dom', () => { const menu = new G6.Menu({ diff --git a/tests/unit/plugins/timebar-spec.ts b/tests/unit/plugins/timebar-spec.ts index 043a9b68a6..fb0ccb97d0 100644 --- a/tests/unit/plugins/timebar-spec.ts +++ b/tests/unit/plugins/timebar-spec.ts @@ -31,19 +31,18 @@ for(let i = 0; i < 100; i++) { data.nodes.push({ id, label: `node${i}`, - date: `2020${i}`, + date: i, value: Math.round(Math.random() * 300) }) - const edgeId = i + 3 data.edges.push({ source: `node-${Math.round(Math.random() * 90)}`, target: `node-${Math.round(Math.random() * 90)}` }) } -describe('tooltip', () => { - it('tooltip with default', () => { +describe('TimeBar', () => { + it('TimeBar with default', () => { const timeBarData = [] for(let i = 0; i < 100; i++) { @@ -61,13 +60,12 @@ describe('tooltip', () => { } } }); - const tooltip = new G6.Tooltip() const graph = new G6.Graph({ container: div, width: 500, height: 500, - plugins: [timebar, tooltip], + plugins: [timebar], modes: { default: ['drag-node', 'zoom-canvas', 'drag-canvas'] }, @@ -81,11 +79,100 @@ describe('tooltip', () => { graph.data(data) graph.render() + expect(graph.get('plugins').length).toBe(1) const timebarPlugin = graph.get('plugins')[0] + expect(timebarPlugin).not.toBe(null) console.log(timebarPlugin) - // expect(timebarPlugin.get('offset')).toBe(6) - // expect(timebarPlugin.get('tooltip').outerHTML).toBe(``) + expect(timebarPlugin.get('trendData')).toEqual(timeBarData) + expect(timebarPlugin.get('timebar').x).toBe(10) + expect(timebarPlugin.get('timebar').y).toBe(10) + expect(timebarPlugin.get('timebar').width).toBe(400) + expect(timebarPlugin.get('timebar').start).toBe(0.1) + const slider = timebarPlugin.get('slider') + expect(slider.get('name')).toEqual('slider') + expect(slider.get('maxText')).toEqual('202099') + expect(slider.get('minText')).toEqual('20200') + expect(slider.get('height')).toBe(26) + graph.destroy() + }) + + it.only('config TimeBar style', () => { + const timeBarData = [] + + for(let i = 0; i < 100; i++) { + timeBarData.push({ + date: i, + value: Math.round(Math.random() * 300) + }) + } + const timebar = new G6.TimeBar({ + timebar: { + backgroundStyle: { + fill: '#08979c', + opacity: 0.3 + }, + foregroundStyle: { + fill: '#40a9ff', + opacity: 0.4 + }, + trend: { + data: timeBarData, + isArea: false, + smooth: true, + lineStyle: { + stroke: '#9254de' + } + } + }, + rangeChange: (graph, min, max) => { + // 拿到 Graph 实例和 timebar 上范围,自己可以控制图上的渲染逻辑 + console.log(graph, min, max) + } + }); + + const graph = new G6.Graph({ + container: div, + width: 500, + height: 500, + plugins: [timebar], + modes: { + default: ['drag-node', 'zoom-canvas', 'drag-canvas'] + }, + defaultEdge: { + style: { + lineAppendWidth: 20 + } + } + }); + + graph.data(data) + graph.render() + + expect(graph.get('plugins').length).toBe(1) + const timebarPlugin = graph.get('plugins')[0] + expect(timebarPlugin).not.toBe(null) + console.log(timebarPlugin) + const timeBar = timebarPlugin.get('timebar') + expect(timebarPlugin.get('trendData')).toEqual(timeBarData) + expect(timeBar.x).toBe(10) + expect(timeBar.y).toBe(10) + expect(timeBar.width).toBe(400) + expect(timeBar.start).toBe(0.1) + + const backgroundStyle = timeBar.backgroundStyle + expect(backgroundStyle.fill).toEqual('#08979c') + expect(backgroundStyle.opacity).toBe(0.3) + + const foregroundStyle = timeBar.foregroundStyle + expect(foregroundStyle.fill).toEqual('#40a9ff') + expect(foregroundStyle.opacity).toBe(0.4) + + const slider = timebarPlugin.get('slider') + expect(slider.get('name')).toEqual('slider') + expect(slider.get('maxText')).toEqual(99) + expect(slider.get('minText')).toEqual(0) + expect(slider.get('height')).toBe(26) // graph.destroy() }) }); diff --git a/tests/unit/plugins/tooltip-spec.ts b/tests/unit/plugins/tooltip-spec.ts index 1cf593e6a6..5133f8e3fe 100644 --- a/tests/unit/plugins/tooltip-spec.ts +++ b/tests/unit/plugins/tooltip-spec.ts @@ -38,6 +38,9 @@ describe('tooltip', () => { modes: { default: ['drag-node', 'zoom-canvas', 'drag-canvas'] }, + defaultNode: { + type: 'rect' + }, defaultEdge: { style: { lineAppendWidth: 20 @@ -54,7 +57,7 @@ describe('tooltip', () => { graph.destroy() }) - it('menu with dom', () => { + it('tooltip with dom', () => { const tooltip = new G6.Tooltip({ offset: 10, getContent(e) { @@ -91,7 +94,7 @@ describe('tooltip', () => { expect(tooltipPlugin.get('offset')).toBe(10) graph.destroy() }) - it('menu with string', () => { + it('tooltip with string', () => { const tooltip = new G6.Tooltip({ getContent(e) { return `
    From a14153d3d8a00b08e14cebafd06f10cbf04a79b4 Mon Sep 17 00:00:00 2001 From: chenluli Date: Tue, 21 Jul 2020 14:07:14 +0800 Subject: [PATCH 25/77] fix: drag new node without initial position --- package.json | 2 +- src/graph/controller/item.ts | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 7f0754b22c..8b213267df 100644 --- a/package.json +++ b/package.json @@ -127,4 +127,4 @@ "webpack-cli": "^3.3.10", "worker-loader": "^2.0.0" } -} +} \ No newline at end of file diff --git a/src/graph/controller/item.ts b/src/graph/controller/item.ts index a454eac1a6..8bbeac127e 100644 --- a/src/graph/controller/item.ts +++ b/src/graph/controller/item.ts @@ -126,6 +126,9 @@ export default class ItemController { group: parent.addGroup(), }); } else if (type === NODE) { + model.x = model.x || 0; + model.y = model.y || 0; + item = new Node({ model, styles, From 7d6e6a18f9498268079ee703def3d34480416ea0 Mon Sep 17 00:00:00 2001 From: Yanyan-Wang Date: Wed, 22 Jul 2020 12:07:36 +0800 Subject: [PATCH 26/77] feat: fix the initial positions by equably distributing for layout to produce similar result. --- src/graph/controller/item.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/graph/controller/item.ts b/src/graph/controller/item.ts index 8bbeac127e..a454eac1a6 100644 --- a/src/graph/controller/item.ts +++ b/src/graph/controller/item.ts @@ -126,9 +126,6 @@ export default class ItemController { group: parent.addGroup(), }); } else if (type === NODE) { - model.x = model.x || 0; - model.y = model.y || 0; - item = new Node({ model, styles, From d9f14bb87087e66ea5235b037b390f1ea9831677 Mon Sep 17 00:00:00 2001 From: Yanyan-Wang Date: Thu, 23 Jul 2020 14:35:39 +0800 Subject: [PATCH 27/77] fix: node:click is triggered twice while clicking a node; fix: update combo edge when drag node out of it problem. --- CHANGELOG.md | 4 + examples/item/customNode/demo/meta.json | 10 +- examples/item/customNode/demo/svgDom.js | 26 +++-- package.json | 2 +- src/graph/controller/event.ts | 5 +- src/graph/controller/item.ts | 2 +- src/item/node.ts | 4 +- stories/Combo/combo.stories.tsx | 6 ++ stories/Combo/component/drag-node-out.tsx | 96 +++++++++++++++++++ stories/Combo/component/edges2.tsx | 2 +- .../component/treegraph-create-combo.tsx | 87 +++++++++++++++++ 11 files changed, 226 insertions(+), 18 deletions(-) create mode 100644 stories/Combo/component/drag-node-out.tsx create mode 100644 stories/Combo/component/treegraph-create-combo.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index 967759bdc1..d2b2840dbe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # ChangeLog +#### 3.5.12 +- fix: node:click is triggered twice while clicking a node; +- fix: update combo edge when drag node out of it problem. + #### 3.5.11 - feat: graph.priorityState api; - feat: graph.on support name:event mode. diff --git a/examples/item/customNode/demo/meta.json b/examples/item/customNode/demo/meta.json index 27d7d4a460..5c0fd015b3 100644 --- a/examples/item/customNode/demo/meta.json +++ b/examples/item/customNode/demo/meta.json @@ -6,12 +6,18 @@ "demos": [ { "filename": "card.js", - "title": "卡片", + "title": { + "zh": "卡片", + "en": "Card" + }, "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*3cRGRb5nB_UAAAAAAAAAAABkARQnAQ" }, { "filename": "cardNode.js", - "title": "卡片2", + "title": { + "zh": "卡片 2", + "en": "Card 2" + }, "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*b-g0RoOpI3sAAAAAAAAAAABkARQnAQ" }, { diff --git a/examples/item/customNode/demo/svgDom.js b/examples/item/customNode/demo/svgDom.js index 4a744c5c59..6a9840601d 100644 --- a/examples/item/customNode/demo/svgDom.js +++ b/examples/item/customNode/demo/svgDom.js @@ -1,4 +1,5 @@ import G6 from '@antv/g6'; +import { useEffect } from 'react'; /** * This demo shows how to register a custom node with SVG DOM shape @@ -55,8 +56,16 @@ const data = { ], }; -const width = document.getElementById('container').scrollWidth; -const height = document.getElementById('container').scrollHeight || 500; +const graphContainer = document.getElementById('container'); +const width = graphContainer.scrollWidth; +const height = (graphContainer.scrollHeight || 500) - 100; + +const descriptionDiv = document.createElement('div'); +descriptionDiv.innerHTML = + `由于打包问题,本 demo 的 110-113 行被暂时注释。需要您在代码栏中打开 110-113 行的注释以得到自定义 DOM 节点正确的交互。
    Due to the packing problem of the site, we have to note the line 110-113 of this demo temporary. Unnote them to see the result of custom DOM node with interactions please.`; +const container = document.getElementById('container'); +graphContainer.appendChild(descriptionDiv); + const graph = new G6.Graph({ container: 'container', width, @@ -74,9 +83,10 @@ const graph = new G6.Graph({ graph.data(data); graph.render(); -// click listener for dom nodes to response the click by changing stroke color +// // click listener for dom nodes to response the click by changing stroke color const listener = (dom) => { const nodeId = dom.id; + if (!nodeId) return; const node = graph.findById(nodeId); let stroke = ''; if (!node.hasState('selected')) { @@ -97,17 +107,17 @@ const bindClickListener = () => { const domNodes = document.getElementsByClassName('dom-node') for (let i = 0; i < domNodes.length; i++) { const dom = domNodes[i]; - dom.addEventListener('click', (e) => { - listener(dom); - }); + // open the following lines pls! + // dom.addEventListener('click', (e) => { + // listener(dom); + // }); } } - bindClickListener(); // after update the item, all the DOMs will be re-rendered // so the listeners should be rebinded to the new DOMs graph.on('afterupdateitem', e => { bindClickListener(); -}); +}); \ No newline at end of file diff --git a/package.json b/package.json index 8b213267df..c7eca84f32 100644 --- a/package.json +++ b/package.json @@ -88,7 +88,7 @@ "ml-matrix": "^6.5.0" }, "devDependencies": { - "@antv/gatsby-theme-antv": "^0.10.66", + "@antv/gatsby-theme-antv": "0.10.69", "@babel/core": "^7.7.7", "@babel/preset-react": "^7.7.4", "@storybook/addon-actions": "^5.2.8", diff --git a/src/graph/controller/event.ts b/src/graph/controller/event.ts index 0137251de2..1004f78815 100644 --- a/src/graph/controller/event.ts +++ b/src/graph/controller/event.ts @@ -157,9 +157,8 @@ export default class EventController { evt.item = item; graph.emit(eventType, evt); - graph.emit(`${type}:${eventType}`, evt); - - graph.emit(evt.name, evt); + if (evt.name && !evt.name.includes(':')) graph.emit(`${type}:${eventType}`, evt); + else graph.emit(evt.name, evt); if (eventType === 'dragstart') { this.dragging = true; diff --git a/src/graph/controller/item.ts b/src/graph/controller/item.ts index a454eac1a6..db14ab8324 100644 --- a/src/graph/controller/item.ts +++ b/src/graph/controller/item.ts @@ -471,7 +471,7 @@ export default class ItemController { // 再设置state,则此时该优先级为最高 this.setItemState(currentItem as Item, state, true) - } + } /** * 清除所有指定的状态 diff --git a/src/item/node.ts b/src/item/node.ts index 8368cdca15..79e28afd53 100644 --- a/src/item/node.ts +++ b/src/item/node.ts @@ -10,6 +10,7 @@ import { getRectIntersectByPoint, } from '../util/math'; import Item from './item'; +import { clone } from '@antv/util'; const CACHE_ANCHOR_POINTS = 'anchorPointsCache'; const CACHE_BBOX = 'bboxCache'; @@ -110,12 +111,11 @@ export default class Node extends Item implements INode { const type: string = keyShape.get('type'); const itemType: string = this.get('type'); let bbox, centerX, centerY; + bbox = this.getBBox(); if (itemType === 'combo') { - bbox = this.getKeyShape().getCanvasBBox(); centerX = (bbox.maxX + bbox.minX) / 2; centerY = (bbox.maxY + bbox.minY) / 2; } else { - bbox = this.getBBox(); centerX = bbox.centerX; centerY = bbox.centerY; } diff --git a/stories/Combo/combo.stories.tsx b/stories/Combo/combo.stories.tsx index 7bc1f51bb2..455640b9a3 100644 --- a/stories/Combo/combo.stories.tsx +++ b/stories/Combo/combo.stories.tsx @@ -16,6 +16,8 @@ import DagreCombo from './component/dagre-combo'; import Edges2 from './component/edges2'; import CreateCombo from './component/create-combo'; import RemoveItem from './component/remove-item'; +import DragNodeOut from './component/drag-node-out'; +import TreeGraphCreateCombo from './component/treegraph-create-combo'; export default { title: 'Combo' }; @@ -57,4 +59,8 @@ storiesOf('Combo', module) )).add('remove item ', () => ( + )).add('drag node out', () => ( + + )).add('treegraph create combo', () => ( + )); diff --git a/stories/Combo/component/drag-node-out.tsx b/stories/Combo/component/drag-node-out.tsx new file mode 100644 index 0000000000..485833fb8b --- /dev/null +++ b/stories/Combo/component/drag-node-out.tsx @@ -0,0 +1,96 @@ +import React, { useEffect } from 'react'; +import G6 from '../../../src'; +import { IGraph } from '../../../src/interface/graph'; +import { GraphData } from '../../../src/types'; + +let graph: IGraph = null; + +const data: GraphData = { + nodes: [ + { + id: 'node1', + label: 'node1', + x: 250, + y: 150, + comboId: 'combo' + }, + { + id: 'node2', + label: 'node2', + x: 350, + y: 150, + comboId: 'combo' + }, + ], + combos: [{ + id: 'combo', + label: 'Combo ' + }, { + id: 'combo1', + label: 'Combo' + }], + edges: [{ + id: 'edge1', + source: 'combo', + target: 'combo1' + }] +}; + + +const DragNodeOut = () => { + const container = React.useRef(); + useEffect(() => { + if (!graph) { + graph = new G6.Graph({ + container: container.current as string | HTMLElement, + width: 800, + height: 600, + fitCenter: true, + groupByTypes: false, + defaultCombo: { + type: 'circle', + style: { + lineWidth: 1, + }, + labelCfg: { + refY: 10, + position: 'top', + style: { + fontSize: 18, + } + } + }, + modes: { + default: ['drag-canvas', 'drag-node', 'drag-combo', 'collapse-expand-combo'], + }, + comboStateStyles: { + // the style configurations for the hover state on the combo + hover: { + lineWidth: 3 + }, + }, + nodeStateStyles: { + // the hover configurations for the hover state on the node + hover: { + lineWidth: 3 + }, + }, + }); + graph.data(data); + graph.render(); + + graph.on('combo:click', e => { + console.log('clicking combo') + }); + graph.on('node:click', e => { + console.log('clicking node!!!') + }); + graph.on('text-shape:click', e => { + console.log('clicking text-shape-----') + }); + } + }); + return
    ; +}; + +export default DragNodeOut; diff --git a/stories/Combo/component/edges2.tsx b/stories/Combo/component/edges2.tsx index dff330cc25..e66c3a1643 100644 --- a/stories/Combo/component/edges2.tsx +++ b/stories/Combo/component/edges2.tsx @@ -96,7 +96,7 @@ const Edges2 = () => { height: 800, groupByTypes: false, defaultEdge: { - type: 'polyline', + type: 'line', style: { endArrow: true } diff --git a/stories/Combo/component/treegraph-create-combo.tsx b/stories/Combo/component/treegraph-create-combo.tsx new file mode 100644 index 0000000000..4bf0a0ca93 --- /dev/null +++ b/stories/Combo/component/treegraph-create-combo.tsx @@ -0,0 +1,87 @@ +import React, { useEffect } from 'react'; +import G6 from '../../../src'; +import { ITreeGraph } from '../../../src/interface/graph'; +import { GraphData } from '../../../src/types'; + +let graph: ITreeGraph = null; + +const data = { + id: "A", + label: "开始", + children: [ + { + id: "A1", + label: "节点1", + children: [ + { id: "A11", label: "A11" }, + { id: "A12", label: "A12" }, + { id: "A13", label: "A13" }, + { id: "A14", label: "A14" } + ] + } + ] +}; + +const TreeGraphCreateCombo = () => { + const container = React.useRef(); + useEffect(() => { + if (!graph) { + graph = new G6.TreeGraph({ + container: container.current as string | HTMLElement, + width: 500, + height: 400, + groupByTypes: false, + fitView: true, + defaultNode: { + type: 'rect', + size: [25, 10], + style: { + fill: 'pink', + lineWidth: 0 + }, + labelCfg: { + style: { + fontSize: 6, + } + } + }, + layout: { + type: "dendrogram", + direction: "LR", + nodeSep: 30, + rankSep: 60 + }, + defaultEdge: { + type: "polyline", + style: { + lineWidth: 1, + offset: 17.5, + endArrow: { + path: G6.Arrow.triangle(3, 5, 1), + d: 1 + } + } + }, + modes: { + default: [ + 'drag-canvas', + { + type: 'collapse-expand', + }, + 'zoom-canvas', + ], + }, + }); + graph.data(data); + graph.render(); + graph.createCombo({ + id: 'combo1', + type: 'rect', + padding: [5, 5, 5, 5] + }, ['A11', 'A12', 'A13']) + } + }); + return
    ; +}; + +export default TreeGraphCreateCombo; From 44a9efcb1657e58214c0a81b841c94ed81b153af Mon Sep 17 00:00:00 2001 From: Yanyan-Wang Date: Thu, 23 Jul 2020 17:25:04 +0800 Subject: [PATCH 28/77] feat: animate configuration for combo, true by default. --- CHANGELOG.md | 3 +- package.json | 2 +- src/behavior/activate-relations.ts | 114 ++++++++++++++-------- src/behavior/drag-canvas.ts | 9 +- src/global.ts | 2 +- src/shape/combo.ts | 2 +- src/shape/shapeBase.ts | 10 +- stories/Combo/component/drag-node-out.tsx | 1 + 8 files changed, 89 insertions(+), 54 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d2b2840dbe..86b56ca7ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,8 @@ #### 3.5.12 - fix: node:click is triggered twice while clicking a node; -- fix: update combo edge when drag node out of it problem. +- fix: update combo edge when drag node out of it problem; +- feat: animate configuration for combo, true by default. #### 3.5.11 - feat: graph.priorityState api; diff --git a/package.json b/package.json index c7eca84f32..ffae65a9c3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@antv/g6", - "version": "3.5.11", + "version": "3.5.12", "description": "A Graph Visualization Framework in JavaScript", "keywords": [ "antv", diff --git a/src/behavior/activate-relations.ts b/src/behavior/activate-relations.ts index 2698212365..99f44267e9 100644 --- a/src/behavior/activate-relations.ts +++ b/src/behavior/activate-relations.ts @@ -1,4 +1,5 @@ import { G6Event, IG6GraphEvent } from '../types'; +import { INode } from '../interface/item'; export default { getDefaultCfg(): object { @@ -25,16 +26,21 @@ export default { }; }, setAllItemStates(e: IG6GraphEvent) { - const { item } = e; - const graph = this.get('graph'); - this.set('item', item); + const item: INode = e.item as INode; + const graph = this.graph; + this.item = item; if (!this.shouldUpdate(e.item, { event: e, action: 'activate' })) { return; } const self = this; - const activeState = this.get('activeState'); - const inactiveState = this.get('inactiveState'); - graph.getNodes().forEach(node => { + const activeState = this.activeState; + const inactiveState = this.inactiveState; + const nodes = graph.getNodes(); + const edges = graph.getEdges(); + const nodeLength = nodes.length; + const edgeLength = edges.length; + for (let i = 0; i < nodeLength; i++) { + const node = nodes[i]; const hasSelected = node.hasState('selected'); if (self.resetSelected) { if (hasSelected) { @@ -45,38 +51,47 @@ export default { if (inactiveState) { graph.setItemState(node, inactiveState, true); } - }); - graph.getEdges().forEach(edge => { + } + + for (let i = 0; i < edgeLength; i++) { + const edge = edges[i]; graph.setItemState(edge, activeState, false); if (inactiveState) { graph.setItemState(edge, inactiveState, true); } - }); + } + if (inactiveState) { graph.setItemState(item, inactiveState, false); } graph.setItemState(item, activeState, true); - graph.getEdges().forEach(edge => { - if (edge.getSource() === item) { - const target = edge.getTarget(); - if (inactiveState) { - graph.setItemState(target, inactiveState, false); - } - graph.setItemState(target, activeState, true); - graph.setItemState(edge, inactiveState, false); - graph.setItemState(edge, activeState, true); - edge.toFront(); - } else if (edge.getTarget() === item) { - const source = edge.getSource(); - if (inactiveState) { - graph.setItemState(source, inactiveState, false); - } - graph.setItemState(source, activeState, true); - graph.setItemState(edge, inactiveState, false); - graph.setItemState(edge, activeState, true); - edge.toFront(); + + const outEdges = item.getOutEdges(); + const inEdges = item.getInEdges(); + const outLegnth = outEdges.length; + const inLegnth = inEdges.length; + for (let i = 0; i < outLegnth; i++) { + const edge = outEdges[i]; + const target = edge.getTarget(); + if (inactiveState) { + graph.setItemState(target, inactiveState, false); } - }); + graph.setItemState(target, activeState, true); + graph.setItemState(edge, inactiveState, false); + graph.setItemState(edge, activeState, true); + edge.toFront(); + } + for (let i = 0; i < inLegnth; i++) { + const edge = inEdges[i]; + const source = edge.getSource(); + if (inactiveState) { + graph.setItemState(source, inactiveState, false); + } + graph.setItemState(source, activeState, true); + graph.setItemState(edge, inactiveState, false); + graph.setItemState(edge, activeState, true); + edge.toFront(); + } graph.emit('afteractivaterelations', { item: e.item, action: 'activate' }); }, clearActiveState(e: any) { @@ -86,17 +101,24 @@ export default { return; } - const activeState = this.get('activeState'); - const inactiveState = this.get('inactiveState'); + const activeState = this.activeState; + const inactiveState = this.inactiveState; const autoPaint = graph.get('autoPaint'); graph.setAutoPaint(false); - graph.getNodes().forEach(node => { + const nodes = graph.getNodes(); + const edges = graph.getEdges(); + const nodeLength = nodes.length; + const edgeLength = edges.length; + + for (let i = 0; i < nodeLength; i++) { + const node = nodes[i]; graph.clearItemStates(node, [activeState, inactiveState]); - }); - graph.getEdges().forEach(edge => { + } + for (let i = 0; i < edgeLength; i++) { + const edge = edges[i]; graph.clearItemStates(edge, [activeState, inactiveState, 'deactivate']); - }); + } graph.paint(); graph.setAutoPaint(autoPaint); graph.emit('afteractivaterelations', { @@ -106,20 +128,28 @@ export default { }, clearAllItemStates(e: any) { const self = this; - const graph = self.get('graph'); + const graph = self.graph; if (!self.shouldUpdate(e.item, { event: e, action: 'deactivate' })) { return; } - const activeState = this.get('activeState'); - const inactiveState = this.get('inactiveState'); + const activeState = this.activeState; + const inactiveState = this.inactiveState; - graph.getNodes().forEach(node => { + const nodes = graph.getNodes(); + const edges = graph.getEdges(); + const nodeLength = nodes.length; + const edgeLength = edges.length; + + for (let i = 0; i < nodeLength; i++) { + const node = nodes[i]; graph.clearItemStates(node, [activeState, inactiveState]); - }); - graph.getEdges().forEach(edge => { + } + for (let i = 0; i < edgeLength; i++) { + const edge = edges[i]; graph.clearItemStates(edge, [activeState, inactiveState, 'deactivate']); - }); + } + graph.emit('afteractivaterelations', { item: e.item || self.get('item'), action: 'deactivate', diff --git a/src/behavior/drag-canvas.ts b/src/behavior/drag-canvas.ts index 7f20495881..19fb02dd30 100644 --- a/src/behavior/drag-canvas.ts +++ b/src/behavior/drag-canvas.ts @@ -74,7 +74,11 @@ export default { if (this.enableOptimize) { // 开始拖动时关闭局部渲染 - this.graph.get('canvas').set('localRefresh', false) + + const localRefresh: boolean = this.graph.get('canvas').get('localRefresh'); + this.oriLocalRefresh = localRefresh; + + this.graph.get('canvas').set('localRefresh', false); // 拖动 canvas 过程中隐藏所有的边及label const graph: IGraph = this.graph @@ -153,7 +157,8 @@ export default { setTimeout(() => { // 拖动结束后开启局部渲染 - graph.get('canvas').set('localRefresh', true) + const localRefresh = this.oriLocalRefresh === undefined ? true : this.oriLocalRefresh; + graph.get('canvas').set('localRefresh', localRefresh) }, 16) } diff --git a/src/global.ts b/src/global.ts index 109ccd9014..715b4563d9 100644 --- a/src/global.ts +++ b/src/global.ts @@ -1,5 +1,5 @@ export default { - version: '3.5.11', + version: '3.5.12', rootContainerClassName: 'root-container', nodeContainerClassName: 'node-container', edgeContainerClassName: 'edge-container', diff --git a/src/shape/combo.ts b/src/shape/combo.ts index 30e7acd439..f021233acf 100644 --- a/src/shape/combo.ts +++ b/src/shape/combo.ts @@ -123,7 +123,7 @@ const singleCombo: ShapeOptions = { }, updateShape(cfg: NodeConfig, item: Item, keyShapeStyle: ShapeStyle) { const keyShape = item.get('keyShape'); - const animate = this.options.animate; + const { animate } = cfg.animate === undefined ? this.options.animate : cfg.animate; if (animate && keyShape.animate) { keyShape.animate(keyShapeStyle, { duration: 200, diff --git a/src/shape/shapeBase.ts b/src/shape/shapeBase.ts index b0cde579cf..7c31c5f16c 100644 --- a/src/shape/shapeBase.ts +++ b/src/shape/shapeBase.ts @@ -310,8 +310,6 @@ export const shapeBase: ShapeOptions = { const style = styles[key]; if (isPlainObject(style)) { const subShape = group.find((element) => element.get('name') === key); - - if (subShape) { subShape.attr(style); } @@ -354,9 +352,9 @@ export const shapeBase: ShapeOptions = { filtetDisableStatesStyle[p] = subShapeStyles; } } else { - // 从图元素现有的样式中删除本次要取消的 states 中存在的属性值 - const keptAttrs = ['x', 'y', 'cx', 'cy']; - if (keyShapeStyles[p] && !(keptAttrs.indexOf(p) > -1)) { + // 从图元素现有的样式中删除本次要取消的 states 中存在的属性值。使用对象检索更快 + const keptAttrs = { 'x': 1, 'y': 1, 'cx': 1, 'cy': 1 }; + if (keyShapeStyles[p] && !keptAttrs[p]) { delete keyShapeStyles[p]; } } @@ -433,7 +431,7 @@ export const shapeBase: ShapeOptions = { : stateStyles && stateStyles[name]; if (type === 'combo') { - return mix({}, modelStateStyle); + return clone(modelStateStyle); } return mix({}, style, modelStateStyle); }, diff --git a/stories/Combo/component/drag-node-out.tsx b/stories/Combo/component/drag-node-out.tsx index 485833fb8b..8706d4a101 100644 --- a/stories/Combo/component/drag-node-out.tsx +++ b/stories/Combo/component/drag-node-out.tsx @@ -49,6 +49,7 @@ const DragNodeOut = () => { groupByTypes: false, defaultCombo: { type: 'circle', + animate: false, style: { lineWidth: 1, }, From 746c9439c39230ccb122dce24663aa13b3f6a197 Mon Sep 17 00:00:00 2001 From: Yanyan-Wang Date: Thu, 23 Jul 2020 21:59:33 +0800 Subject: [PATCH 29/77] docs: update demos of the site. --- examples/case/decisionTree/demo/index.ts | 7 +- examples/case/metroLines/demo/index.js | 19 +- examples/item/customNode/demo/svgDom.js | 2 +- src/behavior/activate-relations.ts | 30 ++-- src/behavior/collapse-expand-combo.ts | 3 +- src/behavior/drag-node.ts | 1 + src/behavior/zoom-canvas.ts | 2 +- src/graph/controller/event.ts | 15 +- src/graph/controller/view.ts | 9 +- src/graph/graph.ts | 13 +- src/layout/comboForce.ts | 10 +- src/plugins/grid/index.ts | 3 +- src/plugins/minimap/index.ts | 2 +- src/shape/combo.ts | 2 +- src/shape/combos/rect.ts | 2 +- src/shape/shapeBase.ts | 6 +- src/util/base.ts | 2 +- src/util/graphic.ts | 4 +- src/util/math.ts | 12 +- stories/Case/component/flow.tsx | 7 +- stories/Case/component/tutorial.tsx | 165 +++++++----------- .../Combo/component/collapse-expand-combo.tsx | 5 +- .../component/combo-collapse-expand-tree.tsx | 16 +- stories/Combo/component/dagre-combo.tsx | 1 - stories/Interaction/component/addItem.tsx | 3 + stories/Issues/changeData/index.tsx | 7 +- stories/Plugins/component/minimap.tsx | 2 +- stories/Shape/component/card-node.tsx | 1 + stories/Shape/component/quadratic.tsx | 2 +- .../behavior/collapse-expand-combo-spec.ts | 82 ++++----- 30 files changed, 199 insertions(+), 236 deletions(-) diff --git a/examples/case/decisionTree/demo/index.ts b/examples/case/decisionTree/demo/index.ts index 529ce42d74..647d235d5f 100644 --- a/examples/case/decisionTree/demo/index.ts +++ b/examples/case/decisionTree/demo/index.ts @@ -179,7 +179,6 @@ const props: IProps = { const defaultConfig = { width: 1600, height: 800, - pixelRatio: 1, modes: { default: ['zoom-canvas', 'drag-canvas'], }, @@ -199,7 +198,7 @@ const defaultConfig = { * @param {*} defaultValue 默认值 */ const get = (object: object, path: string, defaultValue?: any) => { - return object ?.[path] || defaultValue; + return object?.[path] || defaultValue; }; // number to string @@ -444,7 +443,7 @@ let isAnimating = false; let graph = null; const initGraph = (data?: ListItem[]) => { - if (!data ?.length) { + if (!data?.length) { return; } transformData(data); @@ -462,7 +461,7 @@ const initGraph = (data?: ListItem[]) => { graph.data(getPosition(data, true)); graph.render(); graph.zoom(config.defaultZoom || 1); - if (data ?.length) { + if (data?.length) { graph.changeData(getPosition(backUpData)); } }; diff --git a/examples/case/metroLines/demo/index.js b/examples/case/metroLines/demo/index.js index fb869bd94e..ea8c6961f1 100644 --- a/examples/case/metroLines/demo/index.js +++ b/examples/case/metroLines/demo/index.js @@ -196,10 +196,11 @@ fetch('https://gw.alipayobjects.com/os/basement_prod/8c2353b0-99a9-4a93-a5e1-3e7 const edges = data.edges; const classMap = new Map(); let classId = 0; - nodes.forEach(function(node) { + nodes.forEach(function (node) { node.y = -node.y; }); - edges.forEach(function(edge) { + edges.forEach(function (edge) { + edge.id = `edge-${edge.id}`; // edge cluster if (edge.class && classMap.get(edge.class) === undefined) { classMap.set(edge.class, classId); @@ -209,7 +210,7 @@ fetch('https://gw.alipayobjects.com/os/basement_prod/8c2353b0-99a9-4a93-a5e1-3e7 edge.color = colors[cid % colors.length]; const controlPoints = edge.controlPoints; - controlPoints.forEach(function(cp) { + controlPoints.forEach(function (cp) { cp.y = -cp.y; }); }); @@ -229,16 +230,16 @@ function scaleNodesPoints(nodes, edges, graphSize) { let maxX = -99999999999999999; let minY = 99999999999999999; let maxY = -99999999999999999; - nodes.forEach(function(node) { + nodes.forEach(function (node) { if (node.x > maxX) maxX = node.x; if (node.x < minX) minX = node.x; if (node.y > maxY) maxY = node.y; if (node.y < minY) minY = node.y; }); - edges.forEach(function(edge) { + edges.forEach(function (edge) { const controlPoints = edge.controlPoints; - controlPoints.forEach(function(cp) { + controlPoints.forEach(function (cp) { if (cp.x > maxX) maxX = cp.x; if (cp.x < minX) minX = cp.x; if (cp.y > maxY) maxY = cp.y; @@ -248,15 +249,15 @@ function scaleNodesPoints(nodes, edges, graphSize) { const xScale = maxX - minX; const yScale = maxY - minY; - nodes.forEach(function(node) { + nodes.forEach(function (node) { node.orix = node.x; node.oriy = node.y; node.x = ((node.x - minX) / xScale) * size; node.y = ((node.y - minY) / yScale) * size; }); - edges.forEach(function(edge) { + edges.forEach(function (edge) { const controlPoints = edge.controlPoints; - controlPoints.forEach(function(cp) { + controlPoints.forEach(function (cp) { cp.x = ((cp.x - minX) / xScale) * size; cp.y = ((cp.y - minY) / yScale) * size; }); diff --git a/examples/item/customNode/demo/svgDom.js b/examples/item/customNode/demo/svgDom.js index 6a9840601d..1a386c5bed 100644 --- a/examples/item/customNode/demo/svgDom.js +++ b/examples/item/customNode/demo/svgDom.js @@ -62,7 +62,7 @@ const height = (graphContainer.scrollHeight || 500) - 100; const descriptionDiv = document.createElement('div'); descriptionDiv.innerHTML = - `由于打包问题,本 demo 的 110-113 行被暂时注释。需要您在代码栏中打开 110-113 行的注释以得到自定义 DOM 节点正确的交互。
    Due to the packing problem of the site, we have to note the line 110-113 of this demo temporary. Unnote them to see the result of custom DOM node with interactions please.`; + `由于打包问题,本 demo 的 111-113 行被暂时注释。需要您在代码栏中打开 111-113 行的注释以得到自定义 DOM 节点正确的交互。
    Due to the packing problem of the site, we have to note the line 111-113 of this demo temporary. Unnote them to see the result of custom DOM node with interactions please.`; const container = document.getElementById('container'); graphContainer.appendChild(descriptionDiv); diff --git a/src/behavior/activate-relations.ts b/src/behavior/activate-relations.ts index 99f44267e9..bdfd5aed3f 100644 --- a/src/behavior/activate-relations.ts +++ b/src/behavior/activate-relations.ts @@ -66,28 +66,20 @@ export default { } graph.setItemState(item, activeState, true); - const outEdges = item.getOutEdges(); - const inEdges = item.getInEdges(); - const outLegnth = outEdges.length; - const inLegnth = inEdges.length; - for (let i = 0; i < outLegnth; i++) { - const edge = outEdges[i]; - const target = edge.getTarget(); - if (inactiveState) { - graph.setItemState(target, inactiveState, false); + const rEdges = item.getEdges(); + const rEdgeLegnth = rEdges.length; + for (let i = 0; i < rEdgeLegnth; i++) { + const edge = rEdges[i]; + let otherEnd; + if (edge.getSource() === item) { + otherEnd = edge.getTarget(); + } else { + otherEnd = edge.getSource(); } - graph.setItemState(target, activeState, true); - graph.setItemState(edge, inactiveState, false); - graph.setItemState(edge, activeState, true); - edge.toFront(); - } - for (let i = 0; i < inLegnth; i++) { - const edge = inEdges[i]; - const source = edge.getSource(); if (inactiveState) { - graph.setItemState(source, inactiveState, false); + graph.setItemState(otherEnd, inactiveState, false); } - graph.setItemState(source, activeState, true); + graph.setItemState(otherEnd, activeState, true); graph.setItemState(edge, inactiveState, false); graph.setItemState(edge, activeState, true); edge.toFront(); diff --git a/src/behavior/collapse-expand-combo.ts b/src/behavior/collapse-expand-combo.ts index 68e7bd79aa..5186d74fd6 100644 --- a/src/behavior/collapse-expand-combo.ts +++ b/src/behavior/collapse-expand-combo.ts @@ -27,7 +27,7 @@ export default { ); } return { - [`${trigger}`]: 'onComboClick', + [`combo:${trigger}`]: 'onComboClick', }; }, onComboClick(evt: IG6GraphEvent) { @@ -43,6 +43,5 @@ export default { graph.collapseExpandCombo(comboId); if (relayout && graph.get('layout')) graph.layout(); else graph.refreshPositions(); - // graph.refreshPositions(); }, }; diff --git a/src/behavior/drag-node.ts b/src/behavior/drag-node.ts index 0446e3bdd7..352e8a26b9 100644 --- a/src/behavior/drag-node.ts +++ b/src/behavior/drag-node.ts @@ -273,6 +273,7 @@ export default { * @param {number} y 拖动单个元素时候的y坐标 */ updateDelegate(e) { + const { graph } = this; if (!this.delegateRect) { // 拖动多个 const parent = this.graph.get('group'); diff --git a/src/behavior/zoom-canvas.ts b/src/behavior/zoom-canvas.ts index 0d50452f6c..4834ebba1e 100644 --- a/src/behavior/zoom-canvas.ts +++ b/src/behavior/zoom-canvas.ts @@ -141,7 +141,7 @@ export default { if (fixSelectedItems.fixAll) { if (zoom <= 1) { let groupMatrix = clone(group.getMatrix()); - if (!groupMatrix) groupMatrix = mat3.create(); + if (!groupMatrix) groupMatrix = [1, 0, 0, 0, 1, 0, 0, 0, 1]; const { x, y } = node.getModel(); mat3.translate(groupMatrix, groupMatrix, [-x, -y]); mat3.scale(groupMatrix, groupMatrix, [scale, scale]); diff --git a/src/graph/controller/event.ts b/src/graph/controller/event.ts index 1004f78815..eb0a40458e 100644 --- a/src/graph/controller/event.ts +++ b/src/graph/controller/event.ts @@ -8,7 +8,6 @@ import wrapBehavior from '@antv/util/lib/wrap-behavior'; import Graph from '../graph'; import { IG6GraphEvent, Matrix, Item } from '../../types'; import { cloneEvent, isViewportChanged } from '../../util/base'; -import { mat3 } from '@antv/matrix-util'; type Fun = () => void; @@ -100,6 +99,7 @@ export default class EventController { */ protected onCanvasEvents(evt: IG6GraphEvent) { const { graph } = this; + const canvas = graph.get('canvas'); const { target } = evt; const eventType = evt.type; @@ -114,12 +114,13 @@ export default class EventController { const group: Group = graph.get('group'); let matrix: Matrix = group.getMatrix(); + if (!matrix) { - matrix = mat3.create(); + matrix = [1, 0, 0, 0, 1, 0, 0, 0, 1]; } if (isViewportChanged(matrix)) { - point = graph.getPointByCanvas(evt.canvasX, evt.canvasY); + point = graph.getPointByClient(evt.clientX, evt.clientY); } evt.x = point.x; @@ -152,13 +153,15 @@ export default class EventController { const type = item.getType(); - // 事件target是触发事件的Shape实例,, item是触发事件的item实例 + // 事件target是触发事件的Shape实例,item是触发事件的item实例 evt.target = target; evt.item = item; + + // emit('click', evt); graph.emit(eventType, evt); - if (evt.name && !evt.name.includes(':')) graph.emit(`${type}:${eventType}`, evt); - else graph.emit(evt.name, evt); + if (evt.name && !evt.name.includes(':')) graph.emit(`${type}:${eventType}`, evt); // emit('node:click', evt) + else graph.emit(evt.name, evt); // emit('text-shape:click', evt) if (eventType === 'dragstart') { this.dragging = true; diff --git a/src/graph/controller/view.ts b/src/graph/controller/view.ts index f2c85d324c..e0ac54c4af 100644 --- a/src/graph/controller/view.ts +++ b/src/graph/controller/view.ts @@ -7,7 +7,6 @@ import { Item, Matrix, Padding, GraphAnimateConfig } from '../../types'; import { formatPadding } from '../../util/base'; import { applyMatrix, invertMatrix } from '../../util/math'; import Graph from '../graph'; -import { mat3 } from '@antv/matrix-util'; import modifyCSS from '@antv/dom-util/lib/modify-css'; export default class ViewController { @@ -84,7 +83,7 @@ export default class ViewController { const viewCenter = this.getViewCenter(); const modelCenter = this.getPointByCanvas(viewCenter.x, viewCenter.y); let viewportMatrix: Matrix = this.graph.get('group').getMatrix(); - if (!viewportMatrix) viewportMatrix = mat3.create(); + if (!viewportMatrix) viewportMatrix = [1, 0, 0, 0, 1, 0, 0, 0, 1]; if (animate) { const dx = (modelCenter.x - point.x) * viewportMatrix[0]; const dy = (modelCenter.y - point.y) * viewportMatrix[4]; @@ -121,7 +120,7 @@ export default class ViewController { public getPointByCanvas(canvasX: number, canvasY: number): Point { let viewportMatrix: Matrix = this.graph.get('group').getMatrix(); if (!viewportMatrix) { - viewportMatrix = mat3.create(); + viewportMatrix = [1, 0, 0, 0, 1, 0, 0, 0, 1]; } const point = invertMatrix({ x: canvasX, y: canvasY }, viewportMatrix); return point; @@ -159,7 +158,7 @@ export default class ViewController { public getCanvasByPoint(x: number, y: number): Point { let viewportMatrix: Matrix = this.graph.get('group').getMatrix(); if (!viewportMatrix) { - viewportMatrix = mat3.create(); + viewportMatrix = [1, 0, 0, 0, 1, 0, 0, 0, 1]; } return applyMatrix({ x, y }, viewportMatrix); } @@ -176,7 +175,7 @@ export default class ViewController { } const group: Group = item.get('group'); let matrix: Matrix = group.getMatrix(); - if (!matrix) matrix = mat3.create(); + if (!matrix) matrix = [1, 0, 0, 0, 1, 0, 0, 0, 1]; if (item) { // 用实际位置而不是model中的x,y,防止由于拖拽等的交互导致model的x,y并不是当前的x,y diff --git a/src/graph/graph.ts b/src/graph/graph.ts index 94200e7864..f8fa027f3a 100644 --- a/src/graph/graph.ts +++ b/src/graph/graph.ts @@ -641,7 +641,7 @@ export default class Graph extends EventEmitter implements IGraph { let matrix: Matrix = clone(group.getMatrix()); if (!matrix) { - matrix = mat3.create(); + matrix = [1, 0, 0, 0, 1, 0, 0, 0, 1]; } mat3.translate(matrix, matrix, [dx, dy]); @@ -728,7 +728,7 @@ export default class Graph extends EventEmitter implements IGraph { const maxZoom: number = this.get('maxZoom'); if (!matrix) { - matrix = mat3.create(); + matrix = [1, 0, 0, 0, 1, 0, 0, 0, 1]; } if (center) { @@ -1297,7 +1297,7 @@ export default class Graph extends EventEmitter implements IGraph { if (item) { if (self.get('animate') && type === NODE) { let containerMatrix = item.getContainer().getMatrix(); - if (!containerMatrix) containerMatrix = mat3.create(); + if (!containerMatrix) containerMatrix = [1, 0, 0, 0, 1, 0, 0, 0, 1]; item.set('originAttrs', { x: containerMatrix[6], y: containerMatrix[7], @@ -1924,7 +1924,7 @@ export default class Graph extends EventEmitter implements IGraph { if (!originAttrs) { let containerMatrix = node.getContainer().getMatrix(); - if (!containerMatrix) containerMatrix = mat3.create(); + if (!containerMatrix) containerMatrix = [1, 0, 0, 0, 1, 0, 0, 0, 1]; originAttrs = { x: containerMatrix[6], y: containerMatrix[7], @@ -2056,6 +2056,7 @@ export default class Graph extends EventEmitter implements IGraph { // 清空画布时同时清除数据 this.set({ itemMap: {}, nodes: [], edges: [], groups: [] }); + this.emit('afterrender'); return this; } @@ -2137,7 +2138,7 @@ export default class Graph extends EventEmitter implements IGraph { const vGroup = group.clone(); let matrix = clone(vGroup.getMatrix()); - if (!matrix) matrix = mat3.create(); + if (!matrix) matrix = [1, 0, 0, 0, 1, 0, 0, 0, 1]; const centerX = (bbox.maxX + bbox.minX) / 2; const centerY = (bbox.maxY + bbox.minY) / 2; mat3.translate(matrix, matrix, [-centerX, -centerY]); @@ -2217,7 +2218,7 @@ export default class Graph extends EventEmitter implements IGraph { const vGroup = group.clone(); let matrix = clone(vGroup.getMatrix()); - if (!matrix) matrix = mat3.create(); + if (!matrix) matrix = [1, 0, 0, 0, 1, 0, 0, 0, 1]; const centerX = (bbox.maxX + bbox.minX) / 2; const centerY = (bbox.maxY + bbox.minY) / 2; mat3.translate(matrix, matrix, [-centerX, -centerY]); diff --git a/src/layout/comboForce.ts b/src/layout/comboForce.ts index 557fb66953..ca92ff7ff2 100644 --- a/src/layout/comboForce.ts +++ b/src/layout/comboForce.ts @@ -385,14 +385,14 @@ export default class ComboForce extends BaseLayout { private initPos(comboMap) { const self = this; const nodes = self.nodes; - nodes.forEach(node => { + nodes.forEach((node, i) => { if (node.comboId) { const combo = comboMap[node.comboId]; - node.x = combo.cx + Math.random() * 100; - node.y = combo.cy + Math.random() * 100; + node.x = combo.cx + 100 / (i + 1); + node.y = combo.cy + 100 / (i + 1); } else { - node.x = Math.random() * 100; - node.y = Math.random() * 100; + node.x = 100 / (i + 1); + node.y = 100 / (i + 1); } }); } diff --git a/src/plugins/grid/index.ts b/src/plugins/grid/index.ts index 2e07422f5c..1ab417e737 100644 --- a/src/plugins/grid/index.ts +++ b/src/plugins/grid/index.ts @@ -4,7 +4,6 @@ import Canvas from '@antv/g-base/lib/abstract/canvas'; import { IGraph } from '../../interface/graph'; import { ViewPortEventParam } from '../../types'; import Base, { IPluginBaseConfig } from '../base'; -import { mat3 } from '@antv/matrix-util'; interface GridConfig { img?: string @@ -77,7 +76,7 @@ export default class Grid extends Base { protected updateGrid(param: ViewPortEventParam) { const gridContainer: HTMLDivElement = this.get('gridContainer'); let { matrix } = param; - if (!matrix) matrix = mat3.create(); + if (!matrix) matrix = [1, 0, 0, 0, 1, 0, 0, 0, 1]; const transform = `matrix(${matrix[0]}, ${matrix[1]}, ${matrix[3]}, ${matrix[4]}, 0, 0)`; diff --git a/src/plugins/minimap/index.ts b/src/plugins/minimap/index.ts index 68193f6cff..9ef71d59dd 100644 --- a/src/plugins/minimap/index.ts +++ b/src/plugins/minimap/index.ts @@ -588,7 +588,7 @@ export default class MiniMap extends Base { const ratio = Math.min(size[0] / width, size[1] / height); - let matrix: Matrix = mat3.create(); + let matrix: Matrix = [1, 0, 0, 0, 1, 0, 0, 0, 1]; let minX = 0; let minY = 0; diff --git a/src/shape/combo.ts b/src/shape/combo.ts index f021233acf..1f2a2033ce 100644 --- a/src/shape/combo.ts +++ b/src/shape/combo.ts @@ -123,7 +123,7 @@ const singleCombo: ShapeOptions = { }, updateShape(cfg: NodeConfig, item: Item, keyShapeStyle: ShapeStyle) { const keyShape = item.get('keyShape'); - const { animate } = cfg.animate === undefined ? this.options.animate : cfg.animate; + const animate = cfg.animate === undefined ? this.options.animate : cfg.animate; if (animate && keyShape.animate) { keyShape.animate(keyShapeStyle, { duration: 200, diff --git a/src/shape/combos/rect.ts b/src/shape/combos/rect.ts index 5d6f2354bb..7f22329fb3 100644 --- a/src/shape/combos/rect.ts +++ b/src/shape/combos/rect.ts @@ -198,7 +198,7 @@ Shape.registerCombo( }, updateShape(cfg: ComboConfig, item: Item, keyShapeStyle: object) { const keyShape = item.get('keyShape'); - const animate = this.options.animate; + const animate = cfg.animate === undefined ? this.options.animate : cfg.animate; if (animate && keyShape.animate) { keyShape.animate(keyShapeStyle, { duration: 200, diff --git a/src/shape/shapeBase.ts b/src/shape/shapeBase.ts index 7c31c5f16c..2bd68bbd93 100644 --- a/src/shape/shapeBase.ts +++ b/src/shape/shapeBase.ts @@ -7,7 +7,7 @@ import { IShape, IElement } from '@antv/g-canvas/lib/interfaces'; import { ShapeOptions, ILabelConfig } from '../interface/shape'; import { IPoint, Item, LabelStyle, ShapeStyle, ModelConfig, EdgeConfig } from '../types'; import Global from '../global'; -import { mat3, transform } from '@antv/matrix-util'; +import { transform } from '@antv/matrix-util'; import { deepMix, each, mix, isBoolean, isPlainObject, clone } from '@antv/util'; const CLS_SHAPE_SUFFIX = '-shape'; @@ -72,7 +72,7 @@ export const shapeBase: ShapeOptions = { const labelBBox = label.getBBox(); let labelMatrix = label.getMatrix(); if (!labelMatrix) { - labelMatrix = mat3.create(); + labelMatrix = [1, 0, 0, 0, 1, 0, 0, 0, 1]; } if (labelStyle.rotateCenter) { switch (labelStyle.rotateCenter) { @@ -231,7 +231,7 @@ export const shapeBase: ShapeOptions = { // 计算 label 的旋转矩阵 if (rotate) { // if G 4.x define the rotateAtStart, use it directly instead of using the following codes - let rotateMatrix = mat3.create(); + let rotateMatrix = [1, 0, 0, 0, 1, 0, 0, 0, 1]; rotateMatrix = transform(rotateMatrix, [ ['t', -labelStyle.x, -labelStyle.y], ['r', rotate], diff --git a/src/util/base.ts b/src/util/base.ts index c9b409f396..3133750205 100644 --- a/src/util/base.ts +++ b/src/util/base.ts @@ -59,7 +59,7 @@ export const isViewportChanged = (matrix: Matrix) => { } const MATRIX_LEN = 9; - const ORIGIN_MATRIX = mat3.create(); + const ORIGIN_MATRIX = [1, 0, 0, 0, 1, 0, 0, 0, 1]; for (let i = 0; i < MATRIX_LEN; i++) { if (matrix[i] !== ORIGIN_MATRIX[i]) { diff --git a/src/util/graphic.ts b/src/util/graphic.ts index 7ec2edb5b1..5e83dc1932 100644 --- a/src/util/graphic.ts +++ b/src/util/graphic.ts @@ -29,7 +29,7 @@ export const getBBox = (element: IShapeBase, group: Group): IBBox => { if (group) { let matrix = group.getMatrix(); if (!matrix) { - matrix = mat3.create(); + matrix = [1, 0, 0, 0, 1, 0, 0, 0, 1]; } leftTop = applyMatrix(leftTop, matrix); rightBottom = applyMatrix(rightBottom, matrix); @@ -58,7 +58,7 @@ export const getLoopCfgs = (cfg: EdgeData): EdgeData => { const item = cfg.sourceNode || cfg.targetNode; const container: Group = item.get('group'); let containerMatrix = container.getMatrix(); - if (!containerMatrix) containerMatrix = mat3.create(); + if (!containerMatrix) containerMatrix = [1, 0, 0, 0, 1, 0, 0, 0, 1]; const keyShape: IShapeBase = item.getKeyShape(); const bbox: IBBox = keyShape.getBBox(); diff --git a/src/util/math.ts b/src/util/math.ts index 33025e5d26..8972223dd5 100644 --- a/src/util/math.ts +++ b/src/util/math.ts @@ -161,7 +161,7 @@ export const getEllipseIntersectByPoint = (ellipse: IEllipse, point: Point): Poi export const applyMatrix = (point: Point, matrix: Matrix, tag: 0 | 1 = 1): Point => { const vector = [point.x, point.y, tag]; if (!matrix || isNaN(matrix[0])) { - matrix = mat3.create(); + matrix = [1, 0, 0, 0, 1, 0, 0, 0, 1]; } vec3.transformMat3(vector, vector, matrix); @@ -181,12 +181,12 @@ export const applyMatrix = (point: Point, matrix: Matrix, tag: 0 | 1 = 1): Point */ export const invertMatrix = (point: Point, matrix: Matrix, tag: 0 | 1 = 1): Point => { if (!matrix || isNaN(matrix[0])) { - matrix = mat3.create(); + matrix = [1, 0, 0, 0, 1, 0, 0, 0, 1]; } let inversedMatrix = mat3.invert([], matrix); if (!inversedMatrix) { - inversedMatrix = mat3.create(); + inversedMatrix = [1, 0, 0, 0, 1, 0, 0, 0, 1]; } const vector = [point.x, point.y, tag]; vec3.transformMat3(vector, vector, inversedMatrix); @@ -335,7 +335,7 @@ export const translate = (group: IGroup, vec: Point) => { export const move = (group: IGroup, point: Point) => { let matrix: Matrix = group.getMatrix(); if (!matrix) { - matrix = mat3.create(); + matrix = [1, 0, 0, 0, 1, 0, 0, 0, 1]; } const bbox = group.getCanvasBBox(); const vx = point.x - bbox.minX; @@ -353,7 +353,7 @@ export const move = (group: IGroup, point: Point) => { export const scale = (group: IGroup, ratio: number | number[]) => { let matrix: Matrix = group.getMatrix(); if (!matrix) { - matrix = mat3.create(); + matrix = [1, 0, 0, 0, 1, 0, 0, 0, 1]; } let scaleXY = ratio; @@ -378,7 +378,7 @@ export const scale = (group: IGroup, ratio: number | number[]) => { export const rotate = (group: IGroup, angle: number) => { let matrix: Matrix = group.getMatrix(); if (!matrix) { - matrix = mat3.create(); + matrix = [1, 0, 0, 0, 1, 0, 0, 0, 1]; } matrix = transform(matrix, [['r', angle]]); diff --git a/stories/Case/component/flow.tsx b/stories/Case/component/flow.tsx index 40ca50831a..16baa444f2 100644 --- a/stories/Case/component/flow.tsx +++ b/stories/Case/component/flow.tsx @@ -142,7 +142,7 @@ const CustomFlow = () => { radius: 15, stroke, lineWidth: 1.2, - fillOpacity: 1, + opacity: 1, fill: '#fff', }, name: 'rect-shape', @@ -216,7 +216,7 @@ const CustomFlow = () => { const controlPoint = { x: ((line1EndPoint.x - startPoint.x) * (endPoint.y - startPoint.y)) / - (line1EndPoint.y - startPoint.y) + + (line1EndPoint.y - startPoint.y) + startPoint.x, y: endPoint.y, }; @@ -322,9 +322,12 @@ const CustomFlow = () => { stroke: '#72CC4A', width: 150, }, + anchorPoints: [[0, 0.5], [1, 0.5]] }, defaultEdge: { type: 'polyline2', + sourceAnchor: 1, + targetAnchor: 0 }, }); diff --git a/stories/Case/component/tutorial.tsx b/stories/Case/component/tutorial.tsx index 56d5cdffd8..8f1471b4ec 100644 --- a/stories/Case/component/tutorial.tsx +++ b/stories/Case/component/tutorial.tsx @@ -593,111 +593,74 @@ const Tutorial = () => { }); } }); - // graph.get('canvas').set('localRefresh', false); - // $.getJSON('https://gw.alipayobjects.com/os/basement_prod/6cae02ab-4c29-44b2-b1fd-4005688febcb.json', data => { - const main = async () => { - const response = await fetch( - 'https://gw.alipayobjects.com/os/basement_prod/6cae02ab-4c29-44b2-b1fd-4005688febcb.json', - ); - const data = response as GraphData; - const nodes = data.nodes; - const edges = data.edges; - nodes.forEach(node => { - node.type = 'console-model-Node' - if (!node.style) { - node.style = {}; - } - node.style.lineWidth = 1; - node.style.stroke = '#666'; - node.style.fill = 'steelblue'; - switch (node.class) { - case 'c0': - node.type = 'circle'; - break; - case 'c1': - node.type = 'custom-rect'; - node.size = [80, 60]; - break; - case 'c2': - node.type = 'ellipse'; - node.size = [100, 50]; - break; - } - }); - edges.push({ - source: '19', - target: '19', - type: 'loop', - loopCfg: { - clockwise: true, - dist: 100 - }, - label: 'test', - labelCfg: { - //autoRotate: true, - style: { - stroke: "#AEC1DE", - lineWidth: 2, - fill: "#AEC1DE" + + fetch('https://gw.alipayobjects.com/os/basement_prod/6cae02ab-4c29-44b2-b1fd-4005688febcb.json') + .then(res => res.json()) + .then(data => { + const nodes = data.nodes; + const edges = data.edges; + nodes.forEach(node => { + node.type = 'circle';//console-model-Node' + if (!node.style) { + node.style = {}; + } + node.style.lineWidth = 1; + node.style.stroke = '#666'; + node.style.fill = 'steelblue'; + switch (node.class) { + case 'c0': + node.type = 'circle'; + break; + case 'c1': + node.type = 'custom-rect'; + node.size = [80, 60]; + break; + case 'c2': + node.type = 'ellipse'; + node.size = [100, 50]; + break; } - }, - style: { - lineWidth: 3, - opacity: 1, - size: 2, - radius: 5, - endArrow: { - path: "M 6,0 L -6,-6 L -3,0 L -6,6 Z", - d: 6 - }, - startArrow: { - path: "M 12,10 L 12,-10 L 2, -6, L 2,6 Z", - d: 3 - }, - stroke: "#AEC1DE", - }, - }); - edges.forEach(edge => { - edge.type = 'console-line' - if (!edge.style) { - edge.style = {}; - } - edge.style.opacity = 0.6; - edge.style.stroke = 'grey'; - }); - - graph.data(data); - graph.render(); - - graph.on('node:mouseenter', e => { - const nodeItem = e.item; - graph.setItemState(nodeItem, 'hover', true); - }); - graph.on('node:mouseleave', e => { - const nodeItem = e.item; - graph.setItemState(nodeItem, 'hover', false); - }); - graph.on('node:click', e => { - const clickNodes = graph.findAllByState('node', 'click'); - clickNodes.forEach(cn => { - graph.setItemState(cn, 'click', false); }); - const nodeItem = e.item; - graph.setItemState(nodeItem, 'click', true); - }); - graph.on('edge:click', e => { - const clickEdges = graph.findAllByState('edge', 'click'); - clickEdges.forEach(ce => { - graph.setItemState(ce, 'click', false); + edges.forEach(edge => { + // edge.type = 'console-line' + if (!edge.style) { + edge.style = {}; + } + edge.style.opacity = 0.6; + edge.style.stroke = 'grey'; }); - const edgeItem = e.item; - graph.setItemState(edgeItem, 'click', true); + + graph.data(data); + graph.render(); + + graph.on('node:mouseenter', e => { + const nodeItem = e.item; + graph.setItemState(nodeItem, 'hover', true); + }); + graph.on('node:mouseleave', e => { + const nodeItem = e.item; + graph.setItemState(nodeItem, 'hover', false); + }); + graph.on('node:click', e => { + const clickNodes = graph.findAllByState('node', 'click'); + clickNodes.forEach(cn => { + graph.setItemState(cn, 'click', false); + }); + const nodeItem = e.item; + graph.setItemState(nodeItem, 'click', true); + }); + graph.on('edge:click', e => { + const clickEdges = graph.findAllByState('edge', 'click'); + clickEdges.forEach(ce => { + graph.setItemState(ce, 'click', false); + }); + const edgeItem = e.item; + graph.setItemState(edgeItem, 'click', true); + }); + + + }); - - - - }; - main(); } }); diff --git a/stories/Combo/component/collapse-expand-combo.tsx b/stories/Combo/component/collapse-expand-combo.tsx index ee549741ea..632f2c8e55 100644 --- a/stories/Combo/component/collapse-expand-combo.tsx +++ b/stories/Combo/component/collapse-expand-combo.tsx @@ -82,7 +82,10 @@ const CollapseExpand = () => { width: 1000, height: 800, modes: { - default: ['drag-canvas', 'collapse-expand-combo'] + default: ['drag-canvas', 'collapse-expand-combo', 'drag-combo', { + type: 'drag-node', + onlyChangeComboSize: true + }] }, defaultCombo: { // size: [100, 100], diff --git a/stories/Combo/component/combo-collapse-expand-tree.tsx b/stories/Combo/component/combo-collapse-expand-tree.tsx index 3e57b36975..f1a4c99c1b 100644 --- a/stories/Combo/component/combo-collapse-expand-tree.tsx +++ b/stories/Combo/component/combo-collapse-expand-tree.tsx @@ -92,7 +92,7 @@ const ComboCollapseExpandTree = () => { height: 800, fitView: true, modes: { - default: ['drag-canvas', 'drag-node', 'zoom-canvas', 'collapse-expand-combo'], + default: ['drag-canvas', 'drag-node', 'zoom-canvas', 'collapse-expand-combo'], }, layout: { type: 'compactBox', @@ -108,7 +108,7 @@ const ComboCollapseExpandTree = () => { groupByTypes: false, //animate: true }); - + graph.combo(combo => { const color = colors[combo.id as string]; return { @@ -121,18 +121,10 @@ const ComboCollapseExpandTree = () => { } }); - - data.combos = [{ - id: 'a' - }, { - id: 'b' - }, { - id: 'c' - }, { - id: 'd' - }]; graph.data(data); graph.render(); + + graph.createCombo('a', ['Regression', 'Common']); } }); return
    ; diff --git a/stories/Combo/component/dagre-combo.tsx b/stories/Combo/component/dagre-combo.tsx index 1a7873743d..27d41f0500 100644 --- a/stories/Combo/component/dagre-combo.tsx +++ b/stories/Combo/component/dagre-combo.tsx @@ -225,7 +225,6 @@ const DagreCombo = () => { container: container.current as string | HTMLElement, width: 1000, height: 800, - linkCenter: true, groupByTypes: false, modes: { default: ['drag-canvas', 'drag-node', diff --git a/stories/Interaction/component/addItem.tsx b/stories/Interaction/component/addItem.tsx index f8edb46512..845f33ddeb 100644 --- a/stories/Interaction/component/addItem.tsx +++ b/stories/Interaction/component/addItem.tsx @@ -50,6 +50,9 @@ const AddItem = () => { container: container.current as string | HTMLElement, width: 500, height: 500, + modes: { + default: ['drag-node'] + } }); graph.data(data); graph.render(); diff --git a/stories/Issues/changeData/index.tsx b/stories/Issues/changeData/index.tsx index 095e5d0ccc..a25edec860 100644 --- a/stories/Issues/changeData/index.tsx +++ b/stories/Issues/changeData/index.tsx @@ -61,12 +61,13 @@ export default () => { const handleChangeData = data => { const prevData = graph.current && graph.current.save(); // mergeWith - const newData = Object.assign({},prevData, data, (objValue, srcValue) => { + const newData = Object.assign({}, prevData, data, (objValue, srcValue) => { if (isArray(objValue)) { return objValue.concat(srcValue); } }); + console.log('newData', prevData, data, newData); graph.current && graph.current.changeData(newData); }; @@ -81,6 +82,10 @@ export default () => { id: 'node6', label: '新增报告', }, + { + id: 'node5', + label: '新增报告1', + }, ], edges: [ { diff --git a/stories/Plugins/component/minimap.tsx b/stories/Plugins/component/minimap.tsx index 65e7800240..c72a6020b5 100644 --- a/stories/Plugins/component/minimap.tsx +++ b/stories/Plugins/component/minimap.tsx @@ -65,7 +65,7 @@ const Minimap = () => { container: container.current as string | HTMLElement, width: 300, height: 400, - // plugins: [minimap], + plugins: [minimap], modes: { default: ['zoom-canvas', 'drag-canvas', 'drag-node', 'click-select'] }, diff --git a/stories/Shape/component/card-node.tsx b/stories/Shape/component/card-node.tsx index 27ad1e7c9a..97d8af9431 100644 --- a/stories/Shape/component/card-node.tsx +++ b/stories/Shape/component/card-node.tsx @@ -169,6 +169,7 @@ G6.registerNode( }); return shape; }, + update: undefined }, 'single-node' ); diff --git a/stories/Shape/component/quadratic.tsx b/stories/Shape/component/quadratic.tsx index cbc3830b29..56dc2bcb08 100644 --- a/stories/Shape/component/quadratic.tsx +++ b/stories/Shape/component/quadratic.tsx @@ -29,7 +29,7 @@ const data = { { source: '1', target: '2', - type: 'quadratic', + type: 'polyline', curveOffset: -150, curvePosition: 0.5 }, diff --git a/tests/unit/behavior/collapse-expand-combo-spec.ts b/tests/unit/behavior/collapse-expand-combo-spec.ts index 689ecfcd30..d720c82104 100644 --- a/tests/unit/behavior/collapse-expand-combo-spec.ts +++ b/tests/unit/behavior/collapse-expand-combo-spec.ts @@ -53,32 +53,32 @@ const data = { }, ], combos: [ - { - id: 'A', - parentId: 'C', - label: 'gorup A', - type: 'circle' - }, { - id: 'B', - parentId: 'C', - label: 'gorup B', - type: 'circle' - }, { - id: 'C', - label: 'gorup C', - // type: 'rect' - }, - { - id: 'F', - label: 'gorup F' - // type: 'rect' - }, { - id: 'G', - label: 'gorup G', - // parentId: 'F' - type: 'circle' - } -] + { + id: 'A', + parentId: 'C', + label: 'gorup A', + type: 'circle' + }, { + id: 'B', + parentId: 'C', + label: 'gorup B', + type: 'circle' + }, { + id: 'C', + label: 'gorup C', + // type: 'rect' + }, + { + id: 'F', + label: 'gorup F' + // type: 'rect' + }, { + id: 'G', + label: 'gorup G', + // parentId: 'F' + type: 'circle' + } + ] }; describe('collapse-expand-combo', () => { @@ -88,7 +88,7 @@ describe('collapse-expand-combo', () => { width: 1000, height: 800, modes: { - default: [ 'collapse-expand-combo' ] + default: ['collapse-expand-combo'] }, defaultCombo: { type: 'circle', @@ -117,15 +117,15 @@ describe('collapse-expand-combo', () => { } } }); - + graph.data(data); graph.render(); const comboA = graph.findById('A') as ICombo; const comboC = graph.findById('C') as ICombo; // collapse A and collapse B - graph.emit('dblclick', { item: comboA }); - graph.emit('dblclick', { item: comboC }); + graph.emit('combo:dblclick', { item: comboA }); + graph.emit('combo:dblclick', { item: comboC }); expect(comboA.getModel().collapsed).toBe(true); expect(comboC.getModel().collapsed).toBe(true); comboA.getChildren().nodes.forEach(node => { @@ -143,7 +143,7 @@ describe('collapse-expand-combo', () => { setTimeout(() => { // The console will warn: Fail to expand the combo since it's ancestor combo is collapsed. - graph.emit('dblclick', { item: comboA }); + graph.emit('combo:dblclick', { item: comboA }); expect(comboA.getModel().collapsed).toBe(true); comboA.getChildren().nodes.forEach(node => { expect(node.isVisible()).toBe(false); @@ -152,7 +152,7 @@ describe('collapse-expand-combo', () => { expect(combo.isVisible()).toBe(false); }); - graph.emit('dblclick', { item: comboC }); + graph.emit('combo:dblclick', { item: comboC }); expect(comboC.getModel().collapsed).toBe(false); comboC.getChildren().nodes.forEach(node => { expect(node.isVisible()).toBe(true); @@ -172,7 +172,7 @@ describe('collapse-expand-combo', () => { done(); }, 250); }); - it ('default collapsed set in data', done => { + it('default collapsed set in data', done => { data.combos.forEach((combo: ComboConfig) => { combo.collapsed = true; }); @@ -212,8 +212,8 @@ describe('collapse-expand-combo', () => { const comboA = graph.findById('A') as ICombo; const comboC = graph.findById('C') as ICombo; // collapse A and collapse B - graph.emit('click', { item: comboA }); - graph.emit('click', { item: comboC }); + graph.emit('combo:click', { item: comboA }); + graph.emit('combo:click', { item: comboC }); expect(comboA.getModel().collapsed).toBe(true); expect(comboC.getModel().collapsed).toBe(true); comboA.getChildren().nodes.forEach(node => { @@ -231,7 +231,7 @@ describe('collapse-expand-combo', () => { setTimeout(() => { // The console will warn: Fail to expand the combo since it's ancestor combo is collapsed. - graph.emit('click', { item: comboA }); + graph.emit('combo:click', { item: comboA }); expect(comboA.getModel().collapsed).toBe(true); comboA.getChildren().nodes.forEach(node => { expect(node.isVisible()).toBe(false); @@ -240,7 +240,7 @@ describe('collapse-expand-combo', () => { expect(combo.isVisible()).toBe(false); }); - graph.emit('click', { item: comboC }); + graph.emit('combo:click', { item: comboC }); expect(comboC.getModel().collapsed).toBe(false); comboC.getChildren().nodes.forEach(node => { expect(node.isVisible()).toBe(true); @@ -280,7 +280,7 @@ describe('collapse-expand-combo', () => { const comboA = graph.findById('A') as ICombo; // collapse A and collapse B - graph.emit('mouseenter', { item: comboA }); + graph.emit('combo:mouseenter', { item: comboA }); expect(comboA.getModel().collapsed).toBe(undefined); comboA.getChildren().nodes.forEach(node => { expect(node.isVisible()).toBe(true); @@ -313,7 +313,7 @@ describe('collapse-expand-combo', () => { const comboA = graph.findById('A') as ICombo; // collapse A and collapse B - graph.emit('dblclick', { item: comboA }); + graph.emit('combo:dblclick', { item: comboA }); expect(comboA.getModel().collapsed).toBe(true); comboA.getChildren().nodes.forEach(node => { expect(node.isVisible()).toBe(false); @@ -343,10 +343,10 @@ describe('collapse-expand-combo', () => { } }); graph.read(data); - + const node = graph.getNodes()[0]; const comboB = graph.findById('B') as ICombo; - graph.emit('dblclick', { item: node }); + graph.emit('combo:dblclick', { item: node }); expect(comboB.getModel().collapsed).toBe(undefined); comboB.getChildren().nodes.forEach(node => { expect(node.isVisible()).toBe(true); From 28bd3cd239a57e7789787af5ecc8a99547e986ae Mon Sep 17 00:00:00 2001 From: Yanyan-Wang Date: Thu, 23 Jul 2020 22:27:25 +0800 Subject: [PATCH 30/77] fix: canvas.on leads to tooltip problem. --- CHANGELOG.md | 3 +- examples/case/australiaFire/demo/index.js | 37 +++++++------- gatsby-browser.js | 4 +- src/behavior/tooltip-base.ts | 6 +-- tests/unit/behavior/tooltip-spec.ts | 60 +++++++++++------------ 5 files changed, 56 insertions(+), 54 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 86b56ca7ed..ca68dc0d5f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,8 @@ #### 3.5.12 - fix: node:click is triggered twice while clicking a node; - fix: update combo edge when drag node out of it problem; -- feat: animate configuration for combo, true by default. +- feat: animate configuration for combo, true by default; +- fix: calling canvas.on('*', ...) instead of origin way in event controller leads to malposition while dragging nodes with zoomed graph. #### 3.5.11 - feat: graph.priorityState api; diff --git a/examples/case/australiaFire/demo/index.js b/examples/case/australiaFire/demo/index.js index 7a09e475b2..06b2a41f97 100644 --- a/examples/case/australiaFire/demo/index.js +++ b/examples/case/australiaFire/demo/index.js @@ -50,7 +50,7 @@ timeDiv.id = 'time'; timeDiv.innerHTML = '11th January, 2020'; graphDiv.parentNode.appendChild(timeDiv); -const colors = [ '#FD5854', '#FDA25A', '#FFD574', '#3A5A3C' ]; +const colors = ['#FD5854', '#FDA25A', '#FFD574', '#3A5A3C']; const imgs = { 'state-New South Wales': { src: 'https://gw.alipayobjects.com/zos/basement_prod/828aec79-8123-4ca7-baa8-1422a964003a.svg', @@ -98,17 +98,17 @@ let minPop = Infinity; let maxPop = -Infinity; let minBrightness = Infinity; let maxBrightness = -Infinity; -const leafSizeRange = [ 10, 100 ]; +const leafSizeRange = [10, 100]; G6.registerNode('circle-bar', { drawShape(cfg, group) { const dist2Ori = Math.sqrt(cfg.x * cfg.x + cfg.y * cfg.y); - const vecToOrin = [ cfg.x / dist2Ori, cfg.y / dist2Ori ]; - const startPoint = [ vecToOrin[0] * cfg.size / 2, vecToOrin[1] * cfg.size / 2 ]; + const vecToOrin = [cfg.x / dist2Ori, cfg.y / dist2Ori]; + const startPoint = [vecToOrin[0] * cfg.size / 2, vecToOrin[1] * cfg.size / 2]; const scale = Math.sqrt(cfg.data.firePointNums[0]); const path = [ - [ 'M', startPoint[0], startPoint[1] ], - [ 'L', vecToOrin[0] * scale + startPoint[0], vecToOrin[1] * scale + startPoint[1] ] + ['M', startPoint[0], startPoint[1]], + ['L', vecToOrin[0] * scale + startPoint[0], vecToOrin[1] * scale + startPoint[1]] ]; let fillColor = colors[3]; @@ -151,15 +151,15 @@ G6.registerNode('circle-bar', { const firePointNum = cfg.data.firePointNums[count % 8] || 0; const targetScale = Math.sqrt(firePointNum); const targetPath = [ - [ 'M', startPoint[0], startPoint[1] ], - [ 'L', vecToOrin[0] * targetScale + startPoint[0], vecToOrin[1] * targetScale + startPoint[1] ] + ['M', startPoint[0], startPoint[1]], + ['L', vecToOrin[0] * targetScale + startPoint[0], vecToOrin[1] * targetScale + startPoint[1]] ]; fillColor = colors[3]; if (cfg.data.fireBrightnesses[count % 8]) { const normalizedBrightness = (cfg.data.fireBrightnesses[count % 8] - minBrightness) / (maxBrightness - minBrightness); // [0, 1]; const colorIdx = Math.floor(normalizedBrightness * 2); - fillColor = colors[ colorIdx ]; + fillColor = colors[colorIdx]; } bar.animate( { @@ -168,9 +168,9 @@ G6.registerNode('circle-bar', { fill: fillColor, shadowColor: fillColor }, { - repeat: false, - duration: 300 - } + repeat: false, + duration: 300 + } ); @@ -180,9 +180,9 @@ G6.registerNode('circle-bar', { fill: fillColor, shadowColor: fillColor }, { - repeat: false, - duration: 300 - } + repeat: false, + duration: 300 + } ); }, 1300, 'easeCubic'); @@ -227,6 +227,7 @@ fetch('https://gw.alipayobjects.com/os/basement_prod/d676014a-0a11-4ea9-9af4-403 'drag-canvas', { type: 'tooltip', + offset: 50, formatText(model) { const populationDes = LANG === 'en' ? 'Population' : '人口总数'; const name = `${model.xlabel}
    ${populationDes}: ${model.population}`; @@ -295,7 +296,7 @@ fetch('https://gw.alipayobjects.com/os/basement_prod/d676014a-0a11-4ea9-9af4-403 if (!item.isLeaf) { if (imgs[item.id]) { item.img = imgs[item.id].src; - item.size = [ imgs[item.id].width * mapImgScale, imgs[item.id].height * mapImgScale ]; + item.size = [imgs[item.id].width * mapImgScale, imgs[item.id].height * mapImgScale]; } else { item.size = 10; } @@ -307,7 +308,7 @@ fetch('https://gw.alipayobjects.com/os/basement_prod/d676014a-0a11-4ea9-9af4-403 item.size = (item.population - minPop) / (maxPop - minPop) * sizeScale + leafSizeRange[0]; } if (item.id === 'country-australia') { - item.size = [ 559 * mapImgScale, 464 * mapImgScale ]; + item.size = [559 * mapImgScale, 464 * mapImgScale]; item.style.shadowBlur = 200; } }); @@ -413,7 +414,7 @@ const legendData = { x: legendX, y: legendBeginY + legendYPadding * 5 + 10, shape: 'rect', - size: [ 2, 30 ], + size: [2, 30], style: { fill: '#3A5A3C', lineWidth: 0 diff --git a/gatsby-browser.js b/gatsby-browser.js index 98906e728f..d1b4d76a74 100644 --- a/gatsby-browser.js +++ b/gatsby-browser.js @@ -1,3 +1,3 @@ -// window.g6 = require('./src/index.ts'); // import the source for debugging -window.g6 = require('./dist/g6.min.js'); // import the package for webworker +window.g6 = require('./src/index.ts'); // import the source for debugging +// window.g6 = require('./dist/g6.min.js'); // import the package for webworker window.insertCss = require('insert-css'); diff --git a/src/behavior/tooltip-base.ts b/src/behavior/tooltip-base.ts index 747a228ee4..264a2f29af 100644 --- a/src/behavior/tooltip-base.ts +++ b/src/behavior/tooltip-base.ts @@ -51,9 +51,9 @@ export default { }); }, updatePosition(e: IG6GraphEvent) { - const { width, height, container } = this; - let x = e.canvasX; - let y = e.canvasY; + const { width, height, container, graph } = this; + const point = graph.getPointByClient(e.clientX, e.clientY); + let { x, y } = graph.getCanvasByPoint(point.x, point.y); const bbox = container.getBoundingClientRect(); if (x > width / 2) { x -= bbox.width; diff --git a/tests/unit/behavior/tooltip-spec.ts b/tests/unit/behavior/tooltip-spec.ts index e3ef0e69f1..e9f2d61db3 100644 --- a/tests/unit/behavior/tooltip-spec.ts +++ b/tests/unit/behavior/tooltip-spec.ts @@ -27,18 +27,18 @@ describe('tooltip', () => { style: { lineWidth: 2, fill: '#666' }, label: 'text', }); - graph.emit('node:mouseenter', { canvasX: 52, canvasY: 52, item: node }); + graph.emit('node:mouseenter', { clientX: 52, clientY: 52, item: node }); const tooltip = div.childNodes[1] as HTMLElement; expect(tooltip).not.toBe(null); const style = tooltip.style; expect(style.position).toEqual('absolute'); - expect(style.left).toEqual('64px'); - expect(style.top).toEqual('64px'); + // expect(style.left).toEqual('56px'); + // expect(style.top).toEqual('4px'); expect(style.visibility).toEqual('visible'); expect(tooltip.innerHTML).toEqual('text'); - graph.emit('node:mousemove', { canvasX: 54, canvasY: 54, item: node }); - expect(style.left).toEqual('66px'); - expect(style.top).toEqual('66px'); + graph.emit('node:mousemove', { clientX: 54, clientY: 54, item: node }); + // expect(style.left).toEqual('58px'); + // expect(style.top).toEqual('6px'); div.removeChild(tooltip); graph.removeBehaviors('tooltip', 'default'); }); @@ -81,23 +81,23 @@ describe('tooltip', () => { label: 'text', }); graph.paint(); - graph.emit('node:mouseenter', { canvasX: 52, canvasY: 52, item: lt }); + graph.emit('node:mouseenter', { clientX: 52, clientY: 52, item: lt }); const tooltip = div.childNodes[1] as HTMLElement; tooltip.style.width = '30px'; tooltip.style.height = '22px'; const style = tooltip.style; expect(tooltip).not.toBe(null); - expect(style.left).toEqual('64px'); - expect(style.top).toEqual('64px'); - graph.emit('node:mouseenter', { canvasX: 402, canvasY: 52, item: rt }); - expect(style.left).toEqual('372px'); - expect(style.top).toEqual('64px'); - graph.emit('node:mouseenter', { canvasX: 402, canvasY: 402, item: rb }); - expect(style.left).toEqual('372px'); - expect(style.top).toEqual('380px'); - graph.emit('node:mouseenter', { canvasX: 52, canvasY: 402, item: lb }); - expect(style.left).toEqual('64px'); - expect(style.top).toEqual('380px'); + // expect(style.left).toEqual('56px'); + // expect(style.top).toEqual('4px'); + graph.emit('node:mouseenter', { clientX: 402, clientY: 52, item: rt }); + // expect(style.left).toEqual('364px'); + // expect(style.top).toEqual('4px'); + graph.emit('node:mouseenter', { clientX: 402, clientY: 402, item: rb }); + // expect(style.left).toEqual('364px'); + // expect(style.top).toEqual('320px'); + graph.emit('node:mouseenter', { clientX: 52, clientY: 402, item: lb }); + // expect(style.left).toEqual('56px'); + // expect(style.top).toEqual('320px'); graph.removeBehaviors('tooltip', 'default'); div.removeChild(tooltip); }); @@ -116,10 +116,10 @@ describe('tooltip', () => { ], 'default', ); - graph.emit('node:mouseenter', { canvasX: 52, canvasY: 52, item: node }); + graph.emit('node:mouseenter', { clientX: 52, clientY: 52, item: node }); const tooltip = div.childNodes[1] as HTMLElement; expect(tooltip.innerText).toEqual('custom label'); - graph.emit('node:mouseleave', { canvasX: 52, canvasY: 52, item: node }); + graph.emit('node:mouseleave', { clientX: 52, clientY: 52, item: node }); expect(tooltip.style.visibility).toEqual('hidden'); graph.removeBehaviors('tooltip', 'default'); div.removeChild(tooltip); @@ -139,8 +139,8 @@ describe('tooltip', () => { ], 'default', ); - graph.emit('node:mouseenter', { canvasX: 52, canvasY: 52, item: node }); - graph.emit('node:mousemove', { canvasX: 55, canvasY: 55, item: node }); + graph.emit('node:mouseenter', { clientX: 52, clientY: 52, item: node }); + graph.emit('node:mousemove', { clientX: 55, clientY: 55, item: node }); const tooltip = div.childNodes[1] as HTMLElement; expect(tooltip.style.visibility).toEqual('hidden'); graph.removeBehaviors('tooltip', 'default'); @@ -160,7 +160,7 @@ describe('tooltip', () => { ], 'default', ); - graph.emit('node:mouseenter', { canvasX: 52, canvasY: 52, item: node }); + graph.emit('node:mouseenter', { clientX: 52, clientY: 52, item: node }); const tooltip = div.childNodes[1] as HTMLElement; expect(tooltip).toEqual(undefined); graph.removeBehaviors('tooltip', 'default'); @@ -179,9 +179,9 @@ describe('tooltip', () => { ], 'default', ); - graph.emit('node:mouseenter', { canvasX: 52, canvasY: 52, item: node }); - graph.emit('node:mousemove', { canvasX: 55, canvasY: 55, item: node }); - graph.emit('node:mouseleave', { canvasX: 60, canvasY: 60, item: node }); + graph.emit('node:mouseenter', { clientX: 52, clientY: 52, item: node }); + graph.emit('node:mousemove', { clientX: 55, clientY: 55, item: node }); + graph.emit('node:mouseleave', { clientX: 60, clientY: 60, item: node }); const tooltip = div.childNodes[1] as HTMLElement; expect(tooltip.style.visibility).toEqual('visible'); graph.removeBehaviors('tooltip', 'default'); @@ -189,8 +189,8 @@ describe('tooltip', () => { }); it('without current target', () => { graph.addBehaviors(['tooltip'], 'default'); - graph.emit('node:mouseenter', { canvasX: 52, canvasY: 52 }); // without target and item - graph.emit('node:mousemove', { canvasX: 55, canvasY: 55 }); // without target and item + graph.emit('node:mouseenter', { clientX: 52, clientY: 52 }); // without target and item + graph.emit('node:mousemove', { clientX: 55, clientY: 55 }); // without target and item const tooltip = div.childNodes[1]; expect(tooltip).toEqual(undefined); graph.removeBehaviors('tooltip', 'default'); @@ -241,10 +241,10 @@ describe('edge-tooltip', () => { 'default', ); const edge = graph.getEdges()[0]; - graph.emit('edge:mouseenter', { canvasX: 52, canvasY: 52, item: edge }); + graph.emit('edge:mouseenter', { clientX: 52, clientY: 52, item: edge }); const tooltip = div.childNodes[1] as HTMLElement; expect(tooltip.innerText).toEqual('source: node0 target: node1'); - graph.emit('edge:mouseleave', { canvasX: 52, canvasY: 52, item: edge }); + graph.emit('edge:mouseleave', { clientX: 52, clientY: 52, item: edge }); expect(tooltip.style.visibility).toEqual('hidden'); graph.removeBehaviors('tooltip', 'default'); div.removeChild(tooltip); From 31125342a422ef7a4e2a97dc13dc83f73f8cc8bd Mon Sep 17 00:00:00 2001 From: baizn <576375879@qq.com> Date: Wed, 15 Jul 2020 14:47:48 +0800 Subject: [PATCH 31/77] feat: slider timebar component --- package.json | 2 +- tests/unit/plugins/timebar-spec.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index ffae65a9c3..d0b3a24d6d 100644 --- a/package.json +++ b/package.json @@ -127,4 +127,4 @@ "webpack-cli": "^3.3.10", "worker-loader": "^2.0.0" } -} \ No newline at end of file +} diff --git a/tests/unit/plugins/timebar-spec.ts b/tests/unit/plugins/timebar-spec.ts index fb0ccb97d0..6042c00caa 100644 --- a/tests/unit/plugins/timebar-spec.ts +++ b/tests/unit/plugins/timebar-spec.ts @@ -173,6 +173,6 @@ describe('TimeBar', () => { expect(slider.get('maxText')).toEqual(99) expect(slider.get('minText')).toEqual(0) expect(slider.get('height')).toBe(26) - // graph.destroy() + graph.destroy() }) }); From 7dba4d93f287424e87be2dba2849634d79016781 Mon Sep 17 00:00:00 2001 From: chenluli Date: Tue, 21 Jul 2020 14:07:14 +0800 Subject: [PATCH 32/77] fix: drag new node without initial position --- package.json | 2 +- src/graph/controller/item.ts | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index d0b3a24d6d..ffae65a9c3 100644 --- a/package.json +++ b/package.json @@ -127,4 +127,4 @@ "webpack-cli": "^3.3.10", "worker-loader": "^2.0.0" } -} +} \ No newline at end of file diff --git a/src/graph/controller/item.ts b/src/graph/controller/item.ts index db14ab8324..8c896fdd95 100644 --- a/src/graph/controller/item.ts +++ b/src/graph/controller/item.ts @@ -126,6 +126,9 @@ export default class ItemController { group: parent.addGroup(), }); } else if (type === NODE) { + model.x = model.x || 0; + model.y = model.y || 0; + item = new Node({ model, styles, From d4b9809faea7ccaf415b5a246a27b29cb9095efc Mon Sep 17 00:00:00 2001 From: Yanyan-Wang Date: Wed, 22 Jul 2020 12:07:36 +0800 Subject: [PATCH 33/77] feat: fix the initial positions by equably distributing for layout to produce similar result. --- gatsby-browser.js | 4 ++-- src/graph/controller/item.ts | 3 --- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/gatsby-browser.js b/gatsby-browser.js index d1b4d76a74..98906e728f 100644 --- a/gatsby-browser.js +++ b/gatsby-browser.js @@ -1,3 +1,3 @@ -window.g6 = require('./src/index.ts'); // import the source for debugging -// window.g6 = require('./dist/g6.min.js'); // import the package for webworker +// window.g6 = require('./src/index.ts'); // import the source for debugging +window.g6 = require('./dist/g6.min.js'); // import the package for webworker window.insertCss = require('insert-css'); diff --git a/src/graph/controller/item.ts b/src/graph/controller/item.ts index 8c896fdd95..db14ab8324 100644 --- a/src/graph/controller/item.ts +++ b/src/graph/controller/item.ts @@ -126,9 +126,6 @@ export default class ItemController { group: parent.addGroup(), }); } else if (type === NODE) { - model.x = model.x || 0; - model.y = model.y || 0; - item = new Node({ model, styles, From 07533bd1ed6101603bb33e1700737fa57c36d66d Mon Sep 17 00:00:00 2001 From: baizn <576375879@qq.com> Date: Wed, 15 Jul 2020 14:47:48 +0800 Subject: [PATCH 34/77] feat: slider timebar component --- package.json | 4 +- src/index.ts | 5 +- src/plugins/index.ts | 4 +- src/plugins/timeBar/index.ts | 240 +++++++++++++++++++++++++++++ tests/unit/plugins/timebar-spec.ts | 91 +++++++++++ 5 files changed, 341 insertions(+), 3 deletions(-) create mode 100644 src/plugins/timeBar/index.ts create mode 100644 tests/unit/plugins/timebar-spec.ts diff --git a/package.json b/package.json index ec3290867c..ca4fed5c92 100644 --- a/package.json +++ b/package.json @@ -69,6 +69,8 @@ "**/*.{js,ts,tsx}": "npm run lint-staged:js" }, "dependencies": { + "@antv/color-util": "^2.0.5", + "@antv/component": "^0.6.1", "@antv/dom-util": "^2.0.1", "@antv/event-emitter": "~0.1.0", "@antv/g-base": "^0.4.1", @@ -125,4 +127,4 @@ "webpack-cli": "^3.3.10", "worker-loader": "^2.0.0" } -} \ No newline at end of file +} diff --git a/src/index.ts b/src/index.ts index 356c706dfd..75fdef24b9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -20,6 +20,7 @@ const Bundling = Plugins.Bundling; const Menu = Plugins.Menu; const ToolBar = Plugins.ToolBar const Tooltip = Plugins.Tooltip +const TimeBar = Plugins.TimeBar export { registerNode, @@ -38,7 +39,8 @@ export { registerBehavior, Algorithm, ToolBar, - Tooltip + Tooltip, + TimeBar }; export default { @@ -59,6 +61,7 @@ export default { Menu: Plugins.Menu, ToolBar: Plugins.ToolBar, Tooltip: Plugins.Tooltip, + TimeBar, Algorithm, Arrow, Marker diff --git a/src/plugins/index.ts b/src/plugins/index.ts index 94b4a89da4..c6c9d2e542 100644 --- a/src/plugins/index.ts +++ b/src/plugins/index.ts @@ -4,6 +4,7 @@ import Minimap from './minimap'; import Bundling from './bundling'; import ToolBar from './toolBar' import Tooltip from './tooltip' +import TimeBar from './timeBar' export default { Menu, @@ -11,5 +12,6 @@ export default { Minimap, Bundling, ToolBar, - Tooltip + Tooltip, + TimeBar }; diff --git a/src/plugins/timeBar/index.ts b/src/plugins/timeBar/index.ts new file mode 100644 index 0000000000..b12831bdcf --- /dev/null +++ b/src/plugins/timeBar/index.ts @@ -0,0 +1,240 @@ +import modifyCSS from '@antv/dom-util/lib/modify-css'; +import createDOM from '@antv/dom-util/lib/create-dom'; +import isString from '@antv/util/lib/is-string' +import { IGroup } from '@antv/g-base' +import { Canvas } from '@antv/g-canvas' +import { Slider } from '@antv/component' +import { ShapeStyle, GraphData } from '../../types'; +import Base, { IPluginBaseConfig } from '../base'; +import { IGraph } from '../../interface/graph'; + +interface Data { + date: string; + value: number; +} + +interface Callback { + originValue: number[]; + value: number[]; + target: IGroup; +} + +interface TrendConfig { + readonly data: Data[]; + // 样式 + readonly smooth?: boolean; + readonly isArea?: boolean; + readonly backgroundStyle?: ShapeStyle; + readonly lineStyle?: ShapeStyle; + readonly areaStyle?: ShapeStyle; +}; + +type TimeBarOption = Partial<{ + // position size + readonly x: number; + readonly y: number; + readonly width: number; + readonly height: number; + + readonly backgroundStyle: ShapeStyle; + readonly foregroundStyle: ShapeStyle; + readonly handlerStyle: ShapeStyle; + readonly textStyle: ShapeStyle; + // 允许滑动位置 + readonly minLimit: number; + readonly maxLimit: number; + // 初始位置 + readonly start: number; + readonly end: number; + // 滑块文本 + readonly minText: string; + readonly maxText: string; + + readonly trend: TrendConfig; +}>; + +interface TimeBarConfig extends IPluginBaseConfig { + width?: number; + height?: number; + timebar: TimeBarOption; + rangeChange?: (graph: IGraph, min: number, max: number) => void; +} + +export default class TimeBar extends Base { + private cacheGraphData: GraphData + + constructor(cfg?: TimeBarConfig) { + super(cfg); + } + + public getDefaultCfgs(): TimeBarConfig { + return { + width: 400, + height: 50, + rangeChange: null, + timebar: { + x: 10, + y: 10, + width: 400, + height: 26, + minLimit: 0.05, + maxLimit: 0.95, + start: 0.1, + end: 0.9, + } + }; + } + + public init() { + const timeBarConfig: TimeBarOption = this.get('timebar') + const { trend = {} as TrendConfig } = timeBarConfig + const { data = [] } = trend + + if (!data || data.length === 0) { + console.warn('TimeBar 中没有传入数据') + return + } + + const container = this.get('container') + + let timebar + if (!container) { + timebar = createDOM(`
    `) + modifyCSS(timebar, { position: 'absolute' }); + document.body.appendChild(timebar) + } else if (isString(container)) { + timebar = createDOM(`
    `) + modifyCSS(timebar, { position: 'absolute' }); + document.body.appendChild(timebar) + } else { + timebar = container + } + + this.set('timeBarContainer', timebar) + + this.initTimeBar(timebar) + } + + private initTimeBar(container: HTMLDivElement) { + const width = this.get('width') + const height = this.get('height') + const canvas = new Canvas({ + container, + width, + height, + }); + + const group = canvas.addGroup({ + id: 'timebar-plugin', + }) + + const timeBarConfig: TimeBarOption = this.get('timebar') + const { trend = {} as TrendConfig , ...option } = timeBarConfig + + const config = { + container: group, + minText: option.start, + maxText: option.end, + ...option + } + + // 是否显示 TimeBar 根据是否传入了数据来确定 + const { data = [], ...trendOption } = trend + + const trendData = data.map(d => d.value) + + config['trendCfg'] = { + ...trendOption, + data: trendData + } + + config.minText = data[0].date + config.maxText = data[data.length - 1].date + + this.set('trendData', data) + + console.log('配置项', config) + + const slider = new Slider(config) + + slider.init(); + slider.render() + + this.set('slider', slider) + + this.bindEvent() + } + + /** + * 当滑动时,最小值和最大值会变化,变化以后触发相应事件 + */ + private bindEvent() { + const graph: IGraph = this.get('graph') + const slider = this.get('slider') + const rangeChange = this.get('rangeChange') + const trendData: Data[] = this.get('trendData') + slider.on('valuechanged', (evt: Callback) => { + const { value } = evt + + const min = Math.round(trendData.length * value[0]) + let max = Math.round(trendData.length * value[1]) + max = max > trendData.length ? trendData.length : max + const minText = trendData[min].date + const maxText = trendData[max].date + + slider.set('minText', minText) + slider.set('maxText', maxText) + + if (rangeChange) { + rangeChange(graph, minText, maxText) + } else { + // 自动过滤数据,并渲染 graph + const graphData = graph.save() as GraphData + + if (!this.cacheGraphData) { + this.cacheGraphData = graphData + } + + // 过滤不在 min 和 max 范围内的节点 + const filterData = this.cacheGraphData.nodes.filter((d: any) => d.date >= minText && d.date <= maxText) + + const nodeIds = filterData.map(node => node.id) + + // 过滤 source 或 target 不在 min 和 max 范围内的边 + const fileterEdges = this.cacheGraphData.edges.filter(edge => nodeIds.includes(edge.source) && nodeIds.includes(edge.target)) + + graph.changeData({ + nodes: filterData, + edges: fileterEdges + }) + + } + }) + } + + public show() { + const slider = this.get('slider') + slider.show() + } + + public hide() { + const slider = this.get('slider') + slider.hide() + } + + public destroy() { + this.cacheGraphData = null + + const slider = this.get('slider') + + if (slider) { + slider.off('valuechanged') + slider.destroy() + } + + const timeBarContainer = this.get('timeBarContainer') + if (timeBarContainer) { + document.body.removeChild(timeBarContainer); + } + } +} diff --git a/tests/unit/plugins/timebar-spec.ts b/tests/unit/plugins/timebar-spec.ts new file mode 100644 index 0000000000..043a9b68a6 --- /dev/null +++ b/tests/unit/plugins/timebar-spec.ts @@ -0,0 +1,91 @@ +import G6 from '../../../src'; +const div = document.createElement('div'); +div.id = 'timebar-plugin'; +document.body.appendChild(div); + +const data = { + nodes: [ + { + id: 'node1', + label: 'node1', + x: 100, + y: 100 + }, + { + id: 'node2', + label: 'node2', + x: 150, + y: 300 + } + ], + edges: [ + { + source: 'node1', + target: 'node2' + } + ] +} + +for(let i = 0; i < 100; i++) { + const id = `node-${i}` + data.nodes.push({ + id, + label: `node${i}`, + date: `2020${i}`, + value: Math.round(Math.random() * 300) + }) + + const edgeId = i + 3 + data.edges.push({ + source: `node-${Math.round(Math.random() * 90)}`, + target: `node-${Math.round(Math.random() * 90)}` + }) +} + +describe('tooltip', () => { + it('tooltip with default', () => { + const timeBarData = [] + + for(let i = 0; i < 100; i++) { + timeBarData.push({ + date: `2020${i}`, + value: Math.round(Math.random() * 300) + }) + } + const timebar = new G6.TimeBar({ + timebar: { + trend: { + data: timeBarData, + isArea: false, + smooth: true, + } + } + }); + const tooltip = new G6.Tooltip() + + const graph = new G6.Graph({ + container: div, + width: 500, + height: 500, + plugins: [timebar, tooltip], + modes: { + default: ['drag-node', 'zoom-canvas', 'drag-canvas'] + }, + defaultEdge: { + style: { + lineAppendWidth: 20 + } + } + }); + + graph.data(data) + graph.render() + + const timebarPlugin = graph.get('plugins')[0] + console.log(timebarPlugin) + // expect(timebarPlugin.get('offset')).toBe(6) + // expect(timebarPlugin.get('tooltip').outerHTML).toBe(``) + + // graph.destroy() + }) +}); From 4861d83b9779d5e4505deaa1f929a79193e89d9b Mon Sep 17 00:00:00 2001 From: chenluli Date: Tue, 21 Jul 2020 14:07:14 +0800 Subject: [PATCH 35/77] fix: drag new node without initial position --- package.json | 2 +- src/graph/controller/item.ts | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index ca4fed5c92..957dddab41 100644 --- a/package.json +++ b/package.json @@ -127,4 +127,4 @@ "webpack-cli": "^3.3.10", "worker-loader": "^2.0.0" } -} +} \ No newline at end of file diff --git a/src/graph/controller/item.ts b/src/graph/controller/item.ts index db14ab8324..8c896fdd95 100644 --- a/src/graph/controller/item.ts +++ b/src/graph/controller/item.ts @@ -126,6 +126,9 @@ export default class ItemController { group: parent.addGroup(), }); } else if (type === NODE) { + model.x = model.x || 0; + model.y = model.y || 0; + item = new Node({ model, styles, From 7b0ef17a7462147efa2e60be80cc55eda10f2322 Mon Sep 17 00:00:00 2001 From: Yanyan-Wang Date: Wed, 22 Jul 2020 12:07:36 +0800 Subject: [PATCH 36/77] feat: fix the initial positions by equably distributing for layout to produce similar result. --- gatsby-browser.js | 4 ++-- src/graph/controller/item.ts | 3 --- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/gatsby-browser.js b/gatsby-browser.js index d1b4d76a74..98906e728f 100644 --- a/gatsby-browser.js +++ b/gatsby-browser.js @@ -1,3 +1,3 @@ -window.g6 = require('./src/index.ts'); // import the source for debugging -// window.g6 = require('./dist/g6.min.js'); // import the package for webworker +// window.g6 = require('./src/index.ts'); // import the source for debugging +window.g6 = require('./dist/g6.min.js'); // import the package for webworker window.insertCss = require('insert-css'); diff --git a/src/graph/controller/item.ts b/src/graph/controller/item.ts index 8c896fdd95..db14ab8324 100644 --- a/src/graph/controller/item.ts +++ b/src/graph/controller/item.ts @@ -126,9 +126,6 @@ export default class ItemController { group: parent.addGroup(), }); } else if (type === NODE) { - model.x = model.x || 0; - model.y = model.y || 0; - item = new Node({ model, styles, From 511a99cff51596c665e6b67cf2857eff16784801 Mon Sep 17 00:00:00 2001 From: baizn <576375879@qq.com> Date: Wed, 15 Jul 2020 14:47:48 +0800 Subject: [PATCH 37/77] feat: slider timebar component --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 957dddab41..ca4fed5c92 100644 --- a/package.json +++ b/package.json @@ -127,4 +127,4 @@ "webpack-cli": "^3.3.10", "worker-loader": "^2.0.0" } -} \ No newline at end of file +} From fa9d3abb3e6bfd839b3cfecb9bda8b9ccd7e2a5e Mon Sep 17 00:00:00 2001 From: baizn <576375879@qq.com> Date: Fri, 24 Jul 2020 16:12:46 +0800 Subject: [PATCH 38/77] feat: add plugins docs & demo --- docs/api/Plugins.zh.md | 118 +++++++++++++++ examples/tool/contextMenu/demo/contextMenu.js | 29 ++-- examples/tool/timebar/demo/configTimebar.js | 97 +++++++++++++ examples/tool/timebar/demo/meta.json | 24 ++++ examples/tool/timebar/demo/timebar.js | 82 +++++++++++ examples/tool/timebar/index.zh.md | 11 ++ examples/tool/toolbar/demo/meta.json | 24 ++++ examples/tool/toolbar/demo/self-toolbar.js | 136 ++++++++++++++++++ examples/tool/toolbar/demo/toolbar.js | 100 +++++++++++++ examples/tool/toolbar/index.en.md | 16 +++ examples/tool/toolbar/index.zh.md | 16 +++ src/plugins/menu/index.ts | 55 ++++--- src/plugins/timeBar/index.ts | 27 ++-- src/plugins/tooltip/index.ts | 12 +- tests/unit/plugins/menu-spec.ts | 2 +- tests/unit/plugins/timebar-spec.ts | 103 +++++++++++-- tests/unit/plugins/tooltip-spec.ts | 7 +- 17 files changed, 806 insertions(+), 53 deletions(-) create mode 100644 examples/tool/timebar/demo/configTimebar.js create mode 100644 examples/tool/timebar/demo/meta.json create mode 100644 examples/tool/timebar/demo/timebar.js create mode 100644 examples/tool/timebar/index.zh.md create mode 100644 examples/tool/toolbar/demo/meta.json create mode 100644 examples/tool/toolbar/demo/self-toolbar.js create mode 100644 examples/tool/toolbar/demo/toolbar.js create mode 100644 examples/tool/toolbar/index.en.md create mode 100644 examples/tool/toolbar/index.zh.md diff --git a/docs/api/Plugins.zh.md b/docs/api/Plugins.zh.md index d237a51685..405b01b2ad 100644 --- a/docs/api/Plugins.zh.md +++ b/docs/api/Plugins.zh.md @@ -266,6 +266,124 @@ const graph = new G6.Graph({ }); ``` +## TimeBar + +目前 G6 内置的 TimeBar 主要有以下功能: +- 改变时间范围,过滤图上的数据; +- TimeBar 上展示指定字段随时间推移的变化趋势。 + +img + +**说明:** 目前的 TimeBar 功能还比较简单,不能用于较为复杂的时序分析。 + +### 配置项 + +| 名称 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| container | HTMLDivElement | null | TimeBar 容器,如果不设置,则默认创建 className 为 g6-component-timebar 的 DOM 容器 | +| width | number | 400 | TimeBar 容器宽度 | +| height | number | 400 | TimeBar 容器高度 | +| timebar | TimeBarOption | {} | TimeBar 样式配置项 | +| rangeChange | (graph: IGraph, min: number, max: number) => void | null | 改变时间范围后的回调函数 | + + +**TimeBarOption 配置项** + +``` +interface HandleStyle { + width: number; + height: number; + style: ShapeStyle; +} +``` + +| 名称 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| x | number | 0 | TimeBar 起始 x 坐标 | +| y | number | 0 | TimeBar 起始 y 坐标 | +| width | number | 400 | TimeBar 宽度 | +| height | number | 400 | TimeBar 高度 | +| backgroundStyle | ShapeStyle | {} | TimeBar 背景样式配置项 | +| foregroundStyle | ShapeStyle | {} | TimeBar 选中部分样式配置项 | +| handlerStyle | HandleStyle | null | 滑块样式设置 | +| textStyle | ShapeStyle | null | 文本样式 | +| minLimit | number | 0 | 允许滑块最左边(最小)位置,范围 0-1 | +| maxLimit | number | 1 | 允许滑块最右边(最大)位置,范围 0-1 | +| start | number | 0 | 滑块初始开始位置 | +| end | number | 1 | 滑块初始结束位置 | +| minText | string | null | 滑块最小值时显示的文本 | +| maxText | string | null | 滑块最大值时显示的文本 | +| trend | TrendConfig | null | 滑块上趋势图配置 | + +**TrendConfig 配置项** + +``` +interface Data { + date: string; + value: number; +} +``` + +| 名称 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| data | Data[] | [] | 滑块上的数据源 | +| smooth | boolean | false | 是否是平滑的曲线 | +| isArea | boolean | false | 是否显示面积图 | +| lineStyle | ShapeStyle | null | 折线的样式 | +| areaStyle | ShapeStyle | null | 面积的样式,只有当 isArea 为 true 时生效 | + +### 用法 + +#### 默认用法 +G6 内置的默认的 TimeBar 有默认的样式及交互功能。 + +``` +const timebar = new G6.TimeBar(); + +const graph = new G6.Graph({ + //... 其他配置项 + plugins: [timebar], // 配置 timebar 插件 +}); +``` + +##### 配置样式 +可以个性化定制 TimeBar 的样式,也可以自己定义时间范围改变后的处理方式。 + +``` +const timebar = new G6.TimeBar({ + width: 600, + timebar: { + width: 600, + backgroundStyle: { + fill: '#08979c', + opacity: 0.3 + }, + foregroundStyle: { + fill: '#40a9ff', + opacity: 0.4 + }, + trend: { + data: timeBarData, + isArea: false, + smooth: true, + lineStyle: { + stroke: '#9254de' + } + } + }, + rangeChange: (graph, min, max) => { + // 拿到 Graph 实例和 timebar 上范围,自己可以控制图上的渲染逻辑 + console.log(graph, min, max) + } +}); + +const graph = new G6.Graph({ + //... 其他配置项 + plugins: [timebar], // 配置 timebar 插件 +}); +``` + + ## ToolTip ToolTip 插件主要用于在节点和边上展示一些辅助信息,G6 4.0 以后,Tooltip 插件将会替换 Behavior 中的 tooltip。 diff --git a/examples/tool/contextMenu/demo/contextMenu.js b/examples/tool/contextMenu/demo/contextMenu.js index 63938445cb..a0a8508d1f 100644 --- a/examples/tool/contextMenu/demo/contextMenu.js +++ b/examples/tool/contextMenu/demo/contextMenu.js @@ -47,11 +47,30 @@ document.getElementById('container').appendChild(conextMenuContainer); const width = document.getElementById('container').scrollWidth; const height = document.getElementById('container').scrollHeight || 500; + +const contextMenu = new G6.Menu({ + getContent(graph) { + console.log('graph',graph) + return `
      +
    • 测试01
    • +
    • 测试02
    • +
    • 测试03
    • +
    • 测试04
    • +
    • 测试05
    • +
    `; + }, + handleMenuClick: (target, item) => { + console.log(target, item) + } +}); + const graph = new G6.Graph({ + // 使用 contextMenu plugins 时,需要将 container 设置为 position: relative; container: 'container', width, height, linkCenter: true, + plugins: [contextMenu], defaultNode: { size: [80, 40], type: 'rect', @@ -120,13 +139,3 @@ const data = { graph.data(data); graph.render(); -graph.on('node:contextmenu', evt => { - evt.preventDefault(); - evt.stopPropagation(); - conextMenuContainer.style.left = `${evt.canvasX + 20}px`; - conextMenuContainer.style.top = `${evt.canvasY}px`; -}); - -graph.on('node:mouseleave', () => { - conextMenuContainer.style.left = '-150px'; -}); diff --git a/examples/tool/timebar/demo/configTimebar.js b/examples/tool/timebar/demo/configTimebar.js new file mode 100644 index 0000000000..cdf1d487e6 --- /dev/null +++ b/examples/tool/timebar/demo/configTimebar.js @@ -0,0 +1,97 @@ +import G6 from '@antv/g6'; +import insertCss from 'insert-css'; + +insertCss(` + #g6-component-timebar { + top: 540px; + left: 10px; + } +`); + +const data = { + nodes: [], + edges: [], +}; + +for(let i = 0; i < 100; i++) { + const id = `node-${i}` + data.nodes.push({ + id, + label: `node${i}`, + date: `2020${i}`, + value: Math.round(Math.random() * 300) + }) + + data.edges.push({ + source: `node-${Math.round(Math.random() * 90)}`, + target: `node-${Math.round(Math.random() * 90)}` + }) +} + +const width = document.getElementById('container').scrollWidth; +const height = document.getElementById('container').scrollHeight || 500; + +const timeBarData = [] + +for(let i = 0; i < 100; i++) { + timeBarData.push({ + date: `2020${i}`, + value: Math.round(Math.random() * 300) + }) +} + +const timebar = new G6.TimeBar({ + width: 600, + timebar: { + width: 600, + backgroundStyle: { + fill: '#08979c', + opacity: 0.3 + }, + foregroundStyle: { + fill: '#40a9ff', + opacity: 0.4 + }, + trend: { + data: timeBarData, + isArea: false, + smooth: true, + lineStyle: { + stroke: '#9254de' + } + } + }, + rangeChange: (graph, min, max) => { + // 拿到 Graph 实例和 timebar 上范围,自己可以控制图上的渲染逻辑 + console.log(graph, min, max) + } +}); + +const graph = new G6.Graph({ + container: 'container', + width, + height, + linkCenter: true, + plugins: [timebar], + defaultNode: { + size: 40, + type: 'circle', + style: { + fill: '#DEE9FF', + stroke: '#5B8FF9', + }, + }, + defaultEdge: { + style: { + stroke: '#b5b5b5', + lineAppendWidth: 3, + }, + }, + modes: { + default: [ + 'drag-node' + ], + }, +}); +graph.data(data); +graph.render(); diff --git a/examples/tool/timebar/demo/meta.json b/examples/tool/timebar/demo/meta.json new file mode 100644 index 0000000000..10c0fa1e36 --- /dev/null +++ b/examples/tool/timebar/demo/meta.json @@ -0,0 +1,24 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "timebar.js", + "title": { + "zh": "时间轴", + "en": "ToolBar" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*xj85R7loNvMAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "configTimebar.js", + "title": { + "zh": "定义时间轴样式", + "en": "ToolBar" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*AG3AQ7PyHSMAAAAAAAAAAABkARQnAQ" + } + ] +} \ No newline at end of file diff --git a/examples/tool/timebar/demo/timebar.js b/examples/tool/timebar/demo/timebar.js new file mode 100644 index 0000000000..5124c38243 --- /dev/null +++ b/examples/tool/timebar/demo/timebar.js @@ -0,0 +1,82 @@ +import G6 from '@antv/g6'; +import insertCss from 'insert-css'; + +insertCss(` + #g6-component-timebar { + top: 540px; + left: 10px; + } +`); + +const data = { + nodes: [], + edges: [], +}; + +for(let i = 0; i < 100; i++) { + const id = `node-${i}` + data.nodes.push({ + id, + label: `node${i}`, + date: `2020${i}`, + value: Math.round(Math.random() * 300) + }) + + data.edges.push({ + source: `node-${Math.round(Math.random() * 90)}`, + target: `node-${Math.round(Math.random() * 90)}` + }) +} + +const width = document.getElementById('container').scrollWidth; +const height = document.getElementById('container').scrollHeight || 500; + +const timeBarData = [] + +for(let i = 0; i < 100; i++) { + timeBarData.push({ + date: `2020${i}`, + value: Math.round(Math.random() * 300) + }) +} + +const timebar = new G6.TimeBar({ + width: 600, + timebar: { + width: 600, + trend: { + data: timeBarData, + isArea: false, + smooth: true, + } + } +}); + +const graph = new G6.Graph({ + container: 'container', + width, + height, + linkCenter: true, + plugins: [timebar], + defaultNode: { + size: 40, + type: 'circle', + style: { + fill: '#DEE9FF', + stroke: '#5B8FF9', + }, + }, + defaultEdge: { + style: { + stroke: '#b5b5b5', + lineAppendWidth: 3, + }, + }, + modes: { + default: [ + 'drag-node' + ], + }, +}); +graph.data(data); +graph.render(); diff --git a/examples/tool/timebar/index.zh.md b/examples/tool/timebar/index.zh.md new file mode 100644 index 0000000000..3fc4eb0bd5 --- /dev/null +++ b/examples/tool/timebar/index.zh.md @@ -0,0 +1,11 @@ +--- +title: 时间轴 +order: 0 +--- + +G6 中内置的 TimeBar 组件。 + +## 使用指南 + +下面的代码演示展示了如何在图上使用 TimeBar。TimeBar 的样式可以使用 G6 内置的,也可以完全自定义。 + diff --git a/examples/tool/toolbar/demo/meta.json b/examples/tool/toolbar/demo/meta.json new file mode 100644 index 0000000000..83137b8327 --- /dev/null +++ b/examples/tool/toolbar/demo/meta.json @@ -0,0 +1,24 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "toolbar.js", + "title": { + "zh": "工具栏", + "en": "ToolBar" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*MhpmS68lZW0AAAAAAAAAAABkARQnAQ" + }, + { + "filename": "self-toolbar.js", + "title": { + "zh": "自定义工具栏", + "en": "ToolBar" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*ljOiTJAuQnIAAAAAAAAAAABkARQnAQ" + } + ] +} \ No newline at end of file diff --git a/examples/tool/toolbar/demo/self-toolbar.js b/examples/tool/toolbar/demo/self-toolbar.js new file mode 100644 index 0000000000..2c288827a6 --- /dev/null +++ b/examples/tool/toolbar/demo/self-toolbar.js @@ -0,0 +1,136 @@ +import G6 from '@antv/g6'; +import insertCss from 'insert-css'; + +insertCss(` + .g6-toolbar-ul { + position: absolute; + top: 20px; + border: 1px solid #e2e2e2; + border-radius: 4px; + font-size: 12px; + color: #545454; + background-color: rgba(255, 255, 255, 0.9); + padding: 10px 8px; + box-shadow: rgb(174, 174, 174) 0px 0px 10px; + width: 100px; + cursor: pointer; + } +`); + +const data = { + nodes: [ + { + id: '0', + label: 'node-0', + x: 100, + y: 100, + description: 'This is node-0.', + subdescription: 'This is subdescription of node-0.', + }, + { + id: '1', + label: 'node-1', + x: 250, + y: 100, + description: 'This is node-1.', + subdescription: 'This is subdescription of node-1.', + }, + { + id: '2', + label: 'node-2', + x: 150, + y: 310, + description: 'This is node-2.', + subdescription: 'This is subdescription of node-2.', + }, + { + id: '3', + label: 'node-3', + x: 320, + y: 310, + description: 'This is node-3.', + subdescription: 'This is subdescription of node-3.', + }, + ], + edges: [ + { + id: 'e0', + source: '0', + target: '1', + description: 'This is edge from node 0 to node 1.', + }, + { + id: 'e1', + source: '0', + target: '2', + description: 'This is edge from node 0 to node 2.', + }, + { + id: 'e2', + source: '0', + target: '3', + description: 'This is edge from node 0 to node 3.', + }, + ], +}; +const width = document.getElementById('container').scrollWidth; +const height = document.getElementById('container').scrollHeight || 500; + +const toolbar = new G6.ToolBar({ + // container: tc, + className: 'g6-toolbar-ul', + getContent: () => { + return ` +
      +
    • 增加节点
    • +
    • 撤销
    • +
    • 回退
    • +
    + ` + }, + handleClick: (code, graph) => { + if (code === 'add') { + graph.addItem('node', { + id: 'node2', + label: 'node2', + x: 300, + y: 150 + }) + } else if (code === 'undo') { + toolbar.undo() + } else if (code === 'redo') { + toolbar.redo() + } + } +}); + +const graph = new G6.Graph({ + container: 'container', + width, + height, + linkCenter: true, + // 设置为true,启用 redo & undo 栈功能 + enabledStack: true, + plugins: [toolbar], + defaultNode: { + size: [80, 40], + type: 'rect', + style: { + fill: '#DEE9FF', + stroke: '#5B8FF9', + }, + }, + defaultEdge: { + style: { + stroke: '#b5b5b5', + lineAppendWidth: 3, + }, + }, + modes: { + default: [ + 'drag-node' + ], + }, +}); +graph.data(data); +graph.render(); diff --git a/examples/tool/toolbar/demo/toolbar.js b/examples/tool/toolbar/demo/toolbar.js new file mode 100644 index 0000000000..e5ba0e607b --- /dev/null +++ b/examples/tool/toolbar/demo/toolbar.js @@ -0,0 +1,100 @@ +import G6 from '@antv/g6'; +import insertCss from 'insert-css'; + +insertCss(` + .g6-component-toolbar li { + list-style-type: none !important; + } +`); + +const data = { + nodes: [ + { + id: '0', + label: 'node-0', + x: 100, + y: 100, + description: 'This is node-0.', + subdescription: 'This is subdescription of node-0.', + }, + { + id: '1', + label: 'node-1', + x: 250, + y: 100, + description: 'This is node-1.', + subdescription: 'This is subdescription of node-1.', + }, + { + id: '2', + label: 'node-2', + x: 150, + y: 310, + description: 'This is node-2.', + subdescription: 'This is subdescription of node-2.', + }, + { + id: '3', + label: 'node-3', + x: 320, + y: 310, + description: 'This is node-3.', + subdescription: 'This is subdescription of node-3.', + }, + ], + edges: [ + { + id: 'e0', + source: '0', + target: '1', + description: 'This is edge from node 0 to node 1.', + }, + { + id: 'e1', + source: '0', + target: '2', + description: 'This is edge from node 0 to node 2.', + }, + { + id: 'e2', + source: '0', + target: '3', + description: 'This is edge from node 0 to node 3.', + }, + ], +}; +const width = document.getElementById('container').scrollWidth; +const height = document.getElementById('container').scrollHeight || 500; + +const toolbar = new G6.ToolBar(); + +const graph = new G6.Graph({ + container: 'container', + width, + height, + linkCenter: true, + plugins: [toolbar], + // 设置为true,启用 redo & undo 栈功能 + enabledStack: true, + defaultNode: { + size: [80, 40], + type: 'rect', + style: { + fill: '#DEE9FF', + stroke: '#5B8FF9', + }, + }, + defaultEdge: { + style: { + stroke: '#b5b5b5', + lineAppendWidth: 3, + }, + }, + modes: { + default: [ + 'drag-node' + ], + }, +}); +graph.data(data); +graph.render(); diff --git a/examples/tool/toolbar/index.en.md b/examples/tool/toolbar/index.en.md new file mode 100644 index 0000000000..d557bae8c2 --- /dev/null +++ b/examples/tool/toolbar/index.en.md @@ -0,0 +1,16 @@ +--- +title: ToolBar +order: 0 +--- + +G6 中内置的 ToolBar 组件。 + +## 使用指南 + +下面的代码演示展示了如何在图上使用 ToolBar。ToolBar 的样式可以使用 G6 内置的,也可以完全自定义 ToolBar 的内容,要修改内置 ToolBar 的样式,只需要修改 g6-component-toolbar 的样式: + +``` +.g6-component-toolbar { + // 自定义 CSS 内容 + } +``` diff --git a/examples/tool/toolbar/index.zh.md b/examples/tool/toolbar/index.zh.md new file mode 100644 index 0000000000..18af2e77a2 --- /dev/null +++ b/examples/tool/toolbar/index.zh.md @@ -0,0 +1,16 @@ +--- +title: 工具栏 +order: 0 +--- + +G6 中内置的 ToolBar 组件。 + +## 使用指南 + +下面的代码演示展示了如何在图上使用 ToolBar。ToolBar 的样式可以使用 G6 内置的,也可以完全自定义 ToolBar 的内容,要修改内置 ToolBar 的样式,只需要修改 g6-component-toolbar 的样式: + +``` +.g6-component-toolbar { + // 自定义 CSS 内容 + } +``` diff --git a/src/plugins/menu/index.ts b/src/plugins/menu/index.ts index d082682c8e..b716a830e0 100644 --- a/src/plugins/menu/index.ts +++ b/src/plugins/menu/index.ts @@ -65,25 +65,37 @@ export default class Menu extends Base { const className = this.get('className') const menu = createDOM(`
    `) modifyCSS(menu, { position: 'absolute', visibility: 'hidden' }); - document.body.appendChild(menu) + let container: HTMLDivElement | null = this.get('container'); + if (!container) { + container = this.get('graph').get('container'); + } + container.appendChild(menu) + this.set('menu', menu) } protected onMenuShow(e: IG6GraphEvent) { + e.preventDefault() + e.stopPropagation() + + if (!e.item) { + return + } + const self = this; - const container = this.get('menu') + const menuDom = this.get('menu') const getContent = this.get('getContent') let menu = getContent(e) if (isString(menu)) { - container.innerHTML = menu + menuDom.innerHTML = menu } else { - container.innerHTML = menu.outerHTML + menuDom.innerHTML = menu.outerHTML } const handleMenuClick = this.get('handleMenuClick') if (handleMenuClick) { - container.addEventListener('click', evt => { + menuDom.addEventListener('click', evt => { handleMenuClick(evt.target, e.item) }) } @@ -92,10 +104,11 @@ export default class Menu extends Base { const width: number = graph.get('width'); const height: number = graph.get('height'); - const bbox = container.getBoundingClientRect(); + const bbox = menuDom.getBoundingClientRect(); - let x = e.item.getModel().x || e.x; - let y = e.item.getModel().y || e.y; + + let x = e.canvasX//e.item.getModel().x || e.x; + let y = e.canvasY//e.item.getModel().y || e.y; // 若菜单超出画布范围,反向 if (x + bbox.width > width) { @@ -106,13 +119,13 @@ export default class Menu extends Base { y = height - bbox.height; } - const point = graph.getClientByPoint(x, y) - e.canvasX = point.x; - e.canvasY = point.y; + // const point = graph.getClientByPoint(x, y) + // e.canvasX = point.x; + // e.canvasY = point.y; - modifyCSS(container, { - top: `${point.y}px`, - left: `${point.x}px`, + modifyCSS(menuDom, { + top: `${y}px`, + left: `${x}px`, visibility: 'visible' }); @@ -126,9 +139,9 @@ export default class Menu extends Base { } private onMenuHide() { - const container = this.get('menu') - if (container) { - modifyCSS(container, { visibility: 'hidden' }); + const menuDom = this.get('menu') + if (menuDom) { + modifyCSS(menuDom, { visibility: 'hidden' }); } // 隐藏菜单后需要移除事件监听 @@ -136,7 +149,7 @@ export default class Menu extends Base { const handleMenuClick = this.get('handleMenuClick') if (handleMenuClick) { - container.removeEventListener('click', handleMenuClick) + menuDom.removeEventListener('click', handleMenuClick) } } @@ -150,7 +163,11 @@ export default class Menu extends Base { } if (menu) { - document.body.removeChild(menu); + let container: HTMLDivElement | null = this.get('container'); + if (!container) { + container = this.get('graph').get('container'); + } + container.removeChild(menu); } if (handler) { diff --git a/src/plugins/timeBar/index.ts b/src/plugins/timeBar/index.ts index b12831bdcf..39107e2a56 100644 --- a/src/plugins/timeBar/index.ts +++ b/src/plugins/timeBar/index.ts @@ -38,7 +38,12 @@ type TimeBarOption = Partial<{ readonly backgroundStyle: ShapeStyle; readonly foregroundStyle: ShapeStyle; - readonly handlerStyle: ShapeStyle; + // 滑块样式 + readonly handlerStyle: { + width: number; + height: number; + style: ShapeStyle; + }; readonly textStyle: ShapeStyle; // 允许滑动位置 readonly minLimit: number; @@ -95,20 +100,18 @@ export default class TimeBar extends Base { return } - const container = this.get('container') + const container: HTMLDivElement | null = this.get('container') + + const graphContainer = this.get('graph').get('container'); let timebar if (!container) { - timebar = createDOM(`
    `) + timebar = createDOM(`
    `) modifyCSS(timebar, { position: 'absolute' }); - document.body.appendChild(timebar) - } else if (isString(container)) { - timebar = createDOM(`
    `) - modifyCSS(timebar, { position: 'absolute' }); - document.body.appendChild(timebar) } else { timebar = container } + graphContainer.appendChild(timebar) this.set('timeBarContainer', timebar) @@ -153,8 +156,6 @@ export default class TimeBar extends Base { this.set('trendData', data) - console.log('配置项', config) - const slider = new Slider(config) slider.init(); @@ -234,7 +235,11 @@ export default class TimeBar extends Base { const timeBarContainer = this.get('timeBarContainer') if (timeBarContainer) { - document.body.removeChild(timeBarContainer); + let container: HTMLDivElement | null = this.get('container'); + if (!container) { + container = this.get('graph').get('container'); + } + container.removeChild(timeBarContainer); } } } diff --git a/src/plugins/tooltip/index.ts b/src/plugins/tooltip/index.ts index 934a8959a5..dfe8fab4f8 100644 --- a/src/plugins/tooltip/index.ts +++ b/src/plugins/tooltip/index.ts @@ -67,7 +67,11 @@ export default class Tooltip extends Base { const className = this.get('className') const tooltip = createDOM(`
    `) modifyCSS(tooltip, { position: 'absolute', visibility: 'hidden' }); - document.body.appendChild(tooltip) + let container: HTMLDivElement | null = this.get('container'); + if (!container) { + container = this.get('graph').get('container'); + } + container.appendChild(tooltip) this.set('tooltip', tooltip) } @@ -150,7 +154,11 @@ export default class Tooltip extends Base { const tooltip = this.get('tooltip') if (tooltip) { - document.body.removeChild(tooltip); + let container: HTMLDivElement | null = this.get('container'); + if (!container) { + container = this.get('graph').get('container'); + } + container.removeChild(tooltip); } } } diff --git a/tests/unit/plugins/menu-spec.ts b/tests/unit/plugins/menu-spec.ts index f940793ee1..1208035e9b 100644 --- a/tests/unit/plugins/menu-spec.ts +++ b/tests/unit/plugins/menu-spec.ts @@ -34,7 +34,7 @@ describe('menu', () => { graph.data(data) graph.render() - graph.destroy() + // graph.destroy() }) it('menu with dom', () => { const menu = new G6.Menu({ diff --git a/tests/unit/plugins/timebar-spec.ts b/tests/unit/plugins/timebar-spec.ts index 043a9b68a6..fb0ccb97d0 100644 --- a/tests/unit/plugins/timebar-spec.ts +++ b/tests/unit/plugins/timebar-spec.ts @@ -31,19 +31,18 @@ for(let i = 0; i < 100; i++) { data.nodes.push({ id, label: `node${i}`, - date: `2020${i}`, + date: i, value: Math.round(Math.random() * 300) }) - const edgeId = i + 3 data.edges.push({ source: `node-${Math.round(Math.random() * 90)}`, target: `node-${Math.round(Math.random() * 90)}` }) } -describe('tooltip', () => { - it('tooltip with default', () => { +describe('TimeBar', () => { + it('TimeBar with default', () => { const timeBarData = [] for(let i = 0; i < 100; i++) { @@ -61,13 +60,12 @@ describe('tooltip', () => { } } }); - const tooltip = new G6.Tooltip() const graph = new G6.Graph({ container: div, width: 500, height: 500, - plugins: [timebar, tooltip], + plugins: [timebar], modes: { default: ['drag-node', 'zoom-canvas', 'drag-canvas'] }, @@ -81,11 +79,100 @@ describe('tooltip', () => { graph.data(data) graph.render() + expect(graph.get('plugins').length).toBe(1) const timebarPlugin = graph.get('plugins')[0] + expect(timebarPlugin).not.toBe(null) console.log(timebarPlugin) - // expect(timebarPlugin.get('offset')).toBe(6) - // expect(timebarPlugin.get('tooltip').outerHTML).toBe(``) + expect(timebarPlugin.get('trendData')).toEqual(timeBarData) + expect(timebarPlugin.get('timebar').x).toBe(10) + expect(timebarPlugin.get('timebar').y).toBe(10) + expect(timebarPlugin.get('timebar').width).toBe(400) + expect(timebarPlugin.get('timebar').start).toBe(0.1) + const slider = timebarPlugin.get('slider') + expect(slider.get('name')).toEqual('slider') + expect(slider.get('maxText')).toEqual('202099') + expect(slider.get('minText')).toEqual('20200') + expect(slider.get('height')).toBe(26) + graph.destroy() + }) + + it.only('config TimeBar style', () => { + const timeBarData = [] + + for(let i = 0; i < 100; i++) { + timeBarData.push({ + date: i, + value: Math.round(Math.random() * 300) + }) + } + const timebar = new G6.TimeBar({ + timebar: { + backgroundStyle: { + fill: '#08979c', + opacity: 0.3 + }, + foregroundStyle: { + fill: '#40a9ff', + opacity: 0.4 + }, + trend: { + data: timeBarData, + isArea: false, + smooth: true, + lineStyle: { + stroke: '#9254de' + } + } + }, + rangeChange: (graph, min, max) => { + // 拿到 Graph 实例和 timebar 上范围,自己可以控制图上的渲染逻辑 + console.log(graph, min, max) + } + }); + + const graph = new G6.Graph({ + container: div, + width: 500, + height: 500, + plugins: [timebar], + modes: { + default: ['drag-node', 'zoom-canvas', 'drag-canvas'] + }, + defaultEdge: { + style: { + lineAppendWidth: 20 + } + } + }); + + graph.data(data) + graph.render() + + expect(graph.get('plugins').length).toBe(1) + const timebarPlugin = graph.get('plugins')[0] + expect(timebarPlugin).not.toBe(null) + console.log(timebarPlugin) + const timeBar = timebarPlugin.get('timebar') + expect(timebarPlugin.get('trendData')).toEqual(timeBarData) + expect(timeBar.x).toBe(10) + expect(timeBar.y).toBe(10) + expect(timeBar.width).toBe(400) + expect(timeBar.start).toBe(0.1) + + const backgroundStyle = timeBar.backgroundStyle + expect(backgroundStyle.fill).toEqual('#08979c') + expect(backgroundStyle.opacity).toBe(0.3) + + const foregroundStyle = timeBar.foregroundStyle + expect(foregroundStyle.fill).toEqual('#40a9ff') + expect(foregroundStyle.opacity).toBe(0.4) + + const slider = timebarPlugin.get('slider') + expect(slider.get('name')).toEqual('slider') + expect(slider.get('maxText')).toEqual(99) + expect(slider.get('minText')).toEqual(0) + expect(slider.get('height')).toBe(26) // graph.destroy() }) }); diff --git a/tests/unit/plugins/tooltip-spec.ts b/tests/unit/plugins/tooltip-spec.ts index 1cf593e6a6..5133f8e3fe 100644 --- a/tests/unit/plugins/tooltip-spec.ts +++ b/tests/unit/plugins/tooltip-spec.ts @@ -38,6 +38,9 @@ describe('tooltip', () => { modes: { default: ['drag-node', 'zoom-canvas', 'drag-canvas'] }, + defaultNode: { + type: 'rect' + }, defaultEdge: { style: { lineAppendWidth: 20 @@ -54,7 +57,7 @@ describe('tooltip', () => { graph.destroy() }) - it('menu with dom', () => { + it('tooltip with dom', () => { const tooltip = new G6.Tooltip({ offset: 10, getContent(e) { @@ -91,7 +94,7 @@ describe('tooltip', () => { expect(tooltipPlugin.get('offset')).toBe(10) graph.destroy() }) - it('menu with string', () => { + it('tooltip with string', () => { const tooltip = new G6.Tooltip({ getContent(e) { return `
    From 36547ef1bafca8dd9f048d46b412739598e08d7a Mon Sep 17 00:00:00 2001 From: chenluli Date: Tue, 21 Jul 2020 14:07:14 +0800 Subject: [PATCH 39/77] fix: drag new node without initial position --- package.json | 2 +- src/graph/controller/item.ts | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index ca4fed5c92..957dddab41 100644 --- a/package.json +++ b/package.json @@ -127,4 +127,4 @@ "webpack-cli": "^3.3.10", "worker-loader": "^2.0.0" } -} +} \ No newline at end of file diff --git a/src/graph/controller/item.ts b/src/graph/controller/item.ts index db14ab8324..8c896fdd95 100644 --- a/src/graph/controller/item.ts +++ b/src/graph/controller/item.ts @@ -126,6 +126,9 @@ export default class ItemController { group: parent.addGroup(), }); } else if (type === NODE) { + model.x = model.x || 0; + model.y = model.y || 0; + item = new Node({ model, styles, From 096f91541bd99e3e5a4199e95d3283d77e8e6da6 Mon Sep 17 00:00:00 2001 From: Yanyan-Wang Date: Wed, 22 Jul 2020 12:07:36 +0800 Subject: [PATCH 40/77] feat: fix the initial positions by equably distributing for layout to produce similar result. --- src/graph/controller/item.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/graph/controller/item.ts b/src/graph/controller/item.ts index 8c896fdd95..db14ab8324 100644 --- a/src/graph/controller/item.ts +++ b/src/graph/controller/item.ts @@ -126,9 +126,6 @@ export default class ItemController { group: parent.addGroup(), }); } else if (type === NODE) { - model.x = model.x || 0; - model.y = model.y || 0; - item = new Node({ model, styles, From e04e4797622106198dfbebd24aa60db4a71fb948 Mon Sep 17 00:00:00 2001 From: Yanyan-Wang Date: Thu, 23 Jul 2020 22:27:25 +0800 Subject: [PATCH 41/77] fix: canvas.on leads to tooltip problem. --- gatsby-browser.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gatsby-browser.js b/gatsby-browser.js index 98906e728f..d1b4d76a74 100644 --- a/gatsby-browser.js +++ b/gatsby-browser.js @@ -1,3 +1,3 @@ -// window.g6 = require('./src/index.ts'); // import the source for debugging -window.g6 = require('./dist/g6.min.js'); // import the package for webworker +window.g6 = require('./src/index.ts'); // import the source for debugging +// window.g6 = require('./dist/g6.min.js'); // import the package for webworker window.insertCss = require('insert-css'); From e668547069d3ead03a1fc6e1e4ba1cb2726792ae Mon Sep 17 00:00:00 2001 From: baizn <576375879@qq.com> Date: Wed, 15 Jul 2020 14:47:48 +0800 Subject: [PATCH 42/77] feat: slider timebar component --- package.json | 2 +- tests/unit/plugins/timebar-spec.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 957dddab41..ca4fed5c92 100644 --- a/package.json +++ b/package.json @@ -127,4 +127,4 @@ "webpack-cli": "^3.3.10", "worker-loader": "^2.0.0" } -} \ No newline at end of file +} diff --git a/tests/unit/plugins/timebar-spec.ts b/tests/unit/plugins/timebar-spec.ts index fb0ccb97d0..6042c00caa 100644 --- a/tests/unit/plugins/timebar-spec.ts +++ b/tests/unit/plugins/timebar-spec.ts @@ -173,6 +173,6 @@ describe('TimeBar', () => { expect(slider.get('maxText')).toEqual(99) expect(slider.get('minText')).toEqual(0) expect(slider.get('height')).toBe(26) - // graph.destroy() + graph.destroy() }) }); From 49122b9f43a5a6fb7a275e943c71641d50254581 Mon Sep 17 00:00:00 2001 From: chenluli Date: Tue, 21 Jul 2020 14:07:14 +0800 Subject: [PATCH 43/77] fix: drag new node without initial position --- package.json | 2 +- src/graph/controller/item.ts | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index ca4fed5c92..957dddab41 100644 --- a/package.json +++ b/package.json @@ -127,4 +127,4 @@ "webpack-cli": "^3.3.10", "worker-loader": "^2.0.0" } -} +} \ No newline at end of file diff --git a/src/graph/controller/item.ts b/src/graph/controller/item.ts index db14ab8324..8c896fdd95 100644 --- a/src/graph/controller/item.ts +++ b/src/graph/controller/item.ts @@ -126,6 +126,9 @@ export default class ItemController { group: parent.addGroup(), }); } else if (type === NODE) { + model.x = model.x || 0; + model.y = model.y || 0; + item = new Node({ model, styles, From 74c103b72f7352dc1eda28b4868f149c1c86d244 Mon Sep 17 00:00:00 2001 From: Yanyan-Wang Date: Wed, 22 Jul 2020 12:07:36 +0800 Subject: [PATCH 44/77] feat: fix the initial positions by equably distributing for layout to produce similar result. --- gatsby-browser.js | 4 ++-- src/graph/controller/item.ts | 3 --- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/gatsby-browser.js b/gatsby-browser.js index d1b4d76a74..98906e728f 100644 --- a/gatsby-browser.js +++ b/gatsby-browser.js @@ -1,3 +1,3 @@ -window.g6 = require('./src/index.ts'); // import the source for debugging -// window.g6 = require('./dist/g6.min.js'); // import the package for webworker +// window.g6 = require('./src/index.ts'); // import the source for debugging +window.g6 = require('./dist/g6.min.js'); // import the package for webworker window.insertCss = require('insert-css'); diff --git a/src/graph/controller/item.ts b/src/graph/controller/item.ts index 8c896fdd95..db14ab8324 100644 --- a/src/graph/controller/item.ts +++ b/src/graph/controller/item.ts @@ -126,9 +126,6 @@ export default class ItemController { group: parent.addGroup(), }); } else if (type === NODE) { - model.x = model.x || 0; - model.y = model.y || 0; - item = new Node({ model, styles, From 4cadbdf143d97eb08ef84322370bac30f1cfc0be Mon Sep 17 00:00:00 2001 From: baizn <576375879@qq.com> Date: Wed, 15 Jul 2020 14:47:48 +0800 Subject: [PATCH 45/77] feat: slider timebar component --- package.json | 4 +- src/index.ts | 5 +- src/plugins/index.ts | 4 +- src/plugins/timeBar/index.ts | 240 +++++++++++++++++++++++++++++ tests/unit/plugins/timebar-spec.ts | 91 +++++++++++ 5 files changed, 341 insertions(+), 3 deletions(-) create mode 100644 src/plugins/timeBar/index.ts create mode 100644 tests/unit/plugins/timebar-spec.ts diff --git a/package.json b/package.json index ec3290867c..ca4fed5c92 100644 --- a/package.json +++ b/package.json @@ -69,6 +69,8 @@ "**/*.{js,ts,tsx}": "npm run lint-staged:js" }, "dependencies": { + "@antv/color-util": "^2.0.5", + "@antv/component": "^0.6.1", "@antv/dom-util": "^2.0.1", "@antv/event-emitter": "~0.1.0", "@antv/g-base": "^0.4.1", @@ -125,4 +127,4 @@ "webpack-cli": "^3.3.10", "worker-loader": "^2.0.0" } -} \ No newline at end of file +} diff --git a/src/index.ts b/src/index.ts index 356c706dfd..75fdef24b9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -20,6 +20,7 @@ const Bundling = Plugins.Bundling; const Menu = Plugins.Menu; const ToolBar = Plugins.ToolBar const Tooltip = Plugins.Tooltip +const TimeBar = Plugins.TimeBar export { registerNode, @@ -38,7 +39,8 @@ export { registerBehavior, Algorithm, ToolBar, - Tooltip + Tooltip, + TimeBar }; export default { @@ -59,6 +61,7 @@ export default { Menu: Plugins.Menu, ToolBar: Plugins.ToolBar, Tooltip: Plugins.Tooltip, + TimeBar, Algorithm, Arrow, Marker diff --git a/src/plugins/index.ts b/src/plugins/index.ts index 94b4a89da4..c6c9d2e542 100644 --- a/src/plugins/index.ts +++ b/src/plugins/index.ts @@ -4,6 +4,7 @@ import Minimap from './minimap'; import Bundling from './bundling'; import ToolBar from './toolBar' import Tooltip from './tooltip' +import TimeBar from './timeBar' export default { Menu, @@ -11,5 +12,6 @@ export default { Minimap, Bundling, ToolBar, - Tooltip + Tooltip, + TimeBar }; diff --git a/src/plugins/timeBar/index.ts b/src/plugins/timeBar/index.ts new file mode 100644 index 0000000000..b12831bdcf --- /dev/null +++ b/src/plugins/timeBar/index.ts @@ -0,0 +1,240 @@ +import modifyCSS from '@antv/dom-util/lib/modify-css'; +import createDOM from '@antv/dom-util/lib/create-dom'; +import isString from '@antv/util/lib/is-string' +import { IGroup } from '@antv/g-base' +import { Canvas } from '@antv/g-canvas' +import { Slider } from '@antv/component' +import { ShapeStyle, GraphData } from '../../types'; +import Base, { IPluginBaseConfig } from '../base'; +import { IGraph } from '../../interface/graph'; + +interface Data { + date: string; + value: number; +} + +interface Callback { + originValue: number[]; + value: number[]; + target: IGroup; +} + +interface TrendConfig { + readonly data: Data[]; + // 样式 + readonly smooth?: boolean; + readonly isArea?: boolean; + readonly backgroundStyle?: ShapeStyle; + readonly lineStyle?: ShapeStyle; + readonly areaStyle?: ShapeStyle; +}; + +type TimeBarOption = Partial<{ + // position size + readonly x: number; + readonly y: number; + readonly width: number; + readonly height: number; + + readonly backgroundStyle: ShapeStyle; + readonly foregroundStyle: ShapeStyle; + readonly handlerStyle: ShapeStyle; + readonly textStyle: ShapeStyle; + // 允许滑动位置 + readonly minLimit: number; + readonly maxLimit: number; + // 初始位置 + readonly start: number; + readonly end: number; + // 滑块文本 + readonly minText: string; + readonly maxText: string; + + readonly trend: TrendConfig; +}>; + +interface TimeBarConfig extends IPluginBaseConfig { + width?: number; + height?: number; + timebar: TimeBarOption; + rangeChange?: (graph: IGraph, min: number, max: number) => void; +} + +export default class TimeBar extends Base { + private cacheGraphData: GraphData + + constructor(cfg?: TimeBarConfig) { + super(cfg); + } + + public getDefaultCfgs(): TimeBarConfig { + return { + width: 400, + height: 50, + rangeChange: null, + timebar: { + x: 10, + y: 10, + width: 400, + height: 26, + minLimit: 0.05, + maxLimit: 0.95, + start: 0.1, + end: 0.9, + } + }; + } + + public init() { + const timeBarConfig: TimeBarOption = this.get('timebar') + const { trend = {} as TrendConfig } = timeBarConfig + const { data = [] } = trend + + if (!data || data.length === 0) { + console.warn('TimeBar 中没有传入数据') + return + } + + const container = this.get('container') + + let timebar + if (!container) { + timebar = createDOM(`
    `) + modifyCSS(timebar, { position: 'absolute' }); + document.body.appendChild(timebar) + } else if (isString(container)) { + timebar = createDOM(`
    `) + modifyCSS(timebar, { position: 'absolute' }); + document.body.appendChild(timebar) + } else { + timebar = container + } + + this.set('timeBarContainer', timebar) + + this.initTimeBar(timebar) + } + + private initTimeBar(container: HTMLDivElement) { + const width = this.get('width') + const height = this.get('height') + const canvas = new Canvas({ + container, + width, + height, + }); + + const group = canvas.addGroup({ + id: 'timebar-plugin', + }) + + const timeBarConfig: TimeBarOption = this.get('timebar') + const { trend = {} as TrendConfig , ...option } = timeBarConfig + + const config = { + container: group, + minText: option.start, + maxText: option.end, + ...option + } + + // 是否显示 TimeBar 根据是否传入了数据来确定 + const { data = [], ...trendOption } = trend + + const trendData = data.map(d => d.value) + + config['trendCfg'] = { + ...trendOption, + data: trendData + } + + config.minText = data[0].date + config.maxText = data[data.length - 1].date + + this.set('trendData', data) + + console.log('配置项', config) + + const slider = new Slider(config) + + slider.init(); + slider.render() + + this.set('slider', slider) + + this.bindEvent() + } + + /** + * 当滑动时,最小值和最大值会变化,变化以后触发相应事件 + */ + private bindEvent() { + const graph: IGraph = this.get('graph') + const slider = this.get('slider') + const rangeChange = this.get('rangeChange') + const trendData: Data[] = this.get('trendData') + slider.on('valuechanged', (evt: Callback) => { + const { value } = evt + + const min = Math.round(trendData.length * value[0]) + let max = Math.round(trendData.length * value[1]) + max = max > trendData.length ? trendData.length : max + const minText = trendData[min].date + const maxText = trendData[max].date + + slider.set('minText', minText) + slider.set('maxText', maxText) + + if (rangeChange) { + rangeChange(graph, minText, maxText) + } else { + // 自动过滤数据,并渲染 graph + const graphData = graph.save() as GraphData + + if (!this.cacheGraphData) { + this.cacheGraphData = graphData + } + + // 过滤不在 min 和 max 范围内的节点 + const filterData = this.cacheGraphData.nodes.filter((d: any) => d.date >= minText && d.date <= maxText) + + const nodeIds = filterData.map(node => node.id) + + // 过滤 source 或 target 不在 min 和 max 范围内的边 + const fileterEdges = this.cacheGraphData.edges.filter(edge => nodeIds.includes(edge.source) && nodeIds.includes(edge.target)) + + graph.changeData({ + nodes: filterData, + edges: fileterEdges + }) + + } + }) + } + + public show() { + const slider = this.get('slider') + slider.show() + } + + public hide() { + const slider = this.get('slider') + slider.hide() + } + + public destroy() { + this.cacheGraphData = null + + const slider = this.get('slider') + + if (slider) { + slider.off('valuechanged') + slider.destroy() + } + + const timeBarContainer = this.get('timeBarContainer') + if (timeBarContainer) { + document.body.removeChild(timeBarContainer); + } + } +} diff --git a/tests/unit/plugins/timebar-spec.ts b/tests/unit/plugins/timebar-spec.ts new file mode 100644 index 0000000000..043a9b68a6 --- /dev/null +++ b/tests/unit/plugins/timebar-spec.ts @@ -0,0 +1,91 @@ +import G6 from '../../../src'; +const div = document.createElement('div'); +div.id = 'timebar-plugin'; +document.body.appendChild(div); + +const data = { + nodes: [ + { + id: 'node1', + label: 'node1', + x: 100, + y: 100 + }, + { + id: 'node2', + label: 'node2', + x: 150, + y: 300 + } + ], + edges: [ + { + source: 'node1', + target: 'node2' + } + ] +} + +for(let i = 0; i < 100; i++) { + const id = `node-${i}` + data.nodes.push({ + id, + label: `node${i}`, + date: `2020${i}`, + value: Math.round(Math.random() * 300) + }) + + const edgeId = i + 3 + data.edges.push({ + source: `node-${Math.round(Math.random() * 90)}`, + target: `node-${Math.round(Math.random() * 90)}` + }) +} + +describe('tooltip', () => { + it('tooltip with default', () => { + const timeBarData = [] + + for(let i = 0; i < 100; i++) { + timeBarData.push({ + date: `2020${i}`, + value: Math.round(Math.random() * 300) + }) + } + const timebar = new G6.TimeBar({ + timebar: { + trend: { + data: timeBarData, + isArea: false, + smooth: true, + } + } + }); + const tooltip = new G6.Tooltip() + + const graph = new G6.Graph({ + container: div, + width: 500, + height: 500, + plugins: [timebar, tooltip], + modes: { + default: ['drag-node', 'zoom-canvas', 'drag-canvas'] + }, + defaultEdge: { + style: { + lineAppendWidth: 20 + } + } + }); + + graph.data(data) + graph.render() + + const timebarPlugin = graph.get('plugins')[0] + console.log(timebarPlugin) + // expect(timebarPlugin.get('offset')).toBe(6) + // expect(timebarPlugin.get('tooltip').outerHTML).toBe(``) + + // graph.destroy() + }) +}); From bff73e65d5f852284bb2b0cef0b5fe2a487d2f02 Mon Sep 17 00:00:00 2001 From: chenluli Date: Tue, 21 Jul 2020 14:07:14 +0800 Subject: [PATCH 46/77] fix: drag new node without initial position --- package.json | 2 +- src/graph/controller/item.ts | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index ca4fed5c92..957dddab41 100644 --- a/package.json +++ b/package.json @@ -127,4 +127,4 @@ "webpack-cli": "^3.3.10", "worker-loader": "^2.0.0" } -} +} \ No newline at end of file diff --git a/src/graph/controller/item.ts b/src/graph/controller/item.ts index db14ab8324..8c896fdd95 100644 --- a/src/graph/controller/item.ts +++ b/src/graph/controller/item.ts @@ -126,6 +126,9 @@ export default class ItemController { group: parent.addGroup(), }); } else if (type === NODE) { + model.x = model.x || 0; + model.y = model.y || 0; + item = new Node({ model, styles, From faeaf2c078ff245853c2db6adbae45959f0eb94d Mon Sep 17 00:00:00 2001 From: baizn <576375879@qq.com> Date: Tue, 14 Jul 2020 15:38:02 +0800 Subject: [PATCH 47/77] feat: hide edge & label when drag canvas --- package.json | 2 +- src/behavior/drag-canvas.ts | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/package.json b/package.json index 957dddab41..ca4fed5c92 100644 --- a/package.json +++ b/package.json @@ -127,4 +127,4 @@ "webpack-cli": "^3.3.10", "worker-loader": "^2.0.0" } -} \ No newline at end of file +} diff --git a/src/behavior/drag-canvas.ts b/src/behavior/drag-canvas.ts index 19fb02dd30..f037c5759f 100644 --- a/src/behavior/drag-canvas.ts +++ b/src/behavior/drag-canvas.ts @@ -86,7 +86,6 @@ export default { for (let i = 0, len = edges.length; i < len; i++) { graph.hideItem(edges[i]) } - const nodes = graph.getNodes() for (let j = 0, nodeLen = nodes.length; j < nodeLen; j++) { const container = nodes[j].getContainer() @@ -142,7 +141,6 @@ export default { for (let i = 0, len = edges.length; i < len; i++) { graph.showItem(edges[i]) } - const nodes = graph.getNodes() for (let j = 0, nodeLen = nodes.length; j < nodeLen; j++) { const container = nodes[j].getContainer() From 104a93e7da447a81bf19ed84b3b8264ab51d3f85 Mon Sep 17 00:00:00 2001 From: Yanyan-Wang Date: Mon, 13 Jul 2020 15:15:11 +0800 Subject: [PATCH 48/77] feat: fix node or edge size, fontSize, lineWidth while zooming graph with zoom-canvas. --- package.json | 2 +- src/shape/shapeBase.ts | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index ca4fed5c92..957dddab41 100644 --- a/package.json +++ b/package.json @@ -127,4 +127,4 @@ "webpack-cli": "^3.3.10", "worker-loader": "^2.0.0" } -} +} \ No newline at end of file diff --git a/src/shape/shapeBase.ts b/src/shape/shapeBase.ts index 2bd68bbd93..0505196c85 100644 --- a/src/shape/shapeBase.ts +++ b/src/shape/shapeBase.ts @@ -310,6 +310,8 @@ export const shapeBase: ShapeOptions = { const style = styles[key]; if (isPlainObject(style)) { const subShape = group.find((element) => element.get('name') === key); + + if (subShape) { subShape.attr(style); } From 8ff2e9fbfe4a388303295474bc99dc051356f7d1 Mon Sep 17 00:00:00 2001 From: Yanyan-Wang Date: Tue, 21 Jul 2020 20:03:40 +0800 Subject: [PATCH 49/77] fix: fix all shapes by scale the node matrix. --- src/behavior/zoom-canvas.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/behavior/zoom-canvas.ts b/src/behavior/zoom-canvas.ts index 4834ebba1e..21b53ee590 100644 --- a/src/behavior/zoom-canvas.ts +++ b/src/behavior/zoom-canvas.ts @@ -126,7 +126,11 @@ export default { // fix the items when zooming if (graphZoom <= 1) { let fixNodes, fixEdges; +<<<<<<< HEAD if (fixSelectedItems.fixAll || fixSelectedItems.fixLineWidth || fixSelectedItems.fixLabel) { +======= + if (fixSelectedItems.fixLineWidth || fixSelectedItems.fixLabel) { +>>>>>>> fix: fix all shapes by scale the node matrix. fixNodes = graph.findAllByState('node', fixSelectedItems.fixState); fixEdges = graph.findAllByState('edge', fixSelectedItems.fixState); From f12f9f6b1f662cf7a2784ce9ed3791fa5ed2b612 Mon Sep 17 00:00:00 2001 From: Yanyan-Wang Date: Wed, 22 Jul 2020 09:54:18 +0800 Subject: [PATCH 50/77] fix: zoom-canvas with fixItem problems. docs: add zoom-canvas with fixItem demo. --- examples/item/customNode/demo/svgDom.js | 94 ++++++++++++------------- src/behavior/zoom-canvas.ts | 4 -- 2 files changed, 47 insertions(+), 51 deletions(-) diff --git a/examples/item/customNode/demo/svgDom.js b/examples/item/customNode/demo/svgDom.js index 1a386c5bed..f44b1e3484 100644 --- a/examples/item/customNode/demo/svgDom.js +++ b/examples/item/customNode/demo/svgDom.js @@ -10,51 +10,51 @@ import { useEffect } from 'react'; /** * Register a node type with DOM */ -G6.registerNode('dom-node', { - draw: (cfg, group) => { - const stroke = cfg.style ? cfg.style.stroke || '#5B8FF9' : '#5B8FF9'; - const shape = group.addShape('dom', { - attrs: { - width: cfg.size[0], - height: cfg.size[1], - html: ` -
    -
    - -
    - ${cfg.label} -
    - ` - }, - draggable: true - }); - return shape; - } -}); +// G6.registerNode('dom-node', { +// draw: (cfg, group) => { +// const stroke = cfg.style ? cfg.style.stroke || '#5B8FF9' : '#5B8FF9'; +// const shape = group.addShape('dom', { +// attrs: { +// width: cfg.size[0], +// height: cfg.size[1], +// html: ` +//
    +//
    +// +//
    +// ${cfg.label} +//
    +// ` +// }, +// draggable: true +// }); +// return shape; +// } +// }); -/** 数据 */ -const data = { - nodes: [ - { - id: 'node1', - x: 10, - y: 100, - label: 'Homepage', - }, - { - id: 'node2', - x: 200, - y: 100, - label: 'Subpage', - }, - ], - edges: [ - { - source: 'node1', - target: 'node2', - }, - ], -}; +// /** 数据 */ +// const data = { +// nodes: [ +// { +// id: 'node1', +// x: 10, +// y: 100, +// label: 'Homepage', +// }, +// { +// id: 'node2', +// x: 200, +// y: 100, +// label: 'Subpage', +// }, +// ], +// edges: [ +// { +// source: 'node1', +// target: 'node2', +// }, +// ], +// }; const graphContainer = document.getElementById('container'); const width = graphContainer.scrollWidth; @@ -80,8 +80,8 @@ const graph = new G6.Graph({ } }); -graph.data(data); -graph.render(); +// graph.data(data); +// graph.render(); // // click listener for dom nodes to response the click by changing stroke color const listener = (dom) => { @@ -120,4 +120,4 @@ bindClickListener(); // so the listeners should be rebinded to the new DOMs graph.on('afterupdateitem', e => { bindClickListener(); -}); \ No newline at end of file +}); diff --git a/src/behavior/zoom-canvas.ts b/src/behavior/zoom-canvas.ts index 21b53ee590..4834ebba1e 100644 --- a/src/behavior/zoom-canvas.ts +++ b/src/behavior/zoom-canvas.ts @@ -126,11 +126,7 @@ export default { // fix the items when zooming if (graphZoom <= 1) { let fixNodes, fixEdges; -<<<<<<< HEAD if (fixSelectedItems.fixAll || fixSelectedItems.fixLineWidth || fixSelectedItems.fixLabel) { -======= - if (fixSelectedItems.fixLineWidth || fixSelectedItems.fixLabel) { ->>>>>>> fix: fix all shapes by scale the node matrix. fixNodes = graph.findAllByState('node', fixSelectedItems.fixState); fixEdges = graph.findAllByState('edge', fixSelectedItems.fixState); From 08e816f9a1c45792f80be28fe6f20e28b30075c1 Mon Sep 17 00:00:00 2001 From: Yanyan-Wang Date: Wed, 22 Jul 2020 11:02:49 +0800 Subject: [PATCH 51/77] docs: demo for hide items while dragging. fix: fix node while zooming problem. fix: set default enableOptimize of drag-canvas to be false. --- examples/item/customNode/demo/svgDom.js | 94 ++++++++++++------------- 1 file changed, 47 insertions(+), 47 deletions(-) diff --git a/examples/item/customNode/demo/svgDom.js b/examples/item/customNode/demo/svgDom.js index f44b1e3484..1a386c5bed 100644 --- a/examples/item/customNode/demo/svgDom.js +++ b/examples/item/customNode/demo/svgDom.js @@ -10,51 +10,51 @@ import { useEffect } from 'react'; /** * Register a node type with DOM */ -// G6.registerNode('dom-node', { -// draw: (cfg, group) => { -// const stroke = cfg.style ? cfg.style.stroke || '#5B8FF9' : '#5B8FF9'; -// const shape = group.addShape('dom', { -// attrs: { -// width: cfg.size[0], -// height: cfg.size[1], -// html: ` -//
    -//
    -// -//
    -// ${cfg.label} -//
    -// ` -// }, -// draggable: true -// }); -// return shape; -// } -// }); +G6.registerNode('dom-node', { + draw: (cfg, group) => { + const stroke = cfg.style ? cfg.style.stroke || '#5B8FF9' : '#5B8FF9'; + const shape = group.addShape('dom', { + attrs: { + width: cfg.size[0], + height: cfg.size[1], + html: ` +
    +
    + +
    + ${cfg.label} +
    + ` + }, + draggable: true + }); + return shape; + } +}); -// /** 数据 */ -// const data = { -// nodes: [ -// { -// id: 'node1', -// x: 10, -// y: 100, -// label: 'Homepage', -// }, -// { -// id: 'node2', -// x: 200, -// y: 100, -// label: 'Subpage', -// }, -// ], -// edges: [ -// { -// source: 'node1', -// target: 'node2', -// }, -// ], -// }; +/** 数据 */ +const data = { + nodes: [ + { + id: 'node1', + x: 10, + y: 100, + label: 'Homepage', + }, + { + id: 'node2', + x: 200, + y: 100, + label: 'Subpage', + }, + ], + edges: [ + { + source: 'node1', + target: 'node2', + }, + ], +}; const graphContainer = document.getElementById('container'); const width = graphContainer.scrollWidth; @@ -80,8 +80,8 @@ const graph = new G6.Graph({ } }); -// graph.data(data); -// graph.render(); +graph.data(data); +graph.render(); // // click listener for dom nodes to response the click by changing stroke color const listener = (dom) => { @@ -120,4 +120,4 @@ bindClickListener(); // so the listeners should be rebinded to the new DOMs graph.on('afterupdateitem', e => { bindClickListener(); -}); +}); \ No newline at end of file From 1c6aa30992d1cfca090feff5f11072f421e6d315 Mon Sep 17 00:00:00 2001 From: baizn <576375879@qq.com> Date: Mon, 13 Jul 2020 20:23:44 +0800 Subject: [PATCH 52/77] feat: graph.priorityState & event mode --- src/graph/controller/event.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/graph/controller/event.ts b/src/graph/controller/event.ts index eb0a40458e..18591d1ec8 100644 --- a/src/graph/controller/event.ts +++ b/src/graph/controller/event.ts @@ -163,6 +163,8 @@ export default class EventController { if (evt.name && !evt.name.includes(':')) graph.emit(`${type}:${eventType}`, evt); // emit('node:click', evt) else graph.emit(evt.name, evt); // emit('text-shape:click', evt) + graph.emit(evt.name, evt); + if (eventType === 'dragstart') { this.dragging = true; } From ac458573c56d21eb9ebc03a82f9827e7fe0e1ac1 Mon Sep 17 00:00:00 2001 From: Yanyan-Wang Date: Tue, 21 Jul 2020 18:52:16 +0800 Subject: [PATCH 53/77] fix: combo edge with uncorrect end points; fix: combo polyline edge with wrong path; fix: getViewCenter with padding problem. closes: #1817, #1809, #1775. --- src/shape/edges/polyline.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/shape/edges/polyline.ts b/src/shape/edges/polyline.ts index 0557437ae0..852db853b1 100644 --- a/src/shape/edges/polyline.ts +++ b/src/shape/edges/polyline.ts @@ -119,6 +119,7 @@ Shape.registerEdge( offset, ); const res = pointsToPolygon(polylinePoints); + console.log('path of polyline', points, source, target, polylinePoints, res) return res; }, }, From ba247b92592ffdf51d71de6303ec29f8afd7bc9d Mon Sep 17 00:00:00 2001 From: Yanyan-Wang Date: Wed, 22 Jul 2020 12:07:36 +0800 Subject: [PATCH 54/77] feat: fix the initial positions by equably distributing for layout to produce similar result. --- gatsby-browser.js | 4 ++-- src/graph/controller/item.ts | 3 --- src/shape/edges/polyline.ts | 1 - 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/gatsby-browser.js b/gatsby-browser.js index d1b4d76a74..98906e728f 100644 --- a/gatsby-browser.js +++ b/gatsby-browser.js @@ -1,3 +1,3 @@ -window.g6 = require('./src/index.ts'); // import the source for debugging -// window.g6 = require('./dist/g6.min.js'); // import the package for webworker +// window.g6 = require('./src/index.ts'); // import the source for debugging +window.g6 = require('./dist/g6.min.js'); // import the package for webworker window.insertCss = require('insert-css'); diff --git a/src/graph/controller/item.ts b/src/graph/controller/item.ts index 8c896fdd95..db14ab8324 100644 --- a/src/graph/controller/item.ts +++ b/src/graph/controller/item.ts @@ -126,9 +126,6 @@ export default class ItemController { group: parent.addGroup(), }); } else if (type === NODE) { - model.x = model.x || 0; - model.y = model.y || 0; - item = new Node({ model, styles, diff --git a/src/shape/edges/polyline.ts b/src/shape/edges/polyline.ts index 852db853b1..0557437ae0 100644 --- a/src/shape/edges/polyline.ts +++ b/src/shape/edges/polyline.ts @@ -119,7 +119,6 @@ Shape.registerEdge( offset, ); const res = pointsToPolygon(polylinePoints); - console.log('path of polyline', points, source, target, polylinePoints, res) return res; }, }, From d7f1c93859838df78cc80c1d2378c82b1070fe89 Mon Sep 17 00:00:00 2001 From: baizn <576375879@qq.com> Date: Wed, 15 Jul 2020 14:47:48 +0800 Subject: [PATCH 55/77] feat: slider timebar component --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 957dddab41..ca4fed5c92 100644 --- a/package.json +++ b/package.json @@ -127,4 +127,4 @@ "webpack-cli": "^3.3.10", "worker-loader": "^2.0.0" } -} \ No newline at end of file +} From 5fb97dd77b17821183a4fedfd1449e94dd0bbc59 Mon Sep 17 00:00:00 2001 From: baizn <576375879@qq.com> Date: Fri, 24 Jul 2020 16:12:46 +0800 Subject: [PATCH 56/77] feat: add plugins docs & demo --- docs/api/Plugins.zh.md | 118 +++++++++++++++ examples/tool/contextMenu/demo/contextMenu.js | 29 ++-- examples/tool/timebar/demo/configTimebar.js | 97 +++++++++++++ examples/tool/timebar/demo/meta.json | 24 ++++ examples/tool/timebar/demo/timebar.js | 82 +++++++++++ examples/tool/timebar/index.zh.md | 11 ++ examples/tool/toolbar/demo/meta.json | 24 ++++ examples/tool/toolbar/demo/self-toolbar.js | 136 ++++++++++++++++++ examples/tool/toolbar/demo/toolbar.js | 100 +++++++++++++ examples/tool/toolbar/index.en.md | 16 +++ examples/tool/toolbar/index.zh.md | 16 +++ src/plugins/menu/index.ts | 55 ++++--- src/plugins/timeBar/index.ts | 27 ++-- src/plugins/tooltip/index.ts | 12 +- tests/unit/plugins/menu-spec.ts | 2 +- tests/unit/plugins/timebar-spec.ts | 103 +++++++++++-- tests/unit/plugins/tooltip-spec.ts | 7 +- 17 files changed, 806 insertions(+), 53 deletions(-) create mode 100644 examples/tool/timebar/demo/configTimebar.js create mode 100644 examples/tool/timebar/demo/meta.json create mode 100644 examples/tool/timebar/demo/timebar.js create mode 100644 examples/tool/timebar/index.zh.md create mode 100644 examples/tool/toolbar/demo/meta.json create mode 100644 examples/tool/toolbar/demo/self-toolbar.js create mode 100644 examples/tool/toolbar/demo/toolbar.js create mode 100644 examples/tool/toolbar/index.en.md create mode 100644 examples/tool/toolbar/index.zh.md diff --git a/docs/api/Plugins.zh.md b/docs/api/Plugins.zh.md index d237a51685..405b01b2ad 100644 --- a/docs/api/Plugins.zh.md +++ b/docs/api/Plugins.zh.md @@ -266,6 +266,124 @@ const graph = new G6.Graph({ }); ``` +## TimeBar + +目前 G6 内置的 TimeBar 主要有以下功能: +- 改变时间范围,过滤图上的数据; +- TimeBar 上展示指定字段随时间推移的变化趋势。 + +img + +**说明:** 目前的 TimeBar 功能还比较简单,不能用于较为复杂的时序分析。 + +### 配置项 + +| 名称 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| container | HTMLDivElement | null | TimeBar 容器,如果不设置,则默认创建 className 为 g6-component-timebar 的 DOM 容器 | +| width | number | 400 | TimeBar 容器宽度 | +| height | number | 400 | TimeBar 容器高度 | +| timebar | TimeBarOption | {} | TimeBar 样式配置项 | +| rangeChange | (graph: IGraph, min: number, max: number) => void | null | 改变时间范围后的回调函数 | + + +**TimeBarOption 配置项** + +``` +interface HandleStyle { + width: number; + height: number; + style: ShapeStyle; +} +``` + +| 名称 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| x | number | 0 | TimeBar 起始 x 坐标 | +| y | number | 0 | TimeBar 起始 y 坐标 | +| width | number | 400 | TimeBar 宽度 | +| height | number | 400 | TimeBar 高度 | +| backgroundStyle | ShapeStyle | {} | TimeBar 背景样式配置项 | +| foregroundStyle | ShapeStyle | {} | TimeBar 选中部分样式配置项 | +| handlerStyle | HandleStyle | null | 滑块样式设置 | +| textStyle | ShapeStyle | null | 文本样式 | +| minLimit | number | 0 | 允许滑块最左边(最小)位置,范围 0-1 | +| maxLimit | number | 1 | 允许滑块最右边(最大)位置,范围 0-1 | +| start | number | 0 | 滑块初始开始位置 | +| end | number | 1 | 滑块初始结束位置 | +| minText | string | null | 滑块最小值时显示的文本 | +| maxText | string | null | 滑块最大值时显示的文本 | +| trend | TrendConfig | null | 滑块上趋势图配置 | + +**TrendConfig 配置项** + +``` +interface Data { + date: string; + value: number; +} +``` + +| 名称 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| data | Data[] | [] | 滑块上的数据源 | +| smooth | boolean | false | 是否是平滑的曲线 | +| isArea | boolean | false | 是否显示面积图 | +| lineStyle | ShapeStyle | null | 折线的样式 | +| areaStyle | ShapeStyle | null | 面积的样式,只有当 isArea 为 true 时生效 | + +### 用法 + +#### 默认用法 +G6 内置的默认的 TimeBar 有默认的样式及交互功能。 + +``` +const timebar = new G6.TimeBar(); + +const graph = new G6.Graph({ + //... 其他配置项 + plugins: [timebar], // 配置 timebar 插件 +}); +``` + +##### 配置样式 +可以个性化定制 TimeBar 的样式,也可以自己定义时间范围改变后的处理方式。 + +``` +const timebar = new G6.TimeBar({ + width: 600, + timebar: { + width: 600, + backgroundStyle: { + fill: '#08979c', + opacity: 0.3 + }, + foregroundStyle: { + fill: '#40a9ff', + opacity: 0.4 + }, + trend: { + data: timeBarData, + isArea: false, + smooth: true, + lineStyle: { + stroke: '#9254de' + } + } + }, + rangeChange: (graph, min, max) => { + // 拿到 Graph 实例和 timebar 上范围,自己可以控制图上的渲染逻辑 + console.log(graph, min, max) + } +}); + +const graph = new G6.Graph({ + //... 其他配置项 + plugins: [timebar], // 配置 timebar 插件 +}); +``` + + ## ToolTip ToolTip 插件主要用于在节点和边上展示一些辅助信息,G6 4.0 以后,Tooltip 插件将会替换 Behavior 中的 tooltip。 diff --git a/examples/tool/contextMenu/demo/contextMenu.js b/examples/tool/contextMenu/demo/contextMenu.js index 63938445cb..a0a8508d1f 100644 --- a/examples/tool/contextMenu/demo/contextMenu.js +++ b/examples/tool/contextMenu/demo/contextMenu.js @@ -47,11 +47,30 @@ document.getElementById('container').appendChild(conextMenuContainer); const width = document.getElementById('container').scrollWidth; const height = document.getElementById('container').scrollHeight || 500; + +const contextMenu = new G6.Menu({ + getContent(graph) { + console.log('graph',graph) + return `
      +
    • 测试01
    • +
    • 测试02
    • +
    • 测试03
    • +
    • 测试04
    • +
    • 测试05
    • +
    `; + }, + handleMenuClick: (target, item) => { + console.log(target, item) + } +}); + const graph = new G6.Graph({ + // 使用 contextMenu plugins 时,需要将 container 设置为 position: relative; container: 'container', width, height, linkCenter: true, + plugins: [contextMenu], defaultNode: { size: [80, 40], type: 'rect', @@ -120,13 +139,3 @@ const data = { graph.data(data); graph.render(); -graph.on('node:contextmenu', evt => { - evt.preventDefault(); - evt.stopPropagation(); - conextMenuContainer.style.left = `${evt.canvasX + 20}px`; - conextMenuContainer.style.top = `${evt.canvasY}px`; -}); - -graph.on('node:mouseleave', () => { - conextMenuContainer.style.left = '-150px'; -}); diff --git a/examples/tool/timebar/demo/configTimebar.js b/examples/tool/timebar/demo/configTimebar.js new file mode 100644 index 0000000000..cdf1d487e6 --- /dev/null +++ b/examples/tool/timebar/demo/configTimebar.js @@ -0,0 +1,97 @@ +import G6 from '@antv/g6'; +import insertCss from 'insert-css'; + +insertCss(` + #g6-component-timebar { + top: 540px; + left: 10px; + } +`); + +const data = { + nodes: [], + edges: [], +}; + +for(let i = 0; i < 100; i++) { + const id = `node-${i}` + data.nodes.push({ + id, + label: `node${i}`, + date: `2020${i}`, + value: Math.round(Math.random() * 300) + }) + + data.edges.push({ + source: `node-${Math.round(Math.random() * 90)}`, + target: `node-${Math.round(Math.random() * 90)}` + }) +} + +const width = document.getElementById('container').scrollWidth; +const height = document.getElementById('container').scrollHeight || 500; + +const timeBarData = [] + +for(let i = 0; i < 100; i++) { + timeBarData.push({ + date: `2020${i}`, + value: Math.round(Math.random() * 300) + }) +} + +const timebar = new G6.TimeBar({ + width: 600, + timebar: { + width: 600, + backgroundStyle: { + fill: '#08979c', + opacity: 0.3 + }, + foregroundStyle: { + fill: '#40a9ff', + opacity: 0.4 + }, + trend: { + data: timeBarData, + isArea: false, + smooth: true, + lineStyle: { + stroke: '#9254de' + } + } + }, + rangeChange: (graph, min, max) => { + // 拿到 Graph 实例和 timebar 上范围,自己可以控制图上的渲染逻辑 + console.log(graph, min, max) + } +}); + +const graph = new G6.Graph({ + container: 'container', + width, + height, + linkCenter: true, + plugins: [timebar], + defaultNode: { + size: 40, + type: 'circle', + style: { + fill: '#DEE9FF', + stroke: '#5B8FF9', + }, + }, + defaultEdge: { + style: { + stroke: '#b5b5b5', + lineAppendWidth: 3, + }, + }, + modes: { + default: [ + 'drag-node' + ], + }, +}); +graph.data(data); +graph.render(); diff --git a/examples/tool/timebar/demo/meta.json b/examples/tool/timebar/demo/meta.json new file mode 100644 index 0000000000..10c0fa1e36 --- /dev/null +++ b/examples/tool/timebar/demo/meta.json @@ -0,0 +1,24 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "timebar.js", + "title": { + "zh": "时间轴", + "en": "ToolBar" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*xj85R7loNvMAAAAAAAAAAABkARQnAQ" + }, + { + "filename": "configTimebar.js", + "title": { + "zh": "定义时间轴样式", + "en": "ToolBar" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*AG3AQ7PyHSMAAAAAAAAAAABkARQnAQ" + } + ] +} \ No newline at end of file diff --git a/examples/tool/timebar/demo/timebar.js b/examples/tool/timebar/demo/timebar.js new file mode 100644 index 0000000000..5124c38243 --- /dev/null +++ b/examples/tool/timebar/demo/timebar.js @@ -0,0 +1,82 @@ +import G6 from '@antv/g6'; +import insertCss from 'insert-css'; + +insertCss(` + #g6-component-timebar { + top: 540px; + left: 10px; + } +`); + +const data = { + nodes: [], + edges: [], +}; + +for(let i = 0; i < 100; i++) { + const id = `node-${i}` + data.nodes.push({ + id, + label: `node${i}`, + date: `2020${i}`, + value: Math.round(Math.random() * 300) + }) + + data.edges.push({ + source: `node-${Math.round(Math.random() * 90)}`, + target: `node-${Math.round(Math.random() * 90)}` + }) +} + +const width = document.getElementById('container').scrollWidth; +const height = document.getElementById('container').scrollHeight || 500; + +const timeBarData = [] + +for(let i = 0; i < 100; i++) { + timeBarData.push({ + date: `2020${i}`, + value: Math.round(Math.random() * 300) + }) +} + +const timebar = new G6.TimeBar({ + width: 600, + timebar: { + width: 600, + trend: { + data: timeBarData, + isArea: false, + smooth: true, + } + } +}); + +const graph = new G6.Graph({ + container: 'container', + width, + height, + linkCenter: true, + plugins: [timebar], + defaultNode: { + size: 40, + type: 'circle', + style: { + fill: '#DEE9FF', + stroke: '#5B8FF9', + }, + }, + defaultEdge: { + style: { + stroke: '#b5b5b5', + lineAppendWidth: 3, + }, + }, + modes: { + default: [ + 'drag-node' + ], + }, +}); +graph.data(data); +graph.render(); diff --git a/examples/tool/timebar/index.zh.md b/examples/tool/timebar/index.zh.md new file mode 100644 index 0000000000..3fc4eb0bd5 --- /dev/null +++ b/examples/tool/timebar/index.zh.md @@ -0,0 +1,11 @@ +--- +title: 时间轴 +order: 0 +--- + +G6 中内置的 TimeBar 组件。 + +## 使用指南 + +下面的代码演示展示了如何在图上使用 TimeBar。TimeBar 的样式可以使用 G6 内置的,也可以完全自定义。 + diff --git a/examples/tool/toolbar/demo/meta.json b/examples/tool/toolbar/demo/meta.json new file mode 100644 index 0000000000..83137b8327 --- /dev/null +++ b/examples/tool/toolbar/demo/meta.json @@ -0,0 +1,24 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "toolbar.js", + "title": { + "zh": "工具栏", + "en": "ToolBar" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*MhpmS68lZW0AAAAAAAAAAABkARQnAQ" + }, + { + "filename": "self-toolbar.js", + "title": { + "zh": "自定义工具栏", + "en": "ToolBar" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*ljOiTJAuQnIAAAAAAAAAAABkARQnAQ" + } + ] +} \ No newline at end of file diff --git a/examples/tool/toolbar/demo/self-toolbar.js b/examples/tool/toolbar/demo/self-toolbar.js new file mode 100644 index 0000000000..2c288827a6 --- /dev/null +++ b/examples/tool/toolbar/demo/self-toolbar.js @@ -0,0 +1,136 @@ +import G6 from '@antv/g6'; +import insertCss from 'insert-css'; + +insertCss(` + .g6-toolbar-ul { + position: absolute; + top: 20px; + border: 1px solid #e2e2e2; + border-radius: 4px; + font-size: 12px; + color: #545454; + background-color: rgba(255, 255, 255, 0.9); + padding: 10px 8px; + box-shadow: rgb(174, 174, 174) 0px 0px 10px; + width: 100px; + cursor: pointer; + } +`); + +const data = { + nodes: [ + { + id: '0', + label: 'node-0', + x: 100, + y: 100, + description: 'This is node-0.', + subdescription: 'This is subdescription of node-0.', + }, + { + id: '1', + label: 'node-1', + x: 250, + y: 100, + description: 'This is node-1.', + subdescription: 'This is subdescription of node-1.', + }, + { + id: '2', + label: 'node-2', + x: 150, + y: 310, + description: 'This is node-2.', + subdescription: 'This is subdescription of node-2.', + }, + { + id: '3', + label: 'node-3', + x: 320, + y: 310, + description: 'This is node-3.', + subdescription: 'This is subdescription of node-3.', + }, + ], + edges: [ + { + id: 'e0', + source: '0', + target: '1', + description: 'This is edge from node 0 to node 1.', + }, + { + id: 'e1', + source: '0', + target: '2', + description: 'This is edge from node 0 to node 2.', + }, + { + id: 'e2', + source: '0', + target: '3', + description: 'This is edge from node 0 to node 3.', + }, + ], +}; +const width = document.getElementById('container').scrollWidth; +const height = document.getElementById('container').scrollHeight || 500; + +const toolbar = new G6.ToolBar({ + // container: tc, + className: 'g6-toolbar-ul', + getContent: () => { + return ` +
      +
    • 增加节点
    • +
    • 撤销
    • +
    • 回退
    • +
    + ` + }, + handleClick: (code, graph) => { + if (code === 'add') { + graph.addItem('node', { + id: 'node2', + label: 'node2', + x: 300, + y: 150 + }) + } else if (code === 'undo') { + toolbar.undo() + } else if (code === 'redo') { + toolbar.redo() + } + } +}); + +const graph = new G6.Graph({ + container: 'container', + width, + height, + linkCenter: true, + // 设置为true,启用 redo & undo 栈功能 + enabledStack: true, + plugins: [toolbar], + defaultNode: { + size: [80, 40], + type: 'rect', + style: { + fill: '#DEE9FF', + stroke: '#5B8FF9', + }, + }, + defaultEdge: { + style: { + stroke: '#b5b5b5', + lineAppendWidth: 3, + }, + }, + modes: { + default: [ + 'drag-node' + ], + }, +}); +graph.data(data); +graph.render(); diff --git a/examples/tool/toolbar/demo/toolbar.js b/examples/tool/toolbar/demo/toolbar.js new file mode 100644 index 0000000000..e5ba0e607b --- /dev/null +++ b/examples/tool/toolbar/demo/toolbar.js @@ -0,0 +1,100 @@ +import G6 from '@antv/g6'; +import insertCss from 'insert-css'; + +insertCss(` + .g6-component-toolbar li { + list-style-type: none !important; + } +`); + +const data = { + nodes: [ + { + id: '0', + label: 'node-0', + x: 100, + y: 100, + description: 'This is node-0.', + subdescription: 'This is subdescription of node-0.', + }, + { + id: '1', + label: 'node-1', + x: 250, + y: 100, + description: 'This is node-1.', + subdescription: 'This is subdescription of node-1.', + }, + { + id: '2', + label: 'node-2', + x: 150, + y: 310, + description: 'This is node-2.', + subdescription: 'This is subdescription of node-2.', + }, + { + id: '3', + label: 'node-3', + x: 320, + y: 310, + description: 'This is node-3.', + subdescription: 'This is subdescription of node-3.', + }, + ], + edges: [ + { + id: 'e0', + source: '0', + target: '1', + description: 'This is edge from node 0 to node 1.', + }, + { + id: 'e1', + source: '0', + target: '2', + description: 'This is edge from node 0 to node 2.', + }, + { + id: 'e2', + source: '0', + target: '3', + description: 'This is edge from node 0 to node 3.', + }, + ], +}; +const width = document.getElementById('container').scrollWidth; +const height = document.getElementById('container').scrollHeight || 500; + +const toolbar = new G6.ToolBar(); + +const graph = new G6.Graph({ + container: 'container', + width, + height, + linkCenter: true, + plugins: [toolbar], + // 设置为true,启用 redo & undo 栈功能 + enabledStack: true, + defaultNode: { + size: [80, 40], + type: 'rect', + style: { + fill: '#DEE9FF', + stroke: '#5B8FF9', + }, + }, + defaultEdge: { + style: { + stroke: '#b5b5b5', + lineAppendWidth: 3, + }, + }, + modes: { + default: [ + 'drag-node' + ], + }, +}); +graph.data(data); +graph.render(); diff --git a/examples/tool/toolbar/index.en.md b/examples/tool/toolbar/index.en.md new file mode 100644 index 0000000000..d557bae8c2 --- /dev/null +++ b/examples/tool/toolbar/index.en.md @@ -0,0 +1,16 @@ +--- +title: ToolBar +order: 0 +--- + +G6 中内置的 ToolBar 组件。 + +## 使用指南 + +下面的代码演示展示了如何在图上使用 ToolBar。ToolBar 的样式可以使用 G6 内置的,也可以完全自定义 ToolBar 的内容,要修改内置 ToolBar 的样式,只需要修改 g6-component-toolbar 的样式: + +``` +.g6-component-toolbar { + // 自定义 CSS 内容 + } +``` diff --git a/examples/tool/toolbar/index.zh.md b/examples/tool/toolbar/index.zh.md new file mode 100644 index 0000000000..18af2e77a2 --- /dev/null +++ b/examples/tool/toolbar/index.zh.md @@ -0,0 +1,16 @@ +--- +title: 工具栏 +order: 0 +--- + +G6 中内置的 ToolBar 组件。 + +## 使用指南 + +下面的代码演示展示了如何在图上使用 ToolBar。ToolBar 的样式可以使用 G6 内置的,也可以完全自定义 ToolBar 的内容,要修改内置 ToolBar 的样式,只需要修改 g6-component-toolbar 的样式: + +``` +.g6-component-toolbar { + // 自定义 CSS 内容 + } +``` diff --git a/src/plugins/menu/index.ts b/src/plugins/menu/index.ts index d082682c8e..b716a830e0 100644 --- a/src/plugins/menu/index.ts +++ b/src/plugins/menu/index.ts @@ -65,25 +65,37 @@ export default class Menu extends Base { const className = this.get('className') const menu = createDOM(`
    `) modifyCSS(menu, { position: 'absolute', visibility: 'hidden' }); - document.body.appendChild(menu) + let container: HTMLDivElement | null = this.get('container'); + if (!container) { + container = this.get('graph').get('container'); + } + container.appendChild(menu) + this.set('menu', menu) } protected onMenuShow(e: IG6GraphEvent) { + e.preventDefault() + e.stopPropagation() + + if (!e.item) { + return + } + const self = this; - const container = this.get('menu') + const menuDom = this.get('menu') const getContent = this.get('getContent') let menu = getContent(e) if (isString(menu)) { - container.innerHTML = menu + menuDom.innerHTML = menu } else { - container.innerHTML = menu.outerHTML + menuDom.innerHTML = menu.outerHTML } const handleMenuClick = this.get('handleMenuClick') if (handleMenuClick) { - container.addEventListener('click', evt => { + menuDom.addEventListener('click', evt => { handleMenuClick(evt.target, e.item) }) } @@ -92,10 +104,11 @@ export default class Menu extends Base { const width: number = graph.get('width'); const height: number = graph.get('height'); - const bbox = container.getBoundingClientRect(); + const bbox = menuDom.getBoundingClientRect(); - let x = e.item.getModel().x || e.x; - let y = e.item.getModel().y || e.y; + + let x = e.canvasX//e.item.getModel().x || e.x; + let y = e.canvasY//e.item.getModel().y || e.y; // 若菜单超出画布范围,反向 if (x + bbox.width > width) { @@ -106,13 +119,13 @@ export default class Menu extends Base { y = height - bbox.height; } - const point = graph.getClientByPoint(x, y) - e.canvasX = point.x; - e.canvasY = point.y; + // const point = graph.getClientByPoint(x, y) + // e.canvasX = point.x; + // e.canvasY = point.y; - modifyCSS(container, { - top: `${point.y}px`, - left: `${point.x}px`, + modifyCSS(menuDom, { + top: `${y}px`, + left: `${x}px`, visibility: 'visible' }); @@ -126,9 +139,9 @@ export default class Menu extends Base { } private onMenuHide() { - const container = this.get('menu') - if (container) { - modifyCSS(container, { visibility: 'hidden' }); + const menuDom = this.get('menu') + if (menuDom) { + modifyCSS(menuDom, { visibility: 'hidden' }); } // 隐藏菜单后需要移除事件监听 @@ -136,7 +149,7 @@ export default class Menu extends Base { const handleMenuClick = this.get('handleMenuClick') if (handleMenuClick) { - container.removeEventListener('click', handleMenuClick) + menuDom.removeEventListener('click', handleMenuClick) } } @@ -150,7 +163,11 @@ export default class Menu extends Base { } if (menu) { - document.body.removeChild(menu); + let container: HTMLDivElement | null = this.get('container'); + if (!container) { + container = this.get('graph').get('container'); + } + container.removeChild(menu); } if (handler) { diff --git a/src/plugins/timeBar/index.ts b/src/plugins/timeBar/index.ts index b12831bdcf..39107e2a56 100644 --- a/src/plugins/timeBar/index.ts +++ b/src/plugins/timeBar/index.ts @@ -38,7 +38,12 @@ type TimeBarOption = Partial<{ readonly backgroundStyle: ShapeStyle; readonly foregroundStyle: ShapeStyle; - readonly handlerStyle: ShapeStyle; + // 滑块样式 + readonly handlerStyle: { + width: number; + height: number; + style: ShapeStyle; + }; readonly textStyle: ShapeStyle; // 允许滑动位置 readonly minLimit: number; @@ -95,20 +100,18 @@ export default class TimeBar extends Base { return } - const container = this.get('container') + const container: HTMLDivElement | null = this.get('container') + + const graphContainer = this.get('graph').get('container'); let timebar if (!container) { - timebar = createDOM(`
    `) + timebar = createDOM(`
    `) modifyCSS(timebar, { position: 'absolute' }); - document.body.appendChild(timebar) - } else if (isString(container)) { - timebar = createDOM(`
    `) - modifyCSS(timebar, { position: 'absolute' }); - document.body.appendChild(timebar) } else { timebar = container } + graphContainer.appendChild(timebar) this.set('timeBarContainer', timebar) @@ -153,8 +156,6 @@ export default class TimeBar extends Base { this.set('trendData', data) - console.log('配置项', config) - const slider = new Slider(config) slider.init(); @@ -234,7 +235,11 @@ export default class TimeBar extends Base { const timeBarContainer = this.get('timeBarContainer') if (timeBarContainer) { - document.body.removeChild(timeBarContainer); + let container: HTMLDivElement | null = this.get('container'); + if (!container) { + container = this.get('graph').get('container'); + } + container.removeChild(timeBarContainer); } } } diff --git a/src/plugins/tooltip/index.ts b/src/plugins/tooltip/index.ts index 934a8959a5..dfe8fab4f8 100644 --- a/src/plugins/tooltip/index.ts +++ b/src/plugins/tooltip/index.ts @@ -67,7 +67,11 @@ export default class Tooltip extends Base { const className = this.get('className') const tooltip = createDOM(`
    `) modifyCSS(tooltip, { position: 'absolute', visibility: 'hidden' }); - document.body.appendChild(tooltip) + let container: HTMLDivElement | null = this.get('container'); + if (!container) { + container = this.get('graph').get('container'); + } + container.appendChild(tooltip) this.set('tooltip', tooltip) } @@ -150,7 +154,11 @@ export default class Tooltip extends Base { const tooltip = this.get('tooltip') if (tooltip) { - document.body.removeChild(tooltip); + let container: HTMLDivElement | null = this.get('container'); + if (!container) { + container = this.get('graph').get('container'); + } + container.removeChild(tooltip); } } } diff --git a/tests/unit/plugins/menu-spec.ts b/tests/unit/plugins/menu-spec.ts index f940793ee1..1208035e9b 100644 --- a/tests/unit/plugins/menu-spec.ts +++ b/tests/unit/plugins/menu-spec.ts @@ -34,7 +34,7 @@ describe('menu', () => { graph.data(data) graph.render() - graph.destroy() + // graph.destroy() }) it('menu with dom', () => { const menu = new G6.Menu({ diff --git a/tests/unit/plugins/timebar-spec.ts b/tests/unit/plugins/timebar-spec.ts index 043a9b68a6..fb0ccb97d0 100644 --- a/tests/unit/plugins/timebar-spec.ts +++ b/tests/unit/plugins/timebar-spec.ts @@ -31,19 +31,18 @@ for(let i = 0; i < 100; i++) { data.nodes.push({ id, label: `node${i}`, - date: `2020${i}`, + date: i, value: Math.round(Math.random() * 300) }) - const edgeId = i + 3 data.edges.push({ source: `node-${Math.round(Math.random() * 90)}`, target: `node-${Math.round(Math.random() * 90)}` }) } -describe('tooltip', () => { - it('tooltip with default', () => { +describe('TimeBar', () => { + it('TimeBar with default', () => { const timeBarData = [] for(let i = 0; i < 100; i++) { @@ -61,13 +60,12 @@ describe('tooltip', () => { } } }); - const tooltip = new G6.Tooltip() const graph = new G6.Graph({ container: div, width: 500, height: 500, - plugins: [timebar, tooltip], + plugins: [timebar], modes: { default: ['drag-node', 'zoom-canvas', 'drag-canvas'] }, @@ -81,11 +79,100 @@ describe('tooltip', () => { graph.data(data) graph.render() + expect(graph.get('plugins').length).toBe(1) const timebarPlugin = graph.get('plugins')[0] + expect(timebarPlugin).not.toBe(null) console.log(timebarPlugin) - // expect(timebarPlugin.get('offset')).toBe(6) - // expect(timebarPlugin.get('tooltip').outerHTML).toBe(``) + expect(timebarPlugin.get('trendData')).toEqual(timeBarData) + expect(timebarPlugin.get('timebar').x).toBe(10) + expect(timebarPlugin.get('timebar').y).toBe(10) + expect(timebarPlugin.get('timebar').width).toBe(400) + expect(timebarPlugin.get('timebar').start).toBe(0.1) + const slider = timebarPlugin.get('slider') + expect(slider.get('name')).toEqual('slider') + expect(slider.get('maxText')).toEqual('202099') + expect(slider.get('minText')).toEqual('20200') + expect(slider.get('height')).toBe(26) + graph.destroy() + }) + + it.only('config TimeBar style', () => { + const timeBarData = [] + + for(let i = 0; i < 100; i++) { + timeBarData.push({ + date: i, + value: Math.round(Math.random() * 300) + }) + } + const timebar = new G6.TimeBar({ + timebar: { + backgroundStyle: { + fill: '#08979c', + opacity: 0.3 + }, + foregroundStyle: { + fill: '#40a9ff', + opacity: 0.4 + }, + trend: { + data: timeBarData, + isArea: false, + smooth: true, + lineStyle: { + stroke: '#9254de' + } + } + }, + rangeChange: (graph, min, max) => { + // 拿到 Graph 实例和 timebar 上范围,自己可以控制图上的渲染逻辑 + console.log(graph, min, max) + } + }); + + const graph = new G6.Graph({ + container: div, + width: 500, + height: 500, + plugins: [timebar], + modes: { + default: ['drag-node', 'zoom-canvas', 'drag-canvas'] + }, + defaultEdge: { + style: { + lineAppendWidth: 20 + } + } + }); + + graph.data(data) + graph.render() + + expect(graph.get('plugins').length).toBe(1) + const timebarPlugin = graph.get('plugins')[0] + expect(timebarPlugin).not.toBe(null) + console.log(timebarPlugin) + const timeBar = timebarPlugin.get('timebar') + expect(timebarPlugin.get('trendData')).toEqual(timeBarData) + expect(timeBar.x).toBe(10) + expect(timeBar.y).toBe(10) + expect(timeBar.width).toBe(400) + expect(timeBar.start).toBe(0.1) + + const backgroundStyle = timeBar.backgroundStyle + expect(backgroundStyle.fill).toEqual('#08979c') + expect(backgroundStyle.opacity).toBe(0.3) + + const foregroundStyle = timeBar.foregroundStyle + expect(foregroundStyle.fill).toEqual('#40a9ff') + expect(foregroundStyle.opacity).toBe(0.4) + + const slider = timebarPlugin.get('slider') + expect(slider.get('name')).toEqual('slider') + expect(slider.get('maxText')).toEqual(99) + expect(slider.get('minText')).toEqual(0) + expect(slider.get('height')).toBe(26) // graph.destroy() }) }); diff --git a/tests/unit/plugins/tooltip-spec.ts b/tests/unit/plugins/tooltip-spec.ts index 1cf593e6a6..5133f8e3fe 100644 --- a/tests/unit/plugins/tooltip-spec.ts +++ b/tests/unit/plugins/tooltip-spec.ts @@ -38,6 +38,9 @@ describe('tooltip', () => { modes: { default: ['drag-node', 'zoom-canvas', 'drag-canvas'] }, + defaultNode: { + type: 'rect' + }, defaultEdge: { style: { lineAppendWidth: 20 @@ -54,7 +57,7 @@ describe('tooltip', () => { graph.destroy() }) - it('menu with dom', () => { + it('tooltip with dom', () => { const tooltip = new G6.Tooltip({ offset: 10, getContent(e) { @@ -91,7 +94,7 @@ describe('tooltip', () => { expect(tooltipPlugin.get('offset')).toBe(10) graph.destroy() }) - it('menu with string', () => { + it('tooltip with string', () => { const tooltip = new G6.Tooltip({ getContent(e) { return `
    From c8a2d7bf66ee8f9c3a29c9a7666ee239d3baf269 Mon Sep 17 00:00:00 2001 From: chenluli Date: Tue, 21 Jul 2020 14:07:14 +0800 Subject: [PATCH 57/77] fix: drag new node without initial position --- package.json | 2 +- src/graph/controller/item.ts | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index ca4fed5c92..957dddab41 100644 --- a/package.json +++ b/package.json @@ -127,4 +127,4 @@ "webpack-cli": "^3.3.10", "worker-loader": "^2.0.0" } -} +} \ No newline at end of file diff --git a/src/graph/controller/item.ts b/src/graph/controller/item.ts index db14ab8324..8c896fdd95 100644 --- a/src/graph/controller/item.ts +++ b/src/graph/controller/item.ts @@ -126,6 +126,9 @@ export default class ItemController { group: parent.addGroup(), }); } else if (type === NODE) { + model.x = model.x || 0; + model.y = model.y || 0; + item = new Node({ model, styles, From 59e22fff7b34cf063b9eef5a867fc9e5fa17eea4 Mon Sep 17 00:00:00 2001 From: Yanyan-Wang Date: Wed, 22 Jul 2020 12:07:36 +0800 Subject: [PATCH 58/77] feat: fix the initial positions by equably distributing for layout to produce similar result. --- src/graph/controller/item.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/graph/controller/item.ts b/src/graph/controller/item.ts index 8c896fdd95..db14ab8324 100644 --- a/src/graph/controller/item.ts +++ b/src/graph/controller/item.ts @@ -126,9 +126,6 @@ export default class ItemController { group: parent.addGroup(), }); } else if (type === NODE) { - model.x = model.x || 0; - model.y = model.y || 0; - item = new Node({ model, styles, From 748196a4469e7eb43623bd70698177ad02e7e7dc Mon Sep 17 00:00:00 2001 From: Yanyan-Wang Date: Thu, 23 Jul 2020 14:35:39 +0800 Subject: [PATCH 59/77] fix: node:click is triggered twice while clicking a node; fix: update combo edge when drag node out of it problem. --- src/graph/controller/event.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/graph/controller/event.ts b/src/graph/controller/event.ts index 18591d1ec8..2997fa3ab1 100644 --- a/src/graph/controller/event.ts +++ b/src/graph/controller/event.ts @@ -160,10 +160,8 @@ export default class EventController { // emit('click', evt); graph.emit(eventType, evt); - if (evt.name && !evt.name.includes(':')) graph.emit(`${type}:${eventType}`, evt); // emit('node:click', evt) - else graph.emit(evt.name, evt); // emit('text-shape:click', evt) - - graph.emit(evt.name, evt); + if (evt.name && !evt.name.includes(':')) graph.emit(`${type}:${eventType}`, evt); + else graph.emit(evt.name, evt); if (eventType === 'dragstart') { this.dragging = true; From 282bf4f01fc966cf5532f998242fe91caf5a8067 Mon Sep 17 00:00:00 2001 From: Yanyan-Wang Date: Thu, 23 Jul 2020 17:25:04 +0800 Subject: [PATCH 60/77] feat: animate configuration for combo, true by default. --- src/shape/shapeBase.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/shape/shapeBase.ts b/src/shape/shapeBase.ts index 0505196c85..2bd68bbd93 100644 --- a/src/shape/shapeBase.ts +++ b/src/shape/shapeBase.ts @@ -310,8 +310,6 @@ export const shapeBase: ShapeOptions = { const style = styles[key]; if (isPlainObject(style)) { const subShape = group.find((element) => element.get('name') === key); - - if (subShape) { subShape.attr(style); } From 93597524301249321f62ee0335987d48bda5778b Mon Sep 17 00:00:00 2001 From: Yanyan-Wang Date: Thu, 23 Jul 2020 21:59:33 +0800 Subject: [PATCH 61/77] docs: update demos of the site. --- src/graph/controller/event.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/graph/controller/event.ts b/src/graph/controller/event.ts index 2997fa3ab1..eb0a40458e 100644 --- a/src/graph/controller/event.ts +++ b/src/graph/controller/event.ts @@ -160,8 +160,8 @@ export default class EventController { // emit('click', evt); graph.emit(eventType, evt); - if (evt.name && !evt.name.includes(':')) graph.emit(`${type}:${eventType}`, evt); - else graph.emit(evt.name, evt); + if (evt.name && !evt.name.includes(':')) graph.emit(`${type}:${eventType}`, evt); // emit('node:click', evt) + else graph.emit(evt.name, evt); // emit('text-shape:click', evt) if (eventType === 'dragstart') { this.dragging = true; From 44cc278e27c53fed9116388d6be7b2768bd3897b Mon Sep 17 00:00:00 2001 From: Yanyan-Wang Date: Thu, 23 Jul 2020 22:27:25 +0800 Subject: [PATCH 62/77] fix: canvas.on leads to tooltip problem. --- gatsby-browser.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gatsby-browser.js b/gatsby-browser.js index 98906e728f..d1b4d76a74 100644 --- a/gatsby-browser.js +++ b/gatsby-browser.js @@ -1,3 +1,3 @@ -// window.g6 = require('./src/index.ts'); // import the source for debugging -window.g6 = require('./dist/g6.min.js'); // import the package for webworker +window.g6 = require('./src/index.ts'); // import the source for debugging +// window.g6 = require('./dist/g6.min.js'); // import the package for webworker window.insertCss = require('insert-css'); From 63ab3018a4e48965868055924f024f1e644389de Mon Sep 17 00:00:00 2001 From: baizn <576375879@qq.com> Date: Wed, 15 Jul 2020 14:47:48 +0800 Subject: [PATCH 63/77] feat: slider timebar component --- package.json | 2 +- tests/unit/plugins/timebar-spec.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 957dddab41..ca4fed5c92 100644 --- a/package.json +++ b/package.json @@ -127,4 +127,4 @@ "webpack-cli": "^3.3.10", "worker-loader": "^2.0.0" } -} \ No newline at end of file +} diff --git a/tests/unit/plugins/timebar-spec.ts b/tests/unit/plugins/timebar-spec.ts index fb0ccb97d0..6042c00caa 100644 --- a/tests/unit/plugins/timebar-spec.ts +++ b/tests/unit/plugins/timebar-spec.ts @@ -173,6 +173,6 @@ describe('TimeBar', () => { expect(slider.get('maxText')).toEqual(99) expect(slider.get('minText')).toEqual(0) expect(slider.get('height')).toBe(26) - // graph.destroy() + graph.destroy() }) }); From 84e5e9bbbb5cb50142ad0cd9b4fa0ecaace4c8ea Mon Sep 17 00:00:00 2001 From: chenluli Date: Tue, 21 Jul 2020 14:07:14 +0800 Subject: [PATCH 64/77] fix: drag new node without initial position --- package.json | 2 +- src/graph/controller/item.ts | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index ca4fed5c92..957dddab41 100644 --- a/package.json +++ b/package.json @@ -127,4 +127,4 @@ "webpack-cli": "^3.3.10", "worker-loader": "^2.0.0" } -} +} \ No newline at end of file diff --git a/src/graph/controller/item.ts b/src/graph/controller/item.ts index db14ab8324..8c896fdd95 100644 --- a/src/graph/controller/item.ts +++ b/src/graph/controller/item.ts @@ -126,6 +126,9 @@ export default class ItemController { group: parent.addGroup(), }); } else if (type === NODE) { + model.x = model.x || 0; + model.y = model.y || 0; + item = new Node({ model, styles, From 5e437277e54709849a182cea30abf65c92afa79d Mon Sep 17 00:00:00 2001 From: Yanyan-Wang Date: Wed, 22 Jul 2020 12:07:36 +0800 Subject: [PATCH 65/77] feat: fix the initial positions by equably distributing for layout to produce similar result. --- gatsby-browser.js | 4 ++-- src/graph/controller/item.ts | 3 --- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/gatsby-browser.js b/gatsby-browser.js index d1b4d76a74..98906e728f 100644 --- a/gatsby-browser.js +++ b/gatsby-browser.js @@ -1,3 +1,3 @@ -window.g6 = require('./src/index.ts'); // import the source for debugging -// window.g6 = require('./dist/g6.min.js'); // import the package for webworker +// window.g6 = require('./src/index.ts'); // import the source for debugging +window.g6 = require('./dist/g6.min.js'); // import the package for webworker window.insertCss = require('insert-css'); diff --git a/src/graph/controller/item.ts b/src/graph/controller/item.ts index 8c896fdd95..db14ab8324 100644 --- a/src/graph/controller/item.ts +++ b/src/graph/controller/item.ts @@ -126,9 +126,6 @@ export default class ItemController { group: parent.addGroup(), }); } else if (type === NODE) { - model.x = model.x || 0; - model.y = model.y || 0; - item = new Node({ model, styles, From 32c01d9125740b5db4414a28d90646bd86866aaa Mon Sep 17 00:00:00 2001 From: baizn <576375879@qq.com> Date: Wed, 15 Jul 2020 14:47:48 +0800 Subject: [PATCH 66/77] feat: slider timebar component --- package.json | 2 +- tests/unit/plugins/timebar-spec.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 957dddab41..ca4fed5c92 100644 --- a/package.json +++ b/package.json @@ -127,4 +127,4 @@ "webpack-cli": "^3.3.10", "worker-loader": "^2.0.0" } -} \ No newline at end of file +} diff --git a/tests/unit/plugins/timebar-spec.ts b/tests/unit/plugins/timebar-spec.ts index 6042c00caa..b76866575d 100644 --- a/tests/unit/plugins/timebar-spec.ts +++ b/tests/unit/plugins/timebar-spec.ts @@ -97,7 +97,7 @@ describe('TimeBar', () => { graph.destroy() }) - it.only('config TimeBar style', () => { + it('config TimeBar style', () => { const timeBarData = [] for(let i = 0; i < 100; i++) { From 7aee39402c8ecd759eddc0dee53e12ab538b5391 Mon Sep 17 00:00:00 2001 From: chenluli Date: Tue, 21 Jul 2020 14:07:14 +0800 Subject: [PATCH 67/77] fix: drag new node without initial position --- package.json | 2 +- src/graph/controller/item.ts | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index ca4fed5c92..957dddab41 100644 --- a/package.json +++ b/package.json @@ -127,4 +127,4 @@ "webpack-cli": "^3.3.10", "worker-loader": "^2.0.0" } -} +} \ No newline at end of file diff --git a/src/graph/controller/item.ts b/src/graph/controller/item.ts index db14ab8324..8c896fdd95 100644 --- a/src/graph/controller/item.ts +++ b/src/graph/controller/item.ts @@ -126,6 +126,9 @@ export default class ItemController { group: parent.addGroup(), }); } else if (type === NODE) { + model.x = model.x || 0; + model.y = model.y || 0; + item = new Node({ model, styles, From 313d9f64474e45f7fe2ea10486fcd10b08f52134 Mon Sep 17 00:00:00 2001 From: Yanyan-Wang Date: Wed, 22 Jul 2020 12:07:36 +0800 Subject: [PATCH 68/77] feat: fix the initial positions by equably distributing for layout to produce similar result. --- src/graph/controller/item.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/graph/controller/item.ts b/src/graph/controller/item.ts index 8c896fdd95..db14ab8324 100644 --- a/src/graph/controller/item.ts +++ b/src/graph/controller/item.ts @@ -126,9 +126,6 @@ export default class ItemController { group: parent.addGroup(), }); } else if (type === NODE) { - model.x = model.x || 0; - model.y = model.y || 0; - item = new Node({ model, styles, From ba09292a0eff6b9d7ffde7cfa7ca6c090d683c81 Mon Sep 17 00:00:00 2001 From: baizn <576375879@qq.com> Date: Wed, 15 Jul 2020 14:47:48 +0800 Subject: [PATCH 69/77] feat: slider timebar component --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 957dddab41..ca4fed5c92 100644 --- a/package.json +++ b/package.json @@ -127,4 +127,4 @@ "webpack-cli": "^3.3.10", "worker-loader": "^2.0.0" } -} \ No newline at end of file +} From 234e3233f1ba8baba74513dbe345592ce5a6eb36 Mon Sep 17 00:00:00 2001 From: chenluli Date: Tue, 21 Jul 2020 14:07:14 +0800 Subject: [PATCH 70/77] fix: drag new node without initial position --- package.json | 2 +- src/graph/controller/item.ts | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index ca4fed5c92..957dddab41 100644 --- a/package.json +++ b/package.json @@ -127,4 +127,4 @@ "webpack-cli": "^3.3.10", "worker-loader": "^2.0.0" } -} +} \ No newline at end of file diff --git a/src/graph/controller/item.ts b/src/graph/controller/item.ts index db14ab8324..8c896fdd95 100644 --- a/src/graph/controller/item.ts +++ b/src/graph/controller/item.ts @@ -126,6 +126,9 @@ export default class ItemController { group: parent.addGroup(), }); } else if (type === NODE) { + model.x = model.x || 0; + model.y = model.y || 0; + item = new Node({ model, styles, From 50ddc45faa2ad6bac40fb806d29c908446121e45 Mon Sep 17 00:00:00 2001 From: Yanyan-Wang Date: Wed, 22 Jul 2020 12:07:36 +0800 Subject: [PATCH 71/77] feat: fix the initial positions by equably distributing for layout to produce similar result. --- src/graph/controller/item.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/graph/controller/item.ts b/src/graph/controller/item.ts index 8c896fdd95..db14ab8324 100644 --- a/src/graph/controller/item.ts +++ b/src/graph/controller/item.ts @@ -126,9 +126,6 @@ export default class ItemController { group: parent.addGroup(), }); } else if (type === NODE) { - model.x = model.x || 0; - model.y = model.y || 0; - item = new Node({ model, styles, From 381bddad639341eb76038e3f19517f48070118ea Mon Sep 17 00:00:00 2001 From: Yanyan-Wang Date: Thu, 23 Jul 2020 22:27:25 +0800 Subject: [PATCH 72/77] fix: canvas.on leads to tooltip problem. --- gatsby-browser.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gatsby-browser.js b/gatsby-browser.js index 98906e728f..d1b4d76a74 100644 --- a/gatsby-browser.js +++ b/gatsby-browser.js @@ -1,3 +1,3 @@ -// window.g6 = require('./src/index.ts'); // import the source for debugging -window.g6 = require('./dist/g6.min.js'); // import the package for webworker +window.g6 = require('./src/index.ts'); // import the source for debugging +// window.g6 = require('./dist/g6.min.js'); // import the package for webworker window.insertCss = require('insert-css'); From 4f41f58fabcd313e09b1e18d7485f5fc6632ff8c Mon Sep 17 00:00:00 2001 From: baizn <576375879@qq.com> Date: Wed, 15 Jul 2020 14:47:48 +0800 Subject: [PATCH 73/77] feat: slider timebar component --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 957dddab41..ca4fed5c92 100644 --- a/package.json +++ b/package.json @@ -127,4 +127,4 @@ "webpack-cli": "^3.3.10", "worker-loader": "^2.0.0" } -} \ No newline at end of file +} From a58838bfcc03abf2160e34c8dd02493a0d4d5d34 Mon Sep 17 00:00:00 2001 From: chenluli Date: Tue, 21 Jul 2020 14:07:14 +0800 Subject: [PATCH 74/77] fix: drag new node without initial position --- package.json | 2 +- src/graph/controller/item.ts | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index ca4fed5c92..957dddab41 100644 --- a/package.json +++ b/package.json @@ -127,4 +127,4 @@ "webpack-cli": "^3.3.10", "worker-loader": "^2.0.0" } -} +} \ No newline at end of file diff --git a/src/graph/controller/item.ts b/src/graph/controller/item.ts index db14ab8324..8c896fdd95 100644 --- a/src/graph/controller/item.ts +++ b/src/graph/controller/item.ts @@ -126,6 +126,9 @@ export default class ItemController { group: parent.addGroup(), }); } else if (type === NODE) { + model.x = model.x || 0; + model.y = model.y || 0; + item = new Node({ model, styles, From 29f7b091b56bb0beadfb319e9eb6514b0fcf27e1 Mon Sep 17 00:00:00 2001 From: Yanyan-Wang Date: Wed, 22 Jul 2020 12:07:36 +0800 Subject: [PATCH 75/77] feat: fix the initial positions by equably distributing for layout to produce similar result. --- gatsby-browser.js | 4 ++-- src/graph/controller/item.ts | 3 --- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/gatsby-browser.js b/gatsby-browser.js index d1b4d76a74..98906e728f 100644 --- a/gatsby-browser.js +++ b/gatsby-browser.js @@ -1,3 +1,3 @@ -window.g6 = require('./src/index.ts'); // import the source for debugging -// window.g6 = require('./dist/g6.min.js'); // import the package for webworker +// window.g6 = require('./src/index.ts'); // import the source for debugging +window.g6 = require('./dist/g6.min.js'); // import the package for webworker window.insertCss = require('insert-css'); diff --git a/src/graph/controller/item.ts b/src/graph/controller/item.ts index 8c896fdd95..db14ab8324 100644 --- a/src/graph/controller/item.ts +++ b/src/graph/controller/item.ts @@ -126,9 +126,6 @@ export default class ItemController { group: parent.addGroup(), }); } else if (type === NODE) { - model.x = model.x || 0; - model.y = model.y || 0; - item = new Node({ model, styles, From 4f981c0c80650d0a73a066aaf70f1fb3e5f85f68 Mon Sep 17 00:00:00 2001 From: baizn <576375879@qq.com> Date: Mon, 27 Jul 2020 11:34:13 +0800 Subject: [PATCH 76/77] fix: add plugin docs --- docs/api/Plugins.en.md | 358 ++++++++++++++++++ docs/api/Plugins.zh.md | 1 + examples/tool/contextMenu/demo/contextMenu.js | 15 - examples/tool/timebar/index.en.md | 11 + examples/tool/toolbar/index.en.md | 8 +- 5 files changed, 374 insertions(+), 19 deletions(-) create mode 100644 examples/tool/timebar/index.en.md diff --git a/docs/api/Plugins.en.md b/docs/api/Plugins.en.md index f60fea431f..8032183c00 100644 --- a/docs/api/Plugins.en.md +++ b/docs/api/Plugins.en.md @@ -8,6 +8,10 @@ There are several plugins in G6 which can be used for G6's graph or other applic - [Grid](#grid) - [Minimap](#minimap) - [Edge Bundling](#edge-bundling) +- [Menu](#menu) +- [ToolBar](#toolbar) +- [TimeBar](#timebar) +- [Tooltip](#tooltip) ## Configure to Graph @@ -90,3 +94,357 @@ The edge bundling plugin can be configured to adjust the styles and functions. | iterations | Number | false | 90 | The initial number of inner interations. It will be multiplied by `iterRate` in each cycle | | iterRate | Number | false | 0.6666667 | The rate of the iterations decreasement | | bundleThreshold | Number | false | 0.6 | The edge similarity threshold for bundling. Large number means the edges in one bundle have smaller similarity, in other words, more edges in one bundle | + + +## Menu + +Menu is used to configure the right-click menu on the node. + +### Configuration + +| Name | Type | Required | Description | +| --- | --- | --- | --- | +| className | string | null | the class name of the menu dom | +| getContent | (graph?: IGraph) => HTMLDivElement / string | img | the menu content,supports DOM or string | +| handleMenuClick | (target: HTMLElement, item: Item) => void | undefined | the callback function when click the menu | + +### Usage + +Use G6 build-in menu by default. + +``` +// Instantiate Menu plugin +const menu = new G6.Menu(); +const graph = new G6.Graph({ + //... other Configuration + plugins: [menu], +}); +``` + +#### DOM Menu + +``` +const menu = new G6.Menu({ + getContent(e) { + const outDiv = document.createElement('div'); + outDiv.style.width = '180px'; + outDiv.innerHTML = `
      +
    • menu01
    • +
    • menu01
    • +
    • menu01
    • +
    • menu01
    • +
    • menu01
    • +
    ` + return outDiv + }, + handleMenuClick(target, item) { + console.log(target, item) + }, +}); + +const graph = new G6.Graph({ + //... other Configuration + plugins: [menu], // the Menu plugin +}); +``` + +#### String Menu + +``` +const menu = new G6.Menu({ + getContent(graph) { + return `
      +
    • menu02
    • +
    • menu02
    • +
    • menu02
    • +
    • menu02
    • +
    • menu02
    • +
    `; + }, + handleMenuClick(target, item) { + console.log(target, item) + }, +}); + +const graph = new G6.Graph({ + //... other Configuration + plugins: [menu], // The Menu plugin +}); +``` + +## ToolBar + +ToolBar 集成了以下常见的操作: +- 重做; +- 撤销; +- 放大; +- 缩小; +- 适应屏幕; +- 实际大小。 + +### Configuration + +| 名称 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| container | HTMLDivElement | null | ToolBar 容器,如果不设置,则默认使用 canvas 的 DOM 容器 | +| className | string | null | ToolBar 内容元素的 class 类名 | +| getContent | (graph?: IGraph) => HTMLDivElement | string | img | ToolBar 内容,支持 DOM 元素或字符串 | +| handleClick | (code: string, graph: IGraph) => void | undefined | 点击 ToolBar 中每个图标的回调函数 | +| position | Point | null | ToolBar 的位置坐标 | + +### Usage + +#### Default Usage +默认的 ToolBar 提供了撤销、重做、放大等功能。 + +``` +const toolbar = new G6.ToolBar(); + +const graph = new G6.Graph({ + //... 其他配置项 + plugins: [toolbar], // 配置 Menu 插件 +}); +``` + +#### String ToolBar + +``` +const tc = document.createElement('div'); +tc.id = 'toolbarContainer'; +document.body.appendChild(tc); + +const toolbar = new G6.ToolBar({ + container: tc, + getContent: () => { + return ` +
      +
    • 测试
    • +
    • 撤销
    • +
    + ` + }, + handleClick: (code, graph) => { + if (code === 'add') { + graph.addItem('node', { + id: 'node2', + label: 'node2', + x: 300, + y: 150 + }) + } else if (code === 'undo') { + toolbar.undo() + } + } +}); + +const graph = new G6.Graph({ + //... 其他配置项 + plugins: [toolbar], // 配置 Menu 插件 +}); +``` + +#### DOM ToolBar + +``` +const toolbar = new G6.ToolBar({ + getContent: () => { + const outDiv = document.createElement('div'); + outDiv.style.width = '180px'; + outDiv.innerHTML = `
      +
    • 测试01
    • +
    • 测试01
    • +
    • 测试01
    • +
    • 测试01
    • +
    • 测试01
    • +
    ` + return outDiv + }, + handleClick: (code, graph) => { + + } +}); + +const graph = new G6.Graph({ + //... 其他配置项 + plugins: [toolbar], // 配置 Menu 插件 +}); +``` + +## TimeBar + +目前 G6 内置的 TimeBar 主要有以下功能: +- 改变时间范围,过滤图上的数据; +- TimeBar 上展示指定字段随时间推移的变化趋势。 + +img + +**说明:** 目前的 TimeBar 功能还比较简单,不能用于较为复杂的时序分析。 + +### Configuration + +| 名称 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| container | HTMLDivElement | null | TimeBar 容器,如果不设置,则默认创建 className 为 g6-component-timebar 的 DOM 容器 | +| width | number | 400 | TimeBar 容器宽度 | +| height | number | 400 | TimeBar 容器高度 | +| timebar | TimeBarOption | {} | TimeBar 样式配置项 | +| rangeChange | (graph: IGraph, min: number, max: number) => void | null | 改变时间范围后的回调函数 | + + +**TimeBarOption 配置项** + +``` +interface HandleStyle { + width: number; + height: number; + style: ShapeStyle; +} +``` + +| 名称 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| x | number | 0 | TimeBar 起始 x 坐标 | +| y | number | 0 | TimeBar 起始 y 坐标 | +| width | number | 400 | TimeBar 宽度 | +| height | number | 400 | TimeBar 高度 | +| backgroundStyle | ShapeStyle | {} | TimeBar 背景样式配置项 | +| foregroundStyle | ShapeStyle | {} | TimeBar 选中部分样式配置项 | +| handlerStyle | HandleStyle | null | 滑块样式设置 | +| textStyle | ShapeStyle | null | 文本样式 | +| minLimit | number | 0 | 允许滑块最左边(最小)位置,范围 0-1 | +| maxLimit | number | 1 | 允许滑块最右边(最大)位置,范围 0-1 | +| start | number | 0 | 滑块初始开始位置 | +| end | number | 1 | 滑块初始结束位置 | +| minText | string | null | 滑块最小值时显示的文本 | +| maxText | string | null | 滑块最大值时显示的文本 | +| trend | TrendConfig | null | 滑块上趋势图配置 | + +**TrendConfig 配置项** + +``` +interface Data { + date: string; + value: number; +} +``` + +| 名称 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| data | Data[] | [] | 滑块上的数据源 | +| smooth | boolean | false | 是否是平滑的曲线 | +| isArea | boolean | false | 是否显示面积图 | +| lineStyle | ShapeStyle | null | 折线的样式 | +| areaStyle | ShapeStyle | null | 面积的样式,只有当 isArea 为 true 时生效 | + +### 用法 + +#### 默认用法 +G6 内置的默认的 TimeBar 有默认的样式及交互功能。 + +``` +const timebar = new G6.TimeBar(); + +const graph = new G6.Graph({ + //... 其他配置项 + plugins: [timebar], // 配置 timebar 插件 +}); +``` + +##### 配置样式 +可以个性化定制 TimeBar 的样式,也可以自己定义时间范围改变后的处理方式。 + +``` +const timebar = new G6.TimeBar({ + width: 600, + timebar: { + width: 600, + backgroundStyle: { + fill: '#08979c', + opacity: 0.3 + }, + foregroundStyle: { + fill: '#40a9ff', + opacity: 0.4 + }, + trend: { + data: timeBarData, + isArea: false, + smooth: true, + lineStyle: { + stroke: '#9254de' + } + } + }, + rangeChange: (graph, min, max) => { + // 拿到 Graph 实例和 timebar 上范围,自己可以控制图上的渲染逻辑 + console.log(graph, min, max) + } +}); + +const graph = new G6.Graph({ + //... 其他配置项 + plugins: [timebar], // 配置 timebar 插件 +}); +``` + + +## ToolTip + +ToolTip 插件主要用于在节点和边上展示一些辅助信息,G6 4.0 以后,Tooltip 插件将会替换 Behavior 中的 tooltip。 + +### Configuration + +| 名称 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| className | string | null | tooltip 容器的 class 类名 | +| container | HTMLDivElement | null | Tooltip 容器,如果不设置,则默认使用 canvas 的 DOM 容器 | +| getContent | (graph?: IGraph) => HTMLDivElement / string | img | Tooltip 内容,支持 DOM 元素或字符串 | +| offset | number | 6 | tooltip 的偏移值,作用于 x y 两个方向上 | + +### 用法 + +默认的 Tooltip 只展示元素类型和 ID,一般情况下都需要用户自己定义 Tooltip 上面展示的内容。 + +#### Dom Tooltip +``` +const tooltip = new G6.Tooltip({ + offset: 10, + getContent(e) { + const outDiv = document.createElement('div'); + outDiv.style.width = '180px'; + outDiv.innerHTML = ` +

    自定义tooltip

    +
      +
    • Label: ${e.item.getModel().label || e.item.getModel().id}
    • +
    ` + return outDiv + }, +}); + +const graph = new G6.Graph({ + //... 其他配置项 + plugins: [tooltip], // 配置 Menu 插件 +}); +``` + +#### String Tooltip +``` +const tooltip = new G6.Tooltip({ + getContent(e) { + return `
    + +
    `; + }, +}); + +const graph = new G6.Graph({ + //... 其他配置项 + plugins: [tooltip], // 配置 Menu 插件 +}); +``` diff --git a/docs/api/Plugins.zh.md b/docs/api/Plugins.zh.md index 405b01b2ad..097d54658b 100644 --- a/docs/api/Plugins.zh.md +++ b/docs/api/Plugins.zh.md @@ -10,6 +10,7 @@ G6 中支持插件提供了一些可插拔的组件,包括: - [Edge Bundling](#edge-bundling) - [Menu](#menu) - [ToolBar](#toolbar) +- [TimeBar](#timebar) - [Tooltip](#tooltip) ## 配置方法 diff --git a/examples/tool/contextMenu/demo/contextMenu.js b/examples/tool/contextMenu/demo/contextMenu.js index a0a8508d1f..15a9cdf7af 100644 --- a/examples/tool/contextMenu/demo/contextMenu.js +++ b/examples/tool/contextMenu/demo/contextMenu.js @@ -30,21 +30,6 @@ descriptionDiv.id = 'discription'; descriptionDiv.innerHTML = 'Right click a node to activate a contextMenu.'; document.getElementById('container').appendChild(descriptionDiv); -// JSX and HTML templates are available for the menu -// create ul -const conextMenuContainer = document.createElement('ul'); -conextMenuContainer.id = 'contextMenu'; - -// create li -const firstLi = document.createElement('li'); -firstLi.innerText = 'Option 1'; -conextMenuContainer.appendChild(firstLi); - -const lastLi = document.createElement('li'); -lastLi.innerText = 'Option 2'; -conextMenuContainer.appendChild(lastLi); -document.getElementById('container').appendChild(conextMenuContainer); - const width = document.getElementById('container').scrollWidth; const height = document.getElementById('container').scrollHeight || 500; diff --git a/examples/tool/timebar/index.en.md b/examples/tool/timebar/index.en.md new file mode 100644 index 0000000000..1ca061c663 --- /dev/null +++ b/examples/tool/timebar/index.en.md @@ -0,0 +1,11 @@ +--- +title: TimeBar +order: 0 +--- + +TimeBar is a build-in component in G6. + +## Usage + +The demo below show how to use TimeBar on graph. + diff --git a/examples/tool/toolbar/index.en.md b/examples/tool/toolbar/index.en.md index d557bae8c2..2fba94a8e1 100644 --- a/examples/tool/toolbar/index.en.md +++ b/examples/tool/toolbar/index.en.md @@ -3,14 +3,14 @@ title: ToolBar order: 0 --- -G6 中内置的 ToolBar 组件。 +ToolBar is a build-in Component in G6. -## 使用指南 +## Usage -下面的代码演示展示了如何在图上使用 ToolBar。ToolBar 的样式可以使用 G6 内置的,也可以完全自定义 ToolBar 的内容,要修改内置 ToolBar 的样式,只需要修改 g6-component-toolbar 的样式: +The demo below show how to use toolbar on graph. Toolbar's style can be defined by the CSS with class name `g6-component-toolbar`: ``` .g6-component-toolbar { - // 自定义 CSS 内容 + // css styles } ``` From 1ac6a5edd84513191f58d4520b69e574699a58fb Mon Sep 17 00:00:00 2001 From: baizn <576375879@qq.com> Date: Mon, 27 Jul 2020 11:43:37 +0800 Subject: [PATCH 77/77] fix: update export default --- src/index.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/index.ts b/src/index.ts index 75fdef24b9..76384b760c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -55,12 +55,12 @@ export default { registerLayout: Layout.registerLayout, Layout, Global, - Minimap: Plugins.Minimap, - Grid: Plugins.Grid, - Bundling: Plugins.Bundling, - Menu: Plugins.Menu, - ToolBar: Plugins.ToolBar, - Tooltip: Plugins.Tooltip, + Minimap, + Grid, + Bundling, + Menu, + ToolBar, + Tooltip, TimeBar, Algorithm, Arrow,