Skip to content

Commit 3ae00c3

Browse files
committed
refactor for more robust, multi-platform export
1 parent 830adef commit 3ae00c3

File tree

6 files changed

+226
-17
lines changed

6 files changed

+226
-17
lines changed

tools/main.go

Lines changed: 104 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ const (
2525
startingWidth int = 1200
2626
startingHeight int = 800
2727
editorVertOffset = 30
28-
targetPadding = 10
2928
)
3029

3130
var (
@@ -101,6 +100,10 @@ func loop(w *app.Window, th *material.Theme, m *model.Model, ec *EditContext, wi
101100
// gtx is used to pass around rendering and event information.
102101
gtx := app.NewContext(ops, e)
103102

103+
if m.PxPerDp == 0 {
104+
m.PxPerDp = gtx.Metric.PxPerDp
105+
}
106+
104107
ec.windowSize = utils.GlobalDim{W: gtx.Constraints.Max.X, H: gtx.Constraints.Max.Y}
105108

106109
// handle scrolling to zoom
@@ -130,7 +133,7 @@ func loop(w *app.Window, th *material.Theme, m *model.Model, ec *EditContext, wi
130133

131134
// draw the model
132135
if !ec.lazyUpdate {
133-
CalculateModel(m, gtx)
136+
model.CalculateModel(m, gtx)
134137
}
135138
DrawModel(ops, gtx, m, ec)
136139

@@ -405,3 +408,102 @@ func WithinConnection(pos image.Point, c *model.Connection, ec *EditContext, tol
405408
}
406409
return utils.WithinLine(pos, posA, posB, hitRadius)
407410
}
411+
412+
// DrawModel draws the path diagram
413+
func DrawModel(ops *op.Ops, gtx layout.Context, m *model.Model, ec *EditContext) {
414+
for _, n := range m.Nodes {
415+
if !n.Visible {
416+
continue
417+
}
418+
419+
switch n.Class {
420+
case model.OBSERVED:
421+
utils.DrawRect(
422+
ops,
423+
n.Pos.ToGlobal(ec.scaleFactor, ec.viewportCenter, ec.windowSize),
424+
n.Dim.ToGlobal(ec.scaleFactor),
425+
n.Col,
426+
n.Thickness*ec.scaleFactor,
427+
)
428+
case model.LATENT:
429+
utils.DrawEllipse(
430+
ops,
431+
n.Pos.ToGlobal(ec.scaleFactor, ec.viewportCenter, ec.windowSize),
432+
n.Dim.ToGlobal(ec.scaleFactor),
433+
n.Col,
434+
n.Thickness*ec.scaleFactor,
435+
)
436+
}
437+
438+
textOffset := utils.LocalDim{W: n.Dim.W/2.0 - n.Padding, H: m.Font.Size / (1.5 / m.PxPerDp)} // I think 1.5 is a magic number
439+
utils.DrawText(
440+
ops,
441+
gtx,
442+
n.Pos.SubDim(textOffset).ToGlobal(ec.scaleFactor, ec.viewportCenter, ec.windowSize),
443+
n.Text,
444+
m.Font.Face,
445+
unit.Sp(m.Font.Size),
446+
ec.scaleFactor,
447+
)
448+
}
449+
450+
for _, c := range m.Connections {
451+
if !c.UserDefined && !m.ViewGenerated {
452+
continue
453+
}
454+
455+
switch c.Type {
456+
case model.STRAIGHT:
457+
utils.DrawArrowLine(
458+
ops,
459+
c.OriginPos.ToGlobal(ec.scaleFactor, ec.viewportCenter, ec.windowSize),
460+
c.DestinationPos.ToGlobal(ec.scaleFactor, ec.viewportCenter, ec.windowSize),
461+
c.Col,
462+
c.Thickness*ec.scaleFactor,
463+
ec.windowSize,
464+
)
465+
case model.CURVED:
466+
utils.DrawArrowCurve(
467+
ops,
468+
c.OriginPos.ToGlobal(ec.scaleFactor, ec.viewportCenter, ec.windowSize),
469+
c.DestinationPos.ToGlobal(ec.scaleFactor, ec.viewportCenter, ec.windowSize),
470+
c.Col,
471+
c.Thickness*ec.scaleFactor,
472+
c.Curvature,
473+
ec.windowSize,
474+
)
475+
case model.CIRCULAR:
476+
utils.DrawArrowArc(
477+
ops,
478+
c.OriginPos.ToGlobal(ec.scaleFactor, ec.viewportCenter, ec.windowSize),
479+
c.DestinationPos.ToGlobal(ec.scaleFactor, ec.viewportCenter, ec.windowSize),
480+
c.RefPos.ToGlobal(ec.scaleFactor, ec.viewportCenter, ec.windowSize),
481+
model.VarianceRadius*ec.scaleFactor,
482+
c.Col,
483+
c.Thickness*ec.scaleFactor,
484+
ec.windowSize,
485+
)
486+
}
487+
}
488+
// draw estimate labels after ALL of the connections to ensure proper layering
489+
for _, c := range m.Connections {
490+
if !c.UserDefined && !m.ViewGenerated {
491+
continue
492+
}
493+
494+
if m.CoeffDisplay != utils.NONE {
495+
utils.DrawEstimate(
496+
ops,
497+
gtx,
498+
c.EstPos.ToGlobal(ec.scaleFactor, ec.viewportCenter, ec.windowSize),
499+
m.Font.Face,
500+
m.Font.Size,
501+
ec.scaleFactor,
502+
c.EstPadding,
503+
c.EstText,
504+
c.EstDim,
505+
c.EstWidth,
506+
)
507+
}
508+
}
509+
}

tools/model/calculate_layout.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package main
1+
package model
22

33
import (
44
"main/utils"

tools/model/clone.go

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package model
2+
3+
func (m *Model) Clone() *Model {
4+
if m == nil {
5+
return nil
6+
}
7+
8+
// Create node mapping to maintain pointer relationships
9+
nodeMap := make(map[string]*Node)
10+
11+
// Deep copy all nodes first
12+
newNodes := make([]*Node, len(m.Nodes))
13+
for i, n := range m.Nodes {
14+
newNode := n.Clone()
15+
newNodes[i] = newNode
16+
nodeMap[n.VarName] = newNode
17+
}
18+
19+
// Deep copy connections, remapping node pointers
20+
newConnections := make([]*Connection, len(m.Connections))
21+
for i, c := range m.Connections {
22+
newConnections[i] = c.Clone(nodeMap)
23+
}
24+
25+
return &Model{
26+
Nodes: newNodes,
27+
Connections: newConnections,
28+
Font: m.Font,
29+
CoeffDisplay: m.CoeffDisplay,
30+
ViewGenerated: m.ViewGenerated,
31+
PxPerDp: m.PxPerDp,
32+
}
33+
}
34+
35+
// deepCopy creates a deep copy of a Node
36+
func (n *Node) Clone() *Node {
37+
return &Node{
38+
Class: n.Class,
39+
Pos: n.Pos,
40+
Dim: n.Dim,
41+
Col: n.Col,
42+
VarName: n.VarName,
43+
Text: n.Text,
44+
TextWidth: n.TextWidth,
45+
Bold: n.Bold,
46+
Thickness: n.Thickness,
47+
UserDefined: n.UserDefined,
48+
Visible: n.Visible,
49+
Padding: n.Padding,
50+
}
51+
}
52+
53+
// deepCopy creates a deep copy of a Connection, remapping node pointers
54+
func (c *Connection) Clone(nodeMap map[string]*Node) *Connection {
55+
newConn := &Connection{
56+
OriginPos: c.OriginPos,
57+
DestinationPos: c.DestinationPos,
58+
RefPos: c.RefPos,
59+
VarianceAngle: c.VarianceAngle,
60+
Angle: c.Angle,
61+
Col: c.Col,
62+
Thickness: c.Thickness,
63+
Type: c.Type,
64+
EstPos: c.EstPos,
65+
EstDim: c.EstDim,
66+
EstPadding: c.EstPadding,
67+
EstWidth: c.EstWidth,
68+
AlongLineProp: c.AlongLineProp,
69+
Est: c.Est,
70+
PValue: c.PValue,
71+
CI: c.CI,
72+
EstText: c.EstText,
73+
Bold: c.Bold,
74+
Curvature: c.Curvature,
75+
UserDefined: c.UserDefined,
76+
}
77+
78+
// Remap node pointers to the new copies
79+
newConn.Origin = nodeMap[c.Origin.VarName]
80+
newConn.Destination = nodeMap[c.Destination.VarName]
81+
82+
return newConn
83+
}

tools/model/data_structures.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,4 +77,5 @@ type Model struct {
7777
Font FontSettings `json:"font"`
7878
CoeffDisplay utils.CoefficientDisplay `json:"coeff_display,omitempty"`
7979
ViewGenerated bool `json:"view_generated,omitempty"`
80+
PxPerDp float32 `json:"px_per_dp,omitempty"`
8081
}

tools/pdf/export.go

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,16 @@ const (
2424
var pdfFontFS embed.FS
2525

2626
func ExportModel(m *model.Model, filePath string) {
27-
rect, localDim := GetModelSize(m)
27+
// check for different PxPerDp. If not one, copy the model and apply a transformation
28+
var mAdj *model.Model
29+
if m.PxPerDp == 1.0 {
30+
mAdj = m
31+
} else {
32+
mAdj = transformModel(m)
33+
model.CalculateModel(mAdj, layout.Context{})
34+
}
35+
36+
rect, localDim := GetModelSize(mAdj)
2837

2938
pageWidth := localDim.W*ppRatio + 2*docPadding
3039
pageHeight := localDim.H*ppRatio + 2*docPadding
@@ -45,7 +54,7 @@ func ExportModel(m *model.Model, filePath string) {
4554
offsetX := docPadding - rect[0].X
4655
offsetY := docPadding - rect[0].Y
4756

48-
for _, n := range m.Nodes {
57+
for _, n := range mAdj.Nodes {
4958
if !n.Visible {
5059
continue
5160
}
@@ -75,8 +84,8 @@ func ExportModel(m *model.Model, filePath string) {
7584
DrawText(pdf, textPos, n.Text, m.Font.Family, n.Bold, m.Font.Size, ppRatio)
7685
}
7786

78-
for _, c := range m.Connections {
79-
// adjust connection points to PDS coords
87+
for _, c := range mAdj.Connections {
88+
// convert connection points to PDF coords
8089
originPos := utils.LocalPos{
8190
X: (c.OriginPos.X + offsetX) * ppRatio,
8291
Y: (c.OriginPos.Y + offsetY) * ppRatio,
@@ -101,8 +110,8 @@ func ExportModel(m *model.Model, filePath string) {
101110
}
102111
}
103112

104-
// draw estimate labels after ALL of the connections to ensure proper layering
105-
for _, c := range m.Connections {
113+
// draw estimate labels after all the connections to ensure proper layering
114+
for _, c := range mAdj.Connections {
106115
textWidth := utils.GetTextWidth(c.EstText, m.Font.Face, (m.Font.Size-2)*ppRatio, layout.Context{}) + (c.EstPadding * ppRatio)
107116
textPos := utils.LocalPos{
108117
X: (c.EstPos.X+offsetX)*ppRatio - textWidth/2 - textAdj,
@@ -114,7 +123,7 @@ func ExportModel(m *model.Model, filePath string) {
114123
Y: (c.EstPos.Y - c.EstDim.H/2 + offsetY) * ppRatio,
115124
}
116125

117-
rectDim := c.EstDim.Mul(ppRatio)
126+
rectDim := c.EstDim.Div(m.PxPerDp).Mul(ppRatio)
118127

119128
DrawRect(pdf, rectPos, rectDim, color.NRGBA{255, 255, 255, 255}, 0)
120129
DrawText(pdf, textPos, c.EstText, m.Font.Family, false, m.Font.Size-2, ppRatio)
@@ -216,3 +225,11 @@ func createTempFontDir() string {
216225

217226
return tempDir
218227
}
228+
229+
func transformModel(m *model.Model) *model.Model {
230+
res := m.Clone()
231+
for _, n := range res.Nodes {
232+
n.Dim = n.Dim.Div(m.PxPerDp)
233+
}
234+
return res
235+
}

tools/read_write/read_json.go

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -131,8 +131,7 @@ func ModelFromJSON(dir, projectName string) *model.Model {
131131
rhs.Thickness = 3.0
132132
c.Thickness = 2.0
133133

134-
lhs.Padding = estPadding
135-
rhs.Padding = estPadding
134+
c.EstPadding = estPadding
136135

137136
c.Curvature = roundness
138137
c.AlongLineProp = propAlongLine
@@ -158,12 +157,19 @@ func ModelFromJSON(dir, projectName string) *model.Model {
158157

159158
}
160159

161-
m.Font = model.FontSettings{
162-
Family: "sans",
163-
Size: 16,
164-
Face: utils.LoadSansFontFace()[0],
160+
if mExisting != nil {
161+
m.CoeffDisplay = mExisting.CoeffDisplay
162+
m.Font = mExisting.Font
163+
m.PxPerDp = mExisting.PxPerDp
164+
} else {
165+
m.Font = model.FontSettings{
166+
Family: "sans",
167+
Size: 16,
168+
Face: utils.LoadSansFontFace()[0],
169+
}
170+
m.CoeffDisplay = utils.STAR
165171
}
166-
m.CoeffDisplay = utils.STAR
172+
167173
m.Connections = connections
168174
m.Nodes = utils.MapValsToSlice(varMap)
169175
m.Network = CalculateNodeNetwork(connections)

0 commit comments

Comments
 (0)