Edge bundling
Hierarchical edge bundling: the leaves of a tree sit round a circle and each link follows the tree between them, so links between the same branches gather into bundles. Hover, touch or step to a leaf to see what it links to in one colour and what links to it in another.
| Leaf | Group | Imports | Imported by |
|---|---|---|---|
| Button | ui | Theme | Dialog, Form |
| Dialog | ui | Button, Theme | |
| Form | ui | Button, Schema, Store | |
| Table | ui | Query, Theme, Store | |
| Chart | ui | Theme, Query | |
| Theme | ui | Table, Chart, Dialog, Button | |
| Store | state | Cache, Sync | Form, Table, Session, Sync |
| Router | state | Session, Metrics | |
| Session | state | Auth, Store | Router |
| Cache | state | Logger | Store, Query |
| Client | data | Auth, Config, Logger, Metrics | Query, Sync |
| Schema | data | Logger | Form, Query |
| Query | data | Client, Cache, Schema | Table, Chart |
| Sync | data | Client, Store | Store |
| Logger | platform | Config | Cache, Client, Schema, Auth |
| Metrics | platform | Config | Router, Client |
| Config | platform | Client, Auth, Metrics, Logger | |
| Auth | platform | Config, Logger | Session, Client |
Usage
use hydroxy_leptos::edge_bundling_chart::*;
use leptos::prelude::*;
fn package(name: &str, modules: &[&str]) -> Tree {
Tree::branch(name, modules.iter().map(|m| Tree::leaf(*m, 1.0)).collect())
}
#[component]
pub fn Demo() -> impl IntoView {
let tree = Tree::branch(
"app",
vec![
package("ui", &["Button", "Dialog", "Form", "Table", "Chart", "Theme"]),
package("state", &["Store", "Router", "Session", "Cache"]),
package("data", &["Client", "Schema", "Query", "Sync"]),
package("platform", &["Logger", "Metrics", "Config", "Auth"]),
],
);
// Which module imports which.
let imports = [
("Dialog", "Button"),
("Form", "Button"),
("Form", "Schema"),
("Form", "Store"),
("Table", "Query"),
("Table", "Theme"),
("Table", "Store"),
("Chart", "Theme"),
("Chart", "Query"),
("Dialog", "Theme"),
("Button", "Theme"),
("Store", "Cache"),
("Store", "Sync"),
("Router", "Session"),
("Router", "Metrics"),
("Session", "Auth"),
("Session", "Store"),
("Cache", "Logger"),
("Client", "Auth"),
("Client", "Config"),
("Client", "Logger"),
("Client", "Metrics"),
("Query", "Client"),
("Query", "Cache"),
("Query", "Schema"),
("Sync", "Client"),
("Sync", "Store"),
("Schema", "Logger"),
("Auth", "Config"),
("Auth", "Logger"),
("Metrics", "Config"),
("Logger", "Config"),
];
let links: Vec<Link> = imports.iter().map(|(a, b)| Link::new(*a, *b, 1.0)).collect();
view! { <EdgeBundlingChart tree=tree links=links label="Module imports" out_label="Imports" in_label="Imported by" /> }
}use dioxus::prelude::*;
use hydroxy_dioxus::edge_bundling_chart::{EdgeBundlingChart, Link, Tree};
fn package(name: &str, modules: &[&str]) -> Tree {
Tree::branch(name, modules.iter().map(|m| Tree::leaf(*m, 1.0)).collect())
}
#[component]
pub fn EdgeBundlingChartDemo() -> Element {
let tree = Tree::branch(
"app",
vec![
package("ui", &["Button", "Dialog", "Form", "Table", "Chart", "Theme"]),
package("state", &["Store", "Router", "Session", "Cache"]),
package("data", &["Client", "Schema", "Query", "Sync"]),
package("platform", &["Logger", "Metrics", "Config", "Auth"]),
],
);
// Which module imports which.
let imports = [
("Dialog", "Button"),
("Form", "Button"),
("Form", "Schema"),
("Form", "Store"),
("Table", "Query"),
("Table", "Theme"),
("Table", "Store"),
("Chart", "Theme"),
("Chart", "Query"),
("Dialog", "Theme"),
("Button", "Theme"),
("Store", "Cache"),
("Store", "Sync"),
("Router", "Session"),
("Router", "Metrics"),
("Session", "Auth"),
("Session", "Store"),
("Cache", "Logger"),
("Client", "Auth"),
("Client", "Config"),
("Client", "Logger"),
("Client", "Metrics"),
("Query", "Client"),
("Query", "Cache"),
("Query", "Schema"),
("Sync", "Client"),
("Sync", "Store"),
("Schema", "Logger"),
("Auth", "Config"),
("Auth", "Logger"),
("Metrics", "Config"),
("Logger", "Config"),
];
let links: Vec<Link> = imports.iter().map(|(a, b)| Link::new(*a, *b, 1.0)).collect();
rsx! {
EdgeBundlingChart { tree, links, label: "Module imports", out_label: "Imports", in_label: "Imported by" }
}
}
Anatomy
| Component | data-part | Renders |
|---|---|---|
EdgeBundlingChart | root | figure; `data-active` while a leaf is picked out |
| plot | Focusable group holding the drawing and the tooltip |
| link | B-spline routed through the tree; `data-role` is `out` or `in` relative to the picked leaf |
| leaf | Dot and rotated label on the ring; `data-role` is `target`, `source` or `both` relative to the picked leaf, `data-selected` on the picked leaf |
| tooltip | Group and the number of links each way |
| legend | The two link colours |
| sr-live | Announces the picked leaf |
| sr-table | Every leaf with its group and what it links to and from |
Keyboard
| Key | Does |
|---|---|
ArrowRight / ArrowLeft | Step clockwise or anticlockwise round the circle |
Home / End | Jump to the first or last leaf |
Escape | Clear the picked leaf |
Props
| Prop | Type | Notes |
|---|---|---|
tree | Tree | Required. Tree::branch(name, children) and Tree::leaf(name, value); leaves go round the circle in order |
links | Vec<Link> | Required. Link::new(from, to, value) by leaf name; values are ignored |
label | String | Required. What the chart shows; the accessible name |
beta | f64 | Bundling strength from 0 (straight) to 1 (follows the tree). Default 0.85 |
out_label / in_label | String | What a link means each way. Default "Links to" and "Linked from" |
size | u16 | Drawing width and height in px. Default 600 |
Events
| Prop | Type | Notes |
|---|---|---|
on_hover | Callback<Option<Datum>> | The mark under the pointer or keyboard focus changes, or is left. Fires only on a change |
on_select | Callback<Datum> | Click, tap or Enter on a mark |