#graph #graphviz #render-graph #dot #graph-algorithms

dot2

一个用于生成图Graphviz DOT语言文件的库

3个版本 (1个稳定版)

1.0.0 2022年9月22日
1.0.0-beta.12022年5月11日
0.1.0 2022年2月9日

数据结构 中排名 346

Download history 1567/week @ 2024-03-11 923/week @ 2024-03-18 692/week @ 2024-03-25 681/week @ 2024-04-01 303/week @ 2024-04-08 407/week @ 2024-04-15 257/week @ 2024-04-22 574/week @ 2024-04-29 775/week @ 2024-05-06 847/week @ 2024-05-13 507/week @ 2024-05-20 346/week @ 2024-05-27 268/week @ 2024-06-03 201/week @ 2024-06-10 192/week @ 2024-06-17 412/week @ 2024-06-24

每月下载量 1,086
13crate中使用了(其中2个直接使用)

MIT/Apache

58KB
1K SLoC

dot2

Github actions Gitlab CI

生成适合与Graphviz使用的文件

render 函数通过遍历标记图生成输出(例如,一个 output.dot 文件),以便与Graphviz一起使用。(然后Graphviz可以自动布置图的节点和边,并且可选地将图渲染为图像或其他输出格式,如SVG。)

而不是将特定的图数据结构强加给客户端,此库公开了两个客户端可以在将其传递给渲染函数之前在自己的struct上实现的特质。

注意:此库尚未提供对DOT语言的全部表达能力。例如,有许多与提供布局提示相关的属性(例如,从左到右还是从上到下,使用哪个算法等)。此库的当前目的是生成一个非常规的、适合轻松后处理的可读性很高的.dot文件。

示例

第一个示例使用一个非常简单的图表示:表示边的整数对的列表(节点集是隐含的)。每个节点标签直接从表示节点的整数派生,而边的标签都是空字符串。

此示例还说明了如何使用Cow<[T]>根据需要返回一个拥有向量或一个借用切片:我们从零开始构建节点向量,但借用边列表(而不是从头开始构建所有边的副本)。

此示例的输出渲染了五个节点,前四个形成一个菱形无向图,然后指向第五个节点,该节点是循环的。

type Nd = isize;
type Ed = (isize,isize);
struct Edges(Vec<Ed>);

pub fn render_to<W: std::io::Write>(output: &mut W) -> dot2::Result {
    let edges = Edges(vec![(0,1), (0,2), (1,3), (2,3), (3,4), (4,4)]);
    dot2::render(&edges, output)
}

impl<'a> dot2::Labeller<'a> for Edges {
    type Node = Nd;
    type Edge = Ed;
    type Subgraph = ();

    fn graph_id(&'a self) -> dot2::Result<dot2::Id<'a>> {
        dot2::Id::new("example1")
    }

    fn node_id(&'a self, n: &Nd) -> dot2::Result<dot2::Id<'a>> {
        dot2::Id::new(format!("N{}", *n))
    }
}

impl<'a> dot2::GraphWalk<'a> for Edges {
    type Node = Nd;
    type Edge = Ed;
    type Subgraph = ();

    fn nodes(&self) -> dot2::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) -> dot2::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() -> dot2::Result { render_to(&mut Vec::new()) }
# pub fn render_to<W:std::io::Write>(output: &mut W) -> dot2::Result { unimplemented!() }
pub fn main() -> dot2::Result {
    let mut f = std::fs::File::create("example1.dot2")?;
    render_to(&mut f)
}

第一个示例的输出(在example1.dot2中)

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)必须从存储在self中的Vec<&'a (usize,usize)>边中构造一个新的Vec<&'a (usize,usize)>

由于节点集合和边集合总是通过迭代器从头开始构建,因此我们使用collect()方法从Iterator特质收集节点和边到新构建的可增长Vec值(而不是像第一个例子那样使用Cow)。

本例的输出渲染了四个节点,它们构成了集合{x, y}的Hasse图。每条边都用⊆字符标记(使用HTML字符实体&sube指定)。

type Nd = usize;
type Ed<'a> = &'a (usize, usize);

struct Graph {
    nodes: Vec<&'static str>,
    edges: Vec<(usize,usize)>,
}

pub fn render_to<W: std::io::Write>(output: &mut W) -> dot2::Result {
    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 };

    dot2::render(&graph, output)
}

impl<'a> dot2::Labeller<'a> for Graph {
    type Node = Nd;
    type Edge = Ed<'a>;
    type Subgraph = ();

    fn graph_id(&'a self) -> dot2::Result<dot2::Id<'a>> {
        dot2::Id::new("example2")
    }

    fn node_id(&'a self, n: &Nd) -> dot2::Result<dot2::Id<'a>> {
        dot2::Id::new(format!("N{}", n))
    }

    fn node_label<'b>(&'b self, n: &Nd) -> dot2::Result<dot2::label::Text<'b>> {
        Ok(dot2::label::Text::LabelStr(self.nodes[*n].into()))
    }

    fn edge_label<'b>(&'b self, _: &Ed) -> dot2::label::Text<'b> {
        dot2::label::Text::LabelStr("&sube;".into())
    }
}

impl<'a> dot2::GraphWalk<'a> for Graph {
    type Node = Nd;
    type Edge = Ed<'a>;
    type Subgraph = ();

    fn nodes(&self) -> dot2::Nodes<'a,Nd> {
        (0..self.nodes.len()).collect()
    }

    fn edges(&'a self) -> dot2::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() -> dot2::Result { render_to(&mut Vec::new()) }
# pub fn render_to<W:std::io::Write>(output: &mut W) -> dot2::Result { unimplemented!() }
pub fn main() -> dot2::Result {
    let mut f = std::fs::File::create("example2.dot")?;
    render_to(&mut f)
}

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

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

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: std::io::Write>(output: &mut W) -> dot2::Result {
    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 };

    dot2::render(&graph, output)
}

impl<'a> dot2::Labeller<'a> for Graph {
    type Node = Nd<'a>;
    type Edge = Ed<'a>;
    type Subgraph = ();

    fn graph_id(&'a self) -> dot2::Result<dot2::Id<'a>> {
        dot2::Id::new("example3")
    }

    fn node_id(&'a self, n: &Nd<'a>) -> dot2::Result<dot2::Id<'a>> {
        dot2::Id::new(format!("N{}", n.0))
    }

    fn node_label<'b>(&'b self, n: &Nd<'b>) -> dot2::Result<dot2::label::Text<'b>> {
        let &(i, _) = n;

        Ok(dot2::label::Text::LabelStr(self.nodes[i].into()))
    }

    fn edge_label<'b>(&'b self, _: &Ed<'b>) -> dot2::label::Text<'b> {
        dot2::label::Text::LabelStr("&sube;".into())
    }
}

impl<'a> dot2::GraphWalk<'a> for Graph {
    type Node = Nd<'a>;
    type Edge = Ed<'a>;
    type Subgraph = ();

    fn nodes(&'a self) -> dot2::Nodes<'a,Nd<'a>> {
        self.nodes.iter().map(|s| &s[..]).enumerate().collect()
    }

    fn edges(&'a self) -> dot2::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() -> dot2::Result { render_to(&mut Vec::new()) }
# pub fn render_to<W:std::io::Write>(output: &mut W) -> dot2::Result { unimplemented!() }
pub fn main() -> dot2::Result {
    let mut f = std::fs::File::create("example3.dot")?;
    render_to(&mut f)
}

对于这个第四个例子,我们在第一个例子中添加了子图

type Nd = isize;
type Ed = (isize,isize);
type Su = usize;
struct Edges(Vec<Ed>);

pub fn render_to<W: std::io::Write>(output: &mut W) -> dot2::Result {
    let edges = Edges(vec!((0,1), (0,2), (1,3), (2,3), (3,4), (4,4)));
    dot2::render(&edges, output)
}

impl<'a> dot2::Labeller<'a> for Edges {
    type Node = Nd;
    type Edge = Ed;
    type Subgraph = Su;

#   fn graph_id(&'a self) -> dot2::Result<dot2::Id<'a>> {
#       dot2::Id::new("example4")
#   }
#
#   fn node_id(&'a self, n: &Nd) -> dot2::Result<dot2::Id<'a>> {
#       dot2::Id::new(format!("N{}", *n))
#   }
    // ...

    fn subgraph_id(&'a self, s: &Su) -> Option<dot2::Id<'a>> {
        dot2::Id::new(format!("cluster_{}", s)).ok()
    }
}

impl<'a> dot2::GraphWalk<'a> for Edges {
    type Node = Nd;
    type Edge = Ed;
    type Subgraph = Su;

#   fn nodes(&self) -> dot2::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();
#       std::borrow::Cow::Owned(nodes)
#   }
#
#   fn edges(&'a self) -> dot2::Edges<'a,Ed> {
#       let &Edges(ref edges) = self;
#       std::borrow::Cow::Borrowed(&edges[..])
#   }
#
#   fn source(&self, e: &Ed) -> Nd { e.0 }
#
#   fn target(&self, e: &Ed) -> Nd { e.1 }
    // ...

    fn subgraphs(&'a self) -> dot2::Subgraphs<'a, Su> {
        std::borrow::Cow::Borrowed(&[0, 1])
    }

    fn subgraph_nodes(&'a self, s: &Su) -> dot2::Nodes<'a, Nd> {
        let subgraph = if *s == 0 {
            vec![0, 1, 2]
        } else {
            vec![3, 4]
        };

        std::borrow::Cow::Owned(subgraph)
    }
}
# pub fn main() -> dot2::Result { render_to(&mut Vec::new()) }
# pub fn render_to<W:std::io::Write>(output: &mut W) -> dot2::Result { unimplemented!() }
pub fn main() -> dot2::Result {
    let mut f = std::fs::File::create("example4.dot")?;
    render_to(&mut f)
}

相应的输出

digraph example4 {
    subgraph cluster_0 {
        label="";
        N0;
        N1;
        N2;
    }

    subgraph cluster_1 {
        label="";
        N3;
        N4;
    }

    N0[label="{x,y}"];
    N1[label="{x}"];
    N2[label="{y}"];
    N3[label="{}"];
    N0 -> N1[label="&sube;"];
    N0 -> N2[label="&sube;"];
    N1 -> N3[label="&sube;"];
    N2 -> N3[label="&sube;"];
}

参考文献

无运行时依赖项