Hive plot
Two to four axes from the centre, one per kind of node. Each node sits on its kind's axis as far out as its value, and links curve between axes, so the same data always draws the same picture. Hover, touch or step to a node to light its links.
| Node | Type | p95 latency | Links | Linked to |
|---|---|---|---|---|
| Partner API | Clients | 95 ms | 2 | Auth, Catalog |
| Web app | Clients | 120 ms | 6 | Auth, Catalog, Search, Cart, Checkout, S3 |
| iOS app | Clients | 180 ms | 4 | Auth, Catalog, Cart, Checkout |
| Android app | Clients | 210 ms | 3 | Auth, Search, Checkout |
| Auth | Services | 18 ms | 6 | Web app, iOS app, Android app, Partner API, Postgres, Redis |
| Cart | Services | 30 ms | 3 | Web app, iOS app, Redis |
| Catalog | Services | 42 ms | 6 | Web app, iOS app, Partner API, Postgres, Redis, S3 |
| Notifications | Services | 55 ms | 1 | Kafka |
| Search | Services | 85 ms | 3 | Web app, Android app, Elasticsearch |
| Checkout | Services | 140 ms | 5 | Web app, iOS app, Android app, Postgres, Kafka |
| Payments | Services | 260 ms | 2 | Postgres, Kafka |
| Redis | Stores | 2 ms | 3 | Auth, Catalog, Cart |
| Kafka | Stores | 8 ms | 3 | Checkout, Payments, Notifications |
| Postgres | Stores | 12 ms | 4 | Auth, Catalog, Checkout, Payments |
| Elasticsearch | Stores | 38 ms | 1 | Search |
| S3 | Stores | 65 ms | 2 | Web app, Catalog |
Usage
use hydroxy_leptos::hive_plot_chart::*;
use leptos::prelude::*;
pub fn demo() -> AnyView {
// p95 latency in ms places each part of the system along its tier's axis.
let parts = [
("Web app", 0, 120.0),
("iOS app", 0, 180.0),
("Android app", 0, 210.0),
("Partner API", 0, 95.0),
("Auth", 1, 18.0),
("Catalog", 1, 42.0),
("Search", 1, 85.0),
("Cart", 1, 30.0),
("Checkout", 1, 140.0),
("Payments", 1, 260.0),
("Notifications", 1, 55.0),
("Postgres", 2, 12.0),
("Redis", 2, 2.0),
("Elasticsearch", 2, 38.0),
("S3", 2, 65.0),
("Kafka", 2, 8.0),
];
let nodes: Vec<Node> = parts.iter().map(|(id, axis, ms)| Node::new(*id, *axis, *ms)).collect();
let calls = [
("Web app", "Auth"),
("Web app", "Catalog"),
("Web app", "Search"),
("Web app", "Cart"),
("Web app", "Checkout"),
("Web app", "S3"),
("iOS app", "Auth"),
("iOS app", "Catalog"),
("iOS app", "Cart"),
("iOS app", "Checkout"),
("Android app", "Auth"),
("Android app", "Search"),
("Android app", "Checkout"),
("Partner API", "Auth"),
("Partner API", "Catalog"),
("Auth", "Postgres"),
("Auth", "Redis"),
("Catalog", "Postgres"),
("Catalog", "Redis"),
("Catalog", "S3"),
("Search", "Elasticsearch"),
("Cart", "Redis"),
("Checkout", "Postgres"),
("Checkout", "Kafka"),
("Payments", "Postgres"),
("Payments", "Kafka"),
("Notifications", "Kafka"),
];
let links: Vec<Link> = calls.iter().map(|(a, b)| Link::new(*a, *b, 1.0)).collect();
let tiers = vec!["Clients".to_string(), "Services".to_string(), "Stores".to_string()];
view! {
<HivePlotChart
nodes=nodes
links=links
axes=tiers
value_label="p95 latency"
label="Which parts of the system call which"
format=|v: f64| format!("{v:.0} ms")
/>
}
.into_any()
}use dioxus::prelude::*;
use hydroxy_dioxus::hive_plot_chart::{HivePlotChart, Link, Node};
#[component]
pub fn HivePlotChartDemo() -> Element {
// p95 latency in ms places each part of the system along its tier's axis.
let parts = [
("Web app", 0, 120.0),
("iOS app", 0, 180.0),
("Android app", 0, 210.0),
("Partner API", 0, 95.0),
("Auth", 1, 18.0),
("Catalog", 1, 42.0),
("Search", 1, 85.0),
("Cart", 1, 30.0),
("Checkout", 1, 140.0),
("Payments", 1, 260.0),
("Notifications", 1, 55.0),
("Postgres", 2, 12.0),
("Redis", 2, 2.0),
("Elasticsearch", 2, 38.0),
("S3", 2, 65.0),
("Kafka", 2, 8.0),
];
let nodes: Vec<Node> = parts.iter().map(|(id, axis, ms)| Node::new(*id, *axis, *ms)).collect();
let calls = [
("Web app", "Auth"),
("Web app", "Catalog"),
("Web app", "Search"),
("Web app", "Cart"),
("Web app", "Checkout"),
("Web app", "S3"),
("iOS app", "Auth"),
("iOS app", "Catalog"),
("iOS app", "Cart"),
("iOS app", "Checkout"),
("Android app", "Auth"),
("Android app", "Search"),
("Android app", "Checkout"),
("Partner API", "Auth"),
("Partner API", "Catalog"),
("Auth", "Postgres"),
("Auth", "Redis"),
("Catalog", "Postgres"),
("Catalog", "Redis"),
("Catalog", "S3"),
("Search", "Elasticsearch"),
("Cart", "Redis"),
("Checkout", "Postgres"),
("Checkout", "Kafka"),
("Payments", "Postgres"),
("Payments", "Kafka"),
("Notifications", "Kafka"),
];
let links: Vec<Link> = calls.iter().map(|(a, b)| Link::new(*a, *b, 1.0)).collect();
let tiers = vec!["Clients".to_string(), "Services".to_string(), "Stores".to_string()];
rsx! {
HivePlotChart {
nodes,
links,
axes: tiers,
value_label: "p95 latency",
label: "Which parts of the system call which",
format: |v: f64| format!("{v:.0} ms"),
}
}
}
Anatomy
| Component | data-part | Renders |
|---|---|---|
HivePlotChart | root | figure; `data-active` while a node is picked out |
| plot | Focusable group holding the drawing and the tooltip |
| axis | One line per kind, centre outwards, named at its tip by `axis-label` |
| link | Curve between two axes in the source axis's colour; `data-highlighted` when it touches the picked node |
| node | Dot on its axis; `data-highlighted` on the picked node and its neighbours, `data-selected` on the picked node |
| tooltip | Kind, value and link count of the picked node |
| scale-note | The value range from centre to tip, shared by every axis |
| sr-live | Announces the picked node |
| sr-table | Every node axis by axis with its value, links and neighbours |
Keyboard
| Key | Does |
|---|---|
ArrowRight / ArrowLeft | Step to the next or previous node, axis by axis, centre outwards |
Home / End | Jump to the first or last node |
Escape | Clear the picked node |
Props
| Prop | Type | Notes |
|---|---|---|
nodes | Vec<Node> | Required. Node::new(id, axis, value) |
links | Vec<Link> | Required. Link::new(a, b, value) by id; links within one axis are ignored |
label | String | Required. What the chart shows; the accessible name |
axes | Vec<String> | Axis names; two to four, and their count sets the number of axes. Default: one per axis used |
value_label | String | Name of the value along the axes. Default "Value" |
format | Fn(f64) -> String | Formats values in the tooltip, scale note and table |
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 |