tmbb

tmbb

Quartz - Data Visualization Library (aimed at publication, not interactive display)

Playfair (named after: William Playfair) is a data visualization/plotting library with the goal of being able to produce publication-quality figures without the use of any other tools. Because producion publication-quality figures always entails some manual adjustments, Playfair aims to be very customizable. When picking between convenience/terseness or customizability, Playfair will often choose customizability. Playfair doesn’t draw anything directly. Instead, it uses the typst typesetting system as a backend, through the interface provided by ExTypst.

Currently it only supports boxplots (and the boxplots don’t even show the outliers outside the whiskers because I haven’t implemented that part yet). I’ve decided to implement boxplots before more basic plots such as scatter plots becuase boxplots are actually quite complex.

The library is under very active development and APIs might changes without warning. Don’t use this for anything serious yet.

Some example code:

XYPlot.new()
# Data is generated somehwere else in the code
|> BoxPlot.plot("x", "y", data())
|> XYPlot.put_title(Typst.raw("strong()[A. Reaction time according to group]"))
|> XYPlot.put_axis_label("y", Typst.raw("strong()[Reaction time (ms)]"))
|> XYPlot.put_axis_label("x", Typst.raw("strong()[Populations]"))
# Drop the spines for the X2 and Y2 axes (you can also remove those axes instead)
|> XYPlot.drop_axes_spines(["x2", "y2"])
|> XYPlot.render_to_pdf_file!("examples/box-plot-example-without-spines.pdf")

Generated figure (converted from PDF to PNG because ExTypst can’t generate PNG or SVGs directly, althougn I believe typst itself can):

The code is inspired by Python’t Matplotlib but with a more functional style. The goal is to provide all the plot types and general functionality provivded by matplotlib except for the interactive parts. I’m open to support animatinos by generating multiple frames and then gluing them together, but it’s definitely not a priority.

Issues (so far!)

Unlike Matplotlib, it’s not yet possible to have more than one plot in the same figure (Playfair doesn’t even have the notion of a figure), but that’s actually quite simple to implement using typst as a backend (we can just reuse the native typst layouts to compose plots together).

The main issue with Playfair right now is that it isn’t easy to draw arbitrary content in the labels area, for example (you might want that if you’re doing some survival analysis with Kaplan-Meier curves and want to draw a table with the “at risk” counts below the x-axis ticks). In all fairness, this is something with which even Matplotlib strugles a lot. Most of the interesting functionality requires some kind of constraint solver (even a linear solver employing something like the simplex method would probably be enough for most cases), and it’s hard to provide an interface which makes it natural to draw something AND which is compatible with the way constraint solvers like to work.

Another problem with trying solve constraints intelligently is that we actually need the backend (i.e. typst) to evaluate the object sizes (the Elixir part has no idea how text is rendered, or even worse, how mathematical formulas are rendered!), and this means that a lot of the constraint solving must happen inside typst, instead of calling an optimized constraint solver.

Most Liked

tmbb

tmbb

For anyone following this, it turns out I’m a total idiot… Elixir is handling tens of thousands of elements just fine, as I have just discovered with better benchmarking. Even if each element contains a number of totally not optimised polynomials implemented as structs.

It turns out that unlike what I previously reported, the problem is with typst. Problems start to happen when I try to compile typst files of > 20.000 lines with a large number of graphical objects. To be fair to typst, these files are probably much larger than anything anyone has tried to use typst on (I don’t know whether the critical part is the patching or the rendering, but I’m not that interested in finding out).

Because of this, I have decided to keep the core constraint-based architecture the same and drop typst. I’ll be try to use resvg (the rust svg rendering library, which also has Elixir bindings) for text rendering and measuring and to render SVGs into PNGs. I assume that resvg is probably better optimised than typst for rendering larger numbers of shapes.

The only thing I lose is math typesetting, but it turns out plots don’t use that much math typesetting. One can get by with unicode symbols, superscripts, subscripts and not much else.

tmbb

tmbb

Quartz (this library is no longer called playfair) can now draw line plots (by very inefficiently drawing line segments individually instead of using a normal path because I haven’t iomplemented paths yet).

Example here:

Code:

defmodule Quartz.Benchmarks.LinePlot do
  use Dantzig.Polynomial.Operators
  require Quartz.Figure, as: Figure
  alias Quartz.Plot2D
  alias Quartz.Length

  def build_plot() do
    figure =
      Figure.new([width: Length.cm(8), height: Length.cm(6), debug: false], fn _fig ->
        [[bounds]] =
          Figure.bounds_for_plots_in_grid(
            nr_of_rows: 1,
            nr_of_columns: 1,
            padding: Length.pt(16)
          )

      x = for i <- 1..100, do: 0.01 * i
      y = for x_i <- x, do: x_i * 0.3 + (0.05 * :rand.uniform())

      data = %{x: x, y: y}

      _plot =
        Plot2D.new(id: "plot_A")
        |> Plot2D.set_bounds(bounds)
        |> Plot2D.line_plot("x", "y", data)
        # Use typst to explicitly style the title and labels ――――――――――――――――――――――――――――――――
        |> Plot2D.put_title("A. Line plot")
        |> Plot2D.put_axis_label("y", "Prediction: $f(x)$", text: [escape: false])
        |> Plot2D.put_axis_label("x", "Predictor: $x$", text: [escape: false])
        |> Plot2D.finalize()
      end)

    path = Path.join([__DIR__, "line_plot", "example.pdf"])
    Figure.render_to_pdf_file!(figure, path)
  end
end
tmbb

tmbb

Some more examples, this time for distribution plots.

KDE smoothing of the posterior distributions for a NUTS bayesian simulation, showing all 4 chains in the same plot:

The same data plotted using boxplots (boxplots are not very configurable yet, but that is just a question of adding more options):

Finally, the contour plot I’ve shown before, but with a much higher resolution now that we don’t depend on typst and can use the more performant (for this use case) resvg renderer (note that we can fake powers of integers with superscript unicode characters, although for more advanced things one really needs proper math typesetting):

And no, Quartz still can’t rotate text, which messes up the case where the labels of the y-axis are longer, but it works fine for these short labels.

Where Next?

Popular in Announcing Top

danschultzer
None of the current solutions worked well for me, so I went ahead and built a user management system from scratch. This project took far...
548 29377 241
New
tmbb
I’ve published the first version of my Makeup library. It’s a syntax highlighter for Elixir in the spirit of Pygments, Currently it highl...
New
sorentwo
Hello! tl;dr Announcing Oban, an Ecto based job processing library with a focus on reliability and historical observability. After spen...
985 42920 311
New
jakub-zawislak
Hi everyone, I’m coming from the Symfony (PHP) framework. I like Phoenix, but it has a one thing that was build much better in the Symfo...
New
josevalim
Yes, yet another parser combinator library! Most of the parser combinators in the ecosystem are either compile-time, often using AST tra...
159 19228 141
New
josevalim
EDIT: since Ecto 3.0 final version is out, this post was amended to use the final versions in the instructions below. Hi everyone, We a...
New
mtrudel
Bandit is an HTTP server for Plug and WebSock apps. Bandit is written entirely in Elixir and is built atop Thousand Island. It can serve...
New
bluzky
You may know https://ui.shadcn.com/, a UI component library for React. I really love it’s design style and components. I’ve built some co...
384 13736 119
New
kip
Image is an image processing library for Elixir. It is based upon the fabulous vix library that provides a libvips wrapper for Elixir. I...
622 18474 194
New
Qqwy
Hello everyone, I wrote a small library today called MapDiff. It returns a map listing the (smallest amount of) changes to get from map...
New

Other popular topics Top

AstonJ
Posting this to see if we can make things easier for people to get into Neovim. If you use Neovim and have a favourite distro please let ...
New
johnnyicon
Hi all, I’ve just started learning Elixir and Phoenix Framework, so please pardon my n00bness at this stage. I’m trying to use Postgres...
New
aesmail
Hello guys, I have finally made it. I created an admin interface for a framework. It’s been on my todo list for years and with the curre...
New
vrod
I am using the Starship cross-shell prompt – it seems pretty nice, but I get some errors: [WARN] - (starship::utils): Executing command ...
New
aalberti333
As the title describes, I’m trying to run Enum.map() over a list of key/value pairs, where the value is a map. My data looks like this: ...
New
grych
Hi folks, Few months ago I have announced the proof-of-concept of the library to manipulate the browsers DOM objects directly from Elixi...
639 52341 488
New
gausby
I asked this very same question on twitter and got some interesting feedback, but I thought it would be a good question to ask here as we...
1207 39297 209
New
rms.mrcs
Hi, I need to transform a list of numbers into a map where the keys are the indexes and the values are the original values of the list. ...
New
hariharasudhan94
I would like to know what is the best IDE for elixir development?
New
vonH
In asking this question I am more interested about the expressiveness of the language itself and less concerned about the availability of...
New

We're in Beta

About us Mission Statement