• Stars
    star
    937
  • Rank 48,766 (Top 1.0 %)
  • Language
    Java
  • License
    Apache License 2.0
  • Created about 9 years ago
  • Updated over 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Use graphviz with pure java

graphviz-java

Build Status codecov License Maven Central

Use graphviz with pure java. Create graphviz models using java code and convert them into nice graphics.

How it works

Prerequisites

API

Parsing

Examples

Images

Configuration

Javadoc

Sketchy

How it works

To execute the graphviz layout engine, one of these options is used:

  • If the machine has graphviz installed and a dot command is available, spawn a new process running dot.
  • Use this javascript version of graphviz and execute it on the V8 javascript engine. This is done with the bundled J2V8 library.
  • Alternatively, the javascript can be executed on Java's own Nashorn or GraalVM engine (preferring Graal if both are available).

The method(s) to be used can be configured with the Graphviz.useEngine() method.

Prerequisites

This project heavily uses classes from java.awt. Therefore, it does not work on android.

Maven

This project is available via Maven:

<dependency>
    <groupId>guru.nidi</groupId>
    <artifactId>graphviz-java</artifactId>
    <version>0.18.1</version>
</dependency>

graphviz-java contains all needed dependencies including one to J2V8 for the current platform (Linux, Mac OS X, Windows). This should be ok for most use cases.

gradle does not support this way of defining a dependency. Gradle users have to manually add a dependency to J2V8, e.g. com.eclipsesource.j2v8:j2v8_linux_x86_64:4.6.0

Instead of graphviz-java there are two alternative dependencies that can be used:

  • graphviz-java-all-j2v8 additionally contains dependencies to all J2V8 platforms. So the same application can run on Linux, Mac OS X and Windows.
  • graphviz-java-min-deps contains only dependencies that are absolutely necessary. All other dependencies are marked as optional and must added manually. See the pom.xml for details.

Instead of J2V8, one can also use the JDK javascript engine Nashorn. If Nashorn is too slow or on JDK version 15 or newer (where Nashorn has been removed), the graal javascript engine is a third option. It needs this additional dependency:

<dependency>
    <groupId>org.graalvm.js</groupId>
    <artifactId>js</artifactId>
    <version>20.0.0</version>
</dependency>

Logging

Graphviz-java uses the SLF4J facade to log. Users must therefore provide a logging implementation like LOGBack

<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.3</version>
</dependency>

or Log4j

<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.13.0</version>
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-slf4j-impl</artifactId>
    <version>2.13.0</version>
</dependency>

API

The API is separated into a mutable and immutable part. The basic usage is as follows (assuming import static guru.nidi.graphviz.model.Factory.*).

Immutable

Graph g = graph("example1").directed()
        .graphAttr().with(Rank.dir(LEFT_TO_RIGHT))
        .nodeAttr().with(Font.name("arial"))
        .linkAttr().with("class", "link-class")
        .with(
                node("a").with(Color.RED).link(node("b")),
                node("b").link(
                        to(node("c")).with(attr("weight", 5), Style.DASHED)
                )
        );
Graphviz.fromGraph(g).height(100).render(Format.PNG).toFile(new File("example/ex1.png"));

  • Global attributes are set using the graphAttr, linkAttr and nodeAttr methods.
  • Nodes are styled using the with method.
  • To style edges, use the static method to which returns a Link that also has a with method.
  • The with method accepts predefined attributes like Style, Arrow or Shape as well as everything defined in the Graphviz reference e.g. with("weight", 5) or even arbitrary custom attributes.
  • Custom attribute classes can be defined by extending SingleAttributes or MapAttributes.

Attention: Node a = node("a"); a.with(Color.RED); Is not working as it might be expected. All "mutating" methods like with on nodes, links and graphs create new objects and leave the original object unchanged. So in the example above, variable a contains a node that is NOT red. If you want a red node, do a = a.with(Color.RED) or use the mutable API.

Mutable

MutableGraph g = mutGraph("example1").setDirected(true).add(
        mutNode("a").add(Color.RED).addLink(mutNode("b")));
Graphviz.fromGraph(g).width(200).render(Format.PNG).toFile(new File("example/ex1m.png"));

The mutable API provides similar functions as the immutable one with slightly different syntax:

  • mutGraph instead of graph, mutNode instead of node
  • use setters: setDirected instead of directed
  • add instead of width

Imperative

There is a third possibility to use the API, based on the mutable version. Its form is closer to the way dot files are written. In the lambda of the MutableGraph.use method, all referenced nodes, links and graphs are automatically added to the parent graph, without explicitly calling the add method.

MutableGraph g = mutGraph("example1").setDirected(true).use((gr, ctx) -> {
    mutNode("b");
    nodeAttrs().add(Color.RED);
    mutNode("a").addLink(mutNode("b"));
});
Graphviz.fromGraph(g).width(200).render(Format.PNG).toFile(new File("example/ex1i.png"));

This corresponds to the following dot file:

digraph example1 {
    b
    node[color=red]
    a -> b
}

Kotlin DSL

Kotlin DSL is still experimental. Things can change and any feedback is very welcome.

<dependency>
    <groupId>guru.nidi</groupId>
    <artifactId>graphviz-kotlin</artifactId>
    <version>0.18.1</version>
</dependency>

The kotlin DSL based on the imperative API. It defines that following elements:

  • edge, node, graph variables to define global attributes.
  • -, /, [] operators on MutableNode which link, define ports, set attributes.
  • -, /, [] operators on String so that strings can be used directly to define nodes.
  • -, [] operators on Link which allow to chain links and set attributes.

To enable the functions, use import guru.nidi.graphviz.*

graph(directed = true) {
    edge["color" eq "red", Arrow.TEE]
    node[Color.GREEN]
    graph[Rank.dir(LEFT_TO_RIGHT)]

    "a" - "b" - "c"
    ("c"[Color.RED] - "d"[Color.BLUE])[Arrow.VEE]
    "d" / NORTH - "e" / SOUTH
}.toGraphviz().render(PNG).toFile(File("example/ex1.png"))

Parsing

Dot files can be parsed and thus manipulated. Given this file color.dot:

graph {
    { rank=same; white}
    { rank=same; cyan; yellow; pink}
    { rank=same; red; green; blue}
    { rank=same; black}

    white -- cyan -- blue
    white -- yellow -- green
    white -- pink -- red

    cyan -- green -- black
    yellow -- red -- black
    pink -- blue -- black
}

Then running this program:

try (InputStream dot = getClass().getResourceAsStream("/color.dot")) {
    MutableGraph g = new Parser().read(dot);
    Graphviz.fromGraph(g).width(700).render(Format.PNG).toFile(new File("example/ex4-1.png"));

    g.graphAttrs()
            .add(Color.WHITE.gradient(Color.rgb("888888")).background().angle(90))
            .nodeAttrs().add(Color.WHITE.fill())
            .nodes().forEach(node ->
            node.add(
                    Color.named(node.name().toString()),
                    Style.lineWidth(4), Style.FILLED));
    Graphviz.fromGraph(g).width(700).render(Format.PNG).toFile(new File("example/ex4-2.png"));
}

results in this graphics:

Examples

Complex example

Node
        main = node("main").with(Label.html("<b>main</b><br/>start"), Color.rgb("1020d0").font()),
        init = node(Label.markdown("**_init_**")),
        execute = node("execute"),
        compare = node("compare").with(Shape.RECTANGLE, Style.FILLED, Color.hsv(.7, .3, 1.0)),
        mkString = node("mkString").with(Label.lines(LEFT, "make", "a", "multi-line")),
        printf = node("printf");

Graph g = graph("example2").directed().with(
        main.link(
                to(node("parse").link(execute)).with(LinkAttr.weight(8)),
                to(init).with(Style.DOTTED),
                node("cleanup"),
                to(printf).with(Style.BOLD, Label.of("100 times"), Color.RED)),
        execute.link(
                graph().with(mkString, printf),
                to(compare).with(Color.RED)),
        init.link(mkString));

Graphviz.fromGraph(g).width(900).render(Format.PNG).toFile(new File("example/ex2.png"));

Example with records

import static guru.nidi.graphviz.attribute.Records.*;
import static guru.nidi.graphviz.model.Compass.*;
Node
        node0 = node("node0").with(Records.of(rec("f0", ""), rec("f1", ""), rec("f2", ""), rec("f3", ""), rec("f4", ""))),
        node1 = node("node1").with(Records.of(turn(rec("n4"), rec("v", "719"), rec("")))),
        node2 = node("node2").with(Records.of(turn(rec("a1"), rec("805"), rec("p", "")))),
        node3 = node("node3").with(Records.of(turn(rec("i9"), rec("718"), rec("")))),
        node4 = node("node4").with(Records.of(turn(rec("e5"), rec("989"), rec("p", "")))),
        node5 = node("node5").with(Records.of(turn(rec("t2"), rec("v", "959"), rec("")))),
        node6 = node("node6").with(Records.of(turn(rec("o1"), rec("794"), rec("")))),
        node7 = node("node7").with(Records.of(turn(rec("s7"), rec("659"), rec(""))));
Graph g = graph("example3").directed()
        .graphAttr().with(Rank.dir(LEFT_TO_RIGHT))
        .with(
                node0.link(
                        between(port("f0"), node1.port("v", SOUTH)),
                        between(port("f1"), node2.port(WEST)),
                        between(port("f2"), node3.port(WEST)),
                        between(port("f3"), node4.port(WEST)),
                        between(port("f4"), node5.port("v", NORTH))),
                node2.link(between(port("p"), node6.port(NORTH_WEST))),
                node4.link(between(port("p"), node7.port(SOUTH_WEST))));
Graphviz.fromGraph(g).width(900).render(Format.PNG).toFile(new File("example/ex3.png"));

Images

Images can be included in graphviz in two ways.

One possibility is using the <img> tag inside a HTML label:

Graphviz.useEngine(new GraphvizCmdLineEngine());
Graphviz g = Graphviz.fromGraph(graph()
        .with(node(Label.html("<table border='0'><tr><td><img src='graphviz.png' /></td></tr></table>"))));
g.basedir(new File("example")).render(Format.PNG).toFile(new File("example/ex7.png"));

Because viz.js does not support <img> tags, this works only when using the command line engine.

The other possibility is the image attribute of a node:

Graphviz g = Graphviz.fromGraph(graph()
        .with(node(" ").with(Size.std().margin(.8, .7), Image.of("graphviz.png"))));
g.basedir(new File("example")).render(Format.PNG).toFile(new File("example/ex8.png"));

This works with all engines.

In both cases, the basedir() method can be used to define where relative paths are looked up.

Configuration

The size of the resulting image, the rendering engine and the output format can be configured:

Graphviz.useEngine(new GraphvizCmdLineEngine()); // Rasterizer.builtIn() works only with CmdLineEngine
Graph g = graph("example5").directed().with(node("abc").link(node("xyz")));
Graphviz viz = Graphviz.fromGraph(g);
viz.width(200).render(Format.SVG).toFile(new File("example/ex5.svg"));
viz.width(200).rasterize(Rasterizer.BATIK).toFile(new File("example/ex5b.png"));
viz.width(200).rasterize(Rasterizer.SALAMANDER).toFile(new File("example/ex5s.png"));
viz.width(200).rasterize(Rasterizer.builtIn("pdf")).toFile(new File("example/ex5p"));
String dot = viz.render(Format.DOT).toString();
String json = viz.engine(Engine.NEATO).render(Format.JSON).toString();
BufferedImage image = viz.render(Format.PNG).toImage();

To rasterize with batik, provide this library on the classpath:

<dependency>
    <groupId>org.apache.xmlgraphics</groupId>
    <artifactId>batik-rasterizer</artifactId>
    <version>1.13</version>
</dependency>

Processors

Processors can be registered to further customize what goes in and out of the graphviz engine.

  • Pre processors change the dot file that is fed into the graphviz engine.
  • Post processor change the result of the graphviz engine (image, svg,...).
Graph graph = graph().with(node("bad word").link("good word"));
Graphviz g = Graphviz.fromGraph(graph)
        .preProcessor((source, options, processOptions) -> source.replace("bad word", "unicorn"))
        .postProcessor((result, options, processOptions) ->
                result.mapString(svg ->
                        SvgElementFinder.use(svg, finder -> {
                            finder.findNode("unicorn").setAttribute("class", "pink");
                        })));
g.render(Format.PNG).toFile(new File("example/ex9.png"));

Javadoc

To use graphviz inside javadoc comments, add this to pom.xml:

<build>
  <plugins>
    <plugin>
      <artifactId>maven-javadoc-plugin</artifactId>
      <version>3.1.0</version>
      <configuration>
        <taglet>guru.nidi.graphviz.taglet.GraphvizTaglet</taglet>
        <tagletArtifact>
          <groupId>guru.nidi</groupId>
          <artifactId>graphviz-taglet</artifactId>
          <version>0.18.1</version>
        </tagletArtifact>
      </configuration>
    </plugin>
  </plugins>
</build>

To use this with JDK 9 or later, replace graphviz-taglet with graphviz-taglet9.

The usage inside javadoc is then as follows:

/**
 * Support graphviz inside javadoc.
 * <p>
 * {@graphviz
 * graph test { a -- b }
 * }
 * </p>
 * So easy.
 */
public class GraphvizTaglet implements Taglet {}

Sketchy

To change the appearance of the graph into something more sketchy / hand drawn, the Roughifyer processor can be used. First, add the rough module to the dependencies:

<dependency>
    <groupId>guru.nidi</groupId>
    <artifactId>graphviz-rough</artifactId>
    <version>0.18.1</version>
</dependency>

Then, apply the Roughifyer to the graph:

final Graph g = graph("ex1").directed().with(
        graph().cluster()
                .nodeAttr().with(Style.FILLED, Color.WHITE)
                .graphAttr().with(Style.FILLED, Color.LIGHTGREY, Label.of("process #1"))
                .with(node("a0").link(node("a1").link(node("a2")))),
        graph("x").cluster()
                .nodeAttr().with(Style.FILLED)
                .graphAttr().with(Color.BLUE, Label.of("process #2"))
                .with(node("b0").link(node("b1").link(node("b2")))),
        node("start").with(Shape.M_DIAMOND).link("a0", "b0"),
        node("a0").with(Style.FILLED, Color.RED.gradient(Color.BLUE)).link("b1"),
        node("b1").link("a2"),
        node("a2").link("end"),
        node("b2").link("end"),
        node("end").with(Shape.M_SQUARE)
);

Graphviz.fromGraph(g)
        .processor(new Roughifyer()
                .bowing(2)
                .curveStepCount(6)
                .roughness(1)
                .fillStyle(FillStyle.hachure().width(2).gap(5).angle(0))
                .font("*serif", "Comic Sans MS"))
        .render(Format.PNG)
        .toFile(new File("example/ex1-rough.png"));

More Repositories

1

code-assert

Assert that the java code of a project satisfies certain checks.
Java
102
star
2

AngularJS-SpringMVC-example

Ein Beispielprojekt fรผr das Zusammenspiel von AngularJS und Spring-MVC
JavaScript
74
star
3

raml-tester

Test if a request/response matches a given raml definition
Java
71
star
4

jekyll-image_optimizer

A jekyll plugin to optimize images for the web
Ruby
15
star
5

swiss-wowbagger

Let yourself be insulted in swiss german. Schรถner fluchen auf Berndeutsch.
Kotlin
13
star
6

raml-tester-proxy

Test if requests/responses are raml compliant as a standalone proxy
Java
13
star
7

jwt-with-spring

Example project to show how to use JWT and Spring
Java
11
star
8

simple-3d

A simple CSG library to create models for 3D printing
Kotlin
7
star
9

ftp-sync

Java
5
star
10

emoji-art

Convert emojis into ascii art
JavaScript
5
star
11

raml-tester-js

4
star
12

js-border-layout

A simple border layout with CSS and JS
JavaScript
4
star
13

use-webjars-maven-plugin

use webjars as if they would be npm modules
Java
3
star
14

four-fours

Kotlin
2
star
15

java-universal-executable

Create a directly executable file (windows, linux and mac OS) from a java project.
Java
2
star
16

apikana-java

Maven plugin for apikana
Java
2
star
17

firetop

The classic top command using flames
Kotlin
1
star
18

dicamo

Kotlin
1
star
19

graphviz-taglet

Java
1
star
20

map-visualizer

Kotlin
1
star
21

graphviz-taglet9

Use graphviz in javadoc comments
Java
1
star
22

mbrola-jvm

A JVM wrapper around MBROLA diphone synthesizer
Kotlin
1
star
23

raml-tester-uc-servlet

Java
1
star
24

build-tools

Java
1
star
25

mariokart

1
star
26

astronomy

Some astronomy formulas
Kotlin
1
star
27

console-fun

Just a few utilities to help having fun with the console
Java
1
star
28

print3d

Kotlin
1
star
29

typescript-to-json-schema-extra

TypeScript
1
star
30

simple-remote-atlassian

Java
1
star
31

docker-latex

A complete latex system based on docker
JavaScript
1
star
32

jass

The swiss national card game
Kotlin
1
star
33

prok-sound

Experiments with generating raw waveforms
Kotlin
1
star
34

whatsapp-semi-bot

Java
1
star
35

d3-goodies-jvm

Some nice d3.js functions made usable for the JVM
Kotlin
1
star
36

big-banners

Create ASCII art banners
Kotlin
1
star
37

ngram

Kotlin
1
star
38

coarse

๐Ÿฒ Convert an svg server-side with rough
JavaScript
1
star
39

project-euler

Solve problems of projecteuler.net
Python
1
star
40

raml-doc

Demo for a part of the GitHub API
Java
1
star