1use ascii_dag::render::colors::Palette;
13use ascii_dag::{
14 EdgePathView, Graph, LabelSlot, LineWeight, NodeKind, RenderOptions, ScenePlanner,
15};
16use std::fmt::Write;
17
18const CW: usize = 10;
20const CH: usize = 20;
21
22fn px(cell: (usize, usize)) -> (usize, usize) {
23 (cell.0 * CW + CW / 2, cell.1 * CH + CH / 2)
24}
25
26fn css(color: ascii_dag::CellColor) -> String {
27 match color.as_rgb() {
28 Some((r, g, b)) => format!("rgb({r},{g},{b})"),
29 None => "currentColor".into(),
30 }
31}
32
33fn main() {
34 let mut g = Graph::new();
35 g.add_node(1usize, "ingest");
36 g.add_node(2usize, "parse");
37 g.add_node(3usize, "check");
38 g.add_node(4usize, "emit");
39 g.add_edge(1usize, 2usize, Some("raw"));
40 g.add_edge(2usize, 3usize, None);
41 g.add_edge(2usize, 4usize, Some("fast path"));
42 g.add_edge(3usize, 4usize, None);
43 let sg = g.add_subgraph("backend");
44 g.put_nodes(&[2, 3]).inside(sg).unwrap();
45
46 let ir = g.compute_layout();
47
48 let mut planner = ScenePlanner::new();
50 let scene = planner
51 .plan(&ir, &RenderOptions::colored(Palette::Ansi).plan)
52 .quiet()
53 .expect("plan");
54
55 let mut svg = String::new();
56 let (w, h) = (scene.width() * CW, scene.height() * CH + 24);
57 let _ = writeln!(
58 svg,
59 r#"<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {w} {h}" font-family="monospace" font-size="13">"#
60 );
61
62 for s in scene.subgraphs() {
64 let _ = writeln!(
65 svg,
66 r#" <rect x="{}" y="{}" width="{}" height="{}" fill="none" stroke="{}" stroke-dasharray="4 3" rx="6"/>"#,
67 s.x * CW,
68 s.y * CH,
69 s.width * CW,
70 s.height * CH,
71 css(s.color),
72 );
73 let _ = writeln!(
74 svg,
75 r#" <text x="{}" y="{}" opacity="0.7">{}</text>"#,
76 s.x * CW + 8,
77 s.y * CH + 15,
78 s.label,
79 );
80 }
81
82 for e in scene.edges() {
87 let mut pts = vec![px(e.from)];
88 match e.path {
89 EdgePathView::Direct => {}
90 EdgePathView::Corner { bend_at } => {
91 pts.push(px((e.from.0, bend_at)));
92 pts.push(px((e.to.0, bend_at)));
93 }
94 EdgePathView::SideChannel {
95 channel_at,
96 span_start,
97 span_end,
98 } => {
99 pts.push(px((e.from.0, span_start)));
100 pts.push(px((channel_at, span_start)));
101 pts.push(px((channel_at, span_end)));
102 pts.push(px((e.to.0, span_end)));
103 }
104 EdgePathView::MultiSegment { waypoints, .. } => {
105 pts.extend(waypoints.iter().map(|&p| px(p)));
106 }
107 _ => {}
108 }
109 pts.push(px(e.to));
110 let points: Vec<String> = pts.iter().map(|(x, y)| format!("{x},{y}")).collect();
111 let dash = match e.weight {
112 LineWeight::Dashed => r#" stroke-dasharray="5 4""#,
113 _ => "",
114 };
115 let _ = writeln!(
116 svg,
117 r#" <polyline points="{}" fill="none" stroke="{}"{dash} marker-end="url(#arrow)"/>"#,
118 points.join(" "),
119 css(e.color),
120 );
121 if let Some(label) = e.label {
124 if let LabelSlot::Inline { x, y, .. } = label.slot {
125 let _ = writeln!(
126 svg,
127 r#" <text x="{}" y="{}" fill="{}">{}</text>"#,
128 x * CW,
129 y * CH + 14,
130 css(label.color),
131 label.text,
132 );
133 }
134 }
135 }
136
137 for n in scene.nodes() {
139 if matches!(n.kind, NodeKind::Dummy) {
140 continue;
141 }
142 let _ = writeln!(
143 svg,
144 r#" <rect x="{}" y="{}" width="{}" height="{}" fill="white" stroke="black" rx="3"/>"#,
145 n.x * CW,
146 n.y * CH,
147 n.width * CW,
148 n.height.max(1) * CH,
149 );
150 let _ = writeln!(
151 svg,
152 r#" <text x="{}" y="{}" text-anchor="middle">{}</text>"#,
153 (n.x + n.width / 2) * CW,
154 n.y * CH + 15,
155 n.label,
156 );
157 }
158
159 for (i, e) in scene.legend().enumerate() {
161 if let Some(label) = e.label {
162 let _ = writeln!(
163 svg,
164 r#" <text x="4" y="{}" fill="{}">{} → {}: {}</text>"#,
165 scene.height() * CH + 16 + i * 16,
166 css(label.color),
167 e.from_id,
168 e.to_id,
169 label.text,
170 );
171 }
172 }
173
174 let _ = writeln!(
175 svg,
176 r#" <defs><marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M 0 0 L 10 5 L 0 10 z"/></marker></defs>"#
177 );
178 let _ = writeln!(svg, "</svg>");
179 println!("{svg}");
180}