7 个版本
0.0.20230517 | 2023 年 5 月 17 日 |
---|---|
0.0.20230516 | 2023 年 5 月 16 日 |
0.0.20221221 | 2022 年 12 月 21 日 |
0.0.20221125 | 2022 年 11 月 25 日 |
在 数据结构 中排名第 2125
每月下载量 36 次
在 2 个软件包中使用(通过 hkalbasi-rustc-ap-rustc_d…)
36KB
604 行
生成适合与 Graphviz 一起使用的文件
render
函数通过遍历带标签的图生成输出(例如,一个 output.dot
文件),供 Graphviz 使用。 (Graphviz 可以自动布局图的节点和边,并且还可以选择性地将图渲染为图像或其他 输出格式,如 SVG。)
而不是将某个特定的图数据结构强加给客户端,此库公开了两个特质,客户端可以在将它们传递给渲染函数之前在自己的结构体上实现。
注意:此库尚未提供对 DOT 语言 的完整表达能力的访问。例如,有许多与提供布局提示相关的 属性(例如,左到右与自上而下的比较、使用哪种算法等)。此库的当前目的是生成一个结构非常规范且易于后处理的可读 .dot 文件。
示例
第一个示例使用一个非常简单的图表示:一个整数对的列表,代表边(节点集是隐含的)。每个节点标签直接从表示节点的整数中导出,而边标签都是空字符串。
此示例还说明了如何使用 Cow<[T]>
根据需要返回所有权的向量或借用切片:我们从零开始构建节点向量,但借用边列表(而不是从头开始构造所有边的副本)。
此示例的输出渲染了五个节点,前四个形成一个菱形无向图,然后指向第五个循环。
#![feature(rustc_private)]
use std::io::Write;
use rustc_graphviz as dot;
type Nd = isize;
type Ed = (isize,isize);
struct Edges(Vec<Ed>);
pub fn render_to<W: Write>(output: &mut W) {
let edges = Edges(vec![(0,1), (0,2), (1,3), (2,3), (3,4), (4,4)]);
dot::render(&edges, output).unwrap()
}
impl<'a> dot::Labeller<'a> for Edges {
type Node = Nd;
type Edge = Ed;
fn graph_id(&'a self) -> dot::Id<'a> { dot::Id::new("example1").unwrap() }
fn node_id(&'a self, n: &Nd) -> dot::Id<'a> {
dot::Id::new(format!("N{}", *n)).unwrap()
}
}
impl<'a> dot::GraphWalk<'a> for Edges {
type Node = Nd;
type Edge = Ed;
fn nodes(&self) -> dot::Nodes<'a,Nd> {
// (assumes that |N| \approxeq |E|)
let &Edges(ref v) = self;
let mut nodes = Vec::with_capacity(v.len());
for &(s,t) in v {
nodes.push(s); nodes.push(t);
}
nodes.sort();
nodes.dedup();
nodes.into()
}
fn edges(&'a self) -> dot::Edges<'a,Ed> {
let &Edges(ref edges) = self;
(&edges[..]).into()
}
fn source(&self, e: &Ed) -> Nd { let &(s,_) = e; s }
fn target(&self, e: &Ed) -> Nd { let &(_,t) = e; t }
}
pub fn main() {
use std::fs::File;
let mut f = File::create("example1.dot").unwrap();
render_to(&mut f)
}
第一个示例的输出(在 example1.dot
中)
digraph example1 {
N0[label="N0"];
N1[label="N1"];
N2[label="N2"];
N3[label="N3"];
N4[label="N4"];
N0 -> N1[label=""];
N0 -> N2[label=""];
N1 -> N3[label=""];
N2 -> N3[label=""];
N3 -> N4[label=""];
N4 -> N4[label=""];
}
第二个示例说明了如何使用 node_label
和 edge_label
为渲染的图中节点和边添加标签。这里的图包含 nodes
(用于渲染特定节点的标签文本)和 edges
(一个包含 (source,target)
索引的列表)。
本例还说明了如何使用与图共享子结构的数据类型(在本例中为边类型):这里的边类型直接引用了存储在图内部向量中的 (source,target)
对(而不是传递对的副本)。请注意,这意味着 fn edges(&'a self)
必须从存储在 self
中的 Vec<&'a (usize,usize)>
边中构建一个新的 Vec<&'a (usize,usize)>
。
由于节点集和边集总是通过迭代器从头开始构建,我们使用来自 Iterator
特性的 collect()
方法将节点和边收集到新构建的可增长 Vec
值中(而不是像上面第一个示例中使用 Cow
)。
此示例的输出渲染了构成集合 {x, y}
子集的 Hasse 图的四个节点。每条边都用 ⊆ 符号标记(使用 HTML 字符实体 &sube
指定)。
#![feature(rustc_private)]
use std::io::Write;
use rustc_graphviz as dot;
type Nd = usize;
type Ed<'a> = &'a (usize, usize);
struct Graph { nodes: Vec<&'static str>, edges: Vec<(usize,usize)> }
pub fn render_to<W: Write>(output: &mut W) {
let nodes = vec!["{x,y}","{x}","{y}","{}"];
let edges = vec![(0,1), (0,2), (1,3), (2,3)];
let graph = Graph { nodes: nodes, edges: edges };
dot::render(&graph, output).unwrap()
}
impl<'a> dot::Labeller<'a> for Graph {
type Node = Nd;
type Edge = Ed<'a>;
fn graph_id(&'a self) -> dot::Id<'a> { dot::Id::new("example2").unwrap() }
fn node_id(&'a self, n: &Nd) -> dot::Id<'a> {
dot::Id::new(format!("N{}", n)).unwrap()
}
fn node_label(&self, n: &Nd) -> dot::LabelText<'_> {
dot::LabelText::LabelStr(self.nodes[*n].into())
}
fn edge_label(&self, _: &Ed<'_>) -> dot::LabelText<'_> {
dot::LabelText::LabelStr("⊆".into())
}
}
impl<'a> dot::GraphWalk<'a> for Graph {
type Node = Nd;
type Edge = Ed<'a>;
fn nodes(&self) -> dot::Nodes<'a,Nd> { (0..self.nodes.len()).collect() }
fn edges(&'a self) -> dot::Edges<'a,Ed<'a>> { self.edges.iter().collect() }
fn source(&self, e: &Ed<'_>) -> Nd { let & &(s,_) = e; s }
fn target(&self, e: &Ed<'_>) -> Nd { let & &(_,t) = e; t }
}
pub fn main() {
use std::fs::File;
let mut f = File::create("example2.dot").unwrap();
render_to(&mut f)
}
第三个示例与第二个示例类似,但现在每个节点和边都携带对每个节点字符串标签及其索引的引用。(这是另一种说明如何与图本身共享结构以及为什么可能想要这样做的方法。)
此示例的输出与第二个示例相同:集合 {x, y}
子集的 Hasse 图。
#![feature(rustc_private)]
use std::io::Write;
use rustc_graphviz as dot;
type Nd<'a> = (usize, &'a str);
type Ed<'a> = (Nd<'a>, Nd<'a>);
struct Graph { nodes: Vec<&'static str>, edges: Vec<(usize,usize)> }
pub fn render_to<W: Write>(output: &mut W) {
let nodes = vec!["{x,y}","{x}","{y}","{}"];
let edges = vec![(0,1), (0,2), (1,3), (2,3)];
let graph = Graph { nodes: nodes, edges: edges };
dot::render(&graph, output).unwrap()
}
impl<'a> dot::Labeller<'a> for Graph {
type Node = Nd<'a>;
type Edge = Ed<'a>;
fn graph_id(&'a self) -> dot::Id<'a> { dot::Id::new("example3").unwrap() }
fn node_id(&'a self, n: &Nd<'a>) -> dot::Id<'a> {
dot::Id::new(format!("N{}", n.0)).unwrap()
}
fn node_label(&self, n: &Nd<'_>) -> dot::LabelText<'_> {
let &(i, _) = n;
dot::LabelText::LabelStr(self.nodes[i].into())
}
fn edge_label(&self, _: &Ed<'_>) -> dot::LabelText<'_> {
dot::LabelText::LabelStr("⊆".into())
}
}
impl<'a> dot::GraphWalk<'a> for Graph {
type Node = Nd<'a>;
type Edge = Ed<'a>;
fn nodes(&'a self) -> dot::Nodes<'a,Nd<'a>> {
self.nodes.iter().map(|s| &s[..]).enumerate().collect()
}
fn edges(&'a self) -> dot::Edges<'a,Ed<'a>> {
self.edges.iter()
.map(|&(i,j)|((i, &self.nodes[i][..]),
(j, &self.nodes[j][..])))
.collect()
}
fn source(&self, e: &Ed<'a>) -> Nd<'a> { let &(s,_) = e; s }
fn target(&self, e: &Ed<'a>) -> Nd<'a> { let &(_,t) = e; t }
}
pub fn main() {
use std::fs::File;
let mut f = File::create("example3.dot").unwrap();
render_to(&mut f)
}