-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathnode.go
More file actions
182 lines (158 loc) · 4.86 KB
/
Copy pathnode.go
File metadata and controls
182 lines (158 loc) · 4.86 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
package layout
import (
"math"
"strings"
"unicode"
)
// Node is a vertex in a graph. Radius is half the node size; Center is
// filled in by layouting.
type Node struct {
ID string
Label string
Tooltip string
FontName string
FontSize Length
FontColor Color
LineWidth Length
LineColor Color
LineStyle LineStyle
// Peripheries is the number of outlines; 0 and 1 draw one
Peripheries int
Shape Shape
FillColor Color
// Radius is the minimum half size; the label can grow it unless
// FixedSize is set
Radius Vector
// peripheryPad is the padding added to Radius for extra peripheries,
// removed again before the next layout recomputes it
peripheryPad Vector
FixedSize bool
// Image is a URL drawn inside the node
Image string
// computed in layouting
Center Vector
}
// NewNode creates a node with the given id and default styling.
func NewNode(id string) *Node {
node := &Node{}
node.ID = id
node.LineWidth = Point
return node
}
// String returns the node id, or its label when there is no id.
func (node *Node) String() string {
if node == nil {
return "?"
}
if node.ID != "" {
return node.ID
}
return node.Label
}
// DefaultLabel returns the label, falling back to the id.
func (node *Node) DefaultLabel() string {
if node.Label != "" {
return node.Label
}
return node.ID
}
// textRadius returns the half size of multi-line text, measuring each
// line with graph.MeasureText or the built-in approximation.
func (graph *Graph) textRadius(text string, fontName string, fontSize Length) Vector {
lineHeight := graph.LineHeight
if lineHeight < fontSize {
lineHeight = fontSize
}
measure := graph.MeasureText
if measure == nil {
measure = approxTextWidth
}
size := Vector{}
lines := strings.Split(text, "\n")
for _, line := range lines {
size.X = max(size.X, measure(line, fontName, fontSize).X)
}
size.Y = Length(len(lines)) * lineHeight * 0.5
return size
}
// approxTextWidth estimates the half size of one line of proportional text
// from per-character width classes.
func approxTextWidth(line string, _ string, fontSize Length) Vector {
width := Length(0)
for _, r := range line {
var em Length
switch {
case strings.ContainsRune("il.,:;'|!I", r):
em = 0.28
case strings.ContainsRune("jtfr ()[]-", r):
em = 0.36
case strings.ContainsRune("mwMW@", r):
em = 0.85
case unicode.IsUpper(r):
em = 0.68
default:
em = 0.52
}
width += em * fontSize
}
return Vector{X: width / 2, Y: fontSize / 2}
}
// TopLeft returns the top left corner of the node bounds.
func (node *Node) TopLeft() Vector { return Vector{node.Left(), node.Top()} }
// BottomRight returns the bottom right corner of the node bounds.
func (node *Node) BottomRight() Vector { return Vector{node.Right(), node.Bottom()} }
// TopCenter returns the middle of the top edge of the node bounds.
func (node *Node) TopCenter() Vector { return Vector{node.Center.X, node.Top()} }
// BottomCenter returns the middle of the bottom edge of the node bounds.
func (node *Node) BottomCenter() Vector { return Vector{node.Center.X, node.Bottom()} }
// Left returns the x coordinate of the left side of the node bounds.
func (node *Node) Left() Length { return node.Center.X - node.Radius.X }
// Top returns the y coordinate of the top side of the node bounds.
func (node *Node) Top() Length { return node.Center.Y - node.Radius.Y }
// Right returns the x coordinate of the right side of the node bounds.
func (node *Node) Right() Length { return node.Center.X + node.Radius.X }
// Bottom returns the y coordinate of the bottom side of the node bounds.
func (node *Node) Bottom() Length { return node.Center.Y + node.Radius.Y }
// CompassPoint returns the point on the node outline at the compass
// direction, or the center for Center and CompassAuto.
func (node *Node) CompassPoint(c Compass) Vector {
var dir Vector
switch c {
case North:
dir = Vector{0, -1}
case NorthEast:
dir = Vector{1, -1}
case East:
dir = Vector{1, 0}
case SouthEast:
dir = Vector{1, 1}
case South:
dir = Vector{0, 1}
case SouthWest:
dir = Vector{-1, 1}
case West:
dir = Vector{-1, 0}
case NorthWest:
dir = Vector{-1, -1}
default:
return node.Center
}
return node.Boundary(Vector{node.Center.X + dir.X*node.Radius.X, node.Center.Y + dir.Y*node.Radius.Y})
}
// Boundary returns the point on the node outline where the ray from the
// center towards p exits the node.
func (node *Node) Boundary(p Vector) Vector {
dx, dy := float64(p.X-node.Center.X), float64(p.Y-node.Center.Y)
if dx == 0 && dy == 0 {
return node.Center
}
rx, ry := float64(node.Radius.X), float64(node.Radius.Y)
var t float64
switch node.Shape {
case Box, Square, Record:
t = math.Min(rx/math.Abs(dx), ry/math.Abs(dy)) // ray-rect; Inf for zero component is fine
default: // ellipse and circle
t = 1 / math.Hypot(dx/rx, dy/ry)
}
return Vector{node.Center.X + Length(dx*t), node.Center.Y + Length(dy*t)}
}