starplot 0.12.4__py2.py3-none-any.whl → 0.13.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.
Potentially problematic release.
This version of starplot might be problematic. Click here for more details.
- starplot/__init__.py +1 -1
- starplot/base.py +371 -60
- starplot/callables.py +61 -7
- starplot/data/bayer.py +1532 -3
- starplot/data/flamsteed.py +2682 -0
- starplot/data/library/constellation_borders_inv.gpkg +0 -0
- starplot/data/library/constellation_lines_hips.json +3 -1
- starplot/data/stars.py +408 -87
- starplot/horizon.py +398 -0
- starplot/map.py +149 -24
- starplot/models/constellation.py +1 -0
- starplot/optic.py +26 -14
- starplot/plotters/dsos.py +6 -2
- starplot/plotters/stars.py +114 -13
- starplot/styles/base.py +263 -156
- starplot/styles/ext/antique.yml +45 -39
- starplot/styles/ext/blue_dark.yml +32 -36
- starplot/styles/ext/blue_light.yml +43 -25
- starplot/styles/ext/blue_medium.yml +48 -44
- starplot/styles/ext/cb_wong.yml +7 -0
- starplot/styles/ext/grayscale.yml +13 -7
- starplot/styles/ext/grayscale_dark.yml +12 -4
- starplot/styles/ext/map.yml +4 -4
- starplot/styles/ext/nord.yml +32 -32
- starplot/styles/ext/optic.yml +7 -5
- starplot/styles/fonts-library/gfs-didot/DESCRIPTION.en_us.html +9 -0
- starplot/styles/fonts-library/gfs-didot/GFSDidot-Regular.ttf +0 -0
- starplot/styles/fonts-library/gfs-didot/METADATA.pb +16 -0
- starplot/styles/fonts-library/gfs-didot/OFL.txt +94 -0
- starplot/styles/fonts-library/hind/DESCRIPTION.en_us.html +28 -0
- starplot/styles/fonts-library/hind/Hind-Bold.ttf +0 -0
- starplot/styles/fonts-library/hind/Hind-Light.ttf +0 -0
- starplot/styles/fonts-library/hind/Hind-Medium.ttf +0 -0
- starplot/styles/fonts-library/hind/Hind-Regular.ttf +0 -0
- starplot/styles/fonts-library/hind/Hind-SemiBold.ttf +0 -0
- starplot/styles/fonts-library/hind/METADATA.pb +58 -0
- starplot/styles/fonts-library/hind/OFL.txt +93 -0
- starplot/styles/fonts-library/inter/Inter-Black.ttf +0 -0
- starplot/styles/fonts-library/inter/Inter-BlackItalic.ttf +0 -0
- starplot/styles/fonts-library/inter/Inter-Bold.ttf +0 -0
- starplot/styles/fonts-library/inter/Inter-BoldItalic.ttf +0 -0
- starplot/styles/fonts-library/inter/Inter-ExtraBold.ttf +0 -0
- starplot/styles/fonts-library/inter/Inter-ExtraBoldItalic.ttf +0 -0
- starplot/styles/fonts-library/inter/Inter-ExtraLight.ttf +0 -0
- starplot/styles/fonts-library/inter/Inter-ExtraLightItalic.ttf +0 -0
- starplot/styles/fonts-library/inter/Inter-Italic.ttf +0 -0
- starplot/styles/fonts-library/inter/Inter-Light.ttf +0 -0
- starplot/styles/fonts-library/inter/Inter-LightItalic.ttf +0 -0
- starplot/styles/fonts-library/inter/Inter-Medium.ttf +0 -0
- starplot/styles/fonts-library/inter/Inter-MediumItalic.ttf +0 -0
- starplot/styles/fonts-library/inter/Inter-Regular.ttf +0 -0
- starplot/styles/fonts-library/inter/Inter-SemiBold.ttf +0 -0
- starplot/styles/fonts-library/inter/Inter-SemiBoldItalic.ttf +0 -0
- starplot/styles/fonts-library/inter/Inter-Thin.ttf +0 -0
- starplot/styles/fonts-library/inter/Inter-ThinItalic.ttf +0 -0
- starplot/styles/fonts-library/inter/LICENSE.txt +92 -0
- starplot/styles/fonts.py +15 -0
- starplot/styles/markers.py +207 -6
- {starplot-0.12.4.dist-info → starplot-0.13.0.dist-info}/METADATA +9 -10
- starplot-0.13.0.dist-info/RECORD +101 -0
- starplot-0.12.4.dist-info/RECORD +0 -67
- {starplot-0.12.4.dist-info → starplot-0.13.0.dist-info}/LICENSE +0 -0
- {starplot-0.12.4.dist-info → starplot-0.13.0.dist-info}/WHEEL +0 -0
starplot/base.py
CHANGED
|
@@ -2,6 +2,7 @@ from abc import ABC, abstractmethod
|
|
|
2
2
|
from datetime import datetime
|
|
3
3
|
from typing import Dict, Union, Optional
|
|
4
4
|
import logging
|
|
5
|
+
import warnings
|
|
5
6
|
|
|
6
7
|
import numpy as np
|
|
7
8
|
import rtree
|
|
@@ -17,6 +18,7 @@ from starplot.data import load, ecliptic
|
|
|
17
18
|
from starplot.models.planet import PlanetName, PLANET_LABELS_DEFAULT
|
|
18
19
|
from starplot.models.moon import MoonPhase
|
|
19
20
|
from starplot.styles import (
|
|
21
|
+
AnchorPointEnum,
|
|
20
22
|
PlotStyle,
|
|
21
23
|
MarkerStyle,
|
|
22
24
|
ObjectStyle,
|
|
@@ -27,9 +29,16 @@ from starplot.styles import (
|
|
|
27
29
|
MarkerSymbolEnum,
|
|
28
30
|
PathStyle,
|
|
29
31
|
PolygonStyle,
|
|
32
|
+
fonts,
|
|
30
33
|
)
|
|
31
34
|
from starplot.styles.helpers import use_style
|
|
32
35
|
|
|
36
|
+
# ignore noisy matplotlib warnings
|
|
37
|
+
warnings.filterwarnings(
|
|
38
|
+
"ignore",
|
|
39
|
+
message="Setting the 'color' property will override the edgecolor or facecolor properties",
|
|
40
|
+
)
|
|
41
|
+
|
|
33
42
|
LOGGER = logging.getLogger("starplot")
|
|
34
43
|
LOG_HANDLER = logging.StreamHandler()
|
|
35
44
|
LOG_FORMATTER = logging.Formatter(
|
|
@@ -46,6 +55,10 @@ DEFAULT_FOV_STYLE = PolygonStyle(
|
|
|
46
55
|
|
|
47
56
|
DEFAULT_STYLE = PlotStyle()
|
|
48
57
|
|
|
58
|
+
DEFAULT_RESOLUTION = 4096
|
|
59
|
+
|
|
60
|
+
DPI = 100
|
|
61
|
+
|
|
49
62
|
|
|
50
63
|
class BasePlot(ABC):
|
|
51
64
|
_background_clip_path = None
|
|
@@ -55,26 +68,38 @@ class BasePlot(ABC):
|
|
|
55
68
|
dt: datetime = None,
|
|
56
69
|
ephemeris: str = "de421_2001.bsp",
|
|
57
70
|
style: PlotStyle = DEFAULT_STYLE,
|
|
58
|
-
resolution: int =
|
|
71
|
+
resolution: int = 4096,
|
|
59
72
|
hide_colliding_labels: bool = True,
|
|
73
|
+
scale: float = 1.0,
|
|
74
|
+
autoscale: bool = False,
|
|
60
75
|
*args,
|
|
61
76
|
**kwargs,
|
|
62
77
|
):
|
|
63
|
-
px = 1 / plt.rcParams["figure.dpi"] # pixel in inches
|
|
78
|
+
px = 1 / DPI # plt.rcParams["figure.dpi"] # pixel in inches
|
|
64
79
|
|
|
65
|
-
self.pixels_per_point =
|
|
80
|
+
self.pixels_per_point = DPI / 72
|
|
66
81
|
|
|
67
82
|
self.style = style
|
|
68
83
|
self.figure_size = resolution * px
|
|
69
84
|
self.resolution = resolution
|
|
70
85
|
self.hide_colliding_labels = hide_colliding_labels
|
|
71
86
|
|
|
87
|
+
self.scale = scale
|
|
88
|
+
self.autoscale = autoscale
|
|
89
|
+
if self.autoscale:
|
|
90
|
+
self.scale = self.resolution / DEFAULT_RESOLUTION
|
|
91
|
+
|
|
72
92
|
self.dt = dt or timezone("UTC").localize(datetime.now())
|
|
73
93
|
self._ephemeris_name = ephemeris
|
|
74
94
|
self.ephemeris = load(ephemeris)
|
|
75
95
|
|
|
76
96
|
self.labels = []
|
|
77
97
|
self._labels_rtree = rtree.index.Index()
|
|
98
|
+
|
|
99
|
+
# self.labels = []
|
|
100
|
+
self._constellations_rtree = rtree.index.Index()
|
|
101
|
+
self._stars_rtree = rtree.index.Index()
|
|
102
|
+
|
|
78
103
|
self._background_clip_path = None
|
|
79
104
|
|
|
80
105
|
self._legend = None
|
|
@@ -85,13 +110,14 @@ class BasePlot(ABC):
|
|
|
85
110
|
self.logger.setLevel(self.log_level)
|
|
86
111
|
|
|
87
112
|
self.text_border = patheffects.withStroke(
|
|
88
|
-
linewidth=self.style.text_border_width,
|
|
113
|
+
linewidth=self.style.text_border_width * self.scale,
|
|
89
114
|
foreground=self.style.text_border_color.as_hex(),
|
|
90
115
|
)
|
|
91
|
-
self._size_multiplier = self.resolution / 3000
|
|
92
116
|
self.timescale = load.timescale().from_datetime(self.dt)
|
|
93
117
|
|
|
94
118
|
self._objects = models.ObjectList()
|
|
119
|
+
self._labeled_stars = []
|
|
120
|
+
fonts.load()
|
|
95
121
|
|
|
96
122
|
def _plot_kwargs(self) -> dict:
|
|
97
123
|
return {}
|
|
@@ -107,36 +133,62 @@ class BasePlot(ABC):
|
|
|
107
133
|
)
|
|
108
134
|
return len(ix) > 0
|
|
109
135
|
|
|
136
|
+
def _is_object_collision(self, extent) -> bool:
|
|
137
|
+
ix = list(
|
|
138
|
+
self._constellations_rtree.intersection(
|
|
139
|
+
(extent.x0, extent.y0, extent.x1, extent.y1)
|
|
140
|
+
)
|
|
141
|
+
)
|
|
142
|
+
return len(ix) > 0
|
|
143
|
+
|
|
144
|
+
def _is_star_collision(self, extent) -> bool:
|
|
145
|
+
ix = list(
|
|
146
|
+
self._stars_rtree.intersection((extent.x0, extent.y0, extent.x1, extent.y1))
|
|
147
|
+
)
|
|
148
|
+
return len(ix) > 0
|
|
149
|
+
|
|
110
150
|
def _is_clipped(self, extent) -> bool:
|
|
111
151
|
return self._background_clip_path is not None and not all(
|
|
112
152
|
self._background_clip_path.contains_points(extent.get_points())
|
|
113
153
|
)
|
|
114
154
|
|
|
115
|
-
def
|
|
155
|
+
def _add_label_to_rtree(self, label, extent=None):
|
|
156
|
+
extent = extent or label.get_window_extent(
|
|
157
|
+
renderer=self.fig.canvas.get_renderer()
|
|
158
|
+
)
|
|
159
|
+
self.labels.append(label)
|
|
160
|
+
self._labels_rtree.insert(
|
|
161
|
+
0, np.array((extent.x0, extent.y0, extent.x1, extent.y1))
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
def _maybe_remove_label(
|
|
165
|
+
self, label, remove_on_collision=True, remove_on_clipped=True
|
|
166
|
+
) -> bool:
|
|
167
|
+
"""Returns true if the label is removed, else false"""
|
|
116
168
|
extent = label.get_window_extent(renderer=self.fig.canvas.get_renderer())
|
|
117
169
|
|
|
118
170
|
if any([np.isnan(c) for c in (extent.x0, extent.y0, extent.x1, extent.y1)]):
|
|
119
171
|
label.remove()
|
|
120
|
-
return
|
|
172
|
+
return True
|
|
121
173
|
|
|
122
|
-
if
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
174
|
+
if remove_on_clipped and self._is_clipped(extent):
|
|
175
|
+
label.remove()
|
|
176
|
+
return True
|
|
177
|
+
|
|
178
|
+
if remove_on_collision and (
|
|
179
|
+
self._is_label_collision(extent)
|
|
180
|
+
or self._is_object_collision(extent)
|
|
181
|
+
# or self._is_star_collision(extent)
|
|
127
182
|
):
|
|
128
183
|
label.remove()
|
|
129
|
-
return
|
|
184
|
+
return True
|
|
130
185
|
|
|
131
|
-
|
|
132
|
-
self._labels_rtree.insert(
|
|
133
|
-
0, np.array((extent.x0, extent.y0, extent.x1, extent.y1))
|
|
134
|
-
)
|
|
186
|
+
return False
|
|
135
187
|
|
|
136
188
|
def _add_legend_handle_marker(self, label: str, style: MarkerStyle):
|
|
137
189
|
if label is not None and label not in self._legend_handles:
|
|
138
190
|
s = style.matplot_kwargs()
|
|
139
|
-
s["markersize"] = self.style.legend.symbol_size * self.
|
|
191
|
+
s["markersize"] = self.style.legend.symbol_size * self.scale
|
|
140
192
|
self._legend_handles[label] = Line2D(
|
|
141
193
|
[],
|
|
142
194
|
[],
|
|
@@ -146,12 +198,176 @@ class BasePlot(ABC):
|
|
|
146
198
|
label=label,
|
|
147
199
|
)
|
|
148
200
|
|
|
201
|
+
def _collision_score(self, label) -> int:
|
|
202
|
+
config = {
|
|
203
|
+
"labels": 1.0, # always fail
|
|
204
|
+
"stars": 0.5,
|
|
205
|
+
"constellations": 0.8,
|
|
206
|
+
"anchors": [
|
|
207
|
+
("bottom right", 0),
|
|
208
|
+
("top right", 0.2),
|
|
209
|
+
("top left", 0.5),
|
|
210
|
+
],
|
|
211
|
+
"on_fail": "plot",
|
|
212
|
+
}
|
|
213
|
+
extent = label.get_window_extent(renderer=self.fig.canvas.get_renderer())
|
|
214
|
+
|
|
215
|
+
if any(
|
|
216
|
+
[np.isnan(c) for c in (extent.x0, extent.y0, extent.x1, extent.y1)]
|
|
217
|
+
) or self._is_clipped(extent):
|
|
218
|
+
return 1
|
|
219
|
+
|
|
220
|
+
x_labels = (
|
|
221
|
+
len(
|
|
222
|
+
list(
|
|
223
|
+
self._labels_rtree.intersection(
|
|
224
|
+
(extent.x0, extent.y0, extent.x1, extent.y1)
|
|
225
|
+
)
|
|
226
|
+
)
|
|
227
|
+
)
|
|
228
|
+
* config["labels"]
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
if x_labels >= 1:
|
|
232
|
+
return 1
|
|
233
|
+
|
|
234
|
+
x_constellations = (
|
|
235
|
+
len(
|
|
236
|
+
list(
|
|
237
|
+
self._constellations_rtree.intersection(
|
|
238
|
+
(extent.x0, extent.y0, extent.x1, extent.y1)
|
|
239
|
+
)
|
|
240
|
+
)
|
|
241
|
+
)
|
|
242
|
+
* config["constellations"]
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
if x_constellations >= 1:
|
|
246
|
+
return 1
|
|
247
|
+
|
|
248
|
+
x_stars = (
|
|
249
|
+
len(
|
|
250
|
+
list(
|
|
251
|
+
self._stars_rtree.intersection(
|
|
252
|
+
(extent.x0, extent.y0, extent.x1, extent.y1)
|
|
253
|
+
)
|
|
254
|
+
)
|
|
255
|
+
)
|
|
256
|
+
* config["stars"]
|
|
257
|
+
)
|
|
258
|
+
if x_stars >= 1:
|
|
259
|
+
return 1
|
|
260
|
+
|
|
261
|
+
return sum([x_labels, x_constellations, x_stars]) / 3
|
|
262
|
+
|
|
263
|
+
def _text_experimental(
|
|
264
|
+
self,
|
|
265
|
+
ra: float,
|
|
266
|
+
dec: float,
|
|
267
|
+
text: str,
|
|
268
|
+
hide_on_collision: bool = True,
|
|
269
|
+
auto_anchor: bool = True,
|
|
270
|
+
*args,
|
|
271
|
+
**kwargs,
|
|
272
|
+
) -> None:
|
|
273
|
+
if not text:
|
|
274
|
+
return
|
|
275
|
+
|
|
276
|
+
x, y = self._prepare_coords(ra, dec)
|
|
277
|
+
kwargs["path_effects"] = kwargs.get("path_effects", [self.text_border])
|
|
278
|
+
clip_on = kwargs.get("clip_on") or True
|
|
279
|
+
|
|
280
|
+
def plot_text(**kwargs):
|
|
281
|
+
label = self.ax.annotate(
|
|
282
|
+
text,
|
|
283
|
+
(x, y),
|
|
284
|
+
*args,
|
|
285
|
+
**kwargs,
|
|
286
|
+
**self._plot_kwargs(),
|
|
287
|
+
)
|
|
288
|
+
if clip_on:
|
|
289
|
+
label.set_clip_on(True)
|
|
290
|
+
label.set_clip_path(self._background_clip_path)
|
|
291
|
+
return label
|
|
292
|
+
|
|
293
|
+
def add_label(label):
|
|
294
|
+
extent = label.get_window_extent(renderer=self.fig.canvas.get_renderer())
|
|
295
|
+
self.labels.append(label)
|
|
296
|
+
self._labels_rtree.insert(
|
|
297
|
+
0, np.array((extent.x0, extent.y0, extent.x1, extent.y1))
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
label = plot_text(**kwargs)
|
|
301
|
+
|
|
302
|
+
if not clip_on:
|
|
303
|
+
add_label(label)
|
|
304
|
+
return
|
|
305
|
+
|
|
306
|
+
if not hide_on_collision and not auto_anchor:
|
|
307
|
+
add_label(label)
|
|
308
|
+
return
|
|
309
|
+
|
|
310
|
+
# removed = self._maybe_remove_label(label)
|
|
311
|
+
collision = self._collision_score(label)
|
|
312
|
+
|
|
313
|
+
if collision == 0:
|
|
314
|
+
add_label(label)
|
|
315
|
+
return
|
|
316
|
+
|
|
317
|
+
label.remove()
|
|
318
|
+
|
|
319
|
+
collision_scores = []
|
|
320
|
+
original_va = kwargs.pop("va", None)
|
|
321
|
+
original_ha = kwargs.pop("ha", None)
|
|
322
|
+
original_offset_x, original_offset_y = kwargs.pop("xytext", (0, 0))
|
|
323
|
+
anchor_fallbacks = self.style.text_anchor_fallbacks
|
|
324
|
+
for i, a in enumerate(anchor_fallbacks):
|
|
325
|
+
d = AnchorPointEnum.from_str(a).as_matplot()
|
|
326
|
+
va, ha = d["va"], d["ha"]
|
|
327
|
+
offset_x, offset_y = original_offset_x, original_offset_y
|
|
328
|
+
if original_ha != ha:
|
|
329
|
+
offset_x *= -1
|
|
330
|
+
|
|
331
|
+
if original_va != va:
|
|
332
|
+
offset_y *= -1
|
|
333
|
+
|
|
334
|
+
if ha == "center":
|
|
335
|
+
offset_x = 0
|
|
336
|
+
offset_y = 0
|
|
337
|
+
|
|
338
|
+
pt_kwargs = dict(**kwargs, va=va, ha=ha, xytext=(offset_x, offset_y))
|
|
339
|
+
label = plot_text(**pt_kwargs)
|
|
340
|
+
|
|
341
|
+
# if not hide_on_collision and i == len(anchor_fallbacks) - 1:
|
|
342
|
+
# break
|
|
343
|
+
|
|
344
|
+
collision = self._collision_score(label)
|
|
345
|
+
if collision == 0:
|
|
346
|
+
add_label(label)
|
|
347
|
+
return
|
|
348
|
+
|
|
349
|
+
if collision < 1:
|
|
350
|
+
collision_scores.append((collision, pt_kwargs))
|
|
351
|
+
|
|
352
|
+
label.remove()
|
|
353
|
+
# removed = self._maybe_remove_label(label)
|
|
354
|
+
# if not removed:
|
|
355
|
+
# break
|
|
356
|
+
if len(collision_scores) > 0:
|
|
357
|
+
best = sorted(collision_scores, key=lambda c: c[0])[0]
|
|
358
|
+
# return
|
|
359
|
+
if best[0] < 1:
|
|
360
|
+
label = plot_text(**best[1])
|
|
361
|
+
add_label(label)
|
|
362
|
+
|
|
149
363
|
def _text(
|
|
150
364
|
self,
|
|
151
365
|
ra: float,
|
|
152
366
|
dec: float,
|
|
153
367
|
text: str,
|
|
154
368
|
hide_on_collision: bool = True,
|
|
369
|
+
force: bool = False,
|
|
370
|
+
clip_on: bool = True,
|
|
155
371
|
*args,
|
|
156
372
|
**kwargs,
|
|
157
373
|
) -> None:
|
|
@@ -160,21 +376,59 @@ class BasePlot(ABC):
|
|
|
160
376
|
|
|
161
377
|
x, y = self._prepare_coords(ra, dec)
|
|
162
378
|
kwargs["path_effects"] = kwargs.get("path_effects", [self.text_border])
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
(
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
379
|
+
|
|
380
|
+
def plot_text(**kwargs):
|
|
381
|
+
label = self.ax.annotate(
|
|
382
|
+
text,
|
|
383
|
+
(x, y),
|
|
384
|
+
*args,
|
|
385
|
+
**kwargs,
|
|
386
|
+
**self._plot_kwargs(),
|
|
387
|
+
)
|
|
388
|
+
if clip_on:
|
|
389
|
+
label.set_clip_on(True)
|
|
390
|
+
label.set_clip_path(self._background_clip_path)
|
|
391
|
+
return label
|
|
392
|
+
|
|
393
|
+
label = plot_text(**kwargs)
|
|
394
|
+
|
|
395
|
+
if force:
|
|
396
|
+
return
|
|
397
|
+
|
|
398
|
+
removed = self._maybe_remove_label(
|
|
399
|
+
label, remove_on_collision=hide_on_collision, remove_on_clipped=clip_on
|
|
169
400
|
)
|
|
170
|
-
|
|
401
|
+
|
|
402
|
+
if not removed:
|
|
403
|
+
self._add_label_to_rtree(label)
|
|
171
404
|
return
|
|
172
405
|
|
|
173
|
-
|
|
174
|
-
|
|
406
|
+
original_va = kwargs.pop("va", None)
|
|
407
|
+
original_ha = kwargs.pop("ha", None)
|
|
408
|
+
original_offset_x, original_offset_y = kwargs.pop("xytext", (0, 0))
|
|
409
|
+
anchor_fallbacks = self.style.text_anchor_fallbacks
|
|
410
|
+
for i, a in enumerate(anchor_fallbacks):
|
|
411
|
+
d = AnchorPointEnum.from_str(a).as_matplot()
|
|
412
|
+
va, ha = d["va"], d["ha"]
|
|
413
|
+
offset_x, offset_y = original_offset_x, original_offset_y
|
|
414
|
+
if original_ha != ha:
|
|
415
|
+
offset_x *= -1
|
|
416
|
+
|
|
417
|
+
if original_va != va:
|
|
418
|
+
offset_y *= -1
|
|
419
|
+
|
|
420
|
+
if ha == "center":
|
|
421
|
+
offset_x = 0
|
|
422
|
+
offset_y = 0
|
|
423
|
+
|
|
424
|
+
label = plot_text(**kwargs, va=va, ha=ha, xytext=(offset_x, offset_y))
|
|
425
|
+
removed = self._maybe_remove_label(
|
|
426
|
+
label, remove_on_collision=hide_on_collision, remove_on_clipped=clip_on
|
|
427
|
+
)
|
|
175
428
|
|
|
176
|
-
|
|
177
|
-
|
|
429
|
+
if not removed:
|
|
430
|
+
self._add_label_to_rtree(label)
|
|
431
|
+
break
|
|
178
432
|
|
|
179
433
|
@use_style(LabelStyle)
|
|
180
434
|
def text(
|
|
@@ -184,6 +438,8 @@ class BasePlot(ABC):
|
|
|
184
438
|
dec: float,
|
|
185
439
|
style: LabelStyle = None,
|
|
186
440
|
hide_on_collision: bool = True,
|
|
441
|
+
*args,
|
|
442
|
+
**kwargs,
|
|
187
443
|
):
|
|
188
444
|
"""
|
|
189
445
|
Plots text
|
|
@@ -195,15 +451,27 @@ class BasePlot(ABC):
|
|
|
195
451
|
style: Styling of the text
|
|
196
452
|
hide_on_collision: If True, then the text will not be plotted if it collides with another label
|
|
197
453
|
"""
|
|
454
|
+
if not self.in_bounds(ra, dec):
|
|
455
|
+
return
|
|
456
|
+
|
|
198
457
|
style = style or LabelStyle()
|
|
458
|
+
|
|
459
|
+
if style.offset_x == "auto":
|
|
460
|
+
style.offset_x = 0
|
|
461
|
+
|
|
462
|
+
if style.offset_y == "auto":
|
|
463
|
+
style.offset_y = 0
|
|
464
|
+
|
|
199
465
|
self._text(
|
|
200
466
|
ra,
|
|
201
467
|
dec,
|
|
202
468
|
text,
|
|
203
|
-
**style.matplot_kwargs(self.
|
|
469
|
+
**style.matplot_kwargs(self.scale),
|
|
204
470
|
hide_on_collision=hide_on_collision,
|
|
205
|
-
|
|
206
|
-
|
|
471
|
+
xycoords="data",
|
|
472
|
+
xytext=(style.offset_x * self.scale, style.offset_y * self.scale),
|
|
473
|
+
textcoords="offset points",
|
|
474
|
+
**kwargs,
|
|
207
475
|
)
|
|
208
476
|
|
|
209
477
|
@property
|
|
@@ -222,7 +490,7 @@ class BasePlot(ABC):
|
|
|
222
490
|
text: Title text to plot
|
|
223
491
|
style: Styling of the title. If None, then the plot's style (specified when creating the plot) will be used
|
|
224
492
|
"""
|
|
225
|
-
style_kwargs = style.matplot_kwargs(self.
|
|
493
|
+
style_kwargs = style.matplot_kwargs(self.scale)
|
|
226
494
|
style_kwargs.pop("linespacing", None)
|
|
227
495
|
style_kwargs["pad"] = style.line_spacing
|
|
228
496
|
self.ax.set_title(text, **style_kwargs)
|
|
@@ -265,7 +533,7 @@ class BasePlot(ABC):
|
|
|
265
533
|
|
|
266
534
|
self._legend = self.ax.legend(
|
|
267
535
|
handles=self._legend_handles.values(),
|
|
268
|
-
**style.matplot_kwargs(
|
|
536
|
+
**style.matplot_kwargs(self.scale),
|
|
269
537
|
**bbox_kwargs,
|
|
270
538
|
).set_zorder(
|
|
271
539
|
# zorder is not a valid kwarg to legend(), so we have to set it afterwards
|
|
@@ -317,6 +585,7 @@ class BasePlot(ABC):
|
|
|
317
585
|
label: Optional[str] = None,
|
|
318
586
|
legend_label: str = None,
|
|
319
587
|
skip_bounds_check: bool = False,
|
|
588
|
+
**kwargs,
|
|
320
589
|
) -> None:
|
|
321
590
|
"""Plots a marker
|
|
322
591
|
|
|
@@ -335,18 +604,36 @@ class BasePlot(ABC):
|
|
|
335
604
|
|
|
336
605
|
x, y = self._prepare_coords(ra, dec)
|
|
337
606
|
|
|
338
|
-
self.ax.
|
|
607
|
+
self.ax.scatter(
|
|
339
608
|
x,
|
|
340
609
|
y,
|
|
341
|
-
**style.marker.
|
|
610
|
+
**style.marker.matplot_scatter_kwargs(self.scale),
|
|
342
611
|
**self._plot_kwargs(),
|
|
343
|
-
linestyle="None",
|
|
344
612
|
clip_on=True,
|
|
345
613
|
clip_path=self._background_clip_path,
|
|
614
|
+
gid=kwargs.get("gid_marker") or "marker",
|
|
346
615
|
)
|
|
347
616
|
|
|
348
617
|
if label:
|
|
349
|
-
|
|
618
|
+
label_style = style.label
|
|
619
|
+
if label_style.offset_x == "auto" or label_style.offset_y == "auto":
|
|
620
|
+
marker_size = ((style.marker.size / self.scale) ** 2) * (
|
|
621
|
+
self.scale**2
|
|
622
|
+
)
|
|
623
|
+
|
|
624
|
+
label_style = label_style.offset_from_marker(
|
|
625
|
+
marker_symbol=style.marker.symbol,
|
|
626
|
+
marker_size=marker_size,
|
|
627
|
+
scale=self.scale,
|
|
628
|
+
)
|
|
629
|
+
self.text(
|
|
630
|
+
label,
|
|
631
|
+
ra,
|
|
632
|
+
dec,
|
|
633
|
+
label_style,
|
|
634
|
+
hide_on_collision=self.hide_colliding_labels,
|
|
635
|
+
gid=kwargs.get("gid_label") or "marker-label",
|
|
636
|
+
)
|
|
350
637
|
|
|
351
638
|
if legend_label is not None:
|
|
352
639
|
self._add_legend_handle_marker(legend_label, style.marker)
|
|
@@ -386,18 +673,23 @@ class BasePlot(ABC):
|
|
|
386
673
|
(p.ra, p.dec),
|
|
387
674
|
p.apparent_size,
|
|
388
675
|
polygon_style,
|
|
676
|
+
gid="planet-marker",
|
|
389
677
|
)
|
|
390
678
|
self._add_legend_handle_marker(legend_label, style.marker)
|
|
391
679
|
|
|
392
680
|
if label:
|
|
393
|
-
self.text(
|
|
681
|
+
self.text(
|
|
682
|
+
label.upper(), p.ra, p.dec, style.label, gid="planet-label"
|
|
683
|
+
)
|
|
394
684
|
else:
|
|
395
685
|
self.marker(
|
|
396
686
|
ra=p.ra,
|
|
397
687
|
dec=p.dec,
|
|
398
|
-
label=label.upper() if label else None,
|
|
399
688
|
style=style,
|
|
689
|
+
label=label.upper() if label else None,
|
|
400
690
|
legend_label=legend_label,
|
|
691
|
+
gid_marker="planet-marker",
|
|
692
|
+
gid_label="planet-label",
|
|
401
693
|
)
|
|
402
694
|
|
|
403
695
|
@use_style(ObjectStyle, "sun")
|
|
@@ -438,21 +730,24 @@ class BasePlot(ABC):
|
|
|
438
730
|
(s.ra, s.dec),
|
|
439
731
|
s.apparent_size,
|
|
440
732
|
style=polygon_style,
|
|
733
|
+
gid="sun-marker",
|
|
441
734
|
)
|
|
442
735
|
|
|
443
736
|
style.marker.symbol = MarkerSymbolEnum.CIRCLE
|
|
444
737
|
self._add_legend_handle_marker(legend_label, style.marker)
|
|
445
738
|
|
|
446
739
|
if label:
|
|
447
|
-
self.text(label, s.ra, s.dec, style.label)
|
|
740
|
+
self.text(label, s.ra, s.dec, style.label, gid="sun-label")
|
|
448
741
|
|
|
449
742
|
else:
|
|
450
743
|
self.marker(
|
|
451
744
|
ra=s.ra,
|
|
452
745
|
dec=s.dec,
|
|
453
|
-
label=label,
|
|
454
746
|
style=style,
|
|
747
|
+
label=label,
|
|
455
748
|
legend_label=legend_label,
|
|
749
|
+
gid_marker="sun-marker",
|
|
750
|
+
gid_label="sun-label",
|
|
456
751
|
)
|
|
457
752
|
|
|
458
753
|
@abstractmethod
|
|
@@ -491,7 +786,7 @@ class BasePlot(ABC):
|
|
|
491
786
|
patch = patches.Polygon(
|
|
492
787
|
points,
|
|
493
788
|
# closed=False, # needs to be false for circles at poles?
|
|
494
|
-
**style.matplot_kwargs(
|
|
789
|
+
**style.matplot_kwargs(self.scale),
|
|
495
790
|
**kwargs,
|
|
496
791
|
clip_on=True,
|
|
497
792
|
clip_path=self._background_clip_path,
|
|
@@ -504,6 +799,7 @@ class BasePlot(ABC):
|
|
|
504
799
|
style: PolygonStyle,
|
|
505
800
|
points: list = None,
|
|
506
801
|
geometry: Polygon = None,
|
|
802
|
+
**kwargs,
|
|
507
803
|
):
|
|
508
804
|
"""
|
|
509
805
|
Plots a polygon.
|
|
@@ -522,7 +818,7 @@ class BasePlot(ABC):
|
|
|
522
818
|
points = list(zip(*geometry.exterior.coords.xy))
|
|
523
819
|
|
|
524
820
|
_points = [(ra * 15, dec) for ra, dec in points]
|
|
525
|
-
self._polygon(_points, style)
|
|
821
|
+
self._polygon(_points, style, gid=kwargs.get("gid") or "polygon")
|
|
526
822
|
|
|
527
823
|
@use_style(PolygonStyle)
|
|
528
824
|
def rectangle(
|
|
@@ -532,7 +828,6 @@ class BasePlot(ABC):
|
|
|
532
828
|
width_degrees: float,
|
|
533
829
|
style: PolygonStyle,
|
|
534
830
|
angle: float = 0,
|
|
535
|
-
*args,
|
|
536
831
|
**kwargs,
|
|
537
832
|
):
|
|
538
833
|
"""Plots a rectangle
|
|
@@ -550,7 +845,7 @@ class BasePlot(ABC):
|
|
|
550
845
|
width_degrees,
|
|
551
846
|
angle,
|
|
552
847
|
)
|
|
553
|
-
self._polygon(points, style)
|
|
848
|
+
self._polygon(points, style, gid=kwargs.get("gid") or "polygon")
|
|
554
849
|
|
|
555
850
|
@use_style(PolygonStyle)
|
|
556
851
|
def ellipse(
|
|
@@ -563,6 +858,7 @@ class BasePlot(ABC):
|
|
|
563
858
|
num_pts: int = 100,
|
|
564
859
|
start_angle: int = 0,
|
|
565
860
|
end_angle: int = 360,
|
|
861
|
+
**kwargs,
|
|
566
862
|
):
|
|
567
863
|
"""Plots an ellipse
|
|
568
864
|
|
|
@@ -584,7 +880,7 @@ class BasePlot(ABC):
|
|
|
584
880
|
start_angle,
|
|
585
881
|
end_angle,
|
|
586
882
|
)
|
|
587
|
-
self._polygon(points, style)
|
|
883
|
+
self._polygon(points, style, gid=kwargs.get("gid") or "polygon")
|
|
588
884
|
|
|
589
885
|
@use_style(PolygonStyle)
|
|
590
886
|
def circle(
|
|
@@ -593,6 +889,7 @@ class BasePlot(ABC):
|
|
|
593
889
|
radius_degrees: float,
|
|
594
890
|
style: PolygonStyle,
|
|
595
891
|
num_pts: int = 100,
|
|
892
|
+
**kwargs,
|
|
596
893
|
):
|
|
597
894
|
"""Plots a circle
|
|
598
895
|
|
|
@@ -609,10 +906,11 @@ class BasePlot(ABC):
|
|
|
609
906
|
style=style,
|
|
610
907
|
angle=0,
|
|
611
908
|
num_pts=num_pts,
|
|
909
|
+
gid=kwargs.get("gid") or "polygon",
|
|
612
910
|
)
|
|
613
911
|
|
|
614
912
|
@use_style(LineStyle)
|
|
615
|
-
def line(self, coordinates: list[tuple[float, float]], style: LineStyle):
|
|
913
|
+
def line(self, coordinates: list[tuple[float, float]], style: LineStyle, **kwargs):
|
|
616
914
|
"""Plots a line
|
|
617
915
|
|
|
618
916
|
Args:
|
|
@@ -626,7 +924,8 @@ class BasePlot(ABC):
|
|
|
626
924
|
y,
|
|
627
925
|
clip_on=True,
|
|
628
926
|
clip_path=self._background_clip_path,
|
|
629
|
-
|
|
927
|
+
gid=kwargs.get("gid") or "line",
|
|
928
|
+
**style.matplot_kwargs(self.scale),
|
|
630
929
|
**self._plot_kwargs(),
|
|
631
930
|
)
|
|
632
931
|
|
|
@@ -680,21 +979,24 @@ class BasePlot(ABC):
|
|
|
680
979
|
(m.ra, m.dec),
|
|
681
980
|
m.apparent_size,
|
|
682
981
|
style=polygon_style,
|
|
982
|
+
gid="moon-marker",
|
|
683
983
|
)
|
|
684
984
|
|
|
685
985
|
style.marker.symbol = MarkerSymbolEnum.CIRCLE
|
|
686
986
|
self._add_legend_handle_marker(legend_label, style.marker)
|
|
687
987
|
|
|
688
988
|
if label:
|
|
689
|
-
self.text(label, m.ra, m.dec, style.label)
|
|
989
|
+
self.text(label, m.ra, m.dec, style.label, gid="moon-label")
|
|
690
990
|
|
|
691
991
|
else:
|
|
692
992
|
self.marker(
|
|
693
993
|
ra=m.ra,
|
|
694
994
|
dec=m.dec,
|
|
695
|
-
label=label,
|
|
696
995
|
style=style,
|
|
996
|
+
label=label,
|
|
697
997
|
legend_label=legend_label,
|
|
998
|
+
gid_marker="moon-marker",
|
|
999
|
+
gid_label="moon-label",
|
|
698
1000
|
)
|
|
699
1001
|
|
|
700
1002
|
def _moon_with_phase(
|
|
@@ -761,6 +1063,7 @@ class BasePlot(ABC):
|
|
|
761
1063
|
num_pts=num_pts,
|
|
762
1064
|
angle=0,
|
|
763
1065
|
end_angle=180, # plot as a semicircle
|
|
1066
|
+
gid="moon-marker",
|
|
764
1067
|
)
|
|
765
1068
|
# Plot right side
|
|
766
1069
|
self.ellipse(
|
|
@@ -771,6 +1074,7 @@ class BasePlot(ABC):
|
|
|
771
1074
|
num_pts=num_pts,
|
|
772
1075
|
angle=180,
|
|
773
1076
|
end_angle=180, # plot as a semicircle
|
|
1077
|
+
gid="moon-marker",
|
|
774
1078
|
)
|
|
775
1079
|
# Plot middle
|
|
776
1080
|
self.ellipse(
|
|
@@ -778,6 +1082,7 @@ class BasePlot(ABC):
|
|
|
778
1082
|
radius_degrees * 2,
|
|
779
1083
|
radius_degrees,
|
|
780
1084
|
style=middle,
|
|
1085
|
+
gid="moon-marker",
|
|
781
1086
|
)
|
|
782
1087
|
|
|
783
1088
|
def _fov_circle(
|
|
@@ -860,17 +1165,16 @@ class BasePlot(ABC):
|
|
|
860
1165
|
y,
|
|
861
1166
|
dash_capstyle=style.line.dash_capstyle,
|
|
862
1167
|
clip_path=self._background_clip_path,
|
|
863
|
-
|
|
1168
|
+
gid="ecliptic-line",
|
|
1169
|
+
**style.line.matplot_kwargs(self.scale),
|
|
864
1170
|
**self._plot_kwargs(),
|
|
865
1171
|
)
|
|
866
1172
|
|
|
867
|
-
if label:
|
|
868
|
-
|
|
869
|
-
label_spacing = int(len(inbounds) / 3) or 1
|
|
1173
|
+
if label and len(inbounds) > 4:
|
|
1174
|
+
label_spacing = int(len(inbounds) / 4)
|
|
870
1175
|
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
self.text(label, ra, dec, style.label)
|
|
1176
|
+
for ra, dec in [inbounds[label_spacing], inbounds[label_spacing * 2]]:
|
|
1177
|
+
self.text(label, ra, dec, style.label, gid="ecliptic-label")
|
|
874
1178
|
|
|
875
1179
|
@use_style(PathStyle, "celestial_equator")
|
|
876
1180
|
def celestial_equator(
|
|
@@ -897,11 +1201,18 @@ class BasePlot(ABC):
|
|
|
897
1201
|
x,
|
|
898
1202
|
y,
|
|
899
1203
|
clip_path=self._background_clip_path,
|
|
900
|
-
|
|
1204
|
+
gid="celestial-equator-line",
|
|
1205
|
+
**style.line.matplot_kwargs(self.scale),
|
|
901
1206
|
**self._plot_kwargs(),
|
|
902
1207
|
)
|
|
903
1208
|
|
|
904
1209
|
if label:
|
|
905
1210
|
label_spacing = (self.ra_max - self.ra_min) / 3
|
|
906
1211
|
for ra in np.arange(self.ra_min, self.ra_max, label_spacing):
|
|
907
|
-
self.text(
|
|
1212
|
+
self.text(
|
|
1213
|
+
label,
|
|
1214
|
+
ra,
|
|
1215
|
+
0.25,
|
|
1216
|
+
style.label,
|
|
1217
|
+
gid="celestial-equator-label",
|
|
1218
|
+
)
|