From 571f9b73e79abc7116f29e557a38fc68ec045d3e Mon Sep 17 00:00:00 2001 From: Aleksey Veresov Date: Fri, 22 Apr 2022 20:25:47 +0300 Subject: Splits logic and graphics. --- cirsim_fyne/component.go | 178 ++++++++++++++++++++++++++++++++++++ cirsim_fyne/node.go | 94 +++++++++++++++++++ cirsim_fyne/simulation.go | 223 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 495 insertions(+) create mode 100644 cirsim_fyne/component.go create mode 100644 cirsim_fyne/node.go create mode 100644 cirsim_fyne/simulation.go (limited to 'cirsim_fyne') diff --git a/cirsim_fyne/component.go b/cirsim_fyne/component.go new file mode 100644 index 0000000..299b2bb --- /dev/null +++ b/cirsim_fyne/component.go @@ -0,0 +1,178 @@ +package cirsim_fyne + +import ( + "fmt" + "io" + "log" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/canvas" + "fyne.io/fyne/v2/widget" + "git.veresov.xyz/aversey/cirsim/cirsim" + "github.com/wcharczuk/go-chart/v2" + "github.com/wcharczuk/go-chart/v2/drawing" +) + +type component struct { + widget.BaseWidget + modeler cirsim.Modeler + pos fyne.Position + modelName string + nodes [2]int + currentRange chart.Range + chart *canvas.Image + labels []*widget.Label + entries []*widget.Entry +} + +func (c *component) ModelName() string { + return c.modelName +} +func (c *component) Nodes() [2]int { + return c.nodes +} + +func newComponent(settings io.Reader, r chart.Range) *component { + var c component + var a int + var b int + _, err := fmt.Fscanf(settings, "%f %f %s %d %d\n", + &c.pos.X, &c.pos.Y, &c.modelName, &a, &b, + ) + if err != nil { + return nil + } + if a <= 0 || b <= 0 { + log.Fatal("unconnected components in the circuit") + } + c.nodes[0] = a - 1 + c.nodes[1] = b - 1 + c.currentRange = r + return &c +} + +func (c *component) setupModeler(modeler cirsim.Modeler, update func()) { + c.modeler = modeler + c.entries = make([]*widget.Entry, 0) + c.labels = make([]*widget.Label, 0) + params := c.modeler.Parameters() + for k, v := range params { + e := widget.NewEntry() + e.TextStyle.Monospace = true + e.SetPlaceHolder(k) + e.SetText(fmt.Sprintf("%f", v)) + e.OnSubmitted = func(s string) { + var v float64 + _, err := fmt.Sscanf(s+"\n", "%f\n", &v) + if err != nil { + e.SetText(fmt.Sprintf("%f", params[k])) + } else { + c.modeler.UpdateParameter(k, v) + update() + } + } + c.entries = append(c.entries, e) + l := widget.NewLabel(k) + l.TextStyle.Monospace = true + c.labels = append(c.labels, l) + } +} + +func (c *component) renderChart(currentOverTime []float64) { + graph := chart.Chart{ + Width: chartWidth, + Height: chartHeight, + ColorPalette: &componentColorPalette{}, + XAxis: chart.HideXAxis(), + YAxis: chart.YAxis{ + Style: chart.Hidden(), + Range: c.currentRange, + }, + Series: []chart.Series{ + chart.ContinuousSeries{ + XValues: chart.LinearRange(0, float64(len(currentOverTime))-1), + YValues: currentOverTime, + }, + }, + } + writer := &chart.ImageWriter{} + graph.Render(chart.PNG, writer) + img, _ := writer.Image() + c.chart = canvas.NewImageFromImage(img) +} + +type componentColorPalette struct{} + +func (*componentColorPalette) BackgroundColor() drawing.Color { + return drawing.ColorTransparent +} +func (*componentColorPalette) BackgroundStrokeColor() drawing.Color { + return drawing.ColorTransparent +} +func (*componentColorPalette) CanvasColor() drawing.Color { + return drawing.Color{ + R: currentCanvasR, + G: currentCanvasG, + B: currentCanvasB, + A: currentCanvasA, + } +} +func (*componentColorPalette) CanvasStrokeColor() drawing.Color { + return drawing.ColorTransparent +} +func (*componentColorPalette) AxisStrokeColor() drawing.Color { + return drawing.ColorTransparent +} +func (*componentColorPalette) TextColor() drawing.Color { + return drawing.ColorTransparent +} +func (*componentColorPalette) GetSeriesColor(index int) drawing.Color { + return drawing.Color{R: currentR, G: currentG, B: currentB, A: currentA} +} + +func (c *component) CreateRenderer() fyne.WidgetRenderer { return c } +func (c *component) Layout(s fyne.Size) { + const chartWidth = float32(chartWidth) + const chartHeight = float32(chartHeight) + pos := fyne.NewPos(0, 0) + for i, e := range c.entries { + c.labels[i].Resize(fyne.NewSize(chartWidth, c.labels[i].MinSize().Height)) + c.labels[i].Move(pos) + pos.Y += c.labels[i].MinSize().Height + e.Resize(fyne.NewSize(chartWidth, e.MinSize().Height)) + e.Move(pos) + pos.Y += e.MinSize().Height + } + c.chart.Resize(fyne.NewSize(chartWidth, chartHeight)) + c.chart.Move(pos) +} +func (c *component) MinSize() fyne.Size { + const chartWidth = float32(chartWidth) + const chartHeight = float32(chartHeight) + res := fyne.NewSize(chartWidth, chartHeight) + for _, e := range c.entries { + res.Height += e.MinSize().Height + } + for _, l := range c.labels { + res.Height += l.MinSize().Height + } + return res +} +func (c *component) Refresh() { + for _, e := range c.entries { + e.Refresh() + } + for _, l := range c.labels { + l.Refresh() + } +} +func (c *component) Destroy() {} +func (c *component) Objects() []fyne.CanvasObject { + res := []fyne.CanvasObject{} + for i, e := range c.entries { + res = append(res, c.labels[i]) + res = append(res, e) + } + res = append(res, c.chart) + return res +} diff --git a/cirsim_fyne/node.go b/cirsim_fyne/node.go new file mode 100644 index 0000000..84a15eb --- /dev/null +++ b/cirsim_fyne/node.go @@ -0,0 +1,94 @@ +package cirsim_fyne + +import ( + "fmt" + "io" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/canvas" + "fyne.io/fyne/v2/widget" + "github.com/wcharczuk/go-chart/v2" + "github.com/wcharczuk/go-chart/v2/drawing" +) + +type node struct { + widget.BaseWidget + pos fyne.Position + voltageOverTime []float64 + voltageRange chart.Range + chart *canvas.Image +} + +func newNode(settings io.Reader, r chart.Range) *node { + var n node + _, err := fmt.Fscanf(settings, "%f %f\n", &n.pos.X, &n.pos.Y) + if err != nil { + return nil + } + n.voltageRange = r + return &n +} + +func (n *node) renderChart(voltageOverTime []float64) { + graph := chart.Chart{ + Width: chartWidth, + Height: chartHeight, + ColorPalette: &nodeColorPalette{}, + XAxis: chart.HideXAxis(), + YAxis: chart.YAxis{ + Style: chart.Hidden(), + Range: n.voltageRange, + }, + Series: []chart.Series{ + chart.ContinuousSeries{ + XValues: chart.LinearRange(0, float64(len(voltageOverTime))-1), + YValues: voltageOverTime, + }, + }, + } + writer := &chart.ImageWriter{} + graph.Render(chart.PNG, writer) + img, _ := writer.Image() + n.chart = canvas.NewImageFromImage(img) +} + +type nodeColorPalette struct{} + +func (*nodeColorPalette) BackgroundColor() drawing.Color { + return drawing.ColorTransparent +} +func (*nodeColorPalette) BackgroundStrokeColor() drawing.Color { + return drawing.ColorTransparent +} +func (*nodeColorPalette) CanvasColor() drawing.Color { + return drawing.Color{ + R: voltageCanvasR, + G: voltageCanvasG, + B: voltageCanvasB, + A: voltageCanvasA, + } +} +func (*nodeColorPalette) CanvasStrokeColor() drawing.Color { + return drawing.ColorTransparent +} +func (*nodeColorPalette) AxisStrokeColor() drawing.Color { + return drawing.ColorTransparent +} +func (*nodeColorPalette) TextColor() drawing.Color { + return drawing.ColorTransparent +} +func (*nodeColorPalette) GetSeriesColor(index int) drawing.Color { + return drawing.Color{R: voltageR, G: voltageG, B: voltageB, A: voltageA} +} + +func (n *node) CreateRenderer() fyne.WidgetRenderer { return n } +func (n *node) Layout(s fyne.Size) { + n.chart.Resize(s) + n.chart.Move(fyne.NewPos(0, 0)) +} +func (n *node) MinSize() fyne.Size { return n.chart.MinSize() } +func (n *node) Refresh() {} +func (n *node) Destroy() {} +func (n *node) Objects() []fyne.CanvasObject { + return []fyne.CanvasObject{n.chart} +} diff --git a/cirsim_fyne/simulation.go b/cirsim_fyne/simulation.go new file mode 100644 index 0000000..aa53ec8 --- /dev/null +++ b/cirsim_fyne/simulation.go @@ -0,0 +1,223 @@ +package cirsim_fyne + +import ( + "fmt" + "image/color" + "io" + "log" + "os" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/canvas" + "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/layout" + "fyne.io/fyne/v2/widget" + "git.veresov.xyz/aversey/cirsim/cirsim" + "github.com/wcharczuk/go-chart/v2" +) + +const ( + chartWidth int = 140 + chartHeight = 60 + currentCanvasR uint8 = 191 + currentCanvasG = 254 + currentCanvasB = 247 + currentCanvasA = 128 + currentR = 3 + currentG = 247 + currentB = 198 + currentA = 255 + voltageCanvasR = 249 + voltageCanvasG = 254 + voltageCanvasB = 172 + voltageCanvasA = 128 + voltageR = 247 + voltageG = 239 + voltageB = 3 + voltageA = 255 +) + +type simulation struct { + sim cirsim.Simulator + size fyne.Size + nodes []*node + components []*component + voltageRange chart.ContinuousRange + currentRange chart.ContinuousRange + periodEntry *widget.Entry + voltageLabel *canvas.Text + currentLabel *canvas.Text +} + +func New() fyne.CanvasObject { + background := canvas.NewRectangle(color.White) + circuit := canvas.NewImageFromFile("circuit.svg") + settings, err := os.Open("circuit") + if err != nil { + log.Fatal(err) + } + var sim simulation + sim.voltageRange = chart.ContinuousRange{Min: 0, Max: 0} + sim.currentRange = chart.ContinuousRange{Min: 0, Max: 0} + fmt.Fscanf(settings, "%f %f\n\n", &sim.size.Width, &sim.size.Height) + cont := container.New(&sim, sim.newPanel(settings), background, circuit) + sim.addNodes(cont, settings) + sim.addComponents(cont, settings) + settings.Close() + components := make([]cirsim.ComponentSettings, len(sim.components)) + for i := range components { + components[i] = sim.components[i] + } + sim.sim = cirsim.New(len(sim.nodes), components) + sim.periodEntry.SetPlaceHolder( + fmt.Sprintf("default: %fs", sim.sim.Period())) + sim.setupComponentModelers() + sim.update() + return cont +} + +func (sim *simulation) newPanel(settings io.Reader) *fyne.Container { + sim.voltageLabel = canvas.NewText( + fmt.Sprintf(" %e < voltage < %e ", + sim.voltageRange.Min, sim.voltageRange.Max), + color.RGBA{R: voltageR, G: voltageG, B: voltageB, A: voltageA}, + ) + sim.voltageLabel.TextStyle.Monospace = true + sim.currentLabel = canvas.NewText( + fmt.Sprintf(" %e < current < %e ", + sim.currentRange.Min, sim.currentRange.Max), + color.RGBA{R: currentR, G: currentG, B: currentB, A: currentA}, + ) + sim.currentLabel.TextStyle.Monospace = true + periodLabel := widget.NewLabel("Period") + periodLabel.TextStyle.Monospace = true + sim.periodEntry = widget.NewEntry() + sim.periodEntry.TextStyle.Monospace = true + sim.periodEntry.OnSubmitted = sim.updatePeriod + return container.NewHBox( + sim.voltageLabel, + sim.currentLabel, + layout.NewSpacer(), + periodLabel, + container.New(&entryLayout{}, sim.periodEntry), + ) +} + +func (sim *simulation) addNodes(cont *fyne.Container, settings io.Reader) { + n := newNode(settings, &sim.voltageRange) + for n != nil { + sim.nodes = append(sim.nodes, n) + cont.Add(n) + n = newNode(settings, &sim.voltageRange) + } +} + +func (sim *simulation) addComponents(cont *fyne.Container, settings io.Reader) { + c := newComponent(settings, &sim.currentRange) + for c != nil { + sim.components = append(sim.components, c) + cont.Add(c) + c = newComponent(settings, &sim.currentRange) + } +} + +func (sim *simulation) setupComponentModelers() { + for i := range sim.components { + sim.components[i].setupModeler( + sim.sim.ModelerOfComponent(i), sim.update) + } +} + +func (sim *simulation) update() { + sim.sim.Simulate() + sim.voltageRange.Min, sim.voltageRange.Max = sim.sim.VoltageRange() + sim.currentRange.Min, sim.currentRange.Max = sim.sim.CurrentRange() + sim.voltageLabel.Text = fmt.Sprintf(" %e < voltage < %e ", + sim.voltageRange.Min, sim.voltageRange.Max) + sim.currentLabel.Text = fmt.Sprintf(" %e < current < %e ", + sim.currentRange.Min, sim.currentRange.Max) + sim.voltageLabel.Refresh() + sim.currentLabel.Refresh() + for i, n := range sim.nodes { + n.renderChart(sim.sim.VoltagesOfNode(i)) + n.Refresh() + } + for i, c := range sim.components { + c.renderChart(sim.sim.CurrentsOfComponent(i)) + c.Refresh() + } +} + +func (sim *simulation) updatePeriod(period string) { + var periodVal float64 + _, err := fmt.Sscanf(period+"\n", "%f\n", periodVal) + if err != nil { + sim.periodEntry.SetText(fmt.Sprintf("%f", sim.sim.Period())) + } else { + sim.sim.SetPeriod(periodVal) + sim.update() + } +} + +func (l *simulation) MinSize(objects []fyne.CanvasObject) fyne.Size { + return objects[2].MinSize() +} +func (l *simulation) Layout(obs []fyne.CanvasObject, size fyne.Size) { + var p fyne.Position + var s fyne.Size + panelHeight := obs[0].MinSize().Height + H := size.Height - panelHeight + W := size.Width + h := l.size.Height + w := l.size.Width + var scale float32 + if H/W > h/w { + p = fyne.NewPos(0, (H-W*h/w)/2+panelHeight) + s = fyne.NewSize(W, W*h/w) + scale = W / w + } else { + p = fyne.NewPos((W-H*w/h)/2, panelHeight) + s = fyne.NewSize(H*w/h, H) + scale = H / h + } + obs[0].Resize(fyne.NewSize(W, panelHeight)) + obs[0].Move(fyne.NewPos(0, 0)) + obs[1].Resize(size) + obs[1].Move(fyne.NewPos(0, panelHeight)) + obs[2].Resize(s) + obs[2].Move(p) + const chartWidth = float32(chartWidth) + const chartHeight = float32(chartHeight) + for i, n := range l.nodes { + obs[3+i].Resize(fyne.NewSize(chartWidth, chartHeight)) + shift := fyne.NewPos( + p.X+n.pos.X*scale-chartWidth/2.0, + p.Y+n.pos.Y*scale-chartHeight/2.0, + ) + obs[3+i].Move(shift) + } + for i, c := range l.components { + ms := obs[3+len(l.nodes)+i].MinSize() + obs[3+len(l.nodes)+i].Resize(ms) + shift := fyne.NewPos( + p.X+c.pos.X*scale-chartWidth/2.0, + p.Y+c.pos.Y*scale-ms.Height+chartHeight/2.0, + ) + obs[3+len(l.nodes)+i].Move(shift) + } +} + +type entryLayout struct{} + +func (*entryLayout) MinSize(objects []fyne.CanvasObject) fyne.Size { + return fyne.NewSize( + objects[0].MinSize().Width*6, + objects[0].MinSize().Height, + ) +} +func (*entryLayout) Layout(objects []fyne.CanvasObject, size fyne.Size) { + for _, o := range objects { + o.Resize(size) + o.Move(fyne.NewPos(0, 0)) + } +} -- cgit v1.2.3