feat(combo): add circle combo element (#5473)

* feat: circle combo

* refactor: ci

* refactor: registry typo

* feat: test combo children change

* refactor: combo animation

* refactor: circle combo

* refactor: base-combo

* refactor: combo collapsed marker

* test: add combo test

* refactor: fix cr issues
This commit is contained in:
Yuxin 2024-03-04 19:02:12 +08:00 committed by GitHub
parent c0d02134f6
commit f9e2ac6815
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
39 changed files with 3255 additions and 32 deletions

View File

@ -0,0 +1,104 @@
import { Graph } from '@/src';
import type { STDTestCase } from '../types';
export const comboCircle: STDTestCase = async (context) => {
const data = {
nodes: [
{ id: 'node-1', data: {}, style: { parentId: 'combo-2', x: 100, y: 100 } },
{ id: 'node-2', data: {}, style: { parentId: 'combo-1', x: 300, y: 200 } },
{ id: 'node-3', data: {}, style: { parentId: 'combo-1', x: 200, y: 300 } },
],
edges: [
{ id: 'edge-1', source: 'node-1', target: 'node-2' },
{ id: 'edge-2', source: 'node-2', target: 'node-3' },
],
combos: [
{
id: 'combo-1',
style: { parentId: 'combo-2' },
},
{
id: 'combo-2',
style: {
zIndex: -10, // TODO: zIndex?
},
},
],
};
const graph = new Graph({
...context,
data,
node: {
style: {
labelText: (d: any) => d.id,
},
},
combo: {
style: {
padding: 0,
labelText: (d: any) => d.id,
collapsedLineDash: [5, 5],
},
},
});
await graph.render();
const COLLAPSED_ORIGIN = ['top', 'bottom', 'left', 'right', 'center'];
const COLLAPSED_MARKER_TYPE = ['child-count', 'descendant-count', 'node-count'];
comboCircle.form = (panel) => {
const config = {
collapsedOrigin: 'top',
collapsedMarker: true,
collapsedMarkerType: 'child-count',
collapseCombo2: () => {
graph.updateComboData((data) => [
...data,
{
id: 'combo-2',
style: {
collapsed: true,
collapsedOrigin: config.collapsedOrigin,
collapsedMarker: config.collapsedMarker,
collapsedMarkerType: config.collapsedMarkerType,
},
},
]);
graph.render();
},
expandCombo2: () => {
graph.updateComboData((data) => [
...data,
{
id: 'combo-2',
style: {
collapsed: false,
collapsedOrigin: config.collapsedOrigin,
collapsedMarker: config.collapsedMarker,
collapsedMarkerType: config.collapsedMarkerType,
},
},
]);
graph.render();
},
};
return [
panel.add(config, 'collapsedOrigin', COLLAPSED_ORIGIN).onChange((collapsedOrigin: string) => {
config.collapsedOrigin = collapsedOrigin;
}),
panel.add(config, 'collapsedMarker').onChange((collapsedMarker: boolean) => {
config.collapsedMarker = collapsedMarker;
}),
panel.add(config, 'collapsedMarkerType', COLLAPSED_MARKER_TYPE).onChange((collapsedMarkerType: string) => {
config.collapsedMarkerType = collapsedMarkerType;
}),
panel.add(config, 'collapseCombo2'),
panel.add(config, 'expandCombo2'),
];
};
return graph;
};

View File

@ -1,3 +1,4 @@
export * from './behavior-drag-canvas';
export * from './behavior-zoom-canvas';
export * from './combo-circle';
export * from './common-graph';

View File

@ -9,7 +9,22 @@
<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-7" fill="none" transform="matrix(1,0,0,1,0,0)" />
<g id="g-svg-7" fill="none" transform="matrix(1,0,0,1,0,0)">
<g id="combo-1" fill="none" transform="matrix(1,0,0,1,125,150)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(247,251,255,1)"
transform="translate(-36.76955262170047,-36.76955262170047)"
cx="36.76955262170047"
cy="36.76955262170047"
stroke-width="1"
stroke="rgba(153,173,209,1)"
r="36.76955262170047"
/>
</g>
</g>
</g>
<g id="g-svg-6" fill="none" transform="matrix(1,0,0,1,0,0)">
<g
id="node-1-node-2"

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

@ -0,0 +1,297 @@
<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-7" fill="none" transform="matrix(1,0,0,1,0,0)">
<g
id="combo-2"
fill="none"
transform="matrix(1,0,0,1,217.872849,237.012848)"
>
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(253,253,253,1)"
transform="translate(-206.1130007246884,-206.1130007246884)"
cx="206.1130007246884"
cy="206.1130007246884"
stroke-width="1"
stroke="rgba(153,173,209,1)"
r="206.1130007246884"
/>
</g>
<g id="label" fill="none" transform="matrix(1,0,0,1,0,206.113007)">
<g transform="matrix(1,0,0,1,-26.280001,0)">
<path
id="background"
fill="rgba(255,255,255,1)"
d="M 0,0 l 53.56,0 l 0,23 l-53.56 0 z"
opacity="0.75"
stroke-width="0"
width="53.56"
height="23"
/>
</g>
<g transform="matrix(1,0,0,1,0,0)">
<text
id="text"
fill="rgba(0,0,0,1)"
dominant-baseline="central"
paint-order="stroke"
dx="0.5"
dy="11.5px"
font-size="12"
text-anchor="middle"
>
combo-2
</text>
</g>
</g>
</g>
<g
id="combo-1"
fill="none"
transform="matrix(1,0,0,1,250.440002,261.500000)"
>
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(253,253,253,1)"
transform="translate(-105.5257052101804,-105.5257052101804)"
cx="105.5257052101804"
cy="105.5257052101804"
stroke-width="1"
stroke="rgba(153,173,209,1)"
r="105.5257052101804"
/>
</g>
<g id="label" fill="none" transform="matrix(1,0,0,1,0,105.525703)">
<g transform="matrix(1,0,0,1,-25.440001,0)">
<path
id="background"
fill="rgba(255,255,255,1)"
d="M 0,0 l 51.88,0 l 0,23 l-51.88 0 z"
opacity="0.75"
stroke-width="0"
width="51.88"
height="23"
/>
</g>
<g transform="matrix(1,0,0,1,0,0)">
<text
id="text"
fill="rgba(0,0,0,1)"
dominant-baseline="central"
paint-order="stroke"
dx="0.5"
dy="11.5px"
font-size="12"
text-anchor="middle"
>
combo-1
</text>
</g>
</g>
</g>
</g>
<g id="g-svg-6" fill="none" transform="matrix(1,0,0,1,0,0)">
<g
id="edge-1"
fill="none"
marker-start="false"
marker-end="false"
transform="matrix(1,0,0,1,0,0)"
>
<g
id="edge-1"
fill="none"
marker-start="false"
marker-end="false"
stroke="transparent"
stroke-width="3"
/>
<g transform="matrix(1,0,0,1,114.310837,107.155418)">
<path
id="key"
fill="none"
d="M 0,0 L 171.37832988800267,85.68916494400133"
stroke-width="1"
stroke="rgba(153,173,209,1)"
/>
<path
id="key"
fill="none"
d="M 0,0 L 171.37832988800267,85.68916494400133"
stroke-width="3"
stroke="transparent"
/>
</g>
</g>
<g
id="edge-2"
fill="none"
marker-start="false"
marker-end="false"
transform="matrix(1,0,0,1,0,0)"
>
<g
id="edge-2"
fill="none"
marker-start="false"
marker-end="false"
stroke="transparent"
stroke-width="3"
/>
<g transform="matrix(1,0,0,1,211.313705,211.313705)">
<path
id="key"
fill="none"
d="M 77.3725830020305,0 L 0,77.3725830020305"
stroke-width="1"
stroke="rgba(153,173,209,1)"
/>
<path
id="key"
fill="none"
d="M 77.3725830020305,0 L 0,77.3725830020305"
stroke-width="3"
stroke="transparent"
/>
</g>
</g>
</g>
<g id="g-svg-5" fill="none" transform="matrix(1,0,0,1,0,0)">
<g id="node-1" fill="none" transform="matrix(1,0,0,1,100,100)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(34,126,255,1)"
transform="translate(-16,-16)"
cx="16"
cy="16"
stroke-width="0"
stroke="rgba(0,0,0,1)"
r="16"
/>
</g>
<g id="label" fill="none" transform="matrix(1,0,0,1,0,16)">
<g transform="matrix(1,0,0,1,-20.219999,0)">
<path
id="background"
fill="rgba(255,255,255,1)"
d="M 0,0 l 41.44,0 l 0,23 l-41.44 0 z"
opacity="0.5"
stroke-width="0"
width="41.44"
height="23"
/>
</g>
<g transform="matrix(1,0,0,1,0,0)">
<text
id="text"
fill="rgba(0,0,0,0.8509803921568627)"
dominant-baseline="central"
paint-order="stroke"
dx="0.5"
dy="11.5px"
font-size="12"
text-anchor="middle"
>
node-1
</text>
</g>
</g>
</g>
<g id="node-2" fill="none" transform="matrix(1,0,0,1,300,200)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(34,126,255,1)"
transform="translate(-16,-16)"
cx="16"
cy="16"
stroke-width="0"
stroke="rgba(0,0,0,1)"
r="16"
/>
</g>
<g id="label" fill="none" transform="matrix(1,0,0,1,0,16)">
<g transform="matrix(1,0,0,1,-21.059999,0)">
<path
id="background"
fill="rgba(255,255,255,1)"
d="M 0,0 l 43.12,0 l 0,23 l-43.12 0 z"
opacity="0.5"
stroke-width="0"
width="43.12"
height="23"
/>
</g>
<g transform="matrix(1,0,0,1,0,0)">
<text
id="text"
fill="rgba(0,0,0,0.8509803921568627)"
dominant-baseline="central"
paint-order="stroke"
dx="0.5"
dy="11.5px"
font-size="12"
text-anchor="middle"
>
node-2
</text>
</g>
</g>
</g>
<g id="node-3" fill="none" transform="matrix(1,0,0,1,200,300)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(34,126,255,1)"
transform="translate(-16,-16)"
cx="16"
cy="16"
stroke-width="0"
stroke="rgba(0,0,0,1)"
r="16"
/>
</g>
<g id="label" fill="none" transform="matrix(1,0,0,1,0,16)">
<g transform="matrix(1,0,0,1,-21.180000,0)">
<path
id="background"
fill="rgba(255,255,255,1)"
d="M 0,0 l 43.36,0 l 0,23 l-43.36 0 z"
opacity="0.5"
stroke-width="0"
width="43.36"
height="23"
/>
</g>
<g transform="matrix(1,0,0,1,0,0)">
<text
id="text"
fill="rgba(0,0,0,0.8509803921568627)"
dominant-baseline="central"
paint-order="stroke"
dx="0.5"
dy="11.5px"
font-size="12"
text-anchor="middle"
>
node-3
</text>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.9 KiB

View File

@ -0,0 +1,298 @@
<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-7" fill="none" transform="matrix(1,0,0,1,0,0)">
<g
id="combo-2"
fill="none"
transform="matrix(1,0,0,1,217.872849,427.125854)"
>
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(253,253,253,1)"
transform="translate(-16,-16)"
cx="16"
cy="16"
stroke-width="1"
stroke="rgba(153,173,209,1)"
r="16"
stroke-dasharray="5,5"
/>
</g>
<g id="label" fill="none" transform="matrix(1,0,0,1,0,16)">
<g transform="matrix(1,0,0,1,-26.280001,0)">
<path
id="background"
fill="rgba(255,255,255,1)"
d="M 0,0 l 53.56,0 l 0,23 l-53.56 0 z"
opacity="0.75"
stroke-width="0"
width="53.56"
height="23"
/>
</g>
<g transform="matrix(1,0,0,1,0,0)">
<text
id="text"
fill="rgba(0,0,0,1)"
dominant-baseline="central"
paint-order="stroke"
dx="0.5"
dy="11.5px"
font-size="12"
text-anchor="middle"
>
combo-2
</text>
</g>
</g>
</g>
<g
id="combo-1"
fill="none"
transform="matrix(1,0,0,1,250.440002,261.500000)"
>
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(253,253,253,1)"
transform="translate(-105.5257052101804,-105.5257052101804)"
cx="105.5257052101804"
cy="105.5257052101804"
stroke-width="1"
stroke="rgba(153,173,209,1)"
r="105.5257052101804"
/>
</g>
<g id="label" fill="none" transform="matrix(1,0,0,1,0,105.525703)">
<g transform="matrix(1,0,0,1,-25.440001,0)">
<path
id="background"
fill="rgba(255,255,255,1)"
d="M 0,0 l 51.88,0 l 0,23 l-51.88 0 z"
opacity="0.75"
stroke-width="0"
width="51.88"
height="23"
/>
</g>
<g transform="matrix(1,0,0,1,0,0)">
<text
id="text"
fill="rgba(0,0,0,1)"
dominant-baseline="central"
paint-order="stroke"
dx="0.5"
dy="11.5px"
font-size="12"
text-anchor="middle"
>
combo-1
</text>
</g>
</g>
</g>
</g>
<g id="g-svg-6" fill="none" transform="matrix(1,0,0,1,0,0)">
<g
id="edge-1"
fill="none"
marker-start="false"
marker-end="false"
transform="matrix(1,0,0,1,0,0)"
>
<g
id="edge-1"
fill="none"
marker-start="false"
marker-end="false"
stroke="transparent"
stroke-width="3"
/>
<g transform="matrix(1,0,0,1,114.310837,107.155418)">
<path
id="key"
fill="none"
d="M 0,0 L 171.37832988800267,85.68916494400133"
stroke-width="1"
stroke="rgba(153,173,209,1)"
/>
<path
id="key"
fill="none"
d="M 0,0 L 171.37832988800267,85.68916494400133"
stroke-width="3"
stroke="transparent"
/>
</g>
</g>
<g
id="edge-2"
fill="none"
marker-start="false"
marker-end="false"
transform="matrix(1,0,0,1,0,0)"
>
<g
id="edge-2"
fill="none"
marker-start="false"
marker-end="false"
stroke="transparent"
stroke-width="3"
/>
<g transform="matrix(1,0,0,1,211.313705,211.313705)">
<path
id="key"
fill="none"
d="M 77.3725830020305,0 L 0,77.3725830020305"
stroke-width="1"
stroke="rgba(153,173,209,1)"
/>
<path
id="key"
fill="none"
d="M 77.3725830020305,0 L 0,77.3725830020305"
stroke-width="3"
stroke="transparent"
/>
</g>
</g>
</g>
<g id="g-svg-5" fill="none" transform="matrix(1,0,0,1,0,0)">
<g id="node-1" fill="none" transform="matrix(1,0,0,1,100,100)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(34,126,255,1)"
transform="translate(-16,-16)"
cx="16"
cy="16"
stroke-width="0"
stroke="rgba(0,0,0,1)"
r="16"
/>
</g>
<g id="label" fill="none" transform="matrix(1,0,0,1,0,16)">
<g transform="matrix(1,0,0,1,-20.219999,0)">
<path
id="background"
fill="rgba(255,255,255,1)"
d="M 0,0 l 41.44,0 l 0,23 l-41.44 0 z"
opacity="0.5"
stroke-width="0"
width="41.44"
height="23"
/>
</g>
<g transform="matrix(1,0,0,1,0,0)">
<text
id="text"
fill="rgba(0,0,0,0.8509803921568627)"
dominant-baseline="central"
paint-order="stroke"
dx="0.5"
dy="11.5px"
font-size="12"
text-anchor="middle"
>
node-1
</text>
</g>
</g>
</g>
<g id="node-2" fill="none" transform="matrix(1,0,0,1,300,200)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(34,126,255,1)"
transform="translate(-16,-16)"
cx="16"
cy="16"
stroke-width="0"
stroke="rgba(0,0,0,1)"
r="16"
/>
</g>
<g id="label" fill="none" transform="matrix(1,0,0,1,0,16)">
<g transform="matrix(1,0,0,1,-21.059999,0)">
<path
id="background"
fill="rgba(255,255,255,1)"
d="M 0,0 l 43.12,0 l 0,23 l-43.12 0 z"
opacity="0.5"
stroke-width="0"
width="43.12"
height="23"
/>
</g>
<g transform="matrix(1,0,0,1,0,0)">
<text
id="text"
fill="rgba(0,0,0,0.8509803921568627)"
dominant-baseline="central"
paint-order="stroke"
dx="0.5"
dy="11.5px"
font-size="12"
text-anchor="middle"
>
node-2
</text>
</g>
</g>
</g>
<g id="node-3" fill="none" transform="matrix(1,0,0,1,200,300)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(34,126,255,1)"
transform="translate(-16,-16)"
cx="16"
cy="16"
stroke-width="0"
stroke="rgba(0,0,0,1)"
r="16"
/>
</g>
<g id="label" fill="none" transform="matrix(1,0,0,1,0,16)">
<g transform="matrix(1,0,0,1,-21.180000,0)">
<path
id="background"
fill="rgba(255,255,255,1)"
d="M 0,0 l 43.36,0 l 0,23 l-43.36 0 z"
opacity="0.5"
stroke-width="0"
width="43.36"
height="23"
/>
</g>
<g transform="matrix(1,0,0,1,0,0)">
<text
id="text"
fill="rgba(0,0,0,0.8509803921568627)"
dominant-baseline="central"
paint-order="stroke"
dx="0.5"
dy="11.5px"
font-size="12"
text-anchor="middle"
>
node-3
</text>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.8 KiB

View File

@ -0,0 +1,298 @@
<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-7" fill="none" transform="matrix(1,0,0,1,0,0)">
<g
id="combo-2"
fill="none"
transform="matrix(1,0,0,1,27.759853,237.012848)"
>
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(253,253,253,1)"
transform="translate(-16,-16)"
cx="16"
cy="16"
stroke-width="1"
stroke="rgba(153,173,209,1)"
r="16"
stroke-dasharray="5,5"
/>
</g>
<g id="label" fill="none" transform="matrix(1,0,0,1,0,16)">
<g transform="matrix(1,0,0,1,-26.280001,0)">
<path
id="background"
fill="rgba(255,255,255,1)"
d="M 0,0 l 53.56,0 l 0,23 l-53.56 0 z"
opacity="0.75"
stroke-width="0"
width="53.56"
height="23"
/>
</g>
<g transform="matrix(1,0,0,1,0,0)">
<text
id="text"
fill="rgba(0,0,0,1)"
dominant-baseline="central"
paint-order="stroke"
dx="0.5"
dy="11.5px"
font-size="12"
text-anchor="middle"
>
combo-2
</text>
</g>
</g>
</g>
<g
id="combo-1"
fill="none"
transform="matrix(1,0,0,1,250.440002,261.500000)"
>
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(253,253,253,1)"
transform="translate(-105.5257052101804,-105.5257052101804)"
cx="105.5257052101804"
cy="105.5257052101804"
stroke-width="1"
stroke="rgba(153,173,209,1)"
r="105.5257052101804"
/>
</g>
<g id="label" fill="none" transform="matrix(1,0,0,1,0,105.525703)">
<g transform="matrix(1,0,0,1,-25.440001,0)">
<path
id="background"
fill="rgba(255,255,255,1)"
d="M 0,0 l 51.88,0 l 0,23 l-51.88 0 z"
opacity="0.75"
stroke-width="0"
width="51.88"
height="23"
/>
</g>
<g transform="matrix(1,0,0,1,0,0)">
<text
id="text"
fill="rgba(0,0,0,1)"
dominant-baseline="central"
paint-order="stroke"
dx="0.5"
dy="11.5px"
font-size="12"
text-anchor="middle"
>
combo-1
</text>
</g>
</g>
</g>
</g>
<g id="g-svg-6" fill="none" transform="matrix(1,0,0,1,0,0)">
<g
id="edge-1"
fill="none"
marker-start="false"
marker-end="false"
transform="matrix(1,0,0,1,0,0)"
>
<g
id="edge-1"
fill="none"
marker-start="false"
marker-end="false"
stroke="transparent"
stroke-width="3"
/>
<g transform="matrix(1,0,0,1,114.310837,107.155418)">
<path
id="key"
fill="none"
d="M 0,0 L 171.37832988800267,85.68916494400133"
stroke-width="1"
stroke="rgba(153,173,209,1)"
/>
<path
id="key"
fill="none"
d="M 0,0 L 171.37832988800267,85.68916494400133"
stroke-width="3"
stroke="transparent"
/>
</g>
</g>
<g
id="edge-2"
fill="none"
marker-start="false"
marker-end="false"
transform="matrix(1,0,0,1,0,0)"
>
<g
id="edge-2"
fill="none"
marker-start="false"
marker-end="false"
stroke="transparent"
stroke-width="3"
/>
<g transform="matrix(1,0,0,1,211.313705,211.313705)">
<path
id="key"
fill="none"
d="M 77.3725830020305,0 L 0,77.3725830020305"
stroke-width="1"
stroke="rgba(153,173,209,1)"
/>
<path
id="key"
fill="none"
d="M 77.3725830020305,0 L 0,77.3725830020305"
stroke-width="3"
stroke="transparent"
/>
</g>
</g>
</g>
<g id="g-svg-5" fill="none" transform="matrix(1,0,0,1,0,0)">
<g id="node-1" fill="none" transform="matrix(1,0,0,1,100,100)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(34,126,255,1)"
transform="translate(-16,-16)"
cx="16"
cy="16"
stroke-width="0"
stroke="rgba(0,0,0,1)"
r="16"
/>
</g>
<g id="label" fill="none" transform="matrix(1,0,0,1,0,16)">
<g transform="matrix(1,0,0,1,-20.219999,0)">
<path
id="background"
fill="rgba(255,255,255,1)"
d="M 0,0 l 41.44,0 l 0,23 l-41.44 0 z"
opacity="0.5"
stroke-width="0"
width="41.44"
height="23"
/>
</g>
<g transform="matrix(1,0,0,1,0,0)">
<text
id="text"
fill="rgba(0,0,0,0.8509803921568627)"
dominant-baseline="central"
paint-order="stroke"
dx="0.5"
dy="11.5px"
font-size="12"
text-anchor="middle"
>
node-1
</text>
</g>
</g>
</g>
<g id="node-2" fill="none" transform="matrix(1,0,0,1,300,200)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(34,126,255,1)"
transform="translate(-16,-16)"
cx="16"
cy="16"
stroke-width="0"
stroke="rgba(0,0,0,1)"
r="16"
/>
</g>
<g id="label" fill="none" transform="matrix(1,0,0,1,0,16)">
<g transform="matrix(1,0,0,1,-21.059999,0)">
<path
id="background"
fill="rgba(255,255,255,1)"
d="M 0,0 l 43.12,0 l 0,23 l-43.12 0 z"
opacity="0.5"
stroke-width="0"
width="43.12"
height="23"
/>
</g>
<g transform="matrix(1,0,0,1,0,0)">
<text
id="text"
fill="rgba(0,0,0,0.8509803921568627)"
dominant-baseline="central"
paint-order="stroke"
dx="0.5"
dy="11.5px"
font-size="12"
text-anchor="middle"
>
node-2
</text>
</g>
</g>
</g>
<g id="node-3" fill="none" transform="matrix(1,0,0,1,200,300)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(34,126,255,1)"
transform="translate(-16,-16)"
cx="16"
cy="16"
stroke-width="0"
stroke="rgba(0,0,0,1)"
r="16"
/>
</g>
<g id="label" fill="none" transform="matrix(1,0,0,1,0,16)">
<g transform="matrix(1,0,0,1,-21.180000,0)">
<path
id="background"
fill="rgba(255,255,255,1)"
d="M 0,0 l 43.36,0 l 0,23 l-43.36 0 z"
opacity="0.5"
stroke-width="0"
width="43.36"
height="23"
/>
</g>
<g transform="matrix(1,0,0,1,0,0)">
<text
id="text"
fill="rgba(0,0,0,0.8509803921568627)"
dominant-baseline="central"
paint-order="stroke"
dx="0.5"
dy="11.5px"
font-size="12"
text-anchor="middle"
>
node-3
</text>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.8 KiB

View File

@ -0,0 +1,298 @@
<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-7" fill="none" transform="matrix(1,0,0,1,0,0)">
<g
id="combo-2"
fill="none"
transform="matrix(1,0,0,1,407.985840,237.012848)"
>
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(253,253,253,1)"
transform="translate(-16,-16)"
cx="16"
cy="16"
stroke-width="1"
stroke="rgba(153,173,209,1)"
r="16"
stroke-dasharray="5,5"
/>
</g>
<g id="label" fill="none" transform="matrix(1,0,0,1,0,16)">
<g transform="matrix(1,0,0,1,-26.280001,0)">
<path
id="background"
fill="rgba(255,255,255,1)"
d="M 0,0 l 53.56,0 l 0,23 l-53.56 0 z"
opacity="0.75"
stroke-width="0"
width="53.56"
height="23"
/>
</g>
<g transform="matrix(1,0,0,1,0,0)">
<text
id="text"
fill="rgba(0,0,0,1)"
dominant-baseline="central"
paint-order="stroke"
dx="0.5"
dy="11.5px"
font-size="12"
text-anchor="middle"
>
combo-2
</text>
</g>
</g>
</g>
<g
id="combo-1"
fill="none"
transform="matrix(1,0,0,1,250.440002,261.500000)"
>
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(253,253,253,1)"
transform="translate(-105.5257052101804,-105.5257052101804)"
cx="105.5257052101804"
cy="105.5257052101804"
stroke-width="1"
stroke="rgba(153,173,209,1)"
r="105.5257052101804"
/>
</g>
<g id="label" fill="none" transform="matrix(1,0,0,1,0,105.525703)">
<g transform="matrix(1,0,0,1,-25.440001,0)">
<path
id="background"
fill="rgba(255,255,255,1)"
d="M 0,0 l 51.88,0 l 0,23 l-51.88 0 z"
opacity="0.75"
stroke-width="0"
width="51.88"
height="23"
/>
</g>
<g transform="matrix(1,0,0,1,0,0)">
<text
id="text"
fill="rgba(0,0,0,1)"
dominant-baseline="central"
paint-order="stroke"
dx="0.5"
dy="11.5px"
font-size="12"
text-anchor="middle"
>
combo-1
</text>
</g>
</g>
</g>
</g>
<g id="g-svg-6" fill="none" transform="matrix(1,0,0,1,0,0)">
<g
id="edge-1"
fill="none"
marker-start="false"
marker-end="false"
transform="matrix(1,0,0,1,0,0)"
>
<g
id="edge-1"
fill="none"
marker-start="false"
marker-end="false"
stroke="transparent"
stroke-width="3"
/>
<g transform="matrix(1,0,0,1,114.310837,107.155418)">
<path
id="key"
fill="none"
d="M 0,0 L 171.37832988800267,85.68916494400133"
stroke-width="1"
stroke="rgba(153,173,209,1)"
/>
<path
id="key"
fill="none"
d="M 0,0 L 171.37832988800267,85.68916494400133"
stroke-width="3"
stroke="transparent"
/>
</g>
</g>
<g
id="edge-2"
fill="none"
marker-start="false"
marker-end="false"
transform="matrix(1,0,0,1,0,0)"
>
<g
id="edge-2"
fill="none"
marker-start="false"
marker-end="false"
stroke="transparent"
stroke-width="3"
/>
<g transform="matrix(1,0,0,1,211.313705,211.313705)">
<path
id="key"
fill="none"
d="M 77.3725830020305,0 L 0,77.3725830020305"
stroke-width="1"
stroke="rgba(153,173,209,1)"
/>
<path
id="key"
fill="none"
d="M 77.3725830020305,0 L 0,77.3725830020305"
stroke-width="3"
stroke="transparent"
/>
</g>
</g>
</g>
<g id="g-svg-5" fill="none" transform="matrix(1,0,0,1,0,0)">
<g id="node-1" fill="none" transform="matrix(1,0,0,1,100,100)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(34,126,255,1)"
transform="translate(-16,-16)"
cx="16"
cy="16"
stroke-width="0"
stroke="rgba(0,0,0,1)"
r="16"
/>
</g>
<g id="label" fill="none" transform="matrix(1,0,0,1,0,16)">
<g transform="matrix(1,0,0,1,-20.219999,0)">
<path
id="background"
fill="rgba(255,255,255,1)"
d="M 0,0 l 41.44,0 l 0,23 l-41.44 0 z"
opacity="0.5"
stroke-width="0"
width="41.44"
height="23"
/>
</g>
<g transform="matrix(1,0,0,1,0,0)">
<text
id="text"
fill="rgba(0,0,0,0.8509803921568627)"
dominant-baseline="central"
paint-order="stroke"
dx="0.5"
dy="11.5px"
font-size="12"
text-anchor="middle"
>
node-1
</text>
</g>
</g>
</g>
<g id="node-2" fill="none" transform="matrix(1,0,0,1,300,200)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(34,126,255,1)"
transform="translate(-16,-16)"
cx="16"
cy="16"
stroke-width="0"
stroke="rgba(0,0,0,1)"
r="16"
/>
</g>
<g id="label" fill="none" transform="matrix(1,0,0,1,0,16)">
<g transform="matrix(1,0,0,1,-21.059999,0)">
<path
id="background"
fill="rgba(255,255,255,1)"
d="M 0,0 l 43.12,0 l 0,23 l-43.12 0 z"
opacity="0.5"
stroke-width="0"
width="43.12"
height="23"
/>
</g>
<g transform="matrix(1,0,0,1,0,0)">
<text
id="text"
fill="rgba(0,0,0,0.8509803921568627)"
dominant-baseline="central"
paint-order="stroke"
dx="0.5"
dy="11.5px"
font-size="12"
text-anchor="middle"
>
node-2
</text>
</g>
</g>
</g>
<g id="node-3" fill="none" transform="matrix(1,0,0,1,200,300)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(34,126,255,1)"
transform="translate(-16,-16)"
cx="16"
cy="16"
stroke-width="0"
stroke="rgba(0,0,0,1)"
r="16"
/>
</g>
<g id="label" fill="none" transform="matrix(1,0,0,1,0,16)">
<g transform="matrix(1,0,0,1,-21.180000,0)">
<path
id="background"
fill="rgba(255,255,255,1)"
d="M 0,0 l 43.36,0 l 0,23 l-43.36 0 z"
opacity="0.5"
stroke-width="0"
width="43.36"
height="23"
/>
</g>
<g transform="matrix(1,0,0,1,0,0)">
<text
id="text"
fill="rgba(0,0,0,0.8509803921568627)"
dominant-baseline="central"
paint-order="stroke"
dx="0.5"
dy="11.5px"
font-size="12"
text-anchor="middle"
>
node-3
</text>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.8 KiB

View File

@ -0,0 +1,298 @@
<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-7" fill="none" transform="matrix(1,0,0,1,0,0)">
<g
id="combo-2"
fill="none"
transform="matrix(1,0,0,1,217.872849,46.899853)"
>
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(253,253,253,1)"
transform="translate(-16,-16)"
cx="16"
cy="16"
stroke-width="1"
stroke="rgba(153,173,209,1)"
r="16"
stroke-dasharray="5,5"
/>
</g>
<g id="label" fill="none" transform="matrix(1,0,0,1,0,16)">
<g transform="matrix(1,0,0,1,-26.280001,0)">
<path
id="background"
fill="rgba(255,255,255,1)"
d="M 0,0 l 53.56,0 l 0,23 l-53.56 0 z"
opacity="0.75"
stroke-width="0"
width="53.56"
height="23"
/>
</g>
<g transform="matrix(1,0,0,1,0,0)">
<text
id="text"
fill="rgba(0,0,0,1)"
dominant-baseline="central"
paint-order="stroke"
dx="0.5"
dy="11.5px"
font-size="12"
text-anchor="middle"
>
combo-2
</text>
</g>
</g>
</g>
<g
id="combo-1"
fill="none"
transform="matrix(1,0,0,1,250.440002,261.500000)"
>
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(253,253,253,1)"
transform="translate(-105.5257052101804,-105.5257052101804)"
cx="105.5257052101804"
cy="105.5257052101804"
stroke-width="1"
stroke="rgba(153,173,209,1)"
r="105.5257052101804"
/>
</g>
<g id="label" fill="none" transform="matrix(1,0,0,1,0,105.525703)">
<g transform="matrix(1,0,0,1,-25.440001,0)">
<path
id="background"
fill="rgba(255,255,255,1)"
d="M 0,0 l 51.88,0 l 0,23 l-51.88 0 z"
opacity="0.75"
stroke-width="0"
width="51.88"
height="23"
/>
</g>
<g transform="matrix(1,0,0,1,0,0)">
<text
id="text"
fill="rgba(0,0,0,1)"
dominant-baseline="central"
paint-order="stroke"
dx="0.5"
dy="11.5px"
font-size="12"
text-anchor="middle"
>
combo-1
</text>
</g>
</g>
</g>
</g>
<g id="g-svg-6" fill="none" transform="matrix(1,0,0,1,0,0)">
<g
id="edge-1"
fill="none"
marker-start="false"
marker-end="false"
transform="matrix(1,0,0,1,0,0)"
>
<g
id="edge-1"
fill="none"
marker-start="false"
marker-end="false"
stroke="transparent"
stroke-width="3"
/>
<g transform="matrix(1,0,0,1,114.310837,107.155418)">
<path
id="key"
fill="none"
d="M 0,0 L 171.37832988800267,85.68916494400133"
stroke-width="1"
stroke="rgba(153,173,209,1)"
/>
<path
id="key"
fill="none"
d="M 0,0 L 171.37832988800267,85.68916494400133"
stroke-width="3"
stroke="transparent"
/>
</g>
</g>
<g
id="edge-2"
fill="none"
marker-start="false"
marker-end="false"
transform="matrix(1,0,0,1,0,0)"
>
<g
id="edge-2"
fill="none"
marker-start="false"
marker-end="false"
stroke="transparent"
stroke-width="3"
/>
<g transform="matrix(1,0,0,1,211.313705,211.313705)">
<path
id="key"
fill="none"
d="M 77.3725830020305,0 L 0,77.3725830020305"
stroke-width="1"
stroke="rgba(153,173,209,1)"
/>
<path
id="key"
fill="none"
d="M 77.3725830020305,0 L 0,77.3725830020305"
stroke-width="3"
stroke="transparent"
/>
</g>
</g>
</g>
<g id="g-svg-5" fill="none" transform="matrix(1,0,0,1,0,0)">
<g id="node-1" fill="none" transform="matrix(1,0,0,1,100,100)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(34,126,255,1)"
transform="translate(-16,-16)"
cx="16"
cy="16"
stroke-width="0"
stroke="rgba(0,0,0,1)"
r="16"
/>
</g>
<g id="label" fill="none" transform="matrix(1,0,0,1,0,16)">
<g transform="matrix(1,0,0,1,-20.219999,0)">
<path
id="background"
fill="rgba(255,255,255,1)"
d="M 0,0 l 41.44,0 l 0,23 l-41.44 0 z"
opacity="0.5"
stroke-width="0"
width="41.44"
height="23"
/>
</g>
<g transform="matrix(1,0,0,1,0,0)">
<text
id="text"
fill="rgba(0,0,0,0.8509803921568627)"
dominant-baseline="central"
paint-order="stroke"
dx="0.5"
dy="11.5px"
font-size="12"
text-anchor="middle"
>
node-1
</text>
</g>
</g>
</g>
<g id="node-2" fill="none" transform="matrix(1,0,0,1,300,200)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(34,126,255,1)"
transform="translate(-16,-16)"
cx="16"
cy="16"
stroke-width="0"
stroke="rgba(0,0,0,1)"
r="16"
/>
</g>
<g id="label" fill="none" transform="matrix(1,0,0,1,0,16)">
<g transform="matrix(1,0,0,1,-21.059999,0)">
<path
id="background"
fill="rgba(255,255,255,1)"
d="M 0,0 l 43.12,0 l 0,23 l-43.12 0 z"
opacity="0.5"
stroke-width="0"
width="43.12"
height="23"
/>
</g>
<g transform="matrix(1,0,0,1,0,0)">
<text
id="text"
fill="rgba(0,0,0,0.8509803921568627)"
dominant-baseline="central"
paint-order="stroke"
dx="0.5"
dy="11.5px"
font-size="12"
text-anchor="middle"
>
node-2
</text>
</g>
</g>
</g>
<g id="node-3" fill="none" transform="matrix(1,0,0,1,200,300)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(34,126,255,1)"
transform="translate(-16,-16)"
cx="16"
cy="16"
stroke-width="0"
stroke="rgba(0,0,0,1)"
r="16"
/>
</g>
<g id="label" fill="none" transform="matrix(1,0,0,1,0,16)">
<g transform="matrix(1,0,0,1,-21.180000,0)">
<path
id="background"
fill="rgba(255,255,255,1)"
d="M 0,0 l 43.36,0 l 0,23 l-43.36 0 z"
opacity="0.5"
stroke-width="0"
width="43.36"
height="23"
/>
</g>
<g transform="matrix(1,0,0,1,0,0)">
<text
id="text"
fill="rgba(0,0,0,0.8509803921568627)"
dominant-baseline="central"
paint-order="stroke"
dx="0.5"
dy="11.5px"
font-size="12"
text-anchor="middle"
>
node-3
</text>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.8 KiB

View File

@ -0,0 +1,313 @@
<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-7" fill="none" transform="matrix(1,0,0,1,0,0)">
<g
id="combo-2"
fill="none"
transform="matrix(1,0,0,1,217.872849,46.899853)"
>
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(253,253,253,1)"
transform="translate(-16,-16)"
cx="16"
cy="16"
stroke-width="1"
stroke="rgba(153,173,209,1)"
r="16"
stroke-dasharray="5,5"
/>
</g>
<g id="label" fill="none" transform="matrix(1,0,0,1,0,16)">
<g transform="matrix(1,0,0,1,-26.280001,0)">
<path
id="background"
fill="rgba(255,255,255,1)"
d="M 0,0 l 53.56,0 l 0,23 l-53.56 0 z"
opacity="0.75"
stroke-width="0"
width="53.56"
height="23"
/>
</g>
<g transform="matrix(1,0,0,1,0,0)">
<text
id="text"
fill="rgba(0,0,0,1)"
dominant-baseline="central"
paint-order="stroke"
dx="0.5"
dy="11.5px"
font-size="12"
text-anchor="middle"
>
combo-2
</text>
</g>
</g>
<g id="collapsed-marker" fill="none" transform="matrix(1,0,0,1,0,0)">
<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="12"
>
2
</text>
</g>
</g>
</g>
<g
id="combo-1"
fill="none"
transform="matrix(1,0,0,1,250.440002,261.500000)"
>
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(253,253,253,1)"
transform="translate(-105.5257052101804,-105.5257052101804)"
cx="105.5257052101804"
cy="105.5257052101804"
stroke-width="1"
stroke="rgba(153,173,209,1)"
r="105.5257052101804"
/>
</g>
<g id="label" fill="none" transform="matrix(1,0,0,1,0,105.525703)">
<g transform="matrix(1,0,0,1,-25.440001,0)">
<path
id="background"
fill="rgba(255,255,255,1)"
d="M 0,0 l 51.88,0 l 0,23 l-51.88 0 z"
opacity="0.75"
stroke-width="0"
width="51.88"
height="23"
/>
</g>
<g transform="matrix(1,0,0,1,0,0)">
<text
id="text"
fill="rgba(0,0,0,1)"
dominant-baseline="central"
paint-order="stroke"
dx="0.5"
dy="11.5px"
font-size="12"
text-anchor="middle"
>
combo-1
</text>
</g>
</g>
</g>
</g>
<g id="g-svg-6" fill="none" transform="matrix(1,0,0,1,0,0)">
<g
id="edge-1"
fill="none"
marker-start="false"
marker-end="false"
transform="matrix(1,0,0,1,0,0)"
>
<g
id="edge-1"
fill="none"
marker-start="false"
marker-end="false"
stroke="transparent"
stroke-width="3"
/>
<g transform="matrix(1,0,0,1,114.310837,107.155418)">
<path
id="key"
fill="none"
d="M 0,0 L 171.37832988800267,85.68916494400133"
stroke-width="1"
stroke="rgba(153,173,209,1)"
/>
<path
id="key"
fill="none"
d="M 0,0 L 171.37832988800267,85.68916494400133"
stroke-width="3"
stroke="transparent"
/>
</g>
</g>
<g
id="edge-2"
fill="none"
marker-start="false"
marker-end="false"
transform="matrix(1,0,0,1,0,0)"
>
<g
id="edge-2"
fill="none"
marker-start="false"
marker-end="false"
stroke="transparent"
stroke-width="3"
/>
<g transform="matrix(1,0,0,1,211.313705,211.313705)">
<path
id="key"
fill="none"
d="M 77.3725830020305,0 L 0,77.3725830020305"
stroke-width="1"
stroke="rgba(153,173,209,1)"
/>
<path
id="key"
fill="none"
d="M 77.3725830020305,0 L 0,77.3725830020305"
stroke-width="3"
stroke="transparent"
/>
</g>
</g>
</g>
<g id="g-svg-5" fill="none" transform="matrix(1,0,0,1,0,0)">
<g id="node-1" fill="none" transform="matrix(1,0,0,1,100,100)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(34,126,255,1)"
transform="translate(-16,-16)"
cx="16"
cy="16"
stroke-width="0"
stroke="rgba(0,0,0,1)"
r="16"
/>
</g>
<g id="label" fill="none" transform="matrix(1,0,0,1,0,16)">
<g transform="matrix(1,0,0,1,-20.219999,0)">
<path
id="background"
fill="rgba(255,255,255,1)"
d="M 0,0 l 41.44,0 l 0,23 l-41.44 0 z"
opacity="0.5"
stroke-width="0"
width="41.44"
height="23"
/>
</g>
<g transform="matrix(1,0,0,1,0,0)">
<text
id="text"
fill="rgba(0,0,0,0.8509803921568627)"
dominant-baseline="central"
paint-order="stroke"
dx="0.5"
dy="11.5px"
font-size="12"
text-anchor="middle"
>
node-1
</text>
</g>
</g>
</g>
<g id="node-2" fill="none" transform="matrix(1,0,0,1,300,200)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(34,126,255,1)"
transform="translate(-16,-16)"
cx="16"
cy="16"
stroke-width="0"
stroke="rgba(0,0,0,1)"
r="16"
/>
</g>
<g id="label" fill="none" transform="matrix(1,0,0,1,0,16)">
<g transform="matrix(1,0,0,1,-21.059999,0)">
<path
id="background"
fill="rgba(255,255,255,1)"
d="M 0,0 l 43.12,0 l 0,23 l-43.12 0 z"
opacity="0.5"
stroke-width="0"
width="43.12"
height="23"
/>
</g>
<g transform="matrix(1,0,0,1,0,0)">
<text
id="text"
fill="rgba(0,0,0,0.8509803921568627)"
dominant-baseline="central"
paint-order="stroke"
dx="0.5"
dy="11.5px"
font-size="12"
text-anchor="middle"
>
node-2
</text>
</g>
</g>
</g>
<g id="node-3" fill="none" transform="matrix(1,0,0,1,200,300)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(34,126,255,1)"
transform="translate(-16,-16)"
cx="16"
cy="16"
stroke-width="0"
stroke="rgba(0,0,0,1)"
r="16"
/>
</g>
<g id="label" fill="none" transform="matrix(1,0,0,1,0,16)">
<g transform="matrix(1,0,0,1,-21.180000,0)">
<path
id="background"
fill="rgba(255,255,255,1)"
d="M 0,0 l 43.36,0 l 0,23 l-43.36 0 z"
opacity="0.5"
stroke-width="0"
width="43.36"
height="23"
/>
</g>
<g transform="matrix(1,0,0,1,0,0)">
<text
id="text"
fill="rgba(0,0,0,0.8509803921568627)"
dominant-baseline="central"
paint-order="stroke"
dx="0.5"
dy="11.5px"
font-size="12"
text-anchor="middle"
>
node-3
</text>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 9.3 KiB

View File

@ -0,0 +1,313 @@
<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-7" fill="none" transform="matrix(1,0,0,1,0,0)">
<g
id="combo-2"
fill="none"
transform="matrix(1,0,0,1,217.872849,46.899853)"
>
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(253,253,253,1)"
transform="translate(-16,-16)"
cx="16"
cy="16"
stroke-width="1"
stroke="rgba(153,173,209,1)"
r="16"
stroke-dasharray="5,5"
/>
</g>
<g id="label" fill="none" transform="matrix(1,0,0,1,0,16)">
<g transform="matrix(1,0,0,1,-26.280001,0)">
<path
id="background"
fill="rgba(255,255,255,1)"
d="M 0,0 l 53.56,0 l 0,23 l-53.56 0 z"
opacity="0.75"
stroke-width="0"
width="53.56"
height="23"
/>
</g>
<g transform="matrix(1,0,0,1,0,0)">
<text
id="text"
fill="rgba(0,0,0,1)"
dominant-baseline="central"
paint-order="stroke"
dx="0.5"
dy="11.5px"
font-size="12"
text-anchor="middle"
>
combo-2
</text>
</g>
</g>
<g id="collapsed-marker" fill="none" transform="matrix(1,0,0,1,0,0)">
<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="12"
>
4
</text>
</g>
</g>
</g>
<g
id="combo-1"
fill="none"
transform="matrix(1,0,0,1,250.440002,261.500000)"
>
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(253,253,253,1)"
transform="translate(-105.5257052101804,-105.5257052101804)"
cx="105.5257052101804"
cy="105.5257052101804"
stroke-width="1"
stroke="rgba(153,173,209,1)"
r="105.5257052101804"
/>
</g>
<g id="label" fill="none" transform="matrix(1,0,0,1,0,105.525703)">
<g transform="matrix(1,0,0,1,-25.440001,0)">
<path
id="background"
fill="rgba(255,255,255,1)"
d="M 0,0 l 51.88,0 l 0,23 l-51.88 0 z"
opacity="0.75"
stroke-width="0"
width="51.88"
height="23"
/>
</g>
<g transform="matrix(1,0,0,1,0,0)">
<text
id="text"
fill="rgba(0,0,0,1)"
dominant-baseline="central"
paint-order="stroke"
dx="0.5"
dy="11.5px"
font-size="12"
text-anchor="middle"
>
combo-1
</text>
</g>
</g>
</g>
</g>
<g id="g-svg-6" fill="none" transform="matrix(1,0,0,1,0,0)">
<g
id="edge-1"
fill="none"
marker-start="false"
marker-end="false"
transform="matrix(1,0,0,1,0,0)"
>
<g
id="edge-1"
fill="none"
marker-start="false"
marker-end="false"
stroke="transparent"
stroke-width="3"
/>
<g transform="matrix(1,0,0,1,114.310837,107.155418)">
<path
id="key"
fill="none"
d="M 0,0 L 171.37832988800267,85.68916494400133"
stroke-width="1"
stroke="rgba(153,173,209,1)"
/>
<path
id="key"
fill="none"
d="M 0,0 L 171.37832988800267,85.68916494400133"
stroke-width="3"
stroke="transparent"
/>
</g>
</g>
<g
id="edge-2"
fill="none"
marker-start="false"
marker-end="false"
transform="matrix(1,0,0,1,0,0)"
>
<g
id="edge-2"
fill="none"
marker-start="false"
marker-end="false"
stroke="transparent"
stroke-width="3"
/>
<g transform="matrix(1,0,0,1,211.313705,211.313705)">
<path
id="key"
fill="none"
d="M 77.3725830020305,0 L 0,77.3725830020305"
stroke-width="1"
stroke="rgba(153,173,209,1)"
/>
<path
id="key"
fill="none"
d="M 77.3725830020305,0 L 0,77.3725830020305"
stroke-width="3"
stroke="transparent"
/>
</g>
</g>
</g>
<g id="g-svg-5" fill="none" transform="matrix(1,0,0,1,0,0)">
<g id="node-1" fill="none" transform="matrix(1,0,0,1,100,100)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(34,126,255,1)"
transform="translate(-16,-16)"
cx="16"
cy="16"
stroke-width="0"
stroke="rgba(0,0,0,1)"
r="16"
/>
</g>
<g id="label" fill="none" transform="matrix(1,0,0,1,0,16)">
<g transform="matrix(1,0,0,1,-20.219999,0)">
<path
id="background"
fill="rgba(255,255,255,1)"
d="M 0,0 l 41.44,0 l 0,23 l-41.44 0 z"
opacity="0.5"
stroke-width="0"
width="41.44"
height="23"
/>
</g>
<g transform="matrix(1,0,0,1,0,0)">
<text
id="text"
fill="rgba(0,0,0,0.8509803921568627)"
dominant-baseline="central"
paint-order="stroke"
dx="0.5"
dy="11.5px"
font-size="12"
text-anchor="middle"
>
node-1
</text>
</g>
</g>
</g>
<g id="node-2" fill="none" transform="matrix(1,0,0,1,300,200)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(34,126,255,1)"
transform="translate(-16,-16)"
cx="16"
cy="16"
stroke-width="0"
stroke="rgba(0,0,0,1)"
r="16"
/>
</g>
<g id="label" fill="none" transform="matrix(1,0,0,1,0,16)">
<g transform="matrix(1,0,0,1,-21.059999,0)">
<path
id="background"
fill="rgba(255,255,255,1)"
d="M 0,0 l 43.12,0 l 0,23 l-43.12 0 z"
opacity="0.5"
stroke-width="0"
width="43.12"
height="23"
/>
</g>
<g transform="matrix(1,0,0,1,0,0)">
<text
id="text"
fill="rgba(0,0,0,0.8509803921568627)"
dominant-baseline="central"
paint-order="stroke"
dx="0.5"
dy="11.5px"
font-size="12"
text-anchor="middle"
>
node-2
</text>
</g>
</g>
</g>
<g id="node-3" fill="none" transform="matrix(1,0,0,1,200,300)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(34,126,255,1)"
transform="translate(-16,-16)"
cx="16"
cy="16"
stroke-width="0"
stroke="rgba(0,0,0,1)"
r="16"
/>
</g>
<g id="label" fill="none" transform="matrix(1,0,0,1,0,16)">
<g transform="matrix(1,0,0,1,-21.180000,0)">
<path
id="background"
fill="rgba(255,255,255,1)"
d="M 0,0 l 43.36,0 l 0,23 l-43.36 0 z"
opacity="0.5"
stroke-width="0"
width="43.36"
height="23"
/>
</g>
<g transform="matrix(1,0,0,1,0,0)">
<text
id="text"
fill="rgba(0,0,0,0.8509803921568627)"
dominant-baseline="central"
paint-order="stroke"
dx="0.5"
dy="11.5px"
font-size="12"
text-anchor="middle"
>
node-3
</text>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 9.3 KiB

View File

@ -0,0 +1,313 @@
<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-7" fill="none" transform="matrix(1,0,0,1,0,0)">
<g
id="combo-2"
fill="none"
transform="matrix(1,0,0,1,217.872849,46.899853)"
>
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(253,253,253,1)"
transform="translate(-16,-16)"
cx="16"
cy="16"
stroke-width="1"
stroke="rgba(153,173,209,1)"
r="16"
stroke-dasharray="5,5"
/>
</g>
<g id="label" fill="none" transform="matrix(1,0,0,1,0,16)">
<g transform="matrix(1,0,0,1,-26.280001,0)">
<path
id="background"
fill="rgba(255,255,255,1)"
d="M 0,0 l 53.56,0 l 0,23 l-53.56 0 z"
opacity="0.75"
stroke-width="0"
width="53.56"
height="23"
/>
</g>
<g transform="matrix(1,0,0,1,0,0)">
<text
id="text"
fill="rgba(0,0,0,1)"
dominant-baseline="central"
paint-order="stroke"
dx="0.5"
dy="11.5px"
font-size="12"
text-anchor="middle"
>
combo-2
</text>
</g>
</g>
<g id="collapsed-marker" fill="none" transform="matrix(1,0,0,1,0,0)">
<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="12"
>
3
</text>
</g>
</g>
</g>
<g
id="combo-1"
fill="none"
transform="matrix(1,0,0,1,250.440002,261.500000)"
>
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(253,253,253,1)"
transform="translate(-105.5257052101804,-105.5257052101804)"
cx="105.5257052101804"
cy="105.5257052101804"
stroke-width="1"
stroke="rgba(153,173,209,1)"
r="105.5257052101804"
/>
</g>
<g id="label" fill="none" transform="matrix(1,0,0,1,0,105.525703)">
<g transform="matrix(1,0,0,1,-25.440001,0)">
<path
id="background"
fill="rgba(255,255,255,1)"
d="M 0,0 l 51.88,0 l 0,23 l-51.88 0 z"
opacity="0.75"
stroke-width="0"
width="51.88"
height="23"
/>
</g>
<g transform="matrix(1,0,0,1,0,0)">
<text
id="text"
fill="rgba(0,0,0,1)"
dominant-baseline="central"
paint-order="stroke"
dx="0.5"
dy="11.5px"
font-size="12"
text-anchor="middle"
>
combo-1
</text>
</g>
</g>
</g>
</g>
<g id="g-svg-6" fill="none" transform="matrix(1,0,0,1,0,0)">
<g
id="edge-1"
fill="none"
marker-start="false"
marker-end="false"
transform="matrix(1,0,0,1,0,0)"
>
<g
id="edge-1"
fill="none"
marker-start="false"
marker-end="false"
stroke="transparent"
stroke-width="3"
/>
<g transform="matrix(1,0,0,1,114.310837,107.155418)">
<path
id="key"
fill="none"
d="M 0,0 L 171.37832988800267,85.68916494400133"
stroke-width="1"
stroke="rgba(153,173,209,1)"
/>
<path
id="key"
fill="none"
d="M 0,0 L 171.37832988800267,85.68916494400133"
stroke-width="3"
stroke="transparent"
/>
</g>
</g>
<g
id="edge-2"
fill="none"
marker-start="false"
marker-end="false"
transform="matrix(1,0,0,1,0,0)"
>
<g
id="edge-2"
fill="none"
marker-start="false"
marker-end="false"
stroke="transparent"
stroke-width="3"
/>
<g transform="matrix(1,0,0,1,211.313705,211.313705)">
<path
id="key"
fill="none"
d="M 77.3725830020305,0 L 0,77.3725830020305"
stroke-width="1"
stroke="rgba(153,173,209,1)"
/>
<path
id="key"
fill="none"
d="M 77.3725830020305,0 L 0,77.3725830020305"
stroke-width="3"
stroke="transparent"
/>
</g>
</g>
</g>
<g id="g-svg-5" fill="none" transform="matrix(1,0,0,1,0,0)">
<g id="node-1" fill="none" transform="matrix(1,0,0,1,100,100)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(34,126,255,1)"
transform="translate(-16,-16)"
cx="16"
cy="16"
stroke-width="0"
stroke="rgba(0,0,0,1)"
r="16"
/>
</g>
<g id="label" fill="none" transform="matrix(1,0,0,1,0,16)">
<g transform="matrix(1,0,0,1,-20.219999,0)">
<path
id="background"
fill="rgba(255,255,255,1)"
d="M 0,0 l 41.44,0 l 0,23 l-41.44 0 z"
opacity="0.5"
stroke-width="0"
width="41.44"
height="23"
/>
</g>
<g transform="matrix(1,0,0,1,0,0)">
<text
id="text"
fill="rgba(0,0,0,0.8509803921568627)"
dominant-baseline="central"
paint-order="stroke"
dx="0.5"
dy="11.5px"
font-size="12"
text-anchor="middle"
>
node-1
</text>
</g>
</g>
</g>
<g id="node-2" fill="none" transform="matrix(1,0,0,1,300,200)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(34,126,255,1)"
transform="translate(-16,-16)"
cx="16"
cy="16"
stroke-width="0"
stroke="rgba(0,0,0,1)"
r="16"
/>
</g>
<g id="label" fill="none" transform="matrix(1,0,0,1,0,16)">
<g transform="matrix(1,0,0,1,-21.059999,0)">
<path
id="background"
fill="rgba(255,255,255,1)"
d="M 0,0 l 43.12,0 l 0,23 l-43.12 0 z"
opacity="0.5"
stroke-width="0"
width="43.12"
height="23"
/>
</g>
<g transform="matrix(1,0,0,1,0,0)">
<text
id="text"
fill="rgba(0,0,0,0.8509803921568627)"
dominant-baseline="central"
paint-order="stroke"
dx="0.5"
dy="11.5px"
font-size="12"
text-anchor="middle"
>
node-2
</text>
</g>
</g>
</g>
<g id="node-3" fill="none" transform="matrix(1,0,0,1,200,300)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(34,126,255,1)"
transform="translate(-16,-16)"
cx="16"
cy="16"
stroke-width="0"
stroke="rgba(0,0,0,1)"
r="16"
/>
</g>
<g id="label" fill="none" transform="matrix(1,0,0,1,0,16)">
<g transform="matrix(1,0,0,1,-21.180000,0)">
<path
id="background"
fill="rgba(255,255,255,1)"
d="M 0,0 l 43.36,0 l 0,23 l-43.36 0 z"
opacity="0.5"
stroke-width="0"
width="43.36"
height="23"
/>
</g>
<g transform="matrix(1,0,0,1,0,0)">
<text
id="text"
fill="rgba(0,0,0,0.8509803921568627)"
dominant-baseline="central"
paint-order="stroke"
dx="0.5"
dy="11.5px"
font-size="12"
text-anchor="middle"
>
node-3
</text>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 9.3 KiB

View File

@ -9,7 +9,22 @@
<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-7" fill="none" transform="matrix(1,0,0,1,0,0)" />
<g id="g-svg-7" fill="none" transform="matrix(1,0,0,1,0,0)">
<g id="combo-1" fill="none" transform="matrix(1,0,0,1,125,150)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(247,251,255,1)"
transform="translate(-36.76955262170047,-36.76955262170047)"
cx="36.76955262170047"
cy="36.76955262170047"
stroke-width="1"
stroke="rgba(153,173,209,1)"
r="36.76955262170047"
/>
</g>
</g>
</g>
<g id="g-svg-6" fill="none" transform="matrix(1,0,0,1,0,0)">
<g
id="node-1-node-2"

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

@ -0,0 +1,93 @@
import { type Graph } from '@/src';
import { comboCircle } from '@@/demo/case';
import { createDemoGraph } from '@@/utils';
describe('combo', () => {
let graph: Graph;
beforeAll(async () => {
graph = await createDemoGraph(comboCircle, { animation: false });
});
it('default status', async () => {
await expect(graph.getCanvas()).toMatchSnapshot(__filename);
});
it('collapse combo', async () => {
const expandCombo = () => {
graph.updateComboData((data) => [
...data,
{
id: 'combo-2',
style: {
collapsed: false,
},
},
]);
graph.render();
};
const collapseCombo = (collapsedOrigin: string) => {
graph.updateComboData((data) => [
...data,
{
id: 'combo-2',
style: {
collapsed: true,
collapsedOrigin,
collapsedMarker: false,
},
},
]);
graph.render();
};
collapseCombo('top');
await expect(graph.getCanvas()).toMatchSnapshot(__filename, '{name}__collapse_top');
expandCombo();
collapseCombo('right');
await expect(graph.getCanvas()).toMatchSnapshot(__filename, '{name}__collapse_right');
collapseCombo('left');
await expect(graph.getCanvas()).toMatchSnapshot(__filename, '{name}__collapse_left');
expandCombo();
collapseCombo('bottom');
await expect(graph.getCanvas()).toMatchSnapshot(__filename, '{name}__collapse_bottom');
expandCombo();
});
it('collapse combo with collapsed marker', async () => {
const expandCombo = () => {
graph.updateComboData((data) => [
...data,
{
id: 'combo-2',
style: {
collapsed: false,
},
},
]);
graph.render();
};
const collapseCombo = (type: string) => {
graph.updateComboData((data) => [
...data,
{
id: 'combo-2',
style: {
collapsed: true,
collapsedOrigin: 'top',
collapsedMarker: true,
collapsedMarkerType: type,
},
},
]);
graph.render();
};
collapseCombo('child-count');
await expect(graph.getCanvas()).toMatchSnapshot(__filename, '{name}__marker_childCount');
expandCombo();
collapseCombo('descendant-count');
await expect(graph.getCanvas()).toMatchSnapshot(__filename, '{name}__marker_descendantCount');
expandCombo();
collapseCombo('node-count');
await expect(graph.getCanvas()).toMatchSnapshot(__filename, '{name}__marker_nodeCount');
});
});

View File

@ -1,5 +1,6 @@
import {
Circle,
CircleCombo,
Cubic,
CubicHorizontal,
CubicVertical,
@ -34,7 +35,9 @@ describe('registry', () => {
'cubic-horizontal': CubicHorizontal,
'cubic-vertical': CubicVertical,
});
expect(getPlugins('combo')).toEqual({});
expect(getPlugins('combo')).toEqual({
circle: CircleCombo,
});
expect(getPlugins('theme')).toEqual({
dark,
light,

View File

@ -132,11 +132,12 @@ describe('ElementController', () => {
const comboStyle = elementController.getElementComputedStyle('combo', 'combo-1');
expect(comboStyle.children[0].id).toEqual('node-3');
expect(omit(comboStyle, ['children'])).toEqual({
...LIGHT_THEME.combo?.style,
color: BUILT_IN_PALETTES.blues[0],
});
expect(Object.keys(comboStyle.children)).toEqual(['node-3']);
});
it('runtime', async () => {
@ -160,7 +161,6 @@ describe('ElementController', () => {
expect(elementController.getNodes().length).toBe(3);
expect(elementController.getEdges().length).toBe(2);
// TODO 目前暂未提供 combo 图形,因此无法渲染 / Currently, combo graphics are not provided, so they cannot be rendered
expect(elementController.getCombos().length).toBe(0);
expect(elementController.getCombos().length).toBe(1);
});
});

View File

@ -0,0 +1,18 @@
import { getXYByAnchor, parseAnchor } from '@/src/utils/anchor';
import { AABB } from '@antv/g';
describe('anchor', () => {
it('parseAnchor', () => {
expect(parseAnchor([0.5, 0.5])).toEqual([0.5, 0.5]);
expect(parseAnchor('0.5 0.5')).toEqual([0.5, 0.5]);
expect(parseAnchor('1.8 1.8')).toEqual([0.5, 0.5]);
});
it('getXYByAnchor', () => {
const bbox = new AABB();
bbox.setMinMax([0, 0, 0], [100, 100, 0]);
expect(getXYByAnchor(bbox, [0.5, 0.5])).toEqual([50, 50]);
expect(getXYByAnchor(bbox, '0.5 0.5')).toEqual([50, 50]);
expect(getXYByAnchor(bbox, [0.25, 0.25])).toEqual([25, 25]);
});
});

View File

@ -1,6 +1,8 @@
import { Circle } from '@/src/elements';
import {
getBBoxHeight,
getBBoxWidth,
getElementsBBox,
getExpandedBBox,
getIncircleRadius,
getNearestPointToPoint,
@ -32,6 +34,25 @@ describe('bbox', () => {
expect(getNodeBBox([10, 10, 0])).toEqual(bbox);
});
it('getElementsBBox', () => {
expect(getElementsBBox([])).toEqual(new AABB());
const node1 = new Circle({
style: {
x: 100,
y: 100,
},
});
const node2 = new Circle({
style: {
x: 200,
y: 200,
},
});
const bbox = new AABB();
bbox.setMinMax([75, 75, 0], [225, 225, 0]);
expect(getElementsBBox([node1, node2])).toEqual(bbox);
});
it('getPointBBox', () => {
const pointBBox = new AABB();
pointBBox.setMinMax([10, 10, 0], [10, 10, 0]);

View File

@ -0,0 +1,41 @@
import { Circle, CircleCombo } from '@/src/elements';
import {
calculateCollapsedOrigin,
getCollapsedMarkerText,
getDescendantCount,
getXYByCollapsedOrigin,
} from '@/src/utils/combo';
describe('combo', () => {
it('calculateCollapsedOrigin', () => {
expect(calculateCollapsedOrigin('top', [100, 100], [200, 200])).toEqual([0.5, 0.25]);
expect(calculateCollapsedOrigin('bottom', [100, 100], [200, 200])).toEqual([0.5, 0.75]);
expect(calculateCollapsedOrigin('left', [100, 100], [200, 200])).toEqual([0.25, 0.5]);
expect(calculateCollapsedOrigin('right', [100, 100], [200, 200])).toEqual([0.75, 0.5]);
expect(calculateCollapsedOrigin('center', [100, 100], [200, 200])).toEqual([0.5, 0.5]);
expect(calculateCollapsedOrigin([0.5, 0.5], [100, 100], [200, 200])).toEqual([0.5, 0.5]);
});
it('getXYByCollapsedOrigin', () => {
expect(getXYByCollapsedOrigin('top', [100, 100], [100, 100], [200, 200])).toEqual([100, 50]);
expect(getXYByCollapsedOrigin('bottom', [100, 100], [100, 100], [200, 200])).toEqual([100, 150]);
expect(getXYByCollapsedOrigin('left', [100, 100], [100, 100], [200, 200])).toEqual([50, 100]);
expect(getXYByCollapsedOrigin('right', [100, 100], [100, 100], [200, 200])).toEqual([150, 100]);
expect(getXYByCollapsedOrigin('center', [100, 100], [100, 100], [200, 200])).toEqual([100, 100]);
expect(getXYByCollapsedOrigin([0.5, 0.5], [100, 100], [100, 100], [200, 200])).toEqual([100, 100]);
});
it('getCollapsedMarkerText', () => {
const children = [new CircleCombo({ style: { children: [new Circle({})] } })];
expect(getCollapsedMarkerText('child-count', children)).toEqual('1');
expect(getCollapsedMarkerText('descendant-count', children)).toEqual('2');
expect(getCollapsedMarkerText('node-count', children)).toEqual('1');
expect(getCollapsedMarkerText(undefined, children)).toEqual('');
});
it('getDescendantCount', () => {
expect(getDescendantCount([new Circle({}), new Circle({})])).toEqual(2);
expect(getDescendantCount([new CircleCombo({ style: { children: [new Circle({})] } })])).toEqual(2);
expect(getDescendantCount([new CircleCombo({ style: { children: [new Circle({})] } })], true)).toEqual(1);
});
});

View File

@ -1,7 +1,9 @@
import { Rect } from '@/src/elements';
import {
getCubicPath,
getCurveControlPoint,
getLabelPositionStyle,
getPolylineLoopControlPoints,
getPolylinePath,
getQuadraticPath,
getRadians,
@ -157,6 +159,17 @@ describe('edge', () => {
true,
),
).toEqual([['M', 0, 10], ['L', 20, 20], ['L', 50, 50], ['L', 100, 100], ['Z']]);
expect(
getPolylinePath(
[
[0, 10],
[20, 20],
[50, 50],
[100, 100],
],
10,
)[1][1],
).toBeCloseTo(13.33);
});
it('getRadians', () => {
@ -166,4 +179,49 @@ describe('edge', () => {
expect(getRadians(bbox).bottom[0]).toBeCloseTo(EIGHTH_PI * 3);
expect(getRadians(bbox).top[0]).toBeCloseTo(-EIGHTH_PI * 5);
});
it('getPolylineLoopControlPoints', () => {
const node = new Rect({ style: { x: 100, y: 100, size: 100 } });
expect(getPolylineLoopControlPoints(node, [150, 100], [150, 100], 10)).toEqual([
[160, 100],
[160, 110],
[150, 110],
]);
expect(getPolylineLoopControlPoints(node, [100, 150], [100, 150], 10)).toEqual([
[100, 160],
[110, 160],
[110, 150],
]);
expect(getPolylineLoopControlPoints(node, [50, 100], [50, 100], 10)).toEqual([
[40, 100],
[40, 110],
[50, 110],
]);
expect(getPolylineLoopControlPoints(node, [100, 50], [100, 50], 10)).toEqual([
[100, 40],
[110, 40],
[110, 50],
]);
expect(getPolylineLoopControlPoints(node, [150, 150], [100, 150], 10)).toEqual([
[160, 150],
[160, 160],
[100, 160],
]);
expect(getPolylineLoopControlPoints(node, [150, 150], [150, 100], 10)).toEqual([
[160, 150],
[160, 100],
]);
expect(getPolylineLoopControlPoints(node, [120, 50], [140, 50], 10)).toEqual([
[120, 40],
[140, 40],
]);
expect(getPolylineLoopControlPoints(node, [150, 120], [150, 140], 10)).toEqual([
[160, 120],
[160, 140],
]);
expect(getPolylineLoopControlPoints(node, [50, 120], [50, 140], 10)).toEqual([
[40, 120],
[40, 140],
]);
});
});

View File

@ -45,6 +45,7 @@ describe('element', () => {
});
it('isSameNode', () => {
expect(isSameNode(node1, undefined!)).toBeFalsy();
expect(isSameNode(node1, node2)).toBeFalsy();
expect(isSameNode(node1, node1)).toBeTruthy();
});

View File

@ -0,0 +1,136 @@
import type { AABB, BaseStyleProps, DisplayObject, DisplayObjectConfig, Group } from '@antv/g';
import { deepMix, isEmpty } from '@antv/util';
import type { BaseComboProps, Position, PrefixObject, STDSize } from '../../types';
import { getElementsBBox, getExpandedBBox } from '../../utils/bbox';
import { getCollapsedMarkerText, getXYByCollapsedOrigin } from '../../utils/combo';
import { getXYByPosition } from '../../utils/element';
import { subStyleProps } from '../../utils/prefix';
import { parseSize } from '../../utils/size';
import type { BaseNodeStyleProps } from '../nodes';
import { BaseNode } from '../nodes';
import { Icon, IconStyleProps } from '../shapes';
export type CollapsedMarkerStyleProps = IconStyleProps & {
/**
* <zh/> childCount descendantCount , node-count
* <en/> Marker type, child-count means the number of child elements, descendant-count means the number of descendant elements, node-count means the number of descendant nodes
*/
type?: 'child-count' | 'descendant-count' | 'node-count';
};
export type BaseComboStyleProps<KeyStyleProps extends BaseStyleProps = BaseStyleProps> = BaseComboProps &
PrefixObject<KeyStyleProps, 'collapsed'> & {
collapsedMarker?: boolean;
} & PrefixObject<CollapsedMarkerStyleProps, 'collapsedMarker'> &
BaseNodeStyleProps<KeyStyleProps>;
export type ParsedBaseComboStyleProps<KeyStyleProps extends BaseStyleProps> = Required<
BaseComboStyleProps<KeyStyleProps>
>;
export abstract class BaseCombo<
KeyShape extends DisplayObject,
KeyStyleProps extends BaseStyleProps = BaseStyleProps,
> extends BaseNode<KeyShape, KeyStyleProps> {
public type = 'combo';
static defaultStyleProps: BaseComboStyleProps = {
size: 0,
collapsed: false,
collapsedSize: 32,
collapsedOrigin: [0.5, 0.5],
padding: 0,
children: [],
collapsedMarker: true,
collapsedMarkerType: 'child-count',
collapsedMarkerFontSize: 12,
collapsedMarkerTextBaseline: 'middle',
collapsedMarkerTextAlign: 'center',
};
constructor(options: DisplayObjectConfig<BaseComboStyleProps<KeyStyleProps>>) {
super(deepMix({}, { style: BaseCombo.defaultStyleProps }, options));
}
/**
* Draw the key shape of combo
*/
protected abstract drawKeyShape(
attributes: ParsedBaseComboStyleProps<KeyStyleProps>,
container: Group,
): KeyShape | undefined;
protected calculatePosition(attributes: ParsedBaseComboStyleProps<KeyStyleProps>): Position {
const { x: comboX, y: comboY, collapsed, collapsedOrigin } = attributes;
if (!isEmpty(comboX) && !isEmpty(comboY)) return [comboX, comboY, 0] as Position;
const contentBBox = this.getContentBBox(attributes);
let position: Position = contentBBox.center;
const computedExpandedSize = this.getExpandedKeySize(attributes);
const computedCollapsedSize = this.getCollapsedKeySize(attributes);
if (collapsed) {
position = getXYByCollapsedOrigin(
collapsedOrigin!,
contentBBox.center,
computedCollapsedSize,
computedExpandedSize,
);
}
return position;
}
protected getKeySize(attributes: ParsedBaseComboStyleProps<KeyStyleProps>): STDSize {
const { size, collapsed, collapsedSize } = attributes;
if (collapsed && !isEmpty(collapsedSize)) return parseSize(collapsedSize);
if (!collapsed && !isEmpty(size)) return parseSize(size);
return collapsed ? this.getCollapsedKeySize(attributes) : this.getExpandedKeySize(attributes);
}
protected abstract getCollapsedKeySize(attributes: ParsedBaseComboStyleProps<KeyStyleProps>): STDSize;
protected abstract getExpandedKeySize(attributes: ParsedBaseComboStyleProps<KeyStyleProps>): STDSize;
protected getContentBBox(attributes: ParsedBaseComboStyleProps<KeyStyleProps>): AABB {
const { children, padding } = attributes;
let childrenBBox = getElementsBBox(children!);
if (padding) {
childrenBBox = getExpandedBBox(childrenBBox, padding);
}
return childrenBBox;
}
protected drawCollapsedMarkerShape(attributes: ParsedBaseComboStyleProps<KeyStyleProps>, container: Group): void {
this.upsert('collapsed-marker', Icon, this.getCollapsedMarkerStyle(attributes), container);
}
protected getCollapsedMarkerStyle(attributes: ParsedBaseComboStyleProps<KeyStyleProps>): IconStyleProps | false {
if (!attributes.collapsed || !attributes.collapsedMarker) return false;
const { type, ...collapsedMarkerStyle } = subStyleProps<CollapsedMarkerStyleProps>(
this.getGraphicStyle(attributes),
'collapsedMarker',
);
const keyShape = this.getKey();
const [x, y] = getXYByPosition(keyShape.getLocalBounds(), 'center');
if (type) {
const text = getCollapsedMarkerText(type, attributes.children!);
return { ...collapsedMarkerStyle, x, y, text };
}
return { ...collapsedMarkerStyle, x, y };
}
public render(attributes: ParsedBaseComboStyleProps<KeyStyleProps>, container: Group = this) {
super.render(attributes, container);
const [x, y] = this.calculatePosition(attributes);
this.style.x = x;
this.style.y = y;
// collapsed marker
this.drawCollapsedMarkerShape(attributes, container);
}
}

View File

@ -0,0 +1,51 @@
import type { DisplayObjectConfig, CircleStyleProps as GCircleStyleProps } from '@antv/g';
import { Circle as GCircle, Group } from '@antv/g';
import type { STDSize } from '../../types';
import { getBBoxHeight, getBBoxWidth } from '../../utils/bbox';
import { subStyleProps } from '../../utils/prefix';
import { parseSize } from '../../utils/size';
import type { BaseComboStyleProps, ParsedBaseComboStyleProps } from './base-combo';
import { BaseCombo } from './base-combo';
type KeyStyleProps = GCircleStyleProps;
export type CircleComboStyleProps = BaseComboStyleProps<KeyStyleProps>;
type ParsedCircleComboStyleProps = ParsedBaseComboStyleProps<KeyStyleProps>;
type CircleComboOptions = DisplayObjectConfig<CircleComboStyleProps>;
export class CircleCombo extends BaseCombo<GCircle, KeyStyleProps> {
constructor(options: CircleComboOptions) {
super(options);
}
protected drawKeyShape(attributes: ParsedCircleComboStyleProps, container: Group): GCircle | undefined {
return this.upsert('key', GCircle, this.getKeyStyle(attributes), container);
}
protected getKeyStyle(attributes: ParsedCircleComboStyleProps): GCircleStyleProps {
const { collapsed } = attributes;
const keyStyle = super.getKeyStyle(attributes);
const collapsedStyle = subStyleProps(keyStyle, 'collapsed');
const [width] = this.getKeySize(attributes);
return {
...keyStyle,
...(collapsed && collapsedStyle),
r: width / 2,
};
}
protected getCollapsedKeySize(attributes: ParsedCircleComboStyleProps): STDSize {
const [collapsedWidth, collapsedHeight] = parseSize(attributes.collapsedSize);
const collapsedR = Math.max(collapsedWidth, collapsedHeight) / 2;
return [collapsedR * 2, collapsedR * 2, 0];
}
protected getExpandedKeySize(attributes: ParsedCircleComboStyleProps): STDSize {
const [expandedWidth, expandedHeight] = parseSize(attributes.size);
const contentBBox = this.getContentBBox(attributes);
const width = expandedWidth || getBBoxWidth(contentBBox);
const height = expandedHeight || getBBoxHeight(contentBBox);
const expandedR = Math.sqrt(width ** 2 + height ** 2) / 2;
return [expandedR * 2, expandedR * 2, 0];
}
}

View File

@ -0,0 +1,5 @@
export { BaseCombo } from './base-combo';
export { CircleCombo } from './circle';
export type { BaseComboStyleProps } from './base-combo';
export type { CircleComboStyleProps } from './circle';

View File

@ -42,6 +42,8 @@ export type BaseEdgeStyleProps = BaseEdgeProps &
type ParsedBaseEdgeStyleProps = Required<BaseEdgeStyleProps>;
export abstract class BaseEdge extends BaseShape<BaseEdgeStyleProps> {
public type = 'edge';
static defaultStyleProps: Partial<BaseEdgeStyleProps> = {
isBillboard: true,
label: true,

View File

@ -1,2 +1,3 @@
export { CircleCombo } from './combos';
export { Cubic, CubicHorizontal, CubicVertical, Line, Polyline, Quadratic } from './edges';
export { Circle, Ellipse, Image, Rect, Star, Triangle } from './nodes';

View File

@ -35,8 +35,8 @@ export type BaseNodeStyleProps<KeyStyleProps extends BaseStyleProps = BaseNodePr
*/
badges?: NodeBadgeStyleProps[];
/**
* <zh/>
* <en/> Background color palette
* <zh/>
* <en/> Badge background color palette
*/
badgePalette?: string[] | CategoricalPalette;
} & PrefixObject<NodeLabelStyleProps, 'label'> &
@ -58,6 +58,8 @@ export type ParsedBaseNodeStyleProps<KeyStyleProps extends BaseStyleProps> = Req
export abstract class BaseNode<KeyShape extends DisplayObject, KeyStyleProps extends BaseStyleProps> extends BaseShape<
BaseNodeStyleProps<KeyStyleProps>
> {
public type = 'node';
static defaultStyleProps: BaseNodeStyleProps = {
x: 0,
y: 0,

View File

@ -16,6 +16,7 @@ type KeyStyleProps = GRectStyleProps;
export class Rect extends BaseNode<GRect, KeyStyleProps> {
static defaultStyleProps: Partial<RectStyleProps> = {
size: [100, 30],
anchor: [0.5, 0.5],
};
constructor(options: DisplayObjectConfig<RectStyleProps>) {
@ -28,7 +29,6 @@ export class Rect extends BaseNode<GRect, KeyStyleProps> {
...(super.getKeyStyle(attributes) as KeyStyleProps),
width,
height,
anchor: [0.5, 0.5], // !!! It cannot be set to default values because G.CustomElement cannot handle it properly.
};
}

View File

@ -2,6 +2,7 @@ import { fade, translate } from '../animations';
import { DragCanvas, ZoomCanvas } from '../behaviors';
import {
Circle,
CircleCombo,
Cubic,
CubicHorizontal,
CubicVertical,
@ -48,7 +49,9 @@ export const BUILT_IN_PLUGINS = {
'zoom-canvas': ZoomCanvas,
'drag-canvas': DragCanvas,
},
combo: {},
combo: {
circle: CircleCombo,
},
edge: {
cubic: Cubic,
line: Line,

View File

@ -3,12 +3,9 @@ import type { STDAnimation } from '../animations/types';
import type { Behavior } from '../behaviors/types';
import type { STDPalette } from '../palettes/types';
import type { Theme } from '../themes/types';
import type { Edge, Node } from '../types';
import type { Combo, Edge, Node } from '../types';
import type { Widget } from '../widgets/types';
// TODO 待使用正式类型定义 / To be used formal type definition
declare type Combo = unknown;
/**
* <zh/>
*

View File

@ -172,9 +172,9 @@ export class ElementController {
const datum = this.getElementData(elementType, [id])?.[0];
if (!datum) return {};
// `data.style` 中一些样式例如 parentId, collapsed, type 并非直接给元素使用,因此需要过滤掉这些字段
// Some styles in `data.style`, such as parentId, collapsed, type, are not directly used by the element, so these fields need to be filtered out
const { parentId, collapsed, type, states, ...style } = datum.style || {};
// `data.style` 中一些样式例如 parentId, type 并非直接给元素使用,因此需要过滤掉这些字段
// Some styles in `data.style`, such as parentId, type, are not directly used by the element, so these fields need to be filtered out
const { parentId, type, states, ...style } = datum.style || {};
return style;
}
@ -415,9 +415,8 @@ export class ElementController {
private getComboChildren(id: ID) {
const { model } = this.context;
return Object.fromEntries(
model.getComboChildrenData(id).map((datum) => [idOf(datum), this.getElement(idOf(datum))]),
);
return model.getComboChildrenData(id).map((datum) => this.getElement(idOf(datum))!);
}
public getElementComputedStyle(elementType: ElementType, id: ID) {
@ -498,7 +497,7 @@ export class ElementController {
const edgesToRemove = dataOf<EdgeData>(EdgeRemoved);
const combosToRemove = dataOf<ComboData>(ComboRemoved);
// 如果更新了节点,需要更新连接的边和所处的 combo
// 如果更新了节点,需要更新连接的边
// If the node is updated, the connected edge and the combo it is in need to be updated
// TODO 待优化,仅考虑影响边更新的属性,如 x, y, size 等
nodesToUpdate
@ -506,9 +505,18 @@ export class ElementController {
.flat()
.forEach((edge) => edgesToUpdate.push(edge));
// 如果操作(新增/更新/移除)了节点或 combo需要更新相对应的 combo
// If nodes or combos are operated (added/updated/removed), the related combo needs to be updated
model
.getComboData(
[...nodesToUpdate, ...nodesToRemove, ...combosToUpdate, ...combosToRemove].reduce((acc, curr) => {
[
...nodesToAdd,
...nodesToUpdate,
...nodesToRemove,
...combosToAdd,
...combosToUpdate,
...combosToRemove,
].reduce((acc, curr) => {
const parentId = curr?.style?.parentId;
if (parentId) acc.push(parentId);
return acc;
@ -570,7 +578,6 @@ export class ElementController {
const Ctor = getPlugin(elementType, shapeType);
if (!Ctor) return () => null;
const shape = this.container[elementType].appendChild(
// @ts-expect-error TODO fix type
new Ctor({
id,
style: {

View File

@ -133,10 +133,10 @@ export const dark: Theme = {
},
combo: {
style: {
collapsedSize: 32,
fill: COMBO_FILL,
haloLineWidth: 12,
haloStrokeOpacity: 0.25,
iconContentType: 'childCount',
iconFill: COMBO_STROKE,
iconFontSize: 12,
labelBackgroundFill: BG_COLOR,
@ -147,8 +147,8 @@ export const dark: Theme = {
labelFontSize: 12,
labelMaxLines: 1,
lineWidth: 1,
padding: [25, 20, 15, 20],
size: 10,
padding: 10,
size: 0,
stroke: COMBO_STROKE,
},
state: {
@ -179,5 +179,15 @@ export const dark: Theme = {
labelOpacity: 0.25,
},
},
animation: {
enter: 'fade',
exit: 'fade',
hide: 'fade',
show: 'fade',
update: [
{ fields: ['cx', 'cy', 'r', 'x', 'y', 'width', 'height'], shape: 'key' },
{ fields: ['x', 'y'], shape: 'label' },
],
},
},
};

View File

@ -132,10 +132,10 @@ export const light: Theme = {
},
combo: {
style: {
collapsedSize: 32,
fill: COMBO_FILL,
haloLineWidth: 12,
haloStrokeOpacity: 0.25,
iconContentType: 'childCount',
iconFill: COMBO_STROKE,
iconFontSize: 12,
labelBackgroundFill: BG_COLOR,
@ -146,8 +146,8 @@ export const light: Theme = {
labelFontSize: 12,
labelMaxLines: 1,
lineWidth: 1,
padding: [25, 20, 15, 20],
size: 10,
padding: 10,
size: 0,
stroke: COMBO_STROKE,
},
state: {
@ -175,5 +175,12 @@ export const light: Theme = {
stroke: COMBO_STROKE_DISABLED,
},
},
animation: {
enter: 'fade',
exit: 'fade',
hide: 'fade',
show: 'fade',
update: [{ fields: ['x', 'y'] }, { fields: ['r', 'width', 'height'], shape: 'key' }],
},
},
};

View File

@ -0,0 +1,5 @@
import type { Vector2, Vector3 } from './vector';
export type Anchor = string | Vector2 | Vector3;
export type STDAnchor = Vector2;

View File

@ -1,7 +1,9 @@
import type { BaseStyleProps, DisplayObject, PathStyleProps } from '@antv/g';
import type { BaseCombo } from '../elements/combos';
import type { BaseEdge } from '../elements/edges';
import type { BaseNode } from '../elements/nodes';
import type { ComboOptions, EdgeOptions, NodeOptions } from '../spec';
import type { Padding } from './padding';
import type { Size } from './size';
export type ElementType = 'node' | 'edge' | 'combo';
@ -12,6 +14,10 @@ export type Node = BaseNode<DisplayObject, BaseStyleProps>;
export type Edge = BaseEdge;
export type Combo = BaseCombo<DisplayObject, BaseStyleProps>;
export type Element = Node | Edge | Combo;
export type BaseNodeProps = BaseStyleProps & {
/**
* <zh/> x
@ -44,6 +50,11 @@ export type BaseNodeProps = BaseStyleProps & {
* @ignore
*/
points?: ([number, number] | [number, number, number])[];
/**
* <zh/> id
* <en/> The id of the parent node/combo
*/
parentId?: string;
};
export type BaseEdgeProps = BaseStyleProps &
@ -77,3 +88,36 @@ export type BaseEdgeProps = BaseStyleProps &
*/
targetPort?: string;
};
export type BaseComboProps = {
/**
* <zh/> Combo
* <en/> The default size of combo when expanded
*/
size?: Size;
/**
* <zh/> Combo
* <en/> Indicates whether combo is collapsed
*/
collapsed?: boolean;
/**
* <zh/> Combo
* <en/> The default size of combo when collapsed
*/
collapsedSize?: Size;
/**
* <zh/> Combo
* <en/> The origin of combo when collapsed
*/
collapsedOrigin?: string | [number, number];
/**
* <zh/> Combo Combo
* <en/> The children of combo, which can be nodes or combos
*/
children?: (Node | Combo)[];
/**
* <zh/> Combo
* <en/> The padding of combo, only effective when expanded
*/
padding?: Padding;
};

View File

@ -0,0 +1,35 @@
import type { AABB } from '@antv/g';
import type { Position } from '../types';
import type { Anchor, STDAnchor } from '../types/anchor';
import { isBetween } from './math';
/**
* <zh/>
*
* <en/> Parse the origin/anchor
* @param anchor - <zh/> | <en/> Anchor
* @returns <zh/> | <en/> Standard anchor
*/
export function parseAnchor(anchor: Anchor): STDAnchor {
const parsedAnchor = (
typeof anchor === 'string' ? anchor.split(' ').map((v) => parseFloat(v)) : anchor.slice(0, 2)
) as [number, number];
if (!isBetween(parsedAnchor[0], 0, 1) || !isBetween(parsedAnchor[1], 0, 1)) {
return [0.5, 0.5];
}
return parsedAnchor;
}
/**
* <zh/> Canvas
*
* <en/> Get the position of the anchor in the Canvas coordinate system
* @param bbox - <zh/> | <en/> Bounding box
* @param anchor - <zh/> | <en/> Anchor
* @returns <zh/> | <en/> The position on the canvas
*/
export function getXYByAnchor(bbox: AABB, anchor: Anchor): Position {
const [anchorX, anchorY] = parseAnchor(anchor);
const { min, max } = bbox;
return [min[0] + anchorX * (max[0] - min[0]), min[1] + anchorY * (max[1] - min[1])];
}

View File

@ -1,7 +1,7 @@
import { AABB } from '@antv/g';
import { clone } from '@antv/util';
import { TriangleDirection } from '../elements/nodes/triangle';
import type { Node, Padding, Point } from '../types';
import type { Element, Node, Padding, Point } from '../types';
import { isPoint } from './is';
import { isBetween } from './math';
import { parsePadding } from './padding';
@ -41,6 +41,27 @@ export function getNodeBBox(node: Point | Node, padding?: Padding): AABB {
return padding ? getExpandedBBox(bbox, padding) : bbox;
}
/**
* <zh/>
*
* <en/> Get the union bounding box of multiple elements
* @param elements - <zh/> | <en/> Array of elements
* @returns <zh/> | <en/> Bounding box
*/
export function getElementsBBox(elements: Element[]): AABB {
let resBBox: AABB = new AABB(); // Initialize resBBox with an empty AABB object
if (!elements.length) return resBBox;
elements.forEach((element, i) => {
const bbox = element.getBounds();
if (i === 0) resBBox = bbox;
else resBBox = union(resBBox, bbox);
});
return resBBox;
}
/**
* <zh/>
*

View File

@ -0,0 +1,99 @@
import { AABB } from '@antv/g';
import type { CollapsedMarkerStyleProps } from '../elements/combos/base-combo';
import type { Combo, Node, Point, Position, Size } from '../types';
import { getXYByAnchor } from './anchor';
import { isNode } from './element';
import { parseSize } from './size';
/**
* <zh/> Combo
*
* <en/> Calculate the relative position of the origin after the Combo is collapsed
* @param collapsedOrigin - <zh/> | <en/> origin when collapsed
* @param collapsedSize - <zh/> | <en/> folding size
* @param expandedSize - <zh/> | <en/> expanded size
* @returns <zh/> | <en/> origin after folding
*/
export function calculateCollapsedOrigin(
collapsedOrigin: string | [number, number],
collapsedSize: Size,
expandedSize: Size,
): Position {
if (Array.isArray(collapsedOrigin)) return collapsedOrigin;
const [expandedWidth, expandedHeight] = parseSize(expandedSize);
const [collapsedWidth, collapsedHeight] = parseSize(collapsedSize);
const map: Record<string, [number, number]> = {
top: [0.5, collapsedHeight / 2 / expandedHeight],
bottom: [0.5, 1 - collapsedHeight / 2 / expandedHeight],
left: [collapsedWidth / 2 / expandedWidth, 0.5],
right: [1 - collapsedWidth / 2 / expandedWidth, 0.5],
center: [0.5, 0.5],
};
return map[collapsedOrigin] || map.center;
}
/**
* <zh/> Combo
*
* <en/> Calculate the actual position of the origin after the Combo is collapsed
* @param collapsedOrigin - <zh/> | <en/> origin when collapsed
* @param center - <zh/> | <en/> center
* @param collapsedSize - <zh/> | <en/> folding size
* @param expandedSize - <zh/> | <en/> expanded size
* @returns <zh/> | <en/> actual position of the origin
*/
export function getXYByCollapsedOrigin(
collapsedOrigin: string | [number, number],
center: Point,
collapsedSize: Size,
expandedSize: Size,
): Position {
const origin = calculateCollapsedOrigin(collapsedOrigin, collapsedSize, expandedSize);
const [expandedWidth, expandedHeight] = parseSize(expandedSize);
const expandedBBox = new AABB();
expandedBBox.setMinMax(
[center[0] - expandedWidth / 2, center[1] - expandedHeight / 2, 0],
[center[0] + expandedWidth / 2, center[1] + expandedHeight / 2, 0],
);
return getXYByAnchor(expandedBBox, origin);
}
/**
* <zh/>
*
* <en/> Get the text of the collapsed marker
* @param type - <zh/> | <en/> type of the collapsed marker
* @param children - <zh/> | <en/> children
* @returns <zh/> | <en/> text of the collapsed marker
*/
export function getCollapsedMarkerText(type: CollapsedMarkerStyleProps['type'], children: (Node | Combo)[]) {
if (type === 'descendant-count') {
return getDescendantCount(children).toString();
} else if (type === 'child-count') {
return children.length.toString();
} else if (type === 'node-count') {
return getDescendantCount(children, true).toString();
}
return '';
}
/**
* <zh/>
*
* <en/> Get the number of descendant nodes
* @param children - <zh/> | <en/> children
* @param onlyNode - <zh/> Node | <en/> Whether to only count the descendant nodes of the Node type
* @returns <zh/> | <en/> number of descendant nodes
*/
export function getDescendantCount(children: (Node | Combo)[], onlyNode = false): number {
let count = 0;
for (const child of children) {
if (!onlyNode || isNode(child)) {
count += 1;
}
if ('children' in child.attributes) {
count += getDescendantCount(child.attributes.children as (Node | Combo)[], onlyNode);
}
}
return count;
}

View File

@ -451,7 +451,7 @@ export function getPolylineLoopPath(
* @param dist - <zh/> keyShape | <en/> The distance from the edge of the node keyShape to the top of the self-loop
* @returns <zh/> | <en/> Control points
*/
function getPolylineLoopControlPoints(node: Node, sourcePoint: Point, targetPoint: Point, dist: number) {
export function getPolylineLoopControlPoints(node: Node, sourcePoint: Point, targetPoint: Point, dist: number) {
const controlPoints: Point[] = [];
const bbox = getNodeBBox(node);

View File

@ -17,7 +17,7 @@ import { findNearestPoints, getEllipseIntersectPoint } from './point';
* @returns <zh/> BaseNode | <en/> whether the instance is BaseNode
*/
export function isNode(shape: DisplayObject): shape is Node {
return shape instanceof BaseNode;
return shape instanceof BaseNode && shape.type === 'node';
}
/**