blob: af493dc40e560ef9b4d15425d9d10a15f9ca5f4a [file] [log] [blame]
//! Simple graphviz dot file format output.
use std::fmt::{self, Display, Write};
use {
Graph,
EdgeType,
GraphMap,
};
use super::graph::IndexType;
use graphmap::NodeTrait;
use std::collections::HashMap;
/// `Dot` implements output to graphviz .dot format for a graph.
///
/// Formatting and options are rather simple, this is mostly intended
/// for debugging. Exact output may change.
pub struct Dot<'a, G: 'a> {
graph: &'a G,
config: &'a [Config],
}
static TYPE: [&'static str; 2] = ["graph", "digraph"];
static EDGE: [&'static str; 2] = ["--", "->"];
static INDENT: &'static str = " ";
impl<'a, G> Dot<'a, G> {
/// Create a `Dot` formatting wrapper with default configuration.
pub fn new(graph: &'a G) -> Self {
Self::with_config(graph, &[])
}
/// Create a `Dot` formatting wrapper with custom configuration.
pub fn with_config(graph: &'a G, config: &'a [Config]) -> Self {
Dot {
graph: graph,
config: config,
}
}
}
/// `Dot` configuration.
///
/// This enum does not have an exhaustive definition (will be expanded)
#[derive(Debug, PartialEq, Eq)]
pub enum Config {
/// Use indices for node labels.
NodeIndexLabel,
/// Use indices for edge labels.
EdgeIndexLabel,
/// Use no edge labels.
EdgeNoLabel,
#[doc(hidden)]
_Incomplete(()),
}
impl<'a, N, E, Ty, Ix> Dot<'a, Graph<N, E, Ty, Ix>>
where Ty: EdgeType,
Ix: IndexType,
{
fn graph_fmt<F, G>(&self, f: &mut fmt::Formatter,
mut node_fmt: F, mut edge_fmt: G) -> fmt::Result
where F: FnMut(&N, &mut FnMut(&Display) -> fmt::Result) -> fmt::Result,
G: FnMut(&E, &mut FnMut(&Display) -> fmt::Result) -> fmt::Result,
{
let g = self.graph;
try!(writeln!(f, "{} {{", TYPE[g.is_directed() as usize]));
// output all labels
for index in g.node_indices() {
try!(write!(f, "{}{}", INDENT, index.index()));
if self.config.contains(&Config::NodeIndexLabel) {
try!(writeln!(f, ""));
} else {
try!(write!(f, " [label=\""));
try!(node_fmt(&g[index], &mut |d| Escaped(d).fmt(f)));
try!(writeln!(f, "\"]"));
}
}
// output all edges
for (i, edge) in g.raw_edges().iter().enumerate() {
try!(write!(f, "{}{} {} {}",
INDENT,
edge.source().index(),
EDGE[g.is_directed() as usize],
edge.target().index()));
if self.config.contains(&Config::EdgeNoLabel) {
try!(writeln!(f, ""));
} else if self.config.contains(&Config::EdgeIndexLabel) {
try!(writeln!(f, " [label=\"{}\"]", i));
} else {
try!(write!(f, " [label=\""));
try!(edge_fmt(&edge.weight, &mut |d| Escaped(d).fmt(f)));
try!(writeln!(f, "\"]"));
}
}
try!(writeln!(f, "}}"));
Ok(())
}
}
impl<'a, N, E, Ty, Ix> fmt::Display for Dot<'a, Graph<N, E, Ty, Ix>>
where N: fmt::Display,
E: fmt::Display,
Ty: EdgeType,
Ix: IndexType,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.graph_fmt(f, |n, cb| cb(n), |e, cb| cb(e))
}
}
impl<'a, N, E, Ty, Ix> fmt::Debug for Dot<'a, Graph<N, E, Ty, Ix>>
where N: fmt::Debug,
E: fmt::Debug,
Ty: EdgeType,
Ix: IndexType,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.graph_fmt(f,
|n, cb| cb(&DebugFmt(n)),
|e, cb| cb(&DebugFmt(e)))
}
}
impl<'a, N, E> Dot<'a, GraphMap<N, E>>
where N: NodeTrait,
{
fn graphmap_fmt<F, G>(&self, f: &mut fmt::Formatter,
mut node_fmt: F, mut edge_fmt: G) -> fmt::Result
where F: FnMut(&N, &mut FnMut(&Display) -> fmt::Result) -> fmt::Result,
G: FnMut(&E, &mut FnMut(&Display) -> fmt::Result) -> fmt::Result,
{
let g = self.graph;
try!(writeln!(f, "{} {{", TYPE[0]));
let mut labels = HashMap::new();
// output all labels
for (i, node) in g.nodes().enumerate() {
labels.insert(node, i);
try!(write!(f, "{}{}", INDENT, i));
if self.config.contains(&Config::NodeIndexLabel) {
try!(writeln!(f, ""));
} else {
try!(write!(f, " [label=\""));
try!(node_fmt(&node, &mut |d| Escaped(d).fmt(f)));
try!(writeln!(f, "\"]"));
}
}
// output all edges
for (i, (a, b, edge_weight)) in g.all_edges().enumerate() {
try!(write!(f, "{}{} {} {}",
INDENT,
labels[&a],
EDGE[0],
labels[&b]));
if self.config.contains(&Config::EdgeNoLabel) {
try!(writeln!(f, ""));
} else if self.config.contains(&Config::EdgeIndexLabel) {
try!(writeln!(f, " [label=\"{}\"]", i));
} else {
try!(write!(f, " [label=\""));
try!(edge_fmt(&edge_weight, &mut |d| Escaped(d).fmt(f)));
try!(writeln!(f, "\"]"));
}
}
try!(writeln!(f, "}}"));
Ok(())
}
}
impl<'a, N, E> fmt::Display for Dot<'a, GraphMap<N, E>>
where N: fmt::Display + NodeTrait,
E: fmt::Display,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.graphmap_fmt(f, |n, cb| cb(n), |e, cb| cb(e))
}
}
impl<'a, N, E> fmt::Debug for Dot<'a, GraphMap<N, E>>
where N: fmt::Debug + NodeTrait,
E: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.graphmap_fmt(f,
|n, cb| cb(&DebugFmt(n)),
|e, cb| cb(&DebugFmt(e)))
}
}
/// Escape for Graphviz
struct Escaper<W>(W);
impl<W> fmt::Write for Escaper<W>
where W: fmt::Write
{
fn write_str(&mut self, s: &str) -> fmt::Result {
for c in s.chars() {
try!(self.write_char(c));
}
Ok(())
}
fn write_char(&mut self, c: char) -> fmt::Result {
match c {
'"' => try!(self.0.write_char('\\')),
// \l is for left justified linebreak
'\n' => return self.0.write_str(r#"\l"#),
_ => { }
}
self.0.write_char(c)
}
}
/// Pass Display formatting through a simple escaping filter
struct Escaped<T>(T);
impl<T> fmt::Display for Escaped<T>
where T: fmt::Display
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if f.alternate() {
write!(&mut Escaper(f), "{:#}\\l", &self.0)
} else {
write!(&mut Escaper(f), "{}", &self.0)
}
}
}
/// Pass Debug formatting to Display
struct DebugFmt<T>(T);
impl<T> fmt::Display for DebugFmt<T>
where T: fmt::Debug
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
}
}