Skip to content
Snippets Groups Projects
Select Git revision
  • 14bb48043e63f97cdb34e0e42fbe8b05c7c892e2
  • master default protected
  • devel
  • adaptive_step_size
4 results

output_tab.go

Blame
  • user avatar
    Andrew E. Torda authored
    14bb4804
    History
    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()
    		}
    	}
    }