Skip to content

Commit

Permalink
Merge pull request #23 from azriel91/feature/minor-graphviz-enhancements
Browse files Browse the repository at this point in the history
  • Loading branch information
azriel91 authored Jul 17, 2024
2 parents 0afd6c4 + 7c3bab5 commit 907d3f8
Show file tree
Hide file tree
Showing 8 changed files with 237 additions and 7 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## unreleased

* Add `TextEditor` in playground which uses [monaco][monaco] / [rust-monaco][rust-monaco].
* Add `GraphvizAttrs.pack_mode` to specify the `packmode` for subgraphs.

[monaco]: https://github.com/microsoft/monaco-editor
[rust-monaco]: https://github.com/siku2/rust-monaco
Expand Down
9 changes: 7 additions & 2 deletions crate/model/src/common/graphviz_attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,18 @@ use serde::{Deserialize, Serialize};

pub use self::{
edge_constraints::EdgeConstraints, edge_dir::EdgeDir, edge_dirs::EdgeDirs,
edge_minlens::EdgeMinlens,
edge_minlens::EdgeMinlens, pack_mode::PackMode, pack_mode_flag::PackModeFlag,
};

mod edge_constraints;
mod edge_dir;
mod edge_dirs;
mod edge_minlens;
mod pack_mode;
mod pack_mode_flag;

/// Additional attributes specifically for GraphViz.
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
#[serde(default)]
pub struct GraphvizAttrs {
/// The default [`constraint`] value for edges, defaults to `true`.
Expand All @@ -38,6 +40,8 @@ pub struct GraphvizAttrs {
///
/// [`minlen`]: https://graphviz.org/docs/attrs/minlen/
pub edge_minlens: EdgeMinlens,
/// How closely to pack together graph components.
pub pack_mode: PackMode,
}

impl GraphvizAttrs {
Expand Down Expand Up @@ -146,6 +150,7 @@ impl Default for GraphvizAttrs {
edge_dirs: EdgeDirs::default(),
edge_minlen_default: 1,
edge_minlens: EdgeMinlens::default(),
pack_mode: PackMode::default(),
}
}
}
144 changes: 144 additions & 0 deletions crate/model/src/common/graphviz_attrs/pack_mode.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
use std::{fmt, fmt::Display, str::FromStr};

use serde::{de, Deserialize, Deserializer, Serialize, Serializer};

use crate::common::graphviz_attrs::PackModeFlag;

/// The GraphViz [`packMode`] type, used to set the [`packmode`] attribute.
///
/// See also the [`pack.3`] definition and [parsing] code.
///
/// [`packmode`]: https://graphviz.org/docs/attrs/packmode/
/// [`packMode`]: https://graphviz.org/docs/attr-types/packMode/
/// [`pack.3`]: https://gitlab.com/graphviz/graphviz/-/blob/13b87ed889893c07315ef2592b0988038e04027c/lib/pack/pack.3#L13-22
/// [parsing]: https://gitlab.com/graphviz/graphviz/-/blob/13b87ed889893c07315ef2592b0988038e04027c/lib/pack/pack.c#L1173-1214
#[derive(Clone, Debug, PartialEq)]
pub enum PackMode {
Node,
Cluster,
Graph,
Array {
flags: Vec<PackModeFlag>,
number: Option<u32>,
},
Aspect(f32),
}

impl Default for PackMode {
fn default() -> Self {
Self::Array {
flags: Vec::new(),
number: Some(1),
}
}
}

impl FromStr for PackMode {
type Err = String;

fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"node" => Ok(Self::Node),
"cluster" => Ok(Self::Cluster),
"graph" => Ok(Self::Graph),
s if s.starts_with("array") => {
let array_args = &s["array".len()..];
let number_start = array_args.find(|c| char::is_ascii_digit(&c));
let flags_start = if array_args.starts_with('_') {
Some(1)
} else {
None
};
let flags_end = number_start.unwrap_or(array_args.len());
let flags = flags_start
.map(|flags_start| {
let flag_candidates = &array_args[flags_start..flags_end];
flag_candidates.chars().try_fold(
Vec::with_capacity(flag_candidates.len()),
|mut flags, flag_char| {
let pack_mode_flag =
PackModeFlag::from_str(&String::from(flag_char))?;

flags.push(pack_mode_flag);

Ok::<_, String>(flags)
},
)
})
.transpose()?
.unwrap_or_default();
let number = number_start
.map(|number_start| {
let number_str = &array_args[number_start..];
number_str.parse::<u32>().map_err(|_e| {
format!(
"Failed to parse `{number_str}` as a number for `PackMode::Array`."
)
})
})
.transpose()?;

Ok(Self::Array { flags, number })
}
s if s.starts_with("aspect") => {
let aspect_args = &s["aspect".len()..];
let aspect_start = aspect_args.find(|c| char::is_ascii_digit(&c));
let aspect = aspect_start
.map(|aspect_start| {
let aspect_str = &aspect_args[aspect_start..];
aspect_str.parse::<f32>().map_err(|_e| {
format!(
"Failed to parse `{aspect_str}` as a float for `PackMode::Aspect`."
)
})
})
.transpose()?
.unwrap_or(1.0);

Ok(Self::Aspect(aspect))
}
_ => Err(format!("Failed to parse `PackMode` from `{s}`.")),
}
}
}

impl Display for PackMode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PackMode::Node => "node".fmt(f),
PackMode::Cluster => "cluster".fmt(f),
PackMode::Graph => "graph".fmt(f),
PackMode::Array { flags, number } => {
write!(f, "array")?;
if !flags.is_empty() {
write!(f, "_")?;
flags.iter().try_for_each(|flag| flag.fmt(f))?;
}
if let Some(number) = number {
number.fmt(f)?;
}
Ok(())
}
PackMode::Aspect(aspect) => write!(f, "aspect{aspect}"),
}
}
}

impl<'de> Deserialize<'de> for PackMode {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
FromStr::from_str(&s).map_err(de::Error::custom)
}
}

impl Serialize for PackMode {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&self.to_string())
}
}
61 changes: 61 additions & 0 deletions crate/model/src/common/graphviz_attrs/pack_mode_flag.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
use std::{fmt, fmt::Display, str::FromStr};

use serde::{Deserialize, Serialize};

/// Use when [`PackMode::Array`] is used.
///
/// See the GraphViz [`packmode`] attribute and [`packMode`] type.
///
/// [`PackMode::Array`]: crate::common::graphviz_attrs::PackMode::Array
/// [`packmode`]: https://graphviz.org/docs/attrs/packmode/
/// [`packMode`]: https://graphviz.org/docs/attr-types/packMode/
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum PackModeFlag {
/// Column major order: clusters are laid out left to right.
C,
/// For a vertical rankdir (TB, BT), clusters should be top-aligned.
T,
/// For a vertical rankdir (TB, BT), clusters should be bottom-aligned.
B,
/// For a horizontal rankdir (LR, RL), clusters should be left-aligned.
L,
/// For a horizontal rankdir (LR, RL), clusters should be right-aligned.
R,
/// User specified order: clusters are laid out based on the cluster's
/// `sortv` attribute if specified.
///
/// If it isn't specified, `0` is used.
U,
}

impl FromStr for PackModeFlag {
type Err = String;

fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"c" => Ok(Self::C),
"t" => Ok(Self::T),
"b" => Ok(Self::B),
"l" => Ok(Self::L),
"r" => Ok(Self::R),
"u" => Ok(Self::U),
_ => Err(format!(
"Unable to map `{s}` to a `PackModeFlag`. Valid strings are: \"c\", \"t\", \"b\", \"l\", \"r\", \"u\"."
)),
}
}
}

impl Display for PackModeFlag {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PackModeFlag::C => "c".fmt(f),
PackModeFlag::T => "t".fmt(f),
PackModeFlag::B => "b".fmt(f),
PackModeFlag::L => "l".fmt(f),
PackModeFlag::R => "r".fmt(f),
PackModeFlag::U => "u".fmt(f),
}
}
}
2 changes: 1 addition & 1 deletion crate/model/src/info_graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ mod graph_dir;
mod graph_style;
mod tag;

#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
#[serde(default)]
pub struct InfoGraph {
/// Style of graph to render.
Expand Down
14 changes: 10 additions & 4 deletions crate/rt/src/into_graphviz_dot_src/info_graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,9 @@ use crate::{InfoGraphDot, IntoGraphvizDotSrc};
/// [`tailwind-css`]: https://github.com/oovm/tailwind-rs
impl IntoGraphvizDotSrc for &InfoGraph {
fn into(self, theme: &GraphvizDotTheme) -> DotSrcAndStyles {
let graph_attrs = graph_attrs(theme, self.direction());
let node_attrs = node_attrs(self.graph_style(), theme);
let graphviz_attrs = self.graphviz_attrs();
let graph_attrs = graph_attrs(theme, self.direction(), graphviz_attrs);
let node_attrs = node_attrs(self.graph_style(), theme);
let edge_attrs = edge_attrs(graphviz_attrs, theme);

// Build a map from `NodeId` to their `NodeHierarchy`, so that we don't have to
Expand Down Expand Up @@ -174,14 +174,20 @@ impl IntoGraphvizDotSrc for &InfoGraph {
}
}

fn graph_attrs(theme: &GraphvizDotTheme, graph_dir: GraphDir) -> String {
fn graph_attrs(
theme: &GraphvizDotTheme,
graph_dir: GraphDir,
graphviz_attrs: &GraphvizAttrs,
) -> String {
let plain_text_color = theme.plain_text_color();
// Note: `margin` is set to 0.1 because some text lies outside the viewport.
// This may be due to incorrect width calculation for emoji characters, which
// GraphViz falls back to the space character width.

let node_point_size = theme.node_point_size();

let pack_mode = &graphviz_attrs.pack_mode;

let rankdir = match graph_dir {
GraphDir::Horizontal => "LR",
GraphDir::Vertical => "TB",
Expand All @@ -197,7 +203,7 @@ fn graph_attrs(theme: &GraphvizDotTheme, graph_dir: GraphDir) -> String {
ranksep = 0.02
bgcolor = "transparent"
fontname = "helvetica"
packmode = "graph"
packmode = "{pack_mode}"
fontcolor = "{plain_text_color}"
fontsize = {node_point_size}
rankdir = {rankdir}
Expand Down
1 change: 1 addition & 0 deletions playground/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
<link data-trunk rel="copy-dir" href="../target/site/fonts" />
<link data-trunk rel="copy-dir" href="../target/site/pkg" />
<link data-trunk rel="copy-file" href="../target/site/favicon.ico" />
<link data-trunk rel="copy-file" href="../target/site/codicon.ttf" />
</head>
<body></body>
</html>
12 changes: 12 additions & 0 deletions playground/src/app/info_graph_example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,18 @@ graphviz_attrs:
edge_dirs: # forward, back, both, none
github_app_zip__app_download_1: back

# "node", "cluster", "graph", "array(_flags)?(n)?"
#
# array flags:
#
# - <none>: row major order
# - c: column major order
# - t: align clusters top (horizontal graphs)
# - b: align clusters bottom (horizontal graphs)
# - l: align clusters left (vertical graphs)
# - r: align clusters right (vertical graphs)
pack_mode: "array_l1"

theme:
merge_with_base: true
styles:
Expand Down

0 comments on commit 907d3f8

Please sign in to comment.