refactor: adjust element controller (#5513)

* refactor(runtime): element split draw to draw and computeDrawData

* feat(utils): add parentIdOf util

* refactor(runtime): element controller use data flow

* refactor(utils): toGraphlibData does a shallow copy

* refactor: base-shape set visibility, setVisibility support keep raw attributes

* refactor(runtime): refactor element controller, merge runtime style into model

* test: update test case and snapshots

* test(elements): add test case and snapshots

* chore(site): update site demo

* refactor(runtime): add syntax sugar for runtime api

* test: remove layout case demo

* refactor(animation): recover to enter, exit animation stage

* refactor(runtime): add frontElement, backElement api

* refactor(runtime): add showElement, hideElement API

* refactor(behaviors): drag-node adpat new api
This commit is contained in:
Aaron 2024-03-12 10:49:12 +08:00 committed by GitHub
parent 9646b6fc4d
commit d0c92d26b2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
86 changed files with 5798 additions and 919 deletions

View File

@ -0,0 +1,55 @@
import { Graph } from '@/src';
import type { STDTestCase } from '../types';
export const elementPosition: STDTestCase = async (context) => {
const graph = new Graph({
...context,
data: {
nodes: [
{ id: 'node-1', style: { x: 50, y: 50 } },
{ id: 'node-2', style: { x: 200, y: 50 } },
{ id: 'node-3', style: { x: 125, y: 150 } },
],
edges: [
{ id: 'edge-1', source: 'node-1', target: 'node-2' },
{ id: 'edge-2', source: 'node-2', target: 'node-3' },
{ id: 'edge-3', source: 'node-3', target: 'node-1' },
],
},
node: {
style: {
size: 20,
},
},
});
await graph.render();
elementPosition.form = (panel) => {
const config = {
element: 'node-1',
x: 50,
y: 50,
};
const translate = () => {
graph.translateElementTo(
{
[config.element]: [config.x, config.y],
},
false,
);
};
const element = panel.add(config, 'element', ['node-1', 'node-2', 'node-3']).onChange((id: string) => {
const position = graph.getElementPosition(id);
x.setValue(position[0]);
y.setValue(position[1]);
});
const x = panel.add(config, 'x', 0, 300, 1).onChange(translate);
const y = panel.add(config, 'y', 0, 300, 1).onChange(translate);
return [element, x, y];
};
return graph;
};

View File

@ -0,0 +1,87 @@
import { Graph } from '@/src';
import type { STDTestCase } from '../types';
export const elementState: STDTestCase = async (context) => {
const graph = new Graph({
...context,
data: {
nodes: [
{ id: 'node-1', style: { x: 50, y: 50, states: ['active', 'selected'] } },
{ id: 'node-2', style: { x: 200, y: 50 } },
{ id: 'node-3', style: { x: 125, y: 150, states: ['active'] } },
],
edges: [
{ id: 'edge-1', source: 'node-1', target: 'node-2', style: { states: ['active'] } },
{ id: 'edge-2', source: 'node-2', target: 'node-3' },
{ id: 'edge-3', source: 'node-3', target: 'node-1' },
],
},
theme: 'light',
node: {
style: {
lineWidth: 1,
size: 20,
},
state: {
active: {
lineWidth: 2,
},
selected: {
fill: 'pink',
},
},
animation: {
update: [{ fields: ['lineWidth', 'fill'] }],
},
},
edge: {
style: {
lineWidth: 1,
},
state: {
active: {
lineWidth: 2,
stroke: 'pink',
},
},
animation: {
update: [
{
fields: ['lineWidth', 'stroke'],
},
],
},
},
});
await graph.render();
elementState.form = (panel) => {
const config = {
element: 'node-1',
active: true,
selected: true,
};
const setState = () => {
const state: string[] = [];
if (config.active) state.push('active');
if (config.selected) state.push('selected');
graph.setElementState({ [config.element]: state });
};
const element = panel
.add(config, 'element', ['node-1', 'node-2', 'node-3', 'edge-1', 'edge-2', 'edge-3'])
.onChange((id: string) => {
const states = graph.getElementState(id);
selected.setValue(states.includes('selected'));
active.setValue(states.includes('active'));
});
const active = panel.add(config, 'active').onChange(setState);
const selected = panel.add(config, 'selected').onChange(setState);
return [element, active, selected];
};
return graph;
};

View File

@ -0,0 +1,44 @@
import { Graph } from '@/src';
import type { STDTestCase } from '../types';
export const elementVisibility: STDTestCase = async (context) => {
const graph = new Graph({
...context,
data: {
nodes: [
{ id: 'node-1', style: { x: 50, y: 50 } },
{ id: 'node-2', style: { x: 200, y: 50 } },
{ id: 'node-3', style: { x: 125, y: 150 } },
{ id: 'node-4', style: { x: 125, y: 200, visibility: 'hidden' } },
],
edges: [
{ id: 'edge-1', source: 'node-1', target: 'node-2' },
{ id: 'edge-2', source: 'node-2', target: 'node-3' },
{ id: 'edge-3', source: 'node-3', target: 'node-1' },
],
},
theme: 'light',
node: { style: { size: 20, labelText: (d: any) => d.id.at(-1) } },
edge: { style: { endArrow: true, labelText: (d: any) => d.id } },
});
await graph.render();
elementVisibility.form = (panel) => {
const config = {
element: 'node-1',
visible: true,
};
const element = panel
.add(config, 'element', ['node-1', 'node-2', 'node-3', 'node-4', 'edge-1', 'edge-2', 'edge-3'])
.onChange((id: string) => {
visible.setValue(graph.getElementVisibility(id) !== 'hidden');
});
const visible = panel.add(config, 'visible').onChange((value: boolean) => {
value ? graph.showElement(config.element) : graph.hideElement(config.element);
});
return [element, visible];
};
return graph;
};

View File

@ -4,6 +4,9 @@ export * from './behavior-zoom-canvas';
export * from './combo';
export * from './common-graph';
export * from './element-change-type';
export * from './element-position';
export * from './element-state';
export * from './element-visibility';
export * from './graph-event';
export * from './layout-grid';
export * from './layout-mds';

View File

@ -28,8 +28,8 @@ export const controllerElementVisibility: STDTestCase = async (context) => {
const graph = new Graph(options);
await graph.render();
const hide = () => graph.setElementVisibility(['node-3', 'node-2-node-3', 'node-3-node-1'], 'hidden');
const show = () => graph.setElementVisibility(['node-3', 'node-2-node-3', 'node-3-node-1'], 'visible');
const show = () => graph.showElement(['node-3', 'node-2-node-3', 'node-3-node-1']);
const hide = () => graph.hideElement(['node-3', 'node-2-node-3', 'node-3-node-1']);
controllerElementVisibility.form = (panel) => [panel.add({ show }, 'show'), panel.add({ hide }, 'hide')];

View File

@ -22,13 +22,13 @@ export const controllerElementZIndex: STDTestCase = async (context) => {
const graph = new Graph(options);
await graph.render();
const front = () => graph.setElementZIndex('node-2', 'front');
const back = () => graph.setElementZIndex('node-2', 'back');
const front = () => graph.frontElement('node-2');
const back = () => graph.backElement('node-2');
const to = (zIndex: number) => graph.setElementZIndex('node-2', zIndex);
controllerElementZIndex.form = (panel) => [
panel.add({ front }, 'front'),
panel.add({ back }, 'back'),
panel.add({ front }, 'front').name('Bring Element To Front'),
panel.add({ back }, 'back').name('Send Element To Back'),
panel.add({ to: 0 }, 'to', -10, 10, 1).onChange((zIndex: number) => to(zIndex)),
];

View File

@ -64,8 +64,10 @@ export const edgeCubicHorizontal: StaticTestCase = async (context) => {
await graph.render();
graph.setElementState('line-active', 'active');
graph.setElementState('line-selected', 'selected');
graph.setElementState('line-highlight', 'highlight');
graph.setElementState('line-inactive', 'inactive');
graph.setElementState({
'line-active': 'active',
'line-selected': 'selected',
'line-highlight': 'highlight',
'line-inactive': 'inactive',
});
};

View File

@ -64,8 +64,10 @@ export const edgeCubicVertical: StaticTestCase = async (context) => {
await graph.render();
graph.setElementState('line-active', 'active');
graph.setElementState('line-selected', 'selected');
graph.setElementState('line-highlight', 'highlight');
graph.setElementState('line-inactive', 'inactive');
graph.setElementState({
'line-active': 'active',
'line-selected': 'selected',
'line-highlight': 'highlight',
'line-inactive': 'inactive',
});
};

View File

@ -56,8 +56,10 @@ export const edgeCubic: StaticTestCase = async (context) => {
await graph.render();
graph.setElementState('line-active', 'active');
graph.setElementState('line-selected', 'selected');
graph.setElementState('line-highlight', 'highlight');
graph.setElementState('line-inactive', 'inactive');
graph.setElementState({
'line-active': 'active',
'line-selected': 'selected',
'line-highlight': 'highlight',
'line-inactive': 'inactive',
});
};

View File

@ -56,8 +56,10 @@ export const edgeLine: StaticTestCase = async (context) => {
await graph.render();
graph.setElementState('line-active', 'active');
graph.setElementState('line-selected', 'selected');
graph.setElementState('line-highlight', 'highlight');
graph.setElementState('line-inactive', 'inactive');
graph.setElementState({
'line-active': 'active',
'line-selected': 'selected',
'line-highlight': 'highlight',
'line-inactive': 'inactive',
});
};

View File

@ -56,8 +56,10 @@ export const edgeQuadratic: StaticTestCase = async (context) => {
await graph.render();
graph.setElementState('line-active', 'active');
graph.setElementState('line-selected', 'selected');
graph.setElementState('line-highlight', 'highlight');
graph.setElementState('line-inactive', 'inactive');
graph.setElementState({
'line-active': 'active',
'line-selected': 'selected',
'line-highlight': 'highlight',
'line-inactive': 'inactive',
});
};

View File

@ -57,9 +57,11 @@ export const nodeCircle: StaticTestCase = async (context) => {
await graph.render();
graph.setElementState('circle-active', 'active');
graph.setElementState('circle-selected', 'selected');
graph.setElementState('circle-highlight', 'highlight');
graph.setElementState('circle-inactive', 'inactive');
graph.setElementState('circle-disabled', 'disabled');
graph.setElementState({
'circle-active': 'active',
'circle-selected': 'selected',
'circle-highlight': 'highlight',
'circle-inactive': 'inactive',
'circle-disabled': 'disabled',
});
};

View File

@ -54,9 +54,12 @@ export const nodeDiamond: StaticTestCase = async (context) => {
animation,
});
await graph.render();
graph.setElementState('diamond-active', 'active');
graph.setElementState('diamond-selected', 'selected');
graph.setElementState('diamond-highlight', 'highlight');
graph.setElementState('diamond-inactive', 'inactive');
graph.setElementState('diamond-disabled', 'disabled');
graph.setElementState({
'diamond-active': 'active',
'diamond-selected': 'selected',
'diamond-highlight': 'highlight',
'diamond-inactive': 'inactive',
'diamond-disabled': 'disabled',
});
};

View File

@ -57,9 +57,11 @@ export const nodeEllipse: StaticTestCase = async (context) => {
await graph.render();
graph.setElementState('ellipse-active', 'active');
graph.setElementState('ellipse-selected', 'selected');
graph.setElementState('ellipse-highlight', 'highlight');
graph.setElementState('ellipse-inactive', 'inactive');
graph.setElementState('ellipse-disabled', 'disabled');
graph.setElementState({
'ellipse-active': 'active',
'ellipse-selected': 'selected',
'ellipse-highlight': 'highlight',
'ellipse-inactive': 'inactive',
'ellipse-disabled': 'disabled',
});
};

View File

@ -60,9 +60,12 @@ export const nodeImage: StaticTestCase = async (context) => {
});
await graph.render();
graph.setElementState('image-active', 'active');
graph.setElementState('image-selected', 'selected');
graph.setElementState('image-highlight', 'highlight');
graph.setElementState('image-inactive', 'inactive');
graph.setElementState('image-disabled', 'disabled');
graph.setElementState({
'image-active': 'active',
'image-selected': 'selected',
'image-highlight': 'highlight',
'image-inactive': 'inactive',
'image-disabled': 'disabled',
});
};

View File

@ -58,9 +58,11 @@ export const nodeRect: StaticTestCase = async (context) => {
await graph.render();
graph.setElementState('rect-active', 'active');
graph.setElementState('rect-selected', 'selected');
graph.setElementState('rect-highlight', 'highlight');
graph.setElementState('rect-inactive', 'inactive');
graph.setElementState('rect-disabled', 'disabled');
graph.setElementState({
'rect-active': 'active',
'rect-selected': 'selected',
'rect-highlight': 'highlight',
'rect-inactive': 'inactive',
'rect-disabled': 'disabled',
});
};

View File

@ -55,9 +55,11 @@ export const nodeStar: StaticTestCase = async (context) => {
await graph.render();
graph.setElementState('star-active', 'active');
graph.setElementState('star-selected', 'selected');
graph.setElementState('star-highlight', 'highlight');
graph.setElementState('star-inactive', 'inactive');
graph.setElementState('star-disabled', 'disabled');
graph.setElementState({
'star-active': 'active',
'star-selected': 'selected',
'star-highlight': 'highlight',
'star-inactive': 'inactive',
'star-disabled': 'disabled',
});
};

View File

@ -52,8 +52,11 @@ export const nodeTriangle: StaticTestCase = async (context) => {
});
await graph.render();
graph.setElementState('triangle-active', 'active');
graph.setElementState('triangle-selected', 'selected');
graph.setElementState('triangle-highlight', 'highlight');
graph.setElementState('triangle-inactive', 'inactive');
graph.setElementState({
'triangle-active': 'active',
'triangle-selected': 'selected',
'triangle-highlight': 'highlight',
'triangle-inactive': 'inactive',
});
};

View File

@ -10,18 +10,18 @@
<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-1" fill="none" transform="matrix(1,0,0,1,200,150)">
<g id="combo-1" fill="none" transform="matrix(1,0,0,1,210,160)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(253,253,253,1)"
transform="translate(-72.80109889280519,-72.80109889280519)"
cx="72.80109889280519"
cy="72.80109889280519"
transform="translate(-85.44003745317531,-85.44003745317531)"
cx="85.44003745317531"
cy="85.44003745317531"
stroke-dasharray="0,0"
stroke-width="1"
stroke="rgba(153,173,209,1)"
r="72.80109889280519"
r="85.44003745317531"
/>
</g>
</g>

Before

Width:  |  Height:  |  Size: 9.4 KiB

After

Width:  |  Height:  |  Size: 9.4 KiB

View File

@ -10,18 +10,18 @@
<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-1" fill="none" transform="matrix(1,0,0,1,200,150)">
<g id="combo-1" fill="none" transform="matrix(1,0,0,1,220,170)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(253,253,253,1)"
transform="translate(-72.80109889280519,-72.80109889280519)"
cx="72.80109889280519"
cy="72.80109889280519"
transform="translate(-98.48857801796105,-98.48857801796105)"
cx="98.48857801796105"
cy="98.48857801796105"
stroke-dasharray="0,0"
stroke-width="1"
stroke="rgba(153,173,209,1)"
r="72.80109889280519"
r="98.48857801796105"
/>
</g>
</g>

Before

Width:  |  Height:  |  Size: 9.6 KiB

After

Width:  |  Height:  |  Size: 9.6 KiB

View File

@ -10,18 +10,18 @@
<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-1" fill="none" transform="matrix(1,0,0,1,200,150)">
<g id="combo-1" fill="none" transform="matrix(1,0,0,1,220,170)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(253,253,253,1)"
transform="translate(-72.80109889280519,-72.80109889280519)"
cx="72.80109889280519"
cy="72.80109889280519"
transform="translate(-98.48857801796105,-98.48857801796105)"
cx="98.48857801796105"
cy="98.48857801796105"
stroke-dasharray="0,0"
stroke-width="1"
stroke="rgba(153,173,209,1)"
r="72.80109889280519"
r="98.48857801796105"
/>
</g>
</g>

Before

Width:  |  Height:  |  Size: 9.8 KiB

After

Width:  |  Height:  |  Size: 9.8 KiB

View File

@ -10,18 +10,18 @@
<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-1" fill="none" transform="matrix(1,0,0,1,200,150)">
<g id="combo-1" fill="none" transform="matrix(1,0,0,1,220,170)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(253,253,253,1)"
transform="translate(-72.80109889280519,-72.80109889280519)"
cx="72.80109889280519"
cy="72.80109889280519"
transform="translate(-98.48857801796105,-98.48857801796105)"
cx="98.48857801796105"
cy="98.48857801796105"
stroke-dasharray="0,0"
stroke-width="1"
stroke="rgba(153,173,209,1)"
r="72.80109889280519"
r="98.48857801796105"
/>
</g>
</g>

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 9.7 KiB

View File

@ -10,18 +10,18 @@
<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-1" fill="none" transform="matrix(1,0,0,1,200,150)">
<g id="combo-1" fill="none" transform="matrix(1,0,0,1,230,180)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(253,253,253,1)"
transform="translate(-72.80109889280519,-72.80109889280519)"
cx="72.80109889280519"
cy="72.80109889280519"
transform="translate(-111.80339887498948,-111.80339887498948)"
cx="111.80339887498948"
cy="111.80339887498948"
stroke-dasharray="0,0"
stroke-width="1"
stroke="rgba(153,173,209,1)"
r="72.80109889280519"
r="111.80339887498948"
/>
</g>
</g>

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -10,18 +10,18 @@
<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-1" fill="none" transform="matrix(1,0,0,1,200,150)">
<g id="combo-1" fill="none" transform="matrix(1,0,0,1,220,170)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(253,253,253,1)"
transform="translate(-72.80109889280519,-72.80109889280519)"
cx="72.80109889280519"
cy="72.80109889280519"
transform="translate(-98.48857801796105,-98.48857801796105)"
cx="98.48857801796105"
cy="98.48857801796105"
stroke-dasharray="0,0"
stroke-width="1"
stroke="rgba(153,173,209,1)"
r="72.80109889280519"
r="98.48857801796105"
/>
</g>
</g>

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -0,0 +1,157 @@
<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="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,60,50)">
<path
id="key"
fill="none"
d="M 0,0 L 130,0"
stroke-width="1"
stroke="rgba(153,173,209,1)"
/>
<path
id="key"
fill="none"
d="M 0,0 L 130,0"
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,131,58)">
<path
id="key"
fill="none"
d="M 63,0 L 0,84"
stroke-width="1"
stroke="rgba(153,173,209,1)"
/>
<path
id="key"
fill="none"
d="M 63,0 L 0,84"
stroke-width="3"
stroke="transparent"
/>
</g>
</g>
<g
id="edge-3"
fill="none"
marker-start="false"
marker-end="false"
transform="matrix(1,0,0,1,0,0)"
>
<g
id="edge-3"
fill="none"
marker-start="false"
marker-end="false"
stroke="transparent"
stroke-width="3"
/>
<g transform="matrix(1,0,0,1,56,58)">
<path
id="key"
fill="none"
d="M 63,84 L 0,0"
stroke-width="1"
stroke="rgba(153,173,209,1)"
/>
<path
id="key"
fill="none"
d="M 63,84 L 0,0"
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,50,50)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(34,126,255,1)"
transform="translate(-10,-10)"
cx="10"
cy="10"
stroke-width="0"
stroke="rgba(0,0,0,1)"
r="10"
/>
</g>
</g>
<g id="node-2" fill="none" transform="matrix(1,0,0,1,200,50)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(34,126,255,1)"
transform="translate(-10,-10)"
cx="10"
cy="10"
stroke-width="0"
stroke="rgba(0,0,0,1)"
r="10"
/>
</g>
</g>
<g id="node-3" fill="none" transform="matrix(1,0,0,1,125,150)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(34,126,255,1)"
transform="translate(-10,-10)"
cx="10"
cy="10"
stroke-width="0"
stroke="rgba(0,0,0,1)"
r="10"
/>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -0,0 +1,157 @@
<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="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,108.320503,55.547001)">
<path
id="key"
fill="none"
d="M 0,38.90599607549541 L 58.358994113243114,0"
stroke-width="1"
stroke="rgba(153,173,209,1)"
/>
<path
id="key"
fill="none"
d="M 0,0 L 130,0"
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,129.472137,58.944271)">
<path
id="key"
fill="none"
d="M 41.055728090000855,0 L 0,82.1114561800017"
stroke-width="1"
stroke="rgba(153,173,209,1)"
/>
<path
id="key"
fill="none"
d="M 63,0 L 0,84"
stroke-width="3"
stroke="transparent"
/>
</g>
</g>
<g
id="edge-3"
fill="none"
marker-start="false"
marker-end="false"
transform="matrix(1,0,0,1,0,0)"
>
<g
id="edge-3"
fill="none"
marker-start="false"
marker-end="false"
stroke="transparent"
stroke-width="3"
/>
<g transform="matrix(1,0,0,1,104.472137,108.944275)">
<path
id="key"
fill="none"
d="M 16.05572809000084,32.111456180001696 L 0,0"
stroke-width="1"
stroke="rgba(153,173,209,1)"
/>
<path
id="key"
fill="none"
d="M 63,84 L 0,0"
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(-10,-10)"
cx="10"
cy="10"
stroke-width="0"
stroke="rgba(0,0,0,1)"
r="10"
/>
</g>
</g>
<g id="node-2" fill="none" transform="matrix(1,0,0,1,175,50)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(34,126,255,1)"
transform="translate(-10,-10)"
cx="10"
cy="10"
stroke-width="0"
stroke="rgba(0,0,0,1)"
r="10"
/>
</g>
</g>
<g id="node-3" fill="none" transform="matrix(1,0,0,1,125,150)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(34,126,255,1)"
transform="translate(-10,-10)"
cx="10"
cy="10"
stroke-width="0"
stroke="rgba(0,0,0,1)"
r="10"
/>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

@ -0,0 +1,157 @@
<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="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,85,50)">
<path
id="key"
fill="none"
d="M 0,0 L 80,0"
stroke-width="1"
stroke="rgba(153,173,209,1)"
/>
<path
id="key"
fill="none"
d="M 0,0 L 130,0"
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,129.472137,58.944271)">
<path
id="key"
fill="none"
d="M 41.055728090000855,0 L 0,82.1114561800017"
stroke-width="1"
stroke="rgba(153,173,209,1)"
/>
<path
id="key"
fill="none"
d="M 63,0 L 0,84"
stroke-width="3"
stroke="transparent"
/>
</g>
</g>
<g
id="edge-3"
fill="none"
marker-start="false"
marker-end="false"
transform="matrix(1,0,0,1,0,0)"
>
<g
id="edge-3"
fill="none"
marker-start="false"
marker-end="false"
stroke="transparent"
stroke-width="3"
/>
<g transform="matrix(1,0,0,1,79.472137,58.944271)">
<path
id="key"
fill="none"
d="M 41.05572809000084,82.1114561800017 L 0,0"
stroke-width="1"
stroke="rgba(153,173,209,1)"
/>
<path
id="key"
fill="none"
d="M 63,84 L 0,0"
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,75,50)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(34,126,255,1)"
transform="translate(-10,-10)"
cx="10"
cy="10"
stroke-width="0"
stroke="rgba(0,0,0,1)"
r="10"
/>
</g>
</g>
<g id="node-2" fill="none" transform="matrix(1,0,0,1,175,50)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(34,126,255,1)"
transform="translate(-10,-10)"
cx="10"
cy="10"
stroke-width="0"
stroke="rgba(0,0,0,1)"
r="10"
/>
</g>
</g>
<g id="node-3" fill="none" transform="matrix(1,0,0,1,125,150)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(34,126,255,1)"
transform="translate(-10,-10)"
cx="10"
cy="10"
stroke-width="0"
stroke="rgba(0,0,0,1)"
r="10"
/>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

@ -0,0 +1,157 @@
<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="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,60,50)">
<path
id="key"
fill="none"
d="M 0,0 L 105,0"
stroke-width="1"
stroke="rgba(153,173,209,1)"
/>
<path
id="key"
fill="none"
d="M 0,0 L 130,0"
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,129.472137,58.944271)">
<path
id="key"
fill="none"
d="M 41.055728090000855,0 L 0,82.1114561800017"
stroke-width="1"
stroke="rgba(153,173,209,1)"
/>
<path
id="key"
fill="none"
d="M 63,0 L 0,84"
stroke-width="3"
stroke="transparent"
/>
</g>
</g>
<g
id="edge-3"
fill="none"
marker-start="false"
marker-end="false"
transform="matrix(1,0,0,1,0,0)"
>
<g
id="edge-3"
fill="none"
marker-start="false"
marker-end="false"
stroke="transparent"
stroke-width="3"
/>
<g transform="matrix(1,0,0,1,56,58)">
<path
id="key"
fill="none"
d="M 63,84 L 0,0"
stroke-width="1"
stroke="rgba(153,173,209,1)"
/>
<path
id="key"
fill="none"
d="M 63,84 L 0,0"
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,50,50)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(34,126,255,1)"
transform="translate(-10,-10)"
cx="10"
cy="10"
stroke-width="0"
stroke="rgba(0,0,0,1)"
r="10"
/>
</g>
</g>
<g id="node-2" fill="none" transform="matrix(1,0,0,1,175,50)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(34,126,255,1)"
transform="translate(-10,-10)"
cx="10"
cy="10"
stroke-width="0"
stroke="rgba(0,0,0,1)"
r="10"
/>
</g>
</g>
<g id="node-3" fill="none" transform="matrix(1,0,0,1,125,150)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(34,126,255,1)"
transform="translate(-10,-10)"
cx="10"
cy="10"
stroke-width="0"
stroke="rgba(0,0,0,1)"
r="10"
/>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -0,0 +1,157 @@
<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="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,135,100)">
<path
id="key"
fill="none"
d="M 0,0 L 0,0"
stroke-width="1"
stroke="rgba(153,173,209,1)"
/>
<path
id="key"
fill="none"
d="M 0,0 L 130,0"
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,135,100)">
<path
id="key"
fill="none"
d="M 0,0 L 0,0"
stroke-width="1"
stroke="rgba(153,173,209,1)"
/>
<path
id="key"
fill="none"
d="M 63,0 L 0,84"
stroke-width="3"
stroke="transparent"
/>
</g>
</g>
<g
id="edge-3"
fill="none"
marker-start="false"
marker-end="false"
transform="matrix(1,0,0,1,0,0)"
>
<g
id="edge-3"
fill="none"
marker-start="false"
marker-end="false"
stroke="transparent"
stroke-width="3"
/>
<g transform="matrix(1,0,0,1,135,100)">
<path
id="key"
fill="none"
d="M 0,0 L 0,0"
stroke-width="1"
stroke="rgba(153,173,209,1)"
/>
<path
id="key"
fill="none"
d="M 63,84 L 0,0"
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,125,100)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(34,126,255,1)"
transform="translate(-10,-10)"
cx="10"
cy="10"
stroke-width="0"
stroke="rgba(0,0,0,1)"
r="10"
/>
</g>
</g>
<g id="node-2" fill="none" transform="matrix(1,0,0,1,125,100)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(34,126,255,1)"
transform="translate(-10,-10)"
cx="10"
cy="10"
stroke-width="0"
stroke="rgba(0,0,0,1)"
r="10"
/>
</g>
</g>
<g id="node-3" fill="none" transform="matrix(1,0,0,1,125,100)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(34,126,255,1)"
transform="translate(-10,-10)"
cx="10"
cy="10"
stroke-width="0"
stroke="rgba(0,0,0,1)"
r="10"
/>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -0,0 +1,209 @@
<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="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="4"
/>
<g transform="matrix(1,0,0,1,60,50)">
<path
id="halo"
fill="none"
d="M 0,0 L 130,0"
stroke-width="12"
stroke="rgba(255,192,203,1)"
stroke-dasharray="0,0"
pointer-events="none"
stroke-opacity="0.25"
/>
<path
id="halo"
fill="none"
d="M 0,0 L 130,0"
stroke-width="14"
stroke="transparent"
stroke-dasharray="0,0"
pointer-events="none"
stroke-opacity="0.25"
/>
</g>
<g transform="matrix(1,0,0,1,60,50)">
<path
id="key"
fill="none"
d="M 0,0 L 130,0"
stroke-width="2"
stroke="rgba(255,192,203,1)"
/>
<path
id="key"
fill="none"
d="M 0,0 L 130,0"
stroke-width="4"
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,131,58)">
<path
id="key"
fill="none"
d="M 63,0 L 0,84"
stroke-width="1"
stroke="rgba(153,173,209,1)"
/>
<path
id="key"
fill="none"
d="M 63,0 L 0,84"
stroke-width="3"
stroke="transparent"
/>
</g>
</g>
<g
id="edge-3"
fill="none"
marker-start="false"
marker-end="false"
transform="matrix(1,0,0,1,0,0)"
>
<g
id="edge-3"
fill="none"
marker-start="false"
marker-end="false"
stroke="transparent"
stroke-width="3"
/>
<g transform="matrix(1,0,0,1,56,58)">
<path
id="key"
fill="none"
d="M 63,84 L 0,0"
stroke-width="1"
stroke="rgba(153,173,209,1)"
/>
<path
id="key"
fill="none"
d="M 63,84 L 0,0"
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,50,50)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="halo"
fill="none"
transform="translate(-10,-10)"
cx="10"
cy="10"
stroke-width="12"
stroke="rgba(255,192,203,1)"
r="10"
stroke-dasharray="0,0"
stroke-opacity="0.25"
pointer-events="none"
/>
</g>
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(255,192,203,1)"
transform="translate(-10,-10)"
cx="10"
cy="10"
stroke-width="2"
stroke="rgba(0,0,0,1)"
r="10"
/>
</g>
</g>
<g id="node-2" fill="none" transform="matrix(1,0,0,1,200,50)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(34,126,255,1)"
transform="translate(-10,-10)"
cx="10"
cy="10"
stroke-width="1"
stroke="rgba(0,0,0,1)"
r="10"
/>
</g>
</g>
<g id="node-3" fill="none" transform="matrix(1,0,0,1,125,150)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="halo"
fill="none"
transform="translate(-10,-10)"
cx="10"
cy="10"
stroke-width="12"
stroke="rgba(34,126,255,1)"
r="10"
stroke-dasharray="0,0"
stroke-opacity="0.25"
pointer-events="none"
/>
</g>
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(34,126,255,1)"
transform="translate(-10,-10)"
cx="10"
cy="10"
stroke-width="2"
stroke="rgba(0,0,0,1)"
r="10"
/>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

@ -0,0 +1,209 @@
<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="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="4"
/>
<g transform="matrix(1,0,0,1,60,50)">
<path
id="key"
fill="none"
d="M 0,0 L 130,0"
stroke-width="1"
stroke="rgba(153,173,209,1)"
/>
<path
id="key"
fill="none"
d="M 0,0 L 130,0"
stroke-width="1"
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,131,58)">
<path
id="halo"
fill="none"
d="M 63,0 L 0,84"
stroke-width="12"
stroke="rgba(255,192,203,1)"
stroke-dasharray="0,0"
pointer-events="none"
stroke-opacity="0.25"
/>
<path
id="halo"
fill="none"
d="M 63,0 L 0,84"
stroke-width="14"
stroke="transparent"
stroke-dasharray="0,0"
pointer-events="none"
stroke-opacity="0.25"
/>
</g>
<g transform="matrix(1,0,0,1,131,58)">
<path
id="key"
fill="none"
d="M 63,0 L 0,84"
stroke-width="2"
stroke="rgba(255,192,203,1)"
/>
<path
id="key"
fill="none"
d="M 63,0 L 0,84"
stroke-width="2"
stroke="transparent"
/>
</g>
</g>
<g
id="edge-3"
fill="none"
marker-start="false"
marker-end="false"
transform="matrix(1,0,0,1,0,0)"
>
<g
id="edge-3"
fill="none"
marker-start="false"
marker-end="false"
stroke="transparent"
stroke-width="3"
/>
<g transform="matrix(1,0,0,1,56,58)">
<path
id="key"
fill="none"
d="M 63,84 L 0,0"
stroke-width="1"
stroke="rgba(153,173,209,1)"
/>
<path
id="key"
fill="none"
d="M 63,84 L 0,0"
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,50,50)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(34,126,255,1)"
transform="translate(-10,-10)"
cx="10"
cy="10"
stroke-width="1"
stroke="rgba(0,0,0,1)"
r="10"
/>
</g>
</g>
<g id="node-2" fill="none" transform="matrix(1,0,0,1,200,50)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="halo"
fill="none"
transform="translate(-10,-10)"
cx="10"
cy="10"
stroke-width="12"
stroke="rgba(255,192,203,1)"
r="10"
stroke-dasharray="0,0"
stroke-opacity="0.25"
pointer-events="none"
/>
</g>
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(255,192,203,1)"
transform="translate(-10,-10)"
cx="10"
cy="10"
stroke-width="3"
stroke="rgba(0,0,0,1)"
r="10"
/>
</g>
</g>
<g id="node-3" fill="none" transform="matrix(1,0,0,1,125,150)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="halo"
fill="none"
transform="translate(-10,-10)"
cx="10"
cy="10"
stroke-width="12"
stroke="rgba(34,126,255,1)"
r="10"
stroke-dasharray="0,0"
stroke-opacity="0.25"
pointer-events="none"
/>
</g>
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(34,126,255,1)"
transform="translate(-10,-10)"
cx="10"
cy="10"
stroke-width="2"
stroke="rgba(0,0,0,1)"
r="10"
/>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

@ -0,0 +1,224 @@
<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="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="4"
/>
<g transform="matrix(1,0,0,1,60,50)">
<path
id="key"
fill="none"
d="M 0,0 L 130,0"
stroke-width="1"
stroke="rgba(153,173,209,1)"
/>
<path
id="key"
fill="none"
d="M 0,0 L 130,0"
stroke-width="1"
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,131,58)">
<path
id="halo"
fill="none"
d="M 63,0 L 0,84"
stroke-width="12"
stroke="rgba(255,192,203,1)"
stroke-dasharray="0,0"
pointer-events="none"
stroke-opacity="0.25"
/>
<path
id="halo"
fill="none"
d="M 63,0 L 0,84"
stroke-width="14"
stroke="transparent"
stroke-dasharray="0,0"
pointer-events="none"
stroke-opacity="0.25"
/>
</g>
<g transform="matrix(1,0,0,1,131,58)">
<path
id="key"
fill="none"
d="M 63,0 L 0,84"
stroke-width="2"
stroke="rgba(255,192,203,1)"
/>
<path
id="key"
fill="none"
d="M 63,0 L 0,84"
stroke-width="2"
stroke="transparent"
/>
</g>
</g>
<g
id="edge-3"
fill="none"
marker-start="false"
marker-end="false"
transform="matrix(1,0,0,1,0,0)"
>
<g
id="edge-3"
fill="none"
marker-start="false"
marker-end="false"
stroke="transparent"
stroke-width="3"
/>
<g transform="matrix(1,0,0,1,56,58)">
<path
id="key"
fill="none"
d="M 63,84 L 0,0"
stroke-width="1"
stroke="rgba(153,173,209,1)"
/>
<path
id="key"
fill="none"
d="M 63,84 L 0,0"
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,50,50)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="halo"
fill="none"
transform="translate(-10,-10)"
cx="10"
cy="10"
stroke-width="12"
stroke="rgba(255,192,203,1)"
r="10"
stroke-dasharray="0,0"
stroke-opacity="0.25"
pointer-events="none"
/>
</g>
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(255,192,203,1)"
transform="translate(-10,-10)"
cx="10"
cy="10"
stroke-width="3"
stroke="rgba(0,0,0,1)"
r="10"
/>
</g>
</g>
<g id="node-2" fill="none" transform="matrix(1,0,0,1,200,50)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="halo"
fill="none"
transform="translate(-10,-10)"
cx="10"
cy="10"
stroke-width="12"
stroke="rgba(255,192,203,1)"
r="10"
stroke-dasharray="0,0"
stroke-opacity="0.25"
pointer-events="none"
/>
</g>
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(255,192,203,1)"
transform="translate(-10,-10)"
cx="10"
cy="10"
stroke-width="3"
stroke="rgba(0,0,0,1)"
r="10"
/>
</g>
</g>
<g id="node-3" fill="none" transform="matrix(1,0,0,1,125,150)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="halo"
fill="none"
transform="translate(-10,-10)"
cx="10"
cy="10"
stroke-width="12"
stroke="rgba(34,126,255,1)"
r="10"
stroke-dasharray="0,0"
stroke-opacity="0.25"
pointer-events="none"
/>
</g>
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(34,126,255,1)"
transform="translate(-10,-10)"
cx="10"
cy="10"
stroke-width="2"
stroke="rgba(0,0,0,1)"
r="10"
/>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.1 KiB

View File

@ -0,0 +1,224 @@
<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="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="4"
/>
<g transform="matrix(1,0,0,1,60,50)">
<path
id="key"
fill="none"
d="M 0,0 L 130,0"
stroke-width="1"
stroke="rgba(153,173,209,1)"
/>
<path
id="key"
fill="none"
d="M 0,0 L 130,0"
stroke-width="1"
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,131,58)">
<path
id="halo"
fill="none"
d="M 63,0 L 0,84"
stroke-width="12"
stroke="rgba(255,192,203,1)"
stroke-dasharray="0,0"
pointer-events="none"
stroke-opacity="0.25"
/>
<path
id="halo"
fill="none"
d="M 63,0 L 0,84"
stroke-width="14"
stroke="transparent"
stroke-dasharray="0,0"
pointer-events="none"
stroke-opacity="0.25"
/>
</g>
<g transform="matrix(1,0,0,1,131,58)">
<path
id="key"
fill="none"
d="M 63,0 L 0,84"
stroke-width="2"
stroke="rgba(255,192,203,1)"
/>
<path
id="key"
fill="none"
d="M 63,0 L 0,84"
stroke-width="2"
stroke="transparent"
/>
</g>
</g>
<g
id="edge-3"
fill="none"
marker-start="false"
marker-end="false"
transform="matrix(1,0,0,1,0,0)"
>
<g
id="edge-3"
fill="none"
marker-start="false"
marker-end="false"
stroke="transparent"
stroke-width="3"
/>
<g transform="matrix(1,0,0,1,56,58)">
<path
id="key"
fill="none"
d="M 63,84 L 0,0"
stroke-width="1"
stroke="rgba(153,173,209,1)"
/>
<path
id="key"
fill="none"
d="M 63,84 L 0,0"
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,50,50)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="halo"
fill="none"
transform="translate(-10,-10)"
cx="10"
cy="10"
stroke-width="12"
stroke="rgba(34,126,255,1)"
r="10"
stroke-dasharray="0,0"
stroke-opacity="0.25"
pointer-events="none"
/>
</g>
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(34,126,255,1)"
transform="translate(-10,-10)"
cx="10"
cy="10"
stroke-width="2"
stroke="rgba(0,0,0,1)"
r="10"
/>
</g>
</g>
<g id="node-2" fill="none" transform="matrix(1,0,0,1,200,50)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="halo"
fill="none"
transform="translate(-10,-10)"
cx="10"
cy="10"
stroke-width="12"
stroke="rgba(255,192,203,1)"
r="10"
stroke-dasharray="0,0"
stroke-opacity="0.25"
pointer-events="none"
/>
</g>
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(255,192,203,1)"
transform="translate(-10,-10)"
cx="10"
cy="10"
stroke-width="3"
stroke="rgba(0,0,0,1)"
r="10"
/>
</g>
</g>
<g id="node-3" fill="none" transform="matrix(1,0,0,1,125,150)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="halo"
fill="none"
transform="translate(-10,-10)"
cx="10"
cy="10"
stroke-width="12"
stroke="rgba(34,126,255,1)"
r="10"
stroke-dasharray="0,0"
stroke-opacity="0.25"
pointer-events="none"
/>
</g>
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(34,126,255,1)"
transform="translate(-10,-10)"
cx="10"
cy="10"
stroke-width="2"
stroke="rgba(0,0,0,1)"
r="10"
/>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.1 KiB

View File

@ -0,0 +1,440 @@
<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="g-svg-6" fill="none" transform="matrix(1,0,0,1,0,0)">
<g
id="edge-1"
fill="none"
marker-start="false"
marker-end="true"
transform="matrix(1,0,0,1,0,0)"
>
<g
id="edge-1"
fill="none"
marker-start="false"
marker-end="true"
stroke="transparent"
stroke-width="3"
/>
<g transform="matrix(1,0,0,1,60,50)">
<path
id="key"
fill="none"
d="M 0,0 L 124,7.347880794884119e-16"
stroke-width="1"
stroke="rgba(153,173,209,1)"
/>
<path
id="key"
fill="none"
d="M 0,0 L 124,7.347880794884119e-16"
stroke-width="3"
stroke="transparent"
/>
<g transform="matrix(-1,0,-0,-1,124,0)">
<path
id="g-svg-31"
fill="rgba(153,173,209,1)"
d="M 0,5 L 10,0 L 10,10 Z"
transform="translate(-5,-5)"
stroke-width="1"
stroke="rgba(153,173,209,1)"
width="10"
height="10"
stroke-dasharray="0,0"
/>
<path
id="g-svg-31"
fill="transparent"
d="M 0,5 L 10,0 L 10,10 Z"
transform="translate(-5,-5)"
stroke-width="3"
stroke="transparent"
width="10"
height="10"
stroke-dasharray="0,0"
/>
</g>
</g>
<g id="label" fill="none" transform="matrix(1,0,0,1,129,50)">
<g transform="matrix(1,0,0,1,-20.219999,-11.500000)">
<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.75"
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"
font-size="12"
text-anchor="middle"
>
edge-1
</text>
</g>
</g>
</g>
<g
id="edge-2"
fill="none"
marker-start="false"
marker-end="true"
transform="matrix(1,0,0,1,0,0)"
>
<g
id="edge-2"
fill="none"
marker-start="false"
marker-end="true"
stroke="transparent"
stroke-width="3"
/>
<g transform="matrix(1,0,0,1,131,58)">
<path
id="key"
fill="none"
d="M 63,0 L 3.6000000000000005,79.2"
stroke-width="1"
stroke="rgba(153,173,209,1)"
/>
<path
id="key"
fill="none"
d="M 63,0 L 3.6000000000000005,79.2"
stroke-width="3"
stroke="transparent"
/>
<g transform="matrix(0.600000,-0.800000,0.800000,0.600000,3.600000,79.199997)">
<path
id="g-svg-38"
fill="rgba(153,173,209,1)"
d="M 0,5 L 10,0 L 10,10 Z"
transform="translate(-5,-5)"
stroke-width="1"
stroke="rgba(153,173,209,1)"
width="10"
height="10"
stroke-dasharray="0,0"
/>
<path
id="g-svg-38"
fill="transparent"
d="M 0,5 L 10,0 L 10,10 Z"
transform="translate(-5,-5)"
stroke-width="3"
stroke="transparent"
width="10"
height="10"
stroke-dasharray="0,0"
/>
</g>
</g>
<g
id="label"
fill="none"
transform="matrix(0.600005,-0.799996,0.799996,0.600005,160.099976,103.199989)"
>
<g transform="matrix(1,0,0,1,-21.059999,-11.500000)">
<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.75"
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"
font-size="12"
text-anchor="middle"
>
edge-2
</text>
</g>
</g>
</g>
<g
id="edge-3"
fill="none"
marker-start="false"
marker-end="true"
transform="matrix(1,0,0,1,0,0)"
>
<g
id="edge-3"
fill="none"
marker-start="false"
marker-end="true"
stroke="transparent"
stroke-width="3"
/>
<g transform="matrix(1,0,0,1,56,58)">
<path
id="key"
fill="none"
d="M 63,84 L 3.6000000000000005,4.8"
stroke-width="1"
stroke="rgba(153,173,209,1)"
/>
<path
id="key"
fill="none"
d="M 63,84 L 3.6000000000000005,4.8"
stroke-width="3"
stroke="transparent"
/>
<g transform="matrix(0.600000,0.800000,-0.800000,0.600000,3.600000,4.800000)">
<path
id="g-svg-45"
fill="rgba(153,173,209,1)"
d="M 0,5 L 10,0 L 10,10 Z"
transform="translate(-5,-5)"
stroke-width="1"
stroke="rgba(153,173,209,1)"
width="10"
height="10"
stroke-dasharray="0,0"
/>
<path
id="g-svg-45"
fill="transparent"
d="M 0,5 L 10,0 L 10,10 Z"
transform="translate(-5,-5)"
stroke-width="3"
stroke="transparent"
width="10"
height="10"
stroke-dasharray="0,0"
/>
</g>
</g>
<g
id="label"
fill="none"
transform="matrix(0.600000,0.800000,-0.800000,0.600000,85.099998,96.800003)"
>
<g transform="matrix(1,0,0,1,-21.180000,-11.500000)">
<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.75"
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"
font-size="12"
text-anchor="middle"
>
edge-3
</text>
</g>
</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,50,50)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(34,126,255,1)"
transform="translate(-10,-10)"
cx="10"
cy="10"
stroke-width="0"
stroke="rgba(0,0,0,1)"
r="10"
/>
</g>
<g id="label" fill="none" transform="matrix(1,0,0,1,0,10)">
<g transform="matrix(1,0,0,1,-2.880000,0)">
<path
id="background"
fill="rgba(255,255,255,1)"
d="M 0,0 l 6.76,0 l 0,23 l-6.76 0 z"
opacity="0.5"
stroke-width="0"
width="6.76"
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"
>
1
</text>
</g>
</g>
</g>
<g id="node-2" fill="none" transform="matrix(1,0,0,1,200,50)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(34,126,255,1)"
transform="translate(-10,-10)"
cx="10"
cy="10"
stroke-width="0"
stroke="rgba(0,0,0,1)"
r="10"
/>
</g>
<g id="label" fill="none" transform="matrix(1,0,0,1,0,10)">
<g transform="matrix(1,0,0,1,-3.720000,0)">
<path
id="background"
fill="rgba(255,255,255,1)"
d="M 0,0 l 8.440000000000001,0 l 0,23 l-8.440000000000001 0 z"
opacity="0.5"
stroke-width="0"
width="8.440000000000001"
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"
>
2
</text>
</g>
</g>
</g>
<g id="node-3" fill="none" transform="matrix(1,0,0,1,125,150)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(34,126,255,1)"
transform="translate(-10,-10)"
cx="10"
cy="10"
stroke-width="0"
stroke="rgba(0,0,0,1)"
r="10"
/>
</g>
<g id="label" fill="none" transform="matrix(1,0,0,1,0,10)">
<g transform="matrix(1,0,0,1,-3.840000,0)">
<path
id="background"
fill="rgba(255,255,255,1)"
d="M 0,0 l 8.68,0 l 0,23 l-8.68 0 z"
opacity="0.5"
stroke-width="0"
width="8.68"
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"
>
3
</text>
</g>
</g>
</g>
<g id="node-4" fill="none" transform="matrix(1,0,0,1,125,200)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(34,126,255,1)"
transform="translate(-10,-10)"
cx="10"
cy="10"
stroke-width="0"
stroke="rgba(0,0,0,1)"
visibility="hidden"
r="10"
/>
</g>
<g id="label" fill="none" transform="matrix(1,0,0,1,0,10)">
<g transform="matrix(1,0,0,1,-3.960000,0)">
<path
id="background"
fill="rgba(255,255,255,1)"
d="M 0,0 l 8.92,0 l 0,23 l-8.92 0 z"
opacity="0.5"
stroke-width="0"
width="8.92"
height="23"
visibility="hidden"
/>
</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"
visibility="hidden"
>
4
</text>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -0,0 +1,467 @@
<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="g-svg-6" fill="none" transform="matrix(1,0,0,1,0,0)">
<g
id="edge-1"
fill="none"
marker-start="false"
marker-end="true"
transform="matrix(1,0,0,1,0,0)"
>
<g
id="edge-1"
fill="none"
marker-start="false"
marker-end="true"
stroke="transparent"
stroke-width="3"
/>
<g transform="matrix(1,0,0,1,60,50)">
<path
id="key"
fill="none"
d="M 0,0 L 124,7.347880794884119e-16"
stroke-width="1"
stroke="rgba(153,173,209,1)"
visibility="hidden"
/>
<path
id="key"
fill="none"
d="M 0,0 L 124,7.347880794884119e-16"
stroke-width="3"
stroke="transparent"
visibility="hidden"
/>
<g transform="matrix(-1,0,-0,-1,124,0)">
<path
id="g-svg-31"
fill="rgba(153,173,209,1)"
d="M 0,5 L 10,0 L 10,10 Z"
transform="translate(-5,-5)"
stroke-width="1"
stroke="rgba(153,173,209,1)"
width="10"
height="10"
stroke-dasharray="0,0"
visibility="hidden"
/>
<path
id="g-svg-31"
fill="transparent"
d="M 0,5 L 10,0 L 10,10 Z"
transform="translate(-5,-5)"
stroke-width="3"
stroke="transparent"
width="10"
height="10"
stroke-dasharray="0,0"
visibility="hidden"
/>
</g>
</g>
<g id="label" fill="none" transform="matrix(1,0,0,1,129,50)">
<g transform="matrix(1,0,0,1,-20.219999,-11.500000)">
<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.75"
stroke-width="0"
width="41.44"
height="23"
visibility="hidden"
/>
</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"
font-size="12"
text-anchor="middle"
visibility="hidden"
>
edge-1
</text>
</g>
</g>
</g>
<g
id="edge-2"
fill="none"
marker-start="false"
marker-end="true"
transform="matrix(1,0,0,1,0,0)"
>
<g
id="edge-2"
fill="none"
marker-start="false"
marker-end="true"
stroke="transparent"
stroke-width="3"
/>
<g transform="matrix(1,0,0,1,131,58)">
<path
id="key"
fill="none"
d="M 63,0 L 3.6000000000000005,79.2"
stroke-width="1"
stroke="rgba(153,173,209,1)"
visibility="visible"
/>
<path
id="key"
fill="none"
d="M 63,0 L 3.6000000000000005,79.2"
stroke-width="3"
stroke="transparent"
visibility="visible"
/>
<g transform="matrix(0.600000,-0.800000,0.800000,0.600000,3.600000,79.199997)">
<path
id="g-svg-38"
fill="rgba(153,173,209,1)"
d="M 0,5 L 10,0 L 10,10 Z"
transform="translate(-5,-5)"
stroke-width="1"
stroke="rgba(153,173,209,1)"
width="10"
height="10"
stroke-dasharray="0,0"
visibility="visible"
/>
<path
id="g-svg-38"
fill="transparent"
d="M 0,5 L 10,0 L 10,10 Z"
transform="translate(-5,-5)"
stroke-width="3"
stroke="transparent"
width="10"
height="10"
stroke-dasharray="0,0"
visibility="visible"
/>
</g>
</g>
<g
id="label"
fill="none"
transform="matrix(0.600005,-0.799996,0.799996,0.600005,160.099976,103.199989)"
>
<g transform="matrix(1,0,0,1,-21.059999,-11.500000)">
<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.75"
stroke-width="0"
width="43.12"
height="23"
visibility="visible"
/>
</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"
font-size="12"
text-anchor="middle"
visibility="visible"
>
edge-2
</text>
</g>
</g>
</g>
<g
id="edge-3"
fill="none"
marker-start="false"
marker-end="true"
transform="matrix(1,0,0,1,0,0)"
>
<g
id="edge-3"
fill="none"
marker-start="false"
marker-end="true"
stroke="transparent"
stroke-width="3"
/>
<g transform="matrix(1,0,0,1,56,58)">
<path
id="key"
fill="none"
d="M 63,84 L 3.6000000000000005,4.8"
stroke-width="1"
stroke="rgba(153,173,209,1)"
visibility="hidden"
/>
<path
id="key"
fill="none"
d="M 63,84 L 3.6000000000000005,4.8"
stroke-width="3"
stroke="transparent"
visibility="hidden"
/>
<g transform="matrix(0.600000,0.800000,-0.800000,0.600000,3.600000,4.800000)">
<path
id="g-svg-45"
fill="rgba(153,173,209,1)"
d="M 0,5 L 10,0 L 10,10 Z"
transform="translate(-5,-5)"
stroke-width="1"
stroke="rgba(153,173,209,1)"
width="10"
height="10"
stroke-dasharray="0,0"
visibility="hidden"
/>
<path
id="g-svg-45"
fill="transparent"
d="M 0,5 L 10,0 L 10,10 Z"
transform="translate(-5,-5)"
stroke-width="3"
stroke="transparent"
width="10"
height="10"
stroke-dasharray="0,0"
visibility="hidden"
/>
</g>
</g>
<g
id="label"
fill="none"
transform="matrix(0.600000,0.800000,-0.800000,0.600000,85.099998,96.800003)"
>
<g transform="matrix(1,0,0,1,-21.180000,-11.500000)">
<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.75"
stroke-width="0"
width="43.36"
height="23"
visibility="hidden"
/>
</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"
font-size="12"
text-anchor="middle"
visibility="hidden"
>
edge-3
</text>
</g>
</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,50,50)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(34,126,255,1)"
transform="translate(-10,-10)"
cx="10"
cy="10"
stroke-width="0"
stroke="rgba(0,0,0,1)"
r="10"
visibility="hidden"
/>
</g>
<g id="label" fill="none" transform="matrix(1,0,0,1,0,10)">
<g transform="matrix(1,0,0,1,-2.880000,0)">
<path
id="background"
fill="rgba(255,255,255,1)"
d="M 0,0 l 6.76,0 l 0,23 l-6.76 0 z"
opacity="0.5"
stroke-width="0"
width="6.76"
height="23"
visibility="hidden"
/>
</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"
visibility="hidden"
>
1
</text>
</g>
</g>
</g>
<g id="node-2" fill="none" transform="matrix(1,0,0,1,200,50)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(34,126,255,1)"
transform="translate(-10,-10)"
cx="10"
cy="10"
stroke-width="0"
stroke="rgba(0,0,0,1)"
r="10"
visibility="visible"
/>
</g>
<g id="label" fill="none" transform="matrix(1,0,0,1,0,10)">
<g transform="matrix(1,0,0,1,-3.720000,0)">
<path
id="background"
fill="rgba(255,255,255,1)"
d="M 0,0 l 8.440000000000001,0 l 0,23 l-8.440000000000001 0 z"
opacity="0.5"
stroke-width="0"
width="8.440000000000001"
height="23"
visibility="visible"
/>
</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"
visibility="visible"
>
2
</text>
</g>
</g>
</g>
<g id="node-3" fill="none" transform="matrix(1,0,0,1,125,150)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(34,126,255,1)"
transform="translate(-10,-10)"
cx="10"
cy="10"
stroke-width="0"
stroke="rgba(0,0,0,1)"
r="10"
visibility="visible"
/>
</g>
<g id="label" fill="none" transform="matrix(1,0,0,1,0,10)">
<g transform="matrix(1,0,0,1,-3.840000,0)">
<path
id="background"
fill="rgba(255,255,255,1)"
d="M 0,0 l 8.68,0 l 0,23 l-8.68 0 z"
opacity="0.5"
stroke-width="0"
width="8.68"
height="23"
visibility="visible"
/>
</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"
visibility="visible"
>
3
</text>
</g>
</g>
</g>
<g id="node-4" fill="none" transform="matrix(1,0,0,1,125,200)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(34,126,255,1)"
transform="translate(-10,-10)"
cx="10"
cy="10"
stroke-width="0"
stroke="rgba(0,0,0,1)"
visibility="hidden"
r="10"
/>
</g>
<g id="label" fill="none" transform="matrix(1,0,0,1,0,10)">
<g transform="matrix(1,0,0,1,-3.960000,0)">
<path
id="background"
fill="rgba(255,255,255,1)"
d="M 0,0 l 8.92,0 l 0,23 l-8.92 0 z"
opacity="0.5"
stroke-width="0"
width="8.92"
height="23"
visibility="hidden"
/>
</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"
visibility="hidden"
>
4
</text>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -0,0 +1,467 @@
<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="g-svg-6" fill="none" transform="matrix(1,0,0,1,0,0)">
<g
id="edge-1"
fill="none"
marker-start="false"
marker-end="true"
transform="matrix(1,0,0,1,0,0)"
>
<g
id="edge-1"
fill="none"
marker-start="false"
marker-end="true"
stroke="transparent"
stroke-width="3"
/>
<g transform="matrix(1,0,0,1,60,50)">
<path
id="key"
fill="none"
d="M 0,0 L 124,7.347880794884119e-16"
stroke-width="1"
stroke="rgba(153,173,209,1)"
visibility="hidden"
/>
<path
id="key"
fill="none"
d="M 0,0 L 124,7.347880794884119e-16"
stroke-width="3"
stroke="transparent"
visibility="hidden"
/>
<g transform="matrix(-1,0,-0,-1,124,0)">
<path
id="g-svg-31"
fill="rgba(153,173,209,1)"
d="M 0,5 L 10,0 L 10,10 Z"
transform="translate(-5,-5)"
stroke-width="1"
stroke="rgba(153,173,209,1)"
width="10"
height="10"
stroke-dasharray="0,0"
visibility="hidden"
/>
<path
id="g-svg-31"
fill="transparent"
d="M 0,5 L 10,0 L 10,10 Z"
transform="translate(-5,-5)"
stroke-width="3"
stroke="transparent"
width="10"
height="10"
stroke-dasharray="0,0"
visibility="hidden"
/>
</g>
</g>
<g id="label" fill="none" transform="matrix(1,0,0,1,129,50)">
<g transform="matrix(1,0,0,1,-20.219999,-11.500000)">
<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.75"
stroke-width="0"
width="41.44"
height="23"
visibility="hidden"
/>
</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"
font-size="12"
text-anchor="middle"
visibility="hidden"
>
edge-1
</text>
</g>
</g>
</g>
<g
id="edge-2"
fill="none"
marker-start="false"
marker-end="true"
transform="matrix(1,0,0,1,0,0)"
>
<g
id="edge-2"
fill="none"
marker-start="false"
marker-end="true"
stroke="transparent"
stroke-width="3"
/>
<g transform="matrix(1,0,0,1,131,58)">
<path
id="key"
fill="none"
d="M 63,0 L 3.6000000000000005,79.2"
stroke-width="1"
stroke="rgba(153,173,209,1)"
visibility="hidden"
/>
<path
id="key"
fill="none"
d="M 63,0 L 3.6000000000000005,79.2"
stroke-width="3"
stroke="transparent"
visibility="hidden"
/>
<g transform="matrix(0.600000,-0.800000,0.800000,0.600000,3.600000,79.199997)">
<path
id="g-svg-38"
fill="rgba(153,173,209,1)"
d="M 0,5 L 10,0 L 10,10 Z"
transform="translate(-5,-5)"
stroke-width="1"
stroke="rgba(153,173,209,1)"
width="10"
height="10"
stroke-dasharray="0,0"
visibility="hidden"
/>
<path
id="g-svg-38"
fill="transparent"
d="M 0,5 L 10,0 L 10,10 Z"
transform="translate(-5,-5)"
stroke-width="3"
stroke="transparent"
width="10"
height="10"
stroke-dasharray="0,0"
visibility="hidden"
/>
</g>
</g>
<g
id="label"
fill="none"
transform="matrix(0.600005,-0.799996,0.799996,0.600005,160.099976,103.199989)"
>
<g transform="matrix(1,0,0,1,-21.059999,-11.500000)">
<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.75"
stroke-width="0"
width="43.12"
height="23"
visibility="hidden"
/>
</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"
font-size="12"
text-anchor="middle"
visibility="hidden"
>
edge-2
</text>
</g>
</g>
</g>
<g
id="edge-3"
fill="none"
marker-start="false"
marker-end="true"
transform="matrix(1,0,0,1,0,0)"
>
<g
id="edge-3"
fill="none"
marker-start="false"
marker-end="true"
stroke="transparent"
stroke-width="3"
/>
<g transform="matrix(1,0,0,1,56,58)">
<path
id="key"
fill="none"
d="M 63,84 L 3.6000000000000005,4.8"
stroke-width="1"
stroke="rgba(153,173,209,1)"
visibility="hidden"
/>
<path
id="key"
fill="none"
d="M 63,84 L 3.6000000000000005,4.8"
stroke-width="3"
stroke="transparent"
visibility="hidden"
/>
<g transform="matrix(0.600000,0.800000,-0.800000,0.600000,3.600000,4.800000)">
<path
id="g-svg-45"
fill="rgba(153,173,209,1)"
d="M 0,5 L 10,0 L 10,10 Z"
transform="translate(-5,-5)"
stroke-width="1"
stroke="rgba(153,173,209,1)"
width="10"
height="10"
stroke-dasharray="0,0"
visibility="hidden"
/>
<path
id="g-svg-45"
fill="transparent"
d="M 0,5 L 10,0 L 10,10 Z"
transform="translate(-5,-5)"
stroke-width="3"
stroke="transparent"
width="10"
height="10"
stroke-dasharray="0,0"
visibility="hidden"
/>
</g>
</g>
<g
id="label"
fill="none"
transform="matrix(0.600000,0.800000,-0.800000,0.600000,85.099998,96.800003)"
>
<g transform="matrix(1,0,0,1,-21.180000,-11.500000)">
<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.75"
stroke-width="0"
width="43.36"
height="23"
visibility="hidden"
/>
</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"
font-size="12"
text-anchor="middle"
visibility="hidden"
>
edge-3
</text>
</g>
</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,50,50)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(34,126,255,1)"
transform="translate(-10,-10)"
cx="10"
cy="10"
stroke-width="0"
stroke="rgba(0,0,0,1)"
r="10"
visibility="hidden"
/>
</g>
<g id="label" fill="none" transform="matrix(1,0,0,1,0,10)">
<g transform="matrix(1,0,0,1,-2.880000,0)">
<path
id="background"
fill="rgba(255,255,255,1)"
d="M 0,0 l 6.76,0 l 0,23 l-6.76 0 z"
opacity="0.5"
stroke-width="0"
width="6.76"
height="23"
visibility="hidden"
/>
</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"
visibility="hidden"
>
1
</text>
</g>
</g>
</g>
<g id="node-2" fill="none" transform="matrix(1,0,0,1,200,50)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(34,126,255,1)"
transform="translate(-10,-10)"
cx="10"
cy="10"
stroke-width="0"
stroke="rgba(0,0,0,1)"
r="10"
visibility="hidden"
/>
</g>
<g id="label" fill="none" transform="matrix(1,0,0,1,0,10)">
<g transform="matrix(1,0,0,1,-3.720000,0)">
<path
id="background"
fill="rgba(255,255,255,1)"
d="M 0,0 l 8.440000000000001,0 l 0,23 l-8.440000000000001 0 z"
opacity="0.5"
stroke-width="0"
width="8.440000000000001"
height="23"
visibility="hidden"
/>
</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"
visibility="hidden"
>
2
</text>
</g>
</g>
</g>
<g id="node-3" fill="none" transform="matrix(1,0,0,1,125,150)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(34,126,255,1)"
transform="translate(-10,-10)"
cx="10"
cy="10"
stroke-width="0"
stroke="rgba(0,0,0,1)"
r="10"
visibility="hidden"
/>
</g>
<g id="label" fill="none" transform="matrix(1,0,0,1,0,10)">
<g transform="matrix(1,0,0,1,-3.840000,0)">
<path
id="background"
fill="rgba(255,255,255,1)"
d="M 0,0 l 8.68,0 l 0,23 l-8.68 0 z"
opacity="0.5"
stroke-width="0"
width="8.68"
height="23"
visibility="hidden"
/>
</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"
visibility="hidden"
>
3
</text>
</g>
</g>
</g>
<g id="node-4" fill="none" transform="matrix(1,0,0,1,125,200)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(34,126,255,1)"
transform="translate(-10,-10)"
cx="10"
cy="10"
stroke-width="0"
stroke="rgba(0,0,0,1)"
visibility="hidden"
r="10"
/>
</g>
<g id="label" fill="none" transform="matrix(1,0,0,1,0,10)">
<g transform="matrix(1,0,0,1,-3.960000,0)">
<path
id="background"
fill="rgba(255,255,255,1)"
d="M 0,0 l 8.92,0 l 0,23 l-8.92 0 z"
opacity="0.5"
stroke-width="0"
width="8.92"
height="23"
visibility="hidden"
/>
</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"
visibility="hidden"
>
4
</text>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -0,0 +1,467 @@
<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="g-svg-6" fill="none" transform="matrix(1,0,0,1,0,0)">
<g
id="edge-1"
fill="none"
marker-start="false"
marker-end="true"
transform="matrix(1,0,0,1,0,0)"
>
<g
id="edge-1"
fill="none"
marker-start="false"
marker-end="true"
stroke="transparent"
stroke-width="3"
/>
<g transform="matrix(1,0,0,1,60,50)">
<path
id="key"
fill="none"
d="M 0,0 L 124,7.347880794884119e-16"
stroke-width="1"
stroke="rgba(153,173,209,1)"
visibility="hidden"
/>
<path
id="key"
fill="none"
d="M 0,0 L 124,7.347880794884119e-16"
stroke-width="3"
stroke="transparent"
visibility="hidden"
/>
<g transform="matrix(-1,0,-0,-1,124,0)">
<path
id="g-svg-31"
fill="rgba(153,173,209,1)"
d="M 0,5 L 10,0 L 10,10 Z"
transform="translate(-5,-5)"
stroke-width="1"
stroke="rgba(153,173,209,1)"
width="10"
height="10"
stroke-dasharray="0,0"
visibility="hidden"
/>
<path
id="g-svg-31"
fill="transparent"
d="M 0,5 L 10,0 L 10,10 Z"
transform="translate(-5,-5)"
stroke-width="3"
stroke="transparent"
width="10"
height="10"
stroke-dasharray="0,0"
visibility="hidden"
/>
</g>
</g>
<g id="label" fill="none" transform="matrix(1,0,0,1,129,50)">
<g transform="matrix(1,0,0,1,-20.219999,-11.500000)">
<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.75"
stroke-width="0"
width="41.44"
height="23"
visibility="hidden"
/>
</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"
font-size="12"
text-anchor="middle"
visibility="hidden"
>
edge-1
</text>
</g>
</g>
</g>
<g
id="edge-2"
fill="none"
marker-start="false"
marker-end="true"
transform="matrix(1,0,0,1,0,0)"
>
<g
id="edge-2"
fill="none"
marker-start="false"
marker-end="true"
stroke="transparent"
stroke-width="3"
/>
<g transform="matrix(1,0,0,1,131,58)">
<path
id="key"
fill="none"
d="M 63,0 L 3.6000000000000005,79.2"
stroke-width="1"
stroke="rgba(153,173,209,1)"
visibility="visible"
/>
<path
id="key"
fill="none"
d="M 63,0 L 3.6000000000000005,79.2"
stroke-width="3"
stroke="transparent"
visibility="visible"
/>
<g transform="matrix(0.600000,-0.800000,0.800000,0.600000,3.600000,79.199997)">
<path
id="g-svg-38"
fill="rgba(153,173,209,1)"
d="M 0,5 L 10,0 L 10,10 Z"
transform="translate(-5,-5)"
stroke-width="1"
stroke="rgba(153,173,209,1)"
width="10"
height="10"
stroke-dasharray="0,0"
visibility="visible"
/>
<path
id="g-svg-38"
fill="transparent"
d="M 0,5 L 10,0 L 10,10 Z"
transform="translate(-5,-5)"
stroke-width="3"
stroke="transparent"
width="10"
height="10"
stroke-dasharray="0,0"
visibility="visible"
/>
</g>
</g>
<g
id="label"
fill="none"
transform="matrix(0.600005,-0.799996,0.799996,0.600005,160.099976,103.199989)"
>
<g transform="matrix(1,0,0,1,-21.059999,-11.500000)">
<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.75"
stroke-width="0"
width="43.12"
height="23"
visibility="visible"
/>
</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"
font-size="12"
text-anchor="middle"
visibility="visible"
>
edge-2
</text>
</g>
</g>
</g>
<g
id="edge-3"
fill="none"
marker-start="false"
marker-end="true"
transform="matrix(1,0,0,1,0,0)"
>
<g
id="edge-3"
fill="none"
marker-start="false"
marker-end="true"
stroke="transparent"
stroke-width="3"
/>
<g transform="matrix(1,0,0,1,56,58)">
<path
id="key"
fill="none"
d="M 63,84 L 3.6000000000000005,4.8"
stroke-width="1"
stroke="rgba(153,173,209,1)"
visibility="hidden"
/>
<path
id="key"
fill="none"
d="M 63,84 L 3.6000000000000005,4.8"
stroke-width="3"
stroke="transparent"
visibility="hidden"
/>
<g transform="matrix(0.600000,0.800000,-0.800000,0.600000,3.600000,4.800000)">
<path
id="g-svg-45"
fill="rgba(153,173,209,1)"
d="M 0,5 L 10,0 L 10,10 Z"
transform="translate(-5,-5)"
stroke-width="1"
stroke="rgba(153,173,209,1)"
width="10"
height="10"
stroke-dasharray="0,0"
visibility="hidden"
/>
<path
id="g-svg-45"
fill="transparent"
d="M 0,5 L 10,0 L 10,10 Z"
transform="translate(-5,-5)"
stroke-width="3"
stroke="transparent"
width="10"
height="10"
stroke-dasharray="0,0"
visibility="hidden"
/>
</g>
</g>
<g
id="label"
fill="none"
transform="matrix(0.600000,0.800000,-0.800000,0.600000,85.099998,96.800003)"
>
<g transform="matrix(1,0,0,1,-21.180000,-11.500000)">
<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.75"
stroke-width="0"
width="43.36"
height="23"
visibility="hidden"
/>
</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"
font-size="12"
text-anchor="middle"
visibility="hidden"
>
edge-3
</text>
</g>
</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,50,50)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(34,126,255,1)"
transform="translate(-10,-10)"
cx="10"
cy="10"
stroke-width="0"
stroke="rgba(0,0,0,1)"
r="10"
visibility="hidden"
/>
</g>
<g id="label" fill="none" transform="matrix(1,0,0,1,0,10)">
<g transform="matrix(1,0,0,1,-2.880000,0)">
<path
id="background"
fill="rgba(255,255,255,1)"
d="M 0,0 l 6.76,0 l 0,23 l-6.76 0 z"
opacity="0.5"
stroke-width="0"
width="6.76"
height="23"
visibility="hidden"
/>
</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"
visibility="hidden"
>
1
</text>
</g>
</g>
</g>
<g id="node-2" fill="none" transform="matrix(1,0,0,1,200,50)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(34,126,255,1)"
transform="translate(-10,-10)"
cx="10"
cy="10"
stroke-width="0"
stroke="rgba(0,0,0,1)"
r="10"
visibility="visible"
/>
</g>
<g id="label" fill="none" transform="matrix(1,0,0,1,0,10)">
<g transform="matrix(1,0,0,1,-3.720000,0)">
<path
id="background"
fill="rgba(255,255,255,1)"
d="M 0,0 l 8.440000000000001,0 l 0,23 l-8.440000000000001 0 z"
opacity="0.5"
stroke-width="0"
width="8.440000000000001"
height="23"
visibility="visible"
/>
</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"
visibility="visible"
>
2
</text>
</g>
</g>
</g>
<g id="node-3" fill="none" transform="matrix(1,0,0,1,125,150)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(34,126,255,1)"
transform="translate(-10,-10)"
cx="10"
cy="10"
stroke-width="0"
stroke="rgba(0,0,0,1)"
r="10"
visibility="visible"
/>
</g>
<g id="label" fill="none" transform="matrix(1,0,0,1,0,10)">
<g transform="matrix(1,0,0,1,-3.840000,0)">
<path
id="background"
fill="rgba(255,255,255,1)"
d="M 0,0 l 8.68,0 l 0,23 l-8.68 0 z"
opacity="0.5"
stroke-width="0"
width="8.68"
height="23"
visibility="visible"
/>
</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"
visibility="visible"
>
3
</text>
</g>
</g>
</g>
<g id="node-4" fill="none" transform="matrix(1,0,0,1,125,200)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(34,126,255,1)"
transform="translate(-10,-10)"
cx="10"
cy="10"
stroke-width="0"
stroke="rgba(0,0,0,1)"
visibility="hidden"
r="10"
/>
</g>
<g id="label" fill="none" transform="matrix(1,0,0,1,0,10)">
<g transform="matrix(1,0,0,1,-3.960000,0)">
<path
id="background"
fill="rgba(255,255,255,1)"
d="M 0,0 l 8.92,0 l 0,23 l-8.92 0 z"
opacity="0.5"
stroke-width="0"
width="8.92"
height="23"
visibility="hidden"
/>
</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"
visibility="hidden"
>
4
</text>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -0,0 +1,467 @@
<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="g-svg-6" fill="none" transform="matrix(1,0,0,1,0,0)">
<g
id="edge-1"
fill="none"
marker-start="false"
marker-end="true"
transform="matrix(1,0,0,1,0,0)"
>
<g
id="edge-1"
fill="none"
marker-start="false"
marker-end="true"
stroke="transparent"
stroke-width="3"
/>
<g transform="matrix(1,0,0,1,60,50)">
<path
id="key"
fill="none"
d="M 0,0 L 124,7.347880794884119e-16"
stroke-width="1"
stroke="rgba(153,173,209,1)"
visibility="hidden"
/>
<path
id="key"
fill="none"
d="M 0,0 L 124,7.347880794884119e-16"
stroke-width="3"
stroke="transparent"
visibility="hidden"
/>
<g transform="matrix(-1,0,-0,-1,124,0)">
<path
id="g-svg-31"
fill="rgba(153,173,209,1)"
d="M 0,5 L 10,0 L 10,10 Z"
transform="translate(-5,-5)"
stroke-width="1"
stroke="rgba(153,173,209,1)"
width="10"
height="10"
stroke-dasharray="0,0"
visibility="hidden"
/>
<path
id="g-svg-31"
fill="transparent"
d="M 0,5 L 10,0 L 10,10 Z"
transform="translate(-5,-5)"
stroke-width="3"
stroke="transparent"
width="10"
height="10"
stroke-dasharray="0,0"
visibility="hidden"
/>
</g>
</g>
<g id="label" fill="none" transform="matrix(1,0,0,1,129,50)">
<g transform="matrix(1,0,0,1,-20.219999,-11.500000)">
<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.75"
stroke-width="0"
width="41.44"
height="23"
visibility="hidden"
/>
</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"
font-size="12"
text-anchor="middle"
visibility="hidden"
>
edge-1
</text>
</g>
</g>
</g>
<g
id="edge-2"
fill="none"
marker-start="false"
marker-end="true"
transform="matrix(1,0,0,1,0,0)"
>
<g
id="edge-2"
fill="none"
marker-start="false"
marker-end="true"
stroke="transparent"
stroke-width="3"
/>
<g transform="matrix(1,0,0,1,131,58)">
<path
id="key"
fill="none"
d="M 63,0 L 3.6000000000000005,79.2"
stroke-width="1"
stroke="rgba(153,173,209,1)"
visibility="visible"
/>
<path
id="key"
fill="none"
d="M 63,0 L 3.6000000000000005,79.2"
stroke-width="3"
stroke="transparent"
visibility="visible"
/>
<g transform="matrix(0.600000,-0.800000,0.800000,0.600000,3.600000,79.199997)">
<path
id="g-svg-38"
fill="rgba(153,173,209,1)"
d="M 0,5 L 10,0 L 10,10 Z"
transform="translate(-5,-5)"
stroke-width="1"
stroke="rgba(153,173,209,1)"
width="10"
height="10"
stroke-dasharray="0,0"
visibility="visible"
/>
<path
id="g-svg-38"
fill="transparent"
d="M 0,5 L 10,0 L 10,10 Z"
transform="translate(-5,-5)"
stroke-width="3"
stroke="transparent"
width="10"
height="10"
stroke-dasharray="0,0"
visibility="visible"
/>
</g>
</g>
<g
id="label"
fill="none"
transform="matrix(0.600005,-0.799996,0.799996,0.600005,160.099976,103.199989)"
>
<g transform="matrix(1,0,0,1,-21.059999,-11.500000)">
<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.75"
stroke-width="0"
width="43.12"
height="23"
visibility="visible"
/>
</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"
font-size="12"
text-anchor="middle"
visibility="visible"
>
edge-2
</text>
</g>
</g>
</g>
<g
id="edge-3"
fill="none"
marker-start="false"
marker-end="true"
transform="matrix(1,0,0,1,0,0)"
>
<g
id="edge-3"
fill="none"
marker-start="false"
marker-end="true"
stroke="transparent"
stroke-width="3"
/>
<g transform="matrix(1,0,0,1,56,58)">
<path
id="key"
fill="none"
d="M 63,84 L 3.6000000000000005,4.8"
stroke-width="1"
stroke="rgba(153,173,209,1)"
visibility="hidden"
/>
<path
id="key"
fill="none"
d="M 63,84 L 3.6000000000000005,4.8"
stroke-width="3"
stroke="transparent"
visibility="hidden"
/>
<g transform="matrix(0.600000,0.800000,-0.800000,0.600000,3.600000,4.800000)">
<path
id="g-svg-45"
fill="rgba(153,173,209,1)"
d="M 0,5 L 10,0 L 10,10 Z"
transform="translate(-5,-5)"
stroke-width="1"
stroke="rgba(153,173,209,1)"
width="10"
height="10"
stroke-dasharray="0,0"
visibility="hidden"
/>
<path
id="g-svg-45"
fill="transparent"
d="M 0,5 L 10,0 L 10,10 Z"
transform="translate(-5,-5)"
stroke-width="3"
stroke="transparent"
width="10"
height="10"
stroke-dasharray="0,0"
visibility="hidden"
/>
</g>
</g>
<g
id="label"
fill="none"
transform="matrix(0.600000,0.800000,-0.800000,0.600000,85.099998,96.800003)"
>
<g transform="matrix(1,0,0,1,-21.180000,-11.500000)">
<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.75"
stroke-width="0"
width="43.36"
height="23"
visibility="hidden"
/>
</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"
font-size="12"
text-anchor="middle"
visibility="hidden"
>
edge-3
</text>
</g>
</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,50,50)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(34,126,255,1)"
transform="translate(-10,-10)"
cx="10"
cy="10"
stroke-width="0"
stroke="rgba(0,0,0,1)"
r="10"
visibility="visible"
/>
</g>
<g id="label" fill="none" transform="matrix(1,0,0,1,0,10)">
<g transform="matrix(1,0,0,1,-2.880000,0)">
<path
id="background"
fill="rgba(255,255,255,1)"
d="M 0,0 l 6.76,0 l 0,23 l-6.76 0 z"
opacity="0.5"
stroke-width="0"
width="6.76"
height="23"
visibility="visible"
/>
</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"
visibility="visible"
>
1
</text>
</g>
</g>
</g>
<g id="node-2" fill="none" transform="matrix(1,0,0,1,200,50)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(34,126,255,1)"
transform="translate(-10,-10)"
cx="10"
cy="10"
stroke-width="0"
stroke="rgba(0,0,0,1)"
r="10"
visibility="visible"
/>
</g>
<g id="label" fill="none" transform="matrix(1,0,0,1,0,10)">
<g transform="matrix(1,0,0,1,-3.720000,0)">
<path
id="background"
fill="rgba(255,255,255,1)"
d="M 0,0 l 8.440000000000001,0 l 0,23 l-8.440000000000001 0 z"
opacity="0.5"
stroke-width="0"
width="8.440000000000001"
height="23"
visibility="visible"
/>
</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"
visibility="visible"
>
2
</text>
</g>
</g>
</g>
<g id="node-3" fill="none" transform="matrix(1,0,0,1,125,150)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(34,126,255,1)"
transform="translate(-10,-10)"
cx="10"
cy="10"
stroke-width="0"
stroke="rgba(0,0,0,1)"
r="10"
visibility="visible"
/>
</g>
<g id="label" fill="none" transform="matrix(1,0,0,1,0,10)">
<g transform="matrix(1,0,0,1,-3.840000,0)">
<path
id="background"
fill="rgba(255,255,255,1)"
d="M 0,0 l 8.68,0 l 0,23 l-8.68 0 z"
opacity="0.5"
stroke-width="0"
width="8.68"
height="23"
visibility="visible"
/>
</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"
visibility="visible"
>
3
</text>
</g>
</g>
</g>
<g id="node-4" fill="none" transform="matrix(1,0,0,1,125,200)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(34,126,255,1)"
transform="translate(-10,-10)"
cx="10"
cy="10"
stroke-width="0"
stroke="rgba(0,0,0,1)"
visibility="hidden"
r="10"
/>
</g>
<g id="label" fill="none" transform="matrix(1,0,0,1,0,10)">
<g transform="matrix(1,0,0,1,-3.960000,0)">
<path
id="background"
fill="rgba(255,255,255,1)"
d="M 0,0 l 8.92,0 l 0,23 l-8.92 0 z"
opacity="0.5"
stroke-width="0"
width="8.92"
height="23"
visibility="hidden"
/>
</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"
visibility="hidden"
>
4
</text>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -0,0 +1,467 @@
<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="g-svg-6" fill="none" transform="matrix(1,0,0,1,0,0)">
<g
id="edge-1"
fill="none"
marker-start="false"
marker-end="true"
transform="matrix(1,0,0,1,0,0)"
>
<g
id="edge-1"
fill="none"
marker-start="false"
marker-end="true"
stroke="transparent"
stroke-width="3"
/>
<g transform="matrix(1,0,0,1,60,50)">
<path
id="key"
fill="none"
d="M 0,0 L 124,7.347880794884119e-16"
stroke-width="1"
stroke="rgba(153,173,209,1)"
visibility="visible"
/>
<path
id="key"
fill="none"
d="M 0,0 L 124,7.347880794884119e-16"
stroke-width="3"
stroke="transparent"
visibility="visible"
/>
<g transform="matrix(-1,0,-0,-1,124,0)">
<path
id="g-svg-31"
fill="rgba(153,173,209,1)"
d="M 0,5 L 10,0 L 10,10 Z"
transform="translate(-5,-5)"
stroke-width="1"
stroke="rgba(153,173,209,1)"
width="10"
height="10"
stroke-dasharray="0,0"
visibility="visible"
/>
<path
id="g-svg-31"
fill="transparent"
d="M 0,5 L 10,0 L 10,10 Z"
transform="translate(-5,-5)"
stroke-width="3"
stroke="transparent"
width="10"
height="10"
stroke-dasharray="0,0"
visibility="visible"
/>
</g>
</g>
<g id="label" fill="none" transform="matrix(1,0,0,1,129,50)">
<g transform="matrix(1,0,0,1,-20.219999,-11.500000)">
<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.75"
stroke-width="0"
width="41.44"
height="23"
visibility="visible"
/>
</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"
font-size="12"
text-anchor="middle"
visibility="visible"
>
edge-1
</text>
</g>
</g>
</g>
<g
id="edge-2"
fill="none"
marker-start="false"
marker-end="true"
transform="matrix(1,0,0,1,0,0)"
>
<g
id="edge-2"
fill="none"
marker-start="false"
marker-end="true"
stroke="transparent"
stroke-width="3"
/>
<g transform="matrix(1,0,0,1,131,58)">
<path
id="key"
fill="none"
d="M 63,0 L 3.6000000000000005,79.2"
stroke-width="1"
stroke="rgba(153,173,209,1)"
visibility="hidden"
/>
<path
id="key"
fill="none"
d="M 63,0 L 3.6000000000000005,79.2"
stroke-width="3"
stroke="transparent"
visibility="hidden"
/>
<g transform="matrix(0.600000,-0.800000,0.800000,0.600000,3.600000,79.199997)">
<path
id="g-svg-38"
fill="rgba(153,173,209,1)"
d="M 0,5 L 10,0 L 10,10 Z"
transform="translate(-5,-5)"
stroke-width="1"
stroke="rgba(153,173,209,1)"
width="10"
height="10"
stroke-dasharray="0,0"
visibility="hidden"
/>
<path
id="g-svg-38"
fill="transparent"
d="M 0,5 L 10,0 L 10,10 Z"
transform="translate(-5,-5)"
stroke-width="3"
stroke="transparent"
width="10"
height="10"
stroke-dasharray="0,0"
visibility="hidden"
/>
</g>
</g>
<g
id="label"
fill="none"
transform="matrix(0.600005,-0.799996,0.799996,0.600005,160.099976,103.199989)"
>
<g transform="matrix(1,0,0,1,-21.059999,-11.500000)">
<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.75"
stroke-width="0"
width="43.12"
height="23"
visibility="hidden"
/>
</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"
font-size="12"
text-anchor="middle"
visibility="hidden"
>
edge-2
</text>
</g>
</g>
</g>
<g
id="edge-3"
fill="none"
marker-start="false"
marker-end="true"
transform="matrix(1,0,0,1,0,0)"
>
<g
id="edge-3"
fill="none"
marker-start="false"
marker-end="true"
stroke="transparent"
stroke-width="3"
/>
<g transform="matrix(1,0,0,1,56,58)">
<path
id="key"
fill="none"
d="M 63,84 L 3.6000000000000005,4.8"
stroke-width="1"
stroke="rgba(153,173,209,1)"
visibility="hidden"
/>
<path
id="key"
fill="none"
d="M 63,84 L 3.6000000000000005,4.8"
stroke-width="3"
stroke="transparent"
visibility="hidden"
/>
<g transform="matrix(0.600000,0.800000,-0.800000,0.600000,3.600000,4.800000)">
<path
id="g-svg-45"
fill="rgba(153,173,209,1)"
d="M 0,5 L 10,0 L 10,10 Z"
transform="translate(-5,-5)"
stroke-width="1"
stroke="rgba(153,173,209,1)"
width="10"
height="10"
stroke-dasharray="0,0"
visibility="hidden"
/>
<path
id="g-svg-45"
fill="transparent"
d="M 0,5 L 10,0 L 10,10 Z"
transform="translate(-5,-5)"
stroke-width="3"
stroke="transparent"
width="10"
height="10"
stroke-dasharray="0,0"
visibility="hidden"
/>
</g>
</g>
<g
id="label"
fill="none"
transform="matrix(0.600000,0.800000,-0.800000,0.600000,85.099998,96.800003)"
>
<g transform="matrix(1,0,0,1,-21.180000,-11.500000)">
<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.75"
stroke-width="0"
width="43.36"
height="23"
visibility="hidden"
/>
</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"
font-size="12"
text-anchor="middle"
visibility="hidden"
>
edge-3
</text>
</g>
</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,50,50)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(34,126,255,1)"
transform="translate(-10,-10)"
cx="10"
cy="10"
stroke-width="0"
stroke="rgba(0,0,0,1)"
r="10"
visibility="visible"
/>
</g>
<g id="label" fill="none" transform="matrix(1,0,0,1,0,10)">
<g transform="matrix(1,0,0,1,-2.880000,0)">
<path
id="background"
fill="rgba(255,255,255,1)"
d="M 0,0 l 6.76,0 l 0,23 l-6.76 0 z"
opacity="0.5"
stroke-width="0"
width="6.76"
height="23"
visibility="visible"
/>
</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"
visibility="visible"
>
1
</text>
</g>
</g>
</g>
<g id="node-2" fill="none" transform="matrix(1,0,0,1,200,50)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(34,126,255,1)"
transform="translate(-10,-10)"
cx="10"
cy="10"
stroke-width="0"
stroke="rgba(0,0,0,1)"
r="10"
visibility="visible"
/>
</g>
<g id="label" fill="none" transform="matrix(1,0,0,1,0,10)">
<g transform="matrix(1,0,0,1,-3.720000,0)">
<path
id="background"
fill="rgba(255,255,255,1)"
d="M 0,0 l 8.440000000000001,0 l 0,23 l-8.440000000000001 0 z"
opacity="0.5"
stroke-width="0"
width="8.440000000000001"
height="23"
visibility="visible"
/>
</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"
visibility="visible"
>
2
</text>
</g>
</g>
</g>
<g id="node-3" fill="none" transform="matrix(1,0,0,1,125,150)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(34,126,255,1)"
transform="translate(-10,-10)"
cx="10"
cy="10"
stroke-width="0"
stroke="rgba(0,0,0,1)"
r="10"
visibility="hidden"
/>
</g>
<g id="label" fill="none" transform="matrix(1,0,0,1,0,10)">
<g transform="matrix(1,0,0,1,-3.840000,0)">
<path
id="background"
fill="rgba(255,255,255,1)"
d="M 0,0 l 8.68,0 l 0,23 l-8.68 0 z"
opacity="0.5"
stroke-width="0"
width="8.68"
height="23"
visibility="hidden"
/>
</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"
visibility="hidden"
>
3
</text>
</g>
</g>
</g>
<g id="node-4" fill="none" transform="matrix(1,0,0,1,125,200)">
<g transform="matrix(1,0,0,1,0,0)">
<circle
id="key"
fill="rgba(34,126,255,1)"
transform="translate(-10,-10)"
cx="10"
cy="10"
stroke-width="0"
stroke="rgba(0,0,0,1)"
visibility="hidden"
r="10"
/>
</g>
<g id="label" fill="none" transform="matrix(1,0,0,1,0,10)">
<g transform="matrix(1,0,0,1,-3.960000,0)">
<path
id="background"
fill="rgba(255,255,255,1)"
d="M 0,0 l 8.92,0 l 0,23 l-8.92 0 z"
opacity="0.5"
stroke-width="0"
width="8.92"
height="23"
visibility="hidden"
/>
</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"
visibility="hidden"
>
4
</text>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -0,0 +1,43 @@
import type { Graph } from '@/src';
import { elementPosition } from '@@/demo/case';
import { createDemoGraph } from '@@/utils';
describe('element position', () => {
let graph: Graph;
beforeAll(async () => {
graph = await createDemoGraph(elementPosition, { animation: false });
});
it('default status', async () => {
await expect(graph.getCanvas()).toMatchSnapshot(__filename);
});
it('translateElementTo', async () => {
await graph.translateElementTo({
'node-1': [125, 100],
'node-2': [125, 100],
'node-3': [125, 100],
});
await expect(graph.getCanvas()).toMatchSnapshot(__filename, '{name}__translateElementTo');
});
it('translateElementBy', async () => {
await graph.translateElementBy({
'node-1': [-50, -50],
'node-2': [+50, -50],
'node-3': [0, +50],
});
await expect(graph.getCanvas()).toMatchSnapshot(__filename, '{name}__translateElementBy');
});
it('translateElementTo single api', async () => {
graph.translateElementTo('node-1', [50, 50]);
await expect(graph.getCanvas()).toMatchSnapshot(__filename, '{name}__translateElementTo-single');
});
it('translateElementBy single api', async () => {
graph.translateElementBy('node-1', [50, 50]);
await expect(graph.getCanvas()).toMatchSnapshot(__filename, '{name}__translateElementBy-single');
});
});

View File

@ -0,0 +1,34 @@
import type { Graph } from '@/src';
import { elementState } from '@@/demo/case';
import { createDemoGraph } from '@@/utils';
describe('element state', () => {
let graph: Graph;
beforeAll(async () => {
graph = await createDemoGraph(elementState, { animation: false });
});
it('default status', async () => {
await expect(graph.getCanvas()).toMatchSnapshot(__filename);
});
it('set state', async () => {
graph.setElementState({
'node-1': ['active'],
'node-2': ['selected'],
'edge-1': [],
'edge-2': ['active'],
});
await expect(graph.getCanvas()).toMatchSnapshot(__filename, '{name}__setState');
});
it('set state single api', async () => {
graph.setElementState('node-1', ['selected']);
await expect(graph.getCanvas()).toMatchSnapshot(__filename, '{name}__setState-single');
graph.setElementState('node-1', []);
await expect(graph.getCanvas()).toMatchSnapshot(__filename, '{name}__setState-single-default');
});
});

View File

@ -0,0 +1,46 @@
import type { Graph } from '@/src';
import { elementVisibility } from '@@/demo/case';
import { createDemoGraph } from '@@/utils';
describe('element visibility', () => {
let graph: Graph;
beforeAll(async () => {
graph = await createDemoGraph(elementVisibility, { animation: false });
});
it('default status', async () => {
await expect(graph.getCanvas()).toMatchSnapshot(__filename);
});
it('hide', async () => {
await graph.hideElement(['node-1', 'node-2', 'node-3', 'edge-1', 'edge-2', 'edge-3']);
await expect(graph.getCanvas()).toMatchSnapshot(__filename, '{name}__hide');
});
it('show', async () => {
await graph.showElement(['node-1', 'node-2', 'edge-1']);
await expect(graph.getCanvas()).toMatchSnapshot(__filename, '{name}__show');
});
it('show and hide', async () => {
await graph.setElementVisibility({
'node-1': 'hidden',
'node-3': 'visible',
'edge-1': 'hidden',
'edge-2': 'visible',
});
await expect(graph.getCanvas()).toMatchSnapshot(__filename, '{name}__show-and-hide');
});
it('show and hide single api', async () => {
graph.setElementVisibility('node-1', 'visible');
await expect(graph.getCanvas()).toMatchSnapshot(__filename, '{name}__show-single');
graph.setElementVisibility('node-1', 'hidden');
await expect(graph.getCanvas()).toMatchSnapshot(__filename, '{name}__hide-single');
});
});

View File

@ -11,10 +11,10 @@ const data = {
{ id: 'node-3', data: { value: 3 }, style: { fill: 'blue', parentId: 'combo-1' } },
],
edges: [
{ id: 'edge-1', source: 'node-1', target: 'node-2', data: { weight: 1 } },
{ id: 'edge-2', source: 'node-2', target: 'node-3', data: { weight: 2 } },
{ id: 'edge-1', source: 'node-1', target: 'node-2', data: { weight: 1 }, style: {} },
{ id: 'edge-2', source: 'node-2', target: 'node-3', data: { weight: 2 }, style: {} },
],
combos: [{ id: 'combo-1' }],
combos: [{ id: 'combo-1', data: {}, style: {} }],
};
describe('DataController', () => {
@ -32,7 +32,7 @@ describe('DataController', () => {
controller.addComboData([{ id: 'combo-1' }]);
expect(controller.getComboData(['combo-1'])).toEqual([{ id: 'combo-1' }]);
expect(controller.getComboData(['combo-1'])).toEqual([{ id: 'combo-1', data: {}, style: {} }]);
});
it('setData', () => {
@ -85,11 +85,14 @@ describe('DataController', () => {
{ id: 'node-5', data: { value: 5 }, style: { fill: 'black', parentId: 'combo-2' } },
],
edges: [
{ id: 'edge-1', source: 'node-1', target: 'node-2', data: { weight: 1 } },
{ id: 'edge-2', source: 'node-2', target: 'node-3', data: { weight: 2 } },
{ id: 'edge-3', source: 'node-1', target: 'node-5', data: { weight: 3 } },
{ id: 'edge-1', source: 'node-1', target: 'node-2', data: { weight: 1 }, style: {} },
{ id: 'edge-2', source: 'node-2', target: 'node-3', data: { weight: 2 }, style: {} },
{ id: 'edge-3', source: 'node-1', target: 'node-5', data: { weight: 3 }, style: {} },
],
combos: [
{ id: 'combo-1', data: {}, style: {} },
{ id: 'combo-2', data: {}, style: {} },
],
combos: [{ id: 'combo-1' }, { id: 'combo-2' }],
});
});
@ -134,10 +137,13 @@ describe('DataController', () => {
{ id: 'node-3', data: { value: 3 }, style: { fill: 'blue', parentId: 'combo-1' } },
],
edges: [
{ id: 'edge-1', source: 'node-1', target: 'node-2', data: { weight: 678 } },
{ id: 'edge-2', source: 'node-2', target: 'node-3', data: { weight: 666 } },
{ id: 'edge-1', source: 'node-1', target: 'node-2', data: { weight: 678 }, style: {} },
{ id: 'edge-2', source: 'node-2', target: 'node-3', data: { weight: 666 }, style: {} },
],
combos: [
{ id: 'combo-1', data: { value: 100 }, style: { stroke: 'blue' } },
{ id: 'combo-2', data: {}, style: {} },
],
combos: [{ id: 'combo-1', data: { value: 100 }, style: { stroke: 'blue' } }, { id: 'combo-2' }],
});
});
@ -160,20 +166,20 @@ describe('DataController', () => {
controller.updateEdgeData([{ id: 'edge-1', data: { weight: 1 } }]);
expect(controller.getEdgeData()).toEqual([
{ id: 'edge-1', source: 'node-1', target: 'node-2', data: { weight: 1 } },
{ id: 'edge-1', source: 'node-1', target: 'node-2', data: { weight: 1 }, style: {} },
]);
// update source
controller.updateEdgeData([{ id: 'edge-1', source: 'node-2' }]);
expect(controller.getEdgeData()).toEqual([
{ id: 'edge-1', source: 'node-2', target: 'node-2', data: { weight: 1 } },
{ id: 'edge-1', source: 'node-2', target: 'node-2', data: { weight: 1 }, style: {} },
]);
// update target
controller.updateEdgeData([{ id: 'edge-1', target: 'node-1' }]);
expect(controller.getEdgeData()).toEqual([
{ id: 'edge-1', source: 'node-2', target: 'node-1', data: { weight: 1 } },
{ id: 'edge-1', source: 'node-2', target: 'node-1', data: { weight: 1 }, style: {} },
]);
});
@ -203,22 +209,28 @@ describe('DataController', () => {
expect(controller.getData()).toEqual({
nodes: [
{ id: 'node-1', style: { x: 150, y: 150, z: 0, parentId: 'combo-1' } },
{ id: 'node-2', style: { x: 200, y: 200, z: 0, parentId: 'combo-1' } },
{ id: 'node-1', data: {}, style: { x: 150, y: 150, z: 0, parentId: 'combo-1' } },
{ id: 'node-2', data: {}, style: { x: 200, y: 200, z: 0, parentId: 'combo-1' } },
],
edges: [],
combos: [{ id: 'combo-1', style: { x: 100, y: 100, z: 0 } }, { id: 'combo-2' }],
combos: [
{ id: 'combo-1', data: {}, style: { x: 100, y: 100, z: 0 } },
{ id: 'combo-2', data: {}, style: {} },
],
});
controller.updateComboData([{ id: 'combo-1', style: { fill: 'pink' } }]);
expect(controller.getData()).toEqual({
nodes: [
{ id: 'node-1', style: { x: 150, y: 150, z: 0, parentId: 'combo-1' } },
{ id: 'node-2', style: { x: 200, y: 200, z: 0, parentId: 'combo-1' } },
{ id: 'node-1', data: {}, style: { x: 150, y: 150, z: 0, parentId: 'combo-1' } },
{ id: 'node-2', data: {}, style: { x: 200, y: 200, z: 0, parentId: 'combo-1' } },
],
edges: [],
combos: [{ id: 'combo-1', style: { x: 100, y: 100, z: 0, fill: 'pink' } }, { id: 'combo-2' }],
combos: [
{ id: 'combo-1', data: {}, style: { x: 100, y: 100, z: 0, fill: 'pink' } },
{ id: 'combo-2', data: {}, style: {} },
],
});
controller.updateComboData([{ id: 'combo-2' }]);
@ -235,8 +247,8 @@ describe('DataController', () => {
controller.translateComboBy(['combo-1'], [100, 100]);
expect(controller.getData()).toEqual({
nodes: [{ id: 'node-1', style: { parentId: 'combo-1', x: 100, y: 100, z: 0 } }],
combos: [{ id: 'combo-1', style: { x: 100, y: 100, z: 0 } }],
nodes: [{ id: 'node-1', data: {}, style: { parentId: 'combo-1', x: 100, y: 100, z: 0 } }],
combos: [{ id: 'combo-1', data: {}, style: { x: 100, y: 100, z: 0 } }],
edges: [],
});
});
@ -256,9 +268,9 @@ describe('DataController', () => {
controller.translateComboBy(['combo-1'], [66, 67]);
expect(controller.getNodeData()).toEqual([
{ id: 'node-1' },
{ id: 'node-2', style: { x: 66, y: 117, z: 0, fill: 'green', parentId: 'combo-1' } },
{ id: 'node-3', style: { x: 166, y: 167, z: 0, fill: 'blue', parentId: 'combo-1' } },
{ id: 'node-1', data: {}, style: {} },
{ id: 'node-2', data: {}, style: { x: 66, y: 117, z: 0, fill: 'green', parentId: 'combo-1' } },
{ id: 'node-3', data: {}, style: { x: 166, y: 167, z: 0, fill: 'blue', parentId: 'combo-1' } },
]);
});
@ -274,7 +286,7 @@ describe('DataController', () => {
expect(controller.getData()).toEqual({
nodes: [],
edges: [],
combos: [{ id: 'combo-1', style: { x: 100, y: 100, z: 0 } }],
combos: [{ id: 'combo-1', data: {}, style: { x: 100, y: 100, z: 0 } }],
});
});
@ -293,10 +305,10 @@ describe('DataController', () => {
expect(controller.getData()).toEqual({
nodes: [
{ id: 'node-1', style: { parentId: 'combo-1', x: 100, y: 100, z: 0 } },
{ id: 'node-2', style: { parentId: 'combo-1', x: 110, y: 110, z: 0 } },
{ id: 'node-1', data: {}, style: { parentId: 'combo-1', x: 100, y: 100, z: 0 } },
{ id: 'node-2', data: {}, style: { parentId: 'combo-1', x: 110, y: 110, z: 0 } },
],
combos: [{ id: 'combo-1', style: { x: 100, y: 100, z: 0 } }],
combos: [{ id: 'combo-1', data: {}, style: { x: 100, y: 100, z: 0 } }],
edges: [],
});
});
@ -317,8 +329,8 @@ describe('DataController', () => {
{ id: 'node-2', data: { value: 2 }, style: { fill: 'green', parentId: 'combo-1' } },
{ id: 'node-3', data: { value: 3 }, style: { fill: 'blue', parentId: 'combo-1' } },
],
edges: [{ id: 'edge-2', source: 'node-2', target: 'node-3', data: { weight: 2 } }],
combos: [{ id: 'combo-1' }],
edges: [{ id: 'edge-2', source: 'node-2', target: 'node-3', data: { weight: 2 }, style: {} }],
combos: [{ id: 'combo-1', data: {}, style: {} }],
});
controller.removeComboData(['combo-1']);
@ -328,7 +340,7 @@ describe('DataController', () => {
{ id: 'node-2', data: { value: 2 }, style: { fill: 'green', parentId: undefined } },
{ id: 'node-3', data: { value: 3 }, style: { fill: 'blue', parentId: undefined } },
],
edges: [{ id: 'edge-2', source: 'node-2', target: 'node-3', data: { weight: 2 } }],
edges: [{ id: 'edge-2', source: 'node-2', target: 'node-3', data: { weight: 2 }, style: {} }],
combos: [],
});
@ -372,8 +384,8 @@ describe('DataController', () => {
{ id: 'node-3', data: { value: 3 }, style: { fill: 'blue', parentId: undefined } },
],
edges: [
{ id: 'edge-1', source: 'node-1', target: 'node-2', data: { weight: 1 } },
{ id: 'edge-2', source: 'node-2', target: 'node-3', data: { weight: 2 } },
{ id: 'edge-1', source: 'node-1', target: 'node-2', data: { weight: 1 }, style: {} },
{ id: 'edge-2', source: 'node-2', target: 'node-3', data: { weight: 2 }, style: {} },
],
combos: [],
});
@ -384,11 +396,14 @@ describe('DataController', () => {
it('removeComboData with children', () => {
const data = {
nodes: [
{ id: 'node-1', style: { parentId: 'combo-1' } },
{ id: 'node-2', style: { parentId: 'combo-1' } },
{ id: 'node-3', style: { parentId: 'combo-1' } },
{ id: 'node-1', data: {}, style: { parentId: 'combo-1' } },
{ id: 'node-2', data: {}, style: { parentId: 'combo-1' } },
{ id: 'node-3', data: {}, style: { parentId: 'combo-1' } },
],
combos: [
{ id: 'combo-1', data: {}, style: { parentId: 'combo-2' } },
{ id: 'combo-2', data: {}, style: {} },
],
combos: [{ id: 'combo-1', style: { parentId: 'combo-2' } }, { id: 'combo-2' }],
};
const controller = new DataController();
@ -404,12 +419,12 @@ describe('DataController', () => {
expect(controller.getData()).toEqual({
nodes: [
{ id: 'node-1', style: { parentId: 'combo-2' } },
{ id: 'node-2', style: { parentId: 'combo-2' } },
{ id: 'node-3', style: { parentId: 'combo-2' } },
{ id: 'node-1', data: {}, style: { parentId: 'combo-2' } },
{ id: 'node-2', data: {}, style: { parentId: 'combo-2' } },
{ id: 'node-3', data: {}, style: { parentId: 'combo-2' } },
],
edges: [],
combos: [{ id: 'combo-2' }],
combos: [{ id: 'combo-2', data: {}, style: {} }],
});
});
@ -429,18 +444,18 @@ describe('DataController', () => {
const changes = controller.getChanges();
expect(changes).toEqual([
{ value: { id: 'combo-1' }, type: 'ComboAdded' },
{ value: { id: 'combo-1', data: {}, style: {} }, type: 'ComboAdded' },
{ value: { id: 'node-1', data: { value: 1 }, style: { fill: 'red' } }, type: 'NodeAdded' },
{ value: { id: 'node-2', data: { value: 2 }, style: { fill: 'green', parentId: 'combo-1' } }, type: 'NodeAdded' },
{ value: { id: 'node-3', data: { value: 3 }, style: { fill: 'blue', parentId: 'combo-1' } }, type: 'NodeAdded' },
{
value: { id: 'node-2', data: { value: 2 }, style: { fill: 'green', parentId: 'combo-1' } },
type: 'NodeAdded',
value: { id: 'edge-1', source: 'node-1', target: 'node-2', data: { weight: 1 }, style: {} },
type: 'EdgeAdded',
},
{
value: { id: 'node-3', data: { value: 3 }, style: { fill: 'blue', parentId: 'combo-1' } },
type: 'NodeAdded',
value: { id: 'edge-2', source: 'node-2', target: 'node-3', data: { weight: 2 }, style: {} },
type: 'EdgeAdded',
},
{ value: { id: 'edge-1', source: 'node-1', target: 'node-2', data: { weight: 1 } }, type: 'EdgeAdded' },
{ value: { id: 'edge-2', source: 'node-2', target: 'node-3', data: { weight: 2 } }, type: 'EdgeAdded' },
{ value: { id: 'combo-2' }, type: 'ComboAdded' },
{ value: { id: 'node-4', data: { value: 4 }, style: { fill: 'yellow' } }, type: 'NodeAdded' },
{
@ -448,14 +463,20 @@ describe('DataController', () => {
original: { id: 'node-3', data: { value: 3 }, style: { fill: 'blue', parentId: 'combo-1' } },
type: 'NodeUpdated',
},
{ value: { id: 'edge-1', source: 'node-1', target: 'node-2', data: { weight: 1 } }, type: 'EdgeRemoved' },
{ value: { id: 'edge-2', source: 'node-2', target: 'node-3', data: { weight: 2 } }, type: 'EdgeRemoved' },
{
value: { id: 'edge-1', source: 'node-1', target: 'node-2', data: { weight: 1 }, style: {} },
type: 'EdgeRemoved',
},
{
value: { id: 'edge-2', source: 'node-2', target: 'node-3', data: { weight: 2 }, style: {} },
type: 'EdgeRemoved',
},
{ value: { id: 'node-1', data: { value: 1 }, style: { fill: 'red' } }, type: 'NodeRemoved' },
{
value: { id: 'node-2', data: { value: 2 }, style: { fill: 'green', parentId: 'combo-1' } },
type: 'NodeRemoved',
},
{ value: { id: 'combo-1' }, type: 'ComboRemoved' },
{ value: { id: 'combo-1', data: {}, style: {} }, type: 'ComboRemoved' },
]);
expect(reduceDataChanges(changes)).toEqual([
@ -637,21 +658,21 @@ describe('DataController', () => {
controller.addData(clone(data));
expect(controller.getRelatedEdgesData('node-1')).toEqual([
{ id: 'edge-1', source: 'node-1', target: 'node-2', data: { weight: 1 } },
{ id: 'edge-1', source: 'node-1', target: 'node-2', data: { weight: 1 }, style: {} },
]);
controller.addEdgeData([{ id: 'edge-3', source: 'node-1', target: 'node-3', data: { weight: 3 } }]);
expect(controller.getRelatedEdgesData('node-1')).toEqual([
{ id: 'edge-1', source: 'node-1', target: 'node-2', data: { weight: 1 } },
{ id: 'edge-3', source: 'node-1', target: 'node-3', data: { weight: 3 } },
{ id: 'edge-1', source: 'node-1', target: 'node-2', data: { weight: 1 }, style: {} },
{ id: 'edge-3', source: 'node-1', target: 'node-3', data: { weight: 3 }, style: {} },
]);
expect(controller.getRelatedEdgesData('node-1', 'in')).toEqual([]);
expect(controller.getRelatedEdgesData('node-1', 'out')).toEqual([
{ id: 'edge-1', source: 'node-1', target: 'node-2', data: { weight: 1 } },
{ id: 'edge-3', source: 'node-1', target: 'node-3', data: { weight: 3 } },
{ id: 'edge-1', source: 'node-1', target: 'node-2', data: { weight: 1 }, style: {} },
{ id: 'edge-3', source: 'node-1', target: 'node-3', data: { weight: 3 }, style: {} },
]);
});

View File

@ -23,18 +23,18 @@ describe('ElementController', () => {
const edge1Id = idOf(options.data!.edges![0]);
const edge2Id = idOf(options.data!.edges![1]);
expect(elementController.getDataStyle('node', 'node-1')).toEqual(options.data!.nodes![0].style || {});
expect(elementController.getDataStyle('node-1')).toEqual(options.data!.nodes![0].style || {});
// 没有属性 / no style
expect(elementController.getDataStyle('node', 'node-2')).toEqual({ x: 150, y: 100 });
expect(elementController.getDataStyle('node-2')).toEqual({ x: 150, y: 100 });
// 没有样式属性 / No style attribute
expect(elementController.getDataStyle('node', 'node-3')).toEqual({
expect(elementController.getDataStyle('node-3')).toEqual({
x: 125,
y: 150,
parentId: 'combo-1',
states: ['selected'],
});
expect(elementController.getDataStyle('edge', edge1Id)).toEqual(options.data!.edges![0].style || {});
expect(elementController.getDataStyle('combo', 'combo-1')).toEqual({});
expect(elementController.getDataStyle(edge1Id)).toEqual(options.data!.edges![0].style || {});
expect(elementController.getDataStyle('combo-1')).toEqual({});
// ref light theme
expect(elementController.getThemeStyle('node')).toEqual(LIGHT_THEME.node!.style);
@ -72,20 +72,20 @@ describe('ElementController', () => {
});
expect(elementController.getStateStyle('combo-1')).toEqual({});
expect(Object.keys(elementController.getElementsByState('selected'))).toEqual([
'node-3',
idOf(options.data!.edges![1]),
]);
// expect(Object.keys(elementController.getElementsByState('selected'))).toEqual([
// 'node-3',
// idOf(options.data!.edges![1]),
// ]);
elementController.setElementsState({ 'node-1': ['active'] });
expect(elementController.getElementStates('node-1')).toEqual(['active']);
elementController.setElementsState({ 'node-1': [] });
expect(elementController.getElementStates('node-1')).toEqual([]);
// elementController.setElementsState({ 'node-1': ['active'] });
// expect(elementController.getElementStates('node-1')).toEqual(['active']);
// elementController.setElementsState({ 'node-1': [] });
// expect(elementController.getElementStates('node-1')).toEqual([]);
expect(elementController.getElementStates('node-2')).toEqual([]);
expect(elementController.getElementStates('node-3')).toEqual(['selected']);
expect(elementController.getElementStates('edge-1')).toEqual([]);
expect(elementController.getElementStates(idOf(options.data!.edges![1]))).toEqual(['active', 'selected']);
// expect(elementController.getElementStates('node-2')).toEqual([]);
// expect(elementController.getElementStates('node-3')).toEqual(['selected']);
// expect(elementController.getElementStates('edge-1')).toEqual([]);
// expect(elementController.getElementStates(idOf(options.data!.edges![1]))).toEqual(['active', 'selected']);
expect(elementController.getElementComputedStyle('node', 'node-1')).toEqual({
...LIGHT_THEME.node?.style,

View File

@ -14,7 +14,7 @@ describe('element visibility', () => {
});
it('hide', async () => {
graph.setElementVisibility(['node-3', 'node-2-node-3', 'node-3-node-1'], 'hidden');
graph.hideElement(['node-3', 'node-2-node-3', 'node-3-node-1']);
expect(graph.getElementVisibility('node-3')).toBe('hidden');
expect(graph.getElementVisibility('node-2-node-3')).toBe('hidden');
@ -24,7 +24,7 @@ describe('element visibility', () => {
});
it('show', async () => {
graph.setElementVisibility(['node-3', 'node-2-node-3', 'node-3-node-1'], 'visible');
graph.showElement(['node-3', 'node-2-node-3', 'node-3-node-1']);
expect(graph.getElementVisibility('node-3')).toBe('visible');
expect(graph.getElementVisibility('node-2-node-3')).toBe('visible');

View File

@ -14,19 +14,19 @@ describe('element z-index', () => {
});
it('front', async () => {
graph.setElementZIndex('node-2', 'front');
graph.frontElement('node-2');
await expect(graph.getCanvas()).toMatchSnapshot(__filename, '{name}__front');
});
it('back', async () => {
graph.setElementZIndex('node-2', 'back');
graph.backElement('node-2');
await expect(graph.getCanvas()).toMatchSnapshot(__filename, '{name}__back');
});
it('to', async () => {
graph.setElementZIndex('node-2', 0);
graph.setElementZIndex({ 'node-2': 0 });
await expect(graph.getCanvas()).toMatchSnapshot(__filename);
});

View File

@ -6,7 +6,7 @@ import { createDemoGraph } from '@@/utils';
describe('Graph', () => {
let graph: Graph;
beforeAll(async () => {
graph = await createDemoGraph(commonGraph);
graph = await createDemoGraph(commonGraph, { animation: false });
});
const idOf = (d: any) => d.id;
@ -70,22 +70,32 @@ describe('Graph', () => {
});
it('updateData/getData/setData', () => {
expect(graph.getData()).toEqual({ combos: [], ...data });
// 调整之后getData 获取的为当前 graph 最新的数据,而不是初始化时的数据
// After adjustment, the data obtained by getData is the latest data of the graph, not the data when it is initialized
const currData = graph.getData();
expect(currData.nodes?.map(idOf)).toEqual(data.nodes.map(idOf));
expect(currData.edges?.map(idOf)).toEqual(data.edges.map(idOf));
graph.setData({
nodes: [{ id: 'node-1' }, { id: 'node-2' }],
edges: [{ id: 'edge-1', source: 'node-1', target: 'node-2' }],
});
expect(graph.getData()).toEqual({
nodes: [{ id: 'node-1' }, { id: 'node-2' }],
edges: [{ id: 'edge-1', source: 'node-1', target: 'node-2' }],
nodes: [
{ id: 'node-1', data: {}, style: {} },
{ id: 'node-2', data: {}, style: {} },
],
edges: [{ id: 'edge-1', source: 'node-1', target: 'node-2', data: {}, style: {} }],
combos: [],
});
graph.updateData({ edges: [{ id: 'edge-1', style: { lineWidth: 5 } }] });
expect(graph.getData()).toEqual({
nodes: [{ id: 'node-1' }, { id: 'node-2' }],
edges: [{ id: 'edge-1', source: 'node-1', target: 'node-2', style: { lineWidth: 5 } }],
nodes: [
{ id: 'node-1', data: {}, style: {} },
{ id: 'node-2', data: {}, style: {} },
],
edges: [{ id: 'edge-1', source: 'node-1', target: 'node-2', data: {}, style: { lineWidth: 5 } }],
combos: [],
});
});
@ -115,13 +125,13 @@ describe('Graph', () => {
graph.updateEdgeData([{ id: 'edge-2', style: { lineWidth: 10 } }]);
graph.updateComboData([{ id: 'combo-1', style: { stroke: 'red' } }]);
expect(graph.getNodeData()).toEqual([
{ id: 'node-1' },
{ id: 'node-2' },
{ id: 'node-3', style: { x: 100, y: 100, parentId: 'combo-1' } },
{ id: 'node-4', style: { parentId: 'combo-1' } },
{ id: 'node-1', data: {}, style: {} },
{ id: 'node-2', data: {}, style: {} },
{ id: 'node-3', data: {}, style: { x: 100, y: 100, parentId: 'combo-1' } },
{ id: 'node-4', data: {}, style: { parentId: 'combo-1' } },
]);
expect(graph.getEdgeData().map(idOf)).toEqual(['edge-1', 'edge-2']);
expect(graph.getComboData()).toEqual([{ id: 'combo-1', style: { stroke: 'red' } }]);
expect(graph.getComboData()).toEqual([{ id: 'combo-1', data: {}, style: { stroke: 'red' } }]);
graph.removeComboData(['combo-1']);
graph.removeNodeData(['node-3', 'node-4']);
expect(graph.getNodeData().map(idOf)).toEqual(['node-1', 'node-2']);
@ -150,7 +160,7 @@ describe('Graph', () => {
});
it('getNeighborNodesData', () => {
expect(graph.getNeighborNodesData('node-1')).toEqual([{ id: 'node-2' }]);
expect(graph.getNeighborNodesData('node-1')).toEqual([{ id: 'node-2', data: {}, style: {} }]);
});
it('getParentData', () => {
@ -164,24 +174,41 @@ describe('Graph', () => {
expect(renderBounds.max).toEqual([16, 16, 0]);
});
it('setElementState/getElementState/getElementDataByState', () => {
graph.setElementState('node-1', ['selected']);
it('setElementState/getElementState/getElementDataByState', async () => {
await graph.setElementState('node-2', 'selected');
expect(graph.getElementState('node-2')).toEqual(['selected']);
await graph.setElementState('node-2', []);
expect(graph.getElementState('node-2')).toEqual([]);
await graph.setElementState({ 'node-1': 'selected' });
expect(graph.getElementState('node-1')).toEqual(['selected']);
expect(graph.getElementState('node-2')).toEqual([]);
expect(graph.getElementDataByState('node', 'selected')).toEqual([{ id: 'node-1' }]);
expect(graph.getElementDataByState('node', 'selected')).toEqual([
{ id: 'node-1', data: {}, style: { states: ['selected'] } },
]);
});
it('setElementZIndex/getElementZIndex', () => {
graph.setElementZIndex('node-1', 'front');
it('setElementZIndex/getElementZIndex', async () => {
await graph.setElementZIndex('node-1', 2);
expect(graph.getElementZIndex('node-1')).toBe(2);
await graph.setElementZIndex({ 'node-1': 0 });
expect(graph.getElementZIndex('node-1')).toBe(0);
await graph.frontElement('node-1');
expect(graph.getElementZIndex('node-1')).toBe(1);
expect(graph.getElementZIndex('node-2')).toBe(0);
});
it('setElementVisibility/getElementVisibility', () => {
graph.setElementVisibility('node-1', 'hidden');
it('setElementVisibility/getElementVisibility', async () => {
await graph.hideElement('node-1');
expect(graph.getElementVisibility('node-1')).toBe('hidden');
await graph.showElement('node-1');
expect(graph.getElementVisibility('node-1')).toBe('visible');
await graph.setElementVisibility({ 'node-1': 'hidden' });
expect(graph.getElementVisibility('node-1')).toBe('hidden');
expect(graph.getElementVisibility('node-2')).toBe('visible');
graph.setElementVisibility('node-1', 'visible');
await graph.setElementVisibility({ 'node-1': 'visible' });
expect(graph.getElementVisibility('node-1')).toBe('visible');
});

View File

@ -1,4 +1,4 @@
import { cacheStyle, getCachedStyle, setCacheStyle } from '@/src/utils/cache';
import { cacheStyle, getCachedStyle, hasCachedStyle, setCacheStyle } from '@/src/utils/cache';
import { Circle } from '@antv/g';
describe('cache', () => {
@ -11,10 +11,14 @@ describe('cache', () => {
},
});
expect(hasCachedStyle(circle, 'fill')).toBe(false);
cacheStyle(circle, ['fill', 'stroke']);
circle.style.fill = 'green';
expect(hasCachedStyle(circle, 'fill')).toBe(true);
expect(getCachedStyle(circle, 'fill')).toBe('red');
setCacheStyle(circle, 'fill', 'yellow');

View File

@ -9,8 +9,8 @@ describe('graphlib', () => {
{ id: 'node-3', data: { value: 2 }, style: { opacity: 0.5 } },
].map(toGraphlibData),
).toEqual([
{ id: 'node-1', data: { id: 'node-1' } },
{ id: 'node-2', data: { id: 'node-2', data: { value: 1 } } },
{ id: 'node-1', data: { id: 'node-1', data: {}, style: {} } },
{ id: 'node-2', data: { id: 'node-2', data: { value: 1 }, style: {} } },
{ id: 'node-3', data: { id: 'node-3', data: { value: 2 }, style: { opacity: 0.5 } } },
]);
@ -24,7 +24,7 @@ describe('graphlib', () => {
id: 'edge-1',
source: 'node-1',
target: 'node-2',
data: { id: 'edge-1', source: 'node-1', target: 'node-2' },
data: { id: 'edge-1', source: 'node-1', target: 'node-2', data: {}, style: {} },
},
{
id: 'edge-2',
@ -35,6 +35,27 @@ describe('graphlib', () => {
]);
});
it('data isolation', () => {
const raw = {
id: 'node-3',
data: { basic: 2, array: [1, 2, 3], object: { a: 1 } },
style: { x: 100, y: 100, opacity: 0.5, size: [100, 100] },
};
const graphlibData = toGraphlibData(raw);
expect(graphlibData.data).toEqual(raw);
Object.assign(graphlibData.data.data!, { basic: 3, array: [4, 5, 6], object: { b: 2 } });
expect(raw.data).toEqual({ basic: 2, array: [1, 2, 3], object: { a: 1 } });
expect(graphlibData.data.data).toEqual({ basic: 3, array: [4, 5, 6], object: { b: 2 } });
graphlibData.data.style!.x = 200;
graphlibData.data.style!.size = [200, 200];
expect(raw.style).toEqual({ x: 100, y: 100, opacity: 0.5, size: [100, 100] });
});
it('toG6Data', () => {
expect(
[

View File

@ -1,4 +1,4 @@
import { idOf } from '@/src/utils/id';
import { idOf, parentIdOf } from '@/src/utils/id';
describe('id', () => {
it('idOf', () => {
@ -7,4 +7,9 @@ describe('id', () => {
expect(idOf({ source: 'node-1', target: 'edge-1' })).toBe(`node-1-edge-1`);
expect(() => idOf({})).toThrow();
});
it('parentIdOf', () => {
expect(parentIdOf({ style: { parentId: '1' } })).toBe('1');
expect(parentIdOf({})).toBeUndefined();
});
});

View File

@ -1,4 +1,4 @@
import { isComboLayout, isPositionSpecified, isTreeLayout, pickLayoutResult } from '@/src/utils/layout';
import { isComboLayout, isPositionSpecified, isTreeLayout } from '@/src/utils/layout';
describe('layout', () => {
it('isComboLayout', () => {
@ -14,51 +14,6 @@ describe('layout', () => {
expect(isTreeLayout({ type: 'mindmap' })).toBe(true);
});
it('pickLayoutResult', () => {
expect(pickLayoutResult({ nodes: [], edges: [] })).toEqual({ nodes: {}, edges: {} });
expect(
pickLayoutResult({
nodes: [{ id: 'node-1', data: { x: 100, y: 100 } }],
edges: [{ id: 'edge-1', source: 'node-1', target: 'node-2', data: { weight: 1 } }],
}),
).toEqual({
nodes: {
'node-1': [100, 100],
},
edges: {
'edge-1': {},
},
});
expect(
pickLayoutResult({
nodes: [{ id: 'node-1', data: { x: 100, y: 100, z: 100 } }],
edges: [],
}),
).toEqual({
nodes: {
'node-1': [100, 100, 100],
},
edges: {},
});
expect(
pickLayoutResult({
nodes: [
{ id: 'node-1', data: { x: 100, y: 100 } },
{ id: 'node-2', data: { x: 150, y: 100 } },
{ id: 'node-3', data: { x: 100, y: 150 } },
],
edges: [],
}),
).toEqual({
nodes: {
'node-1': [100, 100],
'node-2': [150, 100],
'node-3': [100, 150],
},
edges: {},
});
});
it('isPositionSpecified', () => {
expect(isPositionSpecified({})).toBe(false);
expect(isPositionSpecified({ x: 100 })).toBe(false);

View File

@ -0,0 +1,50 @@
import { BaseShape } from '@/src/elements/shapes/base-shape';
import { Circle } from '@antv/g';
class Shape extends BaseShape<{ visibility: 'visible' | 'hidden' }> {
render() {
this.upsert('visibleShape', Circle, { r: 10 }, this);
this.upsert('hiddenShape', Circle, { r: 10, visibility: 'hidden' }, this);
}
}
describe('visibility', () => {
it('setVisibility', () => {
const shape = new Shape({});
// @ts-expect-error shapeMap is private
const vShape = shape.shapeMap.visibleShape;
// @ts-expect-error shapeMap is private
const hShape = shape.shapeMap.hiddenShape;
expect(shape.style.visibility).toBe(undefined);
expect(vShape.style.visibility).toBe(undefined);
expect(hShape.style.visibility).toBe('hidden');
shape.update({ visibility: 'hidden' });
expect(shape.style.visibility).toBe('hidden');
expect(vShape.style.visibility).toBe('hidden');
expect(hShape.style.visibility).toBe('hidden');
shape.update({ visibility: 'visible' });
expect(shape.style.visibility).toBe('visible');
expect(vShape.style.visibility).toBe('visible');
expect(hShape.style.visibility).toBe('hidden');
});
it('setVisibility default is hidden', () => {
const shape = new Shape({ style: { visibility: 'hidden' } });
// @ts-expect-error shapeMap is private
const vShape = shape.shapeMap.visibleShape;
// @ts-expect-error shapeMap is private
const hShape = shape.shapeMap.hiddenShape;
expect(shape.style.visibility).toBe('hidden');
expect(vShape.style.visibility).toBe('hidden');
expect(hShape.style.visibility).toBe('hidden');
shape.update({ visibility: 'visible' });
expect(shape.style.visibility).toBe('visible');
expect(vShape.style.visibility).toBe('visible');
expect(hShape.style.visibility).toBe('hidden');
});
});

View File

@ -196,7 +196,7 @@ export class DragNode extends BaseBehavior<DragNodeOptions> {
private showEdges() {
if (this.options.shadow || this.hiddenEdges.length === 0) return;
this.context.graph.setElementVisibility(this.hiddenEdges, 'visible');
this.context.graph.showElement(this.hiddenEdges);
this.hiddenEdges = [];
}
@ -204,18 +204,13 @@ export class DragNode extends BaseBehavior<DragNodeOptions> {
const { hideEdges, shadow } = this.options;
if (hideEdges === 'none' || shadow) return;
const { graph } = this.context;
let hiddenEdges: ID[] = [];
if (hideEdges === 'all') {
hiddenEdges = graph.getEdgeData().map((edge) => idOf(edge));
} else {
const edgeSet = new Set<ID>();
this.target.forEach((id) => {
graph.getRelatedEdgesData(id, hideEdges).forEach((edge) => edgeSet.add(idOf(edge)));
});
hiddenEdges = Array.from(edgeSet);
if (hideEdges === 'all') this.hiddenEdges = graph.getEdgeData().map(idOf);
else {
this.hiddenEdges = Array.from(
new Set(this.target.map((id) => graph.getRelatedEdgesData(id, hideEdges).map(idOf)).flat()),
);
}
this.hiddenEdges = hiddenEdges;
graph.setElementVisibility(hiddenEdges, 'hidden');
graph.hideElement(this.hiddenEdges);
}
public destroy() {

View File

@ -4,6 +4,7 @@ import { deepMix } from '@antv/util';
import type { Keyframe } from '../../types';
import { createAnimationsProxy, preprocessKeyframes } from '../../utils/animation';
import { updateStyle } from '../../utils/element';
import { setVisibility } from '../../utils/visibility';
export interface BaseShapeStyleProps extends BaseStyleProps {
x?: number | string;
@ -15,6 +16,7 @@ export abstract class BaseShape<StyleProps extends BaseShapeStyleProps> extends
super(options);
this.render(this.attributes as Required<StyleProps>, this);
this.setVisibility();
this.bindEvents();
}
@ -80,7 +82,8 @@ export abstract class BaseShape<StyleProps extends BaseShapeStyleProps> extends
public update(attr: Partial<StyleProps> = {}): void {
this.attr(deepMix({}, this.attributes, attr));
return this.render(this.attributes as Required<StyleProps>, this);
this.render(this.attributes as Required<StyleProps>, this);
this.setVisibility();
}
/**
@ -140,4 +143,9 @@ export abstract class BaseShape<StyleProps extends BaseShapeStyleProps> extends
return createAnimationsProxy(animationMap);
}
private setVisibility() {
const { visibility } = this.attributes;
setVisibility(this, visibility);
}
}

View File

@ -14,6 +14,7 @@ import type {
PartialEdgeData,
PartialGraphData,
PartialNodeLikeData,
State,
} from '../types';
import type { EdgeDirection } from '../types/edge';
import type { ElementType } from '../types/element';
@ -21,7 +22,7 @@ import type { Point } from '../types/point';
import { cloneElementData, mergeElementsData } from '../utils/data';
import { arrayDiff } from '../utils/diff';
import { toG6Data, toGraphlibData } from '../utils/graphlib';
import { idOf } from '../utils/id';
import { idOf, parentIdOf } from '../utils/id';
import { dfs } from '../utils/traverse';
export class DataController {
@ -179,6 +180,20 @@ export class DataController {
return this.model.getChildren(id, TREE_KEY).map((node) => node.data);
}
/**
* <zh/>
*
* <en/> Get the data of the specified type of element
* @param elementType - <zh/> | <en/> element type
* @returns <zh/> | <en/> element data
*/
public getElementData(elementType: ElementType) {
if (elementType === 'node') return this.getNodeData();
if (elementType === 'edge') return this.getEdgeData();
if (elementType === 'combo') return this.getComboData();
return [];
}
/**
* <zh/> ID
*
@ -189,7 +204,6 @@ export class DataController {
public getElementsData(ids: ID[]): ElementDatum[] {
return ids.map((id) => {
const type = this.getElementType(id);
if (type === 'node') return this.getNodeData([id])[0];
else if (type === 'edge') return this.getEdgeData([id])[0];
return this.getComboData([id])[0];
@ -211,6 +225,15 @@ export class DataController {
}, [] as NodeLikeData[]);
}
public getElementDataByState(elementType: ElementType, state: string) {
const elementData = this.getElementData(elementType);
return elementData.filter((datum) => datum.style?.states?.includes(state));
}
public getElementState(id: ID): State[] {
return this.getElementsData([id])?.[0]?.style?.states || [];
}
public hasNode(id: ID) {
return this.model.hasNode(id) && !this.isCombo(id);
}
@ -327,7 +350,7 @@ export class DataController {
data.forEach((datum) => {
const id = idOf(datum);
const parentId = datum?.style?.parentId;
const parentId = parentIdOf(datum);
if (parentId !== undefined) {
model.attachTreeStructure(COMBO_KEY);
model.setParent(id, parentId, COMBO_KEY);
@ -541,10 +564,10 @@ export class DataController {
this.model.getChildren(id, COMBO_KEY).forEach((child) => {
const childData = child.data;
const childId = idOf(childData);
this.model.setParent(idOf(childData), data?.style?.parentId, COMBO_KEY);
this.model.setParent(idOf(childData), parentIdOf(data), COMBO_KEY);
const value = mergeElementsData(childData, {
id: idOf(childData),
style: { parentId: data?.style?.parentId },
style: { parentId: parentIdOf(data) },
});
this.pushChange({
value,

View File

@ -3,14 +3,14 @@
import type { BaseStyleProps, DisplayObject, IAnimation } from '@antv/g';
import { Group } from '@antv/g';
import type { ID } from '@antv/graphlib';
import { groupBy, isUndefined, pick } from '@antv/util';
import { groupBy } from '@antv/util';
import { executor as animationExecutor } from '../animations';
import type { AnimationContext } from '../animations/types';
import { AnimationType, ChangeTypeEnum, GraphEvent } from '../constants';
import type { BaseNode } from '../elements/nodes';
import type { BaseShape } from '../elements/shapes';
import { getExtension } from '../registry';
import type { ComboData, EdgeData, GraphData, NodeData } from '../spec';
import type { ComboData, EdgeData, NodeData } from '../spec';
import type { AnimationStage } from '../spec/element/animation';
import type { EdgeStyle } from '../spec/element/edge';
import type { NodeLikeStyle } from '../spec/element/node';
@ -22,48 +22,21 @@ import type {
ElementData,
ElementDatum,
ElementType,
LayoutResult,
Node,
Positions,
State,
States,
StyleIterationContext,
ZIndex,
} from '../types';
import { executeAnimatableTasks, inferDefaultValue, withAnimationCallbacks } from '../utils/animation';
import { deduplicate } from '../utils/array';
import { cacheStyle, getCachedStyle, setCacheStyle } from '../utils/cache';
import { cacheStyle, getCachedStyle, hasCachedStyle } from '../utils/cache';
import { reduceDataChanges } from '../utils/change';
import { isEmptyData } from '../utils/data';
import { updateStyle } from '../utils/element';
import type { BaseEvent } from '../utils/event';
import {
AnimateEvent,
ElementStateChangeEvent,
ElementTranslateEvent,
ElementVisibilityChangeEvent,
ElementZIndexChangeEvent,
GraphLifeCycleEvent,
} from '../utils/event';
import { idOf } from '../utils/id';
import { AnimateEvent, GraphLifeCycleEvent } from '../utils/event';
import { idOf, parentIdOf } from '../utils/id';
import { assignColorByPalette, parsePalette } from '../utils/palette';
import { computeElementCallbackStyle } from '../utils/style';
import { setVisibility } from '../utils/visibility';
import type { RuntimeContext } from './types';
type AnimationExecutor = (
id: ID,
shape: DisplayObject,
originalStyle: Record<string, unknown>,
modifiedStyle?: Record<string, unknown>,
) => IAnimation | null;
type RenderContext = {
animator?: AnimationExecutor;
/** <zh/> 是否使用动画,默认为 true | <en/> Whether to use animation, default is true */
animation: boolean;
};
export class ElementController {
private context: RuntimeContext;
@ -79,7 +52,6 @@ export class ElementController {
constructor(context: RuntimeContext) {
this.context = context;
this.initElementState(context.options.data || {});
}
public init() {
@ -98,40 +70,14 @@ export class ElementController {
graph.emit(event.type, event);
}
private getElementData(elementType: ElementType, ids?: ID[]) {
const { model } = this.context;
switch (elementType) {
case 'node':
return model.getNodeData(ids);
case 'edge':
return model.getEdgeData(ids);
case 'combo':
return model.getComboData(ids);
default:
return [];
}
}
private forEachElementData(callback: (elementType: ElementType, elementData: ElementData) => void) {
const elementTypes: ElementType[] = ['node', 'edge', 'combo'];
elementTypes.forEach((elementType) => {
const elementData = this.getElementData(elementType);
const elementData = this.context.model.getElementData(elementType);
callback(elementType, elementData);
});
}
private runtimeStyle: Record<ID, Record<string, unknown>> = {};
private getRuntimeStyle(id: ID) {
return this.runtimeStyle[id] || {};
}
private setRuntimeStyle(id: ID, style: Record<string, unknown>) {
if (!this.runtimeStyle[id]) this.runtimeStyle[id] = { ...style };
else Object.assign(this.runtimeStyle[id], style);
}
private getTheme(elementType: ElementType) {
const { theme } = this.context.options;
if (!theme) return {};
@ -170,8 +116,8 @@ export class ElementController {
};
}
public getDataStyle(elementType: ElementType, id: ID): NodeLikeStyle | EdgeStyle {
const datum = this.getElementData(elementType, [id])?.[0];
public getDataStyle(id: ID): NodeLikeStyle | EdgeStyle {
const datum = this.context.model.getElementsData([id])?.[0];
return datum?.style || {};
}
@ -202,55 +148,13 @@ export class ElementController {
return this.defaultStyle[id] || {};
}
public elementState: Record<ID, State[]> = {};
/**
* <zh/>
*
* <en/> Initialize element state from data
*/
private initElementState(data: GraphData) {
const { nodes = [], edges = [], combos = [] } = data;
[...nodes, ...edges, ...combos].forEach((elementData) => {
const states = elementData.style?.states || [];
const id = idOf(elementData);
this.elementState[id] = states;
});
}
public setElementsState(states: States) {
const graphData: Required<GraphData> = { nodes: [], edges: [], combos: [] };
Object.entries(states).forEach(([id, state]) => {
this.elementState[id] = state;
const elementType = this.context.model.getElementType(id);
const datum = this.context.model.getElementsData([id])[0];
this.computeElementStatesStyle(elementType, state, { datum, index: 0, elementData: [datum] as ElementData });
graphData[`${elementType}s`].push(datum as any);
});
const tasks = this.getUpdateTasks(graphData, { animation: false });
executeAnimatableTasks(tasks, {
before: () => this.emit(new ElementStateChangeEvent(GraphEvent.BEFORE_ELEMENT_STATE_CHANGE, states)),
beforeAnimate: (animation) =>
this.emit(new AnimateEvent(GraphEvent.BEFORE_ANIMATE, AnimationType.ELEMENT_STATE_CHANGE, animation, states)),
afterAnimate: (animation) =>
this.emit(new AnimateEvent(GraphEvent.AFTER_ANIMATE, AnimationType.ELEMENT_STATE_CHANGE, animation, states)),
after: () => this.emit(new ElementStateChangeEvent(GraphEvent.AFTER_ELEMENT_STATE_CHANGE, states)),
});
}
/**
* <zh/>
*
* <en/> Get the state of the specified element
* @param id - <zh/> id | <en/> element id
* @returns <zh/> | <en/> element state array
*/
public getElementStates(id: ID): State[] {
return this.elementState[id] || [];
private getElementState(id: ID) {
try {
const { model } = this.context;
return model.getElementState(id);
} catch {
return [];
}
}
private stateStyle: Record<ID, Record<string, unknown>> = {};
@ -289,7 +193,7 @@ export class ElementController {
elementData
.filter((datum) => ids === undefined || ids.includes(idOf(datum)))
.forEach((datum, index) => {
const states = this.getElementStates(idOf(datum));
const states = this.getElementState(idOf(datum));
this.computeElementStatesStyle(elementType, states, { datum, index, elementData });
});
});
@ -359,30 +263,13 @@ export class ElementController {
{
originalStyle,
modifiedStyle,
states: this.getElementStates(id),
states: this.getElementState(id),
...context,
},
);
};
}
/**
* <zh/>
*
* <en/> Get elements with the specified state
* @param state - <zh/> | <en/> state or state array
* @returns <zh/> id | <en/> key-value pairs of element id and element instance
*/
public getElementsByState(state: State | State[]): Record<string, DisplayObject> {
return Object.fromEntries(
Object.entries(this.elementState)
.filter(([, states]) => {
return (Array.isArray(state) ? state : [state]).every((s) => states.includes(s));
})
.map(([id]) => [id, this.elementMap[id]]),
);
}
/**
* <zh/>
*
@ -420,22 +307,12 @@ export class ElementController {
// 优先级(从低到高) Priority (from low to high):
const themeStyle = this.getThemeStyle(elementType);
const paletteStyle = this.getPaletteStyle(id);
const dataStyle = this.getDataStyle(elementType, id);
const dataStyle = this.getDataStyle(id);
const defaultStyle = this.getDefaultStyle(id);
const themeStateStyle = this.getThemeStateStyle(elementType, this.getElementStates(id));
const themeStateStyle = this.getThemeStateStyle(elementType, this.getElementState(id));
const stateStyle = this.getStateStyle(id);
const runtimeStyle = this.getRuntimeStyle(id);
const style = Object.assign(
{},
themeStyle,
paletteStyle,
dataStyle,
defaultStyle,
themeStateStyle,
stateStyle,
runtimeStyle,
);
const style = Object.assign({}, themeStyle, paletteStyle, dataStyle, defaultStyle, themeStateStyle, stateStyle);
if (elementType === 'edge') {
Object.assign(style, this.getEdgeEndsContext(id));
@ -452,20 +329,45 @@ export class ElementController {
return style;
}
// ---------- Render API ----------
/**
* <zh/>
*
* <en/> start render process
*/
public async draw() {
public async draw(drawContext: DrawContext = { animation: true }) {
const drawData = this.computeDrawData();
if (!drawData) return;
this.init();
// 计算样式 / Calculate style
this.computeStyle();
// 创建渲染任务 / Create render task
const { add, update, remove } = drawData;
const destroyTasks = this.getDestroyTasks(remove, drawContext);
const createTasks = this.getCreateTasks(add, drawContext);
const updateTasks = this.getUpdateTasks(update, drawContext);
await executeAnimatableTasks(
[...destroyTasks, ...createTasks, ...updateTasks],
drawContext.silence
? {}
: {
before: () => this.emit(new GraphLifeCycleEvent(GraphEvent.BEFORE_DRAW)),
beforeAnimate: (animation) =>
this.emit(new AnimateEvent(GraphEvent.BEFORE_ANIMATE, AnimationType.DRAW, animation, drawData)),
afterAnimate: (animation) =>
this.emit(new AnimateEvent(GraphEvent.AFTER_ANIMATE, AnimationType.DRAW, animation, drawData)),
after: () => this.emit(new GraphLifeCycleEvent(GraphEvent.AFTER_DRAW)),
},
)?.finished;
}
private computeDrawData() {
const { model } = this.context;
const tasks = reduceDataChanges(model.getChanges());
if (tasks.length === 0) return;
this.init();
if (tasks.length === 0) return null;
const {
NodeAdded = [],
@ -479,83 +381,80 @@ export class ElementController {
ComboRemoved = [],
} = groupBy(tasks, (change) => change.type) as unknown as Record<`${ChangeTypeEnum}`, DataChange[]>;
const dataOf = <T extends DataChange['value']>(data: DataChange[]) => data.map((datum) => datum.value) as T[];
const dataOf = <T extends DataChange['value']>(data: DataChange[]) =>
new Map(
data.map((datum) => {
const data = datum.value;
return [idOf(data), data] as [ID, T];
}),
);
// 计算要新增的元素 / compute elements to add
const nodesToAdd = dataOf<NodeData>(NodeAdded);
const edgesToAdd = dataOf<EdgeData>(EdgeAdded);
const combosToAdd = dataOf<ComboData>(ComboAdded);
// 计算要更新的元素 / compute elements to update
const nodesToUpdate = dataOf<NodeData>(NodeUpdated);
const edgesToUpdate = dataOf<EdgeData>(EdgeUpdated);
const combosToUpdate = dataOf<ComboData>(ComboUpdated);
this.initElementState({ nodes: nodesToUpdate, edges: edgesToUpdate, combos: combosToUpdate });
// 计算要删除的元素 / compute elements to remove
const nodesToRemove = dataOf<NodeData>(NodeRemoved);
const edgesToRemove = dataOf<EdgeData>(EdgeRemoved);
const combosToRemove = dataOf<ComboData>(ComboRemoved);
// 如果更新了节点,需要更新连接的边
// If the node is updated, the connected edge and the combo it is in need to be updated
// TODO 待优化,仅考虑影响边更新的属性,如 x, y, size 等
nodesToUpdate
.map((node) => model.getRelatedEdgesData(idOf(node)))
.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(
[
...nodesToAdd,
...nodesToUpdate,
...nodesToRemove,
...combosToAdd,
...combosToUpdate,
...combosToRemove,
].reduce((acc, curr) => {
const parentId = curr?.style?.parentId;
if (parentId) acc.push(parentId);
return acc;
}, [] as ID[]),
)
.forEach((combo) => combosToUpdate.push(combo));
// 计算样式 / Calculate style
this.computeStyle();
// 创建渲染任务 / Create render task
const renderContext = { animation: true };
const dataToDestroy = { nodes: nodesToRemove, edges: edgesToRemove, combos: combosToRemove };
const destroyTasks = this.getDestroyTasks(dataToDestroy, renderContext);
const dataToCreate = { nodes: nodesToAdd, edges: edgesToAdd, combos: combosToAdd };
const createTasks = this.getCreateTasks(dataToCreate, renderContext);
const dataToUpdate = {
nodes: nodesToUpdate,
edges: deduplicate(edgesToUpdate, idOf),
combos: deduplicate(combosToUpdate, idOf),
const input: FlowData = {
add: {
nodes: dataOf<NodeData>(NodeAdded),
edges: dataOf<EdgeData>(EdgeAdded),
combos: dataOf<ComboData>(ComboAdded),
},
update: {
nodes: dataOf<NodeData>(NodeUpdated),
edges: dataOf<EdgeData>(EdgeUpdated),
combos: dataOf<ComboData>(ComboUpdated),
},
remove: {
nodes: dataOf<NodeData>(NodeRemoved),
edges: dataOf<EdgeData>(EdgeRemoved),
combos: dataOf<ComboData>(ComboRemoved),
},
};
const updateTasks = this.getUpdateTasks(dataToUpdate, renderContext);
const diffData = { create: dataToCreate, update: dataToUpdate, destroy: dataToDestroy };
return executeAnimatableTasks([...destroyTasks, ...createTasks, ...updateTasks], {
before: () => this.emit(new GraphLifeCycleEvent(GraphEvent.BEFORE_DRAW)),
beforeAnimate: (animation) =>
this.emit(new AnimateEvent(GraphEvent.BEFORE_ANIMATE, AnimationType.DRAW, animation, diffData)),
afterAnimate: (animation) =>
this.emit(new AnimateEvent(GraphEvent.AFTER_ANIMATE, AnimationType.DRAW, animation, diffData)),
after: () => this.emit(new GraphLifeCycleEvent(GraphEvent.AFTER_DRAW)),
})?.finished.then(() => {});
const output = [this.updateRelatedEdgeFlow, this.updateRelatedComboFlow].reduce((data, flow) => flow(data), input);
return output;
}
private createElement(elementType: ElementType, datum: ElementDatum, context: RenderContext) {
/**
*
* If the node is updated, the connected edge and the combo it is in need to be updated
*/
private updateRelatedEdgeFlow: Flow = (input) => {
const { model } = this.context;
const {
update: { nodes, edges },
} = input;
nodes.forEach((_, id) => {
const relatedEdgesData = model.getRelatedEdgesData(id);
relatedEdgesData.forEach((edge) => edges.set(idOf(edge), edge));
});
return input;
};
/**
* // combo combo
* If nodes or combos are operated (added/updated/removed), the related combo needs to be updated
*/
private updateRelatedComboFlow: Flow = (input) => {
const { add, update, remove } = input;
const { model } = this.context;
const comboIds = [add.nodes, update.nodes, remove.nodes, add.combos, update.combos, remove.combos].reduce(
(acc, data) => {
data.forEach((datum) => {
const parentId = parentIdOf(datum);
if (parentId !== undefined) acc.push(parentId);
});
return acc;
},
[] as ID[],
);
model.getComboData(comboIds).forEach((combo, index) => update.combos.set(comboIds[index], combo));
return input;
};
private createElement(elementType: ElementType, datum: ElementDatum, context: DrawContext) {
const { animator } = context;
const id = idOf(datum);
const currentShape = this.getElement(id);
@ -581,11 +480,9 @@ export class ElementController {
return () => animator?.(id, shape, { ...shape.attributes, opacity: 0 }) || null;
}
private getCreateTasks(data: GraphData, context: Omit<RenderContext, 'animator'>): AnimatableTask[] {
if (isEmptyData(data)) return [];
const { nodes = [], edges = [], combos = [] } = data;
const iteration: [ElementType, ElementData][] = [
private getCreateTasks(data: ProcedureData, context: Omit<DrawContext, 'animator'>): AnimatableTask[] {
const { nodes, edges, combos } = data;
const iteration: [ElementType, Map<ID, ElementDatum>][] = [
['node', nodes],
['edge', edges],
['combo', combos],
@ -593,7 +490,7 @@ export class ElementController {
const tasks: AnimatableTask[] = [];
iteration.forEach(([elementType, elementData]) => {
if (elementData.length === 0) return;
if (elementData.size === 0) return;
const animator = this.getAnimationExecutor(elementType, 'enter');
elementData.forEach((datum) =>
tasks.push(() => this.createElement(elementType, datum, { ...context, animator })),
@ -603,7 +500,26 @@ export class ElementController {
return tasks;
}
private updateElement(elementType: ElementType, datum: ElementDatum, context: RenderContext) {
/**
* <zh/> show hide
* - show
* - hide
*
* hide showhide
*
*
*
* <en/> Due to the difference in the timing of show and hide
* - show: immediately shows the element and then executes the animation
* - hide: executes the animation first, and then hides the element
*
* If show is called immediately after hide,it hide the element after hide animation completed
*
* Therefore, the latest visibility of the element needs to be cached
*/
private latestElementVisibilityMap: WeakMap<DisplayObject, BaseStyleProps['visibility']> = new WeakMap();
private updateElement(elementType: ElementType, datum: ElementDatum, context: DrawContext) {
const { animator } = context;
const id = idOf(datum);
@ -621,88 +537,39 @@ export class ElementController {
};
}
// 如果是可见性更新 / If it is a visibility update
if (context.stage === 'visibility' && 'visibility' in style) {
// 缓存原始透明度 / Cache original opacity
if (!hasCachedStyle(shape, 'opacity')) cacheStyle(shape, 'opacity');
const originalOpacity = getCachedStyle(shape, 'opacity') ?? inferDefaultValue('opacity');
this.latestElementVisibilityMap.set(shape, style.visibility);
// show
if (style.visibility !== 'hidden') {
updateStyle(shape, { visibility: 'visible' });
return () => animator?.(id, shape, { ...shape.attributes, opacity: 0 }, { opacity: originalOpacity }) || null;
}
// hide
else if (style.visibility === 'hidden') {
return () =>
withAnimationCallbacks(
animator?.(id, shape, { ...shape.attributes, opacity: originalOpacity }, { opacity: 0 }) || null,
{
after: () => updateStyle(shape, { visibility: this.latestElementVisibilityMap.get(shape) }),
},
);
}
}
const originalStyle = { ...shape.attributes };
updateStyle(shape, style);
return () => animator?.(id, shape, originalStyle) || null;
}
public updateNodeLikePosition(positions: Positions, animation: boolean = true, edgeIds: ID[] = []) {
if (Object.keys(positions).length === 0) return null;
const { model } = this.context;
const animationsFilter: AnimationContext['animationsFilter'] = (animation) => !animation.shape;
const nodeAnimator = this.getAnimationExecutor('node', 'update', animation, { animationsFilter });
const comboAnimator = this.getAnimationExecutor('combo', 'update', animation, { animationsFilter });
const nodeTasks: AnimatableTask[] = [];
Object.entries(positions).forEach(([id, [x, y, z]]) => {
const element = this.getElement(id);
const elementType = this.context.model.isCombo(id) ? 'combo' : 'node';
if (!element) return;
// 更新原生位置属性,避免执行 render 以及 animation 调用 getXxxStyle 流程 / Update the native position attribute to avoid executing the render and animation calls to getXxxStyle
const animator = elementType === 'combo' ? comboAnimator : nodeAnimator;
const originalPosition = pick(element.attributes, ['x', 'y', 'z']);
const modifiedPosition = { x, y, z };
nodeTasks.push(() => {
this.setRuntimeStyle(id, modifiedPosition);
element.attr({ x, y, z });
return () => animator(id, element, originalPosition);
});
});
const edgeTasks = this.getUpdateTasks(
{
nodes: [],
edges: model.getEdgeData(
Object.keys(positions).reduce(
(acc, id) => {
if (!model.isCombo(id)) {
model.getRelatedEdgesData(id).forEach((edge) => acc.push(idOf(edge)));
}
return acc;
},
[...edgeIds],
),
),
combos: [],
},
{ animation },
);
return executeAnimatableTasks([...nodeTasks, ...edgeTasks], {
before: () => this.emit(new ElementTranslateEvent(GraphEvent.BEFORE_ELEMENT_TRANSLATE, positions)),
beforeAnimate: (animation) =>
this.emit(new AnimateEvent(GraphEvent.BEFORE_ANIMATE, AnimationType.ELEMENT_TRANSLATE, animation, positions)),
afterAnimate: (animation) =>
this.emit(new AnimateEvent(GraphEvent.AFTER_ANIMATE, AnimationType.ELEMENT_TRANSLATE, animation, positions)),
after: () => this.emit(new ElementTranslateEvent(GraphEvent.AFTER_ELEMENT_TRANSLATE, positions)),
});
}
/**
* <zh/>
*
* <en/> Update based on layout results
*/
public updateByLayoutResult(layoutResult: LayoutResult, animation: boolean = true) {
const { nodes: nodeLikeResults, edges: edgeResults } = layoutResult;
if (Object.keys(nodeLikeResults).length === 0 && Object.keys(edgeResults).length === 0) return null;
Object.entries(edgeResults).forEach(([id, style]) => {
this.setRuntimeStyle(id, style);
});
this.updateNodeLikePosition(nodeLikeResults, animation, Object.keys(edgeResults));
}
private getUpdateTasks(data: GraphData, context: Omit<RenderContext, 'animator'>): AnimatableTask[] {
if (isEmptyData(data)) return [];
const { nodes = [], edges = [], combos = [] } = data;
const iteration: [ElementType, ElementData][] = [
private getUpdateTasks(data: ProcedureData, context: Omit<DrawContext, 'animator'>): AnimatableTask[] {
const { nodes, edges, combos } = data;
const iteration: [ElementType, Map<ID, ElementDatum>][] = [
['node', nodes],
['edge', edges],
['combo', combos],
@ -711,8 +578,8 @@ export class ElementController {
const { animation } = context;
const tasks: AnimatableTask[] = [];
iteration.forEach(([elementType, elementData]) => {
if (elementData.length === 0) return [];
const animator = this.getAnimationExecutor(elementType, 'update', animation);
if (elementData.size === 0) return [];
const animator = this.getAnimationExecutor(elementType, context.stage || 'update', animation);
elementData.forEach((datum) =>
tasks.push(() => this.updateElement(elementType, datum, { ...context, animator })),
);
@ -721,7 +588,7 @@ export class ElementController {
return tasks;
}
private destroyElement(datum: ElementDatum, context: RenderContext) {
private destroyElement(datum: ElementDatum, context: DrawContext) {
const { animator } = context;
const id = idOf(datum);
const element = this.elementMap[id];
@ -739,11 +606,9 @@ export class ElementController {
};
}
private getDestroyTasks(data: GraphData, context: Omit<RenderContext, 'animator'>): AnimatableTask[] {
if (isEmptyData(data)) return [];
const { nodes = [], edges = [], combos = [] } = data;
const iteration: [ElementType, ElementData][] = [
private getDestroyTasks(data: ProcedureData, context: Omit<DrawContext, 'animator'>): AnimatableTask[] {
const { nodes, edges, combos } = data;
const iteration: [ElementType, Map<ID, ElementDatum>][] = [
['combo', combos],
['edge', edges],
['node', nodes],
@ -751,7 +616,7 @@ export class ElementController {
const tasks: AnimatableTask[] = [];
iteration.forEach(([elementType, elementData]) => {
if (elementData.length === 0) return [];
if (elementData.size === 0) return [];
const animator = this.getAnimationExecutor(elementType, 'exit');
elementData.forEach((datum) => tasks.push(() => this.destroyElement(datum, { ...context, animator })));
});
@ -765,132 +630,60 @@ export class ElementController {
delete this.paletteStyle[id];
delete this.defaultStyle[id];
delete this.stateStyle[id];
delete this.elementState[id];
delete this.elementMap[id];
delete this.shapeTypeMap[id];
delete this.runtimeStyle[id];
}
public async setElementsVisibility(ids: ID[], visibility: BaseStyleProps['visibility'], animation?: boolean) {
if (ids.length === 0) return null;
const isHide = visibility === 'hidden';
const nodes: ID[] = [];
const edges: ID[] = [];
const combos: ID[] = [];
ids.forEach((id) => {
const elementType = this.context.model.getElementType(id);
if (elementType === 'node') nodes.push(id);
if (elementType === 'edge') edges.push(id);
if (elementType === 'combo') combos.push(id);
});
const iteration: [ElementType, ID[]][] = [
['node', nodes],
['edge', edges],
['combo', combos],
];
const cacheOpacity = (element: DisplayObject) => {
if (isUndefined(getCachedStyle(element, 'opacity'))) cacheStyle(element, 'opacity');
};
const show = (id: ID, element: DisplayObject, animator: AnimationExecutor) => {
const originalOpacity =
getCachedStyle(element, 'opacity') ?? element.style.opacity ?? inferDefaultValue('opacity');
cacheOpacity(element);
// 由于 show 和 hide 的时序存在差异
// - show 立即将元素显示出来,然后执行动画
// - hide 先执行动画,然后隐藏元素
// 如果在调用 hide 后立即调用 showhide 动画完成后会将元素隐藏
// 因此需要缓存元素最新的可见性
//
// Due to the difference in the timing of show and hide
// - show: immediately shows the element and then executes the animation
// - hide: executes the animation first, and then hides the element
// If show is called immediately after hide,it hide the element after hide animation completed
// Therefore, the latest visibility of the element needs to be cached
setCacheStyle(element, 'visibility', visibility);
setVisibility(element, visibility);
return animator(id, element, { ...element.attributes, opacity: 0 }, { opacity: originalOpacity });
};
const hide = (id: ID, element: DisplayObject, animator: AnimationExecutor) => {
const originalOpacity =
getCachedStyle(element, 'opacity') ?? element.style.opacity ?? inferDefaultValue('opacity');
cacheOpacity(element);
setCacheStyle(element, 'visibility', visibility);
const animation = animator(id, element, { ...element.attributes, opacity: originalOpacity }, { opacity: 0 });
return withAnimationCallbacks(animation, {
after: () => setVisibility(element, getCachedStyle(element, 'visibility')),
});
};
const tasks: AnimatableTask[] = [];
iteration.forEach(([elementType, elementIds]) => {
if (elementIds.length === 0) return;
const animator = this.getAnimationExecutor(elementType, isHide ? 'hide' : 'show', animation);
elementIds.forEach((id) => {
const element = this.getElement(id);
if (element) {
tasks.push(() => () => (isHide ? hide : show)(id, element, animator));
}
});
});
return executeAnimatableTasks(tasks, {
before: () =>
this.emit(new ElementVisibilityChangeEvent(GraphEvent.BEFORE_ELEMENT_VISIBILITY_CHANGE, ids, visibility)),
beforeAnimate: (animation) =>
this.emit(
new AnimateEvent(GraphEvent.BEFORE_ANIMATE, AnimationType.ELEMENT_VISIBILITY_CHANGE, animation, {
ids,
visibility,
}),
),
afterAnimate: (animation) =>
this.emit(
new AnimateEvent(GraphEvent.AFTER_ANIMATE, AnimationType.ELEMENT_VISIBILITY_CHANGE, animation, {
ids,
visibility,
}),
),
after: () =>
this.emit(new ElementVisibilityChangeEvent(GraphEvent.AFTER_ELEMENT_VISIBILITY_CHANGE, ids, visibility)),
});
}
public setElementZIndex(id: ID, zIndex: ZIndex) {
const element = this.getElement(id);
if (!element) return;
this.emit(new ElementZIndexChangeEvent(GraphEvent.BEFORE_ELEMENT_Z_INDEX_CHANGE, id, zIndex));
if (typeof zIndex === 'number') element.attr({ zIndex });
else {
const elementType = this.context.model.getElementType(id);
const childNodes = this.container[elementType].childNodes as DisplayObject[];
const delta = zIndex === 'front' ? 1 : -1;
const parsedZIndex =
Math[zIndex === 'front' ? 'max' : 'min'](
...childNodes.map((node) => node.attributes.zIndex ?? inferDefaultValue('zIndex')),
) + delta;
element.attr({ zIndex: parsedZIndex });
}
this.emit(new ElementZIndexChangeEvent(GraphEvent.AFTER_ELEMENT_Z_INDEX_CHANGE, id, zIndex));
public getElementZIndexRange(elementType: ElementType) {
const childNodes = this.container[elementType].childNodes as DisplayObject[];
const zIndexes = childNodes.map((node) => node.attributes.zIndex ?? inferDefaultValue('zIndex')).sort();
return [zIndexes.at(0), zIndexes.at(-1)] as [number, number];
}
public destroy() {
Object.values(this.container).forEach((container) => container.destroy());
this.elementMap = {};
this.shapeTypeMap = {};
this.runtimeStyle = {};
this.defaultStyle = {};
this.elementState = {};
this.stateStyle = {};
this.paletteStyle = {};
// @ts-expect-error force delete
delete this.context;
}
}
type AnimationExecutor = (
id: ID,
shape: DisplayObject,
originalStyle: Record<string, unknown>,
modifiedStyle?: Record<string, unknown>,
) => IAnimation | null;
type DrawContext = {
animator?: AnimationExecutor;
/** <zh/> 是否使用动画,默认为 true | <en/> Whether to use animation, default is true */
animation: boolean;
/** <zh/> 当前绘制阶段 | <en/> Current draw stage */
stage?: AnimationStage;
/** <zh/> 是否不抛出事件 | <en/> Whether not to throw events */
silence?: boolean;
};
/**
* <zh/> Element Controller 使 Map
*
* <en/> In Element Controller, in order to improve query performance, use Map to store data uniformly
*/
type ProcedureData = {
nodes: Map<ID, NodeData>;
edges: Map<ID, EdgeData>;
combos: Map<ID, ComboData>;
};
type FlowData = {
add: ProcedureData;
update: ProcedureData;
remove: ProcedureData;
};
type Flow = (input: FlowData) => FlowData;

View File

@ -1,7 +1,7 @@
import EventEmitter from '@antv/event-emitter';
import type { AABB, BaseStyleProps, DataURLOptions } from '@antv/g';
import type { ID } from '@antv/graphlib';
import { debounce, isEqual, isFunction, isNumber, isString, omit } from '@antv/util';
import { debounce, isEqual, isFunction, isNumber, isObject, isString, omit } from '@antv/util';
import { GraphEvent } from '../constants';
import type {
BehaviorOptions,
@ -29,14 +29,13 @@ import type {
PartialGraphData,
PartialNodeLikeData,
Point,
Positions,
Position,
State,
Vector2,
ViewportAnimationEffectTiming,
ZIndex,
} from '../types';
import { sizeOf } from '../utils/dom';
import { GraphLifeCycleEvent, emit } from '../utils/event';
import { ElementStateChangeEvent, GraphLifeCycleEvent, emit } from '../utils/event';
import { parsePoint, toPointObject } from '../utils/point';
import { add, subtract } from '../utils/vector';
import { BehaviorController } from './behavior';
@ -303,14 +302,9 @@ export class Graph extends EventEmitter {
public getElementDataByState(elementType: 'edge', state: State): EdgeData[];
public getElementDataByState(elementType: 'combo', state: State): ComboData[];
public getElementDataByState(elementType: ElementType, state: State): ElementDatum[] {
const ids = Object.entries(this.context.element!.elementState)
.filter(([id, states]) => this.context.model.getElementType(id) === elementType && states.includes(state))
.map(([id]) => id);
return this.context.model.getElementsData(ids);
return this.context.model.getElementDataByState(elementType, state);
}
// ---------- end core API ----------
private createCanvas() {
if (this.context.canvas) return this.context.canvas;
@ -407,7 +401,6 @@ export class Graph extends EventEmitter {
this.destroyed = true;
}
// ---------- Runtime API ----------
public getCanvas(): Canvas {
return this.context.canvas;
}
@ -582,22 +575,83 @@ export class Graph extends EventEmitter {
return subtract([0, 0], this.getCanvasByViewport([0, 0]));
}
public translateElementBy(offsets: Positions, animation?: boolean): void {
const positions = Object.entries(offsets).reduce((acc, [id, offset]) => {
const curr = this.getElementPosition(id);
const next = add(curr, [...offset, 0].slice(0, 3) as Point);
acc[id] = next;
return acc;
}, {} as Positions);
/**
* <zh/>
*
* <en/> Translate the element by the specified distance
* @param id - <zh/> ID | <en/> element ID
* @param offset - <zh/> | <en/> offset
* @param animation - <zh/> | <en/> animation configuration
*/
public async translateElementBy(id: ID, offset: Position, animation?: boolean): Promise<void>;
/**
* <zh/>
*
* <en/> Batch translate elements by the specified distance
* @param offsets - <zh/> | <en/> offset configuration
* @param animation - <zh/> | <en/> animation configuration
*/
public async translateElementBy(offsets: Record<ID, Position>, animation?: boolean): Promise<void>;
public async translateElementBy(
args1: ID | Record<ID, Position>,
args2?: Position | boolean,
args3: boolean = true,
): Promise<void> {
const [config, animation] = isObject(args1)
? [args1, (args2 as boolean) ?? true]
: [{ [args1 as ID]: args2 as Position }, args3];
this.translateElementTo(positions, animation);
const positions = Object.entries(config).reduce(
(acc, [id, offset]) => {
const curr = this.getElementPosition(id);
const next = add(curr, [...offset, 0].slice(0, 3) as Point);
acc[id] = next;
return acc;
},
{} as Record<ID, Position>,
);
await this.translateElementTo(positions, animation);
}
public translateElementTo(positions: Positions, animation?: boolean): void {
this.context.element!.updateNodeLikePosition(positions, animation);
/**
* <zh/>
*
* <en/> Translate the element to the specified position
* @param id - <zh/> ID | <en/> element ID
* @param position - <zh/> | <en/> specified position
* @param animation - <zh/> | <en/> animation configuration
*/
public async translateElementTo(id: ID, position: Position, animation?: boolean): Promise<void>;
/**
* <zh/>
*
* <en/> Batch translate elements to the specified position
* @param positions - <zh/> | <en/> position configuration
* @param animation - <zh/> | <en/> animation configuration
*/
public async translateElementTo(positions: Record<ID, Position>, animation?: boolean): Promise<void>;
public async translateElementTo(
args1: ID | Record<ID, Position>,
args2?: boolean | Position,
args3: boolean = true,
): Promise<void> {
const dataToUpdate: Required<PartialGraphData> = { nodes: [], edges: [], combos: [] };
const [config, animation] = isObject(args1)
? [args1, (args2 as boolean) ?? true]
: [{ [args1 as ID]: args2 as Position }, args3];
Object.entries(config).forEach(([id, [x, y, z = 0]]) => {
const elementType = this.getElementType(id);
dataToUpdate[`${elementType}s`].push({ id, style: { x, y, z } });
});
this.updateData(dataToUpdate);
await this.context.element!.draw({ animation });
}
public getElementPosition(id: ID): Point {
public getElementPosition(id: ID): Position {
const element = this.context.element!.getElement(id)!;
const { x = 0, y = 0, z = 0 } = element.style;
return [x, y, z];
@ -607,45 +661,222 @@ export class Graph extends EventEmitter {
return omit(this.context.element!.getElement(id)!.attributes, ['context']);
}
/**
* <zh/>
*
* <en/> Set element visibility
* @param id - <zh/> ID | <en/> element ID
* @param visibility - <zh/> | <en/> visibility
* @param animation - <zh/> | <en/> animation configuration
*/
public async setElementVisibility(
id: ID | ID[],
id: ID,
visibility: BaseStyleProps['visibility'],
animation?: boolean,
): Promise<void>;
/**
* <zh/>
*
* <en/> Batch set element visibility
* @param visibility - <zh/> | <en/> visibility configuration
* @param animation - <zh/> | <en/> animation configuration
*/
public async setElementVisibility(
visibility: Record<ID, BaseStyleProps['visibility']>,
animation?: boolean,
): Promise<void>;
public async setElementVisibility(
args1: ID | Record<ID, BaseStyleProps['visibility']>,
args2?: boolean | BaseStyleProps['visibility'],
args3: boolean = true,
): Promise<void> {
await this.context.element!.setElementsVisibility(Array.isArray(id) ? id : [id], visibility, animation);
const [config, animation] = isObject(args1)
? [args1, (args2 as boolean) ?? true]
: [{ [args1]: args2 as BaseStyleProps['visibility'] }, args3];
const dataToUpdate: Required<PartialGraphData> = { nodes: [], edges: [], combos: [] };
Object.entries(config).forEach(([id, value]) => {
const elementType = this.getElementType(id);
dataToUpdate[`${elementType}s`].push({ id, style: { visibility: value } });
});
this.updateData(dataToUpdate);
await this.context.element!.draw({ animation, stage: 'visibility' });
}
/**
* <zh/>
*
* <en/> Show element
* @param id - <zh/> ID | <en/> element ID
* @param animation - <zh/> | <en/> animation configuration
*/
public async showElement(id: ID | ID[], animation?: boolean): Promise<void> {
const ids = Array.isArray(id) ? id : [id];
await this.setElementVisibility(
Object.fromEntries(ids.map((_id) => [_id, 'visible'] as [ID, BaseStyleProps['visibility']])),
animation,
);
}
/**
* <zh/>
*
* <en/> Hide element
* @param id - <zh/> ID | <en/> element ID
* @param animation - <zh/> | <en/> animation configuration
*/
public async hideElement(id: ID | ID[], animation?: boolean): Promise<void> {
const ids = Array.isArray(id) ? id : [id];
await this.setElementVisibility(
Object.fromEntries(ids.map((_id) => [_id, 'hidden'] as [ID, BaseStyleProps['visibility']])),
animation,
);
}
/**
* <zh/>
*
* <en/> Get element visibility
* @param id - <zh/> ID | <en/> element ID
* @returns <zh/> | <en/> element visibility
*/
public getElementVisibility(id: ID): BaseStyleProps['visibility'] {
const element = this.context.element!.getElement(id)!;
return element.style.visibility ?? 'visible';
return element?.style?.visibility ?? 'visible';
}
public setElementZIndex(id: ID | ID[], zIndex: ZIndex): void {
const ids = Array.isArray(id) ? id : [id];
ids.forEach((id) => {
this.context.element!.setElementZIndex(id, zIndex);
/**
* <zh/>
*
* <en/> Set element z-index
* @param id - <zh/> ID | <en/> element ID
* @param zIndex - <zh/> | <en/> z-index
*/
public async setElementZIndex(id: ID, zIndex: number): Promise<void>;
/**
* <zh/>
*
* <en/> Batch set element z-index
* @param zIndex - <zh/> | <en/> z-index configuration
*/
public async setElementZIndex(zIndex: Record<ID, number>): Promise<void>;
public async setElementZIndex(args1: ID | Record<ID, number>, args2?: number): Promise<void> {
const dataToUpdate: Required<PartialGraphData> = { nodes: [], edges: [], combos: [] };
const config = isObject(args1) ? args1 : { [args1 as ID]: args2 as number };
Object.entries(config).forEach(([id, value]) => {
const elementType = this.getElementType(id);
dataToUpdate[`${elementType}s`].push({ id, style: { zIndex: value } });
});
this.updateData(dataToUpdate);
await this.context.element!.draw();
}
/**
* <zh/>
*
* <en/> Bring the element to the front
* @param id - <zh/> ID | <en/> element ID
*/
public async frontElement(id: ID | ID[]): Promise<void> {
const ids = Array.isArray(id) ? id : [id];
await this.setElementZIndex(
Object.fromEntries(
ids.map((_id) => {
const elementType = this.getElementType(_id);
const [, max] = this.context.element!.getElementZIndexRange(elementType);
const parsedZIndex = max + 1;
return [_id, parsedZIndex];
}),
),
);
}
/**
* <zh/>
*
* <en/> Send the element to the back
* @param id - <zh/> ID | <en/> element ID
*/
public async backElement(id: ID | ID[]): Promise<void> {
const ids = Array.isArray(id) ? id : [id];
await this.setElementZIndex(
Object.fromEntries(
ids.map((_id) => {
const elementType = this.getElementType(_id);
const [min] = this.context.element!.getElementZIndexRange(elementType);
const parsedZIndex = min - 1;
return [_id, parsedZIndex];
}),
),
);
}
/**
* <zh/>
*
* <en/> Get element z-index
* @param id - <zh/> ID | <en/> element ID
* @returns <zh/> | <en/> element z-index
*/
public getElementZIndex(id: ID): BaseStyleProps['zIndex'] {
const element = this.context.element!.getElement(id)!;
return element.style.zIndex ?? 0;
}
public setElementState(id: ID | ID[], state: CallableValue<State | State[]>): void {
const states = (Array.isArray(id) ? id : [id]).reduce(
(acc, i) => {
const staticState = isFunction(state) ? state(this.getElementState(i)) : state;
acc[i] = Array.isArray(staticState) ? staticState : [staticState];
return acc;
},
{} as Record<ID, State[]>,
);
this.context.element!.setElementsState(states);
/**
* <zh/>
*
* <en/> Set element state
* @param id - <zh/> ID | <en/> element ID
* @param state - <zh/> | <en/> state
* @param animation - <zh/> | <en/> animation configuration
*/
public async setElementState(id: ID, state: State | State[], animation?: boolean): Promise<void>;
/**
* <zh/>
*
* <en/> Batch set element state
* @param state - <zh/> | <en/> state configuration
* @param animation - <zh/> | <en/> animation configuration
*/
public async setElementState(state: Record<ID, State | State[]>, animation?: boolean): Promise<void>;
public async setElementState(
args1: ID | Record<ID, State | State[]>,
args2?: boolean | State | State[],
args3: boolean = true,
): Promise<void> {
const [config, animation] = isObject(args1)
? [args1, (args2 as boolean) ?? true]
: [{ [args1]: args2 as State | State[] }, args3];
emit(this, new ElementStateChangeEvent(GraphEvent.BEFORE_ELEMENT_STATE_CHANGE, config));
const dataToUpdate: Required<PartialGraphData> = { nodes: [], edges: [], combos: [] };
Object.entries(config).forEach(([id, value]) => {
const elementType = this.getElementType(id);
dataToUpdate[`${elementType}s`].push({ id, style: { states: Array.isArray(value) ? value : [value] } });
});
this.updateData(dataToUpdate);
await this.context.element!.draw({ animation });
emit(this, new ElementStateChangeEvent(GraphEvent.AFTER_ELEMENT_STATE_CHANGE, config));
}
/**
* <zh/>
*
* <en/> Get element state
* @param id - <zh/> ID | <en/> element ID
* @returns <zh/> | <en/> element state
*/
public getElementState(id: ID): State[] {
return this.context.element!.getElementStates(id);
return this.context.model.getElementState(id);
}
public getElementRenderBounds(id: ID): AABB {

View File

@ -9,12 +9,13 @@ import type { BaseLayoutOptions } from '../layouts/types';
import { getExtension } from '../registry';
import type { EdgeData, NodeData } from '../spec';
import type { STDLayoutOptions } from '../spec/layout';
import type { NodeLikeData, Point, TreeData } from '../types';
import type { NodeLikeData, PartialGraphData, Point, TreeData } from '../types';
import { getAnimation } from '../utils/animation';
import { isVisible } from '../utils/element';
import { GraphLifeCycleEvent, emit } from '../utils/event';
import { createTreeStructure } from '../utils/graphlib';
import { isComboLayout, isPositionSpecified, isTreeLayout, pickLayoutResult } from '../utils/layout';
import { isComboLayout, isPositionSpecified, isTreeLayout } from '../utils/layout';
import { parsePoint } from '../utils/point';
import { parseSize } from '../utils/size';
import { dfs } from '../utils/traverse';
import { add } from '../utils/vector';
@ -88,7 +89,7 @@ export class LayoutController {
[] as LayoutMapping['nodes'],
);
this.updateElement({ nodes: positions, edges: [] }, false);
this.updateElementPosition({ nodes: positions, edges: [] }, false);
}
public async layout() {
@ -103,7 +104,7 @@ export class LayoutController {
const result = await this.stepLayout(model, { ...this.presetOptions, ...options });
if (!options.animation) {
this.updateElement(result, false);
this.updateElementPosition(result, false);
}
}
emit(graph, new GraphLifeCycleEvent(GraphEvent.AFTER_LAYOUT));
@ -132,7 +133,7 @@ export class LayoutController {
if (animation) {
return await layout.execute(model, {
onTick: (tickData: LayoutMapping) => {
this.updateElement(tickData, false);
this.updateElementPosition(tickData, false);
},
});
}
@ -146,7 +147,7 @@ export class LayoutController {
// 无迭代的布局,直接返回终态位置 / Layout without iteration, return final position directly
const layoutResult = await layout.execute(model);
if (animation) {
this.updateElement(layoutResult, animation);
this.updateElementPosition(layoutResult, animation);
}
return layoutResult;
}
@ -186,7 +187,7 @@ export class LayoutController {
});
if (animation) {
this.updateElement(layoutResult, animation);
this.updateElementPosition(layoutResult, animation);
}
return layoutResult;
@ -347,10 +348,24 @@ export class LayoutController {
return new Ctor(config);
}
private updateElement(layoutData: LayoutMapping, animation: boolean) {
const { element } = this.context;
private updateElementPosition(layoutData: LayoutMapping, animation: boolean) {
const { model, element } = this.context;
if (!element) return null;
element.updateByLayoutResult(pickLayoutResult(layoutData), animation);
const { nodes, edges } = layoutData;
const dataToUpdate: Required<PartialGraphData> = { nodes: [], edges: [], combos: [] };
nodes.forEach(({ id, data: { x, y, z = 0 } }) => {
dataToUpdate.nodes.push({ id, style: { x, y, z } });
});
edges.forEach(({ id, data: { controlPoints } }) => {
if (controlPoints?.length) {
dataToUpdate.edges.push({ id, style: { controlPoints: controlPoints.map(parsePoint) } });
}
});
model.updateData(dataToUpdate);
return element.draw({ animation, silence: true });
}
public destroy() {

View File

@ -13,4 +13,4 @@ export type AnimationOptions =
*
* <en/> Animation stage
*/
export type AnimationStage = 'enter' | 'exit' | 'update' | 'show' | 'hide' | 'transform';
export type AnimationStage = 'enter' | 'update' | 'exit' | 'visibility';

View File

@ -76,8 +76,7 @@ export const dark: Theme = {
animation: {
enter: 'fade',
exit: 'fade',
hide: 'fade',
show: 'fade',
visibility: 'fade',
update: [{ fields: ['x', 'y', 'fill', 'stroke'] }],
},
},
@ -127,8 +126,7 @@ export const dark: Theme = {
animation: {
enter: 'fade',
exit: 'fade',
hide: 'fade',
show: 'fade',
visibility: 'fade',
update: [{ fields: ['stroke'] }, { fields: ['path'], shape: 'key' }],
},
},
@ -184,8 +182,7 @@ export const dark: Theme = {
animation: {
enter: 'fade',
exit: 'fade',
hide: 'fade',
show: 'fade',
visibility: 'fade',
update: [
{ fields: ['cx', 'cy', 'r', 'x', 'y', 'width', 'height'], shape: 'key' },
{ fields: ['x', 'y'], shape: 'label' },

View File

@ -76,8 +76,7 @@ export const light: Theme = {
animation: {
enter: 'fade',
exit: 'fade',
hide: 'fade',
show: 'fade',
visibility: 'fade',
update: [{ fields: ['x', 'y', 'fill', 'stroke'] }],
},
},
@ -126,8 +125,7 @@ export const light: Theme = {
animation: {
enter: 'fade',
exit: 'fade',
hide: 'fade',
show: 'fade',
visibility: 'fade',
update: [{ fields: ['stroke'] }, { fields: ['path'], shape: 'key' }],
},
},
@ -180,8 +178,7 @@ export const light: Theme = {
animation: {
enter: 'fade',
exit: 'fade',
hide: 'fade',
show: 'fade',
visibility: 'fade',
update: [{ fields: ['x', 'y'] }, { fields: ['r', 'width', 'height'], shape: 'key' }],
},
},

View File

@ -10,7 +10,6 @@ export type * from './element';
export type * from './enum';
export type * from './event';
export type * from './graphlib';
export type * from './layout';
export type * from './node';
export type * from './padding';
export type * from './placement';
@ -23,4 +22,3 @@ export type * from './style';
export type * from './tree';
export type * from './vector';
export type * from './viewport';
export type * from './z-index';

View File

@ -1,7 +0,0 @@
import type { ID } from '@antv/graphlib';
import type { Positions } from './position';
export type LayoutResult = {
nodes: Positions;
edges: Record<ID, Record<string, unknown>>;
};

View File

@ -1,6 +1,3 @@
import type { ID } from '@antv/graphlib';
import type { Point } from './point';
export type Position = Point;
export type Positions = Record<ID, Position>;

View File

@ -1,5 +1 @@
import type { ID } from '@antv/graphlib';
export type State = string;
export type States = Record<ID, State[]>;

View File

@ -1 +0,0 @@
export type ZIndex = number | 'front' | 'back';

View File

@ -1,4 +1,7 @@
import type { DisplayObject } from '@antv/g';
import { get, set } from '@antv/util';
const CacheTargetKey = 'cachedStyle';
const getStyleCacheKey = (name: string) => `__${name}__`;
@ -11,12 +14,9 @@ const getStyleCacheKey = (name: string) => `__${name}__`;
*/
export function cacheStyle(element: DisplayObject, name: string | string[]) {
const names = Array.isArray(name) ? name : [name];
if (!get(element, CacheTargetKey)) set(element, CacheTargetKey, {});
names.forEach((n) => {
if (n in element.attributes) {
Object.assign(element.attributes, {
[getStyleCacheKey(n)]: element.attributes[n],
});
}
set(get(element, CacheTargetKey), getStyleCacheKey(n), element.attributes[n]);
});
}
@ -29,7 +29,19 @@ export function cacheStyle(element: DisplayObject, name: string | string[]) {
* @returns <zh/> | <en/> style value
*/
export function getCachedStyle(element: DisplayObject, name: string) {
return element.attributes[getStyleCacheKey(name)];
return get(element, [CacheTargetKey, getStyleCacheKey(name)]);
}
/**
* <zh/>
*
* <en/> Whether there is a cached style
* @param element - <zh/> | <en/> graphic element
* @param name - <zh/> | <en/> style name
* @returns <zh/> | <en/> Whether there is a cached style
*/
export function hasCachedStyle(element: DisplayObject, name: string) {
return getStyleCacheKey(name) in (get(element, CacheTargetKey) || {});
}
/**
@ -41,5 +53,6 @@ export function getCachedStyle(element: DisplayObject, name: string) {
* @param value - <zh/> | <en/> style value
*/
export function setCacheStyle(element: DisplayObject, name: string, value: any) {
element.attributes[getStyleCacheKey(name)] = value;
// element.attributes[getStyleCacheKey(name)] = value;
set(element, [CacheTargetKey, getStyleCacheKey(name)], value);
}

View File

@ -1,8 +1,8 @@
import type { BaseStyleProps, IAnimation } from '@antv/g';
import type { IAnimation } from '@antv/g';
import type { ID } from '@antv/graphlib';
import type { AnimationType, GraphEvent } from '../../constants';
import type { GraphData } from '../../spec';
import type { Positions, States, TransformOptions, ZIndex } from '../../types';
import type { State, TransformOptions } from '../../types';
export class BaseEvent {
constructor(public type: string) {}
@ -63,36 +63,7 @@ export class ViewportEvent extends BaseEvent {
export class ElementStateChangeEvent extends BaseEvent {
constructor(
type: GraphEvent.BEFORE_ELEMENT_STATE_CHANGE | GraphEvent.AFTER_ELEMENT_STATE_CHANGE,
public states: States,
) {
super(type);
}
}
export class ElementTranslateEvent extends BaseEvent {
constructor(
type: GraphEvent.BEFORE_ELEMENT_TRANSLATE | GraphEvent.AFTER_ELEMENT_TRANSLATE,
public positions: Positions,
) {
super(type);
}
}
export class ElementVisibilityChangeEvent extends BaseEvent {
constructor(
type: GraphEvent.BEFORE_ELEMENT_VISIBILITY_CHANGE | GraphEvent.AFTER_ELEMENT_VISIBILITY_CHANGE,
public ids: ID[],
public visibility: BaseStyleProps['visibility'],
) {
super(type);
}
}
export class ElementZIndexChangeEvent extends BaseEvent {
constructor(
type: GraphEvent.BEFORE_ELEMENT_Z_INDEX_CHANGE | GraphEvent.AFTER_ELEMENT_Z_INDEX_CHANGE,
public id: ID,
public zIndex: ZIndex,
public states: Record<ID, State | State[]>,
) {
super(type);
}

View File

@ -15,15 +15,11 @@ export function toGraphlibData(datums: NodeLikeData): Node<NodeLikeData>;
* @returns <zh/> graphlib | <en/> graphlib data
*/
export function toGraphlibData(data: NodeData | EdgeData | ComboData): Node<NodeLikeData> | Edge<EdgeData> {
if (isEdgeData(data)) {
const { style, data: customData, ...rest } = data;
return {
...rest,
data,
id: idOf(data),
} as Edge<EdgeData>;
}
return { id: idOf(data), data } as Node<NodeLikeData>;
const { id = idOf(data), style, data: customData, ...rest } = data;
const _data = { ...data, style: { ...style }, data: { ...customData } };
if (isEdgeData(data)) return { id, data: _data, ...rest } as Edge<EdgeData>;
return { id, data: _data } as Node<NodeLikeData>;
}
export function toG6Data<T extends EdgeData>(data: Edge<T>): T;

View File

@ -7,7 +7,7 @@ import { isEdgeData } from './is';
*
* <en/> get the id of node/edge/combo
* @param data - <zh/> //Combo | <en/> data of node/edge/combo
* @returns - <zh/> //Combo ID | <en/> ID of node/edge/combo
* @returns <zh/> //Combo ID | <en/> ID of node/edge/combo
*/
export function idOf(data: Partial<NodeData | EdgeData | ComboData>) {
if (isString(data.id) || isNumber(data.id)) return data.id;
@ -15,3 +15,14 @@ export function idOf(data: Partial<NodeData | EdgeData | ComboData>) {
throw new Error('The data does not have available id.');
}
/**
* <zh/> /Combo ID
*
* <en/> get the parent id of node/combo
* @param data - <zh/> /Combo | <en/> data of node/combo
* @returns <zh/> /Combo ID | <en/> parent id of node/combo
*/
export function parentIdOf(data: Partial<NodeData | ComboData>) {
return data.style?.parentId;
}

View File

@ -1,9 +1,5 @@
import type { LayoutMapping } from '@antv/layout';
import { isNumber } from '@antv/util';
import type { STDLayoutOptions } from '../spec/layout';
import type { LayoutResult } from '../types';
import { idOf } from './id';
import { parsePoint } from './point';
/**
* <zh/> combo
@ -31,40 +27,6 @@ export function isTreeLayout(options: STDLayoutOptions) {
return ['compact-box', 'mindmap', 'dendrogram', 'indented'].includes(type);
}
/**
* <zh/>
*
* <en/> Extract the style applied to the element from the result of the layout algorithm
* @param result - <zh/> | <en/> The result of the layout algorithm
* @returns <zh/> | <en/> Style applied to the element
*/
export function pickLayoutResult(result: LayoutMapping): LayoutResult {
const { nodes = [], edges = [] } = result;
return {
nodes: Object.fromEntries(
nodes.map((node) => {
const {
id,
data: { x, y, z },
} = node;
if (isNumber(z)) return [id, [x, y, z]];
return [id, [x, y]];
}),
),
edges: Object.fromEntries(
edges.map((edge) => {
const id = idOf(edge);
const { data } = edge;
const result: Record<string, unknown> = {};
if ('controlPoints' in data) result.controlPoints = data.controlPoints!.map(parsePoint);
// if ('points' in data) result.points = data.points!.map(parsePoint);
return [id, result];
}),
),
};
}
/**
* <zh/>
*

View File

@ -1,6 +1,9 @@
import type { BaseStyleProps, DisplayObject } from '@antv/g';
import { cacheStyle, getCachedStyle, hasCachedStyle } from './cache';
import { getDescendantShapes } from './shape';
const PropertyKey = 'visibility';
/**
* <zh/>
*
@ -13,9 +16,16 @@ import { getDescendantShapes } from './shape';
* <en/> After setting enableCSSParsing to false, the compound shape cannot inherit the parent attribute, so the same visibility needs to be applied to all child shapes
*/
export function setVisibility(shape: DisplayObject, visibility: BaseStyleProps['visibility']) {
shape.style.visibility = visibility;
const descendants = getDescendantShapes(shape);
descendants.forEach((descendant) => {
descendant.style.visibility = visibility;
const shapes = [shape, ...getDescendantShapes(shape)];
shapes.forEach((sp) => {
if (!hasCachedStyle(sp, PropertyKey)) cacheStyle(sp, PropertyKey);
const cachedVisibility = getCachedStyle(sp, PropertyKey);
// 如果子图形为隐藏状态,始终保持隐藏状态
// If the child shape is hidden, keep it hidden
if (shape !== sp && cachedVisibility === 'hidden') return;
sp.style.visibility = visibility;
});
}

View File

@ -51,8 +51,10 @@ const graph = new Graph({
graph.render();
graph.on('afterrender', () => {
graph.setElementState('line-active', 'active');
graph.setElementState('line-selected', 'selected');
graph.setElementState('line-highlight', 'highlight');
graph.setElementState('line-inactive', 'inactive');
graph.setElementState({
'line-active': 'active',
'line-selected': 'selected',
'line-highlight': 'highlight',
'line-inactive': 'inactive',
});
});

View File

@ -58,8 +58,10 @@ const graph = new Graph({
graph.render();
graph.on('afterrender', () => {
graph.setElementState('line-active', 'active');
graph.setElementState('line-selected', 'selected');
graph.setElementState('line-highlight', 'highlight');
graph.setElementState('line-inactive', 'inactive');
graph.setElementState({
'line-active': 'active',
'line-selected': 'selected',
'line-highlight': 'highlight',
'line-inactive': 'inactive',
});
});

View File

@ -51,8 +51,10 @@ const graph = new Graph({
graph.render();
graph.on('afterrender', () => {
graph.setElementState('line-active', 'active');
graph.setElementState('line-selected', 'selected');
graph.setElementState('line-highlight', 'highlight');
graph.setElementState('line-inactive', 'inactive');
graph.setElementState({
'line-active': 'active',
'line-selected': 'selected',
'line-highlight': 'highlight',
'line-inactive': 'inactive',
});
});

View File

@ -51,8 +51,10 @@ const graph = new Graph({
graph.render();
graph.on('afterrender', () => {
graph.setElementState('line-active', 'active');
graph.setElementState('line-selected', 'selected');
graph.setElementState('line-highlight', 'highlight');
graph.setElementState('line-inactive', 'inactive');
graph.setElementState({
'line-active': 'active',
'line-selected': 'selected',
'line-highlight': 'highlight',
'line-inactive': 'inactive',
});
});

View File

@ -59,8 +59,10 @@ const graph = new Graph({
graph.render();
graph.on('afterrender', () => {
graph.setElementState('line-active', 'active');
graph.setElementState('line-selected', 'selected');
graph.setElementState('line-highlight', 'highlight');
graph.setElementState('line-inactive', 'inactive');
graph.setElementState({
'line-active': 'active',
'line-selected': 'selected',
'line-highlight': 'highlight',
'line-inactive': 'inactive',
});
});

View File

@ -50,9 +50,11 @@ const graph = new Graph({
graph.render();
graph.on(GraphEvent.AFTER_RENDER, () => {
graph.setElementState('circle-active', 'active');
graph.setElementState('circle-selected', 'selected');
graph.setElementState('circle-highlight', 'highlight');
graph.setElementState('circle-inactive', 'inactive');
graph.setElementState('circle-disabled', 'disabled');
graph.setElementState({
'circle-active': 'active',
'circle-selected': 'selected',
'circle-highlight': 'highlight',
'circle-inactive': 'inactive',
'circle-disabled': 'disabled',
});
});

View File

@ -50,9 +50,11 @@ const graph = new Graph({
graph.render();
graph.on(GraphEvent.AFTER_RENDER, () => {
graph.setElementState('diamond-active', 'active');
graph.setElementState('diamond-selected', 'selected');
graph.setElementState('diamond-highlight', 'highlight');
graph.setElementState('diamond-inactive', 'inactive');
graph.setElementState('diamond-disabled', 'disabled');
graph.setElementState({
'diamond-active': 'active',
'diamond-selected': 'selected',
'diamond-highlight': 'highlight',
'diamond-inactive': 'inactive',
'diamond-disabled': 'disabled',
});
});

View File

@ -50,9 +50,11 @@ const graph = new Graph({
graph.render();
graph.on(GraphEvent.AFTER_RENDER, () => {
graph.setElementState('ellipse-active', 'active');
graph.setElementState('ellipse-selected', 'selected');
graph.setElementState('ellipse-highlight', 'highlight');
graph.setElementState('ellipse-inactive', 'inactive');
graph.setElementState('ellipse-disabled', 'disabled');
graph.setElementState({
'ellipse-active': 'active',
'ellipse-selected': 'selected',
'ellipse-highlight': 'highlight',
'ellipse-inactive': 'inactive',
'ellipse-disabled': 'disabled',
});
});

View File

@ -53,9 +53,11 @@ const graph = new Graph({
graph.render();
graph.on(GraphEvent.AFTER_RENDER, () => {
graph.setElementState('image-active', 'active');
graph.setElementState('image-selected', 'selected');
graph.setElementState('image-highlight', 'highlight');
graph.setElementState('image-inactive', 'inactive');
graph.setElementState('image-disabled', 'disabled');
graph.setElementState({
'image-active': 'active',
'image-selected': 'selected',
'image-highlight': 'highlight',
'image-inactive': 'inactive',
'image-disabled': 'disabled',
});
});

View File

@ -50,9 +50,11 @@ const graph = new Graph({
graph.render();
graph.on(GraphEvent.AFTER_RENDER, () => {
graph.setElementState('rect-active', 'active');
graph.setElementState('rect-selected', 'selected');
graph.setElementState('rect-highlight', 'highlight');
graph.setElementState('rect-inactive', 'inactive');
graph.setElementState('rect-disabled', 'disabled');
graph.setElementState({
'rect-active': 'active',
'rect-selected': 'selected',
'rect-highlight': 'highlight',
'rect-inactive': 'inactive',
'rect-disabled': 'disabled',
});
});

View File

@ -49,9 +49,11 @@ const graph = new Graph({
graph.render();
graph.on(GraphEvent.AFTER_RENDER, () => {
graph.setElementState('rect-active', 'active');
graph.setElementState('rect-selected', 'selected');
graph.setElementState('rect-highlight', 'highlight');
graph.setElementState('rect-inactive', 'inactive');
graph.setElementState('rect-disabled', 'disabled');
graph.setElementState({
'rect-active': 'active',
'rect-selected': 'selected',
'rect-highlight': 'highlight',
'rect-inactive': 'inactive',
'rect-disabled': 'disabled',
});
});

View File

@ -47,9 +47,11 @@ const graph = new Graph({
graph.render();
graph.on(GraphEvent.AFTER_RENDER, () => {
graph.setElementState('star-active', 'active');
graph.setElementState('star-selected', 'selected');
graph.setElementState('star-highlight', 'highlight');
graph.setElementState('star-inactive', 'inactive');
graph.setElementState('star-disabled', 'disabled');
graph.setElementState({
'star-active': 'active',
'star-selected': 'selected',
'star-highlight': 'highlight',
'star-inactive': 'inactive',
'star-disabled': 'disabled',
});
});

View File

@ -45,9 +45,11 @@ const graph = new Graph({
graph.render();
graph.on(GraphEvent.AFTER_RENDER, () => {
graph.setElementState('triangle-active', 'active');
graph.setElementState('triangle-selected', 'selected');
graph.setElementState('triangle-highlight', 'highlight');
graph.setElementState('triangle-inactive', 'inactive');
graph.setElementState('triangle-disabled', 'disabled');
graph.setElementState({
'triangle-active': 'active',
'triangle-selected': 'selected',
'triangle-highlight': 'highlight',
'triangle-inactive': 'inactive',
'triangle-disabled': 'disabled',
});
});

View File

@ -38,16 +38,16 @@ graph.render();
graph.on('node:click', (e) => {
const id = e.target.id;
const node = graph.getNodeData(id);
const label = node?.data?.label;
const label = node?.data?.label as string;
navigator.clipboard.writeText(label);
navigator.clipboard.writeText(label as string);
alert('copied to clipboard!');
});
graph.on('node:pointerenter', (e) => {
graph.setElementState(e.target.id, 'active');
graph.setElementState({ [e.target.id]: 'active' });
});
graph.on('node:pointerout', (e) => {
graph.setElementState(e.target.id, '');
graph.setElementState({ [e.target.id]: [] });
});