Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 36 additions & 6 deletions live.go
Original file line number Diff line number Diff line change
Expand Up @@ -244,27 +244,47 @@ func (l *Live) TrackRows(on bool) *Live {
//
// Resizing to the size it already has is not an error and draws nothing.
func (l *Live) Resize(w, h int) error {
if changed, err := l.setSize(w, h); err != nil || !changed {
return err
}
return l.Draw()
}

// SetSize is [Live.Resize] without the frame: the chart takes the new size
// and draws it on the next [Live.Draw].
//
// It is for a caller that coalesces its frames — a widget that is laid out
// and then redrawn for another reason in the same turn, or one that is not on
// screen at all, would otherwise paint the whole chart once for the size and
// once more for the reason.
func (l *Live) SetSize(w, h int) error {
_, err := l.setSize(w, h)
return err
}

// setSize is SetSize, reporting whether the size changed.
func (l *Live) setSize(w, h int) (bool, error) {
if l.b == nil {
return errors.New("figure: Resize on a closed Live")
return false, errors.New("figure: Resize on a closed Live")
}
if w <= 0 || h <= 0 {
return fmt.Errorf("figure: chart size %dx%d is not positive", w, h)
return false, fmt.Errorf("figure: chart size %dx%d is not positive", w, h)
}
if w == l.width && h == l.height {
return nil
return false, nil
}
l.width, l.height = w, h
l.chart.Width, l.chart.Height = w, h
l.chart.Theme = l.p.themeFor(w, h)
if r, ok := l.b.(ir.Resizer); ok {
if err := r.Resize(ir.Surface{WidthPx: w, HeightPx: h, DPR: l.dpr}); err != nil {
return err
return false, err
}
}
// A frame of a different size is not comparable with the last one: every
// coordinate in it moved, so there is no damage to compute.
l.drawn = false
return l.Draw()
return true, nil
}

// Size reports the surface's current size in device-independent pixels.
Expand Down Expand Up @@ -585,6 +605,17 @@ func (l *Live) PanBy(dx, dy float64) error {
// Autoscale releases every zoom and pan, so the axes come from the data again,
// and redraws. It is the "reset view" every interactive chart needs.
func (l *Live) Autoscale() error {
l.ResetView()
return l.Draw()
}

// ResetView is [Live.Autoscale] without the frame: every zoom and pan is
// released, and the next [Live.Draw] shows the data's own extent.
//
// A caller that releases the view only to set another one straight after —
// linked charts fitted to a common range — would otherwise draw the released
// view first, a frame nobody sees.
func (l *Live) ResetView() {
for _, p := range l.idx.Panels() {
// A fixed coord's domains were never moved, and releasing them would
// retrain them to the data and drop the grid's pinned ticks.
Expand All @@ -596,7 +627,6 @@ func (l *Live) Autoscale() error {
autoscale(p.Y2)
autoscale(p.X2)
}
return l.Draw()
}

// Select reports the rows under a device-space rectangle, firing one [Select]
Expand Down
57 changes: 57 additions & 0 deletions live_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -214,3 +214,60 @@ type opaqueLayer struct{}
func (opaqueLayer) Train(geom.Training) error { return nil }
func (opaqueLayer) Build(ir.Backend, geom.Frame) error { return nil }
func (opaqueLayer) Legend(geom.Frame) (geom.LegendEntry, bool) { return geom.LegendEntry{}, false }

// ResetView releases the zoom as Autoscale does, but leaves the frame to the
// next Draw.
func TestResetViewDrawsNothingUntilDraw(t *testing.T) {
_, live, rec := livePlot(t)
panel := live.Index().Panels()[0]
lo0, hi0 := panel.X.Domain()

sel := ir.Rect{
Min: ir.Point{X: panel.X.Map(1), Y: panel.Y.Map(6)},
Max: ir.Point{X: panel.X.Map(3), Y: panel.Y.Map(2)},
}
if err := live.ZoomTo(sel); err != nil {
t.Fatal(err)
}
calls := len(rec.Calls)

live.ResetView()
if len(rec.Calls) != calls {
t.Errorf("ResetView drew %d calls, want none", len(rec.Calls)-calls)
}
if err := live.Draw(); err != nil {
t.Fatal(err)
}
if len(rec.Calls) == calls {
t.Error("the Draw after ResetView painted nothing")
}
if lo, hi := live.Index().Panels()[0].X.Domain(); lo != lo0 || hi != hi0 {
t.Errorf("the domain is %v..%v after ResetView, want the data's %v..%v", lo, hi, lo0, hi0)
}
}

// SetSize takes the size as Resize does, but leaves the frame to the next
// Draw.
func TestSetSizeDrawsNothingUntilDraw(t *testing.T) {
_, live, rec := livePlot(t)
calls := len(rec.Calls)

if err := live.SetSize(500, 320); err != nil {
t.Fatal(err)
}
if len(rec.Calls) != calls {
t.Errorf("SetSize drew %d calls, want none", len(rec.Calls)-calls)
}
if w, h := live.Size(); w != 500 || h != 320 {
t.Errorf("the size is %dx%d, want 500x320", w, h)
}
if err := live.Draw(); err != nil {
t.Fatal(err)
}
if len(rec.Calls) == calls {
t.Error("the Draw after SetSize painted nothing")
}
if err := live.SetSize(0, 320); err == nil {
t.Error("SetSize took a width of zero")
}
}
Loading