summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAleksey Veresov <aleksey@veresov.pro>2022-04-22 20:25:47 +0300
committerAleksey Veresov <aleksey@veresov.pro>2022-04-22 20:25:47 +0300
commit571f9b73e79abc7116f29e557a38fc68ec045d3e (patch)
treeb833ba89dc23f03932669f37e86fae0087b89820
parent6d289b952d1b5c8b8e151ded0195e4658f5d6d0b (diff)
downloadcirsim-571f9b73e79abc7116f29e557a38fc68ec045d3e.tar
cirsim-571f9b73e79abc7116f29e557a38fc68ec045d3e.tar.xz
cirsim-571f9b73e79abc7116f29e557a38fc68ec045d3e.zip
Splits logic and graphics.
-rw-r--r--cirsim/component.go166
-rw-r--r--cirsim/models.go28
-rw-r--r--cirsim/simulation.go290
-rw-r--r--cirsim_fyne/component.go178
-rw-r--r--cirsim_fyne/node.go (renamed from cirsim/node.go)15
-rw-r--r--cirsim_fyne/simulation.go223
-rw-r--r--main.go4
7 files changed, 508 insertions, 396 deletions
diff --git a/cirsim/component.go b/cirsim/component.go
index 358eee3..99d8b54 100644
--- a/cirsim/component.go
+++ b/cirsim/component.go
@@ -1,173 +1,23 @@
package cirsim
-import (
- "fmt"
- "io"
- "log"
-
- "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 ComponentSettings interface {
+ Nodes() [2]int
+ ModelName() string
+}
type component struct {
- widget.BaseWidget
- modeler
- pos fyne.Position
+ Modeler
currentOverTime []float64
nodes [2]int
- currentRange chart.Range
- chart *canvas.Image
- labels []*widget.Label
- entries []*widget.Entry
}
-func newComponent(settings io.Reader, r chart.Range, update func()) *component {
+func newComponent(settings ComponentSettings) *component {
var c component
- var t string
- var a int
- var b int
- _, err := fmt.Fscanf(settings, "%f %f %s %d %d\n",
- &c.pos.X, &c.pos.Y, &t, &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.setupModeler(t, update)
- c.currentRange = r
+ c.nodes = settings.Nodes()
+ c.Modeler = newModeler(settings.ModelName())
c.currentOverTime = make([]float64, iterations)
for i := range c.currentOverTime {
c.currentOverTime[i] = 0
}
- c.renderChart()
return &c
}
-
-func (c *component) setupModeler(name string, update func()) {
- c.modeler = newModeler(name)
- c.entries = make([]*widget.Entry, 0)
- c.labels = make([]*widget.Label, 0)
- for k, v := range c.parameters() {
- 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", c.parameters()[k]))
- } else {
- c.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() {
- 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(iterations)-1),
- YValues: c.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) {
- 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 {
- 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() {
- c.renderChart()
- 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/models.go b/cirsim/models.go
index 70710d7..56531bf 100644
--- a/cirsim/models.go
+++ b/cirsim/models.go
@@ -5,14 +5,14 @@ import (
"math"
)
-type modeler interface {
+type Modeler interface {
conductance(time, delta, voltage, current float64) float64
current(time, delta, voltage, current float64) float64
- parameters() map[string]float64
- updateParameter(name string, value float64)
+ Parameters() map[string]float64
+ UpdateParameter(name string, value float64)
}
-func newModeler(name string) modeler {
+func newModeler(name string) Modeler {
switch name {
case "resistor":
return newResistor()
@@ -45,10 +45,10 @@ func (m *capacitor) conductance(time, delta, voltage, current float64) float64 {
func (m *capacitor) current(time, delta, voltage, current float64) float64 {
return -current - voltage*2*m.capacitance/delta
}
-func (m *capacitor) parameters() map[string]float64 {
+func (m *capacitor) Parameters() map[string]float64 {
return map[string]float64{"Capacitance": m.capacitance}
}
-func (m *capacitor) updateParameter(name string, value float64) {
+func (m *capacitor) UpdateParameter(name string, value float64) {
if name == "Capacitance" {
m.capacitance = value
}
@@ -68,10 +68,10 @@ func (m *resistor) conductance(time, delta, voltage, current float64) float64 {
func (m *resistor) current(time, delta, voltage, current float64) float64 {
return 0
}
-func (m *resistor) parameters() map[string]float64 {
+func (m *resistor) Parameters() map[string]float64 {
return map[string]float64{"Resistance": m.resistance}
}
-func (m *resistor) updateParameter(name string, value float64) {
+func (m *resistor) UpdateParameter(name string, value float64) {
if name == "Resistance" {
m.resistance = value
}
@@ -91,10 +91,10 @@ func (m *inductor) conductance(time, delta, voltage, current float64) float64 {
func (m *inductor) current(time, delta, voltage, current float64) float64 {
return current + voltage*delta/(2*m.inductance)
}
-func (m *inductor) parameters() map[string]float64 {
+func (m *inductor) Parameters() map[string]float64 {
return map[string]float64{"Inductance": m.inductance}
}
-func (m *inductor) updateParameter(name string, value float64) {
+func (m *inductor) UpdateParameter(name string, value float64) {
if name == "Inductance" {
m.inductance = value
}
@@ -112,10 +112,10 @@ func (m *diode) conductance(time, delta, voltage, current float64) float64 {
func (m *diode) current(time, delta, voltage, current float64) float64 {
return 0
}
-func (m *diode) parameters() map[string]float64 {
+func (m *diode) Parameters() map[string]float64 {
return map[string]float64{}
}
-func (m *diode) updateParameter(name string, value float64) {}
+func (m *diode) UpdateParameter(name string, value float64) {}
type power struct {
maxCurrent float64
@@ -132,10 +132,10 @@ func (m *power) conductance(time, delta, voltage, current float64) float64 {
func (m *power) current(time, delta, voltage, current float64) float64 {
return m.maxCurrent * math.Sin(time*m.frequency*2*math.Pi)
}
-func (m *power) parameters() map[string]float64 {
+func (m *power) Parameters() map[string]float64 {
return map[string]float64{"Current": m.maxCurrent, "Frequency": m.frequency}
}
-func (m *power) updateParameter(name string, value float64) {
+func (m *power) UpdateParameter(name string, value float64) {
if name == "Current" {
m.maxCurrent = value
} else if name == "Frequency" {
diff --git a/cirsim/simulation.go b/cirsim/simulation.go
index 9b7234c..fa3a8bd 100644
--- a/cirsim/simulation.go
+++ b/cirsim/simulation.go
@@ -1,163 +1,73 @@
package cirsim
-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"
- "github.com/wcharczuk/go-chart/v2"
- "gonum.org/v1/gonum/mat"
-)
+import "gonum.org/v1/gonum/mat"
const (
- defaultPeriod float64 = 0.01
- iterations int = 1000
- chartWidth = 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
+ defaultPeriod float64 = 0.01
+ iterations int = 1000
)
+type Simulator interface {
+ Period() float64
+ SetPeriod(float64)
+ VoltageRange() (float64, float64)
+ CurrentRange() (float64, float64)
+ VoltagesOfNode(i int) []float64
+ CurrentsOfComponent(i int) []float64
+ ModelerOfComponent(i int) Modeler
+ Simulate()
+}
+
type simulation struct {
period float64
- size fyne.Size
- nodes []*node
+ voltageMax float64
+ voltageMin float64
+ currentMax float64
+ currentMin float64
+ nodeVoltages [][]float64
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)
- }
+func New(nodesCount int, components []ComponentSettings) Simulator {
var sim simulation
sim.period = defaultPeriod
- 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()
- sim.update()
- return cont
+ sim.nodeVoltages = make([][]float64, nodesCount)
+ for i := range sim.nodeVoltages {
+ sim.nodeVoltages[i] = make([]float64, iterations)
+ }
+ sim.components = make([]*component, 0)
+ for _, c := range components {
+ sim.components = append(sim.components, newComponent(c))
+ }
+ sim.Simulate()
+ return &sim
}
-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.SetPlaceHolder(fmt.Sprintf("default: %fs", defaultPeriod))
- sim.periodEntry.OnSubmitted = sim.updatePeriod
- return container.NewHBox(
- sim.voltageLabel,
- sim.currentLabel,
- layout.NewSpacer(),
- periodLabel,
- container.New(&entryLayout{}, sim.periodEntry),
- )
+func (sim *simulation) Period() float64 {
+ return sim.period
}
-
-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) SetPeriod(period float64) {
+ sim.period = period
}
-
-func (sim *simulation) addComponents(cont *fyne.Container, settings io.Reader) {
- c := newComponent(settings, &sim.currentRange, sim.update)
- for c != nil {
- sim.components = append(sim.components, c)
- cont.Add(c)
- c = newComponent(settings, &sim.currentRange, sim.update)
- }
+func (sim *simulation) VoltageRange() (float64, float64) {
+ return sim.voltageMin, sim.voltageMax
}
-
-func (sim *simulation) update() {
- sim.simulate()
- sim.voltageRange.Max = 0
- sim.voltageRange.Min = 0
- for _, n := range sim.nodes {
- for _, v := range n.voltageOverTime {
- if v > sim.voltageRange.Max {
- sim.voltageRange.Max = v
- } else if v < sim.voltageRange.Min {
- sim.voltageRange.Min = v
- }
- }
- }
- sim.currentRange.Max = sim.components[0].currentOverTime[0]
- sim.currentRange.Min = sim.components[0].currentOverTime[0]
- for _, comp := range sim.components {
- for _, c := range comp.currentOverTime {
- if c > sim.currentRange.Max {
- sim.currentRange.Max = c
- } else if c < sim.currentRange.Min {
- sim.currentRange.Min = c
- }
- }
- }
- 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 _, n := range sim.nodes {
- n.Refresh()
- }
- for _, c := range sim.components {
- c.Refresh()
- }
+func (sim *simulation) CurrentRange() (float64, float64) {
+ return sim.currentMin, sim.currentMax
+}
+func (sim *simulation) VoltagesOfNode(i int) []float64 {
+ return sim.nodeVoltages[i]
+}
+func (sim *simulation) CurrentsOfComponent(i int) []float64 {
+ return sim.components[i].currentOverTime
+}
+func (sim *simulation) ModelerOfComponent(i int) Modeler {
+ return sim.components[i]
}
-func (sim *simulation) simulate() {
+func (sim *simulation) Simulate() {
sim.nullify()
- N := len(sim.nodes)
+ N := len(sim.nodeVoltages)
delta := sim.period / float64(iterations)
for i := 0; i != iterations; i++ {
// fill conductances and currents:
@@ -165,8 +75,8 @@ func (sim *simulation) simulate() {
conductances := mat.NewDense(N+1, N, nil)
currents := mat.NewVecDense(N+1, nil)
for _, c := range sim.components {
- voltage := sim.nodes[c.nodes[1]].voltageOverTime[i] -
- sim.nodes[c.nodes[0]].voltageOverTime[i]
+ voltage := sim.nodeVoltages[c.nodes[1]][i] -
+ sim.nodeVoltages[c.nodes[0]][i]
current := c.current(time, delta, voltage, c.currentOverTime[i])
cond := c.conductance(time, delta, voltage, c.currentOverTime[i])
currents.SetVec(c.nodes[1], currents.AtVec(c.nodes[1])-current)
@@ -192,8 +102,8 @@ func (sim *simulation) simulate() {
voltages := mat.NewVecDense(N, nil)
voltages.SolveVec(conductances, currents)
// save results:
- for j, n := range sim.nodes {
- n.voltageOverTime[i] = voltages.AtVec(j)
+ for j, n := range sim.nodeVoltages {
+ n[i] = voltages.AtVec(j)
}
for _, c := range sim.components {
voltage := voltages.AtVec(c.nodes[1]) - voltages.AtVec(c.nodes[0])
@@ -202,12 +112,13 @@ func (sim *simulation) simulate() {
c.currentOverTime[i] = voltage*cond + current
}
}
+ sim.updateRanges()
}
func (sim *simulation) nullify() {
- for _, n := range sim.nodes {
- for i := range n.voltageOverTime {
- n.voltageOverTime[i] = 0
+ for _, n := range sim.nodeVoltages {
+ for i := range n {
+ n[i] = 0
}
}
for _, c := range sim.components {
@@ -217,72 +128,27 @@ func (sim *simulation) nullify() {
}
}
-func (sim *simulation) updatePeriod(period string) {
- _, err := fmt.Sscanf(period+"\n", "%f\n", &sim.period)
- if err != nil {
- sim.periodEntry.SetText(fmt.Sprintf("%f", sim.period))
- } else {
- 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)
- 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)
+func (sim *simulation) updateRanges() {
+ sim.voltageMax = 0
+ sim.voltageMin = 0
+ for _, n := range sim.nodeVoltages {
+ for _, v := range n {
+ if v > sim.voltageMax {
+ sim.voltageMax = v
+ } else if v < sim.voltageMin {
+ sim.voltageMin = v
+ }
+ }
}
-}
-
-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))
+ sim.currentMax = sim.components[0].currentOverTime[0]
+ sim.currentMin = sim.components[0].currentOverTime[0]
+ for _, comp := range sim.components {
+ for _, c := range comp.currentOverTime {
+ if c > sim.currentMax {
+ sim.currentMax = c
+ } else if c < sim.currentMin {
+ sim.currentMin = c
+ }
+ }
}
}
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/node.go b/cirsim_fyne/node.go
index d26d78f..84a15eb 100644
--- a/cirsim/node.go
+++ b/cirsim_fyne/node.go
@@ -1,4 +1,4 @@
-package cirsim
+package cirsim_fyne
import (
"fmt"
@@ -26,15 +26,10 @@ func newNode(settings io.Reader, r chart.Range) *node {
return nil
}
n.voltageRange = r
- n.voltageOverTime = make([]float64, iterations)
- for i := range n.voltageOverTime {
- n.voltageOverTime[i] = 0
- }
- n.renderChart()
return &n
}
-func (n *node) renderChart() {
+func (n *node) renderChart(voltageOverTime []float64) {
graph := chart.Chart{
Width: chartWidth,
Height: chartHeight,
@@ -46,8 +41,8 @@ func (n *node) renderChart() {
},
Series: []chart.Series{
chart.ContinuousSeries{
- XValues: chart.LinearRange(0, float64(iterations)-1),
- YValues: n.voltageOverTime,
+ XValues: chart.LinearRange(0, float64(len(voltageOverTime))-1),
+ YValues: voltageOverTime,
},
},
}
@@ -92,7 +87,7 @@ func (n *node) Layout(s fyne.Size) {
n.chart.Move(fyne.NewPos(0, 0))
}
func (n *node) MinSize() fyne.Size { return n.chart.MinSize() }
-func (n *node) Refresh() { n.renderChart() }
+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))
+ }
+}
diff --git a/main.go b/main.go
index 23474fa..b925e09 100644
--- a/main.go
+++ b/main.go
@@ -4,7 +4,7 @@ import (
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/theme"
- "git.veresov.xyz/aversey/cirsim/cirsim"
+ "git.veresov.xyz/aversey/cirsim/cirsim_fyne"
)
func main() {
@@ -13,6 +13,6 @@ func main() {
w := a.NewWindow("Circuit Simulator")
w.Resize(fyne.NewSize(1280, 720))
w.SetIcon(theme.SettingsIcon())
- w.SetContent(cirsim.New())
+ w.SetContent(cirsim_fyne.New())
w.ShowAndRun()
}