Functional Weave
Code in Python

charts.layout@1.0.0

README.md

2,305 bytes · view raw

# charts.layout

Three pieces of chart layout that have nothing to do with the data: where the
plot goes, where the legend items go, and which labels to leave out so none
overlap. Pixels in, pixels out, rounded to 2 decimal places by
`math.round-float`, the same in every language.

This is a group; install only what you use, e.g.
`require charts.layout ^1.0.0 only=plotArea`.

## plotArea

The d3 margin convention: the chart is `width` by `height`, the margins are
taken off each side, and what is left is where the data is drawn. `x` and `y`
are its top-left corner (the left and top margins). Margins that leave no
width or height are an error, since a zero or negative plot area either draws
nothing or draws the data mirrored; negative margins are an error too.

## legendRows

Places legend entries left to right, each a colour swatch, 4 pixels, then the
label, with `gap` between entries, wrapping to a new row when the next entry
would pass `maxWidth`. An entry that ends exactly at `maxWidth` stays on the
row. An entry wider than the whole legend is placed on a row of its own
rather than leaving an empty row before it. Row `r` has its top at
`r x rowHeight`; offset the whole legend wherever it belongs.

Nothing here can measure text, so a label's width is estimated as its number
of characters (Unicode code points, so "Café" is 4, not its 5 UTF-8 bytes)
times `charWidth`. About 0.55-0.6 of the font size suits common sans-serif
fonts (7 for 12px); a renderer with real metrics can pass its own average. The
estimate is deliberately simple so every language places the legend the same.

## avoidCollisions

Given label boxes, decides which to show so that no two overlap, greedily:
the most important label first (higher `priority`, then earlier in the list),
and each further label only if it is at least `padding` clear of every label
already shown. Boxes that merely touch do not overlap. The answer is one flag
per box, in input order.

Greedy is not optimal (it can show fewer labels than the best possible
choice), but it is predictable, fast and stable as data changes, and it
always keeps the labels you rank highest, such as the first and last tick or
the largest value. It does not move labels; to try alternative positions,
pass each candidate as a box with a lower priority.