graphviz: support style attributes
This commit is contained in:
parent
adcae006d2
commit
75f8f966f0
@ -1,4 +1,4 @@
|
||||
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
|
||||
// Copyright 2014-2015 The Rust Project Developers. See the COPYRIGHT
|
||||
// file at the top-level directory of this distribution and at
|
||||
// http://rust-lang.org/COPYRIGHT.
|
||||
//
|
||||
@ -312,6 +312,40 @@ pub enum LabelText<'a> {
|
||||
EscStr(Cow<'a, str>),
|
||||
}
|
||||
|
||||
/// The style for a node or edge.
|
||||
/// See http://www.graphviz.org/doc/info/attrs.html#k:style for descriptions.
|
||||
/// Note that some of these are not valid for edges.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||
pub enum Style {
|
||||
None,
|
||||
Solid,
|
||||
Dashed,
|
||||
Dotted,
|
||||
Bold,
|
||||
Rounded,
|
||||
Diagonals,
|
||||
Filled,
|
||||
Striped,
|
||||
Wedged,
|
||||
}
|
||||
|
||||
impl Style {
|
||||
pub fn as_slice(self) -> &'static str {
|
||||
match self {
|
||||
Style::None => "",
|
||||
Style::Solid => "solid",
|
||||
Style::Dashed => "dashed",
|
||||
Style::Dotted => "dotted",
|
||||
Style::Bold => "bold",
|
||||
Style::Rounded => "rounded",
|
||||
Style::Diagonals => "diagonals",
|
||||
Style::Filled => "filled",
|
||||
Style::Striped => "striped",
|
||||
Style::Wedged => "wedged",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// There is a tension in the design of the labelling API.
|
||||
//
|
||||
// For example, I considered making a `Labeller<T>` trait that
|
||||
@ -430,6 +464,16 @@ pub trait Labeller<'a,N,E> {
|
||||
let _ignored = e;
|
||||
LabelStr("".into_cow())
|
||||
}
|
||||
|
||||
/// Maps `n` to a style that will be used in the rendered output.
|
||||
fn node_style(&'a self, _n: &N) -> Style {
|
||||
Style::None
|
||||
}
|
||||
|
||||
/// Maps `e` to a style that will be used in the rendered output.
|
||||
fn edge_style(&'a self, _e: &E) -> Style {
|
||||
Style::None
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> LabelText<'a> {
|
||||
@ -529,6 +573,8 @@ pub trait GraphWalk<'a, N, E> {
|
||||
pub enum RenderOption {
|
||||
NoEdgeLabels,
|
||||
NoNodeLabels,
|
||||
NoEdgeStyles,
|
||||
NoNodeStyles,
|
||||
}
|
||||
|
||||
/// Returns vec holding all the default render options.
|
||||
@ -562,30 +608,53 @@ pub fn render_opts<'a, N:Clone+'a, E:Clone+'a, G:Labeller<'a,N,E>+GraphWalk<'a,N
|
||||
for n in g.nodes().iter() {
|
||||
try!(indent(w));
|
||||
let id = g.node_id(n);
|
||||
if options.contains(&RenderOption::NoNodeLabels) {
|
||||
try!(writeln(w, &[id.as_slice(), ";"]));
|
||||
} else {
|
||||
let escaped = g.node_label(n).escape();
|
||||
try!(writeln(w, &[id.as_slice(),
|
||||
"[label=\"", &escaped, "\"];"]));
|
||||
|
||||
let escaped = &g.node_label(n).escape();
|
||||
|
||||
let mut text = vec![id.as_slice()];
|
||||
|
||||
if !options.contains(&RenderOption::NoNodeLabels) {
|
||||
text.push("[label=\"");
|
||||
text.push(escaped);
|
||||
text.push("\"]");
|
||||
}
|
||||
|
||||
let style = g.node_style(n);
|
||||
if !options.contains(&RenderOption::NoNodeStyles) && style != Style::None {
|
||||
text.push("[style=\"");
|
||||
text.push(style.as_slice());
|
||||
text.push("\"]");
|
||||
}
|
||||
|
||||
text.push(";");
|
||||
try!(writeln(w, &text));
|
||||
}
|
||||
|
||||
for e in g.edges().iter() {
|
||||
let escaped_label = g.edge_label(e).escape();
|
||||
let escaped_label = &g.edge_label(e).escape();
|
||||
try!(indent(w));
|
||||
let source = g.source(e);
|
||||
let target = g.target(e);
|
||||
let source_id = g.node_id(&source);
|
||||
let target_id = g.node_id(&target);
|
||||
if options.contains(&RenderOption::NoEdgeLabels) {
|
||||
try!(writeln(w, &[source_id.as_slice(),
|
||||
" -> ", target_id.as_slice(), ";"]));
|
||||
} else {
|
||||
try!(writeln(w, &[source_id.as_slice(),
|
||||
" -> ", target_id.as_slice(),
|
||||
"[label=\"", &escaped_label, "\"];"]));
|
||||
|
||||
let mut text = vec![source_id.as_slice(), " -> ", target_id.as_slice()];
|
||||
|
||||
if !options.contains(&RenderOption::NoEdgeLabels) {
|
||||
text.push("[label=\"");
|
||||
text.push(escaped_label);
|
||||
text.push("\"]");
|
||||
}
|
||||
|
||||
let style = g.edge_style(e);
|
||||
if !options.contains(&RenderOption::NoEdgeStyles) && style != Style::None {
|
||||
text.push("[style=\"");
|
||||
text.push(style.as_slice());
|
||||
text.push("\"]");
|
||||
}
|
||||
|
||||
text.push(";");
|
||||
try!(writeln(w, &text));
|
||||
}
|
||||
|
||||
writeln(w, &["}"])
|
||||
@ -594,7 +663,7 @@ pub fn render_opts<'a, N:Clone+'a, E:Clone+'a, G:Labeller<'a,N,E>+GraphWalk<'a,N
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use self::NodeLabels::*;
|
||||
use super::{Id, Labeller, Nodes, Edges, GraphWalk, render};
|
||||
use super::{Id, Labeller, Nodes, Edges, GraphWalk, render, Style};
|
||||
use super::LabelText::{self, LabelStr, EscStr};
|
||||
use std::io;
|
||||
use std::io::prelude::*;
|
||||
@ -603,11 +672,14 @@ mod tests {
|
||||
/// each node is an index in a vector in the graph.
|
||||
type Node = usize;
|
||||
struct Edge {
|
||||
from: usize, to: usize, label: &'static str
|
||||
from: usize,
|
||||
to: usize,
|
||||
label: &'static str,
|
||||
style: Style,
|
||||
}
|
||||
|
||||
fn edge(from: usize, to: usize, label: &'static str) -> Edge {
|
||||
Edge { from: from, to: to, label: label }
|
||||
fn edge(from: usize, to: usize, label: &'static str, style: Style) -> Edge {
|
||||
Edge { from: from, to: to, label: label, style: style }
|
||||
}
|
||||
|
||||
struct LabelledGraph {
|
||||
@ -623,6 +695,8 @@ mod tests {
|
||||
/// text.
|
||||
node_labels: Vec<Option<&'static str>>,
|
||||
|
||||
node_styles: Vec<Style>,
|
||||
|
||||
/// Each edge relates a from-index to a to-index along with a
|
||||
/// label; `edges` collects them.
|
||||
edges: Vec<Edge>,
|
||||
@ -654,16 +728,30 @@ mod tests {
|
||||
=> lbls.into_iter().collect(),
|
||||
}
|
||||
}
|
||||
|
||||
fn len(&self) -> usize {
|
||||
match self {
|
||||
&UnlabelledNodes(len) => len,
|
||||
&AllNodesLabelled(ref lbls) => lbls.len(),
|
||||
&SomeNodesLabelled(ref lbls) => lbls.len(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LabelledGraph {
|
||||
fn new(name: &'static str,
|
||||
node_labels: Trivial,
|
||||
edges: Vec<Edge>) -> LabelledGraph {
|
||||
edges: Vec<Edge>,
|
||||
node_styles: Option<Vec<Style>>) -> LabelledGraph {
|
||||
let count = node_labels.len();
|
||||
LabelledGraph {
|
||||
name: name,
|
||||
node_labels: node_labels.to_opt_strs(),
|
||||
edges: edges
|
||||
edges: edges,
|
||||
node_styles: match node_styles {
|
||||
Some(nodes) => nodes,
|
||||
None => vec![Style::None; count],
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -673,7 +761,10 @@ mod tests {
|
||||
node_labels: Trivial,
|
||||
edges: Vec<Edge>) -> LabelledGraphWithEscStrs {
|
||||
LabelledGraphWithEscStrs {
|
||||
graph: LabelledGraph::new(name, node_labels, edges)
|
||||
graph: LabelledGraph::new(name,
|
||||
node_labels,
|
||||
edges,
|
||||
None)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -698,6 +789,12 @@ mod tests {
|
||||
fn edge_label(&'a self, e: & &'a Edge) -> LabelText<'a> {
|
||||
LabelStr(e.label.into_cow())
|
||||
}
|
||||
fn node_style(&'a self, n: &Node) -> Style {
|
||||
self.node_styles[*n]
|
||||
}
|
||||
fn edge_style(&'a self, e: & &'a Edge) -> Style {
|
||||
e.style
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Labeller<'a, Node, &'a Edge> for LabelledGraphWithEscStrs {
|
||||
@ -760,7 +857,7 @@ mod tests {
|
||||
#[test]
|
||||
fn empty_graph() {
|
||||
let labels : Trivial = UnlabelledNodes(0);
|
||||
let r = test_input(LabelledGraph::new("empty_graph", labels, vec!()));
|
||||
let r = test_input(LabelledGraph::new("empty_graph", labels, vec![], None));
|
||||
assert_eq!(r.unwrap(),
|
||||
r#"digraph empty_graph {
|
||||
}
|
||||
@ -770,7 +867,7 @@ r#"digraph empty_graph {
|
||||
#[test]
|
||||
fn single_node() {
|
||||
let labels : Trivial = UnlabelledNodes(1);
|
||||
let r = test_input(LabelledGraph::new("single_node", labels, vec!()));
|
||||
let r = test_input(LabelledGraph::new("single_node", labels, vec![], None));
|
||||
assert_eq!(r.unwrap(),
|
||||
r#"digraph single_node {
|
||||
N0[label="N0"];
|
||||
@ -778,11 +875,23 @@ r#"digraph single_node {
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn single_node_with_style() {
|
||||
let labels : Trivial = UnlabelledNodes(1);
|
||||
let styles = Some(vec![Style::Dashed]);
|
||||
let r = test_input(LabelledGraph::new("single_node", labels, vec![], styles));
|
||||
assert_eq!(r.unwrap(),
|
||||
r#"digraph single_node {
|
||||
N0[label="N0"][style="dashed"];
|
||||
}
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn single_edge() {
|
||||
let labels : Trivial = UnlabelledNodes(2);
|
||||
let result = test_input(LabelledGraph::new("single_edge", labels,
|
||||
vec!(edge(0, 1, "E"))));
|
||||
vec![edge(0, 1, "E", Style::None)], None));
|
||||
assert_eq!(result.unwrap(),
|
||||
r#"digraph single_edge {
|
||||
N0[label="N0"];
|
||||
@ -792,15 +901,30 @@ r#"digraph single_edge {
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn single_edge_with_style() {
|
||||
let labels : Trivial = UnlabelledNodes(2);
|
||||
let result = test_input(LabelledGraph::new("single_edge", labels,
|
||||
vec![edge(0, 1, "E", Style::Bold)], None));
|
||||
assert_eq!(result.unwrap(),
|
||||
r#"digraph single_edge {
|
||||
N0[label="N0"];
|
||||
N1[label="N1"];
|
||||
N0 -> N1[label="E"][style="bold"];
|
||||
}
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_some_labelled() {
|
||||
let labels : Trivial = SomeNodesLabelled(vec![Some("A"), None]);
|
||||
let styles = Some(vec![Style::None, Style::Dotted]);
|
||||
let result = test_input(LabelledGraph::new("test_some_labelled", labels,
|
||||
vec![edge(0, 1, "A-1")]));
|
||||
vec![edge(0, 1, "A-1", Style::None)], styles));
|
||||
assert_eq!(result.unwrap(),
|
||||
r#"digraph test_some_labelled {
|
||||
N0[label="A"];
|
||||
N1[label="N1"];
|
||||
N1[label="N1"][style="dotted"];
|
||||
N0 -> N1[label="A-1"];
|
||||
}
|
||||
"#);
|
||||
@ -810,7 +934,7 @@ r#"digraph test_some_labelled {
|
||||
fn single_cyclic_node() {
|
||||
let labels : Trivial = UnlabelledNodes(1);
|
||||
let r = test_input(LabelledGraph::new("single_cyclic_node", labels,
|
||||
vec!(edge(0, 0, "E"))));
|
||||
vec![edge(0, 0, "E", Style::None)], None));
|
||||
assert_eq!(r.unwrap(),
|
||||
r#"digraph single_cyclic_node {
|
||||
N0[label="N0"];
|
||||
@ -824,8 +948,9 @@ r#"digraph single_cyclic_node {
|
||||
let labels = AllNodesLabelled(vec!("{x,y}", "{x}", "{y}", "{}"));
|
||||
let r = test_input(LabelledGraph::new(
|
||||
"hasse_diagram", labels,
|
||||
vec!(edge(0, 1, ""), edge(0, 2, ""),
|
||||
edge(1, 3, ""), edge(2, 3, ""))));
|
||||
vec![edge(0, 1, "", Style::None), edge(0, 2, "", Style::None),
|
||||
edge(1, 3, "", Style::None), edge(2, 3, "", Style::None)],
|
||||
None));
|
||||
assert_eq!(r.unwrap(),
|
||||
r#"digraph hasse_diagram {
|
||||
N0[label="{x,y}"];
|
||||
@ -858,8 +983,8 @@ r#"digraph hasse_diagram {
|
||||
|
||||
let g = LabelledGraphWithEscStrs::new(
|
||||
"syntax_tree", labels,
|
||||
vec!(edge(0, 1, "then"), edge(0, 2, "else"),
|
||||
edge(1, 3, ";"), edge(2, 3, ";" )));
|
||||
vec![edge(0, 1, "then", Style::None), edge(0, 2, "else", Style::None),
|
||||
edge(1, 3, ";", Style::None), edge(2, 3, ";", Style::None)]);
|
||||
|
||||
render(&g, &mut writer).unwrap();
|
||||
let mut r = String::new();
|
||||
|
Loading…
x
Reference in New Issue
Block a user