vowl_wasm/lib.rs
1//! `vowl-wasm` — an ontology visualisation engine in Rust, compiled to WebAssembly.
2//!
3//! This crate turns an OWL ontology into something a browser can draw: it parses
4//! the ontology, builds a typed graph out of it, settles that graph with a
5//! force-directed layout, and hands the result to JavaScript. It is a clean-room
6//! Rust reimplementation of the **VOWL** visual notation defined by
7//! [VisualDataWeb/WebVOWL](https://github.com/VisualDataWeb/WebVOWL) (MIT,
8//! © 2014–2019 Vincent Link, Steffen Lohmann, Eduard Marbach, Stefan Negru,
9//! Vitalis Wiens). No upstream JavaScript, fixtures, or assets are vendored here;
10//! only the notation and the on-the-wire JSON shape are shared.
11//!
12//! # The two entry points
13//!
14//! [`WebVowl`](bindings::WebVowl) is the general-purpose surface: load an
15//! ontology, run the simulation, read the graph back. It is always available.
16//!
17//! [`NggExplorer`](bindings::explorer::NggExplorer) is a narrower, allocation-free
18//! surface for a rendering worker: it loads a pre-baked binary tier and exposes
19//! the live position buffer as a pointer into WASM linear memory, so JavaScript
20//! can build a zero-copy `Float32Array` over it and never pay a per-frame
21//! serialisation cost. It requires the `ngg1` feature.
22//!
23//! # Example
24//!
25//! Parse a small ontology, settle it, and read the result. This is the same API
26//! JavaScript sees, minus the `camelCase` renaming applied at the WASM boundary.
27//!
28//! ```
29//! use vowl_wasm::bindings::WebVowl;
30//!
31//! let mut vowl = WebVowl::new();
32//!
33//! // WebVOWL-shaped ontology JSON: classes and the properties between them.
34//! vowl.load_ontology(r#"{
35//! "class": [
36//! { "id": "c1", "label": "Person", "type": "owl:Class" },
37//! { "id": "c2", "label": "Organisation", "type": "owl:Class" }
38//! ],
39//! "property": [
40//! { "id": "p1", "label": "worksFor", "type": "owl:ObjectProperty",
41//! "domain": "c1", "range": "c2" }
42//! ]
43//! }"#).expect("valid ontology");
44//!
45//! assert_eq!(vowl.get_node_count(), 2);
46//!
47//! // Settle the layout: seed the simulation, then anneal it.
48//! vowl.init_simulation().expect("graph is loaded");
49//! vowl.run_simulation(120).expect("graph is loaded");
50//!
51//! // Alpha decays as the layout cools.
52//! assert!(vowl.get_alpha() < 1.0);
53//! ```
54//!
55//! # The JavaScript API
56//!
57//! Every method below is exported to JavaScript under the `js_name` shown. This
58//! is the **frozen 0.1.0 surface**: names and signatures will not change without
59//! a major version bump. Later 0.1.x releases only add to it — `getNodeIds`,
60//! `getNodePositions` and `writePositionsInto` arrived in 0.1.1.
61//!
62//! ## Reading positions
63//!
64//! [`get_node_positions`](bindings::WebVowl::get_node_positions) is the
65//! always-available way to get coordinates out after a
66//! [`tick`](bindings::WebVowl::tick). Node **index order is stable for the
67//! lifetime of a loaded graph** — ticking, pinning, filtering and folding never
68//! reorder or remove nodes — so pair it once with
69//! [`get_node_ids`](bindings::WebVowl::get_node_ids) and then read only floats
70//! per frame:
71//!
72//! ```javascript
73//! const ids = vowl.getNodeIds(); // once, after loadOntology
74//! vowl.tick();
75//! const xy = vowl.getNodePositions(); // ids[i] <-> xy[2i], xy[2i + 1]
76//! ```
77//!
78//! `getGraphData()` remains the way to read labels, types and edges, and stays
79//! behind `debug-serde`.
80//!
81//! ## `WebVowl` — always available
82//!
83//! | JavaScript | Rust | Purpose |
84//! |---|---|---|
85//! | `new WebVowl()` | [`WebVowl::new`](bindings::WebVowl::new) | Construct an empty instance |
86//! | `loadOntology(json)` | [`load_ontology`](bindings::WebVowl::load_ontology) | Parse and build the graph |
87//! | `initSimulation()` | [`init_simulation`](bindings::WebVowl::init_simulation) | Seed the force simulation |
88//! | `runSimulation(n)` | [`run_simulation`](bindings::WebVowl::run_simulation) | Advance `n` steps |
89//! | `tick()` | [`tick`](bindings::WebVowl::tick) | Advance one step |
90//! | `isFinished()` | [`is_finished`](bindings::WebVowl::is_finished) | Has the layout settled? |
91//! | `getAlpha()` | [`get_alpha`](bindings::WebVowl::get_alpha) | Annealing temperature |
92//! | `setCenter(x, y)` | [`set_center`](bindings::WebVowl::set_center) | Move the layout centre |
93//! | `setLinkDistance(d)` | [`set_link_distance`](bindings::WebVowl::set_link_distance) | Spring rest length |
94//! | `setChargeStrength(s)` | [`set_charge_strength`](bindings::WebVowl::set_charge_strength) | Repulsion strength |
95//! | `getNodeCount()` | [`get_node_count`](bindings::WebVowl::get_node_count) | Node count |
96//! | `getEdgeCount()` | [`get_edge_count`](bindings::WebVowl::get_edge_count) | Edge count |
97//! | `getNodeIds()` | [`get_node_ids`](bindings::WebVowl::get_node_ids) | Node ids, in index order |
98//! | `getNodePositions()` | [`get_node_positions`](bindings::WebVowl::get_node_positions) | Live `x, y` per node as a `Float32Array` |
99//! | `writePositionsInto(buf)` | [`write_positions_into`](bindings::WebVowl::write_positions_into) | Copy those positions into a caller-owned buffer |
100//! | `getStatistics()` | [`get_statistics`](bindings::WebVowl::get_statistics) | Degree, components, OWL2 metrics |
101//! | `getMetadata()` | [`get_metadata`](bindings::WebVowl::get_metadata) | Ontology header (IRI, version, title) |
102//!
103//! Filtering, pinning and folding (`filterByDegree`, `toggleDatatypes`,
104//! `toggleSetOperators`, `resetVisibility`, `filterHierarchy`, `pinNode`,
105//! `unpinNode`, `isPinned`, `resetPins`, `getPinnedCount`, `getNodeDegree`) are
106//! also always available; see [`bindings::WebVowl`] for the full list.
107//!
108//! ## Feature-gated
109//!
110//! | JavaScript | Feature | Purpose |
111//! |---|---|---|
112//! | `getGraphData()` | `debug-serde` | Full-graph serde export, for diagnostics |
113//! | `checkNodeClick(origin, dir)` | `interaction` | Ray-sphere node hit testing |
114//! | `new NggExplorer()` and its methods | `ngg1` | Zero-copy worker physics |
115//!
116//! # Features
117//!
118//! All optional features are **off by default**; the general-purpose engine —
119//! OWL/JSON parsing, the graph model, force layout (Barnes-Hut quadtree and the
120//! CSR simulation), pinning and statistics — is always compiled.
121//!
122//! - **`ngg1`** — the NGG1 binary tier format (a re-export of the
123//! [`ontomark-ngg1`](https://docs.rs/ontomark-ngg1) codec) and the
124//! [`NggExplorer`](bindings::explorer::NggExplorer) binding.
125//! - **`interaction`** — ray-sphere hit testing for click and selection.
126//! - **`debug-serde`** — the legacy full-graph serde export.
127//! - **`parallel`** — data-parallel ontology loading via rayon. Native only.
128//! - **`simd`** — WASM SIMD128 force accumulation. Needs the `simd128` target
129//! feature.
130//!
131//! # Licence
132//!
133//! MIT. See `LICENSE`, which carries both the upstream WebVOWL notice and
134//! DreamLab-AI's.
135
136#![deny(missing_docs)]
137#![deny(unsafe_code)]
138#![cfg_attr(docsrs, feature(doc_cfg))]
139
140pub mod bindings;
141pub mod debug;
142pub mod graph;
143pub mod layout;
144pub mod ontology;
145pub mod render;
146
147#[cfg(feature = "ngg1")]
148#[cfg_attr(docsrs, doc(cfg(feature = "ngg1")))]
149pub mod ngg1;
150
151#[cfg(feature = "interaction")]
152#[cfg_attr(docsrs, doc(cfg(feature = "interaction")))]
153pub mod interaction;
154
155mod error;
156
157pub use error::{Result, VowlError};
158
159use wasm_bindgen::prelude::*;
160
161/// WASM module entry point, run once when the module is instantiated.
162///
163/// `wasm-bindgen` calls this automatically; there is no need to invoke it from
164/// JavaScript. It is currently a no-op, reserved for panic-hook installation.
165#[wasm_bindgen(start)]
166pub fn init() {}
167
168/// The crate version, as reported to JavaScript.
169///
170/// Returns the `CARGO_PKG_VERSION` this module was compiled from — useful for
171/// asserting that the loaded `.wasm` matches the JavaScript glue shipped with it.
172///
173/// ```
174/// assert_eq!(vowl_wasm::version(), env!("CARGO_PKG_VERSION"));
175/// ```
176#[wasm_bindgen]
177pub fn version() -> String {
178 env!("CARGO_PKG_VERSION").to_string()
179}