Graphviz
Graphs in Graphviz's DOT language laid out and drawn as SVG, with no Graphviz installed: flowcharts, org charts and state machines.
Version 1.0.0 · 4 functions · 1 crate
Functions
What use graphviz.qwer gives a program, with the types Quillo sees. To take only some, or under other names: use graphviz_svg, graphviz_save from graphviz (Quillo 0.6).
graphviz_svg(dot: str) → stror an errorThe graph in dot (DOT text such as digraph { a -> b; b -> c [label="ok"]; }) laid out and drawn as an SVG. Node shapes, colours, labels, records, rankdir=LR and edge styles are understood; subgraphs (clusters) and HTML labels are not.
graphviz_save(dot: str, out: str) → stror an errorThe same drawing saved at out (folders made): the path.
graphviz_check(dot: str) → anyWhether dot reads as a graph: { ok, error }, error the parser's message (null when ok). For a form where people write their own graphs.
graphviz_edges_svg(edges: list, options: any) → stror an errorA graph drawn from edges, each [from, to] or { from, to, label }, without writing DOT: arrows unless options.directed is false, top to bottom unless options.direction is "LR" (left to right). Names are any text.
Get it
$ quillo install graphviz # graphviz.qwer into this folder$ quillo install graphviz --example # and its example, graphviz.qweOr with curl:
$ curl -O https://quillolang.org/plugins/files/graphviz.qwer$ curl -O https://quillolang.org/plugins/files/graphviz.qweThen, in your program, all of it or only what you call:
use graphviz.qweruse graphviz_svg, graphviz_save from graphviz // Quillo 0.6: only theseIt uses this crate, which cargo fetches the first time the plugin is built:
layout-rs = "0.1"Example
// An order's life drawn as a state machine, and a team's reporting lines drawn from a list.//// quillo run graphviz.qwe then open http://localhost:8080/order.svguse "graphviz.qwer"let ORDER = `digraph { rankdir=LR; node [shape=box, style=rounded]; new -> paid [label="pays"]; paid -> shipped [label="leaves the roastery"]; shipped -> delivered; new -> cancelled [label="no payment in 7 days"]; cancelled [color="red"];}`route GET /order.svg = respond(200, graphviz_svg(ORDER)?, { "content-type": "image/svg+xml" })route GET /team.svg { let team = [{ name: "Ada", boss: null }, { name: "Grace", boss: "Ada" }, { name: "Alan", boss: "Ada" }, { name: "Edsger", boss: "Grace" }] let edges = team | filter((p) => p.boss != null) | map((p) => [p.boss, p.name]) respond(200, graphviz_edges_svg(edges, null)?, { "content-type": "image/svg+xml" })}route GET /check = { good: graphviz_check(ORDER), bad: graphviz_check("digraph { a -> }") }$ quillo run graphviz.qwe$ curl 'localhost:8080/order.svg'$ curl 'localhost:8080/team.svg'$ curl 'localhost:8080/check'Source
//! Graphs written in Graphviz's DOT language laid out and drawn as SVG, without Graphviz installed: flowcharts, org charts, dependencies and state machines.//! version: 1.0.0//!//! [dependencies]//! layout-rs = "0.1"use layout::backends::svg::SVGWriter;use layout::gv::{DotParser, GraphBuilder};use serde_json::{json, Value};const MAX_DOT: usize = 512 * 1024;fn render(dot: &str) -> Result<String, String> { if dot.len() > MAX_DOT { return Err("the graph is larger than 512 KB of DOT".into()); } let dot = dot.to_string(); // The layout code can panic on graphs it does not handle: that is an error value here. std::panic::catch_unwind(move || -> Result<String, String> { let g = DotParser::new(&dot).process().map_err(|e| format!("the DOT cannot be read: {}", e))?; let mut gb = GraphBuilder::new(); gb.visit_graph(&g); let mut vg = gb.get(); let mut svg = SVGWriter::new(); vg.do_it(false, false, false, &mut svg); Ok(svg.finalize()) }) .map_err(|_| "the graph could not be laid out".to_string())?}/// The graph in `dot` (DOT text such as `digraph { a -> b; b -> c [label="ok"]; }`) laid out and drawn as an SVG./// Node shapes, colours, labels, records, `rankdir=LR` and edge styles are understood; subgraphs (clusters) and HTML labels are not.pub fn graphviz_svg(dot: &str) -> Result<String, String> { render(dot)}/// The same drawing saved at `out` (folders made): the path.pub fn graphviz_save(dot: &str, out: &str) -> Result<String, String> { let svg = render(dot)?; if let Some(dir) = std::path::Path::new(out).parent().filter(|d| !d.as_os_str().is_empty()) { std::fs::create_dir_all(dir).map_err(|e| format!("{}: {}", dir.display(), e))?; } std::fs::write(out, svg).map_err(|e| format!("{}: {}", out, e))?; Ok(out.to_string())}/// Whether `dot` reads as a graph: `{ ok, error }`, `error` the parser's message (null when ok). For a form where people write their own graphs.pub fn graphviz_check(dot: &str) -> Value { match DotParser::new(dot).process() { Ok(_) => json!({ "ok": true, "error": null }), Err(e) => json!({ "ok": false, "error": format!("the DOT cannot be read: {}", e) }), }}fn quote(s: &str) -> String { format!("\"{}\"", s.replace('\\', "\\\\").replace('"', "\\\"").replace('\n', "\\n"))}/// A graph drawn from `edges`, each `[from, to]` or `{ from, to, label }`, without writing DOT: arrows unless `options.directed` is false,/// top to bottom unless `options.direction` is "LR" (left to right). Names are any text.pub fn graphviz_edges_svg(edges: Vec<serde_json::Value>, options: serde_json::Value) -> Result<String, String> { if edges.len() > 5000 { return Err("up to 5,000 edges".into()); } let directed = options.get("directed").and_then(Value::as_bool).unwrap_or(true); let lr = options.get("direction").and_then(Value::as_str).map_or(false, |d| d.eq_ignore_ascii_case("lr")); let text = |v: Option<&Value>| match v { Some(Value::String(s)) => Some(s.clone()), Some(Value::Number(n)) => Some(n.to_string()), _ => None, }; let mut dot = format!("{} {{\n", if directed { "digraph" } else { "graph" }); if lr { dot += "rankdir=LR;\n"; } for e in &edges { let (from, to, label) = match e { Value::Array(a) => (text(a.first()), text(a.get(1)), text(a.get(2))), Value::Object(_) => (text(e.get("from")), text(e.get("to")), text(e.get("label"))), _ => (None, None, None), }; let (Some(from), Some(to)) = (from, to) else { return Err(format!("an edge is [from, to] or {{ from, to, label }}, not {}", e)) }; dot += &format!("{} {} {}", quote(&from), if directed { "->" } else { "--" }, quote(&to)); if let Some(l) = label { dot += &format!(" [label={}]", quote(&l)); } dot += ";\n"; } dot += "}\n"; render(&dot)}Related
audio_wave.qwer · 1.0.0A WAV file's waveform for an audio player: the loudness of each slice from 0 to 1, or drawn as an SVG of bars.6 views · 7 downloadsInitials avatars avatar_initials.qwer · 1.0.0Round avatars with a person's initials as SVG, the colour from the name so each keeps theirs, the text readable on it.6 views · 7 downloadsPlaceholders placeholder.qwer · 1.0.0Placeholder images as SVG: a box of any size with its dimensions or your text in your colours, as markup or a data: URL.8 views · 8 downloadsQR codes qr.qwer · 1.0.0QR codes as SVG for links, tickets, Wi-Fi and two-factor set-up, in any colours, or drawn as text.10 views · 7 downloadsSparklines sparkline.qwer · 1.0.0Tiny inline charts from a list of numbers: SVG line and bar sparklines for table cells and sentences, and the same in text (▁▃▆█).6 views · 8 downloadsSprite sheets sprites.qwer · 1.0.0Sprite sheets: small pictures packed into one PNG with a CSS class or a JSON frame for each, retina sheets too, and SVG icons gathered into one SVG of symbols to draw with <use href>.8 views · 6 downloadsThread 0 posts
On Graphviz: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.