diff --git a/src/chart/graph/GraphView.ts b/src/chart/graph/GraphView.ts index 08cc6ae46f..20f4448eb3 100644 --- a/src/chart/graph/GraphView.ts +++ b/src/chart/graph/GraphView.ts @@ -37,6 +37,9 @@ import SeriesData from '../../data/SeriesData'; import Line from '../helper/Line'; import { getECData } from '../../util/innerStore'; +import { simpleLayoutEdge } from './simpleLayoutHelper'; +import { circularLayout, rotateNodeLabel } from './circularLayoutHelper'; + function isViewCoordSys(coordSys: CoordinateSystem): coordSys is View { return coordSys.type === 'view'; } @@ -122,6 +125,8 @@ class GraphView extends ChartView { this._startForceLayoutIteration(forceLayout, layoutAnimation); } + const layout = seriesModel.get('layout'); + data.graph.eachNode((node) => { const idx = node.dataIndex; const el = node.getGraphicEl() as Symbol; @@ -130,14 +135,31 @@ class GraphView extends ChartView { el.off('drag').off('dragend'); const draggable = itemModel.get('draggable'); if (draggable) { - el.on('drag', () => { - if (forceLayout) { - forceLayout.warmUp(); - !this._layouting - && this._startForceLayoutIteration(forceLayout, layoutAnimation); - forceLayout.setFixed(idx); - // Write position back to layout - data.setItemLayout(idx, [el.x, el.y]); + el.on('drag', (e) => { + switch (layout) { + case 'force': + forceLayout.warmUp(); + !this._layouting + && this._startForceLayoutIteration(forceLayout, layoutAnimation); + forceLayout.setFixed(idx); + // Write position back to layout + data.setItemLayout(idx, [el.x, el.y]); + break; + case 'circular': + data.setItemLayout(idx, [el.x, el.y]); + // mark node fixed + node.setLayout({ fixed: true }, true); + // recalculate circular layout + circularLayout(seriesModel, 'symbolSize', node, [e.offsetX, e.offsetY]); + this.updateLayout(seriesModel); + break; + case 'none': + default: + data.setItemLayout(idx, [el.x, el.y]); + // update edge + simpleLayoutEdge(seriesModel.getGraph(), seriesModel); + this.updateLayout(seriesModel); + break; } }).on('dragend', () => { if (forceLayout) { @@ -145,7 +167,7 @@ class GraphView extends ChartView { } }); } - el.setDraggable(draggable && !!forceLayout); + el.setDraggable(draggable); const focus = itemModel.get(['emphasis', 'focus']); @@ -170,37 +192,8 @@ class GraphView extends ChartView { && seriesModel.get(['circular', 'rotateLabel']); const cx = data.getLayout('cx'); const cy = data.getLayout('cy'); - data.eachItemGraphicEl(function (el: Symbol, idx) { - const itemModel = data.getItemModel(idx); - let labelRotate = itemModel.get(['label', 'rotate']) || 0; - const symbolPath = el.getSymbolPath(); - if (circularRotateLabel) { - const pos = data.getItemLayout(idx); - let rad = Math.atan2(pos[1] - cy, pos[0] - cx); - if (rad < 0) { - rad = Math.PI * 2 + rad; - } - const isLeft = pos[0] < cx; - if (isLeft) { - rad = rad - Math.PI; - } - const textPosition = isLeft ? 'left' as const : 'right' as const; - - symbolPath.setTextConfig({ - rotation: -rad, - position: textPosition, - origin: 'center' - }); - const emphasisState = symbolPath.ensureState('emphasis'); - zrUtil.extend(emphasisState.textConfig || (emphasisState.textConfig = {}), { - position: textPosition - }); - } - else { - symbolPath.setTextConfig({ - rotation: labelRotate *= Math.PI / 180 - }); - } + data.graph.eachNode((node) => { + rotateNodeLabel(node, circularRotateLabel, cx, cy); }); this._firstRender = false; diff --git a/src/chart/graph/circularLayoutHelper.ts b/src/chart/graph/circularLayoutHelper.ts index 91265bea62..af01fb0f8c 100644 --- a/src/chart/graph/circularLayoutHelper.ts +++ b/src/chart/graph/circularLayoutHelper.ts @@ -20,8 +20,9 @@ import * as vec2 from 'zrender/src/core/vector'; import {getSymbolSize, getNodeGlobalScale} from './graphHelper'; -import GraphSeriesModel, { GraphEdgeItemOption } from './GraphSeries'; -import Graph from '../../data/Graph'; +import GraphSeriesModel, { GraphEdgeItemOption, GraphNodeItemOption } from './GraphSeries'; +import Graph, { GraphNode } from '../../data/Graph'; +import Symbol from '../helper/Symbol'; import SeriesData from '../../data/SeriesData'; import * as zrUtil from 'zrender/src/core/util'; import {getCurvenessForEdge} from '../helper/multipleGraphEdgeHelper'; @@ -51,7 +52,9 @@ const _symbolRadiansHalf: number[] = []; */ export function circularLayout( seriesModel: GraphSeriesModel, - basedOn: 'value' | 'symbolSize' + basedOn: 'value' | 'symbolSize', + draggingNode?: GraphNode, + pointer?: [number, number] ) { const coordSys = seriesModel.coordinateSystem; if (coordSys && coordSys.type !== 'view') { @@ -77,6 +80,17 @@ export function circularLayout( return; } + if (draggingNode) { + const [tempX, tempY] = coordSys.pointToData(pointer) as [number, number]; + const v = [tempX - cx, tempY - cy]; + vec2.normalize(v, v); + vec2.scale(v, v, r); + draggingNode.setLayout([cx + v[0], cy + v[1]], true); + + const circularRotateLabel = seriesModel.get(['circular', 'rotateLabel']); + rotateNodeLabel(draggingNode, circularRotateLabel, cx, cy); + } + _layoutNodesBasedOn[basedOn](seriesModel, graph, nodeData, r, cx, cy, count); graph.eachEdge(function (edge, index) { @@ -163,7 +177,11 @@ const _layoutNodesBasedOn: Record<'value' | 'symbolSize', LayoutNode> = { const radianHalf = halfRemainRadian + _symbolRadiansHalf[node.dataIndex]; angle += radianHalf; - node.setLayout([ + // init circular layout for + // 1. layout undefined node + // 2. not fixed node + (!node.getLayout() || !node.getLayout().fixed) + && node.setLayout([ r * Math.cos(angle) + cx, r * Math.sin(angle) + cy ]); @@ -171,3 +189,46 @@ const _layoutNodesBasedOn: Record<'value' | 'symbolSize', LayoutNode> = { }); } }; + +export function rotateNodeLabel( + node: GraphNode, + circularRotateLabel: boolean, + cx: number, + cy: number +) { + const el = node.getGraphicEl() as Symbol; + // need to check if el exists. '-' value may not create node element. + if (!el) { + return; + } + const nodeModel = node.getModel(); + let labelRotate = nodeModel.get(['label', 'rotate']) || 0; + const symbolPath = el.getSymbolPath(); + if (circularRotateLabel) { + const pos = node.getLayout(); + let rad = Math.atan2(pos[1] - cy, pos[0] - cx); + if (rad < 0) { + rad = Math.PI * 2 + rad; + } + const isLeft = pos[0] < cx; + if (isLeft) { + rad = rad - Math.PI; + } + const textPosition = isLeft ? 'left' as const : 'right' as const; + + symbolPath.setTextConfig({ + rotation: -rad, + position: textPosition, + origin: 'center' + }); + const emphasisState = symbolPath.ensureState('emphasis'); + zrUtil.extend(emphasisState.textConfig || (emphasisState.textConfig = {}), { + position: textPosition + }); + } + else { + symbolPath.setTextConfig({ + rotation: labelRotate *= Math.PI / 180 + }); + } +} diff --git a/src/component/helper/RoamController.ts b/src/component/helper/RoamController.ts index edff61052d..874865ebee 100644 --- a/src/component/helper/RoamController.ts +++ b/src/component/helper/RoamController.ts @@ -173,12 +173,19 @@ class RoamController extends Eventful<{ } private _mousedownHandler(e: ZRElementEvent) { - if (eventTool.isMiddleOrRightButtonOnMouseUpDown(e) - || (e.target && e.target.draggable) - ) { + if (eventTool.isMiddleOrRightButtonOnMouseUpDown(e)) { return; } + let el = e.target; + while (el) { + if (el.draggable) { + return; + } + // check if host is draggable + el = el.__hostTarget || el.parent; + } + const x = e.offsetX; const y = e.offsetY; diff --git a/test/graph-draggable.html b/test/graph-draggable.html new file mode 100644 index 0000000000..d681e98e19 --- /dev/null +++ b/test/graph-draggable.html @@ -0,0 +1,441 @@ + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + +