Integration with the stack
- Data is computed in a renderer method (e.g.
func (rn *Renderer) scatter(…) (…)), returning view structs — never compute chart numbers in the template. - View struct carries the raw values (one struct per mark) plus whatever the builder
needs for scaling/color (e.g. a
YearIdxfor hue, a total series count). - Template calls
@templ.Raw(fooSVG(d.Data, …)). Wrap it in a.bgsurface div if you want a card around it. - Verify for real.
templ generate && go build ./... && go test ./...must stay green, and LOOK at the result: render the section through a throwaway preview harness test (the GEN_PREVIEW pattern in a specimen in the lab's Charts page) orgo run ./cmd/snap, and read the PNGs — the chart bugs worth catching are visual.
Interactive charts are BACKEND-DRIVEN
Filtering, toggling series, switching grouping, zooming a range — all server-side.
This is the load-bearing rule of the stack (the Tao of Datastar; their land-and-stream
architecture). Do not reach for client-side data-show / data-style / DOM mutation
to hide or recolor marks. Instead:
- The control (a
.tagbutton, a segmented toggle) carries the input UP as a signal on a debounced or click@post. - A command handler reads the signal (
datastar.ReadSignals), applies one mutation (toggle a year in/out, change the group key), builds an internal URL with the new state as query params, and re-renders the whole page through the normalStream → resolve → PatchElementspath. Browser URL never changes. - Signals that carry chart state are plain
data-signals(no__ifmissing) so every morph re-merges them server-authoritatively. Snapback on a bus-event morph is fine.
Legends double as filters. Render the toggle tags with a swatch colored by the same
--hue-shift index the marks use, via THE shared helper — hueShift(idx, n) /
hueStyle(idx, n) in view/chartrender.go, the ONLY pair that may exist (a second
copy WILL drift; one already did and was swept). A series' color is then identical
in the chart and its legend by construction.