lets-plot 4.8.1rc1__cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.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.
Files changed (97) hide show
  1. lets_plot/__init__.py +382 -0
  2. lets_plot/_global_settings.py +192 -0
  3. lets_plot/_kbridge.py +197 -0
  4. lets_plot/_type_utils.py +133 -0
  5. lets_plot/_version.py +6 -0
  6. lets_plot/bistro/__init__.py +16 -0
  7. lets_plot/bistro/_plot2d_common.py +106 -0
  8. lets_plot/bistro/corr.py +448 -0
  9. lets_plot/bistro/im.py +196 -0
  10. lets_plot/bistro/joint.py +192 -0
  11. lets_plot/bistro/qq.py +207 -0
  12. lets_plot/bistro/residual.py +341 -0
  13. lets_plot/bistro/waterfall.py +332 -0
  14. lets_plot/export/__init__.py +6 -0
  15. lets_plot/export/ggsave_.py +172 -0
  16. lets_plot/frontend_context/__init__.py +8 -0
  17. lets_plot/frontend_context/_configuration.py +140 -0
  18. lets_plot/frontend_context/_dynamic_configure_html.py +115 -0
  19. lets_plot/frontend_context/_frontend_ctx.py +16 -0
  20. lets_plot/frontend_context/_html_contexts.py +223 -0
  21. lets_plot/frontend_context/_intellij_python_json_ctx.py +38 -0
  22. lets_plot/frontend_context/_isolated_webview_panel_ctx.py +81 -0
  23. lets_plot/frontend_context/_json_contexts.py +39 -0
  24. lets_plot/frontend_context/_jupyter_notebook_ctx.py +82 -0
  25. lets_plot/frontend_context/_mime_types.py +7 -0
  26. lets_plot/frontend_context/_static_html_page_ctx.py +76 -0
  27. lets_plot/frontend_context/_static_svg_ctx.py +26 -0
  28. lets_plot/frontend_context/_webbr_html_page_ctx.py +29 -0
  29. lets_plot/frontend_context/sandbox.py +5 -0
  30. lets_plot/geo_data/__init__.py +19 -0
  31. lets_plot/geo_data/core.py +335 -0
  32. lets_plot/geo_data/geocoder.py +988 -0
  33. lets_plot/geo_data/geocodes.py +512 -0
  34. lets_plot/geo_data/gis/__init__.py +0 -0
  35. lets_plot/geo_data/gis/fluent_dict.py +201 -0
  36. lets_plot/geo_data/gis/geocoding_service.py +42 -0
  37. lets_plot/geo_data/gis/geometry.py +91 -0
  38. lets_plot/geo_data/gis/json_request.py +232 -0
  39. lets_plot/geo_data/gis/json_response.py +308 -0
  40. lets_plot/geo_data/gis/request.py +492 -0
  41. lets_plot/geo_data/gis/response.py +247 -0
  42. lets_plot/geo_data/livemap_helper.py +65 -0
  43. lets_plot/geo_data/to_geo_data_frame.py +141 -0
  44. lets_plot/geo_data/type_assertion.py +34 -0
  45. lets_plot/geo_data_internals/__init__.py +4 -0
  46. lets_plot/geo_data_internals/constants.py +13 -0
  47. lets_plot/geo_data_internals/utils.py +33 -0
  48. lets_plot/mapping.py +115 -0
  49. lets_plot/package_data/lets-plot.min.js +3 -0
  50. lets_plot/plot/__init__.py +64 -0
  51. lets_plot/plot/_global_theme.py +14 -0
  52. lets_plot/plot/annotation.py +290 -0
  53. lets_plot/plot/coord.py +242 -0
  54. lets_plot/plot/core.py +1071 -0
  55. lets_plot/plot/expand_limits_.py +78 -0
  56. lets_plot/plot/facet.py +210 -0
  57. lets_plot/plot/font_features.py +71 -0
  58. lets_plot/plot/geom.py +9146 -0
  59. lets_plot/plot/geom_extras.py +53 -0
  60. lets_plot/plot/geom_function_.py +219 -0
  61. lets_plot/plot/geom_imshow_.py +393 -0
  62. lets_plot/plot/geom_livemap_.py +343 -0
  63. lets_plot/plot/ggbunch_.py +96 -0
  64. lets_plot/plot/gggrid_.py +139 -0
  65. lets_plot/plot/ggtb_.py +81 -0
  66. lets_plot/plot/guide.py +231 -0
  67. lets_plot/plot/label.py +187 -0
  68. lets_plot/plot/marginal_layer.py +181 -0
  69. lets_plot/plot/plot.py +245 -0
  70. lets_plot/plot/pos.py +344 -0
  71. lets_plot/plot/sampling.py +338 -0
  72. lets_plot/plot/sandbox_.py +26 -0
  73. lets_plot/plot/scale.py +3580 -0
  74. lets_plot/plot/scale_colormap_mpl.py +300 -0
  75. lets_plot/plot/scale_convenience.py +155 -0
  76. lets_plot/plot/scale_identity_.py +653 -0
  77. lets_plot/plot/scale_position.py +1342 -0
  78. lets_plot/plot/series_meta.py +209 -0
  79. lets_plot/plot/stat.py +585 -0
  80. lets_plot/plot/subplots.py +331 -0
  81. lets_plot/plot/subplots_util.py +24 -0
  82. lets_plot/plot/theme_.py +790 -0
  83. lets_plot/plot/theme_set.py +418 -0
  84. lets_plot/plot/tooltip.py +486 -0
  85. lets_plot/plot/util.py +267 -0
  86. lets_plot/settings_utils.py +244 -0
  87. lets_plot/tilesets.py +429 -0
  88. lets_plot-4.8.1rc1.dist-info/METADATA +221 -0
  89. lets_plot-4.8.1rc1.dist-info/RECORD +97 -0
  90. lets_plot-4.8.1rc1.dist-info/WHEEL +6 -0
  91. lets_plot-4.8.1rc1.dist-info/licenses/LICENSE +21 -0
  92. lets_plot-4.8.1rc1.dist-info/licenses/licenses/LICENSE.FreeType +166 -0
  93. lets_plot-4.8.1rc1.dist-info/licenses/licenses/LICENSE.ImageMagick +106 -0
  94. lets_plot-4.8.1rc1.dist-info/licenses/licenses/LICENSE.expat +21 -0
  95. lets_plot-4.8.1rc1.dist-info/licenses/licenses/LICENSE.fontconfig +200 -0
  96. lets_plot-4.8.1rc1.dist-info/top_level.txt +2 -0
  97. lets_plot_kotlin_bridge.cpython-311-x86_64-linux-gnu.so +0 -0
lets_plot/_kbridge.py ADDED
@@ -0,0 +1,197 @@
1
+ # Copyright (c) 2020. JetBrains s.r.o.
2
+ # Use of this source code is governed by the MIT license that can be found in the LICENSE file.
3
+
4
+ # noinspection PyUnresolvedReferences
5
+ from typing import Dict
6
+
7
+ import lets_plot_kotlin_bridge
8
+
9
+ from ._global_settings import get_js_cdn_url
10
+ from ._type_utils import standardize_dict
11
+
12
+
13
+ def _generate_dynamic_display_html(plot_spec: Dict) -> str:
14
+ plot_spec = _standardize_plot_spec(plot_spec)
15
+ # Old implementation (deprecated):
16
+ # return lets_plot_kotlin_bridge.generate_html(plot_spec)
17
+
18
+ # New implementation using get_display_html_for_raw_spec with default parameters
19
+ return lets_plot_kotlin_bridge.get_display_html_for_raw_spec(
20
+ plot_spec,
21
+ {}, # empty sizing_options -> defaults to notebookCell sizing (MIN width, SCALED height)
22
+ True, # dynamic_script_loading
23
+ False, # force_immediate_render
24
+ False # responsive
25
+ )
26
+
27
+
28
+ def _generate_svg(plot_spec: Dict, w: float = None, h: float = None, unit: str = None, use_css_pixelated_image_rendering: bool=True) -> str:
29
+ plot_spec = _standardize_plot_spec(plot_spec)
30
+ w = -1.0 if w is None else float(w)
31
+ h = -1.0 if h is None else float(h)
32
+ unit = '' if unit is None else str(unit) # None is not a valid value for str type - PyArg_ParseTuple will fail
33
+ return lets_plot_kotlin_bridge.export_svg(plot_spec, w, h, unit, use_css_pixelated_image_rendering)
34
+
35
+ def _generate_png(bytestring: Dict, output_width: float, output_height: float, unit: str, dpi: int, scale: float) -> str:
36
+ """
37
+ Export a plot to PNG format. Returns base64 encoded string of the PNG image.
38
+ """
39
+ plot_spec = _standardize_plot_spec(bytestring)
40
+ output_width = -1.0 if output_width is None else float(output_width)
41
+ output_height = -1.0 if output_height is None else float(output_height)
42
+ unit = '' if unit is None else str(unit) # None is not a valid value for str type - PyArg_ParseTuple will fail
43
+ dpi = -1 if dpi is None else int(dpi)
44
+ scale = -1.0 if scale is None else float(scale)
45
+ return lets_plot_kotlin_bridge.export_png(plot_spec, output_width, output_height, unit, dpi, scale)
46
+
47
+
48
+ def _generate_mvg(bytestring: Dict, output_width: float, output_height: float, unit: str, dpi: int, scale: float) -> str:
49
+ """
50
+ Export a plot to MVG format. For internal use.
51
+ """
52
+ plot_spec = _standardize_plot_spec(bytestring)
53
+ output_width = -1.0 if output_width is None else float(output_width)
54
+ output_height = -1.0 if output_height is None else float(output_height)
55
+ unit = '' if unit is None else str(unit) # None is not a valid value for str type - PyArg_ParseTuple will fail
56
+ dpi = -1 if dpi is None else int(dpi)
57
+ scale = -1.0 if scale is None else float(scale)
58
+ return lets_plot_kotlin_bridge.export_mvg(plot_spec, output_width, output_height, unit, dpi, scale)
59
+
60
+
61
+ def _generate_static_html_page(plot_spec: Dict, iframe: bool) -> str:
62
+ plot_spec = _standardize_plot_spec(plot_spec)
63
+ scriptUrl = get_js_cdn_url()
64
+ return lets_plot_kotlin_bridge.export_html(plot_spec, scriptUrl, iframe)
65
+
66
+
67
+ def _generate_static_html_page_for_raw_spec(
68
+ plot_spec: Dict,
69
+ sizing_options: Dict,
70
+ dynamic_script_loading: bool = False,
71
+ force_immediate_render: bool = False,
72
+ responsive: bool = False,
73
+ height100pct: bool = False
74
+ ) -> str:
75
+ plot_spec = _standardize_plot_spec(plot_spec)
76
+ sizing_options = standardize_dict(sizing_options)
77
+ scriptUrl = get_js_cdn_url()
78
+ return lets_plot_kotlin_bridge.get_static_html_page_for_raw_spec(
79
+ plot_spec,
80
+ scriptUrl,
81
+ sizing_options,
82
+ dynamic_script_loading,
83
+ force_immediate_render,
84
+ responsive,
85
+ height100pct
86
+ )
87
+
88
+
89
+ def _standardize_plot_spec(plot_spec: Dict) -> Dict:
90
+ """
91
+ :param plot_spec: dict
92
+ """
93
+ if not isinstance(plot_spec, dict):
94
+ raise ValueError("dict expected but was {}".format(type(plot_spec)))
95
+
96
+ return standardize_dict(plot_spec)
97
+
98
+
99
+ def _generate_static_configure_html() -> str:
100
+ """
101
+ Generate static HTML configuration.
102
+
103
+ Returns
104
+ -------
105
+ str
106
+ HTML string containing the static configuration with the script URL from global settings.
107
+ """
108
+ scriptUrl = get_js_cdn_url()
109
+ return lets_plot_kotlin_bridge.get_static_configure_html(scriptUrl)
110
+
111
+
112
+ def _generate_display_html_for_raw_spec(
113
+ plot_spec: Dict,
114
+ sizing_options: Dict,
115
+ *,
116
+ dynamic_script_loading: bool = False,
117
+ force_immediate_render: bool = False,
118
+ responsive: bool = False,
119
+ height100pct: bool = False
120
+ ) -> str:
121
+ """
122
+ Generate HTML for displaying a plot from 'raw' specification (not processed by plot backend)
123
+ with customizable options.
124
+
125
+ Parameters
126
+ ----------
127
+ plot_spec : Dict
128
+ Dict containing the plot specification.
129
+ sizing_options : Dict
130
+ Dict containing sizing policy options (width_mode, height_mode, width, height).
131
+ dynamic_script_loading : bool, default=False
132
+ Controls how the generated JS code interacts with the lets-plot.js library.
133
+ If True, assumes the library loads dynamically (asynchronously).
134
+ If False, assumes the library loads synchronously via a <script> tag in the page header.
135
+ force_immediate_render : bool, default=False
136
+ Controls the timing of plot rendering.
137
+ If True, renders the plot immediately.
138
+ If False, waits for the ResizeObserver event to ensure proper DOM layout.
139
+ responsive : bool, default=False
140
+ If True, makes the plot responsive to container size changes.
141
+ height100pct : bool, default=False
142
+ If True, sets the plot container div height to 100%.
143
+
144
+ Returns
145
+ -------
146
+ str
147
+ HTML string containing the plot with specified options.
148
+
149
+ Notes
150
+ -----
151
+ The sizing_options dict supports the following structure:
152
+ {
153
+ 'width_mode': str, # 'fixed', 'min', 'fit', 'scaled' (case-insensitive)
154
+ 'height_mode': str, # 'fixed', 'min', 'fit', 'scaled' (case-insensitive)
155
+ 'width': number, # optional
156
+ 'height': number # optional
157
+ }
158
+
159
+ Sizing modes determine how the plot dimensions are calculated:
160
+
161
+ 1. FIXED mode:
162
+ - Uses the explicitly provided width/height values
163
+ - Falls back to the default figure size if no values are provided
164
+ - Not responsive to container size
165
+
166
+ 2. MIN mode:
167
+ Applies the smallest dimension among:
168
+ - The default figure size
169
+ - The specified width/height (if provided)
170
+ - The container size (if available)
171
+
172
+ 3. FIT mode:
173
+ Uses either:
174
+ - The specified width/height if provided
175
+ - Otherwise uses container size if available
176
+ - Falls back to default figure size if neither is available
177
+
178
+ 4. SCALED mode:
179
+ - Always preserves the figure's aspect ratio
180
+ - Typical usage: one dimension (usually width) uses FIXED/MIN/FIT mode,
181
+ and SCALED height adjusts to maintain aspect ratio
182
+ - Special case: when both width and height are SCALED:
183
+ * Requires container size to be available
184
+ * Fits a figure within container while preserving the aspect ratio
185
+ * Neither dimension is predetermined
186
+
187
+ """
188
+ plot_spec = _standardize_plot_spec(plot_spec)
189
+ sizing_options = standardize_dict(sizing_options)
190
+ return lets_plot_kotlin_bridge.get_display_html_for_raw_spec(
191
+ plot_spec,
192
+ sizing_options,
193
+ dynamic_script_loading,
194
+ force_immediate_render,
195
+ responsive,
196
+ height100pct
197
+ )
@@ -0,0 +1,133 @@
1
+ #
2
+ # Copyright (c) 2019. JetBrains s.r.o.
3
+ # Use of this source code is governed by the MIT license that can be found in the LICENSE file.
4
+ #
5
+ import json
6
+ import math
7
+ from datetime import datetime, date, time, timezone
8
+
9
+ from typing import Dict
10
+
11
+ try:
12
+ import numpy
13
+ except ImportError:
14
+ numpy = None
15
+
16
+ try:
17
+ import pandas
18
+ except ImportError:
19
+ pandas = None
20
+
21
+ try:
22
+ import polars
23
+ except ImportError:
24
+ polars = None
25
+
26
+ try:
27
+ import shapely
28
+ import shapely.geometry
29
+ except ImportError:
30
+ shapely = None
31
+
32
+ try:
33
+ import jax.numpy as jnp
34
+ except ImportError:
35
+ jnp = None
36
+
37
+
38
+ # Parameter 'value' can also be pandas.DataFrame
39
+ def standardize_dict(value: Dict) -> Dict:
40
+ result = {}
41
+ for k, v in value.items():
42
+ result[_standardize_value(k)] = _standardize_value(v)
43
+
44
+ return result
45
+
46
+
47
+ def is_pandas_data_frame(v) -> bool:
48
+ return pandas and isinstance(v, pandas.DataFrame)
49
+
50
+
51
+ def is_polars_dataframe(v):
52
+ return polars and isinstance(v, polars.DataFrame)
53
+
54
+
55
+ def is_dict_or_dataframe(v):
56
+ return isinstance(v, dict) or (pandas and isinstance(v, pandas.DataFrame))
57
+
58
+
59
+ def is_int(v):
60
+ return isinstance(v, int) or (numpy and isinstance(v, numpy.integer)) or (jnp and isinstance(v, jnp.integer))
61
+
62
+
63
+ def is_float(v):
64
+ return isinstance(v, float) or (numpy and isinstance(v, numpy.floating)) or (jnp and isinstance(v, jnp.floating))
65
+
66
+
67
+ def is_ndarray(data) -> bool:
68
+ return (numpy and isinstance(data, numpy.ndarray)) or (jnp and isinstance(data, jnp.ndarray))
69
+
70
+
71
+ def is_number(v):
72
+ return is_int(v) or is_float(v)
73
+
74
+
75
+ def _standardize_value(v):
76
+ if v is None:
77
+ return v
78
+ if isinstance(v, bool):
79
+ return bool(v)
80
+ if isinstance(v, str):
81
+ return str(v)
82
+ if is_float(v):
83
+ if math.isfinite(v):
84
+ return float(v)
85
+ # None for special values like 'nan' etc. because
86
+ # some JSON parsers (like com.google.gson.Gson) do not handle them well.
87
+ return None
88
+ if is_int(v):
89
+ return float(v)
90
+ if is_dict_or_dataframe(v):
91
+ return standardize_dict(v)
92
+ if is_polars_dataframe(v):
93
+ return standardize_dict(v.to_dict(as_series=False))
94
+ if isinstance(v, list):
95
+ return [_standardize_value(elem) for elem in v]
96
+ if isinstance(v, tuple):
97
+ return tuple(_standardize_value(elem) for elem in v)
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)):
106
+ return _standardize_value(v.tolist())
107
+
108
+ # Universal NaT/NaN check
109
+ if pandas and pandas.isna(v):
110
+ return None
111
+
112
+ if isinstance(v, datetime):
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:
126
+ return None
127
+
128
+ if shapely and isinstance(v, shapely.geometry.base.BaseGeometry):
129
+ return json.dumps(shapely.geometry.mapping(v))
130
+ try:
131
+ return repr(v)
132
+ except Exception:
133
+ raise Exception('Unsupported type: {0}({1})'.format(v, type(v)))
lets_plot/_version.py ADDED
@@ -0,0 +1,6 @@
1
+ #
2
+ # Copyright (c) 2019. JetBrains s.r.o.
3
+ # Use of this source code is governed by the MIT license that can be found in the LICENSE file.
4
+ #
5
+ # see: https://www.python.org/dev/peps/pep-0440/#developmental-releases
6
+ __version__ = '4.8.1rc1'
@@ -0,0 +1,16 @@
1
+ # Copyright (c) 2020. JetBrains s.r.o.
2
+ # Use of this source code is governed by the MIT license that can be found in the LICENSE file.
3
+
4
+ from .corr import *
5
+ from .im import *
6
+ from .qq import *
7
+ from .residual import *
8
+ from .joint import *
9
+ from .waterfall import *
10
+
11
+ __all__ = (im.__all__ +
12
+ corr.__all__ +
13
+ qq.__all__ +
14
+ residual.__all__ +
15
+ joint.__all__ +
16
+ waterfall.__all__)
@@ -0,0 +1,106 @@
1
+ #
2
+ # Copyright (c) 2023. JetBrains s.r.o.
3
+ # Use of this source code is governed by the MIT license that can be found in the LICENSE file.
4
+ #
5
+ from ..plot.core import DummySpec, aes
6
+ from ..plot.geom import *
7
+ from ..plot.marginal_layer import ggmarginal
8
+
9
+ _BINS_DEF = 30
10
+ _COLOR_DEF = "pen"
11
+
12
+ _MARGINAL_ALPHA = .1
13
+
14
+
15
+ def _get_bin_params_2d(xs, ys, binwidth, bins):
16
+ if isinstance(bins, int):
17
+ bins = [bins, bins]
18
+ if isinstance(binwidth, int) or isinstance(binwidth, float):
19
+ binwidth = [binwidth, binwidth]
20
+ if binwidth is not None or bins is not None or len(xs) == 0:
21
+ return binwidth, bins
22
+ binwidth_x = (max(xs) - min(xs)) / _BINS_DEF
23
+ binwidth_y = (max(ys) - min(ys)) / _BINS_DEF
24
+ binwidth_max = max(binwidth_x, binwidth_y)
25
+
26
+ return [binwidth_max, binwidth_max], bins
27
+
28
+
29
+ def _get_geom2d_layer(geom_kind, binwidth2d, bins2d, color, color_by, size, alpha, show_legend):
30
+ if geom_kind == 'point':
31
+ return geom_point(color=color, size=size, alpha=alpha, show_legend=show_legend)
32
+ if geom_kind == 'tile':
33
+ return geom_bin2d(
34
+ aes(fill=('..count..' if color_by is None else color_by)),
35
+ bins=bins2d, binwidth=binwidth2d,
36
+ color=color, size=size, alpha=alpha,
37
+ show_legend=show_legend
38
+ )
39
+ if geom_kind == 'hex':
40
+ return geom_hex(
41
+ aes(fill=('..count..' if color_by is None else color_by)),
42
+ bins=bins2d, binwidth=binwidth2d,
43
+ color=color, size=size, alpha=alpha,
44
+ show_legend=show_legend
45
+ )
46
+ if geom_kind == 'density2d':
47
+ return geom_density2d(
48
+ aes(color=('..group..' if color_by is None else color_by)),
49
+ color=color, size=size, alpha=alpha,
50
+ show_legend=show_legend
51
+ )
52
+ if geom_kind == 'density2df':
53
+ return geom_density2df(
54
+ aes(fill=('..group..' if color_by is None else color_by)),
55
+ color=color, size=size, alpha=alpha,
56
+ show_legend=show_legend
57
+ )
58
+ if geom_kind == 'pointdensity':
59
+ return geom_pointdensity(
60
+ aes(color=('..density..' if color_by is None else color_by)),
61
+ color=color, size=size, alpha=alpha,
62
+ show_legend=show_legend
63
+ )
64
+ if geom_kind == 'none':
65
+ return None
66
+ raise Exception("Unknown geom '{0}'".format(geom_kind))
67
+
68
+
69
+ def _get_marginal_layers(marginal, binwidth2d, bins2d, color, color_by, show_legend):
70
+ marginal_color = None if color_by is not None else (color or _COLOR_DEF)
71
+
72
+ def bin_param_to_1d(param2d, side):
73
+ if param2d is None:
74
+ return None
75
+ else:
76
+ if side in ['t', 'b']:
77
+ return param2d[0]
78
+ else:
79
+ return param2d[1]
80
+
81
+ def _get_marginal_layer(geom_kind, side, size):
82
+ if geom_kind in ['dens', 'density']:
83
+ layer = geom_area(stat='density', position='identity', color=marginal_color, fill=marginal_color,
84
+ alpha=_MARGINAL_ALPHA, show_legend=show_legend)
85
+ elif geom_kind in ['hist', 'histogram']:
86
+ binwidth = bin_param_to_1d(binwidth2d, side)
87
+ bins = bin_param_to_1d(bins2d, side)
88
+ layer = geom_histogram(bins=bins, binwidth=binwidth,
89
+ color=marginal_color, fill=marginal_color, alpha=_MARGINAL_ALPHA,
90
+ show_legend=show_legend)
91
+ elif geom_kind in ['box', 'boxplot']:
92
+ layer = geom_boxplot(color=marginal_color, fill=marginal_color, alpha=_MARGINAL_ALPHA, show_legend=show_legend)
93
+ else:
94
+ raise Exception("Unknown geom '{0}'".format(geom_kind))
95
+
96
+ return ggmarginal(side, size=size, layer=layer)
97
+
98
+ result = DummySpec()
99
+ for layer_description in filter(bool, marginal.split(",")):
100
+ params = layer_description.strip().split(":")
101
+ geom_kind, sides = params[0].strip(), params[1].strip()
102
+ size = float(params[2].strip()) if len(params) > 2 else None
103
+ for side in sides:
104
+ result += _get_marginal_layer(geom_kind, side, size)
105
+
106
+ return result