-
Notifications
You must be signed in to change notification settings - Fork 0
/
draw_context.py
155 lines (127 loc) · 7.02 KB
/
draw_context.py
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
from __future__ import annotations
from collections import UserDict
from collections.abc import Sequence
from typing import Any, Optional, TYPE_CHECKING
import numpy as np
from matplotlib.axes import Axes
from matplotlib.patches import Circle, FancyBboxPatch, Rectangle
from cell import House
if TYPE_CHECKING:
from feature import Square
class DrawContext(UserDict[Any, Any]):
_axis: Axes
done: bool
result: bool
draw_normal_boxes: bool
width: int
def __init__(self, axis: Axes, *, done: bool, result: bool) -> None:
super().__init__()
self._axis = axis
self.done = done
self.result = result
self.draw_normal_boxes = True
self.width = 4
def draw_circle(self, center: tuple[float, float], radius: float, **args: Any) -> None:
self._axis.add_patch(Circle(center, radius=radius, **args))
def draw_text(self, x: float, y: float, text: str, **args: Any) -> None:
self._axis.text(x, y, text, **args)
def draw_rectangle(self, corner: tuple[float, float], *, width: float, height: float, **args: Any) -> None:
self._axis.add_patch(Rectangle(corner, width=width, height=height, **args))
def draw_rectangles(self, points: Sequence[Square], **args: Any) -> None:
args = {'facecolor': 'lightgrey', 'fill': True, **args}
for row, column in points:
self._axis.add_patch(Rectangle((column, row), width=1, height=1, **args))
def draw_line(self, squares: Sequence[Square], *, closed: bool = False,
start_offset: float = 0.0,
end_offset: float = 0.0,
offset: float = 0.0,
**kwargs: Any) -> None:
ys = [row + .5 for row, _ in squares]
xs = [column + .5 for _, column in squares]
if closed:
ys.append(ys[0])
xs.append(xs[0])
start_offset = start_offset or offset
end_offset = end_offset or offset
if start_offset:
point1, point2 = np.array((xs[0], ys[0])), np.array((xs[1], ys[1]))
delta = point2 - point1
distance = np.sqrt(np.sum(delta**2))
point1 += delta * (offset / distance)
xs[0], ys[0] = point1
if end_offset:
point1, point2 = np.array((xs[-1], ys[-1])), np.array((xs[-2], ys[-2]))
delta = point2 - point1
distance = np.sqrt(np.sum(delta**2))
point1 += delta * (offset / distance)
xs[-1], ys[-1] = point1
self._axis.plot(xs, ys, **{'color': 'black', **kwargs})
def plot(self, xs: Any, ys: Any, **args: Any) -> None:
self._axis.plot(xs, ys, **args)
def draw_arrow(self, x: float, y: float, dx: float, dy: float, *, color: Optional[str] = None, **kwargs: Any
) -> None:
self._axis.annotate('', xytext=(x, y), xy=(x + dx, y + dy),
arrowprops=dict(arrowstyle=f"->, head_width=.3, head_length=.3", color=color, **kwargs))
# self._axis.arrow(x, y, dx, dy, **args)
def add_fancy_bbox(self, center: tuple[float, float], width: float, height: float, **args: Any) -> None:
self._axis.add_patch(FancyBboxPatch(center, width=width, height=height, **args))
def draw_outside(self, value: Any, htype: House.Type, row_or_column: int, *,
is_right: bool = False, padding: float = 0, **args: Any) -> None:
args = {'fontsize': 20, 'weight': 'bold', **args}
if htype == House.Type.ROW:
if not is_right:
self.draw_text(.9 - padding, row_or_column + .5, str(value),
va='center', ha='right', **args)
else:
self.draw_text(10.1 + padding, row_or_column + .5, str(value),
va='center', ha='left', **args)
else:
if not is_right:
self.draw_text(row_or_column + .5, .9 - padding, str(value),
va='bottom', ha='center', **args)
else:
self.draw_text(row_or_column + .5, 10.1 + padding, str(value),
va='top', ha='center', **args)
def draw_outline(self, squares: Sequence[Square], *, inset: float = .1, **args: Any) -> None:
args = {'color': 'black', 'linewidth': 2, 'linestyle': "dotted", **args}
squares_set = set(squares)
# A wall is identified by the square it is in, and the direction you'd be facing from the center of that
# square to see the wall. A wall separates a square inside of "squares" from a square out of it.
walls = {(row, column, dr, dc)
for row, column in squares for dr, dc in ((0, 1), (0, -1), (1, 0), (-1, 0))
if (row + dr, column + dc) not in squares_set}
while walls:
start_wall = current_wall = next(iter(walls)) # pick some wall
points: list[np.ndarray] = []
while True:
# Find the connecting point between the current wall and the wall to the right and add it to our
# set of points
row, column, ahead_dr, ahead_dc = current_wall # square, and direction of wall from center
right_dr, right_dc = ahead_dc, -ahead_dr # The direction if we turned right
# Three possible next walls, in order of preference. We are facing the wall.
# We scan the wall from left to right, and then see where it goes next:
# 1) The wall makes a right turn, staying with the current square.
next1 = (row, column, right_dr, right_dc)
# 2) The wall continues in its direction, going into the square on the right.
next2 = (row + right_dr, column + right_dc, ahead_dr, ahead_dc)
# 3) The wall makes a left turn, continuing in the square diagonally ahead to the right.
next3 = (row + right_dr + ahead_dr, column + right_dc + ahead_dc, -right_dr, -right_dc)
# It is possible for next1 and next3 to both be in walls if we have two squares touching diagonally.
# In that case, we prefer to stay within the same cell, so we prefer next1 to next3.
next_wall = next(x for x in (next1, next2, next3) if x in walls)
walls.remove(next_wall)
if next_wall == next2:
# We don't need to add a point if the wall is continuing in the direction it was going.
pass
else:
np_center = np.array((row, column)) + .5
np_ahead = np.array((ahead_dr, ahead_dc))
np_right = np.array((right_dr, right_dc))
right_inset = inset if next_wall == next1 else -inset
points.append(np_center + (.5 - inset) * np_ahead + (.5 - right_inset) * np_right)
if next_wall == start_wall:
break
current_wall = next_wall
points.append(points[0])
pts = np.vstack(points)
self.plot(pts[:, 1], pts[:, 0], **args)