newsworthycharts 1.59.0__py3-none-any.whl → 1.60.1__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.
- newsworthycharts/__init__.py +1 -1
- newsworthycharts/choroplethmap.py +74 -68
- newsworthycharts/map.py +3 -9
- {newsworthycharts-1.59.0.dist-info → newsworthycharts-1.60.1.dist-info}/METADATA +39 -14
- {newsworthycharts-1.59.0.dist-info → newsworthycharts-1.60.1.dist-info}/RECORD +8 -8
- {newsworthycharts-1.59.0.dist-info → newsworthycharts-1.60.1.dist-info}/WHEEL +1 -1
- {newsworthycharts-1.59.0.dist-info → newsworthycharts-1.60.1.dist-info}/LICENSE.txt +0 -0
- {newsworthycharts-1.59.0.dist-info → newsworthycharts-1.60.1.dist-info}/top_level.txt +0 -0
newsworthycharts/__init__.py
CHANGED
@@ -6,6 +6,7 @@ import geopandas as gpd
|
|
6
6
|
import numpy as np
|
7
7
|
import pandas as pd
|
8
8
|
import mapclassify
|
9
|
+
import matplotlib.patches as mpatches
|
9
10
|
|
10
11
|
|
11
12
|
class ChoroplethMap(Map):
|
@@ -29,15 +30,27 @@ class ChoroplethMap(Map):
|
|
29
30
|
|
30
31
|
df = self._prepare_map_data()
|
31
32
|
|
32
|
-
|
33
|
-
|
34
|
-
#
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
33
|
+
args = {
|
34
|
+
"categorical": True,
|
35
|
+
"legend": True, # bug in geopandas, fixed in master but not released
|
36
|
+
"legend_kwds": {
|
37
|
+
"loc": "upper left",
|
38
|
+
"bbox_to_anchor": (1.05, 1.0),
|
39
|
+
},
|
40
|
+
"edgecolor": "white",
|
41
|
+
"linewidth": 0.2,
|
42
|
+
"missing_kwds": {
|
43
|
+
"color": "gainsboro",
|
44
|
+
},
|
45
|
+
}
|
46
|
+
# This should be adjusted per basemap
|
47
|
+
label_kwargs = {
|
48
|
+
"bbox_to_anchor": (0.92, 0.95),
|
49
|
+
"loc": "upper left",
|
50
|
+
}
|
51
|
+
|
52
|
+
patches = [] # for legend
|
53
|
+
if not self.categorical:
|
41
54
|
# mapclassify doesn't work well with nan values,
|
42
55
|
# but we to keep them for plotting, hence
|
43
56
|
# this hack with cutting out nan's and re-pasting them below
|
@@ -58,44 +71,57 @@ class ChoroplethMap(Map):
|
|
58
71
|
_dict = _has_value[["id", "cats"]].set_index("id").to_dict()
|
59
72
|
df["data"] = df["id"].map(_dict["cats"])
|
60
73
|
|
61
|
-
|
62
|
-
"
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
"
|
70
|
-
"
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
74
|
+
# args["column"] = "data"
|
75
|
+
# args["cmap"] = self.color_ramp
|
76
|
+
# We can not provide vmin/vmax to geopandas, so we need to
|
77
|
+
# normalize the data ourselves, otherwise the inset maps will be off
|
78
|
+
import matplotlib.cm as cm
|
79
|
+
import matplotlib as mpl
|
80
|
+
norm = mpl.colors.Normalize(vmin=df["data"].min(), vmax=df["data"].max())
|
81
|
+
mapper = cm.ScalarMappable(norm=norm, cmap=self.color_ramp)
|
82
|
+
df["color"] = df["data"].apply(lambda x: mapper.to_rgba(x) if not np.isnan(x) else "gainsboro")
|
83
|
+
df["color"] = df["color"].fillna("gainsboro")
|
84
|
+
args["color"] = df["color"]
|
85
|
+
|
86
|
+
# Add labels legend (manually, Geopandas is too crude as of now)
|
87
|
+
fmt = self._get_value_axis_formatter()
|
88
|
+
for idx, cat in enumerate(binning.bins):
|
89
|
+
# cat is the upper limit of each category
|
90
|
+
if binning.counts[idx] == 1:
|
91
|
+
txt_val = fmt(cat)
|
92
|
+
elif idx == 0:
|
93
|
+
txt_val = f"– {fmt(cat)}"
|
94
|
+
else:
|
95
|
+
txt_val = f"{fmt(binning.bins[idx - 1])} – {fmt(cat)}"
|
96
|
+
patches.append(mpatches.Patch(color=mapper.to_rgba(cat), label=txt_val))
|
97
|
+
|
98
|
+
elif self.categorical:
|
99
|
+
# We'll categorize manually further down the line,
|
100
|
+
# to easier implement custom coloring
|
101
|
+
# df["data"] = pd.Categorical(
|
102
|
+
# df["data"],
|
103
|
+
# ordered=True,
|
104
|
+
# )
|
105
|
+
|
106
|
+
cat = df[~df["data"].isna()]["data"].astype(str).unique()
|
84
107
|
args["categories"] = cat
|
85
108
|
if self.colors:
|
86
109
|
color_map = self.colors
|
87
110
|
else:
|
88
111
|
color_map = {}
|
112
|
+
if len(cat) > len(self._nwc_style["qualitative_colors"]):
|
113
|
+
raise ValueError(
|
114
|
+
"Too many categories for the available colors in the current style. " + # noqa:W504
|
115
|
+
"Add a custom color map, or use a style with more categorical colors!"
|
116
|
+
)
|
89
117
|
for idx, cat in enumerate(cat):
|
90
118
|
color_map[cat] = self._nwc_style["qualitative_colors"][idx]
|
91
|
-
df["color"] = df["data"].map(color_map)
|
119
|
+
df["color"] = df["data"].astype(str).map(color_map)
|
92
120
|
df["color"] = df["color"].fillna("gainsboro")
|
93
121
|
args["color"] = df["color"]
|
94
122
|
|
95
123
|
# Geopandas does not handle legend if color keyword is used
|
96
124
|
# We need to add it ourselves
|
97
|
-
import matplotlib.patches as mpatches
|
98
|
-
patches = []
|
99
125
|
for label, color in color_map.items():
|
100
126
|
# A bit of an hack:
|
101
127
|
# Check if this corresponds to one of our predefined
|
@@ -104,44 +130,20 @@ class ChoroplethMap(Map):
|
|
104
130
|
color = self._nwc_style[f"{color}_color"]
|
105
131
|
patch = mpatches.Patch(color=color, label=label)
|
106
132
|
patches.append(patch)
|
107
|
-
|
108
|
-
|
109
|
-
**label_kwargs
|
110
|
-
)
|
133
|
+
if self.missing_label:
|
134
|
+
patches.append(mpatches.Patch(color="gainsboro", label=self.missing_label))
|
111
135
|
|
112
|
-
|
136
|
+
df.plot(ax=self.ax, **args)
|
113
137
|
# Add outer edge
|
114
|
-
gpd.GeoSeries(df.
|
138
|
+
gpd.GeoSeries(df.union_all()).plot(
|
115
139
|
ax=self.ax,
|
116
140
|
edgecolor="lightgrey",
|
117
141
|
linewidth=0.2,
|
118
142
|
facecolor="none",
|
119
143
|
color="none",
|
120
144
|
)
|
121
|
-
self.ax.axis("off")
|
122
|
-
|
123
|
-
# Format numbers in legend
|
124
|
-
if not self.categorical:
|
125
|
-
leg = fig.get_legend()
|
126
|
-
fmt = self._get_value_axis_formatter()
|
127
|
-
remove_last = False
|
128
|
-
for lbl in leg.get_texts():
|
129
|
-
val = lbl.get_text()
|
130
|
-
if val == "NaN": # as returned by mapclassify
|
131
|
-
if self.missing_label is not None:
|
132
|
-
val = self.missing_label
|
133
|
-
else:
|
134
|
-
remove_last = True
|
135
|
-
val = ""
|
136
|
-
else:
|
137
|
-
val = float(val)
|
138
|
-
val = fmt(val)
|
139
|
-
lbl.set_text(val)
|
140
|
-
if remove_last:
|
141
|
-
del leg.legend_handles[-1]
|
142
|
-
texts = [lbl.get_text() for lbl in leg.get_texts()]
|
143
|
-
fig.legend(handles=leg.legend_handles, labels=texts, **label_kwargs)
|
144
145
|
|
146
|
+
self.ax.axis("off")
|
145
147
|
for inset in self.insets:
|
146
148
|
if "prefix" in inset:
|
147
149
|
_df = df[df["id"].str.startswith(inset["prefix"])].copy()
|
@@ -150,12 +152,11 @@ class ChoroplethMap(Map):
|
|
150
152
|
if _df["data"].isnull().all():
|
151
153
|
# Skip if no data
|
152
154
|
continue
|
153
|
-
|
154
|
-
|
155
|
-
args["color"] = _df["color"]
|
155
|
+
|
156
|
+
args["color"] = _df["color"]
|
156
157
|
args["legend"] = False
|
157
158
|
axin = self.ax.inset_axes(inset["axes"])
|
158
|
-
gpd.GeoSeries(_df.
|
159
|
+
gpd.GeoSeries(_df.union_all()).plot(
|
159
160
|
ax=axin,
|
160
161
|
edgecolor="lightgrey",
|
161
162
|
linewidth=0.3,
|
@@ -169,3 +170,8 @@ class ChoroplethMap(Map):
|
|
169
170
|
r, (a, b, c, d) = self.ax.indicate_inset_zoom(axin)
|
170
171
|
for _line in [a, b, c, d]:
|
171
172
|
_line.set_visible(False)
|
173
|
+
|
174
|
+
self.ax.legend(
|
175
|
+
handles=patches,
|
176
|
+
**label_kwargs,
|
177
|
+
)
|
newsworthycharts/map.py
CHANGED
@@ -4,7 +4,6 @@ Base class for maps
|
|
4
4
|
from .chart import Chart
|
5
5
|
from .lib.geography import haversine
|
6
6
|
from .translations.regions import NW_MUNI_TO_CLDR
|
7
|
-
from fiona.errors import DriverError
|
8
7
|
import geopandas as gpd
|
9
8
|
import pathlib
|
10
9
|
|
@@ -87,10 +86,10 @@ class Map(Chart):
|
|
87
86
|
|
88
87
|
def __init__(self, *args, **kwargs):
|
89
88
|
super(Map, self).__init__(*args, **kwargs)
|
90
|
-
self.bins = kwargs.get("bins",
|
89
|
+
self.bins = kwargs.get("bins", 5)
|
91
90
|
self.binning_method = kwargs.get("binning_method", "natural_breaks")
|
92
91
|
self.colors = kwargs.get("colors", None)
|
93
|
-
self.color_ramp = kwargs.get("color_ramp", "
|
92
|
+
self.color_ramp = kwargs.get("color_ramp", "YlGn") # YlOrRd
|
94
93
|
self.categorical = kwargs.get("categorical", False)
|
95
94
|
self.base_map = None
|
96
95
|
self.missing_label = None
|
@@ -139,12 +138,7 @@ class Map(Chart):
|
|
139
138
|
|
140
139
|
if self.df is None:
|
141
140
|
__dir = pathlib.Path(__file__).parent.resolve()
|
142
|
-
|
143
|
-
self.df = gpd.read_file(f"{__dir}/maps/{base_map}-{subdivisions}.gpkg")
|
144
|
-
except DriverError:
|
145
|
-
raise ValueError(
|
146
|
-
f"No such basemap: {_bm} (parsed as base: {base_map}, subdivisions: {subdivisions})"
|
147
|
-
)
|
141
|
+
self.df = gpd.read_file(f"{__dir}/maps/{base_map}-{subdivisions}.gpkg", engine="pyogrio")
|
148
142
|
return base_map, subdivisions, subset, *opts
|
149
143
|
|
150
144
|
def _prepare_map_data(self):
|
@@ -1,28 +1,29 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: newsworthycharts
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.60.1
|
4
4
|
Summary: Matplotlib wrapper to create charts and publish them on Amazon S3
|
5
5
|
Home-page: https://github.com/jplusplus/newsworthycharts
|
6
|
-
Download-URL: https://github.com/jplusplus/newsworthycharts/archive/1.
|
6
|
+
Download-URL: https://github.com/jplusplus/newsworthycharts/archive/1.60.1.tar.gz
|
7
7
|
Author: Jens Finnäs and Leo Wallentin, J++ Stockholm
|
8
8
|
Author-email: stockholm@jplusplus.org
|
9
9
|
License: MIT
|
10
10
|
Requires-Python: >=3.9
|
11
11
|
Description-Content-Type: text/x-rst
|
12
12
|
License-File: LICENSE.txt
|
13
|
+
Requires-Dist: boto3 >=1.26
|
14
|
+
Requires-Dist: matplotlib ==3.9.2
|
15
|
+
Requires-Dist: langcodes >=3.3
|
13
16
|
Requires-Dist: Babel <3,>=2.14.0
|
14
|
-
Requires-Dist: Pillow ==10.3.0
|
15
17
|
Requires-Dist: PyYAML >=3
|
16
18
|
Requires-Dist: adjustText ==0.7.3
|
17
|
-
Requires-Dist:
|
18
|
-
Requires-Dist: geopandas ==0.14.4
|
19
|
-
Requires-Dist: langcodes >=3.3
|
20
|
-
Requires-Dist: mapclassify ==2.6.1
|
21
|
-
Requires-Dist: matplotlib-label-lines ==0.5.1
|
22
|
-
Requires-Dist: matplotlib ==3.9.0
|
23
|
-
Requires-Dist: numpy <2,>=1.21.0
|
19
|
+
Requires-Dist: numpy >2
|
24
20
|
Requires-Dist: python-dateutil <3,>=2
|
21
|
+
Requires-Dist: Pillow ==10.4.0
|
25
22
|
Requires-Dist: requests >=2.22
|
23
|
+
Requires-Dist: matplotlib-label-lines ==0.5.1
|
24
|
+
Requires-Dist: geopandas >1
|
25
|
+
Requires-Dist: mapclassify ==2.6.1
|
26
|
+
Requires-Dist: fiona >=1.10
|
26
27
|
|
27
28
|
This module contains methods for producing graphs and publishing them on Amazon S3, or in the location of your choice.
|
28
29
|
|
@@ -136,8 +137,8 @@ These settings are available for all chart types:
|
|
136
137
|
- yline = None # A horizontal line across the chart (Matplotlib: axhline)
|
137
138
|
- labels = [] # Optionally one label for each dataset
|
138
139
|
- annotations = [] # Manually added annotations
|
139
|
-
- interval = None # yearly
|
140
|
-
- units = 'number' # number
|
140
|
+
- interval = None # yearly, quarterly, monthly, weekly, daily
|
141
|
+
- units = 'number' # number, percent, degrees
|
141
142
|
- show_ticks = True # toggle category names, dates, etc
|
142
143
|
- subtitle = None
|
143
144
|
- note = None
|
@@ -236,13 +237,37 @@ To deploy a new version to [PyPi](https://pypi.org/project/newsworthycharts):
|
|
236
237
|
|
237
238
|
Roadmap
|
238
239
|
-------
|
239
|
-
|
240
|
-
-
|
240
|
+
|
241
|
+
- Get rid of custom settings-hack
|
242
|
+
- Remove custom charts (add missing api interfaces to Chart class instead)
|
243
|
+
- Remove DataWrapper class (out-of-scope)
|
241
244
|
- Custom month locator with equal-width month bars
|
245
|
+
- Add color-ramp choropleth maps, as an alternative to binning
|
242
246
|
|
243
247
|
Changelog
|
244
248
|
---------
|
245
249
|
|
250
|
+
- 1.60.1
|
251
|
+
|
252
|
+
- Don't require fiona. Geopandas now support Pyogrio
|
253
|
+
|
254
|
+
- 1.60.0
|
255
|
+
|
256
|
+
- [BREAKING] The default number of bins in maps is now 5, not 9
|
257
|
+
- [BREAKING] Default color ramp for choropleth maps is now `YlGn`
|
258
|
+
- GeoPandas 1.x
|
259
|
+
- numpy 2.x
|
260
|
+
- matplotlib==3.9.2
|
261
|
+
- Pillow==10.4.0
|
262
|
+
- Improved nan handling in labelling continuous choropleth maps
|
263
|
+
- Replace deprecated `.unary_union` with `.union_all()`
|
264
|
+
- Choropleth map legends now print spans for binned data
|
265
|
+
- Use Matplotlib's legend for choropleth maps, to have the same style as other charts, and for much improved flexibility
|
266
|
+
- Inset maps now (finally!) work with continuous data
|
267
|
+
- Better error handling in categorical maps
|
268
|
+
- Cast all categorical values to strings in categorical maps
|
269
|
+
- Replace deprecated imghdr with puremagic in tests
|
270
|
+
|
246
271
|
- 1.59.0
|
247
272
|
|
248
273
|
- Added `.highlight_annotation` to enable turning off the textual annotation on the highlighted point
|
@@ -1,10 +1,10 @@
|
|
1
|
-
newsworthycharts/__init__.py,sha256=
|
1
|
+
newsworthycharts/__init__.py,sha256=4ncjyqowiBpTm3mvedckG9MB2y2Rzc4y2I-5_mVFgeU,1160
|
2
2
|
newsworthycharts/bubblemap.py,sha256=nkocWmpiFgfjEuJGAsthjY5X7Q56jXWsZHUGXw4PwgE,2587
|
3
3
|
newsworthycharts/categoricalchart.py,sha256=LwOZ3VbNy9vzvoK0s77AkbfMt4CXVDSAhnsnBInUIrE,14764
|
4
4
|
newsworthycharts/chart.py,sha256=VXEJe_g_KahyiUXqXnM3vDS7SQoW1Ppktcj13-A2_oc,30631
|
5
|
-
newsworthycharts/choroplethmap.py,sha256=
|
5
|
+
newsworthycharts/choroplethmap.py,sha256=dVRShxjkBKa57r_Kp6ysdNCXGwXh3eZK28MvVtLe7BQ,6677
|
6
6
|
newsworthycharts/datawrapper.py,sha256=RRkAVTpfP4updKxUIBaSmKuBi2RUVPaBRF8HDQhlGGA,11250
|
7
|
-
newsworthycharts/map.py,sha256=
|
7
|
+
newsworthycharts/map.py,sha256=EMpsbvY4V4Y5_ky0Urg4WgvqSEKiDd2kUHXFReMRbb0,6004
|
8
8
|
newsworthycharts/rangeplot.py,sha256=NE1W9TnmlpK6T3RvBJOU3nd73EXqkj17OY9i5zlw_cQ,8366
|
9
9
|
newsworthycharts/scatterplot.py,sha256=6iaMoiZx__Gc-2Hcdw-8Ga5dSonrFo3oexKNmSFuir4,4959
|
10
10
|
newsworthycharts/seasonalchart.py,sha256=rr55yqJUkaYDR9Ik98jes6574oY1U8t8LwoLE3gClW4,1967
|
@@ -28,8 +28,8 @@ newsworthycharts/rc/newsworthy,sha256=X0btLNrmk2DRrfOsKj_WCSIgeD6btacEN2tRF_B4m8
|
|
28
28
|
newsworthycharts/translations/datawrapper_regions.csv,sha256=fzZcQRX6RFMlNNP8mpgfYNdR3Y0QAlQxDXk8FXTaWWI,9214
|
29
29
|
newsworthycharts/translations/regions.py,sha256=Nv1McQjggD4S3JRu82rDMTG3pqUVR13E5-FBpSYbm98,239
|
30
30
|
newsworthycharts/translations/se_municipalities.csv,sha256=br_mm-IvzQtj_W55_ATREhJ97jWnCweBFlDAVY2EBxA,7098
|
31
|
-
newsworthycharts-1.
|
32
|
-
newsworthycharts-1.
|
33
|
-
newsworthycharts-1.
|
34
|
-
newsworthycharts-1.
|
35
|
-
newsworthycharts-1.
|
31
|
+
newsworthycharts-1.60.1.dist-info/LICENSE.txt,sha256=Sq6kGICrehbhC_FolNdXf0djKjTpv3YqjFCIYsxdQN4,1069
|
32
|
+
newsworthycharts-1.60.1.dist-info/METADATA,sha256=JIG6aIDf814dmnAg3XYbfGmlsSnN2_KeJCngrP4K8Ow,28235
|
33
|
+
newsworthycharts-1.60.1.dist-info/WHEEL,sha256=cVxcB9AmuTcXqmwrtPhNK88dr7IR_b6qagTj0UvIEbY,91
|
34
|
+
newsworthycharts-1.60.1.dist-info/top_level.txt,sha256=dn_kzIj8UgUCMsh1PHdVEQJHVGSsN7Z8YJF-8xXa8n0,17
|
35
|
+
newsworthycharts-1.60.1.dist-info/RECORD,,
|
File without changes
|
File without changes
|