Skip to content

v0.8.0 — the first release under this name

Choose a tag to compare

@timzifer timzifer released this 09 Sep 10:37
· 55 commits to main since this release

The first release under this name, and a deliberate v0.x.

go get github.com/timzifer/figure@v0.8.0
Module Tag
github.com/timzifer/figure v0.8.0
github.com/timzifer/figure/backend/gg backend/gg/v0.8.0
github.com/timzifer/figure/backend/window backend/window/v0.8.0
github.com/timzifer/figure/backend/gg/gpu backend/gg/gpu/v0.3.0
github.com/timzifer/figure/arrow/v18 arrow/v18.0.4

Why v0.x

The model is settled and the API is not frozen. Three seams take positional
scale arguments and are the wrong shape for a chart that gains a dimension
(ADR 0056).
Correcting them is free while nobody has to migrate, and v0.x says plainly
that it has happened rather than promising a freeze that would break in the
next release. v1.0.0 returns when the reshaping is done
(ADR 0059).

Every seam an outsider implements now takes one growable value

An interface a third party implements never gains a method — and under that
rule a method with positional parameters can never gain one either, so each is
a future break or a future optional interface. Three optional interfaces in the
tree existed for exactly that reason. They are gone, and the seams that
produced them take a struct
(ADR 0060).

Seam Before After
geom.Geom Train(x, y scale.Scale) error Train(t geom.Training) error
render.Observer Panel(i, area, x, y, cd) Panel(p render.PanelInfo)
render.Observer Layer(i int, label string) Layer(l render.LayerInfo)
render.Observer gains End()
render.LayerAxes, render.EndData optional interfaces removed — fields on LayerInfo, and Observer.End
render.LegendEntry / ColorbarEntry / SizeKeyEntry positional arguments LegendInfo / ColorbarInfo / SizeKeyInfo
coord.Coord Frame(area, x, y) Frame(f coord.Framing)
coord.Coord Furniture(dst, area, m, xTicks, yTicks) Furniture(dst, req coord.FurnitureRequest)
coord.Opposite FurnitureY2 + FurnitureX2 FurnitureOpposite(dst, req)
ir.Target / ir.Resizer Open(w, h int, dpr float64) / Resize(…) Open(s ir.Surface) / Resize(s ir.Surface)
mathtext.Typesetter Typeset(src, font, m) Typeset(req mathtext.Request)
geom.Rows Marks(at []ir.Point, rows []int) Marks(m geom.MarkRows)
scale.Scale Ticks(want int) []Tick Ticks(req scale.TickRequest) []Tick

The migration is mechanical: a method's parameters become field reads, and the
compiler finds every site.

LayerAxes and EndData are removed rather than restructured because an
implementation that omits either is wrong rather than narrower — one indexes
furniture as data, the other reads a secondary axis through the wrong scale.
The three guide reports stay optional, because an observer that does not care
where the legend was drawn is only narrower.

A column is one value, and an identifier is exact

data.Source had three typed accessors and a promise that a fourth kind of
column would arrive as an optional interface. It has one accessor returning a
growable data.Column
(ADR 0061).

type Source interface {
    Len() int
    Columns() []string
    Column(name string) (Column, bool)
}
  • data.KindInt64 — exact integers. A float64 stops counting exactly at
    2^53, and the column that holds large integers is the column that holds
    identifiers. Keys are compared as their spelling, so two ids one apart above
    2^53 used to be one key, one facet panel and one row — and printed as
    9.007199254740992e+15. data.Table.Int64 and the Arrow adapter's integer
    path keep them exact. A position still converts to float64: a pixel cannot
    show the difference, and the exactness that matters is where identity is
    compared.
  • Column.Nulls replaces the data.Nulls interface. Absence travels with
    the values it describes. It had to: two wrapping Sources in this repository
    did not forward the old method, so a marked row silently lost its mark
    through a transition and the blend interpolated a value nobody measured.
  • Column.Text is an optional per-row spelling. It is what lets a Source
    carry values figure has no type for — a physical quantity plots as float64
    positions and spells as "2.5 bar" — with figure knowing nothing about units.
  • spec.ParseInteger carries an exact column through the JSON round trip,
    decoded via json.Number so the digits survive.

Every backend draws the same marker

ir.MarkerPath is exported. Marker outlines were internal, so backend/gg
carried a hand-maintained copy and a backend written outside this repository
would draw a marker added later as a circle — silently. ir.Marker grows at
the end, and MarkerPath appends a circle for a shape it does not know, so a
backend stays correct across that growth. Not a breaking change.

Documentation

The README is a quarter of its former length: what the library is, how to
install it, one working program, the feature surface, and where the seams are.
The walkthroughs moved into linked files under docs/ — the gallery, chart
forms, interaction, scaling out, reading, the JSON dialect — each cross-linked
so the set reads as one document.

The version archaeology is gone with it: no struck-out lines, no "shipped in
vX.Y". The record of how each capability arrived, with the argument that shaped
it, is docs/milestones.md.

Compatibility

Anything written against github.com/timzifer/refract keeps working on its own
tags and receives nothing new. There is no automated migration and none is
claimed: change the import path, then let the compiler find the seams in the
table above.