summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAleksey Veresov <aleksey@veresov.pro>2022-04-19 05:25:30 +0300
committerAleksey Veresov <aleksey@veresov.pro>2022-04-19 05:25:30 +0300
commitc9350c41c38750e2230357f655f6e72035995c34 (patch)
treea5b09c1d2ae709a95076faa64df63f273269275f
downloadcirsim-c9350c41c38750e2230357f655f6e72035995c34.tar
cirsim-c9350c41c38750e2230357f655f6e72035995c34.tar.xz
cirsim-c9350c41c38750e2230357f655f6e72035995c34.zip
.
-rw-r--r--cirsim/component.go178
-rw-r--r--cirsim/models.go102
-rw-r--r--cirsim/node.go96
-rw-r--r--cirsim/simulation.go286
-rw-r--r--go.mod30
-rw-r--r--go.sum113
-rw-r--r--main.go18
7 files changed, 823 insertions, 0 deletions
diff --git a/cirsim/component.go b/cirsim/component.go
new file mode 100644
index 0000000..5b03fd2
--- /dev/null
+++ b/cirsim/component.go
@@ -0,0 +1,178 @@
+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 Component struct {
+ widget.BaseWidget
+ Pos fyne.Position
+ Model Modeler
+ CurrentOverTime []float64
+ ANode *Node
+ BNode *Node
+ CurrentRange chart.Range
+ Image *canvas.Image
+ labels []*widget.Label
+ entries []*widget.Entry
+}
+
+func NewComponent(settings io.Reader, r chart.Range, nodes []*Node, update func()) *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.ANode = nodes[a-1]
+ nodes[a-1].AComponents = append(nodes[a-1].AComponents, &c)
+ c.BNode = nodes[b-1]
+ nodes[b-1].BComponents = append(nodes[b-1].BComponents, &c)
+ switch t {
+ case "resistor":
+ c.Model = &Resistor{100.0}
+ case "capacitor":
+ c.Model = &Capacitor{0.000001}
+ case "inductor":
+ c.Model = &Inductor{0.000001}
+ case "diode":
+ c.Model = &Diode{}
+ case "power":
+ c.Model = &Power{Current: 1.0, Frequency: 1000.0}
+ }
+ c.entries = make([]*widget.Entry, 0)
+ c.labels = make([]*widget.Label, 0)
+ for k, v := range c.Model.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.Model.Parameters()[k]))
+ } else {
+ c.Model.UpdateParameter(k, v)
+ update()
+ }
+ }
+ c.entries = append(c.entries, e)
+ l := widget.NewLabel(k)
+ l.TextStyle.Monospace = true
+ c.labels = append(c.labels, l)
+ }
+ c.CurrentRange = r
+ c.CurrentOverTime = make([]float64, 1000)
+ for i := range c.CurrentOverTime {
+ c.CurrentOverTime[i] = 0
+ }
+ c.renderChart()
+ return &c
+}
+
+func (c *Component) renderChart() {
+ graph := chart.Chart{
+ Width: 140,
+ Height: 60,
+ ColorPalette: &componentColorPalette{},
+ XAxis: chart.HideXAxis(),
+ YAxis: chart.YAxis{
+ Style: chart.Hidden(),
+ Range: c.CurrentRange,
+ },
+ Series: []chart.Series{
+ chart.ContinuousSeries{
+ XValues: chart.LinearRange(0, 999),
+ YValues: c.CurrentOverTime,
+ },
+ },
+ }
+ writer := &chart.ImageWriter{}
+ graph.Render(chart.PNG, writer)
+ img, _ := writer.Image()
+ c.Image = 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: 191, G: 254, B: 247, A: 128}
+}
+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: 3, G: 247, B: 198, A: 255}
+}
+
+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(140, c.labels[i].MinSize().Height))
+ c.labels[i].Move(pos)
+ pos.Y += c.labels[i].MinSize().Height
+ e.Resize(fyne.NewSize(140, e.MinSize().Height))
+ e.Move(pos)
+ pos.Y += e.MinSize().Height
+ }
+ c.Image.Resize(fyne.NewSize(140, 60))
+ c.Image.Move(pos)
+}
+func (c *Component) MinSize() fyne.Size {
+ res := fyne.NewSize(140, 60)
+ 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.Image)
+ return res
+}
diff --git a/cirsim/models.go b/cirsim/models.go
new file mode 100644
index 0000000..f4a73e9
--- /dev/null
+++ b/cirsim/models.go
@@ -0,0 +1,102 @@
+package cirsim
+
+import "math"
+
+type Modeler interface {
+ ModelConductance(time, delta, voltage, current float64) float64
+ ModelCurrent(time, delta, voltage, current float64) float64
+ Parameters() map[string]float64
+ UpdateParameter(name string, value float64)
+}
+
+type Capacitor struct {
+ Capacitance float64
+}
+
+func (m *Capacitor) ModelConductance(time, delta, voltage, current float64) float64 {
+ return 2.0 * m.Capacitance / delta
+}
+func (m *Capacitor) ModelCurrent(time, delta, voltage, current float64) float64 {
+ return -current - voltage*2*m.Capacitance/delta
+}
+func (m *Capacitor) Parameters() map[string]float64 {
+ return map[string]float64{"Capacitance": m.Capacitance}
+}
+func (m *Capacitor) UpdateParameter(name string, value float64) {
+ if name == "Capacitance" {
+ m.Capacitance = value
+ }
+}
+
+type Resistor struct {
+ Resistance float64
+}
+
+func (m *Resistor) ModelConductance(time, delta, voltage, current float64) float64 {
+ return 1.0 / m.Resistance
+}
+func (m *Resistor) ModelCurrent(time, delta, voltage, current float64) float64 {
+ return 0
+}
+func (m *Resistor) Parameters() map[string]float64 {
+ return map[string]float64{"Resistance": m.Resistance}
+}
+func (m *Resistor) UpdateParameter(name string, value float64) {
+ if name == "Resistance" {
+ m.Resistance = value
+ }
+}
+
+type Inductor struct {
+ Inductance float64
+}
+
+func (m *Inductor) ModelConductance(time, delta, voltage, current float64) float64 {
+ return delta / (2.0 * m.Inductance)
+}
+func (m *Inductor) ModelCurrent(time, delta, voltage, current float64) float64 {
+ return current + voltage*delta/(2*m.Inductance)
+}
+func (m *Inductor) Parameters() map[string]float64 {
+ return map[string]float64{"Inductance": m.Inductance}
+}
+func (m *Inductor) UpdateParameter(name string, value float64) {
+ if name == "Inductance" {
+ m.Inductance = value
+ }
+}
+
+type Diode struct{}
+
+func (m *Diode) ModelConductance(time, delta, voltage, current float64) float64 {
+ return 0
+}
+func (m *Diode) ModelCurrent(time, delta, voltage, current float64) float64 {
+ return 0
+}
+func (m *Diode) Parameters() map[string]float64 {
+ return map[string]float64{}
+}
+func (m *Diode) UpdateParameter(name string, value float64) {}
+
+type Power struct {
+ Current float64
+ Frequency float64
+}
+
+func (m *Power) ModelConductance(time, delta, voltage, current float64) float64 {
+ return 0
+}
+func (m *Power) ModelCurrent(time, delta, voltage, current float64) float64 {
+ return m.Current * math.Sin(time*m.Frequency*2*math.Pi)
+}
+func (m *Power) Parameters() map[string]float64 {
+ return map[string]float64{"Current": m.Current, "Frequency": m.Frequency}
+}
+func (m *Power) UpdateParameter(name string, value float64) {
+ if name == "Current" {
+ m.Current = value
+ } else if name == "Frequency" {
+ m.Frequency = value
+ }
+}
diff --git a/cirsim/node.go b/cirsim/node.go
new file mode 100644
index 0000000..a9e8301
--- /dev/null
+++ b/cirsim/node.go
@@ -0,0 +1,96 @@
+package cirsim
+
+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
+ AComponents []*Component
+ BComponents []*Component
+ Image *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
+ n.VoltageOverTime = make([]float64, 1000)
+ for i := range n.VoltageOverTime {
+ n.VoltageOverTime[i] = 0
+ }
+ n.renderChart()
+ return &n
+}
+
+func (n *Node) renderChart() {
+ graph := chart.Chart{
+ Width: 140,
+ Height: 60,
+ ColorPalette: &nodeColorPalette{},
+ XAxis: chart.HideXAxis(),
+ YAxis: chart.YAxis{
+ Style: chart.Hidden(),
+ Range: n.VoltageRange,
+ },
+ Series: []chart.Series{
+ chart.ContinuousSeries{
+ XValues: chart.LinearRange(0, 999),
+ YValues: n.VoltageOverTime,
+ },
+ },
+ }
+ writer := &chart.ImageWriter{}
+ graph.Render(chart.PNG, writer)
+ img, _ := writer.Image()
+ n.Image = 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: 249, G: 254, B: 172, A: 128}
+}
+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: 247, G: 239, B: 3, A: 255}
+}
+
+func (n *Node) CreateRenderer() fyne.WidgetRenderer { return n }
+func (n *Node) Layout(s fyne.Size) {
+ n.Image.Resize(s)
+ n.Image.Move(fyne.NewPos(0, 0))
+}
+func (n *Node) MinSize() fyne.Size { return n.Image.MinSize() }
+func (n *Node) Refresh() { n.renderChart() }
+func (n *Node) Destroy() {}
+func (n *Node) Objects() []fyne.CanvasObject {
+ return []fyne.CanvasObject{n.Image}
+}
diff --git a/cirsim/simulation.go b/cirsim/simulation.go
new file mode 100644
index 0000000..ca0d841
--- /dev/null
+++ b/cirsim/simulation.go
@@ -0,0 +1,286 @@
+package cirsim
+
+import (
+ "fmt"
+ "image/color"
+ "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"
+)
+
+type Simulation struct {
+ Period float64
+ 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)
+ image := canvas.NewImageFromFile("circuit.svg")
+ file, err := os.Open("circuit")
+ if err != nil {
+ log.Fatal(err)
+ }
+ var sim Simulation
+ sim.Period = 0.01
+ sim.VoltageRange = chart.ContinuousRange{Min: 0, Max: 0}
+ sim.CurrentRange = chart.ContinuousRange{Min: 0, Max: 0}
+ fmt.Fscanf(file, "%f %f\n\n", &sim.Size.Width, &sim.Size.Height)
+ sim.voltageLabel = canvas.NewText(
+ fmt.Sprintf(" %e < voltage < %e ",
+ sim.VoltageRange.Min, sim.VoltageRange.Max),
+ color.RGBA{R: 247, G: 239, B: 3, A: 255},
+ )
+ sim.voltageLabel.TextStyle.Monospace = true
+ sim.currentLabel = canvas.NewText(
+ fmt.Sprintf(" %e < current < %e ",
+ sim.CurrentRange.Min, sim.CurrentRange.Max),
+ color.RGBA{R: 3, G: 247, B: 198, A: 255},
+ )
+ 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("default: 0.01s")
+ sim.periodEntry.OnSubmitted = sim.updatePeriod
+ c := container.New(&sim,
+ container.NewHBox(
+ sim.voltageLabel,
+ sim.currentLabel,
+ layout.NewSpacer(),
+ periodLabel,
+ container.New(&entryLayout{}, sim.periodEntry),
+ ),
+ background,
+ image,
+ )
+ for n := NewNode(file, &sim.VoltageRange); n != nil; n = NewNode(file, &sim.VoltageRange) {
+ sim.Nodes = append(sim.Nodes, n)
+ c.Add(n)
+ }
+ for component := NewComponent(file, &sim.CurrentRange, sim.Nodes, sim.simulate); component != nil; component = NewComponent(file, &sim.CurrentRange, sim.Nodes, sim.simulate) {
+ sim.Components = append(sim.Components, component)
+ c.Add(component)
+ }
+ file.Close()
+ sim.simulate()
+ return c
+}
+
+func (sim *Simulation) simulate() {
+ for _, n := range sim.Nodes {
+ for i := range n.VoltageOverTime {
+ n.VoltageOverTime[i] = 0
+ }
+ }
+ for _, c := range sim.Components {
+ for i := range c.CurrentOverTime {
+ c.CurrentOverTime[i] = 0
+ }
+ }
+ maxv := 0.0
+ minv := 0.0
+ maxc := 0.0
+ minc := 0.0
+ for i := 0; i != 1000; i++ {
+ N := len(sim.Nodes)
+ for refine := 0; refine != 10; refine++ {
+ m := mat.NewDense(N+1, N, nil)
+ v := mat.NewVecDense(N+1, nil)
+ for j, n := range sim.Nodes {
+ for _, c := range sim.Components {
+ if c.ANode == n {
+ v.SetVec(j, v.AtVec(j)-c.Model.ModelCurrent(
+ float64(i)*sim.Period/1000, sim.Period/1000,
+ c.BNode.VoltageOverTime[i]-n.VoltageOverTime[i],
+ c.CurrentOverTime[i]))
+ d := 0
+ for k, v := range sim.Nodes {
+ if c.BNode == v {
+ d = k
+ break
+ }
+ }
+ m.Set(j, d, m.At(j, d)-
+ c.Model.ModelConductance(
+ float64(i)*sim.Period/1000, sim.Period/1000,
+ c.BNode.VoltageOverTime[i]-n.VoltageOverTime[i],
+ c.CurrentOverTime[i]))
+ m.Set(j, j, m.At(j, j)+
+ c.Model.ModelConductance(
+ float64(i)*sim.Period/1000, sim.Period/1000,
+ c.BNode.VoltageOverTime[i]-n.VoltageOverTime[i],
+ c.CurrentOverTime[i]))
+ } else if c.BNode == n {
+ v.SetVec(j, v.AtVec(j)+c.Model.ModelCurrent(
+ float64(i)*sim.Period/1000, sim.Period/1000,
+ n.VoltageOverTime[i]-c.ANode.VoltageOverTime[i],
+ c.CurrentOverTime[i]))
+ d := 0
+ for k, v := range sim.Nodes {
+ if c.ANode == v {
+ d = k
+ break
+ }
+ }
+ m.Set(j, d, m.At(j, d)-
+ c.Model.ModelConductance(
+ float64(i)*sim.Period/1000, sim.Period/1000,
+ n.VoltageOverTime[i]-c.ANode.VoltageOverTime[i],
+ c.CurrentOverTime[i]))
+ m.Set(j, j, m.At(j, j)+
+ c.Model.ModelConductance(
+ float64(i)*sim.Period/1000, sim.Period/1000,
+ n.VoltageOverTime[i]-c.ANode.VoltageOverTime[i],
+ c.CurrentOverTime[i]))
+ }
+ }
+ }
+ r := make([]float64, N)
+ for j := 0; j != N-1; j++ {
+ r[j] = 0
+ }
+ r[N-1] = 1
+ m.SetRow(N, r)
+ v.SetVec(N, 0)
+ res := mat.NewVecDense(N, nil)
+ res.SolveVec(m, v)
+ for j, n := range sim.Nodes {
+ n.VoltageOverTime[i] = res.AtVec(j)
+ }
+ for _, c := range sim.Components {
+ c.CurrentOverTime[i] = (c.ANode.VoltageOverTime[i]-c.BNode.VoltageOverTime[i])*
+ c.Model.ModelConductance(
+ float64(i)*sim.Period/1000, sim.Period/1000,
+ c.BNode.VoltageOverTime[i]-c.ANode.VoltageOverTime[i],
+ c.CurrentOverTime[i]) +
+ c.Model.ModelCurrent(
+ float64(i)*sim.Period/1000, sim.Period/1000,
+ c.BNode.VoltageOverTime[i]-c.ANode.VoltageOverTime[i],
+ c.CurrentOverTime[i])
+ }
+ }
+ if i == 999 {
+ break
+ }
+ for _, n := range sim.Nodes {
+ n.VoltageOverTime[i+1] = n.VoltageOverTime[i]
+ if n.VoltageOverTime[i] > maxv {
+ maxv = n.VoltageOverTime[i]
+ } else if n.VoltageOverTime[i] < minv {
+ minv = n.VoltageOverTime[i]
+ }
+ }
+ for _, c := range sim.Components {
+ c.CurrentOverTime[i+1] = c.CurrentOverTime[i]
+ if c.CurrentOverTime[i] > maxc {
+ maxc = c.CurrentOverTime[i]
+ } else if c.CurrentOverTime[i] < minc {
+ minc = c.CurrentOverTime[i]
+ }
+ }
+ }
+ sim.VoltageRange.Max = maxv
+ sim.VoltageRange.Min = minv
+ sim.CurrentRange.Max = maxc
+ sim.CurrentRange.Min = minc
+ 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) 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.simulate()
+ }
+}
+
+func (l *Simulation) MinSize(objects []fyne.CanvasObject) fyne.Size {
+ s := fyne.NewSize(0, 0)
+ for _, i := range objects {
+ switch o := i.(type) {
+ case *canvas.Image:
+ s = o.MinSize()
+ default:
+ }
+ }
+ return s
+}
+
+func (l *Simulation) Layout(obs []fyne.CanvasObject, size fyne.Size) {
+ var p fyne.Position
+ var s fyne.Size
+ panel_height := obs[0].MinSize().Height
+ H := size.Height - panel_height
+ 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+panel_height)
+ s = fyne.NewSize(W, W*h/w)
+ scale = W / w
+ } else {
+ p = fyne.NewPos((W-H*w/h)/2, panel_height)
+ s = fyne.NewSize(H*w/h, H)
+ scale = H / h
+ }
+ obs[0].Resize(fyne.NewSize(W, panel_height))
+ obs[0].Move(fyne.NewPos(0, 0))
+ obs[1].Resize(size)
+ obs[1].Move(fyne.NewPos(0, panel_height))
+ obs[2].Resize(s)
+ obs[2].Move(p)
+ for i, n := range l.Nodes {
+ obs[3+i].Resize(fyne.NewSize(140, 60))
+ shift := fyne.NewPos(p.X+n.Pos.X*scale-70, p.Y+n.Pos.Y*scale-30)
+ 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-70, p.Y+c.Pos.Y*scale-ms.Height+30)
+ 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*4,
+ 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/go.mod b/go.mod
new file mode 100644
index 0000000..2249339
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,30 @@
+module git.veresov.xyz/aversey/cirsim
+
+go 1.18
+
+require (
+ fyne.io/fyne/v2 v2.1.4
+ github.com/wcharczuk/go-chart/v2 v2.1.0
+ gonum.org/v1/gonum v0.11.0
+)
+
+require (
+ github.com/davecgh/go-spew v1.1.1 // indirect
+ github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3 // indirect
+ github.com/fsnotify/fsnotify v1.5.1 // indirect
+ github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6 // indirect
+ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20220320163800-277f93cfa958 // indirect
+ github.com/godbus/dbus/v5 v5.1.0 // indirect
+ github.com/goki/freetype v0.0.0-20220119013949-7a161fd3728c // indirect
+ github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
+ github.com/pmezard/go-difflib v1.0.0 // indirect
+ github.com/srwiley/oksvg v0.0.0-20220128195007-1f435e4c2b44 // indirect
+ github.com/srwiley/rasterx v0.0.0-20220128185129-2efea2b9ea41 // indirect
+ github.com/stretchr/testify v1.7.1 // indirect
+ github.com/yuin/goldmark v1.4.11 // indirect
+ golang.org/x/image v0.0.0-20220413100746-70e8d0d3baa9 // indirect
+ golang.org/x/net v0.0.0-20220412020605-290c469a71a5 // indirect
+ golang.org/x/sys v0.0.0-20220412211240-33da011f77ad // indirect
+ golang.org/x/text v0.3.7 // indirect
+ gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
+)
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..de78981
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,113 @@
+fyne.io/fyne/v2 v2.1.4 h1:bt1+28++kAzRzPB0GM2EuSV4cnl8rXNX4cjfd8G06Rc=
+fyne.io/fyne/v2 v2.1.4/go.mod h1:p+E/Dh+wPW8JwR2DVcsZ9iXgR9ZKde80+Y+40Is54AQ=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
+github.com/Kodeworks/golang-image-ico v0.0.0-20141118225523-73f0f4cfade9/go.mod h1:7uhhqiBaR4CpN0k9rMjOtjpcfGd6DG2m04zQxKnWQ0I=
+github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
+github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3 h1:FDqhDm7pcsLhhWl1QtD8vlzI4mm59llRvNzrFg6/LAA=
+github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3/go.mod h1:CzM2G82Q9BDUvMTGHnXf/6OExw/Dz2ivDj48nVg7Lg8=
+github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
+github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI=
+github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
+github.com/go-gl/gl v0.0.0-20210813123233-e4099ee2221f/go.mod h1:wjpnOv6ONl2SuJSxqCPVaPZibGFdSci9HFocT9qtVYM=
+github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6 h1:zDw5v7qm4yH7N8C8uWd+8Ii9rROdgWxQuGoJ9WDXxfk=
+github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6/go.mod h1:9YTyiznxEY1fVinfM7RvRcjRHbw2xLBJ3AAGIT0I4Nw=
+github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211024062804-40e447a793be/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
+github.com/go-gl/glfw/v3.3/glfw v0.0.0-20220320163800-277f93cfa958 h1:TL70PMkdPCt9cRhKTqsm+giRpgrd0IGEj763nNr2VFY=
+github.com/go-gl/glfw/v3.3/glfw v0.0.0-20220320163800-277f93cfa958/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
+github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
+github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
+github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
+github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
+github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff/go.mod h1:wfqRWLHRBsRgkp5dmbG56SA0DmVtwrF5N3oPdI8t+Aw=
+github.com/goki/freetype v0.0.0-20220119013949-7a161fd3728c h1:JGCm/+tJ9gC6THUxooTldS+CUDsba0qvkvU3DHklqW8=
+github.com/goki/freetype v0.0.0-20220119013949-7a161fd3728c/go.mod h1:wfqRWLHRBsRgkp5dmbG56SA0DmVtwrF5N3oPdI8t+Aw=
+github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
+github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
+github.com/jackmordaunt/icns v0.0.0-20181231085925-4f16af745526/go.mod h1:UQkeMHVoNcyXYq9otUupF7/h/2tmHlhrS2zw7ZVvUqc=
+github.com/josephspurrier/goversioninfo v0.0.0-20200309025242-14b0ab84c6ca/go.mod h1:eJTEwMjXb7kZ633hO3Ln9mBUCOjX2+FlTljvpl9SYdE=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/lucor/goinfo v0.0.0-20210802170112-c078a2b0f08b/go.mod h1:PRq09yoB+Q2OJReAmwzKivcYyremnibWGbK7WfftHzc=
+github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
+github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
+github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
+github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
+github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
+github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
+github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564/go.mod h1:afMbS0qvv1m5tfENCwnOdZGOF8RGR/FsZ7bvBxQGZG4=
+github.com/srwiley/oksvg v0.0.0-20220128195007-1f435e4c2b44 h1:XPYXKIuH/n5zpUoEWk2jWV/SjEMNYmqDYmTgbjmhtaI=
+github.com/srwiley/oksvg v0.0.0-20220128195007-1f435e4c2b44/go.mod h1:cNQ3dwVJtS5Hmnjxy6AgTPd0Inb3pW05ftPSX7NZO7Q=
+github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9/go.mod h1:mvWM0+15UqyrFKqdRjY6LuAVJR0HOVhJlEgZ5JWtSWU=
+github.com/srwiley/rasterx v0.0.0-20220128185129-2efea2b9ea41 h1:YR16ysw3I1bqwtEcYV9dpvhHEe7j55hIClkLoAqY31I=
+github.com/srwiley/rasterx v0.0.0-20220128185129-2efea2b9ea41/go.mod h1:nXTWP6+gD5+LUJ8krVhhoeHjvHTutPxMYl5SvkcnJNE=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
+github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
+github.com/wcharczuk/go-chart/v2 v2.1.0 h1:tY2slqVQ6bN+yHSnDYwZebLQFkphK4WNrVwnt7CJZ2I=
+github.com/wcharczuk/go-chart/v2 v2.1.0/go.mod h1:yx7MvAVNcP/kN9lKXM/NTce4au4DFN99j6i1OwDclNA=
+github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
+github.com/yuin/goldmark v1.3.8/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
+github.com/yuin/goldmark v1.4.11 h1:i45YIzqLnUc2tGaTlJCyUxSG8TvgyGqhqOZOUKIjJ6w=
+github.com/yuin/goldmark v1.4.11/go.mod h1:rmuwmfZ0+bvzB24eSC//bk1R1Zp3hM0OXYv/G2LIilg=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3 h1:n9HxLrNxWWtEb1cA950nuEEj3QnKbtsCJ6KjcgisNUs=
+golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
+golang.org/x/image v0.0.0-20200927104501-e162460cd6b5/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
+golang.org/x/image v0.0.0-20220413100746-70e8d0d3baa9 h1:LRtI4W37N+KFebI/qV0OFiLUv4GLOWeEW5hn/KEJvxE=
+golang.org/x/image v0.0.0-20220413100746-70e8d0d3baa9/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
+golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
+golang.org/x/net v0.0.0-20220412020605-290c469a71a5 h1:bRb386wvrE+oBNdF1d/Xh9mQrfQ4ecYhW5qJ5GvTGT4=
+golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0=
+golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+gonum.org/v1/gonum v0.11.0 h1:f1IJhK4Km5tBJmaiJXtk/PkL4cdVX6J+tGiM187uT5E=
+gonum.org/v1/gonum v0.11.0/go.mod h1:fSG4YDCxxUZQJ7rKsQrj0gMOg00Il0Z96/qMA4bVQhA=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
+gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
+gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/main.go b/main.go
new file mode 100644
index 0000000..23474fa
--- /dev/null
+++ b/main.go
@@ -0,0 +1,18 @@
+package main
+
+import (
+ "fyne.io/fyne/v2"
+ "fyne.io/fyne/v2/app"
+ "fyne.io/fyne/v2/theme"
+ "git.veresov.xyz/aversey/cirsim/cirsim"
+)
+
+func main() {
+ a := app.New()
+ a.Settings().SetTheme(theme.LightTheme())
+ w := a.NewWindow("Circuit Simulator")
+ w.Resize(fyne.NewSize(1280, 720))
+ w.SetIcon(theme.SettingsIcon())
+ w.SetContent(cirsim.New())
+ w.ShowAndRun()
+}