newsworthycharts 1.54.4__py3-none-any.whl → 1.54.5__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.
@@ -1,4 +1,4 @@
1
- __version__ = "1.54.4"
1
+ __version__ = "1.54.5"
2
2
 
3
3
  from .chart import Chart
4
4
  from .choroplethmap import ChoroplethMap
@@ -79,7 +79,10 @@ def get_best_locator(delta, points, interval=None):
79
79
 
80
80
  elif interval == "daily" or interval is None:
81
81
  if delta.days > 30:
82
- # FIXME dont print every month
83
82
  return MonthLocator()
83
+ elif delta.days > 21:
84
+ return DayLocator(interval=10)
85
+ elif delta.days > 7:
86
+ return DayLocator(interval=5)
84
87
  else:
85
88
  return DayLocator()
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: newsworthycharts
3
- Version: 1.54.4
3
+ Version: 1.54.5
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.54.4.tar.gz
6
+ Download-URL: https://github.com/jplusplus/newsworthycharts/archive/1.54.5.tar.gz
7
7
  Author: Jens Finnäs and Leo Wallentin, J++ Stockholm
8
8
  Author-email: stockholm@jplusplus.org
9
9
  License: MIT
@@ -11,15 +11,15 @@ Requires-Python: >=3.9
11
11
  Description-Content-Type: text/x-rst
12
12
  License-File: LICENSE.txt
13
13
  Requires-Dist: Babel <3,>=2.14.0
14
- Requires-Dist: Pillow ==10.1.0
14
+ Requires-Dist: Pillow ==10.2.0
15
15
  Requires-Dist: PyYAML >=3
16
16
  Requires-Dist: adjustText ==0.7.3
17
17
  Requires-Dist: boto3 >=1.26
18
- Requires-Dist: geopandas ==0.14.1
18
+ Requires-Dist: geopandas ==0.14.3
19
19
  Requires-Dist: langcodes >=3.3
20
20
  Requires-Dist: mapclassify ==2.6.1
21
21
  Requires-Dist: matplotlib-label-lines ==0.5.1
22
- Requires-Dist: matplotlib ==3.8.2
22
+ Requires-Dist: matplotlib ==3.8.3
23
23
  Requires-Dist: numpy <2,>=1.21.0
24
24
  Requires-Dist: python-dateutil <3,>=2
25
25
  Requires-Dist: requests >=2.22
@@ -181,6 +181,11 @@ Roadmap
181
181
  Changelog
182
182
  ---------
183
183
 
184
+ - 1.54.5
185
+
186
+ - Improved tick placement in daily charts
187
+ - Minor upgrades: matplotlib==3.8.3; Pillow==10.2.0; geopandas==0.14.3
188
+
184
189
  - 1.54.4
185
190
 
186
191
  - Use Babel 2.14, and pin version
@@ -213,7 +218,7 @@ Changelog
213
218
  - 1.53.0
214
219
 
215
220
  - Fixed bug in value_labels, trying to access a color value that didn't exist
216
- - Dropped Python 3.7 support (upstream)
221
+ - Dropped Python 3.8 support (upstream)
217
222
  - Uses Matplotlib 3.8
218
223
  - Uses Pillow 10
219
224
 
@@ -1,21 +1,16 @@
1
- newsworthycharts/__init__.py,sha256=lUar7rzqonwWFT9D99y-rjteKifHZ4_3CizWrEFoSk8,1160
1
+ newsworthycharts/__init__.py,sha256=YejHDUN0WmsE1o9IIiZZFyVDgdMbw2LUW36azjvNuzM,1160
2
2
  newsworthycharts/bubblemap.py,sha256=_AMj3RkmKDKVmrtj3FuuY776xRUSEf2qE8tG0vQ3Lyg,2619
3
3
  newsworthycharts/categoricalchart.py,sha256=k2cd96pNysbVU88nZduiLGpzyjMsDXTbASAVu6ov-kI,14686
4
4
  newsworthycharts/chart.py,sha256=FX8aMTAZCUYtDpY4UNvKAly7NevZSFAVraK4JNdxp5Q,30557
5
5
  newsworthycharts/choroplethmap.py,sha256=KXtdiRBe-_j_zUozUgdhAWyoYN-eUdFxt9mneN10H1w,6670
6
- newsworthycharts/datalist.py,sha256=pQTzWg1lKslAstiF04oK7FwvoxTK2-1jOvrlOy7bGpk,3721
7
6
  newsworthycharts/datawrapper.py,sha256=RRkAVTpfP4updKxUIBaSmKuBi2RUVPaBRF8HDQhlGGA,11250
8
- newsworthycharts/formatter.py,sha256=N-Z2lDQKcI7Nv9JluAg6yWK8HwPLq09PhbBrSAabGxQ,2728
9
- newsworthycharts/locator.py,sha256=WIe4_jQKrvdpXEayK8zGGgPVyHHoFeO6KBqDTr7qE14,1944
10
7
  newsworthycharts/map.py,sha256=e0WRcLTmb1KPZ7_-05QXgthMEmKrTpnci40DXnmd6Rc,5984
11
- newsworthycharts/mimetypes.py,sha256=t_wD5FD2o1Di7NWAQGwc8ScPry48S1_7hX03_1Do-G8,129
12
8
  newsworthycharts/rangeplot.py,sha256=NE1W9TnmlpK6T3RvBJOU3nd73EXqkj17OY9i5zlw_cQ,8366
13
9
  newsworthycharts/scatterplot.py,sha256=6iaMoiZx__Gc-2Hcdw-8Ga5dSonrFo3oexKNmSFuir4,4959
14
10
  newsworthycharts/seasonalchart.py,sha256=rr55yqJUkaYDR9Ik98jes6574oY1U8t8LwoLE3gClW4,1967
15
11
  newsworthycharts/serialchart.py,sha256=mEE-9Yahas7o86wTaynJY4hyhBm1If5qPYB8gExMXlo,24563
16
12
  newsworthycharts/storage.py,sha256=myERhlpvXyExXxUByBq9eW1bWkCyfH9SwTZbsWSyy3Q,4301
17
13
  newsworthycharts/stripechart.py,sha256=9B6PX2MyLuKNQ8W0OGdKbP0-U32kju0K_NHHwwz_J68,1547
18
- newsworthycharts/utils.py,sha256=aC3OF-HAsEe_WnNV2Ah_oq0QYcAETUscRZ6nQBpazQg,2842
19
14
  newsworthycharts/custom/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
15
  newsworthycharts/custom/climate_cars.py,sha256=WyNLgjgRCv_zRrzVuk_BmcigFSzpzudjEAYmdvdBe8I,9407
21
16
  newsworthycharts/lib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -24,7 +19,7 @@ newsworthycharts/lib/colors.py,sha256=U04TDkvoMQkcldRFXfnwyLOTwq1SWW2se-Ad-DNcw9
24
19
  newsworthycharts/lib/datalist.py,sha256=fsO_esZBt4Aw_4n_l2LuswAYArbM-8Z8G9DAXo1oVZU,5376
25
20
  newsworthycharts/lib/formatter.py,sha256=GNH43hE0bC17OgiV8LYH3YUrEhm7OJh9XzfSV4HVtHo,4838
26
21
  newsworthycharts/lib/geography.py,sha256=K0_teFmuPJwXX7Py-amJB_1YY5_gL2kBYhz1LrRCyTg,584
27
- newsworthycharts/lib/locator.py,sha256=Jdi6P6Z-3DSoG_lhYQKh97_asWt9JqBV3vBzHnf1k0I,2825
22
+ newsworthycharts/lib/locator.py,sha256=73ZqAvvLBhnWd0rKEHTyPiIL1H3MFHiUlb8aRKEm4FA,2938
28
23
  newsworthycharts/lib/mimetypes.py,sha256=bL9HtVWbn2Of39LcBt4u4yelkr4bGZiyebq3OfLnfFY,237
29
24
  newsworthycharts/lib/utils.py,sha256=2Ko0dNFw2eHd32zQwJFUsgDkJGWSl6VacZs4AFPgrc8,7038
30
25
  newsworthycharts/maps/se-4.gpkg,sha256=oWw5j7FPVpI0ig67jNDim8qSn5SG8rcHp0014-uTKZM,290816
@@ -33,8 +28,8 @@ newsworthycharts/rc/newsworthy,sha256=X0btLNrmk2DRrfOsKj_WCSIgeD6btacEN2tRF_B4m8
33
28
  newsworthycharts/translations/datawrapper_regions.csv,sha256=fzZcQRX6RFMlNNP8mpgfYNdR3Y0QAlQxDXk8FXTaWWI,9214
34
29
  newsworthycharts/translations/regions.py,sha256=Nv1McQjggD4S3JRu82rDMTG3pqUVR13E5-FBpSYbm98,239
35
30
  newsworthycharts/translations/se_municipalities.csv,sha256=br_mm-IvzQtj_W55_ATREhJ97jWnCweBFlDAVY2EBxA,7098
36
- newsworthycharts-1.54.4.dist-info/LICENSE.txt,sha256=Sq6kGICrehbhC_FolNdXf0djKjTpv3YqjFCIYsxdQN4,1069
37
- newsworthycharts-1.54.4.dist-info/METADATA,sha256=iqOoG5714-3F9uiMWbM8pePOJHo089roKIdHkRCD8f0,23648
38
- newsworthycharts-1.54.4.dist-info/WHEEL,sha256=5sUXSg9e4bi7lTLOHcm6QEYwO5TIF1TNbTSVFVjcJcc,92
39
- newsworthycharts-1.54.4.dist-info/top_level.txt,sha256=dn_kzIj8UgUCMsh1PHdVEQJHVGSsN7Z8YJF-8xXa8n0,17
40
- newsworthycharts-1.54.4.dist-info/RECORD,,
31
+ newsworthycharts-1.54.5.dist-info/LICENSE.txt,sha256=Sq6kGICrehbhC_FolNdXf0djKjTpv3YqjFCIYsxdQN4,1069
32
+ newsworthycharts-1.54.5.dist-info/METADATA,sha256=yMQ9TNCdd4z4ovk5O7kw6_HCLd4aNJtfdi2pvaQswto,23776
33
+ newsworthycharts-1.54.5.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
34
+ newsworthycharts-1.54.5.dist-info/top_level.txt,sha256=dn_kzIj8UgUCMsh1PHdVEQJHVGSsN7Z8YJF-8xXa8n0,17
35
+ newsworthycharts-1.54.5.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.41.1)
2
+ Generator: bdist_wheel (0.42.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,128 +0,0 @@
1
- """
2
- Holds a class for storing lists of data (timeseries etc), and related methods.
3
- """
4
- from collections.abc import MutableSequence
5
- from math import inf
6
- from .utils import to_float, to_date
7
- from numpy import array, isnan, interp, flatnonzero
8
-
9
-
10
- def fill_na(arr):
11
- """Get an estimate for missing value based on closest non-missing values in
12
- series.
13
- https://stackoverflow.com/questions/9537543/replace-nans-in-numpy-array-with-closest-non-nan-value
14
-
15
- >>> fill_na([2.0, None, 4.0])
16
- [2.0, 3.0, 4.0]
17
- """
18
- if isinstance(arr, list):
19
- arr = array(arr)
20
- arr = arr.astype(float)
21
- mask = isnan(arr)
22
- arr[mask] = interp(flatnonzero(mask),
23
- flatnonzero(~mask),
24
- arr[~mask])
25
-
26
- return arr.tolist()
27
-
28
-
29
- class DataList(MutableSequence):
30
- """ A list of datasets, that keeps track of some useful additional data
31
- such as min/max values.
32
- Datasets are on the format [(x1, y1), (x2, y2), ...]
33
- """
34
-
35
- def __init__(self, *args):
36
- self.min_val = inf
37
- self.max_val = -inf
38
- self._x_points = set()
39
- self.list = list()
40
- self.extend(list(args))
41
-
42
- def check(self, v):
43
- # Update metadata with newly added data
44
- values = [to_float(x[1]) for x in v]
45
- values = [x for x in values if x is not None]
46
- if len(values):
47
- self.min_val = min(self.min_val, min(values))
48
- self.max_val = max(self.max_val, max(values))
49
- self._x_points.update([x[0] for x in v])
50
-
51
- # Normalize to 3 digit syntax
52
- v = [x if len(x) > 2
53
- else (x[0], x[1], None)
54
- for x in v]
55
- # Automatically enumerate empty x values / category names if empty
56
- v = [x if x[0] not in ["", None]
57
- else (i, x[1], x[2])
58
- for i, x in enumerate(v)]
59
- return v
60
-
61
- @property
62
- def values(self):
63
- """ Return values from each data serie """
64
- return [[to_float(x[1]) for x in s] for s in self.list]
65
-
66
- @property
67
- def as_dict(self):
68
- """ Return data points as dictionaries """
69
- return [{x[0]: x[1] for x in s} for s in self.list]
70
-
71
- @property
72
- def filled_values(self):
73
- """ Return values with all gaps filled, so that each series has the
74
- same number of points.
75
-
76
- >>>> dl = DataList([
77
- [("a", 5), ("b", 6), ("c", 7)],
78
- [("a", 1), ("c", 3)]
79
- ])
80
- >>>> dl.filled_y_values
81
- [[5, 6, 7], [1, 2, 3]]
82
- """
83
-
84
- x_points = self.x_points
85
- return [fill_na([to_float(d[x])
86
- if x in d else None
87
- for x in x_points])
88
- for d in self.as_dict]
89
-
90
- @property
91
- def x_points(self):
92
- return sorted(list(self._x_points))
93
-
94
- @property
95
- def inner_min_x(self):
96
- return max(list(filter(lambda x: x[1] is not None, s))[0][0] for s in self.list)
97
-
98
- @property
99
- def inner_max_x(self):
100
- return min(list(filter(lambda x: x[1] is not None, s))[-1][0] for s in self.list)
101
-
102
- @property
103
- def outer_min_x(self):
104
- return min(list(filter(lambda x: x[1] is not None, s))[0][0] for s in self.list)
105
-
106
- @property
107
- def outer_max_x(self):
108
- return max(list(filter(lambda x: x[1] is not None, s))[-1][0] for s in self.list)
109
-
110
- def __len__(self):
111
- return len(self.list)
112
-
113
- def __getitem__(self, i):
114
- return self.list[i]
115
-
116
- def __delitem__(self, i):
117
- del self.list[i]
118
-
119
- def __setitem__(self, i, v):
120
- v = self.check(v)
121
- self.list[i] = v
122
-
123
- def insert(self, i, v):
124
- v = self.check(v)
125
- self.list.insert(i, v)
126
-
127
- def __str__(self):
128
- return str(self.list)
@@ -1,93 +0,0 @@
1
- """
2
- Module for doing (very) simple i18n work.
3
- """
4
- from babel.numbers import format_decimal, format_percent, Locale
5
- from babel.units import format_unit
6
- from decimal import Decimal
7
-
8
-
9
- class Formatter(object):
10
- """
11
- A formatter for a specific language and locale.
12
- Contains some methods for number and text formatting.
13
-
14
- Heavier i18n work should be before involving newsworthycharts.
15
- Usage:
16
-
17
- >>> fmt = Formatter("sv-SE")
18
- >>> fmt.percent(0.14)
19
- "14 %"
20
- """
21
- def __init__(self, lang, decimals=None, scale="celcius"):
22
- """
23
- :param decimals (int): force formatting to N number of decimals
24
- """
25
- self.l = Locale.parse(lang.replace("-", "_"))
26
- self.language = self.l.language
27
- self.decimals = decimals
28
- self.scale = scale
29
-
30
- def __repr__(self):
31
- return "Formatter: " + repr(self.l)
32
-
33
- def __str__(self):
34
- return self.l.get_display_name()
35
-
36
- def percent(self, x, *args, **kwargs):
37
-
38
- if self.decimals is None:
39
- # Show one decimal by default if values is < 1%
40
- if abs(x) < 0.01:
41
- x = round(x, 1+2)
42
- else:
43
- x = round(x, 2)
44
- else:
45
- x = round(x, self.decimals+2)
46
-
47
- return format_percent(x, locale=self.l, decimal_quantization=False)
48
-
49
- def temperature_short(self, x, *args, **kwargs):
50
- """ Format a temperature in deegrees, without scale letter """
51
-
52
- decimals = self.decimals
53
- if decimals is None:
54
- decimals = 1
55
-
56
- x = round(Decimal(x), decimals)
57
- str = format_unit(x, 'temperature-generic', "short", locale=self.l)
58
- return str
59
-
60
- def temperature(self, x, *args, **kwargs):
61
- """ Format a temperature in deegrees, with scale letter """
62
-
63
- decimals = self.decimals
64
- if decimals is None:
65
- decimals = 1
66
-
67
- scale = "temperature-{}".format(self.scale)
68
- x = round(Decimal(x), decimals)
69
- str = format_unit(x, scale, "short", locale=self.l)
70
- return str
71
-
72
- def number(self, x, *args, **kwargs):
73
- """Format as number.
74
-
75
- :param decimals (int): number of decimals.
76
- """
77
- decimals = self.decimals
78
- if decimals is None:
79
- # Default roundings
80
- if abs(x) < 0.1:
81
- decimals = 2
82
- elif abs(x) < 1:
83
- decimals = 1
84
- else:
85
- decimals = 0
86
- x = round(Decimal(x), decimals)
87
- return format_decimal(x, locale=self.l)
88
-
89
- def short_month(self, x, *args, **kwargs):
90
- return self.l.months['format']['abbreviated'][x]
91
-
92
- def month(self, x, *args, **kwargs):
93
- return self.l.months['format']['wide'][x]
@@ -1,60 +0,0 @@
1
- """ Custom locators and related methods
2
- """
3
- from matplotlib.dates import YearLocator, MonthLocator, DayLocator
4
- from datetime import datetime
5
-
6
-
7
- def get_year_ticks(start_date, end_date, max_ticks=5):
8
- """ Get `max_ticks` or less evenly distributed yearly ticks, including
9
- start and end years. All ticks fall on January 1.
10
- """
11
-
12
- years = range(start_date.year, end_date.year+1)
13
- n_years = len(years)
14
- max_ticks = min(max_ticks, n_years)
15
- # Avoid N2 < ticks < N, where there will be odd looking gaps
16
- if n_years > 3:
17
- if round(n_years/2) < max_ticks < n_years:
18
- max_ticks = round(n_years/2)
19
-
20
- # -2 for the ends
21
- # +1 because cutting a cake in n+1 pieces gives n cuts
22
- if max_ticks > 1:
23
- cuts = n_years/(max_ticks-2+1)
24
- else:
25
- cuts = 0
26
- selected_years = [years[int(x * cuts)] for x in range(0, max_ticks-1)]
27
-
28
- # add last year
29
- if max_ticks > 0:
30
- selected_years.append(years[-1])
31
-
32
- # Ticks should be on the first day of the year
33
- selected_dates = [datetime(y, 1, 1) for y in selected_years]
34
- return selected_dates
35
-
36
-
37
- def get_best_locator(delta, points):
38
- """ Get the optimal locator given a time delta and number of points.
39
- This methods will be much more conservative than Matplotlib's AutoLocator,
40
- trying to keep the x axis as clean as possible, while still including
41
- enough clues for the reader to easily understand the graph.
42
- """
43
- if delta.days > 365*150:
44
- return YearLocator(100)
45
- if delta.days > 365*45:
46
- return YearLocator(20)
47
- elif delta.days > 365:
48
- if points > 20:
49
- return YearLocator(10)
50
- elif points > 10:
51
- return YearLocator(5)
52
- elif points > 5:
53
- return YearLocator(2)
54
- else:
55
- return YearLocator()
56
- elif delta.days > 30:
57
- # FIXME dont print every month
58
- return MonthLocator()
59
- else:
60
- return DayLocator()
@@ -1,8 +0,0 @@
1
- """ Mime type data
2
- """
3
-
4
- MIME_TYPES = {
5
- 'png': "image/png",
6
- 'svg': "image/svg+xml",
7
- 'eps': "application/postscript"
8
- }
newsworthycharts/utils.py DELETED
@@ -1,83 +0,0 @@
1
- """ Various utility methods """
2
- from matplotlib import rc_file, rcParams
3
- from matplotlib.colors import to_rgba
4
- from datetime import datetime
5
- import os
6
- import yaml
7
-
8
- HERE = os.path.dirname(__file__)
9
-
10
-
11
- class StyleNotFoundError(FileNotFoundError):
12
- pass
13
-
14
-
15
- def loadstyle(style_name):
16
- """ Load a custom style file, adding both rcParams and custom params.
17
- Writing a proper parser for these settings is in the Matplotlib backlog,
18
- so let's keep calm and avoid inventing their wheel.
19
- """
20
-
21
- style = {}
22
- style_file = os.path.join(HERE, 'rc', style_name)
23
- try:
24
- # Check rc directory for built in styles first
25
- rc_file(style_file)
26
- except FileNotFoundError as e:
27
- # Check current working dir or path
28
- style_file = style_name
29
- try:
30
- rc_file(style_file)
31
- except FileNotFoundError as e:
32
- raise StyleNotFoundError("No such style file found")
33
- style = rcParams.copy()
34
-
35
- # The style files may also contain an extra section with typography
36
- # for titles and captions (these can only be separately styled in code,
37
- # as of Matplotlib 2.2)
38
- # This is a hack, but it's nice to have all styling in one file
39
- # The extra styling is prefixed with `#!`
40
- with open(style_file, 'r') as f:
41
- doc = f.readlines()
42
- rcParamsNewsworthy = "\n".join([d[2:]
43
- for d in doc if d.startswith("#!")])
44
- rcParamsNewsworthy = yaml.safe_load(rcParamsNewsworthy)
45
- style["title_font"] = [x.strip()
46
- for x in rcParamsNewsworthy["title_font"]
47
- .split(",")]
48
- color = rcParamsNewsworthy.get("neutral_color",
49
- rcParams["figure.edgecolor"])
50
- strong_color = rcParamsNewsworthy.get("strong_color", color)
51
- fill_between_color = rcParamsNewsworthy.get("fill_between_color", "F7F4F4")
52
- fill_between_alpha = rcParamsNewsworthy.get("fill_between_alpha", 0.5)
53
- style["neutral_color"] = to_rgba("#" + str(color), 1)
54
- style["strong_color"] = to_rgba("#" + str(strong_color), 1)
55
- style["fill_between_color"] = to_rgba("#" + str(fill_between_color), 1)
56
- style["fill_between_alpha"] = float(fill_between_alpha)
57
- if "logo" in rcParamsNewsworthy:
58
- style["logo"] = rcParamsNewsworthy["logo"]
59
-
60
- return style
61
-
62
-
63
- def rpad(list_, item, length):
64
- """
65
- Right pad a list to a certain length, using `item`
66
- """
67
- if list_ is None:
68
- list_ = []
69
- return list_ + [item for i in range(max(0, length-len(list_)))]
70
-
71
-
72
- def to_float(s):
73
- """Convert string to float, but also handles None and 'null'."""
74
- if s is None:
75
- return None
76
- if str(s) == "null":
77
- return
78
- return float(s)
79
-
80
-
81
- def to_date(s):
82
- """Convert date string to datetime date."""
83
- return datetime.strptime(s, "%Y-%m-%d")