lets-plot 4.6.2__cp311-cp311-macosx_11_0_arm64.whl → 4.7.0__cp311-cp311-macosx_11_0_arm64.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.

Potentially problematic release.


This version of lets-plot might be problematic. Click here for more details.

@@ -18,6 +18,7 @@ ENV_OFFLINE = 'LETS_PLOT_OFFLINE' # bool
18
18
  ENV_NO_JS = 'LETS_PLOT_NO_JS' # bool
19
19
  ENV_MAX_WIDTH = 'LETS_PLOT_MAX_WIDTH'
20
20
  ENV_MAX_HEIGHT = 'LETS_PLOT_MAX_HEIGHT'
21
+ ENV_MAGICK_EXPORT = 'LETS_PLOT_MAGICK_EXPORT' # bool
21
22
  ENV_MAPTILES_KIND = 'LETS_PLOT_MAPTILES_KIND'
22
23
  ENV_MAPTILES_URL = 'LETS_PLOT_MAPTILES_URL'
23
24
  ENV_MAPTILES_THEME = 'LETS_PLOT_MAPTILES_THEME'
@@ -29,6 +30,7 @@ ENV_DEV_OFFLINE = 'LETS_PLOT_DEV_OFFLINE' # bool
29
30
  ENV_DEV_NO_JS = 'LETS_PLOT_DEV_NO_JS' # bool
30
31
  ENV_DEV_MAX_WIDTH = 'LETS_PLOT_DEV_MAX_WIDTH'
31
32
  ENV_DEV_MAX_HEIGHT = 'LETS_PLOT_DEV_MAX_HEIGHT'
33
+ ENV_DEV_MAGICK_EXPORT = 'LETS_PLOT_DEV_MAGICK_EXPORT' # bool
32
34
  ENV_DEV_MAPTILES_KIND = 'LETS_PLOT_DEV_MAPTILES_KIND'
33
35
  ENV_DEV_MAPTILES_URL = 'LETS_PLOT_DEV_MAPTILES_URL'
34
36
  ENV_DEV_MAPTILES_THEME = 'LETS_PLOT_DEV_MAPTILES_THEME'
@@ -45,6 +47,7 @@ JS_NAME = 'js_name'
45
47
  JS_URL_MANUAL = 'js_url_manual'
46
48
  MAX_WIDTH = 'max_width'
47
49
  MAX_HEIGHT = 'max_height'
50
+ MAGICK_EXPORT = 'magick_export'
48
51
 
49
52
  MAPTILES_KIND = 'maptiles_kind'
50
53
  MAPTILES_URL = 'maptiles_url'
@@ -82,6 +85,7 @@ def _get_env_val(actual_name: str) -> Any:
82
85
  _settings = {
83
86
  OFFLINE: _init_value(OFFLINE, False), # default: download from CDN
84
87
  NO_JS: _init_value(NO_JS, False),
88
+ MAGICK_EXPORT: _init_value(MAGICK_EXPORT, True), # default: use cairo for export
85
89
  # JS_BASE_URL: 'https://dl.bintray.com/jetbrains/lets-plot',
86
90
  # JS_BASE_URL: "https://cdnjs.cloudflare.com/ajax/libs/lets-plot",
87
91
  JS_BASE_URL: "https://cdn.jsdelivr.net/gh/JetBrains/lets-plot@v{version}".format(version=__version__),
@@ -97,6 +101,7 @@ _settings = {
97
101
 
98
102
  'dev_' + OFFLINE: _init_value('dev_' + OFFLINE, True), # default: embed js into the notebook
99
103
  'dev_' + NO_JS: _init_value('dev_' + NO_JS, False),
104
+ 'dev_' + MAGICK_EXPORT: _init_value('dev_' + MAGICK_EXPORT, True), # default: use cairo for export
100
105
  # We don't publish "dev" version, it must be served on localhost:
101
106
  # $ cd lets-plot
102
107
  # ./gradlew js-package:jsBrowserDevelopmentWebpack
lets_plot/_kbridge.py CHANGED
@@ -19,6 +19,13 @@ def _generate_svg(plot_spec: Dict, use_css_pixelated_image_rendering: bool = Tru
19
19
  plot_spec = _standardize_plot_spec(plot_spec)
20
20
  return lets_plot_kotlin_bridge.export_svg(plot_spec, use_css_pixelated_image_rendering)
21
21
 
22
+ def _export_png(bytestring: Dict, output_width: float, output_height: float, unit: str, dpi: int, scale: float) -> str:
23
+ """
24
+ Export a plot to PNG format. Returns base64 encoded string of the PNG image.
25
+ """
26
+ plot_spec = _standardize_plot_spec(bytestring)
27
+ return lets_plot_kotlin_bridge.export_png(plot_spec, output_width, output_height, unit, dpi, scale)
28
+
22
29
 
23
30
  def _generate_static_html_page(plot_spec: Dict, iframe: bool) -> str:
24
31
  plot_spec = _standardize_plot_spec(plot_spec)
lets_plot/_type_utils.py CHANGED
@@ -4,7 +4,7 @@
4
4
  #
5
5
  import json
6
6
  import math
7
- from datetime import datetime
7
+ from datetime import datetime, date, time, timezone
8
8
 
9
9
  from typing import Dict
10
10
 
@@ -83,7 +83,7 @@ def _standardize_value(v):
83
83
  if math.isfinite(v):
84
84
  return float(v)
85
85
  # None for special values like 'nan' etc. because
86
- # some json parsers (like com.google.gson.Gson) do not handle them well.
86
+ # some JSON parsers (like com.google.gson.Gson) do not handle them well.
87
87
  return None
88
88
  if is_int(v):
89
89
  return float(v)
@@ -95,13 +95,36 @@ def _standardize_value(v):
95
95
  return [_standardize_value(elem) for elem in v]
96
96
  if isinstance(v, tuple):
97
97
  return tuple(_standardize_value(elem) for elem in v)
98
- if (numpy and isinstance(v, numpy.ndarray)) or (pandas and isinstance(v, pandas.Series)) or (jnp and isinstance(v, jnp.ndarray)):
98
+
99
+ if (numpy and isinstance(v, numpy.ndarray)):
100
+ # Process each array element individually.
101
+ # Don't use '.tolist()' because this will implicitly
102
+ # convert 'datetime64' values to unpredictable 'datetime' objects.
103
+ return [_standardize_value(x) for x in v]
104
+
105
+ if (pandas and isinstance(v, pandas.Series)) or (jnp and isinstance(v, jnp.ndarray)):
99
106
  return _standardize_value(v.tolist())
107
+
108
+ # Universal NaT/NaN check
109
+ if pandas and pandas.isna(v):
110
+ return None
111
+
100
112
  if isinstance(v, datetime):
101
- if pandas and v is pandas.NaT:
113
+ # Datetime: to milliseconds since epoch (time zone aware)
114
+ return v.timestamp() * 1000
115
+ if isinstance(v, date):
116
+ # Local date: to milliseconds since epoch (midnight UTC)
117
+ return datetime.combine(v, time.min, tzinfo=timezone.utc).timestamp() * 1000
118
+ if isinstance(v, time):
119
+ # Local time: to milliseconds since midnight
120
+ return float(v.hour * 3600_000 + v.minute * 60_000 + v.second * 1000 + v.microsecond // 1000)
121
+ if numpy and isinstance(v, numpy.datetime64):
122
+ try:
123
+ # numpy.datetime64: to milliseconds since epoch (Unix time)
124
+ return float(v.astype('datetime64[ms]').astype(numpy.int64))
125
+ except:
102
126
  return None
103
- else:
104
- return v.timestamp() * 1000 # convert from second to millisecond
127
+
105
128
  if shapely and isinstance(v, shapely.geometry.base.BaseGeometry):
106
129
  return json.dumps(shapely.geometry.mapping(v))
107
130
  try:
lets_plot/_version.py CHANGED
@@ -3,4 +3,4 @@
3
3
  # Use of this source code is governed by the MIT license that can be found in the LICENSE file.
4
4
  #
5
5
  # see: https://www.python.org/dev/peps/pep-0440/#developmental-releases
6
- __version__ = '4.6.2'
6
+ __version__ = '4.7.0'
lets_plot/bistro/im.py CHANGED
@@ -53,8 +53,8 @@ def image_matrix(image_data_array,
53
53
 
54
54
  Returns
55
55
  -------
56
- `ggbunch`
57
- Plot bunch object.
56
+ `SupPlotsSpec`
57
+ A specification describing the combined figure with all plots and their layout.
58
58
 
59
59
  Examples
60
60
  --------
@@ -1,7 +1,7 @@
1
1
  # Copyright (c) 2024. JetBrains s.r.o.
2
2
  # Use of this source code is governed by the MIT license that can be found in the LICENSE file.
3
3
 
4
- from lets_plot.plot.core import PlotSpec, aes
4
+ from lets_plot.plot.core import PlotSpec, LayerSpec, FeatureSpecArray, aes
5
5
  from lets_plot.plot.util import as_annotated_data
6
6
 
7
7
  __all__ = ['waterfall_plot']
@@ -17,7 +17,9 @@ def waterfall_plot(data, x, y, *,
17
17
  calc_total=None, total_title=None,
18
18
  hline=None, hline_ontop=None,
19
19
  connector=None,
20
- label=None, label_format=None) -> PlotSpec:
20
+ relative_labels=None, absolute_labels=None,
21
+ label=None, label_format=None,
22
+ background_layers=None) -> PlotSpec:
21
23
  """
22
24
  A waterfall plot shows the cumulative effect of sequentially introduced positive or negative values.
23
25
 
@@ -31,7 +33,8 @@ def waterfall_plot(data, x, y, *,
31
33
  Name of a numeric variable.
32
34
  measure : str
33
35
  Kind of a calculation.
34
- Values in 'measure' column could be:
36
+ It takes the name of a data column.
37
+ The values in the column could be:
35
38
 
36
39
  'absolute' - the value is shown as is;
37
40
  'relative' - the value is shown as a difference from the previous value;
@@ -43,10 +46,14 @@ def waterfall_plot(data, x, y, *,
43
46
  Color of the box boundary lines.
44
47
  For more info see `Color and Fill <https://lets-plot.org/python/pages/aesthetics.html#color-and-fill>`__.
45
48
  Use 'flow_type' to color lines by the direction of the flow.
49
+ Flow type names: "Absolute", "Increase", "Decrease" and "Total".
50
+ You could use these names to change the default colors with the `scale_color_manual()` function.
46
51
  fill : str
47
52
  Fill color of the boxes.
48
53
  For more info see `Color and Fill <https://lets-plot.org/python/pages/aesthetics.html#color-and-fill>`__.
49
54
  Use 'flow_type' to color boxes by the direction of the flow.
55
+ Flow type names: "Absolute", "Increase", "Decrease" and "Total".
56
+ You could use these names to change the default colors with the `scale_fill_manual()` function.
50
57
  size : float, default=0.0
51
58
  Line width of the box boundary lines.
52
59
  alpha : float
@@ -98,13 +105,27 @@ def waterfall_plot(data, x, y, *,
98
105
  Line between neighbouring boxes connecting the end of the previous box and the beginning of the next box.
99
106
  Set 'blank' or result of `element_blank()` to draw nothing.
100
107
  Set `element_line()` to specify parameters.
108
+ relative_labels : dict
109
+ Result of the call to the `layer_labels()` function.
110
+ Specify content and formatting of annotation labels on relative change bars.
111
+ If specified, overrides `label_format` for relative bars.
112
+ See `layer_labels() <https://lets-plot.org/python/pages/api/lets_plot.layer_labels.html>`__.
113
+ absolute_labels : dict
114
+ Result of the call to the `layer_labels()` function.
115
+ Specify content and formatting of annotation labels on absolute value bars.
116
+ If specified, overrides `label_format` for absolute bars.
117
+ See `layer_labels() <https://lets-plot.org/python/pages/api/lets_plot.layer_labels.html>`__.
101
118
  label : str or dict
102
- Label on the box. Shows change value.
119
+ Style configuration for labels on bars. Applied to default labels or to
120
+ relative/absolute labels when `relative_labels` or `absolute_labels` are specified.
103
121
  Set 'blank' or result of `element_blank()` to draw nothing.
104
- Set `element_text()` to specify parameters.
105
- Use 'flow_type' for `color` parameter of the `element_text()` to color labels by the direction of the flow.
122
+ Set `element_text()` to specify style parameters.
123
+ Use `element_text(color='inherit')` to make labels inherit the color of bar borders.
124
+ See `element_text() <https://lets-plot.org/python/pages/api/lets_plot.element_text.html>`__.
106
125
  label_format : str
107
- Format used to transform label mapping values to a string.
126
+ Format string used to transform label values to text. Applied to default labels or to
127
+ relative/absolute labels when `relative_labels` or `absolute_labels` are specified.
128
+ Can be overridden by formatting specified in `relative_labels` or `absolute_labels`.
108
129
  Examples:
109
130
 
110
131
  - '.2f' -> '12.45'
@@ -112,6 +133,8 @@ def waterfall_plot(data, x, y, *,
112
133
  - 'TTL: {.2f}$' -> 'TTL: 12.45$'
113
134
 
114
135
  For more info see `Formatting <https://lets-plot.org/python/pages/formats.html>`__.
136
+ background_layers : LayerSpec or FeatureSpecArray
137
+ Background layers to be added to the plot.
115
138
 
116
139
  Returns
117
140
  -------
@@ -152,6 +175,40 @@ def waterfall_plot(data, x, y, *,
152
175
 
153
176
  |
154
177
 
178
+ .. jupyter-execute::
179
+ :linenos:
180
+ :emphasize-lines: 21-25
181
+
182
+ import numpy as np
183
+ from lets_plot import *
184
+ from lets_plot.bistro.waterfall import *
185
+ LetsPlot.setup_html()
186
+ categories = list("ABCDEF")
187
+ np.random.seed(42)
188
+ data = {
189
+ 'x': categories,
190
+ 'y': np.random.normal(size=len(categories))
191
+ }
192
+ band_data = {
193
+ 'xmin': [-0.5, 2.5],
194
+ 'xmax': [2.5, 5.5],
195
+ 'name': ['Q1', 'Q2']
196
+ }
197
+ text_data = {
198
+ 'x': [0, 3],
199
+ 'y': [2.7, 2.7],
200
+ 'name': ['Q1', 'Q2']
201
+ }
202
+ waterfall_plot(data, 'x', 'y', label_format='.2f',
203
+ background_layers=geom_band(
204
+ aes(xmin='xmin', xmax='xmax', fill='name', color='name'),
205
+ data=band_data, alpha=0.2
206
+ )) + \\
207
+ geom_text(aes(x='x', y='y', label='name'), data=text_data, size=10) + \\
208
+ ggtitle("Waterfall with background layers")
209
+
210
+ |
211
+
155
212
  .. jupyter-execute::
156
213
  :linenos:
157
214
  :emphasize-lines: 12-18
@@ -172,7 +229,7 @@ def waterfall_plot(data, x, y, *,
172
229
  width=.7, size=1, fill="white", color='flow_type', \\
173
230
  hline=element_line(linetype='solid'), hline_ontop=False, \\
174
231
  connector=element_line(linetype='dotted'), \\
175
- label=element_text(color='flow_type'), \\
232
+ label=element_text(color='inherit'), \\
176
233
  total_title="Result", show_legend=True)
177
234
 
178
235
  |
@@ -206,7 +263,7 @@ def waterfall_plot(data, x, y, *,
206
263
 
207
264
  .. jupyter-execute::
208
265
  :linenos:
209
- :emphasize-lines: 11
266
+ :emphasize-lines: 17-18
210
267
 
211
268
  from lets_plot import *
212
269
  from lets_plot.bistro.waterfall import *
@@ -214,15 +271,36 @@ def waterfall_plot(data, x, y, *,
214
271
  data = {
215
272
  'company': ["Badgersoft"] * 7 + ["AIlien Co."] * 7,
216
273
  'accounts': ["initial", "revenue", "costs", "Q1", "revenue", "costs", "Q2"] * 2,
217
- 'values': [200, 200, -100, None, 250, -100, None, \\
274
+ 'values': [200, 200, -100, None, 250, -100, None,
218
275
  150, 50, -100, None, 100, -100, None],
219
276
  'measure': ['absolute', 'relative', 'relative', 'total', 'relative', 'relative', 'total'] * 2,
220
277
  }
221
- waterfall_plot(data, 'accounts', 'values', measure='measure', group='company') + \\
278
+ colors = {
279
+ "Absolute": "darkseagreen",
280
+ "Increase": "palegoldenrod",
281
+ "Decrease": "paleturquoise",
282
+ "Total": "palegreen",
283
+ }
284
+ waterfall_plot(data, 'accounts', 'values', measure='measure', group='company',
285
+ size=.75, label=element_text(color="black")) + \\
286
+ scale_fill_manual(values=colors) + \\
222
287
  facet_wrap(facets='company', scales='free_x')
223
288
 
224
289
  """
225
290
  data, mapping, data_meta = as_annotated_data(data, aes(x=x, y=y))
291
+
292
+ if background_layers is None:
293
+ layers = []
294
+ elif isinstance(background_layers, LayerSpec):
295
+ layers = [background_layers]
296
+ elif isinstance(background_layers, FeatureSpecArray):
297
+ for sublayer in background_layers.elements():
298
+ if not isinstance(sublayer, LayerSpec):
299
+ raise TypeError("Invalid 'layer' type: {}".format(type(sublayer)))
300
+ layers = background_layers.elements()
301
+ else:
302
+ raise TypeError("Invalid 'layer' type: {}".format(type(background_layers)))
303
+
226
304
  return PlotSpec(data=data, mapping=None, scales=[], layers=[], bistro={
227
305
  'name': 'waterfall',
228
306
  'x': x,
@@ -247,6 +325,9 @@ def waterfall_plot(data, x, y, *,
247
325
  'hline': hline,
248
326
  'hline_ontop': hline_ontop,
249
327
  'connector': connector,
328
+ 'relative_labels': relative_labels,
329
+ 'absolute_labels': absolute_labels,
250
330
  'label': label,
251
331
  'label_format': label_format,
252
- }, **data_meta)
332
+ 'background_layers': [layer.as_dict() for layer in layers]
333
+ }, **data_meta)
@@ -48,10 +48,10 @@ def ggsave(plot: Union[PlotSpec, SupPlotsSpec, GGBunch], filename: str, *, path:
48
48
  h : float, default=None
49
49
  Height of the output image in units.
50
50
  Only applicable when exporting to PNG or PDF.
51
- unit : {'in', 'cm', 'mm'}, default=None
51
+ unit : {'in', 'cm', 'mm'}, default='in'
52
52
  Unit of the output image. One of: 'in', 'cm', 'mm'.
53
53
  Only applicable when exporting to PNG or PDF.
54
- dpi : int, default=None
54
+ dpi : int, default=300
55
55
  Resolution in dots per inch.
56
56
  Only applicable when exporting to PNG or PDF.
57
57
 
@@ -66,19 +66,27 @@ def ggsave(plot: Union[PlotSpec, SupPlotsSpec, GGBunch], filename: str, *, path:
66
66
 
67
67
  For PNG and PDF formats:
68
68
 
69
- 1. If `w`, `h`, `unit`, and `dpi` are all specified:
69
+ - If `w`, `h`, `unit`, and `dpi` are all specified:
70
70
 
71
- - `scale` is ignored.
72
- - The plot's pixel size (default or set by `ggsize()`) is converted to the specified units using the given dpi.
73
- - If the aspect ratio of `w` and `h` differs from the plot's pixel aspect ratio:
71
+ - The plot's pixel size (default or set by `ggsize()`) is ignored.
72
+ - The output size is calculated using the specified `w`, `h`, `unit`, and `dpi`.
74
73
 
75
- * The plot maintains its original (pixel) aspect ratio.
76
- * It's fitted within the specified `w` x `h` area.
77
- * Any extra space is left empty.
74
+ - The plot is resized to fit the specified `w` x `h` area, which may affect the layout, tick labels, and other elements.
78
75
 
79
- 2. If `w`, `h` are not specified:
76
+ - If only `dpi` is specified:
80
77
 
81
- - The `scale` parameter is used to determine the output size.
78
+ - The plot's pixel size (default or set by `ggsize()`) is converted to inches using the standard display PPI of 96.
79
+ - The output size is then calculated based on the specified DPI.
80
+
81
+ - The plot maintains its aspect ratio, preserving layout, tick labels, and other visual elements.
82
+ - Useful for printing - the plot will appear nearly the same size as on screen.
83
+
84
+ - If `w`, `h` are not specified:
85
+
86
+ - The `scale` parameter is used to determine the output size.
87
+
88
+ - The plot maintains its aspect ratio, preserving layout, tick labels, and other visual elements.
89
+ - Useful for generating high-resolution images suitable for publication.
82
90
 
83
91
  Examples
84
92
  --------
@@ -99,8 +107,8 @@ def ggsave(plot: Union[PlotSpec, SupPlotsSpec, GGBunch], filename: str, *, path:
99
107
 
100
108
  from lets_plot import *
101
109
  LetsPlot.setup_html()
102
- plot = ggplot() + geom_point(x=0, y=0) + ggsize(800, 400)
103
- ggsave(plot, 'plot.png', w=8, h=4, unit='in', dpi=300)
110
+ plot = ggplot() + geom_point(x=0, y=0)
111
+ ggsave(plot, 'plot.png', w=4, h=3)
104
112
 
105
113
  """
106
114
 
@@ -125,7 +133,7 @@ def ggsave(plot: Union[PlotSpec, SupPlotsSpec, GGBunch], filename: str, *, path:
125
133
  return _to_svg(plot, pathname)
126
134
  elif ext in ['html', 'htm']:
127
135
  return _to_html(plot, pathname, iframe=iframe)
128
- elif ext in ['png', 'pdf']:
136
+ elif ext in ['png', 'pdf', 'bmp']:
129
137
  return _export_as_raster(plot, pathname, scale, export_format=ext, w=w, h=h, unit=unit, dpi=dpi)
130
138
  else:
131
139
  raise ValueError(