1个不稳定版本
0.1.0 | 2022年10月14日 |
---|
#393 in 可视化
78 每月下载量
用于 steelix
59KB
1.5K SLoC
dot-rust
用于生成Graphviz DOT语言文件的库,用于图形。
此代码是从核心Rust中的私有库graphviz
中提取的。它几乎完全是Rust团队的工作。
许可证
根据Apache License 2.0 <LICENSE-APACHE或https://apache.ac.cn/licenses/LICENSE-2.0或MIT许可证 <LICENSE-MIT或http://opensource.org/licenses/MIT,由您选择。此文件不得复制、修改或分发,除非根据这些条款。
lib.rs
:
生成适合与Graphviz一起使用的文件
render
函数通过遍历标记图来生成输出(例如,一个output.dot
文件),以便与Graphviz一起使用。 (Graphviz可以自动布局图中的节点和边,并且可以选择性地将图渲染为图像或其他输出格式,例如SVG。)
而不是向客户端强加某种特定的图形数据结构,此库公开了两个客户端可以在将其传递给渲染函数之前在自己的结构体上实现的特质。
注意:此库尚未提供对DOT语言的全部表达能力。例如,有许多与提供布局提示相关的属性(例如,从左到右还是从上到下,使用哪种算法等)。此库当前的目的是生成一个非常规则的、易于后处理的、可读的.dot文件。
示例
第一个例子使用一个非常简单的图表示方法:一对整数的列表,表示边(节点集是隐含的)。每个节点标签直接来自表示节点的整数,而边标签都是空字符串。
这个例子还说明了如何使用Cow<[T]>
来根据需要返回所有权的向量或借用切片:我们从零开始构建节点向量,但借用边列表(而不是从头开始构造所有边的副本)。
此示例的输出显示了五个节点,前四个形成一个菱形无环图,然后指向第五个循环节点。
use std::borrow::Cow;
use std::io::Write;
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, Nd, Ed> for Edges {
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, Nd, Ed> for Edges {
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();
Cow::Owned(nodes)
}
fn edges(&'a self) -> dot::Edges<'a,Ed> {
let &Edges(ref edges) = self;
Cow::Borrowed(&edges[..])
}
fn source(&self, e: &Ed) -> Nd { e.0 }
fn target(&self, e: &Ed) -> Nd { e.1 }
}
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
值中(而不是使用如上例中使用的来自IntoCow
特质的into
)。
此示例的输出显示了组成集合{x, y}
的子集的Hasse图的四个节点。每条边都带有⊆符号(使用HTML字符实体&sube
指定)。
use std::io::Write;
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, Nd, Ed<'a>> for Graph {
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<'b>(&'b self, n: &Nd) -> dot::LabelText<'b> {
dot::LabelText::LabelStr(self.nodes[*n].into())
}
fn edge_label<'b>(&'b self, _: &Ed) -> dot::LabelText<'b> {
dot::LabelText::LabelStr("⊆".into())
}
}
impl<'a> dot::GraphWalk<'a, Nd, Ed<'a>> for Graph {
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 { e.0 }
fn target(&self, e: &Ed) -> Nd { e.1 }
}
pub fn main() {
use std::fs::File;
let mut f = File::create("example2.dot").unwrap();
render_to(&mut f)
}
第三个示例与第二个类似,但现在每个节点和边都携带每个节点的字符串标签及其索引的引用。(这是另一种说明如何与图本身共享结构和为什么可能想要这样做的方法。)
本例的输出与第二个例子相同:集合的子集的Hasse图如下:{x, y}
。
use std::io::Write;
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, Nd<'a>, Ed<'a>> for Graph {
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<'b>(&'b self, n: &Nd<'b>) -> dot::LabelText<'b> {
let &(i, _) = n;
dot::LabelText::LabelStr(self.nodes[i].into())
}
fn edge_label<'b>(&'b self, _: &Ed<'b>) -> dot::LabelText<'b> {
dot::LabelText::LabelStr("⊆".into())
}
}
impl<'a> dot::GraphWalk<'a, Nd<'a>, Ed<'a>> for Graph {
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> { e.0 }
fn target(&self, e: &Ed<'a>) -> Nd<'a> { e.1 }
}
pub fn main() {
use std::fs::File;
let mut f = File::create("example3.dot").unwrap();
render_to(&mut f)
}