newsworthycharts 1.58.0__tar.gz → 1.60.0__tar.gz

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.
Files changed (51) hide show
  1. {newsworthycharts-1.58.0 → newsworthycharts-1.60.0}/PKG-INFO +45 -6
  2. newsworthycharts-1.58.0/newsworthycharts.egg-info/PKG-INFO → newsworthycharts-1.60.0/README.rst +29 -17
  3. {newsworthycharts-1.58.0 → newsworthycharts-1.60.0}/newsworthycharts/__init__.py +1 -1
  4. {newsworthycharts-1.58.0 → newsworthycharts-1.60.0}/newsworthycharts/chart.py +18 -9
  5. {newsworthycharts-1.58.0 → newsworthycharts-1.60.0}/newsworthycharts/choroplethmap.py +74 -68
  6. {newsworthycharts-1.58.0 → newsworthycharts-1.60.0}/newsworthycharts/map.py +2 -2
  7. {newsworthycharts-1.58.0 → newsworthycharts-1.60.0}/newsworthycharts/serialchart.py +27 -26
  8. newsworthycharts-1.58.0/README.rst → newsworthycharts-1.60.0/newsworthycharts.egg-info/PKG-INFO +56 -4
  9. {newsworthycharts-1.58.0 → newsworthycharts-1.60.0}/newsworthycharts.egg-info/requires.txt +9 -8
  10. {newsworthycharts-1.58.0 → newsworthycharts-1.60.0}/setup.py +5 -4
  11. {newsworthycharts-1.58.0 → newsworthycharts-1.60.0}/test/test_main.py +6 -6
  12. {newsworthycharts-1.58.0 → newsworthycharts-1.60.0}/LICENSE.txt +0 -0
  13. {newsworthycharts-1.58.0 → newsworthycharts-1.60.0}/MANIFEST.in +0 -0
  14. {newsworthycharts-1.58.0 → newsworthycharts-1.60.0}/newsworthycharts/bubblemap.py +0 -0
  15. {newsworthycharts-1.58.0 → newsworthycharts-1.60.0}/newsworthycharts/categoricalchart.py +0 -0
  16. {newsworthycharts-1.58.0 → newsworthycharts-1.60.0}/newsworthycharts/custom/__init__.py +0 -0
  17. {newsworthycharts-1.58.0 → newsworthycharts-1.60.0}/newsworthycharts/custom/climate_cars.py +0 -0
  18. {newsworthycharts-1.58.0 → newsworthycharts-1.60.0}/newsworthycharts/datawrapper.py +0 -0
  19. {newsworthycharts-1.58.0 → newsworthycharts-1.60.0}/newsworthycharts/lib/__init__.py +0 -0
  20. {newsworthycharts-1.58.0 → newsworthycharts-1.60.0}/newsworthycharts/lib/color_fn.py +0 -0
  21. {newsworthycharts-1.58.0 → newsworthycharts-1.60.0}/newsworthycharts/lib/colors.py +0 -0
  22. {newsworthycharts-1.58.0 → newsworthycharts-1.60.0}/newsworthycharts/lib/datalist.py +0 -0
  23. {newsworthycharts-1.58.0 → newsworthycharts-1.60.0}/newsworthycharts/lib/formatter.py +0 -0
  24. {newsworthycharts-1.58.0 → newsworthycharts-1.60.0}/newsworthycharts/lib/geography.py +0 -0
  25. {newsworthycharts-1.58.0 → newsworthycharts-1.60.0}/newsworthycharts/lib/locator.py +0 -0
  26. {newsworthycharts-1.58.0 → newsworthycharts-1.60.0}/newsworthycharts/lib/mimetypes.py +0 -0
  27. {newsworthycharts-1.58.0 → newsworthycharts-1.60.0}/newsworthycharts/lib/utils.py +0 -0
  28. {newsworthycharts-1.58.0 → newsworthycharts-1.60.0}/newsworthycharts/maps/se-4.gpkg +0 -0
  29. {newsworthycharts-1.58.0 → newsworthycharts-1.60.0}/newsworthycharts/maps/se-7.gpkg +0 -0
  30. {newsworthycharts-1.58.0 → newsworthycharts-1.60.0}/newsworthycharts/rangeplot.py +0 -0
  31. {newsworthycharts-1.58.0 → newsworthycharts-1.60.0}/newsworthycharts/rc/newsworthy +0 -0
  32. {newsworthycharts-1.58.0 → newsworthycharts-1.60.0}/newsworthycharts/scatterplot.py +0 -0
  33. {newsworthycharts-1.58.0 → newsworthycharts-1.60.0}/newsworthycharts/seasonalchart.py +0 -0
  34. {newsworthycharts-1.58.0 → newsworthycharts-1.60.0}/newsworthycharts/storage.py +0 -0
  35. {newsworthycharts-1.58.0 → newsworthycharts-1.60.0}/newsworthycharts/stripechart.py +0 -0
  36. {newsworthycharts-1.58.0 → newsworthycharts-1.60.0}/newsworthycharts/translations/datawrapper_regions.csv +0 -0
  37. {newsworthycharts-1.58.0 → newsworthycharts-1.60.0}/newsworthycharts/translations/regions.py +0 -0
  38. {newsworthycharts-1.58.0 → newsworthycharts-1.60.0}/newsworthycharts/translations/se_municipalities.csv +0 -0
  39. {newsworthycharts-1.58.0 → newsworthycharts-1.60.0}/newsworthycharts.egg-info/SOURCES.txt +0 -0
  40. {newsworthycharts-1.58.0 → newsworthycharts-1.60.0}/newsworthycharts.egg-info/dependency_links.txt +0 -0
  41. {newsworthycharts-1.58.0 → newsworthycharts-1.60.0}/newsworthycharts.egg-info/not-zip-safe +0 -0
  42. {newsworthycharts-1.58.0 → newsworthycharts-1.60.0}/newsworthycharts.egg-info/top_level.txt +0 -0
  43. {newsworthycharts-1.58.0 → newsworthycharts-1.60.0}/setup.cfg +0 -0
  44. {newsworthycharts-1.58.0 → newsworthycharts-1.60.0}/test/test_categorical_chart.py +0 -0
  45. {newsworthycharts-1.58.0 → newsworthycharts-1.60.0}/test/test_choropleth_maps.py +0 -0
  46. {newsworthycharts-1.58.0 → newsworthycharts-1.60.0}/test/test_custom_climate_cars.py +0 -0
  47. {newsworthycharts-1.58.0 → newsworthycharts-1.60.0}/test/test_data_list.py +0 -0
  48. {newsworthycharts-1.58.0 → newsworthycharts-1.60.0}/test/test_datawrapper.py +0 -0
  49. {newsworthycharts-1.58.0 → newsworthycharts-1.60.0}/test/test_rangeplot.py +0 -0
  50. {newsworthycharts-1.58.0 → newsworthycharts-1.60.0}/test/test_scatterplot.py +0 -0
  51. {newsworthycharts-1.58.0 → newsworthycharts-1.60.0}/test/test_serial_chart.py +0 -0
@@ -1,15 +1,29 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: newsworthycharts
3
- Version: 1.58.0
3
+ Version: 1.60.0
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.58.0.tar.gz
6
+ Download-URL: https://github.com/jplusplus/newsworthycharts/archive/1.60.0.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
16
+ Requires-Dist: Babel<3,>=2.14.0
17
+ Requires-Dist: PyYAML>=3
18
+ Requires-Dist: adjustText==0.7.3
19
+ Requires-Dist: numpy>2
20
+ Requires-Dist: python-dateutil<3,>=2
21
+ Requires-Dist: Pillow==10.4.0
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.0
13
27
 
14
28
  This module contains methods for producing graphs and publishing them on Amazon S3, or in the location of your choice.
15
29
 
@@ -123,8 +137,8 @@ These settings are available for all chart types:
123
137
  - yline = None # A horizontal line across the chart (Matplotlib: axhline)
124
138
  - labels = [] # Optionally one label for each dataset
125
139
  - annotations = [] # Manually added annotations
126
- - interval = None # yearly|quarterly|monthly|weekly|daily
127
- - units = 'number' # number|percent|degrees
140
+ - interval = None # yearly, quarterly, monthly, weekly, daily
141
+ - units = 'number' # number, percent, degrees
128
142
  - show_ticks = True # toggle category names, dates, etc
129
143
  - subtitle = None
130
144
  - note = None
@@ -132,6 +146,7 @@ These settings are available for all chart types:
132
146
  - ylabel = None
133
147
  - caption = None
134
148
  - highlight = A position (typically a date, category label or index) to highlight. The semantics may differ somewhat between chart types.
149
+ - highlight_annotation = True # Print out values at highlighted point?
135
150
  - decimals = None # None means automatically chose the best number
136
151
  - logo = None # Path to image that will be embedded in the caption area. Can also be set though a style property
137
152
  - color_fn = None # Custom coloring function
@@ -222,13 +237,37 @@ To deploy a new version to [PyPi](https://pypi.org/project/newsworthycharts):
222
237
 
223
238
  Roadmap
224
239
  -------
225
- - Adding more base maps
226
- - Getting rid of custom settings-hack
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)
227
244
  - Custom month locator with equal-width month bars
245
+ - Add color-ramp choropleth maps, as an alternative to binning
228
246
 
229
247
  Changelog
230
248
  ---------
231
249
 
250
+ - 1.60.0
251
+
252
+ - [BREAKING] The default number of bins in maps is now 5, not 9
253
+ - [BREAKING] Default color ramp for choropleth maps is now `YlGn`
254
+ - GeoPandas 1.x
255
+ - numpy 2.x
256
+ - matplotlib==3.9.2
257
+ - Pillow==10.4.0
258
+ - Improved nan handling in labelling continuous choropleth maps
259
+ - Replace deprecated `.unary_union` with `.union_all()`
260
+ - Choropleth map legends now print spans for binned data
261
+ - Use Matplotlib's legend for choropleth maps, to have the same style as other charts, and for much improved flexibility
262
+ - Inset maps now (finally!) work with continuous data
263
+ - Better error handling in categorical maps
264
+ - Cast all categorical values to strings in categorical maps
265
+ - Replace deprecated imghdr with puremagic in tests
266
+
267
+ - 1.59.0
268
+
269
+ - Added `.highlight_annotation` to enable turning off the textual annotation on the highlighted point
270
+
232
271
  - 1.58.0
233
272
 
234
273
  - Matplotlib==3.9
@@ -1,16 +1,3 @@
1
- Metadata-Version: 2.1
2
- Name: newsworthycharts
3
- Version: 1.58.0
4
- Summary: Matplotlib wrapper to create charts and publish them on Amazon S3
5
- Home-page: https://github.com/jplusplus/newsworthycharts
6
- Download-URL: https://github.com/jplusplus/newsworthycharts/archive/1.58.0.tar.gz
7
- Author: Jens Finnäs and Leo Wallentin, J++ Stockholm
8
- Author-email: stockholm@jplusplus.org
9
- License: MIT
10
- Requires-Python: >=3.9
11
- Description-Content-Type: text/x-rst
12
- License-File: LICENSE.txt
13
-
14
1
  This module contains methods for producing graphs and publishing them on Amazon S3, or in the location of your choice.
15
2
 
16
3
  It is written and maintained for `Newsworthy <https://www.newsworthy.se/en/>`_, but could possibly come in handy for other people as well.
@@ -123,8 +110,8 @@ These settings are available for all chart types:
123
110
  - yline = None # A horizontal line across the chart (Matplotlib: axhline)
124
111
  - labels = [] # Optionally one label for each dataset
125
112
  - annotations = [] # Manually added annotations
126
- - interval = None # yearly|quarterly|monthly|weekly|daily
127
- - units = 'number' # number|percent|degrees
113
+ - interval = None # yearly, quarterly, monthly, weekly, daily
114
+ - units = 'number' # number, percent, degrees
128
115
  - show_ticks = True # toggle category names, dates, etc
129
116
  - subtitle = None
130
117
  - note = None
@@ -132,6 +119,7 @@ These settings are available for all chart types:
132
119
  - ylabel = None
133
120
  - caption = None
134
121
  - highlight = A position (typically a date, category label or index) to highlight. The semantics may differ somewhat between chart types.
122
+ - highlight_annotation = True # Print out values at highlighted point?
135
123
  - decimals = None # None means automatically chose the best number
136
124
  - logo = None # Path to image that will be embedded in the caption area. Can also be set though a style property
137
125
  - color_fn = None # Custom coloring function
@@ -222,13 +210,37 @@ To deploy a new version to [PyPi](https://pypi.org/project/newsworthycharts):
222
210
 
223
211
  Roadmap
224
212
  -------
225
- - Adding more base maps
226
- - Getting rid of custom settings-hack
213
+
214
+ - Get rid of custom settings-hack
215
+ - Remove custom charts (add missing api interfaces to Chart class instead)
216
+ - Remove DataWrapper class (out-of-scope)
227
217
  - Custom month locator with equal-width month bars
218
+ - Add color-ramp choropleth maps, as an alternative to binning
228
219
 
229
220
  Changelog
230
221
  ---------
231
222
 
223
+ - 1.60.0
224
+
225
+ - [BREAKING] The default number of bins in maps is now 5, not 9
226
+ - [BREAKING] Default color ramp for choropleth maps is now `YlGn`
227
+ - GeoPandas 1.x
228
+ - numpy 2.x
229
+ - matplotlib==3.9.2
230
+ - Pillow==10.4.0
231
+ - Improved nan handling in labelling continuous choropleth maps
232
+ - Replace deprecated `.unary_union` with `.union_all()`
233
+ - Choropleth map legends now print spans for binned data
234
+ - Use Matplotlib's legend for choropleth maps, to have the same style as other charts, and for much improved flexibility
235
+ - Inset maps now (finally!) work with continuous data
236
+ - Better error handling in categorical maps
237
+ - Cast all categorical values to strings in categorical maps
238
+ - Replace deprecated imghdr with puremagic in tests
239
+
240
+ - 1.59.0
241
+
242
+ - Added `.highlight_annotation` to enable turning off the textual annotation on the highlighted point
243
+
232
244
  - 1.58.0
233
245
 
234
246
  - Matplotlib==3.9
@@ -1,4 +1,4 @@
1
- __version__ = "1.58.0"
1
+ __version__ = "1.60.0"
2
2
 
3
3
  from .chart import Chart
4
4
  from .choroplethmap import ChoroplethMap
@@ -61,6 +61,7 @@ class Chart(object):
61
61
  self.ylabel = None
62
62
  self.caption = None
63
63
  self.highlight = None
64
+ self.highlight_annotation = True
64
65
  self.decimals = None
65
66
  self.yline = None
66
67
  self.type = None
@@ -259,10 +260,15 @@ class Chart(object):
259
260
  hextent = (0, self._w)
260
261
  self._set_size(hextent[1] - hextent[0])
261
262
  x1 = hextent[0] / self._w
262
- text = self._fig.text(x1, 0.01, caption,
263
- in_layout=True,
264
- color=self._nwc_style["neutral_color"], wrap=True,
265
- fontsize=self._nwc_style["caption.fontsize"])
263
+ text = self._fig.text(
264
+ x1,
265
+ 0.01,
266
+ caption,
267
+ in_layout=True,
268
+ color=self._nwc_style["neutral_color"],
269
+ wrap=True,
270
+ fontsize=self._nwc_style["caption.fontsize"],
271
+ )
266
272
  self._fig.canvas.draw()
267
273
  wrapped_text = text._get_wrapped_text()
268
274
  text.set_text(wrapped_text)
@@ -272,11 +278,14 @@ class Chart(object):
272
278
 
273
279
  def _add_title(self, title_text):
274
280
  """Add a title."""
275
- # y=1 wraps title heavily, hence .9999
276
- text = self._fig.suptitle(title_text, wrap=True, x=0, y=0.985,
277
- horizontalalignment="left",
278
- multialignment="left",
279
- fontproperties=self._title_font)
281
+ text = self._fig.suptitle(
282
+ title_text, wrap=True,
283
+ x=0,
284
+ y=0.985, # default: 0.98
285
+ horizontalalignment="left",
286
+ multialignment="left",
287
+ fontproperties=self._title_font,
288
+ )
280
289
 
281
290
  self._title_elem = text
282
291
 
@@ -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
- if self.categorical:
33
- # We'll categorize manually further down the line,
34
- # to easier implement custom coloring
35
- pass
36
- # df["data"] = pd.Categorical(
37
- # df["data"],
38
- # ordered=True,
39
- # )
40
- else:
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
- args = {
62
- "categorical": True,
63
- "legend": True, # bug in geopandas, fixed in master but not released
64
- "legend_kwds": {
65
- "loc": "upper left",
66
- "bbox_to_anchor": (1.05, 1.0),
67
- },
68
- "edgecolor": "white",
69
- "linewidth": 0.2,
70
- "missing_kwds": {
71
- "color": "gainsboro",
72
- },
73
- }
74
- # This should be adjusted per basemap
75
- label_kwargs = {
76
- "bbox_to_anchor": (0.92, 0.95),
77
- "loc": "upper left",
78
- }
79
- if not self.categorical:
80
- args["cmap"] = self.color_ramp
81
- args["column"] = "data"
82
- if self.categorical:
83
- cat = df[~df["data"].isna()]["data"].unique()
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
- self.ax.legend(
108
- handles=patches,
109
- **label_kwargs
110
- )
133
+ if self.missing_label:
134
+ patches.append(mpatches.Patch(color="gainsboro", label=self.missing_label))
111
135
 
112
- fig = df.plot(ax=self.ax, **args)
136
+ df.plot(ax=self.ax, **args)
113
137
  # Add outer edge
114
- gpd.GeoSeries(df.unary_union).plot(
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
- if self.categorical:
154
- # We need a series matching the filtered data
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.unary_union).plot(
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
+ )
@@ -87,10 +87,10 @@ class Map(Chart):
87
87
 
88
88
  def __init__(self, *args, **kwargs):
89
89
  super(Map, self).__init__(*args, **kwargs)
90
- self.bins = kwargs.get("bins", 9)
90
+ self.bins = kwargs.get("bins", 5)
91
91
  self.binning_method = kwargs.get("binning_method", "natural_breaks")
92
92
  self.colors = kwargs.get("colors", None)
93
- self.color_ramp = kwargs.get("color_ramp", "YlOrRd")
93
+ self.color_ramp = kwargs.get("color_ramp", "YlGn") # YlOrRd
94
94
  self.categorical = kwargs.get("categorical", False)
95
95
  self.base_map = None
96
96
  self.missing_label = None
@@ -409,35 +409,36 @@ class SerialChart(Chart):
409
409
  self.ax.legend(handles=patches)
410
410
 
411
411
  # Annotate highlighted points/bars
412
- for hv in highlight_values:
413
- value_label = a_formatter(hv)
414
- xy = (highlight_date, hv)
415
- if self.type[i] == "bars":
416
- if hv >= self.baseline:
417
- dir = "up"
418
- else:
419
- dir = "down"
420
- if self.type[i] == "line":
421
- if len(highlight_values) > 1:
422
- # When highlighting two values on the same point,
423
- # put them in opposite direction
424
- if hv == max(highlight_values):
412
+ if self.highlight_annotation:
413
+ for hv in highlight_values:
414
+ value_label = a_formatter(hv)
415
+ xy = (highlight_date, hv)
416
+ if self.type[i] == "bars":
417
+ if hv >= self.baseline:
425
418
  dir = "up"
426
- elif hv == min(highlight_values):
427
- dir = "down"
428
419
  else:
429
- dir = "left" # To the right we have diff annotation
430
- else:
431
- # Otherwise, use what works best with the line shape
432
- if highlight_date in dates:
433
- i = dates.index(highlight_date)
434
- dir = self._get_annotation_direction(i, values)
420
+ dir = "down"
421
+ if self.type[i] == "line":
422
+ if len(highlight_values) > 1:
423
+ # When highlighting two values on the same point,
424
+ # put them in opposite direction
425
+ if hv == max(highlight_values):
426
+ dir = "up"
427
+ elif hv == min(highlight_values):
428
+ dir = "down"
429
+ else:
430
+ dir = "left" # To the right we have diff annotation
435
431
  else:
436
- # This highlight is probably out of range for this dataset
437
- # Could happen if we have two or more lines,
438
- # with different start and end points.
439
- continue
440
- self._annotate_point(value_label, xy, direction=dir)
432
+ # Otherwise, use what works best with the line shape
433
+ if highlight_date in dates:
434
+ i = dates.index(highlight_date)
435
+ dir = self._get_annotation_direction(i, values)
436
+ else:
437
+ # This highlight is probably out of range for this dataset
438
+ # Could happen if we have two or more lines,
439
+ # with different start and end points.
440
+ continue
441
+ self._annotate_point(value_label, xy, direction=dir)
441
442
 
442
443
  # Add background highlights
443
444
  for (x0, x1) in self.highlighted_x_ranges:
@@ -1,3 +1,30 @@
1
+ Metadata-Version: 2.1
2
+ Name: newsworthycharts
3
+ Version: 1.60.0
4
+ Summary: Matplotlib wrapper to create charts and publish them on Amazon S3
5
+ Home-page: https://github.com/jplusplus/newsworthycharts
6
+ Download-URL: https://github.com/jplusplus/newsworthycharts/archive/1.60.0.tar.gz
7
+ Author: Jens Finnäs and Leo Wallentin, J++ Stockholm
8
+ Author-email: stockholm@jplusplus.org
9
+ License: MIT
10
+ Requires-Python: >=3.9
11
+ Description-Content-Type: text/x-rst
12
+ License-File: LICENSE.txt
13
+ Requires-Dist: boto3>=1.26
14
+ Requires-Dist: matplotlib==3.9.2
15
+ Requires-Dist: langcodes>=3.3
16
+ Requires-Dist: Babel<3,>=2.14.0
17
+ Requires-Dist: PyYAML>=3
18
+ Requires-Dist: adjustText==0.7.3
19
+ Requires-Dist: numpy>2
20
+ Requires-Dist: python-dateutil<3,>=2
21
+ Requires-Dist: Pillow==10.4.0
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.0
27
+
1
28
  This module contains methods for producing graphs and publishing them on Amazon S3, or in the location of your choice.
2
29
 
3
30
  It is written and maintained for `Newsworthy <https://www.newsworthy.se/en/>`_, but could possibly come in handy for other people as well.
@@ -110,8 +137,8 @@ These settings are available for all chart types:
110
137
  - yline = None # A horizontal line across the chart (Matplotlib: axhline)
111
138
  - labels = [] # Optionally one label for each dataset
112
139
  - annotations = [] # Manually added annotations
113
- - interval = None # yearly|quarterly|monthly|weekly|daily
114
- - units = 'number' # number|percent|degrees
140
+ - interval = None # yearly, quarterly, monthly, weekly, daily
141
+ - units = 'number' # number, percent, degrees
115
142
  - show_ticks = True # toggle category names, dates, etc
116
143
  - subtitle = None
117
144
  - note = None
@@ -119,6 +146,7 @@ These settings are available for all chart types:
119
146
  - ylabel = None
120
147
  - caption = None
121
148
  - highlight = A position (typically a date, category label or index) to highlight. The semantics may differ somewhat between chart types.
149
+ - highlight_annotation = True # Print out values at highlighted point?
122
150
  - decimals = None # None means automatically chose the best number
123
151
  - logo = None # Path to image that will be embedded in the caption area. Can also be set though a style property
124
152
  - color_fn = None # Custom coloring function
@@ -209,13 +237,37 @@ To deploy a new version to [PyPi](https://pypi.org/project/newsworthycharts):
209
237
 
210
238
  Roadmap
211
239
  -------
212
- - Adding more base maps
213
- - Getting rid of custom settings-hack
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)
214
244
  - Custom month locator with equal-width month bars
245
+ - Add color-ramp choropleth maps, as an alternative to binning
215
246
 
216
247
  Changelog
217
248
  ---------
218
249
 
250
+ - 1.60.0
251
+
252
+ - [BREAKING] The default number of bins in maps is now 5, not 9
253
+ - [BREAKING] Default color ramp for choropleth maps is now `YlGn`
254
+ - GeoPandas 1.x
255
+ - numpy 2.x
256
+ - matplotlib==3.9.2
257
+ - Pillow==10.4.0
258
+ - Improved nan handling in labelling continuous choropleth maps
259
+ - Replace deprecated `.unary_union` with `.union_all()`
260
+ - Choropleth map legends now print spans for binned data
261
+ - Use Matplotlib's legend for choropleth maps, to have the same style as other charts, and for much improved flexibility
262
+ - Inset maps now (finally!) work with continuous data
263
+ - Better error handling in categorical maps
264
+ - Cast all categorical values to strings in categorical maps
265
+ - Replace deprecated imghdr with puremagic in tests
266
+
267
+ - 1.59.0
268
+
269
+ - Added `.highlight_annotation` to enable turning off the textual annotation on the highlighted point
270
+
219
271
  - 1.58.0
220
272
 
221
273
  - Matplotlib==3.9
@@ -1,13 +1,14 @@
1
+ boto3>=1.26
2
+ matplotlib==3.9.2
3
+ langcodes>=3.3
1
4
  Babel<3,>=2.14.0
2
- Pillow==10.3.0
3
5
  PyYAML>=3
4
6
  adjustText==0.7.3
5
- boto3>=1.26
6
- geopandas==0.14.4
7
- langcodes>=3.3
8
- mapclassify==2.6.1
9
- matplotlib-label-lines==0.5.1
10
- matplotlib==3.9.0
11
- numpy<2,>=1.21.0
7
+ numpy>2
12
8
  python-dateutil<3,>=2
9
+ Pillow==10.4.0
13
10
  requests>=2.22
11
+ matplotlib-label-lines==0.5.1
12
+ geopandas>1
13
+ mapclassify==2.6.1
14
+ fiona>=1.0
@@ -25,18 +25,19 @@ setup(
25
25
  python_requires='>=3.9',
26
26
  install_requires=[
27
27
  "boto3>=1.26",
28
- "matplotlib==3.9.0",
28
+ "matplotlib==3.9.2",
29
29
  "langcodes>=3.3",
30
30
  "Babel>=2.14.0,<3",
31
31
  "PyYAML>=3",
32
32
  "adjustText==0.7.3",
33
- "numpy>=1.21.0,<2",
33
+ "numpy>2",
34
34
  "python-dateutil>=2,<3",
35
- "Pillow==10.3.0",
35
+ "Pillow==10.4.0",
36
36
  "requests>=2.22",
37
37
  "matplotlib-label-lines==0.5.1",
38
- "geopandas==0.14.4",
38
+ "geopandas>1",
39
39
  "mapclassify==2.6.1",
40
+ "fiona>=1.0",
40
41
  ],
41
42
  setup_requires=["flake8"],
42
43
  include_package_data=True,
@@ -1,12 +1,12 @@
1
1
  """ py.test tests for Newsworthycharts
2
2
  """
3
3
  import pytest
4
+ import puremagic
5
+ import numpy as np
4
6
  from newsworthycharts import Chart, SerialChart, CategoricalChart, CHART_ENGINES
5
7
  from newsworthycharts.storage import DictStorage, LocalStorage
6
- from imghdr import what
7
8
  from PIL import Image
8
9
  from hashlib import md5
9
- import numpy as np
10
10
 
11
11
  # store test charts to this folder for visual verfication
12
12
  OUTPUT_DIR = "test/rendered_charts"
@@ -20,7 +20,7 @@ def test_generating_png():
20
20
  c.render("test", "png")
21
21
 
22
22
  assert "png" in container
23
- assert what(container["png"]) == "png"
23
+ assert puremagic.from_stream(container["png"]) == ".png"
24
24
 
25
25
 
26
26
  def test_generating_webp():
@@ -30,7 +30,7 @@ def test_generating_webp():
30
30
  c.render("test", "webp")
31
31
 
32
32
  assert "webp" in container
33
- assert what(container["webp"]) == "webp"
33
+ assert puremagic.from_stream(container["webp"]) == ".webp"
34
34
  im = Image.open(container["webp"])
35
35
  assert im.size == (800, 600)
36
36
 
@@ -43,7 +43,7 @@ def test_dynamic_init():
43
43
  c.render("test", "png")
44
44
 
45
45
  assert "png" in container
46
- assert what(container["png"]) == "png"
46
+ assert puremagic.from_stream(container["png"]) == ".png"
47
47
 
48
48
 
49
49
  def test_factory_function():
@@ -56,7 +56,7 @@ def test_factory_function():
56
56
  c.render("test", "png")
57
57
 
58
58
  assert "png" in container
59
- assert what(container["png"]) == "png"
59
+ assert puremagic.from_stream(container["png"]) == ".png"
60
60
 
61
61
  # Make sure it works in a child class
62
62
  c = SerialChart.init_from({