mirror of
https://gitee.com/antv/g6.git
synced 2024-11-29 18:28:19 +08:00
feat: style standards; perf: type refine (#4470)
* feat: style standards; perf: type refine * chore: refine
This commit is contained in:
parent
9c8db01fe1
commit
05948c5e7d
@ -1,4 +1,12 @@
|
||||
export const RESERVED_SHAPE_IDS = ['keyShape', 'labelShape', 'iconShape'];
|
||||
export const RESERVED_SHAPE_IDS = [
|
||||
'keyShape',
|
||||
'labelShape',
|
||||
'labelBackgroundShape',
|
||||
'iconShape',
|
||||
'haloShape',
|
||||
'anchorShapes',
|
||||
'badgeShapes',
|
||||
];
|
||||
export const OTHER_SHAPES_FIELD_NAME = 'otherShapes';
|
||||
|
||||
export const DEFAULT_LABEL_BG_PADDING = [4, 4, 4, 4];
|
||||
@ -9,6 +17,7 @@ export const DEFAULT_SHAPE_STYLE = {
|
||||
shadowColor: undefined,
|
||||
shadowBlur: 0,
|
||||
lineDash: undefined,
|
||||
zIndex: 0,
|
||||
};
|
||||
/** Default text style to avoid shape value missing */
|
||||
export const DEFAULT_TEXT_STYLE = {
|
||||
|
@ -1,16 +1,12 @@
|
||||
import { Group } from '@antv/g';
|
||||
import { clone } from '@antv/util';
|
||||
import { EdgeDisplayModel, EdgeModel } from '../types';
|
||||
import { EdgeDisplayModel, EdgeModel, NodeModelData } from '../types';
|
||||
import { EdgeModelData } from '../types/edge';
|
||||
import {
|
||||
DisplayMapper,
|
||||
ItemShapeStyles,
|
||||
ITEM_TYPE,
|
||||
State,
|
||||
} from '../types/item';
|
||||
import { DisplayMapper, State } from '../types/item';
|
||||
import { updateShapes } from '../util/shape';
|
||||
import Item from './item';
|
||||
import Node from './node';
|
||||
import { EdgeStyleSet } from 'types/theme';
|
||||
|
||||
interface IProps {
|
||||
model: EdgeModel;
|
||||
@ -22,7 +18,7 @@ interface IProps {
|
||||
};
|
||||
sourceItem: Node;
|
||||
targetItem: Node;
|
||||
themeStyles: ItemShapeStyles;
|
||||
themeStyles: EdgeStyleSet;
|
||||
}
|
||||
|
||||
export default class Edge extends Item {
|
||||
@ -51,18 +47,10 @@ export default class Edge extends Item {
|
||||
diffState?: { previous: State[]; current: State[] },
|
||||
) {
|
||||
// get the end points
|
||||
const sourceBBox = this.sourceItem.getKeyBBox();
|
||||
const targetBBox = this.targetItem.getKeyBBox();
|
||||
const sourcePoint = {
|
||||
x: sourceBBox.center[0],
|
||||
y: sourceBBox.center[1],
|
||||
z: sourceBBox.center[2],
|
||||
};
|
||||
const targetPoint = {
|
||||
x: targetBBox.center[0],
|
||||
y: targetBBox.center[1],
|
||||
z: targetBBox.center[2],
|
||||
};
|
||||
const { x: sx, y: sy, z: sz } = this.sourceItem.model.data as NodeModelData;
|
||||
const { x: tx, y: ty, z: tz } = this.targetItem.model.data as NodeModelData;
|
||||
const sourcePoint = this.sourceItem.getAnchorPoint({ x: tx, y: ty, z: tz });
|
||||
const targetPoint = this.targetItem.getAnchorPoint({ x: sx, y: sy, z: sz });
|
||||
this.renderExt.mergeStyles(displayModel);
|
||||
const shapeMap = this.renderExt.draw(
|
||||
displayModel,
|
||||
@ -75,8 +63,8 @@ export default class Edge extends Item {
|
||||
|
||||
// add shapes to group, and update shapeMap
|
||||
this.shapeMap = updateShapes(this.shapeMap, shapeMap, this.group);
|
||||
|
||||
const { labelShape } = this.shapeMap;
|
||||
const { haloShape, labelShape, labelBackgroundShape } = this.shapeMap;
|
||||
haloShape?.toBack();
|
||||
labelShape?.toFront();
|
||||
|
||||
super.draw(displayModel, diffData, diffState);
|
||||
|
@ -13,10 +13,11 @@ import {
|
||||
State,
|
||||
} from '../types/item';
|
||||
import { NodeShapeMap } from '../types/node';
|
||||
import { ItemStyleSet } from '../types/theme';
|
||||
import { EdgeStyleSet, NodeStyleSet } from '../types/theme';
|
||||
import { isArrayOverlap } from '../util/array';
|
||||
import { mergeStyles, updateShapes } from '../util/shape';
|
||||
import { isEncode } from '../util/type';
|
||||
import { DEFAULT_MAPPER } from '../util/mapper';
|
||||
|
||||
export default abstract class Item implements IItem {
|
||||
public destroyed = false;
|
||||
@ -116,7 +117,7 @@ export default abstract class Item implements IItem {
|
||||
model: ItemModel,
|
||||
diffData?: { previous: ItemModelData; current: ItemModelData },
|
||||
isReplace?: boolean,
|
||||
themeStyles?: ItemStyleSet,
|
||||
themeStyles?: NodeStyleSet | EdgeStyleSet,
|
||||
) {
|
||||
// 1. merge model into this model
|
||||
this.model = model;
|
||||
@ -170,26 +171,34 @@ export default abstract class Item implements IItem {
|
||||
model: ItemDisplayModel;
|
||||
typeChange?: boolean;
|
||||
} {
|
||||
const { mapper } = this;
|
||||
const { mapper, type } = this;
|
||||
const defaultMapper = DEFAULT_MAPPER[type];
|
||||
|
||||
const { data: innerModelData, ...otherFields } = innerModel;
|
||||
const { current = innerModelData, previous } = diffData || {};
|
||||
|
||||
// === no mapper, displayModel = model ===
|
||||
if (!mapper) {
|
||||
this.displayModel = innerModel; // TODO: need clone?
|
||||
this.displayModel = defaultMapper(innerModel);
|
||||
// compare the previous data and current data to find shape changes
|
||||
let typeChange = false;
|
||||
if (current) {
|
||||
typeChange = Boolean(current.type);
|
||||
}
|
||||
return {
|
||||
model: innerModel,
|
||||
model: this.displayModel,
|
||||
typeChange,
|
||||
};
|
||||
}
|
||||
|
||||
// === mapper is function, displayModel is mapper(model), cannot diff the displayModel, so all the shapes need to be updated ===
|
||||
if (isFunction(mapper)) return { model: (mapper as Function)(innerModel) };
|
||||
if (isFunction(mapper))
|
||||
return {
|
||||
model: {
|
||||
...defaultMapper(innerModel),
|
||||
...(mapper as Function)(innerModel),
|
||||
},
|
||||
};
|
||||
|
||||
// === fields' values in mapper are final value or Encode ===
|
||||
const dataChangedFields = isReplace
|
||||
@ -199,20 +208,49 @@ export default abstract class Item implements IItem {
|
||||
|
||||
let typeChange = false;
|
||||
const { data, ...otherProps } = innerModel;
|
||||
const displayModelData = clone(data);
|
||||
const displayModelData = defaultMapper(innerModel).data; //clone(data);
|
||||
// const defaultMappedModel = defaultMapper(innerModel);
|
||||
Object.keys(mapper).forEach((fieldName) => {
|
||||
const subMapper = mapper[fieldName];
|
||||
if (RESERVED_SHAPE_IDS.includes(fieldName)) {
|
||||
// reserved shapes, fieldName is shapeId
|
||||
debugger;
|
||||
let subMapper = mapper[fieldName];
|
||||
const isReservedShapeId = RESERVED_SHAPE_IDS.includes(fieldName);
|
||||
const isShapeId =
|
||||
RESERVED_SHAPE_IDS.includes(fieldName) ||
|
||||
fieldName === OTHER_SHAPES_FIELD_NAME;
|
||||
|
||||
if ((isShapeId && isEncode(subMapper)) || !isShapeId) {
|
||||
// fields not about shape
|
||||
if (!displayModelData.hasOwnProperty(fieldName)) {
|
||||
displayModelData[fieldName] = {};
|
||||
updateShapeChange({
|
||||
const { changed, value: mappedValue } = updateChange({
|
||||
innerModel,
|
||||
mapper: subMapper,
|
||||
mapper,
|
||||
fieldName,
|
||||
dataChangedFields,
|
||||
shapeConfig: displayModelData[fieldName],
|
||||
});
|
||||
if (isShapeId) {
|
||||
if (!mappedValue) return;
|
||||
subMapper = mappedValue;
|
||||
} else {
|
||||
displayModelData[fieldName] = mappedValue;
|
||||
}
|
||||
if (changed && fieldName === 'type') typeChange = true;
|
||||
} else if (
|
||||
fieldName === 'type' &&
|
||||
(!dataChangedFields || dataChangedFields.includes('type'))
|
||||
) {
|
||||
typeChange = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (isReservedShapeId) {
|
||||
// reserved shapes, fieldName is shapeId
|
||||
displayModelData[fieldName] = displayModelData[fieldName] || {};
|
||||
updateShapeChange({
|
||||
innerModel,
|
||||
mapper: subMapper,
|
||||
dataChangedFields,
|
||||
shapeConfig: displayModelData[fieldName],
|
||||
});
|
||||
} else if (fieldName === OTHER_SHAPES_FIELD_NAME) {
|
||||
// other shapes
|
||||
displayModelData[fieldName] = displayModelData[fieldName] || {};
|
||||
@ -229,23 +267,6 @@ export default abstract class Item implements IItem {
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// fields not about shape
|
||||
if (!displayModelData.hasOwnProperty(fieldName)) {
|
||||
const { changed, value: mappedValue } = updateChange({
|
||||
innerModel,
|
||||
mapper,
|
||||
fieldName,
|
||||
dataChangedFields,
|
||||
});
|
||||
displayModelData[fieldName] = mappedValue;
|
||||
if (changed && fieldName === 'type') typeChange = true;
|
||||
} else if (
|
||||
fieldName === 'type' &&
|
||||
(!dataChangedFields || dataChangedFields.includes('type'))
|
||||
) {
|
||||
typeChange = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
const displayModel = {
|
||||
|
@ -1,11 +1,18 @@
|
||||
import { Group } from '@antv/g';
|
||||
import { clone } from '@antv/util';
|
||||
import { Point } from '../types/common';
|
||||
import { NodeModel } from '../types';
|
||||
import { DisplayMapper, ItemShapeStyles, State } from '../types/item';
|
||||
import { DisplayMapper, State } from '../types/item';
|
||||
import { NodeDisplayModel, NodeModelData } from '../types/node';
|
||||
import { ItemStyleSet } from '../types/theme';
|
||||
import { NodeStyleSet } from '../types/theme';
|
||||
import { updateShapes } from '../util/shape';
|
||||
import Item from './item';
|
||||
import {
|
||||
getCircleIntersectByPoint,
|
||||
getEllipseIntersectByPoint,
|
||||
getNearestPoint,
|
||||
getRectIntersectByPoint,
|
||||
} from 'util/point';
|
||||
|
||||
interface IProps {
|
||||
model: NodeModel;
|
||||
@ -15,11 +22,12 @@ interface IProps {
|
||||
stateMapper: {
|
||||
[stateName: string]: DisplayMapper;
|
||||
};
|
||||
themeStyles: ItemShapeStyles;
|
||||
themeStyles: NodeStyleSet;
|
||||
device?: any; // for 3d shapes
|
||||
}
|
||||
export default class Node extends Item {
|
||||
public type: 'node';
|
||||
private anchorPointsCache: Point[];
|
||||
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
@ -48,17 +56,20 @@ export default class Node extends Item {
|
||||
|
||||
// add shapes to group, and update shapeMap
|
||||
this.shapeMap = updateShapes(prevShapeMap, shapeMap, group);
|
||||
|
||||
this.shapeMap.labelShape?.toFront();
|
||||
const { haloShape, labelShape, labelBackgroundShape } = this.shapeMap;
|
||||
haloShape?.toBack();
|
||||
labelShape?.toFront();
|
||||
labelBackgroundShape?.toBack();
|
||||
|
||||
super.draw(displayModel, diffData, diffState);
|
||||
this.anchorPointsCache = undefined;
|
||||
}
|
||||
|
||||
public update(
|
||||
model: NodeModel,
|
||||
diffData?: { previous: NodeModelData; current: NodeModelData },
|
||||
isReplace?: boolean,
|
||||
themeStyles?: ItemStyleSet,
|
||||
themeStyles?: NodeStyleSet,
|
||||
) {
|
||||
super.update(model, diffData, isReplace, themeStyles);
|
||||
const { data } = this.displayModel;
|
||||
@ -86,4 +97,83 @@ export default class Node extends Item {
|
||||
themeStyles: clone(this.themeStyles),
|
||||
});
|
||||
}
|
||||
|
||||
public getAnchorPoint(point: Point) {
|
||||
const { keyShape } = this.shapeMap;
|
||||
const shapeType = keyShape.nodeName;
|
||||
const { x, y, anchorPoints = [] } = this.model.data as NodeModelData;
|
||||
|
||||
let intersectPoint: Point | null;
|
||||
switch (shapeType) {
|
||||
case 'circle':
|
||||
intersectPoint = getCircleIntersectByPoint(
|
||||
{
|
||||
x,
|
||||
y,
|
||||
r: keyShape.attributes.r,
|
||||
},
|
||||
point,
|
||||
);
|
||||
break;
|
||||
case 'ellipse':
|
||||
intersectPoint = getEllipseIntersectByPoint(
|
||||
{
|
||||
x,
|
||||
y,
|
||||
rx: keyShape.attributes.rx,
|
||||
ry: keyShape.attributes.ry,
|
||||
},
|
||||
point,
|
||||
);
|
||||
break;
|
||||
default:
|
||||
const bbox =
|
||||
this.renderExt.boundsCache?.keyShapeLocal ||
|
||||
keyShape.getLocalBounds();
|
||||
intersectPoint = getRectIntersectByPoint(
|
||||
{
|
||||
x: bbox.halfExtents[0],
|
||||
y: bbox.halfExtents[1],
|
||||
width: bbox.max[0] - bbox.min[0],
|
||||
height: bbox.max[1] - bbox.min[1],
|
||||
},
|
||||
point,
|
||||
);
|
||||
}
|
||||
|
||||
let anchorPointsPositions = this.anchorPointsCache;
|
||||
if (!anchorPointsPositions) {
|
||||
const keyShapeBBox =
|
||||
this.renderExt.boundsCache?.keyShapeLocal ||
|
||||
this.shapeMap.keyShape.getLocalBounds();
|
||||
const keyShapeWidth = keyShapeBBox.max[0] - keyShapeBBox.min[0];
|
||||
const keyShapeHeight = keyShapeBBox.max[1] - keyShapeBBox.min[1];
|
||||
anchorPointsPositions = anchorPoints.map((pointRatio) => {
|
||||
const [xRatio, yRatio] = pointRatio;
|
||||
return {
|
||||
x: keyShapeWidth * (xRatio - 0.5) + x,
|
||||
y: keyShapeHeight * (yRatio - 0.5) + y,
|
||||
};
|
||||
});
|
||||
this.anchorPointsCache = anchorPointsPositions;
|
||||
}
|
||||
|
||||
let linkPoint = intersectPoint;
|
||||
// If the node has anchorPoints in the data, find the nearest anchor point.
|
||||
if (anchorPoints.length) {
|
||||
if (!linkPoint) {
|
||||
// If the linkPoint is failed to calculate.
|
||||
linkPoint = point;
|
||||
}
|
||||
linkPoint = getNearestPoint(
|
||||
anchorPointsPositions,
|
||||
linkPoint,
|
||||
).nearestPoint;
|
||||
}
|
||||
if (!linkPoint) {
|
||||
// If the calculations above are all failed, return the data's position
|
||||
return { x, y };
|
||||
}
|
||||
return linkPoint;
|
||||
}
|
||||
}
|
||||
|
@ -551,7 +551,12 @@ const mergeOneLevelData = (
|
||||
const { data: prevData } = prevModel;
|
||||
const mergedData = {};
|
||||
Object.keys(newData).forEach((key) => {
|
||||
if (isObject(prevData[key]) && isObject(newData[key])) {
|
||||
if (isArray(prevData[key]) || isArray(newData[key])) {
|
||||
mergedData[key] = newData[key];
|
||||
} else if (
|
||||
typeof prevData[key] === 'object' &&
|
||||
typeof newData[key] === 'object'
|
||||
) {
|
||||
mergedData[key] = {
|
||||
...(prevData[key] as object),
|
||||
...(newData[key] as object),
|
||||
|
@ -26,8 +26,10 @@ import { upsertTransientItem } from '../../util/item';
|
||||
import { ITEM_TYPE, ShapeStyle, SHAPE_TYPE } from '../../types/item';
|
||||
import {
|
||||
ThemeSpecification,
|
||||
ItemThemeSpecifications,
|
||||
ItemStyleSet,
|
||||
NodeThemeSpecifications,
|
||||
EdgeThemeSpecifications,
|
||||
NodeStyleSet,
|
||||
EdgeStyleSet,
|
||||
} from '../../types/theme';
|
||||
import { isArray, isObject } from '@antv/util';
|
||||
import { DirectionalLight, AmbientLight } from '@antv/g-plugin-3d';
|
||||
@ -277,7 +279,10 @@ export class ItemController {
|
||||
const { isReplace, previous, current } = nodeUpdate[id];
|
||||
// update the theme if the dataType value is changed
|
||||
let themeStyles;
|
||||
if (previous[nodeDataTypeField] !== current[nodeDataTypeField]) {
|
||||
if (
|
||||
nodeDataTypeField &&
|
||||
previous[nodeDataTypeField] !== current[nodeDataTypeField]
|
||||
) {
|
||||
themeStyles = getThemeStyles(
|
||||
this.nodeDataTypeSet,
|
||||
nodeDataTypeField,
|
||||
@ -488,7 +493,7 @@ export class ItemController {
|
||||
return;
|
||||
}
|
||||
|
||||
const shape = upsertShape(type, String(id), style, transientObjectMap);
|
||||
const { shape } = upsertShape(type, String(id), style, transientObjectMap);
|
||||
shape.style.pointerEvents = capture ? 'auto' : 'none';
|
||||
canvas.getRoot().appendChild(shape);
|
||||
}
|
||||
@ -502,7 +507,7 @@ export class ItemController {
|
||||
*/
|
||||
private renderNodes(
|
||||
models: NodeModel[],
|
||||
nodeTheme: ItemThemeSpecifications = {},
|
||||
nodeTheme: NodeThemeSpecifications = {},
|
||||
) {
|
||||
const { nodeExtensions, nodeGroup, nodeDataTypeSet, graph } = this;
|
||||
const { dataTypeField } = nodeTheme;
|
||||
@ -523,7 +528,7 @@ export class ItemController {
|
||||
containerGroup: nodeGroup,
|
||||
mapper: this.nodeMapper,
|
||||
stateMapper: this.nodeStateMapper,
|
||||
themeStyles: themeStyle,
|
||||
themeStyles: themeStyle as NodeStyleSet,
|
||||
device:
|
||||
graph.rendererType === 'webgl-3d'
|
||||
? // TODO: G type
|
||||
@ -539,7 +544,7 @@ export class ItemController {
|
||||
*/
|
||||
private renderEdges(
|
||||
models: EdgeModel[],
|
||||
edgeTheme: ItemThemeSpecifications = {},
|
||||
edgeTheme: EdgeThemeSpecifications = {},
|
||||
) {
|
||||
const { edgeExtensions, edgeGroup, itemMap, edgeDataTypeSet } = this;
|
||||
const { dataTypeField } = edgeTheme;
|
||||
@ -575,7 +580,7 @@ export class ItemController {
|
||||
stateMapper: this.edgeStateMapper,
|
||||
sourceItem,
|
||||
targetItem,
|
||||
themeStyles: themeStyle,
|
||||
themeStyles: themeStyle as EdgeStyleSet,
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -648,8 +653,8 @@ const getThemeStyles = (
|
||||
dataTypeSet: Set<string>,
|
||||
dataTypeField: string,
|
||||
dataType: string,
|
||||
itemTheme: ItemThemeSpecifications,
|
||||
): ItemStyleSet => {
|
||||
itemTheme: NodeThemeSpecifications | EdgeThemeSpecifications,
|
||||
): NodeStyleSet | EdgeStyleSet => {
|
||||
const { styles: themeStyles } = itemTheme;
|
||||
if (!dataTypeField) {
|
||||
// dataType field is not assigned
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { DisplayObject, Line, Polyline } from '@antv/g';
|
||||
import { AABB, DisplayObject, Line, Polyline } from '@antv/g';
|
||||
import { isNumber } from '@antv/util';
|
||||
import {
|
||||
DEFAULT_LABEL_BG_PADDING,
|
||||
@ -10,31 +10,47 @@ import {
|
||||
EdgeDisplayModel,
|
||||
EdgeModelData,
|
||||
EdgeShapeMap,
|
||||
EdgeShapeStyles,
|
||||
} from '../../../types/edge';
|
||||
import {
|
||||
GShapeStyle,
|
||||
ItemShapeStyles,
|
||||
SHAPE_TYPE,
|
||||
ShapeStyle,
|
||||
State,
|
||||
} from '../../../types/item';
|
||||
import { formatPadding, mergeStyles, upsertShape } from '../../../util/shape';
|
||||
import {
|
||||
formatPadding,
|
||||
isStyleAffectBBox,
|
||||
mergeStyles,
|
||||
upsertShape,
|
||||
} from '../../../util/shape';
|
||||
|
||||
export abstract class BaseEdge {
|
||||
type: string;
|
||||
defaultStyles: ItemShapeStyles = {};
|
||||
themeStyles: ItemShapeStyles;
|
||||
mergedStyles: ItemShapeStyles;
|
||||
defaultStyles: EdgeShapeStyles = {};
|
||||
themeStyles: EdgeShapeStyles;
|
||||
mergedStyles: EdgeShapeStyles;
|
||||
labelPosition: {
|
||||
x: number;
|
||||
y: number;
|
||||
transform: string;
|
||||
isRevert: boolean;
|
||||
};
|
||||
boundsCache: {
|
||||
labelShapeGeometry?: AABB;
|
||||
labelBackgroundShapeGeometry?: AABB;
|
||||
};
|
||||
constructor(props) {
|
||||
const { themeStyles } = props;
|
||||
if (themeStyles) this.themeStyles = themeStyles;
|
||||
this.boundsCache = {};
|
||||
}
|
||||
private mergeStyles(model: EdgeDisplayModel) {
|
||||
public mergeStyles(model: EdgeDisplayModel) {
|
||||
this.mergedStyles = this.getMergedStyles(model);
|
||||
}
|
||||
public getMergedStyles(model: EdgeDisplayModel) {
|
||||
const { data } = model;
|
||||
const dataStyles = {} as ItemShapeStyles;
|
||||
const dataStyles = {} as EdgeShapeStyles;
|
||||
Object.keys(data).forEach((fieldName) => {
|
||||
if (RESERVED_SHAPE_IDS.includes(fieldName))
|
||||
dataStyles[fieldName] = data[fieldName] as ShapeStyle;
|
||||
@ -79,16 +95,12 @@ export abstract class BaseEdge {
|
||||
shapeMap: EdgeShapeMap,
|
||||
diffData?: { previous: EdgeModelData; current: EdgeModelData },
|
||||
diffState?: { previous: State[]; current: State[] },
|
||||
): {
|
||||
labelShape: DisplayObject;
|
||||
[id: string]: DisplayObject;
|
||||
} {
|
||||
): DisplayObject {
|
||||
const { keyShape } = shapeMap;
|
||||
|
||||
const { labelShape: shapeStyle } = this.mergedStyles;
|
||||
const {
|
||||
position,
|
||||
background,
|
||||
offsetX: propsOffsetX,
|
||||
offsetY: propsOffsetY,
|
||||
autoRotate = true,
|
||||
@ -123,6 +135,7 @@ export abstract class BaseEdge {
|
||||
positionPreset.pointRatio[0],
|
||||
);
|
||||
let positionStyle: any = { x: point.x, y: point.y };
|
||||
let isRevert = false;
|
||||
if (autoRotate) {
|
||||
const pointOffset = (keyShape as Line | Polyline).getPoint(
|
||||
positionPreset.pointRatio[1],
|
||||
@ -130,6 +143,18 @@ export abstract class BaseEdge {
|
||||
const angle = Math.atan(
|
||||
(point.y - pointOffset.y) / (point.x - pointOffset.x),
|
||||
); // TODO: NaN
|
||||
|
||||
// revert
|
||||
isRevert = pointOffset.x < point.x;
|
||||
if (isRevert) {
|
||||
if (position === 'start') {
|
||||
positionPreset.textAlign = 'right';
|
||||
positionPreset.offsetX = -4;
|
||||
} else if (position === 'end') {
|
||||
positionPreset.textAlign = 'left';
|
||||
positionPreset.offsetX = 4;
|
||||
}
|
||||
}
|
||||
const offsetX = (
|
||||
propsOffsetX === undefined ? positionPreset.offsetX : propsOffsetX
|
||||
) as number;
|
||||
@ -148,51 +173,88 @@ export abstract class BaseEdge {
|
||||
transform: `rotate(${(angle / Math.PI) * 180})`,
|
||||
};
|
||||
}
|
||||
this.labelPosition = {
|
||||
...positionStyle,
|
||||
isRevert,
|
||||
};
|
||||
const style = {
|
||||
...this.defaultStyles.labelShape,
|
||||
textAlign: positionPreset.textAlign,
|
||||
...positionStyle,
|
||||
...otherStyle,
|
||||
};
|
||||
|
||||
const labelShape = upsertShape('text', 'labelShape', style, shapeMap);
|
||||
const shapes = { labelShape };
|
||||
if (background) {
|
||||
const textBBox = labelShape.getGeometryBounds();
|
||||
// TODO: update type define.
|
||||
// @ts-ignore
|
||||
const { padding: propsPadding, ...backgroundStyle } = background;
|
||||
const padding = formatPadding(propsPadding, DEFAULT_LABEL_BG_PADDING);
|
||||
const bgStyle = {
|
||||
fill: '#fff',
|
||||
radius: 4,
|
||||
...backgroundStyle,
|
||||
x: textBBox.min[0] - padding[3] + style.x,
|
||||
y: textBBox.min[1] - padding[0] + style.y,
|
||||
width: textBBox.max[0] - textBBox.min[0] + padding[1] + padding[3],
|
||||
height: textBBox.max[1] - textBBox.min[1] + padding[0] + padding[2],
|
||||
transform: positionStyle.transform,
|
||||
transformOrigin: 'center',
|
||||
};
|
||||
if (position === 'start') {
|
||||
bgStyle.transformOrigin = `${padding[3]} ${
|
||||
padding[0] + bgStyle.height / 2
|
||||
}`;
|
||||
}
|
||||
if (position === 'end') {
|
||||
bgStyle.transformOrigin = `${padding[3] + bgStyle.width} ${
|
||||
padding[0] + bgStyle.height / 2
|
||||
}`;
|
||||
}
|
||||
|
||||
shapes['labelBgShape'] = upsertShape(
|
||||
'rect',
|
||||
'labelBgShape',
|
||||
bgStyle,
|
||||
shapeMap,
|
||||
);
|
||||
const { shape, updateStyles } = this.upsertShape(
|
||||
'text',
|
||||
'labelShape',
|
||||
style,
|
||||
shapeMap,
|
||||
);
|
||||
if (isStyleAffectBBox('text', updateStyles)) {
|
||||
this.boundsCache.labelShapeGeometry = shape.getGeometryBounds();
|
||||
}
|
||||
return shapes;
|
||||
return shape;
|
||||
}
|
||||
|
||||
public drawLabelBackgroundShape(
|
||||
model: EdgeDisplayModel,
|
||||
shapeMap: EdgeShapeMap,
|
||||
diffData?: { previous: EdgeModelData; current: EdgeModelData },
|
||||
diffState?: { previous: State[]; current: State[] },
|
||||
): DisplayObject {
|
||||
const { labelShape } = shapeMap;
|
||||
if (!labelShape || !model.data.labelShape) return;
|
||||
|
||||
const { labelBackgroundShape, labelShape: labelShapeStyle } =
|
||||
this.mergedStyles;
|
||||
|
||||
const textBBox =
|
||||
this.boundsCache.labelShapeGeometry || labelShape.getGeometryBounds();
|
||||
const { x, y, transform, isRevert } = this.labelPosition;
|
||||
const { padding: propsPadding, ...backgroundStyle } = labelBackgroundShape;
|
||||
const padding = formatPadding(propsPadding, DEFAULT_LABEL_BG_PADDING);
|
||||
const textWidth = textBBox.max[0] - textBBox.min[0];
|
||||
const textHeight = textBBox.max[1] - textBBox.min[1];
|
||||
const bgStyle = {
|
||||
fill: '#fff',
|
||||
...backgroundStyle,
|
||||
x: textBBox.min[0] - padding[3] + x,
|
||||
y: textBBox.min[1] - padding[0] + y,
|
||||
width: textWidth + padding[1] + padding[3],
|
||||
height: textHeight + padding[0] + padding[2],
|
||||
transform: transform,
|
||||
};
|
||||
if (labelShapeStyle.position === 'start') {
|
||||
if (isRevert) {
|
||||
bgStyle.transformOrigin = `${bgStyle.width - padding[1]} ${
|
||||
bgStyle.height / 2
|
||||
}`;
|
||||
} else {
|
||||
bgStyle.transformOrigin = `${padding[3]} ${bgStyle.height / 2}`;
|
||||
}
|
||||
} else if (labelShapeStyle.position === 'end') {
|
||||
if (isRevert) {
|
||||
bgStyle.transformOrigin = `${padding[3]} ${bgStyle.height / 2}`;
|
||||
} else {
|
||||
bgStyle.transformOrigin = `${bgStyle.width - padding[1]} ${
|
||||
bgStyle.height / 2
|
||||
}`;
|
||||
}
|
||||
} else {
|
||||
bgStyle.transformOrigin = `${textWidth / 2 + padding[3]} ${
|
||||
textHeight / 2 + padding[0]
|
||||
}`;
|
||||
}
|
||||
|
||||
const { shape, updateStyles } = this.upsertShape(
|
||||
'rect',
|
||||
'labelBackgroundShape',
|
||||
bgStyle,
|
||||
shapeMap,
|
||||
);
|
||||
if (isStyleAffectBBox('rect', updateStyles)) {
|
||||
this.boundsCache.labelBackgroundShapeGeometry = shape.getGeometryBounds();
|
||||
}
|
||||
return shape;
|
||||
}
|
||||
|
||||
public drawIconShape(
|
||||
@ -201,69 +263,112 @@ export abstract class BaseEdge {
|
||||
diffData?: { previous: EdgeModelData; current: EdgeModelData },
|
||||
diffState?: { previous: State[]; current: State[] },
|
||||
): DisplayObject {
|
||||
const { labelShape, labelBgShape, keyShape } = shapeMap;
|
||||
const { labelShape, labelBackgroundShape, keyShape } = shapeMap;
|
||||
const { iconShape: shapeStyle, labelShape: labelShapeProps } =
|
||||
this.mergedStyles;
|
||||
|
||||
const iconShapeType = shapeStyle.text ? 'text' : 'image';
|
||||
if (iconShapeType === 'text') {
|
||||
shapeStyle.textAlign = 'left';
|
||||
shapeStyle.textBaseline = 'top';
|
||||
}
|
||||
const { width, height, fontSize } = shapeStyle;
|
||||
const {
|
||||
width,
|
||||
height,
|
||||
fontSize,
|
||||
text,
|
||||
offsetX = 0,
|
||||
offsetY = 0,
|
||||
} = shapeStyle;
|
||||
const w = (width || fontSize) as number;
|
||||
const h = (height || fontSize) as number;
|
||||
|
||||
const iconShapeType = text ? 'text' : 'image';
|
||||
if (iconShapeType === 'text') {
|
||||
shapeStyle.textAlign = 'left';
|
||||
shapeStyle.textBaseline = 'top';
|
||||
shapeStyle.fontSize = w;
|
||||
} else {
|
||||
shapeStyle.width = w;
|
||||
shapeStyle.height = h;
|
||||
}
|
||||
|
||||
if (labelShapeProps) {
|
||||
const referShape = labelBgShape || labelShape;
|
||||
const { min: referMin, halfExtents: referHalExtents } =
|
||||
const referShape = labelBackgroundShape || labelShape;
|
||||
const referBounds =
|
||||
this.boundsCache.labelBackgroundShapeGeometry ||
|
||||
this.boundsCache.labelShapeGeometry ||
|
||||
referShape.getGeometryBounds();
|
||||
const {
|
||||
min: referMin,
|
||||
max: referMax,
|
||||
halfExtents: referHalExtents,
|
||||
} = referBounds;
|
||||
const referHeight = referMax[1] - referMin[1];
|
||||
const referWidth = referMax[0] - referMin[0];
|
||||
const {
|
||||
x: referX,
|
||||
y: referY,
|
||||
transform: referTransform,
|
||||
textAlign: labelAlign,
|
||||
} = referShape.attributes;
|
||||
shapeStyle.x = referMin[0] - w - 4 + referX;
|
||||
shapeStyle.y = referMin[1] + 2 + referY;
|
||||
const { textAlign: labelAlign } = labelShape.attributes;
|
||||
shapeStyle.x = referMin[0] - w + 4 + referX + offsetX;
|
||||
shapeStyle.y = referMin[1] + (referHeight - h) / 2 + referY + offsetY;
|
||||
if (referTransform) {
|
||||
shapeStyle.transform = referTransform;
|
||||
if (labelAlign === 'right') {
|
||||
shapeStyle.transformOrigin = `${w + 4 + referHalExtents[0] * 2}px ${
|
||||
h / 2
|
||||
}px`;
|
||||
shapeStyle.transformOrigin = `${
|
||||
referWidth / 2 - w / 2 + 4 + referHalExtents[0] - offsetX
|
||||
} ${h / 2 - offsetY}`;
|
||||
} else if (labelAlign === 'left') {
|
||||
shapeStyle.transformOrigin = `${w + 4}px ${h / 2}px`;
|
||||
shapeStyle.transformOrigin = `${w + 4 - offsetX} ${h / 2 - offsetY}`;
|
||||
} else {
|
||||
// labelShape align 'center'
|
||||
shapeStyle.transformOrigin = `${w + 4 + referHalExtents[0]}px ${
|
||||
h / 2
|
||||
}px`;
|
||||
shapeStyle.transformOrigin = `${(w + referWidth) / 2 - offsetX} ${
|
||||
h / 2 - offsetY
|
||||
}`;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const midPoint = (keyShape as Line | Polyline).getPoint(0.5);
|
||||
shapeStyle.x = midPoint.x;
|
||||
shapeStyle.y = midPoint.y;
|
||||
shapeStyle.x = midPoint.x + offsetX;
|
||||
shapeStyle.y = midPoint.y + offsetY;
|
||||
// TODO: rotate
|
||||
}
|
||||
|
||||
// TODO: update type define.
|
||||
return upsertShape(
|
||||
return this.upsertShape(
|
||||
iconShapeType,
|
||||
'iconShape',
|
||||
shapeStyle as unknown as GShapeStyle,
|
||||
shapeStyle as GShapeStyle,
|
||||
shapeMap,
|
||||
);
|
||||
).shape;
|
||||
}
|
||||
|
||||
public drawHaloShape(
|
||||
model: EdgeDisplayModel,
|
||||
shapeMap: EdgeShapeMap,
|
||||
diffData?: { previous: EdgeModelData; current: EdgeModelData },
|
||||
diffState?: { previous: State[]; current: State[] },
|
||||
): DisplayObject {
|
||||
const { keyShape } = shapeMap;
|
||||
const { haloShape: haloShapeStyle } = this.mergedStyles;
|
||||
const { nodeName, attributes } = keyShape;
|
||||
return this.upsertShape(
|
||||
nodeName as SHAPE_TYPE,
|
||||
'haloShape',
|
||||
{
|
||||
...attributes,
|
||||
...haloShapeStyle,
|
||||
isBillboard: true,
|
||||
},
|
||||
shapeMap,
|
||||
).shape;
|
||||
}
|
||||
|
||||
public upsertShape(
|
||||
type: SHAPE_TYPE,
|
||||
id: string,
|
||||
style: { [shapeAttr: string]: unknown },
|
||||
style: ShapeStyle,
|
||||
shapeMap: { [shapeId: string]: DisplayObject },
|
||||
): DisplayObject {
|
||||
// TODO: update type define.
|
||||
return upsertShape(type, id, style as unknown as GShapeStyle, shapeMap);
|
||||
): {
|
||||
updateStyles: ShapeStyle;
|
||||
shape: DisplayObject;
|
||||
} {
|
||||
return upsertShape(type, id, style as GShapeStyle, shapeMap);
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { isStyleAffectBBox } from 'util/shape';
|
||||
import { Point } from '../../../types/common';
|
||||
import {
|
||||
EdgeDisplayModel,
|
||||
@ -35,6 +36,7 @@ export class LineEdge extends BaseEdge {
|
||||
const { data = {} } = model;
|
||||
|
||||
let shapes: EdgeShapeMap = { keyShape: undefined };
|
||||
|
||||
shapes.keyShape = this.drawKeyShape(
|
||||
model,
|
||||
sourcePoint,
|
||||
@ -42,13 +44,27 @@ export class LineEdge extends BaseEdge {
|
||||
shapeMap,
|
||||
diffData,
|
||||
);
|
||||
if (data.labelShape)
|
||||
shapes = {
|
||||
...shapes,
|
||||
...this.drawLabelShape(model, shapeMap, diffData),
|
||||
};
|
||||
if (data.iconShape)
|
||||
|
||||
if (data.haloShape) {
|
||||
shapes.haloShape = this.drawHaloShape(model, shapeMap, diffData);
|
||||
}
|
||||
|
||||
if (data.labelShape) {
|
||||
shapes.labelShape = this.drawLabelShape(model, shapeMap, diffData);
|
||||
}
|
||||
|
||||
// labelBackgroundShape
|
||||
if (data.labelBackgroundShape) {
|
||||
shapes.labelBackgroundShape = this.drawLabelBackgroundShape(
|
||||
model,
|
||||
shapeMap,
|
||||
diffData,
|
||||
);
|
||||
}
|
||||
|
||||
if (data.iconShape) {
|
||||
shapes.iconShape = this.drawIconShape(model, shapeMap, diffData);
|
||||
}
|
||||
|
||||
// TODO: other shapes
|
||||
|
||||
@ -63,11 +79,9 @@ export class LineEdge extends BaseEdge {
|
||||
diffState?: { previous: State[]; current: State[] },
|
||||
) {
|
||||
const { keyShape: keyShapeStyle } = this.mergedStyles;
|
||||
const keyShape = this.upsertShape(
|
||||
return this.upsertShape(
|
||||
'line',
|
||||
'keyShape',
|
||||
// TODO: update type define.
|
||||
// @ts-ignore
|
||||
{
|
||||
...keyShapeStyle,
|
||||
x1: sourcePoint.x,
|
||||
@ -79,7 +93,6 @@ export class LineEdge extends BaseEdge {
|
||||
isBillboard: true,
|
||||
},
|
||||
shapeMap,
|
||||
);
|
||||
return keyShape;
|
||||
).shape;
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { DisplayObject } from '@antv/g';
|
||||
import { AABB, DisplayObject, ImageStyleProps, TextStyleProps } from '@antv/g';
|
||||
import {
|
||||
DEFAULT_LABEL_BG_PADDING,
|
||||
OTHER_SHAPES_FIELD_NAME,
|
||||
@ -7,34 +7,54 @@ import {
|
||||
import { NodeDisplayModel } from '../../../types';
|
||||
import {
|
||||
GShapeStyle,
|
||||
ItemShapeStyles,
|
||||
SHAPE_TYPE,
|
||||
SHAPE_TYPE_3D,
|
||||
ShapeStyle,
|
||||
State,
|
||||
} from '../../../types/item';
|
||||
import { NodeModelData, NodeShapeMap } from '../../../types/node';
|
||||
import { formatPadding, mergeStyles, upsertShape } from '../../../util/shape';
|
||||
import {
|
||||
NodeModelData,
|
||||
NodeShapeMap,
|
||||
NodeShapeStyles,
|
||||
} from '../../../types/node';
|
||||
import {
|
||||
formatPadding,
|
||||
isStyleAffectBBox,
|
||||
mergeStyles,
|
||||
upsertShape,
|
||||
} from '../../../util/shape';
|
||||
|
||||
export abstract class BaseNode {
|
||||
type: string;
|
||||
defaultStyles: ItemShapeStyles;
|
||||
themeStyles: ItemShapeStyles;
|
||||
mergedStyles: ItemShapeStyles;
|
||||
defaultStyles: NodeShapeStyles;
|
||||
themeStyles: NodeShapeStyles;
|
||||
mergedStyles: NodeShapeStyles;
|
||||
boundsCache: {
|
||||
keyShapeLocal?: AABB;
|
||||
labelShapeLocal?: AABB;
|
||||
};
|
||||
constructor(props) {
|
||||
const { themeStyles } = props;
|
||||
if (themeStyles) this.themeStyles = themeStyles;
|
||||
this.boundsCache = {};
|
||||
}
|
||||
public mergeStyles(model: NodeDisplayModel) {
|
||||
this.mergedStyles = this.getMergedStyles(model);
|
||||
}
|
||||
public getMergedStyles(model: NodeDisplayModel) {
|
||||
const { data } = model;
|
||||
const dataStyles = {} as ItemShapeStyles;
|
||||
const dataStyles = {} as NodeShapeStyles;
|
||||
Object.keys(data).forEach((fieldName) => {
|
||||
if (RESERVED_SHAPE_IDS.includes(fieldName))
|
||||
dataStyles[fieldName] = data[fieldName] as ShapeStyle;
|
||||
else if (fieldName === OTHER_SHAPES_FIELD_NAME) {
|
||||
if (RESERVED_SHAPE_IDS.includes(fieldName)) {
|
||||
if (fieldName === 'BadgeShapes') {
|
||||
Object.keys(data[fieldName]).forEach(
|
||||
(badgeShapeId) =>
|
||||
(dataStyles[badgeShapeId] = data[fieldName][badgeShapeId]),
|
||||
);
|
||||
} else {
|
||||
dataStyles[fieldName] = data[fieldName] as ShapeStyle;
|
||||
}
|
||||
} else if (fieldName === OTHER_SHAPES_FIELD_NAME) {
|
||||
Object.keys(data[fieldName]).forEach(
|
||||
(otherShapeId) =>
|
||||
(dataStyles[otherShapeId] = data[fieldName][otherShapeId]),
|
||||
@ -81,17 +101,15 @@ export abstract class BaseNode {
|
||||
shapeMap: NodeShapeMap,
|
||||
diffData?: { previous: NodeModelData; current: NodeModelData },
|
||||
diffState?: { oldState: State[]; newState: State[] },
|
||||
): {
|
||||
labelShape: DisplayObject;
|
||||
[id: string]: DisplayObject;
|
||||
} {
|
||||
): DisplayObject {
|
||||
const { keyShape } = shapeMap;
|
||||
const keyShapeBox = keyShape.getGeometryBounds();
|
||||
this.boundsCache.keyShapeLocal =
|
||||
this.boundsCache.keyShapeLocal || keyShape.getLocalBounds();
|
||||
const keyShapeBox = this.boundsCache.keyShapeLocal;
|
||||
const { labelShape: shapeStyle } = this.mergedStyles;
|
||||
|
||||
const {
|
||||
position,
|
||||
background,
|
||||
offsetX: propsOffsetX,
|
||||
offsetY: propsOffsetY,
|
||||
...otherStyle
|
||||
@ -145,46 +163,60 @@ export abstract class BaseNode {
|
||||
...positionPreset,
|
||||
...otherStyle,
|
||||
};
|
||||
const labelShape = upsertShape('text', 'labelShape', style, shapeMap);
|
||||
const shapes = { labelShape };
|
||||
if (background) {
|
||||
const textBBox = labelShape.getGeometryBounds();
|
||||
// TODO: update type define.
|
||||
// @ts-ignore
|
||||
const { padding: propsPadding, ...backgroundStyle } = background;
|
||||
const padding = formatPadding(propsPadding, DEFAULT_LABEL_BG_PADDING);
|
||||
const bgStyle: any = {
|
||||
fill: '#fff',
|
||||
radius: 4,
|
||||
...backgroundStyle,
|
||||
x: textBBox.min[0] - padding[3] + style.x,
|
||||
y: textBBox.min[1] - padding[0] + style.y,
|
||||
width: textBBox.max[0] - textBBox.min[0] + padding[1] + padding[3],
|
||||
height: textBBox.max[1] - textBBox.min[1] + padding[0] + padding[2],
|
||||
};
|
||||
if (style.stransform) {
|
||||
bgStyle.transform = style.transform;
|
||||
bgStyle.transformOrigin = 'center';
|
||||
if (style.textAlign === 'left') {
|
||||
bgStyle.transformOrigin = `${padding[3]} ${
|
||||
padding[0] + bgStyle.height / 2
|
||||
}`;
|
||||
}
|
||||
if (style.textAlign === 'right') {
|
||||
bgStyle.transformOrigin = `${padding[3] + bgStyle.width} ${
|
||||
padding[0] + bgStyle.height / 2
|
||||
}`;
|
||||
}
|
||||
}
|
||||
const { shape, updateStyles } = this.upsertShape(
|
||||
'text',
|
||||
'labelShape',
|
||||
style,
|
||||
shapeMap,
|
||||
);
|
||||
|
||||
shapes['labelBgShape'] = upsertShape(
|
||||
'rect',
|
||||
'labelBgShape',
|
||||
bgStyle,
|
||||
shapeMap,
|
||||
);
|
||||
if (isStyleAffectBBox('text', updateStyles)) {
|
||||
this.boundsCache.labelShapeLocal = undefined;
|
||||
}
|
||||
return shapes;
|
||||
return shape;
|
||||
}
|
||||
|
||||
public drawLabelBackgroundShape(
|
||||
model: NodeDisplayModel,
|
||||
shapeMap: NodeShapeMap,
|
||||
diffData?: { previous: NodeModelData; current: NodeModelData },
|
||||
diffState?: { oldState: State[]; newState: State[] },
|
||||
): DisplayObject {
|
||||
const { labelShape } = shapeMap;
|
||||
if (!labelShape || !model.data.labelShape) return;
|
||||
this.boundsCache.labelShapeLocal =
|
||||
this.boundsCache.labelShapeLocal || labelShape.getLocalBounds();
|
||||
const { labelShapeLocal: textBBox } = this.boundsCache;
|
||||
|
||||
const { padding: propsPadding, ...backgroundStyle } =
|
||||
this.mergedStyles.labelBackgroundShape;
|
||||
const padding = formatPadding(propsPadding, DEFAULT_LABEL_BG_PADDING);
|
||||
const bgStyle: any = {
|
||||
fill: '#fff',
|
||||
...backgroundStyle,
|
||||
x: textBBox.min[0] - padding[3],
|
||||
y: textBBox.min[1] - padding[0],
|
||||
width: textBBox.max[0] - textBBox.min[0] + padding[1] + padding[3],
|
||||
height: textBBox.max[1] - textBBox.min[1] + padding[0] + padding[2],
|
||||
};
|
||||
const labelShapeAttr = labelShape.attributes;
|
||||
if (labelShapeAttr.transform) {
|
||||
bgStyle.transform = labelShapeAttr.transform;
|
||||
bgStyle.transformOrigin = 'center';
|
||||
if (labelShapeAttr.textAlign === 'left') {
|
||||
bgStyle.transformOrigin = `${padding[3]} ${
|
||||
padding[0] + bgStyle.height / 2
|
||||
}`;
|
||||
}
|
||||
if (labelShapeAttr.textAlign === 'right') {
|
||||
bgStyle.transformOrigin = `${padding[3] + bgStyle.width} ${
|
||||
padding[0] + bgStyle.height / 2
|
||||
}`;
|
||||
}
|
||||
}
|
||||
|
||||
return this.upsertShape('rect', 'labelBackgroundShape', bgStyle, shapeMap)
|
||||
.shape;
|
||||
}
|
||||
|
||||
public drawIconShape(
|
||||
@ -193,26 +225,237 @@ export abstract class BaseNode {
|
||||
diffData?: { previous: NodeModelData; current: NodeModelData },
|
||||
diffState?: { oldState: State[]; newState: State[] },
|
||||
): DisplayObject {
|
||||
const { iconShape } = model.data || {};
|
||||
const { iconShape: shapeStyle } = this.mergedStyles;
|
||||
const iconShapeType = shapeStyle.text ? 'text' : 'image';
|
||||
const {
|
||||
width,
|
||||
height,
|
||||
fontSize,
|
||||
text,
|
||||
offsetX = 0,
|
||||
offsetY = 0,
|
||||
} = shapeStyle;
|
||||
const w = (width || fontSize) as number;
|
||||
const h = (height || fontSize) as number;
|
||||
const iconShapeType = text ? 'text' : 'image';
|
||||
if (iconShapeType === 'image') {
|
||||
const { width, height } = shapeStyle;
|
||||
if (!Object.prototype.hasOwnProperty.call(iconShape, 'x'))
|
||||
shapeStyle.x = -width / 2;
|
||||
if (!Object.prototype.hasOwnProperty.call(iconShape, 'y'))
|
||||
shapeStyle.y = -height / 2;
|
||||
shapeStyle.x = -w / 2 + offsetX;
|
||||
shapeStyle.y = -h / 2 + offsetY;
|
||||
shapeStyle.width = w;
|
||||
shapeStyle.height = h;
|
||||
} else {
|
||||
shapeStyle.textAlign = 'center';
|
||||
shapeStyle.textBaseline = 'middle';
|
||||
shapeStyle.x = offsetX;
|
||||
shapeStyle.y = offsetY;
|
||||
shapeStyle.fontSize = w;
|
||||
}
|
||||
// TODO: update type define.
|
||||
return upsertShape(
|
||||
|
||||
return this.upsertShape(
|
||||
iconShapeType,
|
||||
'iconShape',
|
||||
shapeStyle as unknown as GShapeStyle,
|
||||
shapeStyle as GShapeStyle,
|
||||
shapeMap,
|
||||
);
|
||||
).shape;
|
||||
}
|
||||
|
||||
public drawHaloShape(
|
||||
model: NodeDisplayModel,
|
||||
shapeMap: NodeShapeMap,
|
||||
diffData?: { previous: NodeModelData; current: NodeModelData },
|
||||
diffState?: { previous: State[]; current: State[] },
|
||||
): DisplayObject {
|
||||
const { keyShape } = shapeMap;
|
||||
const { haloShape: haloShapeStyle } = this.mergedStyles;
|
||||
const { nodeName, attributes } = keyShape;
|
||||
return this.upsertShape(
|
||||
nodeName as SHAPE_TYPE,
|
||||
'haloShape',
|
||||
{
|
||||
...attributes,
|
||||
...haloShapeStyle,
|
||||
isBillboard: true,
|
||||
},
|
||||
shapeMap,
|
||||
).shape;
|
||||
}
|
||||
|
||||
public drawAnchorShapes(
|
||||
model: NodeDisplayModel,
|
||||
shapeMap: NodeShapeMap,
|
||||
diffData?: { previous: NodeModelData; current: NodeModelData },
|
||||
diffState?: { previous: State[]; current: State[] },
|
||||
): {
|
||||
[shapeId: string]: DisplayObject;
|
||||
} {
|
||||
const { anchorShapes: configs, keyShape: keyShapeStyle } =
|
||||
this.mergedStyles;
|
||||
const commonStyle = {};
|
||||
const individualConfigs = [];
|
||||
Object.keys(configs).forEach((key) => {
|
||||
const value = configs[key];
|
||||
if (typeof value === 'object' && value.position) {
|
||||
individualConfigs.push(value);
|
||||
} else {
|
||||
commonStyle[key] = value;
|
||||
}
|
||||
});
|
||||
|
||||
if (!individualConfigs.length) return;
|
||||
this.boundsCache.keyShapeLocal =
|
||||
this.boundsCache.keyShapeLocal || shapeMap.keyShape.getLocalBounds();
|
||||
const keyShapeBBox = this.boundsCache.keyShapeLocal;
|
||||
const keyShapeWidth = keyShapeBBox.max[0] - keyShapeBBox.min[0];
|
||||
const keyShapeHeight = keyShapeBBox.max[1] - keyShapeBBox.min[1];
|
||||
|
||||
const shapes = {};
|
||||
individualConfigs.forEach((config, i) => {
|
||||
const { position, fill = keyShapeStyle.fill, ...style } = config;
|
||||
const id = `anchorShape${i}`;
|
||||
shapes[id] = this.upsertShape(
|
||||
'circle',
|
||||
id,
|
||||
{
|
||||
cx: keyShapeWidth * (position[0] - 0.5),
|
||||
cy: keyShapeHeight * (position[1] - 0.5),
|
||||
fill,
|
||||
...commonStyle,
|
||||
...style,
|
||||
} as GShapeStyle,
|
||||
shapeMap,
|
||||
).shape;
|
||||
});
|
||||
return shapes;
|
||||
}
|
||||
|
||||
public drawBadgeShapes(
|
||||
model: NodeDisplayModel,
|
||||
shapeMap: NodeShapeMap,
|
||||
diffData?: { previous: NodeModelData; current: NodeModelData },
|
||||
diffState?: { previous: State[]; current: State[] },
|
||||
): {
|
||||
[shapeId: string]: DisplayObject;
|
||||
} {
|
||||
const configs = this.mergedStyles.badgeShapes;
|
||||
const commonStyle = {};
|
||||
const individualConfigs = [];
|
||||
Object.keys(configs).forEach((key) => {
|
||||
const value = configs[key];
|
||||
if (typeof value === 'object' && value.position) {
|
||||
individualConfigs.push(value);
|
||||
} else {
|
||||
commonStyle[key] = value;
|
||||
}
|
||||
});
|
||||
|
||||
if (!individualConfigs.length) return {};
|
||||
this.boundsCache.keyShapeLocal =
|
||||
this.boundsCache.keyShapeLocal || shapeMap.keyShape.getLocalBounds();
|
||||
const { keyShapeLocal: keyShapeBBox } = this.boundsCache;
|
||||
const keyShapeWidth = keyShapeBBox.max[0] - keyShapeBBox.min[0];
|
||||
const shapes = {};
|
||||
individualConfigs.forEach((config) => {
|
||||
const { position, ...individualStyle } = config;
|
||||
const id = `${position}BadgeShape`;
|
||||
const style = {
|
||||
...commonStyle,
|
||||
...individualStyle,
|
||||
};
|
||||
const {
|
||||
text = '',
|
||||
type,
|
||||
color,
|
||||
size = keyShapeWidth / 3,
|
||||
textColor,
|
||||
zIndex = 2,
|
||||
offsetX = 0,
|
||||
offsetY = 0,
|
||||
...otherStyles
|
||||
} = style;
|
||||
|
||||
const bgHeight = size as number;
|
||||
|
||||
let pos = { x: 0, y: 0 };
|
||||
switch (position) {
|
||||
case 'rightTop':
|
||||
pos.x = keyShapeBBox.max[0] - bgHeight / 2 + offsetX;
|
||||
pos.y = keyShapeBBox.min[1] + size / 4 + offsetY;
|
||||
break;
|
||||
case 'right':
|
||||
pos.x = keyShapeBBox.max[0] - bgHeight / 2 + offsetX;
|
||||
pos.y = offsetY;
|
||||
break;
|
||||
case 'rightBottom':
|
||||
case 'bottomRight':
|
||||
pos.x = keyShapeBBox.max[0] - bgHeight / 2 + offsetX;
|
||||
pos.y = keyShapeBBox.max[1] - size / 4 + offsetY;
|
||||
break;
|
||||
case 'leftTop':
|
||||
case 'topLeft':
|
||||
pos.x = keyShapeBBox.min[0] + bgHeight / 2 + offsetX;
|
||||
pos.y = keyShapeBBox.min[1] + size / 4 + offsetY;
|
||||
break;
|
||||
case 'left':
|
||||
pos.x = keyShapeBBox.min[0] + bgHeight / 2 + offsetX;
|
||||
pos.y = offsetY;
|
||||
break;
|
||||
case 'leftBottom':
|
||||
case 'bottomLeft':
|
||||
pos.x = keyShapeBBox.min[0] + bgHeight / 2 + offsetX;
|
||||
pos.y = keyShapeBBox.max[1] - size / 4 + offsetY;
|
||||
break;
|
||||
case 'top':
|
||||
pos.x = offsetX;
|
||||
pos.y = keyShapeBBox.min[1] + size / 4;
|
||||
break;
|
||||
default:
|
||||
// bottom
|
||||
pos.x = offsetX;
|
||||
pos.y = keyShapeBBox.max[1] - size / 4;
|
||||
break;
|
||||
}
|
||||
|
||||
// a radius rect (as container) + a text / icon
|
||||
shapes[id] = this.upsertShape(
|
||||
'text',
|
||||
id,
|
||||
{
|
||||
text,
|
||||
fill: textColor,
|
||||
fontSize: bgHeight - 2,
|
||||
x: pos.x,
|
||||
y: pos.y,
|
||||
...otherStyles,
|
||||
textAlign: position.includes('right') ? 'left' : 'right',
|
||||
textBaseline: 'middle',
|
||||
zIndex: (zIndex as number) + 1,
|
||||
} as GShapeStyle,
|
||||
shapeMap,
|
||||
).shape;
|
||||
const bbox = shapes[id].getLocalBounds();
|
||||
const bgShapeId = `${position}BadgeBackgroundShape`;
|
||||
const bgWidth =
|
||||
(text as string).length <= 1
|
||||
? bgHeight
|
||||
: Math.max(bgHeight, bbox.max[0] - bbox.min[0]) + 8;
|
||||
shapes[bgShapeId] = this.upsertShape(
|
||||
'rect',
|
||||
bgShapeId,
|
||||
{
|
||||
text,
|
||||
fill: color,
|
||||
height: bgHeight,
|
||||
width: bgWidth,
|
||||
x: bbox.min[0] - 3, // begin at the border, minus half height
|
||||
y: bbox.min[1],
|
||||
radius: bgHeight / 2,
|
||||
zIndex,
|
||||
...otherStyles,
|
||||
} as GShapeStyle,
|
||||
shapeMap,
|
||||
).shape;
|
||||
});
|
||||
|
||||
return shapes;
|
||||
}
|
||||
|
||||
public drawOtherShapes(
|
||||
@ -227,15 +470,12 @@ export abstract class BaseNode {
|
||||
public upsertShape(
|
||||
type: SHAPE_TYPE | SHAPE_TYPE_3D,
|
||||
id: string,
|
||||
style: { [shapeAttr: string]: unknown },
|
||||
style: ShapeStyle,
|
||||
shapeMap: { [shapeId: string]: DisplayObject },
|
||||
): DisplayObject {
|
||||
// TODO: update type define.
|
||||
return upsertShape(
|
||||
type as SHAPE_TYPE,
|
||||
id,
|
||||
style as unknown as GShapeStyle,
|
||||
shapeMap,
|
||||
);
|
||||
): {
|
||||
updateStyles: ShapeStyle;
|
||||
shape: DisplayObject;
|
||||
} {
|
||||
return upsertShape(type as SHAPE_TYPE, id, style as GShapeStyle, shapeMap);
|
||||
}
|
||||
}
|
||||
|
@ -6,18 +6,22 @@ import {
|
||||
ItemShapeStyles,
|
||||
SHAPE_TYPE,
|
||||
SHAPE_TYPE_3D,
|
||||
ShapeStyle,
|
||||
State,
|
||||
} from '../../../types/item';
|
||||
import { NodeModelData, NodeShapeMap } from '../../../types/node';
|
||||
import { formatPadding, mergeStyles, upsertShape } from '../../../util/shape';
|
||||
import {
|
||||
NodeModelData,
|
||||
NodeShapeMap,
|
||||
NodeShapeStyles,
|
||||
} from '../../../types/node';
|
||||
import { upsertShape3D } from '../../../util/shape3d';
|
||||
import { BaseNode } from './base';
|
||||
|
||||
export abstract class BaseNode3D extends BaseNode {
|
||||
type: string;
|
||||
defaultStyles: ItemShapeStyles;
|
||||
themeStyles: ItemShapeStyles;
|
||||
mergedStyles: ItemShapeStyles;
|
||||
themeStyles: NodeShapeStyles;
|
||||
mergedStyles: NodeShapeStyles;
|
||||
device: any; // for 3d renderer
|
||||
constructor(props) {
|
||||
super(props);
|
||||
@ -30,110 +34,8 @@ export abstract class BaseNode3D extends BaseNode {
|
||||
shapeMap: NodeShapeMap,
|
||||
diffData?: { previous: NodeModelData; current: NodeModelData },
|
||||
diffState?: { oldState: State[]; newState: State[] },
|
||||
): {
|
||||
labelShape: DisplayObject;
|
||||
[id: string]: DisplayObject;
|
||||
} {
|
||||
const { keyShape } = shapeMap;
|
||||
const keyShapeBox = keyShape.getGeometryBounds();
|
||||
const { labelShape: shapeStyle } = this.mergedStyles;
|
||||
|
||||
const {
|
||||
position,
|
||||
background,
|
||||
offsetX: propsOffsetX,
|
||||
offsetY: propsOffsetY,
|
||||
...otherStyle
|
||||
} = shapeStyle;
|
||||
const positionPreset = {
|
||||
x: keyShapeBox.center[0],
|
||||
y: keyShapeBox.max[1],
|
||||
textBaseline: 'top',
|
||||
textAlign: 'center',
|
||||
offsetX: 0,
|
||||
offsetY: 0,
|
||||
};
|
||||
switch (position) {
|
||||
case 'center':
|
||||
positionPreset.y = keyShapeBox.center[1];
|
||||
break;
|
||||
case 'top':
|
||||
positionPreset.y = keyShapeBox.min[1];
|
||||
positionPreset.textBaseline = 'bottom';
|
||||
positionPreset.offsetY = -4;
|
||||
break;
|
||||
case 'left':
|
||||
positionPreset.x = keyShapeBox.min[0];
|
||||
positionPreset.y = keyShapeBox.center[1];
|
||||
positionPreset.textAlign = 'right';
|
||||
positionPreset.textBaseline = 'middle';
|
||||
positionPreset.offsetX = -4;
|
||||
break;
|
||||
case 'right':
|
||||
positionPreset.x = keyShapeBox.max[0];
|
||||
positionPreset.y = keyShapeBox.center[1];
|
||||
positionPreset.textAlign = 'left';
|
||||
positionPreset.textBaseline = 'middle';
|
||||
positionPreset.offsetX = 4;
|
||||
break;
|
||||
default: // at bottom by default
|
||||
positionPreset.offsetY = 4;
|
||||
break;
|
||||
}
|
||||
const offsetX = (
|
||||
propsOffsetX === undefined ? positionPreset.offsetX : propsOffsetX
|
||||
) as number;
|
||||
const offsetY = (
|
||||
propsOffsetY === undefined ? positionPreset.offsetY : propsOffsetY
|
||||
) as number;
|
||||
positionPreset.x += offsetX;
|
||||
positionPreset.y += offsetY;
|
||||
|
||||
const style: any = {
|
||||
...this.defaultStyles.labelShape,
|
||||
...positionPreset,
|
||||
...otherStyle,
|
||||
};
|
||||
const labelShape = upsertShape('text', 'labelShape', style, shapeMap);
|
||||
const shapes = { labelShape };
|
||||
if (background) {
|
||||
const textBBox = labelShape.getGeometryBounds();
|
||||
// TODO: update type define.
|
||||
// @ts-ignore
|
||||
const { padding: propsPadding, ...backgroundStyle } = background;
|
||||
const padding = formatPadding(propsPadding, DEFAULT_LABEL_BG_PADDING);
|
||||
const bgStyle: any = {
|
||||
fill: '#fff',
|
||||
radius: 4,
|
||||
...backgroundStyle,
|
||||
x: textBBox.min[0] - padding[3] + style.x,
|
||||
y: textBBox.min[1] - padding[0] + style.y,
|
||||
width: textBBox.max[0] - textBBox.min[0] + padding[1] + padding[3],
|
||||
height: textBBox.max[1] - textBBox.min[1] + padding[0] + padding[2],
|
||||
};
|
||||
if (style.stransform) {
|
||||
bgStyle.transform = style.transform;
|
||||
bgStyle.transformOrigin = 'center';
|
||||
if (style.textAlign === 'left') {
|
||||
bgStyle.transformOrigin = `${padding[3]} ${
|
||||
padding[0] + bgStyle.height / 2
|
||||
}`;
|
||||
}
|
||||
if (style.textAlign === 'right') {
|
||||
bgStyle.transformOrigin = `${padding[3] + bgStyle.width} ${
|
||||
padding[0] + bgStyle.height / 2
|
||||
}`;
|
||||
}
|
||||
}
|
||||
|
||||
shapes['labelBgShape'] = upsertShape(
|
||||
'rect',
|
||||
'labelBgShape',
|
||||
bgStyle,
|
||||
shapeMap,
|
||||
);
|
||||
}
|
||||
return shapes;
|
||||
): DisplayObject {
|
||||
return super.drawLabelShape(model, shapeMap, diffData, diffState);
|
||||
}
|
||||
|
||||
// TODO: 3d icon? - billboard image or text for alpha
|
||||
@ -143,18 +45,59 @@ export abstract class BaseNode3D extends BaseNode {
|
||||
diffData?: { previous: NodeModelData; current: NodeModelData },
|
||||
diffState?: { oldState: State[]; newState: State[] },
|
||||
): DisplayObject {
|
||||
const { iconShape } = model.data || {};
|
||||
const { iconShape: shapeStyle } = this.mergedStyles;
|
||||
const iconShapeType = shapeStyle.text ? 'text' : 'image';
|
||||
if (iconShapeType === 'image') {
|
||||
const { width, height } = shapeStyle;
|
||||
if (!iconShape.hasOwnProperty('x')) shapeStyle.x = -width / 2;
|
||||
if (!iconShape.hasOwnProperty('y')) shapeStyle.y = -height / 2;
|
||||
} else {
|
||||
shapeStyle.textAlign = 'center';
|
||||
shapeStyle.textBaseline = 'middle';
|
||||
}
|
||||
return this.upsertShape(iconShapeType, 'iconShape', shapeStyle, shapeMap);
|
||||
return super.drawIconShape(model, shapeMap, diffData, diffState);
|
||||
}
|
||||
|
||||
// TODO: 3d billboard
|
||||
public drawHaloShape(
|
||||
model: NodeDisplayModel,
|
||||
shapeMap: NodeShapeMap,
|
||||
diffData?: { previous: NodeModelData; current: NodeModelData },
|
||||
diffState?: { previous: State[]; current: State[] },
|
||||
): DisplayObject {
|
||||
const { keyShape } = shapeMap;
|
||||
const { haloShape: haloShapeStyle } = this.mergedStyles;
|
||||
const { nodeName, attributes } = keyShape;
|
||||
return this.upsertShape(
|
||||
nodeName as SHAPE_TYPE,
|
||||
'haloShape',
|
||||
{
|
||||
...attributes,
|
||||
...haloShapeStyle,
|
||||
isBillboard: true,
|
||||
},
|
||||
shapeMap,
|
||||
).shape;
|
||||
}
|
||||
|
||||
/**
|
||||
* 3D node does not support anchor shapes.
|
||||
* @param model
|
||||
* @param shapeMap
|
||||
* @param diffData
|
||||
* @param diffState
|
||||
* @returns
|
||||
*/
|
||||
public drawAnchorShapes(
|
||||
model: NodeDisplayModel,
|
||||
shapeMap: NodeShapeMap,
|
||||
diffData?: { previous: NodeModelData; current: NodeModelData },
|
||||
diffState?: { previous: State[]; current: State[] },
|
||||
): {
|
||||
[shapeId: string]: DisplayObject;
|
||||
} {
|
||||
return {};
|
||||
}
|
||||
|
||||
public drawBadgeShapes(
|
||||
model: NodeDisplayModel,
|
||||
shapeMap: NodeShapeMap,
|
||||
diffData?: { previous: NodeModelData; current: NodeModelData },
|
||||
diffState?: { previous: State[]; current: State[] },
|
||||
): {
|
||||
[shapeId: string]: DisplayObject;
|
||||
} {
|
||||
return super.drawBadgeShapes(model, shapeMap, diffData, diffState);
|
||||
}
|
||||
|
||||
// TODO: 3d shapes?
|
||||
@ -171,15 +114,12 @@ export abstract class BaseNode3D extends BaseNode {
|
||||
public upsertShape(
|
||||
type: SHAPE_TYPE_3D | SHAPE_TYPE,
|
||||
id: string,
|
||||
style: { [shapeAttr: string]: unknown },
|
||||
style: ShapeStyle,
|
||||
shapeMap: { [shapeId: string]: DisplayObject },
|
||||
): DisplayObject {
|
||||
return upsertShape3D(
|
||||
type,
|
||||
id,
|
||||
style as unknown as GShapeStyle,
|
||||
shapeMap,
|
||||
this.device,
|
||||
);
|
||||
): {
|
||||
shape: DisplayObject;
|
||||
updateStyles: ShapeStyle;
|
||||
} {
|
||||
return upsertShape3D(type, id, style as GShapeStyle, shapeMap, this.device);
|
||||
}
|
||||
}
|
||||
|
@ -1,18 +1,23 @@
|
||||
import { DisplayObject } from '@antv/g';
|
||||
import { NodeDisplayModel } from '../../../types';
|
||||
import { GShapeStyle, ItemShapeStyles, State } from '../../../types/item';
|
||||
import { NodeModelData, NodeShapeMap } from '../../../types/node';
|
||||
import { State } from '../../../types/item';
|
||||
import {
|
||||
NodeModelData,
|
||||
NodeShapeMap,
|
||||
NodeShapeStyles,
|
||||
} from '../../../types/node';
|
||||
import { BaseNode } from './base';
|
||||
import { isStyleAffectBBox } from 'util/shape';
|
||||
|
||||
export class CircleNode extends BaseNode {
|
||||
override defaultStyles = {
|
||||
keyShape: {
|
||||
r: 15,
|
||||
r: 16,
|
||||
x: 0,
|
||||
y: 0,
|
||||
},
|
||||
};
|
||||
mergedStyles: ItemShapeStyles;
|
||||
mergedStyles: NodeShapeStyles;
|
||||
constructor(props) {
|
||||
super(props);
|
||||
// suggest to merge default styles like this to avoid style value missing
|
||||
@ -27,16 +32,62 @@ export class CircleNode extends BaseNode {
|
||||
const { data = {} } = model;
|
||||
let shapes: NodeShapeMap = { keyShape: undefined };
|
||||
|
||||
// keyShape
|
||||
shapes.keyShape = this.drawKeyShape(model, shapeMap, diffData);
|
||||
|
||||
// haloShape
|
||||
if (data.haloShape && this.drawHaloShape) {
|
||||
shapes.haloShape = this.drawHaloShape(model, shapeMap, diffData);
|
||||
}
|
||||
|
||||
// labelShape
|
||||
if (data.labelShape) {
|
||||
shapes.labelShape = this.drawLabelShape(model, shapeMap, diffData);
|
||||
}
|
||||
|
||||
// labelBackgroundShape
|
||||
if (data.labelBackgroundShape) {
|
||||
shapes.labelBackgroundShape = this.drawLabelBackgroundShape(
|
||||
model,
|
||||
shapeMap,
|
||||
diffData,
|
||||
);
|
||||
}
|
||||
|
||||
// anchor shapes
|
||||
if (data.anchorShapes) {
|
||||
const anchorShapes = this.drawAnchorShapes(
|
||||
model,
|
||||
shapeMap,
|
||||
diffData,
|
||||
diffState,
|
||||
);
|
||||
shapes = {
|
||||
...shapes,
|
||||
...this.drawLabelShape(model, shapeMap, diffData),
|
||||
...anchorShapes,
|
||||
};
|
||||
}
|
||||
|
||||
// iconShape
|
||||
if (data.iconShape) {
|
||||
shapes.iconShape = this.drawIconShape(model, shapeMap, diffData);
|
||||
}
|
||||
|
||||
// badgeShape
|
||||
if (data.badgeShapes) {
|
||||
const badgeShapes = this.drawBadgeShapes(
|
||||
model,
|
||||
shapeMap,
|
||||
diffData,
|
||||
diffState,
|
||||
);
|
||||
shapes = {
|
||||
...shapes,
|
||||
...badgeShapes,
|
||||
};
|
||||
}
|
||||
|
||||
// otherShapes
|
||||
if (data.otherShapes && this.drawOtherShapes) {
|
||||
shapes = {
|
||||
...shapes,
|
||||
@ -52,11 +103,15 @@ export class CircleNode extends BaseNode {
|
||||
diffData?: { previous: NodeModelData; current: NodeModelData },
|
||||
diffState?: { previous: State[]; current: State[] },
|
||||
): DisplayObject {
|
||||
return this.upsertShape(
|
||||
const { shape, updateStyles } = this.upsertShape(
|
||||
'circle',
|
||||
'keyShape',
|
||||
this.mergedStyles.keyShape,
|
||||
shapeMap,
|
||||
);
|
||||
if (isStyleAffectBBox('circle', updateStyles)) {
|
||||
this.boundsCache.keyShapeLocal = shape.getLocalBounds();
|
||||
}
|
||||
return shape;
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,11 @@
|
||||
import { DisplayObject } from '@antv/g';
|
||||
import { NodeDisplayModel } from '../../../types';
|
||||
import { ItemShapeStyles, State } from '../../../types/item';
|
||||
import { NodeModelData, NodeShapeMap } from '../../../types/node';
|
||||
import { State } from '../../../types/item';
|
||||
import {
|
||||
NodeModelData,
|
||||
NodeShapeMap,
|
||||
NodeShapeStyles,
|
||||
} from '../../../types/node';
|
||||
import { BaseNode3D } from './base3d';
|
||||
|
||||
export class SphereNode extends BaseNode3D {
|
||||
@ -15,7 +19,7 @@ export class SphereNode extends BaseNode3D {
|
||||
z: 0,
|
||||
},
|
||||
};
|
||||
mergedStyles: ItemShapeStyles;
|
||||
mergedStyles: NodeShapeStyles;
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
@ -28,16 +32,48 @@ export class SphereNode extends BaseNode3D {
|
||||
const { data = {} } = model;
|
||||
let shapes: NodeShapeMap = { keyShape: undefined };
|
||||
|
||||
// keyShape
|
||||
shapes.keyShape = this.drawKeyShape(model, shapeMap, diffData);
|
||||
if (data.labelShape) {
|
||||
shapes = {
|
||||
...shapes,
|
||||
...this.drawLabelShape(model, shapeMap, diffData),
|
||||
};
|
||||
|
||||
// haloShape
|
||||
if (data.haloShape && this.drawHaloShape) {
|
||||
shapes.haloShape = this.drawHaloShape(model, shapeMap, diffData);
|
||||
}
|
||||
|
||||
// labelShape
|
||||
if (data.labelShape) {
|
||||
shapes.labelShape = this.drawLabelShape(model, shapeMap, diffData);
|
||||
}
|
||||
|
||||
// labelBackgroundShape
|
||||
if (data.labelBackgroundShape) {
|
||||
shapes.labelBackgroundShape = this.drawLabelBackgroundShape(
|
||||
model,
|
||||
shapeMap,
|
||||
diffData,
|
||||
);
|
||||
}
|
||||
|
||||
// iconShape
|
||||
if (data.iconShape) {
|
||||
shapes.iconShape = this.drawIconShape(model, shapeMap, diffData);
|
||||
}
|
||||
|
||||
// badgeShape
|
||||
if (data.badgeShapes) {
|
||||
const badgeShapes = this.drawBadgeShapes(
|
||||
model,
|
||||
shapeMap,
|
||||
diffData,
|
||||
diffState,
|
||||
);
|
||||
shapes = {
|
||||
...shapes,
|
||||
...badgeShapes,
|
||||
};
|
||||
}
|
||||
|
||||
// otherShapes
|
||||
if (data.otherShapes && this.drawOtherShapes) {
|
||||
shapes = {
|
||||
...shapes,
|
||||
@ -58,6 +94,6 @@ export class SphereNode extends BaseNode3D {
|
||||
'keyShape',
|
||||
this.mergedStyles.keyShape,
|
||||
shapeMap,
|
||||
);
|
||||
).shape;
|
||||
}
|
||||
}
|
||||
|
@ -102,7 +102,7 @@ export default {
|
||||
...DEFAULT_SHAPE_STYLE,
|
||||
lineWidth: 1,
|
||||
stroke: edgeMainStroke,
|
||||
lineAppendWidth: 2,
|
||||
increasedLineWidthForHitTesting: 2,
|
||||
},
|
||||
labelShape: {
|
||||
...DEFAULT_TEXT_STYLE,
|
||||
|
@ -1,75 +1,107 @@
|
||||
import { DEFAULT_SHAPE_STYLE, DEFAULT_TEXT_STYLE } from '../../constant';
|
||||
import { ThemeSpecification } from '../../types/theme';
|
||||
|
||||
const subjectColor = 'rgb(95, 149, 255)';
|
||||
const textColor = 'rgb(0, 0, 0)';
|
||||
const subjectColor = 'rgb(34,126,255)';
|
||||
const textColor = 'rgba(0,0,0,0.85)';
|
||||
|
||||
const activeFill = 'rgb(247, 250, 255)';
|
||||
const nodeMainFill = 'rgb(239, 244, 255)';
|
||||
const nodeColor = 'rgb(34,126,255)';
|
||||
const edgeColor = 'rgb(153, 173, 209)';
|
||||
const comboFill = 'rgb(253, 253, 253)';
|
||||
const disabledFill = 'rgb(250, 250, 250)';
|
||||
const disabledFill = 'rgb(240, 240, 240)';
|
||||
|
||||
const edgeMainStroke = 'rgb(224, 224, 224)';
|
||||
const edgeInactiveStroke = 'rgb(234, 234, 234)';
|
||||
const edgeDisablesStroke = 'rgb(245, 245, 245)';
|
||||
const inactiveStroke = 'rgb(191, 213, 255)';
|
||||
const edgeMainStroke = 'rgb(153, 173, 209)';
|
||||
const edgeDisableStroke = 'rgb(217, 217, 217)';
|
||||
const edgeInactiveStroke = 'rgb(210, 218, 233)';
|
||||
|
||||
const highlightStroke = '#4572d9';
|
||||
const highlightFill = 'rgb(223, 234, 255)';
|
||||
const nodeStroke = 'rgba(0,0,0,0.85)';
|
||||
const haloStroke = 'rgb(0, 0, 0)';
|
||||
|
||||
export default {
|
||||
node: {
|
||||
palette: [],
|
||||
palette: [
|
||||
'#227EFF',
|
||||
'#AD5CFF',
|
||||
'#00B8B8',
|
||||
'#FA822D',
|
||||
'#F252AF',
|
||||
'#1EB8F5',
|
||||
'#108A44',
|
||||
'#F4B106',
|
||||
'#5241A8',
|
||||
'#95CF21',
|
||||
],
|
||||
styles: [
|
||||
{
|
||||
default: {
|
||||
keyShape: {
|
||||
...DEFAULT_SHAPE_STYLE,
|
||||
r: 10,
|
||||
fill: nodeMainFill,
|
||||
stroke: subjectColor,
|
||||
lineWidth: 1,
|
||||
r: 16,
|
||||
fill: nodeColor,
|
||||
lineWidth: 0,
|
||||
zIndex: 0,
|
||||
},
|
||||
labelShape: {
|
||||
...DEFAULT_TEXT_STYLE,
|
||||
fill: '#000',
|
||||
position: 'bottom',
|
||||
offsetY: 4,
|
||||
zIndex: 2,
|
||||
},
|
||||
labelBackgroundShape: {
|
||||
padding: [4, 4, 4, 4],
|
||||
lineWidth: 0,
|
||||
fill: '#fff',
|
||||
opacity: 0.75,
|
||||
zIndex: -1,
|
||||
},
|
||||
iconShape: {
|
||||
...DEFAULT_TEXT_STYLE,
|
||||
fill: '#333',
|
||||
img: 'https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*wAmHQJbNVdwAAAAAAAAAAABkARQnAQ',
|
||||
width: 15,
|
||||
height: 15,
|
||||
fill: '#fff',
|
||||
fontSize: 16,
|
||||
zIndex: 1,
|
||||
},
|
||||
anchorShapes: {
|
||||
lineWidth: 1,
|
||||
stroke: 'rgba(0, 0, 0, 0.65)',
|
||||
zIndex: 2,
|
||||
r: 3,
|
||||
},
|
||||
badgeShapes: {
|
||||
color: 'rgb(140, 140, 140)',
|
||||
textColor: '#fff',
|
||||
zIndex: 2,
|
||||
},
|
||||
},
|
||||
selected: {
|
||||
keyShape: {
|
||||
fill: nodeMainFill,
|
||||
stroke: subjectColor,
|
||||
lineWidth: 4,
|
||||
shadowColor: subjectColor,
|
||||
shadowBlur: 10,
|
||||
stroke: nodeStroke,
|
||||
lineWidth: 3,
|
||||
},
|
||||
labelShape: {
|
||||
fontWeight: 500,
|
||||
},
|
||||
haloShape: {
|
||||
stroke: haloStroke,
|
||||
opacity: 0.06,
|
||||
lineWidth: 20,
|
||||
},
|
||||
},
|
||||
active: {
|
||||
keyShape: {
|
||||
fill: activeFill,
|
||||
stroke: subjectColor,
|
||||
shadowColor: subjectColor,
|
||||
stroke: nodeStroke,
|
||||
lineWidth: 2,
|
||||
shadowBlur: 10,
|
||||
},
|
||||
haloShape: {
|
||||
stroke: haloStroke,
|
||||
opacity: 0.06,
|
||||
lineWidth: 4,
|
||||
zIndex: -1,
|
||||
},
|
||||
},
|
||||
highlight: {
|
||||
keyShape: {
|
||||
fill: highlightFill,
|
||||
stroke: highlightStroke,
|
||||
lineWidth: 2,
|
||||
stroke: nodeStroke,
|
||||
lineWidth: 3,
|
||||
},
|
||||
labelShape: {
|
||||
fontWeight: 500,
|
||||
@ -77,23 +109,37 @@ export default {
|
||||
},
|
||||
inactive: {
|
||||
keyShape: {
|
||||
fill: activeFill,
|
||||
stroke: inactiveStroke,
|
||||
lineWidth: 1,
|
||||
opacity: 0.25,
|
||||
},
|
||||
labelShape: {
|
||||
opacity: 0.25,
|
||||
},
|
||||
iconShape: {
|
||||
opacity: 0.25,
|
||||
},
|
||||
},
|
||||
disable: {
|
||||
keyShape: {
|
||||
fill: disabledFill,
|
||||
stroke: edgeMainStroke,
|
||||
lineWidth: 1,
|
||||
lineWidth: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
edge: {
|
||||
palette: [],
|
||||
palette: [
|
||||
'#63A4FF',
|
||||
'#CD9CFF',
|
||||
'#2DEFEF',
|
||||
'#FFBDA1',
|
||||
'#F49FD0',
|
||||
'#80DBFF',
|
||||
'#41CB7C',
|
||||
'#FFD362',
|
||||
'#A192E8',
|
||||
'#CEFB75',
|
||||
],
|
||||
styles: [
|
||||
{
|
||||
default: {
|
||||
@ -101,42 +147,57 @@ export default {
|
||||
...DEFAULT_SHAPE_STYLE,
|
||||
lineWidth: 1,
|
||||
stroke: edgeMainStroke,
|
||||
lineAppendWidth: 2,
|
||||
increasedLineWidthForHitTesting: 2,
|
||||
},
|
||||
labelShape: {
|
||||
...DEFAULT_TEXT_STYLE,
|
||||
fill: textColor,
|
||||
position: 'middle',
|
||||
textBaseline: 'middle',
|
||||
zIndex: 2,
|
||||
},
|
||||
labelBackgroundShape: {
|
||||
padding: [4, 4, 4, 4],
|
||||
lineWidth: 0,
|
||||
fill: '#fff',
|
||||
opacity: 0.75,
|
||||
zIndex: 1,
|
||||
},
|
||||
iconShape: {
|
||||
...DEFAULT_TEXT_STYLE,
|
||||
fill: '#333',
|
||||
img: 'https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*wAmHQJbNVdwAAAAAAAAAAABkARQnAQ',
|
||||
width: 15,
|
||||
height: 15,
|
||||
fill: 'rgb(140, 140, 140)',
|
||||
fontSize: 16,
|
||||
zIndex: 2,
|
||||
offsetX: -10,
|
||||
},
|
||||
},
|
||||
selected: {
|
||||
keyShape: {
|
||||
stroke: subjectColor,
|
||||
lineWidth: 2,
|
||||
shadowColor: subjectColor,
|
||||
shadowBlur: 10,
|
||||
},
|
||||
labelShape: {
|
||||
fontWeight: 500,
|
||||
},
|
||||
haloShape: {
|
||||
stroke: haloStroke,
|
||||
opacity: 0.06,
|
||||
lineWidth: 12,
|
||||
zIndex: -1,
|
||||
},
|
||||
},
|
||||
active: {
|
||||
keyShape: {
|
||||
stroke: subjectColor,
|
||||
lineWidth: 1,
|
||||
},
|
||||
haloShape: {
|
||||
stroke: haloStroke,
|
||||
opacity: 0.06,
|
||||
lineWidth: 12,
|
||||
zIndex: -1,
|
||||
},
|
||||
},
|
||||
highlight: {
|
||||
keyShape: {
|
||||
stroke: subjectColor,
|
||||
lineWidth: 2,
|
||||
},
|
||||
labelShape: {
|
||||
@ -151,7 +212,7 @@ export default {
|
||||
},
|
||||
disable: {
|
||||
keyShape: {
|
||||
stroke: edgeDisablesStroke,
|
||||
stroke: edgeDisableStroke,
|
||||
lineWidth: 1,
|
||||
},
|
||||
},
|
||||
@ -176,11 +237,9 @@ export default {
|
||||
},
|
||||
selected: {
|
||||
keyShape: {
|
||||
stroke: subjectColor,
|
||||
stroke: nodeStroke,
|
||||
fill: comboFill,
|
||||
shadowColor: subjectColor,
|
||||
lineWidth: 2,
|
||||
shadowBlur: 10,
|
||||
},
|
||||
labelShape: {
|
||||
fontWeight: 500,
|
||||
@ -188,14 +247,13 @@ export default {
|
||||
},
|
||||
active: {
|
||||
keyShape: {
|
||||
stroke: subjectColor,
|
||||
stroke: nodeStroke,
|
||||
lineWidth: 1,
|
||||
fill: activeFill,
|
||||
},
|
||||
},
|
||||
highlight: {
|
||||
keyShape: {
|
||||
stroke: highlightStroke,
|
||||
stroke: nodeStroke,
|
||||
fill: comboFill,
|
||||
lineWidth: 2,
|
||||
},
|
||||
@ -212,7 +270,6 @@ export default {
|
||||
},
|
||||
disable: {
|
||||
keyShape: {
|
||||
stroke: edgeInactiveStroke,
|
||||
fill: disabledFill,
|
||||
lineWidth: 1,
|
||||
},
|
||||
|
@ -1,7 +1,12 @@
|
||||
import { isArray } from '@antv/util';
|
||||
import { ItemStyleSets, ThemeSpecification } from '../../types/theme';
|
||||
import {
|
||||
NodeStyleSets,
|
||||
EdgeStyleSets,
|
||||
ThemeSpecification,
|
||||
} from '../../types/theme';
|
||||
import { mergeStyles } from '../../util/shape';
|
||||
import BaseThemeSolver, { ThemeSpecificationMap } from './base';
|
||||
import { GraphData } from 'types';
|
||||
|
||||
interface SpecThemeSolverOptions {
|
||||
base: 'light' | 'dark';
|
||||
@ -9,17 +14,17 @@ interface SpecThemeSolverOptions {
|
||||
node?: {
|
||||
dataTypeField?: string;
|
||||
palette: string[] | { [dataType: string]: string };
|
||||
getStyleSets: (palette) => ItemStyleSets;
|
||||
getStyleSets: (palette) => NodeStyleSets;
|
||||
};
|
||||
edge?: {
|
||||
dataTypeField?: string;
|
||||
palette: string[] | { [dataType: string]: string };
|
||||
getStyleSets: (palette) => ItemStyleSets;
|
||||
getStyleSets: (palette) => EdgeStyleSets;
|
||||
};
|
||||
combo?: {
|
||||
dataTypeField?: string;
|
||||
palette: string[] | { [dataType: string]: string };
|
||||
getStyleSets: (palette) => ItemStyleSets;
|
||||
getStyleSets: (palette) => NodeStyleSets;
|
||||
};
|
||||
canvas?: {
|
||||
[cssName: string]: unknown;
|
||||
@ -40,14 +45,28 @@ export default class SpecThemeSolver extends BaseThemeSolver {
|
||||
if (specification) {
|
||||
['node', 'edge', 'combo'].forEach((itemType) => {
|
||||
if (!specification[itemType]) return;
|
||||
const { palette, dataTypeField, getStyleSets } =
|
||||
specification[itemType];
|
||||
let {
|
||||
palette = mergedSpec[itemType].palette,
|
||||
dataTypeField,
|
||||
getStyleSets,
|
||||
} = specification[itemType];
|
||||
|
||||
if (dataTypeField && !getStyleSets) {
|
||||
getStyleSets = (paletteProps) => {
|
||||
return paletteProps.map((color) => ({
|
||||
default: {
|
||||
keyShape:
|
||||
itemType === 'edge' ? { stroke: color } : { fill: color },
|
||||
},
|
||||
}));
|
||||
};
|
||||
}
|
||||
|
||||
// merge the custom part spec and the built-in spec
|
||||
const {
|
||||
styles: [baseStyles],
|
||||
} = baseSpec[itemType];
|
||||
const incomingStyles = getStyleSets(palette);
|
||||
const incomingStyles = getStyleSets?.(palette) || {};
|
||||
let mergedStyles;
|
||||
if (isArray(incomingStyles)) {
|
||||
mergedStyles = incomingStyles.map((incomingStyle) => {
|
||||
|
@ -2,14 +2,15 @@ import { Node as GNode, PlainObject } from '@antv/graphlib';
|
||||
import { AnimateAttr } from './animate';
|
||||
import { Padding } from './common';
|
||||
import {
|
||||
BadgePosition,
|
||||
Encode,
|
||||
IItem,
|
||||
ItemShapeStyles,
|
||||
LabelBackground,
|
||||
ShapeAttrEncode,
|
||||
ShapesEncode,
|
||||
ShapeStyle,
|
||||
} from './item';
|
||||
import { AnchorPoint } from './node';
|
||||
|
||||
export type ComboLabelPosition =
|
||||
| 'bottom'
|
||||
@ -32,28 +33,56 @@ export interface ComboUserModelData extends PlainObject {
|
||||
export interface ComboModelData extends ComboUserModelData {
|
||||
visible?: boolean;
|
||||
label?: string;
|
||||
}
|
||||
|
||||
export interface ComboLabelShapeStyle extends ShapeStyle {
|
||||
position?: ComboLabelPosition;
|
||||
offsetX?: number;
|
||||
offsetY?: number;
|
||||
background?: LabelBackground;
|
||||
}
|
||||
|
||||
/** Displayed data, only for drawing and not received by users. */
|
||||
export interface ComboDisplayModelData extends ComboModelData {
|
||||
keyShape?: ShapeStyle;
|
||||
labelShape?: ComboLabelShapeStyle;
|
||||
iconShape?: ShapeStyle;
|
||||
otherShapes?: {
|
||||
[shapeId: string]: ShapeStyle;
|
||||
};
|
||||
anchorPoints?: AnchorPoint[];
|
||||
anchorPoints?: number[][];
|
||||
fixSize?: number | number[];
|
||||
padding?: Padding;
|
||||
}
|
||||
|
||||
export interface ComboShapeStyles extends ItemShapeStyles {
|
||||
keyShape?: ShapeStyle & {
|
||||
padding?: number | number[];
|
||||
};
|
||||
labelShape?: ShapeStyle & {
|
||||
position?: ComboLabelPosition;
|
||||
offsetX?: number;
|
||||
offsetY?: number;
|
||||
};
|
||||
labelBackgroundShape?: ShapeStyle & {
|
||||
padding?: number | number[];
|
||||
};
|
||||
// common badge styles
|
||||
badgeShapes?: ShapeStyle & {
|
||||
color?: string;
|
||||
textColor?: string;
|
||||
// individual styles and their position
|
||||
[key: number]: ShapeStyle & {
|
||||
position?: BadgePosition;
|
||||
color?: string;
|
||||
textColor?: string;
|
||||
};
|
||||
};
|
||||
// common badge styles
|
||||
anchorShapes?: ShapeStyle & {
|
||||
color?: string;
|
||||
textColor?: string;
|
||||
size?: number;
|
||||
offsetX?: number;
|
||||
offsetY?: number;
|
||||
// individual styles and their position
|
||||
[key: number]: ShapeStyle & {
|
||||
position?: BadgePosition;
|
||||
color?: string;
|
||||
textColor?: string;
|
||||
size?: number;
|
||||
offsetX?: number;
|
||||
offsetY?: number;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/** Displayed data, only for drawing and not received by users. */
|
||||
export type ComboDisplayModelData = ComboModelData & ComboShapeStyles;
|
||||
|
||||
/** User input model. */
|
||||
export type ComboUserModel = GNode<ComboUserModelData>;
|
||||
|
||||
@ -72,7 +101,7 @@ interface ComboLabelShapeAttrEncode extends ShapeAttrEncode {
|
||||
}
|
||||
export interface ComboShapesEncode extends ShapesEncode {
|
||||
labelShape?: ComboLabelShapeAttrEncode;
|
||||
anchorPoints?: AnchorPoint[] | Encode<AnchorPoint[]>;
|
||||
anchorPoints?: number[][] | Encode<number[][]>;
|
||||
fixSize?: number | number[] | Encode<number | number[]>;
|
||||
padding?: Padding | Encode<Padding>;
|
||||
}
|
||||
|
@ -1,38 +1,77 @@
|
||||
import { DisplayObject } from '@antv/g';
|
||||
import { Edge as GEdge, PlainObject } from '@antv/graphlib';
|
||||
import { AnimateAttr } from './animate';
|
||||
import {
|
||||
BadgePosition,
|
||||
Encode,
|
||||
IItem,
|
||||
ItemShapeStyles,
|
||||
LabelBackground,
|
||||
ShapeAttrEncode,
|
||||
ShapesEncode,
|
||||
ShapeStyle,
|
||||
} from './item';
|
||||
|
||||
export type EdgeUserModelData = PlainObject;
|
||||
export interface EdgeModelData extends EdgeUserModelData {
|
||||
visible?: boolean;
|
||||
label?: string;
|
||||
}
|
||||
|
||||
export interface EdgeLabelShapeStyle extends ShapeStyle {
|
||||
position?: EdgeLabelPosition;
|
||||
offsetX?: number;
|
||||
offsetY?: number;
|
||||
background?: LabelBackground;
|
||||
autoRotate?: boolean;
|
||||
}
|
||||
export interface EdgeDisplayModelData extends EdgeModelData {
|
||||
keyShape?: ShapeStyle;
|
||||
labelShape?: EdgeLabelShapeStyle;
|
||||
iconShape?: ShapeStyle;
|
||||
otherShapes?: {
|
||||
[shapeId: string]: ShapeStyle;
|
||||
};
|
||||
export interface EdgeUserModelData extends PlainObject {
|
||||
/**
|
||||
* Anchor index to link to the source / target node.
|
||||
*/
|
||||
sourceAnchor?: number;
|
||||
targetAnchor?: number;
|
||||
/**
|
||||
* Edge type, e.g. 'line'.
|
||||
*/
|
||||
type?: string;
|
||||
/**
|
||||
* Subject color for the keyShape and arrows.
|
||||
* More styles should be configured in edge mapper.
|
||||
*/
|
||||
color?: string;
|
||||
/**
|
||||
* The text to show on the edge.
|
||||
* More styles should be configured in edge mapper.
|
||||
*/
|
||||
label?: string;
|
||||
/**
|
||||
* Whether show the edge by default.
|
||||
*/
|
||||
visible?: boolean;
|
||||
/**
|
||||
* The icon to show on the edge.
|
||||
* More styles should be configured in edge mapper.
|
||||
*/
|
||||
icon?: {
|
||||
type: 'icon' | 'text';
|
||||
text?: string;
|
||||
img?: string;
|
||||
};
|
||||
/**
|
||||
* The badges to show on the edge.
|
||||
* More styles should be configured in edge mapper.
|
||||
*/
|
||||
badge?: {
|
||||
type: 'icon' | 'text';
|
||||
text: string;
|
||||
};
|
||||
}
|
||||
export interface EdgeModelData extends EdgeUserModelData {}
|
||||
|
||||
export interface EdgeShapeStyles extends ItemShapeStyles {
|
||||
labelShape?: ShapeStyle & {
|
||||
position?: 'start' | 'middle' | 'end';
|
||||
offsetX?: number;
|
||||
offsetY?: number;
|
||||
autoRotate?: boolean;
|
||||
};
|
||||
labelBackgroundShape?: ShapeStyle & {
|
||||
padding?: number | number[];
|
||||
};
|
||||
badgeShape?: ShapeStyle & {
|
||||
color?: string;
|
||||
textColor?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export type EdgeDisplayModelData = EdgeModelData & EdgeShapeStyles;
|
||||
|
||||
/** User input data. */
|
||||
export type EdgeUserModel = GEdge<EdgeUserModelData>;
|
||||
@ -54,8 +93,8 @@ interface EdgeLabelShapeAttrEncode extends ShapeAttrEncode {
|
||||
|
||||
export interface EdgeShapesEncode extends ShapesEncode {
|
||||
labelShape?: EdgeLabelShapeAttrEncode;
|
||||
sourceAnchor?: number;
|
||||
targetAnchor?: number;
|
||||
labelBackgroundShape?: LabelBackground | Encode<LabelBackground>;
|
||||
badgeShape?: ShapeAttrEncode | Encode<ShapeStyle>;
|
||||
}
|
||||
export interface EdgeEncode extends EdgeShapesEncode {
|
||||
type?: string | Encode<string>;
|
||||
|
@ -40,16 +40,6 @@ import {
|
||||
TorusGeometryProps,
|
||||
} from '@antv/g-plugin-3d';
|
||||
|
||||
export interface ShapeStyle {
|
||||
[shapeAttr: string]: unknown;
|
||||
animate?: AnimateAttr;
|
||||
x?: number;
|
||||
y?: number;
|
||||
width?: number;
|
||||
height?: number;
|
||||
r?: number;
|
||||
}
|
||||
|
||||
export type GShapeStyle = CircleStyleProps &
|
||||
RectStyleProps &
|
||||
EllipseStyleProps &
|
||||
@ -63,6 +53,11 @@ export type GShapeStyle = CircleStyleProps &
|
||||
CubeGeometryProps &
|
||||
PlaneGeometryProps;
|
||||
|
||||
export type ShapeStyle = Partial<
|
||||
GShapeStyle & {
|
||||
animate?: AnimateAttr;
|
||||
}
|
||||
>;
|
||||
export interface Encode<T> {
|
||||
fields: string[];
|
||||
formatter: (values: NodeUserModel | EdgeUserModel | ComboUserModel) => T;
|
||||
@ -82,8 +77,8 @@ export interface LabelBackground {
|
||||
}
|
||||
|
||||
export interface ShapesEncode {
|
||||
keyShape?: ShapeAttrEncode;
|
||||
iconShape?: ShapeAttrEncode;
|
||||
keyShape?: ShapeAttrEncode | Encode<ShapeStyle>;
|
||||
iconShape?: ShapeAttrEncode | Encode<ShapeStyle>;
|
||||
otherShapes?: {
|
||||
[shapeId: string]: {
|
||||
[shapeAtrr: string]: unknown | Encode<unknown>;
|
||||
@ -126,12 +121,35 @@ export type State = {
|
||||
value: boolean | string;
|
||||
};
|
||||
|
||||
export enum BadgePosition {
|
||||
rightTop = 'rightTop',
|
||||
right = 'right',
|
||||
rightBottom = 'rightBottom',
|
||||
bottomRight = 'bottomRight',
|
||||
bottom = 'bottom',
|
||||
bottomLeft = 'bottomLeft',
|
||||
leftBottom = 'leftBottom',
|
||||
left = 'left',
|
||||
leftTop = 'leftTop',
|
||||
topLeft = 'topLeft',
|
||||
top = 'top',
|
||||
topRight = 'topRight',
|
||||
}
|
||||
export type IBadgePosition = `${BadgePosition}`;
|
||||
|
||||
/** Shape styles for an item. */
|
||||
export type ItemShapeStyles = {
|
||||
// labelShape, labelBackgroundShape, badgeShapes, overwrote by node / edge / combo
|
||||
// anchorShapes, overwrote by node / combo
|
||||
keyShape?: ShapeStyle;
|
||||
labelShape?: ShapeStyle;
|
||||
iconShape?: ShapeStyle;
|
||||
[shapeId: string]: ShapeStyle;
|
||||
iconShape?: Partial<
|
||||
TextStyleProps &
|
||||
ImageStyleProps & {
|
||||
offsetX?: number;
|
||||
offsetY?: number;
|
||||
}
|
||||
>;
|
||||
haloShape?: ShapeStyle;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -1,9 +1,11 @@
|
||||
import { DisplayObject } from '@antv/g';
|
||||
import { DisplayObject, Point } from '@antv/g';
|
||||
import { Node as GNode, PlainObject } from '@antv/graphlib';
|
||||
import { AnimateAttr } from './animate';
|
||||
import {
|
||||
BadgePosition,
|
||||
Encode,
|
||||
IItem,
|
||||
ItemShapeStyles,
|
||||
LabelBackground,
|
||||
ShapeAttrEncode,
|
||||
ShapesEncode,
|
||||
@ -14,32 +16,104 @@ export type NodeLabelPosition = 'bottom' | 'center' | 'top' | 'left' | 'right';
|
||||
|
||||
/** Data in user input model. */
|
||||
export interface NodeUserModelData extends PlainObject {
|
||||
parentId?: string;
|
||||
}
|
||||
|
||||
/** Data in inner model. */
|
||||
export interface NodeModelData extends NodeUserModelData {
|
||||
visible?: boolean;
|
||||
/**
|
||||
* Node position.
|
||||
*/
|
||||
x?: number;
|
||||
y?: number;
|
||||
z?: number;
|
||||
/**
|
||||
* Node type, e.g. 'circle'.
|
||||
*/
|
||||
type?: string;
|
||||
/**
|
||||
* Subject color for the keyShape and anchor points.
|
||||
* More styles should be configured in node mapper.
|
||||
*/
|
||||
color?: string;
|
||||
/**
|
||||
* The text to show on the node.
|
||||
* More styles should be configured in node mapper.
|
||||
*/
|
||||
label?: string;
|
||||
/**
|
||||
* Whether show the node by default.
|
||||
*/
|
||||
visible?: boolean;
|
||||
/**
|
||||
* Reserved for combo.
|
||||
*/
|
||||
parentId?: string;
|
||||
/**
|
||||
* The icon to show on the node.
|
||||
* More styles should be configured in node mapper.
|
||||
*/
|
||||
icon?: {
|
||||
type: 'icon' | 'text';
|
||||
text?: string;
|
||||
img?: string;
|
||||
};
|
||||
/**
|
||||
* The ratio position of the keyShape for related edges linking into.
|
||||
* More styles should be configured in node mapper.
|
||||
*/
|
||||
anchorPoints?: number[][];
|
||||
/**
|
||||
* The badges to show on the node.
|
||||
* More styles should be configured in node mapper.
|
||||
*/
|
||||
badges?: {
|
||||
type: 'icon' | 'text';
|
||||
text: string;
|
||||
position: BadgePosition;
|
||||
}[];
|
||||
}
|
||||
|
||||
export interface NodeLabelShapeStyle extends ShapeStyle {
|
||||
position?: NodeLabelPosition;
|
||||
offsetX?: number;
|
||||
offsetY?: number;
|
||||
background?: LabelBackground;
|
||||
/** Data in inner model. Same format to the user data. */
|
||||
export interface NodeModelData extends NodeUserModelData {}
|
||||
|
||||
export interface NodeShapeStyles extends ItemShapeStyles {
|
||||
// keyShape, iconShape, haloShape are defined in ItemShapeStyles
|
||||
labelShape?: ShapeStyle & {
|
||||
position?: 'top' | 'bottom' | 'left' | 'right' | 'center';
|
||||
offsetX?: number;
|
||||
offsetY?: number;
|
||||
};
|
||||
labelBackgroundShape?: ShapeStyle & {
|
||||
padding?: number | number[];
|
||||
};
|
||||
badgeShapes?: ShapeStyle & {
|
||||
// common badge styles
|
||||
color?: string;
|
||||
textColor?: string;
|
||||
// individual styles and their position
|
||||
[key: number]: ShapeStyle & {
|
||||
position?: BadgePosition;
|
||||
color?: string;
|
||||
textColor?: string;
|
||||
};
|
||||
};
|
||||
anchorShapes?: ShapeStyle & {
|
||||
// common badge styles
|
||||
color?: string;
|
||||
textColor?: string;
|
||||
size?: number;
|
||||
offsetX?: number;
|
||||
offsetY?: number;
|
||||
// individual styles and their position
|
||||
[key: number]: ShapeStyle & {
|
||||
position?: BadgePosition;
|
||||
color?: string;
|
||||
textColor?: string;
|
||||
size?: number;
|
||||
offsetX?: number;
|
||||
offsetY?: number;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/** Data in display model. */
|
||||
export interface NodeDisplayModelData extends NodeModelData {
|
||||
keyShape?: ShapeStyle;
|
||||
labelShape?: NodeLabelShapeStyle;
|
||||
iconShape?: ShapeStyle;
|
||||
otherShapes?: {
|
||||
[shapeId: string]: ShapeStyle;
|
||||
};
|
||||
anchorPoints?: AnchorPoint[];
|
||||
}
|
||||
export type NodeDisplayModelData = NodeModelData & NodeShapeStyles;
|
||||
|
||||
/** User input model. */
|
||||
export type NodeUserModel = GNode<NodeUserModelData>;
|
||||
@ -50,24 +124,17 @@ export type NodeModel = GNode<NodeModelData>;
|
||||
/** Displayed model, only for drawing and not received by users. */
|
||||
export type NodeDisplayModel = GNode<NodeDisplayModelData>;
|
||||
|
||||
/** Anchor points, for linking edges and drawing circles. */
|
||||
export interface AnchorPoint {
|
||||
position?: [number, number]; // range from 0 to 1
|
||||
show?: boolean;
|
||||
[shapeAttr: string]: unknown;
|
||||
animate: AnimateAttr;
|
||||
}
|
||||
|
||||
interface NodeLabelShapeAttrEncode extends ShapeAttrEncode {
|
||||
// TODO: extends Text shape attr, import from G
|
||||
position?: NodeLabelPosition | Encode<NodeLabelPosition>;
|
||||
offsetX?: number | Encode<number>;
|
||||
offsetY?: number | Encode<number>;
|
||||
background?: LabelBackground | Encode<LabelBackground>;
|
||||
}
|
||||
export interface NodeShapesEncode extends ShapesEncode {
|
||||
labelShape?: NodeLabelShapeAttrEncode;
|
||||
anchorPoints?: AnchorPoint[] | Encode<AnchorPoint[]>;
|
||||
labelShape?: NodeLabelShapeAttrEncode | Encode<ShapeStyle>;
|
||||
labelBackgroundShape?: ShapeAttrEncode[] | Encode<ShapeStyle[]>;
|
||||
anchorShapes?: ShapeAttrEncode[] | Encode<ShapeStyle[]>;
|
||||
badgeShapes?: ShapeAttrEncode[] | Encode<ShapeStyle[]>;
|
||||
}
|
||||
export interface NodeEncode extends NodeShapesEncode {
|
||||
type?: string | Encode<string>;
|
||||
@ -77,6 +144,8 @@ export interface NodeShapeMap {
|
||||
keyShape: DisplayObject;
|
||||
labelShape?: DisplayObject;
|
||||
iconShape?: DisplayObject;
|
||||
|
||||
// TODO other badge shapes
|
||||
[otherShapeId: string]: DisplayObject;
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,6 @@
|
||||
import { ItemShapeStyles, ShapeStyle } from './item';
|
||||
import { ComboShapeStyles } from './combo';
|
||||
import { EdgeShapeStyles } from './edge';
|
||||
import { NodeShapeStyles } from './node';
|
||||
|
||||
export interface ThemeOption {}
|
||||
/**
|
||||
@ -41,33 +43,53 @@ export type ThemeObjectOptionsOf<T extends ThemeRegistry = {}> = {
|
||||
: never;
|
||||
}[Extract<keyof T, string>];
|
||||
|
||||
export type ItemStyleSets =
|
||||
| ItemStyleSet[]
|
||||
| { [dataTypeValue: string]: ItemStyleSet };
|
||||
|
||||
/** Default and stateStyle for an item */
|
||||
export type ItemStyleSet = {
|
||||
default?: ItemShapeStyles;
|
||||
[stateName: string]: ItemShapeStyles;
|
||||
export type NodeStyleSet = {
|
||||
default?: NodeShapeStyles;
|
||||
seledted?: NodeShapeStyles;
|
||||
[stateName: string]: NodeShapeStyles;
|
||||
};
|
||||
export type EdgeStyleSet = {
|
||||
default?: EdgeShapeStyles;
|
||||
[stateName: string]: EdgeShapeStyles;
|
||||
};
|
||||
export type ComboStyleSet = {
|
||||
default?: ComboShapeStyles;
|
||||
[stateName: string]: ComboShapeStyles;
|
||||
};
|
||||
|
||||
export interface ItemThemeSpecifications {
|
||||
export type NodeStyleSets =
|
||||
| NodeStyleSet[]
|
||||
| { [dataTypeValue: string]: NodeStyleSet };
|
||||
export type EdgeStyleSets =
|
||||
| EdgeStyleSet[]
|
||||
| { [dataTypeValue: string]: EdgeStyleSet };
|
||||
export type ComboStyleSets =
|
||||
| ComboStyleSet[]
|
||||
| { [dataTypeValue: string]: ComboStyleSet };
|
||||
|
||||
export interface NodeThemeSpecifications {
|
||||
dataTypeField?: string;
|
||||
palette?: string[] | { [dataTypeValue: string]: string };
|
||||
styles?:
|
||||
| ItemStyleSet[]
|
||||
| {
|
||||
[dataTypeValue: string]: ItemStyleSet;
|
||||
};
|
||||
styles?: NodeStyleSets;
|
||||
}
|
||||
export interface EdgeThemeSpecifications {
|
||||
dataTypeField?: string;
|
||||
palette?: string[] | { [dataTypeValue: string]: string };
|
||||
styles?: EdgeStyleSets;
|
||||
}
|
||||
export interface ComboThemeSpecifications {
|
||||
dataTypeField?: string;
|
||||
palette?: string[] | { [dataTypeValue: string]: string };
|
||||
styles?: ComboStyleSets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Theme specification
|
||||
*/
|
||||
export interface ThemeSpecification {
|
||||
node?: ItemThemeSpecifications;
|
||||
edge?: ItemThemeSpecifications;
|
||||
combo?: ItemThemeSpecifications;
|
||||
node?: NodeThemeSpecifications;
|
||||
edge?: EdgeThemeSpecifications;
|
||||
combo?: ComboThemeSpecifications;
|
||||
canvas?: {
|
||||
[cssName: string]: unknown;
|
||||
};
|
||||
|
61
packages/g6/src/util/mapper.ts
Normal file
61
packages/g6/src/util/mapper.ts
Normal file
@ -0,0 +1,61 @@
|
||||
import { NodeDisplayModelData } from 'types/node';
|
||||
|
||||
/**
|
||||
* Default mapper to transform simple styles in inner data.
|
||||
*/
|
||||
export const DEFAULT_MAPPER = {
|
||||
node: (innerNodeModel) => {
|
||||
const { id, data } = innerNodeModel;
|
||||
const { color, label, icon, badges, anchorPoints } = data;
|
||||
const resultData: NodeDisplayModelData = { ...data, keyShape: {} };
|
||||
if (color) {
|
||||
resultData.keyShape.fill = color;
|
||||
}
|
||||
if (label) {
|
||||
resultData.labelShape = {
|
||||
text: label,
|
||||
};
|
||||
}
|
||||
if (icon) {
|
||||
resultData.iconShape = icon;
|
||||
}
|
||||
if (badges) {
|
||||
resultData.badgeShapes = badges;
|
||||
}
|
||||
if (anchorPoints) {
|
||||
resultData.anchorShapes = anchorPoints.map((point) => ({
|
||||
position: point,
|
||||
}));
|
||||
}
|
||||
return {
|
||||
id,
|
||||
data: resultData,
|
||||
};
|
||||
},
|
||||
edge: (innerEdgeModel) => {
|
||||
const { id, source, target, data } = innerEdgeModel;
|
||||
const { color, label, icon, badge } = data;
|
||||
const resultData: NodeDisplayModelData = { ...data, keyShape: {} };
|
||||
if (color) {
|
||||
resultData.keyShape.stroke = color;
|
||||
}
|
||||
if (label) {
|
||||
resultData.labelShape = {
|
||||
text: label,
|
||||
};
|
||||
}
|
||||
if (icon) {
|
||||
resultData.iconShape = icon;
|
||||
}
|
||||
if (badge) {
|
||||
resultData.badgeShape = badge;
|
||||
}
|
||||
return {
|
||||
id,
|
||||
source,
|
||||
target,
|
||||
data: resultData,
|
||||
};
|
||||
},
|
||||
combo: (innerComboModel) => innerComboModel,
|
||||
};
|
9
packages/g6/src/util/math.ts
Normal file
9
packages/g6/src/util/math.ts
Normal file
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Whether the value is begween the range of [min, max]
|
||||
* @param {number} value the value to be judged
|
||||
* @param {number} min the min of the range
|
||||
* @param {number} max the max of the range
|
||||
* @return {boolean} bool the result boolean
|
||||
*/
|
||||
export const isBetween = (value: number, min: number, max: number) =>
|
||||
value >= min && value <= max;
|
195
packages/g6/src/util/point.ts
Normal file
195
packages/g6/src/util/point.ts
Normal file
@ -0,0 +1,195 @@
|
||||
import { Point } from '../types/common';
|
||||
import { isBetween } from './math';
|
||||
|
||||
/**
|
||||
* Find the nearest on in points to the curPoint.
|
||||
* @param points
|
||||
* @param curPoint
|
||||
* @returns
|
||||
*/
|
||||
export const getNearestPoint = (
|
||||
points: Point[],
|
||||
curPoint: Point,
|
||||
): {
|
||||
index: number;
|
||||
nearestPoint: Point;
|
||||
} => {
|
||||
let index = 0;
|
||||
let nearestPoint = points[0];
|
||||
let minDistance = distance(points[0], curPoint);
|
||||
for (let i = 0; i < points.length; i++) {
|
||||
const point = points[i];
|
||||
const dis = distance(point, curPoint);
|
||||
if (dis < minDistance) {
|
||||
nearestPoint = point;
|
||||
minDistance = dis;
|
||||
index = i;
|
||||
}
|
||||
}
|
||||
return {
|
||||
index,
|
||||
nearestPoint,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Get distance by two points.
|
||||
* @param p1 first point
|
||||
* @param p2 second point
|
||||
*/
|
||||
export const distance = (p1: Point, p2: Point): number => {
|
||||
const vx = p1.x - p2.x;
|
||||
const vy = p1.y - p2.y;
|
||||
return Math.sqrt(vx * vx + vy * vy);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get point and circle intersect point.
|
||||
* @param {ICircle} circle Circle's center x,y and radius r
|
||||
* @param {Point} point Point x,y
|
||||
* @return {Point} calculated intersect point
|
||||
*/
|
||||
export const getCircleIntersectByPoint = (
|
||||
circleProps: { x: number; y: number; r: number },
|
||||
point: Point,
|
||||
): Point | null => {
|
||||
const { x: cx, y: cy, r } = circleProps;
|
||||
const { x, y } = point;
|
||||
|
||||
const dx = x - cx;
|
||||
const dy = y - cy;
|
||||
if (dx * dx + dy * dy < r * r) {
|
||||
return null;
|
||||
}
|
||||
const angle = Math.atan(dy / dx);
|
||||
return {
|
||||
x: cx + Math.abs(r * Math.cos(angle)) * Math.sign(dx),
|
||||
y: cy + Math.abs(r * Math.sin(angle)) * Math.sign(dy),
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Get point and ellipse inIntersect.
|
||||
* @param {Object} ellipse ellipse center x,y and radius rx,ry
|
||||
* @param {Object} point Point x,y
|
||||
* @return {object} calculated intersect point
|
||||
*/
|
||||
export const getEllipseIntersectByPoint = (
|
||||
ellipseProps: {
|
||||
rx: number;
|
||||
ry: number;
|
||||
x: number;
|
||||
y: number;
|
||||
},
|
||||
point: Point,
|
||||
): Point => {
|
||||
const { rx: a, ry: b, x: cx, y: cy } = ellipseProps;
|
||||
|
||||
const dx = point.x - cx;
|
||||
const dy = point.y - cy;
|
||||
// The angle will be in range [-PI, PI]
|
||||
let angle = Math.atan2(dy / b, dx / a);
|
||||
|
||||
if (angle < 0) {
|
||||
// transfer to [0, 2*PI]
|
||||
angle += 2 * Math.PI;
|
||||
}
|
||||
|
||||
return {
|
||||
x: cx + a * Math.cos(angle),
|
||||
y: cy + b * Math.sin(angle),
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Point and rectangular intersection point.
|
||||
* @param {IRect} rect rect
|
||||
* @param {Point} point point
|
||||
* @return {PointPoint} rst;
|
||||
*/
|
||||
export const getRectIntersectByPoint = (
|
||||
rectProps: { x: number; y: number; width: number; height: number },
|
||||
point: Point,
|
||||
): Point | null => {
|
||||
const { x, y, width, height } = rectProps;
|
||||
const cx = x + width / 2;
|
||||
const cy = y + height / 2;
|
||||
const points: Point[] = [];
|
||||
const center: Point = {
|
||||
x: cx,
|
||||
y: cy,
|
||||
};
|
||||
points.push({
|
||||
x,
|
||||
y,
|
||||
});
|
||||
points.push({
|
||||
x: x + width,
|
||||
y,
|
||||
});
|
||||
points.push({
|
||||
x: x + width,
|
||||
y: y + height,
|
||||
});
|
||||
points.push({
|
||||
x,
|
||||
y: y + height,
|
||||
});
|
||||
points.push({
|
||||
x,
|
||||
y,
|
||||
});
|
||||
let rst: Point | null = null;
|
||||
for (let i = 1; i < points.length; i++) {
|
||||
rst = getLineIntersect(points[i - 1], points[i], center, point);
|
||||
if (rst) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return rst;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the intersect point of two lines.
|
||||
* @param {Point} p0 The start point of the first line.
|
||||
* @param {Point} p1 The end point of the first line.
|
||||
* @param {Point} p2 The start point of the second line.
|
||||
* @param {Point} p3 The end point of the second line.
|
||||
* @return {Point} Calculated intersect point.
|
||||
*/
|
||||
export const getLineIntersect = (
|
||||
p0: Point,
|
||||
p1: Point,
|
||||
p2: Point,
|
||||
p3: Point,
|
||||
): Point | null => {
|
||||
const tolerance = 0.0001;
|
||||
|
||||
const E: Point = {
|
||||
x: p2.x - p0.x,
|
||||
y: p2.y - p0.y,
|
||||
};
|
||||
const D0: Point = {
|
||||
x: p1.x - p0.x,
|
||||
y: p1.y - p0.y,
|
||||
};
|
||||
const D1: Point = {
|
||||
x: p3.x - p2.x,
|
||||
y: p3.y - p2.y,
|
||||
};
|
||||
const kross: number = D0.x * D1.y - D0.y * D1.x;
|
||||
const sqrKross: number = kross * kross;
|
||||
const invertKross: number = 1 / kross;
|
||||
const sqrLen0: number = D0.x * D0.x + D0.y * D0.y;
|
||||
const sqrLen1: number = D1.x * D1.x + D1.y * D1.y;
|
||||
if (sqrKross > tolerance * sqrLen0 * sqrLen1) {
|
||||
const s = (E.x * D1.y - E.y * D1.x) * invertKross;
|
||||
const t = (E.x * D0.y - E.y * D0.x) * invertKross;
|
||||
if (!isBetween(s, 0, 1) || !isBetween(t, 0, 1)) return null;
|
||||
return {
|
||||
x: p0.x + s * D0.x,
|
||||
y: p0.y + s * D0.y,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
};
|
@ -17,8 +17,15 @@ import { clone, isArray, isNumber } from '@antv/util';
|
||||
import { DEFAULT_LABEL_BG_PADDING } from '../constant';
|
||||
import { Point } from '../types/common';
|
||||
import { EdgeShapeMap } from '../types/edge';
|
||||
import { GShapeStyle, SHAPE_TYPE, ItemShapeStyles } from '../types/item';
|
||||
import {
|
||||
GShapeStyle,
|
||||
SHAPE_TYPE,
|
||||
ItemShapeStyles,
|
||||
ShapeStyle,
|
||||
} from '../types/item';
|
||||
import { NodeShapeMap } from '../types/node';
|
||||
import { isArrayOverlap } from './array';
|
||||
import { isBetween } from './math';
|
||||
|
||||
export const ShapeTagMap = {
|
||||
circle: Circle,
|
||||
@ -46,20 +53,30 @@ export const upsertShape = (
|
||||
id: string,
|
||||
style: GShapeStyle,
|
||||
shapeMap: { [shapeId: string]: DisplayObject },
|
||||
): DisplayObject => {
|
||||
): {
|
||||
updateStyles: ShapeStyle;
|
||||
shape: DisplayObject;
|
||||
} => {
|
||||
let shape = shapeMap[id];
|
||||
let updateStyles = {};
|
||||
if (!shape) {
|
||||
shape = createShape(type, style, id);
|
||||
updateStyles = style;
|
||||
} else if (shape.nodeName !== type) {
|
||||
shape.remove();
|
||||
shape = createShape(type, style, id);
|
||||
updateStyles = style;
|
||||
} else {
|
||||
const oldStyles = shape.attributes;
|
||||
Object.keys(style).forEach((key) => {
|
||||
shape.style[key] = style[key];
|
||||
if (oldStyles[key] !== style[key]) {
|
||||
updateStyles[key] = style[key];
|
||||
shape.style[key] = style[key];
|
||||
}
|
||||
});
|
||||
}
|
||||
shapeMap[id] = shape;
|
||||
return shape;
|
||||
return { shape, updateStyles };
|
||||
};
|
||||
|
||||
export const getGroupSucceedMap = (
|
||||
@ -136,6 +153,8 @@ export const formatPadding = (value, defaultArr = DEFAULT_LABEL_BG_PADDING) => {
|
||||
return [value[0], value[0], value[0], value[0]];
|
||||
case 2:
|
||||
return value.concat(value);
|
||||
case 3:
|
||||
return value.concat([value[0]]);
|
||||
default:
|
||||
return value;
|
||||
}
|
||||
@ -167,8 +186,8 @@ const merge2Styles = (
|
||||
styleMap1: ItemShapeStyles,
|
||||
styleMap2: ItemShapeStyles,
|
||||
) => {
|
||||
if (!styleMap1) return clone(styleMap2);
|
||||
else if (!styleMap2) return clone(styleMap1);
|
||||
if (!styleMap1) return { ...styleMap2 };
|
||||
else if (!styleMap2) return { ...styleMap1 };
|
||||
const mergedStyle = clone(styleMap1);
|
||||
Object.keys(styleMap2).forEach((shapeId) => {
|
||||
const style = styleMap2[shapeId];
|
||||
@ -404,12 +423,23 @@ export const getLineIntersect = (
|
||||
return null;
|
||||
};
|
||||
|
||||
const FEILDS_AFFECT_BBOX = {
|
||||
circle: ['r', 'lineWidth'],
|
||||
rect: ['width', 'height', 'lineWidth'],
|
||||
image: ['width', 'height', 'lineWidth'],
|
||||
ellipse: ['rx', 'ry', 'lineWidth'],
|
||||
text: ['fontSize', 'fontWeight'],
|
||||
polygon: ['points', 'lineWidth'],
|
||||
line: ['x1', 'x2', 'y1', 'y2', 'lineWidth'],
|
||||
polyline: ['points', 'lineWidth'],
|
||||
path: ['points', 'lineWidth'],
|
||||
};
|
||||
/**
|
||||
* Whether the value is begween the range of [min, max]
|
||||
* @param {number} value the value to be judged
|
||||
* @param {number} min the min of the range
|
||||
* @param {number} max the max of the range
|
||||
* @return {boolean} bool the result boolean
|
||||
* Will the fields in style affect the bbox.
|
||||
* @param type shape type
|
||||
* @param style style object
|
||||
* @returns
|
||||
*/
|
||||
const isBetween = (value: number, min: number, max: number) =>
|
||||
value >= min && value <= max;
|
||||
export const isStyleAffectBBox = (type: SHAPE_TYPE, style: ShapeStyle) => {
|
||||
return isArrayOverlap(Object.keys(style), FEILDS_AFFECT_BBOX[type]);
|
||||
};
|
||||
|
@ -10,7 +10,12 @@ import {
|
||||
} from '@antv/g-plugin-3d';
|
||||
import { EdgeShapeMap } from '../types/edge';
|
||||
import { NodeShapeMap } from '../types/node';
|
||||
import { GShapeStyle, SHAPE_TYPE, SHAPE_TYPE_3D } from '../types/item';
|
||||
import {
|
||||
GShapeStyle,
|
||||
SHAPE_TYPE,
|
||||
SHAPE_TYPE_3D,
|
||||
ShapeStyle,
|
||||
} from '../types/item';
|
||||
import { ShapeTagMap, createShape } from './shape';
|
||||
|
||||
const GeometryTagMap = {
|
||||
@ -103,20 +108,30 @@ export const upsertShape3D = (
|
||||
style: GShapeStyle,
|
||||
shapeMap: { [shapeId: string]: DisplayObject },
|
||||
device: any,
|
||||
): DisplayObject => {
|
||||
): {
|
||||
updateStyles: ShapeStyle;
|
||||
shape: DisplayObject;
|
||||
} => {
|
||||
let shape = shapeMap[id];
|
||||
let updateStyles = {};
|
||||
if (!shape) {
|
||||
shape = createShape3D(type, style, id, device);
|
||||
updateStyles = style;
|
||||
} else if (shape.nodeName !== type) {
|
||||
shape.remove();
|
||||
shape = createShape3D(type, style, id, device);
|
||||
updateStyles = style;
|
||||
} else {
|
||||
const oldStyles = shape.attributes;
|
||||
Object.keys(style).forEach((key) => {
|
||||
shape.style[key] = style[key];
|
||||
if (oldStyles[key] !== style[key]) {
|
||||
updateStyles[key] = style[key];
|
||||
shape.style[key] = style[key];
|
||||
}
|
||||
});
|
||||
}
|
||||
shapeMap[id] = shape;
|
||||
return shape;
|
||||
return { shape, updateStyles };
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -2,7 +2,6 @@ import * as graphs from './intergration/index';
|
||||
|
||||
const SelectGraph = document.getElementById('select') as HTMLSelectElement;
|
||||
let firstKey;
|
||||
console.log('firstKey', firstKey);
|
||||
const Options = Object.keys(graphs).map((key, index) => {
|
||||
const option = document.createElement('option');
|
||||
if (index === 0) {
|
||||
@ -18,10 +17,9 @@ SelectGraph.replaceChildren(...Options);
|
||||
SelectGraph.onchange = (e) => {
|
||||
//@ts-ignore
|
||||
const { value } = e.target;
|
||||
console.log(value);
|
||||
history.pushState({ value }, '', `?name=${value}`);
|
||||
const container = document.getElementById('container');
|
||||
container.replaceChildren('');
|
||||
container?.replaceChildren('');
|
||||
graphs[value]();
|
||||
};
|
||||
// 初始化
|
||||
|
@ -1370,10 +1370,6 @@ describe('lasso-select behavior with brushStyle', () => {
|
||||
canvas: { x: 200, y: 150 },
|
||||
shiftKey: true,
|
||||
});
|
||||
console.log(
|
||||
'graph.transientCanvas.getRoot().childNodes',
|
||||
graph.transientCanvas.getRoot().childNodes,
|
||||
);
|
||||
expect(graph.transientCanvas.getRoot().childNodes.length).toBe(2);
|
||||
|
||||
// TODO: wait for correct removeBehaviors
|
||||
|
857
packages/g6/tests/unit/edge-spec.ts
Normal file
857
packages/g6/tests/unit/edge-spec.ts
Normal file
@ -0,0 +1,857 @@
|
||||
// @ts-nocheck
|
||||
|
||||
import { DisplayObject } from '@antv/g';
|
||||
import { clone } from '@antv/util';
|
||||
import G6, {
|
||||
EdgeDisplayModel,
|
||||
Graph,
|
||||
IGraph,
|
||||
NodeDisplayModel,
|
||||
} from '../../src/index';
|
||||
import { LineEdge } from '../../src/stdlib/item/edge';
|
||||
import { CircleNode } from '../../src/stdlib/item/node';
|
||||
import { NodeModelData, NodeShapeMap } from '../../src/types/node';
|
||||
import { extend } from '../../src/util/extend';
|
||||
import { upsertShape } from '../../src/util/shape';
|
||||
|
||||
const container = document.createElement('div');
|
||||
document.querySelector('body').appendChild(container);
|
||||
|
||||
let graph: IGraph<any>;
|
||||
|
||||
describe('edge item', () => {
|
||||
it('new graph with two nodes and one edge', (done) => {
|
||||
graph = new G6.Graph({
|
||||
container,
|
||||
width: 500,
|
||||
height: 500,
|
||||
type: 'graph',
|
||||
modes: {
|
||||
default: ['drag-node'],
|
||||
},
|
||||
data: {
|
||||
nodes: [
|
||||
{
|
||||
id: 'node1',
|
||||
data: {
|
||||
x: 100,
|
||||
y: 100,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'node2',
|
||||
data: { x: 300, y: 300 },
|
||||
},
|
||||
],
|
||||
edges: [
|
||||
{
|
||||
id: 'edge1',
|
||||
source: 'node1',
|
||||
target: 'node2',
|
||||
data: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
graph.on('afterrender', () => {
|
||||
const edgeItem = graph.itemController.itemMap['edge1'];
|
||||
expect(edgeItem).not.toBe(undefined);
|
||||
expect(edgeItem.shapeMap.labelShape).toBe(undefined);
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('update edge label', (done) => {
|
||||
const padding = [4, 16, 4, 8];
|
||||
graph.updateData('edge', {
|
||||
id: 'edge1',
|
||||
data: {
|
||||
labelShape: {
|
||||
text: 'edge-label',
|
||||
},
|
||||
labelBackgroundShape: {
|
||||
radius: 10,
|
||||
padding,
|
||||
fill: '#f00',
|
||||
},
|
||||
iconShape: {
|
||||
text: 'A',
|
||||
fill: '#f00',
|
||||
},
|
||||
},
|
||||
});
|
||||
const edgeItem = graph.itemController.itemMap['edge1'];
|
||||
expect(edgeItem.shapeMap.labelShape).not.toBe(undefined);
|
||||
expect(edgeItem.shapeMap.labelShape.attributes.text).toBe('edge-label');
|
||||
const fill = edgeItem.shapeMap.labelShape.attributes.fill;
|
||||
expect(fill).toBe('rgba(0,0,0,0.85)');
|
||||
expect(edgeItem.shapeMap.labelShape.attributes.transform).toBe(
|
||||
'rotate(45)',
|
||||
);
|
||||
expect(edgeItem.shapeMap.labelBackgroundShape.attributes.transform).toBe(
|
||||
'rotate(45)',
|
||||
);
|
||||
let labelBounds = edgeItem.shapeMap.labelShape.getGeometryBounds();
|
||||
expect(edgeItem.shapeMap.labelBackgroundShape.attributes.width).toBe(
|
||||
labelBounds.max[0] - labelBounds.min[0] + padding[1] + padding[3],
|
||||
);
|
||||
expect(edgeItem.shapeMap.labelBackgroundShape.attributes.height).toBe(
|
||||
labelBounds.max[1] - labelBounds.min[1] + padding[0] + padding[2],
|
||||
);
|
||||
|
||||
graph.updateData('edge', {
|
||||
id: 'edge1',
|
||||
data: {
|
||||
labelShape: {
|
||||
fill: '#00f',
|
||||
position: 'start',
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(edgeItem.shapeMap.labelShape.attributes.fill).toBe('#00f');
|
||||
expect(
|
||||
edgeItem.shapeMap.labelShape.attributes.x -
|
||||
edgeItem.shapeMap.labelBackgroundShape.attributes.x,
|
||||
).toBe(padding[3]);
|
||||
labelBounds = edgeItem.shapeMap.labelShape.getGeometryBounds();
|
||||
const labelWidth = labelBounds.max[0] - labelBounds.min[0];
|
||||
const labelHeight = labelBounds.max[1] - labelBounds.min[1];
|
||||
const labelBgBounds =
|
||||
edgeItem.shapeMap.labelBackgroundShape.getGeometryBounds();
|
||||
const labelBgWidth = labelBgBounds.max[0] - labelBgBounds.min[0];
|
||||
const labelBgHeight = labelBgBounds.max[1] - labelBgBounds.min[1];
|
||||
expect(labelBgWidth - labelWidth).toBe(padding[1] + padding[3]);
|
||||
expect(labelBgHeight - labelHeight).toBe(padding[0] + padding[2]);
|
||||
|
||||
graph.updateData('edge', {
|
||||
id: 'edge1',
|
||||
data: {
|
||||
labelShape: undefined,
|
||||
},
|
||||
});
|
||||
expect(edgeItem.shapeMap.labelShape).toBe(undefined);
|
||||
expect(edgeItem.shapeMap.labelBackgroundShape).toBe(undefined);
|
||||
done();
|
||||
});
|
||||
it('update edge icon', (done) => {
|
||||
// add image icon to follow the label at path's center
|
||||
graph.updateData('edge', {
|
||||
id: 'edge1',
|
||||
data: {
|
||||
labelShape: {
|
||||
text: 'abcddd',
|
||||
fill: '#f00',
|
||||
position: 'center',
|
||||
},
|
||||
iconShape: {
|
||||
text: '',
|
||||
img: 'https://gw.alipayobjects.com/zos/basement_prod/012bcf4f-423b-4922-8c24-32a89f8c41ce.svg',
|
||||
// text: 'A',
|
||||
fill: '#f00',
|
||||
fontSize: 20,
|
||||
},
|
||||
},
|
||||
});
|
||||
const edgeItem = graph.itemController.itemMap['edge1'];
|
||||
let { labelShape, iconShape, labelBackgroundShape } = edgeItem.shapeMap;
|
||||
expect(iconShape.attributes.x + iconShape.attributes.width + 6).toBe(
|
||||
labelBackgroundShape.getGeometryBounds().min[0] +
|
||||
labelBackgroundShape.attributes.x,
|
||||
);
|
||||
expect(iconShape.attributes.transform).toBe(
|
||||
labelBackgroundShape.attributes.transform,
|
||||
);
|
||||
expect(
|
||||
Math.floor(iconShape.attributes.y + iconShape.attributes.height / 2),
|
||||
).toBeCloseTo(
|
||||
Math.floor(
|
||||
labelBackgroundShape.getGeometryBounds().center[1] +
|
||||
labelBackgroundShape.attributes.y,
|
||||
),
|
||||
0.01,
|
||||
);
|
||||
|
||||
// update icon to be a text
|
||||
graph.updateData('edge', {
|
||||
id: 'edge1',
|
||||
data: {
|
||||
iconShape: {
|
||||
text: 'A',
|
||||
fill: '#f00',
|
||||
fontWeight: 800,
|
||||
},
|
||||
},
|
||||
});
|
||||
labelShape = edgeItem.shapeMap['labelShape'];
|
||||
iconShape = edgeItem.shapeMap['iconShape'];
|
||||
labelBackgroundShape = edgeItem.shapeMap['labelBackgroundShape'];
|
||||
expect(iconShape.attributes.x + iconShape.attributes.fontSize + 6).toBe(
|
||||
labelBackgroundShape.getGeometryBounds().min[0] +
|
||||
labelBackgroundShape.attributes.x,
|
||||
);
|
||||
expect(iconShape.attributes.transform).toBe(
|
||||
labelBackgroundShape.attributes.transform,
|
||||
);
|
||||
expect(
|
||||
Math.floor(iconShape.attributes.y + iconShape.attributes.fontSize / 2),
|
||||
).toBeCloseTo(
|
||||
Math.floor(
|
||||
labelBackgroundShape.getGeometryBounds().center[1] +
|
||||
labelBackgroundShape.attributes.y,
|
||||
),
|
||||
0.01,
|
||||
);
|
||||
|
||||
// move label to the start, and the icon follows
|
||||
graph.updateData('edge', {
|
||||
id: 'edge1',
|
||||
data: {
|
||||
labelShape: {
|
||||
position: 'start',
|
||||
},
|
||||
},
|
||||
});
|
||||
labelShape = edgeItem.shapeMap['labelShape'];
|
||||
iconShape = edgeItem.shapeMap['iconShape'];
|
||||
labelBackgroundShape = edgeItem.shapeMap['labelBackgroundShape'];
|
||||
expect(iconShape.attributes.x + iconShape.attributes.fontSize + 6).toBe(
|
||||
labelBackgroundShape.getGeometryBounds().min[0] +
|
||||
labelBackgroundShape.attributes.x,
|
||||
);
|
||||
expect(iconShape.attributes.transform).toBe(
|
||||
labelShape.attributes.transform,
|
||||
);
|
||||
expect(iconShape.attributes.y + iconShape.attributes.fontSize / 2).toBe(
|
||||
labelBackgroundShape.getGeometryBounds().center[1] +
|
||||
labelBackgroundShape.attributes.y,
|
||||
);
|
||||
graph.destroy();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('edge mapper', () => {
|
||||
const data = {
|
||||
nodes: [
|
||||
{
|
||||
id: 'node1',
|
||||
data: { x: 100, y: 200 },
|
||||
},
|
||||
{
|
||||
id: 'node2',
|
||||
data: { x: 100, y: 300 },
|
||||
},
|
||||
{
|
||||
id: 'node3',
|
||||
data: { x: 200, y: 300 },
|
||||
},
|
||||
],
|
||||
edges: [
|
||||
{
|
||||
id: 'edge1',
|
||||
source: 'node1',
|
||||
target: 'node2',
|
||||
data: { buStatus: true, buType: 1, buName: 'edge-1' },
|
||||
},
|
||||
{
|
||||
id: 'edge2',
|
||||
source: 'node1',
|
||||
target: 'node3',
|
||||
data: { buStatus: false, buType: 0, buName: 'edge-2' },
|
||||
},
|
||||
],
|
||||
};
|
||||
const graphConfig = {
|
||||
container,
|
||||
width: 500,
|
||||
height: 500,
|
||||
type: 'graph',
|
||||
};
|
||||
it('function mapper', (done) => {
|
||||
const graph = new G6.Graph({
|
||||
...graphConfig,
|
||||
edge: (innerModel) => {
|
||||
const { x, y, buStatus } = innerModel.data;
|
||||
return {
|
||||
...innerModel,
|
||||
data: {
|
||||
x,
|
||||
y,
|
||||
keyShape: {
|
||||
stroke: buStatus ? '#0f0' : '#f00',
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
} as any);
|
||||
graph.read(clone(data));
|
||||
graph.on('afterrender', () => {
|
||||
const edge1 = graph.itemController.itemMap['edge1'];
|
||||
expect(edge1.shapeMap.keyShape.attributes.stroke).toBe('#0f0');
|
||||
let edge2 = graph.itemController.itemMap['edge2'];
|
||||
expect(edge2.shapeMap.keyShape.attributes.stroke).toBe('#f00');
|
||||
|
||||
// update user data
|
||||
graph.updateData('edge', {
|
||||
id: 'edge2',
|
||||
data: {
|
||||
buStatus: true,
|
||||
},
|
||||
});
|
||||
edge2 = graph.itemController.itemMap['edge2'];
|
||||
expect(edge2.shapeMap.keyShape.attributes.stroke).toBe('#0f0');
|
||||
|
||||
graph.destroy();
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('value and encode mapper', (done) => {
|
||||
const graph = new G6.Graph({
|
||||
...graphConfig,
|
||||
edge: {
|
||||
keyShape: {
|
||||
stroke: {
|
||||
fields: ['buStatus'],
|
||||
formatter: (innerModel) =>
|
||||
innerModel.data.buStatus ? '#0f0' : '#f00',
|
||||
},
|
||||
lineWidth: 5,
|
||||
lineDash: {
|
||||
fields: ['buStatus', 'buType'],
|
||||
formatter: (innerModel) =>
|
||||
innerModel.data.buStatus || innerModel.data.buType
|
||||
? undefined
|
||||
: [5, 5],
|
||||
},
|
||||
},
|
||||
labelShape: {
|
||||
text: {
|
||||
fields: ['buName', 'buType'],
|
||||
formatter: (innerModel) =>
|
||||
`${innerModel.data.buName}-${innerModel.data.buType}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
} as any);
|
||||
graph.read(clone(data));
|
||||
graph.on('afterrender', () => {
|
||||
const edge1 = graph.itemController.itemMap['edge1'];
|
||||
expect(edge1.shapeMap.keyShape.attributes.stroke).toBe('#0f0');
|
||||
expect(edge1.shapeMap.keyShape.attributes.lineWidth).toBe(5);
|
||||
expect(edge1.shapeMap.keyShape.attributes.lineDash).toBe('');
|
||||
expect(edge1.shapeMap.labelShape.attributes.text).toBe('edge-1-1');
|
||||
let edge2 = graph.itemController.itemMap['edge2'];
|
||||
expect(edge2.shapeMap.keyShape.attributes.stroke).toBe('#f00');
|
||||
expect(edge2.shapeMap.keyShape.attributes.lineWidth).toBe(5);
|
||||
expect(JSON.stringify(edge2.shapeMap.keyShape.attributes.lineDash)).toBe(
|
||||
'[5,5]',
|
||||
);
|
||||
expect(edge2.shapeMap.labelShape.attributes.text).toBe('edge-2-0');
|
||||
|
||||
// update user data
|
||||
graph.updateData('edge', {
|
||||
id: 'edge2',
|
||||
data: {
|
||||
buStatus: true,
|
||||
buName: 'newedge2name',
|
||||
},
|
||||
});
|
||||
edge2 = graph.itemController.itemMap['edge2'];
|
||||
expect(edge2.shapeMap.keyShape.attributes.stroke).toBe('#0f0');
|
||||
expect(edge2.shapeMap.keyShape.attributes.lineDash).toBe('');
|
||||
expect(edge2.shapeMap.labelShape.attributes.text).toBe('newedge2name-0');
|
||||
|
||||
graph.destroy();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('state', () => {
|
||||
it('edge state', (done) => {
|
||||
const graph = new Graph({
|
||||
container,
|
||||
width: 500,
|
||||
height: 500,
|
||||
type: 'graph',
|
||||
data: {
|
||||
nodes: [
|
||||
{
|
||||
id: 'node1',
|
||||
data: { x: 100, y: 200 },
|
||||
},
|
||||
{
|
||||
id: 'node2',
|
||||
data: { x: 100, y: 300 },
|
||||
},
|
||||
{
|
||||
id: 'node3',
|
||||
data: { x: 200, y: 300 },
|
||||
},
|
||||
],
|
||||
edges: [
|
||||
{
|
||||
id: 'edge1',
|
||||
source: 'node1',
|
||||
target: 'node2',
|
||||
data: {},
|
||||
},
|
||||
{
|
||||
id: 'edge2',
|
||||
source: 'node1',
|
||||
target: 'node3',
|
||||
data: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
edgeState: {
|
||||
selected: {
|
||||
keyShape: {
|
||||
stroke: '#0f0',
|
||||
lineWidth: 2,
|
||||
},
|
||||
},
|
||||
highlight: {
|
||||
keyShape: {
|
||||
stroke: '#00f',
|
||||
opacity: 0.5,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
graph.on('afterrender', () => {
|
||||
expect(graph.findIdByState('edge', 'selected').length).toBe(0);
|
||||
graph.setItemState('edge1', 'selected', true);
|
||||
expect(graph.findIdByState('edge', 'selected').length).toBe(1);
|
||||
expect(graph.findIdByState('edge', 'selected')[0]).toBe('edge1');
|
||||
expect(
|
||||
graph.itemController.itemMap['edge1'].shapeMap.keyShape.style.lineWidth,
|
||||
).toBe(2);
|
||||
expect(
|
||||
graph.itemController.itemMap['edge1'].shapeMap.keyShape.style.stroke,
|
||||
).toBe('#0f0');
|
||||
graph.setItemState('edge1', 'selected', false);
|
||||
expect(graph.findIdByState('edge', 'selected').length).toBe(0);
|
||||
expect(
|
||||
graph.itemController.itemMap['edge1'].shapeMap.keyShape.style.lineWidth,
|
||||
).toBe(1);
|
||||
|
||||
// set multiple edges state
|
||||
graph.setItemState(['edge1', 'edge2'], 'selected', true);
|
||||
expect(graph.findIdByState('edge', 'selected').length).toBe(2);
|
||||
expect(
|
||||
graph.itemController.itemMap['edge1'].shapeMap.keyShape.style.lineWidth,
|
||||
).toBe(2);
|
||||
expect(
|
||||
graph.itemController.itemMap['edge1'].shapeMap.keyShape.style.stroke,
|
||||
).toBe('#0f0');
|
||||
expect(
|
||||
graph.itemController.itemMap['edge2'].shapeMap.keyShape.style.lineWidth,
|
||||
).toBe(2);
|
||||
expect(
|
||||
graph.itemController.itemMap['edge2'].shapeMap.keyShape.style.stroke,
|
||||
).toBe('#0f0');
|
||||
graph.setItemState('edge1', 'selected', false);
|
||||
expect(graph.findIdByState('edge', 'selected').length).toBe(1);
|
||||
expect(graph.findIdByState('edge', 'selected')[0]).toBe('edge2');
|
||||
expect(
|
||||
graph.itemController.itemMap['edge1'].shapeMap.keyShape.style.lineWidth,
|
||||
).toBe(1);
|
||||
graph.setItemState(['edge1', 'edge2'], 'selected', false);
|
||||
expect(graph.findIdByState('edge', 'selected').length).toBe(0);
|
||||
expect(
|
||||
graph.itemController.itemMap['edge2'].shapeMap.keyShape.style.lineWidth,
|
||||
).toBe(1);
|
||||
|
||||
// // set multiple states
|
||||
graph.setItemState(['edge2', 'edge1'], ['selected', 'highlight'], true);
|
||||
expect(graph.findIdByState('edge', 'selected').length).toBe(2);
|
||||
expect(graph.findIdByState('edge', 'highlight').length).toBe(2);
|
||||
// should be merged styles from selected and highlight
|
||||
expect(
|
||||
graph.itemController.itemMap['edge1'].shapeMap.keyShape.style.lineWidth,
|
||||
).toBe(2);
|
||||
expect(
|
||||
graph.itemController.itemMap['edge1'].shapeMap.keyShape.style.stroke,
|
||||
).toBe('#00f');
|
||||
expect(
|
||||
graph.itemController.itemMap['edge1'].shapeMap.keyShape.style.opacity,
|
||||
).toBe(0.5);
|
||||
expect(
|
||||
graph.itemController.itemMap['edge2'].shapeMap.keyShape.style.lineWidth,
|
||||
).toBe(2);
|
||||
expect(
|
||||
graph.itemController.itemMap['edge2'].shapeMap.keyShape.style.stroke,
|
||||
).toBe('#00f');
|
||||
expect(
|
||||
graph.itemController.itemMap['edge2'].shapeMap.keyShape.style.opacity,
|
||||
).toBe(0.5);
|
||||
|
||||
// clear states
|
||||
graph.clearItemState(['edge1', 'edge2']);
|
||||
expect(graph.findIdByState('edge', 'selected').length).toBe(0);
|
||||
expect(graph.findIdByState('edge', 'highlight').length).toBe(0);
|
||||
expect(
|
||||
graph.itemController.itemMap['edge1'].shapeMap.keyShape.style.lineWidth,
|
||||
).toBe(1);
|
||||
expect(
|
||||
graph.itemController.itemMap['edge1'].shapeMap.keyShape.style.opacity,
|
||||
).toBe(1);
|
||||
|
||||
graph.destroy();
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('edge state with assigned style', (done) => {
|
||||
const graph = new Graph({
|
||||
container,
|
||||
width: 500,
|
||||
height: 500,
|
||||
type: 'graph',
|
||||
data: {
|
||||
nodes: [
|
||||
{
|
||||
id: 'node1',
|
||||
data: { x: 100, y: 200 },
|
||||
},
|
||||
{
|
||||
id: 'node2',
|
||||
data: { x: 100, y: 300 },
|
||||
},
|
||||
{
|
||||
id: 'node3',
|
||||
data: { x: 200, y: 300 },
|
||||
},
|
||||
],
|
||||
edges: [
|
||||
{
|
||||
id: 'edge1',
|
||||
source: 'node1',
|
||||
target: 'node2',
|
||||
data: {
|
||||
keyShape: {
|
||||
stroke: '#f00',
|
||||
lineDash: [2, 2],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'edge2',
|
||||
source: 'node1',
|
||||
target: 'node3',
|
||||
data: {
|
||||
keyShape: {
|
||||
stroke: '#f00',
|
||||
lineDash: [2, 2],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
edgeState: {
|
||||
selected: {
|
||||
keyShape: {
|
||||
stroke: '#0f0',
|
||||
lineWidth: 2,
|
||||
},
|
||||
},
|
||||
highlight: {
|
||||
keyShape: {
|
||||
stroke: '#00f',
|
||||
opacity: 0.5,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
graph.on('afterrender', () => {
|
||||
expect(graph.findIdByState('edge', 'selected').length).toBe(0);
|
||||
graph.setItemState('edge1', 'selected', true);
|
||||
expect(graph.findIdByState('edge', 'selected').length).toBe(1);
|
||||
expect(graph.findIdByState('edge', 'selected')[0]).toBe('edge1');
|
||||
expect(
|
||||
graph.itemController.itemMap['edge1'].shapeMap.keyShape.style.lineWidth,
|
||||
).toBe(2);
|
||||
expect(
|
||||
graph.itemController.itemMap['edge1'].shapeMap.keyShape.style.stroke,
|
||||
).toBe('#0f0');
|
||||
expect(
|
||||
JSON.stringify(
|
||||
graph.itemController.itemMap['edge1'].shapeMap.keyShape.style
|
||||
.lineDash,
|
||||
),
|
||||
).toBe('[2,2]');
|
||||
graph.setItemState('edge1', 'selected', false);
|
||||
expect(graph.findIdByState('edge', 'selected').length).toBe(0);
|
||||
expect(
|
||||
graph.itemController.itemMap['edge1'].shapeMap.keyShape.style.lineWidth,
|
||||
).toBe(1);
|
||||
expect(
|
||||
graph.itemController.itemMap['edge1'].shapeMap.keyShape.style.stroke,
|
||||
).toBe('#f00');
|
||||
expect(
|
||||
JSON.stringify(
|
||||
graph.itemController.itemMap['edge1'].shapeMap.keyShape.style
|
||||
.lineDash,
|
||||
),
|
||||
).toBe('[2,2]');
|
||||
|
||||
// set multiple edges state
|
||||
graph.setItemState(['edge1', 'edge2'], 'selected', true);
|
||||
expect(graph.findIdByState('edge', 'selected').length).toBe(2);
|
||||
expect(
|
||||
graph.itemController.itemMap['edge1'].shapeMap.keyShape.style.lineWidth,
|
||||
).toBe(2);
|
||||
expect(
|
||||
graph.itemController.itemMap['edge1'].shapeMap.keyShape.style.stroke,
|
||||
).toBe('#0f0');
|
||||
expect(
|
||||
JSON.stringify(
|
||||
graph.itemController.itemMap['edge1'].shapeMap.keyShape.style
|
||||
.lineDash,
|
||||
),
|
||||
).toBe('[2,2]');
|
||||
expect(
|
||||
graph.itemController.itemMap['edge2'].shapeMap.keyShape.style.lineWidth,
|
||||
).toBe(2);
|
||||
expect(
|
||||
graph.itemController.itemMap['edge2'].shapeMap.keyShape.style.stroke,
|
||||
).toBe('#0f0');
|
||||
expect(
|
||||
JSON.stringify(
|
||||
graph.itemController.itemMap['edge1'].shapeMap.keyShape.style
|
||||
.lineDash,
|
||||
),
|
||||
).toBe('[2,2]');
|
||||
graph.setItemState('edge1', 'selected', false);
|
||||
expect(graph.findIdByState('edge', 'selected').length).toBe(1);
|
||||
expect(graph.findIdByState('edge', 'selected')[0]).toBe('edge2');
|
||||
expect(
|
||||
graph.itemController.itemMap['edge1'].shapeMap.keyShape.style.lineWidth,
|
||||
).toBe(1);
|
||||
expect(
|
||||
graph.itemController.itemMap['edge1'].shapeMap.keyShape.style.stroke,
|
||||
).toBe('#f00');
|
||||
graph.setItemState(['edge1', 'edge2'], 'selected', false);
|
||||
expect(graph.findIdByState('edge', 'selected').length).toBe(0);
|
||||
expect(
|
||||
graph.itemController.itemMap['edge2'].shapeMap.keyShape.style.lineWidth,
|
||||
).toBe(1);
|
||||
expect(
|
||||
graph.itemController.itemMap['edge1'].shapeMap.keyShape.style.stroke,
|
||||
).toBe('#f00');
|
||||
|
||||
// set multiple states
|
||||
graph.setItemState(['edge2', 'edge1'], ['selected', 'highlight'], true);
|
||||
expect(graph.findIdByState('edge', 'selected').length).toBe(2);
|
||||
expect(graph.findIdByState('edge', 'highlight').length).toBe(2);
|
||||
// should be merged styles from selected and highlight
|
||||
expect(
|
||||
graph.itemController.itemMap['edge1'].shapeMap.keyShape.style.lineWidth,
|
||||
).toBe(2);
|
||||
expect(
|
||||
graph.itemController.itemMap['edge1'].shapeMap.keyShape.style.stroke,
|
||||
).toBe('#00f');
|
||||
expect(
|
||||
graph.itemController.itemMap['edge1'].shapeMap.keyShape.style.opacity,
|
||||
).toBe(0.5);
|
||||
expect(
|
||||
graph.itemController.itemMap['edge2'].shapeMap.keyShape.style.lineWidth,
|
||||
).toBe(2);
|
||||
expect(
|
||||
graph.itemController.itemMap['edge2'].shapeMap.keyShape.style.stroke,
|
||||
).toBe('#00f');
|
||||
expect(
|
||||
graph.itemController.itemMap['edge2'].shapeMap.keyShape.style.opacity,
|
||||
).toBe(0.5);
|
||||
|
||||
// clear states
|
||||
graph.clearItemState(['edge1', 'edge2']);
|
||||
expect(graph.findIdByState('edge', 'selected').length).toBe(0);
|
||||
expect(graph.findIdByState('edge', 'highlight').length).toBe(0);
|
||||
expect(
|
||||
graph.itemController.itemMap['edge1'].shapeMap.keyShape.style.lineWidth,
|
||||
).toBe(1);
|
||||
expect(
|
||||
graph.itemController.itemMap['edge1'].shapeMap.keyShape.style.opacity,
|
||||
).toBe(1);
|
||||
|
||||
graph.destroy();
|
||||
done();
|
||||
});
|
||||
});
|
||||
class CustomNode extends CircleNode {
|
||||
public defaultStyles = {
|
||||
keyShape: {
|
||||
r: 25,
|
||||
x: 0,
|
||||
y: 0,
|
||||
fill: '#ff0',
|
||||
lineWidth: 0,
|
||||
stroke: '#0f0',
|
||||
},
|
||||
};
|
||||
public drawLabelShape(
|
||||
model: NodeDisplayModel,
|
||||
shapeMap: NodeShapeMap,
|
||||
diffData?: { oldData: NodeModelData; newData: NodeModelData },
|
||||
) {
|
||||
const { labelShape: propsLabelStyle } = model.data;
|
||||
const labelStyle = Object.assign(
|
||||
{},
|
||||
this.defaultStyles.labelShape,
|
||||
propsLabelStyle,
|
||||
);
|
||||
const labelShape = this.upsertShape(
|
||||
'text',
|
||||
'labelShape',
|
||||
{
|
||||
...labelStyle,
|
||||
text: model.id,
|
||||
},
|
||||
shapeMap,
|
||||
).shape;
|
||||
return labelShape;
|
||||
}
|
||||
public drawOtherShapes(
|
||||
model: NodeDisplayModel,
|
||||
shapeMap: NodeShapeMap,
|
||||
diffData?: { oldData: NodeModelData; newData: NodeModelData },
|
||||
) {
|
||||
return {
|
||||
extraShape: upsertShape(
|
||||
'circle',
|
||||
'extraShape',
|
||||
{
|
||||
r: 4,
|
||||
fill: '#0f0',
|
||||
x: -20,
|
||||
y: 0,
|
||||
},
|
||||
shapeMap,
|
||||
).shape,
|
||||
};
|
||||
}
|
||||
}
|
||||
class CustomEdge extends LineEdge {
|
||||
public afterDraw(
|
||||
model: EdgeDisplayModel,
|
||||
shapeMap: { [shapeId: string]: DisplayObject<any, any> },
|
||||
shapesChanged?: string[],
|
||||
): { [otherShapeId: string]: DisplayObject } {
|
||||
const { keyShape } = shapeMap;
|
||||
const point = keyShape.getPoint(0.3);
|
||||
return {
|
||||
buShape: this.upsertShape(
|
||||
'rect',
|
||||
'buShape',
|
||||
{
|
||||
width: 6,
|
||||
height: 6,
|
||||
x: point.x,
|
||||
y: point.y,
|
||||
fill: '#0f0',
|
||||
...model.data?.otherShapes?.buShape, // merged style from mappers and states
|
||||
},
|
||||
shapeMap,
|
||||
).shape,
|
||||
};
|
||||
}
|
||||
}
|
||||
const CustomGraph = extend(Graph, {
|
||||
nodes: {
|
||||
'custom-node2': CustomNode,
|
||||
},
|
||||
edges: {
|
||||
'custom-edge2': CustomEdge,
|
||||
},
|
||||
});
|
||||
it('custom edge with setState', (done) => {
|
||||
const graph = new CustomGraph({
|
||||
container,
|
||||
width: 500,
|
||||
height: 500,
|
||||
type: 'graph',
|
||||
data: {
|
||||
nodes: [
|
||||
{
|
||||
id: 'node1',
|
||||
data: { x: 100, y: 200, type: 'custom-node2' },
|
||||
},
|
||||
{
|
||||
id: 'node2',
|
||||
data: { x: 100, y: 300, type: 'circle-node' },
|
||||
},
|
||||
{
|
||||
id: 'node3',
|
||||
data: { x: 200, y: 300, labelShape: undefined },
|
||||
},
|
||||
],
|
||||
edges: [
|
||||
{
|
||||
id: 'edge1',
|
||||
source: 'node1',
|
||||
target: 'node2',
|
||||
data: { type: 'custom-edge2' },
|
||||
},
|
||||
{
|
||||
id: 'edge2',
|
||||
source: 'node1',
|
||||
target: 'node3',
|
||||
data: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
node: {
|
||||
// affect the nodes without type field in their data object, which means configurations in the user data has higher priority than that in the mapper
|
||||
type: 'custom-node2',
|
||||
// affect the nodes without labelShape field in their data object, which means configurations in the user data has higher priority than that in the mapper
|
||||
labelShape: {},
|
||||
},
|
||||
edgeState: {
|
||||
selected: {
|
||||
keyShape: {
|
||||
lineWidth: 2,
|
||||
stroke: '#00f',
|
||||
},
|
||||
otherShapes: {
|
||||
buShape: {
|
||||
fill: '#fff',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
graph.on('afterrender', () => {
|
||||
const edge1 = graph.itemController.itemMap['edge1'];
|
||||
graph.setItemState('edge1', 'selected', true);
|
||||
expect(edge1.shapeMap.keyShape.style.stroke).toBe('#00f');
|
||||
expect(edge1.shapeMap.keyShape.style.lineWidth).toBe(2);
|
||||
expect(edge1.shapeMap.buShape.style.fill).toBe('#fff');
|
||||
|
||||
const edge2 = graph.itemController.itemMap['edge2'];
|
||||
graph.setItemState('edge2', 'selected', true);
|
||||
expect(edge2.shapeMap.keyShape.style.stroke).toBe('#00f');
|
||||
expect(edge2.shapeMap.keyShape.style.lineWidth).toBe(2);
|
||||
|
||||
// update node type
|
||||
graph.updateData('edge', {
|
||||
id: 'edge2',
|
||||
data: {
|
||||
type: 'custom-edge2',
|
||||
},
|
||||
});
|
||||
expect(edge2.shapeMap.buShape).not.toBe(undefined);
|
||||
expect(edge2.shapeMap.keyShape.style.stroke).toBe('#00f');
|
||||
expect(edge2.shapeMap.keyShape.style.lineWidth).toBe(2);
|
||||
expect(edge2.shapeMap.buShape.style.fill).toBe('#fff');
|
||||
|
||||
graph.clearItemState(['edge2'], ['selected']);
|
||||
expect(edge2.shapeMap.keyShape.style.stroke).not.toBe('#00f');
|
||||
expect(edge2.shapeMap.keyShape.style.lineWidth).toBe(1);
|
||||
expect(edge2.shapeMap.buShape.style.fill).toBe('#0f0');
|
||||
|
||||
graph.destroy();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
@ -76,7 +76,6 @@ describe('node item', () => {
|
||||
|
||||
graph.on('afterrender', (e) => {
|
||||
graph.on('node:click', (e) => {
|
||||
console.log('nodeclick');
|
||||
graph.setItemState(e.itemId, 'selected', true);
|
||||
});
|
||||
});
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -13,15 +13,71 @@ document.querySelector('body').appendChild(container);
|
||||
|
||||
const data: GraphData = {
|
||||
nodes: [
|
||||
{ id: 'node1', data: { x: 100, y: 200, dt: 'a' } },
|
||||
{ id: 'node2', data: { x: 200, y: 250, dt: 'b' } },
|
||||
{ id: 'node3', data: { x: 300, y: 200, dt: 'c' } },
|
||||
{ id: 'node4', data: { x: 300, y: 250 } },
|
||||
{
|
||||
id: 'node1',
|
||||
data: {
|
||||
x: 100,
|
||||
y: 200,
|
||||
dt: 'a',
|
||||
labelBackgroundShape: {
|
||||
radius: 8,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'node2',
|
||||
data: {
|
||||
x: 200,
|
||||
y: 250,
|
||||
dt: 'b',
|
||||
badges: [
|
||||
{
|
||||
type: 'icon',
|
||||
text: 'a',
|
||||
position: 'rightTop',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'node3',
|
||||
data: {
|
||||
x: 300,
|
||||
y: 200,
|
||||
dt: 'c',
|
||||
labelBackgroundShape: {
|
||||
radius: 8,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'node4',
|
||||
data: {
|
||||
x: 300,
|
||||
y: 250,
|
||||
anchorPoints: [
|
||||
[0.5, 0],
|
||||
[0.5, 1],
|
||||
[0, 0.5],
|
||||
[1, 0.5],
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
edges: [
|
||||
{ id: 'edge1', source: 'node1', target: 'node2', data: { edt: '1' } },
|
||||
{ id: 'edge2', source: 'node1', target: 'node3', data: { edt: '2' } },
|
||||
{ id: 'edge3', source: 'node1', target: 'node4', data: {} },
|
||||
{
|
||||
id: 'edge3',
|
||||
source: 'node1',
|
||||
target: 'node4',
|
||||
data: {
|
||||
edt: 'xxx',
|
||||
labelBackgroundShape: {
|
||||
radius: 8,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@ -44,6 +100,21 @@ describe('theme', () => {
|
||||
formatter: (model) => model.id,
|
||||
},
|
||||
},
|
||||
iconShape: {
|
||||
img: 'https://gw.alipayobjects.com/zos/basement_prod/012bcf4f-423b-4922-8c24-32a89f8c41ce.svg',
|
||||
},
|
||||
anchorShapes: {
|
||||
fields: ['anchorPoints'],
|
||||
formatter: (model) => {
|
||||
return model.data.anchorPoints?.map((point, i) => ({
|
||||
position: point,
|
||||
}));
|
||||
},
|
||||
},
|
||||
badgeShapes: {
|
||||
fields: ['badges'],
|
||||
formatter: (model) => model.data.badges,
|
||||
},
|
||||
},
|
||||
edge: {
|
||||
labelShape: {
|
||||
@ -58,6 +129,7 @@ describe('theme', () => {
|
||||
const node = graph.itemController.itemMap['node1'];
|
||||
const { keyShape: nodeKeyShape, labelShape: nodeLabelShape } =
|
||||
node.shapeMap;
|
||||
expect(node.shapeMap.haloShape).toBe(undefined);
|
||||
expect(nodeKeyShape.style.fill).toBe(
|
||||
LightTheme.node.styles[0].default.keyShape.fill,
|
||||
);
|
||||
@ -67,6 +139,7 @@ describe('theme', () => {
|
||||
const edge = graph.itemController.itemMap['edge1'];
|
||||
const { keyShape: edgeKeyShape, labelShape: edgeLabelShape } =
|
||||
edge.shapeMap;
|
||||
expect(edge.shapeMap.haloShape).toBe(undefined);
|
||||
expect(edgeKeyShape.style.stroke).toBe(
|
||||
LightTheme.edge.styles[0].default.keyShape.stroke,
|
||||
);
|
||||
@ -76,8 +149,12 @@ describe('theme', () => {
|
||||
|
||||
// set state, should response with default theme state style
|
||||
graph.setItemState('node1', 'selected', true);
|
||||
expect(node.shapeMap.haloShape).not.toBe(undefined);
|
||||
expect(node.shapeMap.haloShape.style.lineWidth).not.toBe(
|
||||
LightTheme.node.styles[0].selected.haloShape.lineWith,
|
||||
);
|
||||
expect(nodeKeyShape.style.fill).toBe(
|
||||
LightTheme.node.styles[0].selected.keyShape.fill,
|
||||
LightTheme.node.styles[0].default.keyShape.fill, // no change
|
||||
);
|
||||
expect(nodeLabelShape.style.fontWeight).toBe(
|
||||
LightTheme.node.styles[0].selected.labelShape.fontWeight,
|
||||
@ -91,20 +168,31 @@ describe('theme', () => {
|
||||
);
|
||||
|
||||
graph.setItemState('edge1', 'selected', true);
|
||||
expect(edge.shapeMap.haloShape).not.toBe(undefined);
|
||||
expect(edge.shapeMap.haloShape.style.lineWidth).not.toBe(
|
||||
LightTheme.edge.styles[0].selected.haloShape.lineWith,
|
||||
);
|
||||
expect(edgeKeyShape.style.stroke).toBe(
|
||||
LightTheme.edge.styles[0].selected.keyShape.stroke,
|
||||
LightTheme.edge.styles[0].default.keyShape.stroke, // no change
|
||||
);
|
||||
expect(edgeKeyShape.style.lineWidth).toBe(
|
||||
LightTheme.edge.styles[0].selected.keyShape.lineWidth,
|
||||
);
|
||||
console.log(
|
||||
'xssx',
|
||||
edgeLabelShape.style.fontWeight,
|
||||
LightTheme.edge.styles[0].default.labelShape.fontWeight,
|
||||
);
|
||||
expect(edgeLabelShape.style.fill).toBe(
|
||||
LightTheme.edge.styles[0].default.labelShape.fill,
|
||||
); // no change in theme def
|
||||
);
|
||||
|
||||
const node4 = graph.itemController.itemMap['node4'];
|
||||
expect(node4.shapeMap.anchorShape0).not.toBe(undefined);
|
||||
expect(node4.shapeMap.anchorShape1).not.toBe(undefined);
|
||||
expect(node4.shapeMap.anchorShape2).not.toBe(undefined);
|
||||
expect(node4.shapeMap.anchorShape3).not.toBe(undefined);
|
||||
|
||||
graph.setItemState('node4', 'selected', true);
|
||||
expect(node4.shapeMap.anchorShape0).not.toBe(undefined);
|
||||
expect(node4.shapeMap.anchorShape1).not.toBe(undefined);
|
||||
expect(node4.shapeMap.anchorShape2).not.toBe(undefined);
|
||||
expect(node4.shapeMap.anchorShape3).not.toBe(undefined);
|
||||
|
||||
graph.destroy();
|
||||
done();
|
||||
@ -271,17 +359,11 @@ describe('theme', () => {
|
||||
|
||||
// node setState with state in builtin theme
|
||||
graph.setItemState('node1', 'selected', true);
|
||||
expect(nodeKeyShape1.style.fill).toBe(
|
||||
LightTheme.node.styles[0].selected.keyShape.fill,
|
||||
);
|
||||
expect(nodeLabelShape1.style.fontWeight).toBe(
|
||||
LightTheme.node.styles[0].selected.labelShape.fontWeight,
|
||||
);
|
||||
// edge setState with state in builtin theme
|
||||
graph.setItemState('edge1', 'selected', true);
|
||||
expect(edgeKeyShape1.style.stroke).toBe(
|
||||
LightTheme.edge.styles[0].selected.keyShape.stroke,
|
||||
);
|
||||
expect(edgeLabelShape1.style.fill).toBe('#0f0'); // not assigned in selected theme
|
||||
expect(edgeLabelShape1.style.fontWeight).toBe(
|
||||
LightTheme.edge.styles[0].selected.labelShape.fontWeight,
|
||||
@ -323,9 +405,6 @@ describe('theme', () => {
|
||||
// clear node's one state
|
||||
graph.clearItemState('node1', ['state2']);
|
||||
expect(nodeKeyShape1.style.fill).toBe('#ff0');
|
||||
expect(nodeKeyShape1.style.stroke).toBe(
|
||||
LightTheme.node.styles[0].default.keyShape.stroke,
|
||||
);
|
||||
// clear edge's one state, state1 + state3 is kept
|
||||
graph.clearItemState('edge1', ['state2']);
|
||||
expect(edgeKeyShape1.style.stroke).toBe('#ff0');
|
||||
@ -483,17 +562,11 @@ describe('theme', () => {
|
||||
|
||||
// node setState with state in builtin theme
|
||||
graph.setItemState('node1', 'selected', true);
|
||||
expect(nodeKeyShape1.style.fill).toBe(
|
||||
LightTheme.node.styles[0].selected.keyShape.fill,
|
||||
);
|
||||
expect(nodeLabelShape1.style.fontWeight).toBe(
|
||||
LightTheme.node.styles[0].selected.labelShape.fontWeight,
|
||||
);
|
||||
// edge setState with state in builtin theme
|
||||
graph.setItemState('edge1', 'selected', true);
|
||||
expect(edgeKeyShape1.style.stroke).toBe(
|
||||
LightTheme.edge.styles[0].selected.keyShape.stroke,
|
||||
);
|
||||
expect(edgeLabelShape1.style.fill).toBe('#0f0'); // not assigned in selected theme
|
||||
expect(edgeLabelShape1.style.fontWeight).toBe(
|
||||
LightTheme.edge.styles[0].selected.labelShape.fontWeight,
|
||||
@ -535,9 +608,6 @@ describe('theme', () => {
|
||||
// clear node's one state
|
||||
graph.clearItemState('node1', ['state2']);
|
||||
expect(nodeKeyShape1.style.fill).toBe('#ff0');
|
||||
expect(nodeKeyShape1.style.stroke).toBe(
|
||||
LightTheme.node.styles[0].default.keyShape.stroke,
|
||||
);
|
||||
|
||||
// clear edge's one state, state1 + state3 is kept
|
||||
graph.clearItemState('edge1', ['state2']);
|
||||
@ -572,19 +642,8 @@ describe('theme', () => {
|
||||
shapeMap: NodeShapeMap,
|
||||
diffData?: { oldData: NodeModelData; newData: NodeModelData },
|
||||
) {
|
||||
const extraShape = upsertShape(
|
||||
'circle',
|
||||
'extraShape',
|
||||
{
|
||||
r: 4,
|
||||
fill: '#0f0',
|
||||
x: -20,
|
||||
y: 0,
|
||||
},
|
||||
shapeMap,
|
||||
);
|
||||
const { labelShape: labelStyle } = this.mergedStyles;
|
||||
const labelShape = upsertShape(
|
||||
return this.upsertShape(
|
||||
'text',
|
||||
'labelShape',
|
||||
{
|
||||
@ -592,8 +651,26 @@ describe('theme', () => {
|
||||
text: model.id,
|
||||
},
|
||||
shapeMap,
|
||||
);
|
||||
return { labelShape, extraShape };
|
||||
).shape;
|
||||
}
|
||||
public drawOtherShapes(
|
||||
model: NodeDisplayModel,
|
||||
shapeMap: NodeShapeMap,
|
||||
diffData?: { oldData: NodeModelData; newData: NodeModelData },
|
||||
) {
|
||||
return {
|
||||
extraShape: this.upsertShape(
|
||||
'circle',
|
||||
'extraShape',
|
||||
{
|
||||
r: 4,
|
||||
fill: '#0f0',
|
||||
x: -20,
|
||||
y: 0,
|
||||
},
|
||||
shapeMap,
|
||||
).shape,
|
||||
};
|
||||
}
|
||||
}
|
||||
class CustomEdge extends LineEdge {
|
||||
@ -605,7 +682,7 @@ describe('theme', () => {
|
||||
const { keyShape } = shapeMap;
|
||||
const point = keyShape.getPoint(0.3);
|
||||
return {
|
||||
buShape: upsertShape(
|
||||
buShape: this.upsertShape(
|
||||
'rect',
|
||||
'buShape',
|
||||
{
|
||||
@ -616,7 +693,7 @@ describe('theme', () => {
|
||||
fill: '#0f0',
|
||||
},
|
||||
shapeMap,
|
||||
),
|
||||
).shape,
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -645,6 +722,7 @@ describe('theme', () => {
|
||||
formatter: (model) => model.id,
|
||||
},
|
||||
},
|
||||
otherShapes: {},
|
||||
},
|
||||
edge: {
|
||||
type: 'theme-spec-custom-edge',
|
||||
|
Loading…
Reference in New Issue
Block a user