1use std::collections::{BTreeMap, HashMap};
7
8pub use facett_core::{Edge, Facet, FacetCaps, Layout, Node, Scene, Theme, draw, hash_color, set_theme, theme};
9
10mod depgraph;
11pub use depgraph::{DepEdge, DepGraphView, DepNode, draw_arrow};
12
13pub fn scene_from_labeled_edges<I>(rows: I) -> Scene
18where
19 I: IntoIterator<Item = (i64, i64, String, String)>,
20{
21 let mut scene = Scene::new();
22 let mut idx: HashMap<i64, usize> = HashMap::new();
23 for (s, d, sl, dl) in rows {
24 let si = *idx.entry(s).or_insert_with(|| scene.node(sl.clone(), hash_color(&sl)));
25 let di = *idx.entry(d).or_insert_with(|| scene.node(dl.clone(), hash_color(&dl)));
26 scene.edge(si, di);
27 }
28 scene
29}
30
31pub struct GraphView {
34 pub scene: Scene,
35 pub layout: Layout,
36 pub empty_hint: String,
37 pub title: String,
38}
39
40impl GraphView {
41 pub fn new(scene: Scene) -> Self {
42 Self { scene, layout: Layout::default(), empty_hint: "empty".into(), title: "graph".into() }
43 }
44 pub fn empty_hint(mut self, hint: impl Into<String>) -> Self {
45 self.empty_hint = hint.into();
46 self
47 }
48 pub fn with_title(mut self, title: impl Into<String>) -> Self {
49 self.title = title.into();
50 self
51 }
52 pub fn show(&self, ui: &mut egui::Ui) {
53 draw(ui, &self.scene, self.layout, &self.empty_hint);
54 }
55 pub fn label_counts(&self) -> BTreeMap<String, usize> {
57 let mut m = BTreeMap::new();
58 for n in &self.scene.nodes {
59 *m.entry(n.label.clone()).or_insert(0) += 1;
60 }
61 m
62 }
63}
64
65impl GraphView {
66 pub fn copy_text(&self) -> Option<String> {
69 if self.scene.nodes.is_empty() {
70 return None;
71 }
72 Some(self.scene.nodes.iter().map(|n| n.label.clone()).collect::<Vec<_>>().join("\n"))
73 }
74}
75
76impl facett_core::clip::CopySource for GraphView {
78 fn copy_kinds(&self) -> &[facett_core::clip::ClipKind] {
79 use facett_core::clip::ClipKind;
80 &[ClipKind::Text]
81 }
82 fn copy_payload(&self) -> Option<facett_core::clip::ClipPayload> {
83 self.copy_text().map(facett_core::clip::ClipPayload::Text)
84 }
85}
86
87impl Facet for GraphView {
88 fn title(&self) -> &str {
89 &self.title
90 }
91 fn copy(&mut self) -> Option<String> {
92 use facett_core::clip::CopySource as _;
93 self.copy_payload().map(|p| p.as_text())
94 }
95 fn ui(&mut self, ui: &mut egui::Ui) {
96 self.show(ui);
97 #[cfg(feature = "testmatrix")]
99 facett_core::testmatrix::emit(
100 "facett-graph::GraphView::ui",
101 "ui_render",
102 !self.scene.nodes.is_empty() || !self.empty_hint.is_empty(),
103 &format!("nodes={} edges={}", self.scene.nodes.len(), self.scene.edges.len()),
104 );
105 }
106 fn state_json(&self) -> serde_json::Value {
107 serde_json::json!({
108 "nodes": self.scene.nodes.len(),
109 "edges": self.scene.edges.len(),
110 "labels": self.label_counts(),
111 })
112 }
113 fn caps(&self) -> FacetCaps {
116 FacetCaps::NONE.themeable().resizable().copyable()
117 }
118}
119
120#[cfg(test)]
121mod tests {
122 use super::*;
123
124 #[test]
125 fn typed_copy_is_the_node_label_list() {
126 use facett_core::clip::{ClipKind, CopySource};
127 let scene = scene_from_labeled_edges(vec![(1, 2, "Person".into(), "Company".into())]);
128 let mut g = GraphView::new(scene);
129 let p = g.copy_payload().expect("a populated scene copies");
130 assert_eq!(p.kind(), ClipKind::Text);
131 assert!(p.as_text().contains("Person") && p.as_text().contains("Company"));
132 assert_eq!(<GraphView as Facet>::copy(&mut g), Some(p.as_text()));
133 }
134
135 #[test]
136 fn builds_scene_from_labeled_edges() {
137 let scene = scene_from_labeled_edges(vec![
138 (1, 2, "Person".into(), "Company".into()),
139 (1, 3, "Person".into(), "Address".into()),
140 ]);
141 assert_eq!(scene.nodes.len(), 3, "1, 2, 3 distinct");
142 assert_eq!(scene.edges.len(), 2);
143 assert_eq!(scene.nodes[0].label, "Person");
144 }
145}