This post by David King on inline Graphviz graphs inspired me to write my
first Rack middleware in a long time. David's code uses PHP on the server-end coupled with javascript
to parse out Graphviz data from script tags, and then render the graphs with the appropriate Graphviz tool
and serve up the image. It's a great solution for "graphviz-as-a-service" if you want a really generic way
of service up Graphviz graphs from "anywhere".

However for my own use, I don't tend to like doing stuff like this client side, and I don't want to run PHP
when the rest of my blog is all Ruby. I also don't need a very generic solution.

My blog runs through a multi-stage pipeline that parses an augmented Markdown variation anyway, so I decided
I wanted a solution that does it all server-side and simply caches the output.

So I figured I'd like something that let me pass in the graph that my filters extract, and get either a url
to an inline graph back, or optionally inline SVG, combined with a Rack middleware to handle requests for the
graph image (if not using the inline option) and render it and cache it in a file.

The CACHE_PATH is where we'll store the cached images. ENGINES is a list of the
Graphviz tools to use for layout/rendering.

The rest of this chunk is used to detect the pre-requisites to process the Graphviz output using my XSL for prettying up Graphviz SVG output - it requires the notugly.xsl file from those articles, as well as xsltproc. If you also want to be able to convert the cleaned up SVG to PNG, it requires a convert equivalent to that from ImageMagick (if my XSL isn't used, the code will get Graphviz to render directly to PNG).

classGraphdefinitialize(graph)@graph=graphenddefslug# The MD5 hash is not for security, since I'm not accepting posted graphs,# nor keeping any private graphs. @slug||=Digest::MD5.hexdigest(@graph)enddefwrite(fname)`mkdir -p #{CACHE_PATH}`File.open(fname,"w")do|f|f.write(@graph)endenddefcachefname="#{CACHE_PATH}#{slug}.dot"write(fname)if!File.exists?(fname)slugenddefself.dot_file(slug)"#{CACHE_PATH}#{slug}.dot"endend

This small class just contains the code needed to cache the graph somewhere. The graph needs
to be available in a file when rendering it, but if you want to store the extracted graph
fragments somewhere else, like in a database, replacing this class is an easy way - only the
cache and Graph.dot_file methods are called from elsewhere.

The above methods layout, xsl and convert covers the conversion.
Note: You want to make sure that if you expose this via a web server,
that format is sanitized - in my Rack code, I limit this to the strings
"png" and "svg". For that matter, this applies to src and dest too,
but in my code those never comes from the client.

deffilefile=File.read(target_file)rescuenilreturnfileiffilerenderendend## Cache the graph, and return either a link, or inline SVG depending# on the preferred format#defself.graph_to_html(engine,graph,preferred_format=:inline_svg,url_base="/images/gviz/")hash=Graph.new(graph).cacheCache.new(hash,engine,preferred_format).to_html(url_base)endend

Rack Middleware

I put the above in lib/gviz in my blog app - adjust the require accordingly if not:

engine=match[1]hash=match[2]format=match[3]cache=GViz::Cache.new(hash,engine,format)file=cache.filereturnRack::Response.new("No such graph",404)if!filer=Rack::Response.newr["Content-Type"]=format=="png"?"image/png":"image/svg+xml"r.write(file)r.finishendendend

As example of how to call it from your code, my markdown filter calls this:

GViz.graph_to_html(format,graph,:png)

... to generate the HTML for any graphviz graphs embedded in my pages. Since
I've specified PNG output, it will cache the graph to file if not already present,
and just outputs an image link.

I'm a Norwegian technologist who has been living in London since 2000. I have a son, Tristan, born in 2009.

I've got development and management experience from a number of startups as well as more established companies, after co-founding my first company at age 19. I currently split my time between being the Technical Director of Nudge CRM Ltd, and running my consulting business, primarily related to development and devops. (More...)