Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: mermaid rendering #125

Merged
merged 6 commits into from
Mar 1, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
134 changes: 134 additions & 0 deletions src/render.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
//! 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,
/// Custom style
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)),
}
}

/// 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.as_ref() {
Self::Solid => format!("--{}-->", lbl).into(),
Self::Dotted => format!("-.{}.->", lbl).into(),
Self::Dashed => format!("-.{}.->", lbl).into(),
Self::Custom(s) => s.into(),
Self::Labelled(_, _) => panic!("Nested labelled edges are not supported"),
}
}
}
}
}
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
Loading