@antv/infographic 0.2.15 → 0.2.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -15,6 +15,7 @@ const DEFAULT_ITEM_HEIGHT = 50;
15
15
  const FONT_SIZE = 14;
16
16
  const ARROW_SIZE = 14;
17
17
  const CORNER_RADIUS_NODE = 6;
18
+ const LIFELINE_MASK_GAP = 2;
18
19
  const LANE_PADDING = 60;
19
20
  const BTN_HALF_SIZE = 12;
20
21
  const BTN_MARGIN = 10;
@@ -229,17 +230,6 @@ export const SequenceInteractionFlow = (props) => {
229
230
  const decorElements = [];
230
231
  const defsElements = [];
231
232
  const btnElements = [];
232
- // 绘制生命线
233
- if (showLifeline) {
234
- lanes.forEach((_lane, laneIndex) => {
235
- const centerX = getLaneCenterX(laneIndex);
236
- const startY = padding + headerOffset;
237
- const endY = totalHeight - padding;
238
- decorElements.push(_jsx(Path, { d: `M ${centerX} ${startY} L ${centerX} ${endY}`, stroke: colorBorder, strokeWidth: lifelineWidth, strokeDasharray: "5,5", fill: "none", "data-element-type": "shape" }));
239
- // 绘制生命线末端箭头(实心)
240
- decorElements.push(...createArrowElements(centerX, endY, Math.PI / 2, 'triangle', colorBorder, 1, 10));
241
- });
242
- }
243
233
  // 绘制泳道标题
244
234
  if (showLaneHeader) {
245
235
  lanes.forEach((lane, laneIndex) => {
@@ -282,10 +272,6 @@ export const SequenceInteractionFlow = (props) => {
282
272
  });
283
273
  const nodeColor = getPaletteColor(options, [laneIndex]);
284
274
  const nodeThemeColors = getThemeColors({ colorPrimary: nodeColor }, options);
285
- // 添加节点背景遮挡层,防止生命线虚线透过半透明节点显示
286
- // 只在节点中心放置窄条遮挡生命线,避免圆角处露出白色背景
287
- const maskStripWidth = lifelineWidth + 6;
288
- decorElements.push(_jsx(Rect, { x: centerX - maskStripWidth / 2, y: y, width: maskStripWidth, height: itemHeight, fill: colorBg }));
289
275
  // 构造类似 hierarchy-tree 的 _originalIndex
290
276
  const originalIndex = [laneIndex, rowIndex];
291
277
  // 附加到数据上,确保 Item 组件能正确识别
@@ -322,6 +308,41 @@ export const SequenceInteractionFlow = (props) => {
322
308
  const centerX = getLaneCenterX(laneIndex);
323
309
  btnElements.push(_jsx(BtnAdd, { indexes: [laneIndex, childCount], x: centerX - BTN_HALF_SIZE, y: addNodeY }));
324
310
  });
311
+ // 绘制生命线(使用 mask 挖空节点区域,避免虚线穿透半透明节点)
312
+ if (showLifeline) {
313
+ // 预先按泳道分组节点,避免每条泳道都遍历全部节点
314
+ const nodeRectsByLane = new Map();
315
+ nodeLayoutById.forEach((layout) => {
316
+ let list = nodeRectsByLane.get(layout.laneIndex);
317
+ if (!list) {
318
+ list = [];
319
+ nodeRectsByLane.set(layout.laneIndex, list);
320
+ }
321
+ list.push({
322
+ x: layout.x,
323
+ y: layout.y,
324
+ width: layout.width,
325
+ height: layout.height,
326
+ });
327
+ });
328
+ lanes.forEach((_lane, laneIndex) => {
329
+ var _a;
330
+ const centerX = getLaneCenterX(laneIndex);
331
+ const startY = padding + headerOffset;
332
+ const endY = totalHeight - padding;
333
+ const laneNodeRects = (_a = nodeRectsByLane.get(laneIndex)) !== null && _a !== void 0 ? _a : [];
334
+ // 如果该泳道有节点,创建 mask 来挖空节点区域
335
+ let lifelineMaskAttr;
336
+ if (laneNodeRects.length > 0) {
337
+ const maskId = `lifeline-mask-${instanceId}-${laneIndex}`;
338
+ defsElements.push(_jsxs("mask", { id: maskId, maskUnits: "userSpaceOnUse", x: 0, y: 0, width: totalWidth, height: totalHeight, children: [_jsx(Rect, { x: 0, y: 0, width: totalWidth, height: totalHeight, fill: "white" }), laneNodeRects.map((rect) => (_jsx(Rect, { x: rect.x, y: rect.y - LIFELINE_MASK_GAP, width: rect.width, height: rect.height + LIFELINE_MASK_GAP * 2, fill: "black" })))] }));
339
+ lifelineMaskAttr = `url(#${maskId})`;
340
+ }
341
+ decorElements.push(_jsx(Path, { d: `M ${centerX} ${startY} L ${centerX} ${endY}`, stroke: colorBorder, strokeWidth: lifelineWidth, strokeDasharray: "5,5", fill: "none", "data-element-type": "shape", mask: lifelineMaskAttr }));
342
+ // 绘制生命线末端箭头(实心)
343
+ decorElements.push(...createArrowElements(centerX, endY, Math.PI / 2, 'triangle', colorBorder, 1, 10));
344
+ });
345
+ }
325
346
  // 添加新泳道按钮 (最右侧)
326
347
  const lastLaneRightX = getLaneCenterX(lanes.length - 1) + laneWidth / 2;
327
348
  const newLaneX = lanes.length > 0 ? lastLaneRightX + BTN_LANE_ADD_Gap : padding;
@@ -3,5 +3,6 @@ import type { BaseStructureProps } from './types';
3
3
  export interface SequenceTimelineProps extends BaseStructureProps {
4
4
  gap?: number;
5
5
  lineOffset?: number;
6
+ showStepLabels?: boolean;
6
7
  }
7
8
  export declare const SequenceTimeline: ComponentType<SequenceTimelineProps>;
@@ -5,7 +5,7 @@ import { FlexLayout } from '../layouts/index.js';
5
5
  import { getColorPrimary, getPaletteColor, getPaletteColors } from '../utils/index.js';
6
6
  import { registerStructure } from './registry.js';
7
7
  export const SequenceTimeline = (props) => {
8
- const { Title, Item, data, gap = 10, options } = props;
8
+ const { Title, Item, data, gap = 10, showStepLabels = true, options } = props;
9
9
  const { title, desc, items = [] } = data;
10
10
  const titleContent = Title ? _jsx(Title, { title: title, desc: desc }) : null;
11
11
  const colorPrimary = getColorPrimary(options);
@@ -41,7 +41,9 @@ export const SequenceTimeline = (props) => {
41
41
  const itemY = index * (itemBounds.height + gap);
42
42
  const nodeY = itemY + itemBounds.height / 2;
43
43
  const indexes = [index];
44
- decorElements.push(_jsx(Text, { x: stepLabelX, y: nodeY, width: 70, fontSize: 18, fontWeight: "bold", alignHorizontal: "left", alignVertical: "middle", fill: palette[index % palette.length], children: `STEP ${index + 1}` }));
44
+ if (showStepLabels) {
45
+ decorElements.push(_jsx(Text, { x: stepLabelX, y: nodeY, width: 70, fontSize: 18, fontWeight: "bold", alignHorizontal: "left", alignVertical: "middle", fill: palette[index % palette.length], children: `STEP ${index + 1}` }));
46
+ }
45
47
  itemElements.push(_jsx(Item, { indexes: indexes, datum: item, data: data, x: itemX, y: itemY, positionH: "normal" }));
46
48
  decorElements.push(_jsx(Ellipse, { x: timelineX - nodeRadius, y: nodeY - nodeRadius, width: nodeRadius * 2, height: nodeRadius * 2, fill: palette[index % palette.length] }));
47
49
  btnElements.push(_jsx(BtnRemove, { indexes: indexes, x: itemX - btnBounds.width - 10, y: itemY + (itemBounds.height - btnBounds.height) / 2 }));
@@ -12,8 +12,8 @@ import { exportToSVG } from './svg.js';
12
12
  export function exportToPNGString(svg_1) {
13
13
  return __awaiter(this, arguments, void 0, function* (svg, options = {}) {
14
14
  var _a;
15
- const { dpr = (_a = globalThis.devicePixelRatio) !== null && _a !== void 0 ? _a : 2 } = options;
16
- const node = yield exportToSVG(svg);
15
+ const { dpr = (_a = globalThis.devicePixelRatio) !== null && _a !== void 0 ? _a : 2, removeBackground = false } = options;
16
+ const node = yield exportToSVG(svg, { removeBackground });
17
17
  const { width, height } = getViewBox(node);
18
18
  return new Promise((resolve, reject) => {
19
19
  try {
@@ -18,7 +18,7 @@ export function exportToSVGString(svg_1) {
18
18
  }
19
19
  export function exportToSVG(svg_1) {
20
20
  return __awaiter(this, arguments, void 0, function* (svg, options = {}) {
21
- const { embedResources = true, removeIds = false } = options;
21
+ const { removeBackground = false, embedResources = true, removeIds = false, } = options;
22
22
  const clonedSVG = svg.cloneNode(true);
23
23
  const { width, height } = getViewBox(svg);
24
24
  setAttributes(clonedSVG, { width, height });
@@ -30,6 +30,9 @@ export function exportToSVG(svg_1) {
30
30
  yield embedIcons(clonedSVG);
31
31
  }
32
32
  yield embedFonts(clonedSVG, embedResources);
33
+ if (removeBackground) {
34
+ removeSVGBackground(clonedSVG);
35
+ }
33
36
  cleanSVG(clonedSVG);
34
37
  return clonedSVG;
35
38
  });
@@ -284,6 +287,11 @@ function cleanSVG(svg) {
284
287
  removeUselessAttrs(svg);
285
288
  clearDataset(svg);
286
289
  }
290
+ function removeSVGBackground(svg) {
291
+ svg.style.removeProperty('background-color');
292
+ const background = getElementByRole(svg, "background" /* ElementTypeEnum.Background */);
293
+ background === null || background === void 0 ? void 0 : background.remove();
294
+ }
287
295
  function removeBtnGroup(svg) {
288
296
  const btnGroup = getElementByRole(svg, "btns-group" /* ElementTypeEnum.BtnsGroup */);
289
297
  btnGroup === null || btnGroup === void 0 ? void 0 : btnGroup.remove();
@@ -1,5 +1,10 @@
1
1
  export interface SVGExportOptions {
2
2
  type: 'svg';
3
+ /**
4
+ * 是否移除背景(SVG 背景样式 + 背景矩形)
5
+ * @default false
6
+ */
7
+ removeBackground?: boolean;
3
8
  /**
4
9
  * 是否将远程资源嵌入到 SVG 中
5
10
  * @default true
@@ -13,6 +18,11 @@ export interface SVGExportOptions {
13
18
  }
14
19
  export interface PNGExportOptions {
15
20
  type: 'png';
21
+ /**
22
+ * 是否移除背景(SVG 背景样式 + 背景矩形)
23
+ * @default false
24
+ */
25
+ removeBackground?: boolean;
16
26
  /**
17
27
  * 设备像素比,默认为浏览器的 devicePixelRatio
18
28
  * @default globalThis.devicePixelRatio || 2
@@ -40,6 +40,81 @@ function parseKeyValue(raw) {
40
40
  }
41
41
  return { key: text, value: undefined };
42
42
  }
43
+ function isUnsafeObjectKey(key) {
44
+ return key === '__proto__' || key === 'constructor' || key === 'prototype';
45
+ }
46
+ function assignObjectEntry(parent, rawKey, node, line, errors) {
47
+ if (!rawKey.includes('.')) {
48
+ if (isUnsafeObjectKey(rawKey)) {
49
+ errors.push({
50
+ path: rawKey,
51
+ line,
52
+ code: 'bad_syntax',
53
+ message: `Invalid key part: ${rawKey}`,
54
+ raw: rawKey,
55
+ });
56
+ return null;
57
+ }
58
+ parent.entries[rawKey] = node;
59
+ return { parent, key: rawKey };
60
+ }
61
+ const parts = rawKey.split('.');
62
+ if (parts.some((part) => !part)) {
63
+ errors.push({
64
+ path: rawKey,
65
+ line,
66
+ code: 'bad_syntax',
67
+ message: 'Invalid dotted key path.',
68
+ raw: rawKey,
69
+ });
70
+ return null;
71
+ }
72
+ let current = parent;
73
+ for (let index = 0; index < parts.length - 1; index += 1) {
74
+ const part = parts[index];
75
+ if (isUnsafeObjectKey(part)) {
76
+ errors.push({
77
+ path: rawKey,
78
+ line,
79
+ code: 'bad_syntax',
80
+ message: `Invalid key part in dotted path: ${part}`,
81
+ raw: rawKey,
82
+ });
83
+ return null;
84
+ }
85
+ const existing = current.entries[part];
86
+ if (!existing) {
87
+ const container = createObjectNode(line);
88
+ current.entries[part] = container;
89
+ current = container;
90
+ continue;
91
+ }
92
+ if (existing.kind !== 'object') {
93
+ errors.push({
94
+ path: parts.slice(0, index + 1).join('.'),
95
+ line,
96
+ code: 'bad_syntax',
97
+ message: 'Cannot assign dotted key under a list value.',
98
+ raw: rawKey,
99
+ });
100
+ return null;
101
+ }
102
+ current = existing;
103
+ }
104
+ const finalKey = parts[parts.length - 1];
105
+ if (isUnsafeObjectKey(finalKey)) {
106
+ errors.push({
107
+ path: rawKey,
108
+ line,
109
+ code: 'bad_syntax',
110
+ message: `Invalid key part in dotted path: ${finalKey}`,
111
+ raw: rawKey,
112
+ });
113
+ return null;
114
+ }
115
+ current.entries[finalKey] = node;
116
+ return { parent: current, key: finalKey };
117
+ }
43
118
  function createObjectNode(line, value) {
44
119
  return { kind: 'object', line, value, entries: {} };
45
120
  }
@@ -171,12 +246,14 @@ export function parseSyntaxToAst(input) {
171
246
  return;
172
247
  }
173
248
  const node = createObjectNode(lineNumber, parsed.value);
174
- parentNode.entries[parsed.key] = node;
249
+ const assigned = assignObjectEntry(parentNode, parsed.key, node, lineNumber, errors);
250
+ if (!assigned)
251
+ return;
175
252
  stack.push({
176
253
  indent,
177
254
  node,
178
- parent: parentNode,
179
- key: parsed.key,
255
+ parent: assigned.parent,
256
+ key: assigned.key,
180
257
  });
181
258
  });
182
259
  return { ast: root, errors };
@@ -181,7 +181,7 @@ const BUILT_IN_TEMPLATES = Object.assign(Object.assign(Object.assign(Object.assi
181
181
  }, 'sequence-timeline-plain-text': {
182
182
  design: {
183
183
  title: 'default',
184
- structure: { type: 'sequence-timeline' },
184
+ structure: { type: 'sequence-timeline', showStepLabels: false },
185
185
  items: [{ type: 'plain-text' }],
186
186
  },
187
187
  }, 'sequence-timeline-rounded-rect-node': {
@@ -199,7 +199,7 @@ const BUILT_IN_TEMPLATES = Object.assign(Object.assign(Object.assign(Object.assi
199
199
  }, 'sequence-timeline-simple': {
200
200
  design: {
201
201
  title: 'default',
202
- structure: { type: 'sequence-timeline', gap: 20 },
202
+ structure: { type: 'sequence-timeline', gap: 20, showStepLabels: false },
203
203
  items: [{ type: 'simple', positionV: 'middle' }],
204
204
  },
205
205
  }, 'sequence-cylinders-3d-simple': {
package/esm/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const VERSION = "0.2.15";
1
+ export declare const VERSION = "0.2.16";
package/esm/version.js CHANGED
@@ -1 +1 @@
1
- export const VERSION = '0.2.15';
1
+ export const VERSION = '0.2.16';
@@ -18,6 +18,7 @@ const DEFAULT_ITEM_HEIGHT = 50;
18
18
  const FONT_SIZE = 14;
19
19
  const ARROW_SIZE = 14;
20
20
  const CORNER_RADIUS_NODE = 6;
21
+ const LIFELINE_MASK_GAP = 2;
21
22
  const LANE_PADDING = 60;
22
23
  const BTN_HALF_SIZE = 12;
23
24
  const BTN_MARGIN = 10;
@@ -232,17 +233,6 @@ const SequenceInteractionFlow = (props) => {
232
233
  const decorElements = [];
233
234
  const defsElements = [];
234
235
  const btnElements = [];
235
- // 绘制生命线
236
- if (showLifeline) {
237
- lanes.forEach((_lane, laneIndex) => {
238
- const centerX = getLaneCenterX(laneIndex);
239
- const startY = padding + headerOffset;
240
- const endY = totalHeight - padding;
241
- decorElements.push((0, jsx_runtime_1.jsx)(jsx_1.Path, { d: `M ${centerX} ${startY} L ${centerX} ${endY}`, stroke: colorBorder, strokeWidth: lifelineWidth, strokeDasharray: "5,5", fill: "none", "data-element-type": "shape" }));
242
- // 绘制生命线末端箭头(实心)
243
- decorElements.push(...(0, utils_1.createArrowElements)(centerX, endY, Math.PI / 2, 'triangle', colorBorder, 1, 10));
244
- });
245
- }
246
236
  // 绘制泳道标题
247
237
  if (showLaneHeader) {
248
238
  lanes.forEach((lane, laneIndex) => {
@@ -285,10 +275,6 @@ const SequenceInteractionFlow = (props) => {
285
275
  });
286
276
  const nodeColor = (0, utils_1.getPaletteColor)(options, [laneIndex]);
287
277
  const nodeThemeColors = (0, utils_1.getThemeColors)({ colorPrimary: nodeColor }, options);
288
- // 添加节点背景遮挡层,防止生命线虚线透过半透明节点显示
289
- // 只在节点中心放置窄条遮挡生命线,避免圆角处露出白色背景
290
- const maskStripWidth = lifelineWidth + 6;
291
- decorElements.push((0, jsx_runtime_1.jsx)(jsx_1.Rect, { x: centerX - maskStripWidth / 2, y: y, width: maskStripWidth, height: itemHeight, fill: colorBg }));
292
278
  // 构造类似 hierarchy-tree 的 _originalIndex
293
279
  const originalIndex = [laneIndex, rowIndex];
294
280
  // 附加到数据上,确保 Item 组件能正确识别
@@ -325,6 +311,41 @@ const SequenceInteractionFlow = (props) => {
325
311
  const centerX = getLaneCenterX(laneIndex);
326
312
  btnElements.push((0, jsx_runtime_1.jsx)(components_1.BtnAdd, { indexes: [laneIndex, childCount], x: centerX - BTN_HALF_SIZE, y: addNodeY }));
327
313
  });
314
+ // 绘制生命线(使用 mask 挖空节点区域,避免虚线穿透半透明节点)
315
+ if (showLifeline) {
316
+ // 预先按泳道分组节点,避免每条泳道都遍历全部节点
317
+ const nodeRectsByLane = new Map();
318
+ nodeLayoutById.forEach((layout) => {
319
+ let list = nodeRectsByLane.get(layout.laneIndex);
320
+ if (!list) {
321
+ list = [];
322
+ nodeRectsByLane.set(layout.laneIndex, list);
323
+ }
324
+ list.push({
325
+ x: layout.x,
326
+ y: layout.y,
327
+ width: layout.width,
328
+ height: layout.height,
329
+ });
330
+ });
331
+ lanes.forEach((_lane, laneIndex) => {
332
+ var _a;
333
+ const centerX = getLaneCenterX(laneIndex);
334
+ const startY = padding + headerOffset;
335
+ const endY = totalHeight - padding;
336
+ const laneNodeRects = (_a = nodeRectsByLane.get(laneIndex)) !== null && _a !== void 0 ? _a : [];
337
+ // 如果该泳道有节点,创建 mask 来挖空节点区域
338
+ let lifelineMaskAttr;
339
+ if (laneNodeRects.length > 0) {
340
+ const maskId = `lifeline-mask-${instanceId}-${laneIndex}`;
341
+ defsElements.push((0, jsx_runtime_1.jsxs)("mask", { id: maskId, maskUnits: "userSpaceOnUse", x: 0, y: 0, width: totalWidth, height: totalHeight, children: [(0, jsx_runtime_1.jsx)(jsx_1.Rect, { x: 0, y: 0, width: totalWidth, height: totalHeight, fill: "white" }), laneNodeRects.map((rect) => ((0, jsx_runtime_1.jsx)(jsx_1.Rect, { x: rect.x, y: rect.y - LIFELINE_MASK_GAP, width: rect.width, height: rect.height + LIFELINE_MASK_GAP * 2, fill: "black" })))] }));
342
+ lifelineMaskAttr = `url(#${maskId})`;
343
+ }
344
+ decorElements.push((0, jsx_runtime_1.jsx)(jsx_1.Path, { d: `M ${centerX} ${startY} L ${centerX} ${endY}`, stroke: colorBorder, strokeWidth: lifelineWidth, strokeDasharray: "5,5", fill: "none", "data-element-type": "shape", mask: lifelineMaskAttr }));
345
+ // 绘制生命线末端箭头(实心)
346
+ decorElements.push(...(0, utils_1.createArrowElements)(centerX, endY, Math.PI / 2, 'triangle', colorBorder, 1, 10));
347
+ });
348
+ }
328
349
  // 添加新泳道按钮 (最右侧)
329
350
  const lastLaneRightX = getLaneCenterX(lanes.length - 1) + laneWidth / 2;
330
351
  const newLaneX = lanes.length > 0 ? lastLaneRightX + BTN_LANE_ADD_Gap : padding;
@@ -3,5 +3,6 @@ import type { BaseStructureProps } from './types';
3
3
  export interface SequenceTimelineProps extends BaseStructureProps {
4
4
  gap?: number;
5
5
  lineOffset?: number;
6
+ showStepLabels?: boolean;
6
7
  }
7
8
  export declare const SequenceTimeline: ComponentType<SequenceTimelineProps>;
@@ -8,7 +8,7 @@ const layouts_1 = require("../layouts");
8
8
  const utils_1 = require("../utils");
9
9
  const registry_1 = require("./registry");
10
10
  const SequenceTimeline = (props) => {
11
- const { Title, Item, data, gap = 10, options } = props;
11
+ const { Title, Item, data, gap = 10, showStepLabels = true, options } = props;
12
12
  const { title, desc, items = [] } = data;
13
13
  const titleContent = Title ? (0, jsx_runtime_1.jsx)(Title, { title: title, desc: desc }) : null;
14
14
  const colorPrimary = (0, utils_1.getColorPrimary)(options);
@@ -44,7 +44,9 @@ const SequenceTimeline = (props) => {
44
44
  const itemY = index * (itemBounds.height + gap);
45
45
  const nodeY = itemY + itemBounds.height / 2;
46
46
  const indexes = [index];
47
- decorElements.push((0, jsx_runtime_1.jsx)(jsx_1.Text, { x: stepLabelX, y: nodeY, width: 70, fontSize: 18, fontWeight: "bold", alignHorizontal: "left", alignVertical: "middle", fill: palette[index % palette.length], children: `STEP ${index + 1}` }));
47
+ if (showStepLabels) {
48
+ decorElements.push((0, jsx_runtime_1.jsx)(jsx_1.Text, { x: stepLabelX, y: nodeY, width: 70, fontSize: 18, fontWeight: "bold", alignHorizontal: "left", alignVertical: "middle", fill: palette[index % palette.length], children: `STEP ${index + 1}` }));
49
+ }
48
50
  itemElements.push((0, jsx_runtime_1.jsx)(Item, { indexes: indexes, datum: item, data: data, x: itemX, y: itemY, positionH: "normal" }));
49
51
  decorElements.push((0, jsx_runtime_1.jsx)(jsx_1.Ellipse, { x: timelineX - nodeRadius, y: nodeY - nodeRadius, width: nodeRadius * 2, height: nodeRadius * 2, fill: palette[index % palette.length] }));
50
52
  btnElements.push((0, jsx_runtime_1.jsx)(components_1.BtnRemove, { indexes: indexes, x: itemX - btnBounds.width - 10, y: itemY + (itemBounds.height - btnBounds.height) / 2 }));
@@ -15,8 +15,8 @@ const svg_1 = require("./svg");
15
15
  function exportToPNGString(svg_2) {
16
16
  return __awaiter(this, arguments, void 0, function* (svg, options = {}) {
17
17
  var _a;
18
- const { dpr = (_a = globalThis.devicePixelRatio) !== null && _a !== void 0 ? _a : 2 } = options;
19
- const node = yield (0, svg_1.exportToSVG)(svg);
18
+ const { dpr = (_a = globalThis.devicePixelRatio) !== null && _a !== void 0 ? _a : 2, removeBackground = false } = options;
19
+ const node = yield (0, svg_1.exportToSVG)(svg, { removeBackground });
20
20
  const { width, height } = (0, utils_1.getViewBox)(node);
21
21
  return new Promise((resolve, reject) => {
22
22
  try {
@@ -22,7 +22,7 @@ function exportToSVGString(svg_1) {
22
22
  }
23
23
  function exportToSVG(svg_1) {
24
24
  return __awaiter(this, arguments, void 0, function* (svg, options = {}) {
25
- const { embedResources = true, removeIds = false } = options;
25
+ const { removeBackground = false, embedResources = true, removeIds = false, } = options;
26
26
  const clonedSVG = svg.cloneNode(true);
27
27
  const { width, height } = (0, utils_1.getViewBox)(svg);
28
28
  (0, utils_1.setAttributes)(clonedSVG, { width, height });
@@ -34,6 +34,9 @@ function exportToSVG(svg_1) {
34
34
  yield embedIcons(clonedSVG);
35
35
  }
36
36
  yield (0, font_1.embedFonts)(clonedSVG, embedResources);
37
+ if (removeBackground) {
38
+ removeSVGBackground(clonedSVG);
39
+ }
37
40
  cleanSVG(clonedSVG);
38
41
  return clonedSVG;
39
42
  });
@@ -288,6 +291,11 @@ function cleanSVG(svg) {
288
291
  removeUselessAttrs(svg);
289
292
  clearDataset(svg);
290
293
  }
294
+ function removeSVGBackground(svg) {
295
+ svg.style.removeProperty('background-color');
296
+ const background = (0, utils_1.getElementByRole)(svg, "background" /* ElementTypeEnum.Background */);
297
+ background === null || background === void 0 ? void 0 : background.remove();
298
+ }
291
299
  function removeBtnGroup(svg) {
292
300
  const btnGroup = (0, utils_1.getElementByRole)(svg, "btns-group" /* ElementTypeEnum.BtnsGroup */);
293
301
  btnGroup === null || btnGroup === void 0 ? void 0 : btnGroup.remove();
@@ -1,5 +1,10 @@
1
1
  export interface SVGExportOptions {
2
2
  type: 'svg';
3
+ /**
4
+ * 是否移除背景(SVG 背景样式 + 背景矩形)
5
+ * @default false
6
+ */
7
+ removeBackground?: boolean;
3
8
  /**
4
9
  * 是否将远程资源嵌入到 SVG 中
5
10
  * @default true
@@ -13,6 +18,11 @@ export interface SVGExportOptions {
13
18
  }
14
19
  export interface PNGExportOptions {
15
20
  type: 'png';
21
+ /**
22
+ * 是否移除背景(SVG 背景样式 + 背景矩形)
23
+ * @default false
24
+ */
25
+ removeBackground?: boolean;
16
26
  /**
17
27
  * 设备像素比,默认为浏览器的 devicePixelRatio
18
28
  * @default globalThis.devicePixelRatio || 2
@@ -44,6 +44,81 @@ function parseKeyValue(raw) {
44
44
  }
45
45
  return { key: text, value: undefined };
46
46
  }
47
+ function isUnsafeObjectKey(key) {
48
+ return key === '__proto__' || key === 'constructor' || key === 'prototype';
49
+ }
50
+ function assignObjectEntry(parent, rawKey, node, line, errors) {
51
+ if (!rawKey.includes('.')) {
52
+ if (isUnsafeObjectKey(rawKey)) {
53
+ errors.push({
54
+ path: rawKey,
55
+ line,
56
+ code: 'bad_syntax',
57
+ message: `Invalid key part: ${rawKey}`,
58
+ raw: rawKey,
59
+ });
60
+ return null;
61
+ }
62
+ parent.entries[rawKey] = node;
63
+ return { parent, key: rawKey };
64
+ }
65
+ const parts = rawKey.split('.');
66
+ if (parts.some((part) => !part)) {
67
+ errors.push({
68
+ path: rawKey,
69
+ line,
70
+ code: 'bad_syntax',
71
+ message: 'Invalid dotted key path.',
72
+ raw: rawKey,
73
+ });
74
+ return null;
75
+ }
76
+ let current = parent;
77
+ for (let index = 0; index < parts.length - 1; index += 1) {
78
+ const part = parts[index];
79
+ if (isUnsafeObjectKey(part)) {
80
+ errors.push({
81
+ path: rawKey,
82
+ line,
83
+ code: 'bad_syntax',
84
+ message: `Invalid key part in dotted path: ${part}`,
85
+ raw: rawKey,
86
+ });
87
+ return null;
88
+ }
89
+ const existing = current.entries[part];
90
+ if (!existing) {
91
+ const container = createObjectNode(line);
92
+ current.entries[part] = container;
93
+ current = container;
94
+ continue;
95
+ }
96
+ if (existing.kind !== 'object') {
97
+ errors.push({
98
+ path: parts.slice(0, index + 1).join('.'),
99
+ line,
100
+ code: 'bad_syntax',
101
+ message: 'Cannot assign dotted key under a list value.',
102
+ raw: rawKey,
103
+ });
104
+ return null;
105
+ }
106
+ current = existing;
107
+ }
108
+ const finalKey = parts[parts.length - 1];
109
+ if (isUnsafeObjectKey(finalKey)) {
110
+ errors.push({
111
+ path: rawKey,
112
+ line,
113
+ code: 'bad_syntax',
114
+ message: `Invalid key part in dotted path: ${finalKey}`,
115
+ raw: rawKey,
116
+ });
117
+ return null;
118
+ }
119
+ current.entries[finalKey] = node;
120
+ return { parent: current, key: finalKey };
121
+ }
47
122
  function createObjectNode(line, value) {
48
123
  return { kind: 'object', line, value, entries: {} };
49
124
  }
@@ -175,12 +250,14 @@ function parseSyntaxToAst(input) {
175
250
  return;
176
251
  }
177
252
  const node = createObjectNode(lineNumber, parsed.value);
178
- parentNode.entries[parsed.key] = node;
253
+ const assigned = assignObjectEntry(parentNode, parsed.key, node, lineNumber, errors);
254
+ if (!assigned)
255
+ return;
179
256
  stack.push({
180
257
  indent,
181
258
  node,
182
- parent: parentNode,
183
- key: parsed.key,
259
+ parent: assigned.parent,
260
+ key: assigned.key,
184
261
  });
185
262
  });
186
263
  return { ast: root, errors };
@@ -183,7 +183,7 @@ const BUILT_IN_TEMPLATES = Object.assign(Object.assign(Object.assign(Object.assi
183
183
  }, 'sequence-timeline-plain-text': {
184
184
  design: {
185
185
  title: 'default',
186
- structure: { type: 'sequence-timeline' },
186
+ structure: { type: 'sequence-timeline', showStepLabels: false },
187
187
  items: [{ type: 'plain-text' }],
188
188
  },
189
189
  }, 'sequence-timeline-rounded-rect-node': {
@@ -201,7 +201,7 @@ const BUILT_IN_TEMPLATES = Object.assign(Object.assign(Object.assign(Object.assi
201
201
  }, 'sequence-timeline-simple': {
202
202
  design: {
203
203
  title: 'default',
204
- structure: { type: 'sequence-timeline', gap: 20 },
204
+ structure: { type: 'sequence-timeline', gap: 20, showStepLabels: false },
205
205
  items: [{ type: 'simple', positionV: 'middle' }],
206
206
  },
207
207
  }, 'sequence-cylinders-3d-simple': {
package/lib/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const VERSION = "0.2.15";
1
+ export declare const VERSION = "0.2.16";
package/lib/version.js CHANGED
@@ -1,4 +1,4 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.VERSION = void 0;
4
- exports.VERSION = '0.2.15';
4
+ exports.VERSION = '0.2.16';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@antv/infographic",
3
- "version": "0.2.15",
3
+ "version": "0.2.16",
4
4
  "description": "An Infographic Generation and Rendering Framework, bring words to life!",
5
5
  "keywords": [
6
6
  "antv",