My personal-knowledge-system, with deeply integrated task tracking and long term goal planning capabilities.
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge pull request #11 from suri-codes/viz

vizualize

authored by

Surendra Jammishetti and committed by
GitHub
a6233c0f c5a6228c

+129 -38
+29 -9
src/gui/mod.rs
··· 1 1 use eframe::egui; 2 + use egui_graphs::{SettingsInteraction, SettingsNavigation}; 3 + 4 + use crate::types::{Filaments, Index}; 2 5 3 6 /// The `Filaments Visualizer`, which is an instance of `eframe`, which uses `egui` 4 - #[derive(Default)] 5 7 pub struct FilViz { 6 - /// example for now 7 - text: String, 8 + filaments: Filaments, 8 9 } 9 10 10 11 impl FilViz { 11 12 /// Create a new instance of the `FiLViz` 12 - const fn new(_cc: &eframe::CreationContext<'_>) -> Self { 13 + fn new(_cc: &eframe::CreationContext<'_>, idx: &Index) -> Self { 13 14 // Customize egui here with cc.egui_ctx.set_fonts and cc.egui_ctx.set_global_style. 14 15 // Restore app state using cc.storage (requires the "persistence" feature). 15 16 // Use the cc.gl (a glow::Context) to create graphics shaders and buffers that you can use 16 17 // for e.g. egui::PaintCallback. 17 18 Self { 18 - text: String::new(), 19 + filaments: Filaments::from(idx), 19 20 } 20 21 } 21 22 22 23 /// Create and run the `FilViz`. 23 - pub fn run() -> color_eyre::Result<()> { 24 + pub fn run(idx: &Index) -> color_eyre::Result<()> { 24 25 let native_options = eframe::NativeOptions::default(); 25 26 eframe::run_native( 26 27 "Filaments Visualizer", 27 28 native_options, 28 - Box::new(|cc| Ok(Box::new(Self::new(cc)))), 29 + Box::new(|cc| Ok(Box::new(Self::new(cc, idx)))), 29 30 )?; 30 31 31 32 Ok(()) 32 33 } 33 34 } 34 35 36 + type L = egui_graphs::LayoutForceDirected<egui_graphs::FruchtermanReingoldWithCenterGravity>; 37 + type S = egui_graphs::FruchtermanReingoldWithCenterGravityState; 38 + 35 39 impl eframe::App for FilViz { 36 40 fn ui(&mut self, ui: &mut egui::Ui, _frame: &mut eframe::Frame) { 37 41 egui::CentralPanel::default().show_inside(ui, |ui| { 38 - ui.heading("Hello World!"); 39 - ui.text_edit_singleline(&mut self.text); 42 + let g = &mut self.filaments.graph; 43 + 44 + let mut view = egui_graphs::GraphView::<_, _, _, _, _, _, S, L>::new(g) 45 + .with_interactions( 46 + &SettingsInteraction::new() 47 + .with_hover_enabled(false) 48 + .with_dragging_enabled(false) 49 + .with_node_selection_enabled(false) 50 + .with_node_selection_multi_enabled(false) 51 + .with_edge_selection_enabled(false) 52 + .with_edge_selection_multi_enabled(false), 53 + ) 54 + .with_navigations( 55 + &SettingsNavigation::new().with_fit_to_screen_padding(0.5), // .with_zoom_and_pan_enabled(true) 56 + // .with_fit_to_screen_enabled(false), 57 + ); 58 + 59 + ui.add(&mut view); 40 60 41 61 // credits! 42 62 ui.with_layout(egui::Layout::bottom_up(egui::Align::LEFT), |ui| {
+3 -1
src/main.rs
··· 52 52 let tui_handle = std::thread::spawn({ 53 53 // arc stuff 54 54 let tui_rt = rt.clone(); 55 + let kh = kh.clone(); 55 56 56 57 // closure to run the tui 57 58 move || -> color_eyre::Result<()> { ··· 69 70 if args.visualizer { 70 71 // enter the guard so egui_async works properly 71 72 let _rt_guard = rt.enter(); 72 - FilViz::run()?; 73 + let index = rt.block_on(async { kh.read().await.index.clone() }); 74 + FilViz::run(&index)?; 73 75 } 74 76 75 77 // join on the tui
-3
src/tui/components/zk/mod.rs
··· 95 95 96 96 let kt = kh.read().await; 97 97 98 - info!("{selected_zettel:#?}"); 99 - info!("{kt:#?}"); 100 - 101 98 let zettel = zettels 102 99 .iter() 103 100 .find(|&z| &z.id == selected_zettel)
+34 -12
src/types/filaments.rs
··· 1 1 #![expect(dead_code)] 2 2 use std::{cmp::max, collections::HashMap}; 3 3 4 + use eframe::emath; 4 5 use egui_graphs::{ 5 6 Graph, 6 7 petgraph::{Directed, graph::NodeIndex, prelude::StableGraph}, ··· 16 17 const GRAPH_MIN_EDGES: usize = GRAPH_MIN_NODES * 3; 17 18 18 19 pub struct Filaments { 19 - graph: ZkGraph, 20 + pub graph: ZkGraph, 20 21 /// simple conversions 21 22 zid_to_gid: HashMap<ZettelId, NodeIndex>, 22 23 } 23 24 24 - // pub type FilamentsHandle = Arc<RwLock> 25 - // 26 - 27 - // impl Filaments { 28 - // pub fn construct() -> Result<Self> {} 29 - // } 30 - 31 25 impl From<&Index> for Filaments { 32 26 fn from(value: &Index) -> Self { 33 27 let number_of_zettels = value.zods().len(); 34 28 35 - let mut _graph: ZkGraph = ZkGraph::from(&StableGraph::with_capacity( 29 + let mut zid_to_gid = HashMap::new(); 30 + 31 + let mut graph: ZkGraph = ZkGraph::from(&StableGraph::with_capacity( 36 32 max(number_of_zettels * 2, GRAPH_MIN_EDGES), 37 33 max(number_of_zettels * 3, GRAPH_MIN_EDGES), 38 34 )); 39 35 40 - #[expect(clippy::for_kv_map)] 41 - for (_id, _zod) in value.zods() {} 36 + for (zid, zod) in value.zods() { 37 + let node_idx = graph.add_node_custom(zid.clone(), |node| { 38 + node.set_label(zod.fm.title.clone()); 39 + let disp = node.display_mut(); 40 + disp.radius = 50.0; 42 41 43 - todo!() 42 + // randomize position 43 + let x = rand::random_range(0.0..=100.0); 44 + let y = rand::random_range(0.0..=100.0); 45 + node.set_location(emath::Pos2 { x, y }); 46 + node.set_hovered(true); 47 + }); 48 + 49 + let _ = zid_to_gid.insert(zid.clone(), node_idx); 50 + } 51 + 52 + for (_, links) in value.outgoing_links.clone() { 53 + for link in links { 54 + let start = zid_to_gid 55 + .get(&link.source) 56 + .expect("Invariant broken, must exist in here if its in the index"); 57 + let end = zid_to_gid 58 + .get(&link.dest) 59 + .expect("Invariant broken, must exist in here if its in the index"); 60 + 61 + let _ = graph.add_edge(*start, *end, link); 62 + } 63 + } 64 + 65 + Self { graph, zid_to_gid } 44 66 } 45 67 }
+62 -11
src/types/index.rs
··· 1 1 use std::{ 2 - collections::HashMap, 2 + collections::{HashMap, HashSet}, 3 3 path::{Path, PathBuf}, 4 4 }; 5 5 6 6 use color_eyre::eyre::Result; 7 7 use dto::{ 8 8 ActiveModelTrait, ActiveValue, ColumnTrait, DatabaseConnection, EntityTrait, IntoActiveModel, 9 - QueryFilter, TagActiveModel, TagEntity, ZettelEntity, ZettelModelEx, ZettelTagActiveModel, 10 - ZettelTagColumns, ZettelTagEntity, 9 + NanoId, QueryFilter, TagActiveModel, TagEntity, ZettelEntity, ZettelModelEx, 10 + ZettelTagActiveModel, ZettelTagColumns, ZettelTagEntity, 11 11 }; 12 + use pulldown_cmark::{Event, Options, Parser}; 12 13 use rayon::iter::{ParallelBridge, ParallelIterator}; 13 14 use tracing::info; 14 15 15 - use crate::types::{FrontMatter, ZettelId, frontmatter::Body}; 16 + use crate::types::{FrontMatter, Link, ZettelId, frontmatter::Body}; 16 17 17 18 #[derive(Debug, Clone)] 18 19 pub struct Index { 19 20 pub(super) zods: HashMap<ZettelId, ZettelOnDisk>, 21 + pub(super) outgoing_links: HashMap<ZettelId, Vec<Link>>, 22 + // pub(super) incoming_links: HashMap<ZettelId, Vec<Link>>, 20 23 } 21 24 22 25 #[derive(Debug, Clone)] ··· 32 35 let root = root.canonicalize()?; 33 36 34 37 let mut zods = HashMap::new(); 38 + let mut possible_outgoing_links = HashMap::new(); 35 39 36 40 std::fs::read_dir(root)? 37 41 .par_bridge() ··· 44 48 .and_then(|ext| ext.to_str()) 45 49 .is_some_and(|ext| ext == "md") 46 50 }) 47 - .map(|entry| -> Result<(ZettelId, ZettelOnDisk)> { 51 + .map(|entry| -> Result<(ZettelId, ZettelOnDisk, Vec<Link>)> { 48 52 let path = entry.path(); 49 - let id: ZettelId = path.as_path().try_into()?; 53 + let zid: ZettelId = path.as_path().try_into()?; 50 54 let (fm, body) = FrontMatter::extract_from_file(&path)?; 51 55 56 + let outgoing_links = Self::parse_outgoing_links(&zid, &body); 57 + 52 58 Ok(( 53 - id, 59 + zid, 54 60 ZettelOnDisk { 55 61 fm, 56 62 body, 57 63 path: path.canonicalize()?, 58 64 }, 65 + outgoing_links, 59 66 )) 60 67 }) 61 68 .collect::<Result<Vec<_>>>()? 62 69 .into_iter() 63 70 // .par_bridge() 64 - .for_each(|(id, zod)| { 65 - zods.insert(id, zod); 71 + .for_each(|(id, zod, zettel_outgoing_links)| { 72 + zods.insert(id.clone(), zod); 73 + possible_outgoing_links.insert(id, zettel_outgoing_links); 66 74 }); 67 75 68 - Ok(Self { zods }) 76 + // simple validation step for links 77 + let zid_set = zods.keys().cloned().collect::<HashSet<_>>(); 78 + 79 + let outgoing_links = possible_outgoing_links 80 + .into_iter() 81 + .map(|(id, links)| { 82 + let valid_links = links 83 + .into_iter() 84 + .filter(|link| zid_set.contains(&link.source) && zid_set.contains(&link.dest)) 85 + .collect::<Vec<_>>(); 86 + 87 + (id, valid_links) 88 + }) 89 + .collect::<HashMap<_, _>>(); 90 + 91 + Ok(Self { 92 + zods, 93 + outgoing_links, 94 + }) 95 + } 96 + 97 + pub fn parse_outgoing_links(zid: &ZettelId, body: &str) -> Vec<Link> { 98 + let parser = Parser::new_ext(body, Options::ENABLE_WIKILINKS); 99 + 100 + // let mut links = vec![]; 101 + let mut links = vec![]; 102 + 103 + for event in parser { 104 + if let Event::Start(pulldown_cmark::Tag::Link { 105 + link_type: pulldown_cmark::LinkType::WikiLink { has_pothole: _ }, 106 + dest_url, 107 + .. 108 + }) = event 109 + { 110 + let nano_id = dest_url.as_ref(); 111 + 112 + // how do we validate that this is a proper link? 113 + // we are just going to trust them for now 114 + 115 + links.push(Link::new(zid.clone(), NanoId::from(nano_id))); 116 + } 117 + } 118 + 119 + links 69 120 } 70 121 71 122 pub fn update_path_for_zid(&mut self, zid: &ZettelId, new_path: PathBuf) { ··· 86 137 87 138 /// Sync's the curren title of the `Zettel` with the 88 139 /// provided `zid` with the `DB` 89 - pub async fn sync_title_with_db( 140 + pub async fn sync_zettel_title_with_db( 90 141 &mut self, 91 142 zid: &ZettelId, 92 143 db: &DatabaseConnection,
+1 -1
src/types/kasten.rs
··· 111 111 self.index.process_zid(&zid)?; 112 112 // and then we sync tags 113 113 self.index.sync_tags_with_db(&zid, &self.db).await?; 114 - self.index.sync_title_with_db(&zid, &self.db).await?; 114 + self.index.sync_zettel_title_with_db(&zid, &self.db).await?; 115 115 116 116 Ok(()) 117 117 }
-1
src/types/mod.rs
··· 22 22 pub use link::Link; 23 23 24 24 mod filaments; 25 - #[expect(unused_imports)] 26 25 pub use filaments::Filaments; 27 26 28 27 mod index;