reladraw
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chinesereladraw
reladraw
A text language for diagrams. Source in, standalone SVG out, no runtime dependencies.
Every other diagram language hands the arrangement to a layout engine, so where things land is an output you cannot predict from what you wrote. reladraw inverts that: the file states the arrangement in the terms a person would say out loud — , , — and the tool works out only the distances. Nothing is ever chosen for you.
right of apibetween web and workerlevel with queueThat matters here specifically. You cannot see the SVG you produced. With auto-layout that leaves you guessing; with reladraw the arrangement is in the sentences you just wrote, so re-reading your own source tells you where everything is.
用于图表的文本语言。输入源代码,输出独立 SVG,无运行时依赖。
大多数其他图表语言都会把布局交给布局引擎,因此元素最终落在哪里,是你无法仅根据所写内容预测的输出。reladraw 反其道而行:文件使用人们口头表达位置时会使用的语句来描述布局——、、——工具只负责计算距离。绝不会替你选择任何布局。
right of apibetween web and workerlevel with queue这一点在这里尤其重要。你无法看到自己生成的 SVG。使用自动布局时,这会让你只能猜测;使用 reladraw 时,布局就写在你刚写下的语句里,因此重新阅读自己的源代码就能知道所有元素的位置。
Check it is installed
检查是否已安装
reladraw --helpIf that fails, works without installing, and installs the command.
npx reladrawnpm install -g reladrawreladraw --help如果失败, 可在无需安装的情况下运行, 则会安装该命令。
npx reladrawnpm install -g reladrawThe loop
工作流程
- Write a file.
.reladraw - (
reladraw diagram.reladraw -o diagram.svgwrites to stdout; the default output path is the input with-o -in place of its extension)..svg - If it fails, the error names the statement and what is wrong with it. Fix and re-run.
- Re-read your own source to confirm the picture says what you meant.
Errors are the feedback you have. A file that renders is a file whose arrangement you fully stated, because anything left ambiguous is refused rather than guessed:
diagram.reladraw:3: "c" does not say where it sits vertically: "right of a" and "left of b" would put it in different placesBe honest about what this does not give you. Re-reading the source confirms intent — that the worker landed under the API, that all four machines hang off the hub. It does not confirm outcome: whether a long text overflowed, whether a line crosses four others, whether two independently-anchored clusters collided. A machine-readable diagnostics report is planned and is not built yet, so for a large or dense diagram, say plainly that you have stated the arrangement but not verified the render.
- 编写一个 文件。
.reladraw - 运行 (
reladraw diagram.reladraw -o diagram.svg会写入 stdout;默认输出路径会将输入文件的扩展名替换为-o -)。.svg - 如果执行失败,错误信息会指出具体语句及其问题。修复后重新运行。
- 重新阅读自己的源代码,确认图表表达了你原本的意图。
错误就是你所获得的反馈。一个能够渲染的文件,意味着你已经完整陈述了它的布局,因为任何含糊之处都会被拒绝,而不是被猜测:
diagram.reladraw:3: "c" does not say where it sits vertically: "right of a" and "left of b" would put it in different places要如实说明这不能保证什么。 重新阅读源代码可以确认意图——worker 位于 API 下方,四台机器都连接在 hub 上;但不能确认结果——长文本是否溢出、连线是否穿过另外四条线、两个独立锚定的节点簇是否发生碰撞。机器可读的诊断报告仍在规划中,尚未实现。因此,对于大型或密集的图表,应明确说明:你已经陈述了布局,但没有验证渲染结果。
The one idea
核心理念
A gap is a minimum distance, never an exact one. Everything ends up as close together as your statements allow.
So putting something between two things is what pushes them apart, by exactly what it needs:
node hub "Hub"
node side "Side" left of hub
node wedge "Wedged" right of side left of hub level with hubNothing says how far apart and are. Delete and they close back up. You never pick a number and no number goes stale when a text grows. There are no coordinates in this language, in any form.
hubsidewedge间距是最小距离,绝不是精确距离。 一切都会按照你的陈述允许的方式尽量靠近。
因此,把某个元素放在两个元素之间,会将它们推开,所需距离恰好足够:
node hub "Hub"
node side "Side" left of hub
node wedge "Wedged" right of side left of hub level with hub没有任何语句规定 和 相距多远。删除 后,它们会重新靠拢。你永远不需要选择具体数值,文本增长时也不会有过期数值。这个语言中没有任何形式的坐标。
hubsidewedgeWriting a file
编写文件
One statement per line. No blocks, no continuations, no significant indentation. starts a comment and may trail a statement.
//A statement is a positional head — the keyword, a name, then a text — followed by attributes and placements in any order. A token ending in a colon opens an attribute and nothing else does, so and are the same statement.
node n "text" gap: wide below workernode n "text" below worker gap: wide一条语句占一行。没有块结构、没有续行,缩进也没有特殊意义。 表示注释,可以跟在语句末尾。
//一条语句由位置头开始——依次是关键字、名称和文本——后面可以按任意顺序跟属性和定位。以冒号结尾的词元会开启一个属性,并且只有这种词元才会如此,因此 与 是同一条语句。
node n "text" gap: wide below workernode n "text" below worker gap: wideNodes
节点
node <name> ["<text>" [(<text properties>)]] [<placement> | <attribute>] ...Leave the text out and the node takes its own name as its text ( draws a node reading "parser"). Write for a deliberately blank node. Inside text, — a slash with a space on each side — is a line break; a slash without spaces is an ordinary character, so and URLs survive.
node parser""/TCP/IPContainment is a dotted name and the parent must be declared first. Children stack vertically in written order unless a child carries its own placement.
node server "Server"
node server.api "API"
node server.worker "Worker"A container with and draws nothing and takes no room of its own, which is how you make a group that can be placed against as one shape.
""fill: none border: noneNode attributes: , and (each a color), , , , , , (a quoted destination), and on a container.
stylefillbordershapeiconbadgegapoverlap: allowurlcontents:contents: (widths: match, align: center)widths:naturalmatchfillalign:leftcenterrightnode <name> ["<text>" [(<text properties>)]] [<placement> | <attribute>] ...省略文本时,节点会使用自己的名称作为文本( 会绘制一个显示 “parser” 的节点)。写入 可以有意创建一个空白节点。在文本中,——即两侧各有一个空格的斜杠——表示换行;不带空格的斜杠只是普通字符,因此 和 URL 可以原样保留。
node parser""/TCP/IP包含关系使用点号名称,并且父节点必须先声明。除非子节点带有自己的定位,否则子节点会按书写顺序垂直堆叠。
node server "Server"
node server.api "API"
node server.worker "Worker"一个同时带有 和 的容器不会绘制任何内容,也不占用自身空间;你可以用这种方式创建一个可作为整体形状参与定位的组。
""fill: none border: none节点属性包括:、 和 (均为颜色)、、、、、、(带引号的目标地址),以及容器上的 。
stylefillbordershapeiconbadgegapoverlap: allowurlcontents:contents: (widths: match, align: center)widths:naturalmatchfillalign:leftcenterrightThe text and its brackets
文本及其括号
Everything a text says about itself goes in brackets after it, never among the node's attributes: , (), (fold every n characters), (, how the lines range against each other) and (where the block sits, named from the nine positions — , , and the rest).
colorsizesmall | normal | largewrapalignleft | center | rightattop-leftbottom-centercenternode docker "Docker" (at: bottom-center, align: center) below deploy
node aside "a longer remark that folds" (size: small, wrap: 30) shape: none
edge a -> b "rclone" (color: muted)An edge's text takes the same keys less . A style has no text of its own, so it hangs the bracket off a key: .
atstyle aside text: (size: small, color: muted)A stretch of a text can borrow a style's text color, which is how a node carries a quieter qualifier:
style dim text: (color: muted)
node grinder "Grinder / [dim]medium-fine[/dim]"The mark names a style and never a color; the closer repeats the name; is a literal bracket. There is no attribute — it was removed, and an older file carrying it gets an error naming the mark to write instead.
\[subtext文本对自身的所有设置都应写在后面的括号中,绝不能放在节点属性之间:、()、(每 n 个字符折行)、(,决定各行彼此之间的对齐方式)以及 (文本块所在位置,以九个位置命名——、、 等)。
colorsizesmall | normal | largewrapalignleft | center | rightattop-leftbottom-centercenternode docker "Docker" (at: bottom-center, align: center) below deploy
node aside "a longer remark that folds" (size: small, wrap: 30) shape: none
edge a -> b "rclone" (color: muted)边的文本使用相同的键,但不包括 。style 本身没有文本,因此要把括号挂在某个键后面:。
atstyle aside text: (size: small, color: muted)文本中的一段文字可以借用某个 style 的文本颜色,因此节点可以附加一个更低调的限定说明:
style dim text: (color: muted)
node grinder "Grinder / [dim]medium-fine[/dim]"标记引用的是 style 名称,绝不是颜色;闭合标记会重复该名称; 表示字面量方括号。不存在 属性——它已被移除;如果旧文件中仍包含它,工具会报错并指出应改用哪个标记。
\[subtextThe body
主体
Every node has one body and two keys can name it. is the outline it is drawn with — is the default and is text with no box at all, which is what an annotation is. draws the node as a picture, with no box: , , , , , , . Writing both is an error.
shape: rectangle | document | nonerectanglenoneicon: <name>diskdesktoplaptoppackagecubescubedatabasebadge: <name>node dump "nightly dump" shape: document
node aside "a remark" (wrap: 30) shape: none
node unit icon: cube
node drive "External HD" badge: diskA node drawn as a picture and given no text shows none — everywhere else a node with no text takes its name, but a picture usually is the statement.
A color attribute names the part it colors: is the area, the outline, the text, and the drawn line of an edge. A word is refused on a kind that has no such part, so on a node is an error — with no body there is no outline, and is its only color.
fillbordertextlineborder:shape: nonetext:A style contributes a part only to the kinds that have it, so a style shared between nodes and edges writes one key for each — colors the nodes' borders and the edges' lines from one name.
style backup border: #d2904e line: #d2904eThere is no attribute. It was removed because it named no part; if you have seen it in an older file, it is on a node, on one with no body or a picture body, and on an edge.
strokebordertextlineEvery attribute is checked by name, so do not invent one. A word the tool does not know is an error, and so is a real word on a kind that has no use for it — on a node with no body, or on an edge, on a node that can have no children. The error says either what the kind takes or where the word does belong. A key handed over by a style is exempt, which is what lets one style dress both nodes and edges.
fill:gap:overlap:contents:每个节点都有一种主体形式,可由两个键指定。 指定绘制节点时使用的轮廓—— 是默认值, 表示只有文本而没有外框,适合用于注释。 则会将节点绘制成图片,不带外框:、、、、、、。同时写入两者会报错。
shape: rectangle | document | nonerectanglenoneicon: <name>diskdesktoplaptoppackagecubescubedatabasebadge: <name>node dump "nightly dump" shape: document
node aside "a remark" (wrap: 30) shape: none
node unit icon: cube
node drive "External HD" badge: disk以图片形式绘制且未提供文本的节点不会显示任何内容;其他情况下,无文本节点会使用其名称,而图片通常已经完整表达了节点含义。
颜色属性会指明所着色的部分: 是区域, 是轮廓, 是文本, 是边所绘制的线条。如果在不具备相应部分的对象上使用某个词,就会报错——例如对 节点使用 。因为该节点没有主体,也就没有轮廓, 才是它唯一可用的颜色。
fillbordertextlineshape: noneborder:text:样式只会为具备相应部分的对象贡献属性,因此同时用于节点和边的样式需要为每种对象各写一个键—— 可通过同一个名称同时设置节点的边框颜色和边的线条颜色。
style backup border: #d2904e line: #d2904e不存在 属性。它被移除,因为它没有指明要着色的部分;如果在旧文件中见过它,应在普通节点上改用 ,在无主体或图片主体节点上改用 ,在边上改用 。
strokebordertextline所有属性都会按名称校验,因此不要自行编造。 工具不认识的词会报错;某个实际存在的词若用在不具备该用途的对象上也会报错——例如对无主体节点使用 、对边使用 或 、对不能有子项的节点使用 。错误信息会指出该对象支持哪些键,或指出这个键应放在哪里。由 style 提供的键不受此限制,因此同一个 style 可以同时装饰节点和边。
fill:gap:overlap:contents:Placement
定位
above X below X left of X right of X
above-left of X above-right of X below-left of X below-right of X
level with X top level with X bottom level with X
left level with X right level with Xofandright of borg and bareThree rules that decide most of what you write:
- A lone directional placement binds both axes. also centers the node vertically on Docker, because walking right from something keeps you on its center line. That half drops away as soon as another placement claims that axis.
right of docker - Two placements on one axis with nothing on the other is an error, because the tool would have to choose which row the node shares. Add or a
level with X.below - Exactly one node in the file may be left unplaced. Everything else is positioned, directly or transitively, against it.
Gaps are named — , , (default), — and belong to the placement, in brackets:
nonetightnormalwidenode stack "Stack" below wedge right of wall (gap: tight) gap: widegap:(gap: none)above X below X left of X right of X
above-left of X above-right of X below-left of X below-right of X
level with X top level with X bottom level with X
left level with X right level with X方向之后可以省略 。一个节点可以有任意多个定位语句,其中任意一条都可以通过 联合命名多个目标—— 会相对于恰好包围它们的盒子进行定位。
ofandright of borg and bare以下三条规则可以决定你编写的大部分内容:
- 单独的方向定位会同时约束两个轴。 也会让节点在垂直方向上与 Docker 居中对齐,因为从某物向右走时仍会位于它的中心线上。当另一个定位语句占用该轴时,这半个约束就会消失。
right of docker - 同一轴上有两个定位语句,而另一轴没有任何定位语句,会报错,因为工具必须决定节点与哪一行对齐。请添加 或
level with X。below - 文件中最多只能有一个节点未定位。 其余节点都必须直接或间接地相对于它定位。
间距通过名称指定——、、(默认)、——归属于定位语句,并写在括号中:
nonetightnormalwidenode stack "Stack" below wedge right of wall (gap: tight) gap: widegap:(gap: none)Edges
边
edge <from> -> <to> ["<text>"] [between <a> and <b> [vertically|horizontally]] [attributes]<-<->a <- bb -> aserver.apifrom:to:topbottomleftrightbetween a and bAn edge with text widens the corridor between its own two ends by what the text needs, so texts are safe to add.
edge <from> -> <to> ["<text>"] [between <a> and <b> [vertically|horizontally]] [attributes]<-<->a <- bb -> aserver.apifrom:to:topbottomleftrightbetween a and b带文本的边会按文本所需宽度拓宽其两端之间的通道,因此添加文本是安全的。
Annotations
注释
node <name> "<text>" (wrap: 30) shape: none <placement> ...There is no statement — an annotation is a node with no body, anchored to a node so it travels with it. Always give one a — without one a sentence is drawn as one very long line across whatever is beside it.
note(wrap: n)node <name> "<text>" (wrap: 30) shape: none <placement> ...没有 语句——注释就是一个无主体节点,并锚定到某个节点上,以便随其移动。务必为它提供 ——否则一句话会被绘制成一条很长的横线,横穿旁边的内容。
note(wrap: n)On a box
位于框上
node <name> "<text>" on <node> at <position>Holds a node on another's box at one of nine named points — , , , , , , , , — inset from that corner or edge and overlapping it on purpose. puts it hard against the edge.
top-lefttop-centertop-rightleft-centercenterright-centerbottom-leftbottom-centerbottom-right(gap: none)This is not containment: a dotted name puts something inside a box and widens it, an overlay is stamped on it and changes nothing. Use it for a mark, a count, or a link line at the bottom of a box.
node <name> "<text>" on <node> at <position>可以将节点固定在另一个节点的框上九个命名位置之一——、、、、、、、、——位置会从相应角或边向内缩进,并有意与目标框重叠。 会使其紧贴边缘。
top-lefttop-centertop-rightleft-centercenterright-centerbottom-leftbottom-centerbottom-right(gap: none)这并不是包含关系:点号名称表示把某物放在框内并使其撑大;叠放层则盖在框上,不改变框。可以用它添加标记、计数,或在框底部添加链接线。
Styles
样式
style store fill: #142814 border: #486544 badge: database
node records "Records" style: storeColors are written directly — any hex or CSS color, or . There is no list of color words the tool knows.
nonestyle store fill: #142814 border: #486544 badge: database
node records "Records" style: store颜色直接写入——可以是任意十六进制值或 CSS 颜色,也可以是 。工具没有预置颜色词列表。
noneA complete small file
一个完整的小型文件
// A request path, left to right.
style store fill: #142814 border: #486544 badge: database
node browser "Browser"
node api "API server" right of browser
node db "Postgres" right of api style: store
node worker "Worker" below api
edge browser -> api "HTTP" from: right to: left
edge api -> db "SQL" from: right to: left
edge worker -> db "writes" from: right to: bottom
node aside "The worker shares the database / but takes no HTTP traffic." (wrap: 30) shape: none below worker (gap: tight)// A request path, left to right.
style store fill: #142814 border: #486544 badge: database
node browser "Browser"
node api "API server" right of browser
node db "Postgres" right of api style: store
node worker "Worker" below api
edge browser -> api "HTTP" from: right to: left
edge api -> db "SQL" from: right to: left
edge worker -> db "writes" from: right to: bottom
node aside "The worker shares the database / but takes no HTTP traffic." (wrap: 30) shape: none below worker (gap: tight)What will bite you
容易踩的坑
- Reaching for ,
noteorbox. They are not statements. A note islink; the keywords arenode … shape: noneandnode. Each gets an error naming the replacement.edge - Nodes that nothing orders. Every pair of nodes must clear the other, and where the file says nothing about which side of what, it is an error naming the pair: . Hanging two children off the same side of the same target is the usual cause. Place one against the other.
"b" and "c" overlap, and nothing says which side of the other either one sits on - An annotation with no wrap.
- Reaching for a coordinate, an offset, or a waypoint. None exist. If a line goes somewhere wrong, say more about it with and
between/from:; if a node is in the wrong place, add a placement.to: - is not a comment. It opens a hex color. Comments are
#.// - Guessing at syntax from another language. There are no braces, no semicolons, no , no subgraphs. If you want something not written here, check
-->before inventing it.reference/syntax.md
- 误用 、
note或box。 它们不是语句。注释应写成link;可用关键字只有node … shape: none和node。使用错误写法时,工具会报错并指出替代写法。edge - 节点之间没有任何顺序关系。 每一对节点都必须能够相互错开;当文件未说明二者谁位于谁哪一侧时,会报错并指出相关节点对:。将两个子项挂在同一目标的同一侧是常见原因。应让其中一个相对于另一个定位。
"b" and "c" overlap, and nothing says which side of the other either one sits on - 注释没有 wrap。
- 试图使用坐标、偏移量或路径点。 这些概念都不存在。如果连线位置不对,请使用 和
between/from:进一步描述;如果节点位置不对,请添加定位语句。to: - 不是注释符。 它用于开始十六进制颜色。注释符是
#。// - 根据其他语言猜测语法。 这里没有花括号、分号、或子图。如果想使用本文未涵盖的语法,请先查看
-->,不要自行编造。reference/syntax.md
Reference
参考
reference/syntax.md is the full syntax reference — every construct, the shape, icon and position sets, how edges sharing a side or a channel are ordered, what the language deliberately refuses and why, and the known defects. Read it when you need a construct this page does not cover, or when an error message points at behavior you did not expect.
reference/syntax.md 是完整的语法参考——涵盖所有构造、形状、图标和位置集合、共享同一侧或同一通道的边如何排序、该语言有意拒绝哪些行为及其原因,以及已知缺陷。当需要本文未覆盖的语法,或错误信息指向了意外行为时,请查阅该文档。