This commit is contained in:
Barry Nolte 2023-08-01 20:03:54 -07:00
commit 64f3136c03
No known key found for this signature in database
GPG key ID: 97D97D823FB7D867
847 changed files with 128780 additions and 64926 deletions

View file

@ -16,7 +16,7 @@ jobs:
with:
go-version-file: ./go.mod
cache: true
- run: COLOR=1 CI_FORCE=1 ./make.sh all race
- run: DAILY=1 COLOR=1 CI_FORCE=1 ./make.sh all race
env:
GITHUB_TOKEN: ${{ secrets._GITHUB_TOKEN }}
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}

View file

@ -1,21 +1,5 @@
#### Features 🚀
- Configure timeout value with D2_TIMEOUT env var [#1392](https://github.com/terrastruct/d2/pull/1392)
- Scale renders and disable fit to screen with `--scale` flag [#1413](https://github.com/terrastruct/d2/pull/1413)
- `null` keyword can be used to un-declare. See [docs](https://d2lang.com/tour/TODO) [#1446](https://github.com/terrastruct/d2/pull/1446)
#### Improvements 🧹
- Display version on CLI help invocation [#1400](https://github.com/terrastruct/d2/pull/1400)
- Improved readability of connection labels when they overlap another connection [#447](https://github.com/terrastruct/d2/pull/447)
- Error message when `shape` is given a composite [#1415](https://github.com/terrastruct/d2/pull/1415)
- Improved rendering and text measurement for code shapes [#1425](https://github.com/terrastruct/d2/pull/1425)
- The autoformatter moves board declarations to the bottom of its scope [#1424](https://github.com/terrastruct/d2/pull/1424)
- All font styles in sketch mode use a consistent font-family [#1463](https://github.com/terrastruct/d2/pull/1463)
- Tooltip and link icons are now positioned on shape border [#1466](https://github.com/terrastruct/d2/pull/1466)
- Tooltip and link icons are always rendered over shapes [#1467](https://github.com/terrastruct/d2/pull/1467)
#### Bugfixes ⛑️
- Fixes edge case in compiler using dots in quotes [#1401](https://github.com/terrastruct/d2/pull/1401)
- Fixes grid label font size for TALA [#1412](https://github.com/terrastruct/d2/pull/1412)

View file

@ -0,0 +1,54 @@
D2 v0.6 introduces variable substitutions and globs. These two were the last of the features planned in the initial designs for D2, and v1 is now very close!
The power of variables and globs in a programming language need no introduction, so here's two minimal examples to get started:
**Variables**:
```d2
vars: {
color: aquamarine
}
x.style.fill: ${color}
```
**Globs**:
```d2
x
y
z
*.style.fill: aquamarine
```
Both are live on [D2 Playground](https://play.d2lang.com) so give it a try! Looking forward to your issues and iterating
Layout capability also takes a subtle but important step forward: you can now customize the position of labels and icons.
#### Features 🚀
- Variables and substitutions are implemented. See [docs](https://d2lang.com/tour/vars). [#1473](https://github.com/terrastruct/d2/pull/1473)
- Configure timeout value with D2_TIMEOUT env var [#1392](https://github.com/terrastruct/d2/pull/1392)
- Scale renders and disable fit to screen with `--scale` flag [#1413](https://github.com/terrastruct/d2/pull/1413)
- `null` keyword can be used to un-declare. See [docs](https://d2lang.com/tour/overrides#null) [#1446](https://github.com/terrastruct/d2/pull/1446)
- Develop multi-board diagrams in watch mode (links to layers/scenarios/steps work in `--watch`) [#1503](https://github.com/terrastruct/d2/pull/1503)
- Glob patterns have been implemented. See [docs](https://d2lang.com/tour/globs). [#1479](https://github.com/terrastruct/d2/pull/1479)
- Ampersand filters have been implemented. See [docs](https://d2lang.com/tour/filters). [#1509](https://github.com/terrastruct/d2/pull/1509)
#### Improvements 🧹
- Display version on CLI help invocation [#1400](https://github.com/terrastruct/d2/pull/1400)
- Improved readability of connection labels when they overlap another connection [#447](https://github.com/terrastruct/d2/pull/447)
- Error message when `shape` is given a composite [#1415](https://github.com/terrastruct/d2/pull/1415)
- Improved rendering and text measurement for code shapes [#1425](https://github.com/terrastruct/d2/pull/1425)
- The autoformatter moves board declarations to the bottom of its scope [#1424](https://github.com/terrastruct/d2/pull/1424)
- All font styles in sketch mode use a consistent font-family [#1463](https://github.com/terrastruct/d2/pull/1463)
- Tooltip and link icons are positioned on shape border [#1466](https://github.com/terrastruct/d2/pull/1466)
- Tooltip and link icons are always rendered over shapes [#1467](https://github.com/terrastruct/d2/pull/1467)
- Boards with no objects are considered folders [#1504](https://github.com/terrastruct/d2/pull/1504)
- `DEBUG` environment variable ignored if set incorrectly [#1505](https://github.com/terrastruct/d2/pull/1505)
#### Bugfixes ⛑️
- Fixes edge case in compiler using dots in quotes [#1401](https://github.com/terrastruct/d2/pull/1401)
- Fixes grid label font size for TALA [#1412](https://github.com/terrastruct/d2/pull/1412)
- Fixes person shape label positioning with `multiple` or `3d` [#1478](https://github.com/terrastruct/d2/pull/1478)

View file

@ -1,5 +1,5 @@
# https://hub.docker.com/repository/docker/terrastruct/d2
FROM debian:latest
FROM ubuntu:latest
ARG TARGETARCH

View file

@ -500,6 +500,19 @@ type Number struct {
type UnquotedString struct {
Range Range `json:"range"`
Value []InterpolationBox `json:"value"`
// Pattern holds the parsed glob pattern if in a key and the unquoted string represents a valid pattern.
Pattern []string `json:"pattern,omitempty"`
}
func (s *UnquotedString) Coalesce() {
var b strings.Builder
for _, box := range s.Value {
if box.String == nil {
break
}
b.WriteString(*box.String)
}
s.SetString(b.String())
}
func FlatUnquotedString(s string) *UnquotedString {
@ -513,6 +526,17 @@ type DoubleQuotedString struct {
Value []InterpolationBox `json:"value"`
}
func (s *DoubleQuotedString) Coalesce() {
var b strings.Builder
for _, box := range s.Value {
if box.String == nil {
break
}
b.WriteString(*box.String)
}
s.SetString(b.String())
}
func FlatDoubleQuotedString(s string) *DoubleQuotedString {
return &DoubleQuotedString{
Value: []InterpolationBox{{String: &s}},
@ -586,7 +610,7 @@ func (m *Map) IsFileMap() bool {
type Key struct {
Range Range `json:"range"`
// Indicates this MapKey is an override selector.
// Indicates this MapKey is a filter selector.
Ampersand bool `json:"ampersand,omitempty"`
// At least one of Key and Edges will be set but all four can also be set.
@ -696,6 +720,19 @@ func (mk *Key) SetScalar(scalar ScalarBox) {
}
}
func (mk *Key) HasQueryGlob() bool {
if mk.Key.HasGlob() && len(mk.Edges) == 0 {
return true
}
if mk.EdgeIndex != nil && mk.EdgeIndex.Glob && mk.EdgeKey == nil {
return true
}
if mk.EdgeKey.HasGlob() {
return true
}
return false
}
type KeyPath struct {
Range Range `json:"range"`
Path []*StringBox `json:"path"`
@ -716,6 +753,37 @@ func (kp *KeyPath) IDA() (ida []string) {
return ida
}
func (kp *KeyPath) Copy() *KeyPath {
kp2 := *kp
kp2.Path = nil
kp2.Path = append(kp2.Path, kp.Path...)
return &kp2
}
func (kp *KeyPath) HasDoubleGlob() bool {
if kp == nil {
return false
}
for _, el := range kp.Path {
if el.UnquotedString != nil && el.ScalarString() == "**" {
return true
}
}
return false
}
func (kp *KeyPath) HasGlob() bool {
if kp == nil {
return false
}
for _, el := range kp.Path {
if el.UnquotedString != nil && len(el.UnquotedString.Pattern) > 0 {
return true
}
}
return false
}
type Edge struct {
Range Range `json:"range"`
@ -1034,6 +1102,10 @@ func (sb *StringBox) Unbox() String {
}
}
func (sb *StringBox) ScalarString() string {
return sb.Unbox().ScalarString()
}
// InterpolationBox is used to select between strings and substitutions in unquoted and
// double quoted strings. There is no corresponding interface to avoid unnecessary
// abstraction.
@ -1046,12 +1118,11 @@ type InterpolationBox struct {
// & is only special if it begins a key.
// - is only special if followed by another - in a key.
// ' " and | are only special if they begin an unquoted key or value.
var UnquotedKeySpecials = string([]rune{'#', ';', '\n', '\\', '{', '}', '[', ']', '\'', '"', '|', ':', '.', '-', '<', '>', '*', '&', '(', ')', '@'})
var UnquotedKeySpecials = string([]rune{'#', ';', '\n', '\\', '{', '}', '[', ']', '\'', '"', '|', ':', '.', '-', '<', '>', '*', '&', '(', ')', '@', '&'})
var UnquotedValueSpecials = string([]rune{'#', ';', '\n', '\\', '{', '}', '[', ']', '\'', '"', '|', '$', '@'})
// RawString returns s in a AST String node that can format s in the most aesthetically
// pleasing way.
// TODO: Return StringBox
func RawString(s string, inKey bool) String {
if s == "" {
return FlatDoubleQuotedString(s)
@ -1064,10 +1135,6 @@ func RawString(s string, inKey bool) String {
if i+1 < len(s) && s[i+1] != '-' {
continue
}
case '&':
if i > 0 {
continue
}
}
if strings.ContainsRune(UnquotedKeySpecials, r) {
if !strings.ContainsRune(s, '"') {

View file

@ -102,7 +102,7 @@ func test(t *testing.T, textPath, text string) {
t.Fatal(err)
}
g, err := d2compiler.Compile("", strings.NewReader(text), nil)
g, _, err := d2compiler.Compile("", strings.NewReader(text), nil)
if err != nil {
t.Fatal(err)
}

View file

@ -20,6 +20,7 @@ import (
"oss.terrastruct.com/util-go/go2"
"oss.terrastruct.com/util-go/xmain"
"oss.terrastruct.com/d2/d2graph"
"oss.terrastruct.com/d2/d2lib"
"oss.terrastruct.com/d2/d2parser"
"oss.terrastruct.com/d2/d2plugin"
@ -66,7 +67,8 @@ func Run(ctx context.Context, ms *xmain.State) (err error) {
}
debugFlag, err := ms.Opts.Bool("DEBUG", "debug", "d", false, "print debug logs.")
if err != nil {
return err
ms.Log.Warn.Printf("Invalid DEBUG flag value ignored")
debugFlag = go2.Pointer(false)
}
imgCacheFlag, err := ms.Opts.Bool("IMG_CACHE", "img-cache", "", true, "in watch mode, images used in icons are cached for subsequent compilations. This should be disabled if images might change.")
if err != nil {
@ -117,11 +119,11 @@ func Run(ctx context.Context, ms *xmain.State) (err error) {
fontBoldFlag := ms.Opts.String("D2_FONT_BOLD", "font-bold", "", "", "path to .ttf file to use for the bold font. If none provided, Source Sans Pro Bold is used.")
fontSemiboldFlag := ms.Opts.String("D2_FONT_SEMIBOLD", "font-semibold", "", "", "path to .ttf file to use for the semibold font. If none provided, Source Sans Pro Semibold is used.")
ps, err := d2plugin.ListPlugins(ctx)
plugins, err := d2plugin.ListPlugins(ctx)
if err != nil {
return err
}
err = populateLayoutOpts(ctx, ms, ps)
err = populateLayoutOpts(ctx, ms, plugins)
if err != nil {
return err
}
@ -146,7 +148,7 @@ func Run(ctx context.Context, ms *xmain.State) (err error) {
case "init-playwright":
return initPlaywright()
case "layout":
return layoutCmd(ctx, ms, ps)
return layoutCmd(ctx, ms, plugins)
case "themes":
themesCmd(ctx, ms)
return nil
@ -226,6 +228,38 @@ func Run(ctx context.Context, ms *xmain.State) (err error) {
}
ms.Log.Debug.Printf("using theme %s (ID: %d)", match.Name, *themeFlag)
// If flag is not explicitly set by user, set to nil.
// Later, configs from D2 code will only overwrite if they weren't explicitly set by user
flagSet := make(map[string]struct{})
ms.Opts.Flags.Visit(func(f *pflag.Flag) {
flagSet[f.Name] = struct{}{}
})
if ms.Env.Getenv("D2_LAYOUT") == "" {
if _, ok := flagSet["layout"]; !ok {
layoutFlag = nil
}
}
if ms.Env.Getenv("D2_THEME") == "" {
if _, ok := flagSet["theme"]; !ok {
themeFlag = nil
}
}
if ms.Env.Getenv("D2_SKETCH") == "" {
if _, ok := flagSet["sketch"]; !ok {
sketchFlag = nil
}
}
if ms.Env.Getenv("D2_PAD") == "" {
if _, ok := flagSet["pad"]; !ok {
padFlag = nil
}
}
if ms.Env.Getenv("D2_CENTER") == "" {
if _, ok := flagSet["center"]; !ok {
centerFlag = nil
}
}
if *darkThemeFlag == -1 {
darkThemeFlag = nil // TODO this is a temporary solution: https://github.com/terrastruct/util-go/issues/7
}
@ -241,29 +275,6 @@ func Run(ctx context.Context, ms *xmain.State) (err error) {
scale = scaleFlag
}
plugin, err := d2plugin.FindPlugin(ctx, ps, *layoutFlag)
if err != nil {
if errors.Is(err, exec.ErrNotFound) {
return layoutNotFound(ctx, ps, *layoutFlag)
}
return err
}
err = d2plugin.HydratePluginOpts(ctx, ms, plugin)
if err != nil {
return err
}
pinfo, err := plugin.Info(ctx)
if err != nil {
return err
}
plocation := pinfo.Type
if pinfo.Type == "binary" {
plocation = fmt.Sprintf("executable plugin at %s", humanPath(pinfo.Path))
}
ms.Log.Debug.Printf("using layout plugin %s (%s)", *layoutFlag, plocation)
if !outputFormat.supportsDarkTheme() {
if darkThemeFlag != nil {
ms.Log.Warn.Printf("--dark-theme cannot be used while exporting to another format other than .svg")
@ -285,10 +296,10 @@ func Run(ctx context.Context, ms *xmain.State) (err error) {
}
renderOpts := d2svg.RenderOpts{
Pad: int(*padFlag),
Sketch: *sketchFlag,
Center: *centerFlag,
ThemeID: *themeFlag,
Pad: padFlag,
Sketch: sketchFlag,
Center: centerFlag,
ThemeID: themeFlag,
DarkThemeID: darkThemeFlag,
Scale: scale,
}
@ -298,7 +309,8 @@ func Run(ctx context.Context, ms *xmain.State) (err error) {
return xmain.UsageErrorf("-w[atch] cannot be combined with reading input from stdin")
}
w, err := newWatcher(ctx, ms, watcherOpts{
layoutPlugin: plugin,
plugins: plugins,
layout: layoutFlag,
renderOpts: renderOpts,
animateInterval: *animateIntervalFlag,
host: *hostFlag,
@ -319,7 +331,7 @@ func Run(ctx context.Context, ms *xmain.State) (err error) {
ctx, cancel := timelib.WithTimeout(ctx, time.Minute*2)
defer cancel()
_, written, err := compile(ctx, ms, plugin, renderOpts, fontFamily, *animateIntervalFlag, inputPath, outputPath, *bundleFlag, *forceAppendixFlag, pw.Page)
_, written, err := compile(ctx, ms, plugins, layoutFlag, renderOpts, fontFamily, *animateIntervalFlag, inputPath, outputPath, "", *bundleFlag, *forceAppendixFlag, pw.Page)
if err != nil {
if written {
return fmt.Errorf("failed to fully compile (partial render written): %w", err)
@ -329,7 +341,32 @@ func Run(ctx context.Context, ms *xmain.State) (err error) {
return nil
}
func compile(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, renderOpts d2svg.RenderOpts, fontFamily *d2fonts.FontFamily, animateInterval int64, inputPath, outputPath string, bundle, forceAppendix bool, page playwright.Page) (_ []byte, written bool, _ error) {
func LayoutResolver(ctx context.Context, ms *xmain.State, plugins []d2plugin.Plugin) func(engine string) (d2graph.LayoutGraph, error) {
cached := make(map[string]d2graph.LayoutGraph)
return func(engine string) (d2graph.LayoutGraph, error) {
if c, ok := cached[engine]; ok {
return c, nil
}
plugin, err := d2plugin.FindPlugin(ctx, plugins, engine)
if err != nil {
if errors.Is(err, exec.ErrNotFound) {
return nil, layoutNotFound(ctx, plugins, engine)
}
return nil, err
}
err = d2plugin.HydratePluginOpts(ctx, ms, plugin)
if err != nil {
return nil, err
}
cached[engine] = plugin.Layout
return plugin.Layout, nil
}
}
func compile(ctx context.Context, ms *xmain.State, plugins []d2plugin.Plugin, layout *string, renderOpts d2svg.RenderOpts, fontFamily *d2fonts.FontFamily, animateInterval int64, inputPath, outputPath, boardPath string, bundle, forceAppendix bool, page playwright.Page) (_ []byte, written bool, _ error) {
start := time.Now()
input, err := ms.ReadPath(inputPath)
if err != nil {
@ -341,16 +378,12 @@ func compile(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, rende
return nil, false, err
}
layout := plugin.Layout
opts := &d2lib.CompileOptions{
Layout: layout,
Ruler: ruler,
ThemeID: renderOpts.ThemeID,
FontFamily: fontFamily,
InputPath: inputPath,
}
if renderOpts.Sketch {
opts.FontFamily = go2.Pointer(d2fonts.HandDrawn)
LayoutResolver: LayoutResolver(ctx, ms, plugins),
Layout: layout,
}
cancel := background.Repeat(func() {
@ -358,12 +391,14 @@ func compile(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, rende
}, time.Second*5)
defer cancel()
diagram, g, err := d2lib.Compile(ctx, string(input), opts)
diagram, g, err := d2lib.Compile(ctx, string(input), opts, &renderOpts)
if err != nil {
return nil, false, err
}
cancel()
plugin, _ := d2plugin.FindPlugin(ctx, plugins, *opts.Layout)
if animateInterval > 0 {
masterID, err := diagram.HashID()
if err != nil {
@ -372,6 +407,16 @@ func compile(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, rende
renderOpts.MasterID = masterID
}
pinfo, err := plugin.Info(ctx)
if err != nil {
return nil, false, err
}
plocation := pinfo.Type
if pinfo.Type == "binary" {
plocation = fmt.Sprintf("executable plugin at %s", humanPath(pinfo.Path))
}
ms.Log.Debug.Printf("using layout plugin %s (%s)", *opts.Layout, plocation)
pluginInfo, err := plugin.Info(ctx)
if err != nil {
return nil, false, err
@ -455,7 +500,12 @@ func compile(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, rende
}
}
boards, err := render(ctx, ms, compileDur, plugin, renderOpts, inputPath, outputPath, bundle, forceAppendix, page, ruler, diagram)
board := diagram.GetBoard(boardPath)
if board == nil {
return nil, false, fmt.Errorf("Diagram with path %s not found", boardPath)
}
boards, err := render(ctx, ms, compileDur, plugin, renderOpts, inputPath, outputPath, bundle, forceAppendix, page, ruler, board)
if err != nil {
return nil, false, err
}
@ -805,7 +855,7 @@ func renderPDF(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, opt
if err != nil {
return svg, err
}
err = doc.AddPDFPage(pngImg, boardPath, opts.ThemeID, rootFill, diagram.Shapes, int64(opts.Pad), viewboxX, viewboxY, pageMap)
err = doc.AddPDFPage(pngImg, boardPath, *opts.ThemeID, rootFill, diagram.Shapes, *opts.Pad, viewboxX, viewboxY, pageMap)
if err != nil {
return svg, err
}

View file

@ -8,9 +8,7 @@ function init(reconnectDelay) {
const d2SVG = window.document.querySelector("#d2-svg-container");
const devMode = document.body.dataset.d2DevMode === "true";
const ws = new WebSocket(
`ws://${window.location.host}${window.location.pathname}watch`
);
const ws = new WebSocket(`ws://${window.location.host}/watch`);
let isInit = true;
let ratio;
ws.onopen = () => {
@ -28,7 +26,7 @@ function init(reconnectDelay) {
// we can't just set `d2SVG.innerHTML = msg.svg` need to parse this as xml not html
const parsedXML = new DOMParser().parseFromString(msg.svg, "text/xml");
d2SVG.replaceChildren(parsedXML.documentElement);
changeFavicon("./static/favicon.ico");
changeFavicon("/static/favicon.ico");
const svgEl = d2SVG.querySelector("#d2-svg");
// just use inner SVG in watch mode
svgEl.parentElement.replaceWith(svgEl);
@ -60,7 +58,7 @@ function init(reconnectDelay) {
if (msg.err) {
d2ErrDiv.innerText = msg.err;
d2ErrDiv.style.display = "block";
changeFavicon("./static/favicon-err.ico");
changeFavicon("/static/favicon-err.ico");
d2ErrDiv.scrollIntoView();
}
};

View file

@ -12,6 +12,7 @@ import (
"os"
"path/filepath"
"runtime"
"strings"
"sync"
"time"
@ -41,13 +42,15 @@ var devMode = false
var staticFS embed.FS
type watcherOpts struct {
layoutPlugin d2plugin.Plugin
layout *string
plugins []d2plugin.Plugin
renderOpts d2svg.RenderOpts
animateInterval int64
host string
port string
inputPath string
outputPath string
boardPath string
pwd string
bundle bool
forceAppendix bool
@ -361,7 +364,7 @@ func (w *watcher) compileLoop(ctx context.Context) error {
w.pw = newPW
}
svg, _, err := compile(ctx, w.ms, w.layoutPlugin, w.renderOpts, w.fontFamily, w.animateInterval, w.inputPath, w.outputPath, w.bundle, w.forceAppendix, w.pw.Page)
svg, _, err := compile(ctx, w.ms, w.plugins, w.layout, w.renderOpts, w.fontFamily, w.animateInterval, w.inputPath, w.outputPath, w.boardPath, w.bundle, w.forceAppendix, w.pw.Page)
errs := ""
if err != nil {
if len(svg) > 0 {
@ -429,15 +432,25 @@ func (w *watcher) handleRoot(hw http.ResponseWriter, r *http.Request) {
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>%s</title>
<script src="./static/watch.js"></script>
<link rel="stylesheet" href="./static/watch.css">
<link id="favicon" rel="icon" href="./static/favicon.ico">
<script src="/static/watch.js"></script>
<link rel="stylesheet" href="/static/watch.css">
<link id="favicon" rel="icon" href="/static/favicon.ico">
</head>
<body data-d2-dev-mode=%t>
<div id="d2-err" style="display: none"></div>
<div id="d2-svg-container"></div>
</body>
</html>`, filepath.Base(w.outputPath), w.devMode)
// if path is "/x.svg", we just want "x"
boardPath := strings.TrimPrefix(r.URL.Path, "/")
if idx := strings.LastIndexByte(boardPath, '.'); idx != -1 {
boardPath = boardPath[:idx]
}
if boardPath != w.boardPath {
w.boardPath = boardPath
w.requestCompile()
}
}
func (w *watcher) handleWatch(hw http.ResponseWriter, r *http.Request) error {

View file

@ -27,7 +27,7 @@ type CompileOptions struct {
FS fs.FS
}
func Compile(p string, r io.RuneReader, opts *CompileOptions) (*d2graph.Graph, error) {
func Compile(p string, r io.RuneReader, opts *CompileOptions) (*d2graph.Graph, *d2target.Config, error) {
if opts == nil {
opts = &CompileOptions{}
}
@ -36,7 +36,7 @@ func Compile(p string, r io.RuneReader, opts *CompileOptions) (*d2graph.Graph, e
UTF16: opts.UTF16,
})
if err != nil {
return nil, err
return nil, nil, err
}
ir, err := d2ir.Compile(ast, &d2ir.CompileOptions{
@ -44,16 +44,16 @@ func Compile(p string, r io.RuneReader, opts *CompileOptions) (*d2graph.Graph, e
FS: opts.FS,
})
if err != nil {
return nil, err
return nil, nil, err
}
g, err := compileIR(ast, ir)
if err != nil {
return nil, err
return nil, nil, err
}
g.SortObjectsByAST()
g.SortEdgesByAST()
return g, nil
return g, compileConfig(ir), nil
}
func compileIR(ast *d2ast.Map, m *d2ir.Map) (*d2graph.Graph, error) {
@ -92,6 +92,9 @@ func (c *compiler) compileBoard(g *d2graph.Graph, ir *d2ir.Map) *d2graph.Graph {
g.IsFolderOnly = true
}
}
if len(g.Objects) == 0 {
g.IsFolderOnly = true
}
return g
}
@ -277,6 +280,8 @@ func (c *compiler) compileField(obj *d2graph.Object, f *d2ir.Field) {
}
}
return
} else if f.Name == "vars" {
return
} else if isReserved {
c.compileReserved(&obj.Attributes, f)
return
@ -329,7 +334,7 @@ func (c *compiler) compileField(obj *d2graph.Object, f *d2ir.Field) {
Scope: fr.Context.Scope,
ScopeAST: fr.Context.ScopeAST,
}
if fr.Context.ScopeMap != nil {
if fr.Context.ScopeMap != nil && !d2ir.IsVar(fr.Context.ScopeMap) {
scopeObjIDA := d2graphIDA(d2ir.BoardIDA(fr.Context.ScopeMap))
r.ScopeObj = obj.Graph.Root.EnsureChild(scopeObjIDA)
}
@ -725,7 +730,7 @@ func (c *compiler) compileEdge(obj *d2graph.Object, e *d2ir.Edge) {
Scope: er.Context.Scope,
ScopeAST: er.Context.ScopeAST,
}
if er.Context.ScopeMap != nil {
if er.Context.ScopeMap != nil && !d2ir.IsVar(er.Context.ScopeMap) {
scopeObjIDA := d2graphIDA(d2ir.BoardIDA(er.Context.ScopeMap))
r.ScopeObj = edge.Src.Graph.Root.EnsureChild(scopeObjIDA)
}
@ -1283,3 +1288,45 @@ func parentSeqDiagram(n d2ir.Node) *d2ir.Map {
n = m
}
}
func compileConfig(ir *d2ir.Map) *d2target.Config {
f := ir.GetField("vars", "d2-config")
if f == nil || f.Map() == nil {
return nil
}
configMap := f.Map()
config := &d2target.Config{}
f = configMap.GetField("sketch")
if f != nil {
val, _ := strconv.ParseBool(f.Primary().Value.ScalarString())
config.Sketch = &val
}
f = configMap.GetField("theme-id")
if f != nil {
val, _ := strconv.Atoi(f.Primary().Value.ScalarString())
config.ThemeID = go2.Pointer(int64(val))
}
f = configMap.GetField("dark-theme-id")
if f != nil {
val, _ := strconv.Atoi(f.Primary().Value.ScalarString())
config.DarkThemeID = go2.Pointer(int64(val))
}
f = configMap.GetField("pad")
if f != nil {
val, _ := strconv.Atoi(f.Primary().Value.ScalarString())
config.Pad = go2.Pointer(int64(val))
}
f = configMap.GetField("layout-engine")
if f != nil {
config.LayoutEngine = go2.Pointer(f.Primary().Value.ScalarString())
}
return config
}

File diff suppressed because it is too large Load diff

View file

@ -12,12 +12,15 @@ import (
"oss.terrastruct.com/util-go/assert"
"oss.terrastruct.com/util-go/diff"
"oss.terrastruct.com/util-go/go2"
"oss.terrastruct.com/d2/d2compiler"
"oss.terrastruct.com/d2/d2exporter"
"oss.terrastruct.com/d2/d2graph"
"oss.terrastruct.com/d2/d2layouts/d2dagrelayout"
"oss.terrastruct.com/d2/d2layouts/d2grid"
"oss.terrastruct.com/d2/d2layouts/d2sequence"
"oss.terrastruct.com/d2/d2lib"
"oss.terrastruct.com/d2/d2target"
"oss.terrastruct.com/d2/lib/geo"
"oss.terrastruct.com/d2/lib/log"
@ -219,7 +222,7 @@ func run(t *testing.T, tc testCase) {
ctx = log.WithTB(ctx, t, nil)
ctx = log.Leveled(ctx, slog.LevelDebug)
g, err := d2compiler.Compile("", strings.NewReader(tc.dsl), &d2compiler.CompileOptions{
g, config, err := d2compiler.Compile("", strings.NewReader(tc.dsl), &d2compiler.CompileOptions{
UTF16: true,
})
if err != nil {
@ -241,6 +244,9 @@ func run(t *testing.T, tc testCase) {
if err != nil {
t.Fatal(err)
}
if got != nil {
got.Config = config
}
if tc.assertions != nil {
t.Run("assertions", func(t *testing.T) {
@ -267,3 +273,57 @@ func run(t *testing.T, tc testCase) {
err = diff.TestdataJSON(filepath.Join("..", "testdata", "d2exporter", t.Name()), got)
assert.Success(t, err)
}
// TestHashID tests that 2 diagrams with different theme configs do not equal each other
func TestHashID(t *testing.T) {
ctx := context.Background()
ctx = log.WithTB(ctx, t, nil)
ctx = log.Leveled(ctx, slog.LevelDebug)
aString := `
vars: {
d2-config: {
theme-id: 3
}
}
a -> b
`
bString := `
vars: {
d2-config: {
theme-id: 4
}
}
a -> b
`
da, err := compile(ctx, aString)
assert.JSON(t, nil, err)
db, err := compile(ctx, bString)
assert.JSON(t, nil, err)
hashA, err := da.HashID()
assert.JSON(t, nil, err)
hashB, err := db.HashID()
assert.JSON(t, nil, err)
assert.NotEqual(t, hashA, hashB)
}
func layoutResolver(engine string) (d2graph.LayoutGraph, error) {
return d2dagrelayout.DefaultLayout, nil
}
func compile(ctx context.Context, d2 string) (*d2target.Diagram, error) {
ruler, _ := textmeasure.NewRuler()
opts := &d2lib.CompileOptions{
Ruler: ruler,
LayoutResolver: layoutResolver,
Layout: go2.Pointer("dagre"),
}
d, _, e := d2lib.Compile(ctx, d2, opts, nil)
return d, e
}

View file

@ -1651,6 +1651,7 @@ var SimpleReservedKeywords = map[string]struct{}{
"vertical-gap": {},
"horizontal-gap": {},
"class": {},
"vars": {},
}
// ReservedKeywordHolders are reserved keywords that are meaningless on its own and must hold composites

View file

@ -1,6 +1,7 @@
package d2graph
import (
"sort"
"strings"
"oss.terrastruct.com/d2/d2target"
@ -316,10 +317,29 @@ func (obj *Object) GetLabelTopLeft() *geo.Point {
return labelTL
}
func (obj *Object) GetIconTopLeft() *geo.Point {
if obj.IconPosition == nil {
return nil
}
s := obj.ToShape()
iconPosition := label.Position(*obj.IconPosition)
var box *geo.Box
if iconPosition.IsOutside() {
box = s.GetBox()
} else {
box = s.GetInnerBox()
}
return iconPosition.GetPointOnBox(box, label.PADDING, d2target.MAX_ICON_SIZE, d2target.MAX_ICON_SIZE)
}
func (edge *Edge) TraceToShape(points []*geo.Point, startIndex, endIndex int) (newStart, newEnd int) {
srcShape := edge.Src.ToShape()
dstShape := edge.Dst.ToShape()
startingSegment := geo.Segment{Start: points[startIndex+1], End: points[startIndex]}
// if an edge runs into an outside label, stop the edge at the label instead
overlapsOutsideLabel := false
if edge.Src.HasLabel() {
@ -330,18 +350,27 @@ func (edge *Edge) TraceToShape(points []*geo.Point, startIndex, endIndex int) (n
labelHeight := float64(edge.Src.LabelDimensions.Height)
labelTL := labelPosition.GetPointOnBox(edge.Src.Box, label.PADDING, labelWidth, labelHeight)
startingSegment := geo.Segment{Start: points[startIndex+1], End: points[startIndex]}
labelBox := geo.NewBox(labelTL, labelWidth, labelHeight)
// add left/right padding to box
labelBox.TopLeft.X -= label.PADDING
labelBox.Width += 2 * label.PADDING
for labelBox.Contains(startingSegment.End) && startIndex+1 > endIndex {
startingSegment.Start = startingSegment.End
startingSegment.End = points[startIndex+2]
startIndex++
}
if intersections := labelBox.Intersections(startingSegment); len(intersections) > 0 {
overlapsOutsideLabel = true
p := intersections[0]
if len(intersections) > 1 {
p = findOuterIntersection(labelPosition, intersections)
}
// move starting segment to label intersection point
points[startIndex] = intersections[0]
startingSegment.End = intersections[0]
points[startIndex] = p
startingSegment.End = p
// if the segment becomes too short, just merge it with the next segment
if startIndex < len(points) && startingSegment.Length() < MIN_SEGMENT_LEN {
if startIndex+1 < endIndex && startingSegment.Length() < MIN_SEGMENT_LEN {
points[startIndex+1] = points[startIndex]
startIndex++
}
@ -349,9 +378,20 @@ func (edge *Edge) TraceToShape(points []*geo.Point, startIndex, endIndex int) (n
}
}
if !overlapsOutsideLabel {
if intersections := edge.Src.Intersections(startingSegment); len(intersections) > 0 {
// move starting segment to intersection point
points[startIndex] = intersections[0]
startingSegment.End = intersections[0]
// if the segment becomes too short, just merge it with the next segment
if startIndex+1 < endIndex && startingSegment.Length() < MIN_SEGMENT_LEN {
points[startIndex+1] = points[startIndex]
startIndex++
}
}
// trace the edge to the specific shape's border
points[startIndex] = shape.TraceToShapeBorder(srcShape, points[startIndex], points[startIndex+1])
}
endingSegment := geo.Segment{Start: points[endIndex-1], End: points[endIndex]}
overlapsOutsideLabel = false
if edge.Dst.HasLabel() {
// assumes LabelPosition, LabelWidth, LabelHeight are all set if there is a label
@ -361,18 +401,26 @@ func (edge *Edge) TraceToShape(points []*geo.Point, startIndex, endIndex int) (n
labelHeight := float64(edge.Dst.LabelDimensions.Height)
labelTL := labelPosition.GetPointOnBox(edge.Dst.Box, label.PADDING, labelWidth, labelHeight)
endingSegment := geo.Segment{Start: points[endIndex-1], End: points[endIndex]}
labelBox := geo.NewBox(labelTL, labelWidth, labelHeight)
// add left/right padding to box
labelBox.TopLeft.X -= label.PADDING
labelBox.Width += 2 * label.PADDING
for labelBox.Contains(endingSegment.Start) && endIndex-1 > startIndex {
endingSegment.End = endingSegment.Start
endingSegment.Start = points[endIndex-2]
endIndex--
}
if intersections := labelBox.Intersections(endingSegment); len(intersections) > 0 {
overlapsOutsideLabel = true
p := intersections[0]
if len(intersections) > 1 {
p = findOuterIntersection(labelPosition, intersections)
}
// move ending segment to label intersection point
points[endIndex] = intersections[0]
endingSegment.End = intersections[0]
points[endIndex] = p
endingSegment.End = p
// if the segment becomes too short, just merge it with the previous segment
if endIndex-1 > 0 && endingSegment.Length() < MIN_SEGMENT_LEN {
if endIndex-1 > startIndex && endingSegment.Length() < MIN_SEGMENT_LEN {
points[endIndex-1] = points[endIndex]
endIndex--
}
@ -380,7 +428,39 @@ func (edge *Edge) TraceToShape(points []*geo.Point, startIndex, endIndex int) (n
}
}
if !overlapsOutsideLabel {
if intersections := edge.Dst.Intersections(endingSegment); len(intersections) > 0 {
// move ending segment to intersection point
points[endIndex] = intersections[0]
endingSegment.End = intersections[0]
// if the segment becomes too short, just merge it with the previous segment
if endIndex-1 > startIndex && endingSegment.Length() < MIN_SEGMENT_LEN {
points[endIndex-1] = points[endIndex]
endIndex--
}
}
points[endIndex] = shape.TraceToShapeBorder(dstShape, points[endIndex], points[endIndex-1])
}
return startIndex, endIndex
}
func findOuterIntersection(labelPosition label.Position, intersections []*geo.Point) *geo.Point {
switch labelPosition {
case label.OutsideTopLeft, label.OutsideTopRight, label.OutsideTopCenter:
sort.Slice(intersections, func(i, j int) bool {
return intersections[i].Y < intersections[j].Y
})
case label.OutsideBottomLeft, label.OutsideBottomRight, label.OutsideBottomCenter:
sort.Slice(intersections, func(i, j int) bool {
return intersections[i].Y > intersections[j].Y
})
case label.OutsideLeftTop, label.OutsideLeftMiddle, label.OutsideLeftBottom:
sort.Slice(intersections, func(i, j int) bool {
return intersections[i].X < intersections[j].X
})
case label.OutsideRightTop, label.OutsideRightMiddle, label.OutsideRightBottom:
sort.Slice(intersections, func(i, j int) bool {
return intersections[i].X > intersections[j].X
})
}
return intersections[0]
}

View file

@ -13,7 +13,7 @@ import (
func TestSerialization(t *testing.T) {
t.Parallel()
g, err := d2compiler.Compile("", strings.NewReader("a.a.b -> a.a.c"), nil)
g, _, err := d2compiler.Compile("", strings.NewReader("a.a.b -> a.a.c"), nil)
assert.Nil(t, err)
asserts := func(g *d2graph.Graph) {
@ -53,7 +53,7 @@ func TestCasingRegression(t *testing.T) {
script := `UserCreatedTypeField`
g, err := d2compiler.Compile("", strings.NewReader(script), nil)
g, _, err := d2compiler.Compile("", strings.NewReader(script), nil)
assert.Nil(t, err)
_, ok := g.Root.HasChild([]string{"UserCreatedTypeField"})

View file

@ -2,11 +2,15 @@ package d2ir
import (
"io/fs"
"strconv"
"strings"
"oss.terrastruct.com/d2/d2ast"
"oss.terrastruct.com/d2/d2format"
"oss.terrastruct.com/d2/d2parser"
"oss.terrastruct.com/d2/d2themes"
"oss.terrastruct.com/d2/d2themes/d2themescatalog"
"oss.terrastruct.com/util-go/go2"
)
type compiler struct {
@ -18,6 +22,8 @@ type compiler struct {
// importCache enables reuse of files imported multiple times.
importCache map[string]*Map
utf16 bool
globStack []bool
}
type CompileOptions struct {
@ -52,14 +58,15 @@ func Compile(ast *d2ast.Map, opts *CompileOptions) (*Map, error) {
defer c.popImportStack()
c.compileMap(m, ast, ast)
c.compileClasses(m)
c.compileSubstitutions(m, nil)
c.overlayClasses(m)
if !c.err.Empty() {
return nil, c.err
}
return m, nil
}
func (c *compiler) compileClasses(m *Map) {
func (c *compiler) overlayClasses(m *Map) {
classes := m.GetField("classes")
if classes == nil || classes.Map() == nil {
return
@ -92,10 +99,242 @@ func (c *compiler) compileClasses(m *Map) {
l.Fields = append(l.Fields, base)
}
c.compileClasses(l)
c.overlayClasses(l)
}
}
func (c *compiler) compileSubstitutions(m *Map, varsStack []*Map) {
for _, f := range m.Fields {
if f.Name == "vars" && f.Map() != nil {
varsStack = append([]*Map{f.Map()}, varsStack...)
}
if f.Primary() != nil {
c.resolveSubstitutions(varsStack, f)
}
if arr, ok := f.Composite.(*Array); ok {
for _, val := range arr.Values {
if scalar, ok := val.(*Scalar); ok {
c.resolveSubstitutions(varsStack, scalar)
}
}
} else if f.Map() != nil {
// don't resolve substitutions in vars with the current scope of vars
if f.Name == "vars" {
c.compileSubstitutions(f.Map(), varsStack[1:])
c.validateConfigs(f.Map().GetField("d2-config"))
} else {
c.compileSubstitutions(f.Map(), varsStack)
}
}
}
for _, e := range m.Edges {
if e.Primary() != nil {
c.resolveSubstitutions(varsStack, e)
}
if e.Map() != nil {
c.compileSubstitutions(e.Map(), varsStack)
}
}
}
func (c *compiler) validateConfigs(configs *Field) {
if configs == nil || configs.Map() == nil {
return
}
if NodeBoardKind(ParentMap(ParentMap(configs))) == "" {
c.errorf(configs.LastRef().AST(), `"%s" can only appear at root vars`, configs.Name)
return
}
for _, f := range configs.Map().Fields {
var val string
if f.Primary() == nil {
if f.Name != "theme-colors" {
c.errorf(f.LastRef().AST(), `"%s" needs a value`, f.Name)
continue
}
} else {
val = f.Primary().Value.ScalarString()
}
switch f.Name {
case "sketch", "center":
_, err := strconv.ParseBool(val)
if err != nil {
c.errorf(f.LastRef().AST(), `expected a boolean for "%s", got "%s"`, f.Name, val)
continue
}
case "theme-colors":
if f.Map() == nil {
c.errorf(f.LastRef().AST(), `"%s" needs a map`, f.Name)
continue
}
case "theme-id", "dark-theme-id":
valInt, err := strconv.Atoi(val)
if err != nil {
c.errorf(f.LastRef().AST(), `expected an integer for "%s", got "%s"`, f.Name, val)
continue
}
if d2themescatalog.Find(int64(valInt)) == (d2themes.Theme{}) {
c.errorf(f.LastRef().AST(), `%d is not a valid theme ID`, valInt)
continue
}
case "pad":
_, err := strconv.Atoi(val)
if err != nil {
c.errorf(f.LastRef().AST(), `expected an integer for "%s", got "%s"`, f.Name, val)
continue
}
case "layout-engine":
default:
c.errorf(f.LastRef().AST(), `"%s" is not a valid config`, f.Name)
}
}
}
func (c *compiler) resolveSubstitutions(varsStack []*Map, node Node) {
var subbed bool
var resolvedField *Field
switch s := node.Primary().Value.(type) {
case *d2ast.UnquotedString:
for i, box := range s.Value {
if box.Substitution != nil {
for _, vars := range varsStack {
resolvedField = c.resolveSubstitution(vars, box.Substitution)
if resolvedField != nil {
if resolvedField.Primary() != nil {
if _, ok := resolvedField.Primary().Value.(*d2ast.Null); ok {
resolvedField = nil
}
}
break
}
}
if resolvedField == nil {
c.errorf(node.LastRef().AST(), `could not resolve variable "%s"`, strings.Join(box.Substitution.IDA(), "."))
return
}
if box.Substitution.Spread {
if resolvedField.Composite == nil {
c.errorf(box.Substitution, "cannot spread non-composite")
continue
}
switch n := node.(type) {
case *Scalar: // Array value
resolvedArr, ok := resolvedField.Composite.(*Array)
if !ok {
c.errorf(box.Substitution, "cannot spread non-array into array")
continue
}
arr := n.parent.(*Array)
for i, s := range arr.Values {
if s == n {
arr.Values = append(append(arr.Values[:i], resolvedArr.Values...), arr.Values[i+1:]...)
break
}
}
case *Field:
if resolvedField.Map() != nil {
OverlayMap(ParentMap(n), resolvedField.Map())
}
// Remove the placeholder field
m := n.parent.(*Map)
for i, f2 := range m.Fields {
if n == f2 {
m.Fields = append(m.Fields[:i], m.Fields[i+1:]...)
break
}
}
}
}
if resolvedField.Primary() == nil {
if resolvedField.Composite == nil {
c.errorf(node.LastRef().AST(), `cannot substitute variable without value: "%s"`, strings.Join(box.Substitution.IDA(), "."))
return
}
if len(s.Value) > 1 {
c.errorf(node.LastRef().AST(), `cannot substitute composite variable "%s" as part of a string`, strings.Join(box.Substitution.IDA(), "."))
return
}
switch n := node.(type) {
case *Field:
n.Primary_ = nil
case *Edge:
n.Primary_ = nil
}
} else {
if i == 0 && len(s.Value) == 1 {
node.Primary().Value = resolvedField.Primary().Value
} else {
s.Value[i].String = go2.Pointer(resolvedField.Primary().Value.ScalarString())
subbed = true
}
}
if resolvedField.Composite != nil {
switch n := node.(type) {
case *Field:
n.Composite = resolvedField.Composite
case *Edge:
if resolvedField.Composite.Map() == nil {
c.errorf(node.LastRef().AST(), `cannot substitute array variable "%s" to an edge`, strings.Join(box.Substitution.IDA(), "."))
return
}
n.Map_ = resolvedField.Composite.Map()
}
}
}
}
if subbed {
s.Coalesce()
}
case *d2ast.DoubleQuotedString:
for i, box := range s.Value {
if box.Substitution != nil {
for _, vars := range varsStack {
resolvedField = c.resolveSubstitution(vars, box.Substitution)
if resolvedField != nil {
break
}
}
if resolvedField == nil {
c.errorf(node.LastRef().AST(), `could not resolve variable "%s"`, strings.Join(box.Substitution.IDA(), "."))
return
}
if resolvedField.Primary() == nil && resolvedField.Composite != nil {
c.errorf(node.LastRef().AST(), `cannot substitute map variable "%s" in quotes`, strings.Join(box.Substitution.IDA(), "."))
return
}
s.Value[i].String = go2.Pointer(resolvedField.Primary().Value.ScalarString())
subbed = true
}
}
if subbed {
s.Coalesce()
}
}
}
func (c *compiler) resolveSubstitution(vars *Map, substitution *d2ast.Substitution) *Field {
if vars == nil {
return nil
}
for i, p := range substitution.Path {
f := vars.GetField(p.Unbox().ScalarString())
if f == nil {
return nil
}
if i == len(substitution.Path)-1 {
return f
}
vars = f.Map()
}
return nil
}
func (c *compiler) overlay(base *Map, f *Field) {
if f.Map() == nil || f.Primary() != nil {
c.errorf(f.References[0].Context.Key, "invalid %s", NodeBoardKind(f))
@ -107,6 +346,20 @@ func (c *compiler) overlay(base *Map, f *Field) {
}
func (c *compiler) compileMap(dst *Map, ast, scopeAST *d2ast.Map) {
for _, n := range ast.Nodes {
switch {
case n.MapKey != nil:
ok := c.ampersandFilter(&RefContext{
Key: n.MapKey,
Scope: ast,
ScopeMap: dst,
ScopeAST: scopeAST,
})
if !ok {
return
}
}
}
for _, n := range ast.Nodes {
switch {
case n.MapKey != nil:
@ -116,6 +369,17 @@ func (c *compiler) compileMap(dst *Map, ast, scopeAST *d2ast.Map) {
ScopeMap: dst,
ScopeAST: scopeAST,
})
case n.Substitution != nil:
// placeholder field to be resolved at the end
f := &Field{
parent: dst,
Primary_: &Scalar{
Value: &d2ast.UnquotedString{
Value: []d2ast.InterpolationBox{{Substitution: n.Substitution}},
},
},
}
dst.Fields = append(dst.Fields, f)
case n.Import != nil:
impn, ok := c._import(n.Import)
if !ok {
@ -135,8 +399,6 @@ func (c *compiler) compileMap(dst *Map, ast, scopeAST *d2ast.Map) {
}
}
}
case n.Substitution != nil:
panic("TODO")
}
}
}
@ -150,16 +412,87 @@ func (c *compiler) compileKey(refctx *RefContext) {
}
func (c *compiler) compileField(dst *Map, kp *d2ast.KeyPath, refctx *RefContext) {
if refctx.Key != nil && len(refctx.Key.Edges) == 0 && refctx.Key.Value.Null != nil {
dst.DeleteField(kp.IDA()...)
if refctx.Key.Ampersand {
return
}
f, err := dst.EnsureField(kp, refctx)
fa, err := dst.EnsureField(kp, refctx, true)
if err != nil {
c.err.Errors = append(c.err.Errors, err.(d2ast.Error))
return
}
for _, f := range fa {
c._compileField(f, refctx)
}
}
func (c *compiler) ampersandFilter(refctx *RefContext) bool {
if !refctx.Key.Ampersand {
return true
}
if len(c.globStack) == 0 || !c.globStack[len(c.globStack)-1] {
c.errorf(refctx.Key, "glob filters cannot be used outside globs")
return false
}
if len(refctx.Key.Edges) > 0 {
return true
}
fa, err := refctx.ScopeMap.EnsureField(refctx.Key.Key, refctx, false)
if err != nil {
c.err.Errors = append(c.err.Errors, err.(d2ast.Error))
return false
}
if len(fa) == 0 {
return false
}
for _, f := range fa {
ok := c._ampersandFilter(f, refctx)
if !ok {
return false
}
}
return true
}
func (c *compiler) _ampersandFilter(f *Field, refctx *RefContext) bool {
if refctx.Key.Value.ScalarBox().Unbox() == nil {
c.errorf(refctx.Key, "glob filters cannot be composites")
return false
}
if a, ok := f.Composite.(*Array); ok {
for _, v := range a.Values {
if s, ok := v.(*Scalar); ok {
if refctx.Key.Value.ScalarBox().Unbox().ScalarString() == s.Value.ScalarString() {
return true
}
}
}
}
if f.Primary_ == nil {
return false
}
if refctx.Key.Value.ScalarBox().Unbox().ScalarString() != f.Primary_.Value.ScalarString() {
return false
}
return true
}
func (c *compiler) _compileField(f *Field, refctx *RefContext) {
if len(refctx.Key.Edges) == 0 && refctx.Key.Value.Null != nil {
// For vars, if we delete the field, it may just resolve to an outer scope var of the same name
// Instead we keep it around, so that resolveSubstitutions can find it
if !IsVar(ParentMap(f)) {
ParentMap(f).DeleteField(f.Name)
return
}
}
if refctx.Key.Primary.Unbox() != nil {
f.Primary_ = &Scalar{
parent: f,
@ -199,10 +532,12 @@ func (c *compiler) compileField(dst *Map, kp *d2ast.KeyPath, refctx *RefContext)
// If new board type, use that as the new scope AST, otherwise, carry on
scopeAST = refctx.ScopeAST
}
c.globStack = append(c.globStack, refctx.Key.HasQueryGlob())
c.compileMap(f.Map(), refctx.Key.Value.Map, scopeAST)
c.globStack = c.globStack[:len(c.globStack)-1]
switch NodeBoardKind(f) {
case BoardScenario, BoardStep:
c.compileClasses(f.Map())
c.overlayClasses(f.Map())
}
} else if refctx.Key.Value.Import != nil {
n, ok := c._import(refctx.Key.Value.Import)
@ -241,7 +576,7 @@ func (c *compiler) compileField(dst *Map, kp *d2ast.KeyPath, refctx *RefContext)
c.updateLinks(f.Map())
switch NodeBoardKind(f) {
case BoardScenario, BoardStep:
c.compileClasses(f.Map())
c.overlayClasses(f.Map())
}
}
} else if refctx.Key.Value.ScalarBox().Unbox() != nil {
@ -259,6 +594,21 @@ func (c *compiler) compileField(dst *Map, kp *d2ast.KeyPath, refctx *RefContext)
func (c *compiler) updateLinks(m *Map) {
for _, f := range m.Fields {
if f.Name == "link" {
val := f.Primary().Value.ScalarString()
link, err := d2parser.ParseKey(val)
if err != nil {
continue
}
linkIDA := link.IDA()
if len(linkIDA) == 0 {
continue
}
// When updateLinks is called, all valid board links are already compiled and changed to the qualified path beginning with "root"
if linkIDA[0] != "root" {
continue
}
bida := BoardIDA(f)
aida := IDA(f)
if len(bida) != len(aida) {
@ -337,12 +687,17 @@ func (c *compiler) compileLink(refctx *RefContext) {
}
func (c *compiler) compileEdges(refctx *RefContext) {
if refctx.Key.Key != nil {
f, err := refctx.ScopeMap.EnsureField(refctx.Key.Key, refctx)
if refctx.Key.Key == nil {
c._compileEdges(refctx)
return
}
fa, err := refctx.ScopeMap.EnsureField(refctx.Key.Key, refctx, true)
if err != nil {
c.err.Errors = append(c.err.Errors, err.(d2ast.Error))
return
}
for _, f := range fa {
if _, ok := f.Composite.(*Array); ok {
c.errorf(refctx.Key.Key, "cannot index into array")
return
@ -352,9 +707,13 @@ func (c *compiler) compileEdges(refctx *RefContext) {
parent: f,
}
}
refctx.ScopeMap = f.Map()
refctx2 := *refctx
refctx2.ScopeMap = f.Map()
c._compileEdges(&refctx2)
}
}
func (c *compiler) _compileEdges(refctx *RefContext) {
eida := NewEdgeIDs(refctx.Key)
for i, eid := range eida {
if refctx.Key != nil && refctx.Key.Value.Null != nil {
@ -365,38 +724,30 @@ func (c *compiler) compileEdges(refctx *RefContext) {
refctx = refctx.Copy()
refctx.Edge = refctx.Key.Edges[i]
var e *Edge
if eid.Index != nil {
ea := refctx.ScopeMap.GetEdges(eid)
var ea []*Edge
if eid.Index != nil || eid.Glob {
ea = refctx.ScopeMap.GetEdges(eid, refctx)
if len(ea) == 0 {
c.errorf(refctx.Edge, "indexed edge does not exist")
continue
}
e = ea[0]
for _, e := range ea {
e.References = append(e.References, &EdgeReference{
Context: refctx,
})
refctx.ScopeMap.appendFieldReferences(0, refctx.Edge.Src, refctx)
refctx.ScopeMap.appendFieldReferences(0, refctx.Edge.Dst, refctx)
}
} else {
_, err := refctx.ScopeMap.EnsureField(refctx.Edge.Src, refctx)
if err != nil {
c.err.Errors = append(c.err.Errors, err.(d2ast.Error))
continue
}
_, err = refctx.ScopeMap.EnsureField(refctx.Edge.Dst, refctx)
if err != nil {
c.err.Errors = append(c.err.Errors, err.(d2ast.Error))
continue
}
e, err = refctx.ScopeMap.CreateEdge(eid, refctx)
var err error
ea, err = refctx.ScopeMap.CreateEdge(eid, refctx)
if err != nil {
c.err.Errors = append(c.err.Errors, err.(d2ast.Error))
continue
}
}
for _, e := range ea {
if refctx.Key.EdgeKey != nil {
if e.Map_ == nil {
e.Map_ = &Map{
@ -420,7 +771,9 @@ func (c *compiler) compileEdges(refctx *RefContext) {
parent: e,
}
}
c.globStack = append(c.globStack, refctx.Key.HasQueryGlob())
c.compileMap(e.Map_, refctx.Key.Value.Map, refctx.ScopeAST)
c.globStack = c.globStack[:len(c.globStack)-1]
} else if refctx.Key.Value.ScalarBox().Unbox() != nil {
e.Primary_ = &Scalar{
parent: e,
@ -430,6 +783,7 @@ func (c *compiler) compileEdges(refctx *RefContext) {
}
}
}
}
func (c *compiler) compileArray(dst *Array, a *d2ast.Array, scopeAST *d2ast.Map) {
for _, an := range a.Nodes {
@ -481,7 +835,12 @@ func (c *compiler) compileArray(dst *Array, a *d2ast.Array, scopeAST *d2ast.Map)
irv = n
}
case *d2ast.Substitution:
// panic("TODO")
irv = &Scalar{
parent: dst,
Value: &d2ast.UnquotedString{
Value: []d2ast.InterpolationBox{{Substitution: an.Substitution}},
},
}
}
dst.Values = append(dst.Values, irv)

View file

@ -26,6 +26,8 @@ func TestCompile(t *testing.T) {
t.Run("scenarios", testCompileScenarios)
t.Run("steps", testCompileSteps)
t.Run("imports", testCompileImports)
t.Run("patterns", testCompilePatterns)
t.Run("filters", testCompileFilters)
}
type testCase struct {
@ -84,23 +86,31 @@ func assertQuery(t testing.TB, n d2ir.Node, nfields, nedges int, primary interfa
m := n.Map()
p := n.Primary()
var na []d2ir.Node
if idStr != "" {
var err error
n, err = m.Query(idStr)
na, err = m.QueryAll(idStr)
assert.Success(t, err)
assert.NotEqual(t, n, nil)
p = n.Primary()
m = n.Map()
} else {
na = append(na, n)
}
for _, n := range na {
m = n.Map()
p = n.Primary()
assert.Equal(t, nfields, m.FieldCountRecursive())
assert.Equal(t, nedges, m.EdgeCountRecursive())
if !makeScalar(p).Equal(makeScalar(primary)) {
t.Fatalf("expected primary %#v but got %s", primary, p)
}
}
return n
if len(na) == 0 {
return nil
}
return na[0]
}
func makeScalar(v interface{}) *d2ir.Scalar {

View file

@ -325,6 +325,7 @@ type EdgeID struct {
// If nil, then any EdgeID with equal src/dst/arrows matches.
Index *int `json:"index"`
Glob bool `json:"glob"`
}
func NewEdgeIDs(k *d2ast.Key) (eida []*EdgeID) {
@ -337,6 +338,7 @@ func NewEdgeIDs(k *d2ast.Key) (eida []*EdgeID) {
}
if k.EdgeIndex != nil {
eid.Index = k.EdgeIndex.Int
eid.Glob = k.EdgeIndex.Glob
}
eida = append(eida, eid)
}
@ -585,6 +587,19 @@ func (m *Map) FieldCountRecursive() int {
return acc
}
func (m *Map) IsContainer() bool {
if m == nil {
return false
}
for _, f := range m.Fields {
_, isReserved := d2graph.ReservedKeywords[f.Name]
if !isReserved {
return true
}
}
return false
}
func (m *Map) EdgeCountRecursive() int {
if m == nil {
return 0
@ -651,7 +666,8 @@ func (m *Map) getField(ida []string) *Field {
return nil
}
func (m *Map) EnsureField(kp *d2ast.KeyPath, refctx *RefContext) (*Field, error) {
// EnsureField is a bit of a misnomer. It's more of a Query/Ensure combination function at this point.
func (m *Map) EnsureField(kp *d2ast.KeyPath, refctx *RefContext, create bool) ([]*Field, error) {
i := 0
for kp.Path[i].Unbox().ScalarString() == "_" {
m = ParentMap(m)
@ -663,29 +679,73 @@ func (m *Map) EnsureField(kp *d2ast.KeyPath, refctx *RefContext) (*Field, error)
}
i++
}
return m.ensureField(i, kp, refctx)
var fa []*Field
err := m.ensureField(i, kp, refctx, create, &fa)
return fa, err
}
func (m *Map) ensureField(i int, kp *d2ast.KeyPath, refctx *RefContext, create bool, fa *[]*Field) error {
us, ok := kp.Path[i].Unbox().(*d2ast.UnquotedString)
if ok && us.Pattern != nil {
fa2, ok := m.doubleGlob(us.Pattern)
if ok {
if i == len(kp.Path)-1 {
*fa = append(*fa, fa2...)
} else {
for _, f := range fa2 {
if f.Map() == nil {
f.Composite = &Map{
parent: f,
}
}
err := f.Map().ensureField(i+1, kp, refctx, create, fa)
if err != nil {
return err
}
}
}
return nil
}
for _, f := range m.Fields {
if matchPattern(f.Name, us.Pattern) {
if i == len(kp.Path)-1 {
*fa = append(*fa, f)
} else {
if f.Map() == nil {
f.Composite = &Map{
parent: f,
}
}
err := f.Map().ensureField(i+1, kp, refctx, create, fa)
if err != nil {
return err
}
}
}
}
return nil
}
func (m *Map) ensureField(i int, kp *d2ast.KeyPath, refctx *RefContext) (*Field, error) {
head := kp.Path[i].Unbox().ScalarString()
if _, ok := d2graph.ReservedKeywords[strings.ToLower(head)]; ok {
head = strings.ToLower(head)
if _, ok := d2graph.CompositeReservedKeywords[head]; !ok && i < len(kp.Path)-1 {
return nil, d2parser.Errorf(kp.Path[i].Unbox(), fmt.Sprintf(`"%s" must be the last part of the key`, head))
return d2parser.Errorf(kp.Path[i].Unbox(), fmt.Sprintf(`"%s" must be the last part of the key`, head))
}
}
if head == "_" {
return nil, d2parser.Errorf(kp.Path[i].Unbox(), `parent "_" can only be used in the beginning of paths, e.g. "_.x"`)
return d2parser.Errorf(kp.Path[i].Unbox(), `parent "_" can only be used in the beginning of paths, e.g. "_.x"`)
}
if head == "classes" && NodeBoardKind(m) == "" {
return nil, d2parser.Errorf(kp.Path[i].Unbox(), "%s is only allowed at a board root", head)
return d2parser.Errorf(kp.Path[i].Unbox(), "%s is only allowed at a board root", head)
}
if findBoardKeyword(head) != -1 && NodeBoardKind(m) == "" {
return nil, d2parser.Errorf(kp.Path[i].Unbox(), "%s is only allowed at a board root", head)
return d2parser.Errorf(kp.Path[i].Unbox(), "%s is only allowed at a board root", head)
}
for _, f := range m.Fields {
@ -703,19 +763,23 @@ func (m *Map) ensureField(i int, kp *d2ast.KeyPath, refctx *RefContext) (*Field,
}
if i+1 == len(kp.Path) {
return f, nil
*fa = append(*fa, f)
return nil
}
if _, ok := f.Composite.(*Array); ok {
return nil, d2parser.Errorf(kp.Path[i].Unbox(), "cannot index into array")
return d2parser.Errorf(kp.Path[i].Unbox(), "cannot index into array")
}
if f.Map() == nil {
f.Composite = &Map{
parent: f,
}
}
return f.Map().ensureField(i+1, kp, refctx)
return f.Map().ensureField(i+1, kp, refctx, create, fa)
}
if !create {
return nil
}
f := &Field{
parent: m,
Name: head,
@ -730,12 +794,13 @@ func (m *Map) ensureField(i int, kp *d2ast.KeyPath, refctx *RefContext) (*Field,
}
m.Fields = append(m.Fields, f)
if i+1 == len(kp.Path) {
return f, nil
*fa = append(*fa, f)
return nil
}
f.Composite = &Map{
parent: f,
}
return f.Map().ensureField(i+1, kp, refctx)
return f.Map().ensureField(i+1, kp, refctx, create, fa)
}
func (m *Map) DeleteEdge(eid *EdgeID) *Edge {
@ -800,7 +865,13 @@ func (m *Map) DeleteField(ida ...string) *Field {
return nil
}
func (m *Map) GetEdges(eid *EdgeID) []*Edge {
func (m *Map) GetEdges(eid *EdgeID, refctx *RefContext) []*Edge {
if refctx != nil {
var ea []*Edge
m.getEdges(eid, refctx, &ea)
return ea
}
eid, m, common, err := eid.resolve(m)
if err != nil {
return nil
@ -811,7 +882,7 @@ func (m *Map) GetEdges(eid *EdgeID) []*Edge {
return nil
}
if f.Map() != nil {
return f.Map().GetEdges(eid)
return f.Map().GetEdges(eid, nil)
}
return nil
}
@ -825,65 +896,197 @@ func (m *Map) GetEdges(eid *EdgeID) []*Edge {
return ea
}
func (m *Map) CreateEdge(eid *EdgeID, refctx *RefContext) (*Edge, error) {
if ParentEdge(m) != nil {
return nil, d2parser.Errorf(refctx.Edge, "cannot create edge inside edge")
}
func (m *Map) getEdges(eid *EdgeID, refctx *RefContext, ea *[]*Edge) error {
eid, m, common, err := eid.resolve(m)
if err != nil {
return nil, d2parser.Errorf(refctx.Edge, err.Error())
return err
}
if len(common) > 0 {
f, err := m.EnsureField(d2ast.MakeKeyPath(common), nil)
if err != nil {
return nil, err
commonKP := d2ast.MakeKeyPath(common)
lastMatch := 0
for i, el := range commonKP.Path {
for j := lastMatch; j < len(refctx.Edge.Src.Path); j++ {
realEl := refctx.Edge.Src.Path[j]
if el.ScalarString() == realEl.ScalarString() {
commonKP.Path[i] = realEl
lastMatch += j + 1
}
}
}
fa, err := m.EnsureField(commonKP, nil, false)
if err != nil {
return nil
}
for _, f := range fa {
if _, ok := f.Composite.(*Array); ok {
return nil, d2parser.Errorf(refctx.Edge.Src, "cannot index into array")
return d2parser.Errorf(refctx.Edge.Src, "cannot index into array")
}
if f.Map() == nil {
f.Composite = &Map{
parent: f,
}
}
return f.Map().CreateEdge(eid, refctx)
err = f.Map().getEdges(eid, refctx, ea)
if err != nil {
return err
}
}
return nil
}
srcFA, err := refctx.ScopeMap.EnsureField(refctx.Edge.Src, nil, false)
if err != nil {
return err
}
dstFA, err := refctx.ScopeMap.EnsureField(refctx.Edge.Dst, nil, false)
if err != nil {
return err
}
for _, src := range srcFA {
for _, dst := range dstFA {
eid2 := eid.Copy()
eid2.SrcPath = RelIDA(m, src)
eid2.DstPath = RelIDA(m, dst)
ea2 := m.GetEdges(eid2, nil)
*ea = append(*ea, ea2...)
}
}
return nil
}
func (m *Map) CreateEdge(eid *EdgeID, refctx *RefContext) ([]*Edge, error) {
var ea []*Edge
return ea, m.createEdge(eid, refctx, &ea)
}
func (m *Map) createEdge(eid *EdgeID, refctx *RefContext, ea *[]*Edge) error {
if ParentEdge(m) != nil {
return d2parser.Errorf(refctx.Edge, "cannot create edge inside edge")
}
eid, m, common, err := eid.resolve(m)
if err != nil {
return d2parser.Errorf(refctx.Edge, err.Error())
}
if len(common) > 0 {
commonKP := d2ast.MakeKeyPath(common)
lastMatch := 0
for i, el := range commonKP.Path {
for j := lastMatch; j < len(refctx.Edge.Src.Path); j++ {
realEl := refctx.Edge.Src.Path[j]
if el.ScalarString() == realEl.ScalarString() {
commonKP.Path[i] = realEl
lastMatch += j + 1
}
}
}
fa, err := m.EnsureField(commonKP, nil, true)
if err != nil {
return err
}
for _, f := range fa {
if _, ok := f.Composite.(*Array); ok {
return d2parser.Errorf(refctx.Edge.Src, "cannot index into array")
}
if f.Map() == nil {
f.Composite = &Map{
parent: f,
}
}
err = f.Map().createEdge(eid, refctx, ea)
if err != nil {
return err
}
}
return nil
}
ij := findProhibitedEdgeKeyword(eid.SrcPath...)
if ij != -1 {
return nil, d2parser.Errorf(refctx.Edge.Src.Path[ij].Unbox(), "reserved keywords are prohibited in edges")
return d2parser.Errorf(refctx.Edge.Src.Path[ij].Unbox(), "reserved keywords are prohibited in edges")
}
ij = findBoardKeyword(eid.SrcPath...)
if ij == len(eid.SrcPath)-1 {
return nil, d2parser.Errorf(refctx.Edge.Src.Path[ij].Unbox(), "edge with board keyword alone doesn't make sense")
}
src := m.GetField(eid.SrcPath...)
if NodeBoardKind(src) != "" {
return nil, d2parser.Errorf(refctx.Edge.Src, "cannot create edges between boards")
return d2parser.Errorf(refctx.Edge.Src.Path[ij].Unbox(), "edge with board keyword alone doesn't make sense")
}
ij = findProhibitedEdgeKeyword(eid.DstPath...)
if ij != -1 {
return nil, d2parser.Errorf(refctx.Edge.Dst.Path[ij].Unbox(), "reserved keywords are prohibited in edges")
return d2parser.Errorf(refctx.Edge.Dst.Path[ij].Unbox(), "reserved keywords are prohibited in edges")
}
ij = findBoardKeyword(eid.DstPath...)
if ij == len(eid.DstPath)-1 {
return nil, d2parser.Errorf(refctx.Edge.Dst.Path[ij].Unbox(), "edge with board keyword alone doesn't make sense")
return d2parser.Errorf(refctx.Edge.Dst.Path[ij].Unbox(), "edge with board keyword alone doesn't make sense")
}
srcFA, err := refctx.ScopeMap.EnsureField(refctx.Edge.Src, refctx, true)
if err != nil {
return err
}
dstFA, err := refctx.ScopeMap.EnsureField(refctx.Edge.Dst, refctx, true)
if err != nil {
return err
}
for _, src := range srcFA {
for _, dst := range dstFA {
if src == dst && (refctx.Edge.Src.HasGlob() || refctx.Edge.Dst.HasGlob()) {
// Globs do not make self edges.
continue
}
if refctx.Edge.Src.HasDoubleGlob() {
// If src has a double glob we only select leafs, those without children.
if src.Map().IsContainer() {
continue
}
if ParentBoard(src) != ParentBoard(dst) {
continue
}
}
if refctx.Edge.Dst.HasDoubleGlob() {
// If dst has a double glob we only select leafs, those without children.
if dst.Map().IsContainer() {
continue
}
if ParentBoard(src) != ParentBoard(dst) {
continue
}
}
eid2 := eid.Copy()
eid2.SrcPath = RelIDA(m, src)
eid2.DstPath = RelIDA(m, dst)
e, err := m.createEdge2(eid2, refctx, src, dst)
if err != nil {
return err
}
*ea = append(*ea, e)
}
}
return nil
}
func (m *Map) createEdge2(eid *EdgeID, refctx *RefContext, src, dst *Field) (*Edge, error) {
if NodeBoardKind(src) != "" {
return nil, d2parser.Errorf(refctx.Edge.Src, "cannot create edges between boards")
}
dst := m.GetField(eid.DstPath...)
if NodeBoardKind(dst) != "" {
return nil, d2parser.Errorf(refctx.Edge.Dst, "cannot create edges between boards")
}
if ParentBoard(src) != ParentBoard(dst) {
return nil, d2parser.Errorf(refctx.Edge, "cannot create edges between boards")
}
eid.Index = nil
ea := m.GetEdges(eid)
eid.Glob = true
ea := m.GetEdges(eid, nil)
index := len(ea)
eid.Index = &index
eid.Glob = false
e := &Edge{
parent: m,
ID: eid,
@ -1026,6 +1229,21 @@ func ParentField(n Node) *Field {
}
}
func IsVar(n Node) bool {
for {
if n == nil {
return false
}
if NodeBoardKind(n) != "" {
return false
}
if f, ok := n.(*Field); ok && f.Name == "vars" {
return true
}
n = n.Parent()
}
}
func ParentBoard(n Node) Node {
for {
n = n.Parent()
@ -1144,6 +1362,26 @@ func IDA(n Node) (ida []string) {
}
}
// RelIDA returns the path to n relative to p.
func RelIDA(p, n Node) (ida []string) {
for {
f, ok := n.(*Field)
if ok {
ida = append(ida, f.Name)
if f.Root() {
reverseIDA(ida)
return ida
}
}
f = ParentField(n)
if f == nil || f.Root() || f == p || f.Composite == p {
reverseIDA(ida)
return ida
}
n = f
}
}
func reverseIDA(ida []string) {
for i := 0; i < len(ida)/2; i++ {
tmp := ida[i]

158
d2ir/filter_test.go Normal file
View file

@ -0,0 +1,158 @@
package d2ir_test
import (
"testing"
"oss.terrastruct.com/util-go/assert"
)
func testCompileFilters(t *testing.T) {
t.Parallel()
tca := []testCase{
{
name: "base",
run: func(t testing.TB) {
m, err := compile(t, `jacob: {
shape: circle
}
jeremy: {
shape: rectangle
}
*: {
&shape: rectangle
label: I'm a rectangle
}`)
assert.Success(t, err)
assertQuery(t, m, 1, 0, nil, "jacob")
assertQuery(t, m, 2, 0, nil, "jeremy")
assertQuery(t, m, 0, 0, "I'm a rectangle", "jeremy.label")
},
},
{
name: "order",
run: func(t testing.TB) {
m, err := compile(t, `jacob: {
shape: circle
}
jeremy: {
shape: rectangle
}
*: {
label: I'm a rectangle
&shape: rectangle
}`)
assert.Success(t, err)
assertQuery(t, m, 5, 0, nil, "")
assertQuery(t, m, 1, 0, nil, "jacob")
assertQuery(t, m, 2, 0, nil, "jeremy")
assertQuery(t, m, 0, 0, "I'm a rectangle", "jeremy.label")
},
},
{
name: "array",
run: func(t testing.TB) {
m, err := compile(t, `the-little-cannon: {
class: [server; deployed]
}
dino: {
class: [internal; deployed]
}
catapult: {
class: [jacob; server]
}
*: {
&class: server
style.multiple: true
}
`)
assert.Success(t, err)
assertQuery(t, m, 10, 0, nil, "")
assertQuery(t, m, 3, 0, nil, "the-little-cannon")
assertQuery(t, m, 1, 0, nil, "dino")
assertQuery(t, m, 3, 0, nil, "catapult")
},
},
{
name: "edge",
run: func(t testing.TB) {
m, err := compile(t, `x -> y: {
source-arrowhead.shape: diamond
target-arrowhead.shape: diamond
}
x -> y
(x -> *)[*]: {
&source-arrowhead.shape: diamond
&target-arrowhead.shape: diamond
label: diamond shape arrowheads
}
`)
assert.Success(t, err)
assertQuery(t, m, 7, 2, nil, "")
assertQuery(t, m, 5, 0, nil, "(x -> y)[0]")
assertQuery(t, m, 0, 0, "diamond shape arrowheads", "(x -> y)[0].label")
assertQuery(t, m, 0, 0, nil, "(x -> y)[1]")
},
},
}
runa(t, tca)
t.Run("errors", func(t *testing.T) {
tca := []testCase{
{
name: "bad-syntax",
run: func(t testing.TB) {
_, err := compile(t, `jacob.style: {
fill: red
multiple: true
}
*.&style: {
fill: red
multiple: true
}
`)
assert.ErrorString(t, err, `TestCompile/filters/errors/bad-syntax.d2:6:3: unexpected text after map key
TestCompile/filters/errors/bad-syntax.d2:9:1: unexpected map termination character } in file map`)
},
},
{
name: "no-glob",
run: func(t testing.TB) {
_, err := compile(t, `jacob.style: {
fill: red
multiple: true
}
jasmine.style: {
&fill: red
multiple: false
}
`)
assert.ErrorString(t, err, `TestCompile/filters/errors/no-glob.d2:7:3: glob filters cannot be used outside globs`)
},
},
{
name: "composite",
run: func(t testing.TB) {
_, err := compile(t, `jacob.style: {
fill: red
multiple: true
}
*: {
&style: {
fill: red
multiple: true
}
}
`)
assert.ErrorString(t, err, `TestCompile/filters/errors/composite.d2:6:2: glob filters cannot be composites`)
},
},
}
runa(t, tca)
})
}

View file

@ -138,6 +138,39 @@ label: meow`,
assertQuery(t, m, 0, 0, nil, "q.jon")
},
},
{
name: "vars/1",
run: func(t testing.TB) {
m, err := compileFS(t, "index.d2", map[string]string{
"index.d2": "vars: { ...@x }; q: ${meow}",
"x.d2": "meow: var replaced",
})
assert.Success(t, err)
assertQuery(t, m, 0, 0, "var replaced", "q")
},
},
{
name: "vars/2",
run: func(t testing.TB) {
m, err := compileFS(t, "index.d2", map[string]string{
"index.d2": "vars: { x: 1 }; ...@a",
"a.d2": "vars: { x: 2 }; hi: ${x}",
})
assert.Success(t, err)
assertQuery(t, m, 0, 0, 2, "hi")
},
},
{
name: "vars/3",
run: func(t testing.TB) {
m, err := compileFS(t, "index.d2", map[string]string{
"index.d2": "...@a; vars: { x: 1 }; hi: ${x}",
"a.d2": "vars: { x: 2 }",
})
assert.Success(t, err)
assertQuery(t, m, 0, 0, 1, "hi")
},
},
}
runa(t, tca)

View file

@ -11,7 +11,7 @@ func OverlayMap(base, overlay *Map) {
}
for _, oe := range overlay.Edges {
bea := base.GetEdges(oe.ID)
bea := base.GetEdges(oe.ID, nil)
if len(bea) == 0 {
base.Edges = append(base.Edges, oe.Copy(base).(*Edge))
continue

59
d2ir/pattern.go Normal file
View file

@ -0,0 +1,59 @@
package d2ir
import (
"strings"
"oss.terrastruct.com/d2/d2graph"
)
func (m *Map) doubleGlob(pattern []string) ([]*Field, bool) {
if !(len(pattern) == 3 && pattern[0] == "*" && pattern[1] == "" && pattern[2] == "*") {
return nil, false
}
var fa []*Field
m._doubleGlob(&fa)
return fa, true
}
func (m *Map) _doubleGlob(fa *[]*Field) {
for _, f := range m.Fields {
if _, ok := d2graph.ReservedKeywords[f.Name]; ok {
if _, ok := d2graph.BoardKeywords[f.Name]; !ok {
continue
}
}
*fa = append(*fa, f)
if f.Map() != nil {
f.Map()._doubleGlob(fa)
}
}
}
func matchPattern(s string, pattern []string) bool {
if len(pattern) == 0 {
return true
}
if _, ok := d2graph.ReservedKeywords[s]; ok {
return false
}
for i := 0; i < len(pattern); i++ {
if pattern[i] == "*" {
// * so match next.
if i != len(pattern)-1 {
j := strings.Index(strings.ToLower(s), strings.ToLower(pattern[i+1]))
if j == -1 {
return false
}
s = s[j+len(pattern[i+1]):]
i++
}
} else {
if !strings.HasPrefix(strings.ToLower(s), strings.ToLower(pattern[i])) {
return false
}
s = s[len(pattern[i]):]
}
}
return true
}

334
d2ir/pattern_test.go Normal file
View file

@ -0,0 +1,334 @@
package d2ir_test
import (
"testing"
"oss.terrastruct.com/util-go/assert"
)
func testCompilePatterns(t *testing.T) {
t.Parallel()
tca := []testCase{
{
name: "escaped",
run: func(t testing.TB) {
m, err := compile(t, `animal: meow
action: yes
a\*: globbed`)
assert.Success(t, err)
assertQuery(t, m, 3, 0, nil, "")
assertQuery(t, m, 0, 0, "meow", "animal")
assertQuery(t, m, 0, 0, "yes", "action")
assertQuery(t, m, 0, 0, "globbed", `a\*`)
},
},
{
name: "prefix",
run: func(t testing.TB) {
m, err := compile(t, `animal: meow
action: yes
a*: globbed`)
assert.Success(t, err)
assertQuery(t, m, 2, 0, nil, "")
assertQuery(t, m, 0, 0, "globbed", "animal")
assertQuery(t, m, 0, 0, "globbed", "action")
},
},
{
name: "case/1",
run: func(t testing.TB) {
m, err := compile(t, `animal: meow
action: yes
A*: globbed`)
assert.Success(t, err)
assertQuery(t, m, 2, 0, nil, "")
assertQuery(t, m, 0, 0, "globbed", "animal")
assertQuery(t, m, 0, 0, "globbed", "action")
},
},
{
name: "case/2",
run: func(t testing.TB) {
m, err := compile(t, `diddy kong
Donkey Kong
*kong: yes`)
assert.Success(t, err)
assertQuery(t, m, 2, 0, nil, "")
assertQuery(t, m, 0, 0, "yes", "diddy kong")
assertQuery(t, m, 0, 0, "yes", "Donkey Kong")
},
},
{
name: "suffix",
run: func(t testing.TB) {
m, err := compile(t, `animal: meow
jingle: loud
*l: globbed`)
assert.Success(t, err)
assertQuery(t, m, 2, 0, nil, "")
assertQuery(t, m, 0, 0, "globbed", "animal")
assertQuery(t, m, 0, 0, "globbed", "jingle")
},
},
{
name: "prefix-suffix",
run: func(t testing.TB) {
m, err := compile(t, `tinker: meow
thinker: yes
t*r: globbed`)
assert.Success(t, err)
assertQuery(t, m, 2, 0, nil, "")
assertQuery(t, m, 0, 0, "globbed", "tinker")
assertQuery(t, m, 0, 0, "globbed", "thinker")
},
},
{
name: "prefix-suffix/2",
run: func(t testing.TB) {
m, err := compile(t, `tinker: meow
thinker: yes
t*ink*r: globbed`)
assert.Success(t, err)
assertQuery(t, m, 2, 0, nil, "")
assertQuery(t, m, 0, 0, "globbed", "tinker")
assertQuery(t, m, 0, 0, "globbed", "thinker")
},
},
{
name: "prefix-suffix/3",
run: func(t testing.TB) {
m, err := compile(t, `tinkertinker: meow
thinkerthinker: yes
t*ink*r*t*inke*: globbed`)
assert.Success(t, err)
assertQuery(t, m, 2, 0, nil, "")
assertQuery(t, m, 0, 0, "globbed", "tinkertinker")
assertQuery(t, m, 0, 0, "globbed", "thinkerthinker")
},
},
{
name: "nested/prefix-suffix/3",
run: func(t testing.TB) {
m, err := compile(t, `animate.constant.tinkertinker: meow
astronaut.constant.thinkerthinker: yes
a*n*t*.constant.t*ink*r*t*inke*: globbed`)
assert.Success(t, err)
assertQuery(t, m, 6, 0, nil, "")
assertQuery(t, m, 0, 0, "globbed", "animate.constant.tinkertinker")
assertQuery(t, m, 0, 0, "globbed", "astronaut.constant.thinkerthinker")
},
},
{
name: "edge/1",
run: func(t testing.TB) {
m, err := compile(t, `animate
animal
an* -> an*`)
assert.Success(t, err)
assertQuery(t, m, 2, 2, nil, "")
assertQuery(t, m, 0, 0, nil, "(animate -> animal)[0]")
assertQuery(t, m, 0, 0, nil, "(animal -> animal)[0]")
},
},
{
name: "edge/2",
run: func(t testing.TB) {
m, err := compile(t, `shared.animate
shared.animal
sh*.(an* -> an*)`)
assert.Success(t, err)
assertQuery(t, m, 3, 2, nil, "")
assertQuery(t, m, 2, 2, nil, "shared")
assertQuery(t, m, 0, 0, nil, "shared.(animate -> animal)[0]")
assertQuery(t, m, 0, 0, nil, "shared.(animal -> animate)[0]")
},
},
{
name: "edge/3",
run: func(t testing.TB) {
m, err := compile(t, `shared.animate
shared.animal
sh*.an* -> sh*.an*`)
assert.Success(t, err)
assertQuery(t, m, 3, 2, nil, "")
assertQuery(t, m, 2, 2, nil, "shared")
assertQuery(t, m, 0, 0, nil, "shared.(animate -> animal)[0]")
assertQuery(t, m, 0, 0, nil, "shared.(animal -> animal)[0]")
},
},
{
name: "edge-glob-index",
run: func(t testing.TB) {
m, err := compile(t, `a -> b
a -> b
a -> b
(a -> b)[*].style.fill: red
`)
assert.Success(t, err)
assertQuery(t, m, 8, 3, nil, "")
assertQuery(t, m, 0, 0, "red", "(a -> b)[0].style.fill")
assertQuery(t, m, 0, 0, "red", "(a -> b)[1].style.fill")
assertQuery(t, m, 0, 0, "red", "(a -> b)[2].style.fill")
},
},
{
name: "glob-edge-glob-index",
run: func(t testing.TB) {
m, err := compile(t, `a -> b
a -> b
a -> b
c -> b
(* -> b)[*].style.fill: red
`)
assert.Success(t, err)
assertQuery(t, m, 11, 4, nil, "")
assertQuery(t, m, 0, 0, "red", "(a -> b)[0].style.fill")
assertQuery(t, m, 0, 0, "red", "(a -> b)[1].style.fill")
assertQuery(t, m, 0, 0, "red", "(a -> b)[2].style.fill")
assertQuery(t, m, 0, 0, "red", "(c -> b)[0].style.fill")
},
},
{
name: "edge-nexus",
run: func(t testing.TB) {
m, err := compile(t, `a
b
c
d
* -> nexus
`)
assert.Success(t, err)
assertQuery(t, m, 5, 4, nil, "")
assertQuery(t, m, 0, 0, nil, "(a -> nexus)[0]")
assertQuery(t, m, 0, 0, nil, "(b -> nexus)[0]")
assertQuery(t, m, 0, 0, nil, "(c -> nexus)[0]")
assertQuery(t, m, 0, 0, nil, "(d -> nexus)[0]")
},
},
{
name: "double-glob/1",
run: func(t testing.TB) {
m, err := compile(t, `shared.animate
shared.animal
**.style.fill: red`)
assert.Success(t, err)
assertQuery(t, m, 9, 0, nil, "")
assertQuery(t, m, 8, 0, nil, "shared")
assertQuery(t, m, 1, 0, nil, "shared.style")
assertQuery(t, m, 2, 0, nil, "shared.animate")
assertQuery(t, m, 1, 0, nil, "shared.animate.style")
assertQuery(t, m, 2, 0, nil, "shared.animal")
assertQuery(t, m, 1, 0, nil, "shared.animal.style")
},
},
{
name: "double-glob/edge-no-container",
run: func(t testing.TB) {
m, err := compile(t, `zone A: {
machine A
machine B: {
submachine A
submachine B
}
}
zone A.** -> load balancer
`)
assert.Success(t, err)
assertQuery(t, m, 6, 3, nil, "")
},
},
{
name: "reserved",
run: func(t testing.TB) {
m, err := compile(t, `vars: {
d2-config: {
layout-engine: elk
}
}
Spiderman 1
Spiderman 2
Spiderman 3
* -> *: arrow`)
assert.Success(t, err)
assertQuery(t, m, 6, 6, nil, "")
assertQuery(t, m, 0, 0, "arrow", "(* -> *)[*]")
},
},
{
name: "scenarios",
run: func(t testing.TB) {
m, err := compile(t, `
scenarios: {
meow: {
e
f
g
h
}
}
a
b
c
d
**: something
** -> **
`)
assert.Success(t, err)
assertQuery(t, m, 10, 24, nil, "")
assertQuery(t, m, 0, 0, "something", "**")
assertQuery(t, m, 0, 0, nil, "(* -> *)[*]")
},
},
{
name: "double-glob/edge/1",
run: func(t testing.TB) {
m, err := compile(t, `fast: {
a
far
}
task: {
a
}
task.** -> fast
`)
assert.Success(t, err)
assertQuery(t, m, 5, 1, nil, "")
},
},
{
name: "double-glob/edge/2",
run: func(t testing.TB) {
m, err := compile(t, `a
**.b -> c
`)
assert.Success(t, err)
assertQuery(t, m, 3, 1, nil, "")
},
},
}
runa(t, tca)
t.Run("errors", func(t *testing.T) {
tca := []testCase{
{
name: "glob-edge-glob-index",
run: func(t testing.TB) {
_, err := compile(t, `(* -> b)[*].style.fill: red
`)
assert.ErrorString(t, err, `TestCompile/patterns/errors/glob-edge-glob-index.d2:1:2: indexed edge does not exist`)
},
},
}
runa(t, tca)
})
}

View file

@ -29,8 +29,14 @@ func (m *Map) QueryAll(idStr string) (na []Node, _ error) {
}
eida := NewEdgeIDs(k)
for _, eid := range eida {
ea := m.GetEdges(eid)
for i, eid := range eida {
refctx := &RefContext{
Key: k,
ScopeMap: m,
Edge: k.Edges[i],
}
ea := m.GetEdges(eid, refctx)
for _, e := range ea {
if k.EdgeKey == nil {
na = append(na, e)
@ -56,7 +62,7 @@ func (m *Map) Query(idStr string) (Node, error) {
return nil, nil
}
if len(na) > 1 {
return nil, fmt.Errorf("expected only one query result but got: %#v", err)
return nil, fmt.Errorf("expected only one query result but got: %#v", na)
}
return na[0], nil
}

File diff suppressed because it is too large Load diff

View file

@ -11,6 +11,8 @@ import (
"errors"
"fmt"
"math"
"regexp"
"strconv"
"strings"
"github.com/dop251/goja"
@ -180,6 +182,11 @@ func Layout(ctx context.Context, g *d2graph.Graph, opts *ConfigurableOpts) (err
elkGraph.LayoutOptions.Direction = "DOWN"
}
// set label and icon positions for ELK
for _, obj := range g.Objects {
positionLabelsIcons(obj)
}
elkNodes := make(map[*d2graph.Object]*ELKNode)
elkEdges := make(map[*d2graph.Edge]*ELKEdge)
@ -214,18 +221,7 @@ func Layout(ctx context.Context, g *d2graph.Graph, opts *ConfigurableOpts) (err
}
}
height := obj.Height
width := obj.Width
if obj.HasLabel() {
if obj.HasOutsideBottomLabel() || obj.Icon != nil {
height += float64(obj.LabelDimensions.Height) + label.PADDING
}
width = go2.Max(width, float64(obj.LabelDimensions.Width))
}
// reserve extra space for 3d/multiple by providing elk the larger dimensions
dx, dy := obj.GetModifierElementAdjustments()
width += dx
height += dy
width, height := adjustDimensions(obj)
n := &ELKNode{
ID: obj.AbsID(),
@ -262,41 +258,18 @@ func Layout(ctx context.Context, g *d2graph.Graph, opts *ConfigurableOpts) (err
case "RIGHT", "LEFT":
n.LayoutOptions.NodeSizeMinimum = fmt.Sprintf("(%d, %d)", int(math.Ceil(width)), int(math.Ceil(height)))
}
if n.LayoutOptions.Padding == DefaultOpts.Padding {
labelHeight := 0
if obj.HasLabel() {
labelHeight = obj.LabelDimensions.Height + label.PADDING
}
n.Height += 100 + float64(labelHeight)
n.Width += 100
contentBox := geo.NewBox(geo.NewPoint(0, 0), float64(n.Width), float64(n.Height))
shapeType := d2target.DSL_SHAPE_TO_SHAPE_TYPE[obj.Shape.Value]
s := shape.NewShape(shapeType, contentBox)
paddingTop := n.Height - s.GetInnerBox().Height
n.Height -= (100 + float64(labelHeight))
n.Width -= 100
iconHeight := 0
if obj.Icon != nil && obj.Shape.Value != d2target.ShapeImage {
iconHeight = d2target.GetIconSize(s.GetInnerBox(), string(label.InsideTopLeft)) + label.PADDING*2
}
paddingTop += float64(go2.Max(labelHeight, iconHeight))
n.LayoutOptions.Padding = fmt.Sprintf("[top=%d,left=50,bottom=50,right=50]",
// Default padding
go2.Max(int(math.Ceil(paddingTop)), 50),
)
}
} else {
n.LayoutOptions = &elkOpts{
SelfLoopDistribution: "EQUALLY",
}
}
if obj.IsContainer() {
padding := parsePadding(opts.Padding)
padding = adjustPadding(obj, width, height, padding)
n.LayoutOptions.Padding = padding.String()
}
if obj.HasLabel() {
n.Labels = append(n.Labels, &ELKLabel{
Text: obj.Label.Value,
@ -313,6 +286,41 @@ func Layout(ctx context.Context, g *d2graph.Graph, opts *ConfigurableOpts) (err
elkNodes[obj] = n
})
// adjust parent padding for children with outside positioned icons
for _, obj := range g.Objects {
if !obj.IsContainer() {
continue
}
var hasTop, hasBottom bool
for _, child := range obj.ChildrenArray {
if child.Shape.Value == d2target.ShapeImage || child.IconPosition == nil {
continue
}
switch label.Position(*child.IconPosition) {
case label.OutsideTopLeft, label.OutsideTopCenter, label.OutsideTopRight:
hasTop = true
case label.OutsideBottomLeft, label.OutsideBottomCenter, label.OutsideBottomRight:
hasBottom = true
}
if hasTop && hasBottom {
break
}
}
if hasTop || hasBottom {
padding := parsePadding(elkNodes[obj].LayoutOptions.Padding)
if hasTop {
padding.top = go2.Max(padding.top, d2target.MAX_ICON_SIZE+2*label.PADDING)
}
if hasBottom {
padding.bottom = go2.Max(padding.bottom, d2target.MAX_ICON_SIZE+2*label.PADDING)
}
elkNodes[obj].LayoutOptions.Padding = padding.String()
}
}
for _, edge := range g.Edges {
e := &ELKEdge{
ID: edge.AbsID(),
@ -407,29 +415,6 @@ func Layout(ctx context.Context, g *d2graph.Graph, opts *ConfigurableOpts) (err
obj.Width = math.Ceil(n.Width)
obj.Height = math.Ceil(n.Height)
if obj.Icon != nil && obj.IconPosition == nil {
if len(obj.ChildrenArray) > 0 {
obj.IconPosition = go2.Pointer(string(label.InsideTopLeft))
if obj.LabelPosition == nil {
obj.LabelPosition = go2.Pointer(string(label.InsideTopRight))
}
} else {
obj.IconPosition = go2.Pointer(string(label.InsideMiddleCenter))
}
}
if obj.HasLabel() && obj.LabelPosition == nil {
if len(obj.ChildrenArray) > 0 {
obj.LabelPosition = go2.Pointer(string(label.InsideTopCenter))
} else if obj.HasOutsideBottomLabel() {
obj.LabelPosition = go2.Pointer(string(label.OutsideBottomCenter))
obj.Height -= float64(obj.LabelDimensions.Height) + label.PADDING
} else if obj.Icon != nil {
obj.LabelPosition = go2.Pointer(string(label.InsideTopCenter))
} else {
obj.LabelPosition = go2.Pointer(string(label.InsideMiddleCenter))
}
}
byID[obj.AbsID()] = obj
})
@ -463,18 +448,8 @@ func Layout(ctx context.Context, g *d2graph.Graph, opts *ConfigurableOpts) (err
edge.Route = points
}
// remove the extra width/height we added for 3d/multiple after all objects/connections are placed
// and shift the shapes down accordingly
for _, obj := range g.Objects {
dx, dy := obj.GetModifierElementAdjustments()
if dx != 0 || dy != 0 {
obj.TopLeft.Y += dy
obj.ShiftDescendants(0, dy)
if !obj.IsContainer() {
obj.Width -= dx
obj.Height -= dy
}
}
cleanupAdjustment(obj)
}
for _, edge := range g.Edges {
@ -808,3 +783,286 @@ func childrenMaxSelfLoop(parent *d2graph.Object, isWidth bool) int {
return max
}
type shapePadding struct {
top, left, bottom, right int
}
// parse out values from elk padding string. e.g. "[top=50,left=50,bottom=50,right=50]"
func parsePadding(in string) shapePadding {
reTop := regexp.MustCompile(`top=(\d+)`)
reLeft := regexp.MustCompile(`left=(\d+)`)
reBottom := regexp.MustCompile(`bottom=(\d+)`)
reRight := regexp.MustCompile(`right=(\d+)`)
padding := shapePadding{}
submatches := reTop.FindStringSubmatch(in)
if len(submatches) == 2 {
i, err := strconv.ParseInt(submatches[1], 10, 64)
if err == nil {
padding.top = int(i)
}
}
submatches = reLeft.FindStringSubmatch(in)
if len(submatches) == 2 {
i, err := strconv.ParseInt(submatches[1], 10, 64)
if err == nil {
padding.left = int(i)
}
}
submatches = reBottom.FindStringSubmatch(in)
if len(submatches) == 2 {
i, err := strconv.ParseInt(submatches[1], 10, 64)
if err == nil {
padding.bottom = int(i)
}
}
submatches = reRight.FindStringSubmatch(in)
i, err := strconv.ParseInt(submatches[1], 10, 64)
if len(submatches) == 2 {
if err == nil {
padding.right = int(i)
}
}
return padding
}
func (padding shapePadding) String() string {
return fmt.Sprintf("[top=%d,left=%d,bottom=%d,right=%d]", padding.top, padding.left, padding.bottom, padding.right)
}
func adjustPadding(obj *d2graph.Object, width, height float64, padding shapePadding) shapePadding {
if !obj.IsContainer() {
return padding
}
// compute extra space padding for label/icon
var extraTop, extraBottom, extraLeft, extraRight int
if obj.HasLabel() && obj.LabelPosition != nil {
labelHeight := obj.LabelDimensions.Height + 2*label.PADDING
labelWidth := obj.LabelDimensions.Width + 2*label.PADDING
switch label.Position(*obj.LabelPosition) {
case label.InsideTopLeft, label.InsideTopCenter, label.InsideTopRight:
// Note: for corners we only add height
extraTop = labelHeight
case label.InsideBottomLeft, label.InsideBottomCenter, label.InsideBottomRight:
extraBottom = labelHeight
case label.InsideMiddleLeft:
extraLeft = labelWidth
case label.InsideMiddleRight:
extraRight = labelWidth
}
}
if obj.Icon != nil && obj.Shape.Value != d2target.ShapeImage && obj.IconPosition != nil {
iconSize := d2target.MAX_ICON_SIZE + 2*label.PADDING
switch label.Position(*obj.IconPosition) {
case label.InsideTopLeft, label.InsideTopCenter, label.InsideTopRight:
extraTop = go2.Max(extraTop, iconSize)
case label.InsideBottomLeft, label.InsideBottomCenter, label.InsideBottomRight:
extraBottom = go2.Max(extraBottom, iconSize)
case label.InsideMiddleLeft:
extraLeft = go2.Max(extraLeft, iconSize)
case label.InsideMiddleRight:
extraRight = go2.Max(extraRight, iconSize)
}
}
maxChildWidth, maxChildHeight := math.Inf(-1), math.Inf(-1)
for _, c := range obj.ChildrenArray {
if c.Width > maxChildWidth {
maxChildWidth = c.Width
}
if c.Height > maxChildHeight {
maxChildHeight = c.Height
}
}
// We don't know exactly what the shape dimensions will be after layout, but for more accurate innerBox dimensions,
// we add the maxChildWidth and maxChildHeight with computed additions for the innerBox calculation
width += maxChildWidth + float64(extraLeft+extraRight)
height += maxChildHeight + float64(extraTop+extraBottom)
contentBox := geo.NewBox(geo.NewPoint(0, 0), width, height)
shapeType := d2target.DSL_SHAPE_TO_SHAPE_TYPE[obj.Shape.Value]
s := shape.NewShape(shapeType, contentBox)
innerBox := s.GetInnerBox()
// If the shape inner box + label/icon height becomes greater than the default padding, we want to use that
//
// ┌OUTER───────────────────────────┬────────────────────────────────────────────┐
// │ │ │
// │ ┌INNER──────── ┬ ─────────────│───────────────────────────────────────┐ │
// │ │ │Label Padding │ │ │
// │ │ ┌LABEL─ ┴ ─────────────│───────┐┬ ┌ICON── ┬ ────┐ │ │
// │ │ │ │ ││ │ │ │ │ │
// │ │ │ │ ││Label Height │ Icon│ │ │ │
// │ │ │ │ ││ │ Height│ │ │ │
// │ │ └──────────────────────│───────┘┴ │ │ │ │ │
// │ │ │ └────── ┴ ────┘ │ │
// │ │ │ │ │
// │ │ ┴Default ELK Padding │ │
// │ │ ┌CHILD────────────────────────────────────────────────────────┐ │ │
// │ │ │ │ │ │
// │ │ │ │ │ │
// │ │ │ │ │ │
// │ │ └─────────────────────────────────────────────────────────────┘ │ │
// │ │ │ │
// │ └─────────────────────────────────────────────────────────────────────┘ │
// │ │
// └─────────────────────────────────────────────────────────────────────────────┘
// estimated shape innerBox padding
innerTop := int(math.Ceil(innerBox.TopLeft.Y))
innerBottom := int(math.Ceil(height - (innerBox.TopLeft.Y + innerBox.Height)))
innerLeft := int(math.Ceil(innerBox.TopLeft.X))
innerRight := int(math.Ceil(width - (innerBox.TopLeft.X + innerBox.Width)))
padding.top = go2.Max(padding.top, innerTop+extraTop)
padding.bottom = go2.Max(padding.bottom, innerBottom+extraBottom)
padding.left = go2.Max(padding.left, innerLeft+extraLeft)
padding.right = go2.Max(padding.right, innerRight+extraRight)
return padding
}
func adjustDimensions(obj *d2graph.Object) (width, height float64) {
width = obj.Width
height = obj.Height
// reserve spacing for labels
if obj.HasLabel() {
var position label.Position
if obj.LabelPosition != nil {
position = label.Position(*obj.LabelPosition)
} else if len(obj.ChildrenArray) == 0 && obj.HasOutsideBottomLabel() {
position = label.OutsideBottomCenter
}
if position.IsShapePosition() {
switch position {
case label.OutsideLeftTop, label.OutsideLeftMiddle, label.OutsideLeftBottom,
label.OutsideRightTop, label.OutsideRightMiddle, label.OutsideRightBottom:
width += float64(obj.LabelDimensions.Width) + label.PADDING
default:
// TODO labelWidth+2*label.PADDING
width = go2.Max(width, float64(obj.LabelDimensions.Width))
}
}
// special handling
if obj.HasOutsideBottomLabel() || obj.Icon != nil {
height += float64(obj.LabelDimensions.Height) + label.PADDING
}
}
if obj.Icon != nil && obj.Shape.Value != d2target.ShapeImage {
var position label.Position
if obj.IconPosition != nil {
position = label.Position(*obj.IconPosition)
}
if position.IsShapePosition() {
switch position {
case label.OutsideLeftTop, label.OutsideLeftMiddle, label.OutsideLeftBottom,
label.OutsideRightTop, label.OutsideRightMiddle, label.OutsideRightBottom:
width += d2target.MAX_ICON_SIZE + label.PADDING
default:
width = go2.Max(width, d2target.MAX_ICON_SIZE+2*label.PADDING)
}
}
}
// reserve extra space for 3d/multiple by providing elk the larger dimensions
dx, dy := obj.GetModifierElementAdjustments()
width += dx
height += dy
return
}
func cleanupAdjustment(obj *d2graph.Object) {
// adjust size and position to account for space reserved for labels
if obj.HasLabel() {
position := label.Position(*obj.LabelPosition)
if position.IsShapePosition() {
var labelWidth float64
switch position {
case label.OutsideLeftTop, label.OutsideLeftMiddle, label.OutsideLeftBottom,
label.OutsideRightTop, label.OutsideRightMiddle, label.OutsideRightBottom:
labelWidth = float64(obj.LabelDimensions.Width) + label.PADDING
obj.Width -= labelWidth
}
switch position {
case label.OutsideLeftTop, label.OutsideLeftMiddle, label.OutsideLeftBottom:
obj.TopLeft.X += labelWidth
obj.ShiftDescendants(labelWidth/2, 0)
case label.OutsideRightTop, label.OutsideRightMiddle, label.OutsideRightBottom:
obj.ShiftDescendants(-labelWidth/2, 0)
}
}
}
if obj.Icon != nil && obj.Shape.Value != d2target.ShapeImage {
position := label.Position(*obj.IconPosition)
if position.IsShapePosition() {
var iconWidth float64
switch position {
case label.OutsideLeftTop, label.OutsideLeftMiddle, label.OutsideLeftBottom,
label.OutsideRightTop, label.OutsideRightMiddle, label.OutsideRightBottom:
iconWidth = d2target.MAX_ICON_SIZE + label.PADDING
obj.Width -= iconWidth
}
switch position {
case label.OutsideLeftTop, label.OutsideLeftMiddle, label.OutsideLeftBottom:
obj.TopLeft.X += iconWidth
obj.ShiftDescendants(iconWidth/2, 0)
case label.OutsideRightTop, label.OutsideRightMiddle, label.OutsideRightBottom:
obj.ShiftDescendants(-iconWidth/2, 0)
}
}
}
// special handling to start/end connections below label
if obj.HasOutsideBottomLabel() {
obj.Height -= float64(obj.LabelDimensions.Height) + label.PADDING
}
// remove the extra width/height we added for 3d/multiple after all objects/connections are placed
// and shift the shapes down accordingly
dx, dy := obj.GetModifierElementAdjustments()
if dx != 0 || dy != 0 {
obj.TopLeft.Y += dy
obj.ShiftDescendants(0, dy)
if !obj.IsContainer() {
obj.Width -= dx
obj.Height -= dy
}
}
}
func positionLabelsIcons(obj *d2graph.Object) {
if obj.Icon != nil && obj.IconPosition == nil {
if len(obj.ChildrenArray) > 0 {
obj.IconPosition = go2.Pointer(string(label.InsideTopLeft))
if obj.LabelPosition == nil {
obj.LabelPosition = go2.Pointer(string(label.InsideTopRight))
return
}
} else {
obj.IconPosition = go2.Pointer(string(label.InsideMiddleCenter))
}
}
if obj.HasLabel() && obj.LabelPosition == nil {
if len(obj.ChildrenArray) > 0 {
obj.LabelPosition = go2.Pointer(string(label.InsideTopCenter))
} else if obj.HasOutsideBottomLabel() {
obj.LabelPosition = go2.Pointer(string(label.OutsideBottomCenter))
} else if obj.Icon != nil {
obj.LabelPosition = go2.Pointer(string(label.InsideTopCenter))
} else {
obj.LabelPosition = go2.Pointer(string(label.InsideMiddleCenter))
}
}
}

View file

@ -37,7 +37,7 @@ n2 -> n1: right to left
n1 -> n2
n2 -> n1
`
g, err := d2compiler.Compile("", strings.NewReader(input), nil)
g, _, err := d2compiler.Compile("", strings.NewReader(input), nil)
assert.Nil(t, err)
n1, has := g.Root.HasChild([]string{"n1"})
@ -177,7 +177,7 @@ a.t2 -> b
b -> a.t2`
ctx := log.WithTB(context.Background(), t, nil)
g, err := d2compiler.Compile("", strings.NewReader(input), nil)
g, _, err := d2compiler.Compile("", strings.NewReader(input), nil)
assert.Nil(t, err)
g.Root.Shape = d2graph.Scalar{Value: d2target.ShapeSequenceDiagram}
@ -298,7 +298,7 @@ c
container -> c: edge 1
`
ctx := log.WithTB(context.Background(), t, nil)
g, err := d2compiler.Compile("", strings.NewReader(input), nil)
g, _, err := d2compiler.Compile("", strings.NewReader(input), nil)
assert.Nil(t, err)
container, has := g.Root.HasChild([]string{"container"})

View file

@ -15,8 +15,11 @@ import (
"oss.terrastruct.com/d2/d2layouts/d2near"
"oss.terrastruct.com/d2/d2layouts/d2sequence"
"oss.terrastruct.com/d2/d2renderers/d2fonts"
"oss.terrastruct.com/d2/d2renderers/d2svg"
"oss.terrastruct.com/d2/d2target"
"oss.terrastruct.com/d2/d2themes/d2themescatalog"
"oss.terrastruct.com/d2/lib/textmeasure"
"oss.terrastruct.com/util-go/go2"
)
type CompileOptions struct {
@ -24,8 +27,9 @@ type CompileOptions struct {
FS fs.FS
MeasuredTexts []*d2target.MText
Ruler *textmeasure.Ruler
Layout func(context.Context, *d2graph.Graph) error
ThemeID int64
LayoutResolver func(engine string) (d2graph.LayoutGraph, error)
Layout *string
// FontFamily controls the font family used for all texts that are not the following:
// - code
@ -37,39 +41,45 @@ type CompileOptions struct {
InputPath string
}
func Compile(ctx context.Context, input string, opts *CompileOptions) (*d2target.Diagram, *d2graph.Graph, error) {
if opts == nil {
opts = &CompileOptions{}
func Compile(ctx context.Context, input string, compileOpts *CompileOptions, renderOpts *d2svg.RenderOpts) (*d2target.Diagram, *d2graph.Graph, error) {
if compileOpts == nil {
compileOpts = &CompileOptions{}
}
if renderOpts == nil {
renderOpts = &d2svg.RenderOpts{}
}
g, err := d2compiler.Compile(opts.InputPath, strings.NewReader(input), &d2compiler.CompileOptions{
UTF16: opts.UTF16,
FS: opts.FS,
g, config, err := d2compiler.Compile(compileOpts.InputPath, strings.NewReader(input), &d2compiler.CompileOptions{
UTF16: compileOpts.UTF16,
FS: compileOpts.FS,
})
if err != nil {
return nil, nil, err
}
d, err := compile(ctx, g, opts)
if err != nil {
return nil, nil, err
applyConfigs(config, compileOpts, renderOpts)
applyDefaults(compileOpts, renderOpts)
d, err := compile(ctx, g, compileOpts, renderOpts)
if d != nil {
d.Config = config
}
return d, g, nil
return d, g, err
}
func compile(ctx context.Context, g *d2graph.Graph, opts *CompileOptions) (*d2target.Diagram, error) {
err := g.ApplyTheme(opts.ThemeID)
func compile(ctx context.Context, g *d2graph.Graph, compileOpts *CompileOptions, renderOpts *d2svg.RenderOpts) (*d2target.Diagram, error) {
err := g.ApplyTheme(*renderOpts.ThemeID)
if err != nil {
return nil, err
}
if len(g.Objects) > 0 {
err := g.SetDimensions(opts.MeasuredTexts, opts.Ruler, opts.FontFamily)
err := g.SetDimensions(compileOpts.MeasuredTexts, compileOpts.Ruler, compileOpts.FontFamily)
if err != nil {
return nil, err
}
coreLayout, err := getLayout(opts)
coreLayout, err := getLayout(compileOpts)
if err != nil {
return nil, err
}
@ -96,27 +106,27 @@ func compile(ctx context.Context, g *d2graph.Graph, opts *CompileOptions) (*d2ta
}
}
d, err := d2exporter.Export(ctx, g, opts.FontFamily)
d, err := d2exporter.Export(ctx, g, compileOpts.FontFamily)
if err != nil {
return nil, err
}
for _, l := range g.Layers {
ld, err := compile(ctx, l, opts)
ld, err := compile(ctx, l, compileOpts, renderOpts)
if err != nil {
return nil, err
}
d.Layers = append(d.Layers, ld)
}
for _, l := range g.Scenarios {
ld, err := compile(ctx, l, opts)
ld, err := compile(ctx, l, compileOpts, renderOpts)
if err != nil {
return nil, err
}
d.Scenarios = append(d.Scenarios, ld)
}
for _, l := range g.Steps {
ld, err := compile(ctx, l, opts)
ld, err := compile(ctx, l, compileOpts, renderOpts)
if err != nil {
return nil, err
}
@ -127,7 +137,7 @@ func compile(ctx context.Context, g *d2graph.Graph, opts *CompileOptions) (*d2ta
func getLayout(opts *CompileOptions) (d2graph.LayoutGraph, error) {
if opts.Layout != nil {
return opts.Layout, nil
return opts.LayoutResolver(*opts.Layout)
} else if os.Getenv("D2_LAYOUT") == "dagre" {
defaultLayout := func(ctx context.Context, g *d2graph.Graph) error {
return d2dagrelayout.Layout(ctx, g, nil)
@ -137,3 +147,53 @@ func getLayout(opts *CompileOptions) (d2graph.LayoutGraph, error) {
return nil, errors.New("no available layout")
}
}
// applyConfigs applies the configs read from D2 and applies it to passed in opts
// It will only write to opt fields that are nil, as passed-in opts have precedence
func applyConfigs(config *d2target.Config, compileOpts *CompileOptions, renderOpts *d2svg.RenderOpts) {
if config == nil {
return
}
if compileOpts.Layout == nil {
compileOpts.Layout = config.LayoutEngine
}
if renderOpts.ThemeID == nil {
renderOpts.ThemeID = config.ThemeID
}
if renderOpts.DarkThemeID == nil {
renderOpts.DarkThemeID = config.DarkThemeID
}
if renderOpts.Sketch == nil {
renderOpts.Sketch = config.Sketch
}
if renderOpts.Pad == nil {
renderOpts.Pad = config.Pad
}
if renderOpts.Center == nil {
renderOpts.Center = config.Center
}
}
func applyDefaults(compileOpts *CompileOptions, renderOpts *d2svg.RenderOpts) {
if compileOpts.Layout == nil {
compileOpts.Layout = go2.Pointer("dagre")
}
if renderOpts.ThemeID == nil {
renderOpts.ThemeID = &d2themescatalog.NeutralDefault.ID
}
if renderOpts.Sketch == nil {
renderOpts.Sketch = go2.Pointer(false)
}
if *renderOpts.Sketch {
compileOpts.FontFamily = go2.Pointer(d2fonts.HandDrawn)
}
if renderOpts.Pad == nil {
renderOpts.Pad = go2.Pointer(int64(d2svg.DEFAULT_PADDING))
}
if renderOpts.Center == nil {
renderOpts.Center = go2.Pointer(false)
}
}

View file

@ -305,7 +305,7 @@ func pathFromScopeObj(g *d2graph.Graph, key *d2ast.Key, fromScope *d2graph.Objec
func recompile(ast *d2ast.Map) (*d2graph.Graph, error) {
s := d2format.Format(ast)
g, err := d2compiler.Compile(ast.Range.Path, strings.NewReader(s), nil)
g, _, err := d2compiler.Compile(ast.Range.Path, strings.NewReader(s), nil)
if err != nil {
return nil, fmt.Errorf("failed to recompile:\n%s\n%w", s, err)
}

View file

@ -7002,7 +7002,7 @@ type editTest struct {
func (tc editTest) run(t *testing.T) {
d2Path := fmt.Sprintf("d2/testdata/d2oracle/%v.d2", t.Name())
g, err := d2compiler.Compile(d2Path, strings.NewReader(tc.text), nil)
g, _, err := d2compiler.Compile(d2Path, strings.NewReader(tc.text), nil)
if err != nil {
t.Fatal(err)
}
@ -7265,7 +7265,7 @@ scenarios: {
t.Parallel()
d2Path := fmt.Sprintf("d2/testdata/d2oracle/%v.d2", t.Name())
g, err := d2compiler.Compile(d2Path, strings.NewReader(tc.text), nil)
g, _, err := d2compiler.Compile(d2Path, strings.NewReader(tc.text), nil)
if err != nil {
t.Fatal(err)
}
@ -7725,7 +7725,7 @@ z
t.Parallel()
d2Path := fmt.Sprintf("d2/testdata/d2oracle/%v.d2", t.Name())
g, err := d2compiler.Compile(d2Path, strings.NewReader(tc.text), nil)
g, _, err := d2compiler.Compile(d2Path, strings.NewReader(tc.text), nil)
if err != nil {
t.Fatal(err)
}
@ -8095,7 +8095,7 @@ layers: {
t.Parallel()
d2Path := fmt.Sprintf("d2/testdata/d2oracle/%v.d2", t.Name())
g, err := d2compiler.Compile(d2Path, strings.NewReader(tc.text), nil)
g, _, err := d2compiler.Compile(d2Path, strings.NewReader(tc.text), nil)
if err != nil {
t.Fatal(err)
}
@ -8325,7 +8325,7 @@ scenarios: {
t.Parallel()
d2Path := fmt.Sprintf("d2/testdata/d2oracle/%v.d2", t.Name())
g, err := d2compiler.Compile(d2Path, strings.NewReader(tc.text), nil)
g, _, err := d2compiler.Compile(d2Path, strings.NewReader(tc.text), nil)
if err != nil {
t.Fatal(err)
}

View file

@ -1030,9 +1030,15 @@ func (p *parser) parseUnquotedString(inKey bool) (s *d2ast.UnquotedString) {
var sb strings.Builder
var rawb strings.Builder
lastPatternIndex := 0
defer func() {
sv := strings.TrimRightFunc(sb.String(), unicode.IsSpace)
rawv := strings.TrimRightFunc(rawb.String(), unicode.IsSpace)
if s.Pattern != nil {
if lastPatternIndex < len(sv) {
s.Pattern = append(s.Pattern, sv[lastPatternIndex:])
}
}
if sv == "" {
if len(s.Value) > 0 {
return
@ -1092,7 +1098,7 @@ func (p *parser) parseUnquotedString(inKey bool) (s *d2ast.UnquotedString) {
}
if inKey {
switch r {
case ':', '.', '<', '>':
case ':', '.', '<', '>', '&':
p.rewind()
return s
case '-':
@ -1118,18 +1124,12 @@ func (p *parser) parseUnquotedString(inKey bool) (s *d2ast.UnquotedString) {
rawb.WriteRune(r)
r = r2
case '*':
// TODO: need a peekNotSpace across escaped newlines
r2, eof := p.peek()
if eof {
return s
if sb.Len() == 0 {
s.Pattern = append(s.Pattern, "*")
} else {
s.Pattern = append(s.Pattern, sb.String()[lastPatternIndex:], "*")
}
if r2 == '-' {
p.rewind()
return s
}
sb.WriteRune(r)
rawb.WriteRune(r)
r = r2
lastPatternIndex = len(sb.String()) + 1
}
}
@ -1560,7 +1560,8 @@ func (p *parser) parseArrayNode(r rune) d2ast.ArrayNodeBox {
p.replay(r)
vbox := p.parseValue()
if vbox.UnquotedString != nil && vbox.UnquotedString.ScalarString() == "" {
if vbox.UnquotedString != nil && vbox.UnquotedString.ScalarString() == "" &&
!(len(vbox.UnquotedString.Value) > 0 && vbox.UnquotedString.Value[0].Substitution != nil) {
p.errorf(p.pos, p.pos.Advance(r, p.utf16), "unquoted strings cannot start on %q", r)
}
box.Null = vbox.Null

View file

@ -49,10 +49,10 @@ func Wrap(rootDiagram *d2target.Diagram, svgs [][]byte, renderOpts d2svg.RenderO
// TODO account for stroke width of root border
tl, br := rootDiagram.NestedBoundingBox()
left := tl.X - renderOpts.Pad
top := tl.Y - renderOpts.Pad
width := br.X - tl.X + renderOpts.Pad*2
height := br.Y - tl.Y + renderOpts.Pad*2
left := tl.X - int(*renderOpts.Pad)
top := tl.Y - int(*renderOpts.Pad)
width := br.X - tl.X + int(*renderOpts.Pad)*2
height := br.Y - tl.Y + int(*renderOpts.Pad)*2
fitToScreenWrapperOpening := fmt.Sprintf(`<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="%s" preserveAspectRatio="xMinYMin meet" viewBox="0 0 %d %d">`,
version.Version,
@ -93,7 +93,7 @@ func Wrap(rootDiagram *d2target.Diagram, svgs [][]byte, renderOpts d2svg.RenderO
fmt.Fprintf(buf, `<style type="text/css">%s</style>`, css)
}
if renderOpts.Sketch {
if renderOpts.Sketch != nil && *renderOpts.Sketch {
d2sketch.DefineFillPatterns(buf)
}

View file

@ -17,6 +17,7 @@ import (
"oss.terrastruct.com/util-go/diff"
"oss.terrastruct.com/util-go/go2"
"oss.terrastruct.com/d2/d2graph"
"oss.terrastruct.com/d2/d2layouts/d2dagrelayout"
"oss.terrastruct.com/d2/d2layouts/d2elklayout"
"oss.terrastruct.com/d2/d2lib"
@ -1339,16 +1340,22 @@ func run(t *testing.T, tc testCase) {
return
}
layout := d2dagrelayout.DefaultLayout
if strings.EqualFold(tc.engine, "elk") {
layout = d2elklayout.DefaultLayout
layoutResolver := func(engine string) (d2graph.LayoutGraph, error) {
if strings.EqualFold(engine, "elk") {
return d2elklayout.DefaultLayout, nil
}
return d2dagrelayout.DefaultLayout, nil
}
renderOpts := &d2svg.RenderOpts{
Sketch: go2.Pointer(true),
ThemeID: go2.Pointer(tc.themeID),
}
diagram, _, err := d2lib.Compile(ctx, tc.script, &d2lib.CompileOptions{
Ruler: ruler,
Layout: layout,
Layout: &tc.engine,
LayoutResolver: layoutResolver,
FontFamily: go2.Pointer(d2fonts.HandDrawn),
ThemeID: tc.themeID,
})
}, renderOpts)
if !tassert.Nil(t, err) {
return
}
@ -1356,11 +1363,7 @@ func run(t *testing.T, tc testCase) {
dataPath := filepath.Join("testdata", strings.TrimPrefix(t.Name(), "TestSketch/"))
pathGotSVG := filepath.Join(dataPath, "sketch.got.svg")
svgBytes, err := d2svg.Render(diagram, &d2svg.RenderOpts{
Pad: d2svg.DEFAULT_PADDING,
Sketch: true,
ThemeID: tc.themeID,
})
svgBytes, err := d2svg.Render(diagram, renderOpts)
assert.Success(t, err)
err = os.MkdirAll(dataPath, 0755)
assert.Success(t, err)

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 129 KiB

After

Width:  |  Height:  |  Size: 129 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 120 KiB

After

Width:  |  Height:  |  Size: 120 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 69 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 60 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 130 KiB

After

Width:  |  Height:  |  Size: 130 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 121 KiB

After

Width:  |  Height:  |  Size: 121 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 43 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 64 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 55 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 53 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 76 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 59 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 50 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 163 KiB

After

Width:  |  Height:  |  Size: 163 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 154 KiB

After

Width:  |  Height:  |  Size: 154 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 51 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 163 KiB

After

Width:  |  Height:  |  Size: 163 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 167 KiB

After

Width:  |  Height:  |  Size: 167 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 108 KiB

After

Width:  |  Height:  |  Size: 115 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 77 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 59 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 66 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 110 KiB

After

Width:  |  Height:  |  Size: 110 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 101 KiB

After

Width:  |  Height:  |  Size: 101 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 57 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 493 KiB

After

Width:  |  Height:  |  Size: 497 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 124 KiB

After

Width:  |  Height:  |  Size: 124 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 77 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 68 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 108 KiB

After

Width:  |  Height:  |  Size: 113 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 217 KiB

After

Width:  |  Height:  |  Size: 216 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 217 KiB

After

Width:  |  Height:  |  Size: 216 KiB

View file

@ -16,6 +16,7 @@ import (
"oss.terrastruct.com/util-go/assert"
"oss.terrastruct.com/util-go/diff"
"oss.terrastruct.com/d2/d2graph"
"oss.terrastruct.com/d2/d2layouts/d2dagrelayout"
"oss.terrastruct.com/d2/d2lib"
"oss.terrastruct.com/d2/d2renderers/d2svg"
@ -152,11 +153,17 @@ func run(t *testing.T, tc testCase) {
return
}
renderOpts := &d2svg.RenderOpts{
ThemeID: &tc.themeID,
}
layoutResolver := func(engine string) (d2graph.LayoutGraph, error) {
return d2dagrelayout.DefaultLayout, nil
}
diagram, _, err := d2lib.Compile(ctx, tc.script, &d2lib.CompileOptions{
Ruler: ruler,
Layout: d2dagrelayout.DefaultLayout,
ThemeID: tc.themeID,
})
LayoutResolver: layoutResolver,
}, renderOpts)
if !tassert.Nil(t, err) {
return
}
@ -164,10 +171,7 @@ func run(t *testing.T, tc testCase) {
dataPath := filepath.Join("testdata", strings.TrimPrefix(t.Name(), "TestAppendix/"))
pathGotSVG := filepath.Join(dataPath, "sketch.got.svg")
svgBytes, err := d2svg.Render(diagram, &d2svg.RenderOpts{
Pad: d2svg.DEFAULT_PADDING,
ThemeID: tc.themeID,
})
svgBytes, err := d2svg.Render(diagram, renderOpts)
assert.Success(t, err)
svgBytes = appendix.Append(diagram, ruler, svgBytes)

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 677 KiB

After

Width:  |  Height:  |  Size: 677 KiB

View file

@ -1,12 +1,12 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.5.1-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 304 407"><svg id="d2-svg" class="d2-916646398" width="304" height="407" viewBox="-101 -118 304 407"><rect x="-101.000000" y="-118.000000" width="304" height="407" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.6.0-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 304 407"><svg id="d2-svg" class="d2-3057089836" width="304" height="407" viewBox="-101 -118 304 407"><rect x="-101.000000" y="-118.000000" width="304" height="407" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
.appendix-icon {
filter: drop-shadow(0px 0px 32px rgba(31, 36, 58, 0.1));
}
.d2-916646398 .text-bold {
font-family: "d2-916646398-font-bold";
.d2-3057089836 .text-bold {
font-family: "d2-3057089836-font-bold";
}
@font-face {
font-family: d2-916646398-font-bold;
font-family: d2-3057089836-font-bold;
src: url("data:application/font-woff;base64,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");
}]]></style><style type="text/css"><![CDATA[.shape {
shape-rendering: geometricPrecision;
@ -21,78 +21,78 @@
opacity: 0.5;
}
.d2-916646398 .fill-N1{fill:#0A0F25;}
.d2-916646398 .fill-N2{fill:#676C7E;}
.d2-916646398 .fill-N3{fill:#9499AB;}
.d2-916646398 .fill-N4{fill:#CFD2DD;}
.d2-916646398 .fill-N5{fill:#DEE1EB;}
.d2-916646398 .fill-N6{fill:#EEF1F8;}
.d2-916646398 .fill-N7{fill:#FFFFFF;}
.d2-916646398 .fill-B1{fill:#0D32B2;}
.d2-916646398 .fill-B2{fill:#0D32B2;}
.d2-916646398 .fill-B3{fill:#E3E9FD;}
.d2-916646398 .fill-B4{fill:#E3E9FD;}
.d2-916646398 .fill-B5{fill:#EDF0FD;}
.d2-916646398 .fill-B6{fill:#F7F8FE;}
.d2-916646398 .fill-AA2{fill:#4A6FF3;}
.d2-916646398 .fill-AA4{fill:#EDF0FD;}
.d2-916646398 .fill-AA5{fill:#F7F8FE;}
.d2-916646398 .fill-AB4{fill:#EDF0FD;}
.d2-916646398 .fill-AB5{fill:#F7F8FE;}
.d2-916646398 .stroke-N1{stroke:#0A0F25;}
.d2-916646398 .stroke-N2{stroke:#676C7E;}
.d2-916646398 .stroke-N3{stroke:#9499AB;}
.d2-916646398 .stroke-N4{stroke:#CFD2DD;}
.d2-916646398 .stroke-N5{stroke:#DEE1EB;}
.d2-916646398 .stroke-N6{stroke:#EEF1F8;}
.d2-916646398 .stroke-N7{stroke:#FFFFFF;}
.d2-916646398 .stroke-B1{stroke:#0D32B2;}
.d2-916646398 .stroke-B2{stroke:#0D32B2;}
.d2-916646398 .stroke-B3{stroke:#E3E9FD;}
.d2-916646398 .stroke-B4{stroke:#E3E9FD;}
.d2-916646398 .stroke-B5{stroke:#EDF0FD;}
.d2-916646398 .stroke-B6{stroke:#F7F8FE;}
.d2-916646398 .stroke-AA2{stroke:#4A6FF3;}
.d2-916646398 .stroke-AA4{stroke:#EDF0FD;}
.d2-916646398 .stroke-AA5{stroke:#F7F8FE;}
.d2-916646398 .stroke-AB4{stroke:#EDF0FD;}
.d2-916646398 .stroke-AB5{stroke:#F7F8FE;}
.d2-916646398 .background-color-N1{background-color:#0A0F25;}
.d2-916646398 .background-color-N2{background-color:#676C7E;}
.d2-916646398 .background-color-N3{background-color:#9499AB;}
.d2-916646398 .background-color-N4{background-color:#CFD2DD;}
.d2-916646398 .background-color-N5{background-color:#DEE1EB;}
.d2-916646398 .background-color-N6{background-color:#EEF1F8;}
.d2-916646398 .background-color-N7{background-color:#FFFFFF;}
.d2-916646398 .background-color-B1{background-color:#0D32B2;}
.d2-916646398 .background-color-B2{background-color:#0D32B2;}
.d2-916646398 .background-color-B3{background-color:#E3E9FD;}
.d2-916646398 .background-color-B4{background-color:#E3E9FD;}
.d2-916646398 .background-color-B5{background-color:#EDF0FD;}
.d2-916646398 .background-color-B6{background-color:#F7F8FE;}
.d2-916646398 .background-color-AA2{background-color:#4A6FF3;}
.d2-916646398 .background-color-AA4{background-color:#EDF0FD;}
.d2-916646398 .background-color-AA5{background-color:#F7F8FE;}
.d2-916646398 .background-color-AB4{background-color:#EDF0FD;}
.d2-916646398 .background-color-AB5{background-color:#F7F8FE;}
.d2-916646398 .color-N1{color:#0A0F25;}
.d2-916646398 .color-N2{color:#676C7E;}
.d2-916646398 .color-N3{color:#9499AB;}
.d2-916646398 .color-N4{color:#CFD2DD;}
.d2-916646398 .color-N5{color:#DEE1EB;}
.d2-916646398 .color-N6{color:#EEF1F8;}
.d2-916646398 .color-N7{color:#FFFFFF;}
.d2-916646398 .color-B1{color:#0D32B2;}
.d2-916646398 .color-B2{color:#0D32B2;}
.d2-916646398 .color-B3{color:#E3E9FD;}
.d2-916646398 .color-B4{color:#E3E9FD;}
.d2-916646398 .color-B5{color:#EDF0FD;}
.d2-916646398 .color-B6{color:#F7F8FE;}
.d2-916646398 .color-AA2{color:#4A6FF3;}
.d2-916646398 .color-AA4{color:#EDF0FD;}
.d2-916646398 .color-AA5{color:#F7F8FE;}
.d2-916646398 .color-AB4{color:#EDF0FD;}
.d2-916646398 .color-AB5{color:#F7F8FE;}.appendix text.text{fill:#0A0F25}.md{--color-fg-default:#0A0F25;--color-fg-muted:#676C7E;--color-fg-subtle:#9499AB;--color-canvas-default:#FFFFFF;--color-canvas-subtle:#EEF1F8;--color-border-default:#0D32B2;--color-border-muted:#0D32B2;--color-neutral-muted:#EEF1F8;--color-accent-fg:#0D32B2;--color-accent-emphasis:#0D32B2;--color-attention-subtle:#676C7E;--color-danger-fg:red;}.sketch-overlay-B1{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-B2{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-B3{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-B4{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-B5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-B6{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AA2{fill:url(#streaks-dark);mix-blend-mode:overlay}.sketch-overlay-AA4{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AA5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AB4{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AB5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-N1{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-N2{fill:url(#streaks-dark);mix-blend-mode:overlay}.sketch-overlay-N3{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-N4{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-N5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-N6{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-N7{fill:url(#streaks-bright);mix-blend-mode:darken}.light-code{display: block}.dark-code{display: none}]]></style><a href="root.layers.x" xlink:href="root.layers.x"><g id="x"><g class="shape" ><rect x="0.000000" y="0.000000" width="85.000000" height="66.000000" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="42.500000" y="38.500000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">x</text></g></a><g transform="translate(69 -16)" class="appendix-icon"><circle cx="16" cy="16" r="16" fill="white" stroke="#DEE1EB" /><text class="text-bold" x="16" y="21" style="font-size: 16px;text-anchor:middle;">1</text></g><mask id="d2-916646398" maskUnits="userSpaceOnUse" x="-101" y="-118" width="304" height="285">
.d2-3057089836 .fill-N1{fill:#0A0F25;}
.d2-3057089836 .fill-N2{fill:#676C7E;}
.d2-3057089836 .fill-N3{fill:#9499AB;}
.d2-3057089836 .fill-N4{fill:#CFD2DD;}
.d2-3057089836 .fill-N5{fill:#DEE1EB;}
.d2-3057089836 .fill-N6{fill:#EEF1F8;}
.d2-3057089836 .fill-N7{fill:#FFFFFF;}
.d2-3057089836 .fill-B1{fill:#0D32B2;}
.d2-3057089836 .fill-B2{fill:#0D32B2;}
.d2-3057089836 .fill-B3{fill:#E3E9FD;}
.d2-3057089836 .fill-B4{fill:#E3E9FD;}
.d2-3057089836 .fill-B5{fill:#EDF0FD;}
.d2-3057089836 .fill-B6{fill:#F7F8FE;}
.d2-3057089836 .fill-AA2{fill:#4A6FF3;}
.d2-3057089836 .fill-AA4{fill:#EDF0FD;}
.d2-3057089836 .fill-AA5{fill:#F7F8FE;}
.d2-3057089836 .fill-AB4{fill:#EDF0FD;}
.d2-3057089836 .fill-AB5{fill:#F7F8FE;}
.d2-3057089836 .stroke-N1{stroke:#0A0F25;}
.d2-3057089836 .stroke-N2{stroke:#676C7E;}
.d2-3057089836 .stroke-N3{stroke:#9499AB;}
.d2-3057089836 .stroke-N4{stroke:#CFD2DD;}
.d2-3057089836 .stroke-N5{stroke:#DEE1EB;}
.d2-3057089836 .stroke-N6{stroke:#EEF1F8;}
.d2-3057089836 .stroke-N7{stroke:#FFFFFF;}
.d2-3057089836 .stroke-B1{stroke:#0D32B2;}
.d2-3057089836 .stroke-B2{stroke:#0D32B2;}
.d2-3057089836 .stroke-B3{stroke:#E3E9FD;}
.d2-3057089836 .stroke-B4{stroke:#E3E9FD;}
.d2-3057089836 .stroke-B5{stroke:#EDF0FD;}
.d2-3057089836 .stroke-B6{stroke:#F7F8FE;}
.d2-3057089836 .stroke-AA2{stroke:#4A6FF3;}
.d2-3057089836 .stroke-AA4{stroke:#EDF0FD;}
.d2-3057089836 .stroke-AA5{stroke:#F7F8FE;}
.d2-3057089836 .stroke-AB4{stroke:#EDF0FD;}
.d2-3057089836 .stroke-AB5{stroke:#F7F8FE;}
.d2-3057089836 .background-color-N1{background-color:#0A0F25;}
.d2-3057089836 .background-color-N2{background-color:#676C7E;}
.d2-3057089836 .background-color-N3{background-color:#9499AB;}
.d2-3057089836 .background-color-N4{background-color:#CFD2DD;}
.d2-3057089836 .background-color-N5{background-color:#DEE1EB;}
.d2-3057089836 .background-color-N6{background-color:#EEF1F8;}
.d2-3057089836 .background-color-N7{background-color:#FFFFFF;}
.d2-3057089836 .background-color-B1{background-color:#0D32B2;}
.d2-3057089836 .background-color-B2{background-color:#0D32B2;}
.d2-3057089836 .background-color-B3{background-color:#E3E9FD;}
.d2-3057089836 .background-color-B4{background-color:#E3E9FD;}
.d2-3057089836 .background-color-B5{background-color:#EDF0FD;}
.d2-3057089836 .background-color-B6{background-color:#F7F8FE;}
.d2-3057089836 .background-color-AA2{background-color:#4A6FF3;}
.d2-3057089836 .background-color-AA4{background-color:#EDF0FD;}
.d2-3057089836 .background-color-AA5{background-color:#F7F8FE;}
.d2-3057089836 .background-color-AB4{background-color:#EDF0FD;}
.d2-3057089836 .background-color-AB5{background-color:#F7F8FE;}
.d2-3057089836 .color-N1{color:#0A0F25;}
.d2-3057089836 .color-N2{color:#676C7E;}
.d2-3057089836 .color-N3{color:#9499AB;}
.d2-3057089836 .color-N4{color:#CFD2DD;}
.d2-3057089836 .color-N5{color:#DEE1EB;}
.d2-3057089836 .color-N6{color:#EEF1F8;}
.d2-3057089836 .color-N7{color:#FFFFFF;}
.d2-3057089836 .color-B1{color:#0D32B2;}
.d2-3057089836 .color-B2{color:#0D32B2;}
.d2-3057089836 .color-B3{color:#E3E9FD;}
.d2-3057089836 .color-B4{color:#E3E9FD;}
.d2-3057089836 .color-B5{color:#EDF0FD;}
.d2-3057089836 .color-B6{color:#F7F8FE;}
.d2-3057089836 .color-AA2{color:#4A6FF3;}
.d2-3057089836 .color-AA4{color:#EDF0FD;}
.d2-3057089836 .color-AA5{color:#F7F8FE;}
.d2-3057089836 .color-AB4{color:#EDF0FD;}
.d2-3057089836 .color-AB5{color:#F7F8FE;}.appendix text.text{fill:#0A0F25}.md{--color-fg-default:#0A0F25;--color-fg-muted:#676C7E;--color-fg-subtle:#9499AB;--color-canvas-default:#FFFFFF;--color-canvas-subtle:#EEF1F8;--color-border-default:#0D32B2;--color-border-muted:#0D32B2;--color-neutral-muted:#EEF1F8;--color-accent-fg:#0D32B2;--color-accent-emphasis:#0D32B2;--color-attention-subtle:#676C7E;--color-danger-fg:red;}.sketch-overlay-B1{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-B2{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-B3{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-B4{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-B5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-B6{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AA2{fill:url(#streaks-dark);mix-blend-mode:overlay}.sketch-overlay-AA4{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AA5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AB4{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AB5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-N1{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-N2{fill:url(#streaks-dark);mix-blend-mode:overlay}.sketch-overlay-N3{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-N4{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-N5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-N6{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-N7{fill:url(#streaks-bright);mix-blend-mode:darken}.light-code{display: block}.dark-code{display: none}]]></style><a href="root.layers.x" xlink:href="root.layers.x"><g id="x"><g class="shape" ><rect x="0.000000" y="0.000000" width="85.000000" height="66.000000" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="42.500000" y="38.500000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">x</text></g></a><g transform="translate(69 -16)" class="appendix-icon"><circle cx="16" cy="16" r="16" fill="white" stroke="#DEE1EB" /><text class="text-bold" x="16" y="21" style="font-size: 16px;text-anchor:middle;">1</text></g><mask id="d2-3057089836" maskUnits="userSpaceOnUse" x="-101" y="-118" width="304" height="285">
<rect x="-101" y="-118" width="304" height="285" fill="white"></rect>
<rect x="38.500000" y="22.500000" width="8" height="21" fill="rgba(0,0,0,0.75)"></rect>
</mask><line x1="-41.000000" x2="143.000000" y1="117.000000" y2="117.000000" class=" stroke-B2" /><g class="appendix" x="-1" y="67" width="104" height="100%"><g transform="translate(0 167)" class="appendix-icon"><circle cx="16" cy="0" r="16" fill="white" stroke="#DEE1EB" /><text class="text-bold" x="16" y="5" style="font-size: 16px;text-anchor:middle;">1</text></g><text class="text" x="48" y="172" style="font-size: 16px;">root &gt; x</text></g>

Before

Width:  |  Height:  |  Size: 657 KiB

After

Width:  |  Height:  |  Size: 657 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 662 KiB

After

Width:  |  Height:  |  Size: 662 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 662 KiB

After

Width:  |  Height:  |  Size: 662 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 661 KiB

After

Width:  |  Height:  |  Size: 661 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 661 KiB

After

Width:  |  Height:  |  Size: 661 KiB

View file

@ -69,10 +69,10 @@ var grain string
var paper string
type RenderOpts struct {
Pad int
Sketch bool
Center bool
ThemeID int64
Pad *int64
Sketch *bool
Center *bool
ThemeID *int64
DarkThemeID *int64
Font string
// the svg will be scaled by this factor, if unset the svg will fit to screen
@ -1201,9 +1201,11 @@ func drawShape(writer, appendixWriter io.Writer, diagramHash string, targetShape
offsetY /= 2
}
box.TopLeft.Y -= float64(offsetY)
box.Height += float64(offsetY)
box.Width += d2target.THREE_DEE_OFFSET
} else if targetShape.Multiple {
box.TopLeft.Y -= d2target.MULTIPLE_OFFSET
box.Height += d2target.MULTIPLE_OFFSET
box.Width += d2target.MULTIPLE_OFFSET
}
} else {
@ -1218,13 +1220,12 @@ func drawShape(writer, appendixWriter io.Writer, diagramHash string, targetShape
fontClass := "text"
if targetShape.FontFamily == "mono" {
fontClass = "text-mono"
} else {
}
if targetShape.Bold {
fontClass += "-bold"
} else if targetShape.Italic {
fontClass += "-italic"
}
}
if targetShape.Underline {
fontClass += " text-underline"
}
@ -1340,7 +1341,11 @@ func drawShape(writer, appendixWriter io.Writer, diagramHash string, targetShape
}
}
}
if targetShape.Tooltip != "" {
fmt.Fprintf(writer, `<title>%s</title>`,
svg.EscapeText(targetShape.Tooltip),
)
}
addAppendixItems(appendixWriter, targetShape, s)
fmt.Fprint(writer, closingTag)
@ -1389,12 +1394,12 @@ func addAppendixItems(writer io.Writer, targetShape d2target.Shape, s shape.Shap
x := int(math.Ceil(p1.X))
y := int(math.Ceil(p1.Y))
fmt.Fprintf(writer, `<g transform="translate(%d %d)" class="appendix-icon">%s</g>`,
fmt.Fprintf(writer, `<g transform="translate(%d %d)" class="appendix-icon"><title>%s</title>%s</g>`,
x-appendixIconRadius,
y-appendixIconRadius,
svg.EscapeText(targetShape.Tooltip),
TooltipIcon,
)
fmt.Fprintf(writer, `<title>%s</title>`, svg.EscapeText(targetShape.Tooltip))
}
if targetShape.Link != "" {
if p2 == nil {
@ -1687,26 +1692,28 @@ func appendOnTrigger(buf *bytes.Buffer, source string, triggers []string, newCon
}
}
const DEFAULT_THEME int64 = 0
var DEFAULT_DARK_THEME *int64 = nil // no theme selected
func Render(diagram *d2target.Diagram, opts *RenderOpts) ([]byte, error) {
var sketchRunner *d2sketch.Runner
pad := DEFAULT_PADDING
themeID := DEFAULT_THEME
themeID := d2themescatalog.NeutralDefault.ID
darkThemeID := DEFAULT_DARK_THEME
var scale *float64
if opts != nil {
pad = opts.Pad
if opts.Sketch {
if opts.Pad != nil {
pad = int(*opts.Pad)
}
if opts.Sketch != nil && *opts.Sketch {
var err error
sketchRunner, err = d2sketch.InitSketchVM()
if err != nil {
return nil, err
}
}
themeID = opts.ThemeID
if opts.ThemeID != nil {
themeID = *opts.ThemeID
}
darkThemeID = opts.DarkThemeID
scale = opts.Scale
}
@ -1790,7 +1797,7 @@ func Render(diagram *d2target.Diagram, opts *RenderOpts) ([]byte, error) {
upperBuf := &bytes.Buffer{}
if opts.MasterID == "" {
EmbedFonts(upperBuf, diagramHash, buf.String(), diagram.FontFamily, diagram.GetCorpus()) // EmbedFonts *must* run before `d2sketch.DefineFillPatterns`, but after all elements are appended to `buf`
themeStylesheet, err := ThemeCSS(diagramHash, themeID, darkThemeID)
themeStylesheet, err := ThemeCSS(diagramHash, &themeID, darkThemeID)
if err != nil {
return nil, err
}
@ -1911,7 +1918,7 @@ func Render(diagram *d2target.Diagram, opts *RenderOpts) ([]byte, error) {
}
alignment := "xMinYMin"
if opts.Center {
if opts.Center != nil && *opts.Center {
alignment = "xMidYMid"
}
fitToScreenWrapperOpening := ""
@ -1952,8 +1959,11 @@ func Render(diagram *d2target.Diagram, opts *RenderOpts) ([]byte, error) {
}
// TODO include only colors that are being used to reduce size
func ThemeCSS(diagramHash string, themeID int64, darkThemeID *int64) (stylesheet string, err error) {
out, err := singleThemeRulesets(diagramHash, themeID)
func ThemeCSS(diagramHash string, themeID *int64, darkThemeID *int64) (stylesheet string, err error) {
if themeID == nil {
themeID = &d2themescatalog.NeutralDefault.ID
}
out, err := singleThemeRulesets(diagramHash, *themeID)
if err != nil {
return "", err
}

View file

@ -17,6 +17,7 @@ import (
"oss.terrastruct.com/util-go/diff"
"oss.terrastruct.com/util-go/go2"
"oss.terrastruct.com/d2/d2graph"
"oss.terrastruct.com/d2/d2layouts/d2dagrelayout"
"oss.terrastruct.com/d2/d2lib"
"oss.terrastruct.com/d2/d2renderers/d2fonts"
@ -426,12 +427,18 @@ func run(t *testing.T, tc testCase) {
return
}
renderOpts := &d2svg.RenderOpts{
ThemeID: go2.Pointer(int64(200)),
}
layoutResolver := func(engine string) (d2graph.LayoutGraph, error) {
return d2dagrelayout.DefaultLayout, nil
}
diagram, _, err := d2lib.Compile(ctx, tc.script, &d2lib.CompileOptions{
Ruler: ruler,
Layout: d2dagrelayout.DefaultLayout,
LayoutResolver: layoutResolver,
FontFamily: go2.Pointer(d2fonts.HandDrawn),
ThemeID: 200,
})
}, renderOpts)
if !tassert.Nil(t, err) {
return
}
@ -439,10 +446,7 @@ func run(t *testing.T, tc testCase) {
dataPath := filepath.Join("testdata", strings.TrimPrefix(t.Name(), "TestDarkTheme/"))
pathGotSVG := filepath.Join(dataPath, "dark_theme.got.svg")
svgBytes, err := d2svg.Render(diagram, &d2svg.RenderOpts{
Pad: d2svg.DEFAULT_PADDING,
ThemeID: 200,
})
svgBytes, err := d2svg.Render(diagram, renderOpts)
assert.Success(t, err)
err = os.MkdirAll(dataPath, 0755)
assert.Success(t, err)

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 22 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 66 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 20 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 69 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 117 KiB

After

Width:  |  Height:  |  Size: 117 KiB

View file

@ -6,6 +6,7 @@ import (
"hash/fnv"
"math"
"net/url"
"os"
"strings"
"oss.terrastruct.com/util-go/go2"
@ -38,8 +39,24 @@ const (
var BorderOffset = geo.NewVector(5, 5)
type Config struct {
Sketch *bool `json:"sketch"`
ThemeID *int64 `json:"themeID"`
DarkThemeID *int64 `json:"darkThemeID"`
Pad *int64 `json:"pad"`
Center *bool `json:"center"`
LayoutEngine *string `json:"layoutEngine"`
ThemeOverrides *ThemeOverrides `json:"themeOverrides"`
}
type ThemeOverrides struct {
N1 *string `json:"n1"`
// TODO
}
type Diagram struct {
Name string `json:"name"`
Config *Config `json:"config,omitempty"`
// See docs on the same field in d2graph to understand what it means.
IsFolderOnly bool `json:"isFolderOnly"`
Description string `json:"description,omitempty"`
@ -56,6 +73,76 @@ type Diagram struct {
Steps []*Diagram `json:"steps,omitempty"`
}
// boardPath comes in the form of "x/layers/z/scenarios/a"
// or in the form of "layers/z/scenarios/a"
func (d *Diagram) GetBoard(boardPath string) *Diagram {
path := strings.Split(boardPath, string(os.PathSeparator))
if len(path) == 0 || len(boardPath) == 0 {
return d
}
return d.getBoard(path)
}
func (d *Diagram) getBoard(boardPath []string) *Diagram {
if len(boardPath) == 0 {
return d
}
head := boardPath[0]
if head == "index" {
return d
}
switch head {
case "layers":
if len(boardPath) < 2 {
return nil
}
for _, b := range d.Layers {
if b.Name == boardPath[1] {
return b.getBoard(boardPath[2:])
}
}
case "scenarios":
if len(boardPath) < 2 {
return nil
}
for _, b := range d.Scenarios {
if b.Name == boardPath[1] {
return b.getBoard(boardPath[2:])
}
}
case "steps":
if len(boardPath) < 2 {
return nil
}
for _, b := range d.Steps {
if b.Name == boardPath[1] {
return b.getBoard(boardPath[2:])
}
}
}
for _, b := range d.Layers {
if b.Name == head {
return b.getBoard(boardPath[2:])
}
}
for _, b := range d.Scenarios {
if b.Name == head {
return b.getBoard(boardPath[2:])
}
}
for _, b := range d.Steps {
if b.Name == head {
return b.getBoard(boardPath[2:])
}
}
return nil
}
func (diagram Diagram) Bytes() ([]byte, error) {
b1, err := json.Marshal(diagram.Shapes)
if err != nil {
@ -65,7 +152,19 @@ func (diagram Diagram) Bytes() ([]byte, error) {
if err != nil {
return nil, err
}
base := append(b1, b2...)
b3, err := json.Marshal(diagram.Root)
if err != nil {
return nil, err
}
base := append(append(b1, b2...), b3...)
if diagram.Config != nil {
b, err := json.Marshal(diagram.Config)
if err != nil {
return nil, err
}
base = append(base, b...)
}
for _, d := range diagram.Layers {
slices, err := d.Bytes()
@ -199,11 +298,11 @@ func (diagram Diagram) BoundingBox() (topLeft, bottomRight Point) {
if strings.HasPrefix(targetShape.IconPosition, "OUTSIDE_TOP") {
y1 = go2.Min(y1, targetShape.Pos.Y-label.PADDING-size)
} else if strings.HasPrefix(targetShape.IconPosition, "OUTSIDE_BOTTOM") {
y2 = go2.Max(y2, targetShape.Pos.Y+label.PADDING+size)
y2 = go2.Max(y2, targetShape.Pos.Y+targetShape.Height+label.PADDING+size)
} else if strings.HasPrefix(targetShape.IconPosition, "OUTSIDE_LEFT") {
x1 = go2.Min(x1, targetShape.Pos.X-label.PADDING-size)
} else if strings.HasPrefix(targetShape.IconPosition, "OUTSIDE_RIGHT") {
x2 = go2.Max(x2, targetShape.Pos.X+label.PADDING+size)
x2 = go2.Max(x2, targetShape.Pos.X+targetShape.Width+label.PADDING+size)
}
}

View file

@ -11,21 +11,24 @@ import (
"oss.terrastruct.com/d2/d2renderers/d2svg"
"oss.terrastruct.com/d2/d2themes/d2themescatalog"
"oss.terrastruct.com/d2/lib/textmeasure"
"oss.terrastruct.com/util-go/go2"
)
// Remember to add if err != nil checks in production.
func main() {
ruler, _ := textmeasure.NewRuler()
defaultLayout := func(ctx context.Context, g *d2graph.Graph) error {
return d2dagrelayout.Layout(ctx, g, nil)
layoutResolver := func(engine string) (d2graph.LayoutGraph, error) {
return d2dagrelayout.DefaultLayout, nil
}
diagram, _, _ := d2lib.Compile(context.Background(), "x -> y", &d2lib.CompileOptions{
Layout: defaultLayout,
renderOpts := &d2svg.RenderOpts{
Pad: go2.Pointer(int64(5)),
ThemeID: &d2themescatalog.GrapeSoda.ID,
}
compileOpts := &d2lib.CompileOptions{
LayoutResolver: layoutResolver,
Ruler: ruler,
})
out, _ := d2svg.Render(diagram, &d2svg.RenderOpts{
Pad: d2svg.DEFAULT_PADDING,
ThemeID: d2themescatalog.GrapeSoda.ID,
})
}
diagram, _, _ := d2lib.Compile(context.Background(), "x -> y", compileOpts, renderOpts)
out, _ := d2svg.Render(diagram, renderOpts)
_ = ioutil.WriteFile(filepath.Join("out.svg"), out, 0600)
}

View file

@ -5,6 +5,7 @@ import (
"fmt"
"oss.terrastruct.com/d2/d2format"
"oss.terrastruct.com/d2/d2graph"
"oss.terrastruct.com/d2/d2layouts/d2dagrelayout"
"oss.terrastruct.com/d2/d2lib"
"oss.terrastruct.com/d2/d2oracle"
@ -15,10 +16,14 @@ import (
func main() {
// From one.go
ruler, _ := textmeasure.NewRuler()
_, graph, _ := d2lib.Compile(context.Background(), "x -> y", &d2lib.CompileOptions{
Layout: d2dagrelayout.DefaultLayout,
layoutResolver := func(engine string) (d2graph.LayoutGraph, error) {
return d2dagrelayout.DefaultLayout, nil
}
compileOpts := &d2lib.CompileOptions{
LayoutResolver: layoutResolver,
Ruler: ruler,
})
}
_, graph, _ := d2lib.Compile(context.Background(), "x -> y", compileOpts, nil)
// Create a shape with the ID, "meow"
graph, _, _ = d2oracle.Create(graph, nil, "meow")

View file

@ -16,15 +16,15 @@ import (
// Remember to add if err != nil checks in production.
func main() {
graph, _ := d2compiler.Compile("", strings.NewReader("x -> y"), nil)
graph, config, _ := d2compiler.Compile("", strings.NewReader("x -> y"), nil)
graph.ApplyTheme(d2themescatalog.NeutralDefault.ID)
ruler, _ := textmeasure.NewRuler()
_ = graph.SetDimensions(nil, ruler, nil)
_ = d2dagrelayout.Layout(context.Background(), graph, nil)
diagram, _ := d2exporter.Export(context.Background(), graph, nil)
diagram.Config = config
out, _ := d2svg.Render(diagram, &d2svg.RenderOpts{
Pad: d2svg.DEFAULT_PADDING,
ThemeID: d2themescatalog.NeutralDefault.ID,
ThemeID: &d2themescatalog.NeutralDefault.ID,
})
_ = ioutil.WriteFile(filepath.Join("out.svg"), out, 0600)
}

View file

@ -34,6 +34,7 @@ layers: {
link: steps.1
style.font-size: 24
}
steps: {
1: {
titled: Earn pre-requisite titles (IM)

View file

@ -74,8 +74,17 @@ func TestCLI_E2E(t *testing.T) {
writeFile(t, dir, "empty-layer.d2", `layers: { x: {} }`)
err := runTestMain(t, ctx, dir, env, "empty-layer.d2")
assert.Success(t, err)
},
},
{
name: "layer-link",
run: func(t *testing.T, ctx context.Context, dir string, env *xos.Env) {
writeFile(t, dir, "test.d2", `doh: { link: layers.test2 }; layers: { test2: @test2.d2 }`)
writeFile(t, dir, "test2.d2", `x: I'm a Mac { link: https://example.com }`)
err := runTestMain(t, ctx, dir, env, "test.d2", "layer-link.svg")
assert.Success(t, err)
assert.TestdataDir(t, filepath.Join(dir, "empty-layer"))
assert.TestdataDir(t, filepath.Join(dir, "layer-link"))
},
},
{
@ -111,6 +120,38 @@ func TestCLI_E2E(t *testing.T) {
shape: text
}
steps: {
1: {
Approach road
}
2: {
Approach road -> Cross road
}
3: {
Cross road -> Make you wonder why
}
}
`)
err := runTestMain(t, ctx, dir, env, "--animate-interval=1400", "animation.d2")
assert.Success(t, err)
svg := readFile(t, dir, "animation.svg")
assert.Testdata(t, ".svg", svg)
},
},
{
name: "vars-animation",
run: func(t *testing.T, ctx context.Context, dir string, env *xos.Env) {
writeFile(t, dir, "animation.d2", `vars: {
d2-config: {
theme-id: 300
}
}
Chicken's plan: {
style.font-size: 35
near: top-center
shape: text
}
steps: {
1: {
Approach road
@ -404,6 +445,17 @@ steps: {
assert.Testdata(t, ".svg", svg)
},
},
{
name: "import_vars",
run: func(t *testing.T, ctx context.Context, dir string, env *xos.Env) {
writeFile(t, dir, "hello-world.d2", `vars: { d2-config: @config }; x -> y`)
writeFile(t, dir, "config.d2", `theme-id: 200`)
err := runTestMain(t, ctx, dir, env, filepath.Join(dir, "hello-world.d2"))
assert.Success(t, err)
svg := readFile(t, dir, "hello-world.svg")
assert.Testdata(t, ".svg", svg)
},
},
{
name: "import_spread_nested",
run: func(t *testing.T, ctx context.Context, dir string, env *xos.Env) {
@ -449,6 +501,26 @@ steps: {
})
},
},
{
name: "vars-config",
run: func(t *testing.T, ctx context.Context, dir string, env *xos.Env) {
writeFile(t, dir, "hello-world.d2", `vars: {
d2-config: {
sketch: true
layout-engine: elk
}
}
x -> y -> a.dream
it -> was -> all -> a.dream
i used to read
`)
env.Setenv("D2_THEME", "1")
err := runTestMain(t, ctx, dir, env, "--pad=10", "hello-world.d2")
assert.Success(t, err)
svg := readFile(t, dir, "hello-world.svg")
assert.Testdata(t, ".svg", svg)
},
},
}
ctx := context.Background()

View file

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.5.1-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 256 434"><svg id="d2-svg" class="d2-855222762" width="256" height="434" viewBox="-101 -101 256 434"><rect x="-101.000000" y="-101.000000" width="256.000000" height="434.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
.d2-855222762 .text-bold {
font-family: "d2-855222762-font-bold";
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.6.0-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 256 434"><svg id="d2-svg" class="d2-1843626214" width="256" height="434" viewBox="-101 -101 256 434"><rect x="-101.000000" y="-101.000000" width="256.000000" height="434.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
.d2-1843626214 .text-bold {
font-family: "d2-1843626214-font-bold";
}
@font-face {
font-family: d2-855222762-font-bold;
font-family: d2-1843626214-font-bold;
src: url("data:application/font-woff;base64,d09GRgABAAAAAAZwAAoAAAAACywAAguFAAAAAAAAAAAAAAAAAAAAAAAAAABPUy8yAAAA9AAAAGAAAABgXxHXrmNtYXAAAAFUAAAAMgAAADIADQC0Z2x5ZgAAAYgAAAEQAAABEBXyvOFoZWFkAAACmAAAADYAAAA2G38e1GhoZWEAAALQAAAAJAAAACQKfwXCaG10eAAAAvQAAAAMAAAADAa9AGpsb2NhAAADAAAAAAgAAAAIAFgAtG1heHAAAAMIAAAAIAAAACAAGwD3bmFtZQAAAygAAAMoAAAIKgjwVkFwb3N0AAAGUAAAAB0AAAAg/9EAMgADAioCvAAFAAACigJYAAAASwKKAlgAAAFeADIBKQAAAgsHAwMEAwICBGAAAvcAAAADAAAAAAAAAABBREJPACAAIP//Au7/BgAAA9gBESAAAZ8AAAAAAfAClAAAACAAAwAAAAEAAwABAAAADAAEACYAAAAEAAQAAQAAAHn//wAAAHj///+JAAEAAAAAAAEAAgAAAAAABQBQAAACYgKUAAMACQAPABIAFQAAMxEhESUzJycjBzczNzcjFwM3JwERB1ACEv6lpCcpBCkpBCogmB96X18BTV4ClP1sW01iYvZfOzv+nrm6/o0Bc7oAAAEADgAAAfQB8AAZAAAzEyczFxYWFzM2Njc3MwcXIycmJicjBgYHBw6Yj54sChYKBAgSCCKYkJmeMAwXDAQJFAknAQLuUBUrFRUrFVD/8VIVLBUVKxZSAAABAAz/PgH9AfAAGwAAFyImJzcWFjMyNjc3AzMXFhYXMzY2NzczAw4CeBYhDxoHEgglKAoHv5RHCxIKBAgRCTyNrBc4T8IGBHABBSQdGgHj1SJGJSNHI9X+Cz5VKgAAAAABAAAAAguFT5ZgD18PPPUAAQPoAAAAANhdoIQAAAAA3WYvNv43/sQIbQPxAAEAAwACAAAAAAAAAAEAAAPY/u8AAAiY/jf+NwhtAAEAAAAAAAAAAAAAAAAAAAADArIAUAICAA4CCQAMAAAALABYAIgAAQAAAAMAkAAMAGMABwABAAAAAAAAAAAAAAAAAAQAA3icnJTPbhtVFMZ/TmzTCsECRVW6ie6CRZHo2FRJ1TYrh9SKRRQHjwtCQkgTz/iPMp4ZeSYO4QlY8xa8RVc8BM+BWKP5fOzYBdEmipJ8d+75851zvnOBHf5mm0r1IfBHPTFcYa9+bniLB/UTw9u061uGqzyp/Wm4RlibG67zea1n+CPeVn8z/ID96k+GH7JbbRv+mGfVHcOfbDv+Mvwp+7xd4Aq84FfDFXbJDG+xw4+Gt3mExaxUeUTTcI3P2DNcZw/oM6EgZkLCCMeQCSOumBGR4xMxY8KQiBBHhxYxhb4mBEKO0X9+DfApmBEo4pgCR4xPTEDO2CL+Iq+Uc2Uc6jSzuxYFYwIu5HFJQIIjZURKQsSl4hQUZLyiQYOcgfhmFOR45EyI8UiZMaJBlzan9BkzIcfRVqSSmU/KkIJrAuV3ZlF2ZkBEQm6srkgIxdOJXyTvDqc4umSyXY98uhHhSxzfybvklsr2Kzz9ujVmm3mXbALm6mesrsS6udYEx7ot87b4VrjgFe5e/dlk8v4ehfpfKPIFV5p/qEklYpLg3C4tfCnId49xHOncwVdHvqdDnxO6vKGvc4sePVqc0afDa/l26eH4mi5nHMujI7y4a0sxZ/yA4xs6siljR9afxcQifiYzdefiOFMdUzL1vGTuqdZIFd59wuUOpRvqyOUz0B6Vlk7zS7RnASNTRSaGU/VyqY3c+heaIqaqpZzt7X25DXPbveUW35Bqh0u1LjiVk1swet9UvXc0c60fj4CQlAtZDEiZ0qDgRrzPCbgixnGs7p1oSwpaK58yz41UEjEVgw6J4szI9Dcw3fjGfbChe2dvSSj/kunlqqr7ZHHq1e2M3qh7yzvfuhytTaBhU03X1DQQ18S0H2mn1vn78s31uqU85YiUmPBfL8AzPJrsc8AhY2UY6GZur0NTL0STlxyq+ksiWQ2l58giHODxnAMOeMnzd/q4ZOKMi1txWc/d4pgjuhx+UBUL+y5HvF59+/+sv4tpU7U4nq5OL+49xSd3UOsX2rPb97KniZWTmFu02604I2BacnG76zW5x3j/AAAA//8BAAD///S3T1F4nGJgZgCD/+cYjBiwAAAAAAD//wEAAP//LwECAwAAAA==");
}]]></style><style type="text/css"><![CDATA[.shape {
shape-rendering: geometricPrecision;
@ -18,78 +18,78 @@
opacity: 0.5;
}
.d2-855222762 .fill-N1{fill:#0A0F25;}
.d2-855222762 .fill-N2{fill:#676C7E;}
.d2-855222762 .fill-N3{fill:#9499AB;}
.d2-855222762 .fill-N4{fill:#CFD2DD;}
.d2-855222762 .fill-N5{fill:#DEE1EB;}
.d2-855222762 .fill-N6{fill:#EEF1F8;}
.d2-855222762 .fill-N7{fill:#FFFFFF;}
.d2-855222762 .fill-B1{fill:#0D32B2;}
.d2-855222762 .fill-B2{fill:#0D32B2;}
.d2-855222762 .fill-B3{fill:#E3E9FD;}
.d2-855222762 .fill-B4{fill:#E3E9FD;}
.d2-855222762 .fill-B5{fill:#EDF0FD;}
.d2-855222762 .fill-B6{fill:#F7F8FE;}
.d2-855222762 .fill-AA2{fill:#4A6FF3;}
.d2-855222762 .fill-AA4{fill:#EDF0FD;}
.d2-855222762 .fill-AA5{fill:#F7F8FE;}
.d2-855222762 .fill-AB4{fill:#EDF0FD;}
.d2-855222762 .fill-AB5{fill:#F7F8FE;}
.d2-855222762 .stroke-N1{stroke:#0A0F25;}
.d2-855222762 .stroke-N2{stroke:#676C7E;}
.d2-855222762 .stroke-N3{stroke:#9499AB;}
.d2-855222762 .stroke-N4{stroke:#CFD2DD;}
.d2-855222762 .stroke-N5{stroke:#DEE1EB;}
.d2-855222762 .stroke-N6{stroke:#EEF1F8;}
.d2-855222762 .stroke-N7{stroke:#FFFFFF;}
.d2-855222762 .stroke-B1{stroke:#0D32B2;}
.d2-855222762 .stroke-B2{stroke:#0D32B2;}
.d2-855222762 .stroke-B3{stroke:#E3E9FD;}
.d2-855222762 .stroke-B4{stroke:#E3E9FD;}
.d2-855222762 .stroke-B5{stroke:#EDF0FD;}
.d2-855222762 .stroke-B6{stroke:#F7F8FE;}
.d2-855222762 .stroke-AA2{stroke:#4A6FF3;}
.d2-855222762 .stroke-AA4{stroke:#EDF0FD;}
.d2-855222762 .stroke-AA5{stroke:#F7F8FE;}
.d2-855222762 .stroke-AB4{stroke:#EDF0FD;}
.d2-855222762 .stroke-AB5{stroke:#F7F8FE;}
.d2-855222762 .background-color-N1{background-color:#0A0F25;}
.d2-855222762 .background-color-N2{background-color:#676C7E;}
.d2-855222762 .background-color-N3{background-color:#9499AB;}
.d2-855222762 .background-color-N4{background-color:#CFD2DD;}
.d2-855222762 .background-color-N5{background-color:#DEE1EB;}
.d2-855222762 .background-color-N6{background-color:#EEF1F8;}
.d2-855222762 .background-color-N7{background-color:#FFFFFF;}
.d2-855222762 .background-color-B1{background-color:#0D32B2;}
.d2-855222762 .background-color-B2{background-color:#0D32B2;}
.d2-855222762 .background-color-B3{background-color:#E3E9FD;}
.d2-855222762 .background-color-B4{background-color:#E3E9FD;}
.d2-855222762 .background-color-B5{background-color:#EDF0FD;}
.d2-855222762 .background-color-B6{background-color:#F7F8FE;}
.d2-855222762 .background-color-AA2{background-color:#4A6FF3;}
.d2-855222762 .background-color-AA4{background-color:#EDF0FD;}
.d2-855222762 .background-color-AA5{background-color:#F7F8FE;}
.d2-855222762 .background-color-AB4{background-color:#EDF0FD;}
.d2-855222762 .background-color-AB5{background-color:#F7F8FE;}
.d2-855222762 .color-N1{color:#0A0F25;}
.d2-855222762 .color-N2{color:#676C7E;}
.d2-855222762 .color-N3{color:#9499AB;}
.d2-855222762 .color-N4{color:#CFD2DD;}
.d2-855222762 .color-N5{color:#DEE1EB;}
.d2-855222762 .color-N6{color:#EEF1F8;}
.d2-855222762 .color-N7{color:#FFFFFF;}
.d2-855222762 .color-B1{color:#0D32B2;}
.d2-855222762 .color-B2{color:#0D32B2;}
.d2-855222762 .color-B3{color:#E3E9FD;}
.d2-855222762 .color-B4{color:#E3E9FD;}
.d2-855222762 .color-B5{color:#EDF0FD;}
.d2-855222762 .color-B6{color:#F7F8FE;}
.d2-855222762 .color-AA2{color:#4A6FF3;}
.d2-855222762 .color-AA4{color:#EDF0FD;}
.d2-855222762 .color-AA5{color:#F7F8FE;}
.d2-855222762 .color-AB4{color:#EDF0FD;}
.d2-855222762 .color-AB5{color:#F7F8FE;}.appendix text.text{fill:#0A0F25}.md{--color-fg-default:#0A0F25;--color-fg-muted:#676C7E;--color-fg-subtle:#9499AB;--color-canvas-default:#FFFFFF;--color-canvas-subtle:#EEF1F8;--color-border-default:#0D32B2;--color-border-muted:#0D32B2;--color-neutral-muted:#EEF1F8;--color-accent-fg:#0D32B2;--color-accent-emphasis:#0D32B2;--color-attention-subtle:#676C7E;--color-danger-fg:red;}.sketch-overlay-B1{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-B2{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-B3{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-B4{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-B5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-B6{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AA2{fill:url(#streaks-dark);mix-blend-mode:overlay}.sketch-overlay-AA4{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AA5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AB4{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AB5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-N1{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-N2{fill:url(#streaks-dark);mix-blend-mode:overlay}.sketch-overlay-N3{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-N4{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-N5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-N6{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-N7{fill:url(#streaks-bright);mix-blend-mode:darken}.light-code{display: block}.dark-code{display: none}]]></style><g id="x"><g class="shape" ><rect x="1.000000" y="0.000000" width="53.000000" height="66.000000" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="27.500000" y="38.500000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">x</text></g><g id="y"><g class="shape" ><rect x="0.000000" y="166.000000" width="54.000000" height="66.000000" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="27.000000" y="204.500000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">y</text></g><g id="(x -&gt; y)[0]"><marker id="mk-3488378134" markerWidth="10.000000" markerHeight="12.000000" refX="7.000000" refY="6.000000" viewBox="0.000000 0.000000 10.000000 12.000000" orient="auto" markerUnits="userSpaceOnUse"> <polygon points="0.000000,0.000000 10.000000,6.000000 0.000000,12.000000" class="connection fill-B1" stroke-width="2" /> </marker><path d="M 27.000000 68.000000 C 27.000000 106.000000 27.000000 126.000000 27.000000 162.000000" fill="none" class="connection stroke-B1" style="stroke-width:2;" marker-end="url(#mk-3488378134)" mask="url(#d2-855222762)" /></g><mask id="d2-855222762" maskUnits="userSpaceOnUse" x="-101" y="-101" width="256" height="434">
.d2-1843626214 .fill-N1{fill:#0A0F25;}
.d2-1843626214 .fill-N2{fill:#676C7E;}
.d2-1843626214 .fill-N3{fill:#9499AB;}
.d2-1843626214 .fill-N4{fill:#CFD2DD;}
.d2-1843626214 .fill-N5{fill:#DEE1EB;}
.d2-1843626214 .fill-N6{fill:#EEF1F8;}
.d2-1843626214 .fill-N7{fill:#FFFFFF;}
.d2-1843626214 .fill-B1{fill:#0D32B2;}
.d2-1843626214 .fill-B2{fill:#0D32B2;}
.d2-1843626214 .fill-B3{fill:#E3E9FD;}
.d2-1843626214 .fill-B4{fill:#E3E9FD;}
.d2-1843626214 .fill-B5{fill:#EDF0FD;}
.d2-1843626214 .fill-B6{fill:#F7F8FE;}
.d2-1843626214 .fill-AA2{fill:#4A6FF3;}
.d2-1843626214 .fill-AA4{fill:#EDF0FD;}
.d2-1843626214 .fill-AA5{fill:#F7F8FE;}
.d2-1843626214 .fill-AB4{fill:#EDF0FD;}
.d2-1843626214 .fill-AB5{fill:#F7F8FE;}
.d2-1843626214 .stroke-N1{stroke:#0A0F25;}
.d2-1843626214 .stroke-N2{stroke:#676C7E;}
.d2-1843626214 .stroke-N3{stroke:#9499AB;}
.d2-1843626214 .stroke-N4{stroke:#CFD2DD;}
.d2-1843626214 .stroke-N5{stroke:#DEE1EB;}
.d2-1843626214 .stroke-N6{stroke:#EEF1F8;}
.d2-1843626214 .stroke-N7{stroke:#FFFFFF;}
.d2-1843626214 .stroke-B1{stroke:#0D32B2;}
.d2-1843626214 .stroke-B2{stroke:#0D32B2;}
.d2-1843626214 .stroke-B3{stroke:#E3E9FD;}
.d2-1843626214 .stroke-B4{stroke:#E3E9FD;}
.d2-1843626214 .stroke-B5{stroke:#EDF0FD;}
.d2-1843626214 .stroke-B6{stroke:#F7F8FE;}
.d2-1843626214 .stroke-AA2{stroke:#4A6FF3;}
.d2-1843626214 .stroke-AA4{stroke:#EDF0FD;}
.d2-1843626214 .stroke-AA5{stroke:#F7F8FE;}
.d2-1843626214 .stroke-AB4{stroke:#EDF0FD;}
.d2-1843626214 .stroke-AB5{stroke:#F7F8FE;}
.d2-1843626214 .background-color-N1{background-color:#0A0F25;}
.d2-1843626214 .background-color-N2{background-color:#676C7E;}
.d2-1843626214 .background-color-N3{background-color:#9499AB;}
.d2-1843626214 .background-color-N4{background-color:#CFD2DD;}
.d2-1843626214 .background-color-N5{background-color:#DEE1EB;}
.d2-1843626214 .background-color-N6{background-color:#EEF1F8;}
.d2-1843626214 .background-color-N7{background-color:#FFFFFF;}
.d2-1843626214 .background-color-B1{background-color:#0D32B2;}
.d2-1843626214 .background-color-B2{background-color:#0D32B2;}
.d2-1843626214 .background-color-B3{background-color:#E3E9FD;}
.d2-1843626214 .background-color-B4{background-color:#E3E9FD;}
.d2-1843626214 .background-color-B5{background-color:#EDF0FD;}
.d2-1843626214 .background-color-B6{background-color:#F7F8FE;}
.d2-1843626214 .background-color-AA2{background-color:#4A6FF3;}
.d2-1843626214 .background-color-AA4{background-color:#EDF0FD;}
.d2-1843626214 .background-color-AA5{background-color:#F7F8FE;}
.d2-1843626214 .background-color-AB4{background-color:#EDF0FD;}
.d2-1843626214 .background-color-AB5{background-color:#F7F8FE;}
.d2-1843626214 .color-N1{color:#0A0F25;}
.d2-1843626214 .color-N2{color:#676C7E;}
.d2-1843626214 .color-N3{color:#9499AB;}
.d2-1843626214 .color-N4{color:#CFD2DD;}
.d2-1843626214 .color-N5{color:#DEE1EB;}
.d2-1843626214 .color-N6{color:#EEF1F8;}
.d2-1843626214 .color-N7{color:#FFFFFF;}
.d2-1843626214 .color-B1{color:#0D32B2;}
.d2-1843626214 .color-B2{color:#0D32B2;}
.d2-1843626214 .color-B3{color:#E3E9FD;}
.d2-1843626214 .color-B4{color:#E3E9FD;}
.d2-1843626214 .color-B5{color:#EDF0FD;}
.d2-1843626214 .color-B6{color:#F7F8FE;}
.d2-1843626214 .color-AA2{color:#4A6FF3;}
.d2-1843626214 .color-AA4{color:#EDF0FD;}
.d2-1843626214 .color-AA5{color:#F7F8FE;}
.d2-1843626214 .color-AB4{color:#EDF0FD;}
.d2-1843626214 .color-AB5{color:#F7F8FE;}.appendix text.text{fill:#0A0F25}.md{--color-fg-default:#0A0F25;--color-fg-muted:#676C7E;--color-fg-subtle:#9499AB;--color-canvas-default:#FFFFFF;--color-canvas-subtle:#EEF1F8;--color-border-default:#0D32B2;--color-border-muted:#0D32B2;--color-neutral-muted:#EEF1F8;--color-accent-fg:#0D32B2;--color-accent-emphasis:#0D32B2;--color-attention-subtle:#676C7E;--color-danger-fg:red;}.sketch-overlay-B1{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-B2{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-B3{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-B4{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-B5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-B6{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AA2{fill:url(#streaks-dark);mix-blend-mode:overlay}.sketch-overlay-AA4{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AA5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AB4{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AB5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-N1{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-N2{fill:url(#streaks-dark);mix-blend-mode:overlay}.sketch-overlay-N3{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-N4{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-N5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-N6{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-N7{fill:url(#streaks-bright);mix-blend-mode:darken}.light-code{display: block}.dark-code{display: none}]]></style><g id="x"><g class="shape" ><rect x="1.000000" y="0.000000" width="53.000000" height="66.000000" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="27.500000" y="38.500000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">x</text></g><g id="y"><g class="shape" ><rect x="0.000000" y="166.000000" width="54.000000" height="66.000000" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="27.000000" y="204.500000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">y</text></g><g id="(x -&gt; y)[0]"><marker id="mk-3488378134" markerWidth="10.000000" markerHeight="12.000000" refX="7.000000" refY="6.000000" viewBox="0.000000 0.000000 10.000000 12.000000" orient="auto" markerUnits="userSpaceOnUse"> <polygon points="0.000000,0.000000 10.000000,6.000000 0.000000,12.000000" class="connection fill-B1" stroke-width="2" /> </marker><path d="M 27.000000 68.000000 C 27.000000 106.000000 27.000000 126.000000 27.000000 162.000000" fill="none" class="connection stroke-B1" style="stroke-width:2;" marker-end="url(#mk-3488378134)" mask="url(#d2-1843626214)" /></g><mask id="d2-1843626214" maskUnits="userSpaceOnUse" x="-101" y="-101" width="256" height="434">
<rect x="-101" y="-101" width="256" height="434" fill="white"></rect>
<rect x="23.500000" y="22.500000" width="8" height="21" fill="rgba(0,0,0,0.75)"></rect>
<rect x="22.500000" y="188.500000" width="9" height="21" fill="rgba(0,0,0,0.75)"></rect>

Before

Width:  |  Height:  |  Size: 9.4 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

View file

@ -1,16 +1,16 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.5.1-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 514 665"><svg id="d2-svg" width="514" height="665" viewBox="-206 -166 514 665"><style type="text/css"><![CDATA[
.d2-508224771 .text {
font-family: "d2-508224771-font-regular";
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.6.0-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 514 665"><svg id="d2-svg" width="514" height="665" viewBox="-206 -166 514 665"><style type="text/css"><![CDATA[
.d2-281690071 .text {
font-family: "d2-281690071-font-regular";
}
@font-face {
font-family: d2-508224771-font-regular;
font-family: d2-281690071-font-regular;
src: url("data:application/font-woff;base64,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");
}
.d2-508224771 .text-bold {
font-family: "d2-508224771-font-bold";
.d2-281690071 .text-bold {
font-family: "d2-281690071-font-bold";
}
@font-face {
font-family: d2-508224771-font-bold;
font-family: d2-281690071-font-bold;
src: url("data:application/font-woff;base64,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");
}]]></style><style type="text/css"><![CDATA[.shape {
shape-rendering: geometricPrecision;
@ -25,92 +25,92 @@
opacity: 0.5;
}
.d2-508224771 .fill-N1{fill:#0A0F25;}
.d2-508224771 .fill-N2{fill:#676C7E;}
.d2-508224771 .fill-N3{fill:#9499AB;}
.d2-508224771 .fill-N4{fill:#CFD2DD;}
.d2-508224771 .fill-N5{fill:#DEE1EB;}
.d2-508224771 .fill-N6{fill:#EEF1F8;}
.d2-508224771 .fill-N7{fill:#FFFFFF;}
.d2-508224771 .fill-B1{fill:#0D32B2;}
.d2-508224771 .fill-B2{fill:#0D32B2;}
.d2-508224771 .fill-B3{fill:#E3E9FD;}
.d2-508224771 .fill-B4{fill:#E3E9FD;}
.d2-508224771 .fill-B5{fill:#EDF0FD;}
.d2-508224771 .fill-B6{fill:#F7F8FE;}
.d2-508224771 .fill-AA2{fill:#4A6FF3;}
.d2-508224771 .fill-AA4{fill:#EDF0FD;}
.d2-508224771 .fill-AA5{fill:#F7F8FE;}
.d2-508224771 .fill-AB4{fill:#EDF0FD;}
.d2-508224771 .fill-AB5{fill:#F7F8FE;}
.d2-508224771 .stroke-N1{stroke:#0A0F25;}
.d2-508224771 .stroke-N2{stroke:#676C7E;}
.d2-508224771 .stroke-N3{stroke:#9499AB;}
.d2-508224771 .stroke-N4{stroke:#CFD2DD;}
.d2-508224771 .stroke-N5{stroke:#DEE1EB;}
.d2-508224771 .stroke-N6{stroke:#EEF1F8;}
.d2-508224771 .stroke-N7{stroke:#FFFFFF;}
.d2-508224771 .stroke-B1{stroke:#0D32B2;}
.d2-508224771 .stroke-B2{stroke:#0D32B2;}
.d2-508224771 .stroke-B3{stroke:#E3E9FD;}
.d2-508224771 .stroke-B4{stroke:#E3E9FD;}
.d2-508224771 .stroke-B5{stroke:#EDF0FD;}
.d2-508224771 .stroke-B6{stroke:#F7F8FE;}
.d2-508224771 .stroke-AA2{stroke:#4A6FF3;}
.d2-508224771 .stroke-AA4{stroke:#EDF0FD;}
.d2-508224771 .stroke-AA5{stroke:#F7F8FE;}
.d2-508224771 .stroke-AB4{stroke:#EDF0FD;}
.d2-508224771 .stroke-AB5{stroke:#F7F8FE;}
.d2-508224771 .background-color-N1{background-color:#0A0F25;}
.d2-508224771 .background-color-N2{background-color:#676C7E;}
.d2-508224771 .background-color-N3{background-color:#9499AB;}
.d2-508224771 .background-color-N4{background-color:#CFD2DD;}
.d2-508224771 .background-color-N5{background-color:#DEE1EB;}
.d2-508224771 .background-color-N6{background-color:#EEF1F8;}
.d2-508224771 .background-color-N7{background-color:#FFFFFF;}
.d2-508224771 .background-color-B1{background-color:#0D32B2;}
.d2-508224771 .background-color-B2{background-color:#0D32B2;}
.d2-508224771 .background-color-B3{background-color:#E3E9FD;}
.d2-508224771 .background-color-B4{background-color:#E3E9FD;}
.d2-508224771 .background-color-B5{background-color:#EDF0FD;}
.d2-508224771 .background-color-B6{background-color:#F7F8FE;}
.d2-508224771 .background-color-AA2{background-color:#4A6FF3;}
.d2-508224771 .background-color-AA4{background-color:#EDF0FD;}
.d2-508224771 .background-color-AA5{background-color:#F7F8FE;}
.d2-508224771 .background-color-AB4{background-color:#EDF0FD;}
.d2-508224771 .background-color-AB5{background-color:#F7F8FE;}
.d2-508224771 .color-N1{color:#0A0F25;}
.d2-508224771 .color-N2{color:#676C7E;}
.d2-508224771 .color-N3{color:#9499AB;}
.d2-508224771 .color-N4{color:#CFD2DD;}
.d2-508224771 .color-N5{color:#DEE1EB;}
.d2-508224771 .color-N6{color:#EEF1F8;}
.d2-508224771 .color-N7{color:#FFFFFF;}
.d2-508224771 .color-B1{color:#0D32B2;}
.d2-508224771 .color-B2{color:#0D32B2;}
.d2-508224771 .color-B3{color:#E3E9FD;}
.d2-508224771 .color-B4{color:#E3E9FD;}
.d2-508224771 .color-B5{color:#EDF0FD;}
.d2-508224771 .color-B6{color:#F7F8FE;}
.d2-508224771 .color-AA2{color:#4A6FF3;}
.d2-508224771 .color-AA4{color:#EDF0FD;}
.d2-508224771 .color-AA5{color:#F7F8FE;}
.d2-508224771 .color-AB4{color:#EDF0FD;}
.d2-508224771 .color-AB5{color:#F7F8FE;}.appendix text.text{fill:#0A0F25}.md{--color-fg-default:#0A0F25;--color-fg-muted:#676C7E;--color-fg-subtle:#9499AB;--color-canvas-default:#FFFFFF;--color-canvas-subtle:#EEF1F8;--color-border-default:#0D32B2;--color-border-muted:#0D32B2;--color-neutral-muted:#EEF1F8;--color-accent-fg:#0D32B2;--color-accent-emphasis:#0D32B2;--color-attention-subtle:#676C7E;--color-danger-fg:red;}.sketch-overlay-B1{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-B2{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-B3{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-B4{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-B5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-B6{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AA2{fill:url(#streaks-dark);mix-blend-mode:overlay}.sketch-overlay-AA4{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AA5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AB4{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AB5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-N1{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-N2{fill:url(#streaks-dark);mix-blend-mode:overlay}.sketch-overlay-N3{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-N4{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-N5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-N6{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-N7{fill:url(#streaks-bright);mix-blend-mode:darken}.light-code{display: block}.dark-code{display: none}]]></style><style type="text/css">.md em,
.d2-281690071 .fill-N1{fill:#0A0F25;}
.d2-281690071 .fill-N2{fill:#676C7E;}
.d2-281690071 .fill-N3{fill:#9499AB;}
.d2-281690071 .fill-N4{fill:#CFD2DD;}
.d2-281690071 .fill-N5{fill:#DEE1EB;}
.d2-281690071 .fill-N6{fill:#EEF1F8;}
.d2-281690071 .fill-N7{fill:#FFFFFF;}
.d2-281690071 .fill-B1{fill:#0D32B2;}
.d2-281690071 .fill-B2{fill:#0D32B2;}
.d2-281690071 .fill-B3{fill:#E3E9FD;}
.d2-281690071 .fill-B4{fill:#E3E9FD;}
.d2-281690071 .fill-B5{fill:#EDF0FD;}
.d2-281690071 .fill-B6{fill:#F7F8FE;}
.d2-281690071 .fill-AA2{fill:#4A6FF3;}
.d2-281690071 .fill-AA4{fill:#EDF0FD;}
.d2-281690071 .fill-AA5{fill:#F7F8FE;}
.d2-281690071 .fill-AB4{fill:#EDF0FD;}
.d2-281690071 .fill-AB5{fill:#F7F8FE;}
.d2-281690071 .stroke-N1{stroke:#0A0F25;}
.d2-281690071 .stroke-N2{stroke:#676C7E;}
.d2-281690071 .stroke-N3{stroke:#9499AB;}
.d2-281690071 .stroke-N4{stroke:#CFD2DD;}
.d2-281690071 .stroke-N5{stroke:#DEE1EB;}
.d2-281690071 .stroke-N6{stroke:#EEF1F8;}
.d2-281690071 .stroke-N7{stroke:#FFFFFF;}
.d2-281690071 .stroke-B1{stroke:#0D32B2;}
.d2-281690071 .stroke-B2{stroke:#0D32B2;}
.d2-281690071 .stroke-B3{stroke:#E3E9FD;}
.d2-281690071 .stroke-B4{stroke:#E3E9FD;}
.d2-281690071 .stroke-B5{stroke:#EDF0FD;}
.d2-281690071 .stroke-B6{stroke:#F7F8FE;}
.d2-281690071 .stroke-AA2{stroke:#4A6FF3;}
.d2-281690071 .stroke-AA4{stroke:#EDF0FD;}
.d2-281690071 .stroke-AA5{stroke:#F7F8FE;}
.d2-281690071 .stroke-AB4{stroke:#EDF0FD;}
.d2-281690071 .stroke-AB5{stroke:#F7F8FE;}
.d2-281690071 .background-color-N1{background-color:#0A0F25;}
.d2-281690071 .background-color-N2{background-color:#676C7E;}
.d2-281690071 .background-color-N3{background-color:#9499AB;}
.d2-281690071 .background-color-N4{background-color:#CFD2DD;}
.d2-281690071 .background-color-N5{background-color:#DEE1EB;}
.d2-281690071 .background-color-N6{background-color:#EEF1F8;}
.d2-281690071 .background-color-N7{background-color:#FFFFFF;}
.d2-281690071 .background-color-B1{background-color:#0D32B2;}
.d2-281690071 .background-color-B2{background-color:#0D32B2;}
.d2-281690071 .background-color-B3{background-color:#E3E9FD;}
.d2-281690071 .background-color-B4{background-color:#E3E9FD;}
.d2-281690071 .background-color-B5{background-color:#EDF0FD;}
.d2-281690071 .background-color-B6{background-color:#F7F8FE;}
.d2-281690071 .background-color-AA2{background-color:#4A6FF3;}
.d2-281690071 .background-color-AA4{background-color:#EDF0FD;}
.d2-281690071 .background-color-AA5{background-color:#F7F8FE;}
.d2-281690071 .background-color-AB4{background-color:#EDF0FD;}
.d2-281690071 .background-color-AB5{background-color:#F7F8FE;}
.d2-281690071 .color-N1{color:#0A0F25;}
.d2-281690071 .color-N2{color:#676C7E;}
.d2-281690071 .color-N3{color:#9499AB;}
.d2-281690071 .color-N4{color:#CFD2DD;}
.d2-281690071 .color-N5{color:#DEE1EB;}
.d2-281690071 .color-N6{color:#EEF1F8;}
.d2-281690071 .color-N7{color:#FFFFFF;}
.d2-281690071 .color-B1{color:#0D32B2;}
.d2-281690071 .color-B2{color:#0D32B2;}
.d2-281690071 .color-B3{color:#E3E9FD;}
.d2-281690071 .color-B4{color:#E3E9FD;}
.d2-281690071 .color-B5{color:#EDF0FD;}
.d2-281690071 .color-B6{color:#F7F8FE;}
.d2-281690071 .color-AA2{color:#4A6FF3;}
.d2-281690071 .color-AA4{color:#EDF0FD;}
.d2-281690071 .color-AA5{color:#F7F8FE;}
.d2-281690071 .color-AB4{color:#EDF0FD;}
.d2-281690071 .color-AB5{color:#F7F8FE;}.appendix text.text{fill:#0A0F25}.md{--color-fg-default:#0A0F25;--color-fg-muted:#676C7E;--color-fg-subtle:#9499AB;--color-canvas-default:#FFFFFF;--color-canvas-subtle:#EEF1F8;--color-border-default:#0D32B2;--color-border-muted:#0D32B2;--color-neutral-muted:#EEF1F8;--color-accent-fg:#0D32B2;--color-accent-emphasis:#0D32B2;--color-attention-subtle:#676C7E;--color-danger-fg:red;}.sketch-overlay-B1{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-B2{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-B3{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-B4{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-B5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-B6{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AA2{fill:url(#streaks-dark);mix-blend-mode:overlay}.sketch-overlay-AA4{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AA5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AB4{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AB5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-N1{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-N2{fill:url(#streaks-dark);mix-blend-mode:overlay}.sketch-overlay-N3{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-N4{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-N5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-N6{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-N7{fill:url(#streaks-bright);mix-blend-mode:darken}.light-code{display: block}.dark-code{display: none}]]></style><style type="text/css">.md em,
.md dfn {
font-family: "d2-508224771-font-italic";
font-family: "d2-281690071-font-italic";
}
.md b,
.md strong {
font-family: "d2-508224771-font-bold";
font-family: "d2-281690071-font-bold";
}
.md code,
.md kbd,
.md pre,
.md samp {
font-family: "d2-508224771-font-mono";
font-family: "d2-281690071-font-mono";
font-size: 1em;
}
@ -126,7 +126,7 @@
margin: 0;
color: var(--color-fg-default);
background-color: transparent; /* we don't want to define the background color */
font-family: "d2-508224771-font-regular";
font-family: "d2-281690071-font-regular";
font-size: 16px;
line-height: 1.5;
word-wrap: break-word;
@ -832,7 +832,7 @@
.md .contains-task-list:dir(rtl) .task-list-item-checkbox {
margin: 0 -1.6em 0.25em 0.2em;
}
</style><style type="text/css"><![CDATA[@keyframes d2Transition-d2-508224771-0 {
</style><style type="text/css"><![CDATA[@keyframes d2Transition-d2-281690071-0 {
0%, 0.000000% {
opacity: 0;
}
@ -842,7 +842,7 @@
25.000000%, 100% {
opacity: 0;
}
}@keyframes d2Transition-d2-508224771-1 {
}@keyframes d2Transition-d2-281690071-1 {
0%, 24.982143% {
opacity: 0;
}
@ -852,7 +852,7 @@
50.000000%, 100% {
opacity: 0;
}
}@keyframes d2Transition-d2-508224771-2 {
}@keyframes d2Transition-d2-281690071-2 {
0%, 49.982143% {
opacity: 0;
}
@ -862,26 +862,26 @@
75.000000%, 100% {
opacity: 0;
}
}@keyframes d2Transition-d2-508224771-3 {
}@keyframes d2Transition-d2-281690071-3 {
0%, 74.982143% {
opacity: 0;
}
75.000000%, 100.000000% {
opacity: 1;
}
}]]></style><g style="animation: d2Transition-d2-508224771-0 5600ms infinite" class="d2-508224771" width="412" height="247" viewBox="-206 -166 412 247"><rect x="-206.000000" y="-166.000000" width="412.000000" height="247.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><g id="&#34;Chicken&#39;s plan&#34;"><g class="shape" ></g><text x="0.000000" y="-30.000000" class="text fill-N1" style="text-anchor:middle;font-size:35px">Chicken&#39;s plan</text></g><mask id="d2-508224771" maskUnits="userSpaceOnUse" x="-206" y="-166" width="412" height="247">
}]]></style><g style="animation: d2Transition-d2-281690071-0 5600ms infinite" class="d2-281690071" width="412" height="247" viewBox="-206 -166 412 247"><rect x="-206.000000" y="-166.000000" width="412.000000" height="247.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><g id="&#34;Chicken&#39;s plan&#34;"><g class="shape" ></g><text x="0.000000" y="-30.000000" class="text fill-N1" style="text-anchor:middle;font-size:35px">Chicken&#39;s plan</text></g><mask id="d2-281690071" maskUnits="userSpaceOnUse" x="-206" y="-166" width="412" height="247">
<rect x="-206" y="-166" width="412" height="247" fill="white"></rect>
<rect x="-105.000000" y="-65.000000" width="210" height="45" fill="rgba(0,0,0,0.75)"></rect>
</mask></g><g style="animation: d2Transition-d2-508224771-1 5600ms infinite" class="d2-508224771" width="412" height="333" viewBox="-131 -166 412 333"><rect x="-131.000000" y="-166.000000" width="412.000000" height="333.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><g id="Approach road"><g class="shape" ><rect x="0.000000" y="0.000000" width="150.000000" height="66.000000" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="75.000000" y="38.500000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">Approach road</text></g><g id="&#34;Chicken&#39;s plan&#34;"><g class="shape" ></g><text x="75.000000" y="-30.000000" class="text fill-N1" style="text-anchor:middle;font-size:35px">Chicken&#39;s plan</text></g><mask id="d2-3302893893" maskUnits="userSpaceOnUse" x="-131" y="-166" width="412" height="333">
</mask></g><g style="animation: d2Transition-d2-281690071-1 5600ms infinite" class="d2-281690071" width="412" height="333" viewBox="-131 -166 412 333"><rect x="-131.000000" y="-166.000000" width="412.000000" height="333.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><g id="Approach road"><g class="shape" ><rect x="0.000000" y="0.000000" width="150.000000" height="66.000000" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="75.000000" y="38.500000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">Approach road</text></g><g id="&#34;Chicken&#39;s plan&#34;"><g class="shape" ></g><text x="75.000000" y="-30.000000" class="text fill-N1" style="text-anchor:middle;font-size:35px">Chicken&#39;s plan</text></g><mask id="d2-2457953887" maskUnits="userSpaceOnUse" x="-131" y="-166" width="412" height="333">
<rect x="-131" y="-166" width="412" height="333" fill="white"></rect>
<rect x="22.500000" y="22.500000" width="105" height="21" fill="rgba(0,0,0,0.75)"></rect>
<rect x="-30.000000" y="-65.000000" width="210" height="45" fill="rgba(0,0,0,0.75)"></rect>
</mask></g><g style="animation: d2Transition-d2-508224771-2 5600ms infinite" class="d2-508224771" width="412" height="499" viewBox="-131 -166 412 499"><rect x="-131.000000" y="-166.000000" width="412.000000" height="499.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><g id="Approach road"><g class="shape" ><rect x="0.000000" y="0.000000" width="150.000000" height="66.000000" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="75.000000" y="38.500000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">Approach road</text></g><g id="Cross road"><g class="shape" ><rect x="15.000000" y="166.000000" width="120.000000" height="66.000000" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="75.000000" y="204.500000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">Cross road</text></g><g id="&#34;Chicken&#39;s plan&#34;"><g class="shape" ></g><text x="75.000000" y="-30.000000" class="text fill-N1" style="text-anchor:middle;font-size:35px">Chicken&#39;s plan</text></g><g id="(Approach road -&gt; Cross road)[0]"><marker id="mk-3488378134" markerWidth="10.000000" markerHeight="12.000000" refX="7.000000" refY="6.000000" viewBox="0.000000 0.000000 10.000000 12.000000" orient="auto" markerUnits="userSpaceOnUse"> <polygon points="0.000000,0.000000 10.000000,6.000000 0.000000,12.000000" class="connection fill-B1" stroke-width="2" /> </marker><path d="M 75.000000 68.000000 C 75.000000 106.000000 75.000000 126.000000 75.000000 162.000000" fill="none" class="connection stroke-B1" style="stroke-width:2;" marker-end="url(#mk-3488378134)" mask="url(#d2-2454480105)" /></g><mask id="d2-2454480105" maskUnits="userSpaceOnUse" x="-131" y="-166" width="412" height="499">
</mask></g><g style="animation: d2Transition-d2-281690071-2 5600ms infinite" class="d2-281690071" width="412" height="499" viewBox="-131 -166 412 499"><rect x="-131.000000" y="-166.000000" width="412.000000" height="499.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><g id="Approach road"><g class="shape" ><rect x="0.000000" y="0.000000" width="150.000000" height="66.000000" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="75.000000" y="38.500000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">Approach road</text></g><g id="Cross road"><g class="shape" ><rect x="15.000000" y="166.000000" width="120.000000" height="66.000000" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="75.000000" y="204.500000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">Cross road</text></g><g id="&#34;Chicken&#39;s plan&#34;"><g class="shape" ></g><text x="75.000000" y="-30.000000" class="text fill-N1" style="text-anchor:middle;font-size:35px">Chicken&#39;s plan</text></g><g id="(Approach road -&gt; Cross road)[0]"><marker id="mk-3488378134" markerWidth="10.000000" markerHeight="12.000000" refX="7.000000" refY="6.000000" viewBox="0.000000 0.000000 10.000000 12.000000" orient="auto" markerUnits="userSpaceOnUse"> <polygon points="0.000000,0.000000 10.000000,6.000000 0.000000,12.000000" class="connection fill-B1" stroke-width="2" /> </marker><path d="M 75.000000 68.000000 C 75.000000 106.000000 75.000000 126.000000 75.000000 162.000000" fill="none" class="connection stroke-B1" style="stroke-width:2;" marker-end="url(#mk-3488378134)" mask="url(#d2-1673714443)" /></g><mask id="d2-1673714443" maskUnits="userSpaceOnUse" x="-131" y="-166" width="412" height="499">
<rect x="-131" y="-166" width="412" height="499" fill="white"></rect>
<rect x="22.500000" y="22.500000" width="105" height="21" fill="rgba(0,0,0,0.75)"></rect>
<rect x="37.500000" y="188.500000" width="75" height="21" fill="rgba(0,0,0,0.75)"></rect>
<rect x="-30.000000" y="-65.000000" width="210" height="45" fill="rgba(0,0,0,0.75)"></rect>
</mask></g><g style="animation: d2Transition-d2-508224771-3 5600ms infinite" class="d2-508224771" width="412" height="665" viewBox="-104 -166 412 665"><rect x="-104.000000" y="-166.000000" width="412.000000" height="665.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><g id="Approach road"><g class="shape" ><rect x="27.000000" y="0.000000" width="150.000000" height="66.000000" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="102.000000" y="38.500000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">Approach road</text></g><g id="Cross road"><g class="shape" ><rect x="42.000000" y="166.000000" width="120.000000" height="66.000000" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="102.000000" y="204.500000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">Cross road</text></g><g id="Make you wonder why"><g class="shape" ><rect x="0.000000" y="332.000000" width="203.000000" height="66.000000" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="101.500000" y="370.500000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">Make you wonder why</text></g><g id="&#34;Chicken&#39;s plan&#34;"><g class="shape" ></g><text x="102.000000" y="-30.000000" class="text fill-N1" style="text-anchor:middle;font-size:35px">Chicken&#39;s plan</text></g><g id="(Approach road -&gt; Cross road)[0]"><marker id="mk-3488378134" markerWidth="10.000000" markerHeight="12.000000" refX="7.000000" refY="6.000000" viewBox="0.000000 0.000000 10.000000 12.000000" orient="auto" markerUnits="userSpaceOnUse"> <polygon points="0.000000,0.000000 10.000000,6.000000 0.000000,12.000000" class="connection fill-B1" stroke-width="2" /> </marker><path d="M 101.500000 68.000000 C 101.500000 106.000000 101.500000 126.000000 101.500000 162.000000" fill="none" class="connection stroke-B1" style="stroke-width:2;" marker-end="url(#mk-3488378134)" mask="url(#d2-682060979)" /></g><g id="(Cross road -&gt; Make you wonder why)[0]"><path d="M 101.500000 234.000000 C 101.500000 272.000000 101.500000 292.000000 101.500000 328.000000" fill="none" class="connection stroke-B1" style="stroke-width:2;" marker-end="url(#mk-3488378134)" mask="url(#d2-682060979)" /></g><mask id="d2-682060979" maskUnits="userSpaceOnUse" x="-104" y="-166" width="412" height="665">
</mask></g><g style="animation: d2Transition-d2-281690071-3 5600ms infinite" class="d2-281690071" width="412" height="665" viewBox="-104 -166 412 665"><rect x="-104.000000" y="-166.000000" width="412.000000" height="665.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><g id="Approach road"><g class="shape" ><rect x="27.000000" y="0.000000" width="150.000000" height="66.000000" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="102.000000" y="38.500000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">Approach road</text></g><g id="Cross road"><g class="shape" ><rect x="42.000000" y="166.000000" width="120.000000" height="66.000000" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="102.000000" y="204.500000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">Cross road</text></g><g id="Make you wonder why"><g class="shape" ><rect x="0.000000" y="332.000000" width="203.000000" height="66.000000" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="101.500000" y="370.500000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">Make you wonder why</text></g><g id="&#34;Chicken&#39;s plan&#34;"><g class="shape" ></g><text x="102.000000" y="-30.000000" class="text fill-N1" style="text-anchor:middle;font-size:35px">Chicken&#39;s plan</text></g><g id="(Approach road -&gt; Cross road)[0]"><marker id="mk-3488378134" markerWidth="10.000000" markerHeight="12.000000" refX="7.000000" refY="6.000000" viewBox="0.000000 0.000000 10.000000 12.000000" orient="auto" markerUnits="userSpaceOnUse"> <polygon points="0.000000,0.000000 10.000000,6.000000 0.000000,12.000000" class="connection fill-B1" stroke-width="2" /> </marker><path d="M 101.500000 68.000000 C 101.500000 106.000000 101.500000 126.000000 101.500000 162.000000" fill="none" class="connection stroke-B1" style="stroke-width:2;" marker-end="url(#mk-3488378134)" mask="url(#d2-1425945673)" /></g><g id="(Cross road -&gt; Make you wonder why)[0]"><path d="M 101.500000 234.000000 C 101.500000 272.000000 101.500000 292.000000 101.500000 328.000000" fill="none" class="connection stroke-B1" style="stroke-width:2;" marker-end="url(#mk-3488378134)" mask="url(#d2-1425945673)" /></g><mask id="d2-1425945673" maskUnits="userSpaceOnUse" x="-104" y="-166" width="412" height="665">
<rect x="-104" y="-166" width="412" height="665" fill="white"></rect>
<rect x="49.500000" y="22.500000" width="105" height="21" fill="rgba(0,0,0,0.75)"></rect>
<rect x="64.500000" y="188.500000" width="75" height="21" fill="rgba(0,0,0,0.75)"></rect>

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

View file

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.5.1-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 290 268"><svg id="d2-svg" class="d2-685498927" width="290" height="268" viewBox="-101 -101 290 268"><rect x="-101.000000" y="-101.000000" width="290.000000" height="268.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
.d2-685498927 .text-bold {
font-family: "d2-685498927-font-bold";
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.6.0-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 290 268"><svg id="d2-svg" class="d2-3054270525" width="290" height="268" viewBox="-101 -101 290 268"><rect x="-101.000000" y="-101.000000" width="290.000000" height="268.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
.d2-3054270525 .text-bold {
font-family: "d2-3054270525-font-bold";
}
@font-face {
font-family: d2-685498927-font-bold;
font-family: d2-3054270525-font-bold;
src: url("data:application/font-woff;base64,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");
}]]></style><style type="text/css"><![CDATA[.shape {
shape-rendering: geometricPrecision;
@ -18,78 +18,78 @@
opacity: 0.5;
}
.d2-685498927 .fill-N1{fill:#0A0F25;}
.d2-685498927 .fill-N2{fill:#676C7E;}
.d2-685498927 .fill-N3{fill:#9499AB;}
.d2-685498927 .fill-N4{fill:#CFD2DD;}
.d2-685498927 .fill-N5{fill:#DEE1EB;}
.d2-685498927 .fill-N6{fill:#EEF1F8;}
.d2-685498927 .fill-N7{fill:#FFFFFF;}
.d2-685498927 .fill-B1{fill:#0D32B2;}
.d2-685498927 .fill-B2{fill:#0D32B2;}
.d2-685498927 .fill-B3{fill:#E3E9FD;}
.d2-685498927 .fill-B4{fill:#E3E9FD;}
.d2-685498927 .fill-B5{fill:#EDF0FD;}
.d2-685498927 .fill-B6{fill:#F7F8FE;}
.d2-685498927 .fill-AA2{fill:#4A6FF3;}
.d2-685498927 .fill-AA4{fill:#EDF0FD;}
.d2-685498927 .fill-AA5{fill:#F7F8FE;}
.d2-685498927 .fill-AB4{fill:#EDF0FD;}
.d2-685498927 .fill-AB5{fill:#F7F8FE;}
.d2-685498927 .stroke-N1{stroke:#0A0F25;}
.d2-685498927 .stroke-N2{stroke:#676C7E;}
.d2-685498927 .stroke-N3{stroke:#9499AB;}
.d2-685498927 .stroke-N4{stroke:#CFD2DD;}
.d2-685498927 .stroke-N5{stroke:#DEE1EB;}
.d2-685498927 .stroke-N6{stroke:#EEF1F8;}
.d2-685498927 .stroke-N7{stroke:#FFFFFF;}
.d2-685498927 .stroke-B1{stroke:#0D32B2;}
.d2-685498927 .stroke-B2{stroke:#0D32B2;}
.d2-685498927 .stroke-B3{stroke:#E3E9FD;}
.d2-685498927 .stroke-B4{stroke:#E3E9FD;}
.d2-685498927 .stroke-B5{stroke:#EDF0FD;}
.d2-685498927 .stroke-B6{stroke:#F7F8FE;}
.d2-685498927 .stroke-AA2{stroke:#4A6FF3;}
.d2-685498927 .stroke-AA4{stroke:#EDF0FD;}
.d2-685498927 .stroke-AA5{stroke:#F7F8FE;}
.d2-685498927 .stroke-AB4{stroke:#EDF0FD;}
.d2-685498927 .stroke-AB5{stroke:#F7F8FE;}
.d2-685498927 .background-color-N1{background-color:#0A0F25;}
.d2-685498927 .background-color-N2{background-color:#676C7E;}
.d2-685498927 .background-color-N3{background-color:#9499AB;}
.d2-685498927 .background-color-N4{background-color:#CFD2DD;}
.d2-685498927 .background-color-N5{background-color:#DEE1EB;}
.d2-685498927 .background-color-N6{background-color:#EEF1F8;}
.d2-685498927 .background-color-N7{background-color:#FFFFFF;}
.d2-685498927 .background-color-B1{background-color:#0D32B2;}
.d2-685498927 .background-color-B2{background-color:#0D32B2;}
.d2-685498927 .background-color-B3{background-color:#E3E9FD;}
.d2-685498927 .background-color-B4{background-color:#E3E9FD;}
.d2-685498927 .background-color-B5{background-color:#EDF0FD;}
.d2-685498927 .background-color-B6{background-color:#F7F8FE;}
.d2-685498927 .background-color-AA2{background-color:#4A6FF3;}
.d2-685498927 .background-color-AA4{background-color:#EDF0FD;}
.d2-685498927 .background-color-AA5{background-color:#F7F8FE;}
.d2-685498927 .background-color-AB4{background-color:#EDF0FD;}
.d2-685498927 .background-color-AB5{background-color:#F7F8FE;}
.d2-685498927 .color-N1{color:#0A0F25;}
.d2-685498927 .color-N2{color:#676C7E;}
.d2-685498927 .color-N3{color:#9499AB;}
.d2-685498927 .color-N4{color:#CFD2DD;}
.d2-685498927 .color-N5{color:#DEE1EB;}
.d2-685498927 .color-N6{color:#EEF1F8;}
.d2-685498927 .color-N7{color:#FFFFFF;}
.d2-685498927 .color-B1{color:#0D32B2;}
.d2-685498927 .color-B2{color:#0D32B2;}
.d2-685498927 .color-B3{color:#E3E9FD;}
.d2-685498927 .color-B4{color:#E3E9FD;}
.d2-685498927 .color-B5{color:#EDF0FD;}
.d2-685498927 .color-B6{color:#F7F8FE;}
.d2-685498927 .color-AA2{color:#4A6FF3;}
.d2-685498927 .color-AA4{color:#EDF0FD;}
.d2-685498927 .color-AA5{color:#F7F8FE;}
.d2-685498927 .color-AB4{color:#EDF0FD;}
.d2-685498927 .color-AB5{color:#F7F8FE;}.appendix text.text{fill:#0A0F25}.md{--color-fg-default:#0A0F25;--color-fg-muted:#676C7E;--color-fg-subtle:#9499AB;--color-canvas-default:#FFFFFF;--color-canvas-subtle:#EEF1F8;--color-border-default:#0D32B2;--color-border-muted:#0D32B2;--color-neutral-muted:#EEF1F8;--color-accent-fg:#0D32B2;--color-accent-emphasis:#0D32B2;--color-attention-subtle:#676C7E;--color-danger-fg:red;}.sketch-overlay-B1{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-B2{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-B3{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-B4{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-B5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-B6{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AA2{fill:url(#streaks-dark);mix-blend-mode:overlay}.sketch-overlay-AA4{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AA5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AB4{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AB5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-N1{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-N2{fill:url(#streaks-dark);mix-blend-mode:overlay}.sketch-overlay-N3{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-N4{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-N5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-N6{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-N7{fill:url(#streaks-bright);mix-blend-mode:darken}.light-code{display: block}.dark-code{display: none}]]></style><g id="meow"><g class="shape" ><rect x="0.000000" y="0.000000" width="88.000000" height="66.000000" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="44.000000" y="38.500000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">meow</text></g><mask id="d2-685498927" maskUnits="userSpaceOnUse" x="-101" y="-101" width="290" height="268">
.d2-3054270525 .fill-N1{fill:#0A0F25;}
.d2-3054270525 .fill-N2{fill:#676C7E;}
.d2-3054270525 .fill-N3{fill:#9499AB;}
.d2-3054270525 .fill-N4{fill:#CFD2DD;}
.d2-3054270525 .fill-N5{fill:#DEE1EB;}
.d2-3054270525 .fill-N6{fill:#EEF1F8;}
.d2-3054270525 .fill-N7{fill:#FFFFFF;}
.d2-3054270525 .fill-B1{fill:#0D32B2;}
.d2-3054270525 .fill-B2{fill:#0D32B2;}
.d2-3054270525 .fill-B3{fill:#E3E9FD;}
.d2-3054270525 .fill-B4{fill:#E3E9FD;}
.d2-3054270525 .fill-B5{fill:#EDF0FD;}
.d2-3054270525 .fill-B6{fill:#F7F8FE;}
.d2-3054270525 .fill-AA2{fill:#4A6FF3;}
.d2-3054270525 .fill-AA4{fill:#EDF0FD;}
.d2-3054270525 .fill-AA5{fill:#F7F8FE;}
.d2-3054270525 .fill-AB4{fill:#EDF0FD;}
.d2-3054270525 .fill-AB5{fill:#F7F8FE;}
.d2-3054270525 .stroke-N1{stroke:#0A0F25;}
.d2-3054270525 .stroke-N2{stroke:#676C7E;}
.d2-3054270525 .stroke-N3{stroke:#9499AB;}
.d2-3054270525 .stroke-N4{stroke:#CFD2DD;}
.d2-3054270525 .stroke-N5{stroke:#DEE1EB;}
.d2-3054270525 .stroke-N6{stroke:#EEF1F8;}
.d2-3054270525 .stroke-N7{stroke:#FFFFFF;}
.d2-3054270525 .stroke-B1{stroke:#0D32B2;}
.d2-3054270525 .stroke-B2{stroke:#0D32B2;}
.d2-3054270525 .stroke-B3{stroke:#E3E9FD;}
.d2-3054270525 .stroke-B4{stroke:#E3E9FD;}
.d2-3054270525 .stroke-B5{stroke:#EDF0FD;}
.d2-3054270525 .stroke-B6{stroke:#F7F8FE;}
.d2-3054270525 .stroke-AA2{stroke:#4A6FF3;}
.d2-3054270525 .stroke-AA4{stroke:#EDF0FD;}
.d2-3054270525 .stroke-AA5{stroke:#F7F8FE;}
.d2-3054270525 .stroke-AB4{stroke:#EDF0FD;}
.d2-3054270525 .stroke-AB5{stroke:#F7F8FE;}
.d2-3054270525 .background-color-N1{background-color:#0A0F25;}
.d2-3054270525 .background-color-N2{background-color:#676C7E;}
.d2-3054270525 .background-color-N3{background-color:#9499AB;}
.d2-3054270525 .background-color-N4{background-color:#CFD2DD;}
.d2-3054270525 .background-color-N5{background-color:#DEE1EB;}
.d2-3054270525 .background-color-N6{background-color:#EEF1F8;}
.d2-3054270525 .background-color-N7{background-color:#FFFFFF;}
.d2-3054270525 .background-color-B1{background-color:#0D32B2;}
.d2-3054270525 .background-color-B2{background-color:#0D32B2;}
.d2-3054270525 .background-color-B3{background-color:#E3E9FD;}
.d2-3054270525 .background-color-B4{background-color:#E3E9FD;}
.d2-3054270525 .background-color-B5{background-color:#EDF0FD;}
.d2-3054270525 .background-color-B6{background-color:#F7F8FE;}
.d2-3054270525 .background-color-AA2{background-color:#4A6FF3;}
.d2-3054270525 .background-color-AA4{background-color:#EDF0FD;}
.d2-3054270525 .background-color-AA5{background-color:#F7F8FE;}
.d2-3054270525 .background-color-AB4{background-color:#EDF0FD;}
.d2-3054270525 .background-color-AB5{background-color:#F7F8FE;}
.d2-3054270525 .color-N1{color:#0A0F25;}
.d2-3054270525 .color-N2{color:#676C7E;}
.d2-3054270525 .color-N3{color:#9499AB;}
.d2-3054270525 .color-N4{color:#CFD2DD;}
.d2-3054270525 .color-N5{color:#DEE1EB;}
.d2-3054270525 .color-N6{color:#EEF1F8;}
.d2-3054270525 .color-N7{color:#FFFFFF;}
.d2-3054270525 .color-B1{color:#0D32B2;}
.d2-3054270525 .color-B2{color:#0D32B2;}
.d2-3054270525 .color-B3{color:#E3E9FD;}
.d2-3054270525 .color-B4{color:#E3E9FD;}
.d2-3054270525 .color-B5{color:#EDF0FD;}
.d2-3054270525 .color-B6{color:#F7F8FE;}
.d2-3054270525 .color-AA2{color:#4A6FF3;}
.d2-3054270525 .color-AA4{color:#EDF0FD;}
.d2-3054270525 .color-AA5{color:#F7F8FE;}
.d2-3054270525 .color-AB4{color:#EDF0FD;}
.d2-3054270525 .color-AB5{color:#F7F8FE;}.appendix text.text{fill:#0A0F25}.md{--color-fg-default:#0A0F25;--color-fg-muted:#676C7E;--color-fg-subtle:#9499AB;--color-canvas-default:#FFFFFF;--color-canvas-subtle:#EEF1F8;--color-border-default:#0D32B2;--color-border-muted:#0D32B2;--color-neutral-muted:#EEF1F8;--color-accent-fg:#0D32B2;--color-accent-emphasis:#0D32B2;--color-attention-subtle:#676C7E;--color-danger-fg:red;}.sketch-overlay-B1{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-B2{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-B3{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-B4{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-B5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-B6{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AA2{fill:url(#streaks-dark);mix-blend-mode:overlay}.sketch-overlay-AA4{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AA5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AB4{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AB5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-N1{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-N2{fill:url(#streaks-dark);mix-blend-mode:overlay}.sketch-overlay-N3{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-N4{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-N5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-N6{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-N7{fill:url(#streaks-bright);mix-blend-mode:darken}.light-code{display: block}.dark-code{display: none}]]></style><g id="meow"><g class="shape" ><rect x="0.000000" y="0.000000" width="88.000000" height="66.000000" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="44.000000" y="38.500000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">meow</text></g><mask id="d2-3054270525" maskUnits="userSpaceOnUse" x="-101" y="-101" width="290" height="268">
<rect x="-101" y="-101" width="290" height="268" fill="white"></rect>
<rect x="22.500000" y="22.500000" width="43" height="21" fill="rgba(0,0,0,0.75)"></rect>
</mask></svg></svg>

Before

Width:  |  Height:  |  Size: 8.8 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

View file

@ -1,12 +1,12 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.5.1-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 305 285"><svg id="d2-svg" class="d2-2353227294" width="305" height="285" viewBox="-101 -118 305 285"><rect x="-101.000000" y="-118.000000" width="305.000000" height="285.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.6.0-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 305 285"><svg id="d2-svg" class="d2-1655546234" width="305" height="285" viewBox="-101 -118 305 285"><rect x="-101.000000" y="-118.000000" width="305.000000" height="285.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
.appendix-icon {
filter: drop-shadow(0px 0px 32px rgba(31, 36, 58, 0.1));
}
.d2-2353227294 .text-bold {
font-family: "d2-2353227294-font-bold";
.d2-1655546234 .text-bold {
font-family: "d2-1655546234-font-bold";
}
@font-face {
font-family: d2-2353227294-font-bold;
font-family: d2-1655546234-font-bold;
src: url("data:application/font-woff;base64,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");
}]]></style><style type="text/css"><![CDATA[.shape {
shape-rendering: geometricPrecision;
@ -21,78 +21,78 @@
opacity: 0.5;
}
.d2-2353227294 .fill-N1{fill:#0A0F25;}
.d2-2353227294 .fill-N2{fill:#676C7E;}
.d2-2353227294 .fill-N3{fill:#9499AB;}
.d2-2353227294 .fill-N4{fill:#CFD2DD;}
.d2-2353227294 .fill-N5{fill:#DEE1EB;}
.d2-2353227294 .fill-N6{fill:#EEF1F8;}
.d2-2353227294 .fill-N7{fill:#FFFFFF;}
.d2-2353227294 .fill-B1{fill:#0D32B2;}
.d2-2353227294 .fill-B2{fill:#0D32B2;}
.d2-2353227294 .fill-B3{fill:#E3E9FD;}
.d2-2353227294 .fill-B4{fill:#E3E9FD;}
.d2-2353227294 .fill-B5{fill:#EDF0FD;}
.d2-2353227294 .fill-B6{fill:#F7F8FE;}
.d2-2353227294 .fill-AA2{fill:#4A6FF3;}
.d2-2353227294 .fill-AA4{fill:#EDF0FD;}
.d2-2353227294 .fill-AA5{fill:#F7F8FE;}
.d2-2353227294 .fill-AB4{fill:#EDF0FD;}
.d2-2353227294 .fill-AB5{fill:#F7F8FE;}
.d2-2353227294 .stroke-N1{stroke:#0A0F25;}
.d2-2353227294 .stroke-N2{stroke:#676C7E;}
.d2-2353227294 .stroke-N3{stroke:#9499AB;}
.d2-2353227294 .stroke-N4{stroke:#CFD2DD;}
.d2-2353227294 .stroke-N5{stroke:#DEE1EB;}
.d2-2353227294 .stroke-N6{stroke:#EEF1F8;}
.d2-2353227294 .stroke-N7{stroke:#FFFFFF;}
.d2-2353227294 .stroke-B1{stroke:#0D32B2;}
.d2-2353227294 .stroke-B2{stroke:#0D32B2;}
.d2-2353227294 .stroke-B3{stroke:#E3E9FD;}
.d2-2353227294 .stroke-B4{stroke:#E3E9FD;}
.d2-2353227294 .stroke-B5{stroke:#EDF0FD;}
.d2-2353227294 .stroke-B6{stroke:#F7F8FE;}
.d2-2353227294 .stroke-AA2{stroke:#4A6FF3;}
.d2-2353227294 .stroke-AA4{stroke:#EDF0FD;}
.d2-2353227294 .stroke-AA5{stroke:#F7F8FE;}
.d2-2353227294 .stroke-AB4{stroke:#EDF0FD;}
.d2-2353227294 .stroke-AB5{stroke:#F7F8FE;}
.d2-2353227294 .background-color-N1{background-color:#0A0F25;}
.d2-2353227294 .background-color-N2{background-color:#676C7E;}
.d2-2353227294 .background-color-N3{background-color:#9499AB;}
.d2-2353227294 .background-color-N4{background-color:#CFD2DD;}
.d2-2353227294 .background-color-N5{background-color:#DEE1EB;}
.d2-2353227294 .background-color-N6{background-color:#EEF1F8;}
.d2-2353227294 .background-color-N7{background-color:#FFFFFF;}
.d2-2353227294 .background-color-B1{background-color:#0D32B2;}
.d2-2353227294 .background-color-B2{background-color:#0D32B2;}
.d2-2353227294 .background-color-B3{background-color:#E3E9FD;}
.d2-2353227294 .background-color-B4{background-color:#E3E9FD;}
.d2-2353227294 .background-color-B5{background-color:#EDF0FD;}
.d2-2353227294 .background-color-B6{background-color:#F7F8FE;}
.d2-2353227294 .background-color-AA2{background-color:#4A6FF3;}
.d2-2353227294 .background-color-AA4{background-color:#EDF0FD;}
.d2-2353227294 .background-color-AA5{background-color:#F7F8FE;}
.d2-2353227294 .background-color-AB4{background-color:#EDF0FD;}
.d2-2353227294 .background-color-AB5{background-color:#F7F8FE;}
.d2-2353227294 .color-N1{color:#0A0F25;}
.d2-2353227294 .color-N2{color:#676C7E;}
.d2-2353227294 .color-N3{color:#9499AB;}
.d2-2353227294 .color-N4{color:#CFD2DD;}
.d2-2353227294 .color-N5{color:#DEE1EB;}
.d2-2353227294 .color-N6{color:#EEF1F8;}
.d2-2353227294 .color-N7{color:#FFFFFF;}
.d2-2353227294 .color-B1{color:#0D32B2;}
.d2-2353227294 .color-B2{color:#0D32B2;}
.d2-2353227294 .color-B3{color:#E3E9FD;}
.d2-2353227294 .color-B4{color:#E3E9FD;}
.d2-2353227294 .color-B5{color:#EDF0FD;}
.d2-2353227294 .color-B6{color:#F7F8FE;}
.d2-2353227294 .color-AA2{color:#4A6FF3;}
.d2-2353227294 .color-AA4{color:#EDF0FD;}
.d2-2353227294 .color-AA5{color:#F7F8FE;}
.d2-2353227294 .color-AB4{color:#EDF0FD;}
.d2-2353227294 .color-AB5{color:#F7F8FE;}.appendix text.text{fill:#0A0F25}.md{--color-fg-default:#0A0F25;--color-fg-muted:#676C7E;--color-fg-subtle:#9499AB;--color-canvas-default:#FFFFFF;--color-canvas-subtle:#EEF1F8;--color-border-default:#0D32B2;--color-border-muted:#0D32B2;--color-neutral-muted:#EEF1F8;--color-accent-fg:#0D32B2;--color-accent-emphasis:#0D32B2;--color-attention-subtle:#676C7E;--color-danger-fg:red;}.sketch-overlay-B1{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-B2{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-B3{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-B4{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-B5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-B6{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AA2{fill:url(#streaks-dark);mix-blend-mode:overlay}.sketch-overlay-AA4{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AA5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AB4{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AB5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-N1{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-N2{fill:url(#streaks-dark);mix-blend-mode:overlay}.sketch-overlay-N3{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-N4{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-N5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-N6{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-N7{fill:url(#streaks-bright);mix-blend-mode:darken}.light-code{display: block}.dark-code{display: none}]]></style><a href="y.svg" xlink:href="y.svg"><g id="y"><g class="shape" ><rect x="0.000000" y="0.000000" width="86.000000" height="66.000000" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="43.000000" y="38.500000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">y</text></g></a><g transform="translate(70 -16)" class="appendix-icon"><svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
.d2-1655546234 .fill-N1{fill:#0A0F25;}
.d2-1655546234 .fill-N2{fill:#676C7E;}
.d2-1655546234 .fill-N3{fill:#9499AB;}
.d2-1655546234 .fill-N4{fill:#CFD2DD;}
.d2-1655546234 .fill-N5{fill:#DEE1EB;}
.d2-1655546234 .fill-N6{fill:#EEF1F8;}
.d2-1655546234 .fill-N7{fill:#FFFFFF;}
.d2-1655546234 .fill-B1{fill:#0D32B2;}
.d2-1655546234 .fill-B2{fill:#0D32B2;}
.d2-1655546234 .fill-B3{fill:#E3E9FD;}
.d2-1655546234 .fill-B4{fill:#E3E9FD;}
.d2-1655546234 .fill-B5{fill:#EDF0FD;}
.d2-1655546234 .fill-B6{fill:#F7F8FE;}
.d2-1655546234 .fill-AA2{fill:#4A6FF3;}
.d2-1655546234 .fill-AA4{fill:#EDF0FD;}
.d2-1655546234 .fill-AA5{fill:#F7F8FE;}
.d2-1655546234 .fill-AB4{fill:#EDF0FD;}
.d2-1655546234 .fill-AB5{fill:#F7F8FE;}
.d2-1655546234 .stroke-N1{stroke:#0A0F25;}
.d2-1655546234 .stroke-N2{stroke:#676C7E;}
.d2-1655546234 .stroke-N3{stroke:#9499AB;}
.d2-1655546234 .stroke-N4{stroke:#CFD2DD;}
.d2-1655546234 .stroke-N5{stroke:#DEE1EB;}
.d2-1655546234 .stroke-N6{stroke:#EEF1F8;}
.d2-1655546234 .stroke-N7{stroke:#FFFFFF;}
.d2-1655546234 .stroke-B1{stroke:#0D32B2;}
.d2-1655546234 .stroke-B2{stroke:#0D32B2;}
.d2-1655546234 .stroke-B3{stroke:#E3E9FD;}
.d2-1655546234 .stroke-B4{stroke:#E3E9FD;}
.d2-1655546234 .stroke-B5{stroke:#EDF0FD;}
.d2-1655546234 .stroke-B6{stroke:#F7F8FE;}
.d2-1655546234 .stroke-AA2{stroke:#4A6FF3;}
.d2-1655546234 .stroke-AA4{stroke:#EDF0FD;}
.d2-1655546234 .stroke-AA5{stroke:#F7F8FE;}
.d2-1655546234 .stroke-AB4{stroke:#EDF0FD;}
.d2-1655546234 .stroke-AB5{stroke:#F7F8FE;}
.d2-1655546234 .background-color-N1{background-color:#0A0F25;}
.d2-1655546234 .background-color-N2{background-color:#676C7E;}
.d2-1655546234 .background-color-N3{background-color:#9499AB;}
.d2-1655546234 .background-color-N4{background-color:#CFD2DD;}
.d2-1655546234 .background-color-N5{background-color:#DEE1EB;}
.d2-1655546234 .background-color-N6{background-color:#EEF1F8;}
.d2-1655546234 .background-color-N7{background-color:#FFFFFF;}
.d2-1655546234 .background-color-B1{background-color:#0D32B2;}
.d2-1655546234 .background-color-B2{background-color:#0D32B2;}
.d2-1655546234 .background-color-B3{background-color:#E3E9FD;}
.d2-1655546234 .background-color-B4{background-color:#E3E9FD;}
.d2-1655546234 .background-color-B5{background-color:#EDF0FD;}
.d2-1655546234 .background-color-B6{background-color:#F7F8FE;}
.d2-1655546234 .background-color-AA2{background-color:#4A6FF3;}
.d2-1655546234 .background-color-AA4{background-color:#EDF0FD;}
.d2-1655546234 .background-color-AA5{background-color:#F7F8FE;}
.d2-1655546234 .background-color-AB4{background-color:#EDF0FD;}
.d2-1655546234 .background-color-AB5{background-color:#F7F8FE;}
.d2-1655546234 .color-N1{color:#0A0F25;}
.d2-1655546234 .color-N2{color:#676C7E;}
.d2-1655546234 .color-N3{color:#9499AB;}
.d2-1655546234 .color-N4{color:#CFD2DD;}
.d2-1655546234 .color-N5{color:#DEE1EB;}
.d2-1655546234 .color-N6{color:#EEF1F8;}
.d2-1655546234 .color-N7{color:#FFFFFF;}
.d2-1655546234 .color-B1{color:#0D32B2;}
.d2-1655546234 .color-B2{color:#0D32B2;}
.d2-1655546234 .color-B3{color:#E3E9FD;}
.d2-1655546234 .color-B4{color:#E3E9FD;}
.d2-1655546234 .color-B5{color:#EDF0FD;}
.d2-1655546234 .color-B6{color:#F7F8FE;}
.d2-1655546234 .color-AA2{color:#4A6FF3;}
.d2-1655546234 .color-AA4{color:#EDF0FD;}
.d2-1655546234 .color-AA5{color:#F7F8FE;}
.d2-1655546234 .color-AB4{color:#EDF0FD;}
.d2-1655546234 .color-AB5{color:#F7F8FE;}.appendix text.text{fill:#0A0F25}.md{--color-fg-default:#0A0F25;--color-fg-muted:#676C7E;--color-fg-subtle:#9499AB;--color-canvas-default:#FFFFFF;--color-canvas-subtle:#EEF1F8;--color-border-default:#0D32B2;--color-border-muted:#0D32B2;--color-neutral-muted:#EEF1F8;--color-accent-fg:#0D32B2;--color-accent-emphasis:#0D32B2;--color-attention-subtle:#676C7E;--color-danger-fg:red;}.sketch-overlay-B1{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-B2{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-B3{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-B4{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-B5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-B6{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AA2{fill:url(#streaks-dark);mix-blend-mode:overlay}.sketch-overlay-AA4{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AA5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AB4{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AB5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-N1{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-N2{fill:url(#streaks-dark);mix-blend-mode:overlay}.sketch-overlay-N3{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-N4{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-N5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-N6{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-N7{fill:url(#streaks-bright);mix-blend-mode:darken}.light-code{display: block}.dark-code{display: none}]]></style><a href="y.svg" xlink:href="y.svg"><g id="y"><g class="shape" ><rect x="0.000000" y="0.000000" width="86.000000" height="66.000000" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="43.000000" y="38.500000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">y</text></g></a><g transform="translate(70 -16)" class="appendix-icon"><svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_3440_35088111)">
<path d="M16 31.1109C24.3456 31.1109 31.1111 24.3454 31.1111 15.9998C31.1111 7.65415 24.3456 0.888672 16 0.888672C7.65436 0.888672 0.888885 7.65415 0.888885 15.9998C0.888885 24.3454 7.65436 31.1109 16 31.1109Z" fill="white" stroke="#DEE1EB"/>
<path d="M14.3909 16.7965C14.7364 17.2584 15.1772 17.6406 15.6834 17.9171C16.1896 18.1938 16.7494 18.3582 17.3248 18.3993C17.9001 18.4405 18.4777 18.3575 19.0181 18.1559C19.5586 17.9543 20.0492 17.6389 20.4571 17.2309L22.8708 14.8173C23.6036 14.0586 24.0089 13.0425 23.9998 11.9877C23.9906 10.933 23.5676 9.92404 22.8217 9.17821C22.0759 8.43237 21.067 8.00931 20.0123 8.00015C18.9575 7.99098 17.9413 8.39644 17.1827 9.1292L15.7988 10.505" stroke="#2E3346" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
@ -104,7 +104,7 @@
</clipPath>
</defs>
</svg>
</g><mask id="d2-2353227294" maskUnits="userSpaceOnUse" x="-101" y="-118" width="305" height="285">
</g><mask id="d2-1655546234" maskUnits="userSpaceOnUse" x="-101" y="-118" width="305" height="285">
<rect x="-101" y="-118" width="305" height="285" fill="white"></rect>
<rect x="38.500000" y="22.500000" width="9" height="21" fill="rgba(0,0,0,0.75)"></rect>
</mask></svg></svg>

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View file

@ -1,12 +1,12 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.5.1-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 304 285"><svg id="d2-svg" class="d2-2067460405" width="304" height="285" viewBox="-101 -118 304 285"><rect x="-101.000000" y="-118.000000" width="304.000000" height="285.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.6.0-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 304 285"><svg id="d2-svg" class="d2-3111330921" width="304" height="285" viewBox="-101 -118 304 285"><rect x="-101.000000" y="-118.000000" width="304.000000" height="285.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
.appendix-icon {
filter: drop-shadow(0px 0px 32px rgba(31, 36, 58, 0.1));
}
.d2-2067460405 .text-bold {
font-family: "d2-2067460405-font-bold";
.d2-3111330921 .text-bold {
font-family: "d2-3111330921-font-bold";
}
@font-face {
font-family: d2-2067460405-font-bold;
font-family: d2-3111330921-font-bold;
src: url("data:application/font-woff;base64,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");
}]]></style><style type="text/css"><![CDATA[.shape {
shape-rendering: geometricPrecision;
@ -21,78 +21,78 @@
opacity: 0.5;
}
.d2-2067460405 .fill-N1{fill:#0A0F25;}
.d2-2067460405 .fill-N2{fill:#676C7E;}
.d2-2067460405 .fill-N3{fill:#9499AB;}
.d2-2067460405 .fill-N4{fill:#CFD2DD;}
.d2-2067460405 .fill-N5{fill:#DEE1EB;}
.d2-2067460405 .fill-N6{fill:#EEF1F8;}
.d2-2067460405 .fill-N7{fill:#FFFFFF;}
.d2-2067460405 .fill-B1{fill:#0D32B2;}
.d2-2067460405 .fill-B2{fill:#0D32B2;}
.d2-2067460405 .fill-B3{fill:#E3E9FD;}
.d2-2067460405 .fill-B4{fill:#E3E9FD;}
.d2-2067460405 .fill-B5{fill:#EDF0FD;}
.d2-2067460405 .fill-B6{fill:#F7F8FE;}
.d2-2067460405 .fill-AA2{fill:#4A6FF3;}
.d2-2067460405 .fill-AA4{fill:#EDF0FD;}
.d2-2067460405 .fill-AA5{fill:#F7F8FE;}
.d2-2067460405 .fill-AB4{fill:#EDF0FD;}
.d2-2067460405 .fill-AB5{fill:#F7F8FE;}
.d2-2067460405 .stroke-N1{stroke:#0A0F25;}
.d2-2067460405 .stroke-N2{stroke:#676C7E;}
.d2-2067460405 .stroke-N3{stroke:#9499AB;}
.d2-2067460405 .stroke-N4{stroke:#CFD2DD;}
.d2-2067460405 .stroke-N5{stroke:#DEE1EB;}
.d2-2067460405 .stroke-N6{stroke:#EEF1F8;}
.d2-2067460405 .stroke-N7{stroke:#FFFFFF;}
.d2-2067460405 .stroke-B1{stroke:#0D32B2;}
.d2-2067460405 .stroke-B2{stroke:#0D32B2;}
.d2-2067460405 .stroke-B3{stroke:#E3E9FD;}
.d2-2067460405 .stroke-B4{stroke:#E3E9FD;}
.d2-2067460405 .stroke-B5{stroke:#EDF0FD;}
.d2-2067460405 .stroke-B6{stroke:#F7F8FE;}
.d2-2067460405 .stroke-AA2{stroke:#4A6FF3;}
.d2-2067460405 .stroke-AA4{stroke:#EDF0FD;}
.d2-2067460405 .stroke-AA5{stroke:#F7F8FE;}
.d2-2067460405 .stroke-AB4{stroke:#EDF0FD;}
.d2-2067460405 .stroke-AB5{stroke:#F7F8FE;}
.d2-2067460405 .background-color-N1{background-color:#0A0F25;}
.d2-2067460405 .background-color-N2{background-color:#676C7E;}
.d2-2067460405 .background-color-N3{background-color:#9499AB;}
.d2-2067460405 .background-color-N4{background-color:#CFD2DD;}
.d2-2067460405 .background-color-N5{background-color:#DEE1EB;}
.d2-2067460405 .background-color-N6{background-color:#EEF1F8;}
.d2-2067460405 .background-color-N7{background-color:#FFFFFF;}
.d2-2067460405 .background-color-B1{background-color:#0D32B2;}
.d2-2067460405 .background-color-B2{background-color:#0D32B2;}
.d2-2067460405 .background-color-B3{background-color:#E3E9FD;}
.d2-2067460405 .background-color-B4{background-color:#E3E9FD;}
.d2-2067460405 .background-color-B5{background-color:#EDF0FD;}
.d2-2067460405 .background-color-B6{background-color:#F7F8FE;}
.d2-2067460405 .background-color-AA2{background-color:#4A6FF3;}
.d2-2067460405 .background-color-AA4{background-color:#EDF0FD;}
.d2-2067460405 .background-color-AA5{background-color:#F7F8FE;}
.d2-2067460405 .background-color-AB4{background-color:#EDF0FD;}
.d2-2067460405 .background-color-AB5{background-color:#F7F8FE;}
.d2-2067460405 .color-N1{color:#0A0F25;}
.d2-2067460405 .color-N2{color:#676C7E;}
.d2-2067460405 .color-N3{color:#9499AB;}
.d2-2067460405 .color-N4{color:#CFD2DD;}
.d2-2067460405 .color-N5{color:#DEE1EB;}
.d2-2067460405 .color-N6{color:#EEF1F8;}
.d2-2067460405 .color-N7{color:#FFFFFF;}
.d2-2067460405 .color-B1{color:#0D32B2;}
.d2-2067460405 .color-B2{color:#0D32B2;}
.d2-2067460405 .color-B3{color:#E3E9FD;}
.d2-2067460405 .color-B4{color:#E3E9FD;}
.d2-2067460405 .color-B5{color:#EDF0FD;}
.d2-2067460405 .color-B6{color:#F7F8FE;}
.d2-2067460405 .color-AA2{color:#4A6FF3;}
.d2-2067460405 .color-AA4{color:#EDF0FD;}
.d2-2067460405 .color-AA5{color:#F7F8FE;}
.d2-2067460405 .color-AB4{color:#EDF0FD;}
.d2-2067460405 .color-AB5{color:#F7F8FE;}.appendix text.text{fill:#0A0F25}.md{--color-fg-default:#0A0F25;--color-fg-muted:#676C7E;--color-fg-subtle:#9499AB;--color-canvas-default:#FFFFFF;--color-canvas-subtle:#EEF1F8;--color-border-default:#0D32B2;--color-border-muted:#0D32B2;--color-neutral-muted:#EEF1F8;--color-accent-fg:#0D32B2;--color-accent-emphasis:#0D32B2;--color-attention-subtle:#676C7E;--color-danger-fg:red;}.sketch-overlay-B1{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-B2{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-B3{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-B4{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-B5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-B6{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AA2{fill:url(#streaks-dark);mix-blend-mode:overlay}.sketch-overlay-AA4{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AA5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AB4{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AB5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-N1{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-N2{fill:url(#streaks-dark);mix-blend-mode:overlay}.sketch-overlay-N3{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-N4{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-N5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-N6{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-N7{fill:url(#streaks-bright);mix-blend-mode:darken}.light-code{display: block}.dark-code{display: none}]]></style><a href="x/index.svg" xlink:href="x/index.svg"><g id="x"><g class="shape" ><rect x="0.000000" y="0.000000" width="85.000000" height="66.000000" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="42.500000" y="38.500000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">x</text></g></a><g transform="translate(69 -16)" class="appendix-icon"><svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
.d2-3111330921 .fill-N1{fill:#0A0F25;}
.d2-3111330921 .fill-N2{fill:#676C7E;}
.d2-3111330921 .fill-N3{fill:#9499AB;}
.d2-3111330921 .fill-N4{fill:#CFD2DD;}
.d2-3111330921 .fill-N5{fill:#DEE1EB;}
.d2-3111330921 .fill-N6{fill:#EEF1F8;}
.d2-3111330921 .fill-N7{fill:#FFFFFF;}
.d2-3111330921 .fill-B1{fill:#0D32B2;}
.d2-3111330921 .fill-B2{fill:#0D32B2;}
.d2-3111330921 .fill-B3{fill:#E3E9FD;}
.d2-3111330921 .fill-B4{fill:#E3E9FD;}
.d2-3111330921 .fill-B5{fill:#EDF0FD;}
.d2-3111330921 .fill-B6{fill:#F7F8FE;}
.d2-3111330921 .fill-AA2{fill:#4A6FF3;}
.d2-3111330921 .fill-AA4{fill:#EDF0FD;}
.d2-3111330921 .fill-AA5{fill:#F7F8FE;}
.d2-3111330921 .fill-AB4{fill:#EDF0FD;}
.d2-3111330921 .fill-AB5{fill:#F7F8FE;}
.d2-3111330921 .stroke-N1{stroke:#0A0F25;}
.d2-3111330921 .stroke-N2{stroke:#676C7E;}
.d2-3111330921 .stroke-N3{stroke:#9499AB;}
.d2-3111330921 .stroke-N4{stroke:#CFD2DD;}
.d2-3111330921 .stroke-N5{stroke:#DEE1EB;}
.d2-3111330921 .stroke-N6{stroke:#EEF1F8;}
.d2-3111330921 .stroke-N7{stroke:#FFFFFF;}
.d2-3111330921 .stroke-B1{stroke:#0D32B2;}
.d2-3111330921 .stroke-B2{stroke:#0D32B2;}
.d2-3111330921 .stroke-B3{stroke:#E3E9FD;}
.d2-3111330921 .stroke-B4{stroke:#E3E9FD;}
.d2-3111330921 .stroke-B5{stroke:#EDF0FD;}
.d2-3111330921 .stroke-B6{stroke:#F7F8FE;}
.d2-3111330921 .stroke-AA2{stroke:#4A6FF3;}
.d2-3111330921 .stroke-AA4{stroke:#EDF0FD;}
.d2-3111330921 .stroke-AA5{stroke:#F7F8FE;}
.d2-3111330921 .stroke-AB4{stroke:#EDF0FD;}
.d2-3111330921 .stroke-AB5{stroke:#F7F8FE;}
.d2-3111330921 .background-color-N1{background-color:#0A0F25;}
.d2-3111330921 .background-color-N2{background-color:#676C7E;}
.d2-3111330921 .background-color-N3{background-color:#9499AB;}
.d2-3111330921 .background-color-N4{background-color:#CFD2DD;}
.d2-3111330921 .background-color-N5{background-color:#DEE1EB;}
.d2-3111330921 .background-color-N6{background-color:#EEF1F8;}
.d2-3111330921 .background-color-N7{background-color:#FFFFFF;}
.d2-3111330921 .background-color-B1{background-color:#0D32B2;}
.d2-3111330921 .background-color-B2{background-color:#0D32B2;}
.d2-3111330921 .background-color-B3{background-color:#E3E9FD;}
.d2-3111330921 .background-color-B4{background-color:#E3E9FD;}
.d2-3111330921 .background-color-B5{background-color:#EDF0FD;}
.d2-3111330921 .background-color-B6{background-color:#F7F8FE;}
.d2-3111330921 .background-color-AA2{background-color:#4A6FF3;}
.d2-3111330921 .background-color-AA4{background-color:#EDF0FD;}
.d2-3111330921 .background-color-AA5{background-color:#F7F8FE;}
.d2-3111330921 .background-color-AB4{background-color:#EDF0FD;}
.d2-3111330921 .background-color-AB5{background-color:#F7F8FE;}
.d2-3111330921 .color-N1{color:#0A0F25;}
.d2-3111330921 .color-N2{color:#676C7E;}
.d2-3111330921 .color-N3{color:#9499AB;}
.d2-3111330921 .color-N4{color:#CFD2DD;}
.d2-3111330921 .color-N5{color:#DEE1EB;}
.d2-3111330921 .color-N6{color:#EEF1F8;}
.d2-3111330921 .color-N7{color:#FFFFFF;}
.d2-3111330921 .color-B1{color:#0D32B2;}
.d2-3111330921 .color-B2{color:#0D32B2;}
.d2-3111330921 .color-B3{color:#E3E9FD;}
.d2-3111330921 .color-B4{color:#E3E9FD;}
.d2-3111330921 .color-B5{color:#EDF0FD;}
.d2-3111330921 .color-B6{color:#F7F8FE;}
.d2-3111330921 .color-AA2{color:#4A6FF3;}
.d2-3111330921 .color-AA4{color:#EDF0FD;}
.d2-3111330921 .color-AA5{color:#F7F8FE;}
.d2-3111330921 .color-AB4{color:#EDF0FD;}
.d2-3111330921 .color-AB5{color:#F7F8FE;}.appendix text.text{fill:#0A0F25}.md{--color-fg-default:#0A0F25;--color-fg-muted:#676C7E;--color-fg-subtle:#9499AB;--color-canvas-default:#FFFFFF;--color-canvas-subtle:#EEF1F8;--color-border-default:#0D32B2;--color-border-muted:#0D32B2;--color-neutral-muted:#EEF1F8;--color-accent-fg:#0D32B2;--color-accent-emphasis:#0D32B2;--color-attention-subtle:#676C7E;--color-danger-fg:red;}.sketch-overlay-B1{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-B2{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-B3{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-B4{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-B5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-B6{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AA2{fill:url(#streaks-dark);mix-blend-mode:overlay}.sketch-overlay-AA4{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AA5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AB4{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AB5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-N1{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-N2{fill:url(#streaks-dark);mix-blend-mode:overlay}.sketch-overlay-N3{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-N4{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-N5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-N6{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-N7{fill:url(#streaks-bright);mix-blend-mode:darken}.light-code{display: block}.dark-code{display: none}]]></style><a href="x/index.svg" xlink:href="x/index.svg"><g id="x"><g class="shape" ><rect x="0.000000" y="0.000000" width="85.000000" height="66.000000" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="42.500000" y="38.500000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">x</text></g></a><g transform="translate(69 -16)" class="appendix-icon"><svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_3440_35088111)">
<path d="M16 31.1109C24.3456 31.1109 31.1111 24.3454 31.1111 15.9998C31.1111 7.65415 24.3456 0.888672 16 0.888672C7.65436 0.888672 0.888885 7.65415 0.888885 15.9998C0.888885 24.3454 7.65436 31.1109 16 31.1109Z" fill="white" stroke="#DEE1EB"/>
<path d="M14.3909 16.7965C14.7364 17.2584 15.1772 17.6406 15.6834 17.9171C16.1896 18.1938 16.7494 18.3582 17.3248 18.3993C17.9001 18.4405 18.4777 18.3575 19.0181 18.1559C19.5586 17.9543 20.0492 17.6389 20.4571 17.2309L22.8708 14.8173C23.6036 14.0586 24.0089 13.0425 23.9998 11.9877C23.9906 10.933 23.5676 9.92404 22.8217 9.17821C22.0759 8.43237 21.067 8.00931 20.0123 8.00015C18.9575 7.99098 17.9413 8.39644 17.1827 9.1292L15.7988 10.505" stroke="#2E3346" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
@ -104,7 +104,7 @@
</clipPath>
</defs>
</svg>
</g><mask id="d2-2067460405" maskUnits="userSpaceOnUse" x="-101" y="-118" width="304" height="285">
</g><mask id="d2-3111330921" maskUnits="userSpaceOnUse" x="-101" y="-118" width="304" height="285">
<rect x="-101" y="-118" width="304" height="285" fill="white"></rect>
<rect x="38.500000" y="22.500000" width="8" height="21" fill="rgba(0,0,0,0.75)"></rect>
</mask></svg></svg>

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View file

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.5.1-HEAD" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 434"><svg id="d2-svg" class="d2-855222762" width="256" height="434" viewBox="-101 -101 256 434"><rect x="-101.000000" y="-101.000000" width="256.000000" height="434.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
.d2-855222762 .text-bold {
font-family: "d2-855222762-font-bold";
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.6.0-HEAD" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 434"><svg id="d2-svg" class="d2-1843626214" width="256" height="434" viewBox="-101 -101 256 434"><rect x="-101.000000" y="-101.000000" width="256.000000" height="434.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
.d2-1843626214 .text-bold {
font-family: "d2-1843626214-font-bold";
}
@font-face {
font-family: d2-855222762-font-bold;
font-family: d2-1843626214-font-bold;
src: url("data:application/font-woff;base64,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");
}]]></style><style type="text/css"><![CDATA[.shape {
shape-rendering: geometricPrecision;
@ -18,78 +18,78 @@
opacity: 0.5;
}
.d2-855222762 .fill-N1{fill:#0A0F25;}
.d2-855222762 .fill-N2{fill:#676C7E;}
.d2-855222762 .fill-N3{fill:#9499AB;}
.d2-855222762 .fill-N4{fill:#CFD2DD;}
.d2-855222762 .fill-N5{fill:#DEE1EB;}
.d2-855222762 .fill-N6{fill:#EEF1F8;}
.d2-855222762 .fill-N7{fill:#FFFFFF;}
.d2-855222762 .fill-B1{fill:#0D32B2;}
.d2-855222762 .fill-B2{fill:#0D32B2;}
.d2-855222762 .fill-B3{fill:#E3E9FD;}
.d2-855222762 .fill-B4{fill:#E3E9FD;}
.d2-855222762 .fill-B5{fill:#EDF0FD;}
.d2-855222762 .fill-B6{fill:#F7F8FE;}
.d2-855222762 .fill-AA2{fill:#4A6FF3;}
.d2-855222762 .fill-AA4{fill:#EDF0FD;}
.d2-855222762 .fill-AA5{fill:#F7F8FE;}
.d2-855222762 .fill-AB4{fill:#EDF0FD;}
.d2-855222762 .fill-AB5{fill:#F7F8FE;}
.d2-855222762 .stroke-N1{stroke:#0A0F25;}
.d2-855222762 .stroke-N2{stroke:#676C7E;}
.d2-855222762 .stroke-N3{stroke:#9499AB;}
.d2-855222762 .stroke-N4{stroke:#CFD2DD;}
.d2-855222762 .stroke-N5{stroke:#DEE1EB;}
.d2-855222762 .stroke-N6{stroke:#EEF1F8;}
.d2-855222762 .stroke-N7{stroke:#FFFFFF;}
.d2-855222762 .stroke-B1{stroke:#0D32B2;}
.d2-855222762 .stroke-B2{stroke:#0D32B2;}
.d2-855222762 .stroke-B3{stroke:#E3E9FD;}
.d2-855222762 .stroke-B4{stroke:#E3E9FD;}
.d2-855222762 .stroke-B5{stroke:#EDF0FD;}
.d2-855222762 .stroke-B6{stroke:#F7F8FE;}
.d2-855222762 .stroke-AA2{stroke:#4A6FF3;}
.d2-855222762 .stroke-AA4{stroke:#EDF0FD;}
.d2-855222762 .stroke-AA5{stroke:#F7F8FE;}
.d2-855222762 .stroke-AB4{stroke:#EDF0FD;}
.d2-855222762 .stroke-AB5{stroke:#F7F8FE;}
.d2-855222762 .background-color-N1{background-color:#0A0F25;}
.d2-855222762 .background-color-N2{background-color:#676C7E;}
.d2-855222762 .background-color-N3{background-color:#9499AB;}
.d2-855222762 .background-color-N4{background-color:#CFD2DD;}
.d2-855222762 .background-color-N5{background-color:#DEE1EB;}
.d2-855222762 .background-color-N6{background-color:#EEF1F8;}
.d2-855222762 .background-color-N7{background-color:#FFFFFF;}
.d2-855222762 .background-color-B1{background-color:#0D32B2;}
.d2-855222762 .background-color-B2{background-color:#0D32B2;}
.d2-855222762 .background-color-B3{background-color:#E3E9FD;}
.d2-855222762 .background-color-B4{background-color:#E3E9FD;}
.d2-855222762 .background-color-B5{background-color:#EDF0FD;}
.d2-855222762 .background-color-B6{background-color:#F7F8FE;}
.d2-855222762 .background-color-AA2{background-color:#4A6FF3;}
.d2-855222762 .background-color-AA4{background-color:#EDF0FD;}
.d2-855222762 .background-color-AA5{background-color:#F7F8FE;}
.d2-855222762 .background-color-AB4{background-color:#EDF0FD;}
.d2-855222762 .background-color-AB5{background-color:#F7F8FE;}
.d2-855222762 .color-N1{color:#0A0F25;}
.d2-855222762 .color-N2{color:#676C7E;}
.d2-855222762 .color-N3{color:#9499AB;}
.d2-855222762 .color-N4{color:#CFD2DD;}
.d2-855222762 .color-N5{color:#DEE1EB;}
.d2-855222762 .color-N6{color:#EEF1F8;}
.d2-855222762 .color-N7{color:#FFFFFF;}
.d2-855222762 .color-B1{color:#0D32B2;}
.d2-855222762 .color-B2{color:#0D32B2;}
.d2-855222762 .color-B3{color:#E3E9FD;}
.d2-855222762 .color-B4{color:#E3E9FD;}
.d2-855222762 .color-B5{color:#EDF0FD;}
.d2-855222762 .color-B6{color:#F7F8FE;}
.d2-855222762 .color-AA2{color:#4A6FF3;}
.d2-855222762 .color-AA4{color:#EDF0FD;}
.d2-855222762 .color-AA5{color:#F7F8FE;}
.d2-855222762 .color-AB4{color:#EDF0FD;}
.d2-855222762 .color-AB5{color:#F7F8FE;}.appendix text.text{fill:#0A0F25}.md{--color-fg-default:#0A0F25;--color-fg-muted:#676C7E;--color-fg-subtle:#9499AB;--color-canvas-default:#FFFFFF;--color-canvas-subtle:#EEF1F8;--color-border-default:#0D32B2;--color-border-muted:#0D32B2;--color-neutral-muted:#EEF1F8;--color-accent-fg:#0D32B2;--color-accent-emphasis:#0D32B2;--color-attention-subtle:#676C7E;--color-danger-fg:red;}.sketch-overlay-B1{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-B2{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-B3{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-B4{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-B5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-B6{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AA2{fill:url(#streaks-dark);mix-blend-mode:overlay}.sketch-overlay-AA4{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AA5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AB4{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AB5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-N1{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-N2{fill:url(#streaks-dark);mix-blend-mode:overlay}.sketch-overlay-N3{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-N4{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-N5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-N6{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-N7{fill:url(#streaks-bright);mix-blend-mode:darken}.light-code{display: block}.dark-code{display: none}]]></style><g id="x"><g class="shape" ><rect x="1.000000" y="0.000000" width="53.000000" height="66.000000" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="27.500000" y="38.500000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">x</text></g><g id="y"><g class="shape" ><rect x="0.000000" y="166.000000" width="54.000000" height="66.000000" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="27.000000" y="204.500000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">y</text></g><g id="(x -&gt; y)[0]"><marker id="mk-3488378134" markerWidth="10.000000" markerHeight="12.000000" refX="7.000000" refY="6.000000" viewBox="0.000000 0.000000 10.000000 12.000000" orient="auto" markerUnits="userSpaceOnUse"> <polygon points="0.000000,0.000000 10.000000,6.000000 0.000000,12.000000" class="connection fill-B1" stroke-width="2" /> </marker><path d="M 27.000000 68.000000 C 27.000000 106.000000 27.000000 126.000000 27.000000 162.000000" fill="none" class="connection stroke-B1" style="stroke-width:2;" marker-end="url(#mk-3488378134)" mask="url(#d2-855222762)" /></g><mask id="d2-855222762" maskUnits="userSpaceOnUse" x="-101" y="-101" width="256" height="434">
.d2-1843626214 .fill-N1{fill:#0A0F25;}
.d2-1843626214 .fill-N2{fill:#676C7E;}
.d2-1843626214 .fill-N3{fill:#9499AB;}
.d2-1843626214 .fill-N4{fill:#CFD2DD;}
.d2-1843626214 .fill-N5{fill:#DEE1EB;}
.d2-1843626214 .fill-N6{fill:#EEF1F8;}
.d2-1843626214 .fill-N7{fill:#FFFFFF;}
.d2-1843626214 .fill-B1{fill:#0D32B2;}
.d2-1843626214 .fill-B2{fill:#0D32B2;}
.d2-1843626214 .fill-B3{fill:#E3E9FD;}
.d2-1843626214 .fill-B4{fill:#E3E9FD;}
.d2-1843626214 .fill-B5{fill:#EDF0FD;}
.d2-1843626214 .fill-B6{fill:#F7F8FE;}
.d2-1843626214 .fill-AA2{fill:#4A6FF3;}
.d2-1843626214 .fill-AA4{fill:#EDF0FD;}
.d2-1843626214 .fill-AA5{fill:#F7F8FE;}
.d2-1843626214 .fill-AB4{fill:#EDF0FD;}
.d2-1843626214 .fill-AB5{fill:#F7F8FE;}
.d2-1843626214 .stroke-N1{stroke:#0A0F25;}
.d2-1843626214 .stroke-N2{stroke:#676C7E;}
.d2-1843626214 .stroke-N3{stroke:#9499AB;}
.d2-1843626214 .stroke-N4{stroke:#CFD2DD;}
.d2-1843626214 .stroke-N5{stroke:#DEE1EB;}
.d2-1843626214 .stroke-N6{stroke:#EEF1F8;}
.d2-1843626214 .stroke-N7{stroke:#FFFFFF;}
.d2-1843626214 .stroke-B1{stroke:#0D32B2;}
.d2-1843626214 .stroke-B2{stroke:#0D32B2;}
.d2-1843626214 .stroke-B3{stroke:#E3E9FD;}
.d2-1843626214 .stroke-B4{stroke:#E3E9FD;}
.d2-1843626214 .stroke-B5{stroke:#EDF0FD;}
.d2-1843626214 .stroke-B6{stroke:#F7F8FE;}
.d2-1843626214 .stroke-AA2{stroke:#4A6FF3;}
.d2-1843626214 .stroke-AA4{stroke:#EDF0FD;}
.d2-1843626214 .stroke-AA5{stroke:#F7F8FE;}
.d2-1843626214 .stroke-AB4{stroke:#EDF0FD;}
.d2-1843626214 .stroke-AB5{stroke:#F7F8FE;}
.d2-1843626214 .background-color-N1{background-color:#0A0F25;}
.d2-1843626214 .background-color-N2{background-color:#676C7E;}
.d2-1843626214 .background-color-N3{background-color:#9499AB;}
.d2-1843626214 .background-color-N4{background-color:#CFD2DD;}
.d2-1843626214 .background-color-N5{background-color:#DEE1EB;}
.d2-1843626214 .background-color-N6{background-color:#EEF1F8;}
.d2-1843626214 .background-color-N7{background-color:#FFFFFF;}
.d2-1843626214 .background-color-B1{background-color:#0D32B2;}
.d2-1843626214 .background-color-B2{background-color:#0D32B2;}
.d2-1843626214 .background-color-B3{background-color:#E3E9FD;}
.d2-1843626214 .background-color-B4{background-color:#E3E9FD;}
.d2-1843626214 .background-color-B5{background-color:#EDF0FD;}
.d2-1843626214 .background-color-B6{background-color:#F7F8FE;}
.d2-1843626214 .background-color-AA2{background-color:#4A6FF3;}
.d2-1843626214 .background-color-AA4{background-color:#EDF0FD;}
.d2-1843626214 .background-color-AA5{background-color:#F7F8FE;}
.d2-1843626214 .background-color-AB4{background-color:#EDF0FD;}
.d2-1843626214 .background-color-AB5{background-color:#F7F8FE;}
.d2-1843626214 .color-N1{color:#0A0F25;}
.d2-1843626214 .color-N2{color:#676C7E;}
.d2-1843626214 .color-N3{color:#9499AB;}
.d2-1843626214 .color-N4{color:#CFD2DD;}
.d2-1843626214 .color-N5{color:#DEE1EB;}
.d2-1843626214 .color-N6{color:#EEF1F8;}
.d2-1843626214 .color-N7{color:#FFFFFF;}
.d2-1843626214 .color-B1{color:#0D32B2;}
.d2-1843626214 .color-B2{color:#0D32B2;}
.d2-1843626214 .color-B3{color:#E3E9FD;}
.d2-1843626214 .color-B4{color:#E3E9FD;}
.d2-1843626214 .color-B5{color:#EDF0FD;}
.d2-1843626214 .color-B6{color:#F7F8FE;}
.d2-1843626214 .color-AA2{color:#4A6FF3;}
.d2-1843626214 .color-AA4{color:#EDF0FD;}
.d2-1843626214 .color-AA5{color:#F7F8FE;}
.d2-1843626214 .color-AB4{color:#EDF0FD;}
.d2-1843626214 .color-AB5{color:#F7F8FE;}.appendix text.text{fill:#0A0F25}.md{--color-fg-default:#0A0F25;--color-fg-muted:#676C7E;--color-fg-subtle:#9499AB;--color-canvas-default:#FFFFFF;--color-canvas-subtle:#EEF1F8;--color-border-default:#0D32B2;--color-border-muted:#0D32B2;--color-neutral-muted:#EEF1F8;--color-accent-fg:#0D32B2;--color-accent-emphasis:#0D32B2;--color-attention-subtle:#676C7E;--color-danger-fg:red;}.sketch-overlay-B1{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-B2{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-B3{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-B4{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-B5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-B6{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AA2{fill:url(#streaks-dark);mix-blend-mode:overlay}.sketch-overlay-AA4{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AA5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AB4{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AB5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-N1{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-N2{fill:url(#streaks-dark);mix-blend-mode:overlay}.sketch-overlay-N3{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-N4{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-N5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-N6{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-N7{fill:url(#streaks-bright);mix-blend-mode:darken}.light-code{display: block}.dark-code{display: none}]]></style><g id="x"><g class="shape" ><rect x="1.000000" y="0.000000" width="53.000000" height="66.000000" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="27.500000" y="38.500000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">x</text></g><g id="y"><g class="shape" ><rect x="0.000000" y="166.000000" width="54.000000" height="66.000000" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="27.000000" y="204.500000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">y</text></g><g id="(x -&gt; y)[0]"><marker id="mk-3488378134" markerWidth="10.000000" markerHeight="12.000000" refX="7.000000" refY="6.000000" viewBox="0.000000 0.000000 10.000000 12.000000" orient="auto" markerUnits="userSpaceOnUse"> <polygon points="0.000000,0.000000 10.000000,6.000000 0.000000,12.000000" class="connection fill-B1" stroke-width="2" /> </marker><path d="M 27.000000 68.000000 C 27.000000 106.000000 27.000000 126.000000 27.000000 162.000000" fill="none" class="connection stroke-B1" style="stroke-width:2;" marker-end="url(#mk-3488378134)" mask="url(#d2-1843626214)" /></g><mask id="d2-1843626214" maskUnits="userSpaceOnUse" x="-101" y="-101" width="256" height="434">
<rect x="-101" y="-101" width="256" height="434" fill="white"></rect>
<rect x="23.500000" y="22.500000" width="8" height="21" fill="rgba(0,0,0,0.75)"></rect>
<rect x="22.500000" y="188.500000" width="9" height="21" fill="rgba(0,0,0,0.75)"></rect>

Before

Width:  |  Height:  |  Size: 9.4 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

Some files were not shown because too many files have changed in this diff Show more