newsworthycharts 1.71.0__tar.gz → 1.71.3__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 (53) hide show
  1. {newsworthycharts-1.71.0 → newsworthycharts-1.71.3}/PKG-INFO +32 -5
  2. newsworthycharts-1.71.0/newsworthycharts.egg-info/PKG-INFO → newsworthycharts-1.71.3/README.rst +28 -27
  3. {newsworthycharts-1.71.0 → newsworthycharts-1.71.3}/newsworthycharts/__init__.py +1 -1
  4. {newsworthycharts-1.71.0 → newsworthycharts-1.71.3}/newsworthycharts/categoricalchart.py +5 -1
  5. {newsworthycharts-1.71.0 → newsworthycharts-1.71.3}/newsworthycharts/chart.py +37 -50
  6. newsworthycharts-1.71.3/newsworthycharts/rankchart.py +115 -0
  7. {newsworthycharts-1.71.0 → newsworthycharts-1.71.3}/newsworthycharts/scatterplot.py +7 -2
  8. {newsworthycharts-1.71.0 → newsworthycharts-1.71.3}/newsworthycharts/serialchart.py +51 -14
  9. newsworthycharts-1.71.0/README.rst → newsworthycharts-1.71.3/newsworthycharts.egg-info/PKG-INFO +54 -1
  10. {newsworthycharts-1.71.0 → newsworthycharts-1.71.3}/newsworthycharts.egg-info/requires.txt +2 -2
  11. {newsworthycharts-1.71.0 → newsworthycharts-1.71.3}/setup.py +2 -2
  12. newsworthycharts-1.71.0/newsworthycharts/rankchart.py +0 -35
  13. {newsworthycharts-1.71.0 → newsworthycharts-1.71.3}/LICENSE.txt +0 -0
  14. {newsworthycharts-1.71.0 → newsworthycharts-1.71.3}/MANIFEST.in +0 -0
  15. {newsworthycharts-1.71.0 → newsworthycharts-1.71.3}/newsworthycharts/bubblemap.py +0 -0
  16. {newsworthycharts-1.71.0 → newsworthycharts-1.71.3}/newsworthycharts/choroplethmap.py +0 -0
  17. {newsworthycharts-1.71.0 → newsworthycharts-1.71.3}/newsworthycharts/custom/__init__.py +0 -0
  18. {newsworthycharts-1.71.0 → newsworthycharts-1.71.3}/newsworthycharts/custom/climate_cars.py +0 -0
  19. {newsworthycharts-1.71.0 → newsworthycharts-1.71.3}/newsworthycharts/datawrapper.py +0 -0
  20. {newsworthycharts-1.71.0 → newsworthycharts-1.71.3}/newsworthycharts/lib/__init__.py +0 -0
  21. {newsworthycharts-1.71.0 → newsworthycharts-1.71.3}/newsworthycharts/lib/color_fn.py +0 -0
  22. {newsworthycharts-1.71.0 → newsworthycharts-1.71.3}/newsworthycharts/lib/colors.py +0 -0
  23. {newsworthycharts-1.71.0 → newsworthycharts-1.71.3}/newsworthycharts/lib/datalist.py +0 -0
  24. {newsworthycharts-1.71.0 → newsworthycharts-1.71.3}/newsworthycharts/lib/formatter.py +0 -0
  25. {newsworthycharts-1.71.0 → newsworthycharts-1.71.3}/newsworthycharts/lib/geography.py +0 -0
  26. {newsworthycharts-1.71.0 → newsworthycharts-1.71.3}/newsworthycharts/lib/locator.py +0 -0
  27. {newsworthycharts-1.71.0 → newsworthycharts-1.71.3}/newsworthycharts/lib/mimetypes.py +0 -0
  28. {newsworthycharts-1.71.0 → newsworthycharts-1.71.3}/newsworthycharts/lib/utils.py +0 -0
  29. {newsworthycharts-1.71.0 → newsworthycharts-1.71.3}/newsworthycharts/map.py +0 -0
  30. {newsworthycharts-1.71.0 → newsworthycharts-1.71.3}/newsworthycharts/maps/se-4.gpkg +0 -0
  31. {newsworthycharts-1.71.0 → newsworthycharts-1.71.3}/newsworthycharts/maps/se-7.gpkg +0 -0
  32. {newsworthycharts-1.71.0 → newsworthycharts-1.71.3}/newsworthycharts/rangeplot.py +0 -0
  33. {newsworthycharts-1.71.0 → newsworthycharts-1.71.3}/newsworthycharts/rc/newsworthy +0 -0
  34. {newsworthycharts-1.71.0 → newsworthycharts-1.71.3}/newsworthycharts/seasonalchart.py +0 -0
  35. {newsworthycharts-1.71.0 → newsworthycharts-1.71.3}/newsworthycharts/storage.py +0 -0
  36. {newsworthycharts-1.71.0 → newsworthycharts-1.71.3}/newsworthycharts/stripechart.py +0 -0
  37. {newsworthycharts-1.71.0 → newsworthycharts-1.71.3}/newsworthycharts/translations/datawrapper_regions.csv +0 -0
  38. {newsworthycharts-1.71.0 → newsworthycharts-1.71.3}/newsworthycharts/translations/regions.py +0 -0
  39. {newsworthycharts-1.71.0 → newsworthycharts-1.71.3}/newsworthycharts/translations/se_municipalities.csv +0 -0
  40. {newsworthycharts-1.71.0 → newsworthycharts-1.71.3}/newsworthycharts.egg-info/SOURCES.txt +0 -0
  41. {newsworthycharts-1.71.0 → newsworthycharts-1.71.3}/newsworthycharts.egg-info/dependency_links.txt +0 -0
  42. {newsworthycharts-1.71.0 → newsworthycharts-1.71.3}/newsworthycharts.egg-info/not-zip-safe +0 -0
  43. {newsworthycharts-1.71.0 → newsworthycharts-1.71.3}/newsworthycharts.egg-info/top_level.txt +0 -0
  44. {newsworthycharts-1.71.0 → newsworthycharts-1.71.3}/setup.cfg +0 -0
  45. {newsworthycharts-1.71.0 → newsworthycharts-1.71.3}/test/test_categorical_chart.py +0 -0
  46. {newsworthycharts-1.71.0 → newsworthycharts-1.71.3}/test/test_choropleth_maps.py +0 -0
  47. {newsworthycharts-1.71.0 → newsworthycharts-1.71.3}/test/test_custom_climate_cars.py +0 -0
  48. {newsworthycharts-1.71.0 → newsworthycharts-1.71.3}/test/test_data_list.py +0 -0
  49. {newsworthycharts-1.71.0 → newsworthycharts-1.71.3}/test/test_datawrapper.py +0 -0
  50. {newsworthycharts-1.71.0 → newsworthycharts-1.71.3}/test/test_main.py +0 -0
  51. {newsworthycharts-1.71.0 → newsworthycharts-1.71.3}/test/test_rangeplot.py +0 -0
  52. {newsworthycharts-1.71.0 → newsworthycharts-1.71.3}/test/test_scatterplot.py +0 -0
  53. {newsworthycharts-1.71.0 → newsworthycharts-1.71.3}/test/test_serial_chart.py +0 -0
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: newsworthycharts
3
- Version: 1.71.0
3
+ Version: 1.71.3
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.71.0.tar.gz
6
+ Download-URL: https://github.com/jplusplus/newsworthycharts/archive/1.71.3.tar.gz
7
7
  Author: Jens Finnäs and Leo Wallentin, J++ Stockholm
8
8
  Author-email: stockholm@jplusplus.org
9
9
  License: MIT
@@ -15,13 +15,13 @@ Requires-Dist: matplotlib==3.10.0
15
15
  Requires-Dist: langcodes>=3.3
16
16
  Requires-Dist: Babel<3,>=2.14.0
17
17
  Requires-Dist: PyYAML>=3
18
- Requires-Dist: adjustText==0.7.3
18
+ Requires-Dist: adjustText==1.3.0
19
19
  Requires-Dist: numpy>2
20
20
  Requires-Dist: python-dateutil<3,>=2
21
21
  Requires-Dist: Pillow==11.1.0
22
22
  Requires-Dist: requests>=2.22
23
23
  Requires-Dist: matplotlib-label-lines==0.5.1
24
- Requires-Dist: geopandas>=1
24
+ Requires-Dist: geopandas==1.0.1
25
25
  Requires-Dist: mapclassify==2.8.1
26
26
 
27
27
  This module contains methods for producing graphs and publishing them on Amazon S3, or in the location of your choice.
@@ -150,6 +150,7 @@ These settings are available for all chart types:
150
150
  - logo = None # Path to image that will be embedded in the caption area. Can also be set though a style property
151
151
  - color_fn = None # Custom coloring function
152
152
  - legend_title = None # Title for the legend
153
+ - revert_value_axis = False # Revert the value axis to put 0 top
153
154
 
154
155
  **SerialChart**
155
156
 
@@ -178,7 +179,9 @@ _ Inherits from SerialChart _
178
179
 
179
180
  _ Inherits from SerialChart _
180
181
 
181
- - highlight: The label value to of a line to highlight
182
+ - highlight = None # The label value to of a line to highlight
183
+ - line_marker = "o-" # Matplotlib line marker type.
184
+ - line_marker_size = 5 # Matplotlib line marker size
182
185
 
183
186
  **BubbleMap**
184
187
 
@@ -267,6 +270,30 @@ Roadmap
267
270
  Changelog
268
271
  ---------
269
272
 
273
+ - 1.71.3
274
+
275
+ - Revert 1.71.2 changes to rendering, to make file sizes predictable again
276
+
277
+ - 1.71.2
278
+
279
+ - Annotates all isolated value sequences in BumpChart
280
+ - Allows shared ranks in BumpChart
281
+ - Prefers showing all ticks in BumpChart
282
+ - Fixes some padding issues with reverted value axis
283
+ - Adds `revert_value_axis` option for all charts
284
+ - New custom label collision algorithm for serial charts
285
+ - Removes unused(?) label collision algorithm for categorical charts
286
+ - Upgrades adjustText (now used in ScatterPlot only) to 1.3.0
287
+ - Adds `_after_add_data()` hook for subclasses and extensions
288
+ - Pins Geopandas version (currently 1.0.1)
289
+ - Smaller vertical annotation offset (partially reverting 1.71.1)
290
+
291
+ - 1.71.1
292
+
293
+ - Allow setting line marker size and style in `BumpChart`
294
+ - Various fixes in `BumpChart`
295
+ - Take font size into account when positioning annotations
296
+
270
297
  - 1.71.0
271
298
 
272
299
  - Add `BumpChart`
@@ -1,29 +1,3 @@
1
- Metadata-Version: 2.1
2
- Name: newsworthycharts
3
- Version: 1.71.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.71.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.10.0
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==11.1.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.8.1
26
-
27
1
  This module contains methods for producing graphs and publishing them on Amazon S3, or in the location of your choice.
28
2
 
29
3
  It is written and maintained for `Newsworthy <https://www.newsworthy.se/en/>`_, but could possibly come in handy for other people as well.
@@ -150,6 +124,7 @@ These settings are available for all chart types:
150
124
  - logo = None # Path to image that will be embedded in the caption area. Can also be set though a style property
151
125
  - color_fn = None # Custom coloring function
152
126
  - legend_title = None # Title for the legend
127
+ - revert_value_axis = False # Revert the value axis to put 0 top
153
128
 
154
129
  **SerialChart**
155
130
 
@@ -178,7 +153,9 @@ _ Inherits from SerialChart _
178
153
 
179
154
  _ Inherits from SerialChart _
180
155
 
181
- - highlight: The label value to of a line to highlight
156
+ - highlight = None # The label value to of a line to highlight
157
+ - line_marker = "o-" # Matplotlib line marker type.
158
+ - line_marker_size = 5 # Matplotlib line marker size
182
159
 
183
160
  **BubbleMap**
184
161
 
@@ -267,6 +244,30 @@ Roadmap
267
244
  Changelog
268
245
  ---------
269
246
 
247
+ - 1.71.3
248
+
249
+ - Revert 1.71.2 changes to rendering, to make file sizes predictable again
250
+
251
+ - 1.71.2
252
+
253
+ - Annotates all isolated value sequences in BumpChart
254
+ - Allows shared ranks in BumpChart
255
+ - Prefers showing all ticks in BumpChart
256
+ - Fixes some padding issues with reverted value axis
257
+ - Adds `revert_value_axis` option for all charts
258
+ - New custom label collision algorithm for serial charts
259
+ - Removes unused(?) label collision algorithm for categorical charts
260
+ - Upgrades adjustText (now used in ScatterPlot only) to 1.3.0
261
+ - Adds `_after_add_data()` hook for subclasses and extensions
262
+ - Pins Geopandas version (currently 1.0.1)
263
+ - Smaller vertical annotation offset (partially reverting 1.71.1)
264
+
265
+ - 1.71.1
266
+
267
+ - Allow setting line marker size and style in `BumpChart`
268
+ - Various fixes in `BumpChart`
269
+ - Take font size into account when positioning annotations
270
+
270
271
  - 1.71.0
271
272
 
272
273
  - Add `BumpChart`
@@ -1,4 +1,4 @@
1
- __version__ = "1.71.0"
1
+ __version__ = "1.71.3"
2
2
 
3
3
  from .chart import Chart
4
4
  from .choroplethmap import ChoroplethMap
@@ -1,6 +1,6 @@
1
1
  from .chart import Chart
2
2
  from .lib.utils import to_float
3
- from adjustText import adjust_text
3
+ # from adjustText import adjust_text
4
4
  import numpy as np
5
5
 
6
6
 
@@ -251,11 +251,15 @@ class CategoricalChart(Chart):
251
251
  )
252
252
  if idx > 0:
253
253
  texts += [*_texts]
254
+ """
254
255
  adjust_text(
255
256
  texts,
256
257
  ax=self.ax,
257
258
  autoalign="x" if self.bar_orientation == "horizontal" else "y",
259
+ only_move="x" if self.bar_orientation == "horizontal" else "y",
260
+ # will replace autoalign in newer versions
258
261
  )
262
+ """
259
263
 
260
264
 
261
265
  class CategoricalChartWithReference(CategoricalChart):
@@ -207,9 +207,14 @@ class Chart(object):
207
207
  c = color_name
208
208
  return c
209
209
 
210
- def _annotate_point(self, text, xy,
211
- direction, offset=12,
212
- **kwargs):
210
+ def _annotate_point(
211
+ self,
212
+ text,
213
+ xy,
214
+ direction,
215
+ offset=12,
216
+ **kwargs
217
+ ):
213
218
  """Add a label to a given point.
214
219
 
215
220
  :param text: text content of label
@@ -237,11 +242,11 @@ class Chart(object):
237
242
  elif direction == "left":
238
243
  opts["verticalalignment"] = "center"
239
244
  opts["horizontalalignment"] = "right"
240
- opts["xytext"] = (-offset, 0)
245
+ opts["xytext"] = (-offset, -self._nwc_style["annotation.fontsize"] / 4)
241
246
  elif direction == "right":
242
247
  opts["verticalalignment"] = "center"
243
248
  opts["horizontalalignment"] = "left"
244
- opts["xytext"] = (offset, 0)
249
+ opts["xytext"] = (offset, -self._nwc_style["annotation.fontsize"] / 4)
245
250
  else:
246
251
  msg = f"'{direction}' is an unknown direction for an annotation"
247
252
  raise Exception(msg)
@@ -250,7 +255,7 @@ class Chart(object):
250
255
  opts.update(kwargs)
251
256
 
252
257
  ann = self.ax.annotate(text, xy=xy, **opts)
253
- # ann = self.ax.text(text, xy[0], xy[1])
258
+ # ann = self.ax.text(text, xy[0], xy[1], **opts)
254
259
  self._annotations.append(ann)
255
260
 
256
261
  return ann
@@ -338,6 +343,13 @@ class Chart(object):
338
343
  pass
339
344
  # raise NotImplementedError("This method should be overridden")
340
345
 
346
+ def _after_add_data(self):
347
+ """ Extra operations after data has been added.
348
+ Typically used by subclasses and extensions
349
+ """
350
+ pass
351
+ # raise NotImplementedError("This method should be overridden")
352
+
341
353
  def _mark_broken_axis(self, axis="y"):
342
354
  """Adds a symbol to mark that an axis is broken
343
355
  """
@@ -472,6 +484,8 @@ class Chart(object):
472
484
  # Apply all changes, in the correct order for consistent rendering
473
485
  if len(self.data):
474
486
  self._add_data()
487
+ # Extra hook for extensions and subclasses
488
+ self._after_add_data()
475
489
 
476
490
  # Calculate size in inches
477
491
  # Until 1.45 we did this on init, but now we'd like to enable dynamic heights
@@ -668,7 +682,6 @@ class Chart(object):
668
682
  + self._footer_rel_height
669
683
  )
670
684
  # print(sub_canvas_height, self._note_rel_height, self._footer_rel_height)
671
- self._fig.subplots_adjust(bottom=sub_canvas_height)
672
685
 
673
686
  if self.revert_value_axis:
674
687
  if hasattr(self, "orientation") and self.orientation == "horizontal":
@@ -679,9 +692,10 @@ class Chart(object):
679
692
  self.ax.invert_yaxis()
680
693
  value_axis = self.ax.get_yaxis()
681
694
  value_ticks = self.ax.get_yticks()
682
- value_ticks = [x for x in value_ticks if x <= self.data.max_val]
695
+ value_ticks = [x for x in value_ticks if x <= self.data.max_val and x >= self.ymin]
683
696
  value_axis.set_ticks(value_ticks)
684
697
  # self.ax.set_ylim(self.data.max_val, self.ymin)
698
+ self._fig.subplots_adjust(bottom=sub_canvas_height)
685
699
 
686
700
  @classmethod
687
701
  def init_from(cls, args: dict, storage=LocalStorage(),
@@ -719,7 +733,7 @@ class Chart(object):
719
733
  chart.ticks = args["ticks"]
720
734
  return chart
721
735
 
722
- def render(
736
+ def _render(
723
737
  self,
724
738
  key: str,
725
739
  img_format: str,
@@ -728,9 +742,6 @@ class Chart(object):
728
742
  storage_options: dict={}
729
743
  ):
730
744
  """Render file, and send to storage."""
731
- # Apply all changes, in the correct order for consistent rendering
732
- self._apply_changes_before_rendering(factor=factor, transparent=transparent)
733
-
734
745
  # Save plot in memory, to write it directly to storage
735
746
  buf = BytesIO()
736
747
  args = {
@@ -770,6 +781,19 @@ class Chart(object):
770
781
  buf.seek(0)
771
782
  self._storage.save(key, buf, img_format, storage_options)
772
783
 
784
+ def render(
785
+ self,
786
+ key: str,
787
+ img_format: str,
788
+ transparent: bool=False,
789
+ factor: int=1,
790
+ storage_options: dict={}
791
+ ):
792
+ """Render file, and send to storage."""
793
+ # Apply all changes, in the correct order for consistent rendering
794
+ self._apply_changes_before_rendering(factor=factor, transparent=transparent)
795
+ self._render(key, img_format, transparent, factor, storage_options)
796
+
773
797
  def render_all(self, key: str, transparent=False, factor=1, storage_options={}):
774
798
  """
775
799
  Render all available formats
@@ -780,44 +804,7 @@ class Chart(object):
780
804
  for file_format in self.file_types:
781
805
  if file_format == "dw":
782
806
  continue
783
-
784
- # Save plot in memory, to write it directly to storage
785
- buf = BytesIO()
786
- args = {
787
- 'format': file_format,
788
- 'transparent': transparent,
789
- 'dpi': self._fig.dpi * factor,
790
- }
791
- if file_format == "pdf":
792
- args["metadata"] = {
793
- 'Creator': "Newsworthy",
794
- 'Producer': f"NWCharts {__version__}",
795
- }
796
- elif file_format == "png":
797
- args["metadata"] = {
798
- 'Author': "Newsworthy",
799
- 'Software': f"NWCharts {__version__}",
800
- }
801
- elif file_format == "svg":
802
- args["metadata"] = {
803
- 'Publisher': "Newsworthy",
804
- 'Creator': f"NWCharts {__version__}",
805
- }
806
- elif file_format in ["jpg", "jpeg"]:
807
- args["pil_kwargs"] = {
808
- "quality": 100,
809
- "optimize": True,
810
- }
811
- """
812
- Not yet implemented in Pillow
813
- args["metadata"] = {
814
- 'Publisher': "Newsworthy",
815
- 'Creator': f"NWCharts {__version__}",
816
- }
817
- """
818
- self._fig.savefig(buf, **args) # bbox_inches="tight")
819
- buf.seek(0)
820
- self._storage.save(key, buf, file_format, storage_options)
807
+ self._render(key, file_format, transparent, factor, storage_options)
821
808
 
822
809
  @property
823
810
  def title(self):
@@ -0,0 +1,115 @@
1
+ """
2
+ A chart showing ranking over time (like ”most popular baby names”)
3
+ """
4
+ from .serialchart import SerialChart
5
+ from .lib.utils import to_date
6
+ import numpy as np
7
+
8
+
9
+ class BumpChart(SerialChart):
10
+ """Plot a rank chart
11
+
12
+ Data should be a list of iterables of (rank, date string) tuples, eg:
13
+ `[ [("2010-01-01", 2), ("2011-01-01", 3)] ]`, combined with a list of
14
+ labels in the same order
15
+ """
16
+
17
+ def __init__(self, *args, **kwargs):
18
+ super(BumpChart, self).__init__(*args, **kwargs)
19
+
20
+ if self.line_width is None:
21
+ self.line_width = 0.9
22
+ self.label_placement = 'none'
23
+ self.type = "line"
24
+ self.decimals = 0
25
+ self.revert_value_axis = True
26
+ self.ymin = 1
27
+ self.allow_broken_y_axis = False
28
+ self.grid = False
29
+ self.accentuate_baseline = False
30
+
31
+ self.line_marker = "o-"
32
+ self.line_marker_size = 5
33
+
34
+ def _get_line_colors(self, i, *args):
35
+ if not self.data:
36
+ # Don't waste time
37
+ return None
38
+ if self.highlight and self.highlight in self.labels and i == self.labels.index(self.highlight):
39
+ return self._nwc_style["strong_color"]
40
+ elif self.colors and i < len(self.colors):
41
+ return self.colors[i]
42
+ return self._nwc_style["neutral_color"]
43
+
44
+ def _after_add_data(self):
45
+ # Print out every rank
46
+ if self.data.max_val < 30:
47
+ _range = list(range(1, int(self.data.max_val) + 1))
48
+ self.ax.yaxis.set_ticks(_range, _range)
49
+ # Add labels
50
+ slots_occupied = {
51
+ to_date(k): [] for k in self.data.x_points
52
+ }
53
+ for i, serie in enumerate(self.data):
54
+ values = np.array(self.serie_values[i], dtype=np.float64)
55
+ dates = [to_date(x[0]) for x in serie]
56
+ color = self._get_line_colors(i)
57
+
58
+ endpoints = [
59
+ (d, values[idx])
60
+ for (idx, d) in enumerate(dates) if idx == len(dates) - 1 or np.isnan(values[idx + 1])
61
+ ]
62
+ for ep in endpoints:
63
+ position = ep[1]
64
+ while position in slots_occupied[ep[0]]:
65
+ position += 1
66
+ slots_occupied[ep[0]].append(position)
67
+ self._annotate_point(
68
+ self.labels[i],
69
+ (ep[0], position),
70
+ "right",
71
+ offset=15,
72
+ color=color,
73
+ va="center",
74
+ # arrowprops=dict(arrowstyle="->", color=color),
75
+ )
76
+ """
77
+ labels = []
78
+ for i, serie in enumerate(self.data):
79
+ values = np.array(self.serie_values[i], dtype=np.float64)
80
+ dates = [to_date(x[0]) for x in serie]
81
+ color = self._get_line_colors(i)
82
+
83
+ endpoints = [
84
+ (d, values[idx])
85
+ for (idx, d) in enumerate(dates) if idx == len(dates) - 1 or np.isnan(values[idx + 1])
86
+ ]
87
+ for ep in endpoints:
88
+ lbl = self._annotate_point(
89
+ self.labels[i],
90
+ (ep[0], ep[1]),
91
+ "right",
92
+ offset=15,
93
+ color=color,
94
+ va="center",
95
+ # arrowprops=dict(arrowstyle="->", color=color),
96
+ )
97
+ loops = 0
98
+ overlap = True if len(labels) > 0 else False
99
+ while overlap:
100
+ for i, bb in enumerate(labels):
101
+ if i == len(labels) - 1:
102
+ overlap = False
103
+ break
104
+ bbox1 = lbl.get_window_extent()
105
+ bbox2 = labels[i].get_window_extent()
106
+ print(bbox1, bbox2)
107
+ if bbox1.y1 < bbox2.y0 + 10 and bbox1.x1 > bbox2.x0 + 5: # allow for some overlap
108
+ xy1 = lbl.xyann
109
+ lbl.xyann = (xy1[0], xy1[1] + 1)
110
+ break
111
+ loops += 1
112
+ if loops > 500:
113
+ break
114
+ labels.append(lbl)
115
+ """
@@ -114,8 +114,13 @@ class ScatterPlot(Chart):
114
114
 
115
115
  # These settings could be fine-tuned
116
116
  # Weren't able to add lines between points and labels for example
117
- adjust_text(self._annotations, ax=self.ax, autoalign="y",
118
- expand_points=(1, 1),)
117
+ adjust_text(
118
+ self._annotations,
119
+ ax=self.ax,
120
+ autoalign="y",
121
+ only_move="y", # replacing autoalign i newer versions
122
+ expand_points=(1, 1),
123
+ )
119
124
 
120
125
  if self.ymin is not None:
121
126
  self.ax.set_ylim(self.ymin)
@@ -4,7 +4,7 @@ from .lib.utils import to_float, to_date, guess_date_interval
4
4
  import numpy as np
5
5
  from math import inf
6
6
  from dateutil.relativedelta import relativedelta
7
- from adjustText import adjust_text
7
+ # from adjustText import adjust_text
8
8
  from labellines import labelLines
9
9
 
10
10
 
@@ -28,6 +28,7 @@ class SerialChart(Chart):
28
28
  # draw bars and cut ay axis from this line
29
29
  self.baseline = kwargs.get("baseline", 0)
30
30
  self.baseline_annotation = kwargs.get("baseline_annotation", None)
31
+ self.accentuate_baseline = True
31
32
 
32
33
  self.color_labels = kwargs.get("color_labels", None)
33
34
 
@@ -37,6 +38,7 @@ class SerialChart(Chart):
37
38
  self.grid = True
38
39
  self.point_marker = "."
39
40
  self.line_marker = "-"
41
+ self.line_marker_size = 3
40
42
 
41
43
  self.max_ticks = 10
42
44
 
@@ -177,6 +179,7 @@ class SerialChart(Chart):
177
179
  # Replace None values with 0's to be able to plot bars
178
180
  _values = [0 if v is None else v for v in _values]
179
181
  serie_values.append(_values)
182
+ self.serie_values = serie_values
180
183
 
181
184
  # Select a date to highlight
182
185
  highlight_date = None
@@ -256,7 +259,7 @@ class SerialChart(Chart):
256
259
  dates,
257
260
  values,
258
261
  self.line_marker,
259
- markersize=4,
262
+ markersize=self.line_marker_size,
260
263
  color=color,
261
264
  zorder=zo,
262
265
  lw=lw,
@@ -303,7 +306,8 @@ class SerialChart(Chart):
303
306
  "right",
304
307
  offset=15,
305
308
  color=color,
306
- va="center"
309
+ va="center",
310
+ # arrowprops=dict(arrowstyle="->", color=color),
307
311
  )
308
312
  # store labels to check for overlap later
309
313
  line_label_elems.append(lbl)
@@ -503,13 +507,14 @@ class SerialChart(Chart):
503
507
 
504
508
  # Accentuate y=0 || y=baseline
505
509
  # if (self.data.min_val < self.baseline) or self.baseline_annotation:
506
- self.ax.axhline(
507
- y=self.baseline,
508
- linewidth=1,
509
- color="#444444",
510
- zorder=11,
511
- linestyle="--" if self.baseline else "-"
512
- )
510
+ if self.accentuate_baseline:
511
+ self.ax.axhline(
512
+ y=self.baseline,
513
+ linewidth=1,
514
+ color="#444444",
515
+ zorder=11,
516
+ linestyle="--" if self.baseline else "-"
517
+ )
513
518
  if self.baseline_annotation:
514
519
  xy = (to_date(self.data.outer_min_x), self.baseline)
515
520
  # We only allow baseline to be set for single series bar charts
@@ -572,8 +577,11 @@ class SerialChart(Chart):
572
577
 
573
578
  padding_top = ymax * 0.15
574
579
 
575
- self.ax.set_ylim(ymin=ymin - padding_bottom,
576
- ymax=ymax + padding_top)
580
+ if not self.revert_value_axis:
581
+ self.ax.set_ylim(
582
+ ymin=ymin - padding_bottom,
583
+ ymax=ymax + padding_top,
584
+ )
577
585
 
578
586
  self.ax.yaxis.set_major_formatter(y_formatter)
579
587
  self.ax.yaxis.grid(self.grid)
@@ -673,12 +681,13 @@ class SerialChart(Chart):
673
681
  )
674
682
 
675
683
  def _adust_texts_vertically(self, elements, ha="left"):
684
+ """
685
+ from adjustText import get_bboxes
676
686
  if len(elements) == 2:
677
687
  # Hack: check for overlap and adjust labels only
678
688
  # if such overlap exist.
679
689
  # `adjust_text` tended to offset labels unnecessarily
680
690
  # but it might just be that I haven't worked out how to use it properly
681
- from adjustText import get_bboxes
682
691
  bb1, bb2 = get_bboxes(elements, self._fig.canvas.get_renderer(), (1.0, 1.0), self.ax)
683
692
  if (
684
693
  # first label is above
@@ -689,4 +698,32 @@ class SerialChart(Chart):
689
698
  adjust_text(elements, autoalign="y", ha=ha)
690
699
 
691
700
  else:
692
- adjust_text(elements, autoalign="y", ha=ha)
701
+ adjust_text(
702
+ elements,
703
+ autoalign="y",
704
+ only_move="y", # will replace autoalign in newer versions
705
+ ax=self.ax,
706
+ max_move=(0, 10), # (10, 10) is default
707
+ )
708
+ """
709
+ overlap = True
710
+ loops = 0
711
+ while overlap:
712
+ for i, bb in enumerate(elements):
713
+ if i == len(elements) - 1:
714
+ overlap = False
715
+ break
716
+ bbox1 = elements[i].get_window_extent()
717
+ bbox2 = elements[i + 1].get_window_extent()
718
+ if bbox1.y1 > bbox2.y0 + 10 and bbox1.x1 > bbox2.x0 + 5: # allow for some overlap
719
+ loops += 1
720
+ xy1 = elements[i].xyann
721
+ # xy2 = elements[i + 1].xyann
722
+ # elements[i].update_positions((bbox1.x0, bbox1.y0 - 5))
723
+ # elements[i].update_positions((xy1[0], xy1[1] - 0.02))
724
+ elements[i].xyann = (xy1[0], xy1[1] - 0.01)
725
+ # elements[i].set(arrowprops=dict(arrowstyle="->"))
726
+ # elements[i + 1].xyann = (xy2[0], xy2[1] + 0.005)
727
+ break
728
+ if loops > 5_000:
729
+ break
@@ -1,3 +1,29 @@
1
+ Metadata-Version: 2.1
2
+ Name: newsworthycharts
3
+ Version: 1.71.3
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.71.3.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.10.0
15
+ Requires-Dist: langcodes>=3.3
16
+ Requires-Dist: Babel<3,>=2.14.0
17
+ Requires-Dist: PyYAML>=3
18
+ Requires-Dist: adjustText==1.3.0
19
+ Requires-Dist: numpy>2
20
+ Requires-Dist: python-dateutil<3,>=2
21
+ Requires-Dist: Pillow==11.1.0
22
+ Requires-Dist: requests>=2.22
23
+ Requires-Dist: matplotlib-label-lines==0.5.1
24
+ Requires-Dist: geopandas==1.0.1
25
+ Requires-Dist: mapclassify==2.8.1
26
+
1
27
  This module contains methods for producing graphs and publishing them on Amazon S3, or in the location of your choice.
2
28
 
3
29
  It is written and maintained for `Newsworthy <https://www.newsworthy.se/en/>`_, but could possibly come in handy for other people as well.
@@ -124,6 +150,7 @@ These settings are available for all chart types:
124
150
  - logo = None # Path to image that will be embedded in the caption area. Can also be set though a style property
125
151
  - color_fn = None # Custom coloring function
126
152
  - legend_title = None # Title for the legend
153
+ - revert_value_axis = False # Revert the value axis to put 0 top
127
154
 
128
155
  **SerialChart**
129
156
 
@@ -152,7 +179,9 @@ _ Inherits from SerialChart _
152
179
 
153
180
  _ Inherits from SerialChart _
154
181
 
155
- - highlight: The label value to of a line to highlight
182
+ - highlight = None # The label value to of a line to highlight
183
+ - line_marker = "o-" # Matplotlib line marker type.
184
+ - line_marker_size = 5 # Matplotlib line marker size
156
185
 
157
186
  **BubbleMap**
158
187
 
@@ -241,6 +270,30 @@ Roadmap
241
270
  Changelog
242
271
  ---------
243
272
 
273
+ - 1.71.3
274
+
275
+ - Revert 1.71.2 changes to rendering, to make file sizes predictable again
276
+
277
+ - 1.71.2
278
+
279
+ - Annotates all isolated value sequences in BumpChart
280
+ - Allows shared ranks in BumpChart
281
+ - Prefers showing all ticks in BumpChart
282
+ - Fixes some padding issues with reverted value axis
283
+ - Adds `revert_value_axis` option for all charts
284
+ - New custom label collision algorithm for serial charts
285
+ - Removes unused(?) label collision algorithm for categorical charts
286
+ - Upgrades adjustText (now used in ScatterPlot only) to 1.3.0
287
+ - Adds `_after_add_data()` hook for subclasses and extensions
288
+ - Pins Geopandas version (currently 1.0.1)
289
+ - Smaller vertical annotation offset (partially reverting 1.71.1)
290
+
291
+ - 1.71.1
292
+
293
+ - Allow setting line marker size and style in `BumpChart`
294
+ - Various fixes in `BumpChart`
295
+ - Take font size into account when positioning annotations
296
+
244
297
  - 1.71.0
245
298
 
246
299
  - Add `BumpChart`
@@ -3,11 +3,11 @@ matplotlib==3.10.0
3
3
  langcodes>=3.3
4
4
  Babel<3,>=2.14.0
5
5
  PyYAML>=3
6
- adjustText==0.7.3
6
+ adjustText==1.3.0
7
7
  numpy>2
8
8
  python-dateutil<3,>=2
9
9
  Pillow==11.1.0
10
10
  requests>=2.22
11
11
  matplotlib-label-lines==0.5.1
12
- geopandas>=1
12
+ geopandas==1.0.1
13
13
  mapclassify==2.8.1
@@ -29,13 +29,13 @@ setup(
29
29
  "langcodes>=3.3",
30
30
  "Babel>=2.14.0,<3",
31
31
  "PyYAML>=3",
32
- "adjustText==0.7.3",
32
+ "adjustText==1.3.0",
33
33
  "numpy>2",
34
34
  "python-dateutil>=2,<3",
35
35
  "Pillow==11.1.0",
36
36
  "requests>=2.22",
37
37
  "matplotlib-label-lines==0.5.1",
38
- "geopandas>=1",
38
+ "geopandas==1.0.1",
39
39
  "mapclassify==2.8.1",
40
40
  ],
41
41
  setup_requires=["flake8"],
@@ -1,35 +0,0 @@
1
- """
2
- A chart showing ranking over time (like ”most popular baby names”)
3
- """
4
- from .serialchart import SerialChart
5
-
6
-
7
- class BumpChart(SerialChart):
8
- """Plot a rank chart
9
-
10
- Data should be a list of iterables of (rank, date string) tuples, eg:
11
- `[ [("2010-01-01", 2), ("2011-01-01", 3)] ]`, combined with a list of
12
- labels in the same order
13
- """
14
-
15
- def __init__(self, *args, **kwargs):
16
- super(BumpChart, self).__init__(*args, **kwargs)
17
- self.line_width = 0.5
18
- self.label_placement = 'line'
19
- self.type = "line"
20
- self.decimals = 0
21
- self.revert_value_axis = True
22
- self.ymin = 1
23
- self.allow_broken_y_axis = False
24
- self.grid = False
25
- self.line_marker = "o-"
26
-
27
- def _get_line_colors(self, i, *args):
28
- if not self.data:
29
- # Don't waste time
30
- return None
31
- if self.highlight and self.highlight in self.labels and i == self.labels.index(self.highlight):
32
- return self._nwc_style["strong_color"]
33
- elif self.colors and i < len(self.colors):
34
- return self.colors[i]
35
- return self._nwc_style["neutral_color"]