starplot 0.12.5__py2.py3-none-any.whl → 0.14.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 +3 -2
- starplot/base.py +408 -95
- starplot/callables.py +61 -7
- starplot/coordinates.py +6 -0
- starplot/data/bayer.py +1532 -3
- starplot/data/constellations.py +564 -2
- 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/geometry.py +82 -0
- starplot/horizon.py +458 -0
- starplot/map.py +97 -284
- starplot/models/base.py +9 -2
- starplot/models/constellation.py +1 -1
- starplot/optic.py +32 -14
- starplot/plotters/__init__.py +2 -0
- starplot/plotters/constellations.py +339 -0
- starplot/plotters/dsos.py +5 -1
- starplot/plotters/experimental.py +171 -0
- starplot/plotters/milkyway.py +41 -0
- starplot/plotters/stars.py +143 -13
- starplot/styles/base.py +308 -169
- starplot/styles/ext/antique.yml +54 -46
- starplot/styles/ext/blue_dark.yml +39 -45
- starplot/styles/ext/blue_light.yml +49 -30
- starplot/styles/ext/blue_medium.yml +53 -50
- starplot/styles/ext/cb_wong.yml +16 -7
- starplot/styles/ext/grayscale.yml +17 -10
- starplot/styles/ext/grayscale_dark.yml +18 -8
- starplot/styles/ext/map.yml +10 -7
- starplot/styles/ext/nord.yml +38 -38
- 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/utils.py +19 -0
- starplot/warnings.py +16 -0
- {starplot-0.12.5.dist-info → starplot-0.14.0.dist-info}/METADATA +12 -12
- starplot-0.14.0.dist-info/RECORD +107 -0
- starplot-0.12.5.dist-info/RECORD +0 -67
- {starplot-0.12.5.dist-info → starplot-0.14.0.dist-info}/LICENSE +0 -0
- {starplot-0.12.5.dist-info → starplot-0.14.0.dist-info}/WHEEL +0 -0
starplot/base.py
CHANGED
|
@@ -1,22 +1,24 @@
|
|
|
1
1
|
from abc import ABC, abstractmethod
|
|
2
2
|
from datetime import datetime
|
|
3
3
|
from typing import Dict, Union, Optional
|
|
4
|
+
from random import randrange
|
|
4
5
|
import logging
|
|
5
6
|
|
|
6
7
|
import numpy as np
|
|
7
8
|
import rtree
|
|
8
|
-
from adjustText import adjust_text as _adjust_text
|
|
9
9
|
from matplotlib import patches
|
|
10
10
|
from matplotlib import pyplot as plt, patheffects
|
|
11
11
|
from matplotlib.lines import Line2D
|
|
12
12
|
from pytz import timezone
|
|
13
|
-
from shapely import Polygon
|
|
13
|
+
from shapely import Polygon, Point
|
|
14
14
|
|
|
15
|
-
from starplot import
|
|
15
|
+
from starplot.coordinates import CoordinateSystem
|
|
16
|
+
from starplot import geod, models, warnings
|
|
16
17
|
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,8 +29,14 @@ 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
|
|
35
|
+
from starplot.geometry import (
|
|
36
|
+
unwrap_polygon,
|
|
37
|
+
random_point_in_polygon_at_distance,
|
|
38
|
+
)
|
|
39
|
+
|
|
32
40
|
|
|
33
41
|
LOGGER = logging.getLogger("starplot")
|
|
34
42
|
LOG_HANDLER = logging.StreamHandler()
|
|
@@ -46,35 +54,53 @@ DEFAULT_FOV_STYLE = PolygonStyle(
|
|
|
46
54
|
|
|
47
55
|
DEFAULT_STYLE = PlotStyle()
|
|
48
56
|
|
|
57
|
+
DEFAULT_RESOLUTION = 4096
|
|
58
|
+
|
|
59
|
+
DPI = 100
|
|
60
|
+
|
|
49
61
|
|
|
50
62
|
class BasePlot(ABC):
|
|
51
63
|
_background_clip_path = None
|
|
64
|
+
_coordinate_system = CoordinateSystem.RA_DEC
|
|
52
65
|
|
|
53
66
|
def __init__(
|
|
54
67
|
self,
|
|
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,
|
|
75
|
+
suppress_warnings: bool = True,
|
|
60
76
|
*args,
|
|
61
77
|
**kwargs,
|
|
62
78
|
):
|
|
63
|
-
px = 1 / plt.rcParams["figure.dpi"] # pixel in inches
|
|
64
|
-
|
|
65
|
-
self.pixels_per_point = plt.rcParams["figure.dpi"] / 72
|
|
79
|
+
px = 1 / DPI # plt.rcParams["figure.dpi"] # pixel in inches
|
|
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
|
+
|
|
92
|
+
if suppress_warnings:
|
|
93
|
+
warnings.suppress()
|
|
94
|
+
|
|
72
95
|
self.dt = dt or timezone("UTC").localize(datetime.now())
|
|
73
96
|
self._ephemeris_name = ephemeris
|
|
74
97
|
self.ephemeris = load(ephemeris)
|
|
75
98
|
|
|
76
99
|
self.labels = []
|
|
77
100
|
self._labels_rtree = rtree.index.Index()
|
|
101
|
+
self._constellations_rtree = rtree.index.Index()
|
|
102
|
+
self._stars_rtree = rtree.index.Index()
|
|
103
|
+
|
|
78
104
|
self._background_clip_path = None
|
|
79
105
|
|
|
80
106
|
self._legend = None
|
|
@@ -85,13 +111,14 @@ class BasePlot(ABC):
|
|
|
85
111
|
self.logger.setLevel(self.log_level)
|
|
86
112
|
|
|
87
113
|
self.text_border = patheffects.withStroke(
|
|
88
|
-
linewidth=self.style.text_border_width,
|
|
114
|
+
linewidth=self.style.text_border_width * self.scale,
|
|
89
115
|
foreground=self.style.text_border_color.as_hex(),
|
|
90
116
|
)
|
|
91
|
-
self._size_multiplier = self.resolution / 3000
|
|
92
117
|
self.timescale = load.timescale().from_datetime(self.dt)
|
|
93
118
|
|
|
94
119
|
self._objects = models.ObjectList()
|
|
120
|
+
self._labeled_stars = []
|
|
121
|
+
fonts.load()
|
|
95
122
|
|
|
96
123
|
def _plot_kwargs(self) -> dict:
|
|
97
124
|
return {}
|
|
@@ -99,44 +126,80 @@ class BasePlot(ABC):
|
|
|
99
126
|
def _prepare_coords(self, ra, dec) -> tuple[float, float]:
|
|
100
127
|
return ra, dec
|
|
101
128
|
|
|
102
|
-
def _is_label_collision(self,
|
|
103
|
-
ix = list(
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
)
|
|
129
|
+
def _is_label_collision(self, bbox) -> bool:
|
|
130
|
+
ix = list(self._labels_rtree.intersection(bbox))
|
|
131
|
+
return len(ix) > 0
|
|
132
|
+
|
|
133
|
+
def _is_constellation_collision(self, bbox) -> bool:
|
|
134
|
+
ix = list(self._constellations_rtree.intersection(bbox))
|
|
135
|
+
return len(ix) > 0
|
|
136
|
+
|
|
137
|
+
def _is_star_collision(self, bbox) -> bool:
|
|
138
|
+
ix = list(self._stars_rtree.intersection(bbox))
|
|
108
139
|
return len(ix) > 0
|
|
109
140
|
|
|
110
|
-
def _is_clipped(self,
|
|
141
|
+
def _is_clipped(self, points) -> bool:
|
|
142
|
+
radius = -1.5 * int(self._background_clip_path.get_linewidth())
|
|
111
143
|
return self._background_clip_path is not None and not all(
|
|
112
|
-
self._background_clip_path.contains_points(
|
|
144
|
+
self._background_clip_path.contains_points(points, radius=radius)
|
|
113
145
|
)
|
|
114
146
|
|
|
115
|
-
def
|
|
147
|
+
def _add_label_to_rtree(self, label, extent=None):
|
|
148
|
+
extent = extent or label.get_window_extent(
|
|
149
|
+
renderer=self.fig.canvas.get_renderer()
|
|
150
|
+
)
|
|
151
|
+
self.labels.append(label)
|
|
152
|
+
self._labels_rtree.insert(
|
|
153
|
+
0, np.array((extent.x0 - 1, extent.y0 - 1, extent.x1 + 1, extent.y1 + 1))
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
def _maybe_remove_label(
|
|
157
|
+
self,
|
|
158
|
+
label,
|
|
159
|
+
remove_on_collision=True,
|
|
160
|
+
remove_on_clipped=True,
|
|
161
|
+
remove_on_constellation_collision=True,
|
|
162
|
+
padding=0,
|
|
163
|
+
) -> bool:
|
|
164
|
+
"""Returns true if the label is removed, else false"""
|
|
116
165
|
extent = label.get_window_extent(renderer=self.fig.canvas.get_renderer())
|
|
166
|
+
bbox = (
|
|
167
|
+
extent.x0 - padding,
|
|
168
|
+
extent.y0 - padding,
|
|
169
|
+
extent.x1 + padding,
|
|
170
|
+
extent.y1 + padding,
|
|
171
|
+
)
|
|
172
|
+
points = [(extent.x0, extent.y0), (extent.x1, extent.y1)]
|
|
173
|
+
|
|
174
|
+
# if label.get_text() == "CANIS MAJOR":
|
|
175
|
+
# print(bbox)
|
|
176
|
+
# if label.get_text() == "Electra":
|
|
177
|
+
# print(bbox)
|
|
117
178
|
|
|
118
179
|
if any([np.isnan(c) for c in (extent.x0, extent.y0, extent.x1, extent.y1)]):
|
|
119
180
|
label.remove()
|
|
120
|
-
return
|
|
181
|
+
return True
|
|
121
182
|
|
|
122
|
-
if
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
183
|
+
if remove_on_clipped and self._is_clipped(points):
|
|
184
|
+
label.remove()
|
|
185
|
+
return True
|
|
186
|
+
|
|
187
|
+
if remove_on_collision and (
|
|
188
|
+
self._is_label_collision(bbox) or self._is_star_collision(bbox)
|
|
127
189
|
):
|
|
128
190
|
label.remove()
|
|
129
|
-
return
|
|
191
|
+
return True
|
|
130
192
|
|
|
131
|
-
self.
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
193
|
+
if remove_on_constellation_collision and self._is_constellation_collision(bbox):
|
|
194
|
+
label.remove()
|
|
195
|
+
return True
|
|
196
|
+
|
|
197
|
+
return False
|
|
135
198
|
|
|
136
199
|
def _add_legend_handle_marker(self, label: str, style: MarkerStyle):
|
|
137
200
|
if label is not None and label not in self._legend_handles:
|
|
138
201
|
s = style.matplot_kwargs()
|
|
139
|
-
s["markersize"] = self.style.legend.symbol_size * self.
|
|
202
|
+
s["markersize"] = self.style.legend.symbol_size * self.scale
|
|
140
203
|
self._legend_handles[label] = Line2D(
|
|
141
204
|
[],
|
|
142
205
|
[],
|
|
@@ -146,35 +209,189 @@ class BasePlot(ABC):
|
|
|
146
209
|
label=label,
|
|
147
210
|
)
|
|
148
211
|
|
|
149
|
-
def
|
|
212
|
+
def _collision_score(self, label) -> int:
|
|
213
|
+
config = {
|
|
214
|
+
"labels": 1.0, # always fail
|
|
215
|
+
"stars": 0.5,
|
|
216
|
+
"constellations": 0.8,
|
|
217
|
+
"anchors": [
|
|
218
|
+
("bottom right", 0),
|
|
219
|
+
("top right", 0.2),
|
|
220
|
+
("top left", 0.5),
|
|
221
|
+
],
|
|
222
|
+
"on_fail": "plot",
|
|
223
|
+
}
|
|
224
|
+
extent = label.get_window_extent(renderer=self.fig.canvas.get_renderer())
|
|
225
|
+
|
|
226
|
+
if any(
|
|
227
|
+
[np.isnan(c) for c in (extent.x0, extent.y0, extent.x1, extent.y1)]
|
|
228
|
+
) or self._is_clipped(extent):
|
|
229
|
+
return 1
|
|
230
|
+
|
|
231
|
+
x_labels = (
|
|
232
|
+
len(
|
|
233
|
+
list(
|
|
234
|
+
self._labels_rtree.intersection(
|
|
235
|
+
(extent.x0, extent.y0, extent.x1, extent.y1)
|
|
236
|
+
)
|
|
237
|
+
)
|
|
238
|
+
)
|
|
239
|
+
* config["labels"]
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
if x_labels >= 1:
|
|
243
|
+
return 1
|
|
244
|
+
|
|
245
|
+
x_constellations = (
|
|
246
|
+
len(
|
|
247
|
+
list(
|
|
248
|
+
self._constellations_rtree.intersection(
|
|
249
|
+
(extent.x0, extent.y0, extent.x1, extent.y1)
|
|
250
|
+
)
|
|
251
|
+
)
|
|
252
|
+
)
|
|
253
|
+
* config["constellations"]
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
if x_constellations >= 1:
|
|
257
|
+
return 1
|
|
258
|
+
|
|
259
|
+
x_stars = (
|
|
260
|
+
len(
|
|
261
|
+
list(
|
|
262
|
+
self._stars_rtree.intersection(
|
|
263
|
+
(extent.x0, extent.y0, extent.x1, extent.y1)
|
|
264
|
+
)
|
|
265
|
+
)
|
|
266
|
+
)
|
|
267
|
+
* config["stars"]
|
|
268
|
+
)
|
|
269
|
+
if x_stars >= 1:
|
|
270
|
+
return 1
|
|
271
|
+
|
|
272
|
+
return sum([x_labels, x_constellations, x_stars]) / 3
|
|
273
|
+
|
|
274
|
+
def _text(self, x, y, text, **kwargs):
|
|
275
|
+
label = self.ax.annotate(
|
|
276
|
+
text,
|
|
277
|
+
(x, y),
|
|
278
|
+
**kwargs,
|
|
279
|
+
**self._plot_kwargs(),
|
|
280
|
+
)
|
|
281
|
+
if kwargs.get("clip_on"):
|
|
282
|
+
label.set_clip_on(True)
|
|
283
|
+
label.set_clip_path(self._background_clip_path)
|
|
284
|
+
return label
|
|
285
|
+
|
|
286
|
+
def _text_point(
|
|
150
287
|
self,
|
|
151
288
|
ra: float,
|
|
152
289
|
dec: float,
|
|
153
290
|
text: str,
|
|
154
291
|
hide_on_collision: bool = True,
|
|
155
|
-
|
|
292
|
+
force: bool = False,
|
|
293
|
+
clip_on: bool = True,
|
|
156
294
|
**kwargs,
|
|
157
|
-
)
|
|
295
|
+
):
|
|
158
296
|
if not text:
|
|
159
|
-
return
|
|
297
|
+
return None
|
|
160
298
|
|
|
161
299
|
x, y = self._prepare_coords(ra, dec)
|
|
162
300
|
kwargs["path_effects"] = kwargs.get("path_effects", [self.text_border])
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
301
|
+
|
|
302
|
+
original_va = kwargs.pop("va", None)
|
|
303
|
+
original_ha = kwargs.pop("ha", None)
|
|
304
|
+
original_offset_x, original_offset_y = kwargs.pop("xytext", (0, 0))
|
|
305
|
+
|
|
306
|
+
anchors = [(original_va, original_ha)]
|
|
307
|
+
for a in self.style.text_anchor_fallbacks:
|
|
308
|
+
d = AnchorPointEnum.from_str(a).as_matplot()
|
|
309
|
+
anchors.append((d["va"], d["ha"]))
|
|
310
|
+
|
|
311
|
+
for va, ha in anchors:
|
|
312
|
+
offset_x, offset_y = original_offset_x, original_offset_y
|
|
313
|
+
if original_ha != ha:
|
|
314
|
+
offset_x *= -1
|
|
315
|
+
|
|
316
|
+
if original_va != va:
|
|
317
|
+
offset_y *= -1
|
|
318
|
+
|
|
319
|
+
if ha == "center":
|
|
320
|
+
offset_x = 0
|
|
321
|
+
offset_y = 0
|
|
322
|
+
|
|
323
|
+
label = self._text(
|
|
324
|
+
x, y, text, **kwargs, va=va, ha=ha, xytext=(offset_x, offset_y)
|
|
325
|
+
)
|
|
326
|
+
removed = self._maybe_remove_label(
|
|
327
|
+
label, remove_on_collision=hide_on_collision, remove_on_clipped=clip_on
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
if force or not removed:
|
|
331
|
+
self._add_label_to_rtree(label)
|
|
332
|
+
return label
|
|
333
|
+
|
|
334
|
+
def _text_area(
|
|
335
|
+
self,
|
|
336
|
+
ra: float,
|
|
337
|
+
dec: float,
|
|
338
|
+
text: str,
|
|
339
|
+
area,
|
|
340
|
+
hide_on_collision: bool = True,
|
|
341
|
+
force: bool = False,
|
|
342
|
+
clip_on: bool = True,
|
|
343
|
+
settings: dict = None,
|
|
344
|
+
**kwargs,
|
|
345
|
+
) -> None:
|
|
346
|
+
kwargs["path_effects"] = kwargs.get("path_effects", [self.text_border])
|
|
347
|
+
|
|
348
|
+
avoid_constellation_lines = settings.get("avoid_constellation_lines", False)
|
|
349
|
+
padding = settings.get("label_padding", 3)
|
|
350
|
+
buffer = settings.get("buffer", 0.1)
|
|
351
|
+
max_distance = settings.get("max_distance", 300)
|
|
352
|
+
distance_step_size = settings.get("distance_step_size", 1)
|
|
353
|
+
point_iterations = settings.get("point_generation_max_iterations", 500)
|
|
354
|
+
random_seed = settings.get("seed")
|
|
355
|
+
|
|
356
|
+
areas = (
|
|
357
|
+
[p for p in area.geoms] if "MultiPolygon" == str(area.geom_type) else [area]
|
|
169
358
|
)
|
|
170
|
-
|
|
171
|
-
|
|
359
|
+
new_areas = []
|
|
360
|
+
|
|
361
|
+
for a in areas:
|
|
362
|
+
unwrapped = unwrap_polygon(a)
|
|
363
|
+
buffer = unwrapped.area / 10 * -1 * buffer * self.scale
|
|
364
|
+
new_areas.append(unwrapped.buffer(buffer))
|
|
365
|
+
|
|
366
|
+
for d in range(0, max_distance, distance_step_size):
|
|
367
|
+
distance = d / 10
|
|
368
|
+
poly = randrange(len(new_areas))
|
|
369
|
+
point = random_point_in_polygon_at_distance(
|
|
370
|
+
new_areas[poly],
|
|
371
|
+
Point(ra, dec),
|
|
372
|
+
distance,
|
|
373
|
+
max_iterations=point_iterations,
|
|
374
|
+
seed=random_seed,
|
|
375
|
+
)
|
|
172
376
|
|
|
173
|
-
|
|
174
|
-
|
|
377
|
+
if point is None:
|
|
378
|
+
continue
|
|
379
|
+
|
|
380
|
+
x, y = self._prepare_coords(point.x, point.y)
|
|
381
|
+
label = self._text(x, y, text, **kwargs)
|
|
382
|
+
removed = self._maybe_remove_label(
|
|
383
|
+
label,
|
|
384
|
+
remove_on_collision=hide_on_collision,
|
|
385
|
+
remove_on_clipped=clip_on,
|
|
386
|
+
remove_on_constellation_collision=avoid_constellation_lines,
|
|
387
|
+
padding=padding,
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
# TODO : remove label if not fully inside area?
|
|
175
391
|
|
|
176
|
-
|
|
177
|
-
|
|
392
|
+
if not removed:
|
|
393
|
+
self._add_label_to_rtree(label)
|
|
394
|
+
return label
|
|
178
395
|
|
|
179
396
|
@use_style(LabelStyle)
|
|
180
397
|
def text(
|
|
@@ -184,6 +401,7 @@ class BasePlot(ABC):
|
|
|
184
401
|
dec: float,
|
|
185
402
|
style: LabelStyle = None,
|
|
186
403
|
hide_on_collision: bool = True,
|
|
404
|
+
**kwargs,
|
|
187
405
|
):
|
|
188
406
|
"""
|
|
189
407
|
Plots text
|
|
@@ -195,16 +413,43 @@ class BasePlot(ABC):
|
|
|
195
413
|
style: Styling of the text
|
|
196
414
|
hide_on_collision: If True, then the text will not be plotted if it collides with another label
|
|
197
415
|
"""
|
|
416
|
+
if not text:
|
|
417
|
+
return
|
|
418
|
+
|
|
198
419
|
style = style or LabelStyle()
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
420
|
+
|
|
421
|
+
if style.offset_x == "auto":
|
|
422
|
+
style.offset_x = 0
|
|
423
|
+
|
|
424
|
+
if style.offset_y == "auto":
|
|
425
|
+
style.offset_y = 0
|
|
426
|
+
|
|
427
|
+
if kwargs.get("area"):
|
|
428
|
+
return self._text_area(
|
|
429
|
+
ra,
|
|
430
|
+
dec,
|
|
431
|
+
text,
|
|
432
|
+
**style.matplot_kwargs(self.scale),
|
|
433
|
+
area=kwargs.pop("area"),
|
|
434
|
+
hide_on_collision=hide_on_collision,
|
|
435
|
+
xycoords="data",
|
|
436
|
+
xytext=(style.offset_x * self.scale, style.offset_y * self.scale),
|
|
437
|
+
textcoords="offset points",
|
|
438
|
+
settings=kwargs.pop("auto_adjust_settings"),
|
|
439
|
+
**kwargs,
|
|
440
|
+
)
|
|
441
|
+
else:
|
|
442
|
+
return self._text_point(
|
|
443
|
+
ra,
|
|
444
|
+
dec,
|
|
445
|
+
text,
|
|
446
|
+
**style.matplot_kwargs(self.scale),
|
|
447
|
+
hide_on_collision=hide_on_collision,
|
|
448
|
+
xycoords="data",
|
|
449
|
+
xytext=(style.offset_x * self.scale, style.offset_y * self.scale),
|
|
450
|
+
textcoords="offset points",
|
|
451
|
+
**kwargs,
|
|
452
|
+
)
|
|
208
453
|
|
|
209
454
|
@property
|
|
210
455
|
def objects(self) -> models.ObjectList:
|
|
@@ -222,7 +467,7 @@ class BasePlot(ABC):
|
|
|
222
467
|
text: Title text to plot
|
|
223
468
|
style: Styling of the title. If None, then the plot's style (specified when creating the plot) will be used
|
|
224
469
|
"""
|
|
225
|
-
style_kwargs = style.matplot_kwargs(self.
|
|
470
|
+
style_kwargs = style.matplot_kwargs(self.scale)
|
|
226
471
|
style_kwargs.pop("linespacing", None)
|
|
227
472
|
style_kwargs["pad"] = style.line_spacing
|
|
228
473
|
self.ax.set_title(text, **style_kwargs)
|
|
@@ -265,25 +510,13 @@ class BasePlot(ABC):
|
|
|
265
510
|
|
|
266
511
|
self._legend = self.ax.legend(
|
|
267
512
|
handles=self._legend_handles.values(),
|
|
268
|
-
**style.matplot_kwargs(
|
|
513
|
+
**style.matplot_kwargs(self.scale),
|
|
269
514
|
**bbox_kwargs,
|
|
270
515
|
).set_zorder(
|
|
271
516
|
# zorder is not a valid kwarg to legend(), so we have to set it afterwards
|
|
272
517
|
style.zorder
|
|
273
518
|
)
|
|
274
519
|
|
|
275
|
-
def adjust_text(self, ensure_inside_axes: bool = False, **kwargs) -> None:
|
|
276
|
-
"""Adjust all the labels to avoid overlapping. This function uses the [adjustText](https://adjusttext.readthedocs.io/) library.
|
|
277
|
-
|
|
278
|
-
Args:
|
|
279
|
-
ensure_inside_axes: If True, then labels will be forced to stay within the axes
|
|
280
|
-
**kwargs: Any keyword arguments to pass through to [adjustText](https://adjusttext.readthedocs.io/en/latest/#adjustText.adjust_text)
|
|
281
|
-
|
|
282
|
-
"""
|
|
283
|
-
_adjust_text(
|
|
284
|
-
self.labels, ax=self.ax, ensure_inside_axes=ensure_inside_axes, **kwargs
|
|
285
|
-
)
|
|
286
|
-
|
|
287
520
|
def close_fig(self) -> None:
|
|
288
521
|
"""Closes the underlying matplotlib figure."""
|
|
289
522
|
if self.fig:
|
|
@@ -299,6 +532,7 @@ class BasePlot(ABC):
|
|
|
299
532
|
**kwargs: Any keyword arguments to pass through to matplotlib's `savefig` method
|
|
300
533
|
|
|
301
534
|
"""
|
|
535
|
+
self.logger.debug("Exporting...")
|
|
302
536
|
self.fig.savefig(
|
|
303
537
|
filename,
|
|
304
538
|
format=format,
|
|
@@ -317,6 +551,7 @@ class BasePlot(ABC):
|
|
|
317
551
|
label: Optional[str] = None,
|
|
318
552
|
legend_label: str = None,
|
|
319
553
|
skip_bounds_check: bool = False,
|
|
554
|
+
**kwargs,
|
|
320
555
|
) -> None:
|
|
321
556
|
"""Plots a marker
|
|
322
557
|
|
|
@@ -335,18 +570,36 @@ class BasePlot(ABC):
|
|
|
335
570
|
|
|
336
571
|
x, y = self._prepare_coords(ra, dec)
|
|
337
572
|
|
|
338
|
-
self.ax.
|
|
573
|
+
self.ax.scatter(
|
|
339
574
|
x,
|
|
340
575
|
y,
|
|
341
|
-
**style.marker.
|
|
576
|
+
**style.marker.matplot_scatter_kwargs(self.scale),
|
|
342
577
|
**self._plot_kwargs(),
|
|
343
|
-
linestyle="None",
|
|
344
578
|
clip_on=True,
|
|
345
579
|
clip_path=self._background_clip_path,
|
|
580
|
+
gid=kwargs.get("gid_marker") or "marker",
|
|
346
581
|
)
|
|
347
582
|
|
|
348
583
|
if label:
|
|
349
|
-
|
|
584
|
+
label_style = style.label
|
|
585
|
+
if label_style.offset_x == "auto" or label_style.offset_y == "auto":
|
|
586
|
+
marker_size = ((style.marker.size / self.scale) ** 2) * (
|
|
587
|
+
self.scale**2
|
|
588
|
+
)
|
|
589
|
+
|
|
590
|
+
label_style = label_style.offset_from_marker(
|
|
591
|
+
marker_symbol=style.marker.symbol,
|
|
592
|
+
marker_size=marker_size,
|
|
593
|
+
scale=self.scale,
|
|
594
|
+
)
|
|
595
|
+
self.text(
|
|
596
|
+
label,
|
|
597
|
+
ra,
|
|
598
|
+
dec,
|
|
599
|
+
label_style,
|
|
600
|
+
hide_on_collision=self.hide_colliding_labels,
|
|
601
|
+
gid=kwargs.get("gid_label") or "marker-label",
|
|
602
|
+
)
|
|
350
603
|
|
|
351
604
|
if legend_label is not None:
|
|
352
605
|
self._add_legend_handle_marker(legend_label, style.marker)
|
|
@@ -386,11 +639,14 @@ class BasePlot(ABC):
|
|
|
386
639
|
(p.ra, p.dec),
|
|
387
640
|
p.apparent_size,
|
|
388
641
|
polygon_style,
|
|
642
|
+
gid="planet-marker",
|
|
389
643
|
)
|
|
390
644
|
self._add_legend_handle_marker(legend_label, style.marker)
|
|
391
645
|
|
|
392
646
|
if label:
|
|
393
|
-
self.text(
|
|
647
|
+
self.text(
|
|
648
|
+
label.upper(), p.ra, p.dec, style.label, gid="planet-label"
|
|
649
|
+
)
|
|
394
650
|
else:
|
|
395
651
|
self.marker(
|
|
396
652
|
ra=p.ra,
|
|
@@ -398,6 +654,8 @@ class BasePlot(ABC):
|
|
|
398
654
|
style=style,
|
|
399
655
|
label=label.upper() if label else None,
|
|
400
656
|
legend_label=legend_label,
|
|
657
|
+
gid_marker="planet-marker",
|
|
658
|
+
gid_label="planet-label",
|
|
401
659
|
)
|
|
402
660
|
|
|
403
661
|
@use_style(ObjectStyle, "sun")
|
|
@@ -416,7 +674,8 @@ class BasePlot(ABC):
|
|
|
416
674
|
Args:
|
|
417
675
|
style: Styling of the Sun. If None, then the plot's style (specified when creating the plot) will be used
|
|
418
676
|
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.
|
|
419
|
-
label: How the Sun will be labeled on the plot
|
|
677
|
+
label: How the Sun will be labeled on the plot
|
|
678
|
+
legend_label: How the sun will be labeled in the legend
|
|
420
679
|
"""
|
|
421
680
|
s = models.Sun.get(
|
|
422
681
|
dt=self.dt, lat=self.lat, lon=self.lon, ephemeris=self._ephemeris_name
|
|
@@ -438,13 +697,14 @@ class BasePlot(ABC):
|
|
|
438
697
|
(s.ra, s.dec),
|
|
439
698
|
s.apparent_size,
|
|
440
699
|
style=polygon_style,
|
|
700
|
+
gid="sun-marker",
|
|
441
701
|
)
|
|
442
702
|
|
|
443
703
|
style.marker.symbol = MarkerSymbolEnum.CIRCLE
|
|
444
704
|
self._add_legend_handle_marker(legend_label, style.marker)
|
|
445
705
|
|
|
446
706
|
if label:
|
|
447
|
-
self.text(label, s.ra, s.dec, style.label)
|
|
707
|
+
self.text(label, s.ra, s.dec, style.label, gid="sun-label")
|
|
448
708
|
|
|
449
709
|
else:
|
|
450
710
|
self.marker(
|
|
@@ -453,6 +713,8 @@ class BasePlot(ABC):
|
|
|
453
713
|
style=style,
|
|
454
714
|
label=label,
|
|
455
715
|
legend_label=legend_label,
|
|
716
|
+
gid_marker="sun-marker",
|
|
717
|
+
gid_label="sun-label",
|
|
456
718
|
)
|
|
457
719
|
|
|
458
720
|
@abstractmethod
|
|
@@ -491,7 +753,7 @@ class BasePlot(ABC):
|
|
|
491
753
|
patch = patches.Polygon(
|
|
492
754
|
points,
|
|
493
755
|
# closed=False, # needs to be false for circles at poles?
|
|
494
|
-
**style.matplot_kwargs(
|
|
756
|
+
**style.matplot_kwargs(self.scale),
|
|
495
757
|
**kwargs,
|
|
496
758
|
clip_on=True,
|
|
497
759
|
clip_path=self._background_clip_path,
|
|
@@ -504,6 +766,8 @@ class BasePlot(ABC):
|
|
|
504
766
|
style: PolygonStyle,
|
|
505
767
|
points: list = None,
|
|
506
768
|
geometry: Polygon = None,
|
|
769
|
+
legend_label: str = None,
|
|
770
|
+
**kwargs,
|
|
507
771
|
):
|
|
508
772
|
"""
|
|
509
773
|
Plots a polygon.
|
|
@@ -511,9 +775,11 @@ class BasePlot(ABC):
|
|
|
511
775
|
Must pass in either `points` **or** `geometry` (but not both).
|
|
512
776
|
|
|
513
777
|
Args:
|
|
778
|
+
style: Style of polygon
|
|
514
779
|
points: List of polygon points `[(ra, dec), ...]` - **must be in counterclockwise order**
|
|
515
780
|
geometry: A shapely Polygon. If this value is passed, then the `points` kwarg will be ignored.
|
|
516
|
-
|
|
781
|
+
legend_label: Label for this object in the legend
|
|
782
|
+
|
|
517
783
|
"""
|
|
518
784
|
if points is None and geometry is None:
|
|
519
785
|
raise ValueError("Must pass points or geometry when plotting polygons.")
|
|
@@ -522,7 +788,13 @@ class BasePlot(ABC):
|
|
|
522
788
|
points = list(zip(*geometry.exterior.coords.xy))
|
|
523
789
|
|
|
524
790
|
_points = [(ra * 15, dec) for ra, dec in points]
|
|
525
|
-
self._polygon(_points, style)
|
|
791
|
+
self._polygon(_points, style, gid=kwargs.get("gid") or "polygon")
|
|
792
|
+
|
|
793
|
+
if legend_label is not None:
|
|
794
|
+
self._add_legend_handle_marker(
|
|
795
|
+
legend_label,
|
|
796
|
+
style=style.to_marker_style(symbol=MarkerSymbolEnum.SQUARE),
|
|
797
|
+
)
|
|
526
798
|
|
|
527
799
|
@use_style(PolygonStyle)
|
|
528
800
|
def rectangle(
|
|
@@ -532,7 +804,7 @@ class BasePlot(ABC):
|
|
|
532
804
|
width_degrees: float,
|
|
533
805
|
style: PolygonStyle,
|
|
534
806
|
angle: float = 0,
|
|
535
|
-
|
|
807
|
+
legend_label: str = None,
|
|
536
808
|
**kwargs,
|
|
537
809
|
):
|
|
538
810
|
"""Plots a rectangle
|
|
@@ -541,8 +813,9 @@ class BasePlot(ABC):
|
|
|
541
813
|
center: Center of rectangle (ra, dec)
|
|
542
814
|
height_degrees: Height of rectangle (degrees)
|
|
543
815
|
width_degrees: Width of rectangle (degrees)
|
|
544
|
-
angle: Angle of rotation clockwise (degrees)
|
|
545
816
|
style: Style of rectangle
|
|
817
|
+
angle: Angle of rotation clockwise (degrees)
|
|
818
|
+
legend_label: Label for this object in the legend
|
|
546
819
|
"""
|
|
547
820
|
points = geod.rectangle(
|
|
548
821
|
center,
|
|
@@ -550,7 +823,13 @@ class BasePlot(ABC):
|
|
|
550
823
|
width_degrees,
|
|
551
824
|
angle,
|
|
552
825
|
)
|
|
553
|
-
self._polygon(points, style)
|
|
826
|
+
self._polygon(points, style, gid=kwargs.get("gid") or "polygon")
|
|
827
|
+
|
|
828
|
+
if legend_label is not None:
|
|
829
|
+
self._add_legend_handle_marker(
|
|
830
|
+
legend_label,
|
|
831
|
+
style=style.to_marker_style(symbol=MarkerSymbolEnum.SQUARE),
|
|
832
|
+
)
|
|
554
833
|
|
|
555
834
|
@use_style(PolygonStyle)
|
|
556
835
|
def ellipse(
|
|
@@ -563,6 +842,8 @@ class BasePlot(ABC):
|
|
|
563
842
|
num_pts: int = 100,
|
|
564
843
|
start_angle: int = 0,
|
|
565
844
|
end_angle: int = 360,
|
|
845
|
+
legend_label: str = None,
|
|
846
|
+
**kwargs,
|
|
566
847
|
):
|
|
567
848
|
"""Plots an ellipse
|
|
568
849
|
|
|
@@ -573,6 +854,9 @@ class BasePlot(ABC):
|
|
|
573
854
|
style: Style of ellipse
|
|
574
855
|
angle: Angle of rotation clockwise (degrees)
|
|
575
856
|
num_pts: Number of points to calculate for the ellipse polygon
|
|
857
|
+
start_angle: Angle to start at
|
|
858
|
+
end_angle: Angle to end at
|
|
859
|
+
legend_label: Label for this object in the legend
|
|
576
860
|
"""
|
|
577
861
|
|
|
578
862
|
points = geod.ellipse(
|
|
@@ -584,7 +868,13 @@ class BasePlot(ABC):
|
|
|
584
868
|
start_angle,
|
|
585
869
|
end_angle,
|
|
586
870
|
)
|
|
587
|
-
self._polygon(points, style)
|
|
871
|
+
self._polygon(points, style, gid=kwargs.get("gid") or "polygon")
|
|
872
|
+
|
|
873
|
+
if legend_label is not None:
|
|
874
|
+
self._add_legend_handle_marker(
|
|
875
|
+
legend_label,
|
|
876
|
+
style=style.to_marker_style(symbol=MarkerSymbolEnum.ELLIPSE),
|
|
877
|
+
)
|
|
588
878
|
|
|
589
879
|
@use_style(PolygonStyle)
|
|
590
880
|
def circle(
|
|
@@ -593,6 +883,8 @@ class BasePlot(ABC):
|
|
|
593
883
|
radius_degrees: float,
|
|
594
884
|
style: PolygonStyle,
|
|
595
885
|
num_pts: int = 100,
|
|
886
|
+
legend_label: str = None,
|
|
887
|
+
**kwargs,
|
|
596
888
|
):
|
|
597
889
|
"""Plots a circle
|
|
598
890
|
|
|
@@ -601,6 +893,7 @@ class BasePlot(ABC):
|
|
|
601
893
|
radius_degrees: Radius of circle (degrees)
|
|
602
894
|
style: Style of circle
|
|
603
895
|
num_pts: Number of points to calculate for the circle polygon
|
|
896
|
+
legend_label: Label for this object in the legend
|
|
604
897
|
"""
|
|
605
898
|
self.ellipse(
|
|
606
899
|
center,
|
|
@@ -609,10 +902,17 @@ class BasePlot(ABC):
|
|
|
609
902
|
style=style,
|
|
610
903
|
angle=0,
|
|
611
904
|
num_pts=num_pts,
|
|
905
|
+
gid=kwargs.get("gid") or "polygon",
|
|
612
906
|
)
|
|
613
907
|
|
|
908
|
+
if legend_label is not None:
|
|
909
|
+
self._add_legend_handle_marker(
|
|
910
|
+
legend_label,
|
|
911
|
+
style=style.to_marker_style(symbol=MarkerSymbolEnum.CIRCLE),
|
|
912
|
+
)
|
|
913
|
+
|
|
614
914
|
@use_style(LineStyle)
|
|
615
|
-
def line(self, coordinates: list[tuple[float, float]], style: LineStyle):
|
|
915
|
+
def line(self, coordinates: list[tuple[float, float]], style: LineStyle, **kwargs):
|
|
616
916
|
"""Plots a line
|
|
617
917
|
|
|
618
918
|
Args:
|
|
@@ -626,7 +926,8 @@ class BasePlot(ABC):
|
|
|
626
926
|
y,
|
|
627
927
|
clip_on=True,
|
|
628
928
|
clip_path=self._background_clip_path,
|
|
629
|
-
|
|
929
|
+
gid=kwargs.get("gid") or "line",
|
|
930
|
+
**style.matplot_kwargs(self.scale),
|
|
630
931
|
**self._plot_kwargs(),
|
|
631
932
|
)
|
|
632
933
|
|
|
@@ -680,13 +981,14 @@ class BasePlot(ABC):
|
|
|
680
981
|
(m.ra, m.dec),
|
|
681
982
|
m.apparent_size,
|
|
682
983
|
style=polygon_style,
|
|
984
|
+
gid="moon-marker",
|
|
683
985
|
)
|
|
684
986
|
|
|
685
987
|
style.marker.symbol = MarkerSymbolEnum.CIRCLE
|
|
686
988
|
self._add_legend_handle_marker(legend_label, style.marker)
|
|
687
989
|
|
|
688
990
|
if label:
|
|
689
|
-
self.text(label, m.ra, m.dec, style.label)
|
|
991
|
+
self.text(label, m.ra, m.dec, style.label, gid="moon-label")
|
|
690
992
|
|
|
691
993
|
else:
|
|
692
994
|
self.marker(
|
|
@@ -695,6 +997,8 @@ class BasePlot(ABC):
|
|
|
695
997
|
style=style,
|
|
696
998
|
label=label,
|
|
697
999
|
legend_label=legend_label,
|
|
1000
|
+
gid_marker="moon-marker",
|
|
1001
|
+
gid_label="moon-label",
|
|
698
1002
|
)
|
|
699
1003
|
|
|
700
1004
|
def _moon_with_phase(
|
|
@@ -761,6 +1065,7 @@ class BasePlot(ABC):
|
|
|
761
1065
|
num_pts=num_pts,
|
|
762
1066
|
angle=0,
|
|
763
1067
|
end_angle=180, # plot as a semicircle
|
|
1068
|
+
gid="moon-marker",
|
|
764
1069
|
)
|
|
765
1070
|
# Plot right side
|
|
766
1071
|
self.ellipse(
|
|
@@ -771,6 +1076,7 @@ class BasePlot(ABC):
|
|
|
771
1076
|
num_pts=num_pts,
|
|
772
1077
|
angle=180,
|
|
773
1078
|
end_angle=180, # plot as a semicircle
|
|
1079
|
+
gid="moon-marker",
|
|
774
1080
|
)
|
|
775
1081
|
# Plot middle
|
|
776
1082
|
self.ellipse(
|
|
@@ -778,6 +1084,7 @@ class BasePlot(ABC):
|
|
|
778
1084
|
radius_degrees * 2,
|
|
779
1085
|
radius_degrees,
|
|
780
1086
|
style=middle,
|
|
1087
|
+
gid="moon-marker",
|
|
781
1088
|
)
|
|
782
1089
|
|
|
783
1090
|
def _fov_circle(
|
|
@@ -860,17 +1167,16 @@ class BasePlot(ABC):
|
|
|
860
1167
|
y,
|
|
861
1168
|
dash_capstyle=style.line.dash_capstyle,
|
|
862
1169
|
clip_path=self._background_clip_path,
|
|
863
|
-
|
|
1170
|
+
gid="ecliptic-line",
|
|
1171
|
+
**style.line.matplot_kwargs(self.scale),
|
|
864
1172
|
**self._plot_kwargs(),
|
|
865
1173
|
)
|
|
866
1174
|
|
|
867
|
-
if label:
|
|
868
|
-
|
|
869
|
-
label_spacing = int(len(inbounds) / 3) or 1
|
|
1175
|
+
if label and len(inbounds) > 4:
|
|
1176
|
+
label_spacing = int(len(inbounds) / 4)
|
|
870
1177
|
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
self.text(label, ra, dec, style.label)
|
|
1178
|
+
for ra, dec in [inbounds[label_spacing], inbounds[label_spacing * 2]]:
|
|
1179
|
+
self.text(label, ra, dec, style.label, gid="ecliptic-label")
|
|
874
1180
|
|
|
875
1181
|
@use_style(PathStyle, "celestial_equator")
|
|
876
1182
|
def celestial_equator(
|
|
@@ -897,11 +1203,18 @@ class BasePlot(ABC):
|
|
|
897
1203
|
x,
|
|
898
1204
|
y,
|
|
899
1205
|
clip_path=self._background_clip_path,
|
|
900
|
-
|
|
1206
|
+
gid="celestial-equator-line",
|
|
1207
|
+
**style.line.matplot_kwargs(self.scale),
|
|
901
1208
|
**self._plot_kwargs(),
|
|
902
1209
|
)
|
|
903
1210
|
|
|
904
1211
|
if label:
|
|
905
1212
|
label_spacing = (self.ra_max - self.ra_min) / 3
|
|
906
1213
|
for ra in np.arange(self.ra_min, self.ra_max, label_spacing):
|
|
907
|
-
self.text(
|
|
1214
|
+
self.text(
|
|
1215
|
+
label,
|
|
1216
|
+
ra,
|
|
1217
|
+
0.25,
|
|
1218
|
+
style.label,
|
|
1219
|
+
gid="celestial-equator-label",
|
|
1220
|
+
)
|