2D data visualization for Turmeric: functions, points, intervals, histograms, contours, legends, and PNG output via plutovg.
tur-plot is a Tier 1 spice implemented in Turmeric with thin inline-C helpers
for sampling and rendering. It draws into an off-screen plutovg surface and
returns that surface handle to the caller, or writes directly to PNG.
It is designed to pair naturally with tur-plutovg: use plot when you want a
finished chart surface, or plot-into-canvas when you need to compose several
plots into a larger image.
:spices {
"plot" {:url "https://github.com/turmeric-lang/turmeric-spices"
:ref "plot-v0.3.0"
:subdir "spices/plot"}
}
Each renderer constructor (function, lines, axes, ...) returns an
AnyRenderer, and plot-write-png takes a (Vec AnyRenderer), so a vec-of
over them mixes kinds freely. A sampled curve's callback is a typed function
value -- a defn, or a fn literal that may capture.
(import plot/core :refer [plot-write-png])
(import plot/line :refer [function])
(import plot/decor :refer [axes tick-grid])
(import plot/style :refer [default-line-style default-plot-opts])
(defn quadratic [x :float] :float (* x x))
(plot-write-png
(vec-of (tick-grid)
(axes)
(function quadratic
-2.0 2.0 128
(default-line-style)
"x^2"))
(default-plot-opts)
"quadratic.png")
samples (128 above) is a baseline; the renderer adaptively bisects
segments in high-curvature regions and around NaN boundaries up to a fixed
depth, so smooth curves stay cheap and sharp turns get extra samples
without forcing every caller to crank the count globally.
A renderer's data is a Vec of records from plot/data -- Point, Rect,
ErrorBar, Arrow, Bar and Group -- built with their constructors:
(import plot/data :refer [Point])
(lines (vec-of (Point 0.0 0.0) (Point 1.0 2.0) (Point 2.0 1.5))
(default-line-style) "data")
Every renderer kind is its own type -- FunctionR, LinesR, PointsR,
ContoursR, the Decoration sum type for axes and grids, and so on -- with
named fields and an instance of the Renderer class:
(defclass Renderer [a]
(bounds [r : a] : BBox) ;; the data extent it needs on screen
(draw [r : a ctx : DrawCtx] : void) ;; paint it through the plot's context
(legend-label [r : a] : cstr "") ;; its legend entry, if any
(legend-labels [r : a] : (Vec cstr) ...) ;; its legend rows; by default the one above
(frame-marks [r : a] : FrameMarks (no-frame-marks)) ;; axes / grids for the frame
(categorical? [r : a] : bool false) ;; lays an axis out as categories?
(categories-on-y? [r : a] : bool false) ;; ... along y rather than x?
(category-labels [r : a] : (Vec cstr) (:: (vec-new) (Vec cstr)))) ;; their labels
The plot's extent is the union of its renderers' bounds; the frame draws
the axes and grids their frame-marks ask for; then each one's draw paints
it, in order, and the legend lists their legend-labels, each swatched in
the style that row's series was drawn with. A constructor
boxes its struct with that instance as an AnyRenderer, which is what
lets one Vec hold every kind. Passing anything else to plot -- a value with
no Renderer instance, or a callback of the wrong function type -- is a
compile error, not a mis-rendered chart.