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
@@ -0,0 +1,231 @@
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
+ from .core import FeatureSpec
6
+
7
+ __all__ = ['guide_legend', 'guide_colorbar', 'guides', 'layer_key']
8
+
9
+
10
+ def guide_legend(title=None, *, nrow=None, ncol=None, byrow=None, override_aes=None):
11
+ """
12
+ Legend guide.
13
+
14
+ Parameters
15
+ ----------
16
+ title : str
17
+ Title of guide.
18
+ nrow : int
19
+ Number of rows in legend's guide.
20
+ ncol : int
21
+ Number of columns in legend's guide.
22
+ byrow : bool, default=True
23
+ Type of output: by row, or by column.
24
+ override_aes : dict
25
+ Dictionary that maps aesthetic parameters to new values, overriding the default legend appearance.
26
+ Each value can be a constant applied to all keys or a list that changes particular keys.
27
+
28
+
29
+ Returns
30
+ -------
31
+ ``FeatureSpec``
32
+ Legend guide specification.
33
+
34
+ Notes
35
+ -----
36
+ Legend type guide shows key (i.e., geoms) mapped onto values.
37
+
38
+ Examples
39
+ --------
40
+ .. jupyter-execute::
41
+ :linenos:
42
+ :emphasize-lines: 11
43
+
44
+ import numpy as np
45
+ from lets_plot import *
46
+ LetsPlot.setup_html()
47
+ n = 100
48
+ np.random.seed(42)
49
+ x = np.random.uniform(size=n)
50
+ y = np.random.uniform(size=n)
51
+ c = np.random.choice(list('abcdefgh'), size=n)
52
+ ggplot({'x': x, 'y': y, 'c': c}, aes('x', 'y')) + \\
53
+ geom_point(aes(shape='c'), size=4, alpha=.7) + \\
54
+ scale_shape(guide=guide_legend(nrow=3, override_aes={'color': 'red'}))
55
+
56
+ """
57
+ return _guide('legend', **locals())
58
+
59
+
60
+ def guide_colorbar(title=None, *, barwidth=None, barheight=None, nbin=None):
61
+ """
62
+ Continuous color bar guide.
63
+
64
+ Parameters
65
+ ----------
66
+ title : str
67
+ Title of guide.
68
+ barwidth : float
69
+ Color bar width in px.
70
+ barheight : float
71
+ Color bar height in px.
72
+ nbin : int
73
+ Number of bins in color bar.
74
+
75
+ Returns
76
+ -------
77
+ ``FeatureSpec``
78
+ Color guide specification.
79
+
80
+ Notes
81
+ -----
82
+ Color bar guide shows continuous color scales mapped onto values.
83
+ Color bar is available with scale_fill and scale_color.
84
+
85
+ Examples
86
+ --------
87
+ .. jupyter-execute::
88
+ :linenos:
89
+ :emphasize-lines: 12
90
+
91
+ import numpy as np
92
+ from lets_plot import *
93
+ LetsPlot.setup_html()
94
+ n = 50
95
+ np.random.seed(42)
96
+ x = np.random.uniform(size=n)
97
+ y = np.random.uniform(size=n)
98
+ v = np.random.normal(size=n)
99
+ ggplot({'x': x, 'y': y, 'v': v}, aes('x', 'y')) + \\
100
+ geom_point(aes(fill='v'), size=4, shape=21, color='black') + \\
101
+ scale_fill_gradient2(low='red', mid='yellow', high='blue', \\
102
+ guide=guide_colorbar(nbin=8, barwidth=10))
103
+
104
+ """
105
+ return _guide('colorbar', **locals())
106
+
107
+
108
+ def _guide(name, **kwargs):
109
+ if 'title' in kwargs and isinstance(kwargs['title'], int):
110
+ raise ValueError("Use keyword arguments for all other than 'title' parameters.")
111
+ return FeatureSpec('guide', name=name, **kwargs)
112
+
113
+
114
+ def guides(**kwargs):
115
+ """
116
+ Set guides for each scale.
117
+
118
+ Parameters
119
+ ----------
120
+ kwargs
121
+ Key-value pairs where the key can be:
122
+
123
+ - An aesthetic name
124
+ - 'manual' - a key referring to the default custom legend
125
+ - A group name referring to a custom legend where the group is defined via the `layer_key() <https://lets-plot.org/python/pages/api/lets_plot.layer_key.html>`__ function
126
+
127
+ The value can be either:
128
+
129
+ - A string ('colorbar', 'legend')
130
+ - A call to a guide function (`guide_colorbar() <https://lets-plot.org/python/pages/api/lets_plot.guide_colorbar.html>`__, `guide_legend() <https://lets-plot.org/python/pages/api/lets_plot.guide_legend.html>`__) specifying additional arguments
131
+ - 'none' to hide the guide
132
+
133
+ Returns
134
+ -------
135
+ ``FeatureSpec``
136
+ Guides specification.
137
+
138
+ Examples
139
+ --------
140
+ .. jupyter-execute::
141
+ :linenos:
142
+ :emphasize-lines: 13-14
143
+
144
+ import numpy as np
145
+ from lets_plot import *
146
+ LetsPlot.setup_html()
147
+ n = 25
148
+ np.random.seed(42)
149
+ x = np.random.uniform(size=n)
150
+ y = np.random.uniform(size=n)
151
+ c = np.random.choice(list('abcdefgh'), size=n)
152
+ v = np.random.normal(size=n)
153
+ ggplot({'x': x, 'y': y, 'c': c, 'v': v}, aes('x', 'y')) + \\
154
+ geom_point(aes(shape='c', color='v'), size=4) + \\
155
+ scale_color_gradient2(low='red', mid='yellow', high='blue') + \\
156
+ guides(shape=guide_legend(ncol=2), \\
157
+ color=guide_colorbar(nbin=8, barwidth=20))
158
+
159
+ |
160
+
161
+ .. jupyter-execute::
162
+ :linenos:
163
+ :emphasize-lines: 11
164
+
165
+ import numpy as np
166
+ from lets_plot import *
167
+ LetsPlot.setup_html()
168
+ n = 10
169
+ np.random.seed(42)
170
+ x = list(range(n))
171
+ y = np.random.uniform(size=n)
172
+ ggplot({'x': x, 'y': y}, aes('x', 'y')) + \\
173
+ geom_point(color='red', manual_key="point") + \\
174
+ geom_line(color='blue', manual_key="line") + \\
175
+ guides(manual=guide_legend('Zones', ncol=2))
176
+
177
+ """
178
+ return FeatureSpec('guides', name=None, **kwargs)
179
+
180
+
181
+ def layer_key(label, group=None, *, index=None, **kwargs):
182
+ """
183
+ Configure custom legend.
184
+
185
+ Parameters
186
+ ----------
187
+ label : str
188
+ Text for the element in the custom legend.
189
+ group : str, default='manual'
190
+ Group name by which elements are combined into a legend group.
191
+ index : int
192
+ Position of the element in the custom legend.
193
+ kwargs :
194
+ A list of aesthetic parameters to use in the custom legend.
195
+
196
+ Returns
197
+ -------
198
+ ``FeatureSpec``
199
+ Custom legend specification.
200
+
201
+ Notes
202
+ -----
203
+ The group name specified with the ``group`` parameter can be used in the
204
+ `labs() <https://lets-plot.org/python/pages/api/lets_plot.labs.html>`__ and
205
+ `guides() <https://lets-plot.org/python/pages/api/lets_plot.guides.html>`__ functions
206
+ to further customize the display of this group (e.g. change its name).
207
+ In particular, items in the 'manual' group will be displayed without a title unless you change it manually.
208
+
209
+ ----
210
+
211
+ If you set the same group and label for a legend element in different layers, they will merge into one complex legend element.
212
+
213
+ Examples
214
+ --------
215
+ .. jupyter-execute::
216
+ :linenos:
217
+ :emphasize-lines: 9-10
218
+
219
+ import numpy as np
220
+ from lets_plot import *
221
+ LetsPlot.setup_html()
222
+ n = 10
223
+ np.random.seed(42)
224
+ x = list(range(n))
225
+ y = np.random.uniform(size=n)
226
+ ggplot({'x': x, 'y': y}, aes('x', 'y')) + \\
227
+ geom_point(color='red', manual_key=layer_key("point", shape=21)) + \\
228
+ geom_line(color='blue', linetype=2, manual_key=layer_key("line", linetype=1))
229
+
230
+ """
231
+ return FeatureSpec('layer_key', name=None, label=label, group=group, index=index, **kwargs)
@@ -0,0 +1,187 @@
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
+ from .core import FeatureSpec, FeatureSpecArray
6
+ from .guide import _guide, guides
7
+
8
+ #
9
+ # Plot title
10
+ # Scale names: axis labels / legend titles
11
+ #
12
+ __all__ = ['ggtitle',
13
+ 'labs',
14
+ 'xlab', 'ylab']
15
+
16
+
17
+ def ggtitle(label, subtitle=None):
18
+ """
19
+ Add title to the plot.
20
+
21
+ Parameters
22
+ ----------
23
+ label : str
24
+ The text for the plot title.
25
+ subtitle : str
26
+ The text for the plot subtitle.
27
+
28
+ Returns
29
+ -------
30
+ ``FeatureSpec``
31
+ Plot title specification.
32
+
33
+ Notes
34
+ -----
35
+ Split a long title/subtitle into two lines or more using ``\\n`` as a text separator.
36
+
37
+ Examples
38
+ --------
39
+ .. jupyter-execute::
40
+ :linenos:
41
+ :emphasize-lines: 5
42
+
43
+ from lets_plot import *
44
+ LetsPlot.setup_html()
45
+ data = {'x': list(range(10)), 'y': list(range(10))}
46
+ ggplot(data, aes('x', 'y')) + geom_point(aes(size='y')) + \\
47
+ ggtitle('New Plot Title')
48
+
49
+ """
50
+ return labs(title=label, subtitle=subtitle)
51
+
52
+
53
+ def xlab(label):
54
+ """
55
+ Add label to the x axis.
56
+
57
+ Parameters
58
+ ----------
59
+ label : str
60
+ The text for the x axis label.
61
+
62
+ Returns
63
+ -------
64
+ ``FeatureSpec``
65
+ Axis label specification.
66
+
67
+ Examples
68
+ --------
69
+ .. jupyter-execute::
70
+ :linenos:
71
+ :emphasize-lines: 5
72
+
73
+ from lets_plot import *
74
+ LetsPlot.setup_html()
75
+ data = {'x': list(range(10)), 'y': list(range(10))}
76
+ ggplot(data, aes('x', 'y')) + geom_point(aes(size='y')) + \\
77
+ xlab('x axis label')
78
+
79
+ """
80
+ return labs(x=label)
81
+
82
+
83
+ def ylab(label):
84
+ """
85
+ Add label to the y-axis.
86
+
87
+ Parameters
88
+ ----------
89
+ label : str
90
+ The text for the y-axis label.
91
+
92
+ Returns
93
+ -------
94
+ ``FeatureSpec``
95
+ Axis label specification.
96
+
97
+ Examples
98
+ --------
99
+ .. jupyter-execute::
100
+ :linenos:
101
+ :emphasize-lines: 5
102
+
103
+ from lets_plot import *
104
+ LetsPlot.setup_html()
105
+ data = {'x': list(range(10)), 'y': list(range(10))}
106
+ ggplot(data, aes('x', 'y')) + geom_point(aes(size='y')) + \\
107
+ ylab('y axis label')
108
+
109
+ """
110
+ return labs(y=label)
111
+
112
+
113
+ def labs(title=None, subtitle=None, caption=None, **labels):
114
+ """
115
+ Change plot title, axis labels and legend titles.
116
+
117
+ Parameters
118
+ ----------
119
+ title : str
120
+ The text for the plot title.
121
+ subtitle : str
122
+ The text for the plot subtitle.
123
+ caption : str
124
+ The text for the plot caption.
125
+ labels
126
+ Name-value pairs where the name can be:
127
+
128
+ - An aesthetic name.
129
+ - 'manual' - a key referring to the default custom legend.
130
+ - A group name referring to a custom legend where the group is defined via the `layer_key() <https://lets-plot.org/python/pages/api/lets_plot.layer_key.html>`__ function.
131
+
132
+ The value should be a string, e.g. ``color="New Color label"``.
133
+
134
+ Returns
135
+ -------
136
+ ``FeatureSpec`` or ``FeatureSpecArray``
137
+ Labels specification.
138
+
139
+ Examples
140
+ --------
141
+ .. jupyter-execute::
142
+ :linenos:
143
+ :emphasize-lines: 5-6
144
+
145
+ from lets_plot import *
146
+ LetsPlot.setup_html()
147
+ data = {'x': list(range(10)), 'y': list(range(10))}
148
+ ggplot(data, aes('x', 'y')) + geom_point(aes(size='y')) + \\
149
+ labs(title='New plot title', subtitle='The plot subtitle', caption='The plot caption', \\
150
+ x='New x axis label', y='New y axis label', size='New legend title')
151
+
152
+ |
153
+
154
+ .. jupyter-execute::
155
+ :linenos:
156
+ :emphasize-lines: 11
157
+
158
+ import numpy as np
159
+ from lets_plot import *
160
+ LetsPlot.setup_html()
161
+ n = 10
162
+ np.random.seed(42)
163
+ x = list(range(n))
164
+ y = np.random.uniform(size=n)
165
+ ggplot({'x': x, 'y': y}, aes('x', 'y')) + \\
166
+ geom_point(color='red', manual_key="point") + \\
167
+ geom_line(color='blue', manual_key="line") + \\
168
+ labs(manual='Zones')
169
+
170
+ """
171
+ specs = []
172
+
173
+ # handle ggtitle
174
+ if title is not None or subtitle is not None:
175
+ specs.append(FeatureSpec('ggtitle', name=None, text=title, subtitle=subtitle))
176
+
177
+ # plot caption
178
+ if caption is not None:
179
+ specs.append(FeatureSpec('caption', name=None, text=caption))
180
+
181
+ # guides
182
+ for key, label in labels.items():
183
+ specs.append(guides(**{key: _guide(name=None, title=label)}))
184
+
185
+ if len(specs) == 1:
186
+ return specs[0]
187
+ return FeatureSpecArray(*specs)
@@ -0,0 +1,181 @@
1
+ # Copyright (c) 2022. 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 typing import Union
5
+
6
+ from .core import FeatureSpec, LayerSpec, DummySpec, FeatureSpecArray
7
+
8
+ __all__ = ["ggmarginal"]
9
+
10
+
11
+ def ggmarginal(sides: str, *, size=None, layer: Union[LayerSpec, FeatureSpecArray]) -> FeatureSpec:
12
+ """
13
+ Convert a given geometry layer to a marginal layer.
14
+ You can add one or more marginal layers to a plot to create a marginal plot.
15
+
16
+ Parameters
17
+ ----------
18
+ sides : str
19
+ A string specifying which sides of the plot the marginal layer will appear on.
20
+ It should be set to a string containing any of "trbl", for top, right, bottom, and left.
21
+ size : number or list of numbers, default=0.1
22
+ Size of marginal geometry (width or height, depending on the margin side) as a fraction of the entire
23
+ plotting area of the plot.
24
+ The value should be in range [0.01..0.95].
25
+ layer : ``LayerSpec``
26
+ A marginal geometry layer.
27
+ The result of calling of the ``geom_xxx()``/``stat_xxx()`` function.
28
+ Marginal plot works best with ``density``, ``histogram``, ``boxplot``, ``violin`` and ``freqpoly`` geometry layers.
29
+
30
+ Returns
31
+ -------
32
+ ``FeatureSpec``
33
+ An object specifying a marginal geometry layer or a list of marginal geometry layers.
34
+
35
+ Notes
36
+ -----
37
+ A marginal plot is a scatterplot (sometimes a 2D density plot or other bivariate plot) that has histograms,
38
+ boxplots, or other distribution visualization layers in the margins of the x- and y-axes.
39
+
40
+ Examples
41
+ --------
42
+ .. jupyter-execute::
43
+ :linenos:
44
+ :emphasize-lines: 23
45
+
46
+ import numpy as np
47
+ from lets_plot import *
48
+ LetsPlot.setup_html()
49
+ LetsPlot.set_theme(theme_light())
50
+
51
+ np.random.seed(0)
52
+
53
+ cov0=[[1, -.8],
54
+ [-.8, 1]]
55
+ cov1=[[ 10, .1],
56
+ [.1, .1]]
57
+
58
+ x0, y0 = np.random.multivariate_normal(mean=[-2,0], cov=cov0, size=200).T
59
+ x1, y1 = np.random.multivariate_normal(mean=[0,1], cov=cov1, size=200).T
60
+
61
+ data = dict(
62
+ x = np.concatenate((x0,x1)),
63
+ y = np.concatenate((y0,y1)),
64
+ c = ["A"]*200 + ["B"]*200
65
+ )
66
+
67
+ p = ggplot(data, aes("x", "y", color="c", fill="c")) + geom_point()
68
+ p + ggmarginal("tr", layer=geom_density(alpha=0.3, show_legend=False))
69
+
70
+ """
71
+
72
+ if not isinstance(sides, str):
73
+ raise TypeError("'sides' must be a string.")
74
+ if not 0 < len(sides) <= 4:
75
+ raise ValueError("'sides' must be a string containing 1 to 4 chars: 'l','r','t','b'.")
76
+
77
+ # In some cases (only boxplot so far) a layer may consist of multiple sub-layers of type LayerSpec.
78
+ if isinstance(layer, LayerSpec):
79
+ sublayers = [layer]
80
+ elif isinstance(layer, FeatureSpecArray):
81
+ for sublayer in layer.elements():
82
+ if not isinstance(sublayer, LayerSpec):
83
+ raise TypeError("Invalid 'layer' type: {}".format(type(sublayer)))
84
+ sublayers = layer.elements()
85
+ else:
86
+ raise TypeError("Invalid 'layer' type: {}".format(type(layer)))
87
+
88
+ result = DummySpec()
89
+
90
+ for sublayer in sublayers:
91
+ for i in range(len(sides)):
92
+ side = sides[i]
93
+ margin_size = _to_size(size, i)
94
+ marginal_layer = _to_marginal(side, margin_size, sublayer)
95
+ result = result + marginal_layer
96
+
97
+ return result
98
+
99
+
100
+ def _to_size(size, side_index: int) -> float:
101
+ if size is None:
102
+ return None
103
+
104
+ if isinstance(size, float):
105
+ return size
106
+
107
+ if not (isinstance(size, list) or isinstance(size, tuple)):
108
+ raise TypeError("Invalid 'size' type: {}. Expected: float, list or tuple.".format(type(size)))
109
+
110
+ try:
111
+ return size[side_index]
112
+ except IndexError:
113
+ return None
114
+
115
+
116
+ def _to_marginal(side: str, size, layer: LayerSpec) -> LayerSpec:
117
+ if side not in ['l', 'r', 't', 'b']:
118
+ raise ValueError("Invalid 'side' value: {}. Valid values: 'l','r','t','b'.".format(side))
119
+
120
+ if size is not None:
121
+ if not 0.01 <= size <= 0.95:
122
+ raise ValueError("Invalid 'size' value: {}. Should be in range [0.01..0.95].".format(size))
123
+
124
+ layer_copy = LayerSpec.duplicate(layer)
125
+ marginal_options = dict(
126
+ marginal=True,
127
+ margin_side=side,
128
+ margin_size=size
129
+ )
130
+
131
+ layer_props = layer_copy.props()
132
+ layer_props.update(marginal_options)
133
+
134
+ layer_kind = None
135
+ stat = layer_props.get('stat')
136
+ if stat is not None:
137
+ if stat == 'bin':
138
+ layer_kind = 'histogram'
139
+ elif stat == 'ydensity':
140
+ layer_kind = 'violin'
141
+ elif stat in ('density', 'boxplot', 'boxplot_outlier'):
142
+ layer_kind = stat
143
+ else:
144
+ geom = layer_props.get('geom')
145
+ if geom in ('histogram', 'boxplot', 'violin', 'density', 'freqpoly'):
146
+ layer_kind = geom
147
+
148
+ auto_settings = {}
149
+
150
+ # choose a proper orientation
151
+ if (side in ('l', 'r') and layer_kind in ('histogram', 'density', 'freqpoly')):
152
+ auto_settings['orientation'] = 'y'
153
+
154
+ if layer_kind in ('boxplot', 'boxplot_outlier', 'violin'):
155
+ if side in ('l', 'r'):
156
+ auto_settings['x'] = 0
157
+ elif side in ('t', 'b'):
158
+ auto_settings['y'] = 0
159
+ auto_settings['orientation'] = 'y'
160
+
161
+ # Update layer's options with auto-generated and try not to override user-defined options.
162
+ filtered = {k: v for k, v in layer_props.items() if v is not None}
163
+ layer_props.update(
164
+ {**auto_settings, **filtered}
165
+ )
166
+
167
+ # For 'histogram' set mapping of x or y to '..density..' for compatibility with 'density' geom.
168
+ if layer_kind == 'histogram':
169
+ if side in ('l', 'r'):
170
+ added_mapping = {'x': '..density..'}
171
+ elif side in ('t', 'b'):
172
+ added_mapping = {'y': '..density..'}
173
+
174
+ aes_feature_spec = layer_props.get('mapping')
175
+ mappings = aes_feature_spec.props() if isinstance(aes_feature_spec, FeatureSpec) else {}
176
+ filtered_mappings = {k: v for k, v in mappings.items() if v is not None}
177
+ updated_mappings = {**added_mapping, **filtered_mappings}
178
+ updated_aes_feature_spec = FeatureSpec('mapping', name=None, **updated_mappings)
179
+ layer_props['mapping'] = updated_aes_feature_spec
180
+
181
+ return layer_copy