mirror of
https://gitee.com/antv/g6.git
synced 2024-11-30 10:48:24 +08:00
feat: add triangle node (#5421)
* feat: add triangle shape * feat: update triangle snapshots * fix: fix triangle anchor type * feat: fix review problem * feat: update triangle snapshots * fix: fix review problem --------- Co-authored-by: liwenbo <liwenbo@kanzhun.com>
This commit is contained in:
parent
4bc31f71e7
commit
1ce481d0cc
@ -12,6 +12,7 @@ export * from './layered-canvas';
|
||||
export * from './node-circle';
|
||||
export * from './node-rect';
|
||||
export * from './node-star';
|
||||
export * from './node-triangle';
|
||||
export * from './shape-badge';
|
||||
export * from './shape-icon';
|
||||
export * from './shape-label';
|
||||
|
69
packages/g6/__tests__/demo/static/node-triangle.ts
Normal file
69
packages/g6/__tests__/demo/static/node-triangle.ts
Normal file
@ -0,0 +1,69 @@
|
||||
import { Triangle } from '../../../src/elements/nodes';
|
||||
import type { StaticTestCase } from '../types';
|
||||
|
||||
export const nodeTriangle: StaticTestCase = async (context) => {
|
||||
const { canvas } = context;
|
||||
|
||||
const t1 = new Triangle({
|
||||
style: {
|
||||
width: 96,
|
||||
x: 100,
|
||||
y: 100,
|
||||
fill: 'green',
|
||||
},
|
||||
});
|
||||
|
||||
const t2 = new Triangle({
|
||||
style: {
|
||||
// key
|
||||
x: 300,
|
||||
y: 100,
|
||||
fill: 'red',
|
||||
width: 96,
|
||||
// label
|
||||
labelText: 'triangle node',
|
||||
labelFontSize: 14,
|
||||
labelFill: 'pink',
|
||||
labelPosition: 'bottom',
|
||||
// badge
|
||||
badgeOptions: [
|
||||
{ text: 'A', position: 'right-top', backgroundFill: 'grey', fill: 'white', fontSize: 10, padding: [1, 4] },
|
||||
{ text: 'Important', position: 'right', backgroundFill: 'blue', fill: 'white', fontSize: 10 },
|
||||
{ text: 'Notice', position: 'left-bottom', backgroundFill: 'red', fill: 'white', fontSize: 10 },
|
||||
],
|
||||
// anchor
|
||||
anchorOptions: [
|
||||
{ position: 'left', r: 2, stroke: 'black', lineWidth: 1, zIndex: 2 },
|
||||
{ position: 'right', r: 2, stroke: 'yellow', lineWidth: 2, zIndex: 2 },
|
||||
{ position: 'top', r: 2, stroke: 'green', lineWidth: 1, zIndex: 2 },
|
||||
],
|
||||
// icon
|
||||
iconSrc: 'https://gw.alipayobjects.com/zos/basement_prod/012bcf4f-423b-4922-8c24-32a89f8c41ce.svg',
|
||||
iconWidth: 30,
|
||||
iconHeight: 30,
|
||||
// halo
|
||||
haloOpacity: 0.4,
|
||||
haloStroke: 'grey',
|
||||
haloLineWidth: 12,
|
||||
haloPointerEvents: 'none',
|
||||
},
|
||||
});
|
||||
|
||||
const t3 = new Triangle({
|
||||
style: {
|
||||
// key
|
||||
x: 300,
|
||||
y: 300,
|
||||
fill: 'pink',
|
||||
width: 96,
|
||||
// icon
|
||||
iconText: 'Y',
|
||||
iconFontSize: 30,
|
||||
iconFill: 'black',
|
||||
},
|
||||
});
|
||||
|
||||
canvas.appendChild(t1);
|
||||
canvas.appendChild(t2);
|
||||
canvas.appendChild(t3);
|
||||
};
|
@ -0,0 +1,241 @@
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="500"
|
||||
height="500"
|
||||
style="background: transparent; position: absolute; outline: none;"
|
||||
color-interpolation-filters="sRGB"
|
||||
tabindex="1"
|
||||
>
|
||||
<defs />
|
||||
<g id="g-svg-camera" transform="matrix(1,0,0,1,0,0)">
|
||||
<g id="g-root" fill="none" transform="matrix(1,0,0,1,0,0)">
|
||||
<g
|
||||
id="g-svg-5"
|
||||
fill="none"
|
||||
width="96"
|
||||
transform="matrix(1,0,0,1,100,100)"
|
||||
>
|
||||
<g transform="matrix(1,0,0,1,-41.569218,-36)">
|
||||
<polygon
|
||||
id="key"
|
||||
fill="rgba(0,128,0,1)"
|
||||
points="41.569219381653056,0 83.13843876330611,72 0,72"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
id="g-svg-7"
|
||||
fill="none"
|
||||
width="96"
|
||||
transform="matrix(1,0,0,1,300,100)"
|
||||
>
|
||||
<g transform="matrix(1,0,0,1,-41.569218,-36)">
|
||||
<polygon
|
||||
id="key"
|
||||
fill="rgba(255,0,0,1)"
|
||||
points="41.569219381653056,0 83.13843876330611,72 0,72"
|
||||
/>
|
||||
</g>
|
||||
<g
|
||||
id="icon"
|
||||
fill="none"
|
||||
width="30"
|
||||
height="30"
|
||||
transform="matrix(1,0,0,1,0.000002,9)"
|
||||
>
|
||||
<g transform="matrix(1,0,0,1,0,0)">
|
||||
<image
|
||||
id="icon"
|
||||
fill="none"
|
||||
preserveAspectRatio="none"
|
||||
x="0"
|
||||
y="0"
|
||||
href="https://gw.alipayobjects.com/zos/basement_prod/012bcf4f-423b-4922-8c24-32a89f8c41ce.svg"
|
||||
transform="translate(-15,-15)"
|
||||
width="30"
|
||||
height="30"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="label" fill="none" transform="matrix(1,0,0,1,0.000002,36)">
|
||||
<g transform="matrix(1,0,0,1,-48.450001,-2)">
|
||||
<path
|
||||
id="background"
|
||||
fill="none"
|
||||
d="M 0,0 l 174.27687752661222,0 l 0,31 l-174.27687752661222 0 z"
|
||||
opacity="0.75"
|
||||
stroke-width="0"
|
||||
width="174.27687752661222"
|
||||
height="31"
|
||||
/>
|
||||
</g>
|
||||
<g transform="matrix(1,0,0,1,0,0)">
|
||||
<text
|
||||
id="text"
|
||||
fill="rgba(255,192,203,1)"
|
||||
dominant-baseline="central"
|
||||
paint-order="stroke"
|
||||
dx="0.5"
|
||||
dy="13.5px"
|
||||
font-size="14"
|
||||
text-anchor="middle"
|
||||
>
|
||||
triangle node
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
<g id="badge-0" fill="none" transform="matrix(1,0,0,1,41.569221,-36)">
|
||||
<g id="label" fill="none" transform="matrix(1,0,0,1,0,0)">
|
||||
<g transform="matrix(1,0,0,1,-3.999992,-20)">
|
||||
<path
|
||||
id="background"
|
||||
fill="rgba(128,128,128,1)"
|
||||
d="M 7.95,0 l 0,0 a 7.95,7.95,0,0,1,7.95,7.95 l 0,5.1 a 7.95,7.95,0,0,1,-7.95,7.95 l 0,0 a 7.95,7.95,0,0,1,-7.95,-7.95 l 0,-5.1 a 7.95,7.95,0,0,1,7.95,-7.95 z"
|
||||
opacity="0.75"
|
||||
stroke-width="0"
|
||||
width="15.9"
|
||||
height="21"
|
||||
/>
|
||||
</g>
|
||||
<g transform="matrix(1,0,0,1,0,0)">
|
||||
<text
|
||||
id="text"
|
||||
fill="rgba(255,255,255,1)"
|
||||
dominant-baseline="central"
|
||||
paint-order="stroke"
|
||||
dx="0.5"
|
||||
dy="-9.5px"
|
||||
font-size="10"
|
||||
text-anchor="left"
|
||||
>
|
||||
A
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="badge-1" fill="none" transform="matrix(1,0,0,1,41.569221,0)">
|
||||
<g id="label" fill="none" transform="matrix(1,0,0,1,0,0)">
|
||||
<g transform="matrix(1,0,0,1,-3.999992,-11.500000)">
|
||||
<path
|
||||
id="background"
|
||||
fill="rgba(0,0,255,1)"
|
||||
d="M 11.5,0 l 32.9,0 a 11.5,11.5,0,0,1,11.5,11.5 l 0,0 a 11.5,11.5,0,0,1,-11.5,11.5 l -32.9,0 a 11.5,11.5,0,0,1,-11.5,-11.5 l 0,0 a 11.5,11.5,0,0,1,11.5,-11.5 z"
|
||||
opacity="0.75"
|
||||
stroke-width="0"
|
||||
width="55.9"
|
||||
height="23"
|
||||
/>
|
||||
</g>
|
||||
<g transform="matrix(1,0,0,1,0,0)">
|
||||
<text
|
||||
id="text"
|
||||
fill="rgba(255,255,255,1)"
|
||||
dominant-baseline="central"
|
||||
paint-order="stroke"
|
||||
dx="0.5"
|
||||
font-size="10"
|
||||
text-anchor="left"
|
||||
>
|
||||
Important
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="badge-2" fill="none" transform="matrix(1,0,0,1,-41.569218,36)">
|
||||
<g id="label" fill="none" transform="matrix(1,0,0,1,0,0)">
|
||||
<g transform="matrix(1,0,0,1,-35.400002,-2)">
|
||||
<path
|
||||
id="background"
|
||||
fill="rgba(255,0,0,1)"
|
||||
d="M 11.5,0 l 17.400000000000006,0 a 11.5,11.5,0,0,1,11.5,11.5 l 0,0 a 11.5,11.5,0,0,1,-11.5,11.5 l -17.400000000000006,0 a 11.5,11.5,0,0,1,-11.5,-11.5 l 0,0 a 11.5,11.5,0,0,1,11.5,-11.5 z"
|
||||
opacity="0.75"
|
||||
stroke-width="0"
|
||||
width="40.400000000000006"
|
||||
height="23"
|
||||
/>
|
||||
</g>
|
||||
<g transform="matrix(1,0,0,1,0,0)">
|
||||
<text
|
||||
id="text"
|
||||
fill="rgba(255,255,255,1)"
|
||||
dominant-baseline="central"
|
||||
paint-order="stroke"
|
||||
dx="0.5"
|
||||
dy="9.5px"
|
||||
font-size="10"
|
||||
text-anchor="end"
|
||||
>
|
||||
Notice
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="matrix(1,0,0,1,-41.569218,36)">
|
||||
<circle
|
||||
id="anchor-0"
|
||||
fill="none"
|
||||
transform="translate(-2,-2)"
|
||||
cx="2"
|
||||
cy="2"
|
||||
r="2"
|
||||
stroke="rgba(0,0,0,1)"
|
||||
stroke-width="1"
|
||||
/>
|
||||
</g>
|
||||
<g transform="matrix(1,0,0,1,41.569218,36)">
|
||||
<circle
|
||||
id="anchor-1"
|
||||
fill="none"
|
||||
transform="translate(-2,-2)"
|
||||
cx="2"
|
||||
cy="2"
|
||||
r="2"
|
||||
stroke="rgba(255,255,0,1)"
|
||||
stroke-width="2"
|
||||
/>
|
||||
</g>
|
||||
<g transform="matrix(1,0,0,1,0,-36)">
|
||||
<circle
|
||||
id="anchor-2"
|
||||
fill="none"
|
||||
transform="translate(-2,-2)"
|
||||
cx="2"
|
||||
cy="2"
|
||||
r="2"
|
||||
stroke="rgba(0,128,0,1)"
|
||||
stroke-width="1"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
id="g-svg-29"
|
||||
fill="none"
|
||||
width="96"
|
||||
transform="matrix(1,0,0,1,300,300)"
|
||||
>
|
||||
<g transform="matrix(1,0,0,1,-41.569218,-36)">
|
||||
<polygon
|
||||
id="key"
|
||||
fill="rgba(255,192,203,1)"
|
||||
points="41.569219381653056,0 83.13843876330611,72 0,72"
|
||||
/>
|
||||
</g>
|
||||
<g id="icon" fill="none" transform="matrix(1,0,0,1,0.000002,9)">
|
||||
<g transform="matrix(1,0,0,1,0,0)">
|
||||
<text
|
||||
id="icon"
|
||||
fill="rgba(0,0,0,1)"
|
||||
dominant-baseline="central"
|
||||
paint-order="stroke"
|
||||
dx="0.5"
|
||||
text-anchor="middle"
|
||||
font-size="30"
|
||||
>
|
||||
Y
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 7.3 KiB |
@ -2,8 +2,10 @@ export { BaseNode } from './base-node';
|
||||
export { Circle } from './circle';
|
||||
export { Rect } from './rect';
|
||||
export { Star } from './star';
|
||||
export { Triangle } from './triangle';
|
||||
|
||||
export type { BaseNodeStyleProps } from './base-node';
|
||||
export type { CircleStyleProps } from './circle';
|
||||
export type { RectStyleProps } from './rect';
|
||||
export type { StarStyleProps } from './star';
|
||||
export type { TriangleStyleProps } from './triangle';
|
||||
|
109
packages/g6/src/elements/nodes/triangle.ts
Normal file
109
packages/g6/src/elements/nodes/triangle.ts
Normal file
@ -0,0 +1,109 @@
|
||||
import type { DisplayObjectConfig, Group, PolygonStyleProps } from '@antv/g';
|
||||
import { Polygon } from '@antv/g';
|
||||
import { deepMix, isEmpty } from '@antv/util';
|
||||
import type { Point } from '../../types';
|
||||
import { getTriangleAnchorByPosition, getTriangleAnchors, getTrianglePoints } from '../../utils/element';
|
||||
import { getPolygonIntersectPoint } from '../../utils/point';
|
||||
import { subStyleProps } from '../../utils/prefix';
|
||||
import type { BaseNodeStyleProps, NodeAnchorStyleProps } from './base-node';
|
||||
import { BaseNode } from './base-node';
|
||||
|
||||
type TriangleShapeStyleProps = {
|
||||
/**
|
||||
* 节点宽度
|
||||
*/
|
||||
width?: number;
|
||||
/**
|
||||
* 节点高度
|
||||
*/
|
||||
heigh?: number;
|
||||
/**
|
||||
* 三角形朝向
|
||||
*/
|
||||
direction?: 'up' | 'left' | 'right' | 'down';
|
||||
};
|
||||
|
||||
type KeyShapeStyleProps = Partial<PolygonStyleProps> & TriangleShapeStyleProps;
|
||||
|
||||
export type TriangleStyleProps = BaseNodeStyleProps<KeyShapeStyleProps>;
|
||||
|
||||
type ParsedTriangleStyleProps = Required<TriangleStyleProps>;
|
||||
|
||||
type TriangleOptions = DisplayObjectConfig<TriangleStyleProps>;
|
||||
|
||||
export class Triangle extends BaseNode<KeyShapeStyleProps, Polygon> {
|
||||
static defaultStyleProps: Partial<TriangleShapeStyleProps> = {
|
||||
direction: 'up',
|
||||
};
|
||||
constructor(options: TriangleOptions) {
|
||||
super(deepMix({}, { style: Triangle.defaultStyleProps }, options));
|
||||
}
|
||||
|
||||
protected getKeyStyle(attributes: ParsedTriangleStyleProps): PolygonStyleProps {
|
||||
const {
|
||||
width,
|
||||
heigh = width,
|
||||
direction,
|
||||
...keyStyle
|
||||
} = super.getKeyStyle(attributes) as Required<KeyShapeStyleProps>;
|
||||
const r = Math.min(width, heigh) / 2;
|
||||
const points = getTrianglePoints(r, direction) as [number, number][];
|
||||
return { ...keyStyle, points };
|
||||
}
|
||||
|
||||
protected getHaloStyle(attributes: ParsedTriangleStyleProps): PolygonStyleProps | false {
|
||||
if (attributes.halo === false) return false;
|
||||
|
||||
const haloStyle = subStyleProps(this.getGraphicStyle(attributes), 'halo') as Partial<KeyShapeStyleProps>;
|
||||
const keyStyle = this.getKeyStyle(attributes);
|
||||
|
||||
return {
|
||||
...keyStyle,
|
||||
...haloStyle,
|
||||
};
|
||||
}
|
||||
|
||||
protected getAnchorsStyle(attributes: ParsedTriangleStyleProps): NodeAnchorStyleProps[] {
|
||||
if (attributes.anchor === false) return [];
|
||||
|
||||
const { width, heigh = width, direction } = attributes;
|
||||
const r = Math.min(width, heigh) / 2;
|
||||
const anchors = getTriangleAnchors(r, direction);
|
||||
|
||||
const anchorStyle = this.getGraphicStyle(attributes).anchorOptions || [];
|
||||
|
||||
return anchorStyle.map((anchorStyle) => {
|
||||
const { position, ...style } = anchorStyle;
|
||||
const [cx, cy] = getTriangleAnchorByPosition(position as any, anchors);
|
||||
return { cx, cy, ...style } as NodeAnchorStyleProps;
|
||||
});
|
||||
}
|
||||
|
||||
protected getIconStyle(attributes: ParsedTriangleStyleProps) {
|
||||
if (attributes.icon === false || isEmpty(attributes.iconText || attributes.iconSrc)) return false;
|
||||
|
||||
const { direction } = attributes;
|
||||
const iconStyle = subStyleProps(this.getGraphicStyle(attributes), 'icon');
|
||||
const keyShape = this.shapeMap.key;
|
||||
const { max, center } = keyShape.getLocalBounds();
|
||||
const x = direction === 'up' || direction === 'down' ? center[0] : direction === 'right' ? -max[0] / 4 : max[0] / 4;
|
||||
const y =
|
||||
direction === 'left' || direction === 'right' ? center[1] : direction === 'down' ? -max[1] / 4 : max[1] / 4;
|
||||
|
||||
return {
|
||||
x,
|
||||
y,
|
||||
...iconStyle,
|
||||
};
|
||||
}
|
||||
|
||||
public getIntersectPoint(point: Point): Point {
|
||||
const { points } = this.getKeyStyle(this.attributes as ParsedTriangleStyleProps);
|
||||
const center = [this.attributes.x, this.attributes.y] as Point;
|
||||
return getPolygonIntersectPoint(point, center, points);
|
||||
}
|
||||
|
||||
protected drawKeyShape(attributes: ParsedTriangleStyleProps, container: Group): Polygon {
|
||||
return this.upsert('key', Polygon, this.getKeyStyle(attributes), container) as Polygon;
|
||||
}
|
||||
}
|
@ -15,6 +15,7 @@ export type RelativePosition =
|
||||
|
||||
export type AnchorPosition = [number, number] | 'top' | 'left' | 'right' | 'bottom';
|
||||
export type StarAnchorPosition = 'top' | 'left' | 'right' | 'left-bottom' | 'right-bottom';
|
||||
export type TriangleAnchorPosition = 'top' | 'left' | 'right' | 'bottom';
|
||||
|
||||
export type BadgePosition = RelativePosition;
|
||||
export type LabelPosition = RelativePosition;
|
||||
|
@ -1,7 +1,13 @@
|
||||
import type { AABB, Circle as GCircle, TextStyleProps } from '@antv/g';
|
||||
import { get, isEmpty, isString } from '@antv/util';
|
||||
import type { Node, Point } from '../types';
|
||||
import type { AnchorPosition, LabelPosition, RelativePosition, StarAnchorPosition } from '../types/node';
|
||||
import type {
|
||||
AnchorPosition,
|
||||
LabelPosition,
|
||||
RelativePosition,
|
||||
StarAnchorPosition,
|
||||
TriangleAnchorPosition,
|
||||
} from '../types/node';
|
||||
import { findNearestPoints } from './point';
|
||||
|
||||
/**
|
||||
@ -157,6 +163,86 @@ export function getStarAnchorByPosition(position: StarAnchorPosition, anchors: R
|
||||
return get(anchors, position.toLocaleLowerCase(), anchors['default']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Triangle Points.
|
||||
* @param r - radius of circumcircle of triangle
|
||||
* @param direction - direction of triangle
|
||||
* @returns The PathArray for G
|
||||
*/
|
||||
export function getTrianglePoints(r: number, direction: 'up' | 'left' | 'right' | 'down'): Point[] {
|
||||
const halfHeight = (3 * r) / 4;
|
||||
const halfLength = r * Math.sin((1 / 3) * Math.PI);
|
||||
if (direction === 'down') {
|
||||
return [
|
||||
[0, halfHeight],
|
||||
[halfLength, -halfHeight],
|
||||
[-halfLength, -halfHeight],
|
||||
];
|
||||
}
|
||||
if (direction === 'left') {
|
||||
return [
|
||||
[-halfHeight, 0],
|
||||
[halfHeight, halfLength],
|
||||
[halfHeight, -halfLength],
|
||||
];
|
||||
}
|
||||
if (direction === 'right') {
|
||||
return [
|
||||
[halfHeight, 0],
|
||||
[-halfHeight, halfLength],
|
||||
[-halfHeight, -halfLength],
|
||||
];
|
||||
}
|
||||
// up
|
||||
return [
|
||||
[0, -halfHeight],
|
||||
[halfLength, halfHeight],
|
||||
[-halfLength, halfHeight],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Triangle Anchor Point.
|
||||
* @param r - radius of circumcircle of triangle
|
||||
* @param direction - direction of triangle
|
||||
* @returns Anchor points for Triangle.
|
||||
*/
|
||||
export function getTriangleAnchors(r: number, direction: 'up' | 'left' | 'right' | 'down'): Record<string, Point> {
|
||||
const halfHeight = (3 * r) / 4;
|
||||
const halfLength = r * Math.sin((1 / 3) * Math.PI);
|
||||
|
||||
const anchors: Record<string, Point> = {};
|
||||
if (direction === 'down') {
|
||||
anchors['bottom'] = anchors['default'] = [0, halfHeight];
|
||||
anchors['right'] = [halfLength, -halfHeight];
|
||||
anchors['left'] = [-halfLength, -halfHeight];
|
||||
} else if (direction === 'left') {
|
||||
anchors['top'] = [halfHeight, -halfLength];
|
||||
anchors['bottom'] = [halfHeight, halfLength];
|
||||
anchors['left'] = anchors['default'] = [-halfHeight, 0];
|
||||
} else if (direction === 'right') {
|
||||
anchors['top'] = [-halfHeight, -halfLength];
|
||||
anchors['bottom'] = [-halfHeight, halfLength];
|
||||
anchors['right'] = anchors['default'] = [halfHeight, 0];
|
||||
} else {
|
||||
//up
|
||||
anchors['left'] = [-halfLength, halfHeight];
|
||||
anchors['top'] = anchors['default'] = [0, -halfHeight];
|
||||
anchors['right'] = [halfLength, halfHeight];
|
||||
}
|
||||
return anchors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Star Anchor Point by `position`.
|
||||
* @param position - position
|
||||
* @param anchors - anchors
|
||||
* @returns points
|
||||
*/
|
||||
export function getTriangleAnchorByPosition(position: TriangleAnchorPosition, anchors: Record<string, Point>) {
|
||||
return get(anchors, position.toLocaleLowerCase(), anchors['default']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Rect PathArray.
|
||||
* @param width - rect width
|
||||
|
Loading…
Reference in New Issue
Block a user