#![allow(unused_imports)]
use crate::{error::{Error, Result}, node::NodeIdx};
use dot_generator::*;
use dot_structures::*;
use graphviz_rust::{
attributes::*,
cmd::{CommandArg, Format},
exec, parse,
printer::{DotPrinter, PrinterContext},
};
use std::path::Path;
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DotGraph {
pub bgcolor: Option<String>,
pub rankdir: Option<DotRankDir>,
pub stmts: Vec<DotStmt>,
}
impl Default for DotGraph {
#[inline(always)]
fn default() -> Self {
Self::new()
}
}
impl DotGraph {
const DEFAULT_BGCOLOR: &'static str = "#3c3c3c";
pub fn new() -> Self {
Self {
bgcolor: Some(Self::DEFAULT_BGCOLOR.to_string()),
rankdir: Some(DotRankDir::default()),
stmts: vec![],
}
}
pub fn add(&mut self, stmt: impl Into<DotStmt>) {
self.stmts.push(stmt.into());
}
pub fn write_to_svg(&self, dirpath: &Path, stem: &str) -> Result<()> {
let dot = &format!("{self}");
let g: Graph = parse(dot).map_err(Error::GraphvizParse)?;
let mut pctx = PrinterContext::default();
let svg = exec(g, &mut pctx, vec![Format::Svg.into()])?;
let svg_filepath = dirpath.join(format!("{stem}.svg"));
std::fs::write(&svg_filepath, svg)?;
Ok(())
}
}
impl std::fmt::Display for DotGraph {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "digraph {{")?;
if let Some(bg) = &self.bgcolor {
writeln!(f, " bgcolor=\"{bg}\";")?;
}
if let Some(rankdir) = &self.rankdir {
writeln!(f, " rankdir={rankdir};")?;
}
for stmt in &self.stmts {
writeln!(f, " {stmt}")?;
}
writeln!(f, "}}")?;
Ok(())
}
}
#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DotSubGraph {
pub is_cluster: bool,
pub bgcolor: Option<String>,
pub stmts: Vec<DotStmt>,
}
impl DotSubGraph {
pub fn add(&mut self, stmt: impl Into<DotStmt>) {
self.stmts.push(stmt.into());
}
}
impl std::fmt::Display for DotSubGraph {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let cluster = if self.is_cluster { "cluster" } else { "" };
write!(f, "subgraph {cluster} {{")?;
if let Some(bg) = &self.bgcolor {
write!(f, " bgcolor=\"{bg}\";")?;
}
for stmt in &self.stmts {
write!(f, " {stmt};")?;
}
write!(f, "}}")?;
Ok(())
}
}
#[rustfmt::skip]
#[derive(
Debug,
Clone,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
derive_more::From
)]
pub enum DotStmt {
Node(DotNode),
Edge(DotEdge),
Subgraph(DotSubGraph),
Overlap(DotOverlap),
RankSeparation(usize),
Rank { rank: DotRankStmt, nodes: Vec<NodeIdx> },
}
impl std::fmt::Display for DotStmt {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Node(node) => write!(f, "{node};")?,
Self::Edge(edge) => write!(f, "{edge};")?,
Self::Subgraph(graph) => write!(f, "{graph}")?,
Self::Overlap(DotOverlap::Scale) => write!(f, "overlap=scale;")?,
Self::RankSeparation(sep) => write!(f, "ranksep={sep};")?,
Self::Rank { rank, nodes } => {
write!(f, "rank={rank};")?;
for node in nodes.iter() {
write!(f, " {node};")?;
}
},
}
Ok(())
}
}
#[rustfmt::skip]
#[derive(
Debug,
Clone,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
displaydoc::Display
)]
pub struct DotNode {
pub idx: NodeIdx,
pub attrs: DotAttrs,
}
#[rustfmt::skip]
#[derive(
Debug,
Clone,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
displaydoc::Display
)]
pub struct DotEdge {
pub src: NodeIdx,
pub dst: NodeIdx,
pub attrs: DotAttrs,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum DotOverlap {
Scale
}
#[rustfmt::skip]
#[derive(
Debug,
Default,
Clone,
Copy,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
displaydoc::Display,
)]
pub enum DotRankDir {
#[default]
TopToBottom,
BottomToTop,
LeftToRight,
RightToLeft,
}
#[rustfmt::skip]
#[derive(
Debug,
Clone,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
displaydoc::Display
)]
pub enum DotRankStmt {
Min,
Max,
Same,
}
#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DotAttrs {
pub label: Option<String>,
pub shape: Option<String>,
pub color: Option<String>,
pub fontcolor: Option<String>,
pub decorate: Option<()>,
pub fontsize: Option<usize>,
pub height: Option<usize>,
pub width: Option<usize>,
pub style: Option<DotStyle>,
pub fillcolor: Option<String>,
}
impl std::fmt::Display for DotAttrs {
#[allow(unused_assignments)]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut n = 0;
if let Some(label) = &self.label {
if n > 0 { write!(f, ", ")?; }
write!(f, "label=\"{label}\"")?;
n += 1;
}
if let Some(shape) = &self.shape {
if n > 0 { write!(f, ", ")?; }
write!(f, "shape={shape}")?;
n += 1;
}
if let Some(color) = &self.color {
if n > 0 { write!(f, ", ")?; }
write!(f, "color=\"{color}\"")?;
n += 1;
}
if let Some(fontcolor) = &self.fontcolor {
if n > 0 { write!(f, ", ")?; }
write!(f, "fontcolor={fontcolor}")?;
n += 1;
}
if let Some(()) = &self.decorate {
if n > 0 { write!(f, ", ")?; }
write!(f, "decorate=true")?;
n += 1;
}
if let Some(fontsize) = &self.fontsize {
if n > 0 { write!(f, ", ")?; }
write!(f, "fontsize={fontsize}")?;
n += 1;
}
if let Some(height) = &self.height {
if n > 0 { write!(f, ", ")?; }
write!(f, "height={height}")?;
n += 1;
}
if let Some(width) = &self.width {
if n > 0 { write!(f, ", ")?; }
write!(f, "width={width}")?;
n += 1;
}
if let Some(style) = &self.style {
if n > 0 { write!(f, ", ")?; }
write!(f, "style={style}")?;
n += 1;
}
if let Some(fillcolor) = &self.fillcolor {
if n > 0 { write!(f, ", ")?; }
write!(f, "fillcolor={fillcolor}")?;
n += 1;
}
Ok(())
}
}
#[rustfmt::skip]
#[derive(
Debug,
Default,
Clone,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
displaydoc::Display
)]
pub enum DotStyle {
Filled,
#[default]
Solid,
Dashed,
Dotted,
Bold,
Invis,
}
#[cfg(test)]
mod tests {
use super::*;
#[allow(unused)]
fn make_graph() -> DotGraph {
DotGraph {
stmts: vec![
DotStmt::Node(DotNode {
idx: NodeIdx::from(0), attrs: DotAttrs {
label: "flippity\nfloppity".to_string().into(),
color: "darkred".to_string().into(),
..DotAttrs::default()
},
}),
DotStmt::Edge(DotEdge {
src: NodeIdx::from(0), dst: NodeIdx::from(1), attrs: DotAttrs {
label: "hi tharr\n\"foo\"".to_string().into(),
fontcolor: "deepskyblue".to_string().into(),
..DotAttrs::default()
},
}),
DotStmt::Edge(DotEdge {
src: NodeIdx::from(0), dst: NodeIdx::from(2), attrs: DotAttrs::default(),
}),
DotStmt::Edge(DotEdge {
src: NodeIdx::from(2), dst: NodeIdx::from(3), attrs: DotAttrs::default(),
}),
DotStmt::Edge(DotEdge {
src: NodeIdx::from(24), dst: NodeIdx::from(25), attrs: DotAttrs::default(),
}),
DotStmt::Edge(DotEdge {
src: NodeIdx::from(3), dst: NodeIdx::from(4), attrs: DotAttrs::default(),
}),
],
..DotGraph::default()
}
}
}