diagrams
This document implements the ability to create diagrams in documents. Documents
can take advantage of this by declaring that the use the make_diagrams
service.
1 dot diagrams
Graphviz is a beautiful little program created at AT&T, originally for visualizing complex network layouts. It’s basically the TeX of diagrams—particularly the “general directed graphs” that Brooks mentions. You describe the graph using an extremely simple language (called “dot”), which looks like this:
digraph {
a -> b
}
and send it to a little program (also called “dot”), which can produce graphics like Figure 1 in a variety of formats.
Although Org has very convenient support for dot
diagrams, we forego some of
that convenience to integrate with our build system. (See “because Tup.”)
Instead of using Org Babel to “evaluate” the code block and create the image
link as a “result”, we just tangle the block like any other, and then create the
SVG image in a separate step. This is all done by the following build rules, in
conjunction with one link-rewriting rule in the template you’ll see later.
service make_diagrams : \
foreach $(PUBLISHED)/dot/*.dot \
|> ^ dot %B^ \
dot -Tsvg \
-Gbgcolor=transparent \
-Gfontnames=svg \
"%f" > "%o" \
|> $(PUBLISHED)/images/%B.svg
service make_diagrams : \
foreach $(PUBLISHED)/images/*.svg \
|> !copy_to |> $(SITE_IMAGES)/%b
By default, the box containing a dot diagram has a solid background. But as far
as we’re concerned, that bounding box is immaterial (so to speak). Only the
contents are real. The -Gbgcolor=transparent
sets a graph-level option to turn
off the background.
1.1 stylesheet
You can’t do this this way, because it clobbers any stylesheet loaded specifically with the image, which we do in the program graph. So you’ll have to support this another way.
-Gstylesheet="/static/style/dot-rules.css" \
Regarding the stylesheet, it’s just nominal. The point is that we have a place to put CSS rules that apply to all dot diagrams.
//@require colors
//polygon, rect
// fill rgba($aboutColor, .6)
And of course that’s just for a web (HTML) context.
1.1.1 BUG the above doesn’t work, see note
1.2 placing figures
The image will not automatically appear in place of the dot
code; you have to
add a link to the svg
file. This is convenience we give up by not using Org
Babel. But we can still hide the dot
blocks by default,
<<header-utility>>
(set-babel-header :exports "none" "dot")
This will prevent dot
blocks from being included in the “published” document,
unless we override the setting on the block itself, as we did for the example
above.
1.3 rebase references
Is this needed? You can do the same thing by writing ../site/static/images
, and
it will get handled by the more general rule. Is there some downside to this,
e.g. for PDF export?
We publish these image files to a site-specific, absolute location. This transform allows us to write image links relative to the documents (which means that they actually work in Emacs), but also work on the site.
<!-- Rebase image paths -->
<xsl:template mode="org-copy" match="img/@src[starts-with(., '../published/images/')]">
<xsl:param name="attribute" select="'src'" />
<xsl:attribute name="{$attribute}">
<xsl:value-of select="concat(
'/static/images/',
substring-after(., 'images/'))"/>
</xsl:attribute>
</xsl:template>
This directory (published/images
) could at some point be used for something
other than dot
diagrams. At the moment, it’s not, so this rebase is here.
1.4 on setting header arguments in Org Babel
The function defined here is indeed used, but otherwise maybe this should be deleted or moved back to the bootstrap section. The thing is, that function is only used here.
Source blocks in Org can have “header arguments” to control a number of things, particularly during export and tangle. Header arguments can be set on individual blocks, such as this
#+BEGIN_SRC emacs-lisp :exports none
some junk that you don't want to be exported
#+END_SRC
which sets the :exports none
header. But if a certain header value is used very
commonly, we may set a default for convenience. One way to do that would be
with a PROPERTY
line:
#+PROPERTY: header-args :exports none
This would set the header for all code blocks in the document, and by using such
a line in a SETUPFILE
or INCLUDE
file, we could share common settings among all
of the documents. But doing so would require referencing that file from all
documents. Worse, it would cause all documents to be dependent on that file,
meaning that any change to it would trigger a complete rebuild.
Of course, like everything else in Org (and Emacs generally), this setting is
ultimately controlled through a variable, in this case
org-babel-default-header-args
. The shortest way to set a header is with
add-to-list
:
(add-to-list 'org-babel-default-header-args '(:exports . "none"))
However, this won’t work if the variable isn’t defined. And for
language-specific headers (such as we just used for dot
), it won’t be. So we
use a custom function instead:
(defun set-babel-header (name value &optional language)
"Utility function to set `org-babel' headers."
(let* ((suffix (if language (concat ":" language)))
(symbol (intern (concat "org-babel-default-header-args" suffix))))
(set symbol
(cons `(,name . ,value)
(if (boundp symbol)
(assq-delete-all name (symbol-value symbol)))))))
I never said I was a master of Emacs Lisp, and I’m sure there’s a better way to do that. But it does work.
As discussed in the system, we don’t use Org’s tangle, and although our custom tangle does recognize a few header arguments, it only uses those set on individual blocks, having its own mechanism for system-wide headers.
That header-utility
is a little function that I stole from the old (Emacs-based)
tangler, where I used it to set a bunch of settings. That has been replaced by
the custom tangler, so it’s not needed there, but it’s still kind of a useful
function (albeit overkill for this one case).
1.5 support interactive SVG
SVG graphics straddle the line between static and dynamic media. They can be
flat, they can be printed. But they can also be dynamic and responsive. At the
very least, they can contain functioning links, just like HTML. However, an SVG
document that is imported into HTML as an image (using the <img/>
element) will
not support most of these features. It will be essentially opaque, like a
raster image.
Instead, you have to “embed” it into the document, so that its particulars can be accessed.
<!-- Embed SVG as object. -->
<xsl:template
match="img['.svg' = substring(@src, string-length(@src) - 3)]"
mode="org-copy">
<object type="image/svg+xml">
<!-- We have to do this to get the rebased path. -->
<xsl:apply-templates select="@src" mode="org-copy">
<xsl:with-param name="attribute" select="'data'" />
</xsl:apply-templates>
<xsl:apply-templates select="@*[not(name() = 'src')]"
mode="org-copy" />
<img>
<xsl:apply-templates select="@*" mode="org-copy" />
</img>
</object>
</xsl:template>
The contents of the <object/>
element will be used as a fallback in case the
type of object is not supported. This is unlikely at this point, but in any
event, we fall back to the <img/>
as it was.
This also depends on some additional script, which is currently in the “transitional” folder.