Skip to content

复合图元与连线 ​

这个示例用 @ggn/draw 画出一张完整的业务图。角色、卡片、胶囊和容器都由示例自己注册, draw 只负责矩形、椭圆、路径、文字、连线、渐变、场景和 SVG 输出。

Professional USER GROUP Restaurant SYSTEM Guest Chef Food Critic ACTOR Eat Food Pay for Food Drink Review
深色配方:对照参考稿
Professional USER GROUP Restaurant SYSTEM Guest Chef Food Critic ACTOR Eat Food Pay for Food Drink Review
浅色配方:同一套几何,切换到 light tokens 与浅色背景

实现:只用 @ggn/draw ​

页面里的图不是静态图片。文档、明暗背景、形状、端口、连线和箭头都会实时变成 draw 的 DrawDocument / 场景叶子,再由六段管线导出:

ts
import {
  bindDocument, buildScene, layoutDocument, parseDocument, renderToSvg, resolveTheme,
} from "@ggn/draw";
import {
  USE_CASE_TOKENS, useCaseDesignDocument,
  useCaseDesignRegistry, useCaseDesignRouter,
} from "./usecase-draw";

const registry = useCaseDesignRegistry();
const theme = resolveTheme({
  tokens: USE_CASE_TOKENS,
  typography: { fontSize: 16 },
});
const document = parseDocument(useCaseDesignDocument("dark"));
if (!theme.ok || !document.ok) throw new Error("输入不合法");

const bound = bindDocument(document.value, registry);
if (!bound.ok) throw new Error("绑定失败");

const layout = layoutDocument(bound.value, {
  typography: theme.value.typography,
  router: useCaseDesignRouter,
});
if (!layout.ok) throw new Error("布局失败");

const scene = buildScene(bound.value, layout.value, {
  registry,
  theme: theme.value,
});
if (!scene.ok) throw new Error("场景构建失败");

const svg = renderToSvg(scene.value);

浅色只替换两处:文档的浅色背景,以及 USE_CASE_TOKENS_LIGHT。节点、端口、pins、路由都不动。

设计稿的手画连线也不是一张独立 SVG,而是在 draw 的边注册项里逐片输出:

ts
import { polylineD } from "@ggn/draw";

registry = registry.with({
  edges: {
    link: {
      markers: { end: "design-open-arrow" },
      render(ctx) {
        ctx.out.node({
          kind: "path",
          key: ctx.out.key(""),
          // 路径表完整保存在 showcase.ts;未覆盖的边仍走通用折线。
          d: DESIGN_EDGE_PATHS[ctx.edgeId] ?? polylineD(ctx.route),
          style: { ...ctx.style, fill: "none" },
        });
      },
    },
  },
}, { override: true });

完整实现住在 packages/draw/docs/.vitepress/theme/components/usecase-draw/showcase.ts; 这些形状与配色属于示例本身,不会被写进绘制库。

示例里的四类图元 ​

形状用到的词对照目标稿的哪一条
独立人物椭圆(圆盘)+ 椭圆(头)+ 路径(肩)+ 文字头肩分色;名字在下方,两个端口高低分开
成员卡片矩形 + 圆盘头像 + 文字 + 徽标胶囊头像在上、名字靠底,右上角可放状态标签
胶囊节点矩形(rx = 半高)+ 24×24 字形路径玻璃面、专属图标;设计稿使用无图标槽的裸字形
容器路径(42px 页签)+ 矩形(主体)+ 徽标 + 文字上方圆角、下沿平直;页签宽度可按标题与徽标计算
连线手画 path(圆角折线)+ 渐变描边 + 箭头线色从冷走到暖;Chef 是灰蓝虚线,Food Critic 是完整暖色实线

设计稿的连线是手画的,因此示例用 useCaseDesignRouter 把圆弧折点原样写进路由; 其他图仍然走通用的避障或中点走廊路由。路由只决定折点,端口锚点一个像素都不动。 连线渐变使用 userSpaceOnUse:水平直线的包围盒高度为 0,用 objectBoundingBox 会让整条线在浏览器里消失。

另外,open-arrow 这类端点装饰是库内置的 marker:注册表要 createRegistry({ base: builtinRegistry }) 才拿得到。漏了 base 时不会抛异常,但 build 会产生 UNKNOWN_MARKER 和 FALLBACK_USED, 图上也会少一枚箭头。注册表的基础规则见节点注册。

样式如何复用 ​

形状只拿得到 ctx.style(已解析成具体值的涂法),拿不到主题 token——但这不代表颜色写死: 文档的 styles 段里可以写 { token } 色标,resolveTheme 一换,渐变、图标、端口、投影跟着换。 上面两张图的节点、端口、位置与路由完全一样;只有 tokens 与浅色背景配方不同。

顺带一个「同一份几何怎么吃两套主题」的例子:头像几何只有一份(person.ts 里 44×44 的盘 + 头 + 肩),盘吃 ctx.style 的面与描边,头/肩吃 detailColor / detailAccent。所以换 tokens 只换颜色,形状一个数字都不用动;文档站示例也从这里取几何,两处不会各画各的。

这张图的背景也是文档的一部分:dark 是径向暗角 + 两团环境光 + 20px 点阵; light 是 #F8FAFC → #F1F5F9 斜向渐变 + 更淡的环境光 + 点阵。build 把它们展成铺满画布的矩形。 文档同时声明 canvas: 0 0 920 560,所以导出的 SVG 保持设计稿的原始 viewBox。

想自己改一版:把 <UseCaseCompare /> 里的文档换成你的 DrawDocument(谁连谁 + pins), 注册表换成你的形状——@ggn/draw 这边一行都不用动。

MIT License