a11y

package
v0.10.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 11, 2026 License: MIT Imports: 10 Imported by: 0

Documentation

Overview

Package a11y makes a chart readable by something other than an eye.

A chart is a picture, and a picture is where the information stops for a reader using a screen reader, a reader on a monochrome printout, and any program that would rather have the numbers. This package produces the two things that fix that: a description in words, and the data as a table.

Where it sits

It reads the model and is read by nobody in it — the same arrangement package spec has, and for the same reason. A geom that knew what a screen reader was would be a geom in the wrong package, so a layer says what it is through geom.Desc and this package decides what that means in words.

What figure does with it

github.com/timzifer/figure.Plot.Describe attaches a summary to a plot, and every backend that can carry words then writes it: an SVG gets a <title> and a <desc> and the role that makes a screen reader read them, a PDF gets a document title, a browser canvas gets an aria-label. github.com/timzifer/figure.Plot.DataTable writes the table.

The third channel — not colour alone — is a theme decision rather than a document one, and lives in github.com/timzifer/figure/theme.Redundant.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Table

func Table(c Chart) (string, error)

Table returns what WriteTable writes.

func WriteTable

func WriteTable(w io.Writer, c Chart) error

WriteTable writes the chart's data as an HTML table.

This is the fallback a picture cannot be: the numbers themselves, in reading order, in a form a screen reader navigates cell by cell and a spreadsheet opens. It is what the accessibility guidance means by a data table alternative, and it is also the honest answer to "what is actually in this chart".

One table per layer that has data, each with a caption naming the layer and a column per field it reads — x, y, a second y where a band has one, and the column a colour is taken from. A layer with no data source is an annotation: it is listed with its values rather than given a table of one row.

The output is a fragment, not a document: one or more <table> elements with no wrapper, so it drops into a page beside the chart. Everything written is escaped, including column names and category labels, which come from the caller's data rather than from figure.

Types

type Chart

type Chart struct {
	Title          string
	XTitle, YTitle string
	X, Y           scale.Scale
	Layers         []geom.Geom

	// Z and ZTitle are the depth axis of a three-dimensional chart, and are
	// nil and "" for a flat one. A reader who cannot see the picture needs to
	// be told there are three axes rather than two, and needs the third one's
	// range most of all — it is the one the flat chart of the same table does
	// not have.
	Z      scale.Scale
	ZTitle string

	// Descs are the layers of a chart whose layers are not geom.Geoms, as
	// [github.com/timzifer/figure/three]'s are not: a projected layer emits
	// geometry rather than ink and so cannot implement Geom. It is read when
	// Layers is empty, and it is the same description Layers is reduced to
	// before anything is said about it — so a scene is described in the same
	// voice as a flat chart rather than in a second one.
	Descs []geom.Desc

	// Y2, X2 and their titles are the chart's secondary axes, when it has
	// them. A reader who cannot see the picture needs to be told there are
	// two: a description that named one axis for a chart with two would say
	// the line runs from 0.09 to 0.17 on an axis it called "revenue".
	Y2      scale.Scale
	Y2Title string
	X2      scale.Scale
	X2Title string

	// Facet names the column a faceted chart is split by, if any. The panels
	// themselves are not described one by one: "one panel per region" is the
	// fact a reader needs, and thirty near-identical paragraphs is not.
	Facet string
}

Chart is what a plot tells this package about itself.

It is deliberately the model rather than the picture: the layers, the scales and the titles, with no rectangle anywhere. A description of where the marks landed on a canvas would be a description of a canvas.

type Range

type Range struct {
	Min, Max float64
	Ok       bool
}

Range is the extent of a column. Ok is false when there was nothing finite in it to measure.

type Series

type Series struct {
	// Label names the layer, and Mark is what it draws.
	Label string
	Mark  geom.Mark
	// X and Y name the columns, and Rows is how many there are. Z names the
	// depth column of a three-dimensional layer and is empty for a flat one.
	X, Y string
	Z    string
	Rows int
	// XRange and YRange are the extremes of the plotted columns, and ZRange
	// the depth column's. Ok is false for a layer with no numbers in it — an
	// annotation, or a column of nothing but missing values.
	XRange, YRange Range
	ZRange         Range
	// Time reports whether the corresponding axis is temporal, which decides
	// whether a bound reads as a number or as an instant.
	XTime, YTime bool
	ZTime        bool
	// SecondaryY and SecondaryX report a layer read against the chart's second
	// vertical or horizontal axis, so that a reading of it names the right
	// one.
	SecondaryY, SecondaryX bool
}

Series is one layer, reduced to what can be said about it.

type Summary

type Summary struct {
	// Title is a short label: the chart's own title, or a sentence naming what
	// it plots when it has none.
	Title string
	// Detail is the long reading: what each layer plots, over what range, and
	// how many rows there are.
	Detail string
	// Series is the same thing before it was turned into prose, for a caller
	// writing its own.
	Series []Series
}

Summary is a chart in words.

func Describe

func Describe(c Chart) Summary

Describe reads a chart and says what it shows.

It costs one pass over each plotted column, which is why figure does not do it on every render: describing a million rows is cheap compared with drawing them and is not free, and a chart nobody is describing should not pay for it.

A layer that cannot describe itself — a third-party geom implementing no geom.Describer — is named by its position rather than skipped, because a description that quietly omits a series is worse than one that admits to it.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL