starplot 0.18.3__py2.py3-none-any.whl → 0.19.0__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/data/__init__.py +3 -5
- starplot/data/catalogs.py +23 -11
- starplot/data/db.py +1 -7
- starplot/models/__init__.py +3 -1
- starplot/models/constellation.py +7 -1
- starplot/models/milky_way.py +30 -0
- starplot/models/observer.py +11 -2
- starplot/plots/__init__.py +6 -0
- starplot/{base.py → plots/base.py} +74 -439
- starplot/{horizon.py → plots/horizon.py} +12 -10
- starplot/{map.py → plots/map.py} +10 -7
- starplot/{optic.py → plots/optic.py} +21 -30
- starplot/{zenith.py → plots/zenith.py} +31 -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 +464 -0
- starplot/styles/__init__.py +4 -4
- starplot/styles/base.py +1 -13
- {starplot-0.18.3.dist-info → starplot-0.19.0.dist-info}/METADATA +2 -1
- {starplot-0.18.3.dist-info → starplot-0.19.0.dist-info}/RECORD +29 -27
- starplot/data/library/sky.db +0 -0
- {starplot-0.18.3.dist-info → starplot-0.19.0.dist-info}/WHEEL +0 -0
- {starplot-0.18.3.dist-info → starplot-0.19.0.dist-info}/entry_points.txt +0 -0
- {starplot-0.18.3.dist-info → starplot-0.19.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
from abc import ABC, abstractmethod
|
|
2
2
|
from typing import Dict, Union, Optional
|
|
3
|
-
from random import randrange
|
|
4
3
|
import logging
|
|
5
4
|
|
|
6
5
|
import numpy as np
|
|
7
|
-
import rtree
|
|
8
6
|
from matplotlib import patches
|
|
9
7
|
from matplotlib import pyplot as plt, patheffects
|
|
10
8
|
from matplotlib.axes import Axes
|
|
11
9
|
from matplotlib.figure import Figure
|
|
12
10
|
from matplotlib.lines import Line2D
|
|
13
|
-
from shapely import Polygon
|
|
11
|
+
from shapely import Polygon
|
|
14
12
|
|
|
15
13
|
from starplot.coordinates import CoordinateSystem
|
|
16
14
|
from starplot import geod, models, warnings
|
|
@@ -22,7 +20,6 @@ from starplot.models.moon import MoonPhase
|
|
|
22
20
|
from starplot.models.optics import Optic, Camera
|
|
23
21
|
from starplot.models.observer import Observer
|
|
24
22
|
from starplot.styles import (
|
|
25
|
-
AnchorPointEnum,
|
|
26
23
|
PlotStyle,
|
|
27
24
|
MarkerStyle,
|
|
28
25
|
ObjectStyle,
|
|
@@ -34,11 +31,8 @@ from starplot.styles import (
|
|
|
34
31
|
GradientDirection,
|
|
35
32
|
fonts,
|
|
36
33
|
)
|
|
34
|
+
from starplot.plotters.text import TextPlotterMixin, CollisionHandler
|
|
37
35
|
from starplot.styles.helpers import use_style
|
|
38
|
-
from starplot.geometry import (
|
|
39
|
-
unwrap_polygon_360,
|
|
40
|
-
random_point_in_polygon_at_distance,
|
|
41
|
-
)
|
|
42
36
|
from starplot.profile import profile
|
|
43
37
|
|
|
44
38
|
LOGGER = logging.getLogger("starplot")
|
|
@@ -49,14 +43,12 @@ LOG_FORMATTER = logging.Formatter(
|
|
|
49
43
|
LOG_HANDLER.setFormatter(LOG_FORMATTER)
|
|
50
44
|
LOGGER.addHandler(LOG_HANDLER)
|
|
51
45
|
|
|
52
|
-
DEFAULT_STYLE = PlotStyle()
|
|
53
|
-
|
|
54
46
|
DEFAULT_RESOLUTION = 4096
|
|
55
47
|
|
|
56
48
|
DPI = 100
|
|
57
49
|
|
|
58
50
|
|
|
59
|
-
class BasePlot(ABC):
|
|
51
|
+
class BasePlot(TextPlotterMixin, ABC):
|
|
60
52
|
_background_clip_path = None
|
|
61
53
|
_clip_path_polygon: Polygon = None # clip path in display coordinates
|
|
62
54
|
_coordinate_system = CoordinateSystem.RA_DEC
|
|
@@ -79,19 +71,24 @@ class BasePlot(ABC):
|
|
|
79
71
|
The plot's style.
|
|
80
72
|
"""
|
|
81
73
|
|
|
74
|
+
collision_handler: CollisionHandler
|
|
75
|
+
"""Default [collision handler][starplot.CollisionHandler] for the plot."""
|
|
76
|
+
|
|
82
77
|
def __init__(
|
|
83
78
|
self,
|
|
84
|
-
observer: Observer =
|
|
79
|
+
observer: Observer = None,
|
|
85
80
|
ephemeris: str = "de421.bsp",
|
|
86
|
-
style: PlotStyle =
|
|
81
|
+
style: PlotStyle = None,
|
|
87
82
|
resolution: int = 4096,
|
|
88
|
-
|
|
83
|
+
collision_handler: CollisionHandler = None,
|
|
89
84
|
scale: float = 1.0,
|
|
90
85
|
autoscale: bool = False,
|
|
91
86
|
suppress_warnings: bool = True,
|
|
92
87
|
*args,
|
|
93
88
|
**kwargs,
|
|
94
89
|
):
|
|
90
|
+
super().__init__(*args, **kwargs)
|
|
91
|
+
|
|
95
92
|
if StarplotSettings.svg_text_type == SvgTextType.PATH:
|
|
96
93
|
plt.rcParams["svg.fonttype"] = "path"
|
|
97
94
|
else:
|
|
@@ -102,10 +99,10 @@ class BasePlot(ABC):
|
|
|
102
99
|
|
|
103
100
|
self.language = StarplotSettings.language
|
|
104
101
|
|
|
105
|
-
self.style = style
|
|
102
|
+
self.style = style or PlotStyle()
|
|
106
103
|
self.figure_size = resolution * px
|
|
107
104
|
self.resolution = resolution
|
|
108
|
-
self.
|
|
105
|
+
self.collision_handler = collision_handler or CollisionHandler()
|
|
109
106
|
|
|
110
107
|
self.scale = scale
|
|
111
108
|
self.autoscale = autoscale
|
|
@@ -115,17 +112,11 @@ class BasePlot(ABC):
|
|
|
115
112
|
if suppress_warnings:
|
|
116
113
|
warnings.suppress()
|
|
117
114
|
|
|
118
|
-
self.observer = observer
|
|
115
|
+
self.observer = observer or Observer()
|
|
119
116
|
self._ephemeris_name = ephemeris
|
|
120
117
|
self.ephemeris = load(ephemeris)
|
|
121
118
|
self.earth = self.ephemeris["earth"]
|
|
122
119
|
|
|
123
|
-
self.labels = []
|
|
124
|
-
self._labels_rtree = rtree.index.Index()
|
|
125
|
-
self._constellations_rtree = rtree.index.Index()
|
|
126
|
-
self._stars_rtree = rtree.index.Index()
|
|
127
|
-
self._markers_rtree = rtree.index.Index()
|
|
128
|
-
|
|
129
120
|
self._background_clip_path = None
|
|
130
121
|
|
|
131
122
|
self._legend = None
|
|
@@ -154,127 +145,6 @@ class BasePlot(ABC):
|
|
|
154
145
|
coords = self._background_clip_path.get_verts()
|
|
155
146
|
self._clip_path_polygon = Polygon(coords).buffer(-1 * buffer)
|
|
156
147
|
|
|
157
|
-
def _is_label_collision(self, bbox) -> bool:
|
|
158
|
-
ix = list(self._labels_rtree.intersection(bbox))
|
|
159
|
-
return len(ix) > 0
|
|
160
|
-
|
|
161
|
-
def _is_constellation_collision(self, bbox) -> bool:
|
|
162
|
-
ix = list(self._constellations_rtree.intersection(bbox))
|
|
163
|
-
return len(ix) > 0
|
|
164
|
-
|
|
165
|
-
def _is_star_collision(self, bbox) -> bool:
|
|
166
|
-
ix = list(self._stars_rtree.intersection(bbox))
|
|
167
|
-
return len(ix) > 0
|
|
168
|
-
|
|
169
|
-
def _is_marker_collision(self, bbox) -> bool:
|
|
170
|
-
ix = list(self._markers_rtree.intersection(bbox))
|
|
171
|
-
return len(ix) > 0
|
|
172
|
-
|
|
173
|
-
def _is_clipped(self, points) -> bool:
|
|
174
|
-
p = self._clip_path_polygon
|
|
175
|
-
|
|
176
|
-
for x, y in points:
|
|
177
|
-
if not p.contains(Point(x, y)):
|
|
178
|
-
return True
|
|
179
|
-
|
|
180
|
-
return False
|
|
181
|
-
|
|
182
|
-
def _add_label_to_rtree(self, label, extent=None):
|
|
183
|
-
extent = extent or label.get_window_extent(
|
|
184
|
-
renderer=self.fig.canvas.get_renderer()
|
|
185
|
-
)
|
|
186
|
-
self.labels.append(label)
|
|
187
|
-
self._labels_rtree.insert(
|
|
188
|
-
0, np.array((extent.x0 - 1, extent.y0 - 1, extent.x1 + 1, extent.y1 + 1))
|
|
189
|
-
)
|
|
190
|
-
|
|
191
|
-
def _is_open_space(
|
|
192
|
-
self,
|
|
193
|
-
bbox: tuple[float, float, float, float],
|
|
194
|
-
padding=0,
|
|
195
|
-
avoid_clipped=True,
|
|
196
|
-
avoid_label_collisions=True,
|
|
197
|
-
avoid_marker_collisions=True,
|
|
198
|
-
avoid_constellation_collision=True,
|
|
199
|
-
) -> bool:
|
|
200
|
-
"""
|
|
201
|
-
Returns true if the boox covers an open space (i.e. no collisions)
|
|
202
|
-
|
|
203
|
-
Args:
|
|
204
|
-
bbox: 4-element tuple of lower left and upper right coordinates
|
|
205
|
-
"""
|
|
206
|
-
x0, y0, x1, y1 = bbox
|
|
207
|
-
points = [(x0, y0), (x1, y1)]
|
|
208
|
-
bbox = (
|
|
209
|
-
x0 - padding,
|
|
210
|
-
y0 - padding,
|
|
211
|
-
x1 + padding,
|
|
212
|
-
y1 + padding,
|
|
213
|
-
)
|
|
214
|
-
|
|
215
|
-
if any([np.isnan(c) for c in (x0, y0, x1, y1)]):
|
|
216
|
-
return False
|
|
217
|
-
|
|
218
|
-
if avoid_clipped and self._is_clipped(points):
|
|
219
|
-
return False
|
|
220
|
-
|
|
221
|
-
if avoid_label_collisions and self._is_label_collision(bbox):
|
|
222
|
-
return False
|
|
223
|
-
|
|
224
|
-
if avoid_marker_collisions and (
|
|
225
|
-
self._is_star_collision(bbox) or self._is_marker_collision(bbox)
|
|
226
|
-
):
|
|
227
|
-
return False
|
|
228
|
-
|
|
229
|
-
if avoid_constellation_collision and self._is_constellation_collision(bbox):
|
|
230
|
-
return False
|
|
231
|
-
|
|
232
|
-
return True
|
|
233
|
-
|
|
234
|
-
def _get_label_bbox(self, label):
|
|
235
|
-
extent = label.get_window_extent(renderer=self.fig.canvas.get_renderer())
|
|
236
|
-
return (extent.x0, extent.y0, extent.x1, extent.y1)
|
|
237
|
-
|
|
238
|
-
def _maybe_remove_label(
|
|
239
|
-
self,
|
|
240
|
-
label,
|
|
241
|
-
remove_on_collision=True,
|
|
242
|
-
remove_on_clipped=True,
|
|
243
|
-
remove_on_constellation_collision=True,
|
|
244
|
-
padding=0,
|
|
245
|
-
) -> bool:
|
|
246
|
-
"""Returns true if the label is removed, else false"""
|
|
247
|
-
extent = label.get_window_extent(renderer=self.fig.canvas.get_renderer())
|
|
248
|
-
bbox = (
|
|
249
|
-
extent.x0 - padding,
|
|
250
|
-
extent.y0 - padding,
|
|
251
|
-
extent.x1 + padding,
|
|
252
|
-
extent.y1 + padding,
|
|
253
|
-
)
|
|
254
|
-
points = [(extent.x0, extent.y0), (extent.x1, extent.y1)]
|
|
255
|
-
|
|
256
|
-
if any([np.isnan(c) for c in (extent.x0, extent.y0, extent.x1, extent.y1)]):
|
|
257
|
-
label.remove()
|
|
258
|
-
return True
|
|
259
|
-
|
|
260
|
-
if remove_on_clipped and self._is_clipped(points):
|
|
261
|
-
label.remove()
|
|
262
|
-
return True
|
|
263
|
-
|
|
264
|
-
if remove_on_collision and (
|
|
265
|
-
self._is_label_collision(bbox)
|
|
266
|
-
or self._is_star_collision(bbox)
|
|
267
|
-
or self._is_marker_collision(bbox)
|
|
268
|
-
):
|
|
269
|
-
label.remove()
|
|
270
|
-
return True
|
|
271
|
-
|
|
272
|
-
if remove_on_constellation_collision and self._is_constellation_collision(bbox):
|
|
273
|
-
label.remove()
|
|
274
|
-
return True
|
|
275
|
-
|
|
276
|
-
return False
|
|
277
|
-
|
|
278
148
|
def _add_legend_handle_marker(self, label: str, style: MarkerStyle):
|
|
279
149
|
if label is not None and label not in self._legend_handles:
|
|
280
150
|
s = style.matplot_kwargs()
|
|
@@ -288,235 +158,6 @@ class BasePlot(ABC):
|
|
|
288
158
|
label=label,
|
|
289
159
|
)
|
|
290
160
|
|
|
291
|
-
def _collision_score(self, label) -> int:
|
|
292
|
-
config = {
|
|
293
|
-
"labels": 1.0, # always fail
|
|
294
|
-
"stars": 0.5,
|
|
295
|
-
"constellations": 0.8,
|
|
296
|
-
"anchors": [
|
|
297
|
-
("bottom right", 0),
|
|
298
|
-
("top right", 0.2),
|
|
299
|
-
("top left", 0.5),
|
|
300
|
-
],
|
|
301
|
-
"on_fail": "plot",
|
|
302
|
-
}
|
|
303
|
-
extent = label.get_window_extent(renderer=self.fig.canvas.get_renderer())
|
|
304
|
-
|
|
305
|
-
if any(
|
|
306
|
-
[np.isnan(c) for c in (extent.x0, extent.y0, extent.x1, extent.y1)]
|
|
307
|
-
) or self._is_clipped(extent):
|
|
308
|
-
return 1
|
|
309
|
-
|
|
310
|
-
x_labels = (
|
|
311
|
-
len(
|
|
312
|
-
list(
|
|
313
|
-
self._labels_rtree.intersection(
|
|
314
|
-
(extent.x0, extent.y0, extent.x1, extent.y1)
|
|
315
|
-
)
|
|
316
|
-
)
|
|
317
|
-
)
|
|
318
|
-
* config["labels"]
|
|
319
|
-
)
|
|
320
|
-
|
|
321
|
-
if x_labels >= 1:
|
|
322
|
-
return 1
|
|
323
|
-
|
|
324
|
-
x_constellations = (
|
|
325
|
-
len(
|
|
326
|
-
list(
|
|
327
|
-
self._constellations_rtree.intersection(
|
|
328
|
-
(extent.x0, extent.y0, extent.x1, extent.y1)
|
|
329
|
-
)
|
|
330
|
-
)
|
|
331
|
-
)
|
|
332
|
-
* config["constellations"]
|
|
333
|
-
)
|
|
334
|
-
|
|
335
|
-
if x_constellations >= 1:
|
|
336
|
-
return 1
|
|
337
|
-
|
|
338
|
-
x_stars = (
|
|
339
|
-
len(
|
|
340
|
-
list(
|
|
341
|
-
self._stars_rtree.intersection(
|
|
342
|
-
(extent.x0, extent.y0, extent.x1, extent.y1)
|
|
343
|
-
)
|
|
344
|
-
)
|
|
345
|
-
)
|
|
346
|
-
* config["stars"]
|
|
347
|
-
)
|
|
348
|
-
if x_stars >= 1:
|
|
349
|
-
return 1
|
|
350
|
-
|
|
351
|
-
return sum([x_labels, x_constellations, x_stars]) / 3
|
|
352
|
-
|
|
353
|
-
def _text(self, x, y, text, **kwargs):
|
|
354
|
-
label = self.ax.annotate(
|
|
355
|
-
text,
|
|
356
|
-
(x, y),
|
|
357
|
-
**kwargs,
|
|
358
|
-
**self._plot_kwargs(),
|
|
359
|
-
)
|
|
360
|
-
if kwargs.get("clip_on"):
|
|
361
|
-
label.set_clip_on(True)
|
|
362
|
-
label.set_clip_path(self._background_clip_path)
|
|
363
|
-
return label
|
|
364
|
-
|
|
365
|
-
def _text_point(
|
|
366
|
-
self,
|
|
367
|
-
ra: float,
|
|
368
|
-
dec: float,
|
|
369
|
-
text: str,
|
|
370
|
-
hide_on_collision: bool = True,
|
|
371
|
-
force: bool = False,
|
|
372
|
-
clip_on: bool = True,
|
|
373
|
-
**kwargs,
|
|
374
|
-
):
|
|
375
|
-
if not text:
|
|
376
|
-
return None
|
|
377
|
-
|
|
378
|
-
x, y = self._prepare_coords(ra, dec)
|
|
379
|
-
|
|
380
|
-
if StarplotSettings.svg_text_type == SvgTextType.PATH:
|
|
381
|
-
kwargs["path_effects"] = kwargs.get("path_effects", [self.text_border])
|
|
382
|
-
|
|
383
|
-
remove_on_constellation_collision = kwargs.pop(
|
|
384
|
-
"remove_on_constellation_collision", True
|
|
385
|
-
)
|
|
386
|
-
|
|
387
|
-
original_va = kwargs.pop("va", None)
|
|
388
|
-
original_ha = kwargs.pop("ha", None)
|
|
389
|
-
original_offset_x, original_offset_y = kwargs.pop("xytext", (0, 0))
|
|
390
|
-
|
|
391
|
-
anchors = [(original_va, original_ha)]
|
|
392
|
-
for a in self.style.text_anchor_fallbacks:
|
|
393
|
-
d = AnchorPointEnum.from_str(a).as_matplot()
|
|
394
|
-
anchors.append((d["va"], d["ha"]))
|
|
395
|
-
|
|
396
|
-
for va, ha in anchors:
|
|
397
|
-
offset_x, offset_y = original_offset_x, original_offset_y
|
|
398
|
-
if original_ha != ha:
|
|
399
|
-
offset_x *= -1
|
|
400
|
-
|
|
401
|
-
if original_va != va:
|
|
402
|
-
offset_y *= -1
|
|
403
|
-
|
|
404
|
-
if ha == "center":
|
|
405
|
-
offset_x = 0
|
|
406
|
-
offset_y = 0
|
|
407
|
-
|
|
408
|
-
label = self._text(
|
|
409
|
-
x, y, text, **kwargs, va=va, ha=ha, xytext=(offset_x, offset_y)
|
|
410
|
-
)
|
|
411
|
-
removed = self._maybe_remove_label(
|
|
412
|
-
label,
|
|
413
|
-
remove_on_collision=hide_on_collision,
|
|
414
|
-
remove_on_clipped=clip_on,
|
|
415
|
-
remove_on_constellation_collision=remove_on_constellation_collision,
|
|
416
|
-
)
|
|
417
|
-
|
|
418
|
-
if force or not removed:
|
|
419
|
-
self._add_label_to_rtree(label)
|
|
420
|
-
return label
|
|
421
|
-
|
|
422
|
-
def _text_area(
|
|
423
|
-
self,
|
|
424
|
-
ra: float,
|
|
425
|
-
dec: float,
|
|
426
|
-
text: str,
|
|
427
|
-
area,
|
|
428
|
-
hide_on_collision: bool = True,
|
|
429
|
-
force: bool = False,
|
|
430
|
-
clip_on: bool = True,
|
|
431
|
-
settings: dict = None,
|
|
432
|
-
**kwargs,
|
|
433
|
-
) -> None:
|
|
434
|
-
kwargs["va"] = "center"
|
|
435
|
-
kwargs["ha"] = "center"
|
|
436
|
-
|
|
437
|
-
if StarplotSettings.svg_text_type == SvgTextType.PATH:
|
|
438
|
-
kwargs["path_effects"] = kwargs.get("path_effects", [self.text_border])
|
|
439
|
-
|
|
440
|
-
avoid_constellation_lines = settings.get("avoid_constellation_lines", False)
|
|
441
|
-
padding = settings.get("label_padding", 3)
|
|
442
|
-
settings.get("buffer", 0.1)
|
|
443
|
-
max_distance = settings.get("max_distance", 300)
|
|
444
|
-
distance_step_size = settings.get("distance_step_size", 1)
|
|
445
|
-
point_iterations = settings.get("point_generation_max_iterations", 500)
|
|
446
|
-
random_seed = settings.get("seed")
|
|
447
|
-
|
|
448
|
-
attempts = 0
|
|
449
|
-
height = None
|
|
450
|
-
width = None
|
|
451
|
-
bbox = None
|
|
452
|
-
areas = (
|
|
453
|
-
[p for p in area.geoms] if "MultiPolygon" == str(area.geom_type) else [area]
|
|
454
|
-
)
|
|
455
|
-
new_areas = []
|
|
456
|
-
origin = Point(ra, dec)
|
|
457
|
-
|
|
458
|
-
for a in areas:
|
|
459
|
-
unwrapped = unwrap_polygon_360(a)
|
|
460
|
-
# new_buffer = unwrapped.area / 10 * -1 * buffer * self.scale
|
|
461
|
-
# new_buffer = -1 * buffer * self.scale
|
|
462
|
-
# new_poly = unwrapped.buffer(new_buffer)
|
|
463
|
-
new_areas.append(unwrapped)
|
|
464
|
-
|
|
465
|
-
for d in range(0, max_distance, distance_step_size):
|
|
466
|
-
distance = d / 20
|
|
467
|
-
poly = randrange(len(new_areas))
|
|
468
|
-
point = random_point_in_polygon_at_distance(
|
|
469
|
-
new_areas[poly],
|
|
470
|
-
origin_point=origin,
|
|
471
|
-
distance=distance,
|
|
472
|
-
max_iterations=point_iterations,
|
|
473
|
-
seed=random_seed,
|
|
474
|
-
)
|
|
475
|
-
|
|
476
|
-
if point is None:
|
|
477
|
-
continue
|
|
478
|
-
|
|
479
|
-
x, y = self._prepare_coords(point.x, point.y)
|
|
480
|
-
|
|
481
|
-
if height and width:
|
|
482
|
-
data_xy = self._proj.transform_point(x, y, self._crs)
|
|
483
|
-
display_x, display_y = self.ax.transData.transform(data_xy)
|
|
484
|
-
bbox = (
|
|
485
|
-
display_x - width / 2,
|
|
486
|
-
display_y - height / 2,
|
|
487
|
-
display_x + width / 2,
|
|
488
|
-
display_y + height / 2,
|
|
489
|
-
)
|
|
490
|
-
label = None
|
|
491
|
-
|
|
492
|
-
else:
|
|
493
|
-
label = self._text(x, y, text, **kwargs)
|
|
494
|
-
bbox = self._get_label_bbox(label)
|
|
495
|
-
height = bbox[3] - bbox[1]
|
|
496
|
-
width = bbox[2] - bbox[0]
|
|
497
|
-
|
|
498
|
-
is_open = self._is_open_space(
|
|
499
|
-
bbox,
|
|
500
|
-
padding=padding,
|
|
501
|
-
avoid_clipped=clip_on,
|
|
502
|
-
avoid_constellation_collision=avoid_constellation_lines,
|
|
503
|
-
avoid_marker_collisions=hide_on_collision,
|
|
504
|
-
avoid_label_collisions=hide_on_collision,
|
|
505
|
-
)
|
|
506
|
-
|
|
507
|
-
# # TODO : remove label if not fully inside area?
|
|
508
|
-
|
|
509
|
-
attempts += 1
|
|
510
|
-
|
|
511
|
-
if is_open and label is None:
|
|
512
|
-
label = self._text(x, y, text, **kwargs)
|
|
513
|
-
|
|
514
|
-
if is_open:
|
|
515
|
-
self._add_label_to_rtree(label)
|
|
516
|
-
return label
|
|
517
|
-
elif label is not None:
|
|
518
|
-
label.remove()
|
|
519
|
-
|
|
520
161
|
@property
|
|
521
162
|
def magnitude_range(self) -> tuple[float, float]:
|
|
522
163
|
"""
|
|
@@ -525,64 +166,6 @@ class BasePlot(ABC):
|
|
|
525
166
|
mags = [s.magnitude for s in self.objects.stars]
|
|
526
167
|
return (min(mags), max(mags))
|
|
527
168
|
|
|
528
|
-
@use_style(LabelStyle)
|
|
529
|
-
def text(
|
|
530
|
-
self,
|
|
531
|
-
text: str,
|
|
532
|
-
ra: float,
|
|
533
|
-
dec: float,
|
|
534
|
-
style: LabelStyle = None,
|
|
535
|
-
hide_on_collision: bool = True,
|
|
536
|
-
**kwargs,
|
|
537
|
-
):
|
|
538
|
-
"""
|
|
539
|
-
Plots text
|
|
540
|
-
|
|
541
|
-
Args:
|
|
542
|
-
text: Text to plot
|
|
543
|
-
ra: Right ascension of text (0...360)
|
|
544
|
-
dec: Declination of text (-90...90)
|
|
545
|
-
style: Styling of the text
|
|
546
|
-
hide_on_collision: If True, then the text will not be plotted if it collides with another label
|
|
547
|
-
"""
|
|
548
|
-
if not text:
|
|
549
|
-
return
|
|
550
|
-
|
|
551
|
-
style = style or LabelStyle()
|
|
552
|
-
|
|
553
|
-
if style.offset_x == "auto":
|
|
554
|
-
style.offset_x = 0
|
|
555
|
-
|
|
556
|
-
if style.offset_y == "auto":
|
|
557
|
-
style.offset_y = 0
|
|
558
|
-
|
|
559
|
-
if kwargs.get("area"):
|
|
560
|
-
return self._text_area(
|
|
561
|
-
ra,
|
|
562
|
-
dec,
|
|
563
|
-
text,
|
|
564
|
-
**style.matplot_kwargs(self.scale),
|
|
565
|
-
area=kwargs.pop("area"),
|
|
566
|
-
hide_on_collision=hide_on_collision,
|
|
567
|
-
xycoords="data",
|
|
568
|
-
xytext=(style.offset_x * self.scale, style.offset_y * self.scale),
|
|
569
|
-
textcoords="offset points",
|
|
570
|
-
settings=kwargs.pop("auto_adjust_settings"),
|
|
571
|
-
**kwargs,
|
|
572
|
-
)
|
|
573
|
-
else:
|
|
574
|
-
return self._text_point(
|
|
575
|
-
ra,
|
|
576
|
-
dec,
|
|
577
|
-
text,
|
|
578
|
-
**style.matplot_kwargs(self.scale),
|
|
579
|
-
hide_on_collision=hide_on_collision,
|
|
580
|
-
xycoords="data",
|
|
581
|
-
xytext=(style.offset_x * self.scale, style.offset_y * self.scale),
|
|
582
|
-
textcoords="offset points",
|
|
583
|
-
**kwargs,
|
|
584
|
-
)
|
|
585
|
-
|
|
586
169
|
@property
|
|
587
170
|
def objects(self) -> models.ObjectList:
|
|
588
171
|
"""
|
|
@@ -637,6 +220,7 @@ class BasePlot(ABC):
|
|
|
637
220
|
label: Optional[str] = None,
|
|
638
221
|
legend_label: str = None,
|
|
639
222
|
skip_bounds_check: bool = False,
|
|
223
|
+
collision_handler: CollisionHandler = None,
|
|
640
224
|
**kwargs,
|
|
641
225
|
) -> None:
|
|
642
226
|
"""Plots a marker
|
|
@@ -648,6 +232,7 @@ class BasePlot(ABC):
|
|
|
648
232
|
style: Styling for the marker
|
|
649
233
|
legend_label: How to label the marker in the legend. If `None`, then the marker will not be added to the legend
|
|
650
234
|
skip_bounds_check: If True, then don't check the marker coordinates to ensure they're within the bounds of the plot. If you're plotting many markers, setting this to True can speed up plotting time.
|
|
235
|
+
collision_handler: An instance of [CollisionHandler][starplot.CollisionHandler] that describes what to do on label collisions with other labels, markers, etc. If `None`, then the collision handler of the plot will be used.
|
|
651
236
|
|
|
652
237
|
"""
|
|
653
238
|
|
|
@@ -700,7 +285,7 @@ class BasePlot(ABC):
|
|
|
700
285
|
ra,
|
|
701
286
|
dec,
|
|
702
287
|
label_style,
|
|
703
|
-
|
|
288
|
+
collision_handler=collision_handler or self.collision_handler,
|
|
704
289
|
gid=kwargs.get("gid_label") or "marker-label",
|
|
705
290
|
)
|
|
706
291
|
|
|
@@ -714,6 +299,7 @@ class BasePlot(ABC):
|
|
|
714
299
|
true_size: bool = False,
|
|
715
300
|
labels: Dict[PlanetName, str] = PLANET_LABELS_DEFAULT,
|
|
716
301
|
legend_label: str = "Planet",
|
|
302
|
+
collision_handler: CollisionHandler = None,
|
|
717
303
|
) -> None:
|
|
718
304
|
"""
|
|
719
305
|
Plots the planets.
|
|
@@ -725,6 +311,7 @@ class BasePlot(ABC):
|
|
|
725
311
|
true_size: If True, then each planet's true apparent size in the sky will be plotted. If False, then the style's marker size will be used.
|
|
726
312
|
labels: How the planets will be labeled on the plot and legend. If not specified, then the planet's name will be used (see [`Planet`][starplot.models.planet.PlanetName])
|
|
727
313
|
legend_label: How to label the planets in the legend. If `None`, then the planets will not be added to the legend
|
|
314
|
+
collision_handler: An instance of [CollisionHandler][starplot.CollisionHandler] that describes what to do on label collisions with other labels, markers, etc. If `None`, then the collision handler of the plot will be used.
|
|
728
315
|
"""
|
|
729
316
|
labels = labels or {}
|
|
730
317
|
planets = models.Planet.all(
|
|
@@ -732,6 +319,7 @@ class BasePlot(ABC):
|
|
|
732
319
|
)
|
|
733
320
|
|
|
734
321
|
legend_label = translate(legend_label, self.language)
|
|
322
|
+
handler = collision_handler or self.collision_handler
|
|
735
323
|
|
|
736
324
|
for p in planets:
|
|
737
325
|
label = labels.get(p.name)
|
|
@@ -753,7 +341,12 @@ class BasePlot(ABC):
|
|
|
753
341
|
|
|
754
342
|
if label:
|
|
755
343
|
self.text(
|
|
756
|
-
label.upper(),
|
|
344
|
+
label.upper(),
|
|
345
|
+
p.ra,
|
|
346
|
+
p.dec,
|
|
347
|
+
style.label,
|
|
348
|
+
collision_handler=handler,
|
|
349
|
+
gid="planet-label",
|
|
757
350
|
)
|
|
758
351
|
else:
|
|
759
352
|
self.marker(
|
|
@@ -762,6 +355,7 @@ class BasePlot(ABC):
|
|
|
762
355
|
style=style,
|
|
763
356
|
label=label.upper() if label else None,
|
|
764
357
|
legend_label=legend_label,
|
|
358
|
+
collision_handler=handler,
|
|
765
359
|
gid_marker="planet-marker",
|
|
766
360
|
gid_label="planet-label",
|
|
767
361
|
)
|
|
@@ -773,6 +367,7 @@ class BasePlot(ABC):
|
|
|
773
367
|
true_size: bool = False,
|
|
774
368
|
label: str = "Sun",
|
|
775
369
|
legend_label: str = "Sun",
|
|
370
|
+
collision_handler: CollisionHandler = None,
|
|
776
371
|
) -> None:
|
|
777
372
|
"""
|
|
778
373
|
Plots the Sun.
|
|
@@ -784,6 +379,7 @@ class BasePlot(ABC):
|
|
|
784
379
|
true_size: If True, then the Sun's true apparent size in the sky will be plotted as a circle (the marker style's symbol will be ignored). If False, then the style's marker size will be used.
|
|
785
380
|
label: How the Sun will be labeled on the plot
|
|
786
381
|
legend_label: How the sun will be labeled in the legend
|
|
382
|
+
collision_handler: An instance of [CollisionHandler][starplot.CollisionHandler] that describes what to do on label collisions with other labels, markers, etc. If `None`, then the collision handler of the plot will be used.
|
|
787
383
|
"""
|
|
788
384
|
s = models.Sun.get(
|
|
789
385
|
dt=self.observer.dt,
|
|
@@ -794,6 +390,7 @@ class BasePlot(ABC):
|
|
|
794
390
|
label = translate(label, self.language)
|
|
795
391
|
legend_label = translate(legend_label, self.language)
|
|
796
392
|
s.name = label or s.name
|
|
393
|
+
handler = collision_handler or self.collision_handler
|
|
797
394
|
|
|
798
395
|
if not self.in_bounds(s.ra, s.dec):
|
|
799
396
|
return
|
|
@@ -817,7 +414,14 @@ class BasePlot(ABC):
|
|
|
817
414
|
self._add_legend_handle_marker(legend_label, style.marker)
|
|
818
415
|
|
|
819
416
|
if label:
|
|
820
|
-
self.text(
|
|
417
|
+
self.text(
|
|
418
|
+
label,
|
|
419
|
+
s.ra,
|
|
420
|
+
s.dec,
|
|
421
|
+
style.label,
|
|
422
|
+
collision_handler=handler,
|
|
423
|
+
gid="sun-label",
|
|
424
|
+
)
|
|
821
425
|
|
|
822
426
|
else:
|
|
823
427
|
self.marker(
|
|
@@ -826,6 +430,7 @@ class BasePlot(ABC):
|
|
|
826
430
|
style=style,
|
|
827
431
|
label=label,
|
|
828
432
|
legend_label=legend_label,
|
|
433
|
+
collision_handler=handler,
|
|
829
434
|
gid_marker="sun-marker",
|
|
830
435
|
gid_label="sun-label",
|
|
831
436
|
)
|
|
@@ -1053,6 +658,7 @@ class BasePlot(ABC):
|
|
|
1053
658
|
show_phase: bool = False,
|
|
1054
659
|
label: str = "Moon",
|
|
1055
660
|
legend_label: str = "Moon",
|
|
661
|
+
collision_handler: CollisionHandler = None,
|
|
1056
662
|
) -> None:
|
|
1057
663
|
"""
|
|
1058
664
|
Plots the Moon.
|
|
@@ -1063,7 +669,9 @@ class BasePlot(ABC):
|
|
|
1063
669
|
style: Styling of the Moon. If None, then the plot's style (specified when creating the plot) will be used
|
|
1064
670
|
true_size: If True, then the Moon's true apparent size in the sky will be plotted as a circle (the marker style's symbol will be ignored). If False, then the style's marker size will be used.
|
|
1065
671
|
show_phase: If True, and if `true_size = True`, then the approximate phase of the moon will be illustrated. The dark side of the moon will be colored with the marker's `edge_color`.
|
|
1066
|
-
label: How the Moon will be labeled on the plot
|
|
672
|
+
label: How the Moon will be labeled on the plot
|
|
673
|
+
legend_label: How the Moon will be labeled in the legend
|
|
674
|
+
collision_handler: An instance of [CollisionHandler][starplot.CollisionHandler] that describes what to do on label collisions with other labels, markers, etc. If `None`, then the collision handler of the plot will be used.
|
|
1067
675
|
"""
|
|
1068
676
|
m = models.Moon.get(
|
|
1069
677
|
dt=self.observer.dt,
|
|
@@ -1074,6 +682,7 @@ class BasePlot(ABC):
|
|
|
1074
682
|
label = translate(label, self.language)
|
|
1075
683
|
legend_label = translate(legend_label, self.language)
|
|
1076
684
|
m.name = label or m.name
|
|
685
|
+
handler = collision_handler or self.collision_handler
|
|
1077
686
|
|
|
1078
687
|
if not self.in_bounds(m.ra, m.dec):
|
|
1079
688
|
return
|
|
@@ -1107,7 +716,14 @@ class BasePlot(ABC):
|
|
|
1107
716
|
self._add_legend_handle_marker(legend_label, style.marker)
|
|
1108
717
|
|
|
1109
718
|
if label:
|
|
1110
|
-
self.text(
|
|
719
|
+
self.text(
|
|
720
|
+
label,
|
|
721
|
+
m.ra,
|
|
722
|
+
m.dec,
|
|
723
|
+
style.label,
|
|
724
|
+
collision_handler=handler,
|
|
725
|
+
gid="moon-label",
|
|
726
|
+
)
|
|
1111
727
|
|
|
1112
728
|
else:
|
|
1113
729
|
self.marker(
|
|
@@ -1116,6 +732,7 @@ class BasePlot(ABC):
|
|
|
1116
732
|
style=style,
|
|
1117
733
|
label=label,
|
|
1118
734
|
legend_label=legend_label,
|
|
735
|
+
collision_handler=handler,
|
|
1119
736
|
gid_marker="moon-marker",
|
|
1120
737
|
gid_label="moon-label",
|
|
1121
738
|
)
|
|
@@ -1238,12 +855,18 @@ class BasePlot(ABC):
|
|
|
1238
855
|
)
|
|
1239
856
|
|
|
1240
857
|
@use_style(PathStyle, "ecliptic")
|
|
1241
|
-
def ecliptic(
|
|
858
|
+
def ecliptic(
|
|
859
|
+
self,
|
|
860
|
+
style: PathStyle = None,
|
|
861
|
+
label: str = "ECLIPTIC",
|
|
862
|
+
collision_handler: CollisionHandler = None,
|
|
863
|
+
):
|
|
1242
864
|
"""Plots the ecliptic
|
|
1243
865
|
|
|
1244
866
|
Args:
|
|
1245
867
|
style: Styling of the ecliptic. If None, then the plot's style will be used
|
|
1246
868
|
label: How the ecliptic will be labeled on the plot
|
|
869
|
+
collision_handler: An instance of [CollisionHandler][starplot.CollisionHandler] that describes what to do on label collisions with other labels, markers, etc. If `None`, then the collision handler of the plot will be used.
|
|
1247
870
|
"""
|
|
1248
871
|
x = []
|
|
1249
872
|
y = []
|
|
@@ -1272,11 +895,21 @@ class BasePlot(ABC):
|
|
|
1272
895
|
label_spacing = int(len(inbounds) / 4)
|
|
1273
896
|
|
|
1274
897
|
for ra, dec in [inbounds[label_spacing], inbounds[label_spacing * 2]]:
|
|
1275
|
-
self.text(
|
|
898
|
+
self.text(
|
|
899
|
+
label,
|
|
900
|
+
ra,
|
|
901
|
+
dec,
|
|
902
|
+
style.label,
|
|
903
|
+
collision_handler=collision_handler or self.collision_handler,
|
|
904
|
+
gid="ecliptic-label",
|
|
905
|
+
)
|
|
1276
906
|
|
|
1277
907
|
@use_style(PathStyle, "celestial_equator")
|
|
1278
908
|
def celestial_equator(
|
|
1279
|
-
self,
|
|
909
|
+
self,
|
|
910
|
+
style: PathStyle = None,
|
|
911
|
+
label: str = "CELESTIAL EQUATOR",
|
|
912
|
+
collision_handler: CollisionHandler = None,
|
|
1280
913
|
):
|
|
1281
914
|
"""
|
|
1282
915
|
Plots the celestial equator
|
|
@@ -1284,6 +917,7 @@ class BasePlot(ABC):
|
|
|
1284
917
|
Args:
|
|
1285
918
|
style: Styling of the celestial equator. If None, then the plot's style will be used
|
|
1286
919
|
label: How the celestial equator will be labeled on the plot
|
|
920
|
+
collision_handler: An instance of [CollisionHandler][starplot.CollisionHandler] that describes what to do on label collisions with other labels, markers, etc. If `None`, then the collision handler of the plot will be used.
|
|
1287
921
|
"""
|
|
1288
922
|
x = []
|
|
1289
923
|
y = []
|
|
@@ -1314,5 +948,6 @@ class BasePlot(ABC):
|
|
|
1314
948
|
ra,
|
|
1315
949
|
0.25,
|
|
1316
950
|
style.label,
|
|
951
|
+
collision_handler=collision_handler or self.collision_handler,
|
|
1317
952
|
gid="celestial-equator-label",
|
|
1318
953
|
)
|