Skip to content

Commit

Permalink
feat!: mermaid rendering (#125)
Browse files Browse the repository at this point in the history
Renames the `dot` module to `render`, and adds a mermaid formatter using
the same style configuration structs.

BREAKING CHANGE: Moved `portgraph::dot` to `portgraph::render`
  • Loading branch information
aborgna-q authored Mar 1, 2024
1 parent d94ba4f commit 6eddd63
Show file tree
Hide file tree
Showing 4 changed files with 566 additions and 105 deletions.
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,10 @@ use pyo3::prelude::*;
use serde::{Deserialize, Deserializer, Serialize, Serializer};

pub mod algorithms;
pub mod dot;
pub mod hierarchy;
pub mod multiportgraph;
pub mod portgraph;
pub mod render;
pub mod secondary;
pub mod unmanaged;
pub mod view;
Expand Down
144 changes: 144 additions & 0 deletions src/render.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
//! This module contains rendering logic from portgraphs into graphviz and
//! mermaid diagrams.
mod dot;
mod mermaid;

use std::borrow::Cow;

pub use dot::{DotFormat, DotFormatter};
pub use mermaid::{MermaidFormat, MermaidFormatter};

use self::mermaid::encode_label;

/// Style of a rendered edge.
///
/// Defaults to a box with no label.
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum NodeStyle {
/// Ignore the node. No edges will be connected to it.
Hidden,
/// Draw a box with the label inside.
Box(String),
}

impl NodeStyle {
/// Show a node label with the default style.
pub fn new(label: impl ToString) -> Self {
Self::Box(label.to_string())
}
}

impl Default for NodeStyle {
fn default() -> Self {
Self::Box(String::new())
}
}

/// Style of an edge in a rendered graph. Defaults to a box with no label.
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum PortStyle {
/// Do not draw a label. Edges will be connected to the node.
Hidden,
/// Just the port label. Optionally prepend the port index.
Plain(String, bool),
/// Draw a box around the label. Optionally prepend the port index.
Boxed(String, bool),
}

impl PortStyle {
/// Show a port label with the default style.
pub fn new(label: impl ToString) -> Self {
Self::Boxed(label.to_string(), true)
}

/// Just the port label. Optionally prepend the port index.
pub fn text(label: impl ToString, show_offset: bool) -> Self {
Self::Plain(label.to_string(), show_offset)
}

/// Draw a box around the label. Optionally prepend the port index.
pub fn boxed(label: impl ToString, show_offset: bool) -> Self {
Self::Boxed(label.to_string(), show_offset)
}
}

impl Default for PortStyle {
fn default() -> Self {
Self::Boxed(String::new(), true)
}
}

/// Style of an edge in a rendered graph. Defaults to [`EdgeStyle::Solid`].
#[derive(Clone, Debug, PartialEq, Eq, Default)]
#[non_exhaustive]
pub enum EdgeStyle {
/// Normal line
#[default]
Solid,
/// Dotted line
Dotted,
/// Dashed line
Dashed,
/// Edge style with a label
Labelled(String, Box<EdgeStyle>),
/// Custom style
Custom(String),
}

impl EdgeStyle {
/// Adds a label to the edge style.
///
/// If the edge style already has a label, it will be replaced.
pub fn with_label(self, label: impl ToString) -> Self {
match self {
Self::Labelled(_, e) => Self::Labelled(label.to_string(), e),
_ => Self::Labelled(label.to_string(), Box::new(self)),
}
}

/// Returns the base style of the edge, without labels.
pub fn strip_label(&self) -> &Self {
match self {
Self::Labelled(_, e) => e.strip_label(),
e => e,
}
}

/// Get the style as a graphviz style string
pub(super) fn as_dot_str(&self) -> &str {
match self {
Self::Solid => "",
Self::Dotted => "dotted",
Self::Dashed => "dashed",
Self::Custom(s) => s,
// Ignore edge labels.
Self::Labelled(_lbl, e) => e.as_dot_str(),
}
}

/// Get the style as a graphviz style string
pub(super) fn as_mermaid_str(&self) -> Cow<'_, str> {
match self {
Self::Solid => "-->".into(),
Self::Dotted => "-.->".into(),
// Dashed links are not supported in mermaid, we use dots instead.
Self::Dashed => "-.->".into(),
Self::Custom(s) => s.into(),
Self::Labelled(lbl, e) => {
let lbl = encode_label("", lbl);
match e.strip_label() {
Self::Solid => format!("--{}-->", lbl).into(),
Self::Dotted => format!("-.{}.->", lbl).into(),
Self::Dashed => format!("-.{}.->", lbl).into(),
Self::Custom(s) => s.into(),
Self::Labelled(_, _) => {
unreachable!("`strip_label` cannot return a `Labelled`")
}
}
}
}
}
}
110 changes: 6 additions & 104 deletions src/dot.rs → src/render/dot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,98 +4,9 @@ use std::fmt::Display;

use crate::{Direction, Hierarchy, LinkView, NodeIndex, PortIndex, Weights};

/// Style of an edge in a dot graph. Defaults to "None".
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum NodeStyle {
/// Ignore the node. No edges will be connected to it.
Hidden,
/// Draw a box with the label inside.
Box(String),
}

impl NodeStyle {
/// Show a node label with the default style.
pub fn new(label: impl ToString) -> Self {
Self::Box(label.to_string())
}
}

impl Default for NodeStyle {
fn default() -> Self {
Self::Box(String::new())
}
}

/// Style of an edge in a dot graph. Defaults to `Box("")`.
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum PortStyle {
/// Do not draw a label. Edges will be connected to the node.
Hidden,
/// Just the port label. Optionally prepend the port index.
#[deprecated(note = "Use `PortStyle::Plain(_, true)` instead")]
Text(String),
/// Draw a box around the label. Optionally prepend the port index.
#[deprecated(note = "Use `PortStyle::Boxed(_, true)` instead")]
Box(String),
/// Just the port label. Optionally prepend the port index.
Plain(String, bool),
/// Draw a box around the label. Optionally prepend the port index.
Boxed(String, bool),
}

impl PortStyle {
/// Show a port label with the default style.
pub fn new(label: impl ToString) -> Self {
Self::Boxed(label.to_string(), true)
}

/// Just the port label. Optionally prepend the port index.
pub fn text(label: impl ToString, show_offset: bool) -> Self {
Self::Plain(label.to_string(), show_offset)
}

/// Draw a box around the label. Optionally prepend the port index.
pub fn boxed(label: impl ToString, show_offset: bool) -> Self {
Self::Boxed(label.to_string(), show_offset)
}
}
use super::{EdgeStyle, NodeStyle, PortStyle};

impl Default for PortStyle {
fn default() -> Self {
Self::Boxed(String::new(), true)
}
}

/// Style of an edge in a dot graph. Defaults to [`EdgeStyle::Solid`].
#[derive(Clone, Debug, PartialEq, Eq, Default)]
#[non_exhaustive]
pub enum EdgeStyle {
/// Normal line
#[default]
Solid,
/// Dotted line
Dotted,
/// Dashed line
Dashed,
/// Custom style
Custom(String),
}

impl EdgeStyle {
/// Get the style as a graphviz style string
pub fn as_str(&self) -> &str {
match self {
Self::Solid => "",
Self::Dotted => "dotted",
Self::Dashed => "dashed",
Self::Custom(s) => s,
}
}
}

/// Configurable dot formatter for a `PortGraph`.
/// Configurable mermaid formatter for a `PortGraph`.
pub struct DotFormatter<'g, G: LinkView> {
graph: &'g G,
forest: Option<&'g Hierarchy>,
Expand Down Expand Up @@ -148,6 +59,9 @@ where
}

/// Encode some `Weights` in the dot format.
///
/// This is a convenience method to set the node and port styles based on the weight values.
/// It overrides any previous node or port style set.
pub fn with_weights<'w, N, P>(self, weights: &'w Weights<N, P>) -> Self
where
'w: 'g,
Expand Down Expand Up @@ -266,18 +180,6 @@ where
style: String::new(),
label: make_label(offset, show_offset, &label),
}),
#[allow(deprecated)]
PortStyle::Text(label) => Some(PortCellStrings {
id: format!("{}{}", dir, offset),
style: "border=\"0\"".to_string(),
label: make_label(offset, true, &label),
}),
#[allow(deprecated)]
PortStyle::Box(label) => Some(PortCellStrings {
id: format!("{}{}", dir, offset),
style: String::new(),
label: make_label(offset, true, &label),
}),
})
.collect()
}
Expand Down Expand Up @@ -313,7 +215,7 @@ where
let to_node = self.graph.port_node(to).expect("missing node");
let to_offset = self.graph.port_offset(to).expect("missing port").index();
let edge_style = self.edge_style(from, to);
let edge_label = edge_style.as_str();
let edge_label = edge_style.as_dot_str();
format!(
"{}:out{} -> {}:in{} [style=\"{edge_label}\"]\n",
from_node.index(),
Expand Down
Loading

0 comments on commit 6eddd63

Please sign in to comment.