1use std::fmt::Display;
2
3use recursion::{Collapsible, Expandable, MappableFrame};
4
5pub trait CollapsibleVizExt: Collapsible
7where
8 Self: Sized + Display,
9 <Self::FrameToken as MappableFrame>::Frame<()>: Display,
10{
11 fn collapse_frames_v<Out>(
12 self,
13 collapse_frame: impl FnMut(<Self::FrameToken as MappableFrame>::Frame<Out>) -> Out,
14 ) -> (Out, Viz)
15 where
16 Out: Display;
17
18 fn try_collapse_frames_v<Out, E: Display>(
19 self,
20 collapse_frame: impl FnMut(<Self::FrameToken as MappableFrame>::Frame<Out>) -> Result<Out, E>,
21 ) -> (Result<Out, E>, Viz)
22 where
23 Out: Display;
24}
25
26impl<X: Collapsible> CollapsibleVizExt for X
27where
28 Self: Sized + Display,
29 <Self::FrameToken as MappableFrame>::Frame<()>: Display,
30{
31 fn collapse_frames_v<Out>(
32 self,
33 collapse_frame: impl FnMut(<Self::FrameToken as MappableFrame>::Frame<Out>) -> Out,
34 ) -> (Out, Viz)
35 where
36 Out: Display,
37 {
38 expand_and_collapse_v::<Self::FrameToken, Self, Out>(self, Self::into_frame, collapse_frame)
39 }
40
41 fn try_collapse_frames_v<Out, E: Display>(
42 self,
43 collapse_frame: impl FnMut(<Self::FrameToken as MappableFrame>::Frame<Out>) -> Result<Out, E>,
44 ) -> (Result<Out, E>, Viz)
45 where
46 Out: Display,
47 {
48 try_expand_and_collapse_v::<Self::FrameToken, Self, Out, E>(
49 self,
50 |x| Ok(Self::into_frame(x)),
51 collapse_frame,
52 )
53 }
54}
55
56pub trait ExpandableVizExt: Expandable
57where
58 Self: Sized + Display,
59 <Self::FrameToken as MappableFrame>::Frame<()>: Display,
60{
61 fn expand_frames_v<In>(
62 input: In,
63 expand_frame: impl FnMut(In) -> <Self::FrameToken as MappableFrame>::Frame<In>,
64 ) -> (Self, Viz)
65 where
66 In: Display;
67}
68
69impl<X: Expandable> ExpandableVizExt for X
70where
71 Self: Sized + Display,
72 <Self::FrameToken as MappableFrame>::Frame<()>: Display,
73{
74 fn expand_frames_v<In>(
75 input: In,
76 expand_frame: impl FnMut(In) -> <Self::FrameToken as MappableFrame>::Frame<In>,
77 ) -> (Self, Viz)
78 where
79 In: Display,
80 {
81 expand_and_collapse_v::<Self::FrameToken, In, Self>(input, expand_frame, Self::from_frame)
82 }
83}
84
85type VizNodeId = u32;
86
87#[derive(Clone)]
88pub enum VizAction {
89 ExpandSeed {
91 target_id: VizNodeId,
92 txt: String,
93 seeds: Vec<(VizNodeId, String)>,
94 },
95 CollapseNode {
97 target_id: VizNodeId,
98 txt: String,
99 },
100 InfoCard {
102 info_header: String,
103 info_txt: String,
104 },
105}
106
107#[derive(Clone)]
124pub struct Viz {
125 seed_txt: String,
126 root_id: VizNodeId,
127 actions: Vec<VizAction>,
128}
129
130impl Viz {
131 pub fn label(mut self, info_header: String, info_txt: String) -> Self {
132 let mut actions = vec![VizAction::InfoCard {
133 info_header,
134 info_txt,
135 }];
136 actions.extend(self.actions.into_iter());
137 self.actions = actions;
138
139 self
140 }
141
142 pub fn fuse(self, next: Self, info_header: String, info_txt: String) -> Self {
143 let mut actions = self.actions;
144 actions.push(VizAction::InfoCard {
145 info_txt,
146 info_header,
147 });
148 actions.extend(next.actions.into_iter());
149
150 Self {
151 seed_txt: self.seed_txt,
152 root_id: self.root_id,
153 actions,
154 }
155 }
156
157 pub fn write(self, path: String) {
158 let to_write = serialize_html(self).unwrap();
159
160 println!("write to: {:?}", path);
161
162 std::fs::write(path, to_write).unwrap();
163 }
164}
165
166pub fn serialize_html(v: Viz) -> serde_json::Result<String> {
168 let mut out = String::new();
169 out.push_str(TEMPLATE_BEFORE);
170 out.push_str(&serialize_json(v)?);
171 out.push_str(TEMPLATE_AFTER);
172
173 Ok(out)
174}
175
176pub fn serialize_json(v: Viz) -> serde_json::Result<String> {
177 use serde_json::value::Value;
178 let actions: Vec<Value> = v
179 .actions
180 .into_iter()
181 .map(|elem| match elem {
182 VizAction::ExpandSeed {
183 target_id,
184 txt,
185 seeds,
186 } => {
187 let mut h = serde_json::Map::new();
188 h.insert(
189 "target_id".to_string(),
190 Value::String(target_id.to_string()),
191 );
192 h.insert("txt".to_string(), Value::String(txt));
193 let mut json_seeds = Vec::new();
194 for (node_id, txt) in seeds.into_iter() {
195 let mut h = serde_json::Map::new();
196 h.insert("node_id".to_string(), Value::String(node_id.to_string()));
197 h.insert("txt".to_string(), Value::String(txt));
198 json_seeds.push(Value::Object(h));
199 }
200 h.insert("seeds".to_string(), Value::Array(json_seeds));
201 Value::Object(h)
202 }
203 VizAction::CollapseNode { target_id, txt } => {
204 let mut h = serde_json::Map::new();
205 h.insert(
206 "target_id".to_string(),
207 Value::String(target_id.to_string()),
208 );
209 h.insert("txt".to_string(), Value::String(txt));
210 Value::Object(h)
211 }
212 VizAction::InfoCard {
213 info_txt,
214 info_header,
215 } => {
216 let mut h = serde_json::Map::new();
217 h.insert("info_txt".to_string(), Value::String(info_txt.to_string()));
218 h.insert(
219 "info_header".to_string(),
220 Value::String(info_header.to_string()),
221 );
222 h.insert("typ".to_string(), Value::String("info_card".to_string()));
223 Value::Object(h)
224 }
225 })
226 .collect();
227
228 let viz_root = {
229 let mut h = serde_json::Map::new();
230 h.insert("node_id".to_string(), Value::String(v.root_id.to_string()));
231 h.insert("txt".to_string(), Value::String(v.seed_txt));
232 h.insert("typ".to_string(), Value::String("seed".to_string()));
233 Value::Object(h)
234 };
235
236 let viz_js = {
237 let mut h = serde_json::Map::new();
238 h.insert("root".to_string(), viz_root);
239 h.insert("actions".to_string(), Value::Array(actions));
240 Value::Object(h)
241 };
242
243 serde_json::to_string(&viz_js)
244}
245
246pub fn try_expand_and_collapse_v<F, Seed, Out, E>(
247 seed: Seed,
248 mut coalg: impl FnMut(Seed) -> Result<F::Frame<Seed>, E>,
249 mut alg: impl FnMut(F::Frame<Out>) -> Result<Out, E>,
250) -> (Result<Out, E>, Viz)
251where
252 F: MappableFrame,
253 E: Display,
254 F::Frame<()>: Display,
255 Seed: Display,
256 Out: Display,
257{
258 enum State<Pre, Post> {
259 PreVisit(Pre),
260 PostVisit(Post),
261 }
262
263 let mut keygen = 1; let mut v = Vec::new();
265 let root_seed_txt = format!("{}", seed);
266
267 let mut vals: Vec<Out> = vec![];
268 let mut todo: Vec<State<(VizNodeId, Seed), _>> = vec![State::PreVisit((0, seed))];
269
270 while let Some(item) = todo.pop() {
271 match item {
272 State::PreVisit((viz_node_id, seed)) => {
273 let mut seeds_v = Vec::new();
274
275 let node = match coalg(seed) {
276 Ok(node) => node,
277 Err(e) => {
278 v.push(VizAction::InfoCard {
279 info_header: "Error during expand!".to_string(),
280 info_txt: format!("error: {}", e),
281 });
282 return (
283 Err(e),
284 Viz {
285 seed_txt: root_seed_txt,
286 root_id: 0,
287 actions: v,
288 },
289 );
290 }
291 };
292 let mut topush = Vec::new();
293 let node = F::map_frame(node, |seed| {
294 let k = keygen;
295 keygen += 1;
296 seeds_v.push((k, format!("{}", seed)));
297
298 topush.push(State::PreVisit((k, seed)))
299 });
300
301 v.push(VizAction::ExpandSeed {
302 target_id: viz_node_id,
303 txt: format!("{}", node),
304 seeds: seeds_v,
305 });
306
307 todo.push(State::PostVisit((viz_node_id, node)));
308 todo.extend(topush.into_iter());
309 }
310 State::PostVisit((viz_node_id, node)) => {
311 let node = F::map_frame(node, |_: ()| vals.pop().unwrap());
312
313 let out = match alg(node) {
314 Ok(out) => out,
315 Err(e) => {
316 v.push(VizAction::InfoCard {
317 info_header: "Error during collapse!".to_string(),
318 info_txt: format!("error: {}", e),
319 });
320 return (
321 Err(e),
322 Viz {
323 seed_txt: root_seed_txt,
324 root_id: 0,
325 actions: v,
326 },
327 );
328 }
329 };
330
331 v.push(VizAction::CollapseNode {
332 target_id: viz_node_id,
333 txt: format!("{}", out),
334 });
335
336 vals.push(out)
337 }
338 };
339 }
340
341 let out = vals.pop().unwrap();
342
343 v.push(VizAction::InfoCard {
344 info_header: "Completed".to_string(),
345 info_txt: format!("result: {}", out),
346 });
347
348 (
349 Ok(out),
350 Viz {
351 seed_txt: root_seed_txt,
352 root_id: 0,
353 actions: v,
354 },
355 )
356}
357
358pub fn expand_and_collapse_v<F, Seed, Out>(
361 seed: Seed,
362 mut coalg: impl FnMut(Seed) -> F::Frame<Seed>,
363 mut alg: impl FnMut(F::Frame<Out>) -> Out,
364) -> (Out, Viz)
365where
366 F: MappableFrame,
367 F::Frame<()>: Display,
369 Seed: Display,
370 Out: Display,
371{
372 enum State<Pre, Post> {
373 PreVisit(Pre),
374 PostVisit(Post),
375 }
376
377 let mut keygen = 1; let mut v = Vec::new();
379 let root_seed_txt = format!("{}", seed);
380
381 let mut vals: Vec<Out> = vec![];
382 let mut todo: Vec<State<(VizNodeId, Seed), _>> = vec![State::PreVisit((0, seed))];
383
384 while let Some(item) = todo.pop() {
385 match item {
386 State::PreVisit((viz_node_id, seed)) => {
387 let mut seeds_v = Vec::new();
388
389 let node = coalg(seed);
390 let mut topush = Vec::new();
391 let node = F::map_frame(node, |seed| {
392 let k = keygen;
393 keygen += 1;
394 seeds_v.push((k, format!("{}", seed)));
395
396 topush.push(State::PreVisit((k, seed)))
397 });
398
399 v.push(VizAction::ExpandSeed {
400 target_id: viz_node_id,
401 txt: format!("{}", node),
402 seeds: seeds_v,
403 });
404
405 todo.push(State::PostVisit((viz_node_id, node)));
406 todo.extend(topush.into_iter());
407 }
408 State::PostVisit((viz_node_id, node)) => {
409 let node = F::map_frame(node, |_: ()| vals.pop().unwrap());
410
411 let out = alg(node);
412
413 v.push(VizAction::CollapseNode {
414 target_id: viz_node_id,
415 txt: format!("{}", out),
416 });
417
418 vals.push(out)
419 }
420 };
421 }
422
423 let out = vals.pop().unwrap();
424
425 v.push(VizAction::InfoCard {
426 info_header: "Completed".to_string(),
427 info_txt: format!("result: {}", out),
428 });
429
430 (
431 out,
432 Viz {
433 seed_txt: root_seed_txt,
434 root_id: 0,
435 actions: v,
436 },
437 )
438}
439
440static TEMPLATE_BEFORE: &'static str = r###"
442<!DOCTYPE html>
443<meta charset="UTF-8">
444<style>
445
446.node rect {
447 fill: #fff;
448 stroke-width: 4px;
449 rx: 4px;
450 rY: 4px;
451 }
452
453 .node text {
454 font: 16px verdana;
455}
456
457body {
458 background-color: lightcyan;
459}
460
461.infocard {
462 background-color: white;
463 border-style: solid;
464 width: 500px;
465 padding: 10px;
466 border-radius: 10px;
467}
468
469.infocard .cardheader {
470 font-size: 25px;
471 padding-top: 5px;
472 padding-bottom: 5px;
473 border-bottom: solid;
474 border-width: 5px;
475}
476
477.infocard .cardbody {
478 font-size: 15px;
479 padding: 10px;
480 font-family: "Lucida Console", "Courier New", monospace;
481 background-color: steelblue;
482 color: white;
483}
484
485.link {
486 fill: none;
487 stroke-width: 4px;
488}
489
490</style>
491
492<body>
493
494<div opacity="0" id="titlecard" class="infocard">
495 <div class="cardheader">header</div>
496 <div class="cardbody">body</div>
497</div>
498
499<!-- load the d3.js library -->
500<script src="https://d3js.org/d3.v7.js"></script>
501<script>
502
503
504 // colors for use in nodes, links, etc
505 const collapse_stroke = "mediumVioletRed";
506 const expand_stroke = "steelBlue";
507 const structure_stroke = "black";
508
509
510const data = "###;
511
512static TEMPLATE_AFTER: &'static str = r###"
513
514 var treeData = data.root;
515
516 var actions = data.actions;
517
518
519// Set the dimensions and margins of the diagram
520var margin = {top: 0, right: 10, bottom: 30, left: 30},
521 width = 900 - margin.left - margin.right,
522 height = 410 - margin.top - margin.bottom;
523
524// append the svg object to the body of the page
525// appends a 'group' element to 'svg'
526// moves the 'group' element to the top left margin
527var svg = d3.select("body").append("svg")
528 .attr("width", width + margin.right + margin.left)
529 .attr("height", height + margin.top + margin.bottom)
530 .append("g")
531 .attr("transform", "translate("
532 + margin.left + "," + margin.top + ")");
533
534var i = 0,
535 duration = 250,
536 root;
537
538// declares a tree layout and assigns the size
539var treemap = d3.tree().size([height/1.4, width]);
540
541// Assigns parent, children, height, depth
542 root = d3.hierarchy(treeData, function(d) { return d.children; });
543 root.x0 = height / 2;
544root.y0 = 0;
545
546
547update(root);
548
549var pause = 0;
550
551
552 let intervalId = setInterval(function () {
553 if (pause == 0) {
554 var next = actions.shift();
555 if (next) {
556 if (next.typ == "info_card") {
557 d3.select("#titlecard .cardheader").text(next.info_header);
558 d3.select("#titlecard .cardbody").text(next.info_txt);
559
560 d3.select("#titlecard")
561 .transition().duration(500)
562 .style("border-color", "mediumvioletred")
563 .style("color", "mediumvioletred")
564 .transition().duration(1000)
565 .style("border-color", "black")
566 .style("color", "black");
567
568 } else if (next.seeds) { // in this case, is expand (todo explicit typ field for this)
569
570 let target = root.find(x => x.data.node_id == next.target_id);
571
572 target.data.txt = next.txt;
573 target.data.typ = "structure";
574
575 if (next.seeds.length) {
576 target.children = [];
577 target.data.children = [];
578 } else {
579 delete target.children;
580 delete target.data.children;
581 }
582 next.seeds.forEach(function(seed) {
583 var newNode = d3.hierarchy(seed);
584 newNode.depth = target.depth + 1;
585 newNode.height = target.height - 1;
586 newNode.parent = target;
587
588 newNode.data.typ = "seed";
589
590 target.children.push(newNode);
591 target.data.children.push(newNode.data);
592 });
593
594 update(target);
595
596 } else { // in this case, is collapse
597 let target = root.find(x => x.data.node_id == next.target_id);
598
599 // remove child nodes from tree
600 delete target.children;
601 delete target.data.children;
602 target.data.txt = next.txt;
603 target.data.typ = "collapse";
604
605
606 update(target);
607
608 }
609 } else {
610 clearInterval(intervalId);
611 }} else { pause -= 1;}
612 }, 600);
613
614function update(source) {
615
616 // Assigns the x and y position for the nodes
617 var treeData = treemap(root);
618
619 // Compute the new tree layout.
620 var nodes = treeData.descendants(),
621 links = treeData.descendants().slice(1);
622
623 // Normalize for fixed-depth.
624 nodes.forEach(function(d){ d.y = d.depth * 110});
625
626 // ****************** Nodes section ***************************
627
628 // Update the nodes...
629 var node = svg.selectAll('g.node')
630 .data(nodes, function(d) {return d.id || (d.id = ++i); });
631
632 // Enter any new modes at the parent's previous position.
633 var nodeEnter = node.enter().append('g')
634 .attr('class', 'node')
635 .attr("transform", function(d) {
636 return "translate(" + source.y0 + "," + source.x0 + ")";
637 });
638
639 // Add rect for the nodes
640 nodeEnter.append('rect')
641 .attr('class', 'node')
642 .attr('width', 1e-6)
643 .attr('height', 1e-6)
644 .transition()
645 .duration(duration)
646
647 .transition()
648 .duration(duration)
649 ;
650
651 // Add labels for the nodes
652 nodeEnter.append('text')
653 .attr("dy", ".35em")
654 .attr("x", function(d) {
655 return d.children || d._children ? -13 : 13;
656 })
657 .attr("text-anchor", function(d) {
658 return d.children || d._children ? "end" : "start";
659 })
660 .text(function(d) { return (d.data.txt); });
661
662 // UPDATE
663 var nodeUpdate = nodeEnter.merge(node);
664
665 // Transition to the proper position for the node
666 nodeUpdate.transition()
667 .duration(duration)
668 .attr("transform", function(d) {
669 return "translate(" + d.y + "," + d.x + ")";
670 });
671
672 // Update the node attributes and style
673 nodeUpdate.select('rect.node')
674 .attr('stroke', function(d) {
675 switch(d.data.typ) {
676 case 'structure':
677 return structure_stroke;
678 case 'seed':
679 return expand_stroke;
680 case 'collapse':
681 return collapse_stroke;
682 }
683 })
684 .attr('width', function(d){ return textSize(d.data.txt).width})
685 .attr('height', textSize("x").height + 5 )
686 .attr("transform", function(d) {return "translate(0, -" + (textSize("x").height + 5) / 2 + ")"; })
687 .transition()
688 .duration(duration);
689
690 // update text
691 nodeUpdate.select("text")
692 .text(function(d) { return (d.data.txt); });
693
694
695 // Remove any exiting nodes
696 var nodeExit = node.exit().transition()
697 .duration(duration)
698 .attr("transform", function(d) {
699 return "translate(" + source.y + "," + source.x + ")";
700 })
701 .remove();
702
703 // On exit reduce the node circles size to 0
704 nodeExit.select('rect')
705 .attr('width', 1e-6)
706 .attr('height', 1e-6);
707
708 // On exit reduce the opacity of text labels
709 nodeExit.select('text')
710 .style('fill-opacity', 1e-6);
711
712 // ****************** links section ***************************
713
714 // Update the links...
715 var link = svg.selectAll('path.link')
716 .data(links, function(d) { return d.id; });
717
718 // Enter any new links at the parent's previous position.
719 var linkEnter = link.enter().insert('path', "g")
720 .attr("class", "link")
721 .attr('d', function(d){
722 var o = {x: source.x0, y: source.y0}
723 return diagonal(o, o)
724 });
725
726 // UPDATE
727 var linkUpdate = linkEnter.merge(link);
728
729 // Transition back to the parent element position
730 linkUpdate.transition()
731 .attr('stroke', function(d) {
732 switch(d.data.typ) {
733 case 'structure':
734 return structure_stroke;
735 case 'seed':
736 return expand_stroke;
737 case 'collapse':
738 return collapse_stroke;
739 }})
740 .duration(duration)
741 .attr('d', function(d){ return diagonal(d, d.parent) });
742
743 // Remove any exiting links
744 var linkExit = link.exit().transition()
745 .duration(duration)
746 .attr('d', function(d) {
747 var o = {x: source.x, y: source.y}
748 return diagonal(o, o)
749 })
750 .remove();
751
752 // Store the old positions for transition.
753 nodes.forEach(function(d){
754 d.x0 = d.x;
755 d.y0 = d.y;
756 });
757
758 // Creates a curved (diagonal) path from parent to the child nodes
759 function diagonal(s, d) {
760
761 path = `M ${s.y} ${s.x}
762 C ${(s.y + d.y) / 2} ${s.x},
763 ${(s.y + d.y) / 2} ${d.x},
764 ${d.y} ${d.x}`
765
766 return path
767 }
768 }
769
770 function textSize(text) {
771 if (!d3) return;
772 var container = d3.select('body').append('svg');
773 container.append('text').attr("x", -99999).attr( "y", -99999 ).text(text);
774 var size = container.node().getBBox();
775 container.remove();
776 return { width: size.width + 30, height: size.height + 10 };
777 }
778
779
780</script>
781</body>
782
783"###;