mirror of
https://gitee.com/antv/g6.git
synced 2024-12-02 11:48:29 +08:00
feat(edge): add edge base class and line edge element (#5385)
* feat(edge): add base edge(keyShape,label and halo) * feat: add edge arrow * feat: update ci * refactor: make render function clear * feat(edge): add edge animation * refactor: perf ts type and fix ci * feat(edge): update edge animation * chore: fix code review * fix: ci * test: add unit test * fix: export circle demo * chore: update point format * fix: fix code preview * chore: update ts type * test: perf coverage --------- Co-authored-by: yvonneyx <banxuan.zyx@antgroup.com>
This commit is contained in:
parent
896fe1499d
commit
5f41eaea15
44
packages/g6/__tests__/demo/animation/edge-line.ts
Normal file
44
packages/g6/__tests__/demo/animation/edge-line.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import { Line } from '../../../src/elements/edges';
|
||||
import type { AnimationTestCase } from '../types';
|
||||
|
||||
export const edgeLine: AnimationTestCase = async (context) => {
|
||||
const { canvas } = context;
|
||||
|
||||
const line = new Line({
|
||||
style: {
|
||||
sourcePoint: [100, 50],
|
||||
targetPoint: [300, 50],
|
||||
lineWidth: 2,
|
||||
lineDash: [10, 10],
|
||||
stroke: '#1890FF',
|
||||
halo: true,
|
||||
haloOpacity: 0.25,
|
||||
haloLineWidth: 12,
|
||||
label: true,
|
||||
labelText: 'line-edge',
|
||||
labelFontSize: 12,
|
||||
labelFill: '#000',
|
||||
labelPadding: 0,
|
||||
startArrow: true,
|
||||
startArrowType: 'circle',
|
||||
endArrow: true,
|
||||
endArrowFill: 'red',
|
||||
},
|
||||
});
|
||||
|
||||
await canvas.init();
|
||||
|
||||
canvas.appendChild(line);
|
||||
|
||||
const result = line.animate(
|
||||
[
|
||||
{ sourcePoint: [100, 150], targetPoint: [300, 200], haloOpacity: 0 },
|
||||
{ sourcePoint: [100, 150], targetPoint: [450, 350], haloOpacity: 0.25 },
|
||||
],
|
||||
{ duration: 1000, fill: 'both' },
|
||||
);
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
edgeLine.times = [0, 500, 1000];
|
92
packages/g6/__tests__/demo/static/edge-line.ts
Normal file
92
packages/g6/__tests__/demo/static/edge-line.ts
Normal file
@ -0,0 +1,92 @@
|
||||
import { Circle, Image } from '@antv/g';
|
||||
import { Line } from '../../../src/elements/edges';
|
||||
import type { StaticTestCase } from '../types';
|
||||
|
||||
export const edgeLine: StaticTestCase = async (context) => {
|
||||
const { canvas } = context;
|
||||
|
||||
const line1 = new Line({
|
||||
style: {
|
||||
// key shape
|
||||
sourcePoint: [100, 50],
|
||||
targetPoint: [300, 50],
|
||||
stroke: '#1890FF',
|
||||
lineWidth: 2,
|
||||
cursor: 'pointer',
|
||||
// halo
|
||||
halo: false,
|
||||
haloOpacity: 0.25,
|
||||
haloLineWidth: 12,
|
||||
// label
|
||||
label: true,
|
||||
labelText: '🌲line-edge',
|
||||
labelFontSize: 12,
|
||||
labelFill: '#1890FF',
|
||||
// start arrow
|
||||
startArrow: true,
|
||||
startArrowType: 'diamond',
|
||||
// end arrow
|
||||
endArrow: false,
|
||||
},
|
||||
});
|
||||
|
||||
const line2 = new Line({
|
||||
style: {
|
||||
sourcePoint: [100, 150],
|
||||
targetPoint: [300, 200],
|
||||
lineWidth: 2,
|
||||
lineDash: [10, 10],
|
||||
stroke: '#1890FF',
|
||||
cursor: 'pointer',
|
||||
halo: true,
|
||||
haloOpacity: 0.25,
|
||||
haloLineWidth: 12,
|
||||
label: true,
|
||||
labelText: 'line-edge',
|
||||
labelFontSize: 12,
|
||||
labelFill: '#000',
|
||||
labelPadding: 0,
|
||||
startArrow: true,
|
||||
startArrowType: 'circle',
|
||||
endArrow: true,
|
||||
endArrowFill: 'red',
|
||||
},
|
||||
});
|
||||
|
||||
const line3 = new Line({
|
||||
style: {
|
||||
sourcePoint: [300, 300],
|
||||
targetPoint: [100, 250],
|
||||
lineWidth: 2,
|
||||
lineDash: [10, 10],
|
||||
stroke: '#1890FF',
|
||||
cursor: 'pointer',
|
||||
halo: true,
|
||||
haloOpacity: 0.25,
|
||||
label: true,
|
||||
labelPosition: 'start',
|
||||
labelOffsetX: 25,
|
||||
labelText: 'reverted-line-edge',
|
||||
labelFontSize: 12,
|
||||
labelFill: '#000',
|
||||
labelPadding: 0,
|
||||
startArrow: true,
|
||||
startArrowCtor: Image,
|
||||
startArrowWidth: 50,
|
||||
startArrowHeight: 50,
|
||||
startArrowSrc: 'https://gw.alipayobjects.com/mdn/rms_6ae20b/afts/img/A*N4ZMS7gHsUIAAAAAAAAAAABkARQnAQ',
|
||||
startArrowTransform: 'rotate(90deg)',
|
||||
endArrow: true,
|
||||
endArrowCtor: Circle,
|
||||
endArrowR: 25,
|
||||
endArrowStroke: '#1890FF',
|
||||
endArrowLineWidth: 2,
|
||||
},
|
||||
});
|
||||
|
||||
await canvas.init();
|
||||
|
||||
canvas.appendChild(line1);
|
||||
canvas.appendChild(line2);
|
||||
canvas.appendChild(line3);
|
||||
};
|
0
packages/g6/__tests__/demo/static/edge-quadratic.ts
Normal file
0
packages/g6/__tests__/demo/static/edge-quadratic.ts
Normal file
@ -1,3 +1,4 @@
|
||||
export * from './edge-line';
|
||||
export * from './layered-canvas';
|
||||
export * from './node-circle';
|
||||
export * from './shape-badge';
|
||||
|
@ -0,0 +1,135 @@
|
||||
<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"
|
||||
stroke="none"
|
||||
visibility="visible"
|
||||
font-size="16px"
|
||||
font-family="sans-serif"
|
||||
font-style="normal"
|
||||
font-weight="normal"
|
||||
font-variant="normal"
|
||||
text-anchor="left"
|
||||
stroke-dashoffset="0px"
|
||||
transform="matrix(1,0,0,1,0,0)"
|
||||
>
|
||||
<g
|
||||
id="g-svg-5"
|
||||
fill="none"
|
||||
stroke="rgba(24,144,255,1)"
|
||||
stroke-width="2px"
|
||||
marker-start="true"
|
||||
marker-end="true"
|
||||
stroke-dasharray="10px,10px"
|
||||
transform="matrix(1,0,0,1,0,0)"
|
||||
>
|
||||
<g
|
||||
opacity="0"
|
||||
pointer-events="none"
|
||||
stroke-width="12px"
|
||||
stroke-dasharray="0px,0px"
|
||||
transform="matrix(1,0,0,1,100,150)"
|
||||
>
|
||||
<line
|
||||
id="halo"
|
||||
fill="none"
|
||||
x1="0"
|
||||
y1="0"
|
||||
x2="200"
|
||||
y2="50"
|
||||
stroke="rgba(24,144,255,1)"
|
||||
/>
|
||||
</g>
|
||||
<g
|
||||
stroke-width="2px"
|
||||
stroke-dasharray="10px,10px"
|
||||
transform="matrix(1,0,0,1,100,150)"
|
||||
>
|
||||
<line
|
||||
id="key"
|
||||
fill="none"
|
||||
x1="0"
|
||||
y1="0"
|
||||
x2="200"
|
||||
y2="50"
|
||||
stroke="rgba(24,144,255,1)"
|
||||
/>
|
||||
<g
|
||||
stroke-width="2px"
|
||||
stroke-dasharray="0px,0px"
|
||||
transform="matrix(0.970143,0.242536,-0.242536,0.970143,0,0)"
|
||||
>
|
||||
<path
|
||||
id="g-svg-8"
|
||||
fill="rgba(24,144,255,1)"
|
||||
d="M 0,5 A 5 5 0 1 0 10 5 A 5 5 0 1 0 0 5 Z"
|
||||
transform="translate(-5,-5)"
|
||||
stroke="none"
|
||||
width="10px"
|
||||
height="10px"
|
||||
/>
|
||||
</g>
|
||||
<g
|
||||
stroke-width="2px"
|
||||
stroke-dasharray="0px,0px"
|
||||
transform="matrix(-0.970143,-0.242536,0.242536,-0.970143,200,50)"
|
||||
>
|
||||
<path
|
||||
id="g-svg-10"
|
||||
fill="rgba(255,0,0,1)"
|
||||
d="M 0,5 L 10,0 L 10,10 Z"
|
||||
transform="translate(-5,-5)"
|
||||
stroke="none"
|
||||
width="10px"
|
||||
height="10px"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
id="label"
|
||||
fill="rgba(0,0,0,1)"
|
||||
stroke="none"
|
||||
font-size="12px"
|
||||
text-anchor="middle"
|
||||
transform="matrix(0.970143,0.242536,-0.242536,0.970143,205.335785,170.149292)"
|
||||
>
|
||||
<g transform="matrix(1,0,0,1,-33.187069,-28.430592)">
|
||||
<path
|
||||
id="background"
|
||||
fill="none"
|
||||
d="M 0,0 l 68.37411720861238,0 l 0,50.86117678317855 l-68.37411720861238 0 z"
|
||||
stroke="none"
|
||||
width="68.37411720861238px"
|
||||
height="50.86117678317855px"
|
||||
/>
|
||||
</g>
|
||||
<g
|
||||
font-size="12px"
|
||||
text-anchor="middle"
|
||||
transform="matrix(1,0,0,1,0,0)"
|
||||
>
|
||||
<text
|
||||
id="text"
|
||||
fill="rgba(0,0,0,1)"
|
||||
dominant-baseline="alphabetic"
|
||||
paint-order="stroke"
|
||||
dx="1"
|
||||
stroke="none"
|
||||
>
|
||||
line-edge
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 3.5 KiB |
@ -0,0 +1,135 @@
|
||||
<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"
|
||||
stroke="none"
|
||||
visibility="visible"
|
||||
font-size="16px"
|
||||
font-family="sans-serif"
|
||||
font-style="normal"
|
||||
font-weight="normal"
|
||||
font-variant="normal"
|
||||
text-anchor="left"
|
||||
stroke-dashoffset="0px"
|
||||
transform="matrix(1,0,0,1,0,0)"
|
||||
>
|
||||
<g
|
||||
id="g-svg-5"
|
||||
fill="none"
|
||||
stroke="rgba(24,144,255,1)"
|
||||
stroke-width="2px"
|
||||
marker-start="true"
|
||||
marker-end="true"
|
||||
stroke-dasharray="10px,10px"
|
||||
transform="matrix(1,0,0,1,0,0)"
|
||||
>
|
||||
<g
|
||||
opacity="0.25"
|
||||
pointer-events="none"
|
||||
stroke-width="12px"
|
||||
stroke-dasharray="0px,0px"
|
||||
transform="matrix(1,0,0,1,100,150)"
|
||||
>
|
||||
<line
|
||||
id="halo"
|
||||
fill="none"
|
||||
x1="0"
|
||||
y1="0"
|
||||
x2="350"
|
||||
y2="200"
|
||||
stroke="rgba(24,144,255,1)"
|
||||
/>
|
||||
</g>
|
||||
<g
|
||||
stroke-width="2px"
|
||||
stroke-dasharray="10px,10px"
|
||||
transform="matrix(1,0,0,1,100,150)"
|
||||
>
|
||||
<line
|
||||
id="key"
|
||||
fill="none"
|
||||
x1="0"
|
||||
y1="0"
|
||||
x2="350"
|
||||
y2="200"
|
||||
stroke="rgba(24,144,255,1)"
|
||||
/>
|
||||
<g
|
||||
stroke-width="2px"
|
||||
stroke-dasharray="0px,0px"
|
||||
transform="matrix(0.868243,0.496139,-0.496139,0.868243,0,0)"
|
||||
>
|
||||
<path
|
||||
id="g-svg-8"
|
||||
fill="rgba(24,144,255,1)"
|
||||
d="M 0,5 A 5 5 0 1 0 10 5 A 5 5 0 1 0 0 5 Z"
|
||||
transform="translate(-5,-5)"
|
||||
stroke="none"
|
||||
width="10px"
|
||||
height="10px"
|
||||
/>
|
||||
</g>
|
||||
<g
|
||||
stroke-width="2px"
|
||||
stroke-dasharray="0px,0px"
|
||||
transform="matrix(-0.868243,-0.496139,0.496139,-0.868243,350,200)"
|
||||
>
|
||||
<path
|
||||
id="g-svg-10"
|
||||
fill="rgba(255,0,0,1)"
|
||||
d="M 0,5 L 10,0 L 10,10 Z"
|
||||
transform="translate(-5,-5)"
|
||||
stroke="none"
|
||||
width="10px"
|
||||
height="10px"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
id="label"
|
||||
fill="rgba(0,0,0,1)"
|
||||
stroke="none"
|
||||
font-size="12px"
|
||||
text-anchor="middle"
|
||||
transform="matrix(0.970143,0.242536,-0.242536,0.970143,205.335785,170.149292)"
|
||||
>
|
||||
<g transform="matrix(1,0,0,1,-33.187069,-28.430592)">
|
||||
<path
|
||||
id="background"
|
||||
fill="none"
|
||||
d="M 0,0 l 68.37411720861238,0 l 0,50.86117678317855 l-68.37411720861238 0 z"
|
||||
stroke="none"
|
||||
width="68.37411720861238px"
|
||||
height="50.86117678317855px"
|
||||
/>
|
||||
</g>
|
||||
<g
|
||||
font-size="12px"
|
||||
text-anchor="middle"
|
||||
transform="matrix(1,0,0,1,0,0)"
|
||||
>
|
||||
<text
|
||||
id="text"
|
||||
fill="rgba(0,0,0,1)"
|
||||
dominant-baseline="alphabetic"
|
||||
paint-order="stroke"
|
||||
dx="1"
|
||||
stroke="none"
|
||||
>
|
||||
line-edge
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 3.5 KiB |
315
packages/g6/__tests__/integration/snapshots/static/edge-line.svg
Normal file
315
packages/g6/__tests__/integration/snapshots/static/edge-line.svg
Normal file
@ -0,0 +1,315 @@
|
||||
<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"
|
||||
stroke="none"
|
||||
visibility="visible"
|
||||
font-size="16px"
|
||||
font-family="sans-serif"
|
||||
font-style="normal"
|
||||
font-weight="normal"
|
||||
font-variant="normal"
|
||||
text-anchor="left"
|
||||
stroke-dashoffset="0px"
|
||||
transform="matrix(1,0,0,1,0,0)"
|
||||
>
|
||||
<g
|
||||
id="g-svg-5"
|
||||
fill="none"
|
||||
stroke="rgba(24,144,255,1)"
|
||||
stroke-width="2px"
|
||||
marker-start="true"
|
||||
marker-end="false"
|
||||
transform="matrix(1,0,0,1,0,0)"
|
||||
>
|
||||
<g stroke-width="2px" transform="matrix(1,0,0,1,100,50)">
|
||||
<line
|
||||
id="key"
|
||||
fill="none"
|
||||
x1="0"
|
||||
y1="0"
|
||||
x2="200"
|
||||
y2="0"
|
||||
stroke="rgba(24,144,255,1)"
|
||||
/>
|
||||
<g
|
||||
stroke-width="2px"
|
||||
stroke-dasharray="0px,0px"
|
||||
transform="matrix(1,0,0,1,0,0)"
|
||||
>
|
||||
<path
|
||||
id="g-svg-8"
|
||||
fill="rgba(24,144,255,1)"
|
||||
d="M 0,5 L 5,0 L 10,5 L 5,10 Z"
|
||||
transform="translate(-5,-5)"
|
||||
stroke="none"
|
||||
width="10px"
|
||||
height="10px"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
id="label"
|
||||
fill="rgba(24,144,255,1)"
|
||||
stroke="none"
|
||||
font-size="12px"
|
||||
text-anchor="middle"
|
||||
transform="matrix(1,0,0,1,204,44)"
|
||||
>
|
||||
<g transform="matrix(1,0,0,1,-44.540001,-20)">
|
||||
<path
|
||||
id="background"
|
||||
fill="none"
|
||||
d="M 0,0 l 91.08,0 l 0,34 l-91.08 0 z"
|
||||
stroke="none"
|
||||
width="91.08px"
|
||||
height="34px"
|
||||
/>
|
||||
</g>
|
||||
<g
|
||||
font-size="12px"
|
||||
text-anchor="middle"
|
||||
transform="matrix(1,0,0,1,0,0)"
|
||||
>
|
||||
<text
|
||||
id="text"
|
||||
fill="rgba(24,144,255,1)"
|
||||
dominant-baseline="alphabetic"
|
||||
paint-order="stroke"
|
||||
dx="1"
|
||||
stroke="none"
|
||||
>
|
||||
🌲line-edge
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
id="g-svg-12"
|
||||
fill="none"
|
||||
stroke="rgba(24,144,255,1)"
|
||||
stroke-width="2px"
|
||||
marker-start="true"
|
||||
marker-end="true"
|
||||
stroke-dasharray="10px,10px"
|
||||
transform="matrix(1,0,0,1,0,0)"
|
||||
>
|
||||
<g
|
||||
opacity="0.25"
|
||||
pointer-events="none"
|
||||
stroke-width="12px"
|
||||
stroke-dasharray="0px,0px"
|
||||
transform="matrix(1,0,0,1,100,150)"
|
||||
>
|
||||
<line
|
||||
id="halo"
|
||||
fill="none"
|
||||
x1="0"
|
||||
y1="0"
|
||||
x2="200"
|
||||
y2="50"
|
||||
stroke="rgba(24,144,255,1)"
|
||||
/>
|
||||
</g>
|
||||
<g
|
||||
stroke-width="2px"
|
||||
stroke-dasharray="10px,10px"
|
||||
transform="matrix(1,0,0,1,100,150)"
|
||||
>
|
||||
<line
|
||||
id="key"
|
||||
fill="none"
|
||||
x1="0"
|
||||
y1="0"
|
||||
x2="200"
|
||||
y2="50"
|
||||
stroke="rgba(24,144,255,1)"
|
||||
/>
|
||||
<g
|
||||
stroke-width="2px"
|
||||
stroke-dasharray="0px,0px"
|
||||
transform="matrix(0.970143,0.242536,-0.242536,0.970143,0,0)"
|
||||
>
|
||||
<path
|
||||
id="g-svg-15"
|
||||
fill="rgba(24,144,255,1)"
|
||||
d="M 0,5 A 5 5 0 1 0 10 5 A 5 5 0 1 0 0 5 Z"
|
||||
transform="translate(-5,-5)"
|
||||
stroke="none"
|
||||
width="10px"
|
||||
height="10px"
|
||||
/>
|
||||
</g>
|
||||
<g
|
||||
stroke-width="2px"
|
||||
stroke-dasharray="0px,0px"
|
||||
transform="matrix(-0.970143,-0.242536,0.242536,-0.970143,200,50)"
|
||||
>
|
||||
<path
|
||||
id="g-svg-17"
|
||||
fill="rgba(255,0,0,1)"
|
||||
d="M 0,5 L 10,0 L 10,10 Z"
|
||||
transform="translate(-5,-5)"
|
||||
stroke="none"
|
||||
width="10px"
|
||||
height="10px"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
id="label"
|
||||
fill="rgba(0,0,0,1)"
|
||||
stroke="none"
|
||||
font-size="12px"
|
||||
text-anchor="middle"
|
||||
transform="matrix(0.970143,0.242536,-0.242536,0.970143,205.335785,170.149292)"
|
||||
>
|
||||
<g transform="matrix(1,0,0,1,-33.187069,-28.430592)">
|
||||
<path
|
||||
id="background"
|
||||
fill="none"
|
||||
d="M 0,0 l 68.37411720861238,0 l 0,50.86117678317855 l-68.37411720861238 0 z"
|
||||
stroke="none"
|
||||
width="68.37411720861238px"
|
||||
height="50.86117678317855px"
|
||||
/>
|
||||
</g>
|
||||
<g
|
||||
font-size="12px"
|
||||
text-anchor="middle"
|
||||
transform="matrix(1,0,0,1,0,0)"
|
||||
>
|
||||
<text
|
||||
id="text"
|
||||
fill="rgba(0,0,0,1)"
|
||||
dominant-baseline="alphabetic"
|
||||
paint-order="stroke"
|
||||
dx="1"
|
||||
stroke="none"
|
||||
>
|
||||
line-edge
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
id="g-svg-22"
|
||||
fill="none"
|
||||
stroke="rgba(24,144,255,1)"
|
||||
stroke-width="2px"
|
||||
marker-start="true"
|
||||
marker-end="true"
|
||||
stroke-dasharray="10px,10px"
|
||||
transform="matrix(1,0,0,1,0,0)"
|
||||
>
|
||||
<g
|
||||
opacity="0.25"
|
||||
pointer-events="none"
|
||||
stroke-width="2px"
|
||||
stroke-dasharray="0px,0px"
|
||||
transform="matrix(1,0,0,1,100,250)"
|
||||
>
|
||||
<line
|
||||
id="halo"
|
||||
fill="none"
|
||||
x1="200"
|
||||
y1="50"
|
||||
x2="0"
|
||||
y2="0"
|
||||
stroke="rgba(24,144,255,1)"
|
||||
/>
|
||||
</g>
|
||||
<g
|
||||
stroke-width="2px"
|
||||
stroke-dasharray="10px,10px"
|
||||
transform="matrix(1,0,0,1,100,250)"
|
||||
>
|
||||
<line
|
||||
id="key"
|
||||
fill="none"
|
||||
x1="200"
|
||||
y1="50"
|
||||
x2="0"
|
||||
y2="0"
|
||||
stroke="rgba(24,144,255,1)"
|
||||
/>
|
||||
<g
|
||||
stroke-width="2px"
|
||||
stroke-dasharray="0px,0px"
|
||||
transform="matrix(0.242536,-0.970143,0.970143,0.242536,200,50)"
|
||||
>
|
||||
<image
|
||||
id="g-svg-25"
|
||||
fill="none"
|
||||
preserveAspectRatio="none"
|
||||
x="0"
|
||||
y="0"
|
||||
href="https://gw.alipayobjects.com/mdn/rms_6ae20b/afts/img/A*N4ZMS7gHsUIAAAAAAAAAAABkARQnAQ"
|
||||
transform="translate(-25,-25)"
|
||||
stroke="none"
|
||||
width="50px"
|
||||
height="50px"
|
||||
/>
|
||||
</g>
|
||||
<g
|
||||
stroke-width="2px"
|
||||
stroke-dasharray="0px,0px"
|
||||
transform="matrix(0.970143,0.242536,-0.242536,0.970143,0,0)"
|
||||
>
|
||||
<circle
|
||||
id="g-svg-27"
|
||||
fill="none"
|
||||
transform="translate(-25,-25)"
|
||||
cx="25"
|
||||
cy="25"
|
||||
stroke="rgba(24,144,255,1)"
|
||||
r="25px"
|
||||
width="10px"
|
||||
height="10px"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
id="label"
|
||||
fill="rgba(0,0,0,1)"
|
||||
stroke="none"
|
||||
font-size="12px"
|
||||
text-anchor="end"
|
||||
transform="matrix(0.970143,0.242536,-0.242536,0.970143,277.201660,288.115753)"
|
||||
>
|
||||
<g transform="matrix(1,0,0,1,-115.447052,-41.305889)">
|
||||
<path
|
||||
id="background"
|
||||
fill="none"
|
||||
d="M 0,0 l 123.09411654497103,0 l 0,76.6117655971751 l-123.09411654497103 0 z"
|
||||
stroke="none"
|
||||
width="123.09411654497103px"
|
||||
height="76.6117655971751px"
|
||||
/>
|
||||
</g>
|
||||
<g font-size="12px" text-anchor="end" transform="matrix(1,0,0,1,0,0)">
|
||||
<text
|
||||
id="text"
|
||||
fill="rgba(0,0,0,1)"
|
||||
dominant-baseline="alphabetic"
|
||||
paint-order="stroke"
|
||||
dx="1"
|
||||
stroke="none"
|
||||
>
|
||||
reverted-line-edge
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 8.3 KiB |
81
packages/g6/__tests__/unit/utils/edge.spec.ts
Normal file
81
packages/g6/__tests__/unit/utils/edge.spec.ts
Normal file
@ -0,0 +1,81 @@
|
||||
import { Line } from '@antv/g';
|
||||
import { getLabelPositionStyle } from '../../../src/utils/edge';
|
||||
|
||||
describe('edge', () => {
|
||||
// horizontal line
|
||||
const line = new Line({
|
||||
style: {
|
||||
x1: 0,
|
||||
y1: 100,
|
||||
x2: 100,
|
||||
y2: 100,
|
||||
},
|
||||
});
|
||||
|
||||
// with rotation angle below Math.PI
|
||||
const line1 = new Line({
|
||||
style: {
|
||||
x1: 0,
|
||||
y1: 100,
|
||||
x2: 100,
|
||||
y2: 200,
|
||||
},
|
||||
});
|
||||
|
||||
// with rotation angle over Math.PI
|
||||
const line2 = new Line({
|
||||
style: {
|
||||
x1: 0,
|
||||
y1: 200,
|
||||
x2: 100,
|
||||
y2: 100,
|
||||
},
|
||||
});
|
||||
|
||||
it('getLabelPositionStyle', () => {
|
||||
const labelPosition = getLabelPositionStyle(line, 'center', false);
|
||||
expect(labelPosition.textAlign).toEqual('center');
|
||||
expect(labelPosition.x).toEqual(50);
|
||||
expect(labelPosition.y).toEqual(100);
|
||||
|
||||
const labelPosition2 = getLabelPositionStyle(line, 'center', true, 5, 5);
|
||||
expect(labelPosition2.textAlign).toEqual('center');
|
||||
expect(labelPosition2.x).toEqual(55);
|
||||
expect(labelPosition2.y).toEqual(105);
|
||||
|
||||
const labelPosition3 = getLabelPositionStyle(line, 'start', true, 5, 5);
|
||||
expect(labelPosition3.textAlign).toEqual('left');
|
||||
expect(labelPosition3.x).toEqual(5);
|
||||
expect(labelPosition3.y).toEqual(105);
|
||||
|
||||
const labelPosition4 = getLabelPositionStyle(line, 'end', true, 5, 5);
|
||||
expect(labelPosition4.textAlign).toEqual('right');
|
||||
expect(labelPosition4.x).toBeCloseTo(100 * 0.99 + 5);
|
||||
expect(labelPosition4.y).toEqual(105);
|
||||
|
||||
const labelPosition5 = getLabelPositionStyle(line, 0.5, true, 5, 5);
|
||||
expect(labelPosition5.textAlign).toEqual('center');
|
||||
expect(labelPosition5.x).toEqual(55);
|
||||
expect(labelPosition5.y).toEqual(105);
|
||||
|
||||
// with rotation angle below Math.PI
|
||||
const labelPosition6 = getLabelPositionStyle(line1, 'center', true, 5, 5);
|
||||
expect(labelPosition6.textAlign).toEqual('center');
|
||||
expect(labelPosition6.transform).toEqual('rotate(45deg)');
|
||||
expect(labelPosition6.x).toEqual(50 + 5 * Math.cos(Math.PI / 4) - 5 * Math.sin(Math.PI / 4));
|
||||
expect(labelPosition6.y).toEqual(150 + 5 * Math.sin(Math.PI / 4) + 5 * Math.cos(Math.PI / 4));
|
||||
|
||||
const labelPosition7 = getLabelPositionStyle(line1, 'start', true, 5, 5);
|
||||
expect(labelPosition7.textAlign).toEqual('left');
|
||||
|
||||
const labelPosition8 = getLabelPositionStyle(line1, 'end', true, 5, 5);
|
||||
expect(labelPosition8.textAlign).toEqual('right');
|
||||
|
||||
// with rotation angle over Math.PI
|
||||
const labelPosition9 = getLabelPositionStyle(line2, 'center', true, 5, 5);
|
||||
expect(labelPosition9.textAlign).toEqual('center');
|
||||
expect(labelPosition9.transform).toEqual('rotate(-45deg)');
|
||||
expect(labelPosition9.x).toEqual(50 + 5 * Math.cos(-Math.PI / 4) - 5 * Math.sin(-Math.PI / 4));
|
||||
expect(labelPosition9.y).toEqual(150 + 5 * Math.sin(-Math.PI / 4) + 5 * Math.cos(-Math.PI / 4));
|
||||
});
|
||||
});
|
8
packages/g6/__tests__/unit/utils/point.spec.ts
Normal file
8
packages/g6/__tests__/unit/utils/point.spec.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { isHorizontal } from '../../../src/utils/point';
|
||||
|
||||
describe('Point Functions', () => {
|
||||
it('isHorizontal function', () => {
|
||||
expect(isHorizontal({ x: 100, y: 100 }, { x: 50, y: 100 })).toEqual(true);
|
||||
expect(isHorizontal({ x: 100, y: 100 }, { x: 50, y: 150 })).toEqual(false);
|
||||
});
|
||||
});
|
66
packages/g6/__tests__/unit/utils/symbol.spec.ts
Normal file
66
packages/g6/__tests__/unit/utils/symbol.spec.ts
Normal file
@ -0,0 +1,66 @@
|
||||
import { circle, diamond, rect, simple, triangle, triangleRect, vee } from '../../../src/utils/symbol';
|
||||
|
||||
describe('Symbol Functions', () => {
|
||||
describe('circle', () => {
|
||||
it('should return the correct path for a circle', () => {
|
||||
const path = circle(10, 10);
|
||||
expect(path).toEqual([['M', 0, 0], ['A', 5, 5, 0, 1, 0, 10, 0], ['A', 5, 5, 0, 1, 0, 0, 0], ['Z']]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('triangle', () => {
|
||||
it('should return the correct path for a triangle', () => {
|
||||
const path = triangle(10, 10);
|
||||
expect(path).toEqual([['M', 0, 0], ['L', 10, -5], ['L', 10, 5], ['Z']]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('diamond', () => {
|
||||
it('should return the correct path for a diamond', () => {
|
||||
const path = diamond(10, 10);
|
||||
expect(path).toEqual([['M', 0, 0], ['L', 5, -5], ['L', 10, 0], ['L', 5, 5], ['Z']]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('vee', () => {
|
||||
it('should return the correct path for a vee', () => {
|
||||
const path = vee(10, 10);
|
||||
expect(path).toEqual([['M', 0, 0], ['L', 10, -5], ['L', 6.666666666666667, 0], ['L', 10, 5], ['Z']]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('rect', () => {
|
||||
it('should return the correct path for a rectangle', () => {
|
||||
const path = rect(10, 10);
|
||||
expect(path).toEqual([['M', 0, -5], ['L', 10, -5], ['L', 10, 5], ['L', 0, 5], ['Z']]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('triangleRect', () => {
|
||||
it('should return the correct path for a triangleRect', () => {
|
||||
const path = triangleRect(10, 10);
|
||||
expect(path).toEqual([
|
||||
['M', 0, 0],
|
||||
['L', 5, -5],
|
||||
['L', 5, 5],
|
||||
['Z'],
|
||||
['M', 8.571428571428571, -5],
|
||||
['L', 10, -5],
|
||||
['L', 10, 5],
|
||||
['L', 8.571428571428571, 5],
|
||||
['Z'],
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('simple', () => {
|
||||
it('should return the correct path for a simple shape', () => {
|
||||
const path = simple(10, 10);
|
||||
expect(path).toEqual([
|
||||
['M', 10, -5],
|
||||
['L', 0, 0],
|
||||
['L', 10, 5],
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
189
packages/g6/src/elements/edges/base-edge.ts
Normal file
189
packages/g6/src/elements/edges/base-edge.ts
Normal file
@ -0,0 +1,189 @@
|
||||
import type {
|
||||
BaseStyleProps,
|
||||
DisplayObject,
|
||||
DisplayObjectConfig,
|
||||
Group,
|
||||
LineStyleProps,
|
||||
PathStyleProps,
|
||||
} from '@antv/g';
|
||||
import { Path } from '@antv/g';
|
||||
import { deepMix, isFunction } from '@antv/util';
|
||||
import type { Point, PrefixObject } from '../../types';
|
||||
import type { EdgeKey, EdgeLabelStyleProps } from '../../types/edge';
|
||||
import { getLabelPositionStyle } from '../../utils/edge';
|
||||
import { omitStyleProps, subStyleProps } from '../../utils/prefix';
|
||||
import * as Symbol from '../../utils/symbol';
|
||||
import { SymbolFactor } from '../../utils/symbol';
|
||||
import type { LabelStyleProps } from '../shapes';
|
||||
import { Label } from '../shapes';
|
||||
import type { BaseShapeStyleProps } from '../shapes/base-shape';
|
||||
import { BaseShape } from '../shapes/base-shape';
|
||||
|
||||
type SymbolName = 'triangle' | 'circle' | 'diamond' | 'vee' | 'rect' | 'triangleRect' | 'simple';
|
||||
|
||||
type EdgeArrowStyleProps = {
|
||||
ctor?: { new (...args: any[]): DisplayObject };
|
||||
type?: SymbolName | SymbolFactor;
|
||||
width?: number;
|
||||
height?: number;
|
||||
} & PathStyleProps &
|
||||
Record<string, unknown>;
|
||||
|
||||
export type BaseEdgeStyleProps<KT extends object> = BaseShapeStyleProps &
|
||||
KT & {
|
||||
sourcePoint?: Point;
|
||||
targetPoint?: Point;
|
||||
label?: boolean;
|
||||
halo?: boolean;
|
||||
startArrow?: boolean;
|
||||
endArrow?: boolean;
|
||||
startArrowOffset?: number;
|
||||
endArrowOffset?: number;
|
||||
} & PrefixObject<EdgeLabelStyleProps, 'label'> &
|
||||
PrefixObject<KT, 'halo'> &
|
||||
PrefixObject<EdgeArrowStyleProps, 'startArrow'> &
|
||||
PrefixObject<EdgeArrowStyleProps, 'endArrow'>;
|
||||
|
||||
export type BaseEdgeOptions<KT extends object> = DisplayObjectConfig<BaseEdgeStyleProps<KT>>;
|
||||
|
||||
type ParsedBaseEdgeStyleProps<KT extends object> = Required<BaseEdgeStyleProps<KT>>;
|
||||
export abstract class BaseEdge<KT extends object, KS extends DisplayObject> extends BaseShape<BaseEdgeStyleProps<KT>> {
|
||||
static defaultStyleProps: BaseEdgeStyleProps<Record<string, unknown>> = {
|
||||
label: true,
|
||||
labelPosition: 'center',
|
||||
labelOffsetX: 4,
|
||||
labelOffsetY: -6,
|
||||
labelIsBillboard: true,
|
||||
labelAutoRotate: true,
|
||||
halo: false,
|
||||
haloLineDash: 0,
|
||||
haloPointerEvents: 'none',
|
||||
haloZIndex: -1,
|
||||
haloDroppable: false,
|
||||
startArrow: false,
|
||||
startArrowCtor: Path,
|
||||
startArrowType: 'triangle',
|
||||
startArrowWidth: 10,
|
||||
startArrowHeight: 10,
|
||||
startArrowAnchor: '0.5 0.5',
|
||||
startArrowTransformOrigin: 'center',
|
||||
startArrowLineDash: 0,
|
||||
endArrow: false,
|
||||
endArrowCtor: Path,
|
||||
endArrowType: 'triangle',
|
||||
endArrowWidth: 10,
|
||||
endArrowHeight: 10,
|
||||
endArrowAnchor: '0.5 0.5',
|
||||
endArrowTransformOrigin: 'center',
|
||||
endArrowLineDash: 0,
|
||||
};
|
||||
|
||||
constructor(options: BaseEdgeOptions<KT>) {
|
||||
super(deepMix({}, { style: BaseEdge.defaultStyleProps }, options));
|
||||
}
|
||||
|
||||
protected getKeyStyle(attributes: ParsedBaseEdgeStyleProps<KT>): KT {
|
||||
return omitStyleProps(this.getGraphicStyle(attributes), ['halo', 'label', 'startArrow', 'endArrow']);
|
||||
}
|
||||
|
||||
protected getHaloStyle(attributes: ParsedBaseEdgeStyleProps<KT>): false | KT {
|
||||
if (attributes.halo === false) return false;
|
||||
|
||||
const keyStyle = this.getKeyStyle(attributes);
|
||||
const haloStyle = subStyleProps<LineStyleProps>(this.getGraphicStyle(attributes), 'halo');
|
||||
|
||||
return { ...keyStyle, ...haloStyle };
|
||||
}
|
||||
|
||||
protected getLabelStyle(attributes: ParsedBaseEdgeStyleProps<KT>): false | LabelStyleProps {
|
||||
if (attributes.label === false) return false;
|
||||
|
||||
const labelStyle = subStyleProps<Required<EdgeLabelStyleProps>>(this.getGraphicStyle(attributes), 'label');
|
||||
const { position, offsetX, offsetY, autoRotate, ...restStyle } = labelStyle;
|
||||
const labelPositionStyle = getLabelPositionStyle(
|
||||
this.shapeMap.key as EdgeKey,
|
||||
position,
|
||||
autoRotate,
|
||||
offsetX,
|
||||
offsetY,
|
||||
);
|
||||
|
||||
return { ...labelPositionStyle, ...restStyle } as LabelStyleProps;
|
||||
}
|
||||
|
||||
protected drawArrow(attributes: ParsedBaseEdgeStyleProps<KT>, isStart: boolean) {
|
||||
const arrowType = isStart ? 'startArrow' : 'endArrow';
|
||||
const arrowPresence = attributes[arrowType];
|
||||
|
||||
if (arrowPresence) {
|
||||
const { ctor } = subStyleProps<Required<EdgeArrowStyleProps>>(this.getGraphicStyle(attributes), arrowType);
|
||||
const arrowStyle = this.getArrowStyle(attributes, isStart);
|
||||
this.shapeMap.key.style[isStart ? 'markerStart' : 'markerEnd'] = new ctor({ style: arrowStyle });
|
||||
this.shapeMap.key.style[isStart ? 'markerStartOffset' : 'markerEndOffset'] = isStart
|
||||
? attributes.startArrowOffset
|
||||
: attributes.endArrowOffset;
|
||||
} else {
|
||||
this.shapeMap.key.style[isStart ? 'markerStart' : 'markerEnd'] = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private getArrowStyle(attributes: ParsedBaseEdgeStyleProps<KT>, isStart: boolean) {
|
||||
const { stroke, ...keyStyle } = this.getKeyStyle(attributes) as BaseStyleProps;
|
||||
const arrowType = isStart ? 'startArrow' : 'endArrow';
|
||||
const { width, height, type, ctor, ...arrowStyle } = subStyleProps<Required<EdgeArrowStyleProps>>(
|
||||
this.getGraphicStyle(attributes),
|
||||
arrowType,
|
||||
);
|
||||
|
||||
let path;
|
||||
if (ctor === Path) {
|
||||
const arrowFn = isFunction(type) ? type : Symbol[type] || Symbol.triangle;
|
||||
path = arrowFn(width, height);
|
||||
}
|
||||
|
||||
return {
|
||||
...keyStyle,
|
||||
...(path && { path, fill: type === 'simple' ? '' : stroke }),
|
||||
width,
|
||||
height,
|
||||
...arrowStyle,
|
||||
};
|
||||
}
|
||||
|
||||
protected drawLabelShape(attributes: ParsedBaseEdgeStyleProps<KT>, container: Group) {
|
||||
this.upsert('label', Label, this.getLabelStyle(attributes), container);
|
||||
}
|
||||
|
||||
protected abstract drawKeyShape(attributes: ParsedBaseEdgeStyleProps<KT>, container: Group): KS | undefined;
|
||||
|
||||
public render(attributes = this.parsedAttributes, container: Group = this): void {
|
||||
// 1. key shape
|
||||
const keyShape = this.drawKeyShape(attributes, container);
|
||||
if (!keyShape) return;
|
||||
|
||||
// 2. arrows
|
||||
this.drawArrow(attributes, true);
|
||||
this.drawArrow(attributes, false);
|
||||
|
||||
// 3. label
|
||||
this.drawLabelShape(attributes, container);
|
||||
|
||||
// 4. halo
|
||||
this.upsert(
|
||||
'halo',
|
||||
this.shapeMap.key.constructor as new (...args: any[]) => KS,
|
||||
this.getHaloStyle(attributes),
|
||||
container,
|
||||
);
|
||||
}
|
||||
|
||||
animate(keyframes: Keyframe[] | PropertyIndexedKeyframes, options?: number | KeyframeAnimationOptions) {
|
||||
const result = super.animate(keyframes, options);
|
||||
|
||||
result.onframe = () => {
|
||||
this.drawLabelShape(this.parsedAttributes, this);
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
3
packages/g6/src/elements/edges/index.ts
Normal file
3
packages/g6/src/elements/edges/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export { Line } from './line';
|
||||
|
||||
export type { LineStyleProps } from './line';
|
34
packages/g6/src/elements/edges/line.ts
Normal file
34
packages/g6/src/elements/edges/line.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import type { DisplayObjectConfig, LineStyleProps as GLineStyleProps, Group } from '@antv/g';
|
||||
import { Line as GLine } from '@antv/g';
|
||||
import type { Point } from '../../types';
|
||||
import type { BaseEdgeStyleProps } from './base-edge';
|
||||
import { BaseEdge } from './base-edge';
|
||||
|
||||
export type LineStyleProps = BaseEdgeStyleProps<LineKeyStyleProps>;
|
||||
|
||||
type LineKeyStyleProps = Omit<GLineStyleProps, 'x1' | 'y1' | 'x2' | 'y2'>;
|
||||
|
||||
type ParsedLineStyleProps = Required<LineStyleProps>;
|
||||
|
||||
type LineOptions = DisplayObjectConfig<LineStyleProps>;
|
||||
|
||||
/**
|
||||
* Draw line based on BaseEdge, override drawKeyShape
|
||||
*/
|
||||
export class Line extends BaseEdge<LineKeyStyleProps, GLine> {
|
||||
constructor(options: LineOptions) {
|
||||
super(options);
|
||||
}
|
||||
|
||||
protected drawKeyShape(attributes: ParsedLineStyleProps, container: Group): GLine | undefined {
|
||||
return this.upsert('key', GLine, this.getKeyStyle(attributes), container);
|
||||
}
|
||||
|
||||
protected getKeyStyle(attributes: ParsedLineStyleProps): GLineStyleProps {
|
||||
const { sourcePoint, targetPoint, ...keyShape } = super.getKeyStyle(attributes) as unknown as GLineStyleProps & {
|
||||
sourcePoint: Point;
|
||||
targetPoint: Point;
|
||||
};
|
||||
return { ...keyShape, x1: sourcePoint[0], y1: sourcePoint[1], x2: targetPoint[0], y2: targetPoint[1] };
|
||||
}
|
||||
}
|
@ -36,7 +36,7 @@ export type BaseNodeStyleProps<KT extends object> = BaseShapeStyleProps &
|
||||
'anchor'
|
||||
>;
|
||||
|
||||
// type ParsedCircleStyleProps = Required<BaseNodeStyleProps>;
|
||||
type ParsedBaseNodeStyleProps<KT extends object> = Required<BaseNodeStyleProps<KT>>;
|
||||
|
||||
type BaseNodeOptions<KT extends object> = DisplayObjectConfig<BaseNodeStyleProps<KT>>;
|
||||
|
||||
@ -54,12 +54,12 @@ export abstract class BaseNode<KT extends object, KS> extends BaseShape<BaseNode
|
||||
super(options);
|
||||
}
|
||||
|
||||
protected getKeyStyle(attributes: BaseNodeStyleProps<KT>): KT {
|
||||
protected getKeyStyle(attributes: ParsedBaseNodeStyleProps<KT>): KT {
|
||||
const style = this.getGraphicStyle(attributes);
|
||||
return omitStyleProps(style, ['label', 'halo', 'icon', 'badge', 'anchor']);
|
||||
}
|
||||
|
||||
protected getLabelStyle(attributes: BaseNodeStyleProps<KT>) {
|
||||
protected getLabelStyle(attributes: ParsedBaseNodeStyleProps<KT>) {
|
||||
const { position, ...labelStyle } = subStyleProps<NodeLabelStyleProps>(
|
||||
this.getGraphicStyle(attributes),
|
||||
'label',
|
||||
@ -72,9 +72,9 @@ export abstract class BaseNode<KT extends object, KS> extends BaseShape<BaseNode
|
||||
} as NodeLabelStyleProps;
|
||||
}
|
||||
|
||||
protected abstract getHaloStyle(attributes: BaseNodeStyleProps<KT>): KT;
|
||||
protected abstract getHaloStyle(attributes: ParsedBaseNodeStyleProps<KT>): KT;
|
||||
|
||||
protected getIconStyle(attributes: BaseNodeStyleProps<KT>) {
|
||||
protected getIconStyle(attributes: ParsedBaseNodeStyleProps<KT>) {
|
||||
const iconStyle = subStyleProps(this.getGraphicStyle(attributes), 'icon');
|
||||
const keyShape = this.shapeMap.key;
|
||||
const [x, y] = getXYByPosition(keyShape.getLocalBounds(), 'center');
|
||||
@ -86,7 +86,7 @@ export abstract class BaseNode<KT extends object, KS> extends BaseShape<BaseNode
|
||||
} as IconStyleProps;
|
||||
}
|
||||
|
||||
protected getBadgesStyle(attributes: BaseNodeStyleProps<KT>): NodeBadgeStyleProps[] {
|
||||
protected getBadgesStyle(attributes: ParsedBaseNodeStyleProps<KT>): NodeBadgeStyleProps[] {
|
||||
const badgesStyle = this.getGraphicStyle(attributes).badgeOptions || [];
|
||||
const keyShape = this.shapeMap.key;
|
||||
|
||||
@ -97,7 +97,7 @@ export abstract class BaseNode<KT extends object, KS> extends BaseShape<BaseNode
|
||||
});
|
||||
}
|
||||
|
||||
protected getAnchorsStyle(attributes: BaseNodeStyleProps<KT>): NodeAnchorStyleProps[] {
|
||||
protected getAnchorsStyle(attributes: ParsedBaseNodeStyleProps<KT>): NodeAnchorStyleProps[] {
|
||||
const anchorStyle = this.getGraphicStyle(attributes).anchorOptions || [];
|
||||
const keyShape = this.shapeMap.key;
|
||||
|
||||
@ -108,9 +108,9 @@ export abstract class BaseNode<KT extends object, KS> extends BaseShape<BaseNode
|
||||
});
|
||||
}
|
||||
|
||||
protected abstract drawKeyShape(attributes: BaseNodeStyleProps<KT>, container: Group): KS;
|
||||
protected abstract drawKeyShape(attributes: ParsedBaseNodeStyleProps<KT>, container: Group): KS;
|
||||
|
||||
public render(attributes = this.attributes as BaseNodeStyleProps<KT>, container: Group = this) {
|
||||
public render(attributes = this.parsedAttributes, container: Group = this) {
|
||||
// 1. key shape
|
||||
const keyShape = this.drawKeyShape(attributes, container);
|
||||
if (!keyShape) return;
|
||||
|
@ -6,17 +6,19 @@ import { BaseNode } from './base-node';
|
||||
|
||||
export type CircleStyleProps = BaseNodeStyleProps<GCircleStyleProps>;
|
||||
|
||||
type ParsedCircleStyleProps = Required<CircleStyleProps>;
|
||||
|
||||
type CircleOptions = DisplayObjectConfig<CircleStyleProps>;
|
||||
|
||||
/**
|
||||
* Draw circle based on BaseNode, override drawKeyShape.
|
||||
*/
|
||||
export class Circle extends BaseNode<CircleStyleProps, GCircle> {
|
||||
export class Circle extends BaseNode<GCircleStyleProps, GCircle> {
|
||||
constructor(options: CircleOptions) {
|
||||
super(options);
|
||||
}
|
||||
|
||||
protected getHaloStyle(attributes: CircleStyleProps): GCircleStyleProps {
|
||||
protected getHaloStyle(attributes: ParsedCircleStyleProps): GCircleStyleProps {
|
||||
const haloStyle = subStyleProps(this.getGraphicStyle(attributes), 'halo') as Partial<GCircleStyleProps>;
|
||||
const keyStyle = this.getKeyStyle(attributes);
|
||||
|
||||
@ -32,7 +34,7 @@ export class Circle extends BaseNode<CircleStyleProps, GCircle> {
|
||||
} as GCircleStyleProps;
|
||||
}
|
||||
|
||||
protected drawKeyShape(attributes: CircleStyleProps, container: Group): GCircle {
|
||||
protected drawKeyShape(attributes: ParsedCircleStyleProps, container: Group): GCircle {
|
||||
return this.upsert('key', GCircle, this.getKeyStyle(attributes), container) as GCircle;
|
||||
}
|
||||
|
||||
|
@ -13,6 +13,10 @@ export abstract class BaseShape<T extends BaseShapeStyleProps> extends CustomEle
|
||||
this.bindEvents();
|
||||
}
|
||||
|
||||
get parsedAttributes() {
|
||||
return this.attributes as Required<T>;
|
||||
}
|
||||
|
||||
/**
|
||||
* <zh> 图形实例映射表
|
||||
*
|
||||
@ -40,15 +44,17 @@ export abstract class BaseShape<T extends BaseShapeStyleProps> extends CustomEle
|
||||
protected upsert<P extends DisplayObject>(
|
||||
key: string,
|
||||
Ctor: { new (...args: any[]): P },
|
||||
style: P['attributes'],
|
||||
style: P['attributes'] | false,
|
||||
container: DisplayObject,
|
||||
) {
|
||||
): P | undefined {
|
||||
const target = this.shapeMap[key];
|
||||
// remove
|
||||
// 如果 style 为 false,则删除图形 / remove shape if style is false
|
||||
if (target && style === false) {
|
||||
container.removeChild(target);
|
||||
delete this.shapeMap[key];
|
||||
if (style === false) {
|
||||
if (target) {
|
||||
container.removeChild(target);
|
||||
delete this.shapeMap[key];
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@ -67,7 +73,7 @@ export abstract class BaseShape<T extends BaseShapeStyleProps> extends CustomEle
|
||||
if ('update' in target) (target.update as (...args: unknown[]) => unknown)(style);
|
||||
else target.attr(style);
|
||||
|
||||
return target;
|
||||
return target as P;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -98,7 +104,7 @@ export abstract class BaseShape<T extends BaseShapeStyleProps> extends CustomEle
|
||||
* @param attributes
|
||||
* @param container
|
||||
*/
|
||||
public abstract render(attributes: T, container: Group): void;
|
||||
public abstract render(attributes: Required<T>, container: Group): void;
|
||||
|
||||
public bindEvents() {}
|
||||
|
||||
@ -112,8 +118,8 @@ export abstract class BaseShape<T extends BaseShapeStyleProps> extends CustomEle
|
||||
*/
|
||||
public getGraphicStyle<T extends Record<string, any>>(
|
||||
attributes: T,
|
||||
): Omit<T, 'x' | 'y' | 'transform' | 'transformOrigin' | 'className'> {
|
||||
const { x, y, className, transform, transformOrigin, ...style } = attributes;
|
||||
): Omit<T, 'x' | 'y' | 'transform' | 'transformOrigin' | 'className' | 'anchor'> {
|
||||
const { x, y, className, transform, transformOrigin, anchor, ...style } = attributes;
|
||||
return style;
|
||||
}
|
||||
|
||||
|
@ -1 +1,31 @@
|
||||
import { Line, Path, Polyline } from '@antv/g';
|
||||
import type { LabelStyleProps } from '../elements/shapes';
|
||||
|
||||
export type EdgeDirection = 'in' | 'out' | 'both';
|
||||
|
||||
export type EdgeKey = Line | Path | Polyline;
|
||||
|
||||
export type EdgeLabelPosition = 'start' | 'center' | 'end' | number;
|
||||
|
||||
export type EdgeLabelStyleProps = {
|
||||
/**
|
||||
* <zh/> 标签相对于边的位置。可以是 'start'、'center'、'end' 或特定比率(数字 0-1)
|
||||
* <en/> The position of the label relative to the edge. Can be 'start', 'center', 'end', or a specific ratio (number)
|
||||
*/
|
||||
position?: EdgeLabelPosition;
|
||||
/**
|
||||
* <zh/> 标签平行于边的水平偏移量
|
||||
* <en/> The horizontal offset of the label parallel to the edge
|
||||
*/
|
||||
offsetX?: number;
|
||||
/**
|
||||
* <zh/> 标签垂直于边的垂直偏移量
|
||||
* <en/> The vertical offset of the label perpendicular to the edge
|
||||
*/
|
||||
offsetY?: number;
|
||||
/**
|
||||
* <zh/> 是否自动旋转以与边的方向对齐
|
||||
* <en/> Indicates whether the label should automatically rotate to align with the edge's direction
|
||||
*/
|
||||
autoRotate?: boolean;
|
||||
} & LabelStyleProps;
|
||||
|
94
packages/g6/src/utils/edge.ts
Normal file
94
packages/g6/src/utils/edge.ts
Normal file
@ -0,0 +1,94 @@
|
||||
import { pick } from '@antv/util';
|
||||
import type { EdgeKey, EdgeLabelPosition, EdgeLabelStyleProps } from '../types/edge';
|
||||
import { isHorizontal } from './point';
|
||||
|
||||
/**
|
||||
* <zh/> 获取标签的位置样式
|
||||
*
|
||||
* <en/> Get the style of the label's position
|
||||
* @param key - <zh/> 边对象 | <en/> The edge object
|
||||
* @param position - <zh/> 标签位置 | <en/> Position of the label
|
||||
* @param autoRotate - <zh/> 是否自动旋转 | <en/> Whether to auto-rotate
|
||||
* @param offsetX - <zh/> 标签相对于边的水平偏移量 | <en/> Horizontal offset of the label relative to the edge
|
||||
* @param offsetY - <zh/> 标签相对于边的垂直偏移量 | <en/> Vertical offset of the label relative to the edge
|
||||
* @returns <zh/> 标签的位置样式 | <en/> Returns the style of the label's position
|
||||
*/
|
||||
export function getLabelPositionStyle(
|
||||
key: EdgeKey,
|
||||
position: EdgeLabelPosition,
|
||||
autoRotate: boolean,
|
||||
offsetX?: number,
|
||||
offsetY?: number,
|
||||
): Partial<EdgeLabelStyleProps> {
|
||||
const START_RATIO = 0;
|
||||
const MIDDLE_RATIO = 0.5;
|
||||
const END_RATIO = 0.99;
|
||||
|
||||
let ratio = typeof position === 'number' ? position : MIDDLE_RATIO;
|
||||
if (position === 'start') ratio = START_RATIO;
|
||||
if (position === 'end') ratio = END_RATIO;
|
||||
|
||||
const positionStyle: Partial<EdgeLabelStyleProps> = {
|
||||
textAlign: position === 'start' ? 'left' : position === 'end' ? 'right' : 'center',
|
||||
offsetX,
|
||||
offsetY,
|
||||
};
|
||||
adjustLabelPosition(key, positionStyle, ratio);
|
||||
|
||||
if (autoRotate) applyAutoRotation(key, positionStyle, ratio);
|
||||
|
||||
return pick(positionStyle, ['x', 'y', 'textAlign', 'transform']);
|
||||
}
|
||||
|
||||
/**
|
||||
* <zh/> 根据边主体、位置样式、比例和角度计算标签的精确位置
|
||||
*
|
||||
* <en/> Calculate the precise position of the label based on the edge body, position style, ratio, and angle
|
||||
* @param key - <zh/> 边对象 | <en/> The edge object
|
||||
* @param positionStyle - <zh/> 标签的位置样式 | <en/> The style of the label's position
|
||||
* @param ratio - <zh/> 沿边的比例位置 | <en/> Ratio along the edge
|
||||
* @param angle - <zh/> 旋转角度 | <en/> Rotation angle
|
||||
*/
|
||||
function adjustLabelPosition(key: EdgeKey, positionStyle: Partial<EdgeLabelStyleProps>, ratio: number, angle?: number) {
|
||||
const { x: pointX, y: pointY } = key.getPoint(ratio);
|
||||
const { offsetX = 0, offsetY = 0 } = positionStyle;
|
||||
|
||||
let actualOffsetX = offsetX;
|
||||
let actualOffsetY = offsetY;
|
||||
|
||||
if (angle) {
|
||||
actualOffsetX = offsetX * Math.cos(angle) - offsetY * Math.sin(angle);
|
||||
actualOffsetY = offsetX * Math.sin(angle) + offsetY * Math.cos(angle);
|
||||
}
|
||||
|
||||
positionStyle.x = pointX + actualOffsetX;
|
||||
positionStyle.y = pointY + actualOffsetY;
|
||||
}
|
||||
|
||||
/**
|
||||
* <zh/> 根据边的方向计算并应用标签的旋转角度
|
||||
*
|
||||
* <en/> Calculate and apply the rotation angle of the label based on the direction of the edge
|
||||
* @param key - <zh/> 边对象 | <en/> The edge object
|
||||
* @param positionStyle - <zh/> 标签的位置样式 | <en/> The style of the label's position
|
||||
* @param ratio - <zh/> 沿边的比例位置 | <en/> ratio along the edge
|
||||
*/
|
||||
function applyAutoRotation(key: EdgeKey, positionStyle: Partial<EdgeLabelStyleProps>, ratio: number) {
|
||||
const { textAlign } = positionStyle;
|
||||
const point = key.getPoint(ratio);
|
||||
const pointOffset = key.getPoint(ratio + 0.01);
|
||||
|
||||
if (isHorizontal(point, pointOffset)) return;
|
||||
|
||||
let angle = Math.atan2(pointOffset.y - point.y, pointOffset.x - point.x);
|
||||
|
||||
const isRevert = pointOffset.x < point.x;
|
||||
if (isRevert) {
|
||||
positionStyle.textAlign = textAlign === 'center' ? textAlign : textAlign === 'left' ? 'right' : 'left';
|
||||
positionStyle.offsetX! *= -1;
|
||||
angle += Math.PI;
|
||||
}
|
||||
|
||||
adjustLabelPosition(key, positionStyle, ratio, angle);
|
||||
positionStyle.transform = `rotate(${(angle / Math.PI) * 180}deg)`;
|
||||
}
|
13
packages/g6/src/utils/point.ts
Normal file
13
packages/g6/src/utils/point.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { Point } from '@antv/util';
|
||||
|
||||
/**
|
||||
* <zh/> 判断两个点是否在同一水平线上
|
||||
*
|
||||
* <en/> whether two points are on the same horizontal line
|
||||
* @param p1 - <zh/> 第一个点 | <en/> the first point
|
||||
* @param p2 - <zh/> 第二个点 | <en/> the second point
|
||||
* @returns <zh/> 返回两个点是否在同一水平线上 | <en/> is horizontal or not
|
||||
*/
|
||||
export function isHorizontal(p1: Point, p2: Point): boolean {
|
||||
return p1.y === p2.y;
|
||||
}
|
72
packages/g6/src/utils/symbol.ts
Normal file
72
packages/g6/src/utils/symbol.ts
Normal file
@ -0,0 +1,72 @@
|
||||
/* eslint-disable jsdoc/require-returns */
|
||||
/* eslint-disable jsdoc/require-param */
|
||||
import type { PathArray } from '@antv/util';
|
||||
|
||||
export type SymbolFactor = (width: number, height: number) => PathArray;
|
||||
|
||||
/**
|
||||
* ○
|
||||
*/
|
||||
export const circle: SymbolFactor = (width: number, height: number) => {
|
||||
const r = Math.max(width, height) / 2;
|
||||
return [['M', 0, 0], ['A', r, r, 0, 1, 0, 2 * r, 0], ['A', r, r, 0, 1, 0, 0, 0], ['Z']];
|
||||
};
|
||||
|
||||
/**
|
||||
* ▷
|
||||
*/
|
||||
export const triangle: SymbolFactor = (width: number, height: number) => {
|
||||
return [['M', 0, 0], ['L', width, -height / 2], ['L', width, height / 2], ['Z']];
|
||||
};
|
||||
|
||||
/**
|
||||
* ◇
|
||||
*/
|
||||
export const diamond: SymbolFactor = (width: number, height: number) => {
|
||||
return [['M', 0, 0], ['L', width / 2, -height / 2], ['L', width, 0], ['L', width / 2, height / 2], ['Z']];
|
||||
};
|
||||
|
||||
/**
|
||||
* >>
|
||||
*/
|
||||
export const vee: SymbolFactor = (width: number, height: number) => {
|
||||
return [['M', 0, 0], ['L', width, -height / 2], ['L', (2 * width) / 3, 0], ['L', width, height / 2], ['Z']];
|
||||
};
|
||||
|
||||
/**
|
||||
* □
|
||||
*/
|
||||
export const rect: SymbolFactor = (width: number, height: number) => {
|
||||
return [['M', 0, -height / 2], ['L', width, -height / 2], ['L', width, height / 2], ['L', 0, height / 2], ['Z']];
|
||||
};
|
||||
|
||||
/**
|
||||
* □▷
|
||||
*/
|
||||
export const triangleRect: SymbolFactor = (width: number, height: number) => {
|
||||
const tWidth = width / 2;
|
||||
const rWidth = width / 7;
|
||||
const rBeginX = width - rWidth;
|
||||
return [
|
||||
['M', 0, 0],
|
||||
['L', tWidth, -height / 2],
|
||||
['L', tWidth, height / 2],
|
||||
['Z'],
|
||||
['M', rBeginX, -height / 2],
|
||||
['L', rBeginX + rWidth, -height / 2],
|
||||
['L', rBeginX + rWidth, height / 2],
|
||||
['L', rBeginX, height / 2],
|
||||
['Z'],
|
||||
];
|
||||
};
|
||||
|
||||
/**
|
||||
* >
|
||||
*/
|
||||
export const simple: SymbolFactor = (width: number, height: number) => {
|
||||
return [
|
||||
['M', width, -height / 2],
|
||||
['L', 0, 0],
|
||||
['L', width, height / 2],
|
||||
];
|
||||
};
|
Loading…
Reference in New Issue
Block a user