Select Git revision
output_tab.go
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
output_tab.go 5.25 KiB
// 17 Feb 2022
// Output tab / results
//go:build !no_gfx
// +build !no_gfx
// There should be three states.
// 1. initial, nothing happened yet, but we do not need a signal for this
// 2. calculating, busy
// 3. show results
// 4. an error occurred
package ui
import (
"bytes"
"fmt"
"os"
mcwork "example.com/ackley_mc/mc_work"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/dialog"
"fyne.io/fyne/v2/storage"
"fyne.io/fyne/v2/widget"
)
// runStatTxt should summarise some of the statistics
func runStatTxt(rslt *mcwork.MCresult) fyne.Widget {
s := fmt.Sprintf("Num steps %d\nNum accepted %d\nacceptance rate %.1f %%",
rslt.NStep, rslt.NAcc, (float32(rslt.NAcc)/float32(rslt.NStep))*100.)
if rslt.BestX != nil {
s += fmt.Sprintf("\nbest function value: %.2f\nat %.1g",
rslt.Bestfval, rslt.BestX)
}
r := widget.NewLabel(s)
return r
}
// If we get a message with an error, display it
func showErrTab(cntr *fyne.Container, err error) {
cntr.Objects = nil
cntr.Add(widget.NewCard("ERROR in MC", err.Error(), nil))
}
// showCalcTab is shown while calculating. Have to check if the refresh()
// is actually necessary.
func showCalcTab(cntr *fyne.Container) {
cntr.Objects = nil
cntr.Add(widget.NewCard("calculating", "busy", nil))
}
// fwrt writes the file. It will be called by the filesave dialog
func fwrt(io fyne.URIWriteCloser, err error, d []byte, parent fyne.Window) {
if io == nil {
return // it was cancelled
}
if err != nil {
dialog.ShowError(err, parent)
return
}
defer io.Close()
if _, err := io.Write(d); err != nil {
dialog.ShowError(err, parent)
}
}
// innerWrite will be called by the button to save a file
func innerWrite(d []byte, parent fyne.Window) {
fwrt := func(io fyne.URIWriteCloser, err error) { fwrt(io, err, d, parent) }
t := dialog.NewFileSave(fwrt, parent)
t.SetFilter(storage.NewExtensionFileFilter([]string{"png", "PNG"}))
if cwd, err := os.Getwd(); err == nil {
if y, err := storage.ListerForURI(storage.NewFileURI(cwd)); err == nil {
t.SetLocation(y) // on error, just use default location
}
}
t.Show()
}
var leftText = `Stretch window and play with the divider to make plots bigger.
Clicking on a button below will let one save the plot of the cost function or the
actual X values given to the function`
// leftbar sets up the buttons on the left
func leftbar(win fyne.Window, fdata, xdata []byte) *fyne.Container {
wrtFdata := func() { innerWrite(fdata, win) }
wrtXdata := func() { innerWrite(xdata, win) }
fdataBtn := widget.NewButton("save func\nvalue plot\nto file", wrtFdata)
xdataBtn := widget.NewButton("save\nX coord plot\nto file", wrtXdata)
infobox := widget.NewLabel(leftText)
infobox.Alignment = fyne.TextAlignLeading
infobox.Wrapping = fyne.TextWrapWord
s := &widget.Separator{}
return container.NewVBox(infobox, s, fdataBtn, s, xdataBtn)
}
// png2image takes the file data we have and puts it in a fyne image with
// a size we want. fname is used by fyne to recognise the file type.
func png2image(d []byte, fname string) *canvas.Image {
pictureSize := fyne.Size{Width: 500, Height: 250}
image := canvas.NewImageFromReader(bytes.NewReader(d), fname)
image.FillMode = canvas.ImageFillContain
image.SetMinSize(pictureSize)
return image
}
// This allows us to make a layout for putting two plots on top of each other.
// The object resize()'s so that it fills the width and each object (plot)
// gets half of the vertical space. An rbox satisfies fyne's Layout interface.
type rbox struct{}
// MinSize for an rbox... The width of the two plots, and height for two of them.
func (*rbox) MinSize(objects []fyne.CanvasObject) fyne.Size {
w := objects[0].MinSize().Width
h := objects[0].MinSize().Height
return (fyne.NewSize(w, 2.0*h))
}
// Layout for an rbox. Put two objects on top of each other in a box that resizes.
func (*rbox) Layout(objects []fyne.CanvasObject, cntrSize fyne.Size) {
top := objects[0]
bottom := objects[1]
half := cntrSize.Height / 2.
sWant := fyne.Size{Width: cntrSize.Width, Height: half}
top.Resize(sWant)
bottom.Resize(sWant)
top.Move(fyne.Position{X: 0, Y: 0})
bottom.Move(fyne.Position{X: 0, Y: half})
}
// showResultsTab shows buttons on the left and two plots on the right
func showResultsTab(cntr *fyne.Container, win fyne.Window, rslt mcwork.MCresult) {
cntr.Objects = nil
fImage := png2image(rslt.Fdata, "function.png")
xImage := png2image(rslt.Xdata, "xdata.png")
rbox := container.New(&rbox{}, fImage, xImage)
right := container.NewVSplit(rbox, runStatTxt(&rslt))
right.SetOffset(0.99)
left := leftbar(win, rslt.Fdata, rslt.Xdata)
split := container.NewHSplit(left, right)
split.SetOffset(0.01)
cntr.Add(split)
win.Resize(cntr.Size()) // only necessary on first call
}
// outputTab is run as a background process. After showing the initial
// screen, it sits and waits on notifications. When it gets one,
// it redraws its tab.
func outputTab(genParams genParams, cntr *fyne.Container, form *widget.Form) {
cntr.Add(widget.NewCard("No results yet", "go back to the input", nil))
for s := range genParams.chn {
switch s.status {
case calculating:
showCalcTab(cntr)
case resultsReady:
showResultsTab(cntr, genParams.win, s.MCresult)
form.Enable()
form.Refresh()
case errorCalc:
showErrTab(cntr, s.err)
form.Enable()
}
}
}