Skip to content

Commit d6c5066

Browse files
authored
feat: add relations infographic (#161)
* feat(syntax): support parse graph data * docs: update skills and site docs * refactor(syntax): support parse incomplete relation * refactor(syntax): support edge id * feat(designs): add dagre flow structure * refactor(designs): optimize badge card * feat(designs): add group field with datum and adapt strctures * docs: update docs, prompt and skills * feat(jsx): support animate * feat(designs): relation dagre flow support edge animation * refactor(designs): adjust dagre props * refactor(shared): add relation dataset * feat(designs): support simple circle node * refactor(designs): dagre force use dagre route for loop graph * refactor(syntax): support parse id/group/showArrow/arrowType * fix(utils): measure text ignore non string and number value * refactor(site): optmize gallery * feat: add templates * docs(site): update site docs
1 parent 827399a commit d6c5066

34 files changed

Lines changed: 2405 additions & 95 deletions

.skills/infographic-creator/SKILL.md

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,9 @@ theme
4545
- Key-value pairs are expressed as "key value", and arrays are expressed as "-" items
4646
- The icon value is provided directly with keywords or icon names (such as `mdi/chart-line`)
4747
- `data` should contain title/desc/items (which can be omitted according to semantics)
48-
- `data.items` should contain label(string)/value(number)/desc(string)/icon(string)/children(object), where children represents the hierarchical structure
48+
- `data` can include `relations`/`illus`/`attributes` for graph links, illustrations, and extended fields
49+
- `data.items` should contain id(string)/label(string)/value(number)/desc(string)/icon(string)/illus(string)/group(string)/children(object), where children represents the hierarchical structure
50+
- `data.relations` describes graph edges with `from`/`to` plus optional `id`/`label`/`direction`/`showArrow`/`arrowType`; mermaid-style `A -> B` is supported when generating relation graphs
4951
- For comparison templates (template names starting with `compare-`), construct exactly two root nodes and place every comparison item under them as children to keep the hierarchy clear
5052
- For `hierarchy-structure`, `data.items` renders top-to-bottom (first item at the top) and supports up to 3 levels (root -> group -> item)
5153
- `theme` field is for customizing the theme of the infographic, including palette, font, etc.
@@ -97,16 +99,36 @@ interface Data {
9799
title?: string;
98100
desc?: string;
99101
items: ItemDatum[];
102+
relations?: RelationDatum[];
103+
illus?: Record<string, string | ResourceConfig>;
104+
attributes?: Record<string, object>;
100105
[key: string]: any;
101106
}
102107
103-
interface ItemDatum {
104-
icon?: string;
108+
interface BaseDatum {
109+
id?: string;
110+
icon?: string | ResourceConfig;
105111
label?: string;
106112
desc?: string;
107113
value?: number;
108-
illus?: string;
114+
attributes?: Record<string, object>;
115+
[key: string]: any;
116+
}
117+
118+
interface ItemDatum extends BaseDatum {
119+
illus?: string | ResourceConfig;
109120
children?: ItemDatum[];
121+
group?: string;
122+
[key: string]: any;
123+
}
124+
125+
interface RelationDatum extends BaseDatum {
126+
from: string;
127+
to: string;
128+
label?: string;
129+
direction?: 'forward' | 'both' | 'none';
130+
showArrow?: boolean;
131+
arrowType?: 'arrow' | 'triangle' | 'diamond';
110132
[key: string]: any;
111133
}
112134
```
@@ -201,6 +223,10 @@ interface ItemDatum {
201223
- list-zigzag-down-simple
202224
- list-zigzag-up-compact-card
203225
- list-zigzag-up-simple
226+
- relation-dagre-flow-tb-simple-circle-node
227+
- relation-dagre-flow-tb-animated-simple-circle-node
228+
- relation-dagre-flow-tb-badge-card
229+
- relation-dagre-flow-tb-animated-badge-card
204230

205231
**Template Selection Guidelines:**
206232
- For strict sequential order: processes/steps/development trends → `sequence-*` series

.skills/infographic-syntax-creator/references/prompt.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,16 @@
3030
- 键值对使用「键 空格 值」
3131
- 数组使用 `-` 作为条目前缀(行内写法仅在用户明确要求时使用)
3232
- `data` 常见字段:
33-
- `title`(string) / `desc`(string) / `items`(array)
33+
- `title`(string) / `desc`(string) / `items`(array) / `relations`(array) / `illus`(object) / `attributes`(object)
3434
- `data.items` 常见字段:
35-
- `label`(string) / `value`(number) / `desc`(string) / `icon`(string) / `children`(array)
35+
- `id`(string) / `label`(string) / `value`(number) / `desc`(string) / `icon`(string) / `illus`(string) / `group`(string) / `children`(array) / `attributes`(object)
36+
- `data.relations` 常见字段:
37+
- `id`(string) / `from`(string) / `to`(string) / `label`(string) / `direction`('forward' | 'both' | 'none', 不填默认 'forward') / `showArrow`(boolean) / `arrowType`('arrow' | 'triangle' | 'diamond')
3638
- 对比类模板(名称以 `compare-` 开头)必须构建两个根节点,所有对比项作为这两个根节点的 children
3739
- `hierarchy-structure` 模板最多支持 3 层(根层 → 分组 → 子项),且 `data.items` 顺序即从上到下的层级顺序(第 1 个在最上)
3840
- `theme` 可用 `theme <theme-name>`,或使用 block 自定义 `palette` 等;不写即默认主题,可选主题名:`dark``hand-drawn`
3941
- icon 直接使用图标名(如 `mdi/chart-line`
42+
- 关系类模板(`relation-*`)支持 `relations`,也支持 Mermaid 风格的流式关系写法(如 `A -> B``A <- B`),同时解析出节点列表和边列表。
4043
- 禁止输出 JSON、Markdown 或解释性文字
4144

4245
## 模板选择
@@ -110,6 +113,10 @@
110113
- list-zigzag-down-simple
111114
- list-zigzag-up-compact-card
112115
- list-zigzag-up-simple
116+
- relation-dagre-flow-tb-simple-circle-node
117+
- relation-dagre-flow-tb-animated-simple-circle-node
118+
- relation-dagre-flow-tb-badge-card
119+
- relation-dagre-flow-tb-animated-badge-card
113120

114121
## 生成流程
115122

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
/** @jsxImportSource ../../../src */
2+
import { describe, expect, it } from 'vitest';
3+
import { Group, Path, Rect, renderSVG, Text } from '../../../src';
4+
5+
describe('SVG Animation Examples', () => {
6+
it('should create a pulsing button effect', () => {
7+
const PulsingButton = () => (
8+
<Group>
9+
<Rect
10+
x={10}
11+
y={10}
12+
width={100}
13+
height={40}
14+
rx={5}
15+
fill="#1890ff"
16+
opacity={0.8}
17+
>
18+
<animate
19+
attributeName="opacity"
20+
values="0.8;1;0.8"
21+
dur="2s"
22+
repeatCount="indefinite"
23+
/>
24+
</Rect>
25+
<Text x={60} y={30} fill="white" fontSize={16} textAnchor="middle">
26+
Click Me
27+
</Text>
28+
</Group>
29+
);
30+
31+
const svg = renderSVG(<PulsingButton />);
32+
expect(svg).toContain('<animate');
33+
expect(svg).toContain('attributeName="opacity"');
34+
expect(svg).toContain('values="0.8;1;0.8"');
35+
});
36+
37+
it('should create a dashed line animation (流动的虚线边)', () => {
38+
const FlowingDashedLine = () => (
39+
<Path
40+
d="M 50,50 L 200,50"
41+
stroke="#52c41a"
42+
strokeWidth={3}
43+
fill="none"
44+
strokeDasharray="10,5"
45+
strokeLinecap="round"
46+
>
47+
<animate
48+
attributeName="stroke-dashoffset"
49+
from="0"
50+
to="15"
51+
dur="0.5s"
52+
repeatCount="indefinite"
53+
/>
54+
</Path>
55+
);
56+
57+
const svg = renderSVG(<FlowingDashedLine />);
58+
expect(svg).toContain('stroke-dasharray="10,5"');
59+
expect(svg).toContain('attributeName="stroke-dashoffset"');
60+
expect(svg).toContain('from="0"');
61+
expect(svg).toContain('to="15"');
62+
});
63+
64+
it('should create a path drawing animation (路径绘制动画)', () => {
65+
const DrawingPath = () => {
66+
const pathLength = 300; // 路径总长度
67+
return (
68+
<Path
69+
d="M 20,50 Q 100,20 180,50 T 340,50"
70+
stroke="#fa8c16"
71+
strokeWidth={2}
72+
fill="none"
73+
strokeDasharray={pathLength}
74+
strokeDashoffset={pathLength}
75+
>
76+
<animate
77+
attributeName="stroke-dashoffset"
78+
from={String(pathLength)}
79+
to="0"
80+
dur="3s"
81+
fill="freeze"
82+
/>
83+
</Path>
84+
);
85+
};
86+
87+
const svg = renderSVG(<DrawingPath />);
88+
expect(svg).toContain('stroke-dasharray="300"');
89+
expect(svg).toContain('stroke-dashoffset="300"');
90+
expect(svg).toContain('from="300"');
91+
expect(svg).toContain('to="0"');
92+
});
93+
94+
it('should create a rotating loader (旋转加载器)', () => {
95+
const RotatingLoader = () => (
96+
<Group>
97+
<Rect
98+
x={45}
99+
y={5}
100+
width={10}
101+
height={40}
102+
rx={5}
103+
fill="#13c2c2"
104+
transform="translate(50, 50)"
105+
>
106+
<animateTransform
107+
attributeName="transform"
108+
attributeType="XML"
109+
type="rotate"
110+
from="0 50 50"
111+
to="360 50 50"
112+
dur="1s"
113+
repeatCount="indefinite"
114+
/>
115+
</Rect>
116+
</Group>
117+
);
118+
119+
const svg = renderSVG(<RotatingLoader />);
120+
expect(svg).toContain('<animateTransform');
121+
expect(svg).toContain('type="rotate"');
122+
expect(svg).toContain('from="0 50 50"');
123+
expect(svg).toContain('to="360 50 50"');
124+
});
125+
126+
it('should create a breathing effect (呼吸效果)', () => {
127+
const BreathingCircle = () => (
128+
<circle cx={50} cy={50} r={30} fill="#722ed1">
129+
<animate
130+
attributeName="r"
131+
values="30;40;30"
132+
dur="2s"
133+
repeatCount="indefinite"
134+
/>
135+
<animate
136+
attributeName="opacity"
137+
values="1;0.5;1"
138+
dur="2s"
139+
repeatCount="indefinite"
140+
/>
141+
</circle>
142+
);
143+
144+
const svg = renderSVG(<BreathingCircle />);
145+
expect(svg).toContain('attributeName="r"');
146+
expect(svg).toContain('values="30;40;30"');
147+
expect(svg).toContain('attributeName="opacity"');
148+
});
149+
150+
it('should create a complex edge animation for graphs (复杂的图边动画)', () => {
151+
const AnimatedEdge = ({
152+
d,
153+
color = '#1890ff',
154+
}: {
155+
d: string;
156+
color?: string;
157+
}) => (
158+
<Group>
159+
{/* 背景线 */}
160+
<Path d={d} stroke={color} strokeWidth={2} fill="none" opacity={0.3} />
161+
{/* 流动的虚线 */}
162+
<Path
163+
d={d}
164+
stroke={color}
165+
strokeWidth={2}
166+
fill="none"
167+
strokeDasharray="8,4"
168+
>
169+
<animate
170+
attributeName="stroke-dashoffset"
171+
from="0"
172+
to="12"
173+
dur="0.6s"
174+
repeatCount="indefinite"
175+
/>
176+
</Path>
177+
</Group>
178+
);
179+
180+
const svg = renderSVG(
181+
<AnimatedEdge d="M 10,50 L 100,50 L 150,100" color="#52c41a" />,
182+
);
183+
expect(svg).toContain('stroke-dasharray="8,4"');
184+
expect(svg).toContain('attributeName="stroke-dashoffset"');
185+
expect(svg).toContain('stroke="#52c41a"');
186+
});
187+
});

0 commit comments

Comments
 (0)