ssb-sgis 1.0.1__py3-none-any.whl → 1.0.3__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.
- sgis/__init__.py +107 -121
- sgis/exceptions.py +5 -3
- sgis/geopandas_tools/__init__.py +1 -0
- sgis/geopandas_tools/bounds.py +86 -47
- sgis/geopandas_tools/buffer_dissolve_explode.py +62 -39
- sgis/geopandas_tools/centerlines.py +53 -44
- sgis/geopandas_tools/cleaning.py +87 -104
- sgis/geopandas_tools/conversion.py +164 -107
- sgis/geopandas_tools/duplicates.py +33 -19
- sgis/geopandas_tools/general.py +84 -52
- sgis/geopandas_tools/geometry_types.py +24 -10
- sgis/geopandas_tools/neighbors.py +23 -11
- sgis/geopandas_tools/overlay.py +136 -53
- sgis/geopandas_tools/point_operations.py +11 -10
- sgis/geopandas_tools/polygon_operations.py +53 -61
- sgis/geopandas_tools/polygons_as_rings.py +121 -78
- sgis/geopandas_tools/sfilter.py +17 -17
- sgis/helpers.py +116 -58
- sgis/io/dapla_functions.py +32 -23
- sgis/io/opener.py +13 -6
- sgis/io/read_parquet.py +2 -2
- sgis/maps/examine.py +55 -28
- sgis/maps/explore.py +471 -112
- sgis/maps/httpserver.py +12 -12
- sgis/maps/legend.py +285 -134
- sgis/maps/map.py +248 -129
- sgis/maps/maps.py +123 -119
- sgis/maps/thematicmap.py +260 -94
- sgis/maps/tilesources.py +3 -8
- sgis/networkanalysis/_get_route.py +5 -4
- sgis/networkanalysis/_od_cost_matrix.py +44 -1
- sgis/networkanalysis/_points.py +10 -4
- sgis/networkanalysis/_service_area.py +5 -2
- sgis/networkanalysis/closing_network_holes.py +22 -64
- sgis/networkanalysis/cutting_lines.py +58 -46
- sgis/networkanalysis/directednetwork.py +16 -8
- sgis/networkanalysis/finding_isolated_networks.py +6 -5
- sgis/networkanalysis/network.py +15 -13
- sgis/networkanalysis/networkanalysis.py +79 -61
- sgis/networkanalysis/networkanalysisrules.py +21 -17
- sgis/networkanalysis/nodes.py +2 -3
- sgis/networkanalysis/traveling_salesman.py +6 -3
- sgis/parallel/parallel.py +372 -142
- sgis/raster/base.py +9 -3
- sgis/raster/cube.py +331 -213
- sgis/raster/cubebase.py +15 -29
- sgis/raster/image_collection.py +2560 -0
- sgis/raster/indices.py +17 -12
- sgis/raster/raster.py +356 -275
- sgis/raster/sentinel_config.py +104 -0
- sgis/raster/zonal.py +38 -14
- {ssb_sgis-1.0.1.dist-info → ssb_sgis-1.0.3.dist-info}/LICENSE +1 -1
- {ssb_sgis-1.0.1.dist-info → ssb_sgis-1.0.3.dist-info}/METADATA +87 -16
- ssb_sgis-1.0.3.dist-info/RECORD +61 -0
- {ssb_sgis-1.0.1.dist-info → ssb_sgis-1.0.3.dist-info}/WHEEL +1 -1
- sgis/raster/bands.py +0 -48
- sgis/raster/gradient.py +0 -78
- sgis/raster/methods_as_functions.py +0 -124
- sgis/raster/torchgeo.py +0 -150
- ssb_sgis-1.0.1.dist-info/RECORD +0 -63
sgis/maps/legend.py
CHANGED
|
@@ -4,18 +4,21 @@ The Legend class is best accessed through the 'legend' attribute of the Thematic
|
|
|
4
4
|
class.
|
|
5
5
|
|
|
6
6
|
"""
|
|
7
|
+
|
|
8
|
+
import itertools
|
|
7
9
|
import warnings
|
|
10
|
+
from typing import Any
|
|
8
11
|
|
|
9
12
|
import matplotlib
|
|
10
13
|
import matplotlib.pyplot as plt
|
|
11
14
|
import numpy as np
|
|
12
15
|
import pandas as pd
|
|
16
|
+
from geopandas import GeoDataFrame
|
|
13
17
|
from matplotlib.lines import Line2D
|
|
14
18
|
from pandas import Series
|
|
15
19
|
|
|
16
20
|
from ..geopandas_tools.bounds import points_in_bounds
|
|
17
21
|
|
|
18
|
-
|
|
19
22
|
# the geopandas._explore raises a deprication warning. Ignoring for now.
|
|
20
23
|
warnings.filterwarnings(
|
|
21
24
|
action="ignore", category=matplotlib.MatplotlibDeprecationWarning
|
|
@@ -23,6 +26,82 @@ warnings.filterwarnings(
|
|
|
23
26
|
pd.options.mode.chained_assignment = None
|
|
24
27
|
|
|
25
28
|
|
|
29
|
+
LEGEND_KWARGS = {
|
|
30
|
+
"title",
|
|
31
|
+
"size",
|
|
32
|
+
"position",
|
|
33
|
+
"fontsize",
|
|
34
|
+
"title_fontsize",
|
|
35
|
+
"markersize",
|
|
36
|
+
"framealpha",
|
|
37
|
+
"edgecolor",
|
|
38
|
+
"kwargs",
|
|
39
|
+
"labelspacing",
|
|
40
|
+
"title_color",
|
|
41
|
+
"width",
|
|
42
|
+
"height",
|
|
43
|
+
"labels",
|
|
44
|
+
"pretty_labels",
|
|
45
|
+
"thousand_sep",
|
|
46
|
+
"decimal_mark",
|
|
47
|
+
"label_sep",
|
|
48
|
+
"label_suffix",
|
|
49
|
+
"rounding",
|
|
50
|
+
"facecolor",
|
|
51
|
+
"labelcolor",
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
LOWERCASE_WORDS = {
|
|
55
|
+
"a",
|
|
56
|
+
"an",
|
|
57
|
+
"and",
|
|
58
|
+
"as",
|
|
59
|
+
"at",
|
|
60
|
+
"but",
|
|
61
|
+
"by",
|
|
62
|
+
"for",
|
|
63
|
+
"in",
|
|
64
|
+
"nor",
|
|
65
|
+
"of",
|
|
66
|
+
"on",
|
|
67
|
+
"or",
|
|
68
|
+
"the",
|
|
69
|
+
"up",
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def prettify_label(label: str) -> str:
|
|
74
|
+
"""Replace underscores with spaces and capitalize words that are all lowecase."""
|
|
75
|
+
return " ".join(
|
|
76
|
+
word.title() if word.islower() and word not in LOWERCASE_WORDS else word
|
|
77
|
+
for word in label.replace("_", " ").split()
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def prettify_number(x: int | float, rounding: int) -> int:
|
|
82
|
+
rounding = int(float(f"1e+{abs(rounding)}"))
|
|
83
|
+
rounded_down = int(x // rounding * rounding)
|
|
84
|
+
rounded_up = rounded_down + rounding
|
|
85
|
+
diff_up = abs(x - rounded_up)
|
|
86
|
+
diff_down = abs(x - rounded_down)
|
|
87
|
+
if diff_up < diff_down:
|
|
88
|
+
return rounded_up
|
|
89
|
+
else:
|
|
90
|
+
return rounded_down
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def prettify_bins(bins: list[int | float], rounding: int) -> list[int]:
|
|
94
|
+
return [
|
|
95
|
+
(
|
|
96
|
+
prettify_number(x, rounding)
|
|
97
|
+
if i != len(bins) - 1
|
|
98
|
+
else int(x)
|
|
99
|
+
# else prettify_number(x, rounding) + abs(rounding)
|
|
100
|
+
)
|
|
101
|
+
for i, x in enumerate(bins)
|
|
102
|
+
]
|
|
103
|
+
|
|
104
|
+
|
|
26
105
|
class Legend:
|
|
27
106
|
"""Holds the general attributes of the legend in the ThematicMap class.
|
|
28
107
|
|
|
@@ -33,78 +112,65 @@ class Legend:
|
|
|
33
112
|
If a numeric column is used, additional attributes can be found in the
|
|
34
113
|
ContinousLegend class.
|
|
35
114
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
position: The legend's x and y position in the plot, specified as a tuple of
|
|
40
|
-
x and y position between 0 and 1. E.g. position=(0.8, 0.2) for a position
|
|
41
|
-
in the bottom right corner, (0.2, 0.8) for the upper left corner.
|
|
42
|
-
fontsize: Text size of the legend labels. Defaults to the size of
|
|
43
|
-
the ThematicMap class.
|
|
44
|
-
title_fontsize: Text size of the legend title. Defaults to the
|
|
45
|
-
size * 1.2 of the ThematicMap class.
|
|
46
|
-
markersize: Size of the color circles in the legend. Defaults to the size of
|
|
47
|
-
the ThematicMap class.
|
|
48
|
-
framealpha: Transparency of the legend background.
|
|
49
|
-
edgecolor: Color of the legend border. Defaults to #0f0f0f (almost black).
|
|
50
|
-
kwargs: Stores additional keyword arguments taken by the matplotlib legend
|
|
51
|
-
method. Specify this as e.g. m.legend.kwargs["labelcolor"] = "red", where
|
|
52
|
-
'm' is the name of the ThematicMap instance. See here:
|
|
53
|
-
https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.legend.html
|
|
54
|
-
|
|
55
|
-
Examples
|
|
56
|
-
--------
|
|
57
|
-
Create ten random points with a numeric column from 0 to 9.
|
|
115
|
+
Examples:
|
|
116
|
+
---------
|
|
117
|
+
Create ten points with a numeric column from 0 to 9.
|
|
58
118
|
|
|
59
119
|
>>> import sgis as sg
|
|
60
|
-
>>> points = sg.
|
|
120
|
+
>>> points = sg.to_gdf(
|
|
121
|
+
... [
|
|
122
|
+
... (0, 1),
|
|
123
|
+
... (1, 0),
|
|
124
|
+
... (1, 1),
|
|
125
|
+
... (0, 0),
|
|
126
|
+
... (0.5, 0.5),
|
|
127
|
+
... (0.5, 0.25),
|
|
128
|
+
... (0.25, 0.25),
|
|
129
|
+
... (0.75, 0.75),
|
|
130
|
+
... (0.25, 0.75),
|
|
131
|
+
... (0.75, 0.25),
|
|
132
|
+
... ]
|
|
133
|
+
... )
|
|
61
134
|
>>> points["number"] = range(10)
|
|
62
135
|
>>> points
|
|
63
|
-
|
|
64
|
-
0 POINT (0.
|
|
65
|
-
1 POINT (
|
|
66
|
-
2 POINT (
|
|
67
|
-
3 POINT (0.
|
|
68
|
-
4 POINT (0.
|
|
69
|
-
5 POINT (0.
|
|
70
|
-
6 POINT (0.
|
|
71
|
-
7 POINT (0.
|
|
72
|
-
8 POINT (0.
|
|
73
|
-
9 POINT (0.
|
|
136
|
+
geometry number
|
|
137
|
+
0 POINT (0.00000 1.00000) 0
|
|
138
|
+
1 POINT (1.00000 0.00000) 1
|
|
139
|
+
2 POINT (1.00000 1.00000) 2
|
|
140
|
+
3 POINT (0.00000 0.00000) 3
|
|
141
|
+
4 POINT (0.50000 0.50000) 4
|
|
142
|
+
5 POINT (0.50000 0.25000) 5
|
|
143
|
+
6 POINT (0.25000 0.25000) 6
|
|
144
|
+
7 POINT (0.75000 0.75000) 7
|
|
145
|
+
8 POINT (0.25000 0.75000) 8
|
|
146
|
+
9 POINT (0.75000 0.25000) 9
|
|
74
147
|
|
|
75
148
|
Creating the ThematicMap instance will also create the legend. Since we
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
>>> m = sg.ThematicMap(
|
|
149
|
+
pass a numeric column, a ContinousLegend is created.
|
|
150
|
+
|
|
151
|
+
>>> m = sg.ThematicMap(
|
|
152
|
+
... points,
|
|
153
|
+
... column="number"
|
|
154
|
+
... legend_kwargs=dict(
|
|
155
|
+
... title="Meters",
|
|
156
|
+
... label_sep="to",
|
|
157
|
+
... label_suffix="num",
|
|
158
|
+
... rounding=2,
|
|
159
|
+
... position = (0.35, 0.28),
|
|
160
|
+
... title_fontsize=11,
|
|
161
|
+
... fontsize=9,
|
|
162
|
+
... markersize=7.5,
|
|
163
|
+
... ),
|
|
164
|
+
... )
|
|
165
|
+
>>> m.plot()
|
|
79
166
|
>>> m.legend
|
|
80
167
|
<sgis.maps.legend.ContinousLegend object at 0x00000222206738D0>
|
|
81
|
-
|
|
82
|
-
Changing the attributes that apply to both numeric and categorical columns.
|
|
83
|
-
|
|
84
|
-
>>> m.legend.title = "Meters"
|
|
85
|
-
>>> m.legend.title_fontsize = 11
|
|
86
|
-
>>> m.legend.fontsize = 9
|
|
87
|
-
>>> m.legend.markersize = 7.5
|
|
88
|
-
>>> m.legend.position = (0.35, 0.28)
|
|
89
|
-
>>> m.plot()
|
|
90
|
-
|
|
91
|
-
Additional matplotlib keyword arguments can be specified as kwargs.
|
|
92
|
-
|
|
93
|
-
>>> m.legend.kwargs["labelcolor"] = "red"
|
|
94
|
-
|
|
95
|
-
Since we are using a numeric column, the legend is of type ContinousLegend.
|
|
96
|
-
We can therefore also access the attributes that only apply to numeric columns.
|
|
97
|
-
|
|
98
|
-
>>> m.label_sep = "to"
|
|
99
|
-
>>> m.label_suffix = "num"
|
|
100
|
-
>>> m.rounding = 2
|
|
101
|
-
>>> m.plot()
|
|
102
|
-
|
|
103
168
|
"""
|
|
104
169
|
|
|
105
170
|
def __init__(
|
|
106
171
|
self,
|
|
107
172
|
title: str | None = None,
|
|
173
|
+
pretty_labels: bool = True,
|
|
108
174
|
labels: list[str] | None = None,
|
|
109
175
|
position: tuple[float] | None = None,
|
|
110
176
|
markersize: int | None = None,
|
|
@@ -113,7 +179,32 @@ class Legend:
|
|
|
113
179
|
framealpha: float = 1.0,
|
|
114
180
|
edgecolor: str = "#0f0f0f",
|
|
115
181
|
**kwargs,
|
|
116
|
-
):
|
|
182
|
+
) -> None:
|
|
183
|
+
"""Initialiser.
|
|
184
|
+
|
|
185
|
+
Args:
|
|
186
|
+
title: Legend title. Defaults to the column name if used in the
|
|
187
|
+
ThematicMap class.
|
|
188
|
+
pretty_labels: If True, words will be capitalized and underscores turned to spaces.
|
|
189
|
+
If continous values, numbers will be rounded.
|
|
190
|
+
labels: Labels of the categories.
|
|
191
|
+
position: The legend's x and y position in the plot, specified as a tuple of
|
|
192
|
+
x and y position between 0 and 1. E.g. position=(0.8, 0.2) for a position
|
|
193
|
+
in the bottom right corner, (0.2, 0.8) for the upper left corner.
|
|
194
|
+
fontsize: Text size of the legend labels. Defaults to the size of
|
|
195
|
+
the ThematicMap class.
|
|
196
|
+
title_fontsize: Text size of the legend title. Defaults to the
|
|
197
|
+
size * 1.2 of the ThematicMap class.
|
|
198
|
+
markersize: Size of the color circles in the legend. Defaults to the size of
|
|
199
|
+
the ThematicMap class.
|
|
200
|
+
framealpha: Transparency of the legend background.
|
|
201
|
+
edgecolor: Color of the legend border. Defaults to #0f0f0f (almost black).
|
|
202
|
+
kwargs: Stores additional keyword arguments taken by the matplotlib legend
|
|
203
|
+
method. Specify this as e.g. m.legend.kwargs["labelcolor"] = "red", where
|
|
204
|
+
'm' is the name of the ThematicMap instance. See here:
|
|
205
|
+
https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.legend.html
|
|
206
|
+
|
|
207
|
+
"""
|
|
117
208
|
self.title = title
|
|
118
209
|
|
|
119
210
|
if "size" in kwargs:
|
|
@@ -124,6 +215,7 @@ class Legend:
|
|
|
124
215
|
self._fontsize = fontsize
|
|
125
216
|
self._markersize = markersize
|
|
126
217
|
|
|
218
|
+
self.pretty_labels = pretty_labels
|
|
127
219
|
self.framealpha = framealpha
|
|
128
220
|
self.edgecolor = edgecolor
|
|
129
221
|
self.width = kwargs.pop("width", 0.1)
|
|
@@ -133,12 +225,25 @@ class Legend:
|
|
|
133
225
|
|
|
134
226
|
self.labels = labels
|
|
135
227
|
self._position = position
|
|
136
|
-
self.kwargs = kwargs
|
|
137
228
|
self._position_has_been_set = True if position else False
|
|
138
229
|
|
|
139
|
-
|
|
140
|
-
|
|
230
|
+
self.kwargs = {}
|
|
231
|
+
for key, value in kwargs.items():
|
|
232
|
+
if key not in LEGEND_KWARGS:
|
|
233
|
+
self.kwargs[key] = value
|
|
234
|
+
else:
|
|
235
|
+
try:
|
|
236
|
+
setattr(self, key, value)
|
|
237
|
+
except Exception:
|
|
238
|
+
setattr(self, f"_{key}", value)
|
|
141
239
|
|
|
240
|
+
@property
|
|
241
|
+
def valid_keywords(self) -> set[str]:
|
|
242
|
+
"""List all valid keywords for the class initialiser."""
|
|
243
|
+
return LEGEND_KWARGS
|
|
244
|
+
|
|
245
|
+
def _get_legend_sizes(self, size: int | float, kwargs: dict) -> None:
|
|
246
|
+
"""Adjust fontsize and markersize to size kwarg."""
|
|
142
247
|
if "title_fontsize" in kwargs:
|
|
143
248
|
self._title_fontsize = kwargs["title_fontsize"]
|
|
144
249
|
self._title_fontsize_has_been_set = True
|
|
@@ -157,7 +262,9 @@ class Legend:
|
|
|
157
262
|
else:
|
|
158
263
|
self._markersize = size
|
|
159
264
|
|
|
160
|
-
def _prepare_categorical_legend(
|
|
265
|
+
def _prepare_categorical_legend(
|
|
266
|
+
self, categories_colors: dict, nan_label: str
|
|
267
|
+
) -> None:
|
|
161
268
|
for attr in self.__dict__.keys():
|
|
162
269
|
if attr in self.kwargs:
|
|
163
270
|
self[attr] = self.kwargs.pop(attr)
|
|
@@ -178,6 +285,8 @@ class Legend:
|
|
|
178
285
|
|
|
179
286
|
self._patches, self._categories = [], []
|
|
180
287
|
for category, color in categories_colors.items():
|
|
288
|
+
if self.pretty_labels:
|
|
289
|
+
category = prettify_label(category)
|
|
181
290
|
if category == nan_label:
|
|
182
291
|
self._categories.append(nan_label)
|
|
183
292
|
else:
|
|
@@ -195,7 +304,9 @@ class Legend:
|
|
|
195
304
|
)
|
|
196
305
|
)
|
|
197
306
|
|
|
198
|
-
def _actually_add_legend(self, ax):
|
|
307
|
+
def _actually_add_legend(self, ax: matplotlib.axes.Axes) -> matplotlib.axes.Axes:
|
|
308
|
+
if self.pretty_labels:
|
|
309
|
+
self.title = prettify_label(self.title)
|
|
199
310
|
legend = ax.legend(
|
|
200
311
|
self._patches,
|
|
201
312
|
self._categories,
|
|
@@ -215,7 +326,9 @@ class Legend:
|
|
|
215
326
|
|
|
216
327
|
return ax
|
|
217
328
|
|
|
218
|
-
def _get_best_legend_position(
|
|
329
|
+
def _get_best_legend_position(
|
|
330
|
+
self, gdf: GeoDataFrame, k: int
|
|
331
|
+
) -> tuple[float, float]:
|
|
219
332
|
minx, miny, maxx, maxy = gdf.total_bounds
|
|
220
333
|
diffx = maxx - minx
|
|
221
334
|
diffy = maxy - miny
|
|
@@ -243,51 +356,58 @@ class Legend:
|
|
|
243
356
|
|
|
244
357
|
return bestx_01, besty_01
|
|
245
358
|
|
|
246
|
-
def __getitem__(self, item):
|
|
359
|
+
def __getitem__(self, item: str) -> Any:
|
|
360
|
+
"""Get attribute with square brackets."""
|
|
247
361
|
return getattr(self, item)
|
|
248
362
|
|
|
249
|
-
def __setitem__(self, key, value):
|
|
363
|
+
def __setitem__(self, key: str, value: Any) -> None:
|
|
364
|
+
"""Set attribute with square brackets."""
|
|
250
365
|
setattr(self, key, value)
|
|
251
366
|
|
|
252
|
-
def get(self, key, default=None):
|
|
367
|
+
def get(self, key: Any, default: Any = None) -> Any:
|
|
368
|
+
"""Get value of an attribute of the Legend."""
|
|
253
369
|
try:
|
|
254
370
|
return self[key]
|
|
255
371
|
except (KeyError, ValueError, IndexError, AttributeError):
|
|
256
372
|
return default
|
|
257
373
|
|
|
258
374
|
@property
|
|
259
|
-
def position(self):
|
|
375
|
+
def position(self) -> tuple[float, float]:
|
|
376
|
+
"""Legend position in x, y."""
|
|
260
377
|
return self._position
|
|
261
378
|
|
|
262
379
|
@position.setter
|
|
263
|
-
def position(self, new_value:
|
|
380
|
+
def position(self, new_value: tuple[float, float]) -> None:
|
|
264
381
|
self._position = new_value
|
|
265
382
|
self._position_has_been_set = True
|
|
266
383
|
|
|
267
384
|
@property
|
|
268
|
-
def title_fontsize(self):
|
|
385
|
+
def title_fontsize(self) -> int:
|
|
386
|
+
"""Legend title fontsize."""
|
|
269
387
|
return self._title_fontsize
|
|
270
388
|
|
|
271
389
|
@title_fontsize.setter
|
|
272
|
-
def title_fontsize(self, new_value:
|
|
390
|
+
def title_fontsize(self, new_value: int) -> None:
|
|
273
391
|
self._title_fontsize = new_value
|
|
274
392
|
self._title_fontsize_has_been_set = True
|
|
275
393
|
|
|
276
394
|
@property
|
|
277
|
-
def fontsize(self):
|
|
395
|
+
def fontsize(self) -> int:
|
|
396
|
+
"""Legend fontsize."""
|
|
278
397
|
return self._fontsize
|
|
279
398
|
|
|
280
399
|
@fontsize.setter
|
|
281
|
-
def fontsize(self, new_value:
|
|
400
|
+
def fontsize(self, new_value: int) -> None:
|
|
282
401
|
self._fontsize = new_value
|
|
283
402
|
self._fontsize_has_been_set = True
|
|
284
403
|
|
|
285
404
|
@property
|
|
286
|
-
def markersize(self):
|
|
405
|
+
def markersize(self) -> int:
|
|
406
|
+
"""Legend markersize."""
|
|
287
407
|
return self._markersize
|
|
288
408
|
|
|
289
409
|
@markersize.setter
|
|
290
|
-
def markersize(self, new_value:
|
|
410
|
+
def markersize(self, new_value: int) -> None:
|
|
291
411
|
self._markersize = new_value
|
|
292
412
|
self._markersize_has_been_set = True
|
|
293
413
|
|
|
@@ -311,18 +431,14 @@ class ContinousLegend(Legend):
|
|
|
311
431
|
Defaults to None.
|
|
312
432
|
label_sep: Text to put in between the two numbers in each color group in
|
|
313
433
|
the legend. Defaults to '-'.
|
|
314
|
-
rounding: Number of decimals in the labels. By default, the rounding
|
|
315
|
-
depends on the column's maximum value and standard deviation.
|
|
316
|
-
OBS: The bins will not be rounded, meaning the labels might be wrong
|
|
317
|
-
if not bins are set manually.
|
|
318
434
|
thousand_sep: Separator between each thousand for large numbers. Defaults to
|
|
319
435
|
None, meaning no separator.
|
|
320
436
|
decimal_mark: Text to use as decimal point. Defaults to None, meaning '.' (dot)
|
|
321
437
|
unless 'thousand_sep' is '.'. In this case, ',' (comma) will be used as
|
|
322
438
|
decimal mark.
|
|
323
439
|
|
|
324
|
-
Examples
|
|
325
|
-
|
|
440
|
+
Examples:
|
|
441
|
+
---------
|
|
326
442
|
Create ten random points with a numeric column from 0 to 9.
|
|
327
443
|
|
|
328
444
|
>>> import sgis as sg
|
|
@@ -379,14 +495,43 @@ class ContinousLegend(Legend):
|
|
|
379
495
|
def __init__(
|
|
380
496
|
self,
|
|
381
497
|
labels: list[str] | None = None,
|
|
382
|
-
pretty_labels: bool =
|
|
498
|
+
pretty_labels: bool = True,
|
|
383
499
|
label_suffix: str | None = None,
|
|
384
500
|
label_sep: str = "-",
|
|
385
501
|
rounding: int | None = None,
|
|
386
502
|
thousand_sep: str | None = None,
|
|
387
503
|
decimal_mark: str | None = None,
|
|
388
504
|
**kwargs,
|
|
389
|
-
):
|
|
505
|
+
) -> None:
|
|
506
|
+
"""Initialiser.
|
|
507
|
+
|
|
508
|
+
Args:
|
|
509
|
+
labels: To manually set labels. If set, all other labeling attributes are
|
|
510
|
+
ignored. Should be given as a list of strings with the same length as
|
|
511
|
+
the number of color groups.
|
|
512
|
+
pretty_labels: If False (default), the minimum and maximum values of each
|
|
513
|
+
color group will be used as legend labels. If True, the labels will end
|
|
514
|
+
with the maximum value, but start at 1 + the maximum value of the previous
|
|
515
|
+
group. The labels will be correct but inaccurate.
|
|
516
|
+
label_suffix: The text to put after each number in the legend labels.
|
|
517
|
+
Defaults to None.
|
|
518
|
+
label_sep: Text to put in between the two numbers in each color group in
|
|
519
|
+
the legend. Defaults to '-'.
|
|
520
|
+
rounding: Number of decimals in the labels. By default, the rounding
|
|
521
|
+
depends on the column's maximum value and standard deviation.
|
|
522
|
+
OBS: The bins will not be rounded, meaning the labels might be wrong
|
|
523
|
+
if not bins are set manually.
|
|
524
|
+
thousand_sep: Separator between each thousand for large numbers. Defaults to
|
|
525
|
+
None, meaning no separator.
|
|
526
|
+
decimal_mark: Text to use as decimal point. Defaults to None, meaning '.' (dot)
|
|
527
|
+
unless 'thousand_sep' is '.'. In this case, ',' (comma) will be used as
|
|
528
|
+
decimal mark.
|
|
529
|
+
kwargs: Stores additional keyword arguments taken by the matplotlib legend
|
|
530
|
+
method. Specify this as e.g. m.legend.kwargs["labelcolor"] = "red", where
|
|
531
|
+
'm' is the name of the ThematicMap instance. See here:
|
|
532
|
+
https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.legend.html
|
|
533
|
+
|
|
534
|
+
"""
|
|
390
535
|
super().__init__(**kwargs)
|
|
391
536
|
|
|
392
537
|
self.pretty_labels = pretty_labels
|
|
@@ -396,7 +541,7 @@ class ContinousLegend(Legend):
|
|
|
396
541
|
self.label_sep = label_sep
|
|
397
542
|
self.label_suffix = "" if not label_suffix else label_suffix
|
|
398
543
|
self._rounding = rounding
|
|
399
|
-
self._rounding_has_been_set = True if rounding else False
|
|
544
|
+
# self._rounding_has_been_set = True if rounding else False
|
|
400
545
|
|
|
401
546
|
def _get_rounding(self, array: Series | np.ndarray) -> int:
|
|
402
547
|
def isinteger(x):
|
|
@@ -423,13 +568,15 @@ class ContinousLegend(Legend):
|
|
|
423
568
|
return int(abs(np.log10(std_))) + 1
|
|
424
569
|
|
|
425
570
|
@staticmethod
|
|
426
|
-
def _set_rounding(bins, rounding: int | float):
|
|
427
|
-
if rounding
|
|
428
|
-
return [int(round(
|
|
571
|
+
def _set_rounding(bins, rounding: int | float) -> list[int | float]:
|
|
572
|
+
if not rounding:
|
|
573
|
+
return [int(round(bin_, 0)) for bin_ in bins]
|
|
574
|
+
elif rounding <= 0:
|
|
575
|
+
return [int(round(bin_, rounding)) for bin_ in bins]
|
|
429
576
|
else:
|
|
430
|
-
return [round(
|
|
577
|
+
return [round(bin_, rounding) for bin_ in bins]
|
|
431
578
|
|
|
432
|
-
def _remove_max_legend_value(self):
|
|
579
|
+
def _remove_max_legend_value(self) -> None:
|
|
433
580
|
if not self._legend:
|
|
434
581
|
raise ValueError("Cannot modify legend before it is created.")
|
|
435
582
|
|
|
@@ -439,11 +586,11 @@ class ContinousLegend(Legend):
|
|
|
439
586
|
colors: list[str],
|
|
440
587
|
nan_label: str,
|
|
441
588
|
bin_values: dict,
|
|
442
|
-
):
|
|
589
|
+
) -> None:
|
|
443
590
|
# TODO: clean up this messy method
|
|
444
591
|
|
|
445
|
-
for attr in self.
|
|
446
|
-
if attr in self.
|
|
592
|
+
for attr in self.kwargs:
|
|
593
|
+
if attr in self.__dict__:
|
|
447
594
|
self[attr] = self.kwargs.pop(attr)
|
|
448
595
|
|
|
449
596
|
self._patches, self._categories = [], []
|
|
@@ -466,9 +613,10 @@ class ContinousLegend(Legend):
|
|
|
466
613
|
if len(self.labels) != len(colors):
|
|
467
614
|
raise ValueError(
|
|
468
615
|
"Label list must be same length as the number of groups. "
|
|
469
|
-
f"Got k={len(colors)} and labels={len(
|
|
616
|
+
f"Got k={len(colors)} and labels={len(self.labels)}."
|
|
470
617
|
f"labels: {', '.join(self.labels)}"
|
|
471
618
|
f"colors: {', '.join(colors)}"
|
|
619
|
+
f"bins: {bins}"
|
|
472
620
|
)
|
|
473
621
|
self._categories = self.labels
|
|
474
622
|
|
|
@@ -491,72 +639,69 @@ class ContinousLegend(Legend):
|
|
|
491
639
|
)
|
|
492
640
|
|
|
493
641
|
else:
|
|
494
|
-
for i, (cat1, cat2) in enumerate(
|
|
642
|
+
for i, (cat1, cat2) in enumerate(itertools.pairwise(bins)):
|
|
495
643
|
if nan_label in str(cat1) or nan_label in str(cat2):
|
|
496
644
|
self._categories.append(nan_label)
|
|
497
645
|
continue
|
|
498
646
|
|
|
499
647
|
min_ = np.min(bin_values[i])
|
|
500
648
|
max_ = np.max(bin_values[i])
|
|
501
|
-
|
|
502
|
-
max_rounded = self._set_rounding([max_], self._rounding)[0]
|
|
649
|
+
|
|
503
650
|
if self.pretty_labels:
|
|
504
|
-
if i
|
|
505
|
-
cat1 = int(
|
|
506
|
-
|
|
507
|
-
|
|
651
|
+
if i == 0:
|
|
652
|
+
cat1 = int(min_) if (self.rounding or 0) <= 0 else min_
|
|
653
|
+
|
|
654
|
+
is_last = i == len(bins) - 2
|
|
655
|
+
if is_last:
|
|
656
|
+
cat2 = int(max_) if (self.rounding or 0) <= 0 else max_
|
|
657
|
+
|
|
658
|
+
if (self.rounding or 0) <= 0:
|
|
659
|
+
cat1 = int(cat1)
|
|
660
|
+
cat2 = int(cat2 - 1) if not is_last else int(cat2)
|
|
661
|
+
elif (self.rounding or 0) > 0:
|
|
662
|
+
cat1 = round(cat1, self._rounding)
|
|
663
|
+
cat2 = round(
|
|
664
|
+
cat2 - float(f"1e-{self._rounding}"), self._rounding
|
|
665
|
+
)
|
|
666
|
+
else:
|
|
667
|
+
cat1 = round(cat1, self._rounding)
|
|
668
|
+
cat2 = round(cat2, self._rounding)
|
|
508
669
|
|
|
509
670
|
cat1 = self._format_number(cat1)
|
|
510
671
|
cat2 = self._format_number(cat2)
|
|
511
672
|
|
|
512
673
|
if min_ == max_:
|
|
513
|
-
label = self.
|
|
674
|
+
label = self._get_two_value_label(cat1, cat2)
|
|
514
675
|
self._categories.append(label)
|
|
515
676
|
continue
|
|
516
677
|
|
|
517
|
-
label = self.
|
|
678
|
+
label = self._get_two_value_label(cat1, cat2)
|
|
518
679
|
self._categories.append(label)
|
|
519
680
|
|
|
520
|
-
|
|
681
|
+
continue
|
|
682
|
+
|
|
683
|
+
min_rounded = self._set_rounding([min_], self._rounding)[0]
|
|
684
|
+
max_rounded = self._set_rounding([max_], self._rounding)[0]
|
|
685
|
+
if min_ == max_:
|
|
521
686
|
min_rounded = self._format_number(min_rounded)
|
|
522
|
-
label = self.
|
|
687
|
+
label = self._get_one_value_label(min_rounded)
|
|
523
688
|
self._categories.append(label)
|
|
524
689
|
else:
|
|
525
690
|
min_rounded = self._format_number(min_rounded)
|
|
526
691
|
max_rounded = self._format_number(max_rounded)
|
|
527
|
-
label = self.
|
|
692
|
+
label = self._get_two_value_label(min_rounded, max_rounded)
|
|
528
693
|
self._categories.append(label)
|
|
529
694
|
|
|
530
|
-
def
|
|
531
|
-
legend = ax.legend(
|
|
532
|
-
self._patches,
|
|
533
|
-
self._categories,
|
|
534
|
-
fontsize=self._fontsize,
|
|
535
|
-
title=self.title,
|
|
536
|
-
title_fontsize=self._title_fontsize,
|
|
537
|
-
bbox_to_anchor=self._position + (self.width, self.height),
|
|
538
|
-
fancybox=False,
|
|
539
|
-
framealpha=self.framealpha,
|
|
540
|
-
edgecolor=self.edgecolor,
|
|
541
|
-
labelspacing=self.labelspacing,
|
|
542
|
-
**self.kwargs,
|
|
543
|
-
)
|
|
544
|
-
|
|
545
|
-
if self.title_color:
|
|
546
|
-
plt.setp(legend.get_title(), color=self.title_color)
|
|
547
|
-
|
|
548
|
-
return ax
|
|
549
|
-
|
|
550
|
-
def _two_value_label(self, value1, value2):
|
|
695
|
+
def _get_two_value_label(self, value1: int | float, value2: int | float) -> str:
|
|
551
696
|
return (
|
|
552
697
|
f"{value1} {self.label_suffix} {self.label_sep} "
|
|
553
698
|
f"{value2} {self.label_suffix}"
|
|
554
699
|
)
|
|
555
700
|
|
|
556
|
-
def
|
|
701
|
+
def _get_one_value_label(self, value1: int | float) -> str:
|
|
557
702
|
return f"{value1} {self.label_suffix}"
|
|
558
703
|
|
|
559
|
-
def _format_number(self, number):
|
|
704
|
+
def _format_number(self, number: int | float) -> int | float:
|
|
560
705
|
if not self.thousand_sep and not self.decimal_mark:
|
|
561
706
|
return number
|
|
562
707
|
|
|
@@ -581,10 +726,16 @@ class ContinousLegend(Legend):
|
|
|
581
726
|
return number
|
|
582
727
|
|
|
583
728
|
@property
|
|
584
|
-
def rounding(self):
|
|
729
|
+
def rounding(self) -> int:
|
|
730
|
+
"""Number of decimals in the labels.
|
|
731
|
+
|
|
732
|
+
By default, the rounding
|
|
733
|
+
depends on the column's maximum value and standard deviation.
|
|
734
|
+
OBS: The bins will not be rounded, meaning the labels might be wrong
|
|
735
|
+
if not bins are set manually.
|
|
736
|
+
"""
|
|
585
737
|
return self._rounding
|
|
586
738
|
|
|
587
739
|
@rounding.setter
|
|
588
|
-
def rounding(self, new_value:
|
|
740
|
+
def rounding(self, new_value: int) -> None:
|
|
589
741
|
self._rounding = new_value
|
|
590
|
-
self._rounding_has_been_set = True
|