feat: v5 donut (#4846)
Co-authored-by: yvonneyx <banxuan.zyx@antgroup.com>
@ -6,6 +6,7 @@ export const RESERVED_SHAPE_IDS = [
|
||||
'haloShape',
|
||||
'anchorShapes',
|
||||
'badgeShapes',
|
||||
'donutShapes',
|
||||
];
|
||||
export const OTHER_SHAPES_FIELD_NAME = 'otherShapes';
|
||||
|
||||
|
@ -277,17 +277,19 @@ export class ItemController {
|
||||
);
|
||||
}
|
||||
// collapse the sub tree which has 'collapsed' in initial data
|
||||
const collapseNodes = [];
|
||||
graphCoreTreeDfs(
|
||||
graphCore,
|
||||
graphCore.getRoots('tree'),
|
||||
(child) => {
|
||||
if (child.data.collapsed) collapseNodes.push(child);
|
||||
},
|
||||
'BT',
|
||||
'tree',
|
||||
);
|
||||
this.collapseSubTree(collapseNodes, graphCore, false);
|
||||
if (graphCore.hasTreeStructure('tree')) {
|
||||
const collapseNodes = [];
|
||||
graphCoreTreeDfs(
|
||||
graphCore,
|
||||
graphCore.getRoots('tree'),
|
||||
(child) => {
|
||||
if (child.data.collapsed) collapseNodes.push(child);
|
||||
},
|
||||
'BT',
|
||||
'tree',
|
||||
);
|
||||
this.collapseSubTree(collapseNodes, graphCore, false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -17,6 +17,7 @@ import {
|
||||
SphereNode,
|
||||
TriangleNode,
|
||||
HexagonNode,
|
||||
DonutNode,
|
||||
} from './item/node';
|
||||
import DarkTheme from './theme/dark';
|
||||
import LightTheme from './theme/light';
|
||||
@ -96,6 +97,7 @@ const stdLib = {
|
||||
'hexagon-node': HexagonNode,
|
||||
'triangle-node': TriangleNode,
|
||||
'ellipse-node': EllipseNode,
|
||||
'donut-node': DonutNode,
|
||||
},
|
||||
edges: {
|
||||
'line-edge': LineEdge,
|
||||
|
368
packages/g6/src/stdlib/item/node/donut.ts
Normal file
@ -0,0 +1,368 @@
|
||||
import { DisplayObject } from '@antv/g';
|
||||
import { each } from '@antv/util';
|
||||
import { NodeDisplayModel } from '../../../types';
|
||||
import { ShapeStyle, State } from '../../../types/item';
|
||||
import {
|
||||
NodeModelData,
|
||||
NodeShapeMap,
|
||||
NodeShapeStyles,
|
||||
} from '../../../types/node';
|
||||
import { BaseNode } from './base';
|
||||
|
||||
const defaultDonutPalette = [
|
||||
'#61DDAA',
|
||||
'#65789B',
|
||||
'#F6BD16',
|
||||
'#7262FD',
|
||||
'#78D3F8',
|
||||
'#9661BC',
|
||||
'#F6903D',
|
||||
'#008685',
|
||||
'#F08BB4',
|
||||
];
|
||||
|
||||
type DonutAttrs = {
|
||||
[propKey: string]: number;
|
||||
};
|
||||
|
||||
type DonutColorMap = {
|
||||
[propKey: string]: string;
|
||||
};
|
||||
|
||||
type DonutNodeDisplayModel = NodeDisplayModel & {
|
||||
donutShapes: ShapeStyle & {
|
||||
innerSize: number;
|
||||
attrs: DonutAttrs;
|
||||
colorMap: DonutColorMap;
|
||||
};
|
||||
};
|
||||
|
||||
type DonutSegmentValue = {
|
||||
key: string; // key of the fan, came from the key of corresponding property of donutAttrs
|
||||
value: number; // format number value of the single fan
|
||||
color: string; // color from corresponding position of donutColorMap
|
||||
};
|
||||
|
||||
type DonutSegmentConfig = {
|
||||
arcR: number; // the radius of the fan
|
||||
beginAngle: number; // the beginning angle of the arc
|
||||
config: DonutSegmentValue; // value and color of the fan
|
||||
index: number; // the index of the fan at the donut fans array
|
||||
lineWidth: number; // width of the segment determining the inner size
|
||||
zIndex: number; // shape zIndex
|
||||
totalValue: number; // the total value of the donut configs
|
||||
drawWhole?: boolean; // whether draw a arc with radius 2*PI to represent a circle
|
||||
};
|
||||
|
||||
export class DonutNode extends BaseNode {
|
||||
override defaultStyles = {
|
||||
keyShape: {
|
||||
r: 16,
|
||||
x: 0,
|
||||
y: 0,
|
||||
},
|
||||
donutShapes: {
|
||||
innerSize: 0.6,
|
||||
attrs: {},
|
||||
colorMap: {},
|
||||
zIndex: 1,
|
||||
},
|
||||
};
|
||||
mergedStyles: NodeShapeStyles;
|
||||
constructor(props) {
|
||||
super(props);
|
||||
// suggest to merge default styles like this to avoid style value missing
|
||||
// this.defaultStyles = mergeStyles([this.baseDefaultStyles, this.defaultStyles]);
|
||||
}
|
||||
public draw(
|
||||
model: DonutNodeDisplayModel,
|
||||
shapeMap: NodeShapeMap,
|
||||
diffData?: { previous: NodeModelData; current: NodeModelData },
|
||||
diffState?: { previous: State[]; current: State[] },
|
||||
): NodeShapeMap {
|
||||
const { data = {} } = model;
|
||||
let shapes: NodeShapeMap = { keyShape: undefined };
|
||||
|
||||
// keyShape
|
||||
shapes.keyShape = this.drawKeyShape(model, shapeMap, diffData);
|
||||
|
||||
// haloShape
|
||||
if (data.haloShape && this.drawHaloShape) {
|
||||
shapes.haloShape = this.drawHaloShape(model, shapeMap, diffData);
|
||||
}
|
||||
|
||||
// labelShape
|
||||
if (data.labelShape) {
|
||||
shapes.labelShape = this.drawLabelShape(model, shapeMap, diffData);
|
||||
}
|
||||
|
||||
// labelBackgroundShape
|
||||
if (data.labelBackgroundShape) {
|
||||
shapes.labelBackgroundShape = this.drawLabelBackgroundShape(
|
||||
model,
|
||||
shapeMap,
|
||||
diffData,
|
||||
);
|
||||
}
|
||||
|
||||
// anchor shapes
|
||||
if (data.anchorShapes) {
|
||||
const anchorShapes = this.drawAnchorShapes(
|
||||
model,
|
||||
shapeMap,
|
||||
diffData,
|
||||
diffState,
|
||||
);
|
||||
shapes = {
|
||||
...shapes,
|
||||
...anchorShapes,
|
||||
};
|
||||
}
|
||||
|
||||
// iconShape
|
||||
if (data.iconShape) {
|
||||
shapes.iconShape = this.drawIconShape(model, shapeMap, diffData);
|
||||
}
|
||||
|
||||
// badgeShape
|
||||
if (data.badgeShapes) {
|
||||
const badgeShapes = this.drawBadgeShapes(
|
||||
model,
|
||||
shapeMap,
|
||||
diffData,
|
||||
diffState,
|
||||
);
|
||||
shapes = {
|
||||
...shapes,
|
||||
...badgeShapes,
|
||||
};
|
||||
}
|
||||
|
||||
// donutShapes
|
||||
if (data.donutShapes) {
|
||||
const donutShapes = this.drawDonutShapes(
|
||||
model,
|
||||
shapeMap,
|
||||
diffData,
|
||||
diffState,
|
||||
);
|
||||
shapes = {
|
||||
...shapes,
|
||||
...donutShapes,
|
||||
};
|
||||
}
|
||||
|
||||
// otherShapes
|
||||
if (data.otherShapes && this.drawOtherShapes) {
|
||||
shapes = {
|
||||
...shapes,
|
||||
...this.drawOtherShapes(model, shapeMap, diffData),
|
||||
};
|
||||
}
|
||||
return shapes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw a complete donut composed of several segments
|
||||
* @param model
|
||||
* @param shapeMap
|
||||
* @param diffData
|
||||
* @param diffState
|
||||
* @returns
|
||||
*/
|
||||
private drawDonutShapes(
|
||||
model: DonutNodeDisplayModel,
|
||||
shapeMap: NodeShapeMap,
|
||||
diffData?: { previous: NodeModelData; current: NodeModelData },
|
||||
diffState?: { previous: State[]; current: State[] },
|
||||
): {
|
||||
[shapeId: string]: DisplayObject;
|
||||
} {
|
||||
const {
|
||||
donutShapes: { innerSize, attrs, colorMap, zIndex },
|
||||
} = this.mergedStyles as DonutNodeDisplayModel;
|
||||
|
||||
const attrNum = Object.keys(attrs).length;
|
||||
if (!attrNum) return;
|
||||
|
||||
const { configs, totalValue } = getDonutConfig(attrs, colorMap);
|
||||
if (!totalValue) return;
|
||||
|
||||
const { lineWidth, arcR } = getDonutSize(shapeMap.keyShape, innerSize);
|
||||
let beginAngle = 0;
|
||||
|
||||
const shapes = {};
|
||||
|
||||
each(configs, (config, index) => {
|
||||
const result = this.drawDonutSegment(
|
||||
{
|
||||
arcR,
|
||||
beginAngle,
|
||||
config,
|
||||
index,
|
||||
lineWidth,
|
||||
zIndex,
|
||||
totalValue,
|
||||
drawWhole: attrNum === 1,
|
||||
},
|
||||
shapes,
|
||||
model,
|
||||
shapeMap,
|
||||
diffData,
|
||||
diffState,
|
||||
);
|
||||
if (result.shouldEnd) return;
|
||||
beginAngle = result.beginAngle;
|
||||
});
|
||||
|
||||
return shapes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw a single donut segment
|
||||
* @param cfg The configurations of donut segments
|
||||
* @param shapes The collections of donut segment shapes
|
||||
* @param model
|
||||
* @param shapeMap
|
||||
* @param diffData
|
||||
* @param diffState
|
||||
* @returns
|
||||
*/
|
||||
private drawDonutSegment = (
|
||||
cfg: DonutSegmentConfig,
|
||||
shapes: { [shapeId: string]: DisplayObject },
|
||||
model: DonutNodeDisplayModel,
|
||||
shapeMap: NodeShapeMap,
|
||||
diffData?: { previous: NodeModelData; current: NodeModelData },
|
||||
diffState?: { previous: State[]; current: State[] },
|
||||
): {
|
||||
beginAngle: number; // next begin iangle
|
||||
shouldEnd: boolean; // finish fans drawing
|
||||
} => {
|
||||
const {
|
||||
arcR,
|
||||
beginAngle,
|
||||
config,
|
||||
index,
|
||||
lineWidth,
|
||||
zIndex,
|
||||
totalValue,
|
||||
drawWhole = false,
|
||||
} = cfg;
|
||||
const id = `donutShape${index}`;
|
||||
const percent = config.value / totalValue;
|
||||
if (percent < 0.001) {
|
||||
// too small to add a fan
|
||||
return {
|
||||
beginAngle,
|
||||
shouldEnd: false,
|
||||
};
|
||||
}
|
||||
let arcEnd, endAngle, isLargeArc;
|
||||
const arcBegin = calculateArcEndpoint(arcR, beginAngle);
|
||||
// draw a path represents the whole circle, or the percentage is close to 1
|
||||
if (drawWhole || percent > 0.999) {
|
||||
arcEnd = [arcR, 0.0001]; // [arcR * cos(2 * PI), -arcR * sin(2 * PI)]
|
||||
isLargeArc = 1;
|
||||
} else {
|
||||
const angle = percent * Math.PI * 2;
|
||||
endAngle = beginAngle + angle;
|
||||
arcEnd = calculateArcEndpoint(arcR, endAngle);
|
||||
isLargeArc = angle > Math.PI ? 1 : 0;
|
||||
}
|
||||
const style = {
|
||||
path: [
|
||||
['M', arcBegin[0], arcBegin[1]],
|
||||
['A', arcR, arcR, 0, isLargeArc, 0, arcEnd[0], arcEnd[1]],
|
||||
],
|
||||
stroke:
|
||||
config.color || defaultDonutPalette[index % defaultDonutPalette.length],
|
||||
lineWidth,
|
||||
zIndex,
|
||||
} as ShapeStyle;
|
||||
shapes[id] = this.upsertShape(
|
||||
'path',
|
||||
id,
|
||||
style,
|
||||
shapeMap,
|
||||
model,
|
||||
) as DisplayObject;
|
||||
|
||||
return {
|
||||
beginAngle: endAngle,
|
||||
shouldEnd: drawWhole || percent > 0.999,
|
||||
};
|
||||
};
|
||||
|
||||
public drawKeyShape(
|
||||
model: DonutNodeDisplayModel,
|
||||
shapeMap: NodeShapeMap,
|
||||
diffData?: { previous: NodeModelData; current: NodeModelData },
|
||||
diffState?: { previous: State[]; current: State[] },
|
||||
): DisplayObject {
|
||||
return this.upsertShape(
|
||||
'circle',
|
||||
'keyShape',
|
||||
this.mergedStyles.keyShape,
|
||||
shapeMap,
|
||||
model,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the endpoint of an arc segment.
|
||||
* @param arcR Radius of the arc.
|
||||
* @param angle angle in degrees subtended by arc.
|
||||
*/
|
||||
const calculateArcEndpoint = (arcR: number, angle: number): number[] => [
|
||||
arcR * Math.cos(angle),
|
||||
-arcR * Math.sin(angle),
|
||||
];
|
||||
|
||||
/**
|
||||
* calculate the total value and format single value for each fan
|
||||
* @param donutAttrs
|
||||
* @param donutColorMap
|
||||
* @returns
|
||||
*/
|
||||
const getDonutConfig = (
|
||||
donutAttrs: DonutAttrs,
|
||||
donutColorMap: DonutColorMap,
|
||||
): {
|
||||
totalValue: number;
|
||||
configs: DonutSegmentValue[];
|
||||
} => {
|
||||
let totalValue = 0;
|
||||
const configs = [];
|
||||
Object.keys(donutAttrs).forEach((name) => {
|
||||
const value = +donutAttrs[name];
|
||||
if (isNaN(value)) return;
|
||||
configs.push({
|
||||
key: name,
|
||||
value,
|
||||
color: donutColorMap[name],
|
||||
});
|
||||
totalValue += value;
|
||||
});
|
||||
return { totalValue, configs };
|
||||
};
|
||||
|
||||
/**
|
||||
* calculate the lineWidth and radius for fan shapes according to the keyShape's radius
|
||||
* @param keyShape
|
||||
* @returns
|
||||
*/
|
||||
const getDonutSize = (
|
||||
keyShape,
|
||||
innerSize: number,
|
||||
): {
|
||||
lineWidth: number;
|
||||
arcR: number;
|
||||
} => {
|
||||
const keyShapeR = keyShape.attr('r');
|
||||
const innerR = innerSize * keyShapeR; // The radius of the inner ring of the donut
|
||||
const arcR = (keyShapeR + innerR) / 2; // The average of the radius of the inner ring and the radius of the outer ring
|
||||
const lineWidth = keyShapeR - innerR;
|
||||
return { lineWidth, arcR };
|
||||
};
|
@ -4,3 +4,4 @@ export * from './rect';
|
||||
export * from './hexagon';
|
||||
export * from './triangle';
|
||||
export * from './ellipse';
|
||||
export * from './donut';
|
||||
|
@ -11,6 +11,7 @@ import menu from './demo/menu';
|
||||
import quadratic from './demo/quadratic';
|
||||
import rect from './demo/rect';
|
||||
import tooltip from './demo/tooltip';
|
||||
import donut_node from './item/node/donut-node';
|
||||
import cubic_edge from './item/edge/cubic-edge';
|
||||
import cubic_horizon_edge from './item/edge/cubic-horizon-edge';
|
||||
import cubic_vertical_edge from './item/edge/cubic-vertical-edge';
|
||||
@ -66,6 +67,7 @@ export {
|
||||
layouts_fruchterman_gpu,
|
||||
layouts_fruchterman_wasm,
|
||||
layouts_grid,
|
||||
donut_node,
|
||||
line_edge,
|
||||
menu,
|
||||
performance,
|
||||
|
243
packages/g6/tests/demo/item/node/donut-node.ts
Normal file
@ -0,0 +1,243 @@
|
||||
import { Graph, IGraph } from '../../../../src/index';
|
||||
|
||||
let outerTop = 0;
|
||||
let graph: IGraph;
|
||||
|
||||
const createLabelCheckbox = (
|
||||
container: HTMLElement,
|
||||
labelText: string,
|
||||
checkedCallback: () => void,
|
||||
uncheckedCallback: () => void,
|
||||
top?: number,
|
||||
) => {
|
||||
if (!container) return;
|
||||
|
||||
let innerTop = top;
|
||||
if (!top) {
|
||||
innerTop = outerTop;
|
||||
outerTop += 30;
|
||||
}
|
||||
|
||||
const label = document.createElement('span');
|
||||
label.textContent = labelText;
|
||||
label.style.position = 'absolute';
|
||||
label.style.top = `${innerTop}px`;
|
||||
label.style.left = '16px';
|
||||
label.style.zIndex = '100';
|
||||
|
||||
const cb = document.createElement('input');
|
||||
cb.type = 'checkbox';
|
||||
cb.value = 'highlight';
|
||||
cb.style.position = 'absolute';
|
||||
cb.style.width = '20px';
|
||||
cb.style.height = '20px';
|
||||
cb.style.top = `${innerTop}px`;
|
||||
cb.style.left = '400px';
|
||||
cb.style.zIndex = '100';
|
||||
|
||||
cb.addEventListener('click', (e) => {
|
||||
cb.checked ? checkedCallback() : uncheckedCallback();
|
||||
});
|
||||
|
||||
container.appendChild(label);
|
||||
container.appendChild(cb);
|
||||
};
|
||||
|
||||
const createOperationContainer = (container: HTMLElement) => {
|
||||
const operationContainer = document.createElement('div');
|
||||
operationContainer.id = 'ctrl-container';
|
||||
operationContainer.style.width = '100%';
|
||||
operationContainer.style.height = '150px';
|
||||
operationContainer.style.lineHeight = '50px';
|
||||
operationContainer.style.backgroundColor = '#eee';
|
||||
|
||||
container.appendChild(operationContainer);
|
||||
};
|
||||
|
||||
const createOperations = (): any => {
|
||||
const parentEle = document.getElementById('ctrl-container');
|
||||
if (!parentEle) return;
|
||||
|
||||
// Custom Donut Colors
|
||||
createLabelCheckbox(
|
||||
parentEle,
|
||||
'custom donut colors',
|
||||
() => {
|
||||
graph.updateData('node', {
|
||||
id: 'node1',
|
||||
data: {
|
||||
donutShapes: {
|
||||
colorMap: {
|
||||
income: '#78D3F8',
|
||||
outcome: '#F08BB4',
|
||||
unknown: '#65789B',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
() => {
|
||||
graph.updateData('node', {
|
||||
id: 'node1',
|
||||
data: {
|
||||
donutShapes: {
|
||||
colorMap: {},
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
// Custom Donut innerSize
|
||||
createLabelCheckbox(
|
||||
parentEle,
|
||||
'update donut innerSize',
|
||||
() => {
|
||||
graph.updateData('node', {
|
||||
id: 'node1',
|
||||
data: {
|
||||
donutShapes: {
|
||||
innerSize: 0.8,
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
() => {
|
||||
graph.updateData('node', {
|
||||
id: 'node1',
|
||||
data: {
|
||||
donutShapes: {
|
||||
innerSize: 0.6,
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
// Custom Donut attrs
|
||||
createLabelCheckbox(
|
||||
parentEle,
|
||||
'update donut attrs',
|
||||
() => {
|
||||
graph.updateData('node', {
|
||||
id: 'node1',
|
||||
data: {
|
||||
donutShapes: {
|
||||
attrs: {
|
||||
income: 280,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
() => {
|
||||
graph.updateData('node', {
|
||||
id: 'node1',
|
||||
data: {
|
||||
donutShapes: {
|
||||
attrs: {
|
||||
income: 80,
|
||||
outcome: 40,
|
||||
unknown: 45,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
// select
|
||||
createLabelCheckbox(
|
||||
parentEle,
|
||||
'custom selected style',
|
||||
() => {
|
||||
graph.setItemState('node1', 'selected', true);
|
||||
},
|
||||
() => {
|
||||
graph.setItemState('node1', 'selected', false);
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
export default (context) => {
|
||||
const { container } = context;
|
||||
|
||||
// 1.create operation container
|
||||
createOperationContainer(container!);
|
||||
|
||||
const data = {
|
||||
nodes: [
|
||||
{
|
||||
id: 'node1',
|
||||
data: {
|
||||
x: 100,
|
||||
y: 100,
|
||||
type: 'donut-node',
|
||||
keyShape: {
|
||||
r: 30,
|
||||
},
|
||||
labelShape: {
|
||||
text: 'label',
|
||||
position: 'bottom',
|
||||
},
|
||||
labelBackgroundShape: {
|
||||
fill: 'red',
|
||||
},
|
||||
anchorShapes: [
|
||||
{
|
||||
position: [0, 0.5],
|
||||
r: 2,
|
||||
fill: 'red',
|
||||
},
|
||||
],
|
||||
iconShape: {
|
||||
img: 'https://gw.alipayobjects.com/zos/basement_prod/012bcf4f-423b-4922-8c24-32a89f8c41ce.svg',
|
||||
width: 20,
|
||||
height: 20,
|
||||
},
|
||||
badgeShapes: [
|
||||
{
|
||||
text: '1',
|
||||
position: 'rightTop',
|
||||
color: 'blue',
|
||||
},
|
||||
],
|
||||
donutShapes: {
|
||||
attrs: {
|
||||
income: 80,
|
||||
outcome: 40,
|
||||
unknown: 45,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
graph = new Graph({
|
||||
...context,
|
||||
data,
|
||||
type: 'graph',
|
||||
modes: {
|
||||
default: ['drag-canvas', 'drag-node', 'click-select', 'hover-activate'],
|
||||
},
|
||||
node: (nodeInnerModel: any) => {
|
||||
const { id, data } = nodeInnerModel;
|
||||
return {
|
||||
id,
|
||||
data: {
|
||||
keyShape: {
|
||||
r: 16,
|
||||
},
|
||||
...data,
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
// 2.create operations
|
||||
createOperations();
|
||||
|
||||
return graph;
|
||||
};
|
157
packages/g6/tests/integration/items-node-donut.spec.ts
Normal file
@ -0,0 +1,157 @@
|
||||
import { resetEntityCounter } from '@antv/g';
|
||||
import donutNode from '../demo/item/node/donut-node';
|
||||
import './utils/useSnapshotMatchers';
|
||||
import { createContext } from './utils';
|
||||
|
||||
describe('Items node donut', () => {
|
||||
beforeEach(() => {
|
||||
/**
|
||||
* SVG Snapshot testing will generate a unique id for each element.
|
||||
* Reset to 0 to keep snapshot consistent.
|
||||
*/
|
||||
resetEntityCounter();
|
||||
});
|
||||
|
||||
it('should be rendered correctly with Canvas2D', (done) => {
|
||||
const dir = `${__dirname}/snapshots/canvas/items/node/donut`;
|
||||
const { backgroundCanvas, canvas, transientCanvas, container } =
|
||||
createContext('canvas', 500, 500);
|
||||
|
||||
const graph = donutNode({
|
||||
container,
|
||||
backgroundCanvas,
|
||||
canvas,
|
||||
transientCanvas,
|
||||
width: 500,
|
||||
height: 500,
|
||||
});
|
||||
|
||||
graph.on('afterlayout', async () => {
|
||||
await expect(canvas).toMatchCanvasSnapshot(dir, 'items-node-donut');
|
||||
|
||||
/**
|
||||
* Click the checkbox to set custom colors.
|
||||
*/
|
||||
const $customColors = document.querySelectorAll(
|
||||
'input',
|
||||
)[0] as HTMLInputElement;
|
||||
$customColors.click();
|
||||
await expect(canvas).toMatchCanvasSnapshot(
|
||||
dir,
|
||||
'items-node-donut-custom-colors',
|
||||
);
|
||||
$customColors.click();
|
||||
|
||||
/**
|
||||
* Click the checkbox to set custom inner size.
|
||||
*/
|
||||
const $innerSize = document.querySelectorAll(
|
||||
'input',
|
||||
)[1] as HTMLInputElement;
|
||||
$innerSize.click();
|
||||
await expect(canvas).toMatchCanvasSnapshot(
|
||||
dir,
|
||||
'items-node-donut-custom-innersize',
|
||||
);
|
||||
$innerSize.click();
|
||||
|
||||
/**
|
||||
* Click the checkbox to update attrs.
|
||||
*/
|
||||
const $attrs = document.querySelectorAll('input')[2] as HTMLInputElement;
|
||||
$attrs.click();
|
||||
await expect(canvas).toMatchCanvasSnapshot(
|
||||
dir,
|
||||
'items-node-donut-custom-attrs',
|
||||
);
|
||||
$attrs.click();
|
||||
|
||||
/**
|
||||
* Click the checkbox to set selected style.
|
||||
*/
|
||||
const $selected = document.querySelectorAll(
|
||||
'input',
|
||||
)[3] as HTMLInputElement;
|
||||
$selected.click();
|
||||
await expect(canvas).toMatchCanvasSnapshot(
|
||||
dir,
|
||||
'items-node-donut-selected-style',
|
||||
);
|
||||
$selected.click();
|
||||
|
||||
graph.destroy();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should be rendered correctly with SVG', (done) => {
|
||||
const dir = `${__dirname}/snapshots/svg/items/node/donut`;
|
||||
const { backgroundCanvas, canvas, transientCanvas, container } =
|
||||
createContext('svg', 500, 500);
|
||||
|
||||
const graph = donutNode({
|
||||
container,
|
||||
backgroundCanvas,
|
||||
canvas,
|
||||
transientCanvas,
|
||||
width: 500,
|
||||
height: 500,
|
||||
});
|
||||
|
||||
graph.on('afterlayout', async () => {
|
||||
await expect(canvas).toMatchSVGSnapshot(dir, 'items-node-donut');
|
||||
/**
|
||||
* Click the checkbox to set custom colors.
|
||||
*/
|
||||
const $customColors = document.querySelectorAll(
|
||||
'input',
|
||||
)[0] as HTMLInputElement;
|
||||
$customColors.click();
|
||||
await expect(canvas).toMatchSVGSnapshot(
|
||||
dir,
|
||||
'items-node-donut-custom-colors',
|
||||
);
|
||||
$customColors.click();
|
||||
|
||||
/**
|
||||
* Click the checkbox to set custom inner size.
|
||||
*/
|
||||
const $innerSize = document.querySelectorAll(
|
||||
'input',
|
||||
)[1] as HTMLInputElement;
|
||||
$innerSize.click();
|
||||
await expect(canvas).toMatchSVGSnapshot(
|
||||
dir,
|
||||
'items-node-donut-custom-innersize',
|
||||
);
|
||||
$innerSize.click();
|
||||
|
||||
/**
|
||||
* Click the checkbox to update attrs.
|
||||
*/
|
||||
const $attrs = document.querySelectorAll('input')[2] as HTMLInputElement;
|
||||
$attrs.click();
|
||||
await expect(canvas).toMatchSVGSnapshot(
|
||||
dir,
|
||||
'items-node-donut-custom-attrs',
|
||||
);
|
||||
$attrs.click();
|
||||
|
||||
/**
|
||||
* Click the checkbox to set selected style.
|
||||
*/
|
||||
const $selected = document.querySelectorAll(
|
||||
'input',
|
||||
)[3] as HTMLInputElement;
|
||||
$selected.click();
|
||||
await expect(canvas).toMatchSVGSnapshot(
|
||||
dir,
|
||||
'items-node-donut-selected-style',
|
||||
);
|
||||
$selected.click();
|
||||
|
||||
graph.destroy();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
After Width: | Height: | Size: 4.6 KiB |
After Width: | Height: | Size: 4.7 KiB |
After Width: | Height: | Size: 4.9 KiB |
After Width: | Height: | Size: 7.4 KiB |
After Width: | Height: | Size: 4.8 KiB |
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500" style="background: transparent; position: fixed; outline: none;" color-interpolation-filters="sRGB" tabindex="1"><defs/><g transform="matrix(1,0,0,1,0,0)"><g fill="none" transform="matrix(1,0,0,1,0,0)"><g fill="none" stroke="transparent" stroke-width="01"/><g fill="none" transform="matrix(1,0,0,1,0,0)"/><g fill="none" transform="matrix(1,0,0,1,0,0)"/><g fill="none" transform="matrix(1,0,0,1,0,0)"><g fill="none" transform="matrix(1,0,0,1,100,100)"><g transform="matrix(1,0,0,1,0,0)"><circle fill="rgba(34,126,255,1)" transform="translate(-30,-30)" cx="30" cy="30" r="30" stroke-width="0"/></g><g transform="matrix(0.945946,0,0,1,-16.499999,30)"><path fill="rgba(255,0,0,1)" d="M 0,0 l 37,0 l 0,19 l-37 0 z" stroke-width="0" opacity="0.75" width="37" height="19"/></g><g transform="matrix(1,0,0,1,-10,-10)"><image fill="rgba(255,255,255,1)" preserveAspectRatio="none" x="0" y="0" href="https://gw.alipayobjects.com/zos/basement_prod/012bcf4f-423b-4922-8c24-32a89f8c41ce.svg" font-size="16" font-family="sans-serif" font-weight="normal" font-variant="normal" font-style="normal" stroke-width="0" width="20" height="20"/></g><g transform="matrix(1,0,0,1,-24,-23.999950)"><path fill="none" d="M 47.99999999994792,23.99995 A 24 24 0 1 0 47.99999999994792 24.000049999999998" stroke="rgba(97,221,170,1)" stroke-width="12"/><path fill="none" d="M 47.891326141754035,24.00000000000001 A 24 24 0 0 0 3.552713678800501e-15 21.71865496069962" stroke="transparent" stroke-width="12"/></g><g transform="matrix(1,0,0,1,0,32)"><text fill="rgba(0,0,0,1)" dominant-baseline="central" paint-order="stroke" dx="0" dy="7.5px" text-anchor="middle" font-size="12" font-family="sans-serif" font-weight="normal" font-variant="normal" font-style="normal" stroke-width="0">label</text></g><g transform="matrix(1,0,0,1,-30,0)"><circle fill="rgba(255,0,0,1)" transform="translate(-2,-2)" cx="2" cy="2" stroke-width="1" stroke="rgba(0,0,0,0.65)" r="2"/></g><g transform="matrix(1,0,0,1,15,-33.200001)"><path fill="rgba(0,0,255,1)" d="M 10,0 l 0,0 a 10,10,0,0,1,10,10 l 0,0 a 10,10,0,0,1,-10,10 l 0,0 a 10,10,0,0,1,-10,-10 l 0,0 a 10,10,0,0,1,10,-10 z" height="20" width="20"/></g><g transform="matrix(1,0,0,1,17,-23.200001)"><text fill="rgba(255,255,255,1)" dominant-baseline="central" paint-order="stroke" dx="0.5" font-size="17" text-anchor="left">1</text></g></g></g></g></g></svg>
|
After Width: | Height: | Size: 2.4 KiB |
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500" style="background: transparent; position: fixed; outline: none;" color-interpolation-filters="sRGB" tabindex="1"><defs/><g transform="matrix(1,0,0,1,0,0)"><g fill="none" transform="matrix(1,0,0,1,0,0)"><g fill="none" stroke="transparent" stroke-width="01"/><g fill="none" transform="matrix(1,0,0,1,0,0)"/><g fill="none" transform="matrix(1,0,0,1,0,0)"/><g fill="none" transform="matrix(1,0,0,1,0,0)"><g fill="none" transform="matrix(1,0,0,1,100,100)"><g transform="matrix(1,0,0,1,0,0)"><circle fill="rgba(34,126,255,1)" transform="translate(-30,-30)" cx="30" cy="30" r="30" stroke-width="0"/></g><g transform="matrix(0.945946,0,0,1,-16.499999,30)"><path fill="rgba(255,0,0,1)" d="M 0,0 l 37,0 l 0,19 l-37 0 z" stroke-width="0" opacity="0.75" width="37" height="19"/></g><g transform="matrix(1,0,0,1,-10,-10)"><image fill="rgba(255,255,255,1)" preserveAspectRatio="none" x="0" y="0" href="https://gw.alipayobjects.com/zos/basement_prod/012bcf4f-423b-4922-8c24-32a89f8c41ce.svg" font-size="16" font-family="sans-serif" font-weight="normal" font-variant="normal" font-style="normal" stroke-width="0" width="20" height="20"/></g><g transform="matrix(1,0,0,1,-23.891327,-24)"><path fill="none" d="M 47.891326141754035,24.00000000000001 A 24 24 0 0 0 3.552713678800501e-15 21.71865496069962" stroke="rgba(120,211,248,1)" stroke-width="12"/><path fill="none" d="M 47.891326141754035,24.00000000000001 A 24 24 0 0 0 3.552713678800501e-15 21.71865496069962" stroke="transparent" stroke-width="24"/></g><g transform="matrix(1,0,0,1,-24,-2.281345)"><path fill="none" d="M 0.10867385824597164,1.7763568394002505e-15 A 24 24 0 0 0 20.584443881441153 26.037059644442774" stroke="rgba(240,139,180,1)" stroke-width="12"/><path fill="none" d="M 0.10867385824597164,1.7763568394002505e-15 A 24 24 0 0 0 20.584443881441153 26.037059644442774" stroke="transparent" stroke-width="24"/></g><g transform="matrix(1,0,0,1,-3.415556,0)"><path fill="none" d="M 3.1086244689504383e-15,23.755714605142373 A 24 24 0 0 0 27.415556118558847 -4.779836400494208e-15" stroke="rgba(101,120,155,1)" stroke-width="12"/><path fill="none" d="M 3.1086244689504383e-15,23.755714605142373 A 24 24 0 0 0 27.415556118558847 -4.779836400494208e-15" stroke="transparent" stroke-width="24"/></g><g transform="matrix(1,0,0,1,0,32)"><text fill="rgba(0,0,0,1)" dominant-baseline="central" paint-order="stroke" dx="0" dy="7.5px" text-anchor="middle" font-size="12" font-family="sans-serif" font-weight="normal" font-variant="normal" font-style="normal" stroke-width="0">label</text></g><g transform="matrix(1,0,0,1,-30,0)"><circle fill="rgba(255,0,0,1)" transform="translate(-2,-2)" cx="2" cy="2" stroke-width="1" stroke="rgba(0,0,0,0.65)" r="2"/></g><g transform="matrix(1,0,0,1,15,-33.200001)"><path fill="rgba(0,0,255,1)" d="M 10,0 l 0,0 a 10,10,0,0,1,10,10 l 0,0 a 10,10,0,0,1,-10,10 l 0,0 a 10,10,0,0,1,-10,-10 l 0,0 a 10,10,0,0,1,10,-10 z" height="20" width="20"/></g><g transform="matrix(1,0,0,1,17,-23.200001)"><text fill="rgba(255,255,255,1)" dominant-baseline="central" paint-order="stroke" dx="0.5" font-size="17" text-anchor="left">1</text></g></g></g></g></g></svg>
|
After Width: | Height: | Size: 3.1 KiB |
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500" style="background: transparent; position: fixed; outline: none;" color-interpolation-filters="sRGB" tabindex="1"><defs/><g transform="matrix(1,0,0,1,0,0)"><g fill="none" transform="matrix(1,0,0,1,0,0)"><g fill="none" stroke="transparent" stroke-width="01"/><g fill="none" transform="matrix(1,0,0,1,0,0)"/><g fill="none" transform="matrix(1,0,0,1,0,0)"/><g fill="none" transform="matrix(1,0,0,1,0,0)"><g fill="none" transform="matrix(1,0,0,1,100,100)"><g transform="matrix(1,0,0,1,0,0)"><circle fill="rgba(34,126,255,1)" transform="translate(-30,-30)" cx="30" cy="30" r="30" stroke-width="0"/></g><g transform="matrix(0.945946,0,0,1,-16.499999,30)"><path fill="rgba(255,0,0,1)" d="M 0,0 l 37,0 l 0,19 l-37 0 z" stroke-width="0" opacity="0.75" width="37" height="19"/></g><g transform="matrix(1,0,0,1,-10,-10)"><image fill="rgba(255,255,255,1)" preserveAspectRatio="none" x="0" y="0" href="https://gw.alipayobjects.com/zos/basement_prod/012bcf4f-423b-4922-8c24-32a89f8c41ce.svg" font-size="16" font-family="sans-serif" font-weight="normal" font-variant="normal" font-style="normal" stroke-width="0" width="20" height="20"/></g><g transform="matrix(1,0,0,1,-26.877743,-27)"><path fill="none" d="M 53.877741909473286,27.000000000000046 A 27 27 0 0 0 7.105427357601002e-15 24.433486830787107" stroke="rgba(97,221,170,1)" stroke-width="6"/><path fill="none" d="M 47.891326141754035,24.00000000000001 A 24 24 0 0 0 3.552713678800501e-15 21.71865496069962" stroke="transparent" stroke-width="6"/></g><g transform="matrix(1,0,0,1,-27,-2.566513)"><path fill="none" d="M 0.12225809052671721,0 A 27 27 0 0 0 23.1574993666213 29.291692099998123" stroke="rgba(101,120,155,1)" stroke-width="6"/><path fill="none" d="M 0.10867385824597164,1.7763568394002505e-15 A 24 24 0 0 0 20.584443881441153 26.037059644442774" stroke="transparent" stroke-width="6"/></g><g transform="matrix(1,0,0,1,-3.842501,0)"><path fill="none" d="M 0,26.725178930785177 A 27 27 0 0 0 30.8425006333787 6.178883824198621e-16" stroke="rgba(246,189,22,1)" stroke-width="6"/><path fill="none" d="M 3.1086244689504383e-15,23.755714605142373 A 24 24 0 0 0 27.415556118558847 -4.779836400494208e-15" stroke="transparent" stroke-width="6"/></g><g transform="matrix(1,0,0,1,0,32)"><text fill="rgba(0,0,0,1)" dominant-baseline="central" paint-order="stroke" dx="0" dy="7.5px" text-anchor="middle" font-size="12" font-family="sans-serif" font-weight="normal" font-variant="normal" font-style="normal" stroke-width="0">label</text></g><g transform="matrix(1,0,0,1,-30,0)"><circle fill="rgba(255,0,0,1)" transform="translate(-2,-2)" cx="2" cy="2" stroke-width="1" stroke="rgba(0,0,0,0.65)" r="2"/></g><g transform="matrix(1,0,0,1,15,-33.200001)"><path fill="rgba(0,0,255,1)" d="M 10,0 l 0,0 a 10,10,0,0,1,10,10 l 0,0 a 10,10,0,0,1,-10,10 l 0,0 a 10,10,0,0,1,-10,-10 l 0,0 a 10,10,0,0,1,10,-10 z" height="20" width="20"/></g><g transform="matrix(1,0,0,1,17,-23.200001)"><text fill="rgba(255,255,255,1)" dominant-baseline="central" paint-order="stroke" dx="0.5" font-size="17" text-anchor="left">1</text></g></g></g></g></g></svg>
|
After Width: | Height: | Size: 3.1 KiB |
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500" style="background: transparent; position: fixed; outline: none;" color-interpolation-filters="sRGB" tabindex="1"><defs/><g transform="matrix(1,0,0,1,0,0)"><g fill="none" transform="matrix(1,0,0,1,0,0)"><g fill="none" stroke="transparent" stroke-width="01"/><g fill="none" transform="matrix(1,0,0,1,0,0)"/><g fill="none" transform="matrix(1,0,0,1,0,0)"/><g fill="none" transform="matrix(1,0,0,1,0,0)"><g fill="none" transform="matrix(1,0,0,1,100,100)"><g transform="matrix(1,0,0,1,0,0)"><circle fill="rgba(34,126,255,1)" transform="translate(-30,-30)" cx="30" cy="30" opacity="0.25" r="30" stroke-width="20" stroke="rgba(34,126,255,1)" pointer-events="none"/></g><g transform="matrix(1,0,0,1,0,0)"><circle fill="rgba(34,126,255,1)" transform="translate(-30,-30)" cx="30" cy="30" r="30" stroke-width="3" stroke="rgba(0,0,0,1)"/></g><g transform="matrix(0.945946,0,0,1,-16.499999,30)"><path fill="rgba(255,0,0,1)" d="M 0,0 l 37,0 l 0,19 l-37 0 z" stroke-width="0" opacity="0.75" width="37" height="19"/></g><g transform="matrix(1,0,0,1,-10,-10)"><image fill="rgba(255,255,255,1)" preserveAspectRatio="none" x="0" y="0" href="https://gw.alipayobjects.com/zos/basement_prod/012bcf4f-423b-4922-8c24-32a89f8c41ce.svg" font-size="16" font-family="sans-serif" font-weight="normal" font-variant="normal" font-style="normal" stroke-width="0" width="20" height="20"/></g><g transform="matrix(1,0,0,1,-23.891327,-24)"><path fill="none" d="M 47.891326141754035,24.00000000000001 A 24 24 0 0 0 3.552713678800501e-15 21.71865496069962" stroke="rgba(97,221,170,1)" stroke-width="12"/><path fill="none" d="M 47.891326141754035,24.00000000000001 A 24 24 0 0 0 3.552713678800501e-15 21.71865496069962" stroke="transparent" stroke-width="12"/></g><g transform="matrix(1,0,0,1,-24,-2.281345)"><path fill="none" d="M 0.10867385824597164,1.7763568394002505e-15 A 24 24 0 0 0 20.584443881441153 26.037059644442774" stroke="rgba(101,120,155,1)" stroke-width="12"/><path fill="none" d="M 0.10867385824597164,1.7763568394002505e-15 A 24 24 0 0 0 20.584443881441153 26.037059644442774" stroke="transparent" stroke-width="24"/></g><g transform="matrix(1,0,0,1,-3.415556,0)"><path fill="none" d="M 3.1086244689504383e-15,23.755714605142373 A 24 24 0 0 0 27.415556118558847 -4.779836400494208e-15" stroke="rgba(246,189,22,1)" stroke-width="12"/><path fill="none" d="M 3.1086244689504383e-15,23.755714605142373 A 24 24 0 0 0 27.415556118558847 -4.779836400494208e-15" stroke="transparent" stroke-width="24"/></g><g transform="matrix(1,0,0,1,0,32)"><text fill="rgba(0,0,0,1)" dominant-baseline="central" paint-order="stroke" dx="0" dy="7.5px" text-anchor="middle" font-size="12" font-family="sans-serif" font-weight="700" font-variant="normal" font-style="normal" stroke-width="0">label</text></g><g transform="matrix(1,0,0,1,-30,0)"><circle fill="rgba(255,0,0,1)" transform="translate(-2,-2)" cx="2" cy="2" stroke-width="1" stroke="rgba(0,0,0,0.65)" r="2"/></g><g transform="matrix(1,0,0,1,15,-33.200001)"><path fill="rgba(0,0,255,1)" d="M 10,0 l 0,0 a 10,10,0,0,1,10,10 l 0,0 a 10,10,0,0,1,-10,10 l 0,0 a 10,10,0,0,1,-10,-10 l 0,0 a 10,10,0,0,1,10,-10 z" height="20" width="20"/></g><g transform="matrix(1,0,0,1,17,-23.200001)"><text fill="rgba(255,255,255,1)" dominant-baseline="central" paint-order="stroke" dx="0.5" font-size="17" text-anchor="left">1</text></g></g></g></g></g></svg>
|
After Width: | Height: | Size: 3.3 KiB |
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500" style="background: transparent; position: fixed; outline: none;" color-interpolation-filters="sRGB" tabindex="1"><defs/><g transform="matrix(1,0,0,1,0,0)"><g fill="none" transform="matrix(1,0,0,1,0,0)"><g fill="none" stroke="transparent" stroke-width="01"/><g fill="none" transform="matrix(1,0,0,1,0,0)"/><g fill="none" transform="matrix(1,0,0,1,0,0)"/><g fill="none" transform="matrix(1,0,0,1,0,0)"><g fill="none" transform="matrix(1,0,0,1,100,100)"><g transform="matrix(1,0,0,1,0,0)"><circle fill="rgba(34,126,255,1)" transform="translate(-30,-30)" cx="30" cy="30" r="30" stroke-width="0"/></g><g transform="matrix(0.945946,0,0,1,-16.499999,30)"><path fill="rgba(255,0,0,1)" d="M 0,0 l 37,0 l 0,19 l-37 0 z" stroke-width="0" opacity="0.75" width="37" height="19"/></g><g transform="matrix(1,0,0,1,-10,-10)"><image fill="rgba(255,255,255,1)" preserveAspectRatio="none" x="0" y="0" href="https://gw.alipayobjects.com/zos/basement_prod/012bcf4f-423b-4922-8c24-32a89f8c41ce.svg" font-size="16" font-family="sans-serif" font-weight="normal" font-variant="normal" font-style="normal" stroke-width="0" width="20" height="20"/></g><g transform="matrix(1,0,0,1,-23.891327,-24)"><path fill="none" d="M 47.891326141754035,24.00000000000001 A 24 24 0 0 0 3.552713678800501e-15 21.71865496069962" stroke="rgba(97,221,170,1)" stroke-width="12"/><path fill="none" d="M 47.891326141754035,24.00000000000001 A 24 24 0 0 0 3.552713678800501e-15 21.71865496069962" stroke="transparent" stroke-width="24"/></g><g transform="matrix(1,0,0,1,-24,-2.281345)"><path fill="none" d="M 0.10867385824597164,1.7763568394002505e-15 A 24 24 0 0 0 20.584443881441153 26.037059644442774" stroke="rgba(101,120,155,1)" stroke-width="12"/><path fill="none" d="M 0.10867385824597164,1.7763568394002505e-15 A 24 24 0 0 0 20.584443881441153 26.037059644442774" stroke="transparent" stroke-width="24"/></g><g transform="matrix(1,0,0,1,-3.415556,0)"><path fill="none" d="M 3.1086244689504383e-15,23.755714605142373 A 24 24 0 0 0 27.415556118558847 -4.779836400494208e-15" stroke="rgba(246,189,22,1)" stroke-width="12"/><path fill="none" d="M 3.1086244689504383e-15,23.755714605142373 A 24 24 0 0 0 27.415556118558847 -4.779836400494208e-15" stroke="transparent" stroke-width="24"/></g><g transform="matrix(1,0,0,1,0,32)"><text fill="rgba(0,0,0,1)" dominant-baseline="central" paint-order="stroke" dx="0" dy="7.5px" text-anchor="middle" font-size="12" font-family="sans-serif" font-weight="normal" font-variant="normal" font-style="normal" stroke-width="0">label</text></g><g transform="matrix(1,0,0,1,-30,0)"><circle fill="rgba(255,0,0,1)" transform="translate(-2,-2)" cx="2" cy="2" stroke-width="1" stroke="rgba(0,0,0,0.65)" r="2"/></g><g transform="matrix(1,0,0,1,15,-33.200001)"><path fill="rgba(0,0,255,1)" d="M 10,0 l 0,0 a 10,10,0,0,1,10,10 l 0,0 a 10,10,0,0,1,-10,10 l 0,0 a 10,10,0,0,1,-10,-10 l 0,0 a 10,10,0,0,1,10,-10 z" height="20" width="20"/></g><g transform="matrix(1,0,0,1,17,-23.200001)"><text fill="rgba(255,255,255,1)" dominant-baseline="central" paint-order="stroke" dx="0.5" font-size="17" text-anchor="left">1</text></g></g></g></g></g></svg>
|
After Width: | Height: | Size: 3.1 KiB |