Merge branch 'v5' into feat/timebar

This commit is contained in:
Yanyan Wang 2023-08-31 22:08:43 +08:00 committed by GitHub
commit 1f46b96685
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
91 changed files with 4986 additions and 5853 deletions

View File

@ -2,7 +2,7 @@ import type { ID, IG6GraphEvent } from '../../types';
import { Behavior } from '../../types/behavior';
const KEYBOARD_TRIGGERS = ['shift', 'ctrl', 'alt', 'meta'] as const;
const MOUSE_TRIGGERS = ['mouseenter', 'click'] as const;
const MOUSE_TRIGGERS = ['pointerenter', 'click'] as const;
type Trigger = (typeof MOUSE_TRIGGERS)[number];
@ -78,12 +78,12 @@ export class ActivateRelations extends Behavior {
getEvents = () => {
const { trigger } = this.options;
if (trigger === 'mouseenter') {
if (trigger === 'pointerenter') {
return {
'node:mouseenter': this.setAllItemStates,
'combo:mouseenter': this.setAllItemStates,
'node:mouseleave': this.clearActiveState,
'combo:mouseleave': this.clearActiveState,
'node:pointerenter': this.setAllItemStates,
'combo:pointerenter': this.setAllItemStates,
'node:pointerleave': this.clearActiveState,
'combo:pointerleave': this.clearActiveState,
};
}

View File

@ -90,6 +90,7 @@ const {
import lassoSelector from './selector/lasso';
import rectSelector from './selector/rect';
import Hull from './plugin/hull';
const stdLib = {
transforms: {
@ -275,6 +276,7 @@ const Extensions = {
Fisheye,
Legend,
Timebar,
Hull
};
export default registery;

View File

@ -76,15 +76,15 @@ export abstract class BaseEdge {
// the tag of first rendering
firstRender: boolean;
} = {
hiddenShape: {},
balanceRatio: 1,
zoom: 1,
zoomLevel: 0,
levelShapes: {},
wordWrapWidth: 50,
animateConfig: DEFAULT_ANIMATE_CFG.zoom,
firstRender: true,
};
hiddenShape: {},
balanceRatio: 1,
zoom: 1,
zoomLevel: 0,
levelShapes: {},
wordWrapWidth: 50,
animateConfig: DEFAULT_ANIMATE_CFG.zoom,
firstRender: true,
};
constructor(props) {
const { themeStyles, lodStrategy, zoom } = props;
if (themeStyles) this.themeStyles = themeStyles;
@ -178,14 +178,14 @@ export abstract class BaseEdge {
}
/**
* Draw all elements related to the edge.
* You should call `drawKeyShape` and `drawAnchorShape`,`drawLabelShape`,`drawIconShape`...as you like.
* @param model The displayed model of this edge, only for drawing and not received by users.
* @param shapeMap The shape map that contains all of the elements to show on the edge.
* @param diffData An object that contains previous and current data.
* @param diffState An object that contains previous and current edge's state.
* @returns An object containing the keyShape and optional labelShape, iconShape, and some otherShapes properties
*/
* Draw all elements related to the edge.
* You should call `drawKeyShape` and `drawAnchorShape`,`drawLabelShape`,`drawIconShape`...as you like.
* @param model The displayed model of this edge, only for drawing and not received by users.
* @param shapeMap The shape map that contains all of the elements to show on the edge.
* @param diffData An object that contains previous and current data.
* @param diffState An object that contains previous and current edge's state.
* @returns An object containing the keyShape and optional labelShape, iconShape, and some otherShapes properties
*/
abstract draw(
model: EdgeDisplayModel,
sourcePoint: Point,
@ -201,12 +201,12 @@ export abstract class BaseEdge {
};
/**
* Perform additional drawing operations or add custom shapes after drawing edge.
* @param model The displayed model of this edge, only for drawing and not received by users.
* @param shapeMap The shape map that contains all of the elements to show on the edge.
* @param shapesChanged An array of shape IDs that have changed and need to be updated.
* @returns An object that contains some new shapes to be added to the edge.
*/
* Perform additional drawing operations or add custom shapes after drawing edge.
* @param model The displayed model of this edge, only for drawing and not received by users.
* @param shapeMap The shape map that contains all of the elements to show on the edge.
* @param shapesChanged An array of shape IDs that have changed and need to be updated.
* @returns An object that contains some new shapes to be added to the edge.
*/
public afterDraw(
model: EdgeDisplayModel,
shapeMap: { [shapeId: string]: DisplayObject },
@ -221,7 +221,7 @@ export abstract class BaseEdge {
* @param state state name
* @param value state value
* @param shapeMap The shape map that contains all of the elements to show on the edge.
*/
*/
public setState: (
name: string,
value: boolean,
@ -230,7 +230,7 @@ export abstract class BaseEdge {
/**
* Draw the label shape of the edge
* @param model The displayed model of this edge, only for drawing and not received by users.
* @param model The displayed model of this edge, only for drawing and not received by users.
* @param shapeMap The shape map that contains all of the elements to show on the edge.
* @param diffData An object that contains previous and current data.
* @param diffState An object that contains previous and current edge's state.
@ -344,7 +344,7 @@ export abstract class BaseEdge {
/**
* Draw the label background shape of the edge
* @param model The displayed model of this edge, only for drawing and not received by users.
* @param model The displayed model of this edge, only for drawing and not received by users.
* @param shapeMap The shape map that contains all of the elements to show on the edge.
* @param diffData An object that contains previous and current data.
* @param diffState An object that contains previous and current edge's state.
@ -380,8 +380,9 @@ export abstract class BaseEdge {
};
if (labelShapeStyle.position === 'start') {
if (isRevert) {
bgStyle.transformOrigin = `${bgStyle.width - padding[1]} ${bgStyle.height / 2
}`;
bgStyle.transformOrigin = `${bgStyle.width - padding[1]} ${
bgStyle.height / 2
}`;
} else {
bgStyle.transformOrigin = `${padding[3]} ${bgStyle.height / 2}`;
}
@ -389,12 +390,14 @@ export abstract class BaseEdge {
if (isRevert) {
bgStyle.transformOrigin = `${padding[3]} ${bgStyle.height / 2}`;
} else {
bgStyle.transformOrigin = `${bgStyle.width - padding[1]} ${bgStyle.height / 2
}`;
bgStyle.transformOrigin = `${bgStyle.width - padding[1]} ${
bgStyle.height / 2
}`;
}
} else {
bgStyle.transformOrigin = `${textWidth / 2 + padding[3]} ${textHeight / 2 + padding[0]
}`;
bgStyle.transformOrigin = `${textWidth / 2 + padding[3]} ${
textHeight / 2 + padding[0]
}`;
}
return this.upsertShape(
@ -408,7 +411,7 @@ export abstract class BaseEdge {
/**
* Draw the icon shape of the edge
* @param model The displayed model of this edge, only for drawing and not received by users.
* @param model The displayed model of this edge, only for drawing and not received by users.
* @param shapeMap The shape map that contains all of the elements to show on the edge.
* @param diffData An object that contains previous and current data.
* @param diffState An object that contains previous and current edge's state.
@ -472,14 +475,16 @@ export abstract class BaseEdge {
if (referTransform) {
shapeStyle.transform = referTransform;
if (labelAlign === 'right') {
shapeStyle.transformOrigin = `${referWidth / 2 - w / 2 + 4 + referHalExtents[0] - offsetX
} ${h / 2 - offsetY}`;
shapeStyle.transformOrigin = `${
referWidth / 2 - w / 2 + 4 + referHalExtents[0] - offsetX
} ${h / 2 - offsetY}`;
} else if (labelAlign === 'left') {
shapeStyle.transformOrigin = `${w + 4 - offsetX} ${h / 2 - offsetY}`;
} else {
// labelShape align 'center'
shapeStyle.transformOrigin = `${(w + referWidth) / 2 - offsetX} ${h / 2 - offsetY
}`;
shapeStyle.transformOrigin = `${(w + referWidth) / 2 - offsetX} ${
h / 2 - offsetY
}`;
}
}
} else {
@ -500,7 +505,7 @@ export abstract class BaseEdge {
/**
* Draw the halo shape of the edge
* @param model The displayed model of this edge, only for drawing and not received by users.
* @param model The displayed model of this edge, only for drawing and not received by users.
* @param shapeMap The shape map that contains all of the elements to show on the edge.
* @param diffData An object that contains previous and current data.
* @param diffState An object that contains previous and current edge's state.
@ -529,6 +534,15 @@ export abstract class BaseEdge {
);
}
public drawOtherShapes(
model: EdgeDisplayModel,
shapeMap: EdgeShapeMap,
diffData?: { previous: EdgeModelData; current: EdgeModelData },
diffState?: { previous: State[]; current: State[] },
): { [id: string]: DisplayObject } {
return {};
}
/**
* The listener for graph zooming.
* 1. show / hide some shapes while zoom level changed;
@ -577,7 +591,7 @@ export abstract class BaseEdge {
id,
shapeMap[id],
this.mergedStyles[id] ||
this.mergedStyles[id.replace('Background', '')],
this.mergedStyles[id.replace('Background', '')],
hiddenShape,
animateConfig,
),
@ -643,7 +657,6 @@ export abstract class BaseEdge {
this.nodeMap = nodeMap;
}
/**
* Adds or updates an arrow marker on the specified position of an edge.
* @param position - The position where the arrow marker should be added or updated. Can be either 'start' or 'end'.
@ -702,7 +715,7 @@ export abstract class BaseEdge {
* @param type shape's type
* @param id unique string to indicates the shape
* @param style style to be updated
* @param shapeMap the shape map of a edge
* @param shapeMap the shape map of a edge
* @param model data model of the edge
* @returns The display object representing the shape.
*/

View File

@ -35,7 +35,7 @@ export class LineEdge extends BaseEdge {
): EdgeShapeMap {
const { data = {} } = model;
const shapes: EdgeShapeMap = { keyShape: undefined };
let shapes: EdgeShapeMap = { keyShape: undefined };
shapes.keyShape = this.drawKeyShape(
model,
@ -66,7 +66,13 @@ export class LineEdge extends BaseEdge {
shapes.iconShape = this.drawIconShape(model, shapeMap, diffData);
}
// TODO: other shapes
// otherShapes
if (data.otherShapes) {
shapes = {
...shapes,
...this.drawOtherShapes(model, shapeMap, diffData),
};
}
return shapes;
}

View File

@ -8,7 +8,7 @@ import {
} from '../../../types/edge';
import { State } from '../../../types/item';
import { getPolylinePath } from '../../../util/polyline';
import { pathFinder } from '../../../util/router';
import { RouterCfg, pathFinder } from '../../../util/router';
import Node from '../../../item/node';
import { LineEdge } from './line';
export class PolylineEdge extends LineEdge {
@ -74,7 +74,7 @@ export class PolylineEdge extends LineEdge {
return shapes;
}
private getControlPoints(
public getControlPoints(
model: EdgeDisplayModel,
sourcePoint: Point,
targetPoint: Point,
@ -95,16 +95,18 @@ export class PolylineEdge extends LineEdge {
model: EdgeDisplayModel,
points: Point[],
radius: number,
routeCfg?: Record<string, any>,
routeCfg?: RouterCfg,
auto?: boolean,
): string {
const { id: edgeId, source: sourceNodeId, target: targetNodeId } = model;
// Draw a polyline with control points
if (!auto) return getPolylinePath(edgeId, points, radius);
if (!auto && routeCfg.name !== 'er')
return getPolylinePath(edgeId, points, radius);
// Find the shortest path computed by A* routing algorithm
const polylinePoints = pathFinder(
points,
sourceNodeId,
targetNodeId,
this.nodeMap as unknown as Map<ID, Node>,
@ -136,7 +138,7 @@ export class PolylineEdge extends LineEdge {
const routeCfg = mix(
{},
{ offset: keyShapeStyle.offset },
{ offset: keyShapeStyle.offset, name: 'orth' },
keyShapeStyle.routeCfg,
);

View File

@ -96,7 +96,6 @@ export abstract class BaseNode {
};
}
/**
* Get merged styles from `getMergedStyles` and assigns the merged styles to the 'mergedStyles' property.
* @param model - The NodeDisplayModel or ComboDisplayModel to merge the styles from.
@ -199,12 +198,12 @@ export abstract class BaseNode {
/**
* Draw all elements related to the node.
* You should call `drawKeyShape` and `drawAnchorShape`,`drawLabelShape`,`drawIconShape`...as you like.
* @param model The displayed model of this node, only for drawing and not received by users.
* @param model The displayed model of this node, only for drawing and not received by users.
* @param shapeMap The shape map that contains all of the elements to show on the node.
* @param diffData An object that contains previous and current data.
* @param diffState An object that contains previous and current node's state.
* @returns An object containing the keyShape and optional labelShape, iconShape, and some otherShapes properties
*/
*/
abstract draw(
model: NodeDisplayModel | ComboDisplayModel,
shapeMap: { [shapeId: string]: DisplayObject },
@ -222,11 +221,11 @@ export abstract class BaseNode {
/**
* Perform additional drawing operations or add custom shapes after drawing node.
* @param model The displayed model of this node, only for drawing and not received by users.
* @param model The displayed model of this node, only for drawing and not received by users.
* @param shapeMap The shape map that contains all of the elements to show on the node.
* @param shapesChanged An array of shape IDs that have changed and need to be updated.
* @returns An object that contains some new shapes to be added to the node.
*/
*/
public afterDraw(
model: NodeDisplayModel | ComboDisplayModel,
shapeMap: { [shapeId: string]: DisplayObject },
@ -241,7 +240,7 @@ export abstract class BaseNode {
* @param state state name
* @param value state value
* @param shapeMap The shape map that contains all of the elements to show on the node.
*/
*/
public setState: (
name: string,
value: boolean,
@ -251,7 +250,7 @@ export abstract class BaseNode {
/**
* The key function of drawing shape.
* Draw the key shape of the node based on the provided model and shape map.
* @param model The displayed model of this node, only for drawing and not received by users.
* @param model The displayed model of this node, only for drawing and not received by users.
* @param shapeMap The shape map that contains all of the elements to show on the node.
* @param diffData An object that contains previous and current data.
* @param diffState An object that contains previous and current node's state.
@ -269,7 +268,7 @@ export abstract class BaseNode {
/**
* Draw the label shape of the node
* @param model The displayed model of this node, only for drawing and not received by users.
* @param model The displayed model of this node, only for drawing and not received by users.
* @param shapeMap The shape map that contains all of the elements to show on the node.
* @param diffData An object that contains previous and current data.
* @param diffState An object that contains previous and current node's state.
@ -377,7 +376,7 @@ export abstract class BaseNode {
/**
* Draw the label background shape of the node
* @param model The displayed model of this node, only for drawing and not received by users.
* @param model The displayed model of this node, only for drawing and not received by users.
* @param shapeMap The shape map that contains all of the elements to show on the node.
* @param diffData An object that contains previous and current data.
* @param diffState An object that contains previous and current node's state.
@ -434,7 +433,7 @@ export abstract class BaseNode {
/**
* Draw the icon shape of the node
* @param model The displayed model of this node, only for drawing and not received by users.
* @param model The displayed model of this node, only for drawing and not received by users.
* @param shapeMap The shape map that contains all of the elements to show on the node.
* @param diffData An object that contains previous and current data.
* @param diffState An object that contains previous and current node's state.
@ -485,7 +484,7 @@ export abstract class BaseNode {
/**
* Draw the halo shape of the node
* @param model The displayed model of this node, only for drawing and not received by users.
* @param model The displayed model of this node, only for drawing and not received by users.
* @param shapeMap The shape map that contains all of the elements to show on the node.
* @param diffData An object that contains previous and current data.
* @param diffState An object that contains previous and current node's state.
@ -525,7 +524,7 @@ export abstract class BaseNode {
/**
* Draw the anchors shape of the node
* @param model The displayed model of this node, only for drawing and not received by users.
* @param model The displayed model of this node, only for drawing and not received by users.
* @param shapeMap The shape map that contains all of the elements to show on the node.
* @param diffData An object that contains previous and current data.
* @param diffState An object that contains previous and current node's state.
@ -606,8 +605,8 @@ export abstract class BaseNode {
/**
* Configures the anchor positions based on the provided keyShapeStyle and returns the configuration.
* e.g for a CircleNode, it returns: `{"right":keyShapeStyle.x+keyShapeStyle.r, keyShapeStyle.y}`
* @param keyShapeStyle - The keyShapeStyle object that contains the style information of the key shape.
* @returns The anchor position configuration as an IAnchorPositionMap object.
* @param keyShapeStyle - The keyShapeStyle object that contains the style information of the key shape.
* @returns The anchor position configuration as an IAnchorPositionMap object.
*/
public calculateAnchorPosition(keyShapeStyle: any): IAnchorPositionMap {
const x = convertToNumber(keyShapeStyle.x);
@ -623,7 +622,7 @@ export abstract class BaseNode {
/**
* Draw the badges shape of the node
* @param model The displayed model of this node, only for drawing and not received by users.
* @param model The displayed model of this node, only for drawing and not received by users.
* @param shapeMap The shape map that contains all of the elements to show on the node.
* @param diffData An object that contains previous and current data.
* @param diffState An object that contains previous and current node's state.
@ -786,7 +785,7 @@ export abstract class BaseNode {
/**
* Draw other shapes(such as preRect,stateIcon) of the node
* @param model The displayed model of this node, only for drawing and not received by users.
* @param model The displayed model of this node, only for drawing and not received by users.
* @param shapeMap The shape map that contains all of the elements to show on the node.
* @param diffData An object that contains previous and current data.
* @param diffState An object that contains previous and current node's state.
@ -976,14 +975,14 @@ export abstract class BaseNode {
}
/**
* Create (if does not exit in shapeMap) or update the shape according to the configurations.
* @param type shape's type
* @param id unique string to indicates the shape
* @param style style to be updated
* @param shapeMap the shape map of a node / combo
* @param model data model of the node / combo
* @returns The display object representing the shape.
*/
* Create (if does not exit in shapeMap) or update the shape according to the configurations.
* @param type shape's type
* @param id unique string to indicates the shape
* @param style style to be updated
* @param shapeMap the shape map of a node / combo
* @param model data model of the node / combo
* @returns The display object representing the shape.
*/
public upsertShape(
type: SHAPE_TYPE | SHAPE_TYPE_3D,
id: string,

View File

@ -28,7 +28,7 @@ export abstract class BaseNode3D extends BaseNode {
/**
* Draw the label shape of the 3D node
* @param model The displayed model of this 3D node, only for drawing and not received by users.
* @param model The displayed model of this 3D node, only for drawing and not received by users.
* @param shapeMap The shape map that contains all of the elements to show on the 3D node.
* @param diffData An object that contains previous and current data.
* @param diffState An object that contains previous and current 3D node's state.
@ -111,7 +111,7 @@ export abstract class BaseNode3D extends BaseNode {
/**
* Draw the icon shape of the 3D node
* @param model The displayed model of this 3D node, only for drawing and not received by users.
* @param model The displayed model of this 3D node, only for drawing and not received by users.
* @param shapeMap The shape map that contains all of the elements to show on the 3D node.
* @param diffData An object that contains previous and current data.
* @param diffState An object that contains previous and current 3D node's state.
@ -206,5 +206,5 @@ export abstract class BaseNode3D extends BaseNode {
* @param shapeMap The shape map that contains all of the elements to show on the node.
* @param zoom The zoom level of the graph.
*/
public onZoom = (shapeMap: NodeShapeMap, zoom: number) => { };
public onZoom = (shapeMap: NodeShapeMap, zoom: number) => {};
}

View File

@ -9,6 +9,11 @@ import {
} from '../../../types/node';
import { BaseNode } from './base';
const offsetAngleMap = {
horizontal: 0,
vertical: Math.PI / 2,
};
export class HexagonNode extends BaseNode {
override defaultStyles = {
keyShape: {
@ -119,9 +124,9 @@ export class HexagonNode extends BaseNode {
private getHexagonPoints(r: number, direction: string): [number, number][] {
const angleIncrement = Math.PI / 3; //The angle increment between vertex.
const v = [];
const offsetAngle = direction === 'horizontal' ? 0 : Math.PI / 2;
for (let i = 0; i < 6; i++) {
const angle = i * angleIncrement + offsetAngle;
const angle = i * angleIncrement + offsetAngleMap[direction];
const vx = r * Math.cos(angle);
const vy = r * Math.sin(angle);
v.push([vx, vy]);
@ -153,8 +158,7 @@ export class HexagonNode extends BaseNode {
? anchorPositionHorizontal
: anchorPositionVertical;
const angleIncrement = Math.PI / 3; //The angle increment between vertex.
const offsetAngle =
keyShapeStyle.direction === 'horizontal' ? 0 : Math.PI / 2;
const offsetAngle = offsetAngleMap[keyShapeStyle.direction];
const r = keyShapeStyle.r;
const anchorPositionMap = {};
for (let i = 0; i < 6; i++) {

View File

@ -1,6 +1,6 @@
import { deepMix, map } from '@antv/util';
import { PolyPoint } from 'types/common';
import { ID } from 'types';
import { ID } from '../types';
import { Point, PolyPoint } from '../types/common';
import Node from '../item/node';
import { simplePathFinder, SortedArray } from './polyline';
import {
@ -14,6 +14,7 @@ import { getLineIntersect } from './shape';
import { eulerDist, manhattanDist } from './math';
export interface RouterCfg {
name: 'orth' | 'er';
/** Spacing between lines and points */
offset?: number;
/** Grid size */
@ -46,6 +47,7 @@ export interface RouterCfg {
}
const defaultCfg: RouterCfg = {
name: 'orth',
obstacleAvoidance: false,
offset: 20,
maxAllowedDirectionChange: Math.PI / 2,
@ -73,6 +75,7 @@ const straightPath = (start: PolyPoint, end: PolyPoint): PolyPoint[] => {
};
export const octolinearCfg: RouterCfg = {
name: 'er',
maxAllowedDirectionChange: Math.PI / 4,
directions: [
{ stepX: 1, stepY: 0 },
@ -85,7 +88,7 @@ export const octolinearCfg: RouterCfg = {
{ stepX: 1, stepY: -1 },
],
distFunc: eulerDist,
fallbackRoute: straightPath,
fallbackRoute: simplePathFinder,
};
/**
@ -368,6 +371,7 @@ const getControlPoints = (
/** Find the shortest path computed by A* routing algorithm */
export const pathFinder = (
points: Point[],
sourceNodeId: ID,
targetNodeId: ID,
nodeMap: Map<ID, Node>,
@ -376,11 +380,16 @@ export const pathFinder = (
const startNode = nodeMap.get(sourceNodeId);
const endNode = nodeMap.get(targetNodeId);
const startPoint: PolyPoint = startNode.getPosition();
const endPoint: PolyPoint = endNode.getPosition();
const startPoint: PolyPoint = startNode?.getPosition() || points[0];
const endPoint: PolyPoint =
endNode?.getPosition() || points[points.length - 1];
if (isNaN(startPoint.x) || isNaN(endPoint.x)) return [];
const cfg: RouterCfg = deepMix(defaultCfg, routerCfg);
const defaultCfgs =
routerCfg.name === 'orth' ? defaultCfg : deepMix(defaultCfg, octolinearCfg);
const cfg: RouterCfg = deepMix(defaultCfgs, routerCfg);
if (!cfg.obstacleAvoidance) {
return cfg.fallbackRoute(startPoint, endPoint, startNode, endNode, cfg);

View File

@ -240,13 +240,7 @@ export default () => {
data: defaultData,
modes: {
// supported behavior
default: ['activate-relations'],
},
node: {
anchorPoints: [
[0.5, 0],
[0.5, 1],
],
default: ['activate-relations', 'drag-node'],
},
});

View File

@ -53,7 +53,7 @@ const defaultData = {
data: {
type: 'loop-edge',
keyShape: {
endArrow: {},
endArrow: true,
},
},
},
@ -64,7 +64,7 @@ const defaultData = {
data: {
type: 'loop-edge',
keyShape: {
endArrow: {},
endArrow: true,
},
},
},
@ -75,7 +75,7 @@ const defaultData = {
data: {
type: 'loop-edge',
keyShape: {
endArrow: {},
endArrow: true,
},
},
},

View File

@ -1,4 +1,5 @@
// @ts-nocheck
import { Extensions, Graph, IGraph, extend } from '../../../../src/index';
import { TestCaseContext } from '../../interface';
import { Graph, Extensions, extend } from '../../../../src/index';
let graph: IGraph;
@ -348,11 +349,19 @@ export default (context: TestCaseContext) => {
data: defaultData,
modes: {
// supported behavior
default: ['activate-relations', 'drag-node'],
default: ['drag-node'],
},
edge: (edgeInnerModel: any) => {
const { id, data } = edgeInnerModel;
return { id, data };
return {
id,
data: {
...data,
keyShape: {
...data.keyShape,
},
},
};
},
});

View File

@ -1,4 +1,4 @@
import G6, { ID, stdLib } from '../../../src/index';
import G6, { ID, Extensions } from '../../../src/index';
import { ShapeStyle } from '../../../src/types/item';
export default (
context,
@ -15,7 +15,7 @@ export default (
style,
nonMembers = [],
} = options;
const hullPlugin = new stdLib.plugins.hull({
const hullPlugin = new Extensions.Hull({
key: 'hull-plugin1',
style,
hulls: [

View File

@ -1,180 +1,175 @@
import G6 from '@antv/g6';
import { Extensions, Graph, stdLib, extend } from '@antv/g6';
/**
* The demo shows customing a combo type by extending the built-in circle combo
* by Shiwu
*
*/
// The symbols for the marker inside the combo
const collapseIcon = (x, y, r) => {
return [
['M', x - r, y],
['a', r, r, 0, 1, 0, r * 2, 0],
['a', r, r, 0, 1, 0, -r * 2, 0],
['M', x - r + 4, y],
['L', x - r + 2 * r - 4, y],
];
};
const expandIcon = (x, y, r) => {
return [
['M', x - r, y],
['a', r, r, 0, 1, 0, r * 2, 0],
['a', r, r, 0, 1, 0, -r * 2, 0],
['M', x - r + 4, y],
['L', x - r + 2 * r - 4, y],
['M', x - r + r, y - r + 4],
['L', x, y + r - 4],
];
};
G6.registerCombo(
'cCircle',
{
drawShape: function draw(cfg, group) {
const self = this;
// Get the shape style, where the style.r corresponds to the R in the Illustration of Built-in Rect Combo
const style = self.getShapeStyle(cfg);
// Add a circle shape as keyShape which is the same as the extended 'circle' type Combo
const circle = group.addShape('circle', {
attrs: {
...style,
x: 0,
y: 0,
r: style.r,
},
draggable: true,
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
name: 'combo-keyShape',
});
// Add the marker on the bottom
const marker = group.addShape('marker', {
attrs: {
...style,
fill: '#fff',
opacity: 1,
x: 0,
y: style.r,
r: 10,
symbol: collapseIcon,
},
draggable: true,
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
name: 'combo-marker-shape',
});
return circle;
},
// Define the updating logic for the marker
afterUpdate: function afterUpdate(cfg, combo) {
const self = this;
// Get the shape style, where the style.r corresponds to the R in the Illustration of Built-in Rect Combo
const style = self.getShapeStyle(cfg);
const group = combo.get('group');
// Find the marker shape in the graphics group of the Combo
const marker = group.find((ele) => ele.get('name') === 'combo-marker-shape');
// Update the marker shape
marker.attr({
x: 0,
y: style.r,
// The property 'collapsed' in the combo data represents the collapsing state of the Combo
// Update the symbol according to 'collapsed'
symbol: cfg.collapsed ? expandIcon : collapseIcon,
});
},
},
'circle',
);
const data = {
nodes: [
{ id: 'node1', x: 250, y: 200, comboId: 'combo1' },
{ id: 'node2', x: 300, y: 200, comboId: 'combo1' },
{ id: 'node3', x: 100, y: 200, comboId: 'combo3' },
],
combos: [
{ id: 'combo1', label: 'Combo 1', parentId: 'combo2' },
{ id: 'combo2', label: 'Combo 2' },
{ id: 'combo3', label: 'Combo 3', collapsed: true },
],
};
const container = document.getElementById('container');
const width = container.scrollWidth;
const height = container.scrollHeight || 500;
const descriptionDiv = document.createElement('div');
descriptionDiv.innerHTML = 'Click the bottom marker to collapse/expand the combo.';
const container = document.getElementById('container');
container.appendChild(descriptionDiv);
const width = container.scrollWidth;
const height = (container.scrollHeight || 500) - 20;
const graph = new G6.Graph({
class CCircleCombo extends Extensions.CircleCombo {
drawOtherShapes(model, shapeMap, diffData) {
const { data } = model;
const keyShapeBBox = shapeMap.keyShape.getLocalBounds();
const otherShapes = {
markerShape: this.upsertShape(
'path',
'markerShape',
{
cursor: 'pointer',
stroke: '#666',
lineWidth: 1,
fill: '#fff',
path: data.collapsed
? stdLib.markers.expand(keyShapeBBox.center[0], keyShapeBBox.max[1], 8)
: stdLib.markers.collapse(keyShapeBBox.center[0], keyShapeBBox.max[1], 8),
},
shapeMap,
model,
),
};
return otherShapes;
}
}
const ExtGraph = extend(Graph, {
combos: {
'c-circle-combo': CCircleCombo,
},
behaviors: {
'hover-activate': Extensions.HoverActivate,
},
});
const graph = new ExtGraph({
container: 'container',
width,
height,
// Set groupByTypes to false to get rendering result with reasonable visual zIndex for combos
groupByTypes: false,
// Configure the combos globally
defaultCombo: {
// The type of the combos. You can also assign type in the data of combos
type: 'cCircle',
labelCfg: {
refY: 2,
},
// ... Other global configurations for combos
stackCfg: {
ignoreStateChange: true,
},
comboStateStyles: {
dragenter: {
lineWidth: 4,
stroke: '#FE9797',
node: {
labelShape: {
position: 'center',
text: {
fields: ['id'],
formatter: (model) => model.id,
},
},
animates: {
update: [
{
fields: ['opacity'],
shapeId: 'haloShape',
},
],
},
},
combo: (model) => {
return {
id: model.id,
data: {
type: 'c-circle-combo',
...model.data,
keyShape: {
padding: [10, 20, 30, 40],
r: 50,
},
labelShape: {
text: model.data.label,
position: 'top',
},
otherShapes: {},
animates: {
buildIn: [
{
fields: ['opacity'],
duration: 500,
delay: 500 + Math.random() * 500,
},
],
buildOut: [
{
fields: ['opacity'],
duration: 200,
},
],
update: [
{
fields: ['lineWidth', 'r'],
shapeId: 'keyShape',
},
{
fields: ['opacity'],
shapeId: 'haloShape',
},
],
},
},
};
},
data: {
nodes: [
{
id: 'node1',
data: {
x: 350,
y: 200,
parentId: 'combo1',
},
},
{ id: 'node2', data: { x: 350, y: 250, parentId: 'combo1' } },
{ id: 'node3', data: { x: 100, y: 200, parentId: 'combo3' } },
],
edges: [
{ id: 'edge1', source: 'node1', target: 'node2' },
{ id: 'edge2', source: 'node1', target: 'node3' },
{ id: 'edge3', source: 'combo1', target: 'node3' },
],
combos: [
{ id: 'combo1', data: { label: 'Combo 1', parentId: 'combo2' } },
{ id: 'combo2', data: { label: 'Combo 2' } },
{ id: 'combo3', data: { label: 'Combo 3', collapsed: true } },
],
},
modes: {
default: ['drag-combo', 'drag-node', 'drag-canvas', 'click-select'],
default: [
{
type: 'drag-node',
enableTransient: false,
updateComboStructure: false,
},
'drag-canvas',
{
type: 'click-select',
itemTypes: ['node', 'edge', 'combo'],
},
{
type: 'drag-combo',
enableTransient: true,
updateComboStructure: true,
},
],
},
});
graph.data(data);
graph.render();
// collapse/expand when click the marker
graph.on('combo:click', (e) => {
if (e.target.get('name') === 'combo-marker-shape') {
// graph.collapseExpandCombo(e.item.getModel().id);
graph.collapseExpandCombo(e.item);
if (graph.get('layout')) graph.layout();
else graph.refreshPositions();
/** Click the bottom marker to collapse/expand the combo. */
graph.on('combo:click', (event) => {
const { itemId, target } = event;
if (target.id === 'markerShape') {
const model = graph.getComboData(itemId);
if (model.data.collapsed) {
graph.expandCombo(itemId);
} else {
graph.collapseCombo(itemId);
}
}
});
graph.on('combo:dragend', (e) => {
graph.getCombos().forEach((combo) => {
graph.setItemState(combo, 'dragenter', false);
});
});
graph.on('node:dragend', (e) => {
graph.getCombos().forEach((combo) => {
graph.setItemState(combo, 'dragenter', false);
});
});
graph.on('combo:dragenter', (e) => {
graph.setItemState(e.item, 'dragenter', true);
});
graph.on('combo:dragleave', (e) => {
graph.setItemState(e.item, 'dragenter', false);
});
graph.on('combo:mouseenter', (evt) => {
const { item } = evt;
graph.setItemState(item, 'active', true);
});
graph.on('combo:mouseleave', (evt) => {
const { item } = evt;
graph.setItemState(item, 'active', false);
});
if (typeof window !== 'undefined')
window.onresize = () => {
if (!graph || graph.get('destroyed')) return;
if (!graph || graph.destroyed) return;
if (!container || !container.scrollWidth || !container.scrollHeight) return;
graph.changeSize(container.scrollWidth, container.scrollHeight - 20);
graph.setSize([container.scrollWidth, container.scrollHeight]);
};

View File

@ -1,178 +1,174 @@
import G6 from '@antv/g6';
import { Extensions, Graph, stdLib, extend } from '@antv/g6';
/**
* The demo shows customing a combo type by extending the built-in circle combo
* by Shiwu
*
*/
// The symbols for the marker inside the combo
const collapseIcon = (x, y, r) => {
return [
['M', x - r, y],
['a', r, r, 0, 1, 0, r * 2, 0],
['a', r, r, 0, 1, 0, -r * 2, 0],
['M', x - r + 4, y],
['L', x - r + 2 * r - 4, y],
];
};
const expandIcon = (x, y, r) => {
return [
['M', x - r, y],
['a', r, r, 0, 1, 0, r * 2, 0],
['a', r, r, 0, 1, 0, -r * 2, 0],
['M', x - r + 4, y],
['L', x - r + 2 * r - 4, y],
['M', x - r + r, y - r + 4],
['L', x, y + r - 4],
];
};
G6.registerCombo(
'cRect',
{
drawShape: function drawShape(cfg, group) {
const self = this;
// Get the padding from the configuration
cfg.padding = cfg.padding || [50, 20, 20, 20];
// Get the shape's style, where the style.width and style.height correspond to the width and height in the figure of Illustration of Built-in Rect Combo
const style = self.getShapeStyle(cfg);
// Add a rect shape as the keyShape which is the same as the extended rect Combo
const rect = group.addShape('rect', {
attrs: {
...style,
x: -style.width / 2 - (cfg.padding[3] - cfg.padding[1]) / 2,
y: -style.height / 2 - (cfg.padding[0] - cfg.padding[2]) / 2,
width: style.width,
height: style.height,
},
draggable: true,
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
name: 'combo-keyShape',
});
// Add the circle on the right
group.addShape('marker', {
attrs: {
...style,
fill: '#fff',
opacity: 1,
// cfg.style.width and cfg.style.heigth correspond to the innerWidth and innerHeight in the figure of Illustration of Built-in Rect Combo
x: cfg.style.width / 2 + cfg.padding[1],
y: (cfg.padding[2] - cfg.padding[0]) / 2,
r: 10,
symbol: collapseIcon,
},
draggable: true,
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
name: 'combo-marker-shape',
});
return rect;
},
// Define the updating logic of the right circle
afterUpdate: function afterUpdate(cfg, combo) {
const group = combo.get('group');
// Find the circle shape in the graphics group of the Combo by name
const marker = group.find((ele) => ele.get('name') === 'combo-marker-shape');
// Update the position of the right circle
marker.attr({
// cfg.style.width and cfg.style.heigth correspond to the innerWidth and innerHeight in the figure of Illustration of Built-in Rect Combo
x: cfg.style.width / 2 + cfg.padding[1],
y: (cfg.padding[2] - cfg.padding[0]) / 2,
// The property 'collapsed' in the combo data represents the collapsing state of the Combo
// Update the symbol according to 'collapsed'
symbol: cfg.collapsed ? expandIcon : collapseIcon,
});
},
},
'rect',
);
const data = {
nodes: [
{ id: 'node1', x: 250, y: 200, comboId: 'combo1' },
{ id: 'node2', x: 300, y: 200, comboId: 'combo1' },
{ id: 'node3', x: 100, y: 200, comboId: 'combo3' },
],
combos: [
{ id: 'combo1', label: 'Combo 1', parentId: 'combo2' },
{ id: 'combo2', label: 'Combo 2' },
{ id: 'combo3', label: 'Combo 3', collapsed: true },
],
};
const container = document.getElementById('container');
const width = container.scrollWidth;
const height = container.scrollHeight || 500;
const descriptionDiv = document.createElement('div');
descriptionDiv.innerHTML = 'Click the right marker to collapse/expand the combo.';
const container = document.getElementById('container');
descriptionDiv.innerHTML = 'Click the bottom marker to collapse/expand the combo.';
container.appendChild(descriptionDiv);
const width = container.scrollWidth;
const height = (container.scrollHeight || 500) - 20;
const graph = new G6.Graph({
class CRectCombo extends Extensions.RectCombo {
drawOtherShapes(model, shapeMap, diffData) {
const { data } = model;
const keyShapeBBox = shapeMap.keyShape.getLocalBounds();
const otherShapes = {
markerShape: this.upsertShape(
'path',
'markerShape',
{
cursor: 'pointer',
stroke: '#666',
lineWidth: 1,
fill: '#fff',
path: data.collapsed
? stdLib.markers.expand(keyShapeBBox.center[0], keyShapeBBox.max[1], 8)
: stdLib.markers.collapse(keyShapeBBox.center[0], keyShapeBBox.max[1], 8),
},
shapeMap,
model,
),
};
return otherShapes;
}
}
const ExtGraph = extend(Graph, {
combos: {
'c-rect-combo': CRectCombo,
},
// behaviors: {
// 'hover-activate': Extensions.HoverActivate,
// },
});
const graph = new ExtGraph({
container: 'container',
width,
height,
// Set groupByTypes to false to get rendering result with reasonable visual zIndex for combos
groupByTypes: false,
// Configure the combos globally
defaultCombo: {
// The type of the combos. You can also assign type in the data of combos
type: 'cRect',
// ... Other global configurations for combos
stackCfg: {
ignoreStateChange: true,
},
comboStateStyles: {
dragenter: {
lineWidth: 4,
stroke: '#FE9797',
node: {
labelShape: {
position: 'center',
text: {
fields: ['id'],
formatter: (model) => model.id,
},
},
animates: {
update: [
{
fields: ['opacity'],
shapeId: 'haloShape',
},
],
},
},
combo: (model) => {
return {
id: model.id,
data: {
type: 'c-rect-combo',
...model.data,
keyShape: {
padding: [10, 20, 30, 40],
r: 50,
},
labelShape: {
text: model.data.label,
position: 'top',
},
otherShapes: {},
animates: {
buildIn: [
{
fields: ['opacity'],
duration: 500,
delay: 500 + Math.random() * 500,
},
],
buildOut: [
{
fields: ['opacity'],
duration: 200,
},
],
update: [
{
fields: ['lineWidth', 'r'],
shapeId: 'keyShape',
},
{
fields: ['opacity'],
shapeId: 'haloShape',
},
],
},
},
};
},
data: {
nodes: [
{
id: 'node1',
data: {
x: 350,
y: 200,
parentId: 'combo1',
},
},
{ id: 'node2', data: { x: 350, y: 250, parentId: 'combo1' } },
{ id: 'node3', data: { x: 100, y: 200, parentId: 'combo3' } },
],
edges: [
{ id: 'edge1', source: 'node1', target: 'node2' },
{ id: 'edge2', source: 'node1', target: 'node3' },
],
combos: [
{ id: 'combo1', data: { label: 'Combo 1', parentId: 'combo2' } },
{ id: 'combo2', data: { label: 'Combo 2' } },
{ id: 'combo3', data: { label: 'Combo 3', collapsed: true } },
],
},
modes: {
default: ['drag-combo', 'drag-node', 'drag-canvas', 'click-select'],
default: [
{
type: 'drag-node',
enableTransient: false,
updateComboStructure: false,
},
'drag-canvas',
{
type: 'click-select',
itemTypes: ['node', 'edge', 'combo'],
},
{
type: 'drag-combo',
enableTransient: true,
updateComboStructure: true,
},
],
},
});
graph.data(data);
graph.render();
// collapse/expand when click the marker
graph.on('combo:click', (e) => {
if (e.target.get('name') === 'combo-marker-shape') {
// graph.collapseExpandCombo(e.item.getModel().id);
graph.collapseExpandCombo(e.item);
if (graph.get('layout')) graph.layout();
else graph.refreshPositions();
/** Click the bottom marker to collapse/expand the combo. */
graph.on('combo:click', (event) => {
const { itemId, target } = event;
if (target.id === 'markerShape') {
const model = graph.getComboData(itemId);
if (model.data.collapsed) {
graph.expandCombo(itemId);
} else {
graph.collapseCombo(itemId);
}
}
});
graph.on('combo:dragend', (e) => {
graph.getCombos().forEach((combo) => {
graph.setItemState(combo, 'dragenter', false);
});
});
graph.on('node:dragend', (e) => {
graph.getCombos().forEach((combo) => {
graph.setItemState(combo, 'dragenter', false);
});
});
graph.on('combo:dragenter', (e) => {
graph.setItemState(e.item, 'dragenter', true);
});
graph.on('combo:dragleave', (e) => {
graph.setItemState(e.item, 'dragenter', false);
});
graph.on('combo:mouseenter', (evt) => {
const { item } = evt;
graph.setItemState(item, 'active', true);
});
graph.on('combo:mouseleave', (evt) => {
const { item } = evt;
graph.setItemState(item, 'active', false);
});
if (typeof window !== 'undefined')
window.onresize = () => {
if (!graph || graph.get('destroyed')) return;
if (!graph || graph.destroyed) return;
if (!container || !container.scrollWidth || !container.scrollHeight) return;
graph.changeSize(container.scrollWidth, container.scrollHeight - 20);
graph.setSize([container.scrollWidth, container.scrollHeight]);
};

View File

@ -1,91 +1,134 @@
import G6 from '@antv/g6';
import { Graph } from '@antv/g6';
const data = {
nodes: [
{ id: 'node1', x: 350, y: 200, comboId: 'combo1' },
{ id: 'node2', x: 350, y: 250, comboId: 'combo1' },
{ id: 'node3', x: 100, y: 200, comboId: 'combo3' },
],
edges: [
{ source: 'node1', target: 'node2' },
{ source: 'node1', target: 'node3' },
{ source: 'combo1', target: 'node3' },
],
combos: [
{ id: 'combo1', label: 'Combo 1', parentId: 'combo2' },
{ id: 'combo2', label: 'Combo 2' },
{ id: 'combo3', label: 'Combo 3', collapsed: true },
],
};
const container = document.getElementById('container');
const width = container.scrollWidth;
const height = container.scrollHeight || 500;
const descriptionDiv = document.createElement('div');
descriptionDiv.innerHTML =
'Double click the combo to collapse/expand it. Drag the node or combo to change the hierarchy.';
const container = document.getElementById('container');
container.appendChild(descriptionDiv);
const width = container.scrollWidth;
const height = (container.scrollHeight || 500) - 20;
const graph = new G6.Graph({
const graph = new Graph({
container: 'container',
width,
height,
// Set groupByTypes to false to get rendering result with reasonable visual zIndex for combos
groupByTypes: false,
defaultCombo: {
type: 'circle',
style: {
lineWidth: 1,
stackCfg: {
ignoreStateChange: true,
},
node: {
labelShape: {
position: 'center',
text: {
fields: ['id'],
formatter: (model) => model.id,
},
},
labelCfg: {
refY: 15,
position: 'bottom',
animates: {
update: [
{
fields: ['opacity'],
shapeId: 'haloShape',
},
],
},
},
comboStateStyles: {
dragenter: {
lineWidth: 4,
stroke: '#FE9797',
},
combo: (model) => {
return {
id: model.id,
data: {
type: 'circle-combo',
...model.data,
keyShape: {
padding: [10, 20, 30, 40],
r: 50,
},
labelShape: {
text: model.data.label,
position: 'top',
},
animates: {
buildIn: [
{
fields: ['opacity'],
duration: 500,
delay: 500 + Math.random() * 500,
},
],
buildOut: [
{
fields: ['opacity'],
duration: 200,
},
],
update: [
{
fields: ['lineWidth', 'r'],
shapeId: 'keyShape',
},
{
fields: ['opacity'],
shapeId: 'haloShape',
},
],
},
},
};
},
data: {
nodes: [
{
id: 'node1',
data: {
x: 350,
y: 200,
parentId: 'combo1',
},
},
{ id: 'node2', data: { x: 350, y: 250, parentId: 'combo1' } },
{ id: 'node3', data: { x: 100, y: 200, parentId: 'combo3' } },
],
edges: [
{ id: 'edge1', source: 'node1', target: 'node2' },
{ id: 'edge2', source: 'node1', target: 'node3' },
{ id: 'edge3', source: 'combo1', target: 'node3' },
],
combos: [
{ id: 'combo1', data: { label: 'Combo 1', parentId: 'combo2' } },
{ id: 'combo2', data: { label: 'Combo 2' } },
{ id: 'combo3', data: { label: 'Combo 3', collapsed: true } },
],
},
modes: {
default: ['drag-canvas', 'drag-node', 'drag-combo', 'collapse-expand-combo', 'click-select'],
default: [
'collapse-expand-combo',
{
type: 'drag-node',
enableTransient: false,
updateComboStructure: false,
},
'drag-canvas',
{
type: 'click-select',
itemTypes: ['node', 'edge', 'combo'],
},
{
type: 'hover-activate',
itemTypes: ['node', 'edge', 'combo'],
},
{
type: 'drag-combo',
enableTransient: true,
updateComboStructure: true,
},
],
},
});
graph.data(data);
graph.render();
graph.on('combo:dragend', (e) => {
graph.getCombos().forEach((combo) => {
graph.setItemState(combo, 'dragenter', false);
});
});
graph.on('node:dragend', (e) => {
graph.getCombos().forEach((combo) => {
graph.setItemState(combo, 'dragenter', false);
});
});
graph.on('combo:dragenter', (e) => {
graph.setItemState(e.item, 'dragenter', true);
});
graph.on('combo:dragleave', (e) => {
graph.setItemState(e.item, 'dragenter', false);
});
graph.on('combo:mouseenter', (evt) => {
const { item } = evt;
graph.setItemState(item, 'active', true);
});
graph.on('combo:mouseleave', (evt) => {
const { item } = evt;
graph.setItemState(item, 'active', false);
});
if (typeof window !== 'undefined')
window.onresize = () => {
if (!graph || graph.get('destroyed')) return;
if (!graph || graph.destroyed) return;
if (!container || !container.scrollWidth || !container.scrollHeight) return;
graph.changeSize(container.scrollWidth, container.scrollHeight - 20);
graph.setSize([container.scrollWidth, container.scrollHeight]);
};

View File

@ -1,94 +1,133 @@
import G6 from '@antv/g6';
import { Graph } from '@antv/g6';
const container = document.getElementById('container');
const width = container.scrollWidth;
const height = container.scrollHeight || 500;
const data = {
nodes: [
{ id: 'node1', x: 350, y: 200, comboId: 'combo1' },
{ id: 'node2', x: 350, y: 250, comboId: 'combo1' },
{ id: 'node3', x: 100, y: 200, comboId: 'combo3' },
],
edges: [
{ source: 'node1', target: 'node2' },
{ source: 'node1', target: 'node3' },
{ source: 'combo1', target: 'node3' },
],
combos: [
{ id: 'combo1', label: 'Combo 1', parentId: 'combo2' },
{ id: 'combo2', label: 'Combo 2' },
{ id: 'combo3', label: 'Combo 3' },
],
};
const descriptionDiv = document.createElement('div');
descriptionDiv.innerHTML =
'Double click the combo to collapse/expand it. Drag the node or combo to change the hierarchy.';
const container = document.getElementById('container');
container.appendChild(descriptionDiv);
const width = container.scrollWidth;
const height = (container.scrollHeight || 500) - 20;
const graph = new G6.Graph({
const graph = new Graph({
container: 'container',
width,
height,
// Set groupByTypes to false to get rendering result with reasonable visual zIndex for combos
groupByTypes: false,
defaultCombo: {
type: 'rect',
size: [40, 10], // The minimum size of the Combo
padding: [30, 20, 10, 20],
style: {
lineWidth: 1,
stackCfg: {
ignoreStateChange: true,
},
node: {
labelShape: {
position: 'center',
text: {
fields: ['id'],
formatter: (model) => model.id,
},
},
labelCfg: {
refY: 10,
refX: 20,
position: 'top',
animates: {
update: [
{
fields: ['opacity'],
shapeId: 'haloShape',
},
],
},
},
comboStateStyles: {
dragenter: {
lineWidth: 4,
stroke: '#FE9797',
},
combo: (model) => {
return {
id: model.id,
data: {
type: 'rect-combo',
...model.data,
keyShape: {
padding: [10, 20, 30, 40],
r: 50,
},
labelShape: {
text: model.data.label,
position: 'top',
},
animates: {
buildIn: [
{
fields: ['opacity'],
duration: 500,
delay: 500 + Math.random() * 500,
},
],
buildOut: [
{
fields: ['opacity'],
duration: 200,
},
],
update: [
{
fields: ['lineWidth', 'r'],
shapeId: 'keyShape',
},
{
fields: ['opacity'],
shapeId: 'haloShape',
},
],
},
},
};
},
data: {
nodes: [
{
id: 'node1',
data: {
x: 350,
y: 200,
parentId: 'combo1',
},
},
{ id: 'node2', data: { x: 350, y: 250, parentId: 'combo1' } },
{ id: 'node3', data: { x: 100, y: 200, parentId: 'combo3' } },
],
edges: [
{ id: 'edge1', source: 'node1', target: 'node2' },
{ id: 'edge2', source: 'node1', target: 'node3' },
],
combos: [
{ id: 'combo1', data: { label: 'Combo 1', parentId: 'combo2' } },
{ id: 'combo2', data: { label: 'Combo 2' } },
{ id: 'combo3', data: { label: 'Combo 3', collapsed: true } },
],
},
modes: {
default: ['drag-canvas', 'drag-node', 'drag-combo', 'collapse-expand-combo', 'click-select'],
default: [
'collapse-expand-combo',
{
type: 'drag-node',
enableTransient: false,
updateComboStructure: false,
},
'drag-canvas',
{
type: 'click-select',
itemTypes: ['node', 'edge', 'combo'],
},
{
type: 'hover-activate',
itemTypes: ['node', 'edge', 'combo'],
},
{
type: 'drag-combo',
enableTransient: true,
updateComboStructure: true,
},
],
},
});
graph.data(data);
graph.render();
graph.on('combo:dragend', (e) => {
graph.getCombos().forEach((combo) => {
graph.setItemState(combo, 'dragenter', false);
});
});
graph.on('node:dragend', (e) => {
graph.getCombos().forEach((combo) => {
graph.setItemState(combo, 'dragenter', false);
});
});
graph.on('combo:dragenter', (e) => {
graph.setItemState(e.item, 'dragenter', true);
});
graph.on('combo:dragleave', (e) => {
graph.setItemState(e.item, 'dragenter', false);
});
graph.on('combo:mouseenter', (evt) => {
const { item } = evt;
graph.setItemState(item, 'active', true);
});
graph.on('combo:mouseleave', (evt) => {
const { item } = evt;
graph.setItemState(item, 'active', false);
});
if (typeof window !== 'undefined')
window.onresize = () => {
if (!graph || graph.get('destroyed')) return;
if (!graph || graph.destroyed) return;
if (!container || !container.scrollWidth || !container.scrollHeight) return;
graph.changeSize(container.scrollWidth, container.scrollHeight - 20);
graph.setSize([container.scrollWidth, container.scrollHeight]);
};

View File

@ -1,11 +0,0 @@
---
title: API
---
# Built-in Behaviors
Refer to [Built-in Behavior](/en/docs/manual/middle/states/defaultBehavior).
# Custom Behavior
Refer to [Custom Behavior](/en/docs/manual/middle/states/custom-behavior).

View File

@ -1,11 +0,0 @@
---
title: API
---
# 复合交互
请参考教程 [内置 Behavior](/en/docs/manual/middle/states/defaultBehavior)。
# 自定义复合交互
请参考教程 [自定义 Behavior](/en/docs/manual/middle/states/custom-behavior)。

View File

@ -1,34 +0,0 @@
import G6 from '@antv/g6';
const container = document.getElementById('container');
const tipDiv = document.createElement('div');
tipDiv.innerHTML = `Press both the keys 'control' and '1' to call graph.fitView. The keys and the called function can be configured.【ATTENTION】: make sure the focus is on the canvas when you pressing keys
<br /> 按住 'control' 并按下 '1' 将会调用 graph.fitView组合按键及被调用的函数及其参数均可被配置注意使用组合件调用函数时请保证当前焦点在画布上`;
container.appendChild(tipDiv);
const width = container.scrollWidth;
const height = (container.scrollHeight || 500) - 100;
const graph = new G6.Graph({
container: 'container',
width,
height,
fitCenter: true,
modes: {
default: ['shortcuts-call'],
},
});
fetch('https://gw.alipayobjects.com/os/bmw-prod/b0ca4b15-bd0c-43ec-ae41-c810374a1d55.json')
.then((res) => res.json())
.then((data) => {
graph.data(data);
graph.render();
graph.zoom(2);
});
if (typeof window !== 'undefined')
window.onresize = () => {
if (!graph || graph.get('destroyed')) return;
if (!container || !container.scrollWidth || !container.scrollHeight) return;
graph.changeSize(container.scrollWidth, container.scrollHeight - 100);
};

View File

@ -1,18 +0,0 @@
{
"title": {
"zh": "中文分类",
"en": "Category"
},
"demos": [
{
"filename": "fitView.js",
"title": "使用快捷键适配画布",
"screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*FALhS5MhAzAAAAAAAAAAAAAAARQnAQ"
},
{
"filename": "moveTo.js",
"title": "使用快捷键移动到左上角",
"screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*7RPkSJpwzrQAAAAAAAAAAAAAARQnAQ"
}
]
}

View File

@ -1,41 +0,0 @@
import G6 from '@antv/g6';
const container = document.getElementById('container');
const tipDiv = document.createElement('div');
tipDiv.innerHTML = `Press both the keys 'control' and 'm' to call graph.fitView. The keys and the called function can be configured.【ATTENTION】: make sure the focus is on the canvas when you pressing keys
<br /> 按住 'control' 并按下 'm' 将会调用 graph.moveTo组合按键及被调用的函数及其参数均可被配置注意使用组合件调用函数时请保证当前焦点在画布上`;
container.appendChild(tipDiv);
const width = container.scrollWidth;
const height = (container.scrollHeight || 500) - 100;
const graph = new G6.Graph({
container: 'container',
width,
height,
fitCenter: true,
modes: {
default: [
{
type: 'shortcuts-call',
functionName: 'moveTo',
functionParams: [0, 0],
combinedKey: 'm',
},
],
},
});
fetch('https://gw.alipayobjects.com/os/bmw-prod/b0ca4b15-bd0c-43ec-ae41-c810374a1d55.json')
.then((res) => res.json())
.then((data) => {
graph.data(data);
graph.render();
graph.zoom(2);
});
if (typeof window !== 'undefined')
window.onresize = () => {
if (!graph || graph.get('destroyed')) return;
if (!container || !container.scrollWidth || !container.scrollHeight) return;
graph.changeSize(container.scrollWidth, container.scrollHeight - 100);
};

View File

@ -1,12 +0,0 @@
---
title: Fit View with Shortcuts
order: 15
---
Allow the end-user to call a function of Graph with shortcuts keys.
允许终端用户使用键盘组合键调用 Graph 的函数。
## Usage
E.g. press down 'control' and '1' on keyboard to make the graph fit the canvas. Attention: make sure the focus is on the canvas when the end-user is pressing keys to call the function. For more information, please refer to [shortcuts-call](/en/docs/manual/middle/states/defaultBehavior/shortcuts-call).

View File

@ -1,10 +0,0 @@
---
title: 快捷键适应画布
order: 15
---
允许终端用户使用键盘组合键调用 Graph 的函数。
## 如何使用
例如按下键盘上的 control 与 1对图进行适应画布。注意终端用户使用该功能时焦点必须在画布上才能够正确触发。详细内容参考文档 [shortcuts-call](/zh/docs/manual/middle/states/defaultBehavior/shortcuts-call)。

View File

@ -1,5 +1,4 @@
import G6 from '@antv/g6';
import insertCss from 'insert-css';
import { Graph, extend, Extensions } from '@antv/g6';
insertCss(`
.g6-component-tooltip {
@ -13,89 +12,78 @@ insertCss(`
}
`);
const tooltip = new G6.Tooltip({
offsetX: 10,
offsetY: 10,
fixToNode: [1, 0.5],
// the types of items that allow the tooltip show up
// 允许出现 tooltip 的 item 类型
itemTypes: ['node', 'edge'],
// custom the tooltip's content
// 自定义 tooltip 内容
getContent: (e) => {
const outDiv = document.createElement('div');
outDiv.style.width = 'fit-content';
outDiv.style.height = 'fit-content';
const model = e.item.getModel();
if (e.item.getType() === 'node') {
outDiv.innerHTML = `${model.name}`;
} else {
const source = e.item.getSource();
const target = e.item.getTarget();
outDiv.innerHTML = `来源:${source.getModel().name}<br/>去向:${target.getModel().name}`;
}
return outDiv;
},
});
const container = document.getElementById('container');
const width = container.scrollWidth;
const height = container.scrollHeight || 500;
const graph = new G6.Graph({
container: 'container',
width,
height,
layout: {
type: 'force',
edgeStrength: 0.7,
const ExtGraph = extend(Graph, {
plugins: {
tooltip: Extensions.Tooltip,
},
plugins: [tooltip],
modes: {
default: ['drag-canvas', 'activate-relations'],
behaviors: {
'activate-relations': Extensions.ActivateRelations,
},
defaultNode: {
size: [10, 10],
/* style for the keyShape */
// style: {
// lineWidth: 2,
// fill: '#DEE9FF',
// stroke: '#5B8FF9',
// },
},
defaultEdge: {
/* style for the keyShape */
style: {
stroke: '#aaa',
lineAppendWidth: 2,
opacity: 0.3,
},
},
/* styles for different states, there are built-in styles for states: active, inactive, selected, highlight, disable */
// nodeStateStyles: {
// active: {
// opacity: 1,
// },
// inactive: {
// opacity: 0.2,
// },
// },
// edgeStateStyles: {
// active: {
// stroke: '#999',
// },
// },
});
fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/xiaomi.json')
.then((res) => res.json())
.then((data) => {
graph.data(data);
graph.render();
});
console.log('data', data);
const graph = new ExtGraph({
container: 'container',
width,
height,
autoFit: 'view',
layout: {
type: 'force',
preventOverlap: true,
nodeSize: 32,
workerEnabled: true,
},
plugins: [
{
key: 'tooltip1',
type: 'tooltip',
trigger: 'pointerenter',
getContent: (e) => {
let innerHTML;
const getNodeNameById = (id) => data.nodes.find((node) => node.id === id).name;
if (e.itemType === 'node') {
innerHTML = getNodeNameById(e.itemId);
} else {
const { source, target } = data.edges.find((edge) => edge.id === e.itemId);
innerHTML = `来源:${getNodeNameById(source)}<br/>去向:${getNodeNameById(target)}`;
}
return `
<div class='g6-component-tooltip'>
${innerHTML}
</div>
`;
},
},
],
modes: {
default: [{ type: 'activate-relations', trigger: 'pointerenter' }, 'drag-canvas'],
},
data,
node: {
keyShape: {
r: 10,
},
},
edge: {
keyShape: {
stroke: '#aaa',
lineAppendWidth: 2,
opacity: 0.3,
},
},
});
if (typeof window !== 'undefined')
window.onresize = () => {
if (!graph || graph.get('destroyed')) return;
if (!container || !container.scrollWidth || !container.scrollHeight) return;
graph.changeSize(container.scrollWidth, container.scrollHeight);
};
if (typeof window !== 'undefined')
window.onresize = () => {
if (!graph || graph.destroyed) return;
if (!container || !container.scrollWidth || !container.scrollHeight) return;
graph.setSize([container.scrollWidth, container.scrollHeight]);
};
});

View File

@ -1,5 +1,4 @@
import G6 from '@antv/g6';
import insertCss from 'insert-css';
import { Graph, extend, Extensions } from '@antv/g6';
insertCss(`
.g6-component-tooltip {
@ -13,135 +12,144 @@ insertCss(`
}
`);
const tooltip = new G6.Tooltip({
offsetX: 10,
offsetY: 10,
fixToNode: [1, 0.5],
// the types of items that allow the tooltip show up
// 允许出现 tooltip 的 item 类型
itemTypes: ['node', 'edge'],
// custom the tooltip's content
// 自定义 tooltip 内容
getContent: (e) => {
const outDiv = document.createElement('div');
outDiv.style.width = 'fit-content';
outDiv.style.height = 'fit-content';
const model = e.item.getModel();
if (e.item.getType() === 'node') {
outDiv.innerHTML = `${model.name}`;
} else {
const source = e.item.getSource();
const target = e.item.getTarget();
outDiv.innerHTML = `来源:${source.getModel().name}<br/>去向:${target.getModel().name}`;
}
return outDiv;
},
});
const container = document.getElementById('container');
const width = container.scrollWidth;
const height = container.scrollHeight || 500;
const graph = new G6.Graph({
container: 'container',
width,
height,
plugins: [tooltip],
layout: {
type: 'force',
edgeStrength: 0.7,
const ExtGraph = extend(Graph, {
plugins: {
tooltip: Extensions.Tooltip,
},
modes: {
default: ['drag-canvas'],
},
defaultNode: {
size: [10, 10],
style: {
lineWidth: 2,
fill: '#DEE9FF',
stroke: '#5B8FF9',
},
},
defaultEdge: {
size: 1,
style: {
stroke: '#e2e2e2',
lineAppendWidth: 2,
},
},
nodeStateStyles: {
highlight: {
opacity: 1,
},
dark: {
opacity: 0.2,
},
},
edgeStateStyles: {
highlight: {
stroke: '#999',
},
behaviors: {
'activate-relations': Extensions.ActivateRelations,
},
});
function clearAllStats() {
graph.setAutoPaint(false);
graph.getNodes().forEach(function (node) {
graph.clearItemStates(node);
});
graph.getEdges().forEach(function (edge) {
graph.clearItemStates(edge);
});
graph.paint();
graph.setAutoPaint(true);
}
graph.on('node:mouseenter', function (e) {
const item = e.item;
graph.setAutoPaint(false);
graph.getNodes().forEach(function (node) {
graph.clearItemStates(node);
graph.setItemState(node, 'dark', true);
});
graph.setItemState(item, 'dark', false);
graph.setItemState(item, 'highlight', true);
graph.getEdges().forEach(function (edge) {
if (edge.getSource() === item) {
graph.setItemState(edge.getTarget(), 'dark', false);
graph.setItemState(edge.getTarget(), 'highlight', true);
graph.setItemState(edge, 'highlight', true);
edge.toFront();
} else if (edge.getTarget() === item) {
graph.setItemState(edge.getSource(), 'dark', false);
graph.setItemState(edge.getSource(), 'highlight', true);
graph.setItemState(edge, 'highlight', true);
edge.toFront();
} else {
graph.setItemState(edge, 'highlight', false);
}
});
graph.paint();
graph.setAutoPaint(true);
});
graph.on('node:mouseleave', clearAllStats);
graph.on('canvas:click', clearAllStats);
fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/xiaomi.json')
.then((res) => res.json())
.then((data) => {
graph.data({
nodes: data.nodes,
edges: data.edges.map(function (edge, i) {
edge.id = 'edge' + i;
return Object.assign({}, edge);
}),
console.log('data', data);
const graph = new ExtGraph({
container: 'container',
width,
height,
autoFit: 'view',
layout: {
type: 'force',
preventOverlap: true,
nodeSize: 32,
workerEnabled: true,
},
plugins: [
{
key: 'tooltip1',
type: 'tooltip',
trigger: 'pointerenter',
getContent: (e) => {
let innerHTML;
const getNodeNameById = (id) => data.nodes.find((node) => node.id === id).name;
if (e.itemType === 'node') {
innerHTML = getNodeNameById(e.itemId);
} else {
const { source, target } = data.edges.find((edge) => edge.id === e.itemId);
innerHTML = `来源:${getNodeNameById(source)}<br/>去向:${getNodeNameById(target)}`;
}
return `
<div class='g6-component-tooltip'>
${innerHTML}
</div>
`;
},
},
],
modes: {
default: ['drag-canvas'],
},
data,
node: {
keyShape: {
r: 10,
},
highlight: {
keyShape: {
fill: '#0f0',
},
},
dark: {
keyShape: {
opacity: 0.2,
},
},
},
nodeState: {
highlight: {
keyShape: {
opacity: 1,
fill: '#0f0',
},
},
dark: {
keyShape: {
opacity: 0.2,
},
},
},
edge: {
keyShape: {
stroke: '#aaa',
lineAppendWidth: 2,
opacity: 0.5,
},
},
edgeState: {
highlight: {
keyShape: {
stroke: '#999',
},
},
dark: {
keyshape: {
stroke: '#aaa',
opacity: 0.3,
},
},
},
});
graph.render();
});
graph.on('node:pointerenter', function (e) {
const item = e.itemId;
const allNodesId = graph.getAllNodesData().map((node) => node.id);
graph.clearItemState(allNodesId);
graph.setItemState(allNodesId, 'dark', true);
graph.setItemState(item, 'dark', false);
graph.setItemState(item, 'highlight', true);
graph.getAllEdgesData().forEach(function (edge) {
const sourceId = edge.source;
const targetId = edge.target;
if (sourceId === item) {
graph.setItemState(targetId, 'dark', false);
graph.setItemState(targetId, 'highlight', true);
graph.setItemState(edge.id, 'highlight', true);
} else if (targetId === item) {
graph.setItemState(sourceId, 'dark', false);
graph.setItemState(sourceId, 'highlight', true);
graph.setItemState(edge.id, 'highlight', true);
} else {
graph.setItemState(edge.id, 'highlight', false);
}
});
});
if (typeof window !== 'undefined')
window.onresize = () => {
if (!graph || graph.get('destroyed')) return;
if (!container || !container.scrollWidth || !container.scrollHeight) return;
graph.changeSize(container.scrollWidth, container.scrollHeight);
};
graph.on('node:pointerleave', function (e) {
const allNodesIds = graph.getAllNodesData().map((node) => node.id);
const allEdgesIds = graph.getAllEdgesData().map((edge) => edge.id);
graph.clearItemState([...allNodesIds, ...allEdgesIds]);
});
if (typeof window !== 'undefined')
window.onresize = () => {
if (!graph || graph.destroyed) return;
if (!container || !container.scrollWidth || !container.scrollHeight) return;
graph.setSize([container.scrollWidth, container.scrollHeight]);
};
});

View File

@ -1,132 +1,161 @@
import G6 from '@antv/g6';
import { Graph, Extensions, extend } from '@antv/g6';
const data = {
nodes: [
{
id: '1',
label: '公司1',
group: 1,
data: {
label: '公司1',
group: 1,
},
},
{
id: '2',
label: '公司2',
group: 1,
data: {
label: '公司2',
group: 1,
},
},
{
id: '3',
label: '公司3',
group: 1,
data: {
label: '公司3',
group: 1,
},
},
{
id: '4',
label: '公司4',
group: 1,
data: {
label: '公司4',
},
},
{
id: '5',
label: '公司5',
group: 2,
data: {
label: '公司5',
group: 1,
},
},
{
id: '6',
label: '公司6',
group: 2,
data: {
label: '公司6',
group: 2,
},
},
{
id: '7',
label: '公司7',
group: 2,
data: {
label: '公司7',
group: 2,
},
},
{
id: '8',
label: '公司8',
group: 2,
data: {
label: '公司8',
group: 1,
},
},
{
id: '9',
label: '公司9',
group: 2,
data: {
label: '公司9',
group: 2,
},
},
],
edges: [
{
id: 'edge1',
source: '1',
target: '1',
type: 'loop',
data: { type: 'loop-edge' },
},
{
id: 'edge2',
source: '2',
target: '2',
type: 'loop',
data: {
type: 'loop-edge',
},
},
{
id: 'edge3',
source: '1',
target: '2',
data: {
type: 'A',
dataType: 'A',
amount: '100,000 元',
date: '2019-08-03',
},
},
{
id: 'edge4',
source: '1',
target: '3',
data: {
type: 'B',
dataType: 'B',
amount: '100,000 元',
date: '2019-08-03',
},
},
{
id: 'edge5',
source: '2',
target: '5',
data: {
type: 'C',
dataType: 'C',
amount: '100,000 元',
date: '2019-08-03',
},
},
{
id: 'edge6',
source: '5',
target: '6',
data: {
type: 'B',
dataType: 'B',
amount: '100,000 元',
date: '2019-08-03',
},
},
{
id: 'edge7',
source: '3',
target: '4',
data: {
type: 'C',
dataType: 'C',
amount: '100,000 元',
date: '2019-08-03',
},
},
{
id: 'edge8',
source: '4',
target: '7',
data: {
type: 'B',
dataType: 'B',
amount: '100,000 元',
date: '2019-08-03',
},
},
{
id: 'edge9',
source: '1',
target: '8',
data: {
type: 'B',
dataType: 'B',
amount: '100,000 元',
date: '2019-08-03',
},
},
{
id: 'edge10',
source: '1',
target: '9',
data: {
type: 'C',
dataType: 'C',
amount: '100,000 元',
date: '2019-08-03',
},
@ -137,76 +166,114 @@ const data = {
const container = document.getElementById('container');
const width = container.scrollWidth;
const height = container.scrollHeight || 500;
const graph = new G6.Graph({
container: 'container',
const hullPlugin = new Extensions.Hull({
key: 'hull-plugin',
});
const ExtGraph = extend(Graph, {
edges: {
'loop-edge': Extensions.LoopEdge,
},
});
const graph = new ExtGraph({
container,
width,
height,
plugins: [hullPlugin],
modes: {
default: ['drag-canvas', 'zoom-canvas', 'drag-node'],
},
fitView: true,
autoFit: 'view',
layout: {
type: 'grid',
},
data,
});
graph.data(data);
graph.render();
const hull1 = graph.createHull({
hullPlugin.addHull({
id: 'hull1',
type: 'smooth-convex',
padding: 15,
members: graph.getNodes().filter((node) => node.getModel().group === 1),
type: 'smooth-convex', //'bubble' | 'round-convex' | 'smooth-convex';
members: graph
.getAllNodesData()
.filter((model) => model.data.group === 1)
.map((node) => node.id),
labelShape: {
text: 'Group1',
position: 'left',
offsetY: -2,
},
});
const hull2 = graph.createHull({
hullPlugin.addHull({
id: 'hull2',
members: graph.getNodes().filter((node) => node.getModel().group === 2),
padding: 15,
type: 'bubble',
type: 'round-convex',
members: graph
.getAllNodesData()
.filter((model) => model.data.group === 2)
.map((node) => node.id),
labelShape: {
text: 'Group2',
position: 'left',
offsetY: -2,
},
style: {
fill: 'pink',
stroke: 'red',
},
update: 'drag',
});
graph.on('canvas:contextmenu', (ev) => {
ev.preventDefault();
ev.stopPropagation();
const item = graph.addItem('node', {
x: ev.x,
y: ev.y,
id: Math.random(),
group: 2,
let memberAdded = false;
let nonMemberAdded = false;
const updateActions = [
{
name: 'Add/Delete Member',
action: () => {
if (!memberAdded) {
hullPlugin.addHullMember('hull1', ['4']);
} else {
hullPlugin.removeHullMember('hull1', ['4']);
}
memberAdded = !memberAdded;
},
},
{
name: 'Update Config',
action: () => {
hullPlugin.updateHull({
id: 'hull1',
style: { fill: '#ff0' },
labelShape: { text: 'updated-label', position: 'top' },
});
},
},
];
const btnContainer = document.createElement('div');
btnContainer.style.position = 'absolute';
container.appendChild(btnContainer);
const tip = document.createElement('span');
tip.innerHTML = `👉 update:`;
btnContainer.appendChild(tip);
updateActions.forEach((item, i) => {
const btn = document.createElement('a');
btn.innerHTML = item.name;
btn.style.backgroundColor = 'rgba(255, 255, 255, 0.8)';
btn.style.padding = '4px';
btn.style.marginLeft = i > 0 ? '24px' : '8px';
btnContainer.appendChild(btn);
btn.addEventListener('click', () => {
item.action();
});
hull2.addMember(item);
});
graph.on('afterupdateitem', (e) => {
if (hull1.members.indexOf(e.item) > -1 || hull1.nonMembers.indexOf(e.item) > -1) {
hull1.updateData(hull1.members);
}
});
graph.on('node:dragend', (e) => {
const item = e.item;
const memberIdx = hull2.members.indexOf(item);
if (memberIdx > -1) {
// 如果移出原hull范围则去掉
if (!hull2.contain(item)) {
hull2.removeMember(item);
} else {
hull2.updateData(hull2.members);
}
} else {
if (hull2.contain(item)) hull2.addMember(item);
}
});
if (typeof window !== 'undefined')
window.onresize = () => {
if (!graph || graph.get('destroyed')) return;
if (!graph || graph.destroyed) return;
if (!container || !container.scrollWidth || !container.scrollHeight) return;
graph.changeSize(container.scrollWidth, container.scrollHeight);
graph.setSize([container.scrollWidth, container.scrollHeight]);
};

View File

@ -1,46 +1,44 @@
import G6 from '@antv/g6';
import G6, { Extensions } from '@antv/g6';
const data = {
nodes: [
{ id: 'node0', size: 50 },
{ id: 'node1', size: 30 },
{ id: 'node2', size: 30 },
{ id: 'node3', size: 30 },
{ id: 'node4', size: 30, isLeaf: true },
{ id: 'node5', size: 30, isLeaf: true },
{ id: 'node6', size: 15, isLeaf: true },
{ id: 'node7', size: 15, isLeaf: true },
{ id: 'node8', size: 15, isLeaf: true },
{ id: 'node9', size: 15, isLeaf: true },
{ id: 'node10', size: 15, isLeaf: true },
{ id: 'node11', size: 15, isLeaf: true },
{ id: 'node12', size: 15, isLeaf: true },
{ id: 'node13', size: 15, isLeaf: true },
{ id: 'node14', size: 15, isLeaf: true },
{ id: 'node15', size: 15, isLeaf: true },
{ id: 'node16', size: 15, isLeaf: true },
{ id: 'node0', data: { size: 50 } },
{ id: 'node1', data: { size: 30 } },
{ id: 'node2', data: { size: 30 } },
{ id: 'node3', data: { size: 30 } },
{ id: 'node4', data: { size: 30, isLeaf: true } },
{ id: 'node5', data: { size: 30, isLeaf: true } },
{ id: 'node6', data: { size: 15, isLeaf: true } },
{ id: 'node7', data: { size: 15, isLeaf: true } },
{ id: 'node8', data: { size: 15, isLeaf: true } },
{ id: 'node9', data: { size: 15, isLeaf: true } },
{ id: 'node10', data: { size: 15, isLeaf: true } },
{ id: 'node11', data: { size: 15, isLeaf: true } },
{ id: 'node12', data: { size: 15, isLeaf: true } },
{ id: 'node13', data: { size: 15, isLeaf: true } },
{ id: 'node14', data: { size: 15, isLeaf: true } },
{ id: 'node15', data: { size: 15, isLeaf: true } },
{ id: 'node16', data: { size: 15, isLeaf: true } },
],
edges: [
{ source: 'node0', target: 'node1' },
{ source: 'node0', target: 'node2' },
{ source: 'node0', target: 'node3' },
{ source: 'node0', target: 'node4' },
{ source: 'node0', target: 'node5' },
{ source: 'node1', target: 'node6' },
{ source: 'node1', target: 'node7' },
{ source: 'node2', target: 'node8' },
{ source: 'node2', target: 'node9' },
{ source: 'node2', target: 'node10' },
{ source: 'node2', target: 'node11' },
{ source: 'node2', target: 'node12' },
{ source: 'node2', target: 'node13' },
{ source: 'node3', target: 'node14' },
{ source: 'node3', target: 'node15' },
{ source: 'node3', target: 'node16' },
{ id: 'edge1', source: 'node0', target: 'node1' },
{ id: 'edge2', source: 'node0', target: 'node2' },
{ id: 'edge3', source: 'node0', target: 'node3' },
{ id: 'edge4', source: 'node0', target: 'node4' },
{ id: 'edge5', source: 'node0', target: 'node5' },
{ id: 'edge6', source: 'node1', target: 'node6' },
{ id: 'edge7', source: 'node1', target: 'node7' },
{ id: 'edge8', source: 'node2', target: 'node8' },
{ id: 'edge9', source: 'node2', target: 'node9' },
{ id: 'edge10', source: 'node2', target: 'node10' },
{ id: 'edge11', source: 'node2', target: 'node11' },
{ id: 'edge12', source: 'node2', target: 'node12' },
{ id: 'edge13', source: 'node2', target: 'node13' },
{ id: 'edge14', source: 'node3', target: 'node14' },
{ id: 'edge15', source: 'node3', target: 'node15' },
{ id: 'edge16', source: 'node3', target: 'node16' },
],
};
const nodes = data.nodes;
const descriptionDiv = document.createElement('div');
descriptionDiv.innerHTML = 'Wait for the layout to complete...';
const container = document.getElementById('container');
@ -49,86 +47,91 @@ container.appendChild(descriptionDiv);
const width = container.scrollWidth;
const height = (container.scrollHeight || 500) - 20;
const hullPlugin = new Extensions.Hull({
key: 'hull-plugin1',
});
const graph = new G6.Graph({
container: 'container',
width,
height,
plugins: [hullPlugin],
modes: {
default: ['drag-canvas', 'zoom-canvas', 'drag-node', 'lasso-select'],
},
layout: {
type: 'force',
preventOverlap: true,
workerEnabled: true,
linkDistance: (d) => {
if (d.source.id === 'node0') {
if (d.source === 'node0') {
return 300;
}
return 60;
},
nodeStrength: (d) => {
if (d.isLeaf) {
if (d.data.isLeaf) {
return -50;
}
return -10;
},
edgeStrength: (d) => {
if (d.source.id === 'node1' || d.source.id === 'node2' || d.source.id === 'node3') {
if (d.source === 'node1' || d.source === 'node2' || d.source === 'node3') {
return 0.7;
}
return 0.1;
},
},
data,
node: {
keyShape: {
r: {
fields: ['size'],
formatter: (model) => model.data.size,
},
},
},
});
graph.data({
nodes,
edges: data.edges.map(function (edge, i) {
edge['id'] = 'edge' + i;
return Object.assign({}, edge);
}),
});
graph.render();
let centerNodes = graph.getNodes().filter((node) => !node.getModel().isLeaf);
let centerNodes = graph
.getAllNodesData()
.filter((model) => !model.data.isLeaf)
.map((node) => node.id);
graph.on('afterlayout', () => {
descriptionDiv.innerHTML = '';
const hull1 = graph.createHull({
hullPlugin.addHull({
id: 'centerNode-hull',
type: 'bubble',
members: centerNodes,
padding: 10,
labelShape: {
text: 'centerNode-hull',
position: 'left',
offsetY: -2,
},
});
const hull2 = graph.createHull({
hullPlugin.addHull({
id: 'leafNode-hull1',
members: ['node6', 'node7'],
padding: 10,
style: {
fill: 'lightgreen',
stroke: 'green',
},
});
const hull3 = graph.createHull({
hullPlugin.addHull({
id: 'leafNode-hull2',
members: ['node8', 'node9', 'node10', 'node11', 'node12', 'node13'],
padding: 10,
style: {
fill: 'lightgreen',
stroke: 'green',
},
});
graph.on('afterupdateitem', (e) => {
hull1.updateData(hull1.members);
hull2.updateData(hull2.members);
hull3.updateData(hull3.members);
});
});
if (typeof window !== 'undefined')
window.onresize = () => {
if (!graph || graph.get('destroyed')) return;
if (!graph || graph.destroyed) return;
if (!container || !container.scrollWidth || !container.scrollHeight) return;
graph.changeSize(container.scrollWidth, container.scrollHeight - 20);
graph.setSize([container.scrollWidth, container.scrollHeight]);
};

View File

@ -1,114 +1,108 @@
import G6 from '@antv/g6';
import { Graph } from '@antv/g6';
/**
* 本示例演示以下功能
* 1如何使用图片作为节点背景
* 2点击切换节点背景图片
*
*/
const img = new Image();
img.src = 'https://gw.alipayobjects.com/os/s/prod/antv/assets/image/logo-with-text-73b8a.svg';
// 点击图片节点,切换背景图片
const img2 = new Image();
img2.src = 'https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*wAmHQJbNVdwAAAAAAAAAAABkARQnAQ';
const data = {
nodes: [
{
x: 150,
y: 100,
type: 'circleNode',
label: 'circle',
id: 'node1',
labelCfg: {
position: 'center',
id: 'circle',
data: {
x: 100,
y: 150,
labelShape: {
position: 'center',
},
},
},
{
x: 350,
y: 100,
type: 'image',
id: 'node2',
img: img.src,
size: [120, 60],
label: 'avatar',
style: {
cursor: 'pointer',
},
labelCfg: {
position: 'bottom',
id: 'avatar',
data: {
x: 350,
y: 100,
labelShape: {
text: 'label before been hovered',
},
},
},
],
edges: [
{
id: 'edge-1',
source: 'node1',
target: 'node2',
label: 'line',
labelCfg: {
refY: 10,
data: {
labelShape: {
text: 'label before been hovered',
},
},
},
],
};
// 避免拖动过程中闪烁使用加载已经LOAD好的图片
img.onload = function () {
const container = document.getElementById('container');
const width = container.scrollWidth;
const height = container.scrollHeight || 500;
const graph = new G6.Graph({
container: 'container',
width,
height,
defaultNode: {
style: {
fill: '#DEE9FF',
stroke: '#5B8FF9',
const container = document.getElementById('container');
const width = container.scrollWidth;
const height = container.scrollHeight || 500;
const descriptionDiv = document.createElement('div');
descriptionDiv.innerHTML =
'Mouse hover over node to update node style and its label text. <br/> Mouse hover over edge to update edge style and its label text';
container.appendChild(descriptionDiv);
const graph = new Graph({
container,
width,
height,
modes: {
default: ['drag-node'],
},
node: {
keyShape: {
r: {
fields: ['size'],
formatter: (model) => model.data.size / 2,
},
},
defaultEdge: {
color: '#e2e2e2',
labelShape: {
position: 'center',
text: {
fields: ['label'],
formatter: (model) => model.data.label,
},
},
modes: {
default: [
'drag-node',
{
type: 'drag-node',
},
],
labelBackgroundShape: {},
otherShapes: {},
},
data,
});
// 节点上的点击事件
graph.on('node:pointerenter', (event) => {
const { itemId } = event;
graph.updateData('node', {
id: itemId,
data: {
labelShape: {
text: `after been hovered ${itemId}`,
fill: '#003a8c',
},
},
});
graph.data(data);
graph.render();
});
graph.on('node:click', function (evt) {
const target = evt.target;
const type = target.get('type');
const hasChangeBg = target.get('hasChangeBg');
if (type === 'image') {
if (!hasChangeBg) {
// 点击图片节点时,切换背景图片
target.attr('img', img2);
target.attr('imgSrc', 'http://seopic.699pic.com/photo/50055/5642.jpg_wh1200.jpg');
target.set('hasChangeBg', true);
} else {
target.attr('img', img);
target.attr(
'imgSrc',
'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1566553535233&di=b0b17eeea7bd7356a6f42ebfd48e9441&imgtype=0&src=http%3A%2F%2Fa2.att.hudong.com%2F64%2F29%2F01300543361379145388299988437_s.jpg',
);
target.set('hasChangeBg', false);
}
graph.paint();
}
graph.on('node:pointerleave', (event) => {
const { itemId } = event;
graph.updateData('node', {
id: itemId,
data: {
labelShape: {
text: 'label before been hovered',
fill: '#000',
},
},
});
});
if (typeof window !== 'undefined')
window.onresize = () => {
if (!graph || graph.get('destroyed')) return;
if (!container || !container.scrollWidth || !container.scrollHeight) return;
graph.changeSize(container.scrollWidth, container.scrollHeight);
};
};
if (typeof window !== 'undefined')
window.onresize = () => {
if (!graph || graph.destroyed) return;
if (!container || !container.scrollWidth || !container.scrollHeight) return;
graph.setSize([container.scrollWidth, container.scrollHeight]);
};

View File

@ -11,14 +11,6 @@
"en": "Update the Label"
},
"screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*NJm8S4sfgp4AAAAAAAAAAABkARQnAQ"
},
{
"filename": "changeImg.js",
"title": {
"zh": "切换节点背景图片",
"en": "Change the Image"
},
"screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*kwp1S6PJIVUAAAAAAAAAAABkARQnAQ"
}
]
}

View File

@ -1,33 +1,31 @@
import G6 from '@antv/g6';
/**
* 本示例演示以下功能
* 鼠标 hover 节点更新节点样式及其标签文本
* 鼠标 hover 边更新边样式及其标签文本
* by 十吾
*/
import { Graph } from '@antv/g6';
const data = {
nodes: [
{
id: 'node1',
x: 100,
y: 100,
label: 'label before\nbeen hovered',
data: {
x: 100,
y: 150,
label: 'label before been hovered',
},
},
{
id: 'node2',
x: 400,
y: 100,
label: 'label before\nbeen hovered',
data: {
x: 300,
y: 150,
label: 'label before been hovered',
},
},
],
edges: [
{
id: 'edge1',
source: 'node1',
target: 'node2',
label: 'label before\nbeen hovered',
labelCfg: {
refY: 10,
data: {
label: 'label before been hovered',
},
},
],
@ -36,75 +34,97 @@ const data = {
const container = document.getElementById('container');
const width = container.scrollWidth;
const height = container.scrollHeight || 500;
const graph = new G6.Graph({
container: 'container',
const graph = new Graph({
container,
width,
height,
defaultEdge: {
color: '#e2e2e2',
lineAppendWidth: 3,
modes: {
default: ['drag-node'],
},
data,
node: {
labelShape: {
position: 'center',
text: {
fields: ['label'],
formatter: (model) => model.data.label,
},
maxLines: 4,
},
},
edge: {
labelShape: {
text: {
fields: ['label'],
formatter: (model) => model.data.label,
},
maxLines: 2,
},
},
});
graph.data(data);
graph.render();
graph.on('node:mouseenter', function (evt) {
const node = evt.item;
const model = node.getModel();
model.oriLabel = model.label;
graph.updateItem(node, {
label: `after been hovered ${model.id}`,
labelCfg: {
style: {
fill: '#003a8c',
},
// 节点上的点击事件
graph.on('node:pointerenter', (event) => {
const { itemId } = event;
graph.updateData('node', {
id: itemId,
data: {
label: `after been hovered ${itemId}`,
labelShape: {
fill: '#0f0',
}
},
});
});
graph.on('node:mouseleave', function (evt) {
const node = evt.item;
const model = node.getModel();
graph.updateItem(node, {
label: model.oriLabel,
labelCfg: {
style: {
fill: '#555',
},
graph.on('edge:pointerenter', (event) => {
const { itemId } = event;
graph.updateData('edge', {
id: itemId,
data: {
label: `after been hovered ${itemId}`,
labelShape: {
fill: '#0f0',
}
},
});
});
graph.on('edge:mouseenter', function (evt) {
const edge = evt.item;
const model = edge.getModel();
model.oriLabel = model.label;
graph.updateItem(edge, {
label: 'after been hovered',
labelCfg: {
style: {
fill: '#003a8c',
},
graph.on('node:pointerleave', (event) => {
const { itemId } = event;
graph.updateData('node', {
id: itemId,
data: {
label: 'label before been hovered',
labelShape: {
fill: '#000',
}
},
});
});
graph.on('edge:mouseleave', function (evt) {
const edge = evt.item;
graph.setItemState(edge, 'hover', false);
graph.updateItem(edge, {
label: 'label before \n been hovered',
labelCfg: {
style: {
fill: '#555',
},
graph.on('edge:pointerleave', (event) => {
const { itemId } = event;
graph.updateData('edge', {
id: itemId,
data: {
label: 'label before been hovered',
labelShape: {
fill: '#000',
}
},
});
});
const descriptionDiv = document.createElement('div');
descriptionDiv.innerHTML =
'Mouse hover over node to update node style and its label text. <br/> Mouse hover over edge to update edge style and its label text';
container.appendChild(descriptionDiv);
if (typeof window !== 'undefined')
window.onresize = () => {
if (!graph || graph.get('destroyed')) return;
if (!graph || graph.destroyed) return;
if (!container || !container.scrollWidth || !container.scrollHeight) return;
graph.changeSize(container.scrollWidth, container.scrollHeight);
graph.setSize([container.scrollWidth, container.scrollHeight]);
};

View File

@ -1,39 +1,45 @@
import G6 from '@antv/g6';
/**
* Focus a node
* by Changzhe
*/
const data = {
nodes: [
{
id: 'node1',
x: 150,
y: 50,
label: 'node1',
data: {
x: 150,
y: 50,
label: 'node1',
},
},
{
id: 'node2',
x: 200,
y: 150,
label: 'node2',
data: {
x: 200,
y: 150,
label: 'node2',
},
},
{
id: 'node3',
x: 100,
y: 150,
label: 'node3',
data: {
x: 100,
y: 150,
label: 'node3',
},
},
],
edges: [
{
id: 'edge1',
source: 'node1',
target: 'node2',
},
{
id: 'edge2',
source: 'node2',
target: 'node3',
},
{
id: 'edge3',
source: 'node3',
target: 'node1',
},
@ -43,38 +49,37 @@ const data = {
const container = document.getElementById('container');
const width = container.scrollWidth;
const height = container.scrollHeight || 500;
const graph = new G6.Graph({
container: 'container',
width,
height,
fitView: false,
defaultNode: {
style: {
fill: '#DEE9FF',
stroke: '#5B8FF9',
data,
node: {
labelShape: {
position: 'center',
text: {
fields: ['label'],
formatter: (model) => model.data.label,
},
},
},
defaultEdge: {
style: {
edge: {
keyShape: {
stroke: '#b5b5b5',
},
},
});
graph.data(data);
graph.render();
function handleNodeClick(event) {
const item = event.item;
// animately move the graph to focus on the item.
graph.focusItem(item);
}
// listen to the node click event
graph.on('node:click', handleNodeClick);
// 节点上的点击事件
graph.on('node:click', (event) => {
const { itemId } = event;
graph.focusItem(itemId);
});
if (typeof window !== 'undefined')
window.onresize = () => {
if (!graph || graph.get('destroyed')) return;
if (!graph || graph.destroyed) return;
if (!container || !container.scrollWidth || !container.scrollHeight) return;
graph.changeSize(container.scrollWidth, container.scrollHeight);
graph.setSize([container.scrollWidth, container.scrollHeight]);
};

View File

@ -1,39 +1,45 @@
import G6 from '@antv/g6';
/**
* Focus a node with Animation
* by Changzhe
*/
const data = {
nodes: [
{
id: 'node1',
x: 150,
y: 50,
label: 'node1',
data: {
x: 150,
y: 50,
label: 'node1',
},
},
{
id: 'node2',
x: 200,
y: 150,
label: 'node2',
data: {
x: 200,
y: 150,
label: 'node2',
},
},
{
id: 'node3',
x: 100,
y: 150,
label: 'node3',
data: {
x: 100,
y: 150,
label: 'node3',
},
},
],
edges: [
{
id: 'edge1',
source: 'node1',
target: 'node2',
},
{
id: 'edge2',
source: 'node2',
target: 'node3',
},
{
id: 'edge3',
source: 'node3',
target: 'node1',
},
@ -43,48 +49,40 @@ const data = {
const container = document.getElementById('container');
const width = container.scrollWidth;
const height = container.scrollHeight || 500;
const graph = new G6.Graph({
container: 'container',
width,
height,
fitView: false,
defaultNode: {
style: {
fill: '#DEE9FF',
stroke: '#5B8FF9',
data,
node: {
labelShape: {
position: 'center',
text: {
fields: ['label'],
formatter: (model) => model.data.label,
},
},
},
defaultEdge: {
style: {
edge: {
keyShape: {
stroke: '#b5b5b5',
},
},
// The global configuration for graph animation also takes effect on the focusItem
// animate: true,
// animateCfg: {
// easing: 'easeCubic',
// duration: 500
// }
});
graph.data(data);
graph.render();
function handleNodeClick(event) {
const item = event.item;
// animately move the graph to focus on the item.
// the second parameter controlls whether move with animation, the third parameter is the animate configuration
graph.focusItem(item, true, {
easing: 'easeCubic',
duration: 500,
// 节点上的点击事件
graph.on('node:click', (event) => {
const { itemId } = event;
graph.focusItem(itemId, {
easing: 'ease-in',
duration: 1000,
});
}
// listen to the node click event
graph.on('node:click', handleNodeClick);
});
if (typeof window !== 'undefined')
window.onresize = () => {
if (!graph || graph.get('destroyed')) return;
if (!graph || graph.destroyed) return;
if (!container || !container.scrollWidth || !container.scrollHeight) return;
graph.changeSize(container.scrollWidth, container.scrollHeight);
graph.setSize([container.scrollWidth, container.scrollHeight]);
};

View File

@ -1,91 +1,83 @@
import G6 from '@antv/g6';
import { Graph, extend, Extensions } from '@antv/g6';
const data = {
nodes: [
{ id: 'node1', x: 150, y: 250 },
{ id: 'node2', x: 350, y: 250 },
{ id: 'node1', data: { x: 150, y: 250 } },
{ id: 'node2', data: { x: 350, y: 250 } },
],
edges: [
{
id: 'edge1',
source: 'node1',
target: 'node2',
},
],
};
let shift = true;
const container = document.getElementById('container');
const switchDiv = document.createElement('div');
switchDiv.innerHTML = `Press 'shift' and drag begin on empty space to brush select. Click 「HERE」 to switch trigger to \'drag\', and disable drag-canvas
<br /> 按住 'shift' 并从画布空白处开始拖拽即可开始框选点击这里 trigger 切换为 'drag'同时关闭画布拖拽`;
container.appendChild(switchDiv);
const width = container.scrollWidth;
const height = (container.scrollHeight || 500) - 30;
const graph = new G6.Graph({
container: 'container',
width,
height,
fitCenter: true,
modes: {
default: ['brush-select', 'drag-node', 'drag-canvas'],
altSelect: [
const ExtGraph = extend(Graph, {
behaviors: {
'hover-activate': Extensions.HoverActivate,
'brush-select': Extensions.BrushSelect,
},
});
const modes = {};
const ALLOWED_TRIGGERS = ['shift', 'alt', 'ctrl', 'drag', 'meta'];
ALLOWED_TRIGGERS.forEach((trigger) => {
const defaultBehavior = ['drag-node', 'hover-activate'];
if (trigger === 'shift') {
modes.default = [...defaultBehavior, 'brush-select'];
} else {
modes[`${trigger}Select`] = [
...defaultBehavior,
{
type: 'brush-select',
trigger: 'drag',
trigger: trigger,
},
'drag-node',
],
},
/* styles for different states, there are built-in styles for states: active, inactive, selected, highlight, disable. you can extend or custom it by the following lines */
/* 不同状态下节点和边的样式G6 提供以下状态名的默认样式active, inactive, selected, highlight, disable。可以通过如下方式修改或者扩展全局状态样式*/
// nodeStateStyles: {
// selected: {
// stroke: '#f00',
// lineWidth: 3
// }
// },
// edgeStateStyles: {
// selected: {
// lineWidth: 3,
// stroke: '#f00'
// }
// }
});
graph.on('node:mouseenter', (e) => {
graph.setItemState(e.item, 'active', true);
});
graph.on('node:mouseleave', (e) => {
graph.setItemState(e.item, 'active', false);
});
graph.on('nodeselectchange', (e) => {
console.log(e.selectedItems, e.select);
});
switchDiv.addEventListener('click', (e) => {
shift = !shift;
if (shift) {
graph.setMode('default');
switchDiv.innerHTML = `Press \'shift\' and drag begin on empty space to brush select. Click Here to switch trigger to \'drag\', and disable drag-canvas
<br /> 按住 'shift' 并从画布空白处开始拖拽即可开始框选点击这里 trigger 切换为 'drag'同时关闭画布拖拽`;
} else {
graph.setMode('altSelect');
switchDiv.innerHTML = `Press \'alt\' and drag begin on empty space to brush select. Click Here to switch trigger to key \'shift\', and enable drag-canvas
<br /> 按住 'alt' 并从画布空白处开始拖拽即可开始框选点击这里 trigger 切换为 'shift'同时开启画布拖拽`;
];
}
});
graph.data(data);
graph.render();
const graph = new ExtGraph({
container,
width,
height,
autoFit: 'center',
modes,
data,
});
const switchDiv = document.createElement('div');
switchDiv.innerHTML = `👉 Trigger`;
switchDiv.style.position = 'absolute';
switchDiv.style.zIndex = 10;
container.appendChild(switchDiv);
const selector = document.createElement('select');
selector.id = 'selector';
ALLOWED_TRIGGERS.forEach((trigger, index) => {
const option = document.createElement('option');
option.value = trigger;
option.innerHTML = `${trigger}`;
selector.appendChild(option);
});
switchDiv.appendChild(selector);
// Listen to the selector, change the graph mode when the selector is changed
selector.addEventListener('change', (e) => {
const value = e.target.value;
const mode = value === 'shift' ? 'default' : `${value}Select`;
// change the graph mode
graph.setMode(mode);
});
if (typeof window !== 'undefined')
window.onresize = () => {
if (!graph || graph.get('destroyed')) return;
if (!graph || graph.destroyed) return;
if (!container || !container.scrollWidth || !container.scrollHeight) return;
graph.changeSize(container.scrollWidth, container.scrollHeight - 30);
graph.setSize([container.scrollWidth, container.scrollHeight]);
};

View File

@ -1,91 +1,82 @@
import G6 from '@antv/g6';
import { Graph, extend, Extensions } from '@antv/g6';
const data = {
nodes: [
{ id: 'node1', x: 150, y: 250 },
{ id: 'node2', x: 350, y: 250 },
{ id: 'node1', data: { x: 150, y: 250 } },
{ id: 'node2', data: { x: 350, y: 250 } },
],
edges: [
{
id: 'edge1',
source: 'node1',
target: 'node2',
},
],
};
let shift = true;
const graphDiv = document.getElementById('container');
const switchDiv = document.createElement('div');
switchDiv.innerHTML = `Press \'shift\' to select multiple nodes. Click Here to switch trigger to key \'alt\'
<br /> 按住 'shift' 可多选节点点击这里 trigger 切换为 'alt'`;
graphDiv.appendChild(switchDiv);
const container = document.getElementById('container');
const width = container.scrollWidth;
const height = (container.scrollHeight || 500) - 30;
const graph = new G6.Graph({
container: 'container',
width,
height,
fitCenter: true,
modes: {
default: ['click-select', 'drag-node'],
altSelect: [
const ExtGraph = extend(Graph, {
behaviors: {
'hover-activate': Extensions.HoverActivate,
},
});
const modes = {};
const ALLOWED_TRIGGERS = ['shift', 'ctrl', 'alt', 'meta'];
ALLOWED_TRIGGERS.forEach((trigger) => {
const defaultBehavior = ['drag-node', 'hover-activate'];
if (trigger === 'shift') {
modes.default = [...defaultBehavior, 'click-select'];
} else {
modes[`${trigger}Select`] = [
...defaultBehavior,
{
type: 'click-select',
trigger: 'alt',
trigger: trigger,
},
'drag-node',
],
},
/* styles for different states, there are built-in styles for states: active, inactive, selected, highlight, disable. you can extend or custom it by the following lines */
/* 不同状态下节点和边的样式G6 提供以下状态名的默认样式active, inactive, selected, highlight, disable。可以通过如下方式修改或者扩展全局状态样式*/
// nodeStateStyles: {
// selected: {
// stroke: '#f00',
// lineWidth: 3
// }
// },
// edgeStateStyles: {
// selected: {
// lineWidth: 3,
// stroke: '#f00'
// }
// }
});
graph.on('node:mouseenter', (e) => {
graph.setItemState(e.item, 'active', true);
});
graph.on('node:mouseleave', (e) => {
graph.setItemState(e.item, 'active', false);
});
graph.on('nodeselectchange', (e) => {
console.log(e.selectedItems, e.select);
});
switchDiv.addEventListener('click', (e) => {
shift = !shift;
if (shift) {
graph.setMode('default');
switchDiv.innerHTML = `Press \'shift\' to select multiple nodes. Click Here to switch trigger to key \'alt\'
<br /> 按住 'shift' 可多选节点点击这里 trigger 切换为 'alt'`;
} else {
graph.setMode('altSelect');
switchDiv.innerHTML = `Press \'alt\' to select multiple nodes. Click Here to switch trigger to key \'shift\'
<br /> 按住 'alt' 可多选节点点击这里 trigger 切换为 'shift'`;
];
}
});
graph.data(data);
graph.render();
const graph = new ExtGraph({
container,
width,
height,
autoFit: 'center',
modes,
data,
});
const switchDiv = document.createElement('div');
switchDiv.innerHTML = `👉 Trigger`;
switchDiv.style.position = 'absolute';
switchDiv.style.zIndex = 10;
container.appendChild(switchDiv);
const selector = document.createElement('select');
selector.id = 'selector';
ALLOWED_TRIGGERS.forEach((trigger, index) => {
const option = document.createElement('option');
option.value = trigger;
option.innerHTML = `${trigger}`;
selector.appendChild(option);
});
switchDiv.appendChild(selector);
// Listen to the selector, change the graph mode when the selector is changed
selector.addEventListener('change', (e) => {
const value = e.target.value;
const mode = value === 'shift' ? 'default' : `${value}Select`;
// change the graph mode
graph.setMode(mode);
});
if (typeof window !== 'undefined')
window.onresize = () => {
if (!graph || graph.get('destroyed')) return;
if (!graph || graph.destroyed) return;
if (!container || !container.scrollWidth || !container.scrollHeight) return;
graph.changeSize(container.scrollWidth, container.scrollHeight - 30);
graph.setSize([container.scrollWidth, container.scrollHeight]);
};

View File

@ -1,89 +1,83 @@
import G6 from '@antv/g6';
import { Graph, extend, Extensions } from '@antv/g6';
const data = {
nodes: [
{ id: 'node1', x: 150, y: 250 },
{ id: 'node2', x: 350, y: 250 },
{ id: 'node1', data: { x: 150, y: 250 } },
{ id: 'node2', data: { x: 350, y: 250 } },
],
edges: [
{
id: 'edge1',
source: 'node1',
target: 'node2',
},
],
};
let shift = true;
const switchDiv = document.createElement('div');
switchDiv.innerHTML = `Press down the 'shift' on keyboard and drag to begin select. Click「HERE」to switch trigger to \'drag\', and custom lasso style, and disable drag-canvas
<br /> 按住 'shift' 可开始拉索选择点击这里切换 trigger 'drag'同时修改拉索样式和关闭画布拖拽`;
const container = document.getElementById('container');
container.appendChild(switchDiv);
const width = container.scrollWidth;
const height = (container.scrollHeight || 500) - 30;
const graph = new G6.Graph({
container: 'container',
width,
height,
fitCenter: true,
modes: {
default: ['lasso-select', 'drag-node', 'drag-canvas'],
dragLasso: [
const ExtGraph = extend(Graph, {
behaviors: {
'hover-activate': Extensions.HoverActivate,
'lasso-select': Extensions.LassoSelect,
},
});
const modes = {};
const ALLOWED_TRIGGERS = ['shift', 'alt', 'ctrl', 'drag', 'meta'];
ALLOWED_TRIGGERS.forEach((trigger) => {
const defaultBehavior = ['drag-node', 'hover-activate'];
if (trigger === 'shift') {
modes.default = [...defaultBehavior, 'lasso-select'];
} else {
modes[`${trigger}Select`] = [
...defaultBehavior,
{
type: 'lasso-select',
delegateStyle: {
fill: '#f00',
fillOpacity: 0.05,
stroke: '#f00',
lineWidth: 1,
},
onSelect: (nodes, edges) => {
console.log('onSelect', nodes, edges);
},
trigger: 'drag',
trigger: trigger,
},
'drag-node',
],
},
nodeStateStyles: {
selected: {
stroke: '#f00',
lineWidth: 3,
},
},
edgeStateStyles: {
selected: {
lineWidth: 3,
stroke: '#f00',
},
},
});
graph.data(data);
graph.render();
graph.on('nodeselectchange', (e) => {
console.log(e.selectedItems, e.select);
});
switchDiv.addEventListener('click', (e) => {
shift = !shift;
if (shift) {
graph.setMode('default');
switchDiv.innerHTML = `Press down the 'shift' on keyboard and drag to begin select. Click「HERE」to switch trigger to \'drag\', and custom lasso style, and disable drag-canvas
<br /> 按住 'shift' 可开始拉索选择点击这里切换 trigger 'drag'同时修改拉索样式和关闭画布拖拽`;
} else {
graph.setMode('dragLasso');
switchDiv.innerHTML = `Drag on the canvas to begin lasso select. Click「HERE」to switch trigger to \'shift\', and enable drag-canvas
<br /> 拖拽画布即可进行拉索选择点击这里切换 trigger 'drag'同时开启画布拖拽`;
];
}
});
const graph = new ExtGraph({
container,
width,
height,
autoFit: 'center',
modes,
data,
});
const switchDiv = document.createElement('div');
switchDiv.innerHTML = `👉 Trigger`;
switchDiv.style.position = 'absolute';
switchDiv.style.zIndex = 10;
container.appendChild(switchDiv);
const selector = document.createElement('select');
selector.id = 'selector';
ALLOWED_TRIGGERS.forEach((trigger, index) => {
const option = document.createElement('option');
option.value = trigger;
option.innerHTML = `${trigger}`;
selector.appendChild(option);
});
switchDiv.appendChild(selector);
// Listen to the selector, change the graph mode when the selector is changed
selector.addEventListener('change', (e) => {
const value = e.target.value;
const mode = value === 'shift' ? 'default' : `${value}Select`;
// change the graph mode
graph.setMode(mode);
});
if (typeof window !== 'undefined')
window.onresize = () => {
if (!graph || graph.get('destroyed')) return;
if (!graph || graph.destroyed) return;
if (!container || !container.scrollWidth || !container.scrollHeight) return;
graph.changeSize(container.scrollWidth, container.scrollHeight - 30);
graph.setSize([container.scrollWidth, container.scrollHeight]);
};

View File

@ -1,75 +1,19 @@
import G6 from '@antv/g6';
import { Graph, Extensions, extend } from '@antv/g6';
/**
* 该案例演示当点击叶子节点时如何动态向树图中添加多条数据
* 主要演示changeData的用法
*/
const ExtGraph = extend(Graph, {
edges: {
'cubic-horizontal-edge': Extensions.CubicHorizontalEdge,
'cubic-vertical-edge': Extensions.CubicVerticalEdge,
},
});
const container = document.getElementById('container');
const width = container.scrollWidth;
const height = container.scrollHeight || 500;
const graph = new G6.TreeGraph({
container: 'container',
width,
height,
modes: {
default: ['collapse-expand', 'drag-canvas'],
},
fitView: true,
layout: {
type: 'compactBox',
direction: 'LR',
defalutPosition: [],
getId: function getId(d) {
return d.id;
},
getHeight: function getHeight() {
return 16;
},
getWidth: function getWidth() {
return 16;
},
getVGap: function getVGap() {
return 50;
},
getHGap: function getHGap() {
return 100;
},
},
});
graph.node(function (node) {
return {
size: 16,
anchorPoints: [
[0, 0.5],
[1, 0.5],
],
style: {
fill: '#DEE9FF',
stroke: '#5B8FF9',
},
label: node.id,
labelCfg: {
position: node.children && node.children.length > 0 ? 'left' : 'right',
},
};
});
let i = 0;
graph.edge(function () {
i++;
return {
type: 'cubic-horizontal',
color: '#A3B1BF',
label: i,
};
});
const data = {
isRoot: true,
id: 'Root',
style: {
fill: 'red',
},
children: [
{
id: 'SubTreeNode1',
@ -127,59 +71,103 @@ const data = {
},
],
};
graph.data(data);
graph.render();
const graph = new ExtGraph({
container,
width,
height,
data,
modes: {
default: ['drag-canvas', 'zoom-canvas', 'drag-node'],
},
node: (model) => {
return {
id: model.id,
data: {
...model.data,
labelBackgroundShape: {},
labelShape: {
text: model.id,
position: model.data.childrenIds?.length ? 'left' : 'right',
offsetX: model.data.childrenIds?.length ? -10 : 10,
maxWidth: '300%',
},
anchorPoints: [
[0, 0.5],
[1, 0.5],
],
},
};
},
edge: {
type: 'cubic-horizontal-edge',
},
layout: {
type: 'compactBox',
direction: 'LR',
getHeight: function getHeight() {
return 32;
},
getWidth: function getWidth() {
return 32;
},
getVGap: function getVGap() {
return 10;
},
getHGap: function getHGap() {
return 100;
},
},
autoFit: 'view',
data: {
type: 'treeData',
value: data,
},
});
/** Click */
let count = 0;
graph.on('node:click', function (evt) {
const item = evt.item;
graph.on('node:click', (event) => {
const { itemId } = event;
const nodeId = item.get('id');
const model = item.getModel();
const children = model.children;
if (!children || children.length === 0) {
const childData = [
{
id: 'child-data-' + count,
type: 'rect',
children: [
{
id: 'x-' + count,
},
{
id: 'y-' + count,
},
],
},
{
id: 'child-data1-' + count,
children: [
{
id: 'x1-' + count,
},
{
id: 'y1-' + count,
},
],
},
];
const newNodes = [
{ id: `child-data1-${count}`, data: { type: 'rect-node' } },
{ id: `x1-${count}` },
{ id: `y1-${count}` },
{ id: `child-data2-${count}`, data: { type: 'rect-node' } },
{ id: `x2-${count}` },
{ id: `y2-${count}` },
];
const parentData = graph.findDataById(nodeId);
if (!parentData.children) {
parentData.children = [];
}
// 如果childData是一个数组则直接赋值给parentData.children
// 如果是一个对象则使用parentData.children.push(obj)
parentData.children = childData;
graph.changeData();
count++;
}
const newEdges = [
{ id: `edge1-${count}`, source: itemId, target: `child-data1-${count}` },
{ id: `edge2-${count}`, source: itemId, target: `child-data2-${count}` },
{ id: `edge3-${count}`, source: `child-data1-${count}`, target: `x1-${count}` },
{ id: `edge4-${count}`, source: `child-data1-${count}`, target: `y1-${count}` },
{ id: `edge5-${count}`, source: `child-data2-${count}`, target: `x2-${count}` },
{ id: `edge6-${count}`, source: `child-data2-${count}`, target: `y2-${count}` },
];
graph.addData('node', newNodes);
graph.addData('edge', newEdges);
graph.layout();
count++;
});
graph.on('canvas:click', (e) => graph.layout());
const tip = document.createElement('span');
tip.innerHTML = 'Click on the leaf node to dynamically add multiple pieces of data to the tree diagram.';
tip.style.position = 'absolute';
container.appendChild(tip);
if (typeof window !== 'undefined')
window.onresize = () => {
if (!graph || graph.get('destroyed')) return;
if (!graph || graph.destroyed) return;
if (!container || !container.scrollWidth || !container.scrollHeight) return;
graph.changeSize(container.scrollWidth, container.scrollHeight);
graph.setSize([container.scrollWidth, container.scrollHeight]);
};
// });

View File

@ -4,22 +4,6 @@
"en": "Category"
},
"demos": [
{
"filename": "dragSubtree.js",
"title": {
"zh": "拖拽子树改变结构",
"en": "Move Subtree to New Parent"
},
"screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*EU_yRa8hMJUAAAAAAAAAAABkARQnAQ"
},
{
"filename": "collapseSlibing.js",
"title": {
"zh": "合并同类兄弟节点",
"en": "Collapse the Sibling Nodes"
},
"screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*ugs9Qad-bQMAAAAAAAAAAAAAARQnAQ"
},
{
"filename": "changeData.js",
"title": {
@ -27,14 +11,6 @@
"en": "Using changeData"
},
"screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*wc1-QaqOPsgAAAAAAAAAAABkARQnAQ"
},
{
"filename": "loadTreeData.js",
"title": {
"zh": "使用 addChild",
"en": "Using addChild"
},
"screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*wc1-QaqOPsgAAAAAAAAAAABkARQnAQ"
}
]
}

View File

@ -1,220 +1,181 @@
import G6 from '@antv/g6';
/**
* The usage of arrows
* by Shiwu
*/
const data = {
nodes: [
{
id: '0',
x: 150,
y: 50,
},
{
id: '1',
x: 350,
y: 50,
},
{
id: '2',
x: 150,
y: 100,
},
{
id: '3',
x: 350,
y: 100,
},
{
id: '4',
x: 150,
y: 150,
},
{
id: '5',
x: 350,
y: 150,
},
{
id: '6',
x: 150,
y: 200,
},
{
id: '7',
x: 350,
y: 200,
},
{
id: '8',
x: 150,
y: 250,
},
{
id: '9',
x: 350,
y: 250,
},
{
id: '10',
x: 150,
y: 300,
},
{
id: '11',
x: 350,
y: 300,
},
{
id: '12',
x: 150,
y: 350,
},
{
id: '13',
x: 350,
y: 350,
},
{
id: '14',
x: 150,
y: 400,
},
{
id: '15',
x: 350,
y: 400,
},
],
edges: [
{
id: 'edge0',
source: '0',
target: '1',
label: 'default arrow',
style: {
endArrow: true,
},
},
{
id: 'edge1',
source: '2',
target: '3',
label: 'triangle arrow',
style: {
endArrow: {
path: G6.Arrow.triangle(),
},
},
},
{
id: 'edge2',
source: '4',
target: '5',
label: 'vee arrow',
style: {
endArrow: {
path: G6.Arrow.vee(),
},
},
},
{
id: 'edge3',
source: '6',
target: '7',
label: 'circle arrow',
style: {
endArrow: {
path: G6.Arrow.circle(5, 3),
d: 3,
},
},
},
{
id: 'edge4',
source: '8',
target: '9',
label: 'diamond arrow',
style: {
endArrow: {
path: G6.Arrow.diamond(10, 10, 3),
d: 3,
},
},
},
{
id: 'edge5',
source: '10',
target: '11',
label: 'rect arrow',
style: {
endArrow: {
path: G6.Arrow.rect(10, 10, 3),
d: 3,
},
},
},
{
id: 'edge6',
source: '12',
target: '13',
label: 'rect arrow 2',
style: {
endArrow: {
path: G6.Arrow.rect(10, 2, 5),
d: 5,
},
},
},
{
id: 'edge7',
source: '14',
target: '15',
label: 'triangleRect arrow',
style: {
endArrow: {
path: G6.Arrow.triangleRect(10, 10, 10, 2, 4),
},
},
},
],
};
import { Graph } from '@antv/g6';
const container = document.getElementById('container');
const width = container.scrollWidth;
const height = container.scrollHeight || 500;
data.edges.forEach((edge) => {
edge.style.stroke = '#F6BD16';
console.log(edge.style.endArrow);
if (edge.id !== 'edge0') edge.style.endArrow.fill = '#F6BD16';
});
const graph = new G6.Graph({
container: 'container',
const graph = new Graph({
container,
width,
height,
// translate the graph to align the canvas's center, support by v3.5.1
fitCenter: true,
defaultNode: {
size: 15,
style: {
fill: '#DEE9FF',
stroke: '#5B8FF9',
},
},
modes: {
// behaviors
default: ['drag-node'],
default: ['zoom-canvas', 'drag-canvas', 'drag-node', 'click-select'],
},
data: {
nodes: [
{
id: 'node1',
data: {
x: 80,
y: 100,
},
},
{
id: 'node2',
data: { x: 250, y: 100 },
},
{
id: 'node3',
data: {
x: 80,
y: 150,
},
},
{
id: 'node4',
data: { x: 250, y: 150 },
},
{
id: 'node5',
data: {
x: 80,
y: 200,
},
},
{
id: 'node6',
data: { x: 250, y: 200 },
},
{
id: 'node7',
data: {
x: 80,
y: 250,
},
},
{
id: 'node8',
data: { x: 250, y: 250 },
},
{
id: 'node9',
data: {
x: 80,
y: 300,
},
},
{
id: 'node10',
data: { x: 250, y: 300 },
},
{
id: 'node11',
data: {
x: 80,
y: 350,
},
},
{
id: 'node12',
data: { x: 250, y: 350 },
},
],
edges: [
{
id: 'edge1',
source: 'node1',
target: 'node2',
data: {
keyShape: {
endArrow: true,
},
labelShape: {
text: 'simple-arrow',
},
},
},
{
id: 'edge2',
source: 'node3',
target: 'node4',
data: {
keyShape: {
endArrow: {
type: 'vee',
},
},
labelShape: {
text: 'vee-arrow',
},
},
},
{
id: 'edge3',
source: 'node5',
target: 'node6',
data: {
keyShape: {
endArrow: {
type: 'circle',
},
},
labelShape: {
text: 'circle-arrow',
},
},
},
{
id: 'edge4',
source: 'node7',
target: 'node8',
data: {
keyShape: {
endArrow: {
type: 'rect',
},
},
labelShape: {
text: 'rect-arrow',
},
},
},
{
id: 'edge5',
source: 'node9',
target: 'node10',
data: {
keyShape: {
endArrow: {
type: 'diamond',
},
},
labelShape: {
text: 'diamond-arrow',
},
},
},
{
id: 'edge6',
source: 'node11',
target: 'node12',
data: {
keyShape: {
endArrow: {
type: 'triangle-rect',
},
},
labelShape: {
text: 'triangle-arrow',
},
},
},
],
},
});
graph.data(data);
graph.render();
if (typeof window !== 'undefined')
window.onresize = () => {
if (!graph || graph.get('destroyed')) return;
if (!graph || graph.destroyed) return;
if (!container || !container.scrollWidth || !container.scrollHeight) return;
graph.changeSize(container.scrollWidth, container.scrollHeight);
graph.setSize([container.scrollWidth, container.scrollHeight]);
};

View File

@ -1,92 +1,87 @@
import G6 from '@antv/g6';
/**
* The usage of arrows
* by Shiwu
*/
const data = {
nodes: [
{
id: '0',
x: 150,
y: 50,
},
{
id: '1',
x: 350,
y: 50,
},
{
id: '2',
x: 150,
y: 100,
},
{
id: '3',
x: 350,
y: 100,
},
],
edges: [
{
id: 'edge0',
source: '0',
target: '1',
label: 'custom arrow 1',
style: {
endArrow: {
path: 'M 3,-5 L 3,5 L 15,10 L 15,-10 Z',
},
},
},
{
id: 'edge1',
source: '2',
target: '3',
label: 'custom arrow 2',
style: {
endArrow: {
path: 'M0,0 L10,4 L14,14 L18,4 L28,0 L18,-4 L14,-14 L10,-4 Z',
},
},
},
],
};
import { Graph } from '@antv/g6';
const container = document.getElementById('container');
const width = container.scrollWidth;
const height = container.scrollHeight || 500;
data.edges.forEach((edge) => {
edge.style.stroke = '#F6BD16';
console.log(edge.style.endArrow);
edge.style.endArrow.fill = '#F6BD16';
});
const graph = new G6.Graph({
const graph = new Graph({
container: 'container',
width,
height,
// translate the graph to align the canvas's center, support by v3.5.1
fitCenter: true,
defaultNode: {
size: 15,
style: {
fill: '#DEE9FF',
stroke: '#5B8FF9',
},
},
modes: {
// behaviors
default: ['drag-node'],
default: ['zoom-canvas', 'drag-canvas', 'drag-node', 'click-select'],
},
data: {
nodes: [
{
id: 'node1',
data: {
x: 80,
y: 100,
},
},
{
id: 'node2',
data: { x: 300, y: 100 },
},
{
id: 'node3',
data: {
x: 80,
y: 150,
},
},
{
id: 'node4',
data: { x: 300, y: 150 },
},
],
edges: [
{
id: 'edge1',
source: 'node1',
target: 'node2',
data: {
keyShape: {
fill: '#F6BD16',
stroke: '#F6BD16',
endArrow: {
path: 'M0,0 L10,4 L14,14 L18,4 L28,0 L18,-4 L14,-14 L10,-4 Z',
fill: '#F6BD16',
stroke: '#F6BD16',
},
},
labelShape: {
text: 'custom arrow 1',
},
},
},
{
id: 'edge2',
source: 'node3',
target: 'node4',
data: {
keyShape: {
fill: '#F6BD16',
stroke: '#F6BD16',
endArrow: {
path: 'M 3,-5 L 3,5 L 15,10 L 15,-10 Z',
fill: '#F6BD16',
stroke: '#F6BD16',
},
},
labelShape: {
text: 'custom arrow 2',
},
},
},
],
},
});
graph.data(data);
graph.render();
if (typeof window !== 'undefined')
window.onresize = () => {
if (!graph || graph.get('destroyed')) return;
if (!graph || graph.destroyed) return;
if (!container || !container.scrollWidth || !container.scrollHeight) return;
graph.changeSize(container.scrollWidth, container.scrollHeight);
graph.setSize([container.scrollWidth, container.scrollHeight]);
};

View File

@ -1,146 +1,150 @@
import G6 from '@antv/g6';
import { Extensions, Graph, stdLib, extend } from '@antv/g6';
/**
* The demo shows customing a combo type by extending the built-in circle combo
* by Shiwu
*
*/
// The symbols for the marker inside the combo
const collapseIcon = (x, y, r) => {
return [
['M', x - r, y],
['a', r, r, 0, 1, 0, r * 2, 0],
['a', r, r, 0, 1, 0, -r * 2, 0],
['M', x - r + 4, y],
['L', x - r + 2 * r - 4, y],
];
};
const expandIcon = (x, y, r) => {
return [
['M', x - r, y],
['a', r, r, 0, 1, 0, r * 2, 0],
['a', r, r, 0, 1, 0, -r * 2, 0],
['M', x - r + 4, y],
['L', x - r + 2 * r - 4, y],
['M', x - r + r, y - r + 4],
['L', x, y + r - 4],
];
};
G6.registerCombo(
'cCircle',
{
drawShape: function draw(cfg, group) {
const self = this;
// Get the shape style, where the style.r corresponds to the R in the Illustration of Built-in Rect Combo
const style = self.getShapeStyle(cfg);
// Add a circle shape as keyShape which is the same as the extended 'circle' type Combo
const circle = group.addShape('circle', {
attrs: {
...style,
x: 0,
y: 0,
r: style.r,
},
draggable: true,
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
name: 'combo-keyShape',
});
// Add the marker on the bottom
const marker = group.addShape('marker', {
attrs: {
...style,
fill: '#fff',
opacity: 1,
x: 0,
y: style.r,
r: 10,
symbol: collapseIcon,
},
draggable: true,
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
name: 'combo-marker-shape',
});
return circle;
},
// Define the updating logic for the marker
afterUpdate: function afterUpdate(cfg, combo) {
const self = this;
// Get the shape style, where the style.r corresponds to the R in the Illustration of Built-in Rect Combo
const style = self.getShapeStyle(cfg);
const group = combo.get('group');
// Find the marker shape in the graphics group of the Combo
const marker = group.find((ele) => ele.get('name') === 'combo-marker-shape');
// Update the marker shape
marker.attr({
x: 0,
y: style.r,
// The property 'collapsed' in the combo data represents the collapsing state of the Combo
// Update the symbol according to 'collapsed'
symbol: cfg.collapsed ? expandIcon : collapseIcon,
});
},
},
'circle',
);
const data = {
nodes: [
{ id: 'node1', x: 250, y: 200, comboId: 'combo1' },
{ id: 'node2', x: 300, y: 200, comboId: 'combo1' },
{ id: 'node3', x: 100, y: 200, comboId: 'combo3' },
],
combos: [
{ id: 'combo1', label: 'Combo 1', parentId: 'combo2' },
{ id: 'combo2', label: 'Combo 2' },
{ id: 'combo3', label: 'Combo 3', collapsed: true },
],
};
const container = document.getElementById('container');
const width = container.scrollWidth;
const height = container.scrollHeight || 500;
const descriptionDiv = document.createElement('div');
descriptionDiv.innerHTML = 'Click the bottom marker to collapse/expand the combo.';
const container = document.getElementById('container');
container.appendChild(descriptionDiv);
const width = container.scrollWidth;
const height = (container.scrollHeight || 500) - 20;
const graph = new G6.Graph({
class CCircleCombo extends Extensions.CircleCombo {
drawOtherShapes(model, shapeMap, diffData) {
const { data } = model;
const keyShapeBBox = shapeMap.keyShape.getLocalBounds();
const otherShapes = {
markerShape: this.upsertShape(
'path',
'markerShape',
{
cursor: 'pointer',
stroke: '#666',
lineWidth: 1,
fill: '#fff',
path: data.collapsed
? stdLib.markers.expand(keyShapeBBox.center[0], keyShapeBBox.max[1], 8)
: stdLib.markers.collapse(keyShapeBBox.center[0], keyShapeBBox.max[1], 8),
},
shapeMap,
model,
),
};
return otherShapes;
}
}
const ExtGraph = extend(Graph, {
combos: {
'c-circle-combo': CCircleCombo,
},
behaviors: {
'hover-activate': Extensions.HoverActivate,
},
});
const graph = new ExtGraph({
container: 'container',
width,
height,
// Set groupByTypes to false to get rendering result with reasonable visual zIndex for combos
groupByTypes: false,
// Configure the combos globally
defaultCombo: {
// The type of the combos. You can also assign type in the data of combos
type: 'cCircle',
labelCfg: {
refY: 2,
stackCfg: {
ignoreStateChange: true,
},
node: {
labelShape: {
position: 'center',
text: {
fields: ['id'],
formatter: (model) => model.id,
},
},
// ... Other global configurations for combos
// animates: {
// update: [
// {
// fields: ['opacity'],
// shapeId: 'haloShape',
// },
// ],
// },
},
combo: (model) => {
return {
id: model.id,
data: {
type: 'c-circle-combo',
...model.data,
keyShape: {
padding: [10, 20, 30, 40],
r: 50,
},
labelShape: {
text: model.data.label,
position: 'top',
},
otherShapes: {},
},
};
},
data: {
nodes: [
{
id: 'node1',
data: {
x: 350,
y: 200,
parentId: 'combo1',
},
},
{ id: 'node2', data: { x: 350, y: 250, parentId: 'combo1' } },
{ id: 'node3', data: { x: 100, y: 200, parentId: 'combo3' } },
],
edges: [
{ id: 'edge1', source: 'node1', target: 'node2' },
{ id: 'edge2', source: 'node1', target: 'node3' },
{ id: 'edge3', source: 'combo1', target: 'node3' },
],
combos: [
{ id: 'combo1', data: { label: 'Combo 1', parentId: 'combo2' } },
{ id: 'combo2', data: { label: 'Combo 2' } },
{ id: 'combo3', data: { label: 'Combo 3', collapsed: true } },
],
},
modes: {
default: ['drag-combo', 'drag-node', 'drag-canvas'],
default: [
{
type: 'drag-node',
enableTransient: false,
updateComboStructure: false,
},
'drag-canvas',
{
type: 'click-select',
itemTypes: ['node', 'edge', 'combo'],
},
{
type: 'drag-combo',
enableTransient: true,
updateComboStructure: true,
},
],
},
});
graph.data(data);
graph.render();
// collapse/expand when click the marker
graph.on('combo:click', (e) => {
if (e.target.get('name') === 'combo-marker-shape') {
// graph.collapseExpandCombo(e.item.getModel().id);
graph.collapseExpandCombo(e.item);
if (graph.get('layout')) graph.layout();
else graph.refreshPositions();
/** Click the bottom marker to collapse/expand the combo. */
graph.on('combo:click', (event) => {
const { itemId, target } = event;
if (target.id === 'markerShape') {
const model = graph.getComboData(itemId);
if (model.data.collapsed) {
graph.expandCombo(itemId);
} else {
graph.collapseCombo(itemId);
}
}
});
if (typeof window !== 'undefined')
window.onresize = () => {
if (!graph || graph.get('destroyed')) return;
if (!graph || graph.destroyed) return;
if (!container || !container.scrollWidth || !container.scrollHeight) return;
graph.changeSize(container.scrollWidth, container.scrollHeight - 20);
graph.setSize([container.scrollWidth, container.scrollHeight]);
};

View File

@ -1,144 +1,174 @@
import G6 from '@antv/g6';
import { Extensions, Graph, stdLib, extend } from '@antv/g6';
/**
* The demo shows customing a combo type by extending the built-in circle combo
* by Shiwu
*
*/
// The symbols for the marker inside the combo
const collapseIcon = (x, y, r) => {
return [
['M', x - r, y],
['a', r, r, 0, 1, 0, r * 2, 0],
['a', r, r, 0, 1, 0, -r * 2, 0],
['M', x - r + 4, y],
['L', x - r + 2 * r - 4, y],
];
};
const expandIcon = (x, y, r) => {
return [
['M', x - r, y],
['a', r, r, 0, 1, 0, r * 2, 0],
['a', r, r, 0, 1, 0, -r * 2, 0],
['M', x - r + 4, y],
['L', x - r + 2 * r - 4, y],
['M', x - r + r, y - r + 4],
['L', x, y + r - 4],
];
};
G6.registerCombo(
'cRect',
{
drawShape: function drawShape(cfg, group) {
const self = this;
// Get the padding from the configuration
cfg.padding = cfg.padding || [50, 20, 20, 20];
// Get the shape's style, where the style.width and style.height correspond to the width and height in the figure of Illustration of Built-in Rect Combo
const style = self.getShapeStyle(cfg);
// Add a rect shape as the keyShape which is the same as the extended rect Combo
const rect = group.addShape('rect', {
attrs: {
...style,
x: -style.width / 2 - (cfg.padding[3] - cfg.padding[1]) / 2,
y: -style.height / 2 - (cfg.padding[0] - cfg.padding[2]) / 2,
width: style.width,
height: style.height,
},
draggable: true,
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
name: 'combo-keyShape',
});
// Add the circle on the right
group.addShape('marker', {
attrs: {
...style,
fill: '#fff',
opacity: 1,
// cfg.style.width and cfg.style.heigth correspond to the innerWidth and innerHeight in the figure of Illustration of Built-in Rect Combo
x: cfg.style.width / 2 + cfg.padding[1],
y: (cfg.padding[2] - cfg.padding[0]) / 2,
r: 10,
symbol: collapseIcon,
},
draggable: true,
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
name: 'combo-marker-shape',
});
return rect;
},
// Define the updating logic of the right circle
afterUpdate: function afterUpdate(cfg, combo) {
const group = combo.get('group');
// Find the circle shape in the graphics group of the Combo by name
const marker = group.find((ele) => ele.get('name') === 'combo-marker-shape');
// Update the position of the right circle
marker.attr({
// cfg.style.width and cfg.style.heigth correspond to the innerWidth and innerHeight in the figure of Illustration of Built-in Rect Combo
x: cfg.style.width / 2 + cfg.padding[1],
y: (cfg.padding[2] - cfg.padding[0]) / 2,
// The property 'collapsed' in the combo data represents the collapsing state of the Combo
// Update the symbol according to 'collapsed'
symbol: cfg.collapsed ? expandIcon : collapseIcon,
});
},
},
'rect',
);
const data = {
nodes: [
{ id: 'node1', x: 250, y: 200, comboId: 'combo1' },
{ id: 'node2', x: 300, y: 200, comboId: 'combo1' },
{ id: 'node3', x: 100, y: 200, comboId: 'combo3' },
],
combos: [
{ id: 'combo1', label: 'Combo 1', parentId: 'combo2' },
{ id: 'combo2', label: 'Combo 2' },
{ id: 'combo3', label: 'Combo 3', collapsed: true },
],
};
const container = document.getElementById('container');
const width = container.scrollWidth;
const height = container.scrollHeight || 500;
const descriptionDiv = document.createElement('div');
descriptionDiv.innerHTML = 'Click the right marker to collapse/expand the combo.';
const container = document.getElementById('container');
descriptionDiv.innerHTML = 'Click the bottom marker to collapse/expand the combo.';
container.appendChild(descriptionDiv);
const width = container.scrollWidth;
const height = (container.scrollHeight || 500) - 20;
const graph = new G6.Graph({
class CRectCombo extends Extensions.RectCombo {
drawOtherShapes(model, shapeMap, diffData) {
const { data } = model;
const keyShapeBBox = shapeMap.keyShape.getLocalBounds();
const otherShapes = {
markerShape: this.upsertShape(
'path',
'markerShape',
{
cursor: 'pointer',
stroke: '#666',
lineWidth: 1,
fill: '#fff',
path: data.collapsed
? stdLib.markers.expand(keyShapeBBox.center[0], keyShapeBBox.max[1], 8)
: stdLib.markers.collapse(keyShapeBBox.center[0], keyShapeBBox.max[1], 8),
},
shapeMap,
model,
),
};
return otherShapes;
}
}
const ExtGraph = extend(Graph, {
combos: {
'c-rect-combo': CRectCombo,
},
// behaviors: {
// 'hover-activate': Extensions.HoverActivate,
// },
});
const graph = new ExtGraph({
container: 'container',
width,
height,
// Set groupByTypes to false to get rendering result with reasonable visual zIndex for combos
groupByTypes: false,
// Configure the combos globally
defaultCombo: {
// The type of the combos. You can also assign type in the data of combos
type: 'cRect',
// ... Other global configurations for combos
stackCfg: {
ignoreStateChange: true,
},
node: {
labelShape: {
position: 'center',
text: {
fields: ['id'],
formatter: (model) => model.id,
},
},
animates: {
update: [
{
fields: ['opacity'],
shapeId: 'haloShape',
},
],
},
},
combo: (model) => {
return {
id: model.id,
data: {
type: 'c-rect-combo',
...model.data,
keyShape: {
padding: [10, 20, 30, 40],
r: 50,
},
labelShape: {
text: model.data.label,
position: 'top',
},
otherShapes: {},
animates: {
buildIn: [
{
fields: ['opacity'],
duration: 500,
delay: 500 + Math.random() * 500,
},
],
buildOut: [
{
fields: ['opacity'],
duration: 200,
},
],
update: [
{
fields: ['lineWidth', 'r'],
shapeId: 'keyShape',
},
{
fields: ['opacity'],
shapeId: 'haloShape',
},
],
},
},
};
},
data: {
nodes: [
{
id: 'node1',
data: {
x: 350,
y: 200,
parentId: 'combo1',
},
},
{ id: 'node2', data: { x: 350, y: 250, parentId: 'combo1' } },
{ id: 'node3', data: { x: 100, y: 200, parentId: 'combo3' } },
],
edges: [
{ id: 'edge1', source: 'node1', target: 'node2' },
{ id: 'edge2', source: 'node1', target: 'node3' },
],
combos: [
{ id: 'combo1', data: { label: 'Combo 1', parentId: 'combo2' } },
{ id: 'combo2', data: { label: 'Combo 2' } },
{ id: 'combo3', data: { label: 'Combo 3', collapsed: true } },
],
},
modes: {
default: ['drag-combo', 'drag-node', 'drag-canvas'],
default: [
{
type: 'drag-node',
enableTransient: false,
updateComboStructure: false,
},
'drag-canvas',
{
type: 'click-select',
itemTypes: ['node', 'edge', 'combo'],
},
{
type: 'drag-combo',
enableTransient: true,
updateComboStructure: true,
},
],
},
});
graph.data(data);
graph.render();
// collapse/expand when click the marker
graph.on('combo:click', (e) => {
if (e.target.get('name') === 'combo-marker-shape') {
// graph.collapseExpandCombo(e.item.getModel().id);
graph.collapseExpandCombo(e.item);
if (graph.get('layout')) graph.layout();
else graph.refreshPositions();
/** Click the bottom marker to collapse/expand the combo. */
graph.on('combo:click', (event) => {
const { itemId, target } = event;
if (target.id === 'markerShape') {
const model = graph.getComboData(itemId);
if (model.data.collapsed) {
graph.expandCombo(itemId);
} else {
graph.collapseCombo(itemId);
}
}
});
if (typeof window !== 'undefined')
window.onresize = () => {
if (!graph || graph.get('destroyed')) return;
if (!graph || graph.destroyed) return;
if (!container || !container.scrollWidth || !container.scrollHeight) return;
graph.changeSize(container.scrollWidth, container.scrollHeight - 20);
graph.setSize([container.scrollWidth, container.scrollHeight]);
};

View File

@ -1,136 +1,108 @@
import G6 from '@antv/g6';
/**
* Extend the built-in line edge and override getPath and getShapeStyle to custom a polyline edge
* by siogo's issuehttps://github.com/antvis/g6/issues/814
*
* If you want to fit the dragging, you need to adjust the controlpoints while dragging
*
*/
G6.registerEdge(
'line-arrow',
{
getPath(points) {
const startPoint = points[0];
const endPoint = points[1];
return [
['M', startPoint.x, startPoint.y],
['L', endPoint.x / 3 + (2 / 3) * startPoint.x, startPoint.y],
['L', endPoint.x / 3 + (2 / 3) * startPoint.x, endPoint.y],
['L', endPoint.x, endPoint.y],
];
},
getShapeStyle(cfg) {
const startPoint = cfg.startPoint;
const endPoint = cfg.endPoint;
const controlPoints = this.getControlPoints(cfg);
let points = [startPoint]; // the start point
// the control points
if (controlPoints) {
points = points.concat(controlPoints);
}
// the end point
points.push(endPoint);
const path = this.getPath(points);
const style = Object.assign(
{},
G6.Global.defaultEdge.style,
{
stroke: '#BBB',
lineWidth: 1,
path,
},
cfg.style,
);
return style;
},
},
'line',
);
const data = {
nodes: [
{
id: '7',
x: 150,
y: 100,
size: 40,
anchorPoints: [
[1, 0.5],
[1, 0],
],
},
{
id: '8',
x: 300,
y: 200,
size: 40,
anchorPoints: [
[0, 0.5],
[0, 1],
],
},
],
edges: [
{
source: '7',
target: '8',
sourceAnchor: 0,
targetAnchor: 0,
},
],
};
import { Graph, Extensions, extend } from '@antv/g6';
const container = document.getElementById('container');
const width = container.scrollWidth;
const height = 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,
modes: {
// behavior
default: ['drag-node', 'drag-canvas'],
},
defaultNode: {
type: 'circle',
style: {
fill: '#DEE9FF',
stroke: '#5B8FF9',
},
linkPoints: {
left: true,
right: true,
fill: '#fff',
stroke: '#1890FF',
size: 3,
},
},
defaultEdge: {
type: 'line-arrow',
style: {
stroke: '#F6BD16',
startArrow: {
path: 'M 0,0 L 12,6 L 9,0 L 12,-6 Z',
fill: '#F6BD16',
},
endArrow: {
path: 'M 0,0 L 12,6 L 9,0 L 12,-6 Z',
fill: '#F6BD16',
},
},
class CustomPolyline extends Extensions.LineEdge {
getPath(sourcePoint, targetPoint) {
return [
['M', sourcePoint.x, sourcePoint.y],
['L', targetPoint.x / 3 + (2 / 3) * sourcePoint.x, sourcePoint.y],
['L', targetPoint.x / 3 + (2 / 3) * sourcePoint.x, targetPoint.y],
['L', targetPoint.x, targetPoint.y],
];
}
drawKeyShape(model, sourcePoint, targetPoint, shapeMap, diffData) {
const { keyShape: keyShapeStyle } = this.mergedStyles;
const { startArrow, endArrow, ...others } = keyShapeStyle;
const lineStyle = {
...others,
path: this.getPath(sourcePoint, targetPoint),
isBillboard: true,
};
this.upsertArrow('start', startArrow, others, model, lineStyle);
this.upsertArrow('end', endArrow, others, model, lineStyle);
return this.upsertShape('path', 'keyShape', lineStyle, shapeMap, model);
}
}
const ExtGraph = extend(Graph, {
edges: {
'custom-polyline': CustomPolyline,
},
});
graph.data(data);
graph.render();
const graph = new ExtGraph({
container: 'container',
width,
height,
modes: {
default: ['click-select', 'drag-node'],
},
data: {
nodes: [
{
id: 'node1',
data: {
x: 150,
y: 100,
r: 40,
anchorPoints: [
[1, 0.5],
[1, 0],
],
},
},
{
id: 'node2',
data: {
x: 300,
y: 200,
r: 40,
anchorPoints: [
[0, 0.5],
[0, 1],
],
},
},
],
edges: [
{
id: 'edge1',
source: 'node1',
target: 'node2',
},
],
},
edge: (edgeInnerModel) => {
const { id, data } = edgeInnerModel;
return {
id,
data: {
...data,
type: 'custom-polyline',
keyShape: {
lineWidth: 1,
stroke: '#F6BD16',
startArrow: {
path: 'M 0,0 L 12,6 L 9,0 L 12,-6 Z',
fill: '#F6BD16',
},
endArrow: {
path: 'M 0,0 L 12,6 L 9,0 L 12,-6 Z',
fill: '#F6BD16',
},
},
},
};
},
});
if (typeof window !== 'undefined')
window.onresize = () => {
if (!graph || graph.get('destroyed')) return;
if (!graph || graph.destroyed) return;
if (!container || !container.scrollWidth || !container.scrollHeight) return;
graph.changeSize(container.scrollWidth, container.scrollHeight);
graph.setSize([container.scrollWidth, container.scrollHeight]);
};

View File

@ -1,128 +0,0 @@
import G6 from '@antv/g6';
/**
* Custom a new polyline
* by siogo's issuehttps://github.com/antvis/g6/issues/814
*
* If you want to fit the dragging, you need to adjust the controlpoints while dragging
*/
G6.registerEdge('line-arrow', {
options: {
style: {
stroke: '#ccc',
},
},
draw: function draw(cfg, group) {
const startPoint = cfg.startPoint;
const endPoint = cfg.endPoint;
const stroke = (cfg.style && cfg.style.stroke) || this.options.style.stroke;
const startArrow = (cfg.style && cfg.style.startArrow) || undefined;
const endArrow = (cfg.style && cfg.style.endArrow) || undefined;
const keyShape = group.addShape('path', {
attrs: {
path: [
['M', startPoint.x, startPoint.y],
['L', endPoint.x / 3 + (2 / 3) * startPoint.x, startPoint.y],
['L', endPoint.x / 3 + (2 / 3) * startPoint.x, endPoint.y],
['L', endPoint.x, endPoint.y],
],
stroke,
lineWidth: 1,
startArrow,
endArrow,
},
className: 'edge-shape',
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
name: 'edge-shape',
});
return keyShape;
},
});
const data = {
nodes: [
{
id: '7',
x: 150,
y: 100,
size: 40,
anchorPoints: [
[1, 0.5],
[1, 0],
],
},
{
id: '8',
x: 300,
y: 200,
size: 40,
anchorPoints: [
[0, 0.5],
[0, 1],
],
},
],
edges: [
{
source: '7',
target: '8',
sourceAnchor: 0,
targetAnchor: 0,
},
],
};
const container = document.getElementById('container');
const width = container.scrollWidth;
const height = 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,
modes: {
// behavior
default: ['drag-node', 'drag-canvas'],
},
defaultNode: {
type: 'circle',
style: {
fill: '#DEE9FF',
stroke: '#5B8FF9',
},
linkPoints: {
left: true,
right: true,
fill: '#fff',
stroke: '#1890FF',
size: 3,
},
},
defaultEdge: {
type: 'line-arrow',
style: {
stroke: '#F6BD16',
startArrow: {
path: 'M 0,0 L 12,6 L 9,0 L 12,-6 Z',
fill: '#F6BD16',
},
endArrow: {
path: 'M 0,0 L 12,6 L 9,0 L 12,-6 Z',
fill: '#F6BD16',
},
},
},
});
graph.data(data);
graph.render();
if (typeof window !== 'undefined')
window.onresize = () => {
if (!graph || graph.get('destroyed')) return;
if (!container || !container.scrollWidth || !container.scrollHeight) return;
graph.changeSize(container.scrollWidth, container.scrollHeight);
};

View File

@ -1,137 +1,95 @@
import G6 from '@antv/g6';
/**
* Custom the edge with multiple labels
* by Changzhe
*/
// custom the edge with multiple labels
G6.registerEdge('multipleLabelsEdge', {
options: {
style: {
stroke: '#000',
},
},
labelAutoRotate: true,
draw(cfg, group) {
const startPoint = cfg.startPoint;
const endPoint = cfg.endPoint;
const stroke = (cfg.style && cfg.style.stroke) || this.options.style.stroke;
const shape = group.addShape('path', {
attrs: {
stroke,
path: [
['M', startPoint.x, startPoint.y],
['L', endPoint.x, endPoint.y],
],
},
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
name: 'path-shape',
});
if (cfg.label && cfg.label.length) {
// the left label
group.addShape('text', {
attrs: {
text: cfg.label[0],
fill: '#595959',
textAlign: 'start',
textBaseline: 'middle',
x: startPoint.x,
y: startPoint.y - 10,
},
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
name: 'left-text-shape',
});
if (cfg.label.length > 1) {
// the right label
group.addShape('text', {
attrs: {
text: cfg.label[1],
fill: '#595959',
textAlign: 'end',
textBaseline: 'middle',
x: endPoint.x,
y: endPoint.y - 10,
},
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
name: 'right-text-shape',
});
}
}
// return the keyShape
return shape;
},
});
const data = {
nodes: [
{
id: 'node1',
x: 100,
y: 100,
label: 'node1',
},
{
id: 'node2',
x: 300,
y: 100,
label: 'node2',
},
],
edges: [
{
source: 'node1',
target: 'node2',
// The left and right labels
label: ['hello', 'world'],
},
],
};
import { Graph, Extensions, extend } from '@antv/g6';
const container = document.getElementById('container');
const width = container.scrollWidth;
const height = container.scrollHeight || 500;
const graph = new G6.Graph({
class MultipleLabelsEdge extends Extensions.LineEdge {
drawOtherShapes(model, shapeMap, diffData) {
const labels = model.data.labels;
// the left label
const startPoint = shapeMap.keyShape.getPoint(0);
const leftTextStyle = {
text: labels[0],
fill: '#595959',
fontSize: 12,
textAlign: 'start',
textBaseline: 'bottom',
x: startPoint.x,
y: startPoint.y,
};
shapeMap.leftTextShape = this.upsertShape('text', 'leftTextShape', leftTextStyle, shapeMap, model);
// the right label
const endPoint = shapeMap.keyShape.getPoint(1);
const rightTextStyle = {
text: labels[1],
fill: '#595959',
fontSize: 12,
textAlign: 'end',
textBaseline: 'bottom',
x: endPoint.x,
y: endPoint.y,
};
shapeMap.rightTextShape = this.upsertShape('text', 'rightTextShape', rightTextStyle, shapeMap, model);
return shapeMap;
}
}
const ExtGraph = extend(Graph, {
edges: {
'multiple-labels-edge': MultipleLabelsEdge,
},
});
const graph = new ExtGraph({
container: 'container',
width,
height,
// translate the graph to align the canvas's center, support by v3.5.1
fitCenter: true,
modes: {
default: [
default: ['click-select', 'drag-node'],
},
data: {
nodes: [
{
type: 'drag-node',
delegate: false,
id: 'node1',
data: {
x: 100,
y: 100,
},
},
'drag-canvas',
{
type: 'zoom-canvas',
sensitivity: 0.5,
id: 'node2',
data: {
x: 300,
y: 100,
},
},
],
edges: [
{
id: 'edge1',
source: 'node1',
target: 'node2',
data: {
// The left and right labels
labels: ['hello', 'world'],
},
},
],
},
defaultNode: {
type: 'circle',
size: [50],
linkPoints: {
left: true,
right: true,
},
},
defaultEdge: {
type: 'multipleLabelsEdge',
style: {
edge: {
type: 'multiple-labels-edge',
keyShape: {
stroke: '#F6BD16',
},
otherShapes: {},
},
});
graph.data(data);
graph.render();
if (typeof window !== 'undefined')
window.onresize = () => {
if (!graph || graph.get('destroyed')) return;
if (!graph || graph.destroyed) return;
if (!container || !container.scrollWidth || !container.scrollHeight) return;
graph.changeSize(container.scrollWidth, container.scrollHeight);
graph.setSize([container.scrollWidth, container.scrollHeight]);
};

View File

@ -1,115 +1,118 @@
import G6 from '@antv/g6';
/**
* Custom the edge with extra shapes
* by Shiwu
*/
// custom the edge with an extra rect
G6.registerEdge(
'extra-shape-edge',
{
afterDraw(cfg, group) {
// get the first shape in the graphics group of this edge, it is the path of the edge here
// 获取图形组中的第一个图形,在这里就是边的路径图形
const shape = group.get('children')[0];
// get the coordinate of the mid point on the path
// 获取路径图形的中点坐标
const midPoint = shape.getPoint(0.5);
const rectColor = cfg.midPointColor || '#333';
// add a rect on the mid point of the path. note that the origin of a rect shape is on its lefttop
// 在中点增加一个矩形,注意矩形的原点在其左上角
group.addShape('rect', {
attrs: {
width: 10,
height: 10,
fill: rectColor || '#333',
// x and y should be minus width / 2 and height / 2 respectively to translate the center of the rect to the midPoint
// x 和 y 分别减去 width / 2 与 height / 2使矩形中心在 midPoint 上
x: midPoint.x - 5,
y: midPoint.y - 5,
},
});
// get the coordinate of the quatile on the path
// 获取路径上的四分位点坐标
const quatile = shape.getPoint(0.25);
const quatileColor = cfg.quatileColor || '#333';
// add a circle on the quatile of the path
// 在四分位点上放置一个圆形
group.addShape('circle', {
attrs: {
r: 5,
fill: quatileColor || '#333',
x: quatile.x,
y: quatile.y,
},
});
},
update: undefined,
},
'cubic',
);
const data = {
nodes: [
{
id: 'node1',
x: 100,
y: 100,
},
{
id: 'node2',
x: 300,
y: 100,
},
{
id: 'node3',
x: 300,
y: 200,
},
],
edges: [
{
source: 'node1',
target: 'node2',
midPointColor: '#f00',
quatileColor: '#f00',
},
{
source: 'node1',
target: 'node3',
midPointColor: '#0f0',
quatileColor: '#0f0',
},
],
};
import { Graph, Extensions, extend } from '@antv/g6';
const container = document.getElementById('container');
const width = container.scrollWidth;
const height = container.scrollHeight || 500;
const graph = new G6.Graph({
class CustomEdge extends Extensions.CubicEdge {
afterDraw(model, shapeMap, shapesChanged) {
const data = model.data;
const { keyShape } = shapeMap;
const midPoint = keyShape.getPoint(0.5);
shapeMap.midShape = this.upsertShape(
'rect',
'midShape',
{
width: 10,
height: 10,
x: midPoint.x - 5,
y: midPoint.y - 5,
fill: data.midShapeColor,
...model.data?.otherShapes?.midShape, // merged style from mappers and states
},
shapeMap,
model,
);
// get the coordinate of the quatile on the path
// 获取路径上的四分位点坐标
const quatile = keyShape.getPoint(0.25);
// add a circle on the quatile of the path
// 在四分位点上放置一个圆形
shapeMap.quatileShape = this.upsertShape(
'circle',
'quatileShape',
{
r: 5,
cx: quatile.x,
cy: quatile.y,
zIndex: 1,
fill: data.quatileShapeColor,
...model.data?.otherShapes?.quatileShape, // merged style from mappers and states
},
shapeMap,
model,
);
return shapeMap;
}
}
const ExtGraph = extend(Graph, {
edges: {
'custom-edge': CustomEdge,
},
});
const graph = new ExtGraph({
container: 'container',
width,
height,
// translate the graph to align the canvas's center, support by v3.5.1
fitCenter: true,
modes: {
default: ['drag-node', 'drag-canvas'],
default: ['click-select'],
},
defaultEdge: {
type: 'extra-shape-edge',
style: {
stroke: '#F6BD16',
},
data: {
nodes: [
{
id: 'node1',
data: {
x: 100,
y: 100,
},
},
{
id: 'node2',
data: {
x: 300,
y: 100,
},
},
{
id: 'node3',
data: {
x: 300,
y: 200,
},
},
],
edges: [
{
id: 'edge1',
source: 'node1',
target: 'node2',
data: {
midShapeColor: '#f00',
quatileShapeColor: '#f00',
},
},
{
id: 'edge2',
source: 'node1',
target: 'node3',
data: {
midShapeColor: '#0f0',
quatileShapeColor: '#0f0',
},
},
],
},
edge: {
type: 'custom-edge',
otherShapes: {},
},
});
graph.data(data);
graph.render();
if (typeof window !== 'undefined')
window.onresize = () => {
if (!graph || graph.get('destroyed')) return;
if (!graph || graph.destroyed) return;
if (!container || !container.scrollWidth || !container.scrollHeight) return;
graph.changeSize(container.scrollWidth, container.scrollHeight);
graph.setSize([container.scrollWidth, container.scrollHeight]);
};

View File

@ -20,14 +20,6 @@
},
"screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*FuS3Qq_lDiIAAAAAAAAAAABkARQnAQ"
},
{
"filename": "customPolyline2.js",
"title": {
"zh": "自定义折线 2",
"en": "Custom Polyline 2"
},
"screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*ACNnQLPy20EAAAAAAAAAAABkARQnAQ"
},
{
"filename": "edgeMulLabel.js",
"title": {

View File

@ -1,213 +1,120 @@
import G6 from '@antv/g6';
/**
* Custom a area chart node
* by Jingxi
*
* Custom a pie chart node
*/
import { Graph, Extensions, extend, stdLib } from '@antv/g6';
/**
* Register a area chart node
*/
G6.registerNode('area', {
draw(cfg, group) {
const baseR = 30;
// Ref line
let refR = baseR;
const refInc = 10;
for (let i = 0; i < 6; i++) {
group.addShape('circle', {
attrs: {
x: 0,
y: 0,
r: (refR += refInc),
stroke: '#bae7ff',
lineDash: [4, 4],
},
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
name: 'circle-shape',
});
}
const everyIncAngle = (2 * Math.PI * (360 / 5)) / 360;
const tempIncValues = [baseR, baseR, baseR, baseR, baseR];
const allRs = [];
cfg.details.forEach((cat) => {
const oneRs = [];
cat.values.forEach((v, i) => {
const R = tempIncValues[i] + v * 0.4;
oneRs.push(R);
tempIncValues[i] = R;
});
allRs.push(oneRs);
});
const strokeColors = [
'rgba(91, 143, 249,1)',
'rgba(90, 216, 166,1)',
'rgba(246, 189, 22,1)',
'rgba(232, 104, 74,1)',
'rgba(255, 157, 77,1)',
];
const fillColors = [
'rgba(91, 143, 249,0.5)',
'rgba(90, 216, 166,0.5)',
'rgba(246, 189, 22,0.5)',
'rgba(232, 104, 74,0.5)',
'rgba(255, 157, 77,0.5)',
];
allRs.reverse().forEach((Rs, index) => {
let curAngle = 0;
const poss = [];
Rs.forEach((r) => {
const xPos = r * Math.cos(curAngle);
const yPos = r * Math.sin(curAngle);
curAngle += everyIncAngle;
poss.push([xPos, yPos]);
});
const Ls = poss.map((p, i) => {
if (i === 0) {
return ['M', ...p];
}
return ['L', ...p];
});
group.addShape('path', {
attrs: {
path: [
...Ls,
['Z'], // close the path
],
stroke: strokeColors[index],
fill: fillColors[index],
},
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
name: 'path-shape1',
});
});
let nowAngle2 = 0;
const everyIncAngleCat = (2 * Math.PI * (360 / 5)) / 360;
for (let i = 0; i < 5; i++) {
const r = 30 + 60;
const xPos = r * Math.cos(nowAngle2);
const yPos = r * Math.sin(nowAngle2);
group.addShape('path', {
attrs: {
path: [
['M', 0, 0],
['L', xPos, yPos],
],
lineDash: [4, 4],
stroke: 'darkgray',
},
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
name: 'path-shape2',
});
nowAngle2 += everyIncAngleCat;
}
// add a circle with the same filling color with background
group.addShape('circle', {
// attrs: style
attrs: {
x: 0, // 居中
y: 0,
r: baseR,
fill: cfg.centerColor,
stroke: 'darkgray',
},
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
name: 'circle-shape',
});
if (cfg.label) {
group.addShape('text', {
// attrs: style
attrs: {
x: 0,
y: 0,
textAlign: 'center',
textBaseline: 'middle',
text: cfg.label,
fill: 'white',
fontStyle: 'bold',
},
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
name: 'text-shape',
});
}
return group;
},
});
/** data */
const data = {
nodes: [
{
id: 'nodeD',
x: 150,
y: 150,
label: 'Area1',
type: 'area',
anchorPoints: [
[0, 0.5],
[1, 0.5],
],
details: [
{ cat: 'pv', values: [20, 30, 40, 30, 30], color: '#5ad8a6' },
{ cat: 'dal', values: [40, 30, 20, 30, 50], color: '#ff99c3' },
{ cat: 'uv', values: [40, 30, 30, 40, 40], color: '#6dc8ec' },
{ cat: 'sal', values: [20, 30, 50, 20, 20], color: '#269a99' },
{ cat: 'cal', values: [10, 10, 20, 20, 20], color: '#9270CA' },
],
centerColor: '#5b8ff9',
},
{
id: 'nodeD2',
x: 500,
y: 150,
label: 'Area2',
type: 'area',
anchorPoints: [
[0, 0.5],
[1, 0.5],
],
details: [
{ cat: 'pv', values: [10, 10, 80, 20, 10], color: '#5ad8a6' },
{ cat: 'dal', values: [20, 30, 10, 50, 40], color: '#ff99c3' },
{ cat: 'uv', values: [10, 50, 30, 20, 30], color: '#6dc8ec' },
{ cat: 'sal', values: [70, 30, 20, 20, 20], color: '#269a99' },
{ cat: 'cal', values: [50, 10, 20, 70, 30], color: '#9270CA' },
],
centerColor: '#5b8ff9',
},
],
edges: [
{
source: 'nodeD',
target: 'nodeD2',
},
],
};
const lightBlue = '#5b8ff9';
const lightOrange = '#5ad8a6';
const container = document.getElementById('container');
const width = container.scrollWidth;
const height = container.scrollHeight || 500;
const graph = new G6.Graph({
class PieNode extends Extensions.CircleNode {
drawOtherShapes(model, shapeMap) {
const { data: cfg } = model;
// node radius
const radius = cfg.size / 2;
// the ratio of indegree to outdegree
const inPercentage = cfg.inDegree / cfg.degree;
// the anble for the indegree fan
const inAngle = inPercentage * Math.PI * 2;
// the end position for the in-degree fan
const inArcEnd = [radius * Math.cos(inAngle), -radius * Math.sin(inAngle)];
let isInBigArc = 0,
isOutBigArc = 1;
if (inAngle > Math.PI) {
isInBigArc = 1;
isOutBigArc = 0;
}
const otherShapes = {
['in-fan-shape']: this.upsertShape(
'path',
'in-fan-shape',
{
path: [
['M', radius, 0],
['A', radius, radius, 0, isInBigArc, 0, inArcEnd[0], inArcEnd[1]],
['L', 0, 0],
['Z'],
],
lineWidth: 0,
fill: lightOrange,
},
shapeMap,
model,
),
['out-fan-shape']: this.upsertShape(
'path',
'out-fan-shape',
{
path: [
['M', inArcEnd[0], inArcEnd[1]],
['A', radius, radius, 0, isOutBigArc, 0, radius, 0],
['L', 0, 0],
['Z'],
],
lineWidth: 0,
fill: lightBlue,
},
shapeMap,
model,
),
};
return otherShapes;
}
}
const ExtGraph = extend(Graph, {
nodes: {
'pie-node': PieNode,
},
});
const graph = new ExtGraph({
container: 'container',
width,
height,
// translate the graph to align the canvas's center, support by v3.5.1
fitCenter: true,
});
autoFit: 'center',
modes: {
default: ['drag-node'],
},
data: {
nodes: [
{
id: 'node1',
data: {
size: 80,
inDegree: 80,
degree: 360,
x: 150,
y: 150,
},
},
{
id: 'node2',
data: {
size: 80,
inDegree: 280,
degree: 360,
x: 350,
y: 150,
},
},
],
edges: [{ id: 'edge1', source: 'node1', target: 'node2' }],
},
graph.data(data);
graph.render();
node: {
type: 'pie-node',
otherShapes: {},
},
});
if (typeof window !== 'undefined')
window.onresize = () => {
if (!graph || graph.get('destroyed')) return;
if (!graph || graph.destroyed) return;
if (!container || !container.scrollWidth || !container.scrollHeight) return;
graph.changeSize(container.scrollWidth, container.scrollHeight);
graph.setSize([container.scrollWidth, container.scrollHeight]);
};

View File

@ -1,8 +1,9 @@
import G6 from '@antv/g6';
import { Extensions, Graph, extend, stdLib } from '@antv/g6';
let graph;
const ERROR_COLOR = '#F5222D';
const getNodeConfig = (node) => {
if (node.nodeError) {
return {
@ -34,207 +35,68 @@ const getNodeConfig = (node) => {
return config;
};
const COLLAPSE_ICON = function COLLAPSE_ICON(x, y, r) {
return [
['M', x - r, y],
['a', r, r, 0, 1, 0, r * 2, 0],
['a', r, r, 0, 1, 0, -r * 2, 0],
['M', x - r + 4, y],
['L', x - r + 2 * r - 4, y],
];
};
const EXPAND_ICON = function EXPAND_ICON(x, y, r) {
return [
['M', x - r, y],
['a', r, r, 0, 1, 0, r * 2, 0],
['a', r, r, 0, 1, 0, -r * 2, 0],
['M', x - r + 4, y],
['L', x - r + 2 * r - 4, y],
['M', x - r + r, y - r + 4],
['L', x, y + r - 4],
];
};
const nodeBasicMethod = {
createNodeBox: (group, config, w, h, isRoot) => {
/* 最外面的大矩形 */
const container = group.addShape('rect', {
attrs: {
x: 0,
y: 0,
width: w,
height: h,
},
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
name: 'big-rect-shape',
});
class CardNode extends Extensions.RectNode {
drawOtherShapes(model, shapeMap) {
const { data: cfg } = model;
const config = getNodeConfig(cfg);
const isRoot = cfg.dataType === 'root';
const nodeError = cfg.nodeError;
const otherShapes = {};
if (!isRoot) {
/* 左边的小圆点 */
group.addShape('circle', {
attrs: {
x: 3,
y: h / 2,
r: 6,
fill: config.basicColor,
},
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
name: 'left-dot-shape',
});
otherShapes['left-dot-shape'] = this.upsertShape(
'circle',
'left-dot-shape',
{ cx: 3, cy: 32, r: 6, fill: config.basicColor },
shapeMap,
model,
);
}
/* 矩形 */
group.addShape('rect', {
attrs: {
/** rect shape */
otherShapes['rect-shape'] = this.upsertShape(
'rect',
'rect-shape',
{
x: 3,
y: 0,
width: w - 19,
height: h,
width: 243 - 19,
height: 64,
fill: config.bgColor,
stroke: config.borderColor,
radius: 2,
cursor: 'pointer',
zIndex: 0,
},
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
name: 'rect-shape',
});
shapeMap,
model,
);
/* 左边的粗线 */
group.addShape('rect', {
attrs: {
/* Left bolder border */
otherShapes['left-border-shape'] = this.upsertShape(
'rect',
'left-border-shape',
{
x: 3,
y: 0,
width: 3,
height: h,
height: 64,
fill: config.basicColor,
radius: 1.5,
zIndex: 0,
},
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
name: 'left-border-shape',
});
return container;
},
/* 生成树上的 marker */
createNodeMarker: (group, collapsed, x, y) => {
group.addShape('circle', {
attrs: {
x,
y,
r: 13,
fill: 'rgba(47, 84, 235, 0.05)',
opacity: 0,
zIndex: -2,
},
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
name: 'collapse-icon-bg',
});
group.addShape('marker', {
attrs: {
x,
y,
r: 7,
symbol: collapsed ? EXPAND_ICON : COLLAPSE_ICON,
stroke: 'rgba(0,0,0,0.25)',
fill: 'rgba(0,0,0,0)',
lineWidth: 1,
cursor: 'pointer',
},
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
name: 'collapse-icon',
});
},
afterDraw: (cfg, group) => {
/* 操作 marker 的背景色显示隐藏 */
const icon = group.find((element) => element.get('name') === 'collapse-icon');
if (icon) {
const bg = group.find((element) => element.get('name') === 'collapse-icon-bg');
icon.on('mouseenter', () => {
bg.attr('opacity', 1);
graph.get('canvas').draw();
});
icon.on('mouseleave', () => {
bg.attr('opacity', 0);
graph.get('canvas').draw();
});
}
/* ip 显示 */
const ipBox = group.find((element) => element.get('name') === 'ip-box');
if (ipBox) {
/* ip 复制的几个元素 */
const ipLine = group.find((element) => element.get('name') === 'ip-cp-line');
const ipBG = group.find((element) => element.get('name') === 'ip-cp-bg');
const ipIcon = group.find((element) => element.get('name') === 'ip-cp-icon');
const ipCPBox = group.find((element) => element.get('name') === 'ip-cp-box');
const onMouseEnter = () => {
ipLine.attr('opacity', 1);
ipBG.attr('opacity', 1);
ipIcon.attr('opacity', 1);
graph.get('canvas').draw();
};
const onMouseLeave = () => {
ipLine.attr('opacity', 0);
ipBG.attr('opacity', 0);
ipIcon.attr('opacity', 0);
graph.get('canvas').draw();
};
ipBox.on('mouseenter', () => {
onMouseEnter();
});
ipBox.on('mouseleave', () => {
onMouseLeave();
});
ipCPBox.on('mouseenter', () => {
onMouseEnter();
});
ipCPBox.on('mouseleave', () => {
onMouseLeave();
});
ipCPBox.on('click', () => { });
}
},
setState: (name, value, item) => {
const hasOpacityClass = [
'ip-cp-line',
'ip-cp-bg',
'ip-cp-icon',
'ip-cp-box',
'ip-box',
'collapse-icon-bg',
];
const group = item.getContainer();
const childrens = group.get('children');
graph.setAutoPaint(false);
if (name === 'emptiness') {
if (value) {
childrens.forEach((shape) => {
if (hasOpacityClass.indexOf(shape.get('name')) > -1) {
return;
}
shape.attr('opacity', 0.4);
});
} else {
childrens.forEach((shape) => {
if (hasOpacityClass.indexOf(shape.get('name')) > -1) {
return;
}
shape.attr('opacity', 1);
});
}
}
graph.setAutoPaint(true);
},
};
G6.registerNode('card-node', {
draw: (cfg, group) => {
const config = getNodeConfig(cfg);
const isRoot = cfg.dataType === 'root';
const nodeError = cfg.nodeError;
/* the biggest rect */
const container = nodeBasicMethod.createNodeBox(group, config, 243, 64, isRoot);
shapeMap,
model,
);
if (cfg.dataType !== 'root') {
/* the type text */
group.addShape('text', {
attrs: {
otherShapes['type-text-shape'] = this.upsertShape(
'text',
'type-text-shape',
{
text: cfg.dataType,
x: 3,
y: -10,
@ -243,28 +105,32 @@ G6.registerNode('card-node', {
textBaseline: 'middle',
fill: 'rgba(0,0,0,0.65)',
},
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
name: 'type-text-shape',
});
shapeMap,
model,
);
}
if (cfg.ip) {
/* ip start */
/* ipBox */
const ipRect = group.addShape('rect', {
attrs: {
fill: nodeError ? null : '#FFF',
stroke: nodeError ? 'rgba(255,255,255,0.65)' : null,
otherShapes['ip-container-shape'] = this.upsertShape(
'rect',
'ip-container-shape',
{
fill: nodeError ? 'rgba(0,0,0,0)' : '#fff',
stroke: nodeError ? 'rgba(255,255,255,0.65)' : 'rgba(0,0,0,0)',
radius: 2,
cursor: 'pointer',
},
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
name: 'ip-container-shape',
});
shapeMap,
model,
);
/* ip */
const ipText = group.addShape('text', {
attrs: {
otherShapes['ip-text-shape'] = this.upsertShape(
'text',
'ip-text-shape',
{
text: cfg.ip,
x: 0,
y: 19,
@ -274,42 +140,48 @@ G6.registerNode('card-node', {
fill: nodeError ? 'rgba(255,255,255,0.85)' : 'rgba(0,0,0,0.65)',
cursor: 'pointer',
},
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
name: 'ip-text-shape',
});
shapeMap,
model,
);
const ipBBox = ipText.getBBox();
const ipBBox = otherShapes['ip-text-shape'].getBBox();
console.log('ipBBox', ipBBox);
/* the distance from the IP to the right is 12px */
ipText.attr({
otherShapes['ip-text-shape'].attr({
x: 224 - 12 - ipBBox.width,
});
/* ipBox */
ipRect.attr({
otherShapes['ip-container-shape'].attr({
x: 224 - 12 - ipBBox.width - 4,
y: ipBBox.minY - 5,
y: ipBBox.y - 5,
width: ipBBox.width + 8,
height: ipBBox.height + 10,
cursor: 'pointer',
});
/* a transparent shape on the IP for click listener */
group.addShape('rect', {
attrs: {
otherShapes['ip-box'] = this.upsertShape(
'rect',
'ip-box',
{
stroke: '',
cursor: 'pointer',
x: 224 - 12 - ipBBox.width - 4,
y: ipBBox.minY - 5,
y: ipBBox.y - 5,
width: ipBBox.width + 8,
height: ipBBox.height + 10,
fill: '#fff',
opacity: 0,
},
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
name: 'ip-box',
});
shapeMap,
model,
);
/* copyIpLine */
group.addShape('rect', {
attrs: {
otherShapes['ip-cp-line'] = this.upsertShape(
'rect',
'ip-cp-line',
{
x: 194,
y: 7,
width: 1,
@ -317,59 +189,60 @@ G6.registerNode('card-node', {
fill: '#E3E6E8',
opacity: 0,
},
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
name: 'ip-cp-line',
});
shapeMap,
model,
);
/* copyIpBG */
group.addShape('rect', {
attrs: {
otherShapes['ip-cp-bg'] = this.upsertShape(
'rect',
'ip-cp-bg',
{
x: 195,
y: 8,
width: 22,
height: 22,
fill: '#FFF',
cursor: 'pointer',
opacity: 0,
opacity: cfg.showIcon ? 1 : 0,
},
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
name: 'ip-cp-bg',
});
shapeMap,
model,
);
/* copyIpIcon */
group.addShape('image', {
attrs: {
otherShapes['ip-cp-icon'] = this.upsertShape(
'image',
'ip-cp-icon',
{
x: 200,
y: 13,
height: 12,
width: 10,
width: 12,
img: 'https://os.alipayobjects.com/rmsportal/DFhnQEhHyPjSGYW.png',
cursor: 'pointer',
opacity: 0,
opacity: cfg.showIcon ? 1 : 0,
},
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
name: 'ip-cp-icon',
});
/* a transparent rect on the icon area for click listener */
group.addShape('rect', {
attrs: {
x: 195,
y: 8,
width: 22,
height: 22,
fill: '#FFF',
cursor: 'pointer',
opacity: 0,
},
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
name: 'ip-cp-box',
tooltip: 'Copy the IP',
});
shapeMap,
model,
);
/* a transparent rect on the icon area for click listener */
otherShapes['ip-cp-box'] = this.upsertShape(
'rect',
'ip-cp-box',
{ x: 195, y: 8, width: 22, height: 22, fill: '#FFF', cursor: 'pointer', opacity: 0 },
shapeMap,
model,
);
/* ip end */
}
/* name */
group.addShape('text', {
attrs: {
otherShapes['name-text-shape'] = this.upsertShape(
'text',
'name-text-shape',
{
text: cfg.name,
x: 19,
y: 19,
@ -380,13 +253,15 @@ G6.registerNode('card-node', {
fill: config.fontColor,
cursor: 'pointer',
},
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
name: 'name-text-shape',
});
shapeMap,
model,
);
/* the description text */
group.addShape('text', {
attrs: {
otherShapes['bottom-text-shape'] = this.upsertShape(
'text',
'bottom-text-shape',
{
text: cfg.keyInfo,
x: 19,
y: 45,
@ -396,95 +271,152 @@ G6.registerNode('card-node', {
fill: config.fontColor,
cursor: 'pointer',
},
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
name: 'bottom-text-shape',
});
shapeMap,
model,
);
if (nodeError) {
group.addShape('text', {
attrs: {
otherShapes['error-text-shape'] = this.upsertShape(
'text',
'error-text-shape',
{
x: 191,
y: 62,
text: '⚠️',
fill: '#000',
fontSize: 18,
},
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
name: 'error-text-shape',
});
shapeMap,
model,
);
}
const hasChildren = cfg.children && cfg.children.length > 0;
const keyShapeBBox = shapeMap.keyShape.getLocalBounds();
if (hasChildren) {
nodeBasicMethod.createNodeMarker(group, cfg.collapsed, 236, 32);
otherShapes['marker-shape'] = this.upsertShape(
'path',
'markerShape',
{
zIndex: 10,
cursor: 'pointer',
stroke: '#666',
lineWidth: 1,
fill: '#fff',
path: stdLib.markers.expand(keyShapeBBox.max[0] - 15, keyShapeBBox.center[1], 8),
},
shapeMap,
model,
);
}
return container;
return otherShapes;
}
}
const ExtGraph = extend(Graph, {
nodes: {
'card-node': CardNode,
},
afterDraw: nodeBasicMethod.afterDraw,
setState: nodeBasicMethod.setState,
});
const container = document.getElementById('container');
const width = container.scrollWidth;
const height = container.scrollHeight || 500;
graph = new G6.Graph({
container: 'container',
width,
height,
// translate the graph to align the canvas's center, support by v3.5.1
fitCenter: true,
modes: {
default: ['drag-node'],
},
defaultNode: {
type: 'card-node',
},
});
const data = {
nodes: [
{
name: 'cardNodeApp',
ip: '127.0.0.1',
nodeError: true,
dataType: 'root',
keyInfo: 'this is a card node info',
x: 100,
y: 50,
id: 'node1',
data: {
showIcon: false,
name: 'cardNodeApp',
ip: '127.0.0.1',
nodeError: true,
dataType: 'root',
keyInfo: 'this is a card node info',
x: 100,
y: 50,
},
},
{
name: 'cardNodeApp',
ip: '127.0.0.1',
nodeError: false,
dataType: 'subRoot',
keyInfo: 'this is sub root',
x: 100,
y: 150,
id: 'node2',
data: {
showIcon: false,
name: 'cardNodeApp',
ip: '127.0.0.1',
nodeError: false,
dataType: 'subRoot',
keyInfo: 'this is sub root',
x: 100,
y: 150,
},
},
{
name: 'cardNodeApp',
ip: '127.0.0.1',
nodeError: false,
dataType: 'subRoot',
keyInfo: 'this is sub root',
x: 100,
y: 250,
children: [
{
name: 'sub',
},
],
id: 'node3',
data: {
showIcon: false,
name: 'cardNodeApp',
ip: '127.0.0.1',
nodeError: false,
dataType: 'subRoot',
keyInfo: 'this is sub root',
x: 100,
y: 250,
children: [
{
name: 'sub',
},
],
},
},
],
edges: [],
};
graph.data(data);
graph.render();
graph = new ExtGraph({
container,
width,
height,
autoFit: 'center',
data,
node: {
type: 'card-node',
keyShape: {
x: 243 / 2,
y: 32,
width: 243,
height: 64,
fill: 'transport',
},
otherShapes: {},
},
});
graph.on('node:pointermove', (event) => {
const { itemId, target } = event;
if (target.id === 'ip-container-shape') {
graph.updateData('node', {
id: itemId,
data: {
showIcon: true,
},
});
}
});
graph.on('node:pointerleave', (event) => {
const { itemId } = event;
graph.updateData('node', {
id: itemId,
data: {
showIcon: false,
},
});
});
if (typeof window !== 'undefined')
window.onresize = () => {
if (!graph || graph.get('destroyed')) return;
if (!graph || graph.destroyed) return;
if (!container || !container.scrollWidth || !container.scrollHeight) return;
graph.changeSize(container.scrollWidth, container.scrollHeight);
graph.setSize([container.scrollWidth, container.scrollHeight]);
};

View File

@ -1,196 +1,216 @@
import G6 from '@antv/g6';
import { Graph, Extensions, extend, stdLib } from '@antv/g6';
const container = document.getElementById('container');
const width = container.scrollWidth;
const height = container.scrollHeight || 500;
const ICON_MAP = {
a: 'https://gw.alipayobjects.com/mdn/rms_8fd2eb/afts/img/A*0HC-SawWYUoAAAAAAAAAAABkARQnAQ',
b: 'https://gw.alipayobjects.com/mdn/rms_8fd2eb/afts/img/A*sxK0RJ1UhNkAAAAAAAAAAABkARQnAQ',
};
G6.registerNode(
'card-node',
{
drawShape: function drawShape(cfg, group) {
const color = cfg.error ? '#F4664A' : '#30BF78';
const r = 2;
const shape = group.addShape('rect', {
attrs: {
x: 0,
y: 0,
width: 200,
height: 60,
stroke: color,
radius: r,
},
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
name: 'main-box',
draggable: true,
});
class CardNode extends Extensions.RectNode {
drawOtherShapes(model, shapeMap, diffData, diffState) {
const keyShapeBBox = shapeMap.keyShape.getLocalBounds();
const x = -keyShapeBBox.halfExtents[0],
y = -keyShapeBBox.halfExtents[1];
const { data } = model;
group.addShape('rect', {
attrs: {
x: 0,
y: 0,
width: 200,
height: 20,
fill: color,
radius: [r, r, 0, 0],
},
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
name: 'title-box',
draggable: true,
});
const color = data.error ? '#F4664A' : '#30BF78';
const r = 2;
const otherShapes = {
// title box
titleBox: this.upsertShape(
'rect',
'titleBox',
{ x: 0, y: 0, width: 200, height: 20, fill: color, radius: [r, r, 0, 0] },
shapeMap,
model,
),
// left icon
group.addShape('image', {
attrs: {
nodeIcon: this.upsertShape(
'image',
'nodeIcon',
{
x: 4,
y: 2,
height: 16,
width: 16,
cursor: 'pointer',
img: ICON_MAP[cfg.nodeType || 'app'],
img: ICON_MAP[data.nodeType],
},
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
name: 'node-icon',
});
shapeMap,
model,
),
// title text
group.addShape('text', {
attrs: {
title: this.upsertShape(
'text',
'title',
{
textBaseline: 'top',
y: 2,
x: 24,
lineHeight: 20,
text: cfg.title,
text: data.title,
fill: '#fff',
fontSize: 12,
},
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
name: 'title',
});
shapeMap,
model,
),
};
if (cfg.nodeLevel > 0) {
group.addShape('marker', {
attrs: {
x: 184,
y: 30,
r: 6,
cursor: 'pointer',
symbol: cfg.collapse ? G6.Marker.expand : G6.Marker.collapse,
stroke: '#666',
lineWidth: 1,
},
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
name: 'collapse-icon',
});
}
if (data.nodeLevel > 0) {
otherShapes.markerShape = this.upsertShape(
'path',
'markerShape',
{
cursor: 'pointer',
stroke: '#666',
lineWidth: 1,
fill: '#fff',
path: data.collapsed
? stdLib.markers.expand(keyShapeBBox.max[0], keyShapeBBox.center[1], 6)
: stdLib.markers.collapse(keyShapeBBox.max[0], keyShapeBBox.center[1], 6),
},
shapeMap,
);
}
// The content list
cfg.panels.forEach((item, index) => {
// name text
group.addShape('text', {
attrs: {
textBaseline: 'top',
y: 25,
x: 24 + index * 60,
lineHeight: 20,
text: item.title,
fill: 'rgba(0,0,0, 0.4)',
},
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
name: `index-title-${index}`,
});
// content list
data.panels.forEach((panel, index) => {
otherShapes[`panel-title-${index}`] = this.upsertShape(
'text',
`panel-title-${index}`,
{
textBaseline: 'top',
y: 25,
x: 24 + index * 60,
lineHeight: 20,
text: panel.title,
fill: 'rgba(0,0,0,0.4)',
fontSize: 12,
},
shapeMap,
model,
);
otherShapes[`panel-value-${index}`] = this.upsertShape(
'text',
`panel-value-${index}`,
{
textBaseline: 'top',
y: 42,
x: 24 + index * 60,
lineHeight: 20,
text: panel.value,
fill: '#595959',
fontSize: 12,
},
shapeMap,
model,
);
});
// value text
group.addShape('text', {
attrs: {
textBaseline: 'top',
y: 42,
x: 24 + index * 60,
lineHeight: 20,
text: item.value,
fill: '#595959',
},
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
name: `index-title-${index}`,
});
});
return shape;
},
return otherShapes;
}
}
const ExtGraph = extend(Graph, {
nodes: {
'card-node': CardNode,
},
'single-node',
);
});
const container = document.getElementById('container');
const width = container.scrollWidth;
const height = container.scrollHeight || 500;
const graph = new G6.Graph({
const graph = new ExtGraph({
container: 'container',
width,
height,
// translate the graph to align the canvas's center, support by v3.5.1
fitCenter: true,
autoFit: 'center',
modes: {
default: ['drag-canvas', 'drag-node'],
default: ['drag-node'],
},
defaultNode: {
type: 'card-node',
data: {
nodes: [
{
id: 'node1',
data: {
x: 100,
y: 100,
nodeType: 'a',
title: 'node1',
error: true,
nodeLevel: 2,
panels: [
{ title: '成功率', value: '11%' },
{ title: '耗时', value: '111' },
{ title: '错误数', value: '111' },
],
},
},
{
id: 'node2',
data: {
x: 100,
y: 200,
nodeType: 'b',
title: 'node2',
error: false,
nodeLevel: 0,
panels: [
{ title: '成功率', value: '11%' },
{ title: '耗时', value: '111' },
{ title: '错误数', value: '111' },
],
},
},
{
id: 'node3',
data: {
x: 100,
y: 300,
title: 'node3',
error: false,
nodeType: 'a',
nodeLevel: 3,
panels: [
{ title: '成功率', value: '11%' },
{ title: '耗时', value: '111' },
{ title: '错误数', value: '111' },
],
collapse: true,
},
},
],
},
node: (nodeInnerModel) => {
const { id, data } = nodeInnerModel;
const color = data.error ? '#F4664A' : '#30BF78';
const r = 2;
return {
id,
data: {
...data,
type: 'card-node',
keyShape: {
x: 100,
y: 30,
width: 200,
height: 60,
fill: '#fff',
lineWidth: 1,
stroke: color,
radius: r,
},
otherShapes: {},
},
};
},
fitView: true,
});
const data = {
nodes: [
{
title: 'node1',
error: true,
nodeType: 'a',
id: 'node1',
nodeLevel: 2,
panels: [
{ title: '成功率', value: '11%' },
{ title: '耗时', value: '111' },
{ title: '错误数', value: '111' },
],
x: 100,
y: 100,
},
{
title: 'node2',
error: false,
nodeType: 'b',
id: 'node2',
nodeLevel: 0,
panels: [
{ title: '成功率', value: '11%' },
{ title: '耗时', value: '111' },
{ title: '错误数', value: '111' },
],
x: 100,
y: 200,
},
{
title: 'node3',
error: false,
nodeType: 'a',
id: 'node3',
nodeLevel: 3,
panels: [
{ title: '成功率', value: '11%' },
{ title: '耗时', value: '111' },
{ title: '错误数', value: '111' },
],
collapse: true,
x: 100,
y: 300,
},
],
};
graph.data(data);
graph.render();
if (typeof window !== 'undefined')
window.onresize = () => {
if (!graph || graph.get('destroyed')) return;
if (!graph || graph.destroyed) return;
if (!container || !container.scrollWidth || !container.scrollHeight) return;
graph.changeSize(container.scrollWidth, container.scrollHeight);
graph.setSize([container.scrollWidth, container.scrollHeight]);
};

View File

@ -20,102 +20,6 @@
},
"screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*b-g0RoOpI3sAAAAAAAAAAABkARQnAQ"
},
{
"filename": "jsxNode.js",
"title": {
"zh": "使用 JSX 自定义节点",
"en": "Custom Node with JSX"
},
"screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*NcHWTKo3sEoAAAAAAAAAAAAAARQnAQ"
},
{
"filename": "jsxNodeWithAnimate.js",
"title": {
"zh": "使用 JSX 自定义节点(带动画)",
"en": "Custom Node with JSX and animation"
},
"screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*XfMbSZSrlREAAAAAAAAAAAAAARQnAQ"
},
{
"filename": "lineChartNode.js",
"title": {
"zh": "节点中嵌入 G2 的折线图",
"en": "G2 Line Node"
},
"screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*ZmavSL0dRJIAAAAAAAAAAAAAARQnAQ"
},
{
"filename": "intervalChartNode.js",
"title": {
"zh": "节点中嵌入 G2 的柱状图,点击节点可切换数据",
"en": "G2 Interval Node"
},
"screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*D00uR5wb__kAAAAAAAAAAABkARQnAQ"
},
{
"filename": "pointChartNode.js",
"title": {
"zh": "节点中嵌入 G2 的散点图",
"en": "G2 Point Node"
},
"screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*K7r7S7x-YhcAAAAAAAAAAAAAARQnAQ"
},
{
"filename": "pieChartNode.js",
"title": {
"zh": "节点中嵌入 G2 的饼图",
"en": "G2 Pie Node"
},
"screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*wCxXSK4eWPoAAAAAAAAAAAAAARQnAQ"
},
{
"filename": "svgDom.js",
"title": {
"zh": "DOM 节点",
"en": "Dom Node"
},
"screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*VgQlQK1MdbIAAAAAAAAAAABkARQnAQ"
},
{
"filename": "areaChart.js",
"title": {
"zh": "使用 G 自定义的面积图节点",
"en": "Area Chart Node"
},
"screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*YWuhR5ZjZfkAAAAAAAAAAABkARQnAQ"
},
{
"filename": "stackChart.js",
"title": {
"zh": "使用 G 自定义的堆叠图节点",
"en": "Stacked Chart Node"
},
"screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*giZSRafSctgAAAAAAAAAAABkARQnAQ"
},
{
"filename": "lineChart.js",
"title": {
"zh": "使用 G 自定义的折线图节点",
"en": "Line Chart Node"
},
"screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*mKmaTIBtVAUAAAAAAAAAAABkARQnAQ"
},
{
"filename": "barChart.js",
"title": {
"zh": "使用 G 自定义的南丁格尔图节点",
"en": "Nightingale Node"
},
"screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*lX3gRZWvbp4AAAAAAAAAAABkARQnAQ"
},
{
"filename": "pointChart.js",
"title": {
"zh": "使用 G 自定义的点线图节点",
"en": "Point Chart Node"
},
"screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*M0ycSarMV2sAAAAAAAAAAABkARQnAQ"
},
{
"filename": "pieChart.js",
"title": {
@ -123,22 +27,6 @@
"en": "Pie Chart Node"
},
"screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*idENQpnwA1sAAAAAAAAAAABkARQnAQ"
},
{
"filename": "list.js",
"title": {
"zh": "列表",
"en": "List"
},
"screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*pxKjQ5dZx80AAAAAAAAAAABkARQnAQ"
},
{
"filename": "scrollNode.js",
"title": {
"zh": "节点内容可滚动",
"en": "Scrollable Node"
},
"screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*n-ipTrTaPD4AAAAAAAAAAAAAARQnAQ"
}
]
}

View File

@ -1,108 +1,120 @@
import G6 from '@antv/g6';
/**
* Custom a pie chart node
* by Shiwu
*
*/
import { Graph, Extensions, extend, stdLib } from '@antv/g6';
const lightBlue = '#5b8ff9';
const lightOrange = '#5ad8a6';
// register a pie chart node
G6.registerNode('pie-node', {
draw: (cfg, group) => {
const radius = cfg.size / 2; // node radius
const inPercentage = cfg.inDegree / cfg.degree; // the ratio of indegree to outdegree
const inAngle = inPercentage * Math.PI * 2; // the anble for the indegree fan
const inArcEnd = [radius * Math.cos(inAngle), -radius * Math.sin(inAngle)]; // the end position for the in-degree fan
const container = document.getElementById('container');
const width = container.scrollWidth;
const height = container.scrollHeight || 500;
class PieNode extends Extensions.CircleNode {
drawOtherShapes(model, shapeMap) {
const { data: cfg } = model;
// node radius
const radius = cfg.size / 2;
// the ratio of indegree to outdegree
const inPercentage = cfg.inDegree / cfg.degree;
// the anble for the indegree fan
const inAngle = inPercentage * Math.PI * 2;
// the end position for the in-degree fan
const inArcEnd = [radius * Math.cos(inAngle), -radius * Math.sin(inAngle)];
let isInBigArc = 0,
isOutBigArc = 1;
if (inAngle > Math.PI) {
isInBigArc = 1;
isOutBigArc = 0;
}
// fan shape for the in degree
const fanIn = group.addShape('path', {
attrs: {
path: [
['M', radius, 0],
['A', radius, radius, 0, isInBigArc, 0, inArcEnd[0], inArcEnd[1]],
['L', 0, 0],
['Z'],
],
lineWidth: 0,
fill: lightOrange,
},
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
name: 'in-fan-shape',
});
// draw the fan shape
group.addShape('path', {
attrs: {
path: [
['M', inArcEnd[0], inArcEnd[1]],
['A', radius, radius, 0, isOutBigArc, 0, radius, 0],
['L', 0, 0],
['Z'],
],
lineWidth: 0,
fill: lightBlue,
},
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
name: 'out-fan-shape',
});
// 返回 keyshape
return fanIn;
const otherShapes = {
['in-fan-shape']: this.upsertShape(
'path',
'in-fan-shape',
{
path: [
['M', radius, 0],
['A', radius, radius, 0, isInBigArc, 0, inArcEnd[0], inArcEnd[1]],
['L', 0, 0],
['Z'],
],
lineWidth: 0,
fill: lightOrange,
},
shapeMap,
model,
),
['out-fan-shape']: this.upsertShape(
'path',
'out-fan-shape',
{
path: [
['M', inArcEnd[0], inArcEnd[1]],
['A', radius, radius, 0, isOutBigArc, 0, radius, 0],
['L', 0, 0],
['Z'],
],
lineWidth: 0,
fill: lightBlue,
},
shapeMap,
model,
),
};
return otherShapes;
}
}
const ExtGraph = extend(Graph, {
nodes: {
'pie-node': PieNode,
},
});
const data = {
nodes: [
{
id: 'pie1',
size: 80,
inDegree: 80,
degree: 360,
x: 150,
y: 150,
},
{
id: 'pie2',
size: 80,
inDegree: 280,
degree: 360,
x: 350,
y: 150,
},
],
edges: [
{
source: 'pie1',
target: 'pie2',
},
],
};
const container = document.getElementById('container');
const width = container.scrollWidth;
const height = container.scrollHeight || 500;
const graph = new G6.Graph({
const graph = new ExtGraph({
container: 'container',
width,
height,
// translate the graph to align the canvas's center, support by v3.5.1
fitCenter: true,
linkCenter: true,
defaultNode: {
autoFit: 'center',
modes: {
default: ['drag-node'],
},
data: {
nodes: [
{
id: 'node1',
data: {
size: 80,
inDegree: 80,
degree: 360,
x: 150,
y: 150,
},
},
{
id: 'node2',
data: {
size: 80,
inDegree: 280,
degree: 360,
x: 350,
y: 150,
},
},
],
edges: [{ id: 'edge1', source: 'node1', target: 'node2' }],
},
node: {
type: 'pie-node',
otherShapes: {},
},
});
graph.data(data);
graph.render();
if (typeof window !== 'undefined')
window.onresize = () => {
if (!graph || graph.get('destroyed')) return;
if (!graph || graph.destroyed) return;
if (!container || !container.scrollWidth || !container.scrollHeight) return;
graph.changeSize(container.scrollWidth, container.scrollHeight);
graph.setSize([container.scrollWidth, container.scrollHeight]);
};

View File

@ -1,30 +1,37 @@
import G6 from '@antv/g6';
/**
* Custom a pie chart node
*/
import { Graph, Extensions, extend, stdLib } from '@antv/g6';
// Custom a mark node
G6.registerNode('justPoints', {
draw(cfg, group) {
class PointChartNode extends Extensions.CircleNode {
drawOtherShapes(model, shapeMap) {
const { data: cfg } = model;
const baseR = 30;
let nowAngle = 0;
const otherShapes = {};
// Ref line
let refR = baseR;
const refInc = 10;
for (let i = 0; i < 5; i++) {
group.addShape('circle', {
attrs: {
otherShapes[`circle-shape-${i}`] = this.upsertShape(
'circle',
`circle-shape-${i}`,
{
x: 0,
y: 0,
r: (refR += refInc),
stroke: '#5ad8a6',
lineDash: [4, 4],
},
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
name: 'circle-shape',
});
shapeMap,
model,
);
}
const everyIncAngle = (2 * Math.PI * (360 / 5 / 5)) / 360;
nowAngle = nowAngle + everyIncAngle / 2;
cfg.details.forEach((cat) => {
cfg.details.forEach((cat, dIndex) => {
// Calculate the positions for vertexes
const postions = [];
cat.values.forEach((item, index) => {
@ -41,19 +48,18 @@ G6.registerNode('justPoints', {
}
});
console.log('postions', postions);
// add marks
postions.forEach((pos, index) => {
if (index !== 5) {
group.addShape('circle', {
attrs: {
x: pos[0],
y: pos[1],
r: 3,
lineWidth: 2,
stroke: cat.color,
},
name: 'circle-marker-shape',
});
postions.forEach((pos, i) => {
if (i !== 5) {
otherShapes[`circle-marker-shape-${dIndex}-${i}`] = this.upsertShape(
'circle',
`circle-marker-shape-${dIndex}-${i}`,
{ x: 20 * i, y: 30 * i, r: 3, lineWidth: 2, stroke: cat.color, zIndex: 20, fill: 'red' },
shapeMap,
model,
);
}
});
});
@ -65,8 +71,10 @@ G6.registerNode('justPoints', {
const xPos = r * Math.cos(nowAngle2);
const yPos = r * Math.sin(nowAngle2);
group.addShape('path', {
attrs: {
otherShapes[`path-shape-${i}`] = this.upsertShape(
'path',
`path-shape-${i}`,
{
path: [
['M', 0, 0],
['L', xPos, yPos],
@ -74,109 +82,103 @@ G6.registerNode('justPoints', {
lineDash: [4, 4],
stroke: '#5ad8a6',
},
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
name: 'path-shape',
});
shapeMap,
model,
);
nowAngle2 += everyIncAngleCat;
}
// add a circle with the same color with the background color
group.addShape('circle', {
attrs: {
x: 0,
y: 0,
r: baseR,
fill: cfg.centerColor,
stroke: 'darkgray',
},
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
name: 'circle-shape',
});
otherShapes['circle-shape'] = this.upsertShape(
'circle',
'circle-shape',
{ x: 0, y: 0, r: baseR, fill: cfg.centerColor, stroke: 'darkgray' },
shapeMap,
model,
);
if (cfg.label) {
group.addShape('text', {
attrs: {
x: 0,
y: 0,
textAlign: 'center',
textBaseline: 'middle',
text: cfg.label,
fill: '#fff',
fontStyle: 'bold',
},
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
name: 'text-shape',
});
otherShapes['text-shape'] = this.upsertShape(
'circle',
'text-shape',
{ x: 0, y: 0, textAlign: 'center', textBaseline: 'middle', text: cfg.label, fill: '#fff', fontStyle: 'bold' },
shapeMap,
model,
);
}
return group;
console.log('otherShapes', otherShapes);
return otherShapes;
}
}
const ExtGraph = extend(Graph, {
nodes: {
'point-chart-node': PointChartNode,
},
});
const container = document.getElementById('container');
const width = container.scrollWidth;
const height = container.scrollHeight || 500;
const graph = new G6.Graph({
const graph = new ExtGraph({
container: 'container',
width,
height,
// translate the graph to align the canvas's center, support by v3.5.1
fitCenter: true,
autoFit: 'center',
modes: {
default: ['drag-node'],
},
data: {
nodes: [
{
id: 'point1',
data: {
x: 150,
y: 150,
details: [
{ cat: 'pv', values: [20, 30, 40, 30, 30], color: '#5B8FF9' },
{ cat: 'dal', values: [40, 30, 20, 30, 50], color: '#5AD8A6' },
{ cat: 'uv', values: [40, 30, 30, 40, 40], color: '#5D7092' },
{ cat: 'sal', values: [20, 30, 50, 20, 20], color: '#F6BD16' },
{ cat: 'cal', values: [10, 10, 20, 20, 20], color: '#E8684A' },
],
centerColor: '#5b8ff9',
},
},
{
id: 'point2',
data: {
x: 500,
y: 150,
details: [
{ cat: 'pv', values: [10, 10, 50, 20, 10], color: '#5ad8a6' },
{ cat: 'dal', values: [20, 30, 10, 50, 40], color: '#ff99c3' },
{ cat: 'uv', values: [10, 50, 30, 20, 30], color: '#6dc8ec' },
{ cat: 'sal', values: [50, 30, 20, 20, 20], color: '#269a99' },
{ cat: 'cal', values: [50, 10, 20, 50, 30], color: '#9270CA' },
],
centerColor: '#5b8ff9',
},
},
],
edges: [{ id: 'edge1', source: 'point1', target: 'point2' }],
},
node: {
type: 'point-chart-node',
iconShape: {
text: {
fields: ['id'],
formatter: (model) => model.id,
},
},
otherShapes: {},
},
});
const data = {
nodes: [
{
id: 'nodeC',
x: 150,
y: 150,
label: 'Point2',
type: 'justPoints',
anchorPoints: [
[0, 0.5],
[1, 0.5],
],
details: [
{ cat: 'pv', values: [20, 30, 40, 30, 30], color: '#5B8FF9' },
{ cat: 'dal', values: [40, 30, 20, 30, 50], color: '#5AD8A6' },
{ cat: 'uv', values: [40, 30, 30, 40, 40], color: '#5D7092' },
{ cat: 'sal', values: [20, 30, 50, 20, 20], color: '#F6BD16' },
{ cat: 'cal', values: [10, 10, 20, 20, 20], color: '#E8684A' },
],
centerColor: '#5b8ff9',
},
{
id: 'nodeC2',
x: 500,
y: 150,
label: 'Point2',
type: 'justPoints',
anchorPoints: [
[0, 0.5],
[1, 0.5],
],
details: [
{ cat: 'pv', values: [10, 10, 50, 20, 10], color: '#5ad8a6' },
{ cat: 'dal', values: [20, 30, 10, 50, 40], color: '#ff99c3' },
{ cat: 'uv', values: [10, 50, 30, 20, 30], color: '#6dc8ec' },
{ cat: 'sal', values: [50, 30, 20, 20, 20], color: '#269a99' },
{ cat: 'cal', values: [50, 10, 20, 50, 30], color: '#9270CA' },
],
centerColor: '#5b8ff9',
},
],
edges: [
{
source: 'nodeC',
target: 'nodeC2',
},
],
};
graph.data(data);
graph.render();
if (typeof window !== 'undefined')
window.onresize = () => {
if (!graph || graph.get('destroyed')) return;
if (!graph || graph.destroyed) return;
if (!container || !container.scrollWidth || !container.scrollHeight) return;
graph.changeSize(container.scrollWidth, container.scrollHeight);
graph.setSize([container.scrollWidth, container.scrollHeight]);
};

View File

@ -1,109 +1,116 @@
import G6 from '@antv/g6';
import { Graph } from '@antv/g6';
const data = {
nodes: [
{
id: 'node1',
x: 250,
y: 150,
comboId: 'combo',
},
{
id: 'node2',
x: 350,
y: 150,
comboId: 'combo',
},
{
id: 'node3',
x: 250,
y: 300,
comboId: 'combo2',
},
{
id: 'node4',
x: 450,
y: 300,
comboId: 'combo2',
},
],
combos: [
{
id: 'combo',
label: 'Combo',
},
{
id: 'combo2',
label: 'with substitute icon while collapsed',
collapsed: true,
collapsedSubstituteIcon: {
show: true,
img: 'https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*IEQFS5VtXX8AAAAAAAAAAABkARQnAQ',
width: 72,
height: 72
}
},
],
};
const container = document.getElementById('container');
const width = container.scrollWidth;
const height = container.scrollHeight || 500;
const width = document.getElementById('container').scrollWidth;
const height = document.getElementById('container').scrollHeight || 500;
const graph = new G6.Graph({
const graph = new Graph({
container: 'container',
width,
height,
// translate the graph to align the canvas's center, support by v3.5.1
fitCenter: true,
// Set groupByTypes to false to get rendering result with reasonable visual zIndex for combos
groupByTypes: false,
modes: {
default: ['drag-canvas', 'drag-node', 'drag-combo', 'collapse-expand-combo'],
stackCfg: {
ignoreStateChange: true,
},
defaultCombo: {
type: 'circle',
/* style for the keyShape */
// style: {
// lineWidth: 1,
// },
labelCfg: {
/* label's offset to the keyShape */
// refY: 10,
/* label's position, options: center, top, bottom, left, right */
position: 'top',
/* label's style */
// style: {
// fontSize: 18,
// },
node: {
labelShape: {
position: 'center',
text: {
fields: ['id'],
formatter: (model) => model.id,
},
},
animates: {
update: [
{
fields: ['opacity'],
shapeId: 'haloShape',
},
],
},
},
/* styles for different states, there are built-in styles for states: active, inactive, selected, highlight, disable */
/* you can extend it or override it as you want */
// comboStateStyles: {
// active: {
// fill: '#f00',
// opacity: 0.5
// },
// },
combo: (model) => {
return {
id: model.id,
data: {
type: 'circle-combo',
...model.data,
keyShape: {
padding: [10, 20, 30, 40],
r: 50,
},
labelShape: {
text: model.id,
},
animates: {
buildIn: [
{
fields: ['opacity'],
duration: 500,
delay: 500 + Math.random() * 500,
},
],
buildOut: [
{
fields: ['opacity'],
duration: 200,
},
],
update: [
{
fields: ['lineWidth', 'r'],
shapeId: 'keyShape',
},
{
fields: ['opacity'],
shapeId: 'haloShape',
},
],
},
},
};
},
data: {
nodes: [
{ id: 'node1', data: { x: 250, y: 150, parentId: 'combo1' } },
{ id: 'node2', data: { x: 350, y: 150, parentId: 'combo1' } },
{ id: 'node3', data: { x: 250, y: 300, parentId: 'combo2' } },
],
edges: [],
combos: [
{ id: 'combo1', data: { parentId: 'combo2' } }, // collapsed: true
{ id: 'combo2', data: {} },
],
},
modes: {
default: [
'collapse-expand-combo',
{
type: 'drag-node',
enableTransient: false,
updateComboStructure: false,
},
'drag-canvas',
{
type: 'click-select',
itemTypes: ['node', 'edge', 'combo'],
},
{
type: 'hover-activate',
itemTypes: ['node', 'edge', 'combo'],
},
{
type: 'drag-combo',
enableTransient: true,
updateComboStructure: true,
},
],
},
});
graph.data(data);
graph.render();
graph.on('combo:mouseenter', (evt) => {
const { item } = evt;
graph.setItemState(item, 'active', true);
});
graph.on('combo:mouseleave', (evt) => {
const { item } = evt;
graph.setItemState(item, 'active', false);
});
graph.on('combo:click', (evt) => {
const { item } = evt;
graph.setItemState(item, 'selected', true);
});
graph.on('canvas:click', (evt) => {
graph.getCombos().forEach((combo) => {
graph.clearItemStates(combo);
});
});
if (typeof window !== 'undefined')
window.onresize = () => {
if (!graph || graph.destroyed) return;
if (!container || !container.scrollWidth || !container.scrollHeight) return;
graph.setSize([container.scrollWidth, container.scrollHeight]);
};

View File

@ -1,111 +1,116 @@
import G6 from '@antv/g6';
import { Graph } from '@antv/g6';
const data = {
nodes: [
{
id: 'node1',
x: 250,
y: 150,
comboId: 'combo',
},
{
id: 'node2',
x: 350,
y: 150,
comboId: 'combo',
},
{
id: 'node3',
x: 250,
y: 300,
comboId: 'combo2',
},
{
id: 'node4',
x: 450,
y: 300,
comboId: 'combo2',
},
],
combos: [
{
id: 'combo',
label: 'Combo',
},
{
id: 'combo2',
label: 'with substitute icon while collapsed',
collapsed: true,
collapsedSubstituteIcon: {
show: true,
img: 'https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*IEQFS5VtXX8AAAAAAAAAAABkARQnAQ',
width: 72,
height: 72
}
},
],
};
const container = document.getElementById('container');
const width = container.scrollWidth;
const height = container.scrollHeight || 500;
const width = document.getElementById('container').scrollWidth;
const height = document.getElementById('container').scrollHeight || 500;
const graph = new G6.Graph({
const graph = new Graph({
container: 'container',
width,
height,
// translate the graph to align the canvas's center, support by v3.5.1
fitCenter: true,
// Set groupByTypes to false to get rendering result with reasonable visual zIndex for combos
groupByTypes: false,
modes: {
default: ['drag-canvas', 'drag-node', 'drag-combo', 'collapse-expand-combo'],
stackCfg: {
ignoreStateChange: true,
},
defaultCombo: {
type: 'rect',
/* The minimum size of the combo. combo 最小大小 */
size: [50, 50],
/* style for the keyShape */
// style: {
// lineWidth: 1,
// },
labelCfg: {
/* label's offset to the keyShape */
// refY: 10,
/* label's position, options: center, top, bottom, left, right */
position: 'top',
/* label's style */
// style: {
// fontSize: 18,
// },
node: {
labelShape: {
position: 'center',
text: {
fields: ['id'],
formatter: (model) => model.id,
},
},
animates: {
update: [
{
fields: ['opacity'],
shapeId: 'haloShape',
},
],
},
},
/* styles for different states, there are built-in styles for states: active, inactive, selected, highlight, disable */
/* you can extend it or override it as you want */
// comboStateStyles: {
// active: {
// fill: '#f00',
// opacity: 0.5
// },
// },
combo: (model) => {
return {
id: model.id,
data: {
type: 'rect-combo',
...model.data,
keyShape: {
padding: [10, 20, 30, 40],
r: 50,
},
labelShape: {
text: model.id,
},
animates: {
buildIn: [
{
fields: ['opacity'],
duration: 500,
delay: 500 + Math.random() * 500,
},
],
buildOut: [
{
fields: ['opacity'],
duration: 200,
},
],
update: [
{
fields: ['lineWidth', 'r'],
shapeId: 'keyShape',
},
{
fields: ['opacity'],
shapeId: 'haloShape',
},
],
},
},
};
},
data: {
nodes: [
{ id: 'node1', data: { x: 250, y: 150, parentId: 'combo1' } },
{ id: 'node2', data: { x: 350, y: 150, parentId: 'combo1' } },
{ id: 'node3', data: { x: 250, y: 300, parentId: 'combo2' } },
],
edges: [],
combos: [
{ id: 'combo1', data: { parentId: 'combo2' } },
{ id: 'combo2', data: {} },
],
},
modes: {
default: [
'collapse-expand-combo',
{
type: 'drag-node',
enableTransient: false,
updateComboStructure: false,
},
'drag-canvas',
{
type: 'click-select',
itemTypes: ['node', 'edge', 'combo'],
},
{
type: 'hover-activate',
itemTypes: ['node', 'edge', 'combo'],
},
{
type: 'drag-combo',
enableTransient: true,
updateComboStructure: true,
},
],
},
});
graph.data(data);
graph.render();
graph.on('combo:mouseenter', (evt) => {
const { item } = evt;
graph.setItemState(item, 'active', true);
});
graph.on('combo:mouseleave', (evt) => {
const { item } = evt;
graph.setItemState(item, 'active', false);
});
graph.on('combo:click', (evt) => {
const { item } = evt;
graph.setItemState(item, 'selected', true);
});
graph.on('canvas:click', (evt) => {
graph.getCombos().forEach((combo) => {
graph.clearItemStates(combo);
});
});
if (typeof window !== 'undefined')
window.onresize = () => {
if (!graph || graph.destroyed) return;
if (!container || !container.scrollWidth || !container.scrollHeight) return;
graph.setSize([container.scrollWidth, container.scrollHeight]);
};

View File

@ -1,107 +0,0 @@
import G6 from '@antv/g6';
/**
* The usage of arc edge
* by Shiwu
*/
const data = {
nodes: [
{
id: '0',
x: 150,
y: 50,
},
{
id: '1',
x: 350,
y: 250,
},
],
edges: [
// Built-in arc edges
{
id: 'edge0',
source: '0',
target: '1',
label: 'curveOffset = 20',
curveOffset: 20,
},
{
id: 'edge1',
source: '0',
target: '1',
label: 'curveOffset = 50', // the bending degree
curveOffset: 50,
},
{
id: 'edge2',
source: '0',
target: '1',
label: 'curveOffset = -50', // the bending degree
curveOffset: -50,
},
],
};
const width = document.getElementById('container').scrollWidth;
const height = document.getElementById('container').scrollHeight || 500;
const graph = new G6.Graph({
container: 'container',
width,
height,
linkCenter: true,
// translate the graph to align the canvas's center, support by v3.5.1
fitCenter: true,
modes: {
// supported behavior
default: ['drag-node'],
},
defaultEdge: {
type: 'arc',
/* you can configure the global edge style as following lines */
// style: {
// stroke: '#F6BD16',
// },
labelCfg: {
autoRotate: true,
refY: -10,
},
},
/* styles for different states, there are built-in styles for states: active, inactive, selected, highlight, disable */
// edgeStateStyles: {
// // edge style of active state
// active: {
// opacity: 0.5,
// stroke: '#f00'
// },
// // edge style of selected state
// selected: {
// stroke: '#ff0'
// lineWidth: 3,
// },
// },
});
graph.data(data);
graph.render();
graph.on('edge:mouseenter', (evt) => {
const { item } = evt;
graph.setItemState(item, 'active', true);
});
graph.on('edge:mouseleave', (evt) => {
const { item } = evt;
graph.setItemState(item, 'active', false);
});
graph.on('edge:click', (evt) => {
const { item } = evt;
graph.setItemState(item, 'selected', true);
});
graph.on('canvas:click', (evt) => {
graph.getEdges().forEach((edge) => {
graph.clearItemStates(edge);
});
});

View File

@ -0,0 +1,74 @@
import { Graph, Extensions, extend } from '@antv/g6';
const container = document.getElementById('container');
const width = container.scrollWidth;
const height = container.scrollHeight || 500;
const ExtGraph = extend(Graph, {
edges: {
'cubic-edge': Extensions.CubicEdge,
},
});
const graph = new ExtGraph({
container: 'container',
width,
height,
modes: {
default: ['zoom-canvas', 'drag-canvas', 'drag-node', 'click-select'],
},
data: {
nodes: [
{
id: 'node1',
data: {
x: 150,
y: 100,
},
},
{
id: 'node2',
data: { x: 250, y: 200 },
},
],
edges: [
{
id: 'edge1',
source: 'node1',
target: 'node2',
data: {
type: 'cubic-edge',
keyShape: {
/**
* 控制点数组默认曲线中心附近
* precise x-axis, y-axis coordinates of control points. Default is center of the curve
*/
controlPoints: [],
/**
* 控制点在两端点连线上的相对位置范围 0 - 1
* Relative position of the control point on the line between the two endpoints, range 0 - 1
*/
curvePosition: 0.5,
/**
* 控制点距离两端点连线的距离可理解为控制边的弯曲程度
* Distance of the control point from the line connecting the two end points
*/
curveOffset: 30,
},
},
},
],
},
edge: {
keyShape: {
endArrow: true,
},
},
});
if (typeof window !== 'undefined')
window.onresize = () => {
if (!graph || graph.destroyed) return;
if (!container || !container.scrollWidth || !container.scrollHeight) return;
graph.setSize([container.scrollWidth, container.scrollHeight]);
};

View File

@ -1,131 +0,0 @@
import G6 from '@antv/g6';
/**
* The usage of cubic edge
* **/
G6.registerNode(
'my-rect',
{
getAnchorPoints: function getAnchorPoints() {
return [
[0.5, 0],
[0.5, 1],
];
},
},
'rect',
);
const data = {
nodes: [
{
id: 'node0',
x: 200,
y: 10,
size: 20,
},
{
id: 'node1',
x: 200,
y: 50,
label: '1222',
type: 'my-rect',
},
{
id: 'node2',
x: 150,
y: 150,
type: 'my-rect',
},
{
id: 'node3',
x: 250,
y: 150,
type: 'my-rect',
},
{
id: 'node4',
x: 200,
y: 250,
type: 'my-rect',
},
],
edges: [
{
source: 'node0',
target: 'node1',
},
{
source: 'node1',
target: 'node2',
},
{
source: 'node1',
target: 'node3',
},
{
source: 'node2',
target: 'node4',
},
{
source: 'node3',
target: 'node4',
},
],
};
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,
modes: {
default: ['drag-canvas'],
},
defaultEdge: {
type: 'cubic-vertical',
/* you can configure the global edge style as following lines */
// style: {
// stroke: '#F6BD16',
// },
},
/* styles for different states, there are built-in styles for states: active, inactive, selected, highlight, disable */
// edgeStateStyles: {
// // edge style of active state
// active: {
// opacity: 0.5,
// stroke: '#f00'
// },
// // edge style of selected state
// selected: {
// stroke: '#ff0'
// lineWidth: 3,
// },
// },
});
graph.data(data);
graph.render();
graph.on('edge:mouseenter', (evt) => {
const { item } = evt;
graph.setItemState(item, 'active', true);
});
graph.on('edge:mouseleave', (evt) => {
const { item } = evt;
graph.setItemState(item, 'active', false);
});
graph.on('edge:click', (evt) => {
const { item } = evt;
graph.setItemState(item, 'selected', true);
});
graph.on('canvas:click', (evt) => {
graph.getEdges().forEach((edge) => {
graph.clearItemStates(edge);
});
});

View File

@ -1,107 +0,0 @@
import G6 from '@antv/g6';
/**
* The usage of cubic edge
*
* **/
const data = {
nodes: [
{
id: 'node5',
x: 150,
y: 200,
label: '5',
anchorPoints: [
[0, 0.5],
[1, 0.5],
],
},
{
id: 'node6',
x: 300,
y: 150,
label: '6',
anchorPoints: [
[0, 0.5],
[1, 0.5],
],
},
{
id: 'node7',
x: 300,
y: 250,
label: '7',
anchorPoints: [
[0, 0.5],
[1, 0.5],
],
},
],
edges: [
{
source: 'node5',
target: 'node6',
type: 'cubic-horizontal',
},
{
source: 'node5',
target: 'node7',
type: 'cubic-horizontal',
},
],
};
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,
modes: {
default: ['drag-canvas'],
},
defaultEdge: {
type: 'cubic-horizontal',
/* you can configure the global edge style as following lines */
// style: {
// stroke: '#F6BD16',
// },
},
/* styles for different states, there are built-in styles for states: active, inactive, selected, highlight, disable */
// edgeStateStyles: {
// // edge style of active state
// active: {
// opacity: 0.5,
// stroke: '#f00'
// },
// // edge style of selected state
// selected: {
// stroke: '#ff0'
// lineWidth: 3,
// },
// },
});
graph.data(data);
graph.render();
graph.on('edge:mouseenter', (evt) => {
const { item } = evt;
graph.setItemState(item, 'active', true);
});
graph.on('edge:mouseleave', (evt) => {
const { item } = evt;
graph.setItemState(item, 'active', false);
});
graph.on('edge:click', (evt) => {
const { item } = evt;
graph.setItemState(item, 'selected', true);
});
graph.on('canvas:click', (evt) => {
graph.getEdges().forEach((edge) => {
graph.clearItemStates(edge);
});
});

View File

@ -0,0 +1,87 @@
import { Graph, Extensions, extend } from '@antv/g6';
const data = {
nodes: [
{
id: 'node1',
data: {
x: 100,
y: 150,
},
},
{
id: 'node2',
data: { x: 200, y: 100 },
},
{
id: 'node3',
data: { x: 200, y: 200 },
},
],
edges: [
{
id: 'edge1',
source: 'node1',
target: 'node2',
},
{
id: 'edge2',
source: 'node1',
target: 'node3',
},
],
};
const container = document.getElementById('container');
const width = container.scrollWidth;
const height = container.scrollHeight || 500;
const ExtGraph = extend(Graph, {
edges: {
'cubic-horizontal-edge': Extensions.CubicHorizontalEdge,
},
});
const graph = new ExtGraph({
container: 'container',
width,
height,
modes: {
default: ['zoom-canvas', 'drag-canvas', 'drag-node', 'click-select'],
},
data,
node: {
anchorPoints: [
[0, 0.5],
[1, 0.5],
],
},
edge: {
type: 'cubic-horizontal-edge',
keyShape: {
/**
* 控制点数组默认曲线 1/3 2/3
* precise x-axis, y-axis coordinates of control points. Default: 1/3 and 2/3 of the curve
*/
// controlPoints: [],
/**
* 控制点在两端点连线上的相对位置范围 0 - 1
* Relative position of the control point on the line between the two endpoints, range 0 - 1
*/
// curvePosition: 0.5,
/**
* 控制点距离两端点连线的距离可理解为控制边的弯曲程度
* Distance of the control point from the line connecting the two end points
*/
// curveOffset: 30,
endArrow: true,
},
},
});
if (typeof window !== 'undefined')
window.onresize = () => {
if (!graph || graph.destroyed) return;
if (!container || !container.scrollWidth || !container.scrollHeight) return;
graph.setSize([container.scrollWidth, container.scrollHeight]);
};

View File

@ -1,96 +1,105 @@
import G6 from '@antv/g6';
import { Graph, Extensions, extend } from '@antv/g6';
const data = {
nodes: [
{
id: '0',
x: 150,
y: 150,
id: 'node1',
data: {
x: 100,
y: 100,
},
},
{
id: '1',
x: 350,
y: 150,
id: 'node2',
data: { x: 200, y: 100, type: 'rect-node' },
},
{
id: 'node3',
data: {
x: 300,
y: 100,
type: 'ellipse-node',
keyShape: {
rx: 20,
ry: 16,
},
},
},
],
edges: [
// 内置 loop
{
source: '0',
target: '0',
id: 'edge1',
source: 'node1',
target: 'node1',
},
{
source: '1',
target: '1',
id: 'edge2',
source: 'node2',
target: 'node2',
},
{
id: 'edge3',
source: 'node3',
target: 'node3',
},
],
};
const width = document.getElementById('container').scrollWidth;
const height = document.getElementById('container').scrollHeight || 500;
const graph = new G6.Graph({
const container = document.getElementById('container');
const width = container.scrollWidth;
const height = container.scrollHeight || 500;
const ExtGraph = extend(Graph, {
nodes: {
'ellipse-node': Extensions.EllipseNode,
},
edges: {
'loop-edge': Extensions.LoopEdge,
},
});
const graph = new ExtGraph({
container: 'container',
width,
height,
// translate the graph to align the canvas's center
fitCenter: true,
modes: {
// supported behavior
default: ['drag-node'],
default: ['zoom-canvas', 'drag-canvas', 'drag-node', 'click-select'],
},
defaultEdge: {
type: 'loop',
/* you can configure the global edge style as following lines */
style: {
// stroke: '#F6BD16',
endArrow: {
path: 'M 0,0 L 20,10 L 20,-10 Z',
fill: '#eee',
data,
edge: {
type: 'loop-edge',
keyShape: {
...data.keyShape,
loopCfg: {
/**
* 指定自环与节点的相对位置默认为top支持的值有top, top-right, right,bottom-right, bottom, bottom-left, left, top-left
* The relative position relationship between self-loop edge and keyShape.
*/
// position: 'top',
/**
* 指定是否顺时针画环默认为 true
* Specifies whether to draw the loop clockwise, default is true.
*/
// clockwise: true,
/**
* 从节点 keyShape 的边缘到自环最顶端的位置用于指定自环的曲度默认为节点的高度
* The position from edge of keyShape to the topmost point of the self-loop, defaults to the height of the node.
*/
// dist: 100,
/**
* 对于非圆形节点设置的连接点与节点中心坐标top-rightbottom-right,top-left,bottom-left较特殊为四个角坐标 x 轴或 y 轴方向的偏移量默认为 `节点宽高中最小值的1/4`
* Except circle, the offset of the connection point from center (top-right, bottom-right, top-left, bottom-left, and more specifically, the four corners of the node) in the x/y-axis direction is set to be `¼ of the smallest value of the node's width or height'.
*/
// pointPadding: 20,
},
},
// 更多关于 loop 的配置请参考http://antv.alipay.com/zh/docs/manual/middle/elements/edges/loop/#%E8%87%AA%E7%8E%AF%E7%89%B9%E6%AE%8A%E9%85%8D%E7%BD%AE-loopcfg
loopCfg: {
position: 'top',
endArrow: true,
},
},
/* styles for different states, there are built-in styles for states: active, inactive, selected, highlight, disable */
// edgeStateStyles: {
// // edge style of active state
// active: {
// opacity: 0.5,
// stroke: '#f00'
// },
// // edge style of selected state
// selected: {
// stroke: '#ff0'
// lineWidth: 3,
// },
// },
});
// 需要等 G 4.0 局部渲染完善后,就不用临时关闭了
const canvas = graph.get('canvas');
canvas.set('localRefresh', false);
graph.data(data);
graph.render();
graph.on('edge:mouseenter', (evt) => {
const { item } = evt;
graph.setItemState(item, 'active', true);
});
graph.on('edge:mouseleave', (evt) => {
const { item } = evt;
graph.setItemState(item, 'active', false);
});
graph.on('edge:click', (evt) => {
const { item } = evt;
graph.setItemState(item, 'selected', true);
});
graph.on('canvas:click', (evt) => {
graph.getEdges().forEach((edge) => {
graph.clearItemStates(edge);
});
});
if (typeof window !== 'undefined')
window.onresize = () => {
if (!graph || graph.destroyed) return;
if (!container || !container.scrollWidth || !container.scrollHeight) return;
graph.setSize([container.scrollWidth, container.scrollHeight]);
};

View File

@ -7,15 +7,15 @@
{
"filename": "polyline1.js",
"title": {
"zh": "折线 1",
"en": "Polyline 1"
"zh": "折线",
"en": "Polyline"
},
"screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*tGX6S5r94wkAAAAAAAAAAABkARQnAQ"
},
{
"filename": "polyline2.js",
"title": {
"zh": "折线 2",
"zh": "折线(设置控制点)",
"en": "Polyline 2"
},
"screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*CA7oQJIkIl8AAAAAAAAAAABkARQnAQ"
@ -23,13 +23,29 @@
{
"filename": "polyline3.js",
"title": {
"zh": "折线 3",
"zh": "折线(智能寻径)",
"en": "Polyline 3"
},
"screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*E-MJQqf9mSUAAAAAAAAAAABkARQnAQ"
},
{
"filename": "cubic1.js",
"filename": "quadratic.js",
"title": {
"zh": "二次贝塞尔曲线",
"en": "Quadratic"
},
"screenshot": ""
},
{
"filename": "cubic.js",
"title": {
"zh": "三次贝塞尔曲线",
"en": "Cubic"
},
"screenshot": ""
},
{
"filename": "verticalCubic.js",
"title": {
"zh": "三次贝塞尔曲线-垂直",
"en": "Vertical Cubic"
@ -37,21 +53,13 @@
"screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*UO_sQp0XftMAAAAAAAAAAABkARQnAQ"
},
{
"filename": "cubic2.js",
"filename": "horizontalCubic.js",
"title": {
"zh": "三次贝塞尔曲线-水平",
"en": "Horizontal Cubic"
},
"screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*LxFySJ-uvhMAAAAAAAAAAABkARQnAQ"
},
{
"filename": "arc.js",
"title": {
"zh": "弧线",
"en": "Arc"
},
"screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*xkK6QoNUszgAAAAAAAAAAABkARQnAQ"
},
{
"filename": "loop.js",
"title": {

View File

@ -1,87 +1,71 @@
import G6 from '@antv/g6';
import { Graph, Extensions, extend } from '@antv/g6';
/**
* The usage of built-in polyline
* by Shiwu
*/
const container = document.getElementById('container');
const width = container.scrollWidth;
const height = container.scrollHeight || 500;
const data = {
nodes: [
{
id: '0',
x: 150,
y: 100,
},
{
id: '1',
x: 350,
y: 300,
},
],
edges: [
// Built-in polyline
{
source: '0',
target: '1',
},
],
};
const ExtGraph = extend(Graph, {
edges: {
'polyline-edge': Extensions.PolylineEdge,
},
});
const width = document.getElementById('container').scrollWidth;
const height = document.getElementById('container').scrollHeight || 500;
const graph = new G6.Graph({
const graph = new ExtGraph({
container: 'container',
width,
height,
// translate the graph to align the canvas's center, support by v3.5.1
fitCenter: true,
// make the edge link to the centers of the end nodes
linkCenter: true,
modes: {
// behavior
default: ['drag-node'],
default: ['zoom-canvas', 'drag-canvas', 'drag-node', 'click-select'],
},
defaultEdge: {
type: 'polyline',
/* you can configure the global edge style as following lines */
// style: {
// stroke: '#F6BD16',
// },
data: {
nodes: [
{
id: 'node1',
data: {
x: 150,
y: 100,
},
},
{
id: 'node2',
data: { x: 300, y: 200 },
},
],
edges: [
{
id: 'edge1',
source: 'node1',
target: 'node2',
data: {
type: 'polyline-edge',
keyShape: {
/**
* 拐弯处的圆角弧度默认为直角
*/
radius: 0,
/**
* 拐弯处距离节点最小距离, 默认为 5
*/
offset: 5,
/**
* 控制点数组不指定时根据 A* 算法自动生成折线若指定了则按照 controlPoints 指定的位置进行弯折
*/
// controlPoints: [],
},
},
},
],
},
edge: {
keyShape: {
endArrow: true,
},
},
/* styles for different states, there are built-in styles for states: active, inactive, selected, highlight, disable */
// edgeStateStyles: {
// // edge style of active state
// active: {
// opacity: 0.5,
// stroke: '#f00'
// },
// // edge style of selected state
// selected: {
// stroke: '#ff0'
// lineWidth: 3,
// },
// },
});
graph.data(data);
graph.render();
graph.on('edge:mouseenter', (evt) => {
const { item } = evt;
graph.setItemState(item, 'active', true);
});
graph.on('edge:mouseleave', (evt) => {
const { item } = evt;
graph.setItemState(item, 'active', false);
});
graph.on('edge:click', (evt) => {
const { item } = evt;
graph.setItemState(item, 'selected', true);
});
graph.on('canvas:click', (evt) => {
graph.getEdges().forEach((edge) => {
graph.clearItemStates(edge);
});
});
if (typeof window !== 'undefined')
window.onresize = () => {
if (!graph || graph.destroyed) return;
if (!container || !container.scrollWidth || !container.scrollHeight) return;
graph.setSize([container.scrollWidth, container.scrollHeight]);
};

View File

@ -1,88 +1,81 @@
import G6 from '@antv/g6';
import { Graph, Extensions, extend } from '@antv/g6';
/**
* Built-in polyline edge with configurations
* by 十吾
*/
const container = document.getElementById('container');
const width = container.scrollWidth;
const height = container.scrollHeight || 500;
const data = {
nodes: [
{
id: '2',
x: 150,
y: 150,
},
{
id: '3',
x: 350,
y: 250,
},
],
edges: [
{
source: '2',
target: '3',
},
],
};
const ExtGraph = extend(Graph, {
edges: {
'polyline-edge': Extensions.PolylineEdge,
},
});
const width = document.getElementById('container').scrollWidth;
const height = document.getElementById('container').scrollHeight || 500;
const graph = new G6.Graph({
const graph = new ExtGraph({
container: 'container',
width,
height,
/* translate the graph to align the canvas's center, support by v3.5.1 */
fitCenter: true,
modes: {
/* behavior */
default: ['drag-node'],
default: ['zoom-canvas', 'drag-canvas', 'drag-node', 'click-select'],
},
defaultEdge: {
type: 'polyline',
/* configure the bending radius and min distance to the end nodes */
style: {
radius: 10,
offset: 30,
data: {
nodes: [
{
id: 'node1',
data: {
x: 140,
y: 130,
},
},
{
id: 'node2',
data: { x: 400, y: 200 },
},
],
edges: [
{
id: 'edge1',
source: 'node1',
target: 'node2',
data: {
type: 'polyline-edge',
keyShape: {
/**
* 拐弯处的圆角弧度默认为直角
*/
radius: 20,
/**
* 拐弯处距离节点最小距离, 默认为 5
*/
// offset: 0,
/**
* 控制点数组不指定时根据 A* 算法自动生成折线若指定了则按照 controlPoints 指定的位置进行弯折
*/
controlPoints: [
{ x: 220, y: 220 },
{ x: 300, y: 130 },
],
},
},
},
],
},
node: (nodeInnerModel) => {
const { id, data } = nodeInnerModel;
return {
id,
data,
};
},
edge: {
keyShape: {
endArrow: true,
/* and other styles */
// stroke: '#F6BD16',
},
},
/* styles for different states, there are built-in styles for states: active, inactive, selected, highlight, disable */
// edgeStateStyles: {
// // edge style of active state
// active: {
// opacity: 0.5,
// stroke: '#f00'
// },
// // edge style of selected state
// selected: {
// stroke: '#ff0'
// lineWidth: 3,
// },
// },
});
graph.data(data);
graph.render();
graph.on('edge:mouseenter', (evt) => {
const { item } = evt;
graph.setItemState(item, 'active', true);
});
graph.on('edge:mouseleave', (evt) => {
const { item } = evt;
graph.setItemState(item, 'active', false);
});
graph.on('edge:click', (evt) => {
const { item } = evt;
graph.setItemState(item, 'selected', true);
});
graph.on('canvas:click', (evt) => {
graph.getEdges().forEach((edge) => {
graph.clearItemStates(edge);
});
});
if (typeof window !== 'undefined')
window.onresize = () => {
if (!graph || graph.destroyed) return;
if (!container || !container.scrollWidth || !container.scrollHeight) return;
graph.setSize([container.scrollWidth, container.scrollHeight]);
};

View File

@ -1,109 +1,119 @@
import G6 from '@antv/g6';
/**
* Usage of built-in polyline edge with controlPoints
* by 十吾
*/
import { Graph, Extensions, extend } from '@antv/g6';
const data = {
nodes: [
{
id: '4',
x: 150,
y: 100,
id: 'node1',
data: {
x: 140,
y: 130,
},
},
{
id: '5',
x: 350,
y: 250,
id: 'node2',
data: {
x: 400,
y: 200,
},
},
{
id: 'obstacle1',
data: {
x: 300,
y: 90,
// preventPolylineEdgeOverlap: true,
},
},
{
id: 'obstacle2',
data: {
x: 300,
y: 200,
},
},
],
edges: [
{
source: '4',
target: '5',
// assign the control points to control the bending positions
controlPoints: [
{
x: 260,
y: 80,
id: 'edge1',
source: 'node1',
target: 'node2',
data: {
type: 'polyline-edge',
keyShape: {
/**
* 拐弯处的圆角弧度默认为直角值为 0
* The radius of the corner rounding, defaults to a right angle
*/
// radius: 20,
/**
* 拐弯处距离节点最小距离, 默认为 5
* Minimum distance from the node at the corner, default is 5.
*/
offset: 20,
/**
* 控制点数组不指定时根据 A* 算法自动生成折线若指定了则按照 controlPoints 指定的位置进行弯折
* An array of control points that, when not specified, automatically generates the bends according to the A* algorithm. If specified, bends are made at the position specified by controlPoints.
*/
// controlPoints: [],
routeCfg: {
/**
* 目前支持正交路由 'orth' 和地铁路由 'er'
*/
// name: 'er',
/**
* 是否开启自动避障默认为 false
* Whether to enable automatic obstacle avoidance, default is false
*/
obstacleAvoidance: true,
},
},
{
x: 320,
y: 50,
},
{
x: 390,
y: 110,
},
{
x: 420,
y: 110,
},
{
x: 420,
y: 140,
},
],
},
},
],
};
const width = document.getElementById('container').scrollWidth;
const height = document.getElementById('container').scrollHeight || 500;
const graph = new G6.Graph({
const container = document.getElementById('container');
const width = container.scrollWidth;
const height = container.scrollHeight || 500;
const ExtGraph = extend(Graph, {
edges: {
'polyline-edge': Extensions.PolylineEdge,
},
});
const graph = new ExtGraph({
container: 'container',
width,
height,
// translate the graph to align the canvas's center, support by v3.5.1
fitCenter: true,
// make the edge link to the centers of the end nodes
linkCenter: true,
modes: {
// behavior
default: ['drag-node'],
default: ['zoom-canvas', 'drag-canvas', 'drag-node', 'click-select'],
},
defaultEdge: {
type: 'polyline',
/* you can configure the global edge style as following lines */
// style: {
// stroke: '#F6BD16',
// },
data,
node: {
type: 'rect-node',
keyShape: {
width: 60,
height: 30,
},
labelShape: {
text: {
fields: ['id'],
formatter: (model) => model.id,
},
position: 'bottom',
},
},
edge: {
keyShape: {
endArrow: true,
},
},
/* styles for different states, there are built-in styles for states: active, inactive, selected, highlight, disable */
// edgeStateStyles: {
// // edge style of active state
// active: {
// opacity: 0.5,
// stroke: '#f00'
// },
// // edge style of selected state
// selected: {
// stroke: '#ff0'
// lineWidth: 3,
// },
// },
});
graph.data(data);
graph.render();
graph.on('edge:mouseenter', (evt) => {
const { item } = evt;
graph.setItemState(item, 'active', true);
});
graph.on('edge:mouseleave', (evt) => {
const { item } = evt;
graph.setItemState(item, 'active', false);
});
graph.on('edge:click', (evt) => {
const { item } = evt;
graph.setItemState(item, 'selected', true);
});
graph.on('canvas:click', (evt) => {
graph.getEdges().forEach((edge) => {
graph.clearItemStates(edge);
});
});
if (typeof window !== 'undefined')
window.onresize = () => {
if (!graph || graph.destroyed) return;
if (!container || !container.scrollWidth || !container.scrollHeight) return;
graph.setSize([container.scrollWidth, container.scrollHeight]);
};

View File

@ -0,0 +1,83 @@
import { Graph, Extensions, extend } from '@antv/g6';
const data = {
nodes: [
{
id: 'node1',
data: {
x: 150,
y: 100,
},
},
{
id: 'node2',
data: { x: 250, y: 200 },
},
],
edges: [
{
id: 'edge1',
source: 'node1',
target: 'node2',
data: {
type: 'quadratic-edge',
keyShape: {
/**
* 控制点数组默认曲线中心附近
* precise x-axis, y-axis coordinates of control points. Default is center of the curve
*/
controlPoints: [],
/**
* 控制点在两端点连线上的相对位置范围 0 - 1
* Relative position of the control point on the line between the two endpoints, range 0 - 1
*/
curvePosition: 0.5,
/**
* 控制点距离两端点连线的距离可理解为控制边的弯曲程度
* Distance of the control point from the line connecting the two end points
*/
curveOffset: 30,
},
},
},
],
};
const container = document.getElementById('container');
const width = container.scrollWidth;
const height = container.scrollHeight || 500;
const ExtGraph = extend(Graph, {
edges: {
'quadratic-edge': Extensions.QuadraticEdge,
},
});
const graph = new ExtGraph({
container: 'container',
width,
height,
modes: {
default: ['zoom-canvas', 'drag-canvas', 'drag-node', 'click-select'],
},
data,
node: (nodeInnerModel) => {
const { id, data } = nodeInnerModel;
return {
id,
data,
};
},
edge: {
keyShape: {
endArrow: true,
},
},
});
if (typeof window !== 'undefined')
window.onresize = () => {
if (!graph || graph.destroyed) return;
if (!container || !container.scrollWidth || !container.scrollHeight) return;
graph.setSize([container.scrollWidth, container.scrollHeight]);
};

View File

@ -0,0 +1,106 @@
import { Graph, Extensions, extend } from '@antv/g6';
const data = {
nodes: [
{
id: 'node1',
data: {
x: 150,
y: 100,
},
},
{
id: 'node2',
data: { x: 200, y: 200 },
},
{
id: 'node3',
data: { x: 100, y: 200 },
},
{
id: 'node4',
data: { x: 150, y: 300 },
},
],
edges: [
{
id: 'edge1',
source: 'node1',
target: 'node2',
},
{
id: 'edge2',
source: 'node1',
target: 'node3',
},
{
id: 'edge3',
source: 'node2',
target: 'node4',
},
{
id: 'edge4',
source: 'node3',
target: 'node4',
},
],
};
const container = document.getElementById('container');
const width = container.scrollWidth;
const height = container.scrollHeight || 500;
const ExtGraph = extend(Graph, {
edges: {
'cubic-horizontal-edge': Extensions.CubicHorizontalEdge,
'cubic-vertical-edge': Extensions.CubicVerticalEdge,
},
});
const graph = new ExtGraph({
container: 'container',
width,
height,
modes: {
default: ['zoom-canvas', 'drag-canvas', 'drag-node', 'click-select'],
},
data,
node: {
anchorPoints: [
[0.5, 0],
[1, 0],
],
keyShape: {
width: 80,
height: 30,
},
},
edge: {
type: 'cubic-vertical-edge',
keyShape: {
endArrow: true,
/**
* 控制点数组默认曲线 1/3 2/3
* precise x-axis, y-axis coordinates of control points. Default: 1/3 and 2/3 of the curve
*/
// controlPoints: [],
/**
* 控制点在两端点连线上的相对位置范围 0 - 1
* Relative position of the control point on the line between the two endpoints, range 0 - 1
*/
// curvePosition: 0.5,
/**
* 控制点距离两端点连线的距离可理解为控制边的弯曲程度
* Distance of the control point from the line connecting the two end points
*/
// curveOffset: 30,
},
},
});
if (typeof window !== 'undefined')
window.onresize = () => {
if (!graph || graph.destroyed) return;
if (!container || !container.scrollWidth || !container.scrollHeight) return;
graph.setSize([container.scrollWidth, container.scrollHeight]);
};

View File

@ -4,103 +4,64 @@ const data = {
nodes: [
{
id: 'circle',
label: 'Circle',
x: 250,
y: 150,
data: {
x: 250,
y: 150,
},
},
],
};
const width = document.getElementById('container').scrollWidth;
const height = document.getElementById('container').scrollHeight || 500;
const container = document.getElementById('container');
const width = container.scrollWidth;
const height = 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,
modes: {
default: ['drag-canvas', 'drag-node'],
default: ['zoom-canvas', 'drag-canvas', 'drag-node', 'click-select'],
},
defaultNode: {
/* node type */
type: 'circle',
/* node size */
size: [60],
/* style for the keyShape */
// style: {
// fill: '#9EC9FF',
// stroke: '#5B8FF9',
// lineWidth: 3,
// },
labelCfg: {
/* label's position, options: center, top, bottom, left, right */
data,
node: {
keyShape: {
r: 30,
},
labelShape: {
text: {
fields: ['id'],
formatter: (model) => model.id,
},
position: 'bottom',
/* label's offset to the keyShape, 4 by default */
// offset: 12,
/* label's style */
// style: {
// fontSize: 20,
// fill: '#ccc',
// fontWeight: 500
// }
},
/* configurations for four linkpoints */
linkPoints: {
top: true,
right: true,
bottom: true,
left: true,
/* linkPoints' size, 8 by default */
// size: 5,
/* linkPoints' style */
// fill: '#ccc',
// stroke: '#333',
// lineWidth: 2,
labelBackgroundShape: {
fill: 'red',
},
/* icon configuration */
icon: {
/* whether show the icon, false by default */
show: true,
/* icon's img address, string type */
// img: 'https://gw.alipayobjects.com/zos/basement_prod/012bcf4f-423b-4922-8c24-32a89f8c41ce.svg',
/* icon's size, 20 * 20 by default: */
// width: 40,
// height: 40
anchorShapes: [
{
position: [0, 0.5],
r: 2,
fill: 'red',
},
],
iconShape: {
img: 'https://gw.alipayobjects.com/zos/basement_prod/012bcf4f-423b-4922-8c24-32a89f8c41ce.svg',
width: 20,
height: 20,
},
badgeShapes: [
{
text: '1',
position: 'rightTop',
color: 'blue',
},
],
},
/* styles for different states, there are built-in styles for states: active, inactive, selected, highlight, disable */
// nodeStateStyles: {
// // node style of active state
// active: {
// fillOpacity: 0.8,
// },
// // node style of selected state
// selected: {
// lineWidth: 5,
// },
// },
});
graph.data(data);
graph.render();
graph.on('node:mouseenter', (evt) => {
const { item } = evt;
graph.setItemState(item, 'active', true);
});
graph.on('node:mouseleave', (evt) => {
const { item } = evt;
graph.setItemState(item, 'active', false);
});
graph.on('node:click', (evt) => {
const { item } = evt;
graph.setItemState(item, 'selected', true);
});
graph.on('canvas:click', (evt) => {
graph.getNodes().forEach((node) => {
graph.clearItemStates(node);
});
});
if (typeof window !== 'undefined')
window.onresize = () => {
if (!graph || graph.destroyed) return;
if (!container || !container.scrollWidth || !container.scrollHeight) return;
graph.setSize([container.scrollWidth, container.scrollHeight]);
};

View File

@ -1,106 +1,75 @@
import G6 from '@antv/g6';
import { Graph, Extensions, extend } from '@antv/g6';
const data = {
nodes: [
{
id: 'diamond',
label: 'Diamond',
x: 250,
y: 150,
data: {
x: 250,
y: 150,
},
},
],
};
const width = document.getElementById('container').scrollWidth;
const height = document.getElementById('container').scrollHeight || 500;
const graph = new G6.Graph({
const container = document.getElementById('container');
const width = container.scrollWidth;
const height = container.scrollHeight || 500;
const ExtGraph = extend(Graph, {
nodes: {
'diamond-node': Extensions.DiamondNode,
},
});
const graph = new ExtGraph({
container: 'container',
width,
height,
// translate the graph to align the canvas's center, support by v3.5.1
fitCenter: true,
modes: {
default: ['drag-canvas', 'drag-node'],
default: ['zoom-canvas', 'drag-canvas', 'drag-node', 'click-select'],
},
defaultNode: {
type: 'diamond',
/* node size */
size: [80, 60],
/* style for the keyShape */
// style: {
// fill: '#9EC9FF',
// stroke: '#5B8FF9',
// lineWidth: 3,
// },
labelCfg: {
/* label's position, options: center, top, bottom, left, right */
data,
node: {
type: 'diamond-node',
keyShape: {
r: 30,
},
labelShape: {
text: {
fields: ['id'],
formatter: (model) => model.id,
},
position: 'bottom',
/* label's offset to the keyShape, 4 by default */
// offset: 12,
/* label's style */
// style: {
// fontSize: 20,
// fill: '#ccc',
// fontWeight: 500
// }
},
/* configurations for four linkpoints */
linkPoints: {
top: true,
right: true,
bottom: true,
left: true,
/* linkPoints' size, 8 by default */
// size: 5,
/* linkPoints' style */
// fill: '#ccc',
// stroke: '#333',
// lineWidth: 2,
labelBackgroundShape: {
fill: 'red',
},
/* icon configuration */
icon: {
/* whether show the icon, false by default */
show: true,
/* icon's img address, string type */
// img: 'https://gw.alipayobjects.com/zos/basement_prod/012bcf4f-423b-4922-8c24-32a89f8c41ce.svg',
/* icon's size, 20 * 20 by default: */
// width: 40,
// height: 40
anchorShapes: [
{
position: [0, 0.5],
r: 2,
fill: 'red',
},
],
iconShape: {
img: 'https://gw.alipayobjects.com/zos/basement_prod/012bcf4f-423b-4922-8c24-32a89f8c41ce.svg',
width: 20,
height: 20,
},
badgeShapes: [
{
text: '1',
position: 'rightTop',
color: 'blue',
},
],
},
/* styles for different states, there are built-in styles for states: active, inactive, selected, highlight, disable */
// nodeStateStyles: {
// // node style of active state
// active: {
// fillOpacity: 0.8,
// },
// // node style of selected state
// selected: {
// lineWidth: 5,
// },
// },
});
graph.data(data);
graph.render();
graph.on('node:mouseenter', (evt) => {
const { item } = evt;
graph.setItemState(item, 'active', true);
});
graph.on('node:mouseleave', (evt) => {
const { item } = evt;
graph.setItemState(item, 'active', false);
});
graph.on('node:click', (evt) => {
const { item } = evt;
graph.setItemState(item, 'selected', true);
});
graph.on('canvas:click', (evt) => {
graph.getNodes().forEach((node) => {
graph.clearItemStates(node);
});
});
if (typeof window !== 'undefined')
window.onresize = () => {
if (!graph || graph.destroyed) return;
if (!container || !container.scrollWidth || !container.scrollHeight) return;
graph.setSize([container.scrollWidth, container.scrollHeight]);
};

View File

@ -1,118 +1,111 @@
import G6 from '@antv/g6';
import { Graph, Extensions, extend } from '@antv/g6';
const container = document.getElementById('container');
const width = container.scrollWidth;
const height = container.scrollHeight || 500;
const data = {
nodes: [
{
id: 'donut',
label: 'Donut',
x: 250,
y: 150,
// the attributes for drawing donut
donutAttrs: {
prop1: 10,
prop2: 20,
prop3: 25
},
// the color map for drawing donut
donutColorMap: {
prop1: '#8eaade',
prop2: '#5c7cb8',
prop3: '#1e3f7d'
data: {
x: 250,
y: 150,
/**
* 甜甜圈内径在 keyShape 半径的占比取值为 0-1默认 0.6
* The ratio of the inner diameter of the donut to the radius of the keyShape, the value is 0-1
*/
innerSize: 0.6,
/**
* 甜甜圈字段每个字段必须为 [key: string]: number
* Donut fields, each field must be [key: string]: number
*/
donutAttr: {
income: 80,
outcome: 40,
unknown: 45,
},
/**
* 甜甜圈颜色映射字段名与 attrs 中的字段名对应不指定则使用默认色板
* A donut colormap with field names corresponding to those in attrs. If not specified, the default swatch will be used
*/
donutColorMap: {
income: '#78D3F8',
outcome: '#F08BB4',
unknown: '#65789B',
},
},
},
],
};
const width = document.getElementById('container').scrollWidth;
const height = document.getElementById('container').scrollHeight || 500;
const graph = new G6.Graph({
const ExtGraph = extend(Graph, {
nodes: {
'donut-node': Extensions.DonutNode,
},
});
const graph = new ExtGraph({
container: 'container',
width,
height,
// translate the graph to align the canvas's center, support by v3.5.1
fitCenter: true,
modes: {
default: ['drag-canvas', 'drag-node'],
default: ['zoom-canvas', 'drag-canvas', 'drag-node', 'click-select'],
},
defaultNode: {
/* node type, the priority is lower than the type in the node data */
type: 'donut',
/* node size */
size: 60,
/* style for the keyShape */
// style: {
// fill: '#9EC9FF',
// stroke: '#5B8FF9',
// lineWidth: 3,
// },
labelCfg: {
/* label's position, options: center, top, bottom, left, right */
data,
node: {
type: 'donut-node',
keyShape: {
r: 30,
},
labelShape: {
text: {
fields: ['id'],
formatter: (model) => model.id,
},
position: 'bottom',
/* label's offset to the keyShape, 4 by default */
// offset: 12,
/* label's style */
// style: {
// fontSize: 20,
// fill: '#ccc',
// fontWeight: 500
// }
},
/* configurations for four linkpoints */
// linkPoints: {
// top: true,
// right: true,
// bottom: true,
// left: true,
// /* linkPoints' size, 8 by default */
// // size: 5,
// /* linkPoints' style */
// // fill: '#ccc',
// // stroke: '#333',
// // lineWidth: 2,
// },
/* icon configuration */
icon: {
/* whether show the icon, false by default */
show: true,
/* icon's img address, string type */
// img: 'https://gw.alipayobjects.com/zos/basement_prod/012bcf4f-423b-4922-8c24-32a89f8c41ce.svg',
/* icon's size, 20 * 20 by default: */
// width: 40,
// height: 40
labelBackgroundShape: {
fill: 'red',
},
anchorShapes: [
{
position: [0, 0.5],
r: 2,
fill: 'red',
},
],
iconShape: {
img: 'https://gw.alipayobjects.com/zos/basement_prod/012bcf4f-423b-4922-8c24-32a89f8c41ce.svg',
width: 20,
height: 20,
},
badgeShapes: [
{
text: '1',
position: 'rightTop',
color: 'blue',
},
],
/** Shapes of donut */
donutShapes: {
innerSize: { fields: ['innerSize'], formatter: (model) => model.data.innerSize },
attrs: {
fields: ['donutAttr'],
formatter: (model) => model.data.donutAttr,
},
colorMap: {
fields: ['donutColorMap'],
formatter: (model) => model.data.donutColorMap,
},
},
},
/* styles for different states, there are built-in styles for states: active, inactive, selected, highlight, disable */
// nodeStateStyles: {
// // node style of active state
// active: {
// fillOpacity: 0.8,
// },
// // node style of selected state
// selected: {
// lineWidth: 5,
// },
// },
});
graph.data(data);
graph.render();
graph.on('node:mouseenter', (evt) => {
const { item } = evt;
graph.setItemState(item, 'active', true);
});
graph.on('node:mouseleave', (evt) => {
const { item } = evt;
graph.setItemState(item, 'active', false);
});
graph.on('node:click', (evt) => {
const { item } = evt;
graph.setItemState(item, 'selected', true);
});
graph.on('canvas:click', (evt) => {
graph.getNodes().forEach((node) => {
graph.clearItemStates(node);
});
});
if (typeof window !== 'undefined')
window.onresize = () => {
if (!graph || graph.destroyed) return;
if (!container || !container.scrollWidth || !container.scrollHeight) return;
graph.setSize([container.scrollWidth, container.scrollHeight]);
};

View File

@ -1,105 +1,84 @@
import G6 from '@antv/g6';
import { Graph, Extensions, extend } from '@antv/g6';
const data = {
nodes: [
{
id: 'ellipse',
label: 'Ellipse',
x: 250,
y: 150,
data: {
x: 250,
y: 150,
type: 'ellipse-node',
keyShape: {
/**
* 水平半径默认 30
* the horizontal radius of the ellipse, default is 30
*/
// rx: 30,
/**
* 垂直半径数字可选默认 20
* the vertical radius of the ellipse, default is 20
*/
// ry: 20,
},
},
},
],
};
const width = document.getElementById('container').scrollWidth;
const height = document.getElementById('container').scrollHeight || 500;
const graph = new G6.Graph({
const container = document.getElementById('container');
const width = container.scrollWidth;
const height = container.scrollHeight || 500;
const ExtGraph = extend(Graph, {
nodes: {
'ellipse-node': Extensions.EllipseNode,
},
});
const graph = new ExtGraph({
container: 'container',
width,
height,
// translate the graph to align the canvas's center, support by v3.5.1
fitCenter: true,
modes: {
default: ['drag-canvas', 'drag-node'],
default: ['zoom-canvas', 'drag-canvas', 'drag-node', 'click-select'],
},
defaultNode: {
type: 'ellipse',
size: [80, 50],
/* style for the keyShape */
// style: {
// fill: '#9EC9FF',
// stroke: '#5B8FF9',
// lineWidth: 3,
// },
labelCfg: {
/* label's position, options: center, top, bottom, left, right */
data,
node: {
labelShape: {
text: {
fields: ['id'],
formatter: (model) => model.id,
},
position: 'bottom',
/* label's offset to the keyShape, 4 by default */
// offset: 12,
/* label's style */
// style: {
// fontSize: 20,
// fill: '#ccc',
// fontWeight: 500
// }
},
/* configurations for four linkpoints */
linkPoints: {
top: true,
right: true,
bottom: true,
left: true,
/* linkPoints' size, 8 by default */
// size: 5,
/* linkPoints' style */
// fill: '#ccc',
// stroke: '#333',
// lineWidth: 2,
labelBackgroundShape: {
fill: 'red',
},
/* icon configuration */
icon: {
/* whether show the icon, false by default */
show: true,
/* icon's img address, string type */
// img: 'https://gw.alipayobjects.com/zos/basement_prod/012bcf4f-423b-4922-8c24-32a89f8c41ce.svg',
/* icon's size, 20 * 20 by default: */
// width: 40,
// height: 40
anchorShapes: [
{
position: [0, 0.5],
r: 2,
fill: 'red',
},
],
iconShape: {
img: 'https://gw.alipayobjects.com/zos/basement_prod/012bcf4f-423b-4922-8c24-32a89f8c41ce.svg',
width: 20,
height: 20,
},
badgeShapes: [
{
text: '1',
position: 'rightTop',
color: 'blue',
},
],
},
/* styles for different states, there are built-in styles for states: active, inactive, selected, highlight, disable */
// nodeStateStyles: {
// // node style of active state
// active: {
// fillOpacity: 0.8,
// },
// // node style of selected state
// selected: {
// lineWidth: 5,
// },
// },
});
graph.data(data);
graph.render();
graph.on('node:mouseenter', (evt) => {
const { item } = evt;
graph.setItemState(item, 'active', true);
});
graph.on('node:mouseleave', (evt) => {
const { item } = evt;
graph.setItemState(item, 'active', false);
});
graph.on('node:click', (evt) => {
const { item } = evt;
graph.setItemState(item, 'selected', true);
});
graph.on('canvas:click', (evt) => {
graph.getNodes().forEach((node) => {
graph.clearItemStates(node);
});
});
if (typeof window !== 'undefined')
window.onresize = () => {
if (!graph || graph.destroyed) return;
if (!container || !container.scrollWidth || !container.scrollHeight) return;
graph.setSize([container.scrollWidth, container.scrollHeight]);
};

View File

@ -0,0 +1,85 @@
import { Graph, Extensions, extend } from '@antv/g6';
const data = {
nodes: [
{
id: 'hexagon',
data: {
x: 250,
y: 150,
type: 'hexagon-node',
keyShape: {
/**
* 六边形的半径默认为 20
* the radius of the hexagon, default is 20
*/
r: 20,
/**
* 六边形方向默认为 horizontal
* the direction of the hexagon, default is 'horizontal'
* supported value: 'horizontal' | 'vertical'
*/
direction: 'horizontal',
},
},
},
],
};
const container = document.getElementById('container');
const width = container.scrollWidth;
const height = container.scrollHeight || 500;
const ExtGraph = extend(Graph, {
nodes: {
'hexagon-node': Extensions.HexagonNode,
},
});
const graph = new ExtGraph({
container: 'container',
width,
height,
modes: {
default: ['zoom-canvas', 'drag-canvas', 'drag-node', 'click-select'],
},
data,
node: {
labelShape: {
text: {
fields: ['id'],
formatter: (model) => model.id,
},
position: 'bottom',
},
labelBackgroundShape: {
fill: 'red',
},
anchorShapes: [
{
position: [0, 0.5],
r: 2,
fill: 'red',
},
],
iconShape: {
img: 'https://gw.alipayobjects.com/zos/basement_prod/012bcf4f-423b-4922-8c24-32a89f8c41ce.svg',
width: 20,
height: 20,
},
badgeShapes: [
{
text: '1',
position: 'rightTop',
color: 'blue',
},
],
},
});
if (typeof window !== 'undefined')
window.onresize = () => {
if (!graph || graph.destroyed) return;
if (!container || !container.scrollWidth || !container.scrollHeight) return;
graph.setSize([container.scrollWidth, container.scrollHeight]);
};

View File

@ -1,43 +1 @@
import G6 from '@antv/g6';
const data = {
nodes: [
{
id: 'image',
img: 'https://gw.alipayobjects.com/os/s/prod/antv/assets/image/logo-with-text-73b8a.svg',
x: 250,
y: 150,
},
],
};
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,
defaultNode: {
type: 'image',
size: [260, 80],
clipCfg: {
show: false,
// Clip type options: circle, ellipse, rect, path
type: 'circle',
// circle
r: 30,
// clip style
style: {
lineWidth: 1,
},
},
},
modes: {
default: ['drag-canvas', 'drag-node'],
},
});
graph.data(data);
graph.render();
// TODO

View File

@ -69,12 +69,12 @@
"screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*i2tETYnJ0dIAAAAAAAAAAABkARQnAQ"
},
{
"filename": "image.js",
"filename": "hexagon.js",
"title": {
"zh": "图片",
"en": "Image"
"zh": "六边形",
"en": "Hexagon"
},
"screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*o-MuTpQaj7EAAAAAAAAAAABkARQnAQ"
"screenshot": ""
}
]
}

View File

@ -1,112 +1,95 @@
import G6 from '@antv/g6';
import { Graph, Extensions, extend } from '@antv/g6';
const data = {
nodes: [
{
id: 'rect1',
label: 'rect1',
description: 'description, hidden when undefined',
x: 250,
y: 150,
id: 'node1',
data: {
x: 250,
y: 150,
type: 'modelRect-node',
},
},
],
};
const width = document.getElementById('container').scrollWidth;
const height = document.getElementById('container').scrollHeight || 500;
const graph = new G6.Graph({
const container = document.getElementById('container');
const width = container.scrollWidth;
const height = container.scrollHeight || 500;
const ExtGraph = extend(Graph, {
nodes: {
'modelRect-node': Extensions.ModelRectNode,
},
});
const graph = new ExtGraph({
container: 'container',
width,
height,
// translate the graph to align the canvas's center, support by v3.5.1
fitCenter: true,
defaultNode: {
type: 'modelRect',
size: [270, 80],
style: {
radius: 5,
stroke: '#69c0ff',
fill: '#ffffff',
lineWidth: 1,
fillOpacity: 1,
},
// label configurations
labelCfg: {
style: {
fill: '#595959',
fontSize: 14,
},
offset: 30,
},
// left rect
preRect: {
show: true,
width: 4,
fill: '#40a9ff',
radius: 2,
},
// configurations for the four linkpoints
linkPoints: {
top: false,
right: false,
bottom: false,
left: false,
// the size of the linkpoints' circle
size: 10,
lineWidth: 1,
fill: '#72CC4A',
stroke: '#72CC4A',
},
// configurations for the icon
logoIcon: {
// whether to show the icon
show: true,
x: 0,
y: 0,
// the image url for the icon, string type
img:
'https://gw.alipayobjects.com/zos/basement_prod/4f81893c-1806-4de4-aff3-9a6b266bc8a2.svg',
width: 16,
height: 16,
// adjust the offset along x-axis for the icon
offset: 0,
},
// configurations for state icon
stateIcon: {
// whether to show the icon
show: true,
x: 0,
y: 0,
// the image url for the icon, string type
img:
'https://gw.alipayobjects.com/zos/basement_prod/300a2523-67e0-4cbf-9d4a-67c077b40395.svg',
width: 16,
height: 16,
// adjust hte offset along x-axis for the icon
offset: -5,
},
},
modes: {
default: ['drag-canvas', 'drag-node'],
default: ['zoom-canvas', 'drag-canvas', 'drag-node', 'click-select'],
},
nodeStateStyles: {
hover: {
lineWidth: 2,
stroke: '#1890ff',
fill: '#e6f7ff',
},
data,
node: (innerModel) => {
return {
...innerModel,
data: {
...innerModel.data,
labelShape: {
text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed vestibulum dapibus lectus, sit amet volutpat ante commodo et. Nulla auctor metus id mauris iaculis tempus. Nunc tincidunt libero et justo laoreet, id tristique purus efficitur. Integer aliquet risus nec ullamcorper bibendum. Proin luctus enim a odio aliquam, id condimentum lorem fermentum. Vivamus fermentum lacus vitae metus placerat, eu rutrum est lacinia. Aenean ullamcorper rhoncus ligula, a facilisis metus eleifend nec. Sed scelerisque, ipsum eget consectetur fermentum, sapien orci bibendum ligula, eget aliquam dolor augue vitae mauris. Cras at justo at lectus tempor fringilla. In ac dolor et elit semper sollicitudin. Duis ac volutpat nunc, sed fringilla leo. Nunc tempus, justo eu consequat lobortis, sapien eros varius nulla, vitae pretium libero magna nec ligula. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam id nisi vel nibh eleifend tempor. Fusce eget eleifend augue.',
// offsetX: 0,
// offsetY: 0,
position: 'bottom',
maxWidth: '0',
},
anchorShapes: [
{
position: 'left',
r: 2,
fill: 'red',
},
],
otherShapes: {
// left rect
preRect: {
fill: '#5CAAF8',
},
// text description
description: {
show: true,
text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed vestibulum dapibus lectus, sit amet volutpat ante commodo et. Nulla auctor metus id mauris iaculis tempus. Nunc tincidunt libero et justo laoreet, id tristique purus efficitur. Integer aliquet risus nec ullamcorper bibendum. Proin luctus enim a odio aliquam, id condimentum lorem fermentum. Vivamus fermentum lacus vitae metus placerat, eu rutrum est lacinia. Aenean ullamcorper rhoncus ligula, a facilisis metus eleifend nec. Sed scelerisque, ipsum eget consectetur fermentum, sapien orci bibendum ligula, eget aliquam dolor augue vitae mauris. Cras at justo at lectus tempor fringilla. In ac dolor et elit semper sollicitudin. Duis ac volutpat nunc, sed fringilla leo. Nunc tempus, justo eu consequat lobortis, sapien eros varius nulla, vitae pretium libero magna nec ligula. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam id nisi vel nibh eleifend tempor. Fusce eget eleifend augue.',
fontSize: 12,
offsetX: 0,
offsetY: 0,
},
// left icon describing the information
logoIcon: {
img: 'https://gw.alipayobjects.com/zos/basement_prod/4f81893c-1806-4de4-aff3-9a6b266bc8a2.svg',
// text: 'logo',
width: 16,
height: 16,
offsetX: 0,
offsetY: 0,
},
// right icon describing the state
stateIcon: {
img: 'https://gw.alipayobjects.com/zos/basement_prod/300a2523-67e0-4cbf-9d4a-67c077b40395.svg',
// text: 'state',
width: 16,
height: 16,
offsetX: 0,
offsetY: 0,
},
},
},
};
},
});
graph.data(data);
graph.render();
graph.on('node:mouseenter', (evt) => {
const { item } = evt;
graph.setItemState(item, 'hover', true);
});
graph.on('node:mouseleave', (evt) => {
const { item } = evt;
graph.setItemState(item, 'hover', false);
});
if (typeof window !== 'undefined')
window.onresize = () => {
if (!graph || graph.destroyed) return;
if (!container || !container.scrollWidth || !container.scrollHeight) return;
graph.setSize([container.scrollWidth, container.scrollHeight]);
};

View File

@ -3,106 +3,72 @@ import G6 from '@antv/g6';
const data = {
nodes: [
{
id: 'rect',
label: 'rect',
x: 250,
y: 150,
id: 'node1',
data: {
x: 250,
y: 150,
type: 'rect-node',
keyShape: {
/**
* 矩形的宽度默认为32
* the width of the rectangle, default is 32
*/
// width: 32,
/**
* 矩形的高度默认为32
* the height of the rectangle, default is 32
*/
// height: 32,
},
},
},
],
};
const width = document.getElementById('container').scrollWidth;
const height = document.getElementById('container').scrollHeight || 500;
const container = document.getElementById('container');
const width = container.scrollWidth;
const height = 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,
modes: {
default: ['drag-canvas', 'drag-node'],
default: ['zoom-canvas', 'drag-canvas', 'drag-node', 'click-select'],
},
defaultNode: {
/* node type */
type: 'rect',
/* node size */
size: [80, 40],
/* style for the keyShape */
// style: {
// fill: '#9EC9FF',
// stroke: '#5B8FF9',
// lineWidth: 3,
// radius: 10
// },
labelCfg: {
/* label's position, options: center, top, bottom, left, right */
position: 'center',
/* label's offset to the keyShape, 4 by default */
// offset: 12,
/* label's style */
// style: {
// fontSize: 20,
// fill: '#ccc',
// fontWeight: 500
// }
data,
node: {
labelShape: {
text: 'label',
position: 'bottom',
},
/* configurations for four linkpoints */
linkPoints: {
top: true,
right: true,
bottom: true,
left: true,
/* linkPoints' size, 8 by default */
// size: 5,
/* linkPoints' style */
// fill: '#ccc',
// stroke: '#333',
// lineWidth: 2,
labelBackgroundShape: {
fill: 'red',
},
/* icon configuration */
icon: {
/* whether show the icon, false by default */
show: true,
/* icon's img address, string type */
// img: 'https://gw.alipayobjects.com/zos/basement_prod/012bcf4f-423b-4922-8c24-32a89f8c41ce.svg',
/* icon's size, 20 * 20 by default: */
// width: 40,
// height: 40
anchorShapes: [
{
position: [0, 0.5],
r: 2,
fill: 'red',
},
],
iconShape: {
img: 'https://gw.alipayobjects.com/zos/basement_prod/012bcf4f-423b-4922-8c24-32a89f8c41ce.svg',
width: 20,
height: 20,
},
badgeShapes: [
{
text: '1',
position: 'rightTop',
color: 'blue',
},
],
},
/* styles for different states, there are built-in styles for states: active, inactive, selected, highlight, disable */
// nodeStateStyles: {
// // node style of active state
// active: {
// fillOpacity: 0.8,
// },
// // node style of selected state
// selected: {
// lineWidth: 5,
// },
// },
});
graph.data(data);
graph.render();
graph.on('node:mouseenter', (evt) => {
const { item } = evt;
graph.setItemState(item, 'active', true);
});
graph.on('node:mouseleave', (evt) => {
const { item } = evt;
graph.setItemState(item, 'active', false);
});
graph.on('node:click', (evt) => {
const { item } = evt;
graph.setItemState(item, 'selected', true);
});
graph.on('canvas:click', (evt) => {
graph.getNodes().forEach((node) => {
graph.clearItemStates(node);
});
});
if (typeof window !== 'undefined')
window.onresize = () => {
if (!graph || graph.destroyed) return;
if (!container || !container.scrollWidth || !container.scrollHeight) return;
graph.setSize([container.scrollWidth, container.scrollHeight]);
};

View File

@ -0,0 +1 @@
// TODO

View File

@ -1,106 +1,81 @@
import G6 from '@antv/g6';
import { Graph, Extensions, extend } from '@antv/g6';
const data = {
nodes: [
{
id: 'star',
label: 'Star',
x: 250,
y: 200,
id: 'node1',
data: {
x: 250,
y: 150,
type: 'star-node',
keyShape: {
/**
* 星形的外部半径默认为 20
* outer radius of the star默认为 20
*/
// outerR: 20,
/**
* 星形的内部半径默认为 7.5
* inner radius of the star默认为 7.5
*/
// innerR: 7.5
},
},
},
],
};
const width = document.getElementById('container').scrollWidth;
const height = document.getElementById('container').scrollHeight || 500;
const graph = new G6.Graph({
const container = document.getElementById('container');
const width = container.scrollWidth;
const height = container.scrollHeight || 500;
const ExtGraph = extend(Graph, {
nodes: {
'star-node': Extensions.StarNode,
},
});
const graph = new ExtGraph({
container: 'container',
width,
height,
// translate the graph to align the canvas's center, support by v3.5.1
fitCenter: true,
modes: {
default: ['drag-canvas', 'drag-node'],
default: ['zoom-canvas', 'drag-canvas', 'drag-node', 'click-select'],
},
defaultNode: {
/* node type */
type: 'star',
/* node size */
size: [60, 30],
/* style for the keyShape */
// style: {
// fill: '#9EC9FF',
// stroke: '#5B8FF9',
// lineWidth: 3,
// },
labelCfg: {
/* label's position, options: center, top, bottom, left, right */
data,
node: {
labelShape: {
text: 'label',
position: 'bottom',
/* label's offset to the keyShape, 4 by default */
offset: 20,
/* label's style */
// style: {
// fontSize: 20,
// fill: '#ccc',
// fontWeight: 500
// }
},
/* configurations for four linkpoints */
linkPoints: {
top: true,
right: true,
bottom: true,
left: true,
/* linkPoints' size, 8 by default */
// size: 5,
/* linkPoints' style */
// fill: '#ccc',
// stroke: '#333',
// lineWidth: 2,
labelBackgroundShape: {
fill: 'red',
},
/* icon configuration */
icon: {
/* whether show the icon, false by default */
show: true,
/* icon's img address, string type */
// img: 'https://gw.alipayobjects.com/zos/basement_prod/012bcf4f-423b-4922-8c24-32a89f8c41ce.svg',
/* icon's size, 20 * 20 by default: */
// width: 40,
// height: 40
anchorShapes: [
{
position: [0, 0.5],
r: 2,
fill: 'red',
},
],
iconShape: {
img: 'https://gw.alipayobjects.com/zos/basement_prod/012bcf4f-423b-4922-8c24-32a89f8c41ce.svg',
width: 20,
height: 20,
},
/* styles for different states, there are built-in styles for states: active, inactive, selected, highlight, disable */
// nodeStateStyles: {
// // node style of active state
// active: {
// fillOpacity: 0.8,
// },
// // node style of selected state
// selected: {
// lineWidth: 5,
// },
// },
badgeShapes: [
{
text: '1',
position: 'rightTop',
color: 'blue',
},
],
},
});
graph.data(data);
graph.render();
graph.on('node:mouseenter', (evt) => {
const { item } = evt;
graph.setItemState(item, 'active', true);
});
graph.on('node:mouseleave', (evt) => {
const { item } = evt;
graph.setItemState(item, 'active', false);
});
graph.on('node:click', (evt) => {
const { item } = evt;
graph.setItemState(item, 'selected', true);
});
graph.on('canvas:click', (evt) => {
graph.getNodes().forEach((node) => {
graph.clearItemStates(node);
});
});
if (typeof window !== 'undefined')
window.onresize = () => {
if (!graph || graph.destroyed) return;
if (!container || !container.scrollWidth || !container.scrollHeight) return;
graph.setSize([container.scrollWidth, container.scrollHeight]);
};

View File

@ -1,106 +1,81 @@
import G6 from '@antv/g6';
import { Graph, Extensions, extend } from '@antv/g6';
const data = {
nodes: [
{
id: 'circle',
label: 'Triangle',
x: 250,
y: 200,
id: 'node1',
data: {
x: 250,
y: 150,
type: 'triangle-node',
keyShape: {
/**
* 三角形的半径默认为12
* the radius of the triangle, default is 12
*/
r: 12,
/**
* 三角形的方向可选值 'up'|'left'|'right'|'down'
* the direction of the triangle, 'up' indicates upward, 'left' indicates leftward, 'right' indicates rightward, 'down' indicates downward
*/
direction: 'up',
},
},
},
],
};
const width = document.getElementById('container').scrollWidth;
const height = document.getElementById('container').scrollHeight || 500;
const graph = new G6.Graph({
const container = document.getElementById('container');
const width = container.scrollWidth;
const height = container.scrollHeight || 500;
const ExtGraph = extend(Graph, {
nodes: {
'triangle-node': Extensions.TriangleNode,
},
});
const graph = new ExtGraph({
container: 'container',
width,
height,
// translate the graph to align the canvas's center, support by v3.5.1
fitCenter: true,
modes: {
default: ['drag-canvas', 'drag-node'],
default: ['zoom-canvas', 'drag-canvas', 'drag-node', 'click-select'],
},
defaultNode: {
type: 'triangle',
size: [40],
// options: down, up
direction: 'up',
/* style for the keyShape */
// style: {
// fill: '#9EC9FF',
// stroke: '#5B8FF9',
// lineWidth: 3,
// },
labelCfg: {
/* label's position, options: center, top, bottom, left, right */
data,
node: {
labelShape: {
text: 'label',
position: 'bottom',
/* label's offset to the keyShape, 4 by default */
offset: 20,
/* label's style */
// style: {
// fontSize: 20,
// fill: '#ccc',
// fontWeight: 500
// }
},
/* configurations for four linkpoints */
linkPoints: {
top: true,
right: true,
bottom: true,
left: true,
/* linkPoints' size, 8 by default */
// size: 5,
/* linkPoints' style */
// fill: '#ccc',
// stroke: '#333',
// lineWidth: 2,
labelBackgroundShape: {
fill: 'red',
},
/* icon configuration */
icon: {
/* whether show the icon, false by default */
show: true,
/* icon's img address, string type */
// img: 'https://gw.alipayobjects.com/zos/basement_prod/012bcf4f-423b-4922-8c24-32a89f8c41ce.svg',
/* icon's size, 20 * 20 by default: */
// width: 40,
// height: 40
anchorShapes: [
{
position: [0, 0.5],
r: 2,
fill: 'red',
},
],
iconShape: {
img: 'https://gw.alipayobjects.com/zos/basement_prod/012bcf4f-423b-4922-8c24-32a89f8c41ce.svg',
width: 20,
height: 20,
},
/* styles for different states, there are built-in styles for states: active, inactive, selected, highlight, disable */
// nodeStateStyles: {
// // node style of active state
// active: {
// fillOpacity: 0.8,
// },
// // node style of selected state
// selected: {
// lineWidth: 5,
// },
// },
badgeShapes: [
{
text: '1',
position: 'rightTop',
color: 'blue',
},
],
},
});
graph.data(data);
graph.render();
graph.on('node:mouseenter', (evt) => {
const { item } = evt;
graph.setItemState(item, 'active', true);
});
graph.on('node:mouseleave', (evt) => {
const { item } = evt;
graph.setItemState(item, 'active', false);
});
graph.on('node:click', (evt) => {
const { item } = evt;
graph.setItemState(item, 'selected', true);
});
graph.on('canvas:click', (evt) => {
graph.getNodes().forEach((node) => {
graph.clearItemStates(node);
});
});
if (typeof window !== 'undefined')
window.onresize = () => {
if (!graph || graph.destroyed) return;
if (!container || !container.scrollWidth || !container.scrollHeight) return;
graph.setSize([container.scrollWidth, container.scrollHeight]);
};

View File

@ -1,290 +1,243 @@
import G6 from "@antv/g6";
import { Graph, Extensions, extend, stdLib } from '@antv/g6';
/**
* Process the long label, hover to show the complete label, click the icon to copy the label
* provided by GitHub user @WontonCat
* Thanks for contributing!
*/
const container = document.getElementById('container');
const tipDiv = document.createElement('div');
tipDiv.innerHTML = `Hover to show the complete label, click the icon to copy the content. Hover 显示完整 label点击左侧 icon 复制 label 内容`;
document.getElementById('container').appendChild(tipDiv);
const tipDiv = document.createElement('div');
tipDiv.innerHTML = `Click to show the complete label. <br/> Click the icon to copy the content.`;
container.appendChild(tipDiv);
/**
* format the string
* @param {string} str The origin string
* @param {number} maxWidth max width
* @param {number} fontSize font size
* @return {string} the processed result
*/
const fittingString = (str, maxWidth, fontSize) => {
const ellipsis = "...";
const ellipsisLength = G6.Util.getTextSize(ellipsis, fontSize)[0];
let currentWidth = 0;
let res = str;
const pattern = new RegExp("[\u4E00-\u9FA5]+"); // distinguish the Chinese charactors and letters
str.split("").forEach((letter, i) => {
if (currentWidth > maxWidth - ellipsisLength) return;
if (pattern.test(letter)) {
// Chinese charactors
currentWidth += fontSize;
} else {
// get the width of single letter according to the fontSize
currentWidth += G6.Util.getLetterWidth(letter, fontSize);
}
if (currentWidth > maxWidth - ellipsisLength) {
res = `${str.substr(0, i)}${ellipsis}`;
}
});
return res;
};
const copyStr = (str) => {
const input = document.createElement("textarea");
input.value = str;
document.body.appendChild(input);
input.select();
document.execCommand("Copy");
document.body.removeChild(input);
alert("Copy Success!");
};
const data = {
nodes: [
{
x: 100,
y: 0,
id: "node1",
topText: "This label is too long to be displayed",
bottomText: "This label is too long to be displayed"
},
{
x: 100,
y: 100,
id: "node2",
topText: "Short Label",
bottomText: "Click the Logo to Copy!"
}
]
};
const width = document.getElementById("container").scrollWidth;
const height = document.getElementById("container").scrollHeight || 500;
const graph = new G6.Graph({
container: "container",
width,
height: height - 50,
// translate the graph to align the canvas's center, support by v3.5.1
fitCenter: true,
defaultNode: {
type: "copy-node"
},
defaultEdge: {
color: "#F6BD16"
}
});
const width = container.scrollWidth;
const height = container.scrollHeight || 500;
const nodeHeight = 80;
const nodeWidth = 200;
const fillColor = "#f6e9d7";
const fontColor = "#ff7900";
const fillColor = '#f6e9d7';
const fontColor = '#ff7900';
const padding = 7;
G6.registerNode(
"copy-node",
{
drawShape: function drawShape(cfg, group) {
const rect = group.addShape("rect", {
attrs: {
x: 0,
y: 0,
height: nodeHeight,
width: nodeWidth,
fill: fillColor,
stroke: fontColor,
lineWidth: 2,
radius: 5
}
});
class CopyNode extends Extensions.RectNode {
drawOtherShapes(model, shapeMap, diffData, diffState) {
const { data: cfg } = model;
// 上部文字区域
const topGroup = group.addGroup({
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
name: "top-group"
});
topGroup.addShape("rect", {
attrs: {
fill: "#fff",
stroke: "#c7d0d1",
const topGroup = {
topBox: this.upsertShape(
'rect',
'topBox',
{
fill: '#fff',
stroke: '#c7d0d1',
x: padding,
y: padding,
width: nodeWidth - padding * 2,
height: 0.5 * nodeHeight - padding,
lineWidth: 1.5,
radius: 4
}
});
topGroup.addShape("text", {
attrs: {
text: fittingString(cfg.topText, nodeWidth - padding * 2 - 10, 14),
x: 0.5 * nodeWidth,
radius: 4,
},
shapeMap,
model,
),
topText: this.upsertShape(
'text',
'topText',
{
// text: fittingString(cfg.topText, nodeWidth - padding * 2 - 10, 14),
text: cfg.topText,
x: cfg.isTopTextEllipsis ? padding : padding + 24,
y: (0.5 * nodeHeight + padding) * 0.5,
fontSize: 14,
textAlign: "center",
textBaseline: "middle",
textAlign: 'start',
textBaseline: 'middle',
shadowColor: fontColor,
fill: fontColor
fill: fontColor,
wordWrap: cfg.isTopTextEllipsis,
wordWrapWidth: nodeWidth - padding * 2,
textOverflow: 'ellipsis',
maxLines: 1,
},
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
name: "top-text"
});
topGroup.addShape("image", {
attrs: {
shapeMap,
model,
),
topImage: this.upsertShape(
'image',
'topImage',
{
x: padding + 5,
y: padding + (0.5 * nodeHeight - padding) * 0.5 - 10,
height: 20,
width: 20,
img: "https://gw.alipayobjects.com/zos/antfincdn/FLrTNDvlna/antv.png",
opacity: 0,
cursor: "pointer"
img: 'https://gw.alipayobjects.com/zos/antfincdn/FLrTNDvlna/antv.png',
opacity: cfg.isTopTextEllipsis ? 0 : 1,
cursor: 'pointer',
},
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
name: "top-copy-img"
});
shapeMap,
model,
),
};
// 下部文字区域
const bottomGroup = group.addGroup({
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
name: "bottom-group"
});
bottomGroup.addShape("text", {
attrs: {
text: fittingString(cfg.bottomText, nodeWidth - 10, 14),
x: 0.5 * nodeWidth,
const bottomGroup = {
bottomText: this.upsertShape(
'text',
'bottomText',
{
// text: fittingString(cfg.bottomText, nodeWidth - 10, 14),
text: cfg.bottomText,
x: cfg.isBottomTextEllipsis ? padding : padding + 20,
y: nodeHeight - (0.5 * nodeHeight + padding) * 0.5,
fontSize: 14,
textAlign: "center",
textBaseline: "middle",
textAlign: 'start',
textBaseline: 'middle',
shadowColor: fontColor,
fill: fontColor
fill: fontColor,
wordWrap: cfg.isBottomTextEllipsis,
wordWrapWidth: nodeWidth,
textOverflow: 'ellipsis',
maxLines: 1,
},
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
name: "bottom-text"
});
bottomGroup.addShape("image", {
attrs: {
shapeMap,
model,
),
bottomImage: this.upsertShape(
'image',
'bottomImage',
{
x: 5,
y: 0.5 * nodeHeight + 8,
height: 20,
width: 20,
img: "https://gw.alipayobjects.com/zos/antfincdn/FLrTNDvlna/antv.png",
opacity: 0,
cursor: "pointer"
img: 'https://gw.alipayobjects.com/zos/antfincdn/FLrTNDvlna/antv.png',
opacity: cfg.isBottomTextEllipsis ? 0 : 1,
cursor: 'pointer',
},
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
name: "bottom-copy-img"
});
shapeMap,
model,
),
};
return rect;
},
setState(name, value, item) {
const group = item.get("group");
const model = item.get("model");
const { topText, bottomText } = model;
return { ...topGroup, ...bottomGroup };
}
}
if (name === "top-group-active") {
const img = group.find((e) => e.get("name") === "top-copy-img");
img.attr({
opacity: value ? 1 : 0
});
const text = group.find((e) => e.get("name") === "top-text");
// 区域是否够长 ? 居中, 展示内容不变 : 左对其, 展示完整
const cutStr = fittingString(topText, nodeWidth - padding * 2 - 10, 14);
if (cutStr === topText) {
text.attr({
fontWeight: value ? 800 : 700,
shadowBlur: value ? 12.2 : 0,
text: value
? topText
: fittingString(topText, nodeWidth - padding * 2 - 10, 14)
});
} else {
text.attr({
fontWeight: value ? 800 : 700,
x: value ? padding + 30 : 0.5 * nodeWidth,
shadowBlur: value ? 12.2 : 0,
textAlign: value ? "left" : "center",
text: value
? topText
: fittingString(topText, nodeWidth - padding * 2 - 10, 14)
});
}
}
if (name === "bottom-group-active") {
const img = group.find((e) => e.get("name") === "bottom-copy-img");
img.attr({
opacity: value ? 1 : 0
});
const text = group.find((e) => e.get("name") === "bottom-text");
const cutStr = fittingString(bottomText, nodeWidth - 10, 14);
if (cutStr === bottomText) {
text.attr({
fontWeight: value ? 800 : 700,
shadowBlur: value ? 12.2 : 0,
text: value
? bottomText
: fittingString(bottomText, nodeWidth - 10, 14)
});
} else {
text.attr({
fontWeight: value ? 800 : 700,
x: value ? 30 : 0.5 * nodeWidth,
shadowBlur: value ? 12.2 : 0,
textAlign: value ? "left" : "center",
text: value
? bottomText
: fittingString(bottomText, nodeWidth - 10, 14)
});
}
}
}
const ExtGraph = extend(Graph, {
nodes: {
'copy-node': CopyNode,
},
"single-node"
);
graph.on("top-group:mouseenter", (e) => {
graph.setItemState(e.item, "top-group-active", true);
});
graph.on("top-group:mouseleave", (e) => {
graph.setItemState(e.item, "top-group-active", false);
});
graph.on("bottom-group:mouseenter", (e) => {
graph.setItemState(e.item, "bottom-group-active", true);
});
graph.on("bottom-group:mouseleave", (e) => {
graph.setItemState(e.item, "bottom-group-active", false);
});
graph.on("node:click", (e) => {
const name = e.target.get("name");
const model = e.item.get("model");
if (name === "top-copy-img") {
const text = model.topText;
const graph = new ExtGraph({
container: 'container',
width,
height,
modes: {
default: ['drag-node'],
},
data: {
nodes: [
{
id: 'node1',
data: {
x: 100,
y: 100,
topText: 'This label is too long to be displayed',
bottomText: 'This label is too long to be displayed',
isTopTextEllipsis: true,
isBottomTextEllipsis: true,
},
},
{
id: 'node2',
data: {
x: 100,
y: 200,
topText: 'Short Label',
bottomText: 'Click the Logo to Copy!',
isTopTextEllipsis: true,
isBottomTextEllipsis: true,
},
},
],
},
node: {
type: 'copy-node',
keyShape: {
x: nodeWidth / 2,
y: nodeHeight / 2,
width: nodeWidth,
height: nodeHeight,
fill: fillColor,
lineWidth: 2,
stroke: fontColor,
radius: 5,
},
otherShapes: {},
},
});
const copyStr = (str) => {
const input = document.createElement('textarea');
input.value = str;
document.body.appendChild(input);
input.select();
document.execCommand('Copy');
document.body.removeChild(input);
alert('Copy Success!');
};
const resetTextEllipsis = () => {
const nodeIds = graph.getAllNodesData().map((node) => node.id);
nodeIds.map((nodeId) => {
graph.updateData('node', {
id: nodeId,
data: {
isTopTextEllipsis: true,
isBottomTextEllipsis: true,
},
});
});
};
graph.on('node:pointermove', (event) => {
const { itemId, target } = event;
resetTextEllipsis();
if (target.id === 'topText' || target.id === 'topImage') {
graph.updateData('node', {
id: itemId,
data: {
isTopTextEllipsis: false,
},
});
}
if (target.id === 'bottomText' || target.id === 'bottomImage') {
graph.updateData('node', {
id: itemId,
data: {
isBottomTextEllipsis: false,
},
});
}
});
graph.on('node:click', (event) => {
const { itemId, target } = event;
if (target.id === 'topImage') {
const model = graph.getNodeData(itemId);
const text = model.data.topText;
copyStr(text);
} else if (name === "bottom-copy-img") {
const text = model.bottomText;
}
if (target.id === 'bottomImage') {
const model = graph.getNodeData(itemId);
const text = model.data.bottomText;
copyStr(text);
}
});
graph.data(data);
graph.render();
graph.on('node:pointerleave', (event) => {
resetTextEllipsis();
});
if (typeof window !== 'undefined')
window.onresize = () => {
if (!graph || graph.destroyed) return;
if (!container || !container.scrollWidth || !container.scrollHeight) return;
graph.setSize([container.scrollWidth, container.scrollHeight]);
};

View File

@ -1,125 +1,70 @@
/**
* When the label is too long to be displayed, hide the overflow characters and show a ellipsis instead
* There are two ways to process the long labels
* 1. Process in the data;
* 2. Process when custom the edge or node like this:
* group.addShape('text', {
* attrs: {
* text: fittingString(cfg.label, 50, 12),
* ...
* },
* // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
* name: 'text-shape'
* })
*
*/
import G6 from '@antv/g6';
/**
* format the string
* @param {string} str The origin string
* @param {number} maxWidth max width
* @param {number} fontSize font size
* @return {string} the processed result
*/
const fittingString = (str, maxWidth, fontSize) => {
const ellipsis = '...';
const ellipsisLength = G6.Util.getTextSize(ellipsis, fontSize)[0];
let currentWidth = 0;
let res = str;
const pattern = new RegExp('[\u4E00-\u9FA5]+'); // distinguish the Chinese charactors and letters
str.split('').forEach((letter, i) => {
if (currentWidth > maxWidth - ellipsisLength) return;
if (pattern.test(letter)) {
// Chinese charactors
currentWidth += fontSize;
} else {
// get the width of single letter according to the fontSize
currentWidth += G6.Util.getLetterWidth(letter, fontSize);
}
if (currentWidth > maxWidth - ellipsisLength) {
res = `${str.substr(0, i)}${ellipsis}`;
}
});
return res;
};
const globalFontSize = 12;
import { Graph } from '@antv/g6';
const data = {
nodes: [
{
x: 100,
y: 100,
size: 40,
label: 'This label is too long to be displayed',
id: 'node1',
anchorPoints: [
[0, 0.5],
[1, 0.5],
],
data: {
x: 100,
y: 150,
label: 'This label is too long to be displayed',
size: 50,
},
},
{
x: 300,
y: 100,
size: 80,
label: 'This label is also too long to be displayed',
id: 'node2',
anchorPoints: [
[0, 0.5],
[1, 0.5],
],
data: {
x: 400,
y: 150,
label: 'This label is too long to be displayed',
size: 100,
},
},
],
edges: [
{
id: 'edge1',
source: 'node1',
target: 'node2',
label: 'This label is too long to be displayed',
labelCfg: {
refY: 20,
style: {
fontSize: globalFontSize,
},
},
style: {
endArrow: true,
data: {
label: 'This label is too long to be displayed',
},
},
],
};
const width = document.getElementById('container').scrollWidth;
const height = document.getElementById('container').scrollHeight || 500;
const graph = new G6.Graph({
container: 'container',
const container = document.getElementById('container');
const width = container.scrollWidth;
const height = container.scrollHeight || 500;
const graph = new Graph({
container,
width,
height,
// translate the graph to align the canvas's center, support by v3.5.1
fitCenter: true,
defaultNode: {
style: {
fill: '#DEE9FF',
stroke: '#5B8FF9',
modes: {
default: ['drag-node'],
},
node: {
keyShape: {
r: {
fields: ['size'],
formatter: (model) => model.data.size / 2,
},
},
labelCfg: {
style: {
fontSize: globalFontSize,
labelShape: {
position: 'center',
text: {
fields: ['label'],
formatter: (model) => model.data.label,
},
},
},
defaultEdge: {
color: '#F6BD16',
},
data,
});
// Modify the label in the data
data.nodes.forEach(function (node) {
node.label = fittingString(node.label, node.size, globalFontSize);
});
data.edges.forEach(function (edge) {
edge.label = fittingString(edge.label, 120, globalFontSize);
});
graph.data(data);
graph.render();
if (typeof window !== 'undefined')
window.onresize = () => {
if (!graph || graph.destroyed) return;
if (!container || !container.scrollWidth || !container.scrollHeight) return;
graph.setSize([container.scrollWidth, container.scrollHeight]);
};

View File

@ -1,100 +1,82 @@
import G6 from '@antv/g6';
/**
* Process the long label by breaking the text
* by Jingxi
*
*/
import { Graph } from '@antv/g6';
/**
* format the string
* @param {string} str The origin string
* @param {number} maxWidth max width
* @param {number} fontSize font size
* @return {string} the processed result
*/
const fittingString = (str, maxWidth, fontSize) => {
let currentWidth = 0;
let res = str;
const pattern = new RegExp('[\u4E00-\u9FA5]+'); // distinguish the Chinese charactors and letters
str.split('').forEach((letter, i) => {
if (currentWidth > maxWidth) return;
if (pattern.test(letter)) {
// Chinese charactors
currentWidth += fontSize;
} else {
// get the width of single letter according to the fontSize
currentWidth += G6.Util.getLetterWidth(letter, fontSize);
}
if (currentWidth > maxWidth) {
res = `${str.substr(0, i)}\n${str.substr(i)}`;
}
});
return res;
};
const globalFontSize = 12;
const data = {
nodes: [
{
x: 100,
y: 100,
label: fittingString('Break the line if it is too long', 80, globalFontSize),
id: 'node1',
labelCfg: {
position: 'bottom',
data: {
x: 100,
y: 150,
label: `This label is too long to be displayed`,
size: 50,
},
anchorPoints: [
[0, 0.5],
[1, 0.5],
],
},
{
x: 300,
y: 100,
label: fittingString('Break the line if it is too long', 80, globalFontSize),
id: 'node2',
labelCfg: {
position: 'bottom',
data: {
x: 400,
y: 150,
label: 'This label is too long to be displayed',
size: 100,
},
anchorPoints: [
[0, 0.5],
[1, 0.5],
],
},
],
edges: [
{
id: 'edge1',
source: 'node1',
target: 'node2',
label: fittingString('Break the line if it is too long', 100, globalFontSize),
labelCfg: {
refY: 20,
},
style: {
endArrow: true,
data: {
label: 'This label is too long to be displayed',
},
},
],
};
const width = document.getElementById('container').scrollWidth;
const height = document.getElementById('container').scrollHeight || 500;
const graph = new G6.Graph({
container: 'container',
const container = document.getElementById('container');
const width = container.scrollWidth;
const height = container.scrollHeight || 500;
const graph = new Graph({
container,
width,
height,
// translate the graph to align the canvas's center, support by v3.5.1
fitCenter: true,
defaultNode: {
type: 'rect',
style: {
fill: '#DEE9FF',
stroke: '#5B8FF9',
modes: {
default: ['drag-node'],
},
node: {
keyShape: {
r: {
fields: ['size'],
formatter: (model) => model.data.size / 2,
},
},
labelShape: {
position: 'center',
maxLines: 4,
text: {
fields: ['label'],
formatter: (model) => model.data.label,
},
},
},
defaultEdge: {
color: '#F6BD16',
edge: {
labelShape: {
position: 'center',
maxLines: 4,
text: {
fields: ['label'],
formatter: (model) => model.data.label,
},
wordWrapWidth: 80,
},
},
data,
});
graph.data(data);
graph.render();
if (typeof window !== 'undefined')
window.onresize = () => {
if (!graph || graph.destroyed) return;
if (!container || !container.scrollWidth || !container.scrollHeight) return;
graph.setSize([container.scrollWidth, container.scrollHeight]);
};

View File

@ -23,7 +23,7 @@
{
"filename": "labelLen1.js",
"title": {
"zh": "使用换行符处理文本超长",
"zh": "多行显示超长文本",
"en": "Break Line for Long Label"
},
"screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*EC0-R7tAn0MAAAAAAAAAAABkARQnAQ"

View File

@ -1,88 +1,69 @@
import G6 from '@antv/g6';
import { Graph } from '@antv/g6';
const data = {
nodes: [
{
id: 'node1',
x: 150,
y: 50,
label: 'node1',
},
{
id: 'node2',
x: 250,
y: 200,
label: 'node2',
},
{
id: 'node3',
x: 100,
y: 350,
label: 'node3',
},
],
edges: [
{
source: 'node1',
target: 'node2',
label: 'edge 1',
},
{
source: 'node2',
target: 'node3',
label: 'edge 2',
},
{
source: 'node3',
target: 'node1',
label: 'edge 3',
},
],
};
const container = document.getElementById('container');
const width = container.scrollWidth;
const height = container.scrollHeight || 500;
const width = document.getElementById('container').scrollWidth;
const height = document.getElementById('container').scrollHeight || 500;
const graph = new G6.Graph({
const graph = new Graph({
container: 'container',
width,
height,
// translate the graph to align the canvas's center, support by v3.5.1
fitCenter: true,
defaultNode: {
type: 'circle',
labelCfg: {
position: 'bottom',
},
modes: {
default: ['zoom-canvas', 'drag-canvas', 'drag-node', 'click-select'],
},
defaultEdge: {
labelCfg: {
autoRotate: true,
style: {
fill: '#1890ff',
fontSize: 14,
background: {
fill: '#ffffff',
stroke: '#9EC9FF',
padding: [2, 2, 2, 2],
radius: 2,
data: {
nodes: [
{
id: 'node1',
data: {
x: 150,
y: 100,
},
},
},
{
id: 'node2',
data: { x: 250, y: 200 },
},
{
id: 'node3',
data: { x: 450, y: 200 },
},
],
edges: [
{
id: 'edge1',
source: 'node1',
target: 'node2',
},
{
id: 'edge2',
source: 'node1',
target: 'node3',
},
{
id: 'edge3',
source: 'node2',
target: 'node3',
},
],
},
modes: {
default: ['drag-canvas', 'drag-node'],
},
nodeStateStyles: {
// style configurations for hover state
hover: {
fillOpacity: 0.8,
edge: {
labelShape: {
text: {
fields: ['id'],
formatter: (model) => model.id,
},
position: 'bottom',
},
// style configurations for selected state
selected: {
lineWidth: 5,
labelBackgroundShape: {
fill: 'red',
},
},
});
graph.data(data);
graph.render();
if (typeof window !== 'undefined')
window.onresize = () => {
if (!graph || graph.destroyed) return;
if (!container || !container.scrollWidth || !container.scrollHeight) return;
graph.setSize([container.scrollWidth, container.scrollHeight]);
};

View File

@ -1,83 +1,69 @@
import G6 from '@antv/g6';
import { Graph } from '@antv/g6';
const data = {
nodes: [
{
id: 'node1',
x: 150,
y: 50,
label: 'node1',
},
{
id: 'node2',
x: 250,
y: 200,
label: 'node2',
},
{
id: 'node3',
x: 100,
y: 350,
label: 'node3',
},
],
edges: [
{
source: 'node1',
target: 'node2',
label: 'edge 1',
},
{
source: 'node2',
target: 'node3',
label: 'edge 2',
},
{
source: 'node3',
target: 'node1',
label: 'edge 3',
},
],
};
const container = document.getElementById('container');
const width = container.scrollWidth;
const height = container.scrollHeight || 500;
const width = document.getElementById('container').scrollWidth;
const height = document.getElementById('container').scrollHeight || 500;
const graph = new G6.Graph({
const graph = new Graph({
container: 'container',
width,
height,
// translate the graph to align the canvas's center, support by v3.5.1
fitCenter: true,
defaultNode: {
type: 'circle',
labelCfg: {
style: {
fill: '#1890ff',
fontSize: 14,
background: {
fill: '#ffffff',
stroke: '#9EC9FF',
padding: [2, 2, 2, 2],
radius: 2,
modes: {
default: ['zoom-canvas', 'drag-canvas', 'drag-node', 'click-select'],
},
data: {
nodes: [
{
id: 'node1',
data: {
x: 150,
y: 100,
},
},
{
id: 'node2',
data: { x: 250, y: 200 },
},
{
id: 'node3',
data: { x: 450, y: 200 },
},
],
edges: [
{
id: 'edge1',
source: 'node1',
target: 'node2',
},
{
id: 'edge2',
source: 'node1',
target: 'node3',
},
{
id: 'edge3',
source: 'node2',
target: 'node3',
},
],
},
node: {
labelShape: {
text: {
fields: ['id'],
formatter: (model) => model.id,
},
position: 'bottom',
},
},
modes: {
default: ['drag-canvas', 'drag-node'],
},
nodeStateStyles: {
// style configurations for hover state
hover: {
fillOpacity: 0.8,
},
// style configurations for selected state
selected: {
lineWidth: 5,
labelBackgroundShape: {
fill: 'red',
},
},
});
graph.data(data);
graph.render();
if (typeof window !== 'undefined')
window.onresize = () => {
if (!graph || graph.destroyed) return;
if (!container || !container.scrollWidth || !container.scrollHeight) return;
graph.setSize([container.scrollWidth, container.scrollHeight]);
};

View File

@ -1,16 +0,0 @@
{
"title": {
"zh": "中文分类",
"en": "Category"
},
"demos": [
{
"filename": "multiEdges.js",
"title": {
"zh": "两节点间存在多条边",
"en": "Multiple Edges Between 2 Nodes"
},
"screenshot": "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*g2p_Qa_wZcIAAAAAAAAAAABkARQnAQ"
}
]
}

View File

@ -1,91 +0,0 @@
import G6 from '@antv/g6';
const data = {
nodes: [
{
id: 'node1',
x: 50,
y: 350,
label: 'A',
},
{
id: 'node2',
x: 250,
y: 150,
label: 'B',
},
{
id: 'node3',
x: 450,
y: 350,
label: 'C',
},
],
edges: [],
};
for (let i = 0; i < 10; i++) {
data.edges.push({
source: 'node1',
target: 'node2',
label: `${i}th edge of A-B`,
});
}
for (let i = 0; i < 5; i++) {
data.edges.push({
source: 'node2',
target: 'node3',
label: `${i}th edge of B-C`,
});
}
G6.Util.processParallelEdges(data.edges);
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,
// the edges are linked to the center of source and target ndoes
linkCenter: true,
defaultNode: {
type: 'circle',
size: [40],
color: '#5B8FF9',
style: {
fill: '#9EC9FF',
lineWidth: 3,
},
labelCfg: {
style: {
fill: '#000',
fontSize: 14,
},
},
},
defaultEdge: {
type: 'quadratic',
labelCfg: {
autoRotate: true,
},
},
modes: {
default: ['drag-canvas', 'drag-node'],
},
nodeStateStyles: {
// style configurations for hover state
hover: {
fillOpacity: 0.8,
},
// style configurations for selected state
selected: {
lineWidth: 5,
},
},
});
graph.data(data);
graph.render();

View File

@ -1,6 +0,0 @@
---
title: Multiple Edges Between 2 Nodes
order: 9
---
Process the parellel edges whose end nodes are the same by the built-in Util method `G6.Util.processParallelEdges(data.edges)`. If the edges are loop, they will be asigned `type: 'loop'` with different position and loop height to avoid edge overlappings. If the edges are not loop, they will be assigned `type: quadratic` with different control points to avoid edge overlappings. To achieve better rendering result, we recommended configure the `linkCenter` to be `true` while instantiating the Graph.

View File

@ -1,6 +0,0 @@
---
title: 两节点间存在多条边
order: 9
---
两节点间存在多条边时,可以使用 G6 内置的工具函数 `G6.Util.processParallelEdges(data.edges)` 处理。它将会自动检测边数据中两端点相同的边。若为自环边,则它们的 `type` 将会被设置为 `'loop'`,并且自动计算自环高度与位置以放置重叠;若不是自环边,则它们的 `type` 将会被设置为 `'quadratic'` 二次贝塞尔曲线,并自动计算控制点位置,以避免相互重叠。为达到更好的展示效果,建议在实例化图时,将 `linkCenter` 设置为 `true`

View File

@ -967,7 +967,6 @@ graph.on('node:click', async (ev) => {
const unitRadius = 40;
// re-place the clicked node far away the exisiting items
// along the radius from center node to it
debugger;
const focus = graph.getNodeData(focusNode.id);
const vx = nodeModel.data.x - focus.data.x;
const vy = nodeModel.data.y - focus.data.y;

View File

@ -46,8 +46,10 @@
"@antv/layout-wasm": "^1.3.4",
"@antv/util": "^2.0.9",
"@antv/vis-predict-engine": "^0.1.1",
"@faker-js/faker": "^8.0.2",
"@microsoft/api-extractor": "^7.33.6",
"dumi": "^2.2.4",
"google-translate-api-x": "^10.6.7",
"insert-css": "^2.0.0",
"stats.js": "^0.17.0",
"typedoc": "^0.24.8",