implot

package
v0.0.21 Latest Latest
Warning

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

Go to latest
Published: Aug 21, 2026 License: MIT Imports: 16 Imported by: 0

Documentation

Overview

Package implot is a Go port of the core of ImPlot (https://github.com/epezent/implot, MIT, © 2020 Evan Pezent), re-targeted from Dear ImGui's draw list onto the imzero2 painter lane (ADR-0149).

The port preserves ImPlot's frame protocol and interaction semantics — Begin / Setup* / plot items / End ordering, drag pan, wheel zoom anchored at the pointer, double-click auto-fit, box-zoom — and re-idiomizes the API surface: Go slices instead of getter templates, methods on a *Plot instead of an implicit current-plot context, float64 plot space projected to float32 only at paint-command emission (SD4).

For straight-line plot bodies prefer the range-based scope over the loose pair — `for p := range implot.Scoped(...)` (and sp.Scoped inside Subplots cells) guarantees End on early exit, the same contract as the house c.IdScope; Begin/End remains for bodies where the handle must outlive a lexical block.

Coverage (M1–M7 per the ADR, plus the SD7 migration batch): linear/time/log10/symlog axes with the ported locators, grid, line/scatter/bars/shaded (constant or two-curve)/stairs/stems/infinite lines, error bars, heatmaps (rect and texture routes), histograms (1D/2D), pie, digital channels, images, inlay text, a letter-value box series, interactive legend, drag tools/annotations/tags, a native context menu, subplots and linked axes, caller-supplied axis ticks, viewport constraints, NoInputs/NoLegend, and plot-space click/hover readback. Same-label items share one legend entry and palette slot (the upstream label→item registry semantics), so error bars merge with the series they decorate.

Caller-drawn custom items (Custom / CustomUnclipped, custom.go) re-express upstream's custom-rendering idiom — GetPlotDrawList + PushPlotClipRect + PlotToPixels — as draw closures invoked during End, because under this port's deferred emission the frame transform does not exist earlier (see custom.go). Declaration order is z-order, as upstream's draw-list order is; labeled customs join the legend like any item.

Per-axis gesture locks (AxisFlagsNoPan / AxisFlagsNoZoom, and AxisFlagsLock for both) carry upstream's ImPlotAxisFlags_Lock, split so that an axis can be scrolled but not scaled — what a categorical or depth axis wants when its span is derived from the plot-area height to hold a constant pixels-per-unit. The locks apply per gesture kind, not to the resulting range: an anchored wheel zoom shifts an axis's centre as well as its span, so restoring only the span would let the wheel pan a NoZoom axis.

House extensions beyond the upstream surface, named as such in their doc comments: Boxes, IncludeX/IncludeY, TimeTicksLocal, AxisFlagsFollow, Clicked/HoverPlotPos, NewDetached, the pixel-space readbacks HoverPixelPos/ClickedPixelPos/PlotAreaPrev (hit-testing pixel-pinned custom geometry), and the two process-wide palette selectors — SetChrome for the frame around the data (IDS-token default, the port's original palette as ChromeClassic; chrome.go) and SetSeriesPalette for the data itself (IDS qualitative default, upstream's Deep as PaletteDeep; palette.go). The two are independent.

Tick labels are placed rather than simply drawn (ticklabels.go), which upstream does not do: a label band that does not fit first locates fewer ticks (for located axes, where a tick is a choice), then slides labels to the nearest free spot and stacks the band into extra rows, drawing a leader line from each moved label back to its tick, and only then drops every k-th label. Tick marks and grid lines are never dropped. A band whose labels already fit is untouched — same text, same positions, no leader lines — so this shows up only where the alternative was an unreadable smear (a category axis from SetupAxisTicks, typically).

Known deviations from upstream:

  • Box-zoom is Shift+drag (upstream: right-drag). The response-flag register does not yet distinguish which button a drag uses.
  • Setup* calls after the first item are ignored with a debug log (upstream asserts).
  • The y-axis label renders horizontally (no rotated-text command).
  • SymLog uses the asinh transform with the default locator on raw values; upstream's dedicated symlog locator is not yet ported.
  • Time axes label in UTC only and place major ticks only — upstream's minor time ticks and second-line context labels are deferred.
  • Digital channels use fixed bit-height/gap constants (there is no style system to override upstream's defaults).
  • Image takes caller-owned RGBA pixels plus a content version (the painter lane's ship-once texture protocol); upstream's GPU texture-id parameter has no equivalent on this substrate.
  • Error bars draw in a fixed foreground color and a fixed whisker width (upstream styles both).
  • Series cycle the IDS qualitative palette by default, not upstream's Deep colormap; SetSeriesPalette(PaletteDeep) restores it.

Interaction state is read one frame behind, like every imzero2 register (ADR-0140 wheel, R24 canvas pointer, R7 response flags) — imperceptible at interactive rates.

Derivative-work notice: the tick locator, fit and interaction logic are ported from implot.cpp; see LICENSE-implot.txt in this directory for the upstream license text carried per ADR-0149 SD8.

Index

Constants

View Source
const GlyphWidthRatio = 0.62

GlyphWidthRatio is the house estimate of a glyph's advance as a fraction of the font size, for the proportional UI font at Latin text. It is also close enough for the monospace face, whose digits this package's tick labels are.

Variables

View Source
var PackageProps = packageprops.Props{
	WASMWASI:         packageprops.WASMCompiles,
	WASMJS:           packageprops.WASMCompiles,
	WASMFreestanding: packageprops.WASMCompiles,
}

PackageProps records this package's curated properties (ADR-0080). Seeded to match its sibling widgets; curate, then reconcile with `boxer code analysis golang wasmsurvey props verify`.

Functions

func ContrastRatio

func ContrastRatio(a uint32, b uint32) float64

ContrastRatio is the WCAG contrast ratio between two colours, from 1 (the same luminance) to 21 (black against white). It is the number a threshold like "4.5:1 for body text" refers to.

func Elide

func Elide(s string, availPx float32, fontSize float32) string

Elide shortens s to fit availPx at fontSize, appending an ellipsis, or returns "" when not even the ellipsis and one glyph would fit — a one-glyph label is noise, not information.

It lives here, beside the estimate, because it is the same decision: the string it returns fits the space it was cut for only because it was budgeted with the estimate the caller sized the space with. Two widgets eliding by different rules is the disagreement this file exists to stop.

The budget is pixels rather than characters, which a character budget cannot approximate: it charges a CJK glyph the same as an "l", and those labels then overflow the box they were cut to fit.

Not every widget wants this — a bar chart that drops a label it cannot fit is making a different, equally reasonable call. This is for the ones that would rather show a prefix.

func EstimateRuneWidth

func EstimateRuneWidth(r rune, fontSize float32) float32

EstimateRuneWidth is EstimateTextWidth for one rune — the form an elision loop wants, so it can walk a string without building a candidate per step.

func EstimateTextWidth

func EstimateTextWidth(s string, fontSize float32) float32

EstimateTextWidth estimates the rendered width of s at fontSize, in the canvas pixels every Paint* opcode takes.

It counts runes, not bytes. Charging a multi-byte glyph once per byte overstates every non-ASCII label by its encoding length — a three-byte CJK glyph billed at 1.86 em against a true advance near 1.0 — which is how a widget ends up hiding labels that would have fit.

Kerning, ligatures and font fallback are not modelled, so treat the result as a budget rather than a measurement.

func MinBoxHeight

func MinBoxHeight(hasTitle bool, hasXLabel bool, hasYLabel bool, lanes int) (h float32)

MinBoxHeight reports the shortest box a plot with these label choices can draw inside without clipping its own x tick labels. lanes is the x label depth (1 unless the axis stacks; see maxBandLanes, which bounds stacking by a quarter of the canvas so a stacked axis cannot outgrow its box).

Size a pane-following plot with this as the floor. A floor chosen for READABILITY instead is a different and softer thing — it may sit well above this, but it must never sit below it, or the box clips the labels from the inside while the pane looks roomy enough.

func RelativeLuminance

func RelativeLuminance(rgba uint32) float64

RelativeLuminance is WCAG 2.x relative luminance of an 0xRRGGBBAA colour, in [0,1]: each channel is linearised out of the sRGB transfer function and then weighted by the Rec.709 primaries.

Alpha is ignored — a translucent fill composites against something this function cannot see, so the caller has to resolve that first if it matters.

The usual reason to want it is picking an ink: the contrast ratio between two colours is (L1+0.05)/(L2+0.05) with L1 the lighter, and the luminance at which two candidate inks contrast equally is sqrt((Ldark+0.05)*(Llight+0.05)) - 0.05.

func Scoped

func Scoped(ids *c.WidgetIdStack, title string, w float32, h float32) iter.Seq[*Plot]

Scoped opens a plot and yields it exactly once; End runs when the body finishes or breaks early, so the id scope always closes — the range-based counterpart to Begin/End, mirroring c.IdScope (including its deferred pop-on-panic discipline). Prefer it for straight-line plot bodies:

for p := range implot.Scoped(ids, "##rates", w, h) {
	p.SetupAxes("t", "MiB/s", implot.AxisFlagsNone, implot.AxisFlagsNone)
	p.Line("rate", xs, ys)
}

Begin/End remains for bodies where the handle must outlive a lexical block; an explicit End inside a Scoped body is harmless (End is idempotent).

func SetChrome

func SetChrome(chrome ChromeE)

SetChrome switches the chrome palette. Call it at application startup (or between frames); it reassigns package state and is not safe against a concurrently rendering plot. Selecting a chrome is process-wide — per-plot styling remains out of scope until a style system exists (doc.go).

func SetSeriesPalette

func SetSeriesPalette(palette PaletteE)

SetSeriesPalette switches the data-series palette. Same contract as SetChrome: process-wide, called at application startup (or between frames), not safe against a concurrently rendering plot. A per-series SetNextColor still overrides whichever palette is active.

func Subplots

func Subplots(ids *c.WidgetIdStack, title string, rows int, cols int, w float32, h float32, flags SubplotFlags, cell func(sp *SubplotCtx, row int, col int))

Subplots lays out rows×cols plot cells in a grid, owning the row scaffolding and the shared-axis storage. The callback runs once per cell in row-major order and must open exactly one plot — via sp.Scoped (preferred), or sp.Plot paired with End.

func TimeTicksLocal

func TimeTicksLocal(minUnix float64, maxUnix float64, widthPx float32) (values []float64, labels []string)

TimeTicksLocal computes calendar-aware tick positions and labels in the machine's local time for a Unix-seconds axis spanning [minUnix, maxUnix] at the given pixel width, shaped for SetupAxisTicks. It exists for the monitoring panels (imztop, imzrt), which read wall-clock history — a house extension, not upstream API: upstream's time locator (ScaleTime here) labels in UTC and knows no locale calendar.

Types

type AxisE

type AxisE uint8

AxisE selects an axis for SetupAxisLimits. M1 has one x and one y.

const (
	AxisX1 AxisE = iota
	AxisY1
)

type AxisFlags

type AxisFlags uint32

AxisFlags configures one axis at Setup time.

const (
	AxisFlagsNone AxisFlags = 0
	// AxisFlagsAutoFit refits the axis to the frame's data every frame.
	AxisFlagsAutoFit AxisFlags = 1 << 0
	// AxisFlagsNoGrid suppresses the grid lines for this axis.
	AxisFlagsNoGrid AxisFlags = 1 << 1
	// AxisFlagsNoTickLabels suppresses the tick labels (marks remain).
	AxisFlagsNoTickLabels AxisFlags = 1 << 2
	// AxisFlagsFollow keeps refitting the axis to the data until the user
	// pans or zooms it; a double-click (or context-menu) fit resumes
	// following. The egui_plot auto-bounds model, carried over for the
	// monitoring panels whose data is a rolling window — not an upstream
	// ImPlot flag.
	AxisFlagsFollow AxisFlags = 1 << 3
	// AxisFlagsNoPan stops gestures translating this axis: a drag moves
	// only the other one. The axis still zooms, about its own centre.
	AxisFlagsNoPan AxisFlags = 1 << 4
	// AxisFlagsNoZoom stops gestures scaling this axis: the wheel, box-zoom
	// and a double-click fit leave its span alone, so a caller that derives
	// the span from the plot-area height keeps a constant pixels-per-unit.
	// Panning still slides the window, which is how a depth axis scrolls.
	AxisFlagsNoZoom AxisFlags = 1 << 5
	// AxisFlagsLock is NoPan|NoZoom: the range is the caller's alone, and
	// no gesture changes it — upstream's ImPlotAxisFlags_Lock, which locks
	// both ends together.
	AxisFlagsLock = AxisFlagsNoPan | AxisFlagsNoZoom
)

type ChromeE

type ChromeE uint8

ChromeE selects the plot chrome palette: backgrounds, grid, border, axis/legend/readout text, the box-zoom rect, error-bar whiskers and the contrast-text pair. It does NOT touch the data-series palette — that has its own selector (SetSeriesPalette, palette.go); series colors and their legend swatches are identical under both chromes.

const (
	// ChromeIDS derives the chrome from the IDS semantic palette
	// (styletokens, ADR-0031 neutral spine + accent role). The default.
	ChromeIDS ChromeE = iota
	// ChromeClassic is the hand-matched dark palette the port shipped
	// with (the M1–SD7 captures), kept selectable for continuity. Its
	// values are visually close to the IDS spine but predate it.
	ChromeClassic
)

type Cond

type Cond uint8

Cond controls when SetupAxisLimits applies, mirroring ImPlot's ImPlotCond.

const (
	// CondOnce applies the limits only the first time this plot id is seen.
	CondOnce Cond = iota
	// CondAlways applies the limits every frame (the axis is then not
	// user-navigable).
	CondAlways
)

type DrawCtx

type DrawCtx struct {
	// T converts plot space ↔ canvas pixels for this frame.
	T Transform
	// AreaX/AreaY/AreaW/AreaH is the plot-area rect: the region inside the
	// axes, which clipped closures are confined to. Pixel-pinned geometry
	// (lane rows, bottom-pinned strips — the Digital-item pattern) anchors
	// on it.
	AreaX, AreaY, AreaW, AreaH float32
	// W, H is the full canvas size, gutters included — the bound for
	// CustomUnclipped drawing.
	W, H float32
	// Color is the item's resolved color: its palette slot by label, or the
	// SetNextColor override. Weight is the resolved stroke weight — the
	// SetNextWeight override or the series default, with the legend-hover
	// emphasis (×2) already applied, so drawing with it echoes the
	// built-in series highlight for free.
	Color  uint32
	Weight float32
	// Highlighted reports that the item's legend row is hovered, so custom
	// items can echo the built-in series hover emphasis.
	Highlighted bool
}

DrawCtx is what a Custom closure receives: this frame's transform, the plot-area rect and full canvas size (canvas pixels — the space every Paint* opcode emitted by the closure uses), and the item's resolved style. Valid only during the call; do not retain it.

type MarkerE

type MarkerE uint8

MarkerE selects a scatter glyph; the numbering is the paintMarkers wire contract (= ImPlot's marker numbering).

const (
	MarkerCircle MarkerE = iota
	MarkerSquare
	MarkerDiamond
	MarkerUp
	MarkerDown
	MarkerLeft
	MarkerRight
	MarkerCross
	MarkerPlus
	MarkerAsterisk
)

type PaletteE

type PaletteE uint8

PaletteE selects the data-series palette: the color a series takes from its slot, and the swatch its legend row draws. Independent of the chrome palette (chrome.go) — chrome is the frame around the data, this is the data.

The two differ in length: PaletteIDS cycles seven colors, PaletteDeep ten. A slot therefore does *not* map to the same position under both, and the table is sized per palette rather than fixed at ten — sizing it at ten and filling it from a seven-cycle would make implot's slot 7 silently alias slot 0 while styletokens.QualitativeCycle(7) did the same thing one wrap earlier, so the two would disagree past slot 9.

const (
	// PaletteIDS cycles the IDS qualitative data-encoding palette
	// (styletokens.QualitativeCycle — Okabe-Ito, ADR-0031 §SD7 / ADR-0156).
	// The default: series identity is a data encoding, and the fleet reads
	// its qualitative colors from one source.
	PaletteIDS PaletteE = iota
	// PaletteDeep is ImPlot's default colormap ("Deep", seaborn's deep 10),
	// the palette the port shipped with. Kept selectable for continuity
	// with upstream captures, as ChromeClassic is for the chrome.
	PaletteDeep
)

type Plot

type Plot struct {
	// contains filtered or unexported fields
}

Plot is the frame-transient handle between Begin and End. Methods follow ImPlot's protocol: Setup* first, then items; the first item locks setup.

func Begin

func Begin(ids *c.WidgetIdStack, title string, w float32, h float32) *Plot

Begin opens a plot with the given title (which is also its identity, as in ImPlot — the "##" convention applies: everything from "##" on is identity only and does not render, so "##rates" shows no title bar) and canvas size in pixels. Interactions from the previous frame are applied to the retained ranges here, before any Setup call. Every Begin must be paired with End.

func NewDetached

func NewDetached() *Plot

NewDetached returns a plot handle bound to no canvas and no frame, for headless tests of widgets that declare into a *Plot: items accumulate and fit extents compute, nothing renders. End must not be called on it (there is no id stack to close).

func (*Plot) Annotation

func (p *Plot) Annotation(x float64, y float64, dxPx float32, dyPx float32, colHex uint32, clamp bool, text string) *Plot

Annotation declares a text callout at the plot point, offset by pixels; with clamp the box stays inside the plot area even when the point pans out (ImPlot's annotation contract).

func (*Plot) AxisLimits

func (p *Plot) AxisLimits(axis AxisE) (vmin float64, vmax float64, ok bool)

AxisLimits reports an axis's current visible range — upstream's GetPlotLimits, narrowed to one axis. It is the readback a caller needs to re-pin a range it derives from the plot area (a depth axis whose span is the area height over a fixed row height) without discarding the scroll position a pan has since put there.

The range is this frame's if Setup has already run, and last frame's otherwise; ok is false until the plot has resolved a range once.

func (*Plot) AxisRangePrev

func (p *Plot) AxisRangePrev(axis AxisE) (vmin float64, vmax float64, ok bool)

AxisRangePrev returns the axis range the plot held entering this frame — what the last gesture (pan, wheel, box-zoom) left behind, before this frame's Setup calls and autofit revise it. It is the range counterpart of PlotAreaPrev, and carries the same one-frame lag for the same reason: a caller that must decide WHAT to declare needs the viewport before End has laid this frame out.

The use it exists for is viewport-aware decimation — a caller holding more samples than the axis has pixels reduces to what the range can show, which it cannot do without knowing the range. ok is false until the plot has rendered once (declare the full series on that first frame) and for a degenerate range, so a caller never divides by a zero span.

func (*Plot) Bars

func (p *Plot) Bars(label string, xs []float64, ys []float64, width float64) *Plot

Bars declares vertical bars centered on xs with heights ys and the given bar width in plot units (one paintRectsFilled opcode per series). Bars are drawn from y=0 to ys[i], like ImPlot's default.

func (*Plot) Boxes

func (p *Plot) Boxes(label string, args []float64, q1s []float64, medians []float64, q3s []float64, wmins []float64, wmaxs []float64, widths []float64, fills []uint32, strokeHex uint32, strokeW float32) *Plot

Boxes declares a box / letter-value series: box i centers on args[i], its body spans q1s[i]..q3s[i] with a median line at medians[i], whiskers extend to wmins[i]/wmaxs[i] where those reach beyond the body, the box is widths[i] wide in plot units and filled with fills[i] (0xRRGGBBAA). One stroke color/width serves the series. A house extension: upstream ImPlot has no box item, and the letter-value widgets need per-box fills.

func (*Plot) Clicked

func (p *Plot) Clicked() (x float64, y float64, ok bool)

Clicked reports a primary click on the plot area, with the click position in plot space — one frame behind, like every register read. Serves nearest-point selection on scatter clouds.

func (*Plot) ClickedPixelPos

func (p *Plot) ClickedPixelPos() (px float32, py float32, ok bool)

ClickedPixelPos reports a primary click on the plot area in canvas pixels — the pixel-space complement of Clicked. One frame behind.

func (*Plot) Custom

func (p *Plot) Custom(label string, fn func(DrawCtx)) *Plot

Custom records a caller-drawn item: fn runs during End, after auto-fit has resolved and the frame transform exists, clipped to the plot area. Items emit in declaration order, so declaring a Custom before a series draws under it and after draws over it — upstream's call-order z-model, preserved across the deferred emission.

A labeled Custom participates like any item: it takes a palette slot, gets a legend row with a visibility toggle (a hidden item's closure is not invoked), merges with same-label items, and reports legend hover via DrawCtx.Highlighted. label "" is anonymous: no legend row, own palette slot. SetNextColor / SetNextWeight apply and arrive as DrawCtx.Color / DrawCtx.Weight.

The closure must only paint (and read caller state): declaring items, Setup* calls, or sense regions from inside it is unsupported — item declarations during emission are debug-logged no-ops, and sense-region emission order is the plot's hit-test priority contract. Custom items do not contribute to auto-fit; declare their extent with IncludeX / IncludeY. On a detached plot (NewDetached) the closure never runs.

Validation: nil fn is a no-op.

func (*Plot) CustomUnclipped

func (p *Plot) CustomUnclipped(label string, fn func(DrawCtx)) *Plot

CustomUnclipped is Custom without the plot-area clip: the closure may paint anywhere on the plot's canvas (0,0 .. W,H), e.g. callouts that spill past the area border, or decorations in the gutters. The gutters are laid out for the axes, not reserved for custom content — drawing into them shares space with tick labels and titles (a gutter-reservation knob is deferred until the timeline adoption proves the need; see the ADR-0149 update).

Validation: nil fn is a no-op.

func (*Plot) Digital

func (p *Plot) Digital(label string, xs []float64, ys []float64) *Plot

Digital declares a digital channel: y > 0 is high (the value scales the bit height, so 0/1 data reads as a logic trace), rendered as filled runs pinned to the bottom of the plot area in pixel space — digital channels never scale or pan with the y axis, per upstream. Visible digital series stack upward in declaration order. Contributes only x to auto-fit.

func (*Plot) DragLineX

func (p *Plot) DragLineX(key string, x *float64, colHex uint32) (dragged bool)

DragLineX declares a draggable vertical line at *x. While the user drags its grab strip the value updates through the pointer (one-frame lag, like every gesture); the return reports that a drag moved it this frame. The pointer must be stable across frames (the usual heap-pointer rule). The tool's sense region sits above the plot-area region, so dragging a line never pans the plot.

func (*Plot) DragLineY

func (p *Plot) DragLineY(key string, y *float64, colHex uint32) (dragged bool)

DragLineY declares a draggable horizontal line at *y.

func (*Plot) DragPoint

func (p *Plot) DragPoint(key string, x *float64, y *float64, colHex uint32) (dragged bool)

DragPoint declares a draggable point at (*x, *y).

func (*Plot) End

func (p *Plot) End()

End resolves fits, lays the plot out, emits every paint command and the canvas, stores this frame's transform for next frame's gestures, and closes the id scope opened by Begin. Idempotent: a second call is a debug-logged no-op, so an explicit End inside a Scoped body cannot double-pop the id scope.

func (*Plot) ErrorBars

func (p *Plot) ErrorBars(label string, xs []float64, ys []float64, neg []float64, pos []float64) *Plot

ErrorBars declares vertical error whiskers about (xs, ys): bar i spans ys[i]-neg[i] to ys[i]+pos[i]. Pass the same slice twice for symmetric errors. Reusing the label of the series the bars decorate merges them into that series' legend entry and visibility toggle; the whiskers themselves draw in a fixed foreground color, as upstream's error-bar style color does.

func (*Plot) ErrorBarsH

func (p *Plot) ErrorBarsH(label string, xs []float64, ys []float64, neg []float64, pos []float64) *Plot

ErrorBarsH declares horizontal error whiskers: bar i spans xs[i]-neg[i] to xs[i]+pos[i] at height ys[i].

func (*Plot) FitNext

func (p *Plot) FitNext() *Plot

FitNext requests a data re-fit of both axes this frame — the programmatic double-click fit (upstream's SetNextAxesToFit, applied to the already-open plot). Drive it from a one-frame flag, e.g. a "Reset zoom" button clicked last frame.

func (*Plot) Heatmap

func (p *Plot) Heatmap(label string, values []float64, rows int, cols int, cm *colormap.Config, x0 float64, y0 float64, x1 float64, y1 float64) *Plot

Heatmap declares a rows×cols grid of values, row-major with row 0 at the TOP edge (y1), colorized through the colormap config — the integration point with the house `colormap` widget (its palettes, range and log normalization apply as-is). Small grids draw as a rect batch; grids past heatmapRectCells route through a cached texture per SD5, re-shipping pixels only when the content signature changes or the host reports the texture starved.

func (*Plot) Histogram

func (p *Plot) Histogram(label string, samples []float64, bins int, density bool) *Plot

Histogram bins the samples (Sturges' rule when bins <= 0) and declares the result as a bar series over the sample range. With density the bar heights integrate to one.

func (*Plot) Histogram2D

func (p *Plot) Histogram2D(label string, xs []float64, ys []float64, xBins int, yBins int, cm *colormap.Config) *Plot

Histogram2D bins (xs, ys) pairs into an xBins×yBins grid and declares it as a heatmap over the data extent, colorized by count.

func (*Plot) HoverPixelPos

func (p *Plot) HoverPixelPos() (px float32, py float32, ok bool)

HoverPixelPos returns the pointer position in canvas pixels while it is over the plot area — the pixel-space complement of HoverPlotPos, for hit-testing pixel-pinned custom geometry (lane rows). One frame behind, like every register read; ok is false when the pointer is elsewhere or the plot has not rendered yet.

func (*Plot) HoverPlotPos

func (p *Plot) HoverPlotPos() (x float64, y float64, ok bool)

HoverPlotPos returns the pointer's plot-space position while it is over the plot area (one-frame lag). ok is false when the pointer is elsewhere or the plot has not rendered yet.

func (*Plot) Image

func (p *Plot) Image(label string, pix []uint32, rows int, cols int, x0 float64, y0 float64, x1 float64, y1 float64, version uint64) *Plot

Image declares a rows×cols RGBA (0xRRGGBBAA, row 0 at the TOP edge y1 — Heatmap's orientation contract) texture drawn across the plot-space rect (x0,y0)-(x1,y1). The caller owns the pixel buffer and bumps version when its content changes; an unchanged version ships no pixels and reuses the GPU-resident texture (the mapRaster ship-once protocol — the substrate has no upstream-style GPU texture handles, see doc.go).

func (*Plot) IncludeX

func (p *Plot) IncludeX(v float64) *Plot

IncludeX extends auto-fit to cover v on the x axis (the fit itself still only applies on fitting frames: first show, AutoFit, or a double-click / context-menu fit).

func (*Plot) IncludeY

func (p *Plot) IncludeY(v float64) *Plot

IncludeY extends auto-fit to cover v on the y axis.

func (*Plot) InfLinesH

func (p *Plot) InfLinesH(label string, ys []float64) *Plot

InfLinesH declares horizontal reference lines at ys, spanning the plot width. Contributes only y to auto-fit.

func (*Plot) InfLinesV

func (p *Plot) InfLinesV(label string, xs []float64) *Plot

InfLinesV declares vertical reference lines at xs, spanning the plot height. Contributes only x to auto-fit, per ImPlot.

func (*Plot) Line

func (p *Plot) Line(label string, xs []float64, ys []float64) *Plot

Line declares a line series. The slices must be equal-length; NaN points split the line, as in ImPlot. Data is not copied — the slices must stay valid until End.

func (*Plot) NoInputs

func (p *Plot) NoInputs() *Plot

NoInputs disables every interaction surface of this plot for the frame — upstream's ImPlotFlags_NoInputs: no pan/zoom/box-zoom/fit gestures, no wheel capture (the wheel scrolls the surrounding pane instead), no clickable legend rows. For sparklines and other inert thumbnails. Callable any time before End.

func (*Plot) NoLegend

func (p *Plot) NoLegend() *Plot

NoLegend suppresses the legend for the frame even when labeled series exist — upstream's ImPlotFlags_NoLegend.

func (*Plot) Pie

func (p *Plot) Pie(labels []string, values []float64, x float64, y float64, radius float64, angle0Deg float64, labelFmt string) *Plot

Pie declares a pie chart at center (x, y) with the given radius, all in plot units — like upstream, the disc goes elliptical when the two axes' pixel densities differ. Each slice is its own legend entry (click to hide; a hidden slice keeps its angular span). Slices start at angle0 (degrees, plot-space CCW from +x; 90 = top) and advance counter- clockwise. Values are normalized to a full circle when they sum past 1; a smaller sum leaves the pie partial, per upstream's auto-normalize. labelFmt ("" = none) formats each slice's value at the slice centroid.

func (*Plot) PlotAreaPrev

func (p *Plot) PlotAreaPrev() (x float32, y float32, w float32, h float32, ok bool)

PlotAreaPrev returns last frame's plot-area rect in canvas pixels. It is the declaration-time counterpart of DrawCtx's area fields: hit tests against pixel-pinned custom geometry run before End has laid this frame out, so they test against the previous frame's rect — the same one-frame lag as every readback, imperceptible at interactive rates. ok is false until the plot has rendered once.

func (*Plot) Scatter

func (p *Plot) Scatter(label string, xs []float64, ys []float64, marker MarkerE, radius float32) *Plot

Scatter declares a marker series (one paintMarkers opcode per series).

func (*Plot) SetNextColor

func (p *Plot) SetNextColor(colHex uint32) *Plot

SetNextColor overrides the next declared item's series color (the color half of upstream's SetNextLineStyle). It applies to the immediately following item declaration only; the legend swatch follows the override.

func (*Plot) SetNextWeight

func (p *Plot) SetNextWeight(weight float32) *Plot

SetNextWeight overrides the next declared item's stroke weight (the weight half of upstream's SetNextLineStyle).

func (*Plot) SetupAxes

func (p *Plot) SetupAxes(xlabel string, ylabel string, xflags AxisFlags, yflags AxisFlags) *Plot

SetupAxes names the axes and sets their flags. Must precede the first item call, per the ImPlot protocol.

func (*Plot) SetupAxisLimits

func (p *Plot) SetupAxisLimits(axis AxisE, vmin float64, vmax float64, cond Cond) *Plot

SetupAxisLimits sets an axis range. CondOnce applies only the first time this plot id is seen; CondAlways pins the axis every frame.

func (*Plot) SetupAxisLimitsConstraints

func (p *Plot) SetupAxisLimitsConstraints(axis AxisE, vmin float64, vmax float64) *Plot

SetupAxisLimitsConstraints clamps the axis's visible range inside [vmin, vmax] — upstream's SetupAxisLimitsConstraints. Pan and zoom cannot escape the constraint; a viewport wider than it is pulled in. Like all Setup state it is re-declared every frame.

func (p *Plot) SetupAxisLinks(axis AxisE, vmin *float64, vmax *float64) *Plot

SetupAxisLinks links this axis to caller-held min/max values — the upstream contract verbatim: if the values changed since this plot last wrote them (another linked plot moved the axis), the plot adopts them; otherwise this plot's own gestures win, and End writes the resulting range back through the pointers. Plots rendered later in the same frame see an earlier plot's move immediately; earlier ones catch up next frame. The pointers must be stable across frames.

func (*Plot) SetupAxisScale

func (p *Plot) SetupAxisScale(axis AxisE, scale ScaleE) *Plot

SetupAxisScale selects the axis scale (linear, time, log10, symlog). Like every Setup call it must precede the first item — and, like every Setup call, it holds for THIS frame only: an axis whose scale is not re-declared is linear again next frame. A caller toggling a log axis therefore states the choice, not the change.

func (*Plot) SetupAxisTicks

func (p *Plot) SetupAxisTicks(axis AxisE, values []float64, labels []string) *Plot

SetupAxisTicks replaces the axis's located ticks with caller-supplied major ticks (upstream's SetupAxisTicks): values in plot space with their labels, re-declared every frame like all Setup state. Ticks outside the visible range are dropped at render. An empty values slice restores the default locator.

func (*Plot) Shaded

func (p *Plot) Shaded(label string, xs []float64, ys []float64, yref float64) *Plot

Shaded declares a filled region between the curve and yref (per-segment convex quads — a whole-polygon fill would be concave).

func (*Plot) ShadedBetween

func (p *Plot) ShadedBetween(label string, xs []float64, ys1 []float64, ys2 []float64) *Plot

ShadedBetween declares a filled region between two curves sharing xs — upstream's two-curve PlotShaded. Each segment renders as one quad; segments where the curves cross render the crossing quad as-is, like upstream. With SetupNextColor the fill uses the given color verbatim (bring your own alpha); the palette default gets a soft alpha.

func (*Plot) Stairs

func (p *Plot) Stairs(label string, xs []float64, ys []float64) *Plot

Stairs declares a step-after series (one polyline opcode).

func (*Plot) Stems

func (p *Plot) Stems(label string, xs []float64, ys []float64, yref float64) *Plot

Stems declares vertical stems from yref to ys with a circle head.

func (*Plot) TagX

func (p *Plot) TagX(x float64, colHex uint32) *Plot

TagX declares a colored value tag on the x axis at x; TagY on the y axis.

func (*Plot) TagY

func (p *Plot) TagY(y float64, colHex uint32) *Plot

func (*Plot) Text

func (p *Plot) Text(x float64, y float64, colHex uint32, text string) *Plot

Text declares an inlay text annotation centered at the plot point — upstream's PlotText. No legend entry; the point contributes to auto-fit like upstream.

type Range

type Range struct {
	Min float64
	Max float64
}

Range is a closed interval in plot space. Plot space is float64 throughout (ADR-0149 SD4); pixels appear only at emission.

func (Range) Clamp

func (r Range) Clamp(v float64) float64

Clamp returns v limited to the range.

func (Range) Contains

func (r Range) Contains(v float64) bool

func (Range) Size

func (r Range) Size() float64

type ScaleE

type ScaleE uint8

ScaleE selects an axis scale, mirroring ImPlotScale. Time is a linear transform with the time locator/formatter; Log10 and SymLog change the value↔pixel mapping itself.

const (
	ScaleLinear ScaleE = iota
	// ScaleTime treats values as Unix seconds (UTC labels, ImPlot's
	// default; a UseLocalTime knob can follow when needed).
	ScaleTime
	ScaleLog10
	// ScaleSymLog is ImPlot's asinh-based symmetric log: linear near
	// zero, logarithmic in both tails.
	ScaleSymLog
)

type SubplotCtx

type SubplotCtx struct {
	// contains filtered or unexported fields
}

SubplotCtx hands each cell its pre-linked plot. Re-idiomized from upstream's implicit BeginSubplots/EndSubplots cursor: Go gets an explicit per-cell callback (doc.go records the deviation).

func (*SubplotCtx) Plot

func (sp *SubplotCtx) Plot(title string) *Plot

Plot opens this cell's plot (the callback must End it) with the grid's links applied per the flags.

func (*SubplotCtx) Scoped

func (sp *SubplotCtx) Scoped(title string) iter.Seq[*Plot]

Scoped opens this cell's plot and yields it exactly once; End runs when the body finishes or breaks early — the range-based counterpart to Plot, mirroring the package-level Scoped.

type SubplotFlags

type SubplotFlags uint32

SubplotFlags selects which axes a Subplots grid shares.

const (
	SubplotFlagsNone SubplotFlags = 0
	// SubplotFlagsLinkAllX links the x axis across every cell.
	SubplotFlagsLinkAllX SubplotFlags = 1 << 0
	// SubplotFlagsLinkAllY links the y axis across every cell.
	SubplotFlagsLinkAllY SubplotFlags = 1 << 1
	// SubplotFlagsLinkRows links the y axis within each row.
	SubplotFlagsLinkRows SubplotFlags = 1 << 2
	// SubplotFlagsLinkCols links the x axis within each column.
	SubplotFlagsLinkCols SubplotFlags = 1 << 3
)

type Transform

type Transform struct {
	// contains filtered or unexported fields
}

Transform maps plot space to canvas pixels through the axis scales — the axis-separated re-idiomization of upstream's PlotToPixels / PixelsToPlot pair. Obtain it from a DrawCtx; it is valid only for the frame (and the plot) it was handed on. The zero value is unusable.

func (Transform) PlotX

func (tr Transform) PlotX(px float32) float64

PlotX inverts a canvas-pixel x to plot space, through the axis scale's inverse (correct on log/symlog, not just linear).

func (Transform) PlotY

func (tr Transform) PlotY(px float32) float64

PlotY inverts a canvas-pixel y to plot space.

func (Transform) PxX

func (tr Transform) PxX(v float64) float32

PxX projects a plot-space x to a canvas-pixel x.

func (Transform) PxY

func (tr Transform) PxY(v float64) float32

PxY projects a plot-space y to a canvas-pixel y (plot-space up is pixel-space down).

Jump to

Keyboard shortcuts

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