Skip to content

Commit

Permalink
feat: fix item size while zooming (#5097)
Browse files Browse the repository at this point in the history
Co-authored-by: yvonneyx <[email protected]>
  • Loading branch information
yvonneyx and yvonneyx authored Dec 8, 2023
1 parent a18d168 commit 7d3f191
Show file tree
Hide file tree
Showing 8 changed files with 497 additions and 14 deletions.
24 changes: 19 additions & 5 deletions packages/g6/src/item/node.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Group } from '@antv/g';
import { clone, debounce, throttle } from '@antv/util';
import { Group, AABB } from '@antv/g';
import { clone } from '@antv/util';
import { Point } from '../types/common';
import { ComboDisplayModel, ComboModel, IGraph, NodeModel } from '../types';
import { ComboDisplayModel, ComboModel, ID, IGraph, NodeModel } from '../types';
import { DisplayMapper, State, LodLevelRanges } from '../types/item';
import { NodeDisplayModel, NodeModelData } from '../types/node';
import { ComboStyleSet, NodeStyleSet } from '../types/theme';
Expand Down Expand Up @@ -40,6 +40,8 @@ interface IProps {
export default class Node extends Item {
public type: 'node' | 'combo';

private renderBoundsCache: Map<ID, AABB> = new Map();

constructor(props: IProps) {
super(props);
this.init({ ...props, type: props.type || 'node' });
Expand Down Expand Up @@ -207,6 +209,7 @@ export default class Node extends Item {
group.style.z = position.z;
onfinish(displayModel.id, !animate);
}

/**
* Update label positions on label canvas by getting viewport position from transformed canvas position.
*/
Expand All @@ -216,16 +219,27 @@ export default class Node extends Item {
}
const { graph, group, labelGroup, displayModel, shapeMap, renderExt } =
this;

let [x, y, z] = group.getPosition();
if (group.getAnimations().length) {
const { x: dataX, y: dataY, z: dataZ } = displayModel.data;
x = dataX as number;
y = dataY as number;
z = dataZ as number;
}
const renderBounds = group.getRenderBounds();
const id = this.getID();
if (!this.renderBoundsCache.has(id)) {
this.renderBoundsCache.set(id, clone(renderBounds));
}
const dy =
renderBounds.halfExtents[1] -
this.renderBoundsCache.get(id).halfExtents[1];
const zoom = graph.getZoom();
const { x: vx, y: vy, z: vz } = graph.getViewportByCanvas({ x, y, z });
const {
x: vx,
y: vy,
z: vz,
} = graph.getViewportByCanvas({ x, y: y + dy, z });
if (labelGroup.style.x !== vx) {
labelGroup.style.x = vx;
}
Expand Down
139 changes: 130 additions & 9 deletions packages/g6/src/stdlib/behavior/zoom-canvas.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isNumber } from '@antv/util';
import { isNumber, isBoolean } from '@antv/util';
import { ID, IG6GraphEvent } from '../../types';
import { Behavior } from '../../types/behavior';

Expand Down Expand Up @@ -46,14 +46,22 @@ export interface ZoomCanvasOptions {
* Whether allow the behavior happen on the current item.
*/
shouldBegin?: (event: IG6GraphEvent) => boolean;

// TODO: fixSelectedItems, optimizeZoom
// fixSelectedItems: {
// fixAll: false,
// fixLineWidth: false,
// fixLabel: false,
// fixState: 'selected',
// },
/**
* Whether to fix the stroke thickness, text size, overall size, etc. of selected elements. false by default.
* @property {boolean} fixAll: fix the overall size of the element, higher priority than fixSelectedItems.fixLineWidth and fixSelectedItems.fixLabel;
* @property {boolean} fixLineWidth: fix the stroke thickness of keyShape;
* @property {boolean} fixLabel: fix the text size of labelShape, labelBackgroundShape;
* @property {string} fixState: the state of the element to be fixed. Default is `selected` ;
*/
fixSelectedItems:
| boolean
| {
fixAll?: boolean;
fixLineWidth?: boolean;
fixLabel?: boolean;
fixState: string;
};
// TODO: optimizeZoom
// optimizeZoom: hide shapes when zoom ratio is smaller than optimizeZoom
}

Expand All @@ -68,6 +76,7 @@ const DEFAULT_OPTIONS: Required<ZoomCanvasOptions> = {
minZoom: 0.00001,
maxZoom: 1000,
shouldBegin: () => true,
fixSelectedItems: false,
};

export class ZoomCanvas extends Behavior {
Expand All @@ -80,6 +89,16 @@ export class ZoomCanvas extends Behavior {
private tileRequestId?: number;
private lastWheelTriggerTime?: number;

private zoomCache: {
fixIds: Set<ID>;
balanceRatio?: Map<ID, number>;
lineWidth?: Map<ID, number>;
} = {
fixIds: new Set(),
balanceRatio: new Map(),
lineWidth: new Map(),
};

constructor(options: Partial<ZoomCanvasOptions>) {
const finalOptions = Object.assign({}, DEFAULT_OPTIONS, options);
if (!VALID_TRIGGERS.includes(finalOptions.trigger)) {
Expand All @@ -88,6 +107,18 @@ export class ZoomCanvas extends Behavior {
);
finalOptions.trigger = 'wheel';
}
const { fixSelectedItems } = finalOptions;
if (isBoolean(fixSelectedItems) && fixSelectedItems) {
finalOptions.fixSelectedItems = {
fixAll: true,
fixState: 'selected',
};
}
if (!isBoolean(fixSelectedItems)) {
if (!fixSelectedItems.fixState)
// @ts-ignore
finalOptions.fixSelectedItems.fixState = 'selected';
}
super(finalOptions);
}

Expand Down Expand Up @@ -233,6 +264,7 @@ export class ZoomCanvas extends Behavior {
if (!this.zooming) {
this.graph.canvas.getConfig().disableHitTesting = true;
this.hideShapes();
this.clearCache();
this.zooming = true;
}

Expand Down Expand Up @@ -260,6 +292,11 @@ export class ZoomCanvas extends Behavior {
if (minZoom && zoomTo < minZoom) return;
if (maxZoom && zoomTo > maxZoom) return;

const { fixSelectedItems } = this.options;
if (fixSelectedItems) {
this.balanceItemSize();
}

graph.zoom(zoomRatio, { x: client.x, y: client.y });

this.lastWheelTriggerTime = now;
Expand All @@ -276,6 +313,90 @@ export class ZoomCanvas extends Behavior {
}
}

private clearCache() {
this.zoomCache.fixIds.forEach((fixId) => {
const item = this.graph.itemController.itemMap.get(fixId);
item.displayModel.labelShapeVisible = undefined;
});
this.zoomCache.fixIds.clear();
}

private balanceItemSize() {
const { graph } = this;
const zoom = graph.getZoom();

let fixNodeIds = [];
let fixEdgeIds = [];

if (zoom < 1) {
let typeArr = [];
const { fixSelectedItems } = this.options;
const { fixLabel, fixAll, fixLineWidth, fixState } = fixSelectedItems;
if (fixLabel) typeArr.push('labelSize');
if (fixLineWidth) typeArr.push('lineWidth');
if (fixAll) typeArr = ['fullSize'];

fixNodeIds = graph.findIdByState('node', fixState);
fixEdgeIds = graph.findIdByState('edge', fixState);

const fixIds = fixNodeIds.concat(fixEdgeIds);
if (!fixIds.length) return;
this.zoomCache.fixIds = new Set([...fixIds]);
fixIds.forEach((id) => {
const item = graph.itemController.itemMap.get(id);
const balanceRatio = 1 / zoom || 1;

const itemType = item.getType();
if (itemType === 'edge' && typeArr.includes('fullSize')) {
typeArr = ['labelSize', 'lineWidth'];
}

const balanceLabelShape = () => {
item.updateLabelPosition();
item.displayModel.labelShapeVisible = true;
graph.showItem(id, {
shapeIds: ['labelShape', 'labelBackgroundShape'],
disableAnimate: true,
});
};

typeArr.forEach((type) => {
switch (type) {
case 'lineWidth': {
const { keyShape } = item.shapeMap;
if (!this.zoomCache.lineWidth.has(id)) {
this.zoomCache.lineWidth.set(id, keyShape.attributes.lineWidth);
}
const oriLineWidth = this.zoomCache.lineWidth.get(id);
keyShape.attr('lineWidth', oriLineWidth * balanceRatio);
break;
}
case 'fullSize': {
const { group } = item;
const transform = group.style.transform;
if (!this.zoomCache.balanceRatio.has(id)) {
const oriBalanceRatio =
Number(transform?.match(/scale\(([\d.]+),/)?.[1]) || 1;
this.zoomCache.balanceRatio.set(id, oriBalanceRatio);
}
const balanceRatioCache = this.zoomCache.balanceRatio.get(id);
const newBalanceRatio = balanceRatioCache * balanceRatio;
group.style.transform = `scale(${newBalanceRatio}, ${newBalanceRatio})`;
balanceLabelShape();
break;
}
case 'labelSize': {
balanceLabelShape();
break;
}
default:
break;
}
});
});
}
}

public onKeydown(event) {
const { key } = event;
const {
Expand Down
133 changes: 133 additions & 0 deletions packages/g6/tests/demo/behaviors/zoom-canvas.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import { extend, Extensions, Graph } from '../../../src/index';
import { TestCaseContext } from '../interface';

export default (context: TestCaseContext) => {
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 ExtGraph = extend(Graph, {
transforms: {
'transform-v4-data': Extensions.TransformV4Data,
},
behaviors: {
'zoom-canvas': Extensions.ZoomCanvas,
},
});
const graph = new ExtGraph({
...context,
node: {
lodStrategy: {},
labelShape: {
text: {
fields: ['id'],
formatter: (model) => model.id,
},
},
},
edge: {
lodStrategy: {},
},
nodeState: {
yourStateName: {
keyShape: {
stroke: '#f00',
lineWidth: 3,
},
},
},
edgeState: {
yourStateName: {
keyShape: {
stroke: '#f00',
lineWidth: 3,
},
},
},
data,
transforms: [
'transform-v4-data', // 内置的数据处理器,将 v4 的数据格式转换为 v5
],
plugins: [
{
type: 'lod-controller',
// disableLod: true,
},
],
layout: {
type: 'force',
linkDistance: 100,
},
modes: {
default: [
{
type: 'zoom-canvas',
// fixSelectedItems: true, // `false` by default
fixSelectedItems: {
fixAll: true,
fixLineWidth: true,
fixLabel: false,
fixState: 'yourStateName', // `selected` by default
},
},
'drag-node',
'click-select',
'drag-canvas',
],
},
});

graph.on('node:click', (e) => {
graph.setItemState(e.itemId, 'yourStateName', true);
});
graph.on('edge:click', (e) => {
graph.setItemState(e.itemId, 'yourStateName', true);
});

graph.on('canvas:click', (e) => {
graph.findIdByState('node', 'yourStateName').forEach((node) => {
graph.setItemState(node, 'yourStateName', false);
});
graph.findIdByState('edge', 'yourStateName').forEach((edge) => {
graph.setItemState(edge, 'yourStateName', false);
});
});

return graph;
};
2 changes: 2 additions & 0 deletions packages/g6/tests/demo/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import behaviors_create_edge from './behaviors/create-edge';
import behaviors_dragCanvas from './behaviors/drag-canvas';
import behaviors_scrollCanvas from './behaviors/scroll-canvas';
import behaviors_shortcuts_call from './behaviors/shortcuts-call';
import behaviors_zoomCanvas from './behaviors/zoom-canvas';
import circularUpdate from './layouts/circular-update';
import comboBasic from './combo/combo-basic';
import comboDagre from './layouts/dagre-combo';
Expand Down Expand Up @@ -92,6 +93,7 @@ export {
behaviors_dragCanvas,
behaviors_scrollCanvas,
behaviors_shortcuts_call,
behaviors_zoomCanvas,
circularUpdate,
comboBasic,
comboDagre,
Expand Down
Loading

0 comments on commit 7d3f191

Please sign in to comment.