hakyll-diagrams: A Hakyll plugin for rendering diagrams figures from embedded Haskell code.

[ bsd3, library, web ] [ Propose Tags ] [ Report a vulnerability ]

Compiles any Haskell diagrams code embedded in input source files (Markdown, reStructuredText, etc.), replacing them with the rendered diagrams figures in the generated HTML output. The diagrams figures can be inlined as SVG code or referenced as external .svg image files using <img> tags.

For example, when processing a Markdown input source file, a code block like this:

``` diagram { svg:width=300 }
let
  target = mconcat
    [ circle 1 # lw 0 # fc red
    , circle 2 # lw 0 # fc white
    , circle 3 # lw 0 # fc red
    ]

  background = roundedRect 8 8 0.1
    # lw 0
    # fc (sRGB24read "#808080")
    # opacity 0.15

in target <> background
```

will be replaced in the resulting HTML page by this figure:

A thoughtful documentation with usage examples and results can be found at the project repository.


[Skip to Readme]

Modules

[Index] [Quick Jump]

Downloads

Maintainer's Corner

Package maintainers

For package maintainers and hackage trustees

Candidates

  • No Candidates
Versions [RSS] 0.1.0.0
Dependencies base (>=4.17.2 && <4.22), base16-bytestring (>=1.0.2 && <1.1), cryptohash-sha1 (>=0.11.101 && <0.12), data-default (>=0.7.1 && <0.9), diagrams (>=1.4.1 && <1.5), diagrams-core (>=1.5.1 && <1.6), diagrams-lib (>=1.4.6 && <1.6), diagrams-svg (>=1.4.3 && <1.6), hakyll (>=4.16.2 && <4.17), hint (>=0.9.0 && <0.10), pandoc-types (>=1.23 && <1.24), split (>=0.2.3 && <0.3), svg-builder (>=0.1.1 && <0.2), text (>=2.0.2 && <2.2) [details]
Tested with ghc ==9.4.8 || ==9.12.2
License BSD-3-Clause
Author Renato Garcia
Maintainer fgarcia.renato@gmail.com
Uploaded by renato_garcia at 2026-01-16T23:31:16Z
Category Web
Home page https://github.com/renatoGarcia/hakyll-diagrams
Source repo head: git clone https://github.com/renatoGarcia/hakyll-diagrams.git
Distributions
Downloads 1 total (1 in the last 30 days)
Rating (no votes yet) [estimated by Bayesian average]
Your Rating
  • λ
  • λ
  • λ
Status Docs available [build log]
Last success reported on 2026-01-17 [all 1 reports]

Readme for hakyll-diagrams-0.1.0.0

[back to package description]

hakyll-diagrams

Contents

Overview

Compiles any diagrams code found within markdown source files and inserts the resulting figures into the generated HTML output. The figures can be embedded as inline SVG code or referenced via external image files using <img> tags.

For example, when a block like this is found in a markdown file:

``` diagram { svg:width=300 }
let
  target = mconcat
    [ circle 1 # lw 0 # fc red
    , circle 2 # lw 0 # fc white
    , circle 3 # lw 0 # fc red
    ]

  background = roundedRect 8 8 0.1
    # lw 0
    # fc (sRGB24read "#808080")
    # opacity 0.15

in target <> background
```

That code will be compiled, and replaced by the rendered SVG figure:

If we add a few attributes to the rendered SVG figure:

``` diagram {
  figcaption="An interactive figure. It reacts to mouse hover and each part is a link."
  svg:width=400
}
let
  hydrogen =
    mconcat
      [ proton
      , electron # moveTo p0
      , orbit
      ]
    where
      orbit =
        circle 2
          # svgAttr "pointer-events" "stroke"
          # href "https://en.wikipedia.org/wiki/Atomic_orbital"
          # svgClass "svg_orbit"
      electron =
        circle 0.1
          # fc blue
          # lw 0
          # href "https://en.wikipedia.org/wiki/Electron"
          # svgClass "svg_electron"
      proton =
        circle 0.3
          # fc red
          # lw 0
          # href "https://en.wikipedia.org/wiki/Proton"
          # svgClass "svg_proton"
      p0 = fromJust $ maxTraceP origin (angleV $ 3/8 @@ turn) orbit

  background =
    roundedRect 5 5 0.1
      # lw 0
      # fc (sRGB24read "#808080")
      # opacity 0.15

in hydrogen <> background
```

We will generate this figure:

And with the help of some additional CSS or JavaScript code, we can make it an interactive figure. That will not be possible to be shown here, because GitHub has restrictions on adding inline SVG and JavaScript code to the Markdown files, however that same figure with interactive features is available here.

Usage

Input format

In a source file, any Haskell code within a code block having diagram as one of its classes will be rendered to a diagram figure. In a markdown file, it means any code block like this:

``` diagram {
        <options>
    }
<code>
```

Where <options> is a list of library and HTML tag attributes, fully described in the Options section, and <code> must consist of a single Haskell expression whose resulting value has type Diagram SVG.

Code

The code block must contain a single Haskell expression of type Diagram SVG.

The interpreter environment is configurable via the Options record, which serves as an argument to the drawDiagramsWith function. All exports from modules listed in globalModules and localModules will be available in the context.

Options

The options configure what the diagram rendering will produce as results, and specify any additional attributes for the resulting HTML and SVG elements.

A valid options list must be in the format:

options    := {option}
option     := id | class | figcaption | attribute
id         := "#" name
class      := "." name
figcaption := "figcaption=" value
attritube  := tag ":" name "=" value
tag        := "figure" | "img" | "svg"
name       := <any valid HTML attribute name>
value      := <any valid HTML attribute value>

An input like this:

``` diagram { img:src="external_figure.svg" figcaption="foo"
      .my_class_a .my_class_b  #my_id
      img:alt="a circle" figure:myattr=foo img:width=10
      svg:class="my_svg_class" svg:width=50
    }
circle 10
```

Will generate an external SVG file named external_figure.svg, written under the Hakyll's configured destinationDirectory. The <svg> tag will have the following attributes:

<svg width="50.0000" class="my_svg_class" ... >

Besides generating an external file, that input will be replaced in the resulting HTML file by a code like:

<figure id="my_id" class="my_class_a my_class_b" data-myattr="foo">
  <img src="external_figure.svg" alt="a circle" width="10" />
  <figcaption>foo</figcaption>
</figure>

Regardless of any options, a <svg> element will always be generated. If we have set an img:src="path" attribute, the rendered SVG will be written to an external file at path, and an <img> element will link to it. If we don't have an img:src="path" attribute, the rendered SVG will be inlined in the HTML code.

If we have set a figcaption="caption" attribute, the <svg> or <img> element will be nested in a <figure> element. Otherwise they will be inserted without a parent.

The attributes must be in the format: tag:name=value, and tag must be one of: figure, img, and svg. This means that the pair name=value will be assigned to the tag element. If that element would not be generated, such as when we don't have an <img> tag because of an inlined SVG, or if the tag has any other value than those valid three, it will be ignored without an error.

The #id and .class values will be assigned to the outermost element, whether it is <figure>, <img>, or <svg>.

Metadata options

We can use the Hakyll's metadata block to create an Options record to be used as argument to the drawDiagramsWith functions that will render that page diagrams.

This works starting from a base Options record, whose fields will be modified by the values at the metadata block. Each one of the record fields can be assigned a value through a metadata key named the same as that field prefixed with dg..

  • Each field must be formatted as a list where each element is separated by a comma ,.
  • Any occurrence of an ellipsis ... will be replaced by the current value of that field as in the base Options.
  • When setting either globalModules or localModules, a value like Utils will result in an unqualified import of Utils module. A value like Commons as Cm will result in a qualified import of Commons module under Cm name.
  • Any field not present will retain the same value as in the base Options. An empty string "" will set the field to an empty list.

When starting with base:

Options
  { globalModules =
      [ ("Prelude", Nothing)
      , ("Diagrams.Prelude", Nothing)
      , ("Diagrams.Backend.SVG", Nothing)
      ]
  , localModules = []
  , searchPaths = ["app", "posts"]
  , languageExtensions = ["NoMonomorphismRestriction"]
  }

the metadata block:

---
dg.localModules: Utils, Commons as Cm
dg.searchPaths: lib, ..., foo
dg.languageExtensions: ""
---

will result in:

Options
  { globalModules =
      [ ("Prelude", Nothing)
      , ("Diagrams.Prelude", Nothing)
      , ("Diagrams.Backend.SVG", Nothing)
      ]
  , localModules = [("Utils", Nothing), ("Commons", Just "Cm")]
  , searchPaths = ["lib", "app", "posts", "foo"]
  , languageExtensions = []
  }

Haskell API

All the API documentation is available in the Haddock generated project page.

The main functions are drawDiagrams and drawDiagramsWith. Both functions accept a Pandoc data structure and transform it by replacing CodeBlock's having a diagram class with their rendered diagram figures, as explained in the Input format section.

The simplest way to integrate these functions into a Hakyll's rule is with code like this:

match "posts/*.md" $ do
  route $ setExtension "html"
  compile $
    pandocCompilerWithTransformM
      defaultHakyllReaderOptions
      defaultHakyllWriterOptions
      drawDiagrams

We can use either readOptionsFromMetadata or readOptionsFromMetadataWith function to control at the input source level what Options will be used to configure the interpreter environment when rendering the diagrams on that page. The Metadata options section explains the metadata block syntax.

One simple way to add this metadata reading functionality to the example code above is to replace the drawDiagrams function by a drawDiagrams' defined as:

drawDiagrams' :: Pandoc -> Compiler Pandoc
drawDiagrams' pandoc = readOptionsFromMetadataWith opts >>= flip drawDiagramsWith pandoc
  where
    opts = defaultOptions { searchPaths  = ["app", "posts"] }

Package databases

The hint library is used to interpret the Haskell code, and therefore all of its limitations will apply here as well. In particular, if there isn't a .ghc.environment.\<something> package environment file (as generated by cabal --write-ghc-environment-files=always option), setting either HAKYLL_DIAGRAMS_PACKAGE_ENV (a single path) or HAKYLL_DIAGRAMS_PACKAGE_DB (N comma-separated paths) environment variables may be required. If these environment variables are defined, their values will be used to set the GHC -package-env and -package-db options respectively.

Similar projects

There are two diagrams projects, diagrams-pandoc and diagrams-builder, that implement a portion of the functionality of this hakyll-diagrams library. They are not used as dependencies because they would not allow some useful features. For example, with diagrams-builder it is not possible to import a local module qualified, and it has some potential issues when combining them. With diagrams-pandoc we cannot add arbitrary attributes to a specific HTML element, or generate an inlined SVG instead of an externally linked file.

There is also a homonymous hakyll-diagrams library that integrates the two diagrams libraries with hakyll, running them inside the Compiler monad. Since it uses those two diagrams libraries, it has the same limitations discussed above. Because of this, I have decided to create a new hakyll-diagrams library with all the desired features.