odis-web is a Leptos/WASM front-end for the odis FCA library. It compiles to
WebAssembly via Cargo and is bundled with Trunk.
| Tool | Install |
|---|---|
| Rust (stable) | rustup update |
| WASM target | rustup target add wasm32-unknown-unknown |
| Trunk | cargo install trunk |
No Node.js or npm needed. Trunk fetches and manages Tailwind CSS 3.4 internally via the
[tools]section inTrunk.toml.
Start a local dev server with live reload from the odis-web/ directory:
cd odis-web
trunk serveTrunk serves the app at http://localhost:8080 by default and rebuilds on
source changes.
cd odis-web
trunk build --releaseOutput lands in odis-web/dist/. Deploying is normally left to the workflow
in .github/workflows/deploy.yml; by hand, copy the contents to the checkout of
domduerr/odis-web.github.io to
publish a new version of the hosted app.
odis-web/
├── index.html # HTML entry point (Trunk reads link/script tags)
├── Trunk.toml # Trunk configuration (Tailwind version, etc.)
└── src/
├── main.rs # App root: signal setup, provide_context
├── js_fn.rs # JS interop helpers (clipboard, SVG download)
├── components/
│ ├── layout.rs # View enum, Sidebar, Header
│ ├── views.rs # Top-level view components
│ ├── context.rs # FormalContext editor / file loader
│ ├── graph.rs # Concept-lattice SVG graph
│ ├── iceberg.rs # Iceberg-lattice view
│ ├── exploration.rs # Attribute exploration dialog
│ ├── table.rs # Cross-table editor
│ ├── svg_download.rs # SVG download button
│ ├── ui.rs # Shared panel, control and typography styles
│ └── svg/ # Low-level SVG primitives
├── core/
│ ├── export.rs # Context serialisation helpers
│ ├── formatters.rs # Human-readable label formatters
│ └── layout_math.rs # Coordinate helpers
└── utils/
└── browser.rs # Browser API wrappers
The app root (main.rs) creates two shared signals and makes them available
to the entire component tree:
// main.rs — App()
let context: RwSignal<FormalContext<String>> = RwSignal::new(create_default_context());
provide_context(context);
let context_version: RwSignal<u64> = RwSignal::new(0);
provide_context(context_version);Any component that needs the context reads it with:
let context = use_context::<RwSignal<FormalContext<String>>>().unwrap();When a component mutates the context it increments context_version to trigger
reactive recomputation in views that depend on derived data:
let version = use_context::<RwSignal<u64>>().unwrap();
context.update(|ctx| { /* mutation */ });
version.update(|v| *v += 1);-
Create the component — add
my_view.rsinsidesrc/components/and export a single#[component] pub fn MyView() -> impl IntoViewfunction. Read the shared context at the top:let context = use_context::<RwSignal<FormalContext<String>>>().unwrap();
-
Add a
Viewvariant — opensrc/components/layout.rsand append your variant to theViewenum:pub enum View { FormalContext, Concepts, ConceptLattice, CanonicalBasis, Exploration, IcebergLattice, MyView, // ← add here }
-
Wire the Sidebar — still in
layout.rs, add a sidebar button inside theSidebarcomponent that sets the current view:<SidebarButton label="My View" active=Signal::derive(move || current_view.get() == View::MyView) on_click=move || current_view.set(View::MyView) />
-
Render the view — open
src/components/views.rsand add a branch in the view-switching block:View::MyView => view! { <MyView /> },
Import the new module at the top of
views.rs:use super::my_view::MyView;
| File | Purpose |
|---|---|
layout.rs |
View enum, Sidebar, Header |
views.rs |
Dispatches rendering by current View |
context.rs |
Context editor and .cxt file import |
graph.rs |
Concept-lattice SVG with pan/zoom |
iceberg.rs |
Iceberg-lattice (min-support filter) view |
exploration.rs |
Attribute exploration |
table.rs |
Cross-table incidence editor |
svg_download.rs |
SVG export / clipboard button |
ui.rs |
Shared Panel, button/input classes, set and implication typography |
svg/ |
Low-level SVG node/edge rendering primitives |