@littledragon_wxl/drawio-style-graph 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/LICENSE +21 -0
  3. package/README.md +175 -0
  4. package/README_zh.md +171 -0
  5. package/SKILL.md +419 -0
  6. package/data/SHAPE-INDEX-NOTICE.md +17 -0
  7. package/data/lobe-icons.json +878 -0
  8. package/data/shape-index.json.gz +0 -0
  9. package/package.json +43 -0
  10. package/references/autolayout.md +125 -0
  11. package/references/diagram-types.md +83 -0
  12. package/references/shapes.md +151 -0
  13. package/references/style-application-guide.md +120 -0
  14. package/references/style-diagram-matrix.md +159 -0
  15. package/references/style-extraction.md +255 -0
  16. package/references/style-presets.md +110 -0
  17. package/references/styles/style-1-flat-icon.md +79 -0
  18. package/references/styles/style-2-dark-terminal.md +80 -0
  19. package/references/styles/style-3-blueprint.md +84 -0
  20. package/references/styles/style-4-notion-clean.md +78 -0
  21. package/references/styles/style-5-glassmorphism.md +85 -0
  22. package/references/styles/style-6-claude-official.md +84 -0
  23. package/references/styles/style-7-openai.md +94 -0
  24. package/references/styles/style-8-dark-luxury.md +109 -0
  25. package/references/troubleshooting.md +63 -0
  26. package/scripts/aiicons.py +201 -0
  27. package/scripts/autolayout.py +341 -0
  28. package/scripts/encode_drawio_url.py +58 -0
  29. package/scripts/goimports.py +141 -0
  30. package/scripts/jsimports.py +162 -0
  31. package/scripts/pyclasses.py +156 -0
  32. package/scripts/pyimports.py +153 -0
  33. package/scripts/repair_png.py +37 -0
  34. package/scripts/rustimports.py +203 -0
  35. package/scripts/shapesearch.py +162 -0
  36. package/scripts/validate.py +137 -0
  37. package/styles/built-in/corporate.json +49 -0
  38. package/styles/built-in/default.json +49 -0
  39. package/styles/built-in/handdrawn.json +49 -0
  40. package/styles/schema-drawio.json +112 -0
  41. package/styles/schema.json +213 -0
@@ -0,0 +1,203 @@
1
+ #!/usr/bin/env python3
2
+ """Extract a Rust crate's module-use graph as autolayout graph JSON.
3
+
4
+ The Rust counterpart to pyimports.py / jsimports.py / goimports.py. Treats each
5
+ .rs file as a module (path-derived: src/foo/bar.rs -> module foo::bar; main.rs /
6
+ lib.rs / mod.rs name the enclosing module), and records intra-crate `use` edges
7
+ resolved through Rust's path roots:
8
+
9
+ use crate::a::b::C; -> edge to module a::b
10
+ use super::sibling; -> resolved against the current module's parent
11
+ use self::child::Item;-> resolved against the current module
12
+ use other_crate::...; / use std::...; -> external, ignored
13
+
14
+ Brace groups (`use crate::a::{B, C};`, `use crate::{a, b};`) are expanded.
15
+ Transitive reduction is on by default so the diagram stays readable.
16
+
17
+ python3 rustimports.py ./mycrate --group -o graph.json
18
+ python3 autolayout.py graph.json -o diagram.drawio
19
+
20
+ Parsing is regex-based, not a full parser: inline `mod { ... }` blocks are not
21
+ split out, `#[cfg]`-gated modules are always included, and 2015-edition bare
22
+ intra-crate paths (without `crate::`) are not resolved.
23
+
24
+ Usage: python3 rustimports.py <crate_dir> [-o graph.json] [--direction TB|LR]
25
+ [--group] [--no-reduce]
26
+ """
27
+ import argparse
28
+ import json
29
+ import os
30
+ import re
31
+ import subprocess
32
+ import sys
33
+
34
+ USE = re.compile(r"\buse\s+([^;]+);")
35
+
36
+
37
+ def crate_name(root):
38
+ cargo = os.path.join(root, "Cargo.toml")
39
+ if os.path.exists(cargo):
40
+ m = re.search(r'(?m)^\s*name\s*=\s*"([^"]+)"',
41
+ open(cargo, encoding="utf-8", errors="ignore").read())
42
+ if m:
43
+ return m.group(1)
44
+ return "crate"
45
+
46
+
47
+ def discover(root):
48
+ """Map module path (tuple of segments; () is the crate root) -> file path."""
49
+ root = os.path.abspath(root)
50
+ src = os.path.join(root, "src") if os.path.isdir(os.path.join(root, "src")) else root
51
+ modules = {}
52
+ for dirpath, dirs, files in os.walk(src):
53
+ dirs[:] = [d for d in dirs if d != "target" and not d.startswith(".")]
54
+ for fn in files:
55
+ if not fn.endswith(".rs"):
56
+ continue
57
+ parts = os.path.relpath(os.path.join(dirpath, fn), src)[:-3].split(os.sep)
58
+ if parts[-1] == "mod":
59
+ parts = parts[:-1]
60
+ if len(parts) == 1 and parts[0] in ("main", "lib"):
61
+ parts = [] # crate root
62
+ modules[tuple(parts)] = os.path.join(dirpath, fn)
63
+ return modules, src
64
+
65
+
66
+ def split_top(inner):
67
+ """Split a brace group on top-level commas, ignoring nested braces."""
68
+ out, depth, cur = [], 0, ""
69
+ for ch in inner:
70
+ if ch == "{":
71
+ depth += 1
72
+ elif ch == "}":
73
+ depth -= 1
74
+ if ch == "," and depth == 0:
75
+ out.append(cur)
76
+ cur = ""
77
+ else:
78
+ cur += ch
79
+ if cur.strip():
80
+ out.append(cur)
81
+ return out
82
+
83
+
84
+ def base_segments(prefix, current):
85
+ """Classify a `use` path prefix into intra-crate base segments, or None."""
86
+ segs = [s for s in (p.strip() for p in prefix.split("::")) if s]
87
+ if not segs:
88
+ return None
89
+ if segs[0] == "crate":
90
+ return segs[1:]
91
+ if segs[0] == "self":
92
+ return list(current) + segs[1:]
93
+ if segs[0] == "super":
94
+ n = 0
95
+ while segs and segs[0] == "super":
96
+ n += 1
97
+ segs = segs[1:]
98
+ if n > len(current):
99
+ return None # climbs above the crate root
100
+ return list(current)[: len(current) - n] + segs
101
+ return None # std / external crate
102
+
103
+
104
+ def resolve(parts, modules, current):
105
+ """Longest known module prefix of `parts` (a tuple), or None."""
106
+ if not parts:
107
+ return () if () in modules and () != tuple(current) else None
108
+ p = list(parts)
109
+ while p:
110
+ if tuple(p) in modules and tuple(p) != tuple(current):
111
+ return tuple(p)
112
+ p = p[:-1]
113
+ return None
114
+
115
+
116
+ def edges_of(current, path, modules):
117
+ """Intra-crate module paths used by the module at `current`."""
118
+ found = set()
119
+ try:
120
+ src = open(path, encoding="utf-8", errors="ignore").read()
121
+ except OSError:
122
+ return found
123
+ for stmt in USE.findall(src):
124
+ if "{" in stmt:
125
+ prefix = stmt[: stmt.index("{")]
126
+ inner = stmt[stmt.index("{") + 1: stmt.rindex("}")] if "}" in stmt else ""
127
+ leaves = split_top(inner)
128
+ else:
129
+ prefix, leaves = stmt, [None]
130
+ base = base_segments(prefix, current)
131
+ if base is None:
132
+ continue
133
+ for leaf in leaves:
134
+ segs = list(base)
135
+ if leaf:
136
+ first = leaf.strip().split("::")[0].split()[0]
137
+ if first and first not in ("self", "*"):
138
+ segs.append(first)
139
+ target = resolve(tuple(segs), modules, current)
140
+ if target is not None and target != current:
141
+ found.add(target)
142
+ return found
143
+
144
+
145
+ def transitive_reduce(nodes, edges):
146
+ """Drop edges implied by a longer path, via Graphviz `tred`."""
147
+ idx = {n: i for i, n in enumerate(nodes)}
148
+ dot = "digraph{" + "".join(f"{idx[s]}->{idx[t]};" for s, t in edges) + "}"
149
+ try:
150
+ out = subprocess.run(["tred"], input=dot, capture_output=True,
151
+ text=True, check=True).stdout
152
+ except (FileNotFoundError, subprocess.CalledProcessError) as exc:
153
+ sys.stderr.write(f"warning: tred unavailable, keeping all edges ({exc})\n")
154
+ return edges
155
+ rev = {i: n for n, i in idx.items()}
156
+ return [(rev[int(a)], rev[int(b)]) for a, b in re.findall(r"(\d+)\s*->\s*(\d+)", out)]
157
+
158
+
159
+ def main():
160
+ ap = argparse.ArgumentParser(description="Rust module-use graph -> autolayout graph JSON.")
161
+ ap.add_argument("crate", help="crate directory (contains Cargo.toml and/or src/)")
162
+ ap.add_argument("-o", "--output", help="output JSON path (default: stdout)")
163
+ ap.add_argument("--direction", default="TB", choices=["TB", "LR"])
164
+ ap.add_argument("--group", action="store_true",
165
+ help="box modules by their parent module path (nested)")
166
+ ap.add_argument("--no-reduce", action="store_true",
167
+ help="keep every edge (skip transitive reduction)")
168
+ args = ap.parse_args()
169
+
170
+ modules, _ = discover(args.crate)
171
+ if not modules:
172
+ sys.exit(f"error: no .rs modules found under {args.crate}")
173
+ name = crate_name(args.crate)
174
+ mid = lambda parts: name if not parts else "::".join(parts)
175
+ edges = sorted({(mid(m), mid(t)) for m, path in modules.items()
176
+ for t in edges_of(m, path, modules)})
177
+ raw = len(edges)
178
+ if not args.no_reduce:
179
+ edges = transitive_reduce([mid(m) for m in modules], edges)
180
+
181
+ def node(parts):
182
+ d = {"id": mid(parts), "label": name if not parts else parts[-1]}
183
+ if args.group and len(parts) > 1:
184
+ d["group"] = "/".join(parts[:-1]) # parent module path -> nested boxes
185
+ return d
186
+
187
+ graph = {
188
+ "direction": args.direction,
189
+ "nodes": [node(m) for m in modules],
190
+ "edges": [{"source": s, "target": t} for s, t in edges],
191
+ }
192
+ text = json.dumps(graph, indent=2)
193
+ if args.output:
194
+ open(args.output, "w", encoding="utf-8").write(text)
195
+ sys.stderr.write(f"wrote {args.output}\n")
196
+ else:
197
+ sys.stdout.write(text)
198
+ note = "" if args.no_reduce else f" (reduced from {raw})"
199
+ sys.stderr.write(f"{len(modules)} modules, {len(edges)} edges{note}\n")
200
+
201
+
202
+ if __name__ == "__main__":
203
+ main()
@@ -0,0 +1,162 @@
1
+ #!/usr/bin/env python3
2
+ """Search 10k+ official draw.io shapes for their exact style strings.
3
+
4
+ Resolves a keyword query (e.g. "aws lambda", "uml actor", "k8s pod") to the
5
+ matching palette shapes so a diagram can use the real draw.io `style=` string
6
+ instead of a hand-guessed one. Covers AWS / Azure / GCP / Cisco / Kubernetes /
7
+ UML / BPMN / P&ID / electrical / flowchart / network / general shape sets.
8
+
9
+ Based on the search in jgraph/drawio-mcp (Apache-2.0): tag map with exact +
10
+ Soundex matching, strict AND first, scored OR fallback. The matched set is
11
+ identical to upstream; the one addition is a tiebreaker that, among shapes with
12
+ the same tag score, prefers ones whose title contains the query terms verbatim
13
+ (so "dynamodb" returns the shape titled "DynamoDB", not a neighbor merely tagged
14
+ with it). The bundled index (data/shape-index.json.gz) is the upstream draw.io
15
+ shape data — see data/SHAPE-INDEX-NOTICE.md.
16
+
17
+ Usage:
18
+ python3 shapesearch.py "aws lambda" [--limit N] [--json]
19
+ """
20
+ import argparse
21
+ import gzip
22
+ import json
23
+ import os
24
+ import re
25
+ import sys
26
+
27
+ INDEX = os.path.join(os.path.dirname(__file__), "..", "data", "shape-index.json.gz")
28
+ _SOUNDEX_MAP = "01230120022455012603010202" # A..Z digit codes
29
+ _TRAIL = re.compile(r"\.*\d*$") # strip trailing digits/dots before soundex
30
+
31
+
32
+ def soundex(name):
33
+ if not name:
34
+ return ""
35
+ s = [name[0].upper()]
36
+ si = 1
37
+ for ch in name[1:]:
38
+ c = ord(ch.upper()) - 65
39
+ if 0 <= c <= 25 and _SOUNDEX_MAP[c] != "0":
40
+ code = _SOUNDEX_MAP[c]
41
+ if code != s[si - 1]:
42
+ s.append(code)
43
+ si += 1
44
+ if si > 3:
45
+ break
46
+ s += ["0"] * (4 - len(s))
47
+ return "".join(s[:4])
48
+
49
+
50
+ def build_tag_map(shapes):
51
+ """tag (and its Soundex) -> set of shape indices."""
52
+ tag_map = {}
53
+ for i, shape in enumerate(shapes):
54
+ raw = shape.get("tags")
55
+ if not raw:
56
+ continue
57
+ seen = set()
58
+ for token in re.sub(r"[/,()]", " ", raw.lower()).split(" "):
59
+ if len(token) < 2 or token in seen:
60
+ continue
61
+ seen.add(token)
62
+ tag_map.setdefault(token, set()).add(i)
63
+ sx = soundex(_TRAIL.sub("", token))
64
+ if sx and sx != token and sx not in seen:
65
+ seen.add(sx)
66
+ tag_map.setdefault(sx, set()).add(i)
67
+ return tag_map
68
+
69
+
70
+ def split_compound(token):
71
+ """'pid2misc' -> ['pid','misc']; 'discInst' -> ['disc','inst']."""
72
+ spaced = re.sub(r"([a-z])([A-Z])", r"\1 \2", token)
73
+ spaced = re.sub(r"([a-zA-Z])(\d)", r"\1 \2", spaced)
74
+ spaced = re.sub(r"(\d)([a-zA-Z])", r"\1 \2", spaced)
75
+ return [p for p in spaced.lower().split() if len(p) >= 2]
76
+
77
+
78
+ def match_term(tag_map, term):
79
+ exact = set(tag_map.get(term, set()))
80
+ phonetic = set()
81
+ sx = soundex(_TRAIL.sub("", term))
82
+ if sx and sx != term:
83
+ phonetic = {i for i in tag_map.get(sx, set()) if i not in exact}
84
+ return exact, phonetic
85
+
86
+
87
+ def search(shapes, tag_map, query, limit):
88
+ if not query:
89
+ return []
90
+ terms, seen = [], set()
91
+ for raw in query.lower().split():
92
+ subs = split_compound(raw) or ([raw] if len(raw) >= 2 else [])
93
+ for t in subs:
94
+ if t not in seen:
95
+ seen.add(t)
96
+ terms.append(t)
97
+ if not terms:
98
+ return []
99
+
100
+ term_matches = [match_term(tag_map, t) for t in terms]
101
+
102
+ # Strict AND across all terms first.
103
+ and_set = None
104
+ for exact, phonetic in term_matches:
105
+ combined = exact | phonetic
106
+ and_set = combined if and_set is None else (and_set & combined)
107
+ if not and_set:
108
+ break
109
+
110
+ # Score: +1.0 exact, +0.5 Soundex-only, per term. AND results if any, else OR.
111
+ scores = {}
112
+ pool = and_set if and_set else None
113
+ for exact, phonetic in term_matches:
114
+ for idx in exact:
115
+ if pool is None or idx in pool:
116
+ scores[idx] = scores.get(idx, 0) + 1.0
117
+ for idx in phonetic:
118
+ if (pool is None or idx in pool) and idx not in exact:
119
+ scores[idx] = scores.get(idx, 0) + 0.5
120
+
121
+ # Rank by tag score desc, then by how many query terms appear verbatim in the
122
+ # title, then casefolded title, then index. The title-hit tiebreak (our one
123
+ # addition over upstream) only reorders *within* an equal tag-score group, so
124
+ # e.g. the shape literally titled "DynamoDB" ranks above a neighbor that is
125
+ # merely tagged `dynamodb` (like "Attribute"). The trailing index keeps ties
126
+ # deterministic.
127
+ term_set = set(terms)
128
+
129
+ def title_hits(idx):
130
+ toks = set(re.split(r"[^a-z0-9]+", shapes[idx].get("title", "").casefold()))
131
+ return len(term_set & toks)
132
+
133
+ ranked = sorted(scores, key=lambda i: (-scores[i], -title_hits(i),
134
+ shapes[i].get("title", "").casefold(), i))
135
+ return [{"style": shapes[i]["style"], "w": shapes[i]["w"],
136
+ "h": shapes[i]["h"], "title": shapes[i]["title"]} for i in ranked[:limit]]
137
+
138
+
139
+ def main():
140
+ ap = argparse.ArgumentParser(description="Search official draw.io shapes for their style strings.")
141
+ ap.add_argument("query", help='keywords, e.g. "aws lambda" or "uml actor"')
142
+ ap.add_argument("--limit", type=int, default=10)
143
+ ap.add_argument("--json", action="store_true", help="emit JSON instead of a table")
144
+ args = ap.parse_args()
145
+
146
+ if not os.path.exists(INDEX):
147
+ sys.exit(f"error: shape index not found at {INDEX}")
148
+ with gzip.open(INDEX, "rt", encoding="utf-8") as f:
149
+ shapes = json.load(f)
150
+
151
+ results = search(shapes, build_tag_map(shapes), args.query, args.limit)
152
+ if not results:
153
+ sys.exit(f"no shapes matched {args.query!r}")
154
+ if args.json:
155
+ print(json.dumps(results, indent=2, ensure_ascii=False))
156
+ else:
157
+ for r in results:
158
+ print(f"{r['title']} ({r['w']}x{r['h']})\n {r['style']}")
159
+
160
+
161
+ if __name__ == "__main__":
162
+ main()
@@ -0,0 +1,137 @@
1
+ #!/usr/bin/env python3
2
+ """Deterministic structural linter for .drawio files.
3
+
4
+ Catches the class of mistakes a vision self-check is slow and unreliable at:
5
+ dangling edge endpoints, duplicate or reserved ids, broken parent references,
6
+ and (as warnings) off-grid geometry and overlapping sibling nodes. Runs without
7
+ launching draw.io, so it is a fast pre-check before the visual review step.
8
+
9
+ python3 validate.py diagram.drawio
10
+
11
+ Exit status is non-zero when any error (or, with --strict, any warning) is
12
+ found, so it can gate a workflow. Compressed (non-XML) diagram pages are
13
+ skipped with a warning — this skill always writes uncompressed XML.
14
+
15
+ Usage: python3 validate.py <file.drawio> [--strict]
16
+ """
17
+ import argparse
18
+ import sys
19
+ import xml.etree.ElementTree as ET
20
+
21
+ RESERVED = {"0", "1"}
22
+
23
+
24
+ def rect(cell):
25
+ """Return (x, y, w, h) floats for a cell's geometry, or None if absent/bad.
26
+
27
+ x/y default to 0 when omitted: draw.io treats a missing position as the
28
+ origin, and container-managed children (table rows, swimlane/UML-class
29
+ lines under tableLayout) legitimately omit x/y while keeping width/height.
30
+ Only width/height are required to be present and numeric.
31
+ """
32
+ g = cell.find("mxGeometry")
33
+ if g is None:
34
+ return None
35
+ try:
36
+ return (float(g.get("x", "0")), float(g.get("y", "0")),
37
+ float(g.get("width", "nan")), float(g.get("height", "nan")))
38
+ except ValueError:
39
+ return None
40
+
41
+
42
+ def is_edge_label(cell):
43
+ """True for a draw.io edge label / relative-positioned child vertex.
44
+
45
+ These legitimately omit width/height: their position is given relative to a
46
+ parent edge (style ``edgeLabel``) or via ``relative="1"`` geometry. Treating
47
+ them as normal vertices wrongly flags them as missing/invalid geometry.
48
+ """
49
+ if "edgeLabel" in (cell.get("style") or ""):
50
+ return True
51
+ g = cell.find("mxGeometry")
52
+ return g is not None and g.get("relative") == "1"
53
+
54
+
55
+ def overlap(a, b):
56
+ ax, ay, aw, ah = a
57
+ bx, by, bw, bh = b
58
+ return ax < bx + bw and bx < ax + aw and ay < by + bh and by < ay + ah
59
+
60
+
61
+ def check_page(diagram):
62
+ """Return (errors, warnings) for one <diagram> page."""
63
+ name = diagram.get("name", "?")
64
+ model = diagram.find("mxGraphModel")
65
+ if model is None:
66
+ if (diagram.text or "").strip():
67
+ return [], [f"page {name!r}: compressed, skipped (cannot lint)"]
68
+ return [f"page {name!r}: no <mxGraphModel>"], []
69
+ root = model.find("root")
70
+ cells = root.findall("mxCell") if root is not None else []
71
+ errors, warns = [], []
72
+ ids = {}
73
+ for c in cells:
74
+ cid = c.get("id")
75
+ if cid in ids:
76
+ errors.append(f"duplicate id {cid!r}")
77
+ ids[cid] = c
78
+ parents = {c.get("parent") for c in cells} # ids that have children
79
+ for c in cells:
80
+ cid, parent = c.get("id"), c.get("parent")
81
+ is_v, is_e = c.get("vertex") == "1", c.get("edge") == "1"
82
+ if parent is not None and parent not in ids:
83
+ errors.append(f"cell {cid!r} parent {parent!r} does not exist")
84
+ for end in ("source", "target"):
85
+ ref = c.get(end)
86
+ if ref and ref not in ids:
87
+ errors.append(f"edge {cid!r} {end} {ref!r} does not exist")
88
+ if (is_v or is_e) and cid in RESERVED:
89
+ errors.append(f"cell {cid!r} reuses reserved id 0/1")
90
+ if is_v and not is_edge_label(c):
91
+ r = rect(c)
92
+ if r is None or any(v != v for v in r): # None or NaN
93
+ errors.append(f"vertex {cid!r} has missing/invalid geometry")
94
+ else:
95
+ x, y, w, h = r
96
+ if w <= 0 or h <= 0:
97
+ warns.append(f"vertex {cid!r} non-positive size {w:g}x{h:g}")
98
+ if x < 0 or y < 0:
99
+ warns.append(f"vertex {cid!r} negative position ({x:g},{y:g})")
100
+ # Sibling overlap: only leaf vertices (containers legitimately wrap children).
101
+ boxes = [(c.get("id"), c.get("parent"), rect(c)) for c in cells
102
+ if c.get("vertex") == "1" and c.get("id") not in parents and rect(c)
103
+ and not any(v != v for v in rect(c))]
104
+ for i in range(len(boxes)):
105
+ for j in range(i + 1, len(boxes)):
106
+ (ia, pa, ra), (ib, pb, rb) = boxes[i], boxes[j]
107
+ if pa == pb and overlap(ra, rb):
108
+ warns.append(f"vertices {ia!r} and {ib!r} overlap")
109
+ return errors, warns
110
+
111
+
112
+ def main():
113
+ ap = argparse.ArgumentParser(description="Lint a .drawio file for structural errors.")
114
+ ap.add_argument("file")
115
+ ap.add_argument("--strict", action="store_true", help="treat warnings as failure too")
116
+ args = ap.parse_args()
117
+ try:
118
+ tree = ET.parse(args.file)
119
+ except (ET.ParseError, OSError) as exc:
120
+ sys.exit(f"error: cannot parse {args.file}: {exc}")
121
+ pages = tree.getroot().findall("diagram") or [tree.getroot()]
122
+ errors, warns = [], []
123
+ for page in pages:
124
+ e, w = check_page(page)
125
+ errors += e
126
+ warns += w
127
+ for w in warns:
128
+ print(f"warning: {w}")
129
+ for e in errors:
130
+ print(f"error: {e}")
131
+ print(f"{len(errors)} error(s), {len(warns)} warning(s)")
132
+ if errors or (args.strict and warns):
133
+ sys.exit(1)
134
+
135
+
136
+ if __name__ == "__main__":
137
+ main()
@@ -0,0 +1,49 @@
1
+ {
2
+ "$schema": "../schema.json",
3
+ "name": "corporate",
4
+ "version": 1,
5
+ "default": false,
6
+ "source": { "type": "built-in" },
7
+ "confidence": "high",
8
+ "palette": {
9
+ "primary": { "fillColor": "#e3f2fd", "strokeColor": "#1565c0" },
10
+ "success": { "fillColor": "#e8f5e9", "strokeColor": "#2e7d32" },
11
+ "warning": { "fillColor": "#fff9c4", "strokeColor": "#f57c00" },
12
+ "accent": { "fillColor": "#fff3e0", "strokeColor": "#e65100" },
13
+ "danger": { "fillColor": "#ffebee", "strokeColor": "#c62828" },
14
+ "neutral": { "fillColor": "#eceff1", "strokeColor": "#455a64" },
15
+ "secondary": { "fillColor": "#f3e5f5", "strokeColor": "#6a1b9a" }
16
+ },
17
+ "roles": {
18
+ "service": "primary",
19
+ "database": "success",
20
+ "queue": "warning",
21
+ "gateway": "accent",
22
+ "error": "danger",
23
+ "external": "neutral",
24
+ "security": "secondary"
25
+ },
26
+ "shapes": {
27
+ "service": "rounded=0",
28
+ "database": "shape=cylinder3",
29
+ "queue": "rounded=0",
30
+ "decision": "rhombus",
31
+ "external": "rounded=0;dashed=1",
32
+ "container": "swimlane;startSize=30"
33
+ },
34
+ "font": {
35
+ "fontFamily": "Arial",
36
+ "fontSize": 11,
37
+ "titleFontSize": 13,
38
+ "titleBold": true
39
+ },
40
+ "edges": {
41
+ "style": "edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1",
42
+ "arrow": "endArrow=classic;endFill=1",
43
+ "dashedFor": ["optional", "async"]
44
+ },
45
+ "extras": {
46
+ "sketch": false,
47
+ "globalStrokeWidth": 1
48
+ }
49
+ }
@@ -0,0 +1,49 @@
1
+ {
2
+ "$schema": "../schema.json",
3
+ "name": "default",
4
+ "version": 1,
5
+ "default": false,
6
+ "source": { "type": "built-in" },
7
+ "confidence": "high",
8
+ "palette": {
9
+ "primary": { "fillColor": "#dae8fc", "strokeColor": "#6c8ebf" },
10
+ "success": { "fillColor": "#d5e8d4", "strokeColor": "#82b366" },
11
+ "warning": { "fillColor": "#fff2cc", "strokeColor": "#d6b656" },
12
+ "accent": { "fillColor": "#ffe6cc", "strokeColor": "#d79b00" },
13
+ "danger": { "fillColor": "#f8cecc", "strokeColor": "#b85450" },
14
+ "neutral": { "fillColor": "#f5f5f5", "strokeColor": "#666666" },
15
+ "secondary": { "fillColor": "#e1d5e7", "strokeColor": "#9673a6" }
16
+ },
17
+ "roles": {
18
+ "service": "primary",
19
+ "database": "success",
20
+ "queue": "warning",
21
+ "gateway": "accent",
22
+ "error": "danger",
23
+ "external": "neutral",
24
+ "security": "secondary"
25
+ },
26
+ "shapes": {
27
+ "service": "rounded=1",
28
+ "database": "shape=cylinder3",
29
+ "queue": "rounded=1",
30
+ "decision": "rhombus",
31
+ "external": "rounded=1;dashed=1",
32
+ "container": "swimlane;startSize=30"
33
+ },
34
+ "font": {
35
+ "fontFamily": "Helvetica",
36
+ "fontSize": 12,
37
+ "titleFontSize": 14,
38
+ "titleBold": true
39
+ },
40
+ "edges": {
41
+ "style": "edgeStyle=orthogonalEdgeStyle;rounded=1;orthogonalLoop=1;jettySize=auto;html=1",
42
+ "arrow": "endArrow=classic;endFill=1",
43
+ "dashedFor": []
44
+ },
45
+ "extras": {
46
+ "sketch": false,
47
+ "globalStrokeWidth": 1
48
+ }
49
+ }
@@ -0,0 +1,49 @@
1
+ {
2
+ "$schema": "../schema.json",
3
+ "name": "handdrawn",
4
+ "version": 1,
5
+ "default": false,
6
+ "source": { "type": "built-in" },
7
+ "confidence": "high",
8
+ "palette": {
9
+ "primary": { "fillColor": "#ffe4b5", "strokeColor": "#b8651e" },
10
+ "success": { "fillColor": "#def0dc", "strokeColor": "#5c8a49" },
11
+ "warning": { "fillColor": "#fff4cc", "strokeColor": "#b8901a" },
12
+ "accent": { "fillColor": "#ffd9b3", "strokeColor": "#c25100" },
13
+ "danger": { "fillColor": "#ffcdbf", "strokeColor": "#a53d3d" },
14
+ "neutral": { "fillColor": "#f5e6d3", "strokeColor": "#8b7355" },
15
+ "secondary": { "fillColor": "#e6d7e8", "strokeColor": "#7b4397" }
16
+ },
17
+ "roles": {
18
+ "service": "primary",
19
+ "database": "success",
20
+ "queue": "warning",
21
+ "gateway": "accent",
22
+ "error": "danger",
23
+ "external": "neutral",
24
+ "security": "secondary"
25
+ },
26
+ "shapes": {
27
+ "service": "rounded=1",
28
+ "database": "shape=cylinder3",
29
+ "queue": "rounded=1",
30
+ "decision": "rhombus",
31
+ "external": "rounded=1;dashed=1",
32
+ "container": "swimlane;startSize=30"
33
+ },
34
+ "font": {
35
+ "fontFamily": "Helvetica",
36
+ "fontSize": 12,
37
+ "titleFontSize": 14,
38
+ "titleBold": true
39
+ },
40
+ "edges": {
41
+ "style": "edgeStyle=orthogonalEdgeStyle;curved=1;rounded=1;orthogonalLoop=1;jettySize=auto;html=1",
42
+ "arrow": "endArrow=classic;endFill=1",
43
+ "dashedFor": ["optional"]
44
+ },
45
+ "extras": {
46
+ "sketch": true,
47
+ "globalStrokeWidth": 2
48
+ }
49
+ }