forked from mum4k/termdash
-
Notifications
You must be signed in to change notification settings - Fork 0
/
hv_line_graph.go
206 lines (181 loc) · 5.7 KB
/
hv_line_graph.go
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
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
// Copyright 2018 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package draw
// hv_line_graph.go helps to keep track of locations where lines cross.
import (
"fmt"
"image"
"github.com/mum4k/termdash/linestyle"
)
// hVLineEdge is an edge between two points on the graph.
type hVLineEdge struct {
// from is the starting node of this edge.
// From is guaranteed to be less than to.
from image.Point
// to is the ending point of this edge.
to image.Point
}
// newHVLineEdge returns a new edge between the two points.
func newHVLineEdge(from, to image.Point) hVLineEdge {
return hVLineEdge{
from: from,
to: to,
}
}
// hVLineNode represents one node in the graph.
// I.e. one cell.
type hVLineNode struct {
// p is the point where this node is.
p image.Point
// edges are the edges between this node and the surrounding nodes.
// The code only supports horizontal and vertical lines so there can only
// ever be edges to nodes on these planes.
edges map[hVLineEdge]bool
}
// newHVLineNode creates a new newHVLineNode.
func newHVLineNode(p image.Point) *hVLineNode {
return &hVLineNode{
p: p,
edges: map[hVLineEdge]bool{},
}
}
// hasDown determines if this node has an edge to the one below it.
func (n *hVLineNode) hasDown() bool {
target := newHVLineEdge(n.p, image.Point{n.p.X, n.p.Y + 1})
_, ok := n.edges[target]
return ok
}
// hasUp determines if this node has an edge to the one above it.
func (n *hVLineNode) hasUp() bool {
target := newHVLineEdge(image.Point{n.p.X, n.p.Y - 1}, n.p)
_, ok := n.edges[target]
return ok
}
// hasLeft determines if this node has an edge to the next node on the left.
func (n *hVLineNode) hasLeft() bool {
target := newHVLineEdge(image.Point{n.p.X - 1, n.p.Y}, n.p)
_, ok := n.edges[target]
return ok
}
// hasRight determines if this node has an edge to the next node on the right.
func (n *hVLineNode) hasRight() bool {
target := newHVLineEdge(n.p, image.Point{n.p.X + 1, n.p.Y})
_, ok := n.edges[target]
return ok
}
// rune, given the selected line style returns the correct line character to
// represent this node.
// Only handles nodes with two or more edges, as returned by multiEdgeNodes().
func (n *hVLineNode) rune(ls linestyle.LineStyle) (rune, error) {
parts, err := lineParts(ls)
if err != nil {
return -1, err
}
switch len(n.edges) {
case 2:
switch {
case n.hasLeft() && n.hasRight():
return parts[hLine], nil
case n.hasUp() && n.hasDown():
return parts[vLine], nil
case n.hasDown() && n.hasRight():
return parts[topLeftCorner], nil
case n.hasDown() && n.hasLeft():
return parts[topRightCorner], nil
case n.hasUp() && n.hasRight():
return parts[bottomLeftCorner], nil
case n.hasUp() && n.hasLeft():
return parts[bottomRightCorner], nil
default:
return -1, fmt.Errorf("unexpected two edges in node representing point %v: %v", n.p, n.edges)
}
case 3:
switch {
case n.hasUp() && n.hasLeft() && n.hasRight():
return parts[hAndUp], nil
case n.hasDown() && n.hasLeft() && n.hasRight():
return parts[hAndDown], nil
case n.hasUp() && n.hasDown() && n.hasRight():
return parts[vAndRight], nil
case n.hasUp() && n.hasDown() && n.hasLeft():
return parts[vAndLeft], nil
default:
return -1, fmt.Errorf("unexpected three edges in node representing point %v: %v", n.p, n.edges)
}
case 4:
return parts[vAndH], nil
default:
return -1, fmt.Errorf("unexpected number of edges(%d) in node representing point %v", len(n.edges), n.p)
}
}
// hVLineGraph represents lines on the canvas as a bidirectional graph of
// nodes. Helps to determine the characters that should be used where multiple
// lines cross.
type hVLineGraph struct {
nodes map[image.Point]*hVLineNode
}
// newHVLineGraph creates a new hVLineGraph.
func newHVLineGraph() *hVLineGraph {
return &hVLineGraph{
nodes: make(map[image.Point]*hVLineNode),
}
}
// getOrCreateNode gets an existing or creates a new node for the point.
func (g *hVLineGraph) getOrCreateNode(p image.Point) *hVLineNode {
if n, ok := g.nodes[p]; ok {
return n
}
n := newHVLineNode(p)
g.nodes[p] = n
return n
}
// addLine adds a line to the graph.
// This adds edges between all the points on the line.
func (g *hVLineGraph) addLine(line *hVLine) {
switch {
case line.horizontal():
for curX := line.start.X; curX < line.end.X; curX++ {
from := image.Point{curX, line.start.Y}
to := image.Point{curX + 1, line.start.Y}
n1 := g.getOrCreateNode(from)
n2 := g.getOrCreateNode(to)
edge := newHVLineEdge(from, to)
n1.edges[edge] = true
n2.edges[edge] = true
}
case line.vertical():
for curY := line.start.Y; curY < line.end.Y; curY++ {
from := image.Point{line.start.X, curY}
to := image.Point{line.start.X, curY + 1}
n1 := g.getOrCreateNode(from)
n2 := g.getOrCreateNode(to)
edge := newHVLineEdge(from, to)
n1.edges[edge] = true
n2.edges[edge] = true
}
}
}
// multiEdgeNodes returns all nodes that have more than one edge. These are
// the nodes where we might need to use different line characters to represent
// the crossing of multiple lines.
func (g *hVLineGraph) multiEdgeNodes() []*hVLineNode {
var nodes []*hVLineNode
for _, n := range g.nodes {
if len(n.edges) <= 1 {
continue
}
nodes = append(nodes, n)
}
return nodes
}