OTAS stamps are one of the most recognisable ways in which we visually represent our data. We generate stamps for both single-stock and list data across many metrics, always seeking to present the most important information clearly and concisely. Currently, our stamps are handwritten in the SVG vector graphic format. In this post, I’m going to explore recreating one of our stamps in Haskell using the diagrams framework, leveraging the expressiveness and reuse that come with the abstraction as much as possible. This is not a *diagrams* tutorial; please refer to the manual for a comprehensive introduction.

I’m going to recreate one of our simpler stamps, the Signals stamp. This is for simplicity: I am confident *diagrams *is expressive enough to recreate all of our stamps. The Signals stamp displays the most recent close-to-close technical signal which has fired for the stock, showing the number of days ago the signals fired and the average one-month return the stock has experienced when this signal has fired in the past. Here’s an enlarged image of the stamp:

*diagrams* provides a very general Diagram type which can represent, amongst others, two-dimensional and three-dimensional objects. I’ll build up the stamp by combining smaller elements into a larger whole, focusing first on the core details contained in the inner rectangle: the row of boxes and lines of text. As we’ll see, the framework provides a rich array of primitive two-dimensional shapes with which to begin our diagram, and a host of combinators for combining them intuitively. Bear in mind that the Diagram type is wholly independent of the back-end used to render it: back-ends exist for software such as Cairo and PostScript as well as a first-class Haskell SVG representation provided by the diagrams-svg library.

First, let’s get some imports and definitions out of the way:

```
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedStrings #-}
module Stamps where
import Diagrams.Prelude
import Diagrams.Backend.SVG
import Text.Printf
data SignalsFlag = SignalsFlag
{ avgHistReturn :: Double
, daysAgo :: Int
}
lightGrey
= sRGB24read "#BBBBBB"
mediumGrey
= sRGB24read "#AAAAAA"
darkGrey
= sRGB24read "#999999"
brightGreen
= sRGB24read "#40981C"
mud
= sRGB24read "#101010"
steel
= sRGB24read "#2F2F2F"
```

Our point of reference is Diagrams.Prelude in the diagrams-lib package (I’m using version 1.3), which exports the core types and combinators together with handy tools from *lens* and *base*. Additionally, we import `Diagrams.Backend.SVG`

from diagrams-svg as our back-end. As I explained above, *Diagrams* is back-end agnostic: we import one here only to simplify the type signatures. The remainder of the above snippet defines a representation of a signal and a set of colours, read from a hexadecimal colour code.

Primitive shapes such as squares and rectangles, as well as combinations of these, have type `Diagram B`

(the `B`

type is provided by the back-end). Crucially, each diagram has a *local origin* which determines how it composes with other diagrams. As you may guess, regular polygons use, by default, the canonical origin, but this may not be as easy to determine for more complex structures.

The most basic combinators are `(<>)`

, `(|||)`

and `(===)`

and their list equivalents `mconcat`

, `hcat`

and `vcat`

. `(<>)`

(monoidal append) superimposes the first diagram atop the second (clearly diagrams form a monoid under such an operation), so `circle 5 <> square 3`

is a circle of radius 5 overlaid on a 3 by 3 square, such that their origins coincide (the resulting diagram has the same origin). `(|||)`

combines two diagrams side-by-side, with the resulting origin equal to the origin of the first shape, while `(===)`

performs vertical composition. These combinators are instances of the more general `beside`

function, but they are sufficient here. Additionally, `beneath`

is a handy synonym for `flip (<>)`

, and `#`

is reverse function application, provided to aid readability.

Here’s the function to generate the internals of the signals stamp. Remember, we’re using a `SignalsFlag`

to generate a `Diagram B`

, hence the type signature of `signalsStamp`

.

```
signalsStamp :: SignalsFlag -> Diagram B
signalsStamp flag
= vcat $
[ strutY 1.2 `beneath` text perf
# fontSizeL 1.2
# fc white
, strutY 1 `beneath` text "20d avg. perf."
# fontSizeL 0.8
# fc mediumGrey
, strutY 0.5
, boxRow
# alignX 0
, strutY 1 `beneath` text "days ago"
# fontSizeL 0.8
# fc mediumGrey
, strutY 1 -- Padding for aesthetic
]
where
perf
= printf "%+.2f%%" (view avgHistReturn flag)
boxRow :: Diagram B
boxRow
= hcat boxes
where
boxes
= map (\k -> box k (k == view daysAgo flag)) [5, 4 .. 1]
where
box n hasSignal
= square `beneath` text (show n)
# fc white
where
square
= unitSquare
# lc lightGrey
# if hasSignal
then
fc brightGreen
else
id
```

Before I explain the code, here’s the resulting SVG:

The top level of the function vertically concatenates (`vcat`

) the four components of the diagram. Text is enclosed in `strutY`

s, invisible constructs which represent empty space in the diagram, and which are also used for padding. Font size is adjusted relative to the enclosing strut with the `fontSizeL`

function, so the size of the rendered text is a function both of the size of the strut and the font size. `boxRow`

is a `Diagram B`

representing the row of boxes and needs to be aligned with `alignX`

to recentre the origin horizontally prior to vertical concatenation.

Constructing `boxRow`

requires horizontally concatenating (`hcat`

) five boxes labelled 5 through 1, with the box corresponding to the signal’s `daysAgo`

field shaded green. The `unitSquare`

primitive constructs a 1 by 1 square, which we fill with `fc`

if the boolean `hasSignal`

is true. Remember, to insert text into the square we superimpose the string onto it with `(<>)`

. A simple map generating the box indices and `hasSignal`

values finishes the construction of the row.

From this small example, I hope it’s clear that simple diagrams, at least, may be constructed in a clear, declarative way. Next, I want to focus on constructing the series of rectangles which will surround the above image, the *bezel*. This is where the higher-level representation of diagrams really shines: reusability. We can construct a function which will take any diagram, scale it to a uniform size, and then enclose it in our bezel. This could be reused for every stamp we create, with no code duplication. To implement this, we’ll create a function `bezel :: String -> Diagram B -> Diagram B`

which will wrap the given diagram in a series of rectangles and add the given title:

```
bezel :: String -> Diagram B -> Diagram B
bezel title stamp
= mconcat
[ (title' === centre)
# alignY 0
, roundedRect (r + innerWidth + outerWidth + outerWidth') (1 + innerWidth + outerWidth + titleHeight + outerWidth') curvature
# fc mud
# font "Arial"
]
where
-- Ratio between width and height of stamp
r
= 0.8
stamp'
= stamp
# scaleToY 1
# scaleToX r
innerWidth
= 0.1
outerWidth
= 0.025
outerWidth'
= 0.075
curvature
= 0.075
titleHeight
= 0.15
title'
= strutY titleHeight `beneath` text title
# fc darkGrey
# fontSizeL titleHeight
# font "Arial"
centre
= mconcat
[ stamp'
# alignY 0
, roundedRect (r + innerWidth) (1 + innerWidth) curvature
# fc steel
, rect (r + innerWidth + outerWidth) (1 + innerWidth + outerWidth)
# fc black
]
```

The core specification is given at the top level and in the definition of `centre`

. `centre`

encloses the scaled stamp (of aspect ratio `r`

) in first a steel-coloured rounded rectangle and then a black regular rectangle. The scaling is important, as it allows the function to operate correctly regardless of the width and height of the diagram argument. Then, the title is vertically concatenated `(===)`

to this object and superimposed onto a final mud-coloured rounded rectangle.

Here’s the result of `bezel "Signals" mempty`

, the bezel enclosing the empty diagram:

And here’s the final result, `bezel "Signals" (signalsStamp $ SignalsFlag 2.06 5)`

:

I’m very happy with this result, especially for such straightforward code. I haven’t been truly faithful to the dimensions of the original image, but they could be recreated with a little more effort. As I mentioned, our bezel definition is reusable across other diagrams, so to recreate our other stamps I would only need focus on the core structure, logic which I’m confident would be straightforward with *Diagrams*. But *Diagrams* is capable of far more than this, and I encourage you to explore the manual for an in-depth introduction.