starplot 0.13.0__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 +187 -185
- starplot/coordinates.py +6 -0
- starplot/data/constellations.py +564 -2
- starplot/geometry.py +82 -0
- starplot/horizon.py +237 -177
- starplot/map.py +57 -369
- starplot/models/base.py +9 -2
- starplot/models/constellation.py +1 -1
- starplot/optic.py +7 -1
- starplot/plotters/__init__.py +2 -0
- starplot/plotters/constellations.py +339 -0
- starplot/plotters/experimental.py +171 -0
- starplot/plotters/milkyway.py +41 -0
- starplot/plotters/stars.py +56 -27
- starplot/styles/base.py +49 -17
- starplot/styles/ext/antique.yml +11 -9
- starplot/styles/ext/blue_dark.yml +8 -10
- starplot/styles/ext/blue_light.yml +9 -8
- starplot/styles/ext/blue_medium.yml +12 -13
- starplot/styles/ext/cb_wong.yml +9 -7
- starplot/styles/ext/grayscale.yml +4 -3
- starplot/styles/ext/grayscale_dark.yml +7 -5
- starplot/styles/ext/map.yml +9 -6
- starplot/styles/ext/nord.yml +7 -7
- starplot/styles/ext/optic.yml +1 -1
- starplot/utils.py +19 -0
- starplot/warnings.py +16 -0
- {starplot-0.13.0.dist-info → starplot-0.14.0.dist-info}/METADATA +10 -9
- {starplot-0.13.0.dist-info → starplot-0.14.0.dist-info}/RECORD +32 -26
- {starplot-0.13.0.dist-info → starplot-0.14.0.dist-info}/LICENSE +0 -0
- {starplot-0.13.0.dist-info → starplot-0.14.0.dist-info}/WHEEL +0 -0
starplot/__init__.py
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
"""Star charts and maps of the sky"""
|
|
2
2
|
|
|
3
|
-
__version__ = "0.
|
|
3
|
+
__version__ = "0.14.0"
|
|
4
4
|
|
|
5
5
|
from .base import BasePlot # noqa: F401
|
|
6
6
|
from .map import MapPlot, Projection # noqa: F401
|
|
7
|
+
from .horizon import HorizonPlot # noqa: F401
|
|
8
|
+
from .optic import OpticPlot # noqa: F401
|
|
7
9
|
from .models import (
|
|
8
10
|
DSO, # noqa: F401
|
|
9
11
|
Star, # noqa: F401
|
|
@@ -13,5 +15,4 @@ from .models import (
|
|
|
13
15
|
Sun, # noqa: F401
|
|
14
16
|
ObjectList, # noqa: F401
|
|
15
17
|
)
|
|
16
|
-
from .optic import OpticPlot # noqa: F401
|
|
17
18
|
from .styles import * # noqa: F401 F403
|
starplot/base.py
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
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
|
-
import warnings
|
|
6
6
|
|
|
7
7
|
import numpy as np
|
|
8
8
|
import rtree
|
|
9
|
-
from adjustText import adjust_text as _adjust_text
|
|
10
9
|
from matplotlib import patches
|
|
11
10
|
from matplotlib import pyplot as plt, patheffects
|
|
12
11
|
from matplotlib.lines import Line2D
|
|
13
12
|
from pytz import timezone
|
|
14
|
-
from shapely import Polygon
|
|
13
|
+
from shapely import Polygon, Point
|
|
15
14
|
|
|
16
|
-
from starplot import
|
|
15
|
+
from starplot.coordinates import CoordinateSystem
|
|
16
|
+
from starplot import geod, models, warnings
|
|
17
17
|
from starplot.data import load, ecliptic
|
|
18
18
|
from starplot.models.planet import PlanetName, PLANET_LABELS_DEFAULT
|
|
19
19
|
from starplot.models.moon import MoonPhase
|
|
@@ -32,13 +32,12 @@ from starplot.styles import (
|
|
|
32
32
|
fonts,
|
|
33
33
|
)
|
|
34
34
|
from starplot.styles.helpers import use_style
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
"ignore",
|
|
39
|
-
message="Setting the 'color' property will override the edgecolor or facecolor properties",
|
|
35
|
+
from starplot.geometry import (
|
|
36
|
+
unwrap_polygon,
|
|
37
|
+
random_point_in_polygon_at_distance,
|
|
40
38
|
)
|
|
41
39
|
|
|
40
|
+
|
|
42
41
|
LOGGER = logging.getLogger("starplot")
|
|
43
42
|
LOG_HANDLER = logging.StreamHandler()
|
|
44
43
|
LOG_FORMATTER = logging.Formatter(
|
|
@@ -62,6 +61,7 @@ DPI = 100
|
|
|
62
61
|
|
|
63
62
|
class BasePlot(ABC):
|
|
64
63
|
_background_clip_path = None
|
|
64
|
+
_coordinate_system = CoordinateSystem.RA_DEC
|
|
65
65
|
|
|
66
66
|
def __init__(
|
|
67
67
|
self,
|
|
@@ -72,11 +72,11 @@ class BasePlot(ABC):
|
|
|
72
72
|
hide_colliding_labels: bool = True,
|
|
73
73
|
scale: float = 1.0,
|
|
74
74
|
autoscale: bool = False,
|
|
75
|
+
suppress_warnings: bool = True,
|
|
75
76
|
*args,
|
|
76
77
|
**kwargs,
|
|
77
78
|
):
|
|
78
79
|
px = 1 / DPI # plt.rcParams["figure.dpi"] # pixel in inches
|
|
79
|
-
|
|
80
80
|
self.pixels_per_point = DPI / 72
|
|
81
81
|
|
|
82
82
|
self.style = style
|
|
@@ -89,14 +89,15 @@ class BasePlot(ABC):
|
|
|
89
89
|
if self.autoscale:
|
|
90
90
|
self.scale = self.resolution / DEFAULT_RESOLUTION
|
|
91
91
|
|
|
92
|
+
if suppress_warnings:
|
|
93
|
+
warnings.suppress()
|
|
94
|
+
|
|
92
95
|
self.dt = dt or timezone("UTC").localize(datetime.now())
|
|
93
96
|
self._ephemeris_name = ephemeris
|
|
94
97
|
self.ephemeris = load(ephemeris)
|
|
95
98
|
|
|
96
99
|
self.labels = []
|
|
97
100
|
self._labels_rtree = rtree.index.Index()
|
|
98
|
-
|
|
99
|
-
# self.labels = []
|
|
100
101
|
self._constellations_rtree = rtree.index.Index()
|
|
101
102
|
self._stars_rtree = rtree.index.Index()
|
|
102
103
|
|
|
@@ -125,31 +126,22 @@ class BasePlot(ABC):
|
|
|
125
126
|
def _prepare_coords(self, ra, dec) -> tuple[float, float]:
|
|
126
127
|
return ra, dec
|
|
127
128
|
|
|
128
|
-
def _is_label_collision(self,
|
|
129
|
-
ix = list(
|
|
130
|
-
self._labels_rtree.intersection(
|
|
131
|
-
(extent.x0, extent.y0, extent.x1, extent.y1)
|
|
132
|
-
)
|
|
133
|
-
)
|
|
129
|
+
def _is_label_collision(self, bbox) -> bool:
|
|
130
|
+
ix = list(self._labels_rtree.intersection(bbox))
|
|
134
131
|
return len(ix) > 0
|
|
135
132
|
|
|
136
|
-
def
|
|
137
|
-
ix = list(
|
|
138
|
-
self._constellations_rtree.intersection(
|
|
139
|
-
(extent.x0, extent.y0, extent.x1, extent.y1)
|
|
140
|
-
)
|
|
141
|
-
)
|
|
133
|
+
def _is_constellation_collision(self, bbox) -> bool:
|
|
134
|
+
ix = list(self._constellations_rtree.intersection(bbox))
|
|
142
135
|
return len(ix) > 0
|
|
143
136
|
|
|
144
|
-
def _is_star_collision(self,
|
|
145
|
-
ix = list(
|
|
146
|
-
self._stars_rtree.intersection((extent.x0, extent.y0, extent.x1, extent.y1))
|
|
147
|
-
)
|
|
137
|
+
def _is_star_collision(self, bbox) -> bool:
|
|
138
|
+
ix = list(self._stars_rtree.intersection(bbox))
|
|
148
139
|
return len(ix) > 0
|
|
149
140
|
|
|
150
|
-
def _is_clipped(self,
|
|
141
|
+
def _is_clipped(self, points) -> bool:
|
|
142
|
+
radius = -1.5 * int(self._background_clip_path.get_linewidth())
|
|
151
143
|
return self._background_clip_path is not None and not all(
|
|
152
|
-
self._background_clip_path.contains_points(
|
|
144
|
+
self._background_clip_path.contains_points(points, radius=radius)
|
|
153
145
|
)
|
|
154
146
|
|
|
155
147
|
def _add_label_to_rtree(self, label, extent=None):
|
|
@@ -158,31 +150,50 @@ class BasePlot(ABC):
|
|
|
158
150
|
)
|
|
159
151
|
self.labels.append(label)
|
|
160
152
|
self._labels_rtree.insert(
|
|
161
|
-
0, np.array((extent.x0, extent.y0, extent.x1, extent.y1))
|
|
153
|
+
0, np.array((extent.x0 - 1, extent.y0 - 1, extent.x1 + 1, extent.y1 + 1))
|
|
162
154
|
)
|
|
163
155
|
|
|
164
156
|
def _maybe_remove_label(
|
|
165
|
-
self,
|
|
157
|
+
self,
|
|
158
|
+
label,
|
|
159
|
+
remove_on_collision=True,
|
|
160
|
+
remove_on_clipped=True,
|
|
161
|
+
remove_on_constellation_collision=True,
|
|
162
|
+
padding=0,
|
|
166
163
|
) -> bool:
|
|
167
164
|
"""Returns true if the label is removed, else false"""
|
|
168
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)
|
|
169
178
|
|
|
170
179
|
if any([np.isnan(c) for c in (extent.x0, extent.y0, extent.x1, extent.y1)]):
|
|
171
180
|
label.remove()
|
|
172
181
|
return True
|
|
173
182
|
|
|
174
|
-
if remove_on_clipped and self._is_clipped(
|
|
183
|
+
if remove_on_clipped and self._is_clipped(points):
|
|
175
184
|
label.remove()
|
|
176
185
|
return True
|
|
177
186
|
|
|
178
187
|
if remove_on_collision and (
|
|
179
|
-
self._is_label_collision(
|
|
180
|
-
or self._is_object_collision(extent)
|
|
181
|
-
# or self._is_star_collision(extent)
|
|
188
|
+
self._is_label_collision(bbox) or self._is_star_collision(bbox)
|
|
182
189
|
):
|
|
183
190
|
label.remove()
|
|
184
191
|
return True
|
|
185
192
|
|
|
193
|
+
if remove_on_constellation_collision and self._is_constellation_collision(bbox):
|
|
194
|
+
label.remove()
|
|
195
|
+
return True
|
|
196
|
+
|
|
186
197
|
return False
|
|
187
198
|
|
|
188
199
|
def _add_legend_handle_marker(self, label: str, style: MarkerStyle):
|
|
@@ -260,70 +271,44 @@ class BasePlot(ABC):
|
|
|
260
271
|
|
|
261
272
|
return sum([x_labels, x_constellations, x_stars]) / 3
|
|
262
273
|
|
|
263
|
-
def
|
|
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(
|
|
264
287
|
self,
|
|
265
288
|
ra: float,
|
|
266
289
|
dec: float,
|
|
267
290
|
text: str,
|
|
268
291
|
hide_on_collision: bool = True,
|
|
269
|
-
|
|
270
|
-
|
|
292
|
+
force: bool = False,
|
|
293
|
+
clip_on: bool = True,
|
|
271
294
|
**kwargs,
|
|
272
|
-
)
|
|
295
|
+
):
|
|
273
296
|
if not text:
|
|
274
|
-
return
|
|
297
|
+
return None
|
|
275
298
|
|
|
276
299
|
x, y = self._prepare_coords(ra, dec)
|
|
277
300
|
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
301
|
|
|
319
|
-
collision_scores = []
|
|
320
302
|
original_va = kwargs.pop("va", None)
|
|
321
303
|
original_ha = kwargs.pop("ha", None)
|
|
322
304
|
original_offset_x, original_offset_y = kwargs.pop("xytext", (0, 0))
|
|
323
|
-
|
|
324
|
-
|
|
305
|
+
|
|
306
|
+
anchors = [(original_va, original_ha)]
|
|
307
|
+
for a in self.style.text_anchor_fallbacks:
|
|
325
308
|
d = AnchorPointEnum.from_str(a).as_matplot()
|
|
326
|
-
|
|
309
|
+
anchors.append((d["va"], d["ha"]))
|
|
310
|
+
|
|
311
|
+
for va, ha in anchors:
|
|
327
312
|
offset_x, offset_y = original_offset_x, original_offset_y
|
|
328
313
|
if original_ha != ha:
|
|
329
314
|
offset_x *= -1
|
|
@@ -335,100 +320,78 @@ class BasePlot(ABC):
|
|
|
335
320
|
offset_x = 0
|
|
336
321
|
offset_y = 0
|
|
337
322
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
collision = self._collision_score(label)
|
|
345
|
-
if collision == 0:
|
|
346
|
-
add_label(label)
|
|
347
|
-
return
|
|
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
|
+
)
|
|
348
329
|
|
|
349
|
-
if
|
|
350
|
-
|
|
330
|
+
if force or not removed:
|
|
331
|
+
self._add_label_to_rtree(label)
|
|
332
|
+
return label
|
|
351
333
|
|
|
352
|
-
|
|
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
|
-
|
|
363
|
-
def _text(
|
|
334
|
+
def _text_area(
|
|
364
335
|
self,
|
|
365
336
|
ra: float,
|
|
366
337
|
dec: float,
|
|
367
338
|
text: str,
|
|
339
|
+
area,
|
|
368
340
|
hide_on_collision: bool = True,
|
|
369
341
|
force: bool = False,
|
|
370
342
|
clip_on: bool = True,
|
|
371
|
-
|
|
343
|
+
settings: dict = None,
|
|
372
344
|
**kwargs,
|
|
373
345
|
) -> None:
|
|
374
|
-
if not text:
|
|
375
|
-
return
|
|
376
|
-
|
|
377
|
-
x, y = self._prepare_coords(ra, dec)
|
|
378
346
|
kwargs["path_effects"] = kwargs.get("path_effects", [self.text_border])
|
|
379
347
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
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
|
|
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")
|
|
397
355
|
|
|
398
|
-
|
|
399
|
-
|
|
356
|
+
areas = (
|
|
357
|
+
[p for p in area.geoms] if "MultiPolygon" == str(area.geom_type) else [area]
|
|
400
358
|
)
|
|
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
|
+
)
|
|
401
376
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
return
|
|
405
|
-
|
|
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
|
|
377
|
+
if point is None:
|
|
378
|
+
continue
|
|
423
379
|
|
|
424
|
-
|
|
380
|
+
x, y = self._prepare_coords(point.x, point.y)
|
|
381
|
+
label = self._text(x, y, text, **kwargs)
|
|
425
382
|
removed = self._maybe_remove_label(
|
|
426
|
-
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,
|
|
427
388
|
)
|
|
428
389
|
|
|
390
|
+
# TODO : remove label if not fully inside area?
|
|
391
|
+
|
|
429
392
|
if not removed:
|
|
430
393
|
self._add_label_to_rtree(label)
|
|
431
|
-
|
|
394
|
+
return label
|
|
432
395
|
|
|
433
396
|
@use_style(LabelStyle)
|
|
434
397
|
def text(
|
|
@@ -438,7 +401,6 @@ class BasePlot(ABC):
|
|
|
438
401
|
dec: float,
|
|
439
402
|
style: LabelStyle = None,
|
|
440
403
|
hide_on_collision: bool = True,
|
|
441
|
-
*args,
|
|
442
404
|
**kwargs,
|
|
443
405
|
):
|
|
444
406
|
"""
|
|
@@ -451,7 +413,7 @@ class BasePlot(ABC):
|
|
|
451
413
|
style: Styling of the text
|
|
452
414
|
hide_on_collision: If True, then the text will not be plotted if it collides with another label
|
|
453
415
|
"""
|
|
454
|
-
if not
|
|
416
|
+
if not text:
|
|
455
417
|
return
|
|
456
418
|
|
|
457
419
|
style = style or LabelStyle()
|
|
@@ -462,17 +424,32 @@ class BasePlot(ABC):
|
|
|
462
424
|
if style.offset_y == "auto":
|
|
463
425
|
style.offset_y = 0
|
|
464
426
|
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
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
|
+
)
|
|
476
453
|
|
|
477
454
|
@property
|
|
478
455
|
def objects(self) -> models.ObjectList:
|
|
@@ -540,18 +517,6 @@ class BasePlot(ABC):
|
|
|
540
517
|
style.zorder
|
|
541
518
|
)
|
|
542
519
|
|
|
543
|
-
def adjust_text(self, ensure_inside_axes: bool = False, **kwargs) -> None:
|
|
544
|
-
"""Adjust all the labels to avoid overlapping. This function uses the [adjustText](https://adjusttext.readthedocs.io/) library.
|
|
545
|
-
|
|
546
|
-
Args:
|
|
547
|
-
ensure_inside_axes: If True, then labels will be forced to stay within the axes
|
|
548
|
-
**kwargs: Any keyword arguments to pass through to [adjustText](https://adjusttext.readthedocs.io/en/latest/#adjustText.adjust_text)
|
|
549
|
-
|
|
550
|
-
"""
|
|
551
|
-
_adjust_text(
|
|
552
|
-
self.labels, ax=self.ax, ensure_inside_axes=ensure_inside_axes, **kwargs
|
|
553
|
-
)
|
|
554
|
-
|
|
555
520
|
def close_fig(self) -> None:
|
|
556
521
|
"""Closes the underlying matplotlib figure."""
|
|
557
522
|
if self.fig:
|
|
@@ -567,6 +532,7 @@ class BasePlot(ABC):
|
|
|
567
532
|
**kwargs: Any keyword arguments to pass through to matplotlib's `savefig` method
|
|
568
533
|
|
|
569
534
|
"""
|
|
535
|
+
self.logger.debug("Exporting...")
|
|
570
536
|
self.fig.savefig(
|
|
571
537
|
filename,
|
|
572
538
|
format=format,
|
|
@@ -708,7 +674,8 @@ class BasePlot(ABC):
|
|
|
708
674
|
Args:
|
|
709
675
|
style: Styling of the Sun. If None, then the plot's style (specified when creating the plot) will be used
|
|
710
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.
|
|
711
|
-
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
|
|
712
679
|
"""
|
|
713
680
|
s = models.Sun.get(
|
|
714
681
|
dt=self.dt, lat=self.lat, lon=self.lon, ephemeris=self._ephemeris_name
|
|
@@ -799,6 +766,7 @@ class BasePlot(ABC):
|
|
|
799
766
|
style: PolygonStyle,
|
|
800
767
|
points: list = None,
|
|
801
768
|
geometry: Polygon = None,
|
|
769
|
+
legend_label: str = None,
|
|
802
770
|
**kwargs,
|
|
803
771
|
):
|
|
804
772
|
"""
|
|
@@ -807,9 +775,11 @@ class BasePlot(ABC):
|
|
|
807
775
|
Must pass in either `points` **or** `geometry` (but not both).
|
|
808
776
|
|
|
809
777
|
Args:
|
|
778
|
+
style: Style of polygon
|
|
810
779
|
points: List of polygon points `[(ra, dec), ...]` - **must be in counterclockwise order**
|
|
811
780
|
geometry: A shapely Polygon. If this value is passed, then the `points` kwarg will be ignored.
|
|
812
|
-
|
|
781
|
+
legend_label: Label for this object in the legend
|
|
782
|
+
|
|
813
783
|
"""
|
|
814
784
|
if points is None and geometry is None:
|
|
815
785
|
raise ValueError("Must pass points or geometry when plotting polygons.")
|
|
@@ -820,6 +790,12 @@ class BasePlot(ABC):
|
|
|
820
790
|
_points = [(ra * 15, dec) for ra, dec in points]
|
|
821
791
|
self._polygon(_points, style, gid=kwargs.get("gid") or "polygon")
|
|
822
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
|
+
)
|
|
798
|
+
|
|
823
799
|
@use_style(PolygonStyle)
|
|
824
800
|
def rectangle(
|
|
825
801
|
self,
|
|
@@ -828,6 +804,7 @@ class BasePlot(ABC):
|
|
|
828
804
|
width_degrees: float,
|
|
829
805
|
style: PolygonStyle,
|
|
830
806
|
angle: float = 0,
|
|
807
|
+
legend_label: str = None,
|
|
831
808
|
**kwargs,
|
|
832
809
|
):
|
|
833
810
|
"""Plots a rectangle
|
|
@@ -836,8 +813,9 @@ class BasePlot(ABC):
|
|
|
836
813
|
center: Center of rectangle (ra, dec)
|
|
837
814
|
height_degrees: Height of rectangle (degrees)
|
|
838
815
|
width_degrees: Width of rectangle (degrees)
|
|
839
|
-
angle: Angle of rotation clockwise (degrees)
|
|
840
816
|
style: Style of rectangle
|
|
817
|
+
angle: Angle of rotation clockwise (degrees)
|
|
818
|
+
legend_label: Label for this object in the legend
|
|
841
819
|
"""
|
|
842
820
|
points = geod.rectangle(
|
|
843
821
|
center,
|
|
@@ -847,6 +825,12 @@ class BasePlot(ABC):
|
|
|
847
825
|
)
|
|
848
826
|
self._polygon(points, style, gid=kwargs.get("gid") or "polygon")
|
|
849
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
|
+
)
|
|
833
|
+
|
|
850
834
|
@use_style(PolygonStyle)
|
|
851
835
|
def ellipse(
|
|
852
836
|
self,
|
|
@@ -858,6 +842,7 @@ class BasePlot(ABC):
|
|
|
858
842
|
num_pts: int = 100,
|
|
859
843
|
start_angle: int = 0,
|
|
860
844
|
end_angle: int = 360,
|
|
845
|
+
legend_label: str = None,
|
|
861
846
|
**kwargs,
|
|
862
847
|
):
|
|
863
848
|
"""Plots an ellipse
|
|
@@ -869,6 +854,9 @@ class BasePlot(ABC):
|
|
|
869
854
|
style: Style of ellipse
|
|
870
855
|
angle: Angle of rotation clockwise (degrees)
|
|
871
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
|
|
872
860
|
"""
|
|
873
861
|
|
|
874
862
|
points = geod.ellipse(
|
|
@@ -882,6 +870,12 @@ class BasePlot(ABC):
|
|
|
882
870
|
)
|
|
883
871
|
self._polygon(points, style, gid=kwargs.get("gid") or "polygon")
|
|
884
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
|
+
)
|
|
878
|
+
|
|
885
879
|
@use_style(PolygonStyle)
|
|
886
880
|
def circle(
|
|
887
881
|
self,
|
|
@@ -889,6 +883,7 @@ class BasePlot(ABC):
|
|
|
889
883
|
radius_degrees: float,
|
|
890
884
|
style: PolygonStyle,
|
|
891
885
|
num_pts: int = 100,
|
|
886
|
+
legend_label: str = None,
|
|
892
887
|
**kwargs,
|
|
893
888
|
):
|
|
894
889
|
"""Plots a circle
|
|
@@ -898,6 +893,7 @@ class BasePlot(ABC):
|
|
|
898
893
|
radius_degrees: Radius of circle (degrees)
|
|
899
894
|
style: Style of circle
|
|
900
895
|
num_pts: Number of points to calculate for the circle polygon
|
|
896
|
+
legend_label: Label for this object in the legend
|
|
901
897
|
"""
|
|
902
898
|
self.ellipse(
|
|
903
899
|
center,
|
|
@@ -909,6 +905,12 @@ class BasePlot(ABC):
|
|
|
909
905
|
gid=kwargs.get("gid") or "polygon",
|
|
910
906
|
)
|
|
911
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
|
+
|
|
912
914
|
@use_style(LineStyle)
|
|
913
915
|
def line(self, coordinates: list[tuple[float, float]], style: LineStyle, **kwargs):
|
|
914
916
|
"""Plots a line
|