6个版本

使用旧的Rust 2015

0.1.4 2018年4月27日
0.1.3 2018年1月22日
0.1.2 2015年11月22日
0.1.0 2015年10月29日
0.0.4 2015年4月11日

#73 in 可视化

Download history 10691/week @ 2024-03-14 11132/week @ 2024-03-21 10200/week @ 2024-03-28 10270/week @ 2024-04-04 10317/week @ 2024-04-11 10666/week @ 2024-04-18 9582/week @ 2024-04-25 9311/week @ 2024-05-02 9094/week @ 2024-05-09 9606/week @ 2024-05-16 9778/week @ 2024-05-23 9132/week @ 2024-05-30 9617/week @ 2024-06-06 10540/week @ 2024-06-13 10532/week @ 2024-06-20 8248/week @ 2024-06-27

40,472 每月下载量
用于 63 个crate(35个直接使用)

MIT/Apache

55KB
1K SLoC

Build Status

dot-rust

用于生成图状DOT语言文件的库。

此代码是从Rust核心库中的私有graphviz库中提取出来的。这几乎完全是Rust团队的工作。

许可

根据Apache License,版本2.0(https://apache.ac.cn/licenses/LICENSE-2.0>)或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_labeledge_label 为渲染图中节点和边添加标签。这里的图同时携带了 nodes(用于渲染特定节点的标签文本)和 edges(再次是一个 (source,target) 索引列表)。

这个示例还说明了如何使用与图共享子结构的类型(在这个例子中是边类型):这里的边类型是对存储在图内部向量中的 (source,target) 对的直接引用(而不是传递对对的副本)。请注意,这意味着 fn edges(&'a self) 必须从 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("&sube;".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)
}

第三个示例与第二个示例类似,但现在每个节点和边都携带对每个节点字符串标签的引用以及该节点的索引。(这是如何与图本身共享结构的另一个说明,以及为什么可能想要这样做的原因。)

这个示例的输出与第二个示例相同:构成集合 {x, y} 的子集的 Hasse 图。

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("&sube;".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)
}

参考文献

无运行时依赖