Documentation
¶
Overview ¶
Package fynefigure draws figure charts into a Fyne canvas object.
t := fynefigure.New() live, err := p.Live(t) // ... live.Draw(); t.Present() ... container.NewStack(t.Object())
It is an ir.Target like any other, and what it adds is that the pixels end up in a Fyne widget tree. The package next door, fynefigure/chart, is what joins one to a *figure.Plot and turns Fyne's mouse events into hovers, pans and zooms, and fynefigure/orbit does the same for a *three.Plot, turning its cameras — a backend must not know what a scale or a panel is, so steering is a separate package for the same reason figure keeps backend/window and backend/window/show apart.
Why a raster and not canvas objects ¶
Fyne's canvas package draws lines, rectangles, circles, regular polygons, arcs, text and images. It has no path API: no Bézier segments, no clipping, no affine transform, no stroke joins or dashes, no rotated text. Its own rasterizer is under internal/ and cannot be imported. So an ir.Backend built on Fyne primitives could not draw a smoothed line, a polar chart, a clipped panel or a rotated axis label at all.
The alternative — a second rasterizer of figure's own, drawing into an image — is the thing figure's ADR 0021 rejects: the day it disagrees with the first is the day a chart looks different on screen than in the file it exports. So this package does what the native window does. It rasterizes with backend/gg, which is figure's one rasterizer, and presents the result. A chart in a Fyne widget is the same pixels as the PNG beside it, and text is measured with the face it is drawn with rather than approximated.
Index ¶
- type Frame
- type Option
- type Ref
- type Selection
- type Target
- func (t *Target) Close() error
- func (t *Target) Frames() uint64
- func (t *Target) Image() image.Image
- func (t *Target) Input()
- func (t *Target) Object() fyne.CanvasObject
- func (t *Target) OnFrame(fn func(Frame))
- func (t *Target) OnGeometry(fn func(widthPx, heightPx int))
- func (t *Target) Open(s ir.Surface) (ir.Backend, error)
- func (t *Target) Present()
- func (t *Target) Render(fn func() error) error
- func (t *Target) SetFont(regular, bold, italic []byte) error
- func (t *Target) Size() (w, h int, dpr float64)
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Frame ¶
type Frame struct {
// At is when it finished.
At time.Time
// Cost is how long it held the surface: the pointer handling, hit test and
// layout the call did as well as the rasterizing, because that is the time
// the chart was not available for the next event.
Cost time.Duration
// Painted is whether it put a new frame in the surface. Most pointer moves
// over a chart nobody is zooming do not: figure paints nothing for a frame
// identical to the last.
Painted bool
// Latency is how long after the pointer event it answers the frame was
// finished, pacing included — zero for a frame that answers none. See
// [Target.Input].
Latency time.Duration
// W and H are the frame's logical size and DPR the ratio its buffer is
// scaled by. They are set only on a painted frame.
W, H int
DPR float64
}
Frame is what one call through Target.Render cost.
type Option ¶
type Option func(*config)
Option configures a Target.
func DamageBudget ¶
DamageBudget turns partial repaints back on, for frames whose damaged region covers no more than f of the surface. Zero, the default, repaints the whole frame every time.
figure works out where a frame changed and offers the rasterizer the chance to repaint only that. It sounds like a saving and here it is not: the rasterizer clears the damaged box and clips every drawing call to it, and its clip is a mask it rasterizes across the surface and then samples per pixel — on top of the clip a panel already puts there. Measured on a 900x480 chart, per frame:
whole frame partial a stream sliding left 29 ms 180 ms a stream growing at its right-hand tip 30 ms 170 ms panning a static chart 50 ms 50 ms
Six times slower where it does anything, and a wash where it does not. So the default is off, and this is here for a chart whose changes really are confined to a corner of it, and for the day the rasterizer's clip gets cheaper. There is a benchmark; measure before turning it on.
func Font ¶
Font replaces the rasterizer's embedded Go fonts with supplied TrueType or OpenType files. Pass bold or italic as nil to reuse regular for that style.
It is how a chart is drawn in the application's own typeface: package fynefigure/chart reads the faces off the Fyne theme and passes them here. Without it a chart uses the same fonts every other figure raster does, which is what makes its pixels comparable with an exported PNG.
func ScaleMode ¶
func ScaleMode(m canvas.ImageScale) Option
ScaleMode sets how Fyne resamples the chart if it ever has to.
It normally does not: the raster is generated at exactly the pixel size the painter asks for. The exception is the one frame after a display's device pixel ratio changes, where the previous frame is stretched while the next is rasterized at the new ratio. The default is canvas.ImageScaleSmooth.
type Ref ¶
type Ref struct {
// Key is the row's identity, or "" for a layer that names no key column.
Key string
// View is the panel or view the reader picked in, or -1 when it came from
// somewhere that was not a pointer.
View int
// Layer is which layer of it, and Row the source row in the table that
// layer was given. Row is -1 when the hit reported none, which is what an
// index that was not tracking rows reports.
Layer, Row int
}
Ref is one row a reader picked: which row, and what it is called.
Two of them name the same row when both carry a non-empty Key and the keys are equal, and otherwise when their layer and row agree. The key is what crosses a table — github.com/timzifer/figure/geom.KeyBy names the column that identifies a row, so two charts drawn from two tables can agree about one measurement — and the layer and row are the fallback for a layer that names no key, where the only honest claim is about one table.
View is deliberately not part of identity. A figure with four cameras is one chart looked at four ways, so a row picked in the plan is the same row in the three-quarter view; View records where the reader was pointing when they picked it, which is worth keeping and is not what makes it that row.
type Selection ¶
type Selection []Ref
Selection is the set of rows a reader has picked, in the order they picked them.
It is a slice rather than a map because it is small, ordered and compared far more often than it is searched: a widget asks "is this the same selection as last frame" on every pointer move and "is this row in it" once per row it draws. The order is worth keeping — the first row picked is the one a caller showing a single reading shows.
The zero Selection is empty and usable.
func (Selection) Add ¶
Add returns the selection with a row added, or unchanged when it is already there. It does not modify the receiver, so a handler may keep the value it was handed.
func (Selection) Clone ¶
Clone returns a copy, for a caller keeping a selection past the call it arrived in.
func (Selection) Equal ¶
Equal reports whether two selections name the same rows in the same order.
It is what a widget asks before telling anybody that the selection changed, so that a click landing on the row that was already picked is not an event — and, with the no-echo rule the two widgets follow, what keeps two linked charts from telling each other about a selection for ever.
type Target ¶
type Target struct {
// contains filtered or unexported fields
}
Target is a render destination that draws into memory and shows the result in a Fyne canvas object.
It is ggbackend.Surface with a Fyne front end. The surface keeps its pixels, so the backend it opens implements ir.Resizer and ir.Partial: a resized chart is repainted in place, and a live one repaints only where it changed. What this type adds is Target.Object, the object to put in a widget tree, and Target.Present, which shows the frame that was just drawn.
A Target draws one chart at a time and is not safe for concurrent use.
func New ¶
New returns a target that has not been opened yet. Nothing is rasterized until a chart is drawn into it.
func (*Target) Close ¶
Close releases the pixel buffer. The frame is not available afterwards.
Plot.Render closes the target it was given, so rendering a chart once into a Target leaves nothing to show — a chart in a widget is drawn through Plot.Live, which keeps its target open and redraws into it frame after frame. That is the difference between a document and a surface, and it is why this type is one.
func (*Target) Frames ¶
Frames reports how many frames have been painted into the surface.
It is not a frame number: figure paints nothing for a frame identical to the one before it, so this counts the frames that changed something. It is what a test asserts on, and what tells a caller whether the last draw did any work.
func (*Target) Image ¶
Image returns the pixels of the last frame, or nil before the first one.
The image is the rasterizer's own buffer, so it is valid until the next frame overwrites it. A caller keeping one past that must copy it. It is what a test compares and what an export of exactly what is on screen would read.
func (*Target) Input ¶
func (t *Target) Input()
Input marks that a pointer event has arrived for the chart drawn here.
The next frame painted after it reports in Frame.Latency how long after the event it was finished: the wait a paced widget imposed on it as well as the drawing, which is the roundtrip a reader feels. A call through Render that handles the event and paints nothing answers it too, with no frame to report. Only the first event of a run is kept — the frame that answers it answers every event that arrived after it.
It must not be called from inside Render. It costs nothing when no OnFrame callback is registered, beyond the lock.
func (*Target) Object ¶
func (t *Target) Object() fyne.CanvasObject
Object returns the canvas object the chart is shown in.
It is a canvas.Raster, so Fyne asks it for an image at the size it is about to paint, in *physical* pixels — which is the size the chart should be rasterized at, device pixel ratio included. The generator never draws: it hands back the frame that is already there. When the painter asks for a size the chart was not rasterized at — the frame after a window moves to a display with a different ratio — the previous frame is stretched for that one paint and the size is reported through Target.OnGeometry, so that whoever owns the chart can rasterize the next one correctly.
func (*Target) OnFrame ¶
OnFrame registers a callback for every call through Target.Render, painted or not. It is how a caller measures what a chart costs to draw without wrapping every path that draws it.
It is called with the surface held, on whichever goroutine drew — Fyne's, for a widget — so a handler must only record: calling back into the target is a deadlock, and anything slow is time every frame pays. Pass nil to stop.
func (*Target) OnGeometry ¶
OnGeometry registers a callback for the pixel size Fyne is painting at, when that is not the size the current frame was rasterized at.
It is called from the painter, which is the goroutine Fyne draws on, so a handler must not draw a chart from it — it should ask for one, and let the redraw happen in its turn.
func (*Target) Open ¶
Open prepares the target for a chart of the given size. It is ir.Target's half of the contract and is called by figure, not by a caller.
func (*Target) Present ¶
func (t *Target) Present()
Present shows the frame that was last drawn.
It is what to call after Live.Draw, and it is cheap when nothing changed: Live paints nothing when a frame is identical to the last, and a frame nobody painted is not handed to Fyne. So a pointer moving over a chart that is not being zoomed costs a comparison of two integers rather than a texture upload.
The frame Fyne is handed comes out of the rasterizer's buffer, and the painter reads it while it uploads the texture. So the surface is held whenever it is drawn into or read from — see Target.Render — rather than resting on the drawing and the painting being the same goroutine, which they are on a desktop driver and are not under Fyne's test one.
func (*Target) Render ¶
Render runs fn with the surface held, and is how a frame is drawn.
Everything that draws into the surface goes through here, so that a frame cannot be painted while Fyne is reading the last one. fn is whatever rasterizes — Live.Draw, Live.Resize, Live.Rescale — and its error is returned unchanged.
It must not be called from inside another Render, and fn must not reach back into the target: this is a plain lock, not a reentrant one.
func (*Target) SetFont ¶
SetFont replaces the typeface the rasterizer draws labels with, keeping the canvas object the chart is already shown in.
A font is fixed when a rasterizer is made, so this makes a new one — which is why it closes the surface, and why the chart drawn into it has to be opened again afterwards. What it deliberately does not replace is Target.Object: a widget holds that object, and handing it a different one would leave it showing a chart nothing draws into any more.
Pass all three as nil to go back to the rasterizer's own fonts.
Directories
¶
| Path | Synopsis |
|---|---|
|
Package chart shows a figure plot in a Fyne widget.
|
Package chart shows a figure plot in a Fyne widget. |
|
internal
|
|
|
look
Package look reads what a Fyne theme asks of a chart: the page it sits on, the size of the text around it and the typeface that text is set in.
|
Package look reads what a Fyne theme asks of a chart: the page it sits on, the size of the text around it and the typeface that text is set in. |
|
Package orbit shows a three-dimensional figure plot in a Fyne widget, and turns it under the pointer.
|
Package orbit shows a three-dimensional figure plot in a Fyne widget, and turns it under the pointer. |