starplot 0.18.3__py2.py3-none-any.whl → 0.19.2__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- starplot/__init__.py +33 -27
- starplot/config.py +11 -0
- starplot/data/__init__.py +3 -5
- starplot/data/catalogs.py +24 -12
- starplot/data/constellations.py +1 -0
- starplot/data/db.py +1 -7
- starplot/geod.py +3 -4
- starplot/geometry.py +17 -1
- starplot/mixins.py +11 -0
- starplot/models/__init__.py +3 -1
- starplot/models/constellation.py +20 -3
- starplot/models/milky_way.py +30 -0
- starplot/models/moon.py +1 -1
- starplot/models/observer.py +11 -2
- starplot/models/planet.py +1 -1
- starplot/models/sun.py +1 -1
- starplot/plots/__init__.py +6 -0
- starplot/{base.py → plots/base.py} +107 -456
- starplot/{horizon.py → plots/horizon.py} +12 -10
- starplot/{map.py → plots/map.py} +11 -7
- starplot/{optic.py → plots/optic.py} +21 -30
- starplot/{zenith.py → plots/zenith.py} +37 -8
- starplot/plotters/__init__.py +9 -7
- starplot/plotters/arrow.py +1 -1
- starplot/plotters/constellations.py +46 -61
- starplot/plotters/dsos.py +33 -16
- starplot/plotters/experimental.py +0 -1
- starplot/plotters/milkyway.py +15 -6
- starplot/plotters/stars.py +19 -36
- starplot/plotters/text.py +524 -0
- starplot/styles/__init__.py +4 -4
- starplot/styles/base.py +1 -13
- {starplot-0.18.3.dist-info → starplot-0.19.2.dist-info}/METADATA +2 -1
- {starplot-0.18.3.dist-info → starplot-0.19.2.dist-info}/RECORD +37 -35
- starplot/data/library/sky.db +0 -0
- {starplot-0.18.3.dist-info → starplot-0.19.2.dist-info}/WHEEL +0 -0
- {starplot-0.18.3.dist-info → starplot-0.19.2.dist-info}/entry_points.txt +0 -0
- {starplot-0.18.3.dist-info → starplot-0.19.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,524 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import rtree
|
|
5
|
+
from shapely import Point
|
|
6
|
+
from shapely.errors import GEOSException
|
|
7
|
+
from matplotlib.text import Text
|
|
8
|
+
|
|
9
|
+
from starplot.config import settings as StarplotSettings, SvgTextType
|
|
10
|
+
from starplot.styles import AnchorPointEnum, LabelStyle
|
|
11
|
+
from starplot.styles.helpers import use_style
|
|
12
|
+
from starplot.geometry import (
|
|
13
|
+
random_point_in_polygon_at_distance,
|
|
14
|
+
union_at_zero,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
"""
|
|
18
|
+
Long term strategy:
|
|
19
|
+
|
|
20
|
+
- plot all markers FIRST (but keep track of labels)
|
|
21
|
+
- on export, find best positions for labels
|
|
22
|
+
- introduce some "priority" for labels (e.g. order by)
|
|
23
|
+
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
BBox = tuple[float, float, float, float]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class CollisionHandler:
|
|
31
|
+
"""
|
|
32
|
+
Dataclass that describes how to handle label collisions with other objects, like text, markers, constellation lines, etc.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
allow_clipped: bool = False
|
|
36
|
+
"""If True, then labels will be plotted if they're clipped (i.e. part of the label is outside the plot area)"""
|
|
37
|
+
|
|
38
|
+
allow_label_collisions: bool = False
|
|
39
|
+
"""If True, then labels will be plotted if they collide with another label"""
|
|
40
|
+
|
|
41
|
+
allow_marker_collisions: bool = False
|
|
42
|
+
"""If True, then labels will be plotted if they collide with another marker"""
|
|
43
|
+
|
|
44
|
+
allow_constellation_line_collisions: bool = False
|
|
45
|
+
"""If True, then labels will be plotted if they collide with a constellation line"""
|
|
46
|
+
|
|
47
|
+
plot_on_fail: bool = False
|
|
48
|
+
"""If True, then labels will be plotted even if no allowed position is found. They will be plotted at their last attempted position."""
|
|
49
|
+
|
|
50
|
+
attempts: int = 500
|
|
51
|
+
"""Max attempts to find a good label position"""
|
|
52
|
+
|
|
53
|
+
seed: int = None
|
|
54
|
+
"""Random seed for randomly generating points"""
|
|
55
|
+
|
|
56
|
+
anchor_fallbacks: list[AnchorPointEnum] = None
|
|
57
|
+
"""
|
|
58
|
+
If a point-based label's preferred anchor point results in a collision, then these fallbacks will be tried in
|
|
59
|
+
sequence until a collision-free position is found.
|
|
60
|
+
|
|
61
|
+
Default:
|
|
62
|
+
```python
|
|
63
|
+
[
|
|
64
|
+
AnchorPointEnum.BOTTOM_RIGHT,
|
|
65
|
+
AnchorPointEnum.TOP_LEFT,
|
|
66
|
+
AnchorPointEnum.TOP_RIGHT,
|
|
67
|
+
AnchorPointEnum.BOTTOM_LEFT,
|
|
68
|
+
AnchorPointEnum.BOTTOM_CENTER,
|
|
69
|
+
AnchorPointEnum.TOP_CENTER,
|
|
70
|
+
AnchorPointEnum.RIGHT_CENTER,
|
|
71
|
+
AnchorPointEnum.LEFT_CENTER,
|
|
72
|
+
]
|
|
73
|
+
```
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
def __post_init__(self):
|
|
77
|
+
self.anchor_fallbacks = self.anchor_fallbacks or [
|
|
78
|
+
AnchorPointEnum.BOTTOM_RIGHT,
|
|
79
|
+
AnchorPointEnum.TOP_LEFT,
|
|
80
|
+
AnchorPointEnum.TOP_RIGHT,
|
|
81
|
+
AnchorPointEnum.BOTTOM_LEFT,
|
|
82
|
+
AnchorPointEnum.BOTTOM_CENTER,
|
|
83
|
+
AnchorPointEnum.TOP_CENTER,
|
|
84
|
+
AnchorPointEnum.RIGHT_CENTER,
|
|
85
|
+
AnchorPointEnum.LEFT_CENTER,
|
|
86
|
+
]
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class TextPlotterMixin:
|
|
90
|
+
def __init__(self, *args, **kwargs):
|
|
91
|
+
self.labels = []
|
|
92
|
+
self._labels_rtree = rtree.index.Index()
|
|
93
|
+
self._constellations_rtree = rtree.index.Index()
|
|
94
|
+
self._stars_rtree = rtree.index.Index()
|
|
95
|
+
self._markers_rtree = rtree.index.Index()
|
|
96
|
+
self.collision_handler = kwargs.pop("collision_handler", CollisionHandler())
|
|
97
|
+
|
|
98
|
+
def _is_label_collision(self, bbox: BBox) -> bool:
|
|
99
|
+
ix = list(self._labels_rtree.intersection(bbox))
|
|
100
|
+
return len(ix) > 0
|
|
101
|
+
|
|
102
|
+
def _is_constellation_collision(self, bbox: BBox) -> bool:
|
|
103
|
+
ix = list(self._constellations_rtree.intersection(bbox))
|
|
104
|
+
return len(ix) > 0
|
|
105
|
+
|
|
106
|
+
def _is_star_collision(self, bbox: BBox) -> bool:
|
|
107
|
+
ix = list(self._stars_rtree.intersection(bbox))
|
|
108
|
+
return len(ix) > 0
|
|
109
|
+
|
|
110
|
+
def _is_marker_collision(self, bbox: BBox) -> bool:
|
|
111
|
+
ix = list(self._markers_rtree.intersection(bbox))
|
|
112
|
+
return len(ix) > 0
|
|
113
|
+
|
|
114
|
+
def _is_clipped(self, points) -> bool:
|
|
115
|
+
p = self._clip_path_polygon
|
|
116
|
+
|
|
117
|
+
for x, y in points:
|
|
118
|
+
if not p.contains(Point(x, y)):
|
|
119
|
+
return True
|
|
120
|
+
|
|
121
|
+
return False
|
|
122
|
+
|
|
123
|
+
def _add_label_to_rtree(self, label: Text, extent=None):
|
|
124
|
+
extent = extent or label.get_window_extent(
|
|
125
|
+
renderer=self.fig.canvas.get_renderer()
|
|
126
|
+
)
|
|
127
|
+
self.labels.append(label)
|
|
128
|
+
self._labels_rtree.insert(
|
|
129
|
+
0, (extent.x0 - 1, extent.y0 - 1, extent.x1 + 1, extent.y1 + 1)
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
def _is_open_space(
|
|
133
|
+
self,
|
|
134
|
+
bbox: BBox,
|
|
135
|
+
padding=0,
|
|
136
|
+
avoid_clipped=True,
|
|
137
|
+
avoid_label_collisions=True,
|
|
138
|
+
avoid_marker_collisions=True,
|
|
139
|
+
avoid_constellation_collision=True,
|
|
140
|
+
) -> bool:
|
|
141
|
+
"""
|
|
142
|
+
Returns true if the boox covers an open space (i.e. no collisions)
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
bbox: 4-element tuple of lower left and upper right coordinates
|
|
146
|
+
"""
|
|
147
|
+
x0, y0, x1, y1 = bbox
|
|
148
|
+
points = [(x0, y0), (x1, y1)]
|
|
149
|
+
bbox = (
|
|
150
|
+
x0 - padding,
|
|
151
|
+
y0 - padding,
|
|
152
|
+
x1 + padding,
|
|
153
|
+
y1 + padding,
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
if any([np.isnan(c) for c in (x0, y0, x1, y1)]):
|
|
157
|
+
return False
|
|
158
|
+
|
|
159
|
+
if avoid_clipped and self._is_clipped(points):
|
|
160
|
+
return False
|
|
161
|
+
|
|
162
|
+
if avoid_label_collisions and self._is_label_collision(bbox):
|
|
163
|
+
return False
|
|
164
|
+
|
|
165
|
+
if avoid_marker_collisions and (
|
|
166
|
+
self._is_star_collision(bbox) or self._is_marker_collision(bbox)
|
|
167
|
+
):
|
|
168
|
+
return False
|
|
169
|
+
|
|
170
|
+
if avoid_constellation_collision and self._is_constellation_collision(bbox):
|
|
171
|
+
return False
|
|
172
|
+
|
|
173
|
+
return True
|
|
174
|
+
|
|
175
|
+
def _get_label_bbox(self, label: Text) -> BBox:
|
|
176
|
+
extent = label.get_window_extent(renderer=self.fig.canvas.get_renderer())
|
|
177
|
+
return (float(extent.x0), float(extent.y0), float(extent.x1), float(extent.y1))
|
|
178
|
+
|
|
179
|
+
def _maybe_remove_label(
|
|
180
|
+
self,
|
|
181
|
+
label: Text,
|
|
182
|
+
collision_handler: CollisionHandler,
|
|
183
|
+
) -> bool:
|
|
184
|
+
"""Returns true if the label is removed, else false"""
|
|
185
|
+
extent = label.get_window_extent(renderer=self.fig.canvas.get_renderer())
|
|
186
|
+
bbox = (float(extent.x0), float(extent.y0), float(extent.x1), float(extent.y1))
|
|
187
|
+
points = [(extent.x0, extent.y0), (extent.x1, extent.y1)]
|
|
188
|
+
|
|
189
|
+
if any([np.isnan(c) for c in bbox]):
|
|
190
|
+
label.remove()
|
|
191
|
+
return True
|
|
192
|
+
|
|
193
|
+
if not collision_handler.allow_clipped and self._is_clipped(points):
|
|
194
|
+
label.remove()
|
|
195
|
+
return True
|
|
196
|
+
|
|
197
|
+
if not collision_handler.allow_label_collisions and self._is_label_collision(
|
|
198
|
+
bbox
|
|
199
|
+
):
|
|
200
|
+
label.remove()
|
|
201
|
+
return True
|
|
202
|
+
|
|
203
|
+
if not collision_handler.allow_marker_collisions and (
|
|
204
|
+
self._is_star_collision(bbox) or self._is_marker_collision(bbox)
|
|
205
|
+
):
|
|
206
|
+
label.remove()
|
|
207
|
+
return True
|
|
208
|
+
|
|
209
|
+
if (
|
|
210
|
+
not collision_handler.allow_constellation_line_collisions
|
|
211
|
+
and self._is_constellation_collision(bbox)
|
|
212
|
+
):
|
|
213
|
+
label.remove()
|
|
214
|
+
return True
|
|
215
|
+
|
|
216
|
+
return False
|
|
217
|
+
|
|
218
|
+
def _text(self, x, y, text, **kwargs) -> Text:
|
|
219
|
+
"""Plots text at (x, y)"""
|
|
220
|
+
label = self.ax.annotate(
|
|
221
|
+
text,
|
|
222
|
+
(x, y),
|
|
223
|
+
**kwargs,
|
|
224
|
+
**self._plot_kwargs(),
|
|
225
|
+
)
|
|
226
|
+
if kwargs.get("clip_on"):
|
|
227
|
+
label.set_clip_on(True)
|
|
228
|
+
label.set_clip_path(self._background_clip_path)
|
|
229
|
+
return label
|
|
230
|
+
|
|
231
|
+
def _text_point(
|
|
232
|
+
self,
|
|
233
|
+
ra: float,
|
|
234
|
+
dec: float,
|
|
235
|
+
text: str,
|
|
236
|
+
collision_handler: CollisionHandler,
|
|
237
|
+
**kwargs,
|
|
238
|
+
) -> Text | None:
|
|
239
|
+
if not text:
|
|
240
|
+
return None
|
|
241
|
+
|
|
242
|
+
x, y = self._prepare_coords(ra, dec)
|
|
243
|
+
|
|
244
|
+
if StarplotSettings.svg_text_type == SvgTextType.PATH:
|
|
245
|
+
kwargs["path_effects"] = kwargs.get("path_effects", [self.text_border])
|
|
246
|
+
|
|
247
|
+
original_va = kwargs.pop("va", None)
|
|
248
|
+
original_ha = kwargs.pop("ha", None)
|
|
249
|
+
original_offset_x, original_offset_y = kwargs.pop("xytext", (0, 0))
|
|
250
|
+
attempts = 0
|
|
251
|
+
|
|
252
|
+
anchors = [(original_va, original_ha)]
|
|
253
|
+
for a in collision_handler.anchor_fallbacks:
|
|
254
|
+
d = AnchorPointEnum.from_str(a).as_matplot()
|
|
255
|
+
anchors.append((d["va"], d["ha"]))
|
|
256
|
+
|
|
257
|
+
for va, ha in anchors:
|
|
258
|
+
attempts += 1
|
|
259
|
+
offset_x, offset_y = original_offset_x, original_offset_y
|
|
260
|
+
if original_ha != ha:
|
|
261
|
+
offset_x *= -1
|
|
262
|
+
|
|
263
|
+
if original_va != va:
|
|
264
|
+
offset_y *= -1
|
|
265
|
+
|
|
266
|
+
if ha == "center":
|
|
267
|
+
offset_x = 0
|
|
268
|
+
offset_y = 0
|
|
269
|
+
|
|
270
|
+
label = self._text(
|
|
271
|
+
x, y, text, va=va, ha=ha, xytext=(offset_x, offset_y), **kwargs
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
if (
|
|
275
|
+
collision_handler.plot_on_fail
|
|
276
|
+
and label
|
|
277
|
+
and (attempts == collision_handler.attempts or attempts == len(anchors))
|
|
278
|
+
):
|
|
279
|
+
self._add_label_to_rtree(label)
|
|
280
|
+
return label
|
|
281
|
+
|
|
282
|
+
removed = self._maybe_remove_label(label, collision_handler)
|
|
283
|
+
|
|
284
|
+
if not removed:
|
|
285
|
+
self._add_label_to_rtree(label)
|
|
286
|
+
return label
|
|
287
|
+
elif attempts == collision_handler.attempts or attempts == len(anchors):
|
|
288
|
+
return None
|
|
289
|
+
|
|
290
|
+
# from matplotlib.patches import Rectangle
|
|
291
|
+
# bbox = label.get_window_extent(renderer=self.fig.canvas.get_renderer())
|
|
292
|
+
# bbox = bbox.transformed(self.ax.transAxes.inverted())
|
|
293
|
+
# # bbox = bbox.padded(1)
|
|
294
|
+
# # bbox = bbox.expanded(1, 2)
|
|
295
|
+
# rect = Rectangle(
|
|
296
|
+
# # Bbox(x0=0.19034844035799406, y0=0.8351746595026188, x1=0.20519408725358892, y1=0.8615984521601776)
|
|
297
|
+
# (bbox.x0, bbox.y0), # (x, y) position in display pixels
|
|
298
|
+
# width=bbox.width,
|
|
299
|
+
# height=bbox.height,
|
|
300
|
+
# transform=self.ax.transAxes,
|
|
301
|
+
# fill=False,
|
|
302
|
+
# facecolor='none',
|
|
303
|
+
# edgecolor='red',
|
|
304
|
+
# linewidth=1,
|
|
305
|
+
# alpha=1,
|
|
306
|
+
# zorder=100_000,
|
|
307
|
+
# )
|
|
308
|
+
# self.ax.add_patch(rect)
|
|
309
|
+
|
|
310
|
+
def _text_area(
|
|
311
|
+
self,
|
|
312
|
+
ra: float,
|
|
313
|
+
dec: float,
|
|
314
|
+
text: str,
|
|
315
|
+
area,
|
|
316
|
+
collision_handler: CollisionHandler,
|
|
317
|
+
**kwargs,
|
|
318
|
+
) -> Text | None:
|
|
319
|
+
kwargs["va"] = "center"
|
|
320
|
+
kwargs["ha"] = "center"
|
|
321
|
+
|
|
322
|
+
if StarplotSettings.svg_text_type == SvgTextType.PATH:
|
|
323
|
+
kwargs["path_effects"] = kwargs.get("path_effects", [self.text_border])
|
|
324
|
+
|
|
325
|
+
padding = 0
|
|
326
|
+
max_distance = 2_000
|
|
327
|
+
distance_step_size = 2
|
|
328
|
+
attempts = 0
|
|
329
|
+
height = None
|
|
330
|
+
width = None
|
|
331
|
+
bbox = None
|
|
332
|
+
|
|
333
|
+
origin = Point(ra, dec)
|
|
334
|
+
|
|
335
|
+
total_area = (
|
|
336
|
+
area
|
|
337
|
+
if area.geom_type != "MultiPolygon"
|
|
338
|
+
else union_at_zero(area.geoms[0], area.geoms[1])
|
|
339
|
+
)
|
|
340
|
+
original_size = total_area.area
|
|
341
|
+
buffer = -0.05 if original_size < 400 else -1
|
|
342
|
+
|
|
343
|
+
# Intersect with extent
|
|
344
|
+
extent = self._extent_mask()
|
|
345
|
+
|
|
346
|
+
try:
|
|
347
|
+
area = area.intersection(extent)
|
|
348
|
+
except GEOSException:
|
|
349
|
+
# TODO : handle this better
|
|
350
|
+
pass
|
|
351
|
+
|
|
352
|
+
area = (
|
|
353
|
+
area
|
|
354
|
+
if area.geom_type != "MultiPolygon"
|
|
355
|
+
else union_at_zero(area.geoms[0], area.geoms[1])
|
|
356
|
+
)
|
|
357
|
+
area = area.buffer(buffer, cap_style="square", join_style="mitre")
|
|
358
|
+
|
|
359
|
+
if not area.contains(origin) or area.area < (original_size * 0.9):
|
|
360
|
+
origin = area.centroid
|
|
361
|
+
|
|
362
|
+
if self.debug_text and area.is_valid and not origin.is_empty:
|
|
363
|
+
"""Plots marker at origin and polygon of area"""
|
|
364
|
+
self.marker(
|
|
365
|
+
origin.x,
|
|
366
|
+
origin.y,
|
|
367
|
+
style={
|
|
368
|
+
"marker": {
|
|
369
|
+
"symbol": "triangle",
|
|
370
|
+
"color": "red",
|
|
371
|
+
}
|
|
372
|
+
},
|
|
373
|
+
)
|
|
374
|
+
self.polygon(
|
|
375
|
+
geometry=area,
|
|
376
|
+
style={
|
|
377
|
+
"edge_color": "red",
|
|
378
|
+
"edge_width": 2,
|
|
379
|
+
},
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
for d in range(0, max_distance, distance_step_size):
|
|
383
|
+
if not area.contains(origin):
|
|
384
|
+
continue
|
|
385
|
+
distance = d / 25
|
|
386
|
+
point = random_point_in_polygon_at_distance(
|
|
387
|
+
area,
|
|
388
|
+
origin_point=origin,
|
|
389
|
+
distance=distance,
|
|
390
|
+
max_iterations=10,
|
|
391
|
+
seed=collision_handler.seed,
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
if point is None:
|
|
395
|
+
continue
|
|
396
|
+
|
|
397
|
+
x, y = self._prepare_coords(point.x, point.y)
|
|
398
|
+
|
|
399
|
+
if height and width:
|
|
400
|
+
data_xy = self._proj.transform_point(x, y, self._crs)
|
|
401
|
+
display_x, display_y = self.ax.transData.transform(data_xy)
|
|
402
|
+
bbox = (
|
|
403
|
+
display_x - width / 2,
|
|
404
|
+
display_y - height / 2,
|
|
405
|
+
display_x + width / 2,
|
|
406
|
+
display_y + height / 2,
|
|
407
|
+
)
|
|
408
|
+
label = None
|
|
409
|
+
|
|
410
|
+
else:
|
|
411
|
+
label = self._text(x, y, text, **kwargs)
|
|
412
|
+
bbox = self._get_label_bbox(label)
|
|
413
|
+
height = bbox[3] - bbox[1]
|
|
414
|
+
width = bbox[2] - bbox[0]
|
|
415
|
+
|
|
416
|
+
is_open = self._is_open_space(
|
|
417
|
+
bbox,
|
|
418
|
+
padding=padding,
|
|
419
|
+
avoid_clipped=not collision_handler.allow_clipped,
|
|
420
|
+
avoid_constellation_collision=not collision_handler.allow_constellation_line_collisions,
|
|
421
|
+
avoid_marker_collisions=not collision_handler.allow_marker_collisions,
|
|
422
|
+
avoid_label_collisions=not collision_handler.allow_label_collisions,
|
|
423
|
+
)
|
|
424
|
+
|
|
425
|
+
# # TODO : remove label if not fully inside area?
|
|
426
|
+
|
|
427
|
+
attempts += 1
|
|
428
|
+
|
|
429
|
+
if is_open and label is None:
|
|
430
|
+
label = self._text(x, y, text, **kwargs)
|
|
431
|
+
|
|
432
|
+
if is_open or (
|
|
433
|
+
collision_handler.plot_on_fail
|
|
434
|
+
and attempts == collision_handler.attempts
|
|
435
|
+
):
|
|
436
|
+
self._add_label_to_rtree(label)
|
|
437
|
+
return label
|
|
438
|
+
|
|
439
|
+
elif label is not None:
|
|
440
|
+
label.remove()
|
|
441
|
+
|
|
442
|
+
elif attempts == collision_handler.attempts:
|
|
443
|
+
return None
|
|
444
|
+
|
|
445
|
+
@use_style(LabelStyle)
|
|
446
|
+
def text(
|
|
447
|
+
self,
|
|
448
|
+
text: str,
|
|
449
|
+
ra: float,
|
|
450
|
+
dec: float,
|
|
451
|
+
style: LabelStyle = None,
|
|
452
|
+
collision_handler: CollisionHandler = None,
|
|
453
|
+
**kwargs,
|
|
454
|
+
):
|
|
455
|
+
"""
|
|
456
|
+
Plots text
|
|
457
|
+
|
|
458
|
+
Args:
|
|
459
|
+
text: Text to plot
|
|
460
|
+
ra: Right ascension of text (0...360)
|
|
461
|
+
dec: Declination of text (-90...90)
|
|
462
|
+
style: Styling of the text
|
|
463
|
+
collision_handler: An instance of [CollisionHandler][starplot.CollisionHandler] that describes what to do on collisions with other labels, markers, etc. If `None`, then the collision handler of the plot will be used.
|
|
464
|
+
"""
|
|
465
|
+
if not text:
|
|
466
|
+
return
|
|
467
|
+
|
|
468
|
+
style = style.model_copy() # need a copy because we possibly mutate it below
|
|
469
|
+
|
|
470
|
+
collision_handler = collision_handler or self.collision_handler
|
|
471
|
+
|
|
472
|
+
if style.offset_x == "auto":
|
|
473
|
+
style.offset_x = 0
|
|
474
|
+
|
|
475
|
+
if style.offset_y == "auto":
|
|
476
|
+
style.offset_y = 0
|
|
477
|
+
|
|
478
|
+
if kwargs.get("area"):
|
|
479
|
+
label = self._text_area(
|
|
480
|
+
ra,
|
|
481
|
+
dec,
|
|
482
|
+
text,
|
|
483
|
+
**style.matplot_kwargs(self.scale),
|
|
484
|
+
area=kwargs.pop("area"),
|
|
485
|
+
collision_handler=collision_handler,
|
|
486
|
+
xycoords="data",
|
|
487
|
+
xytext=(style.offset_x * self.scale, style.offset_y * self.scale),
|
|
488
|
+
textcoords="offset points",
|
|
489
|
+
**kwargs,
|
|
490
|
+
)
|
|
491
|
+
else:
|
|
492
|
+
label = self._text_point(
|
|
493
|
+
ra,
|
|
494
|
+
dec,
|
|
495
|
+
text,
|
|
496
|
+
**style.matplot_kwargs(self.scale),
|
|
497
|
+
collision_handler=collision_handler,
|
|
498
|
+
xycoords="data",
|
|
499
|
+
xytext=(style.offset_x * self.scale, style.offset_y * self.scale),
|
|
500
|
+
textcoords="offset points",
|
|
501
|
+
**kwargs,
|
|
502
|
+
)
|
|
503
|
+
|
|
504
|
+
if self.debug_text and label:
|
|
505
|
+
"""Plots bounding box around label"""
|
|
506
|
+
from matplotlib.patches import Rectangle
|
|
507
|
+
|
|
508
|
+
bbox = label.get_window_extent(renderer=self.fig.canvas.get_renderer())
|
|
509
|
+
bbox = bbox.transformed(self.ax.transAxes.inverted())
|
|
510
|
+
rect = Rectangle(
|
|
511
|
+
(bbox.x0, bbox.y0), # (x, y) position in display pixels
|
|
512
|
+
width=bbox.width,
|
|
513
|
+
height=bbox.height,
|
|
514
|
+
transform=self.ax.transAxes,
|
|
515
|
+
fill=False,
|
|
516
|
+
facecolor="none",
|
|
517
|
+
edgecolor="red",
|
|
518
|
+
linewidth=1,
|
|
519
|
+
alpha=1,
|
|
520
|
+
zorder=100_000,
|
|
521
|
+
)
|
|
522
|
+
self.ax.add_patch(rect)
|
|
523
|
+
|
|
524
|
+
return label
|
starplot/styles/__init__.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
from .helpers import * # noqa: F401,F403
|
|
1
|
+
# ruff: noqa: F401,F403
|
|
3
2
|
|
|
4
|
-
|
|
3
|
+
from .base import *
|
|
4
|
+
from .helpers import *
|
|
5
5
|
|
|
6
|
-
import starplot.styles.extensions as style_extensions
|
|
6
|
+
import starplot.styles.extensions as style_extensions
|
starplot/styles/base.py
CHANGED
|
@@ -2,7 +2,7 @@ import json
|
|
|
2
2
|
|
|
3
3
|
from enum import Enum
|
|
4
4
|
from pathlib import Path
|
|
5
|
-
from typing import Optional, Union
|
|
5
|
+
from typing import Optional, Union
|
|
6
6
|
|
|
7
7
|
import yaml
|
|
8
8
|
|
|
@@ -813,18 +813,6 @@ class PlotStyle(BaseStyle):
|
|
|
813
813
|
|
|
814
814
|
text_border_color: ColorStr = ColorStr("#fff")
|
|
815
815
|
|
|
816
|
-
text_anchor_fallbacks: List[AnchorPointEnum] = [
|
|
817
|
-
AnchorPointEnum.BOTTOM_RIGHT,
|
|
818
|
-
AnchorPointEnum.TOP_LEFT,
|
|
819
|
-
AnchorPointEnum.TOP_RIGHT,
|
|
820
|
-
AnchorPointEnum.BOTTOM_LEFT,
|
|
821
|
-
AnchorPointEnum.BOTTOM_CENTER,
|
|
822
|
-
AnchorPointEnum.TOP_CENTER,
|
|
823
|
-
AnchorPointEnum.RIGHT_CENTER,
|
|
824
|
-
AnchorPointEnum.LEFT_CENTER,
|
|
825
|
-
]
|
|
826
|
-
"""If a label's preferred anchor point results in a collision, then these fallbacks will be tried in sequence until a collision-free position is found."""
|
|
827
|
-
|
|
828
816
|
# Borders
|
|
829
817
|
border_font_size: int = 18
|
|
830
818
|
border_font_weight: FontWeightEnum = FontWeightEnum.BOLD
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: starplot
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.19.2
|
|
4
4
|
Summary: Star charts and maps of the sky
|
|
5
5
|
Keywords: astronomy,stars,charts,maps,constellations,sky,plotting
|
|
6
6
|
Author-email: Steve Berardi <hello@steveberardi.com>
|
|
@@ -136,6 +136,7 @@ See more details on the [Public Roadmap](https://trello.com/b/sUksygn4/starplot-
|
|
|
136
136
|
- [starplot-hyg](https://github.com/steveberardi/starplot-hyg)
|
|
137
137
|
- [starplot-gaia-dr3](https://github.com/steveberardi/starplot-gaia-dr3)
|
|
138
138
|
- [starplot-hyperleda](https://github.com/steveberardi/starplot-hyperleda)
|
|
139
|
+
- [starplot-milkyway](https://github.com/steveberardi/starplot-milkyway)
|
|
139
140
|
|
|
140
141
|
## License
|
|
141
142
|
[MIT License](https://github.com/steveberardi/starplot/blob/main/LICENSE)
|
|
@@ -1,24 +1,19 @@
|
|
|
1
|
-
starplot/__init__.py,sha256=
|
|
2
|
-
starplot/base.py,sha256=dR6rhNT8AUvPlbRKzKiL0n4YA59QDS7ZnZcAW6e9PjM,42538
|
|
1
|
+
starplot/__init__.py,sha256=bHPp8kjV-1kjqngkE4G5ms_NgLCgpD1Vr1NvkpD8sg8,964
|
|
3
2
|
starplot/callables.py,sha256=k8Ra0nmNfc8vjOpNPdQfAqpNaM2ajCPB3XkgzCqrcQ0,4039
|
|
4
3
|
starplot/cli.py,sha256=Tjkc_5khvb1mL_Ai_met__zViINvtL4SgbYc0_mB2_U,622
|
|
5
|
-
starplot/config.py,sha256=
|
|
4
|
+
starplot/config.py,sha256=8IbQTlQP-e7Yvv63zS9z5Ifcumr696MvdGT3JDvV-ik,2919
|
|
6
5
|
starplot/coordinates.py,sha256=7LDz32VTKa8H-4F67-XvzmjpcTVojZwYVJzXZkBaZ3U,136
|
|
7
|
-
starplot/geod.py,sha256=
|
|
8
|
-
starplot/geometry.py,sha256=
|
|
9
|
-
starplot/
|
|
10
|
-
starplot/map.py,sha256=2rxnAgJYfHcgdFL1__1MXyg_R7ynN2RACNF_edD-PkA,19185
|
|
11
|
-
starplot/mixins.py,sha256=ZZBVg1Ad3xnbyLskCEhpDcd9Jmb_2_jT8hUiiCbU658,12268
|
|
12
|
-
starplot/optic.py,sha256=wi2ZMBC09Due_YBAh6IRAsYR2wk-vQRSeWfNNymrQOc,17518
|
|
6
|
+
starplot/geod.py,sha256=au25V21-sexxIPXUbN02nfd9pxOzwjdCxyUXDDsUIZc,2611
|
|
7
|
+
starplot/geometry.py,sha256=NYhfaNJtl-Cbtyq1IfrQimaqKChm9ZNQNsRdpYLR0PU,7248
|
|
8
|
+
starplot/mixins.py,sha256=PoSn8QfLS-P8dUF8rNrFOvhs9yb4D-ZRV2R1-1DSiaA,12536
|
|
13
9
|
starplot/profile.py,sha256=V5LOZFDdnGo-P8ikWvV3jmUVJIKO3gd4H2bjBlk7aUM,300
|
|
14
10
|
starplot/projections.py,sha256=6mj8uJkWE-79giXF-OVS8ixUbbojOHuwI51C-ovHyIo,5101
|
|
15
11
|
starplot/utils.py,sha256=49m8QXJl188Pgpef_82gyykly7ZjfAuHVEcSA5QFITA,3720
|
|
16
12
|
starplot/warnings.py,sha256=uKvGSAVpWKZIHMKxxegO5owFJnKvBYLyq3pJatD0qQ4,594
|
|
17
|
-
starplot/
|
|
18
|
-
starplot/data/
|
|
19
|
-
starplot/data/
|
|
20
|
-
starplot/data/
|
|
21
|
-
starplot/data/db.py,sha256=AZ-q_bqnxs1i2NacLWBbqam6M5JYa3Ie2tXeMIOEKY8,778
|
|
13
|
+
starplot/data/__init__.py,sha256=GBJw579v-5ZNsKk1RxE7Fdnjf4hDIW2gevQJAZ7-6b0,550
|
|
14
|
+
starplot/data/catalogs.py,sha256=Izf2XjKDIcLA-sM9TEL8Csg5CSizYxmvhHq_UXSHMt0,12010
|
|
15
|
+
starplot/data/constellations.py,sha256=-fjneGcLTIxZMxrTQRRoHaMKWy1taW6rQQ3RQ7LWgnE,2330
|
|
16
|
+
starplot/data/db.py,sha256=8SMDx6IkWqhnS2QobL5bp6qD0yze7P05C7b0ls_dbXk,669
|
|
22
17
|
starplot/data/dsos.py,sha256=8rp_Heme3bYFFWUuAPlWMHlfzsMsZ2W_Lq2jwgSWlQ8,2377
|
|
23
18
|
starplot/data/ecliptic.py,sha256=Qre9YdFbTC9mAx-vd2C0Ou4CsnRehIScnTpmEUDDYcM,4638
|
|
24
19
|
starplot/data/stars.py,sha256=C8U29m6pW8L6jwyFqDU1mQgfzeBZjme7OrgYCQ-8ghU,2390
|
|
@@ -27,32 +22,39 @@ starplot/data/utils.py,sha256=W2Su63PA_tKbpCkKAAdjwhBpgiq6hYkCEAVz4cC1y7k,978
|
|
|
27
22
|
starplot/data/library/constellation_names.parquet,sha256=ol_pv7FJTbDzsac-gWrXH3URijlDbCBgZrREmmDrn9w,13412
|
|
28
23
|
starplot/data/library/dso_names.parquet,sha256=BRHfI9HTJW1jiOkPnVkvj7EJejZKaTgUp22uU__0Ee8,24541
|
|
29
24
|
starplot/data/library/readme.md,sha256=sJyJ1EkC5lcwLbvhTF96XlVa8tC6HyuXWWRooMyRNLA,68
|
|
30
|
-
starplot/data/library/sky.db,sha256=E4uB7NLPGF3wrOgHbBuWMlVZ7qvox70Yd3yhytJqlQA,1585152
|
|
31
25
|
starplot/data/library/star_designations.parquet,sha256=WJLiv73VoVfptWT1oK1zzgio5UdGd_ebuy2Ew1-M9xQ,70170
|
|
32
|
-
starplot/models/__init__.py,sha256=
|
|
26
|
+
starplot/models/__init__.py,sha256=jypNZ_41BtVbV4xS7QDAwwey7rTxUTXNKuXIAV3HhE8,468
|
|
33
27
|
starplot/models/base.py,sha256=wIoxP96wPc8glIFUgFhSeCtzU_pz597vWBdveqSoBNY,2175
|
|
34
28
|
starplot/models/comet.py,sha256=pG_yNVmWCNCaAEZGhAoeRtDe8FaDKWmsqt9-6BCicUo,11210
|
|
35
|
-
starplot/models/constellation.py,sha256=
|
|
29
|
+
starplot/models/constellation.py,sha256=aDoh1wWB4za92gHSCwxl86aVhOtXZdcrYblVSAVhrog,4908
|
|
36
30
|
starplot/models/dso.py,sha256=Iehc1VGkpQsZDEVIft7KZyjXUevesg3FgBUsWU_Gc3E,9019
|
|
37
|
-
starplot/models/
|
|
31
|
+
starplot/models/milky_way.py,sha256=8ll-JV_PH9J0xOUL7R-t3vdcHTrynU0fuZD-R8LiVWI,889
|
|
32
|
+
starplot/models/moon.py,sha256=QLzANZ59ghG0fK0RJXjg8vdegJHPcAA8DgQPUBpHBUw,4628
|
|
38
33
|
starplot/models/objects.py,sha256=BXwUMT-zPiOYBWYV7L-TRDfPo6lsx_AIk3yxJ3qi0ck,683
|
|
39
|
-
starplot/models/observer.py,sha256=
|
|
34
|
+
starplot/models/observer.py,sha256=x55ZaW4sSRtdM1prNo2bCiAfM4mdAlTf-xh00GVnKAE,2048
|
|
40
35
|
starplot/models/optics.py,sha256=6quf3Cxga5pjTunchtGe2OKCU6hgsm5yg7NfYWIfoNA,9314
|
|
41
|
-
starplot/models/planet.py,sha256=
|
|
36
|
+
starplot/models/planet.py,sha256=G-MpJGydmakCqSnXu6G3MdjyFXNtfWI51ST8ewYJh6w,4749
|
|
42
37
|
starplot/models/satellite.py,sha256=X5StTyVjpWQ94FT21OU8ArzxrdIDGB581bstWFNvT3k,5228
|
|
43
38
|
starplot/models/star.py,sha256=0EkqS-i2ErLVPFY7GYqEWWIqhaiZU0RwDDJiIVABua4,6970
|
|
44
|
-
starplot/models/sun.py,sha256=
|
|
45
|
-
starplot/
|
|
46
|
-
starplot/
|
|
47
|
-
starplot/
|
|
48
|
-
starplot/
|
|
49
|
-
starplot/
|
|
39
|
+
starplot/models/sun.py,sha256=L93DKFFgfzVuwESA-8C6g7-gUC4D5tix41SabZ-WP84,2414
|
|
40
|
+
starplot/plots/__init__.py,sha256=YtvBn2ClaGrmnOfFKdXs9duOZNBmRDvZwNJYF_7TTPU,143
|
|
41
|
+
starplot/plots/base.py,sha256=ZabMdrdoiSQ08KlzwHileWaFv1PRCD9RZBPm5vVU9qo,33606
|
|
42
|
+
starplot/plots/horizon.py,sha256=a2a78DPnlmnYyp1xucsQdAR7SepPp2Q6SCPAlu2ygsk,20374
|
|
43
|
+
starplot/plots/map.py,sha256=gyCU5mIm_prUg7gtsKjKO7ppBuwhJNwQoJ1vIEK93TQ,19454
|
|
44
|
+
starplot/plots/optic.py,sha256=0kgj6UC_MouoG5hrywW9zseCR5TtpYlPsoT-XGoqwyo,17305
|
|
45
|
+
starplot/plots/zenith.py,sha256=i_83cXpNk2KB81hFhqUw6ConLzUKyrzb2NxoWxjZCwk,7792
|
|
46
|
+
starplot/plotters/__init__.py,sha256=RzsH2t4PbdsEMBjcgIhQ_ng7fIgjW4CobQcwtGo9oTc,315
|
|
47
|
+
starplot/plotters/arrow.py,sha256=hI2-WGSWjMwK5rax4cMDsjorX2ILylnn1RbWNHbPq5g,6583
|
|
48
|
+
starplot/plotters/constellations.py,sha256=xT_QXHIbUvRKztLe1tjju5BWjfE9ro3Mi5YilHgTQtc,11787
|
|
49
|
+
starplot/plotters/dsos.py,sha256=xhQIXgDiQ0FdiOSC5mJybFkYeiDwPlkwRAaV70Sfl9c,11007
|
|
50
|
+
starplot/plotters/experimental.py,sha256=wDNhPUbmK61xkZvTiwTh3ChpCkwzQmUQ0cY4DqDFH7U,15942
|
|
50
51
|
starplot/plotters/gradients.py,sha256=ZonNeIEmbcZZWjFtlPahGtGCgeTCeimp84PZnqj8dXY,5781
|
|
51
52
|
starplot/plotters/legend.py,sha256=PwqhSQkYsxPNLMabL0Ox5eo0F3jMS4Ekhm2aHaHS3W4,8761
|
|
52
|
-
starplot/plotters/milkyway.py,sha256=
|
|
53
|
-
starplot/plotters/stars.py,sha256=
|
|
54
|
-
starplot/
|
|
55
|
-
starplot/styles/
|
|
53
|
+
starplot/plotters/milkyway.py,sha256=U5uRE4teoWk2v65RC2SkMKPX1LurR6zw7vzLK5mVxJE,1605
|
|
54
|
+
starplot/plotters/stars.py,sha256=rM8LJxCC1mwZjKr-OpcH6g4SlOwUlm-8yI1DSzaLbCQ,11660
|
|
55
|
+
starplot/plotters/text.py,sha256=r1_G4Oe487RxEl0Tjv_8964YfD644H-sdIvqPbK1Eck,16907
|
|
56
|
+
starplot/styles/__init__.py,sha256=HmIDU0cFu4ar8zKet_FEJoNdButQHJMprHnL_4JE2Mc,123
|
|
57
|
+
starplot/styles/base.py,sha256=9xeU0lhTO3eaqVMz1QmS7HMtRQ-OdtUwBP96kzQ7cO4,40881
|
|
56
58
|
starplot/styles/extensions.py,sha256=qVNE9DaSvG8BPr6vPfT8v5vyZNBYB0AjkR-e9oPMk1I,2707
|
|
57
59
|
starplot/styles/fonts.py,sha256=wC3cHuFkBUaZM5fKpT_ExV7anrRKMJX46mjEfcSRQMU,379
|
|
58
60
|
starplot/styles/helpers.py,sha256=AGgHWaHLzJZ6jicvwPzY-p5oSHE0H8gDk1raCmeRFtg,3032
|
|
@@ -88,8 +90,8 @@ starplot/styles/fonts-library/inter/Inter-Regular.ttf,sha256=ZPi-blXDfjLvA9qZcUv
|
|
|
88
90
|
starplot/styles/fonts-library/inter/Inter-SemiBold.ttf,sha256=DcmOiqWVhTlIgPJauJ5tkVrVE0Ui6WGwRspR-tOhglU,413976
|
|
89
91
|
starplot/styles/fonts-library/inter/Inter-SemiBoldItalic.ttf,sha256=HhKJRT16iVz7c1adSFFjTIsOSdFQxN1S1Ev10gaQgnI,418520
|
|
90
92
|
starplot/styles/fonts-library/inter/LICENSE.txt,sha256=JiSB6ERSGzJvXs0FPlm5jIstp4yO4b27boF0MF5Uk1o,4380
|
|
91
|
-
starplot-0.
|
|
92
|
-
starplot-0.
|
|
93
|
-
starplot-0.
|
|
94
|
-
starplot-0.
|
|
95
|
-
starplot-0.
|
|
93
|
+
starplot-0.19.2.dist-info/entry_points.txt,sha256=Sm6jC6h_RcaMGC8saLnYmT0SdhcF9_rMeQIiHneLHyc,46
|
|
94
|
+
starplot-0.19.2.dist-info/licenses/LICENSE,sha256=jcjClHF4BQwhz-kDgia-KphO9Zxu0rCa2BbiA7j1jeU,1070
|
|
95
|
+
starplot-0.19.2.dist-info/WHEEL,sha256=Dyt6SBfaasWElUrURkknVFAZDHSTwxg3PaTza7RSbkY,100
|
|
96
|
+
starplot-0.19.2.dist-info/METADATA,sha256=RiagUiM6UY4PQd2kYrSEOqDjURoTlV6z04vJbJYvwu4,5497
|
|
97
|
+
starplot-0.19.2.dist-info/RECORD,,
|
starplot/data/library/sky.db
DELETED
|
Binary file
|