From b66c269721118ed4f84f10317c62b9f2aa2230f0 Mon Sep 17 00:00:00 2001 From: ASmirnov-HORIS Date: Tue, 16 Feb 2021 12:56:36 +0300 Subject: [PATCH 01/81] Update docstring for geom_point() function. --- python-package/lets_plot/plot/geom.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/python-package/lets_plot/plot/geom.py b/python-package/lets_plot/plot/geom.py index e5c7259beea..14942573e5b 100644 --- a/python-package/lets_plot/plot/geom.py +++ b/python-package/lets_plot/plot/geom.py @@ -97,6 +97,12 @@ def geom_point(mapping=None, *, data=None, stat=None, position=None, show_legend The `map` parameter of `Geocoder` type implicitly invoke `centroids()` function. + Note + ---- + The `data` and `map` parameters of `GeoDataFrame` type support shapes `Point` and `MultiPoint`. + + The `map` parameter of `Geocoder` type implicitly invoke `centroids()` function. + Note ---- The conventions for the values of `map_join` parameter are as follows. From b760969266fe9cb0bb1a444b52cb73300ea10aa6 Mon Sep 17 00:00:00 2001 From: ASmirnov-HORIS Date: Thu, 25 Feb 2021 19:30:35 +0300 Subject: [PATCH 02/81] Docstring: Update data, map and map_join parameters description. --- python-package/lets_plot/plot/geom.py | 49 +++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/python-package/lets_plot/plot/geom.py b/python-package/lets_plot/plot/geom.py index 14942573e5b..6095c1e8517 100644 --- a/python-package/lets_plot/plot/geom.py +++ b/python-package/lets_plot/plot/geom.py @@ -298,6 +298,31 @@ def geom_path(mapping=None, *, data=None, stat=None, position=None, show_legend= - map_join=['County_name', 'State_Name'] + Note + ---- + The `data` and `map` parameters of `GeoDataFrame` type support shapes `LineString` and `MultiLineString`. + + Note + ---- + The conventions for the values of `map_join` parameter are as follows. + + - Joining data and `GeoDataFrame` object + + Data has a column named 'State_name' and `GeoDataFrame` has a matching column named 'state': + - map_join=['State_Name', 'state'] + - map_join=[['State_Name'], ['state']] + + - Joining data and `Geocoder` object + + Data has a column named 'State_name'. The matching key in `Geocoder` is always 'state' (providing it is a state-level geocoder) and can be omitted: + - map_join='State_Name' + - map_join=['State_Name'] + + - Joining data by composite key + + Joining by composite key works like in examples above, but instead of using a string for a simple key you need to use an array of strings for a composite key. The names in the composite key must be in the same order as in the US street addresses convention: 'city', 'county', 'state', 'country'. For example, the data has columns 'State_name' and 'County_name'. Joining with a 2-keys county level `Geocoder` object (the `Geocoder` keys 'county' and 'state' are omitted in this case): + - map_join=['County_name', 'State_Name'] + Examples -------- .. jupyter-execute:: @@ -1989,6 +2014,12 @@ def geom_polygon(mapping=None, *, data=None, stat=None, position=None, show_lege The `map` parameter of `Geocoder` type implicitly invoke `boundaries()` function. + Note + ---- + The `data` and `map` parameters of `GeoDataFrame` type support shapes `Polygon` and `MultiPolygon`. + + The `map` parameter of `Geocoder` type implicitly invoke `boundaries()` function. + Note ---- The conventions for the values of `map_join` parameter are as follows. @@ -2159,6 +2190,12 @@ def geom_map(mapping=None, *, data=None, stat=None, position=None, show_legend=N The `map` parameter of `Geocoder` type implicitly invoke `boundaries()` function. + Note + ---- + The `data` and `map` parameters of `GeoDataFrame` type support shapes `Polygon` and `MultiPolygon`. + + The `map` parameter of `Geocoder` type implicitly invoke `boundaries()` function. + Note ---- The conventions for the values of `map_join` parameter are as follows. @@ -3856,6 +3893,12 @@ def geom_rect(mapping=None, *, data=None, stat=None, position=None, show_legend= The `map` parameter of `Geocoder` type implicitly invoke `limits()` function. + Note + ---- + The `data` and `map` parameters of `GeoDataFrame` type support shapes `MultiPoint`, `Line`, `MultiLine`, `Polygon` and `MultiPolygon`. + + The `map` parameter of `Geocoder` type implicitly invoke `limits()` function. + Note ---- The conventions for the values of `map_join` parameter are as follows. @@ -4118,6 +4161,12 @@ def geom_text(mapping=None, *, data=None, stat=None, position=None, show_legend= The `map` parameter of `Geocoder` type implicitly invoke `centroids()` function. + Note + ---- + The `data` and `map` parameters of `GeoDataFrame` type support shapes `Point` and `MultiPoint`. + + The `map` parameter of `Geocoder` type implicitly invoke `centroids()` function. + Note ---- The conventions for the values of `map_join` parameter are as follows. From 639b4389dd17ab97b8a6ab351a959c0f398e2d79 Mon Sep 17 00:00:00 2001 From: EArkhipova-HORIS Date: Fri, 26 Feb 2021 11:53:39 +0300 Subject: [PATCH 03/81] Updated core.py docstrings. --- python-package/lets_plot/plot/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python-package/lets_plot/plot/core.py b/python-package/lets_plot/plot/core.py index 4c73a9dc7a5..868787105cd 100644 --- a/python-package/lets_plot/plot/core.py +++ b/python-package/lets_plot/plot/core.py @@ -61,7 +61,7 @@ def aes(x=None, y=None, **other): LetsPlot.setup_html() ggplot() + geom_polygon(aes(x=[0, 1, 2], y=[2, 1, 4]), \\ color='black', alpha=.5, size=1) - + """ return FeatureSpec('mapping', name=None, x=x, y=y, **other) From 7a6ba280c1003b37249c553106f63481c465b2de Mon Sep 17 00:00:00 2001 From: EArkhipova-HORIS Date: Tue, 2 Mar 2021 19:07:17 +0300 Subject: [PATCH 04/81] Minor fixes --- python-package/lets_plot/plot/coord.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python-package/lets_plot/plot/coord.py b/python-package/lets_plot/plot/coord.py index 2564850e466..1cdcbf37cc6 100644 --- a/python-package/lets_plot/plot/coord.py +++ b/python-package/lets_plot/plot/coord.py @@ -123,7 +123,7 @@ def coord_map(xlim=None, ylim=None): -------- .. jupyter-execute:: :linenos: - :emphasize-lines: 6 + :emphasize-lines: 5 from lets_plot import * from lets_plot.geo_data import * From 3003b515c085f9f148add68639a21a7781b1b89b Mon Sep 17 00:00:00 2001 From: EArkhipova-HORIS Date: Wed, 3 Mar 2021 19:34:03 +0300 Subject: [PATCH 05/81] Update examples in coord.py docstrings, update geom_extras.py docstrings --- python-package/lets_plot/plot/coord.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/python-package/lets_plot/plot/coord.py b/python-package/lets_plot/plot/coord.py index 1cdcbf37cc6..45c00cc52d2 100644 --- a/python-package/lets_plot/plot/coord.py +++ b/python-package/lets_plot/plot/coord.py @@ -48,6 +48,7 @@ def coord_cartesian(xlim=None, ylim=None): 'g': ['a', 'a', 'b', 'b', 'c', 'c']} ggplot(data) + geom_line(aes(x='x', y='y', group='g')) + \\ coord_cartesian(xlim=(4, 23), ylim=(3, 22)) + """ return _coord('cartesian', xlim=xlim, ylim=ylim) @@ -92,6 +93,7 @@ def coord_fixed(ratio=1., xlim=None, ylim=None): y = 25 * x ** 2 + np.random.normal(size=n) ggplot({'x': x, 'y': y}, aes(x='x', y='y')) + \\ geom_point() + coord_fixed(ratio=.2, ylim=(7, 20)) + """ return _coord('fixed', ratio=ratio, xlim=xlim, ylim=ylim) @@ -123,7 +125,7 @@ def coord_map(xlim=None, ylim=None): -------- .. jupyter-execute:: :linenos: - :emphasize-lines: 5 + :emphasize-lines: 6 from lets_plot import * from lets_plot.geo_data import * @@ -131,6 +133,7 @@ def coord_map(xlim=None, ylim=None): us = geocode_states('US-48').get_boundaries(4) ggplot() + geom_map(map=us, fill='gray', color='white') + \\ coord_map(xlim=(-130, -100)) + """ return _coord('map', xlim=xlim, ylim=ylim) From f27c9258c37791925ca5f64ac6ee3443a0cb9753 Mon Sep 17 00:00:00 2001 From: ASmirnov-HORIS Date: Fri, 12 Mar 2021 18:35:22 +0300 Subject: [PATCH 06/81] Docstrings: replace >>> to :linenos: in geom.py. --- python-package/lets_plot/plot/geom.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/python-package/lets_plot/plot/geom.py b/python-package/lets_plot/plot/geom.py index 6095c1e8517..03fc0b29836 100644 --- a/python-package/lets_plot/plot/geom.py +++ b/python-package/lets_plot/plot/geom.py @@ -357,6 +357,7 @@ def geom_path(mapping=None, *, data=None, stat=None, position=None, show_legend= scale_color_discrete() """ + return _geom('path', mapping=mapping, data=data, @@ -3281,6 +3282,8 @@ def geom_density2d(mapping=None, *, data=None, stat=None, position=None, show_le j * 400, i * 400, 400, 400) bunch.show() + | + .. jupyter-execute:: :linenos: :emphasize-lines: 9-10 From 19f7c85674e19ed76e08eb58bf0fccd342368e30 Mon Sep 17 00:00:00 2001 From: EArkhipova-HORIS Date: Mon, 15 Mar 2021 18:58:34 +0300 Subject: [PATCH 07/81] Update examples in plot.py, update docstring in label.py. --- python-package/lets_plot/plot/plot.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/python-package/lets_plot/plot/plot.py b/python-package/lets_plot/plot/plot.py index 38e43bf3262..9028a731915 100644 --- a/python-package/lets_plot/plot/plot.py +++ b/python-package/lets_plot/plot/plot.py @@ -201,6 +201,9 @@ def add_plot(self, plot_spec: PlotSpec, x, y, width=None, height=None): self.items.append(dict(feature_spec=plot_spec, x=x, y=y, width=width, height=height)) def as_dict(self): + """ + Translate self to dictionary. + """ d = super().as_dict() d['kind'] = self.kind From d33102bb5755ecbde21fda6cfce97918f7c24394 Mon Sep 17 00:00:00 2001 From: ASmirnov-HORIS Date: Wed, 17 Mar 2021 18:49:47 +0300 Subject: [PATCH 08/81] Update docstring for the geocode() function. --- python-package/lets_plot/geo_data/core.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/python-package/lets_plot/geo_data/core.py b/python-package/lets_plot/geo_data/core.py index 167729c4723..a4e758e0214 100644 --- a/python-package/lets_plot/geo_data/core.py +++ b/python-package/lets_plot/geo_data/core.py @@ -279,7 +279,11 @@ def regions_city(request=None, within=None): def geocode(level=None, names=None, countries=None, states=None, counties=None, scope=None) -> NamesGeocoder: """ +<<<<<<< HEAD Create a `Geocoder`. Allows to refine ambiguous request with `where()` method, +======= + Create a `Geocoder`. Allows to refine ambiguous request with where method, +>>>>>>> Update docstring for the geocode() function. scope that limits area of geocoding or with parents. Parameters @@ -311,6 +315,7 @@ def geocode(level=None, names=None, countries=None, states=None, counties=None, -------- .. jupyter-execute:: :linenos: +<<<<<<< HEAD :emphasize-lines: 5 from IPython.display import display @@ -320,11 +325,18 @@ def geocode(level=None, names=None, countries=None, states=None, counties=None, states = geocode('state').scope('Italy').get_boundaries(6) display(states.head()) ggplot() + geom_map(data=states) +======= + :emphasize-lines: 2 + + from lets_plot.geo_data import * + geocode('state').scope('Italia').get_boundaries() +>>>>>>> Update docstring for the geocode() function. | .. jupyter-execute:: :linenos: +<<<<<<< HEAD :emphasize-lines: 5, 8 from IPython.display import display @@ -339,6 +351,14 @@ def geocode(level=None, names=None, countries=None, states=None, counties=None, ggplot() + \\ geom_livemap() + \\ geom_point(data=cities, tooltips=layer_tooltips().line('@{found name}')) +======= + :emphasize-lines: 2, 4 + + from lets_plot.geo_data import * + states = geocode(level='state', scope='US').get_geocodes().state + names = ['York'] * len(states) + geocode(names=names, states=states).ignore_not_found().get_centroids() +>>>>>>> Update docstring for the geocode() function. """ return NamesGeocoder(level, names) \ From 33afa100e1102c81308bd16423701a8da0be49e3 Mon Sep 17 00:00:00 2001 From: ASmirnov-HORIS Date: Fri, 19 Mar 2021 15:56:15 +0300 Subject: [PATCH 09/81] Update docstrings in geo_data. --- python-package/lets_plot/geo_data/core.py | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/python-package/lets_plot/geo_data/core.py b/python-package/lets_plot/geo_data/core.py index a4e758e0214..167729c4723 100644 --- a/python-package/lets_plot/geo_data/core.py +++ b/python-package/lets_plot/geo_data/core.py @@ -279,11 +279,7 @@ def regions_city(request=None, within=None): def geocode(level=None, names=None, countries=None, states=None, counties=None, scope=None) -> NamesGeocoder: """ -<<<<<<< HEAD Create a `Geocoder`. Allows to refine ambiguous request with `where()` method, -======= - Create a `Geocoder`. Allows to refine ambiguous request with where method, ->>>>>>> Update docstring for the geocode() function. scope that limits area of geocoding or with parents. Parameters @@ -315,7 +311,6 @@ def geocode(level=None, names=None, countries=None, states=None, counties=None, -------- .. jupyter-execute:: :linenos: -<<<<<<< HEAD :emphasize-lines: 5 from IPython.display import display @@ -325,18 +320,11 @@ def geocode(level=None, names=None, countries=None, states=None, counties=None, states = geocode('state').scope('Italy').get_boundaries(6) display(states.head()) ggplot() + geom_map(data=states) -======= - :emphasize-lines: 2 - - from lets_plot.geo_data import * - geocode('state').scope('Italia').get_boundaries() ->>>>>>> Update docstring for the geocode() function. | .. jupyter-execute:: :linenos: -<<<<<<< HEAD :emphasize-lines: 5, 8 from IPython.display import display @@ -351,14 +339,6 @@ def geocode(level=None, names=None, countries=None, states=None, counties=None, ggplot() + \\ geom_livemap() + \\ geom_point(data=cities, tooltips=layer_tooltips().line('@{found name}')) -======= - :emphasize-lines: 2, 4 - - from lets_plot.geo_data import * - states = geocode(level='state', scope='US').get_geocodes().state - names = ['York'] * len(states) - geocode(names=names, states=states).ignore_not_found().get_centroids() ->>>>>>> Update docstring for the geocode() function. """ return NamesGeocoder(level, names) \ From b33d33187386286f727000daa57cb8fd0dfd12d5 Mon Sep 17 00:00:00 2001 From: ASmirnov-HORIS Date: Wed, 24 Mar 2021 14:43:05 +0300 Subject: [PATCH 10/81] Add link to documentation page for formatting in tooltips and geom_text docstrings. --- python-package/lets_plot/plot/geom.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/python-package/lets_plot/plot/geom.py b/python-package/lets_plot/plot/geom.py index 03fc0b29836..01dda84f60f 100644 --- a/python-package/lets_plot/plot/geom.py +++ b/python-package/lets_plot/plot/geom.py @@ -4194,6 +4194,10 @@ def geom_text(mapping=None, *, data=None, stat=None, position=None, show_legend= - map_join=['County_name', 'State_Name'] + Note + ---- + To learn more about formatting, please visit the `documentation page `_. + Examples -------- .. jupyter-execute:: From ab0cb8c739d25c6962c777f03cc822cd97dc5ba2 Mon Sep 17 00:00:00 2001 From: ASmirnov-HORIS Date: Wed, 24 Mar 2021 15:20:12 +0300 Subject: [PATCH 11/81] Fix lists in docstrings notes (according to Numpydoc and rst rules). --- python-package/lets_plot/plot/geom.py | 65 --------------------------- python-package/lets_plot/plot/plot.py | 3 -- 2 files changed, 68 deletions(-) diff --git a/python-package/lets_plot/plot/geom.py b/python-package/lets_plot/plot/geom.py index 01dda84f60f..3f201012e03 100644 --- a/python-package/lets_plot/plot/geom.py +++ b/python-package/lets_plot/plot/geom.py @@ -97,12 +97,6 @@ def geom_point(mapping=None, *, data=None, stat=None, position=None, show_legend The `map` parameter of `Geocoder` type implicitly invoke `centroids()` function. - Note - ---- - The `data` and `map` parameters of `GeoDataFrame` type support shapes `Point` and `MultiPoint`. - - The `map` parameter of `Geocoder` type implicitly invoke `centroids()` function. - Note ---- The conventions for the values of `map_join` parameter are as follows. @@ -298,31 +292,6 @@ def geom_path(mapping=None, *, data=None, stat=None, position=None, show_legend= - map_join=['County_name', 'State_Name'] - Note - ---- - The `data` and `map` parameters of `GeoDataFrame` type support shapes `LineString` and `MultiLineString`. - - Note - ---- - The conventions for the values of `map_join` parameter are as follows. - - - Joining data and `GeoDataFrame` object - - Data has a column named 'State_name' and `GeoDataFrame` has a matching column named 'state': - - map_join=['State_Name', 'state'] - - map_join=[['State_Name'], ['state']] - - - Joining data and `Geocoder` object - - Data has a column named 'State_name'. The matching key in `Geocoder` is always 'state' (providing it is a state-level geocoder) and can be omitted: - - map_join='State_Name' - - map_join=['State_Name'] - - - Joining data by composite key - - Joining by composite key works like in examples above, but instead of using a string for a simple key you need to use an array of strings for a composite key. The names in the composite key must be in the same order as in the US street addresses convention: 'city', 'county', 'state', 'country'. For example, the data has columns 'State_name' and 'County_name'. Joining with a 2-keys county level `Geocoder` object (the `Geocoder` keys 'county' and 'state' are omitted in this case): - - map_join=['County_name', 'State_Name'] - Examples -------- .. jupyter-execute:: @@ -2015,12 +1984,6 @@ def geom_polygon(mapping=None, *, data=None, stat=None, position=None, show_lege The `map` parameter of `Geocoder` type implicitly invoke `boundaries()` function. - Note - ---- - The `data` and `map` parameters of `GeoDataFrame` type support shapes `Polygon` and `MultiPolygon`. - - The `map` parameter of `Geocoder` type implicitly invoke `boundaries()` function. - Note ---- The conventions for the values of `map_join` parameter are as follows. @@ -2191,12 +2154,6 @@ def geom_map(mapping=None, *, data=None, stat=None, position=None, show_legend=N The `map` parameter of `Geocoder` type implicitly invoke `boundaries()` function. - Note - ---- - The `data` and `map` parameters of `GeoDataFrame` type support shapes `Polygon` and `MultiPolygon`. - - The `map` parameter of `Geocoder` type implicitly invoke `boundaries()` function. - Note ---- The conventions for the values of `map_join` parameter are as follows. @@ -3199,11 +3156,6 @@ def geom_density2d(mapping=None, *, data=None, stat=None, position=None, show_le - size : lines width. Defines line width. - linetype : type of the line of border. Codes and names: 0 = 'blank', 1 = 'solid', 2 = 'dashed', 3 = 'dotted', 4 = 'dotdash', 5 = 'longdash', 6 = 'twodash'. - Note - ---- - 'density2d' statistical transformation combined with parameter value `contour=False` - could be used to draw heatmaps (see the example below). - Examples -------- .. jupyter-execute:: @@ -3388,11 +3340,6 @@ def geom_density2df(mapping=None, *, data=None, stat=None, position=None, show_l - alpha : transparency level of a layer. Understands numbers between 0 and 1. - fill : color of geometry filling. - Note - ---- - 'density2df' statistical transformation combined with parameter value `contour=False` - could be used to draw heatmaps (see the example below). - Examples -------- .. jupyter-execute:: @@ -3896,12 +3843,6 @@ def geom_rect(mapping=None, *, data=None, stat=None, position=None, show_legend= The `map` parameter of `Geocoder` type implicitly invoke `limits()` function. - Note - ---- - The `data` and `map` parameters of `GeoDataFrame` type support shapes `MultiPoint`, `Line`, `MultiLine`, `Polygon` and `MultiPolygon`. - - The `map` parameter of `Geocoder` type implicitly invoke `limits()` function. - Note ---- The conventions for the values of `map_join` parameter are as follows. @@ -4164,12 +4105,6 @@ def geom_text(mapping=None, *, data=None, stat=None, position=None, show_legend= The `map` parameter of `Geocoder` type implicitly invoke `centroids()` function. - Note - ---- - The `data` and `map` parameters of `GeoDataFrame` type support shapes `Point` and `MultiPoint`. - - The `map` parameter of `Geocoder` type implicitly invoke `centroids()` function. - Note ---- The conventions for the values of `map_join` parameter are as follows. diff --git a/python-package/lets_plot/plot/plot.py b/python-package/lets_plot/plot/plot.py index 9028a731915..38e43bf3262 100644 --- a/python-package/lets_plot/plot/plot.py +++ b/python-package/lets_plot/plot/plot.py @@ -201,9 +201,6 @@ def add_plot(self, plot_spec: PlotSpec, x, y, width=None, height=None): self.items.append(dict(feature_spec=plot_spec, x=x, y=y, width=width, height=height)) def as_dict(self): - """ - Translate self to dictionary. - """ d = super().as_dict() d['kind'] = self.kind From c8c1cedf8815ebcce52cabbb57c67e300138cc8a Mon Sep 17 00:00:00 2001 From: ASmirnov-HORIS Date: Wed, 24 Mar 2021 19:40:34 +0300 Subject: [PATCH 12/81] Update links in docstrings. --- python-package/lets_plot/plot/geom.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python-package/lets_plot/plot/geom.py b/python-package/lets_plot/plot/geom.py index 3f201012e03..c17668525dc 100644 --- a/python-package/lets_plot/plot/geom.py +++ b/python-package/lets_plot/plot/geom.py @@ -4131,7 +4131,7 @@ def geom_text(mapping=None, *, data=None, stat=None, position=None, show_legend= Note ---- - To learn more about formatting, please visit the `documentation page `_. + For more info see the `formatting reference `_. Examples -------- From f019eada69d2dbccb3a12e51365617599a2665ce Mon Sep 17 00:00:00 2001 From: ASmirnov-HORIS Date: Fri, 20 Aug 2021 12:39:30 +0300 Subject: [PATCH 13/81] Fix errors of the rebase conflicts resolving. --- python-package/lets_plot/plot/geom.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/python-package/lets_plot/plot/geom.py b/python-package/lets_plot/plot/geom.py index c17668525dc..f968aae8b2f 100644 --- a/python-package/lets_plot/plot/geom.py +++ b/python-package/lets_plot/plot/geom.py @@ -3156,6 +3156,11 @@ def geom_density2d(mapping=None, *, data=None, stat=None, position=None, show_le - size : lines width. Defines line width. - linetype : type of the line of border. Codes and names: 0 = 'blank', 1 = 'solid', 2 = 'dashed', 3 = 'dotted', 4 = 'dotdash', 5 = 'longdash', 6 = 'twodash'. + Note + ---- + 'density2d' statistical transformation combined with parameter value `contour=False` + could be used to draw heatmaps (see the example below). + Examples -------- .. jupyter-execute:: @@ -3340,6 +3345,11 @@ def geom_density2df(mapping=None, *, data=None, stat=None, position=None, show_l - alpha : transparency level of a layer. Understands numbers between 0 and 1. - fill : color of geometry filling. + Note + ---- + 'density2df' statistical transformation combined with parameter value `contour=False` + could be used to draw heatmaps (see the example below). + Examples -------- .. jupyter-execute:: @@ -4129,10 +4139,6 @@ def geom_text(mapping=None, *, data=None, stat=None, position=None, show_legend= - map_join=['County_name', 'State_Name'] - Note - ---- - For more info see the `formatting reference `_. - Examples -------- .. jupyter-execute:: From b0580ea6516aa12df222f8e6fb139fb10559d7d0 Mon Sep 17 00:00:00 2001 From: ASmirnov-HORIS Date: Fri, 15 Oct 2021 12:42:49 +0300 Subject: [PATCH 14/81] Add violin geom without any functionality. --- .../jetbrains/datalore/plot/base/GeomKind.kt | 1 + .../jetbrains/datalore/plot/base/GeomMeta.kt | 2 + .../plot/base/aes/AestheticsDefaults.kt | 4 ++ .../datalore/plot/base/geom/ViolinGeom.kt | 27 ++++++++++++++ .../builder/assemble/geom/GeomProvider.kt | 9 +++++ .../datalore/plot/config/GeomProto.kt | 1 + .../plot/config/GeomProtoClientSide.kt | 6 +++ .../jetbrains/datalore/plot/config/Option.kt | 7 ++++ .../plotDemo/model/plotConfig/Violin.kt | 37 +++++++++++++++++++ .../kotlin/plotDemo/plotConfig/ViolinJfx.kt | 18 +++++++++ python-package/lets_plot/plot/geom.py | 15 +++++++- 11 files changed, 126 insertions(+), 1 deletion(-) create mode 100644 plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt create mode 100644 plot-demo-common/src/commonMain/kotlin/jetbrains/datalore/plotDemo/model/plotConfig/Violin.kt create mode 100644 plot-demo/src/jvmJfxMain/kotlin/plotDemo/plotConfig/ViolinJfx.kt diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/GeomKind.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/GeomKind.kt index 5aff2f9a848..a1af1860cb4 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/GeomKind.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/GeomKind.kt @@ -23,6 +23,7 @@ enum class GeomKind { H_LINE, V_LINE, BOX_PLOT, + VIOLIN, LIVE_MAP, POINT, RIBBON, diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/GeomMeta.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/GeomMeta.kt index 77cfdd9af4b..10f5c9bc94c 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/GeomMeta.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/GeomMeta.kt @@ -211,6 +211,8 @@ object GeomMeta { Aes.WIDTH ) + GeomKind.VIOLIN -> listOf() + GeomKind.RIBBON -> listOf( Aes.X, Aes.YMIN, Aes.YMAX, diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/aes/AestheticsDefaults.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/aes/AestheticsDefaults.kt index 68e0c41c722..4e801c07b39 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/aes/AestheticsDefaults.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/aes/AestheticsDefaults.kt @@ -143,6 +143,10 @@ open class AestheticsDefaults { return crossBar() } + fun violin(): AestheticsDefaults { + return base() + } + fun livemap(displayMode: LivemapConstants.DisplayMode): AestheticsDefaults { return when (displayMode) { LivemapConstants.DisplayMode.POINT -> point() diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt new file mode 100644 index 00000000000..b61594d91ed --- /dev/null +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2021. JetBrains s.r.o. + * Use of this source code is governed by the MIT license that can be found in the LICENSE file. + */ + +package jetbrains.datalore.plot.base.geom + +import jetbrains.datalore.plot.base.* +import jetbrains.datalore.plot.base.render.SvgRoot + +class ViolinGeom : GeomBase() { + + override fun buildIntern( + root: SvgRoot, + aesthetics: Aesthetics, + pos: PositionAdjustment, + coord: CoordinateSystem, + ctx: GeomContext + ) { + // TODO + } + + companion object { + const val HANDLES_GROUPS = false + } + +} \ No newline at end of file diff --git a/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/assemble/geom/GeomProvider.kt b/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/assemble/geom/GeomProvider.kt index d16e1799997..e12bd82a1a7 100644 --- a/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/assemble/geom/GeomProvider.kt +++ b/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/assemble/geom/GeomProvider.kt @@ -228,6 +228,15 @@ abstract class GeomProvider private constructor(val geomKind: GeomKind) { ).build() } + fun violin(supplier: () -> Geom): GeomProvider { + return GeomProviderBuilder( + GeomKind.VIOLIN, + AestheticsDefaults.violin(), + ViolinGeom.HANDLES_GROUPS, + supplier + ).build() + } + fun livemap( options: LiveMapOptions ): GeomProvider { diff --git a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/GeomProto.kt b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/GeomProto.kt index 43454091368..3dd49a6af95 100644 --- a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/GeomProto.kt +++ b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/GeomProto.kt @@ -45,6 +45,7 @@ open class GeomProto constructor(val geomKind: GeomKind) { H_LINE -> DefaultSampling.H_LINE V_LINE -> DefaultSampling.V_LINE BOX_PLOT -> Samplings.NONE // DefaultSampling.BOX_PLOT + VIOLIN -> Samplings.NONE // DefaultSampling.VIOLIN RIBBON -> DefaultSampling.RIBBON AREA -> DefaultSampling.AREA DENSITY -> DefaultSampling.DENSITY diff --git a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/GeomProtoClientSide.kt b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/GeomProtoClientSide.kt index dfe6a5b8903..bad8b6c551a 100644 --- a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/GeomProtoClientSide.kt +++ b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/GeomProtoClientSide.kt @@ -82,6 +82,11 @@ class GeomProtoClientSide(geomKind: GeomKind) : GeomProto(geomKind) { geom } + GeomKind.VIOLIN -> return GeomProvider.violin { + val geom = ViolinGeom() + geom + } + GeomKind.LIVE_MAP -> { return GeomProvider.livemap(parseFromLayerOptions(opts)) } @@ -191,6 +196,7 @@ class GeomProtoClientSide(geomKind: GeomKind) : GeomProto(geomKind) { PROVIDER[GeomKind.H_LINE] = GeomProvider.hline() PROVIDER[GeomKind.V_LINE] = GeomProvider.vline() // boxplot - special case + // violin - special case PROVIDER[GeomKind.RIBBON] = GeomProvider.ribbon() PROVIDER[GeomKind.AREA] = GeomProvider.area() PROVIDER[GeomKind.DENSITY] = GeomProvider.density() diff --git a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/Option.kt b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/Option.kt index b53c78c48a9..1a80ae2247d 100644 --- a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/Option.kt +++ b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/Option.kt @@ -7,6 +7,7 @@ package jetbrains.datalore.plot.config import jetbrains.datalore.plot.base.Aes import jetbrains.datalore.plot.base.GeomKind +import jetbrains.datalore.plot.base.GeomKind.VIOLIN import jetbrains.datalore.plot.builder.theme2.values.ThemeOption.ELEMENT_BLANK_SHORTHAND object Option { @@ -138,6 +139,10 @@ object Option { const val SIZE = "outlier_size" } + object Violin { + // TODO + } + object Jitter { const val WIDTH = "width" const val HEIGHT = "height" @@ -439,6 +444,7 @@ object Option { private const val H_LINE = "hline" private const val V_LINE = "vline" private const val BOX_PLOT = "boxplot" + private const val VIOLIN = "violin" const val LIVE_MAP = "livemap" const val POINT = "point" private const val RIBBON = "ribbon" @@ -478,6 +484,7 @@ object Option { map[H_LINE] = GeomKind.H_LINE map[V_LINE] = GeomKind.V_LINE map[BOX_PLOT] = GeomKind.BOX_PLOT + map[VIOLIN] = GeomKind.VIOLIN map[LIVE_MAP] = GeomKind.LIVE_MAP map[POINT] = GeomKind.POINT map[RIBBON] = GeomKind.RIBBON diff --git a/plot-demo-common/src/commonMain/kotlin/jetbrains/datalore/plotDemo/model/plotConfig/Violin.kt b/plot-demo-common/src/commonMain/kotlin/jetbrains/datalore/plotDemo/model/plotConfig/Violin.kt new file mode 100644 index 00000000000..61abb713d01 --- /dev/null +++ b/plot-demo-common/src/commonMain/kotlin/jetbrains/datalore/plotDemo/model/plotConfig/Violin.kt @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2021. JetBrains s.r.o. + * Use of this source code is governed by the MIT license that can be found in the LICENSE file. + */ + +package jetbrains.datalore.plotDemo.model.plotConfig + +import jetbrains.datalore.plot.parsePlotSpec +import jetbrains.datalore.plotDemo.data.Iris + +class Violin { + fun plotSpecList(): List> { + return listOf( + basic() + ) + } + + private fun basic(): MutableMap { + val spec = "{" + + " 'kind': 'plot'," + + " 'mapping': {" + + " 'x': 'sepal length (cm)'" + + " }," + + + " 'layers': [" + + " {" + + " 'geom': 'violin'" + + " }" + + " ]" + + "}" + + val plotSpec = HashMap(parsePlotSpec(spec)) + plotSpec["data"] = Iris.df + return plotSpec + + } +} \ No newline at end of file diff --git a/plot-demo/src/jvmJfxMain/kotlin/plotDemo/plotConfig/ViolinJfx.kt b/plot-demo/src/jvmJfxMain/kotlin/plotDemo/plotConfig/ViolinJfx.kt new file mode 100644 index 00000000000..0d5b66465ca --- /dev/null +++ b/plot-demo/src/jvmJfxMain/kotlin/plotDemo/plotConfig/ViolinJfx.kt @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2021. JetBrains s.r.o. + * Use of this source code is governed by the MIT license that can be found in the LICENSE file. + */ + +package jetbrains.datalore.plotDemo.plotConfig + +import jetbrains.datalore.plotDemo.model.plotConfig.Violin +import jetbrains.datalore.vis.demoUtils.PlotSpecsDemoWindowJfx + +fun main() { + with(Violin()) { + PlotSpecsDemoWindowJfx( + "Violin", + plotSpecList() + ).open() + } +} \ No newline at end of file diff --git a/python-package/lets_plot/plot/geom.py b/python-package/lets_plot/plot/geom.py index 8c43781c548..65a896e5011 100644 --- a/python-package/lets_plot/plot/geom.py +++ b/python-package/lets_plot/plot/geom.py @@ -17,7 +17,7 @@ 'geom_contour', 'geom_contourf', 'geom_polygon', 'geom_map', 'geom_abline', 'geom_hline', 'geom_vline', - 'geom_boxplot', + 'geom_boxplot', 'geom_violin', 'geom_ribbon', 'geom_area', 'geom_density', 'geom_density2d', 'geom_density2df', 'geom_jitter', 'geom_freqpoly', 'geom_step', 'geom_rect', 'geom_segment', @@ -2695,6 +2695,19 @@ def geom_boxplot(mapping=None, *, data=None, stat=None, position=None, show_lege **other_args) +def geom_violin(mapping=None, *, data=None, stat=None, position=None, show_legend=None, sampling=None, tooltips=None, + **other_args): + return _geom('violin', + mapping=mapping, + data=data, + stat=stat, + position=position, + show_legend=show_legend, + sampling=sampling, + tooltips=tooltips, + **other_args) + + def geom_ribbon(mapping=None, *, data=None, stat=None, position=None, show_legend=None, sampling=None, tooltips=None, **other_args): """ From 60b9d430bb181a952fab9b849fb2afacc1d48f48 Mon Sep 17 00:00:00 2001 From: ASmirnov-HORIS Date: Tue, 16 Feb 2021 12:56:36 +0300 Subject: [PATCH 15/81] Update docstring for geom_point() function. --- python-package/lets_plot/plot/geom.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/python-package/lets_plot/plot/geom.py b/python-package/lets_plot/plot/geom.py index 8c43781c548..4291b21b07c 100644 --- a/python-package/lets_plot/plot/geom.py +++ b/python-package/lets_plot/plot/geom.py @@ -97,6 +97,12 @@ def geom_point(mapping=None, *, data=None, stat=None, position=None, show_legend The `map` parameter of `Geocoder` type implicitly invoke `centroids()` function. + Note + ---- + The `data` and `map` parameters of `GeoDataFrame` type support shapes `Point` and `MultiPoint`. + + The `map` parameter of `Geocoder` type implicitly invoke `centroids()` function. + Note ---- The conventions for the values of `map_join` parameter are as follows. From 7d56a677ef29c5ba7807ab8bcbe22a3500c3643e Mon Sep 17 00:00:00 2001 From: ASmirnov-HORIS Date: Thu, 25 Feb 2021 19:30:35 +0300 Subject: [PATCH 16/81] Docstring: Update data, map and map_join parameters description. --- python-package/lets_plot/plot/geom.py | 49 +++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/python-package/lets_plot/plot/geom.py b/python-package/lets_plot/plot/geom.py index 4291b21b07c..e775391dbf6 100644 --- a/python-package/lets_plot/plot/geom.py +++ b/python-package/lets_plot/plot/geom.py @@ -298,6 +298,31 @@ def geom_path(mapping=None, *, data=None, stat=None, position=None, show_legend= - map_join=['County_name', 'State_Name'] + Note + ---- + The `data` and `map` parameters of `GeoDataFrame` type support shapes `LineString` and `MultiLineString`. + + Note + ---- + The conventions for the values of `map_join` parameter are as follows. + + - Joining data and `GeoDataFrame` object + + Data has a column named 'State_name' and `GeoDataFrame` has a matching column named 'state': + - map_join=['State_Name', 'state'] + - map_join=[['State_Name'], ['state']] + + - Joining data and `Geocoder` object + + Data has a column named 'State_name'. The matching key in `Geocoder` is always 'state' (providing it is a state-level geocoder) and can be omitted: + - map_join='State_Name' + - map_join=['State_Name'] + + - Joining data by composite key + + Joining by composite key works like in examples above, but instead of using a string for a simple key you need to use an array of strings for a composite key. The names in the composite key must be in the same order as in the US street addresses convention: 'city', 'county', 'state', 'country'. For example, the data has columns 'State_name' and 'County_name'. Joining with a 2-keys county level `Geocoder` object (the `Geocoder` keys 'county' and 'state' are omitted in this case): + - map_join=['County_name', 'State_Name'] + Examples -------- .. jupyter-execute:: @@ -1990,6 +2015,12 @@ def geom_polygon(mapping=None, *, data=None, stat=None, position=None, show_lege The `map` parameter of `Geocoder` type implicitly invoke `boundaries()` function. + Note + ---- + The `data` and `map` parameters of `GeoDataFrame` type support shapes `Polygon` and `MultiPolygon`. + + The `map` parameter of `Geocoder` type implicitly invoke `boundaries()` function. + Note ---- The conventions for the values of `map_join` parameter are as follows. @@ -2160,6 +2191,12 @@ def geom_map(mapping=None, *, data=None, stat=None, position=None, show_legend=N The `map` parameter of `Geocoder` type implicitly invoke `boundaries()` function. + Note + ---- + The `data` and `map` parameters of `GeoDataFrame` type support shapes `Polygon` and `MultiPolygon`. + + The `map` parameter of `Geocoder` type implicitly invoke `boundaries()` function. + Note ---- The conventions for the values of `map_join` parameter are as follows. @@ -3859,6 +3896,12 @@ def geom_rect(mapping=None, *, data=None, stat=None, position=None, show_legend= The `map` parameter of `Geocoder` type implicitly invoke `limits()` function. + Note + ---- + The `data` and `map` parameters of `GeoDataFrame` type support shapes `MultiPoint`, `Line`, `MultiLine`, `Polygon` and `MultiPolygon`. + + The `map` parameter of `Geocoder` type implicitly invoke `limits()` function. + Note ---- The conventions for the values of `map_join` parameter are as follows. @@ -4121,6 +4164,12 @@ def geom_text(mapping=None, *, data=None, stat=None, position=None, show_legend= The `map` parameter of `Geocoder` type implicitly invoke `centroids()` function. + Note + ---- + The `data` and `map` parameters of `GeoDataFrame` type support shapes `Point` and `MultiPoint`. + + The `map` parameter of `Geocoder` type implicitly invoke `centroids()` function. + Note ---- The conventions for the values of `map_join` parameter are as follows. From 0b81f55c0c77b13b1059f7d5cedd646b9ab96e82 Mon Sep 17 00:00:00 2001 From: EArkhipova-HORIS Date: Tue, 2 Mar 2021 19:07:17 +0300 Subject: [PATCH 17/81] Minor fixes --- python-package/lets_plot/plot/coord.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python-package/lets_plot/plot/coord.py b/python-package/lets_plot/plot/coord.py index ee20752d21c..7b2e06c7852 100644 --- a/python-package/lets_plot/plot/coord.py +++ b/python-package/lets_plot/plot/coord.py @@ -132,7 +132,7 @@ def coord_map(xlim=None, ylim=None, flip=False): -------- .. jupyter-execute:: :linenos: - :emphasize-lines: 6 + :emphasize-lines: 5 from lets_plot import * from lets_plot.geo_data import * From 43b1e8fd1a089ce17ea58e672fca68e64282ed1f Mon Sep 17 00:00:00 2001 From: EArkhipova-HORIS Date: Wed, 3 Mar 2021 19:34:03 +0300 Subject: [PATCH 18/81] Update examples in coord.py docstrings, update geom_extras.py docstrings --- python-package/lets_plot/plot/coord.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python-package/lets_plot/plot/coord.py b/python-package/lets_plot/plot/coord.py index 7b2e06c7852..ee20752d21c 100644 --- a/python-package/lets_plot/plot/coord.py +++ b/python-package/lets_plot/plot/coord.py @@ -132,7 +132,7 @@ def coord_map(xlim=None, ylim=None, flip=False): -------- .. jupyter-execute:: :linenos: - :emphasize-lines: 5 + :emphasize-lines: 6 from lets_plot import * from lets_plot.geo_data import * From 4beaf1e418d63a60a27243f80d4807bbe3a30726 Mon Sep 17 00:00:00 2001 From: EArkhipova-HORIS Date: Mon, 15 Mar 2021 18:58:34 +0300 Subject: [PATCH 19/81] Update examples in plot.py, update docstring in label.py. --- python-package/lets_plot/plot/plot.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/python-package/lets_plot/plot/plot.py b/python-package/lets_plot/plot/plot.py index 38e43bf3262..9028a731915 100644 --- a/python-package/lets_plot/plot/plot.py +++ b/python-package/lets_plot/plot/plot.py @@ -201,6 +201,9 @@ def add_plot(self, plot_spec: PlotSpec, x, y, width=None, height=None): self.items.append(dict(feature_spec=plot_spec, x=x, y=y, width=width, height=height)) def as_dict(self): + """ + Translate self to dictionary. + """ d = super().as_dict() d['kind'] = self.kind From 0b54bd50b894b1d22d88fb9d5b39c025df6161cc Mon Sep 17 00:00:00 2001 From: ASmirnov-HORIS Date: Wed, 24 Mar 2021 14:43:05 +0300 Subject: [PATCH 20/81] Add link to documentation page for formatting in tooltips and geom_text docstrings. --- python-package/lets_plot/plot/geom.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/python-package/lets_plot/plot/geom.py b/python-package/lets_plot/plot/geom.py index e775391dbf6..cdbbbce9b20 100644 --- a/python-package/lets_plot/plot/geom.py +++ b/python-package/lets_plot/plot/geom.py @@ -4194,6 +4194,10 @@ def geom_text(mapping=None, *, data=None, stat=None, position=None, show_legend= - map_join=['County_name', 'State_Name'] + Note + ---- + To learn more about formatting, please visit the `documentation page `_. + Examples -------- .. jupyter-execute:: From 48e15f691a8405d115316151fddb58eed80b2bc0 Mon Sep 17 00:00:00 2001 From: ASmirnov-HORIS Date: Wed, 24 Mar 2021 15:20:12 +0300 Subject: [PATCH 21/81] Fix lists in docstrings notes (according to Numpydoc and rst rules). --- python-package/lets_plot/plot/geom.py | 65 --------------------------- python-package/lets_plot/plot/plot.py | 3 -- 2 files changed, 68 deletions(-) diff --git a/python-package/lets_plot/plot/geom.py b/python-package/lets_plot/plot/geom.py index cdbbbce9b20..a2ace6a4c9d 100644 --- a/python-package/lets_plot/plot/geom.py +++ b/python-package/lets_plot/plot/geom.py @@ -97,12 +97,6 @@ def geom_point(mapping=None, *, data=None, stat=None, position=None, show_legend The `map` parameter of `Geocoder` type implicitly invoke `centroids()` function. - Note - ---- - The `data` and `map` parameters of `GeoDataFrame` type support shapes `Point` and `MultiPoint`. - - The `map` parameter of `Geocoder` type implicitly invoke `centroids()` function. - Note ---- The conventions for the values of `map_join` parameter are as follows. @@ -298,31 +292,6 @@ def geom_path(mapping=None, *, data=None, stat=None, position=None, show_legend= - map_join=['County_name', 'State_Name'] - Note - ---- - The `data` and `map` parameters of `GeoDataFrame` type support shapes `LineString` and `MultiLineString`. - - Note - ---- - The conventions for the values of `map_join` parameter are as follows. - - - Joining data and `GeoDataFrame` object - - Data has a column named 'State_name' and `GeoDataFrame` has a matching column named 'state': - - map_join=['State_Name', 'state'] - - map_join=[['State_Name'], ['state']] - - - Joining data and `Geocoder` object - - Data has a column named 'State_name'. The matching key in `Geocoder` is always 'state' (providing it is a state-level geocoder) and can be omitted: - - map_join='State_Name' - - map_join=['State_Name'] - - - Joining data by composite key - - Joining by composite key works like in examples above, but instead of using a string for a simple key you need to use an array of strings for a composite key. The names in the composite key must be in the same order as in the US street addresses convention: 'city', 'county', 'state', 'country'. For example, the data has columns 'State_name' and 'County_name'. Joining with a 2-keys county level `Geocoder` object (the `Geocoder` keys 'county' and 'state' are omitted in this case): - - map_join=['County_name', 'State_Name'] - Examples -------- .. jupyter-execute:: @@ -2015,12 +1984,6 @@ def geom_polygon(mapping=None, *, data=None, stat=None, position=None, show_lege The `map` parameter of `Geocoder` type implicitly invoke `boundaries()` function. - Note - ---- - The `data` and `map` parameters of `GeoDataFrame` type support shapes `Polygon` and `MultiPolygon`. - - The `map` parameter of `Geocoder` type implicitly invoke `boundaries()` function. - Note ---- The conventions for the values of `map_join` parameter are as follows. @@ -2191,12 +2154,6 @@ def geom_map(mapping=None, *, data=None, stat=None, position=None, show_legend=N The `map` parameter of `Geocoder` type implicitly invoke `boundaries()` function. - Note - ---- - The `data` and `map` parameters of `GeoDataFrame` type support shapes `Polygon` and `MultiPolygon`. - - The `map` parameter of `Geocoder` type implicitly invoke `boundaries()` function. - Note ---- The conventions for the values of `map_join` parameter are as follows. @@ -3199,11 +3156,6 @@ def geom_density2d(mapping=None, *, data=None, stat=None, position=None, show_le - size : lines width. Defines line width. - linetype : type of the line of border. Codes and names: 0 = 'blank', 1 = 'solid', 2 = 'dashed', 3 = 'dotted', 4 = 'dotdash', 5 = 'longdash', 6 = 'twodash'. - Note - ---- - 'density2d' statistical transformation combined with parameter value `contour=False` - could be used to draw heatmaps (see the example below). - Examples -------- .. jupyter-execute:: @@ -3388,11 +3340,6 @@ def geom_density2df(mapping=None, *, data=None, stat=None, position=None, show_l - alpha : transparency level of a layer. Understands numbers between 0 and 1. - fill : color of geometry filling. - Note - ---- - 'density2df' statistical transformation combined with parameter value `contour=False` - could be used to draw heatmaps (see the example below). - Examples -------- .. jupyter-execute:: @@ -3896,12 +3843,6 @@ def geom_rect(mapping=None, *, data=None, stat=None, position=None, show_legend= The `map` parameter of `Geocoder` type implicitly invoke `limits()` function. - Note - ---- - The `data` and `map` parameters of `GeoDataFrame` type support shapes `MultiPoint`, `Line`, `MultiLine`, `Polygon` and `MultiPolygon`. - - The `map` parameter of `Geocoder` type implicitly invoke `limits()` function. - Note ---- The conventions for the values of `map_join` parameter are as follows. @@ -4164,12 +4105,6 @@ def geom_text(mapping=None, *, data=None, stat=None, position=None, show_legend= The `map` parameter of `Geocoder` type implicitly invoke `centroids()` function. - Note - ---- - The `data` and `map` parameters of `GeoDataFrame` type support shapes `Point` and `MultiPoint`. - - The `map` parameter of `Geocoder` type implicitly invoke `centroids()` function. - Note ---- The conventions for the values of `map_join` parameter are as follows. diff --git a/python-package/lets_plot/plot/plot.py b/python-package/lets_plot/plot/plot.py index 9028a731915..38e43bf3262 100644 --- a/python-package/lets_plot/plot/plot.py +++ b/python-package/lets_plot/plot/plot.py @@ -201,9 +201,6 @@ def add_plot(self, plot_spec: PlotSpec, x, y, width=None, height=None): self.items.append(dict(feature_spec=plot_spec, x=x, y=y, width=width, height=height)) def as_dict(self): - """ - Translate self to dictionary. - """ d = super().as_dict() d['kind'] = self.kind From e99686a41dad42efaa927ecadef0166ba2d02d8b Mon Sep 17 00:00:00 2001 From: ASmirnov-HORIS Date: Wed, 24 Mar 2021 19:40:34 +0300 Subject: [PATCH 22/81] Update links in docstrings. --- python-package/lets_plot/plot/geom.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python-package/lets_plot/plot/geom.py b/python-package/lets_plot/plot/geom.py index a2ace6a4c9d..443d617f0c0 100644 --- a/python-package/lets_plot/plot/geom.py +++ b/python-package/lets_plot/plot/geom.py @@ -4131,7 +4131,7 @@ def geom_text(mapping=None, *, data=None, stat=None, position=None, show_legend= Note ---- - To learn more about formatting, please visit the `documentation page `_. + For more info see the `formatting reference `_. Examples -------- From c4a0b86d9e0237fa1eb5be0dcafe367617303a16 Mon Sep 17 00:00:00 2001 From: ASmirnov-HORIS Date: Fri, 20 Aug 2021 12:39:30 +0300 Subject: [PATCH 23/81] Fix errors of the rebase conflicts resolving. --- python-package/lets_plot/plot/geom.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/python-package/lets_plot/plot/geom.py b/python-package/lets_plot/plot/geom.py index 443d617f0c0..8c43781c548 100644 --- a/python-package/lets_plot/plot/geom.py +++ b/python-package/lets_plot/plot/geom.py @@ -3156,6 +3156,11 @@ def geom_density2d(mapping=None, *, data=None, stat=None, position=None, show_le - size : lines width. Defines line width. - linetype : type of the line of border. Codes and names: 0 = 'blank', 1 = 'solid', 2 = 'dashed', 3 = 'dotted', 4 = 'dotdash', 5 = 'longdash', 6 = 'twodash'. + Note + ---- + 'density2d' statistical transformation combined with parameter value `contour=False` + could be used to draw heatmaps (see the example below). + Examples -------- .. jupyter-execute:: @@ -3340,6 +3345,11 @@ def geom_density2df(mapping=None, *, data=None, stat=None, position=None, show_l - alpha : transparency level of a layer. Understands numbers between 0 and 1. - fill : color of geometry filling. + Note + ---- + 'density2df' statistical transformation combined with parameter value `contour=False` + could be used to draw heatmaps (see the example below). + Examples -------- .. jupyter-execute:: @@ -4129,10 +4139,6 @@ def geom_text(mapping=None, *, data=None, stat=None, position=None, show_legend= - map_join=['County_name', 'State_Name'] - Note - ---- - For more info see the `formatting reference `_. - Examples -------- .. jupyter-execute:: From fbdac79c7263dfaad38858bf827fc3bb344da062 Mon Sep 17 00:00:00 2001 From: ASmirnov-HORIS Date: Mon, 1 Nov 2021 17:43:11 +0300 Subject: [PATCH 24/81] Fix in plot-demo-common/.../Violin. --- .../jetbrains/datalore/plotDemo/model/plotConfig/Violin.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plot-demo-common/src/commonMain/kotlin/jetbrains/datalore/plotDemo/model/plotConfig/Violin.kt b/plot-demo-common/src/commonMain/kotlin/jetbrains/datalore/plotDemo/model/plotConfig/Violin.kt index 61abb713d01..058cb3fc8a3 100644 --- a/plot-demo-common/src/commonMain/kotlin/jetbrains/datalore/plotDemo/model/plotConfig/Violin.kt +++ b/plot-demo-common/src/commonMain/kotlin/jetbrains/datalore/plotDemo/model/plotConfig/Violin.kt @@ -19,7 +19,8 @@ class Violin { val spec = "{" + " 'kind': 'plot'," + " 'mapping': {" + - " 'x': 'sepal length (cm)'" + + " 'x': 'target'" + + " 'y': 'sepal length (cm)'" + " }," + " 'layers': [" + From 3966ed852fbbf5c8cd8ddd2d9c58dbdd4e30c405 Mon Sep 17 00:00:00 2001 From: ASmirnov-HORIS Date: Tue, 2 Nov 2021 15:44:40 +0300 Subject: [PATCH 25/81] Trying to show at least vertical line for violin geom. --- .../plot/base/aes/AestheticsDefaults.kt | 4 ++- .../datalore/plot/base/geom/ViolinGeom.kt | 27 ++++++++++++++++++- .../datalore/plot/config/GeomProto.kt | 9 +++++++ .../plot/config/GeomProtoClientSide.kt | 3 +-- 4 files changed, 39 insertions(+), 4 deletions(-) diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/aes/AestheticsDefaults.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/aes/AestheticsDefaults.kt index 4e801c07b39..1af0cd854b0 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/aes/AestheticsDefaults.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/aes/AestheticsDefaults.kt @@ -144,7 +144,9 @@ open class AestheticsDefaults { } fun violin(): AestheticsDefaults { - return base() + return AestheticsDefaults() + .update(Aes.COLOR, Color.BLACK) + .update(Aes.FILL, Color.WHITE) } fun livemap(displayMode: LivemapConstants.DisplayMode): AestheticsDefaults { diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt index b61594d91ed..d72a20db238 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt @@ -5,7 +5,10 @@ package jetbrains.datalore.plot.base.geom +import jetbrains.datalore.base.geometry.DoubleVector import jetbrains.datalore.plot.base.* +import jetbrains.datalore.plot.base.geom.util.GeomHelper +import jetbrains.datalore.plot.base.geom.util.GeomUtil import jetbrains.datalore.plot.base.render.SvgRoot class ViolinGeom : GeomBase() { @@ -17,7 +20,29 @@ class ViolinGeom : GeomBase() { coord: CoordinateSystem, ctx: GeomContext ) { - // TODO + buildLines(root, aesthetics, pos, coord, ctx) + } + + private fun buildLines( + root: SvgRoot, + aesthetics: Aesthetics, + pos: PositionAdjustment, + coord: CoordinateSystem, + ctx: GeomContext + ) { + val geomHelper = GeomHelper(pos, coord, ctx) + val helper = geomHelper.createSvgElementHelper() + + for (p in GeomUtil.withDefined(aesthetics.dataPoints(), Aes.X, Aes.YMIN, Aes.YMAX)) { + val x = p.x()!! + val ymin = p.ymin()!! + val ymax = p.ymax()!! + + val start = DoubleVector(x, ymin) + val end = DoubleVector(x, ymax) + val line = helper.createLine(start, end, p) + root.add(line) + } } companion object { diff --git a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/GeomProto.kt b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/GeomProto.kt index 3dd49a6af95..fe90b4caae2 100644 --- a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/GeomProto.kt +++ b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/GeomProto.kt @@ -104,6 +104,8 @@ open class GeomProto constructor(val geomKind: GeomKind) { crossBarDefaults() DEFAULTS[BOX_PLOT] = boxplotDefaults() + DEFAULTS[VIOLIN] = + violinDefaults() DEFAULTS[AREA] = areaDefaults() DEFAULTS[DENSITY] = @@ -171,6 +173,13 @@ open class GeomProto constructor(val geomKind: GeomKind) { return defaults } + private fun violinDefaults(): Map { + val defaults = HashMap() + defaults["stat"] = "boxplot" // TODO: "violin" + defaults["position"] = mapOf(Meta.NAME to "dodge", "width" to 0.95) + return defaults + } + private fun areaDefaults(): Map { val defaults = HashMap() defaults["stat"] = "identity" diff --git a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/GeomProtoClientSide.kt b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/GeomProtoClientSide.kt index bad8b6c551a..5cd245b3834 100644 --- a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/GeomProtoClientSide.kt +++ b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/GeomProtoClientSide.kt @@ -83,8 +83,7 @@ class GeomProtoClientSide(geomKind: GeomKind) : GeomProto(geomKind) { } GeomKind.VIOLIN -> return GeomProvider.violin { - val geom = ViolinGeom() - geom + ViolinGeom() } GeomKind.LIVE_MAP -> { From 0819ad1fb42ac664a51e7ff1db76bb3104dc1d0d Mon Sep 17 00:00:00 2001 From: ASmirnov-HORIS Date: Thu, 18 Nov 2021 16:23:37 +0300 Subject: [PATCH 26/81] Fix aesthetics for the violin geom. --- .../jetbrains/datalore/plot/base/GeomMeta.kt | 6 +++++- .../plotDemo/model/plotConfig/Violin.kt | 2 +- .../kotlin/plotDemo/plotConfig/ViolinBatik.kt | 18 ++++++++++++++++++ 3 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 plot-demo/src/jvmBatikMain/kotlin/plotDemo/plotConfig/ViolinBatik.kt diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/GeomMeta.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/GeomMeta.kt index 10f5c9bc94c..726fff86767 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/GeomMeta.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/GeomMeta.kt @@ -211,7 +211,11 @@ object GeomMeta { Aes.WIDTH ) - GeomKind.VIOLIN -> listOf() + GeomKind.VIOLIN -> listOf( + Aes.X, + Aes.YMIN, + Aes.YMAX + ) GeomKind.RIBBON -> listOf( Aes.X, diff --git a/plot-demo-common/src/commonMain/kotlin/jetbrains/datalore/plotDemo/model/plotConfig/Violin.kt b/plot-demo-common/src/commonMain/kotlin/jetbrains/datalore/plotDemo/model/plotConfig/Violin.kt index 058cb3fc8a3..1acda78d27d 100644 --- a/plot-demo-common/src/commonMain/kotlin/jetbrains/datalore/plotDemo/model/plotConfig/Violin.kt +++ b/plot-demo-common/src/commonMain/kotlin/jetbrains/datalore/plotDemo/model/plotConfig/Violin.kt @@ -19,7 +19,7 @@ class Violin { val spec = "{" + " 'kind': 'plot'," + " 'mapping': {" + - " 'x': 'target'" + + " 'x': 'target'," + " 'y': 'sepal length (cm)'" + " }," + diff --git a/plot-demo/src/jvmBatikMain/kotlin/plotDemo/plotConfig/ViolinBatik.kt b/plot-demo/src/jvmBatikMain/kotlin/plotDemo/plotConfig/ViolinBatik.kt new file mode 100644 index 00000000000..5d9ae14dd8e --- /dev/null +++ b/plot-demo/src/jvmBatikMain/kotlin/plotDemo/plotConfig/ViolinBatik.kt @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2021. JetBrains s.r.o. + * Use of this source code is governed by the MIT license that can be found in the LICENSE file. + */ + +package jetbrains.datalore.plotDemo.plotConfig + +import jetbrains.datalore.plotDemo.model.plotConfig.Violin +import jetbrains.datalore.vis.demoUtils.PlotSpecsDemoWindowBatik + +fun main() { + with(Violin()) { + PlotSpecsDemoWindowBatik( + "Violin plot", + plotSpecList() + ).open() + } +} \ No newline at end of file From 0d060b059e27a9485a9a622e7394217a94b5be7f Mon Sep 17 00:00:00 2001 From: ASmirnov-HORIS Date: Thu, 18 Nov 2021 19:19:44 +0300 Subject: [PATCH 27/81] Replace statistic for violin geom to its own. --- .../datalore/plot/base/stat/ViolinStat.kt | 93 +++++++++++++++++++ .../datalore/plot/config/GeomProto.kt | 2 +- .../datalore/plot/config/StatKind.kt | 1 + .../datalore/plot/config/StatProto.kt | 6 ++ 4 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/ViolinStat.kt diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/ViolinStat.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/ViolinStat.kt new file mode 100644 index 00000000000..3882c6815a3 --- /dev/null +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/ViolinStat.kt @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2021. JetBrains s.r.o. + * Use of this source code is governed by the MIT license that can be found in the LICENSE file. + */ + +package jetbrains.datalore.plot.base.stat + +import jetbrains.datalore.plot.base.Aes +import jetbrains.datalore.plot.base.DataFrame +import jetbrains.datalore.plot.base.StatContext +import jetbrains.datalore.plot.base.data.TransformVar +import jetbrains.datalore.plot.common.data.SeriesUtil + +class ViolinStat : BaseStat(DEF_MAPPING) { + + override fun consumes(): List> { + return listOf(Aes.X, Aes.Y) + } + + override fun apply(data: DataFrame, statCtx: StatContext, messageConsumer: (s: String) -> Unit): DataFrame { + if (!hasRequiredValues(data, Aes.Y)) { + return withEmptyStatValues() + } + + val ys = data.getNumeric(TransformVar.Y) + val xs = if (data.has(TransformVar.X)) { + data.getNumeric(TransformVar.X) + } else { + List(ys.size) { 0.0 } + } + + val statData = buildStat(xs, ys) + + val statCount = statData.remove(Stats.COUNT) + if (statCount == null || statCount.all { it == 0.0 }) { + return withEmptyStatValues() + } + + val builder = DataFrame.Builder() + for ((variable, series) in statData) { + builder.putNumeric(variable, series) + } + return builder.build() + } + + companion object { + private val DEF_MAPPING: Map, DataFrame.Variable> = mapOf( + Aes.X to Stats.X, + Aes.YMIN to Stats.Y_MIN, + Aes.YMAX to Stats.Y_MAX + ) + + fun buildStat( + xs: List, + ys: List + ): MutableMap> { + + val xyPairs = xs.zip(ys).filter { (x, y) -> + SeriesUtil.allFinite(x, y) + } + if (xyPairs.isEmpty()) { + return mutableMapOf() + } + + val binnedData: MutableMap> = HashMap() + for ((x, y) in xyPairs) { + binnedData.getOrPut(x!!) { ArrayList() }.add(y!!) + } + + val statX = ArrayList() + val statMin = ArrayList() + val statMax = ArrayList() + val statCount = ArrayList() + + for ((x, bin) in binnedData) { + val count = bin.size.toDouble() + val summary = FiveNumberSummary(bin) + + statX.add(x) + statMin.add(summary.min) + statMax.add(summary.max) + statCount.add(count) + } + + return mutableMapOf( + Stats.X to statX, + Stats.Y_MIN to statMin, + Stats.Y_MAX to statMax, + Stats.COUNT to statCount, + ) + } + } +} \ No newline at end of file diff --git a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/GeomProto.kt b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/GeomProto.kt index fe90b4caae2..df2702a71a6 100644 --- a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/GeomProto.kt +++ b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/GeomProto.kt @@ -175,7 +175,7 @@ open class GeomProto constructor(val geomKind: GeomKind) { private fun violinDefaults(): Map { val defaults = HashMap() - defaults["stat"] = "boxplot" // TODO: "violin" + defaults["stat"] = "violin" defaults["position"] = mapOf(Meta.NAME to "dodge", "width" to 0.95) return defaults } diff --git a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/StatKind.kt b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/StatKind.kt index 7ecdb4c0a05..a5d173edda5 100644 --- a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/StatKind.kt +++ b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/StatKind.kt @@ -16,6 +16,7 @@ enum class StatKind { CONTOUR, CONTOURF, BOXPLOT, + VIOLIN, DENSITY, DENSITY2D, DENSITY2DF, diff --git a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/StatProto.kt b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/StatProto.kt index c26f8e687c0..3dfd24ce023 100644 --- a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/StatProto.kt +++ b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/StatProto.kt @@ -96,6 +96,8 @@ object StatProto { ) } + StatKind.VIOLIN -> return configureViolinStat(options) + StatKind.DENSITY -> return configureDensityStat(options) StatKind.DENSITY2D -> return configureDensity2dStat(options, false) @@ -170,6 +172,10 @@ object StatProto { ) } + private fun configureViolinStat(options: OptionsAccessor): ViolinStat { + return ViolinStat() + } + private fun configureDensityStat(options: OptionsAccessor): DensityStat { var bwValue: Double? = null var bwMethod: DensityStat.BandWidthMethod = DensityStat.DEF_BW From 612617793aaef733bffe1c30dd23b52b61331ec3 Mon Sep 17 00:00:00 2001 From: ASmirnov-HORIS Date: Fri, 19 Nov 2021 16:21:57 +0300 Subject: [PATCH 28/81] Upgrade the violin statistic. --- .../jetbrains/datalore/plot/base/GeomMeta.kt | 4 +- .../datalore/plot/base/geom/ViolinGeom.kt | 14 +-- .../datalore/plot/base/stat/ViolinStat.kt | 113 ++++++++++++------ 3 files changed, 87 insertions(+), 44 deletions(-) diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/GeomMeta.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/GeomMeta.kt index 726fff86767..c6548224f22 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/GeomMeta.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/GeomMeta.kt @@ -213,8 +213,8 @@ object GeomMeta { GeomKind.VIOLIN -> listOf( Aes.X, - Aes.YMIN, - Aes.YMAX + Aes.Y, + Aes.WEIGHT ) GeomKind.RIBBON -> listOf( diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt index d72a20db238..1d0710a464b 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt @@ -30,16 +30,16 @@ class ViolinGeom : GeomBase() { coord: CoordinateSystem, ctx: GeomContext ) { + // TODO: Replace it by normal geometry builder val geomHelper = GeomHelper(pos, coord, ctx) val helper = geomHelper.createSvgElementHelper() - for (p in GeomUtil.withDefined(aesthetics.dataPoints(), Aes.X, Aes.YMIN, Aes.YMAX)) { - val x = p.x()!! - val ymin = p.ymin()!! - val ymax = p.ymax()!! - - val start = DoubleVector(x, ymin) - val end = DoubleVector(x, ymax) + for (p in GeomUtil.withDefined(aesthetics.dataPoints(), Aes.X, Aes.Y, Aes.WEIGHT)) { + val xStart = p.x()!! - 20 * p.weight()!! + val xEnd = p.x()!! + 20 * p.weight()!! + val y = p.y()!! + val start = DoubleVector(xStart, y) + val end = DoubleVector(xEnd, y) val line = helper.createLine(start, end, p) root.add(line) } diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/ViolinStat.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/ViolinStat.kt index 3882c6815a3..1120bb6491b 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/ViolinStat.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/ViolinStat.kt @@ -5,6 +5,7 @@ package jetbrains.datalore.plot.base.stat +import jetbrains.datalore.base.gcommon.collect.ClosedRange import jetbrains.datalore.plot.base.Aes import jetbrains.datalore.plot.base.DataFrame import jetbrains.datalore.plot.base.StatContext @@ -14,7 +15,7 @@ import jetbrains.datalore.plot.common.data.SeriesUtil class ViolinStat : BaseStat(DEF_MAPPING) { override fun consumes(): List> { - return listOf(Aes.X, Aes.Y) + return listOf(Aes.X, Aes.Y, Aes.WEIGHT) } override fun apply(data: DataFrame, statCtx: StatContext, messageConsumer: (s: String) -> Unit): DataFrame { @@ -22,19 +23,33 @@ class ViolinStat : BaseStat(DEF_MAPPING) { return withEmptyStatValues() } - val ys = data.getNumeric(TransformVar.Y) + val ys: List + val ws: List + // TODO: Move filtering and sorting into the buildStat() + if (data.has(TransformVar.WEIGHT)) { + val (ysFiltered, wsFiltered) = SeriesUtil.filterFinite( + data.getNumeric(TransformVar.Y), + data.getNumeric(TransformVar.WEIGHT) + ) + val (ysSorted, wsSorted) = (ysFiltered zip wsFiltered) + .sortedBy { it.first } + .unzip() + ys = ysSorted + ws = wsSorted + } else { + ys = data.getNumeric(TransformVar.Y) + .filterNotNull().filter { it.isFinite() } + .sorted() + ws = List(ys.size) { 1.0 } + } + if (ys.isEmpty()) return withEmptyStatValues() val xs = if (data.has(TransformVar.X)) { data.getNumeric(TransformVar.X) } else { - List(ys.size) { 0.0 } + List(ys.size) { 0.0 } } - val statData = buildStat(xs, ys) - - val statCount = statData.remove(Stats.COUNT) - if (statCount == null || statCount.all { it == 0.0 }) { - return withEmptyStatValues() - } + val statData = buildStat(xs, ys, ws) val builder = DataFrame.Builder() for ((variable, series) in statData) { @@ -46,47 +61,75 @@ class ViolinStat : BaseStat(DEF_MAPPING) { companion object { private val DEF_MAPPING: Map, DataFrame.Variable> = mapOf( Aes.X to Stats.X, - Aes.YMIN to Stats.Y_MIN, - Aes.YMAX to Stats.Y_MAX + Aes.Y to Stats.Y, + Aes.WEIGHT to Stats.DENSITY, ) fun buildStat( xs: List, - ys: List + ys: List, + ws: List ): MutableMap> { - val xyPairs = xs.zip(ys).filter { (x, y) -> - SeriesUtil.allFinite(x, y) - } - if (xyPairs.isEmpty()) { - return mutableMapOf() - } - - val binnedData: MutableMap> = HashMap() - for ((x, y) in xyPairs) { - binnedData.getOrPut(x!!) { ArrayList() }.add(y!!) + val binnedData: MutableMap, MutableList>> = HashMap() + for ((x, p) in xs zip (ys zip ws)) { + binnedData.getOrPut(x!!) { Pair(ArrayList(), ArrayList()) } + binnedData[x]?.first?.add(p.first!!) + binnedData[x]?.second?.add(p.second!!) } val statX = ArrayList() - val statMin = ArrayList() - val statMax = ArrayList() - val statCount = ArrayList() + val statY = ArrayList() + val statDensity = ArrayList() for ((x, bin) in binnedData) { - val count = bin.size.toDouble() - val summary = FiveNumberSummary(bin) - - statX.add(x) - statMin.add(summary.min) - statMax.add(summary.max) - statCount.add(count) + val y = bin.first + val weights = bin.second + val ySummary = FiveNumberSummary(y) + val rangeY = ClosedRange(ySummary.min, ySummary.max) + val n = 512 // TODO: Should be a parameter + val localStatY = DensityStatUtil.createStepValues(rangeY, n) + statX += MutableList(localStatY.size) { x } + statY += localStatY + val localStatDensity = ArrayList() + + val fullScalMax = 5000 // TODO: Should be a parameter + val kernel = DensityStat.Kernel.GAUSSIAN // TODO: Should be a parameter + val bandWidth = DensityStatUtil.bandWidth( + DensityStat.DEF_BW, + y + ) + val adjust = 1.0 // TODO: Should be a parameter + val kernelFun: (Double) -> Double = DensityStatUtil.kernel(kernel) + val densityFunction: (Double) -> Double = when (y.size <= fullScalMax) { + true -> DensityStatUtil.densityFunctionFullScan( + y, + weights, + kernelFun, + bandWidth, + adjust + ) + false -> DensityStatUtil.densityFunctionFast( + y, + weights, + kernelFun, + bandWidth, + adjust + ) + } + + val nTotal = weights.sum() + for (u in localStatY) { + val d = densityFunction(u) + localStatDensity.add(d / nTotal) + } + statDensity += localStatDensity } return mutableMapOf( Stats.X to statX, - Stats.Y_MIN to statMin, - Stats.Y_MAX to statMax, - Stats.COUNT to statCount, + Stats.Y to statY, + Stats.DENSITY to statDensity, ) } } From e0308a9b4cfc34e77243e4c16007b7b20e822104 Mon Sep 17 00:00:00 2001 From: ASmirnov-HORIS Date: Fri, 19 Nov 2021 18:17:59 +0300 Subject: [PATCH 29/81] Fix an error in the violin stat. --- .../datalore/plot/base/stat/ViolinStat.kt | 83 +++++++------------ 1 file changed, 32 insertions(+), 51 deletions(-) diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/ViolinStat.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/ViolinStat.kt index 1120bb6491b..79b3d3f8a8e 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/ViolinStat.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/ViolinStat.kt @@ -23,31 +23,17 @@ class ViolinStat : BaseStat(DEF_MAPPING) { return withEmptyStatValues() } - val ys: List - val ws: List - // TODO: Move filtering and sorting into the buildStat() - if (data.has(TransformVar.WEIGHT)) { - val (ysFiltered, wsFiltered) = SeriesUtil.filterFinite( - data.getNumeric(TransformVar.Y), - data.getNumeric(TransformVar.WEIGHT) - ) - val (ysSorted, wsSorted) = (ysFiltered zip wsFiltered) - .sortedBy { it.first } - .unzip() - ys = ysSorted - ws = wsSorted - } else { - ys = data.getNumeric(TransformVar.Y) - .filterNotNull().filter { it.isFinite() } - .sorted() - ws = List(ys.size) { 1.0 } - } - if (ys.isEmpty()) return withEmptyStatValues() + val ys = data.getNumeric(TransformVar.Y) val xs = if (data.has(TransformVar.X)) { data.getNumeric(TransformVar.X) } else { List(ys.size) { 0.0 } } + val ws = if (data.has(TransformVar.WEIGHT)) { + data.getNumeric(TransformVar.WEIGHT) + } else { + List(ys.size) { 1.0 } + } val statData = buildStat(xs, ys, ws) @@ -70,7 +56,6 @@ class ViolinStat : BaseStat(DEF_MAPPING) { ys: List, ws: List ): MutableMap> { - val binnedData: MutableMap, MutableList>> = HashMap() for ((x, p) in xs zip (ys zip ws)) { binnedData.getOrPut(x!!) { Pair(ArrayList(), ArrayList()) } @@ -83,53 +68,49 @@ class ViolinStat : BaseStat(DEF_MAPPING) { val statDensity = ArrayList() for ((x, bin) in binnedData) { - val y = bin.first - val weights = bin.second - val ySummary = FiveNumberSummary(y) + val (ysFiltered, wsFiltered) = SeriesUtil.filterFinite(bin.first, bin.second) + val (ysSorted, wsSorted) = (ysFiltered zip wsFiltered) + .sortedBy { it.first } + .unzip() + val binY = ysSorted.toMutableList() + val binW = wsSorted.toMutableList() + + val ySummary = FiveNumberSummary(binY) val rangeY = ClosedRange(ySummary.min, ySummary.max) - val n = 512 // TODO: Should be a parameter - val localStatY = DensityStatUtil.createStepValues(rangeY, n) - statX += MutableList(localStatY.size) { x } - statY += localStatY - val localStatDensity = ArrayList() - - val fullScalMax = 5000 // TODO: Should be a parameter - val kernel = DensityStat.Kernel.GAUSSIAN // TODO: Should be a parameter - val bandWidth = DensityStatUtil.bandWidth( - DensityStat.DEF_BW, - y - ) - val adjust = 1.0 // TODO: Should be a parameter - val kernelFun: (Double) -> Double = DensityStatUtil.kernel(kernel) - val densityFunction: (Double) -> Double = when (y.size <= fullScalMax) { + val localStatY = DensityStatUtil.createStepValues(rangeY, DensityStat.DEF_N) + + val bandWidth = DensityStatUtil.bandWidth(DensityStat.DEF_BW, binY) + val kernelFun: (Double) -> Double = DensityStatUtil.kernel(DensityStat.DEF_KERNEL) + val densityFunction: (Double) -> Double = when (binY.size <= DensityStat.DEF_FULL_SCAN_MAX) { true -> DensityStatUtil.densityFunctionFullScan( - y, - weights, + binY, + binW, kernelFun, bandWidth, - adjust + DensityStat.DEF_ADJUST ) false -> DensityStatUtil.densityFunctionFast( - y, - weights, + binY, + binW, kernelFun, bandWidth, - adjust + DensityStat.DEF_ADJUST ) } - val nTotal = weights.sum() - for (u in localStatY) { - val d = densityFunction(u) - localStatDensity.add(d / nTotal) + val nTotal = binW.sum() + for (y in localStatY) { + statDensity.add(densityFunction(y) / nTotal) } - statDensity += localStatDensity + + statX += MutableList(localStatY.size) { x } + statY += localStatY } return mutableMapOf( Stats.X to statX, Stats.Y to statY, - Stats.DENSITY to statDensity, + Stats.DENSITY to statDensity ) } } From 41d12b3ace750295f682d97cd878535b3d4898ad Mon Sep 17 00:00:00 2001 From: ASmirnov-HORIS Date: Mon, 22 Nov 2021 19:38:37 +0300 Subject: [PATCH 30/81] Upgrade the geometry of the violin. --- .../datalore/plot/base/geom/ViolinGeom.kt | 31 +++++++++++-------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt index 1d0710a464b..b2d9022dbb5 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt @@ -7,9 +7,10 @@ package jetbrains.datalore.plot.base.geom import jetbrains.datalore.base.geometry.DoubleVector import jetbrains.datalore.plot.base.* -import jetbrains.datalore.plot.base.geom.util.GeomHelper import jetbrains.datalore.plot.base.geom.util.GeomUtil +import jetbrains.datalore.plot.base.geom.util.LinesHelper import jetbrains.datalore.plot.base.render.SvgRoot +import jetbrains.datalore.plot.common.data.SeriesUtil class ViolinGeom : GeomBase() { @@ -30,19 +31,23 @@ class ViolinGeom : GeomBase() { coord: CoordinateSystem, ctx: GeomContext ) { - // TODO: Replace it by normal geometry builder - val geomHelper = GeomHelper(pos, coord, ctx) - val helper = geomHelper.createSvgElementHelper() - - for (p in GeomUtil.withDefined(aesthetics.dataPoints(), Aes.X, Aes.Y, Aes.WEIGHT)) { - val xStart = p.x()!! - 20 * p.weight()!! - val xEnd = p.x()!! + 20 * p.weight()!! - val y = p.y()!! - val start = DoubleVector(xStart, y) - val end = DoubleVector(xEnd, y) - val line = helper.createLine(start, end, p) - root.add(line) + val helper = LinesHelper(pos, coord, ctx) + val groupedDataPoints = aesthetics.dataPoints().groupBy { it.x()!! } + for ((_, nonOrderedDataPoints) in groupedDataPoints) { + val dataPoints = GeomUtil.ordered_Y(nonOrderedDataPoints, false) + val toLocationBound = fun (sign: Double): (p: DataPointAesthetics) -> DoubleVector? { + return fun (p: DataPointAesthetics): DoubleVector? { + val x = p.x()!! + 30 * sign * p.weight()!! // TODO: Remove magic constant + return if (SeriesUtil.isFinite(x) && SeriesUtil.isFinite(p.y())) { + DoubleVector(x, p.y()!!) + } else null + } + } + val paths = helper.createBands(dataPoints, toLocationBound(-1.0), toLocationBound(1.0)) + paths.reverse() + appendNodes(paths, root) } + // TODO: Build hints } companion object { From 2dd3595994cff1cbc7d235637a682d19e3bafe02 Mon Sep 17 00:00:00 2001 From: ASmirnov-HORIS Date: Tue, 23 Nov 2021 13:41:00 +0300 Subject: [PATCH 31/81] Fix width for the geom violin. Hints also added but stil doesn't work. --- .../jetbrains/datalore/plot/base/GeomMeta.kt | 8 +- .../datalore/plot/base/geom/ViolinGeom.kt | 78 +++++++++++++++---- .../builder/assemble/geom/GeomProvider.kt | 7 +- .../datalore/plot/config/GeomProto.kt | 3 +- .../plot/config/GeomProtoClientSide.kt | 6 +- .../jetbrains/datalore/plot/config/Option.kt | 4 - .../plotDemo/model/plotConfig/Violin.kt | 6 +- 7 files changed, 82 insertions(+), 30 deletions(-) diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/GeomMeta.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/GeomMeta.kt index c6548224f22..cb5b6dba72e 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/GeomMeta.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/GeomMeta.kt @@ -214,7 +214,13 @@ object GeomMeta { GeomKind.VIOLIN -> listOf( Aes.X, Aes.Y, - Aes.WEIGHT + Aes.WEIGHT, + + Aes.ALPHA, + Aes.COLOR, + Aes.FILL, + Aes.LINETYPE, + Aes.SIZE, // line width ) GeomKind.RIBBON -> listOf( diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt index b2d9022dbb5..c38951ccf4f 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt @@ -7,8 +7,9 @@ package jetbrains.datalore.plot.base.geom import jetbrains.datalore.base.geometry.DoubleVector import jetbrains.datalore.plot.base.* -import jetbrains.datalore.plot.base.geom.util.GeomUtil -import jetbrains.datalore.plot.base.geom.util.LinesHelper +import jetbrains.datalore.plot.base.geom.util.* +import jetbrains.datalore.plot.base.interact.GeomTargetCollector +import jetbrains.datalore.plot.base.interact.TipLayoutHint import jetbrains.datalore.plot.base.render.SvgRoot import jetbrains.datalore.plot.common.data.SeriesUtil @@ -33,25 +34,76 @@ class ViolinGeom : GeomBase() { ) { val helper = LinesHelper(pos, coord, ctx) val groupedDataPoints = aesthetics.dataPoints().groupBy { it.x()!! } + val halfWidth = halfWidth(aesthetics, ctx) for ((_, nonOrderedDataPoints) in groupedDataPoints) { val dataPoints = GeomUtil.ordered_Y(nonOrderedDataPoints, false) - val toLocationBound = fun (sign: Double): (p: DataPointAesthetics) -> DoubleVector? { - return fun (p: DataPointAesthetics): DoubleVector? { - val x = p.x()!! + 30 * sign * p.weight()!! // TODO: Remove magic constant - return if (SeriesUtil.isFinite(x) && SeriesUtil.isFinite(p.y())) { - DoubleVector(x, p.y()!!) - } else null - } - } - val paths = helper.createBands(dataPoints, toLocationBound(-1.0), toLocationBound(1.0)) + val paths = helper.createBands(dataPoints, toLocationBound(-1.0, halfWidth), toLocationBound(1.0, halfWidth)) paths.reverse() appendNodes(paths, root) + + helper.setAlphaEnabled(false) + appendNodes(helper.createLines(dataPoints, toLocationBound(-1.0, halfWidth)), root) + appendNodes(helper.createLines(dataPoints, toLocationBound(1.0, halfWidth)), root) + } + + buildHints(aesthetics, pos, coord, ctx, -1.0) + buildHints(aesthetics, pos, coord, ctx, 1.0) + } + + private fun buildHints(aesthetics: Aesthetics, pos: PositionAdjustment, coord: CoordinateSystem, ctx: GeomContext, sign: Double) { + val halfWidth = halfWidth(aesthetics, ctx) + val geomHelper = GeomHelper(pos, coord, ctx) + val multiPointDataList = MultiPointDataConstructor.createMultiPointDataByGroup( + aesthetics.dataPoints(), + MultiPointDataConstructor.singlePointAppender { p -> toClient(geomHelper, p, sign, halfWidth) }, + MultiPointDataConstructor.reducer(0.999, false) + ) + + val targetCollector = getGeomTargetCollector(ctx) + for (multiPointData in multiPointDataList) { + targetCollector.addPath( + multiPointData.points, + multiPointData.localToGlobalIndex, + setupTooltipParams(multiPointData.aes), + if (ctx.flipped) { + TipLayoutHint.Kind.VERTICAL_TOOLTIP + } else { + TipLayoutHint.Kind.HORIZONTAL_TOOLTIP + } + ) + } + } + + protected open fun setupTooltipParams(aes: DataPointAesthetics): GeomTargetCollector.TooltipParams { + return GeomTargetCollector.TooltipParams.params().setColor(HintColorUtil.fromFill(aes)) + } + + private fun toClient(geomHelper: GeomHelper, p: DataPointAesthetics, sign: Double, halfWidth: Double): DoubleVector? { + val coord = toLocationBound(sign, halfWidth)(p) + return if (coord != null) { + geomHelper.toClient(coord, p) + } else { + null + } + } + + private fun halfWidth(aesthetics: Aesthetics, ctx: GeomContext): Double { + val wMax = aesthetics.dataPoints().map { it.weight()!! }.maxOrNull()!! + return ctx.getResolution(Aes.X) / (2 * wMax) + } + + private fun toLocationBound(sign: Double, halfWidth: Double): (p: DataPointAesthetics) -> DoubleVector? { + return fun (p: DataPointAesthetics): DoubleVector? { + val x = p.x()!! + halfWidth * DEF_WIDTH * sign * p.weight()!! + return if (SeriesUtil.isFinite(x) && SeriesUtil.isFinite(p.y())) { + DoubleVector(x, p.y()!!) + } else null } - // TODO: Build hints } companion object { - const val HANDLES_GROUPS = false + const val HANDLES_GROUPS = true + const val DEF_WIDTH = 0.95 } } \ No newline at end of file diff --git a/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/assemble/geom/GeomProvider.kt b/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/assemble/geom/GeomProvider.kt index e12bd82a1a7..78376ee15cd 100644 --- a/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/assemble/geom/GeomProvider.kt +++ b/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/assemble/geom/GeomProvider.kt @@ -228,13 +228,12 @@ abstract class GeomProvider private constructor(val geomKind: GeomKind) { ).build() } - fun violin(supplier: () -> Geom): GeomProvider { + fun violin(): GeomProvider { return GeomProviderBuilder( GeomKind.VIOLIN, AestheticsDefaults.violin(), - ViolinGeom.HANDLES_GROUPS, - supplier - ).build() + ViolinGeom.HANDLES_GROUPS + ) { ViolinGeom() }.build() } fun livemap( diff --git a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/GeomProto.kt b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/GeomProto.kt index df2702a71a6..5eced9fac38 100644 --- a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/GeomProto.kt +++ b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/GeomProto.kt @@ -9,6 +9,7 @@ import jetbrains.datalore.plot.base.Aes import jetbrains.datalore.plot.base.GeomKind import jetbrains.datalore.plot.base.GeomKind.* import jetbrains.datalore.plot.base.GeomMeta +import jetbrains.datalore.plot.base.geom.ViolinGeom import jetbrains.datalore.plot.base.pos.PositionAdjustments import jetbrains.datalore.plot.builder.assemble.PosProvider import jetbrains.datalore.plot.builder.assemble.geom.DefaultSampling @@ -176,7 +177,7 @@ open class GeomProto constructor(val geomKind: GeomKind) { private fun violinDefaults(): Map { val defaults = HashMap() defaults["stat"] = "violin" - defaults["position"] = mapOf(Meta.NAME to "dodge", "width" to 0.95) + defaults["position"] = mapOf(Meta.NAME to "dodge", "width" to ViolinGeom.DEF_WIDTH) return defaults } diff --git a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/GeomProtoClientSide.kt b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/GeomProtoClientSide.kt index 5cd245b3834..0eaae1bd6c2 100644 --- a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/GeomProtoClientSide.kt +++ b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/GeomProtoClientSide.kt @@ -82,10 +82,6 @@ class GeomProtoClientSide(geomKind: GeomKind) : GeomProto(geomKind) { geom } - GeomKind.VIOLIN -> return GeomProvider.violin { - ViolinGeom() - } - GeomKind.LIVE_MAP -> { return GeomProvider.livemap(parseFromLayerOptions(opts)) } @@ -195,7 +191,7 @@ class GeomProtoClientSide(geomKind: GeomKind) : GeomProto(geomKind) { PROVIDER[GeomKind.H_LINE] = GeomProvider.hline() PROVIDER[GeomKind.V_LINE] = GeomProvider.vline() // boxplot - special case - // violin - special case + PROVIDER[GeomKind.VIOLIN] = GeomProvider.violin() PROVIDER[GeomKind.RIBBON] = GeomProvider.ribbon() PROVIDER[GeomKind.AREA] = GeomProvider.area() PROVIDER[GeomKind.DENSITY] = GeomProvider.density() diff --git a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/Option.kt b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/Option.kt index 9ebb8fc926d..eed948bee4d 100644 --- a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/Option.kt +++ b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/Option.kt @@ -139,10 +139,6 @@ object Option { const val SIZE = "outlier_size" } - object Violin { - // TODO - } - object Jitter { const val WIDTH = "width" const val HEIGHT = "height" diff --git a/plot-demo-common/src/commonMain/kotlin/jetbrains/datalore/plotDemo/model/plotConfig/Violin.kt b/plot-demo-common/src/commonMain/kotlin/jetbrains/datalore/plotDemo/model/plotConfig/Violin.kt index 1acda78d27d..81bf75c3e09 100644 --- a/plot-demo-common/src/commonMain/kotlin/jetbrains/datalore/plotDemo/model/plotConfig/Violin.kt +++ b/plot-demo-common/src/commonMain/kotlin/jetbrains/datalore/plotDemo/model/plotConfig/Violin.kt @@ -20,12 +20,14 @@ class Violin { " 'kind': 'plot'," + " 'mapping': {" + " 'x': 'target'," + - " 'y': 'sepal length (cm)'" + + " 'y': 'sepal length (cm)'," + + " 'fill': 'target'" + " }," + " 'layers': [" + " {" + - " 'geom': 'violin'" + + " 'geom': 'violin'," + + " 'alpha': 0.7" + " }" + " ]" + "}" From 432bab94e2944da636c113aa2969907756cb84d7 Mon Sep 17 00:00:00 2001 From: ASmirnov-HORIS Date: Tue, 23 Nov 2021 14:26:39 +0300 Subject: [PATCH 32/81] Fix tooltips for geom violin. --- .../jetbrains/datalore/plot/config/GeomInteractionUtil.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/GeomInteractionUtil.kt b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/GeomInteractionUtil.kt index 5f654a85c54..daf8e7cdf9e 100644 --- a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/GeomInteractionUtil.kt +++ b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/GeomInteractionUtil.kt @@ -279,7 +279,8 @@ object GeomInteractionUtil { GeomKind.POINT, GeomKind.JITTER, GeomKind.CONTOUR, - GeomKind.DENSITY2D -> return builder.bivariateFunction(GeomInteractionBuilder.NON_AREA_GEOM) + GeomKind.DENSITY2D, + GeomKind.VIOLIN -> return builder.bivariateFunction(GeomInteractionBuilder.NON_AREA_GEOM) GeomKind.PATH -> { when (statKind) { StatKind.CONTOUR, StatKind.CONTOURF, StatKind.DENSITY2D -> return builder.bivariateFunction( From c0fea5e688754f091edacb1c8707f0558e005146 Mon Sep 17 00:00:00 2001 From: ASmirnov-HORIS Date: Wed, 24 Nov 2021 18:05:17 +0300 Subject: [PATCH 33/81] Use the 'DensityStat' in the 'ViolinStat'. --- .../datalore/plot/base/stat/ViolinStat.kt | 74 +++++++++++++++++-- .../datalore/plot/config/StatProto.kt | 6 +- 2 files changed, 68 insertions(+), 12 deletions(-) diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/ViolinStat.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/ViolinStat.kt index 79b3d3f8a8e..5d9ee7a3707 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/ViolinStat.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/ViolinStat.kt @@ -19,6 +19,13 @@ class ViolinStat : BaseStat(DEF_MAPPING) { } override fun apply(data: DataFrame, statCtx: StatContext, messageConsumer: (s: String) -> Unit): DataFrame { + // TODO: return applyCustom(data, statCtx, messageConsumer) + return applyDensity(data, statCtx, messageConsumer) + } + + // TODO: v1 + private fun applyCustom(data: DataFrame, statCtx: StatContext, messageConsumer: (s: String) -> Unit): DataFrame { + if (!hasRequiredValues(data, Aes.Y)) { return withEmptyStatValues() } @@ -44,6 +51,62 @@ class ViolinStat : BaseStat(DEF_MAPPING) { return builder.build() } + // TODO: v2 + private fun applyDensity(data: DataFrame, statCtx: StatContext, messageConsumer: (s: String) -> Unit): DataFrame { + + if (!hasRequiredValues(data, Aes.Y)) { + return withEmptyStatValues() + } + + val ys = data.getNumeric(TransformVar.Y) + val xs = if (data.has(TransformVar.X)) { + data.getNumeric(TransformVar.X) + } else { + List(ys.size) { 0.0 } + } + val ws = if (data.has(TransformVar.WEIGHT)) { + data.getNumeric(TransformVar.WEIGHT) + } else { + List(ys.size) { 1.0 } + } + + val binnedData = (xs zip (ys zip ws)) + .groupBy({ it.first }, { it.second }) + .mapValues { it.value.unzip() } + + val statX = ArrayList() + val statY = ArrayList() + val statDensity = ArrayList() + + for ((x, bin) in binnedData) { + val binY = bin.first + val binW = bin.second + val bData = DataFrame.Builder() + .putNumeric(TransformVar.X, binY) + .putNumeric(TransformVar.WEIGHT, binW) + .build() + val bandWidth = DensityStatUtil.bandWidth(DensityStat.DEF_BW, binY) + val dStat = DensityStat( + bandWidth = bandWidth, + bandWidthMethod = DensityStat.DEF_BW, + adjust = DensityStat.DEF_ADJUST, + kernel = DensityStat.DEF_KERNEL, + n = DensityStat.DEF_N, + fullScalMax = DensityStat.DEF_FULL_SCAN_MAX + ) + val sData = dStat.apply(bData, SimpleStatContext(bData), messageConsumer) + statX += MutableList(sData.rowCount()) { x!! } + statY += sData.getNumeric(Stats.X).map { it!! } + statDensity += sData.getNumeric(Stats.DENSITY).map { it!! } + } + + return DataFrame.Builder() + .putNumeric(Stats.X, statX) + .putNumeric(Stats.Y, statY) + .putNumeric(Stats.DENSITY, statDensity) + .build() + } + companion object { private val DEF_MAPPING: Map, DataFrame.Variable> = mapOf( Aes.X to Stats.X, @@ -56,12 +119,9 @@ class ViolinStat : BaseStat(DEF_MAPPING) { ys: List, ws: List ): MutableMap> { - val binnedData: MutableMap, MutableList>> = HashMap() - for ((x, p) in xs zip (ys zip ws)) { - binnedData.getOrPut(x!!) { Pair(ArrayList(), ArrayList()) } - binnedData[x]?.first?.add(p.first!!) - binnedData[x]?.second?.add(p.second!!) - } + val binnedData = (xs zip (ys zip ws)) + .groupBy({ it.first }, { it.second }) + .mapValues { it.value.unzip() } val statX = ArrayList() val statY = ArrayList() @@ -103,7 +163,7 @@ class ViolinStat : BaseStat(DEF_MAPPING) { statDensity.add(densityFunction(y) / nTotal) } - statX += MutableList(localStatY.size) { x } + statX += MutableList(localStatY.size) { x!! } statY += localStatY } diff --git a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/StatProto.kt b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/StatProto.kt index 3dfd24ce023..0a2f3602b3b 100644 --- a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/StatProto.kt +++ b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/StatProto.kt @@ -96,7 +96,7 @@ object StatProto { ) } - StatKind.VIOLIN -> return configureViolinStat(options) + StatKind.VIOLIN -> return ViolinStat() StatKind.DENSITY -> return configureDensityStat(options) @@ -172,10 +172,6 @@ object StatProto { ) } - private fun configureViolinStat(options: OptionsAccessor): ViolinStat { - return ViolinStat() - } - private fun configureDensityStat(options: OptionsAccessor): DensityStat { var bwValue: Double? = null var bwMethod: DensityStat.BandWidthMethod = DensityStat.DEF_BW From 4b95827284a29a396fe7ca16d6e8ca1ca57fc5ec Mon Sep 17 00:00:00 2001 From: ASmirnov-HORIS Date: Thu, 25 Nov 2021 12:13:34 +0300 Subject: [PATCH 34/81] Remove the first version of the ViolinStat realization. Tiny fixes in the ViolinGeom. --- .../datalore/plot/base/geom/ViolinGeom.kt | 6 +- .../datalore/plot/base/stat/ViolinStat.kt | 142 +++++------------- 2 files changed, 39 insertions(+), 109 deletions(-) diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt index c38951ccf4f..347326c3b5b 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt @@ -33,7 +33,7 @@ class ViolinGeom : GeomBase() { ctx: GeomContext ) { val helper = LinesHelper(pos, coord, ctx) - val groupedDataPoints = aesthetics.dataPoints().groupBy { it.x()!! } + val groupedDataPoints = aesthetics.dataPoints().groupBy { it.x() } val halfWidth = halfWidth(aesthetics, ctx) for ((_, nonOrderedDataPoints) in groupedDataPoints) { val dataPoints = GeomUtil.ordered_Y(nonOrderedDataPoints, false) @@ -88,8 +88,8 @@ class ViolinGeom : GeomBase() { } private fun halfWidth(aesthetics: Aesthetics, ctx: GeomContext): Double { - val wMax = aesthetics.dataPoints().map { it.weight()!! }.maxOrNull()!! - return ctx.getResolution(Aes.X) / (2 * wMax) + val maxWeight: Double = aesthetics.dataPoints().map { it.weight()!! }.maxOrNull() ?: 0.0 + return ctx.getResolution(Aes.X) / (2 * maxWeight) } private fun toLocationBound(sign: Double, halfWidth: Double): (p: DataPointAesthetics) -> DoubleVector? { diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/ViolinStat.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/ViolinStat.kt index 5d9ee7a3707..cf9af42231d 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/ViolinStat.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/ViolinStat.kt @@ -5,27 +5,19 @@ package jetbrains.datalore.plot.base.stat -import jetbrains.datalore.base.gcommon.collect.ClosedRange import jetbrains.datalore.plot.base.Aes import jetbrains.datalore.plot.base.DataFrame import jetbrains.datalore.plot.base.StatContext import jetbrains.datalore.plot.base.data.TransformVar -import jetbrains.datalore.plot.common.data.SeriesUtil class ViolinStat : BaseStat(DEF_MAPPING) { + // TODO: Try to replace by XMIN, XMAX and Y override fun consumes(): List> { return listOf(Aes.X, Aes.Y, Aes.WEIGHT) } override fun apply(data: DataFrame, statCtx: StatContext, messageConsumer: (s: String) -> Unit): DataFrame { - // TODO: return applyCustom(data, statCtx, messageConsumer) - return applyDensity(data, statCtx, messageConsumer) - } - - // TODO: v1 - private fun applyCustom(data: DataFrame, statCtx: StatContext, messageConsumer: (s: String) -> Unit): DataFrame { - if (!hasRequiredValues(data, Aes.Y)) { return withEmptyStatValues() } @@ -42,7 +34,7 @@ class ViolinStat : BaseStat(DEF_MAPPING) { List(ys.size) { 1.0 } } - val statData = buildStat(xs, ys, ws) + val statData = buildStat(xs, ys, ws, messageConsumer) val builder = DataFrame.Builder() for ((variable, series) in statData) { @@ -51,62 +43,6 @@ class ViolinStat : BaseStat(DEF_MAPPING) { return builder.build() } - // TODO: v2 - private fun applyDensity(data: DataFrame, statCtx: StatContext, messageConsumer: (s: String) -> Unit): DataFrame { - - if (!hasRequiredValues(data, Aes.Y)) { - return withEmptyStatValues() - } - - val ys = data.getNumeric(TransformVar.Y) - val xs = if (data.has(TransformVar.X)) { - data.getNumeric(TransformVar.X) - } else { - List(ys.size) { 0.0 } - } - val ws = if (data.has(TransformVar.WEIGHT)) { - data.getNumeric(TransformVar.WEIGHT) - } else { - List(ys.size) { 1.0 } - } - - val binnedData = (xs zip (ys zip ws)) - .groupBy({ it.first }, { it.second }) - .mapValues { it.value.unzip() } - - val statX = ArrayList() - val statY = ArrayList() - val statDensity = ArrayList() - - for ((x, bin) in binnedData) { - val binY = bin.first - val binW = bin.second - val bData = DataFrame.Builder() - .putNumeric(TransformVar.X, binY) - .putNumeric(TransformVar.WEIGHT, binW) - .build() - val bandWidth = DensityStatUtil.bandWidth(DensityStat.DEF_BW, binY) - val dStat = DensityStat( - bandWidth = bandWidth, - bandWidthMethod = DensityStat.DEF_BW, - adjust = DensityStat.DEF_ADJUST, - kernel = DensityStat.DEF_KERNEL, - n = DensityStat.DEF_N, - fullScalMax = DensityStat.DEF_FULL_SCAN_MAX - ) - val sData = dStat.apply(bData, SimpleStatContext(bData), messageConsumer) - statX += MutableList(sData.rowCount()) { x!! } - statY += sData.getNumeric(Stats.X).map { it!! } - statDensity += sData.getNumeric(Stats.DENSITY).map { it!! } - } - - return DataFrame.Builder() - .putNumeric(Stats.X, statX) - .putNumeric(Stats.Y, statY) - .putNumeric(Stats.DENSITY, statDensity) - .build() - } - companion object { private val DEF_MAPPING: Map, DataFrame.Variable> = mapOf( Aes.X to Stats.X, @@ -114,13 +50,14 @@ class ViolinStat : BaseStat(DEF_MAPPING) { Aes.WEIGHT to Stats.DENSITY, ) - fun buildStat( + private fun buildStat( xs: List, ys: List, - ws: List + ws: List, + messageConsumer: (s: String) -> Unit ): MutableMap> { val binnedData = (xs zip (ys zip ws)) - .groupBy({ it.first }, { it.second }) + .groupBy({ it.first!! }, { it.second }) .mapValues { it.value.unzip() } val statX = ArrayList() @@ -128,43 +65,10 @@ class ViolinStat : BaseStat(DEF_MAPPING) { val statDensity = ArrayList() for ((x, bin) in binnedData) { - val (ysFiltered, wsFiltered) = SeriesUtil.filterFinite(bin.first, bin.second) - val (ysSorted, wsSorted) = (ysFiltered zip wsFiltered) - .sortedBy { it.first } - .unzip() - val binY = ysSorted.toMutableList() - val binW = wsSorted.toMutableList() - - val ySummary = FiveNumberSummary(binY) - val rangeY = ClosedRange(ySummary.min, ySummary.max) - val localStatY = DensityStatUtil.createStepValues(rangeY, DensityStat.DEF_N) - - val bandWidth = DensityStatUtil.bandWidth(DensityStat.DEF_BW, binY) - val kernelFun: (Double) -> Double = DensityStatUtil.kernel(DensityStat.DEF_KERNEL) - val densityFunction: (Double) -> Double = when (binY.size <= DensityStat.DEF_FULL_SCAN_MAX) { - true -> DensityStatUtil.densityFunctionFullScan( - binY, - binW, - kernelFun, - bandWidth, - DensityStat.DEF_ADJUST - ) - false -> DensityStatUtil.densityFunctionFast( - binY, - binW, - kernelFun, - bandWidth, - DensityStat.DEF_ADJUST - ) - } - - val nTotal = binW.sum() - for (y in localStatY) { - statDensity.add(densityFunction(y) / nTotal) - } - - statX += MutableList(localStatY.size) { x!! } - statY += localStatY + val statData = buildBinStat(bin.first, bin.second, messageConsumer) + statX += MutableList(statData.getValue(Stats.Y).size) { x } + statY += statData.getValue(Stats.Y) + statDensity += statData.getValue(Stats.DENSITY) } return mutableMapOf( @@ -173,5 +77,31 @@ class ViolinStat : BaseStat(DEF_MAPPING) { Stats.DENSITY to statDensity ) } + + private fun buildBinStat( + binY: List, + binW: List, + messageConsumer: (s: String) -> Unit + ): MutableMap> { + // TODO: Replace defaults by params + val stat = DensityStat( + bandWidth = DensityStatUtil.bandWidth(DensityStat.DEF_BW, binY), + bandWidthMethod = DensityStat.DEF_BW, + adjust = DensityStat.DEF_ADJUST, + kernel = DensityStat.DEF_KERNEL, + n = DensityStat.DEF_N, + fullScalMax = DensityStat.DEF_FULL_SCAN_MAX + ) + val data = DataFrame.Builder() + .putNumeric(TransformVar.X, binY) + .putNumeric(TransformVar.WEIGHT, binW) + .build() + val statData = stat.apply(data, SimpleStatContext(data), messageConsumer) + + return mutableMapOf( + Stats.Y to statData.getNumeric(Stats.X).requireNoNulls(), + Stats.DENSITY to statData.getNumeric(Stats.DENSITY).requireNoNulls() + ) + } } } \ No newline at end of file From 57b585074bfc02ff3e2ee7d9549d78d52d02b32b Mon Sep 17 00:00:00 2001 From: ASmirnov-HORIS Date: Fri, 26 Nov 2021 12:23:42 +0300 Subject: [PATCH 35/81] Tiny fixes. --- .../kotlin/jetbrains/datalore/plot/base/GeomMeta.kt | 2 +- .../kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt | 5 +++-- .../kotlin/jetbrains/datalore/plot/base/stat/ViolinStat.kt | 3 +-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/GeomMeta.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/GeomMeta.kt index cb5b6dba72e..2392e1ff8db 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/GeomMeta.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/GeomMeta.kt @@ -220,7 +220,7 @@ object GeomMeta { Aes.COLOR, Aes.FILL, Aes.LINETYPE, - Aes.SIZE, // line width + Aes.SIZE ) GeomKind.RIBBON -> listOf( diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt index 347326c3b5b..72da060d26a 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt @@ -95,8 +95,9 @@ class ViolinGeom : GeomBase() { private fun toLocationBound(sign: Double, halfWidth: Double): (p: DataPointAesthetics) -> DoubleVector? { return fun (p: DataPointAesthetics): DoubleVector? { val x = p.x()!! + halfWidth * DEF_WIDTH * sign * p.weight()!! - return if (SeriesUtil.isFinite(x) && SeriesUtil.isFinite(p.y())) { - DoubleVector(x, p.y()!!) + val y = p.y()!! + return if (SeriesUtil.isFinite(x) && SeriesUtil.isFinite(y)) { + DoubleVector(x, y) } else null } } diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/ViolinStat.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/ViolinStat.kt index cf9af42231d..19d2e47cbc5 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/ViolinStat.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/ViolinStat.kt @@ -12,7 +12,6 @@ import jetbrains.datalore.plot.base.data.TransformVar class ViolinStat : BaseStat(DEF_MAPPING) { - // TODO: Try to replace by XMIN, XMAX and Y override fun consumes(): List> { return listOf(Aes.X, Aes.Y, Aes.WEIGHT) } @@ -47,7 +46,7 @@ class ViolinStat : BaseStat(DEF_MAPPING) { private val DEF_MAPPING: Map, DataFrame.Variable> = mapOf( Aes.X to Stats.X, Aes.Y to Stats.Y, - Aes.WEIGHT to Stats.DENSITY, + Aes.WEIGHT to Stats.DENSITY ) private fun buildStat( From ebaf8340393772b182f3967ddf5fedfd13e9082e Mon Sep 17 00:00:00 2001 From: ASmirnov-HORIS Date: Fri, 26 Nov 2021 12:53:33 +0300 Subject: [PATCH 36/81] Add WEIGHT to the 'noGuideNeeded' aesthetics list. --- .../src/commonMain/kotlin/jetbrains/datalore/plot/base/Aes.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/Aes.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/Aes.kt index 2c55d533da2..7e13fb9fbd2 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/Aes.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/Aes.kt @@ -153,6 +153,7 @@ class Aes private constructor(val name: String, val isNumeric: Boolean = true aes == SLOPE || aes == WIDTH || aes == HEIGHT || + aes == WEIGHT || aes == HJUST || aes == VJUST || aes == ANGLE || From 770793a725ac04715c9d877c3bd3a713984c7faa Mon Sep 17 00:00:00 2001 From: ASmirnov-HORIS Date: Fri, 26 Nov 2021 13:04:38 +0300 Subject: [PATCH 37/81] Add statistics COUNT and SCALED to the ViolinStat. --- .../jetbrains/datalore/plot/base/stat/ViolinStat.kt | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/ViolinStat.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/ViolinStat.kt index 19d2e47cbc5..5d4d64a8c79 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/ViolinStat.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/ViolinStat.kt @@ -62,18 +62,24 @@ class ViolinStat : BaseStat(DEF_MAPPING) { val statX = ArrayList() val statY = ArrayList() val statDensity = ArrayList() + val statCount = ArrayList() + val statScaled = ArrayList() for ((x, bin) in binnedData) { val statData = buildBinStat(bin.first, bin.second, messageConsumer) statX += MutableList(statData.getValue(Stats.Y).size) { x } statY += statData.getValue(Stats.Y) statDensity += statData.getValue(Stats.DENSITY) + statCount += statData.getValue(Stats.COUNT) + statScaled += statData.getValue(Stats.SCALED) } return mutableMapOf( Stats.X to statX, Stats.Y to statY, - Stats.DENSITY to statDensity + Stats.DENSITY to statDensity, + Stats.COUNT to statCount, + Stats.SCALED to statScaled ) } @@ -99,7 +105,9 @@ class ViolinStat : BaseStat(DEF_MAPPING) { return mutableMapOf( Stats.Y to statData.getNumeric(Stats.X).requireNoNulls(), - Stats.DENSITY to statData.getNumeric(Stats.DENSITY).requireNoNulls() + Stats.DENSITY to statData.getNumeric(Stats.DENSITY).requireNoNulls(), + Stats.COUNT to statData.getNumeric(Stats.COUNT).requireNoNulls(), + Stats.SCALED to statData.getNumeric(Stats.SCALED).requireNoNulls() ) } } From 7b859e4bcf6997ba41315c088d0e119aed38bf7c Mon Sep 17 00:00:00 2001 From: ASmirnov-HORIS Date: Tue, 30 Nov 2021 15:16:05 +0300 Subject: [PATCH 38/81] Switch to the first version of the ViolinStat (with tiny modifications). --- .../datalore/plot/base/stat/ViolinStat.kt | 78 ++++++++++--------- 1 file changed, 43 insertions(+), 35 deletions(-) diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/ViolinStat.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/ViolinStat.kt index 5d4d64a8c79..b470af8eeed 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/ViolinStat.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/ViolinStat.kt @@ -5,10 +5,12 @@ package jetbrains.datalore.plot.base.stat +import jetbrains.datalore.base.gcommon.collect.ClosedRange import jetbrains.datalore.plot.base.Aes import jetbrains.datalore.plot.base.DataFrame import jetbrains.datalore.plot.base.StatContext import jetbrains.datalore.plot.base.data.TransformVar +import jetbrains.datalore.plot.common.data.SeriesUtil class ViolinStat : BaseStat(DEF_MAPPING) { @@ -33,7 +35,7 @@ class ViolinStat : BaseStat(DEF_MAPPING) { List(ys.size) { 1.0 } } - val statData = buildStat(xs, ys, ws, messageConsumer) + val statData = buildStat(xs, ys, ws) val builder = DataFrame.Builder() for ((variable, series) in statData) { @@ -52,8 +54,7 @@ class ViolinStat : BaseStat(DEF_MAPPING) { private fun buildStat( xs: List, ys: List, - ws: List, - messageConsumer: (s: String) -> Unit + ws: List ): MutableMap> { val binnedData = (xs zip (ys zip ws)) .groupBy({ it.first!! }, { it.second }) @@ -66,12 +67,23 @@ class ViolinStat : BaseStat(DEF_MAPPING) { val statScaled = ArrayList() for ((x, bin) in binnedData) { - val statData = buildBinStat(bin.first, bin.second, messageConsumer) - statX += MutableList(statData.getValue(Stats.Y).size) { x } - statY += statData.getValue(Stats.Y) - statDensity += statData.getValue(Stats.DENSITY) - statCount += statData.getValue(Stats.COUNT) - statScaled += statData.getValue(Stats.SCALED) + val (filteredY, filteredW) = SeriesUtil.filterFinite(bin.first, bin.second) + val (binY, binW) = (filteredY zip filteredW) + .sortedBy { it.first } + .unzip() + val ySummary = FiveNumberSummary(binY) + val rangeY = ClosedRange(ySummary.min, ySummary.max) + val binStatY = DensityStatUtil.createStepValues(rangeY, DensityStat.DEF_N) + val densityFunction = getDensityFunction(binY, binW) + val binStatCount = binStatY.map { densityFunction(it) } + val weightsSum = binW.sum() + val maxBinCount = binStatCount.maxOrNull()!! + + statX += MutableList(binStatY.size) { x } + statY += binStatY + statDensity += binStatCount.map { it / weightsSum } + statCount += binStatCount + statScaled += binStatCount.map { it / maxBinCount } } return mutableMapOf( @@ -83,32 +95,28 @@ class ViolinStat : BaseStat(DEF_MAPPING) { ) } - private fun buildBinStat( - binY: List, - binW: List, - messageConsumer: (s: String) -> Unit - ): MutableMap> { - // TODO: Replace defaults by params - val stat = DensityStat( - bandWidth = DensityStatUtil.bandWidth(DensityStat.DEF_BW, binY), - bandWidthMethod = DensityStat.DEF_BW, - adjust = DensityStat.DEF_ADJUST, - kernel = DensityStat.DEF_KERNEL, - n = DensityStat.DEF_N, - fullScalMax = DensityStat.DEF_FULL_SCAN_MAX - ) - val data = DataFrame.Builder() - .putNumeric(TransformVar.X, binY) - .putNumeric(TransformVar.WEIGHT, binW) - .build() - val statData = stat.apply(data, SimpleStatContext(data), messageConsumer) - - return mutableMapOf( - Stats.Y to statData.getNumeric(Stats.X).requireNoNulls(), - Stats.DENSITY to statData.getNumeric(Stats.DENSITY).requireNoNulls(), - Stats.COUNT to statData.getNumeric(Stats.COUNT).requireNoNulls(), - Stats.SCALED to statData.getNumeric(Stats.SCALED).requireNoNulls() - ) + private fun getDensityFunction( + binY: List, + binW: List + ): (Double) -> Double { + val bandWidth = DensityStatUtil.bandWidth(DensityStat.DEF_BW, binY) + val kernelFun: (Double) -> Double = DensityStatUtil.kernel(DensityStat.DEF_KERNEL) + return when (binY.size <= DensityStat.DEF_FULL_SCAN_MAX) { + true -> DensityStatUtil.densityFunctionFullScan( + binY, + binW, + kernelFun, + bandWidth, + DensityStat.DEF_ADJUST + ) + false -> DensityStatUtil.densityFunctionFast( + binY, + binW, + kernelFun, + bandWidth, + DensityStat.DEF_ADJUST + ) + } } } } \ No newline at end of file From b02d58c3fff37dac6698fe6636c937f05ed0a517 Mon Sep 17 00:00:00 2001 From: ASmirnov-HORIS Date: Wed, 1 Dec 2021 12:02:30 +0300 Subject: [PATCH 39/81] Small refactoring and new parameters in the ViolinStat. --- .../datalore/plot/base/geom/ViolinGeom.kt | 8 +- .../datalore/plot/base/stat/DensityStat.kt | 62 +++++---- .../datalore/plot/base/stat/ViolinStat.kt | 130 ++++++++---------- .../datalore/plot/config/StatProto.kt | 28 +++- 4 files changed, 127 insertions(+), 101 deletions(-) diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt index 72da060d26a..3fc0fd2760b 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt @@ -80,15 +80,11 @@ class ViolinGeom : GeomBase() { private fun toClient(geomHelper: GeomHelper, p: DataPointAesthetics, sign: Double, halfWidth: Double): DoubleVector? { val coord = toLocationBound(sign, halfWidth)(p) - return if (coord != null) { - geomHelper.toClient(coord, p) - } else { - null - } + return coord?.let { geomHelper.toClient(it, p) } } private fun halfWidth(aesthetics: Aesthetics, ctx: GeomContext): Double { - val maxWeight: Double = aesthetics.dataPoints().map { it.weight()!! }.maxOrNull() ?: 0.0 + val maxWeight = aesthetics.dataPoints().map { it.weight()!! }.maxOrNull() ?: 0.0 return ctx.getResolution(Aes.X) / (2 * maxWeight) } diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/DensityStat.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/DensityStat.kt index 96c40c978a2..9be97add3cd 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/DensityStat.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/DensityStat.kt @@ -72,30 +72,11 @@ class DensityStat( val statDensity = ArrayList() val statCount = ArrayList() val statScaled = ArrayList() - - val bandWidth = bandWidth ?: DensityStatUtil.bandWidth( - bandWidthMethod, - xs + val densityFunction = getDensityFunction( + xs, weights, + bandWidth, bandWidthMethod, adjust, kernel, fullScalMax ) - val kernelFun: (Double) -> Double = DensityStatUtil.kernel(kernel) - val densityFunction: (Double) -> Double = when (xs.size <= fullScalMax) { - true -> DensityStatUtil.densityFunctionFullScan( - xs, - weights, - kernelFun, - bandWidth, - adjust - ) - false -> DensityStatUtil.densityFunctionFast( - xs, - weights, - kernelFun, - bandWidth, - adjust - ) - } - val nTotal = weights.sum() for (x in statX) { val d = densityFunction(x) @@ -138,11 +119,44 @@ class DensityStat( val DEF_BW = NRD0 const val DEF_FULL_SCAN_MAX = 5000 + const val MAX_N = 1024 + + fun getDensityFunction( + values: List, + weights: List, + bandWidth: Double?, + bandWidthMethod: BandWidthMethod, + adjust: Double, + kernel: Kernel, + fullScalMax: Int + ): (Double) -> Double { + val bandWidthValue = bandWidth ?: DensityStatUtil.bandWidth( + bandWidthMethod, + values + ) + val kernelFun: (Double) -> Double = DensityStatUtil.kernel(kernel) + + return when (values.size <= fullScalMax) { + true -> DensityStatUtil.densityFunctionFullScan( + values, + weights, + kernelFun, + bandWidthValue, + adjust + ) + false -> DensityStatUtil.densityFunctionFast( + values, + weights, + kernelFun, + bandWidthValue, + adjust + ) + } + } + private val DEF_MAPPING: Map, DataFrame.Variable> = mapOf( Aes.X to Stats.X, Aes.Y to Stats.DENSITY ) - - private const val MAX_N = 1024 } } diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/ViolinStat.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/ViolinStat.kt index b470af8eeed..1c02cb44383 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/ViolinStat.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/ViolinStat.kt @@ -12,7 +12,20 @@ import jetbrains.datalore.plot.base.StatContext import jetbrains.datalore.plot.base.data.TransformVar import jetbrains.datalore.plot.common.data.SeriesUtil -class ViolinStat : BaseStat(DEF_MAPPING) { +class ViolinStat( + private val bandWidth: Double?, + private val bandWidthMethod: DensityStat.BandWidthMethod, + private val adjust: Double, + private val kernel: DensityStat.Kernel, + private val n: Int, + private val fullScalMax: Int +) : BaseStat(DEF_MAPPING) { + + init { + require(n <= DensityStat.MAX_N) { + "The input n = $n > ${DensityStat.MAX_N} is too large!" + } + } override fun consumes(): List> { return listOf(Aes.X, Aes.Y, Aes.WEIGHT) @@ -44,79 +57,58 @@ class ViolinStat : BaseStat(DEF_MAPPING) { return builder.build() } + private fun buildStat( + xs: List, + ys: List, + ws: List + ): MutableMap> { + val binnedData = (xs zip (ys zip ws)) + .groupBy({ it.first!! }, { it.second }) + .mapValues { it.value.unzip() } + + val statX = ArrayList() + val statY = ArrayList() + val statDensity = ArrayList() + val statCount = ArrayList() + val statScaled = ArrayList() + + for ((x, bin) in binnedData) { + val (filteredY, filteredW) = SeriesUtil.filterFinite(bin.first, bin.second) + val (binY, binW) = (filteredY zip filteredW) + .sortedBy { it.first } + .unzip() + val ySummary = FiveNumberSummary(binY) + val rangeY = ClosedRange(ySummary.min, ySummary.max) + val binStatY = DensityStatUtil.createStepValues(rangeY, n) + val densityFunction = DensityStat.getDensityFunction( + binY, binW, + bandWidth, bandWidthMethod, adjust, kernel, fullScalMax + ) + val binStatCount = binStatY.map { densityFunction(it) } + val weightsSum = binW.sum() + val maxBinCount = binStatCount.maxOrNull()!! + + statX += MutableList(binStatY.size) { x } + statY += binStatY + statDensity += binStatCount.map { it / weightsSum } + statCount += binStatCount + statScaled += binStatCount.map { it / maxBinCount } + } + + return mutableMapOf( + Stats.X to statX, + Stats.Y to statY, + Stats.DENSITY to statDensity, + Stats.COUNT to statCount, + Stats.SCALED to statScaled + ) + } + companion object { private val DEF_MAPPING: Map, DataFrame.Variable> = mapOf( Aes.X to Stats.X, Aes.Y to Stats.Y, Aes.WEIGHT to Stats.DENSITY ) - - private fun buildStat( - xs: List, - ys: List, - ws: List - ): MutableMap> { - val binnedData = (xs zip (ys zip ws)) - .groupBy({ it.first!! }, { it.second }) - .mapValues { it.value.unzip() } - - val statX = ArrayList() - val statY = ArrayList() - val statDensity = ArrayList() - val statCount = ArrayList() - val statScaled = ArrayList() - - for ((x, bin) in binnedData) { - val (filteredY, filteredW) = SeriesUtil.filterFinite(bin.first, bin.second) - val (binY, binW) = (filteredY zip filteredW) - .sortedBy { it.first } - .unzip() - val ySummary = FiveNumberSummary(binY) - val rangeY = ClosedRange(ySummary.min, ySummary.max) - val binStatY = DensityStatUtil.createStepValues(rangeY, DensityStat.DEF_N) - val densityFunction = getDensityFunction(binY, binW) - val binStatCount = binStatY.map { densityFunction(it) } - val weightsSum = binW.sum() - val maxBinCount = binStatCount.maxOrNull()!! - - statX += MutableList(binStatY.size) { x } - statY += binStatY - statDensity += binStatCount.map { it / weightsSum } - statCount += binStatCount - statScaled += binStatCount.map { it / maxBinCount } - } - - return mutableMapOf( - Stats.X to statX, - Stats.Y to statY, - Stats.DENSITY to statDensity, - Stats.COUNT to statCount, - Stats.SCALED to statScaled - ) - } - - private fun getDensityFunction( - binY: List, - binW: List - ): (Double) -> Double { - val bandWidth = DensityStatUtil.bandWidth(DensityStat.DEF_BW, binY) - val kernelFun: (Double) -> Double = DensityStatUtil.kernel(DensityStat.DEF_KERNEL) - return when (binY.size <= DensityStat.DEF_FULL_SCAN_MAX) { - true -> DensityStatUtil.densityFunctionFullScan( - binY, - binW, - kernelFun, - bandWidth, - DensityStat.DEF_ADJUST - ) - false -> DensityStatUtil.densityFunctionFast( - binY, - binW, - kernelFun, - bandWidth, - DensityStat.DEF_ADJUST - ) - } - } } } \ No newline at end of file diff --git a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/StatProto.kt b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/StatProto.kt index 0a2f3602b3b..612b5c8da1f 100644 --- a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/StatProto.kt +++ b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/StatProto.kt @@ -96,7 +96,7 @@ object StatProto { ) } - StatKind.VIOLIN -> return ViolinStat() + StatKind.VIOLIN -> return configureViolinStat(options) StatKind.DENSITY -> return configureDensityStat(options) @@ -172,6 +172,31 @@ object StatProto { ) } + private fun configureViolinStat(options: OptionsAccessor): ViolinStat { + var bwValue: Double? = null + var bwMethod: DensityStat.BandWidthMethod = DensityStat.DEF_BW + options[Density.BAND_WIDTH]?.run { + if (this is Number) { + bwValue = this.toDouble() + } else if (this is String) { + bwMethod = DensityStatUtil.toBandWidthMethod(this) + } + } + + val kernel = options.getString(Density.KERNEL)?.let { + DensityStatUtil.toKernel(it) + } + + return ViolinStat( + bandWidth = bwValue, + bandWidthMethod = bwMethod, + adjust = options.getDoubleDef(Density.ADJUST, DensityStat.DEF_ADJUST), + kernel = kernel ?: DensityStat.DEF_KERNEL, + n = options.getIntegerDef(Density.N, DensityStat.DEF_N), + fullScalMax = options.getIntegerDef(Density.FULL_SCAN_MAX, DensityStat.DEF_FULL_SCAN_MAX) + ) + } + private fun configureDensityStat(options: OptionsAccessor): DensityStat { var bwValue: Double? = null var bwMethod: DensityStat.BandWidthMethod = DensityStat.DEF_BW @@ -197,7 +222,6 @@ object StatProto { ) } - private fun configureDensity2dStat(options: OptionsAccessor, filled: Boolean): AbstractDensity2dStat { var bwValueX: Double? = null var bwValueY: Double? = null From 7418aa7e1fc52ce07742ccc5b4fc68eec4a905a5 Mon Sep 17 00:00:00 2001 From: ASmirnov-HORIS Date: Wed, 1 Dec 2021 14:48:05 +0300 Subject: [PATCH 40/81] Remove one extra space. --- .../kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt index 3fc0fd2760b..c53530ccf43 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt @@ -33,7 +33,7 @@ class ViolinGeom : GeomBase() { ctx: GeomContext ) { val helper = LinesHelper(pos, coord, ctx) - val groupedDataPoints = aesthetics.dataPoints().groupBy { it.x() } + val groupedDataPoints = aesthetics.dataPoints().groupBy { it.x() } val halfWidth = halfWidth(aesthetics, ctx) for ((_, nonOrderedDataPoints) in groupedDataPoints) { val dataPoints = GeomUtil.ordered_Y(nonOrderedDataPoints, false) From 34b6d6183dad8c23eb64710eacf7bcec209d9c07 Mon Sep 17 00:00:00 2001 From: ASmirnov-HORIS Date: Wed, 1 Dec 2021 19:35:42 +0300 Subject: [PATCH 41/81] Move the densityFunction() to the DensityStatUtil. --- .../datalore/plot/base/stat/DensityStat.kt | 35 +------------------ .../plot/base/stat/DensityStatUtil.kt | 18 ++++++++++ .../datalore/plot/base/stat/ViolinStat.kt | 2 +- 3 files changed, 20 insertions(+), 35 deletions(-) diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/DensityStat.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/DensityStat.kt index 9be97add3cd..c2bd1f9b59d 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/DensityStat.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/DensityStat.kt @@ -72,7 +72,7 @@ class DensityStat( val statDensity = ArrayList() val statCount = ArrayList() val statScaled = ArrayList() - val densityFunction = getDensityFunction( + val densityFunction = DensityStatUtil.densityFunction( xs, weights, bandWidth, bandWidthMethod, adjust, kernel, fullScalMax ) @@ -121,39 +121,6 @@ class DensityStat( const val MAX_N = 1024 - fun getDensityFunction( - values: List, - weights: List, - bandWidth: Double?, - bandWidthMethod: BandWidthMethod, - adjust: Double, - kernel: Kernel, - fullScalMax: Int - ): (Double) -> Double { - val bandWidthValue = bandWidth ?: DensityStatUtil.bandWidth( - bandWidthMethod, - values - ) - val kernelFun: (Double) -> Double = DensityStatUtil.kernel(kernel) - - return when (values.size <= fullScalMax) { - true -> DensityStatUtil.densityFunctionFullScan( - values, - weights, - kernelFun, - bandWidthValue, - adjust - ) - false -> DensityStatUtil.densityFunctionFast( - values, - weights, - kernelFun, - bandWidthValue, - adjust - ) - } - } - private val DEF_MAPPING: Map, DataFrame.Variable> = mapOf( Aes.X to Stats.X, Aes.Y to Stats.DENSITY diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/DensityStatUtil.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/DensityStatUtil.kt index be91d21b33a..408bd416a58 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/DensityStatUtil.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/DensityStatUtil.kt @@ -70,6 +70,24 @@ object DensityStatUtil { } } + internal fun densityFunction( + values: List, + weights: List, + bw: Double?, + bwMethod: DensityStat.BandWidthMethod, + ad: Double, + ker: DensityStat.Kernel, + fullScalMax: Int + ): (Double) -> Double { + val bandWidth = bw ?: bandWidth(bwMethod, values) + val kernelFun: (Double) -> Double = kernel(ker) + + return when (values.size <= fullScalMax) { + true -> densityFunctionFullScan(values, weights, kernelFun, bandWidth, ad) + false -> densityFunctionFast(values, weights, kernelFun, bandWidth, ad) + } + } + internal fun densityFunctionFullScan( xs: List, weights: List, diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/ViolinStat.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/ViolinStat.kt index 1c02cb44383..75db506c100 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/ViolinStat.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/ViolinStat.kt @@ -80,7 +80,7 @@ class ViolinStat( val ySummary = FiveNumberSummary(binY) val rangeY = ClosedRange(ySummary.min, ySummary.max) val binStatY = DensityStatUtil.createStepValues(rangeY, n) - val densityFunction = DensityStat.getDensityFunction( + val densityFunction = DensityStatUtil.densityFunction( binY, binW, bandWidth, bandWidthMethod, adjust, kernel, fullScalMax ) From 73904636b80a133d3d4fcd96b711c95789b3953a Mon Sep 17 00:00:00 2001 From: ASmirnov-HORIS Date: Wed, 1 Dec 2021 20:02:29 +0300 Subject: [PATCH 42/81] Add the 'Nullable Test' and fix the corresponding bug. --- .../datalore/plot/base/stat/ViolinStat.kt | 1 + .../plotDemo/model/plotConfig/Violin.kt | 24 ++++++++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/ViolinStat.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/ViolinStat.kt index 75db506c100..7c2ea0ad72e 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/ViolinStat.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/ViolinStat.kt @@ -63,6 +63,7 @@ class ViolinStat( ws: List ): MutableMap> { val binnedData = (xs zip (ys zip ws)) + .filter { it.first?.isFinite() == true } .groupBy({ it.first!! }, { it.second }) .mapValues { it.value.unzip() } diff --git a/plot-demo-common/src/commonMain/kotlin/jetbrains/datalore/plotDemo/model/plotConfig/Violin.kt b/plot-demo-common/src/commonMain/kotlin/jetbrains/datalore/plotDemo/model/plotConfig/Violin.kt index 81bf75c3e09..6530a25447b 100644 --- a/plot-demo-common/src/commonMain/kotlin/jetbrains/datalore/plotDemo/model/plotConfig/Violin.kt +++ b/plot-demo-common/src/commonMain/kotlin/jetbrains/datalore/plotDemo/model/plotConfig/Violin.kt @@ -11,7 +11,8 @@ import jetbrains.datalore.plotDemo.data.Iris class Violin { fun plotSpecList(): List> { return listOf( - basic() + basic(), + withNan() ) } @@ -37,4 +38,25 @@ class Violin { return plotSpec } + + private fun withNan(): MutableMap { + val spec = "{" + + " 'kind': 'plot'," + + " 'data' : {'class': ['A', 'A', 'A', null, 'B', 'B', 'B', 'B']," + + " 'value': [0, 0, 2, 2, 1, 1, 3, null]" + + " }," + + " 'mapping': {" + + " 'x': 'class'," + + " 'y': 'value'" + + " }," + + " 'layers': [" + + " {" + + " 'geom': 'violin'" + + " }" + + " ]" + + "}" + + return HashMap(parsePlotSpec(spec)) + + } } \ No newline at end of file From 7e024cb427449e5d66a51c58795ab0de2dc66558 Mon Sep 17 00:00:00 2001 From: ASmirnov-HORIS Date: Wed, 1 Dec 2021 21:01:44 +0300 Subject: [PATCH 43/81] Add the notebook for demonstrating the new geometry. --- docs/f-01-11/notebooks/geom_violin.ipynb | 1242 ++++++++++++++++++++++ 1 file changed, 1242 insertions(+) create mode 100644 docs/f-01-11/notebooks/geom_violin.ipynb diff --git a/docs/f-01-11/notebooks/geom_violin.ipynb b/docs/f-01-11/notebooks/geom_violin.ipynb new file mode 100644 index 00000000000..c1f244d0778 --- /dev/null +++ b/docs/f-01-11/notebooks/geom_violin.ipynb @@ -0,0 +1,1242 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + " \n", + " \n", + " " + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import numpy as np\n", + "import pandas as pd\n", + "\n", + "from lets_plot import *\n", + "from lets_plot.mapping import as_discrete\n", + "LetsPlot.setup_html()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test datasets" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
sepal_lengthsepal_widthpetal_lengthpetal_widthspecies
05.13.51.40.2setosa
14.93.01.40.2setosa
24.73.21.30.2setosa
34.63.11.50.2setosa
45.03.61.40.2setosa
\n", + "
" + ], + "text/plain": [ + " sepal_length sepal_width petal_length petal_width species\n", + "0 5.1 3.5 1.4 0.2 setosa\n", + "1 4.9 3.0 1.4 0.2 setosa\n", + "2 4.7 3.2 1.3 0.2 setosa\n", + "3 4.6 3.1 1.5 0.2 setosa\n", + "4 5.0 3.6 1.4 0.2 setosa" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "iris_df = pd.read_csv(\"https://raw.githubusercontent.com/JetBrains/lets-plot-docs/master/data/iris.csv\")\n", + "\n", + "iris_df.head()" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
speciessepal_lengthweight
0setosa4.3000000.222676
1setosa4.3029350.228662
2setosa4.3058710.234639
3setosa4.3088060.240684
4setosa4.3117420.246886
\n", + "
" + ], + "text/plain": [ + " species sepal_length weight\n", + "0 setosa 4.300000 0.222676\n", + "1 setosa 4.302935 0.228662\n", + "2 setosa 4.305871 0.234639\n", + "3 setosa 4.308806 0.240684\n", + "4 setosa 4.311742 0.246886" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def construct_violin_df(df, xname, yname, n=512):\n", + " from functools import reduce\n", + "\n", + " from scipy.stats import gaussian_kde\n", + "\n", + " def get_weights(values):\n", + " def nrd0_bw(kde):\n", + " iqr = np.quantile(kde.dataset, .75) - np.quantile(kde.dataset, .25)\n", + " std = np.std(kde.dataset)\n", + " size = kde.dataset.size\n", + " if iqr > 0:\n", + " return .9 * min(std, iqr / 1.34) * (size ** -.2)\n", + " if std > 0:\n", + " return .9 * std * (size ** -.2)\n", + "\n", + " yrange = np.linspace(values.min(), values.max(), n)\n", + "\n", + " return {yname: yrange, 'weight': gaussian_kde(values, bw_method=nrd0_bw)(yrange)}\n", + "\n", + " def reducer(agg_df, xval):\n", + " weights = get_weights(df[df[xname] == xval][yname])\n", + " y = weights[yname]\n", + " x = [xval] * y.size\n", + " w = weights['weight']\n", + "\n", + " return pd.concat([agg_df, pd.DataFrame({xname: x, yname: y, 'weight': w})], ignore_index=True)\n", + "\n", + " return reduce(reducer, df[xname], pd.DataFrame(columns=[xname, yname, 'weight']))\n", + "\n", + "violin_df = construct_violin_df(iris_df, 'species', 'sepal_length')\n", + "violin_df.head()" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
vc1c2
00.496714Ab
1-0.138264Bb
20.647689Aa
31.523030Aa
4-0.234153Ca
\n", + "
" + ], + "text/plain": [ + " v c1 c2\n", + "0 0.496714 A b\n", + "1 -0.138264 B b\n", + "2 0.647689 A a\n", + "3 1.523030 A a\n", + "4 -0.234153 C a" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "size = 100\n", + "np.random.seed(42)\n", + "random_df = pd.DataFrame({\n", + " 'v': np.random.normal(size=size),\n", + " 'c1': np.random.choice(['A', 'B', 'C'], size=size),\n", + " 'c2': np.random.choice(['a', 'b'], size=size)\n", + "})\n", + "\n", + "random_df.head()" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
vc1c2
00.496714Ab
1-0.138264NaNb
2NaNAa
31.523030ANaN
4-0.234153Ca
\n", + "
" + ], + "text/plain": [ + " v c1 c2\n", + "0 0.496714 A b\n", + "1 -0.138264 NaN b\n", + "2 NaN A a\n", + "3 1.523030 A NaN\n", + "4 -0.234153 C a" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def mask(p=.1, seed=42):\n", + " np.random.seed(seed)\n", + " return np.random.choice([True, False], random_df.shape[0], p=[p, 1 - p])\n", + "\n", + "nullable_df = random_df.copy()\n", + "nullable_df.loc[mask(seed=1), 'v'] = np.nan\n", + "nullable_df.loc[mask(seed=2), 'c1'] = np.nan\n", + "nullable_df.loc[mask(seed=6), 'c2'] = np.nan\n", + "\n", + "nullable_df.head()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Minimalistic example" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + " " + ], + "text/plain": [ + "" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ggplot(random_df, aes(y='v')) + geom_violin() + ggtitle(\"Simplest example\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Comparison of geoms" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + " " + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "p_d = ggplot(iris_df) + \\\n", + " geom_density(aes(x='sepal_length', fill='species'), color='black', alpha=.7) + \\\n", + " facet_grid(x='species') + \\\n", + " coord_flip() + \\\n", + " ggtitle(\"geom_density()\")\n", + "p_v = ggplot(iris_df, aes('species', 'sepal_length')) + \\\n", + " geom_violin(aes(fill='species'), alpha=.7) + \\\n", + " ggtitle(\"geom_violin()\")\n", + "\n", + "w, h = 400, 300\n", + "bunch = GGBunch()\n", + "bunch.add_plot(p_d, 0, 0, w, h)\n", + "bunch.add_plot(p_v, w, 0, w, h)\n", + "bunch.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Custom density parameters" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + " " + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "p = ggplot(iris_df, aes('species', 'sepal_length'))\n", + "p_default = p + geom_violin() + ggtitle(\"Default\")\n", + "p_kernel = p + geom_violin(kernel='epanechikov') + ggtitle(\"kernel='epanechikov'\")\n", + "p_bw = p + geom_violin(bw=.1) + ggtitle(\"bw=0.1\")\n", + "p_adjust = p + geom_violin(adjust=2) + ggtitle(\"adjust=2\")\n", + "\n", + "w, h = 400, 300\n", + "bunch = GGBunch()\n", + "bunch.add_plot(p_default, 0, 0, w, h)\n", + "bunch.add_plot(p_kernel, w, 0, w, h)\n", + "bunch.add_plot(p_bw, 0, h, w, h)\n", + "bunch.add_plot(p_adjust, w, h, w, h)\n", + "bunch.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Grouping and tooltips" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + " " + ], + "text/plain": [ + "" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ggplot(random_df, aes(x='c1', y='v')) + \\\n", + " geom_violin(aes(fill='c2'), tooltips=layer_tooltips().line('^x')\n", + " .line('category|@c2')\n", + " .line('v|@v')\n", + " .line('@|@..density..')\n", + " .line('count|@..count..')\n", + " .line('scaled|@..scaled..')) + \\\n", + " ggtitle(\"Grouping and tooltips\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## `coord_flip()`" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + " " + ], + "text/plain": [ + "" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ggplot(iris_df, aes('species', 'sepal_length')) + \\\n", + " geom_violin() + \\\n", + " coord_flip() + \\\n", + " ggtitle(\"Use coord_flip()\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## \"identity\" statistic" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + " " + ], + "text/plain": [ + "" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ggplot(violin_df, aes('species', 'sepal_length')) + \\\n", + " geom_violin(aes(weight='weight'), stat='identity') + \\\n", + " ggtitle(\"Use 'identity' statistic\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Additional layers" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + " " + ], + "text/plain": [ + "" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ggplot(random_df, aes(as_discrete('c1', order=-1), 'v')) + \\\n", + " geom_violin(aes(color='c1', fill='c1'), alpha=.5, size=2, \\\n", + " sampling=sampling_group_systematic(2)) + \\\n", + " facet_grid(x='c2') + \\\n", + " scale_y_continuous(breaks=list(np.linspace(-3, 3, 9))) + \\\n", + " scale_color_brewer(type='qual', palette='Set1') + \\\n", + " scale_fill_brewer(type='qual', palette='Set1') + \\\n", + " ylim(-3, 3) + \\\n", + " coord_fixed(ratio=.5) + \\\n", + " theme_grey() + \\\n", + " ggtitle(\"Some additional aesthetics, parameters and layers\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Dataset with NaN's" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + " " + ], + "text/plain": [ + "" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ggplot(nullable_df, aes('c1', 'v')) + geom_violin()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.12" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} From 2e3d1484e5f265bc880b3ab30755d7c33382d4b8 Mon Sep 17 00:00:00 2001 From: ASmirnov-HORIS Date: Thu, 2 Dec 2021 13:31:51 +0300 Subject: [PATCH 44/81] Rename the directory: docs/f-01-11/ -> docs/f-21-12/. --- docs/{f-01-11 => f-21-12}/notebooks/geom_violin.ipynb | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename docs/{f-01-11 => f-21-12}/notebooks/geom_violin.ipynb (100%) diff --git a/docs/f-01-11/notebooks/geom_violin.ipynb b/docs/f-21-12/notebooks/geom_violin.ipynb similarity index 100% rename from docs/f-01-11/notebooks/geom_violin.ipynb rename to docs/f-21-12/notebooks/geom_violin.ipynb From d66bdcc92866e86d4c94b833004ff2e766004ae0 Mon Sep 17 00:00:00 2001 From: ASmirnov-HORIS Date: Fri, 3 Dec 2021 16:08:38 +0300 Subject: [PATCH 45/81] Add 'violinwidth' aesthetic and '..violinwidth..' statistic. Now the scale='width' version of statistic computation is realized by default. --- .../jetbrains/datalore/plot/base/Aes.kt | 3 ++- .../datalore/plot/base/DataPointAesthetics.kt | 2 ++ .../jetbrains/datalore/plot/base/GeomMeta.kt | 2 +- .../datalore/plot/base/aes/AesInitValue.kt | 2 ++ .../datalore/plot/base/aes/AesVisitor.kt | 6 +++++ .../plot/base/aes/AestheticsBuilder.kt | 5 ++++ .../datalore/plot/base/data/TransformVar.kt | 5 ++++ .../datalore/plot/base/geom/ViolinGeom.kt | 23 +++++++------------ .../geom/util/DataPointAestheticsDelegate.kt | 4 ++++ .../datalore/plot/base/stat/Stats.kt | 2 ++ .../datalore/plot/base/stat/ViolinStat.kt | 15 ++++++------ .../builder/scale/DefaultMapperProvider.kt | 2 ++ .../plot/builder/scale/DefaultNaValue.kt | 2 ++ .../transform/bistro/util/LayerOptions.kt | 1 + .../plotDemo/model/plotConfig/Violin.kt | 1 - 15 files changed, 50 insertions(+), 25 deletions(-) diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/Aes.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/Aes.kt index 7e13fb9fbd2..ec1a50fd742 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/Aes.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/Aes.kt @@ -39,6 +39,7 @@ class Aes private constructor(val name: String, val isNumeric: Boolean = true val SIZE: Aes = Aes("size") val WIDTH: Aes = Aes("width") val HEIGHT: Aes = Aes("height") + val VIOLINWIDTH: Aes = Aes("violinwidth") val WEIGHT: Aes = Aes("weight") val INTERCEPT: Aes = Aes("intercept") val SLOPE: Aes = Aes("slope") @@ -153,7 +154,7 @@ class Aes private constructor(val name: String, val isNumeric: Boolean = true aes == SLOPE || aes == WIDTH || aes == HEIGHT || - aes == WEIGHT || + aes == VIOLINWIDTH || aes == HJUST || aes == VJUST || aes == ANGLE || diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/DataPointAesthetics.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/DataPointAesthetics.kt index 6c6a68b23be..02d3c411177 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/DataPointAesthetics.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/DataPointAesthetics.kt @@ -38,6 +38,8 @@ interface DataPointAesthetics { fun height(): Double? + fun violinwidth(): Double? + fun weight(): Double? fun intercept(): Double? diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/GeomMeta.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/GeomMeta.kt index 2392e1ff8db..981dbb71ef2 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/GeomMeta.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/GeomMeta.kt @@ -214,7 +214,7 @@ object GeomMeta { GeomKind.VIOLIN -> listOf( Aes.X, Aes.Y, - Aes.WEIGHT, + Aes.VIOLINWIDTH, Aes.ALPHA, Aes.COLOR, diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/aes/AesInitValue.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/aes/AesInitValue.kt index 658128c5db3..127872b9343 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/aes/AesInitValue.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/aes/AesInitValue.kt @@ -32,6 +32,7 @@ import jetbrains.datalore.plot.base.Aes.Companion.SPEED import jetbrains.datalore.plot.base.Aes.Companion.SYM_X import jetbrains.datalore.plot.base.Aes.Companion.SYM_Y import jetbrains.datalore.plot.base.Aes.Companion.UPPER +import jetbrains.datalore.plot.base.Aes.Companion.VIOLINWIDTH import jetbrains.datalore.plot.base.Aes.Companion.VJUST import jetbrains.datalore.plot.base.Aes.Companion.WEIGHT import jetbrains.datalore.plot.base.Aes.Companion.WIDTH @@ -67,6 +68,7 @@ object AesInitValue { VALUE_MAP[SIZE] = 0.5 // Line thickness. Should be redefined for other shapes VALUE_MAP[WIDTH] = 1.0 VALUE_MAP[HEIGHT] = 1.0 + VALUE_MAP[VIOLINWIDTH] = 1.0 VALUE_MAP[WEIGHT] = 1.0 VALUE_MAP[INTERCEPT] = 0.0 VALUE_MAP[SLOPE] = 1.0 diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/aes/AesVisitor.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/aes/AesVisitor.kt index 563eaa79a15..e2135fb1b6c 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/aes/AesVisitor.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/aes/AesVisitor.kt @@ -29,6 +29,7 @@ import jetbrains.datalore.plot.base.Aes.Companion.SPEED import jetbrains.datalore.plot.base.Aes.Companion.SYM_X import jetbrains.datalore.plot.base.Aes.Companion.SYM_Y import jetbrains.datalore.plot.base.Aes.Companion.UPPER +import jetbrains.datalore.plot.base.Aes.Companion.VIOLINWIDTH import jetbrains.datalore.plot.base.Aes.Companion.VJUST import jetbrains.datalore.plot.base.Aes.Companion.WEIGHT import jetbrains.datalore.plot.base.Aes.Companion.WIDTH @@ -103,6 +104,9 @@ abstract class AesVisitor { if (aes == HEIGHT) { return height() } + if (aes == VIOLINWIDTH) { + return violinwidth() + } if (aes == WEIGHT) { return weight() } @@ -207,6 +211,8 @@ abstract class AesVisitor { protected abstract fun height(): T + protected abstract fun violinwidth(): T + protected abstract fun weight(): T protected abstract fun intercept(): T diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/aes/AestheticsBuilder.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/aes/AestheticsBuilder.kt index 1d762aa897f..00412ebb828 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/aes/AestheticsBuilder.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/aes/AestheticsBuilder.kt @@ -32,6 +32,7 @@ import jetbrains.datalore.plot.base.Aes.Companion.SPEED import jetbrains.datalore.plot.base.Aes.Companion.SYM_X import jetbrains.datalore.plot.base.Aes.Companion.SYM_Y import jetbrains.datalore.plot.base.Aes.Companion.UPPER +import jetbrains.datalore.plot.base.Aes.Companion.VIOLINWIDTH import jetbrains.datalore.plot.base.Aes.Companion.VJUST import jetbrains.datalore.plot.base.Aes.Companion.WEIGHT import jetbrains.datalore.plot.base.Aes.Companion.WIDTH @@ -398,6 +399,10 @@ class AestheticsBuilder @JvmOverloads constructor(private var myDataPointCount: return get(HEIGHT) } + override fun violinwidth(): Double { + return get(VIOLINWIDTH) + } + override fun weight(): Double { return get(WEIGHT) } diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/data/TransformVar.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/data/TransformVar.kt index c4196b24826..947a3ff84c4 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/data/TransformVar.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/data/TransformVar.kt @@ -24,6 +24,7 @@ object TransformVar { val SIZE = DataFrame.Variable("transform.SIZE", TRANSFORM) val WIDTH = DataFrame.Variable("transform.WIDTH", TRANSFORM) val HEIGHT = DataFrame.Variable("transform.HEIGHT", TRANSFORM) + val VIOLINWIDTH = DataFrame.Variable("transform.VIOLINWIDTH", TRANSFORM) val WEIGHT = DataFrame.Variable("transform.WEIGHT", TRANSFORM) val INTERCEPT = DataFrame.Variable("transform.INTERCEPT", TRANSFORM) val SLOPE = DataFrame.Variable("transform.SLOPE", TRANSFORM) @@ -129,6 +130,10 @@ object TransformVar { return HEIGHT } + override fun violinwidth(): DataFrame.Variable { + return VIOLINWIDTH + } + override fun weight(): DataFrame.Variable { return WEIGHT } diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt index c53530ccf43..e9bdc1ca455 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt @@ -34,16 +34,15 @@ class ViolinGeom : GeomBase() { ) { val helper = LinesHelper(pos, coord, ctx) val groupedDataPoints = aesthetics.dataPoints().groupBy { it.x() } - val halfWidth = halfWidth(aesthetics, ctx) for ((_, nonOrderedDataPoints) in groupedDataPoints) { val dataPoints = GeomUtil.ordered_Y(nonOrderedDataPoints, false) - val paths = helper.createBands(dataPoints, toLocationBound(-1.0, halfWidth), toLocationBound(1.0, halfWidth)) + val paths = helper.createBands(dataPoints, toLocationBound(-1.0, ctx), toLocationBound(1.0, ctx)) paths.reverse() appendNodes(paths, root) helper.setAlphaEnabled(false) - appendNodes(helper.createLines(dataPoints, toLocationBound(-1.0, halfWidth)), root) - appendNodes(helper.createLines(dataPoints, toLocationBound(1.0, halfWidth)), root) + appendNodes(helper.createLines(dataPoints, toLocationBound(-1.0, ctx)), root) + appendNodes(helper.createLines(dataPoints, toLocationBound(1.0, ctx)), root) } buildHints(aesthetics, pos, coord, ctx, -1.0) @@ -51,11 +50,10 @@ class ViolinGeom : GeomBase() { } private fun buildHints(aesthetics: Aesthetics, pos: PositionAdjustment, coord: CoordinateSystem, ctx: GeomContext, sign: Double) { - val halfWidth = halfWidth(aesthetics, ctx) val geomHelper = GeomHelper(pos, coord, ctx) val multiPointDataList = MultiPointDataConstructor.createMultiPointDataByGroup( aesthetics.dataPoints(), - MultiPointDataConstructor.singlePointAppender { p -> toClient(geomHelper, p, sign, halfWidth) }, + MultiPointDataConstructor.singlePointAppender { p -> toClient(geomHelper, p, sign, ctx) }, MultiPointDataConstructor.reducer(0.999, false) ) @@ -78,19 +76,14 @@ class ViolinGeom : GeomBase() { return GeomTargetCollector.TooltipParams.params().setColor(HintColorUtil.fromFill(aes)) } - private fun toClient(geomHelper: GeomHelper, p: DataPointAesthetics, sign: Double, halfWidth: Double): DoubleVector? { - val coord = toLocationBound(sign, halfWidth)(p) + private fun toClient(geomHelper: GeomHelper, p: DataPointAesthetics, sign: Double, ctx: GeomContext): DoubleVector? { + val coord = toLocationBound(sign, ctx)(p) return coord?.let { geomHelper.toClient(it, p) } } - private fun halfWidth(aesthetics: Aesthetics, ctx: GeomContext): Double { - val maxWeight = aesthetics.dataPoints().map { it.weight()!! }.maxOrNull() ?: 0.0 - return ctx.getResolution(Aes.X) / (2 * maxWeight) - } - - private fun toLocationBound(sign: Double, halfWidth: Double): (p: DataPointAesthetics) -> DoubleVector? { + private fun toLocationBound(sign: Double, ctx: GeomContext): (p: DataPointAesthetics) -> DoubleVector? { return fun (p: DataPointAesthetics): DoubleVector? { - val x = p.x()!! + halfWidth * DEF_WIDTH * sign * p.weight()!! + val x = p.x()!! + ctx.getResolution(Aes.X) / 2 * DEF_WIDTH * sign * p.violinwidth()!! val y = p.y()!! return if (SeriesUtil.isFinite(x) && SeriesUtil.isFinite(y)) { DoubleVector(x, y) diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/util/DataPointAestheticsDelegate.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/util/DataPointAestheticsDelegate.kt index 1ee2b01e061..b070dee4cf1 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/util/DataPointAestheticsDelegate.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/util/DataPointAestheticsDelegate.kt @@ -70,6 +70,10 @@ open class DataPointAestheticsDelegate(private val p: DataPointAesthetics) : return p.height() } + override fun violinwidth(): Double? { + return p.violinwidth() + } + override fun weight(): Double? { return p.weight() } diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/Stats.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/Stats.kt index e3934ddf337..348fd0bc4df 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/Stats.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/Stats.kt @@ -26,6 +26,7 @@ object Stats { val MIDDLE = DataFrame.Variable("..middle..", STAT, "middle") val UPPER = DataFrame.Variable("..upper..", STAT, "upper") val WIDTH = DataFrame.Variable("..width..", STAT, "width") + val VIOLIN_WIDTH = DataFrame.Variable("..violinwidth..", STAT, "violinwidth") val CORR = DataFrame.Variable("..corr..", STAT, "corr") val CORR_ABS = DataFrame.Variable("..corr_abs..", STAT, "corr_abs") @@ -50,6 +51,7 @@ object Stats { MIDDLE, UPPER, WIDTH, + VIOLIN_WIDTH, SCALED, GROUP, CORR, diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/ViolinStat.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/ViolinStat.kt index 7c2ea0ad72e..8cf75860c92 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/ViolinStat.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/ViolinStat.kt @@ -23,12 +23,12 @@ class ViolinStat( init { require(n <= DensityStat.MAX_N) { - "The input n = $n > ${DensityStat.MAX_N} is too large!" + "The input n = $n > ${DensityStat.MAX_N} is too large!" } } override fun consumes(): List> { - return listOf(Aes.X, Aes.Y, Aes.WEIGHT) + return listOf(Aes.X, Aes.Y, Aes.VIOLINWIDTH) } override fun apply(data: DataFrame, statCtx: StatContext, messageConsumer: (s: String) -> Unit): DataFrame { @@ -42,8 +42,8 @@ class ViolinStat( } else { List(ys.size) { 0.0 } } - val ws = if (data.has(TransformVar.WEIGHT)) { - data.getNumeric(TransformVar.WEIGHT) + val ws = if (data.has(TransformVar.VIOLINWIDTH)) { + data.getNumeric(TransformVar.VIOLINWIDTH) } else { List(ys.size) { 1.0 } } @@ -86,12 +86,12 @@ class ViolinStat( bandWidth, bandWidthMethod, adjust, kernel, fullScalMax ) val binStatCount = binStatY.map { densityFunction(it) } - val weightsSum = binW.sum() + val widthsSum = binW.sum() val maxBinCount = binStatCount.maxOrNull()!! statX += MutableList(binStatY.size) { x } statY += binStatY - statDensity += binStatCount.map { it / weightsSum } + statDensity += binStatCount.map { it / widthsSum } statCount += binStatCount statScaled += binStatCount.map { it / maxBinCount } } @@ -99,6 +99,7 @@ class ViolinStat( return mutableMapOf( Stats.X to statX, Stats.Y to statY, + Stats.VIOLIN_WIDTH to statScaled, Stats.DENSITY to statDensity, Stats.COUNT to statCount, Stats.SCALED to statScaled @@ -109,7 +110,7 @@ class ViolinStat( private val DEF_MAPPING: Map, DataFrame.Variable> = mapOf( Aes.X to Stats.X, Aes.Y to Stats.Y, - Aes.WEIGHT to Stats.DENSITY + Aes.VIOLINWIDTH to Stats.VIOLIN_WIDTH ) } } \ No newline at end of file diff --git a/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/scale/DefaultMapperProvider.kt b/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/scale/DefaultMapperProvider.kt index 6fec092f202..ba2f184b7b9 100644 --- a/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/scale/DefaultMapperProvider.kt +++ b/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/scale/DefaultMapperProvider.kt @@ -30,6 +30,7 @@ import jetbrains.datalore.plot.base.Aes.Companion.SPEED import jetbrains.datalore.plot.base.Aes.Companion.SYM_X import jetbrains.datalore.plot.base.Aes.Companion.SYM_Y import jetbrains.datalore.plot.base.Aes.Companion.UPPER +import jetbrains.datalore.plot.base.Aes.Companion.VIOLINWIDTH import jetbrains.datalore.plot.base.Aes.Companion.VJUST import jetbrains.datalore.plot.base.Aes.Companion.WEIGHT import jetbrains.datalore.plot.base.Aes.Companion.WIDTH @@ -97,6 +98,7 @@ object DefaultMapperProvider { this.put(WIDTH, NUMERIC_IDENTITY) this.put(HEIGHT, NUMERIC_IDENTITY) this.put(WEIGHT, NUMERIC_IDENTITY) + this.put(VIOLINWIDTH, NUMERIC_IDENTITY) this.put(INTERCEPT, NUMERIC_IDENTITY) this.put(SLOPE, NUMERIC_IDENTITY) this.put(XINTERCEPT, NUMERIC_IDENTITY) diff --git a/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/scale/DefaultNaValue.kt b/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/scale/DefaultNaValue.kt index 53e947771d1..acecbfcfbfc 100644 --- a/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/scale/DefaultNaValue.kt +++ b/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/scale/DefaultNaValue.kt @@ -32,6 +32,7 @@ import jetbrains.datalore.plot.base.Aes.Companion.SPEED import jetbrains.datalore.plot.base.Aes.Companion.SYM_X import jetbrains.datalore.plot.base.Aes.Companion.SYM_Y import jetbrains.datalore.plot.base.Aes.Companion.UPPER +import jetbrains.datalore.plot.base.Aes.Companion.VIOLINWIDTH import jetbrains.datalore.plot.base.Aes.Companion.VJUST import jetbrains.datalore.plot.base.Aes.Companion.WEIGHT import jetbrains.datalore.plot.base.Aes.Companion.WIDTH @@ -67,6 +68,7 @@ object DefaultNaValue { VALUE_MAP.put(SIZE, AesScaling.sizeFromCircleDiameter(1.0)) VALUE_MAP.put(WIDTH, 1.0) VALUE_MAP.put(HEIGHT, 1.0) + VALUE_MAP.put(VIOLINWIDTH, 1.0) VALUE_MAP.put(WEIGHT, 1.0) VALUE_MAP.put(INTERCEPT, 0.0) VALUE_MAP.put(SLOPE, 1.0) diff --git a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/server/config/transform/bistro/util/LayerOptions.kt b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/server/config/transform/bistro/util/LayerOptions.kt index 06d4bb85df9..6529d4b3f11 100644 --- a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/server/config/transform/bistro/util/LayerOptions.kt +++ b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/server/config/transform/bistro/util/LayerOptions.kt @@ -42,6 +42,7 @@ class LayerOptions : Options() { var size: Double? by map(Aes.SIZE) var width: Double? by map(Aes.WIDTH) var height: Double? by map(Aes.HEIGHT) + var violinwidth: Double? by map(Aes.VIOLINWIDTH) var weight: Double? by map(Aes.WEIGHT) var intercept: Double? by map(Aes.INTERCEPT) var slope: Double? by map(Aes.SLOPE) diff --git a/plot-demo-common/src/commonMain/kotlin/jetbrains/datalore/plotDemo/model/plotConfig/Violin.kt b/plot-demo-common/src/commonMain/kotlin/jetbrains/datalore/plotDemo/model/plotConfig/Violin.kt index 6530a25447b..b85661bfd26 100644 --- a/plot-demo-common/src/commonMain/kotlin/jetbrains/datalore/plotDemo/model/plotConfig/Violin.kt +++ b/plot-demo-common/src/commonMain/kotlin/jetbrains/datalore/plotDemo/model/plotConfig/Violin.kt @@ -24,7 +24,6 @@ class Violin { " 'y': 'sepal length (cm)'," + " 'fill': 'target'" + " }," + - " 'layers': [" + " {" + " 'geom': 'violin'," + From 274089d3778bd061446cfed160c327346b3176b7 Mon Sep 17 00:00:00 2001 From: ASmirnov-HORIS Date: Thu, 16 Dec 2021 10:12:15 +0300 Subject: [PATCH 46/81] Rename ViolinStat to YDensityStat and other small fixes. --- .../jetbrains/datalore/plot/base/geom/ViolinGeom.kt | 11 +++++++---- .../base/stat/{ViolinStat.kt => YDensityStat.kt} | 12 ++++++------ .../datalore/plot/builder/scale/DefaultNaValue.kt | 2 +- .../jetbrains/datalore/plot/config/GeomProto.kt | 2 +- .../kotlin/jetbrains/datalore/plot/config/Option.kt | 1 - .../jetbrains/datalore/plot/config/StatKind.kt | 2 +- .../jetbrains/datalore/plot/config/StatProto.kt | 8 ++++---- 7 files changed, 20 insertions(+), 18 deletions(-) rename plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/{ViolinStat.kt => YDensityStat.kt} (94%) diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt index e9bdc1ca455..f52b976f6a9 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt @@ -33,7 +33,12 @@ class ViolinGeom : GeomBase() { ctx: GeomContext ) { val helper = LinesHelper(pos, coord, ctx) - val groupedDataPoints = aesthetics.dataPoints().groupBy { it.x() } + val groupedDataPoints = GeomUtil.withDefined( + aesthetics.dataPoints(), + Aes.X, + Aes.Y, + Aes.VIOLINWIDTH + ).groupBy { it.x() } for ((_, nonOrderedDataPoints) in groupedDataPoints) { val dataPoints = GeomUtil.ordered_Y(nonOrderedDataPoints, false) val paths = helper.createBands(dataPoints, toLocationBound(-1.0, ctx), toLocationBound(1.0, ctx)) @@ -85,9 +90,7 @@ class ViolinGeom : GeomBase() { return fun (p: DataPointAesthetics): DoubleVector? { val x = p.x()!! + ctx.getResolution(Aes.X) / 2 * DEF_WIDTH * sign * p.violinwidth()!! val y = p.y()!! - return if (SeriesUtil.isFinite(x) && SeriesUtil.isFinite(y)) { - DoubleVector(x, y) - } else null + return DoubleVector(x, y) } } diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/ViolinStat.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/YDensityStat.kt similarity index 94% rename from plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/ViolinStat.kt rename to plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/YDensityStat.kt index 8cf75860c92..8fd16afdcfc 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/ViolinStat.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/YDensityStat.kt @@ -12,13 +12,13 @@ import jetbrains.datalore.plot.base.StatContext import jetbrains.datalore.plot.base.data.TransformVar import jetbrains.datalore.plot.common.data.SeriesUtil -class ViolinStat( +class YDensityStat( private val bandWidth: Double?, private val bandWidthMethod: DensityStat.BandWidthMethod, private val adjust: Double, private val kernel: DensityStat.Kernel, private val n: Int, - private val fullScalMax: Int + private val fullScanMax: Int ) : BaseStat(DEF_MAPPING) { init { @@ -28,7 +28,7 @@ class ViolinStat( } override fun consumes(): List> { - return listOf(Aes.X, Aes.Y, Aes.VIOLINWIDTH) + return listOf(Aes.X, Aes.Y, Aes.WEIGHT) } override fun apply(data: DataFrame, statCtx: StatContext, messageConsumer: (s: String) -> Unit): DataFrame { @@ -42,8 +42,8 @@ class ViolinStat( } else { List(ys.size) { 0.0 } } - val ws = if (data.has(TransformVar.VIOLINWIDTH)) { - data.getNumeric(TransformVar.VIOLINWIDTH) + val ws = if (data.has(TransformVar.WEIGHT)) { + data.getNumeric(TransformVar.WEIGHT) } else { List(ys.size) { 1.0 } } @@ -83,7 +83,7 @@ class ViolinStat( val binStatY = DensityStatUtil.createStepValues(rangeY, n) val densityFunction = DensityStatUtil.densityFunction( binY, binW, - bandWidth, bandWidthMethod, adjust, kernel, fullScalMax + bandWidth, bandWidthMethod, adjust, kernel, fullScanMax ) val binStatCount = binStatY.map { densityFunction(it) } val widthsSum = binW.sum() diff --git a/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/scale/DefaultNaValue.kt b/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/scale/DefaultNaValue.kt index acecbfcfbfc..31e993460f4 100644 --- a/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/scale/DefaultNaValue.kt +++ b/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/scale/DefaultNaValue.kt @@ -68,7 +68,7 @@ object DefaultNaValue { VALUE_MAP.put(SIZE, AesScaling.sizeFromCircleDiameter(1.0)) VALUE_MAP.put(WIDTH, 1.0) VALUE_MAP.put(HEIGHT, 1.0) - VALUE_MAP.put(VIOLINWIDTH, 1.0) + VALUE_MAP.put(VIOLINWIDTH, 0.0) VALUE_MAP.put(WEIGHT, 1.0) VALUE_MAP.put(INTERCEPT, 0.0) VALUE_MAP.put(SLOPE, 1.0) diff --git a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/GeomProto.kt b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/GeomProto.kt index 5eced9fac38..cd3f5fa1218 100644 --- a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/GeomProto.kt +++ b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/GeomProto.kt @@ -176,7 +176,7 @@ open class GeomProto constructor(val geomKind: GeomKind) { private fun violinDefaults(): Map { val defaults = HashMap() - defaults["stat"] = "violin" + defaults["stat"] = "ydensity" defaults["position"] = mapOf(Meta.NAME to "dodge", "width" to ViolinGeom.DEF_WIDTH) return defaults } diff --git a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/Option.kt b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/Option.kt index eed948bee4d..e295387ac00 100644 --- a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/Option.kt +++ b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/Option.kt @@ -7,7 +7,6 @@ package jetbrains.datalore.plot.config import jetbrains.datalore.plot.base.Aes import jetbrains.datalore.plot.base.GeomKind -import jetbrains.datalore.plot.base.GeomKind.VIOLIN import jetbrains.datalore.plot.builder.defaultTheme.values.ThemeOption.ELEMENT_BLANK_SHORTHAND object Option { diff --git a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/StatKind.kt b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/StatKind.kt index a5d173edda5..1597470d0a9 100644 --- a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/StatKind.kt +++ b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/StatKind.kt @@ -16,7 +16,7 @@ enum class StatKind { CONTOUR, CONTOURF, BOXPLOT, - VIOLIN, + YDENSITY, DENSITY, DENSITY2D, DENSITY2DF, diff --git a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/StatProto.kt b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/StatProto.kt index 612b5c8da1f..7b6bf997460 100644 --- a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/StatProto.kt +++ b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/StatProto.kt @@ -96,7 +96,7 @@ object StatProto { ) } - StatKind.VIOLIN -> return configureViolinStat(options) + StatKind.YDENSITY -> return configureViolinStat(options) StatKind.DENSITY -> return configureDensityStat(options) @@ -172,7 +172,7 @@ object StatProto { ) } - private fun configureViolinStat(options: OptionsAccessor): ViolinStat { + private fun configureViolinStat(options: OptionsAccessor): YDensityStat { var bwValue: Double? = null var bwMethod: DensityStat.BandWidthMethod = DensityStat.DEF_BW options[Density.BAND_WIDTH]?.run { @@ -187,13 +187,13 @@ object StatProto { DensityStatUtil.toKernel(it) } - return ViolinStat( + return YDensityStat( bandWidth = bwValue, bandWidthMethod = bwMethod, adjust = options.getDoubleDef(Density.ADJUST, DensityStat.DEF_ADJUST), kernel = kernel ?: DensityStat.DEF_KERNEL, n = options.getIntegerDef(Density.N, DensityStat.DEF_N), - fullScalMax = options.getIntegerDef(Density.FULL_SCAN_MAX, DensityStat.DEF_FULL_SCAN_MAX) + fullScanMax = options.getIntegerDef(Density.FULL_SCAN_MAX, DensityStat.DEF_FULL_SCAN_MAX) ) } From 69436dbffc73f536d362ab5d5cbddf9ee7c2356e Mon Sep 17 00:00:00 2001 From: ASmirnov-HORIS Date: Thu, 16 Dec 2021 11:31:21 +0300 Subject: [PATCH 47/81] Move calculation of the ..violinwidth.. to the normalize() method. --- .../kotlin/jetbrains/datalore/plot/base/Stat.kt | 2 ++ .../jetbrains/datalore/plot/base/stat/BaseStat.kt | 3 +++ .../datalore/plot/base/stat/YDensityStat.kt | 12 +++++++++++- .../datalore/plot/builder/data/DataProcessing.kt | 7 ++++--- .../jetbrains/datalore/plot/config/StatProto.kt | 4 ++-- 5 files changed, 22 insertions(+), 6 deletions(-) diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/Stat.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/Stat.kt index 417dd00221d..83a8331c250 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/Stat.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/Stat.kt @@ -8,6 +8,8 @@ package jetbrains.datalore.plot.base interface Stat { fun apply(data: DataFrame, statCtx: StatContext, messageConsumer: (s: String) -> Unit = {}): DataFrame + fun normalize(data: DataFrame): DataFrame + fun consumes(): List> fun hasDefaultMapping(aes: Aes<*>): Boolean diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/BaseStat.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/BaseStat.kt index 6a87b44da29..79b48563d92 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/BaseStat.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/BaseStat.kt @@ -11,6 +11,9 @@ import jetbrains.datalore.plot.base.Stat import jetbrains.datalore.plot.base.data.TransformVar abstract class BaseStat(private val defaultMappings: Map, DataFrame.Variable>) : Stat { + override fun normalize(data: DataFrame): DataFrame { + return data + } override fun hasDefaultMapping(aes: Aes<*>): Boolean { return defaultMappings.containsKey(aes) diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/YDensityStat.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/YDensityStat.kt index 8fd16afdcfc..2c43ddeb200 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/YDensityStat.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/YDensityStat.kt @@ -57,6 +57,17 @@ class YDensityStat( return builder.build() } + override fun normalize(data: DataFrame): DataFrame { + val statDensity = data.getNumeric(Stats.DENSITY).map { it!! } + val densityMax = statDensity.maxOrNull()!! + val builder = DataFrame.Builder() + for (variable in data.variables()) { + builder.put(variable, data[variable]) + } + builder.putNumeric(Stats.VIOLIN_WIDTH, statDensity.map { it / densityMax }) + return builder.build() + } + private fun buildStat( xs: List, ys: List, @@ -99,7 +110,6 @@ class YDensityStat( return mutableMapOf( Stats.X to statX, Stats.Y to statY, - Stats.VIOLIN_WIDTH to statScaled, Stats.DENSITY to statDensity, Stats.COUNT to statCount, Stats.SCALED to statScaled diff --git a/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/data/DataProcessing.kt b/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/data/DataProcessing.kt index 658ecdb6cc7..a550d720b45 100644 --- a/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/data/DataProcessing.kt +++ b/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/data/DataProcessing.kt @@ -134,7 +134,7 @@ object DataProcessing { groupSizeListAfterStat = groupMerger.getGroupSizes() } - val dataAfterStat = Builder().run { + val resultData = Builder().run { // put results for (variable in resultSeries.keys) { put(variable, resultSeries[variable]!!) @@ -149,14 +149,15 @@ object DataProcessing { // build DataFrame build() } + val dataAfterNormalizing = stat.normalize(resultData) val groupingContextAfterStat = GroupingContext.withOrderedGroups( - dataAfterStat, + dataAfterNormalizing, groupSizeListAfterStat ) return DataAndGroupingContext( - dataAfterStat, + dataAfterNormalizing, groupingContextAfterStat ) } diff --git a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/StatProto.kt b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/StatProto.kt index 7b6bf997460..7a954ff40be 100644 --- a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/StatProto.kt +++ b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/StatProto.kt @@ -96,7 +96,7 @@ object StatProto { ) } - StatKind.YDENSITY -> return configureViolinStat(options) + StatKind.YDENSITY -> return configureYDensityStat(options) StatKind.DENSITY -> return configureDensityStat(options) @@ -172,7 +172,7 @@ object StatProto { ) } - private fun configureViolinStat(options: OptionsAccessor): YDensityStat { + private fun configureYDensityStat(options: OptionsAccessor): YDensityStat { var bwValue: Double? = null var bwMethod: DensityStat.BandWidthMethod = DensityStat.DEF_BW options[Density.BAND_WIDTH]?.run { From 73d451d1387352cf68489b53cb35ed7d0da535e3 Mon Sep 17 00:00:00 2001 From: ASmirnov-HORIS Date: Thu, 16 Dec 2021 11:43:28 +0300 Subject: [PATCH 48/81] Tiny fix. --- .../jetbrains/datalore/plot/base/stat/DensityStatUtil.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/DensityStatUtil.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/DensityStatUtil.kt index 408bd416a58..0762016d095 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/DensityStatUtil.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/DensityStatUtil.kt @@ -77,12 +77,12 @@ object DensityStatUtil { bwMethod: DensityStat.BandWidthMethod, ad: Double, ker: DensityStat.Kernel, - fullScalMax: Int + fullScanMax: Int ): (Double) -> Double { val bandWidth = bw ?: bandWidth(bwMethod, values) val kernelFun: (Double) -> Double = kernel(ker) - return when (values.size <= fullScalMax) { + return when (values.size <= fullScanMax) { true -> densityFunctionFullScan(values, weights, kernelFun, bandWidth, ad) false -> densityFunctionFast(values, weights, kernelFun, bandWidth, ad) } From 64de861f1e3edee8310e0526a651c905e6bd8e86 Mon Sep 17 00:00:00 2001 From: ASmirnov-HORIS Date: Fri, 17 Dec 2021 16:09:18 +0300 Subject: [PATCH 49/81] Fixes in ViolinGeom, YDensityStat. Update some names. --- .../jetbrains/datalore/plot/base/Stat.kt | 2 +- .../datalore/plot/base/aes/AesInitValue.kt | 2 +- .../datalore/plot/base/geom/ViolinGeom.kt | 61 +++++++++---------- .../datalore/plot/base/stat/BaseStat.kt | 4 +- .../datalore/plot/base/stat/DensityStat.kt | 6 +- .../datalore/plot/base/stat/Stats.kt | 4 +- .../datalore/plot/base/stat/YDensityStat.kt | 15 ++--- .../plot/builder/data/DataProcessing.kt | 4 +- .../datalore/plot/config/GeomProto.kt | 2 +- .../datalore/plot/config/StatProto.kt | 2 +- 10 files changed, 49 insertions(+), 53 deletions(-) diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/Stat.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/Stat.kt index 83a8331c250..2eb003c9e01 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/Stat.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/Stat.kt @@ -8,7 +8,7 @@ package jetbrains.datalore.plot.base interface Stat { fun apply(data: DataFrame, statCtx: StatContext, messageConsumer: (s: String) -> Unit = {}): DataFrame - fun normalize(data: DataFrame): DataFrame + fun normalize(dataAfterStat: DataFrame): DataFrame fun consumes(): List> diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/aes/AesInitValue.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/aes/AesInitValue.kt index 127872b9343..fdcd75fa3f5 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/aes/AesInitValue.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/aes/AesInitValue.kt @@ -68,7 +68,7 @@ object AesInitValue { VALUE_MAP[SIZE] = 0.5 // Line thickness. Should be redefined for other shapes VALUE_MAP[WIDTH] = 1.0 VALUE_MAP[HEIGHT] = 1.0 - VALUE_MAP[VIOLINWIDTH] = 1.0 + VALUE_MAP[VIOLINWIDTH] = 0.0 VALUE_MAP[WEIGHT] = 1.0 VALUE_MAP[INTERCEPT] = 0.0 VALUE_MAP[SLOPE] = 1.0 diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt index f52b976f6a9..e59ce80a6d8 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt @@ -8,10 +8,9 @@ package jetbrains.datalore.plot.base.geom import jetbrains.datalore.base.geometry.DoubleVector import jetbrains.datalore.plot.base.* import jetbrains.datalore.plot.base.geom.util.* -import jetbrains.datalore.plot.base.interact.GeomTargetCollector +import jetbrains.datalore.plot.base.interact.GeomTargetCollector.TooltipParams import jetbrains.datalore.plot.base.interact.TipLayoutHint import jetbrains.datalore.plot.base.render.SvgRoot -import jetbrains.datalore.plot.common.data.SeriesUtil class ViolinGeom : GeomBase() { @@ -32,32 +31,36 @@ class ViolinGeom : GeomBase() { coord: CoordinateSystem, ctx: GeomContext ) { - val helper = LinesHelper(pos, coord, ctx) - val groupedDataPoints = GeomUtil.withDefined( - aesthetics.dataPoints(), - Aes.X, - Aes.Y, - Aes.VIOLINWIDTH - ).groupBy { it.x() } - for ((_, nonOrderedDataPoints) in groupedDataPoints) { - val dataPoints = GeomUtil.ordered_Y(nonOrderedDataPoints, false) - val paths = helper.createBands(dataPoints, toLocationBound(-1.0, ctx), toLocationBound(1.0, ctx)) - paths.reverse() - appendNodes(paths, root) - - helper.setAlphaEnabled(false) - appendNodes(helper.createLines(dataPoints, toLocationBound(-1.0, ctx)), root) - appendNodes(helper.createLines(dataPoints, toLocationBound(1.0, ctx)), root) - } + GeomUtil.withDefined(aesthetics.dataPoints(), Aes.X, Aes.Y, Aes.VIOLINWIDTH) + .groupBy(DataPointAesthetics::x) + .map { (x, nonOrderedPoints) -> x to GeomUtil.ordered_Y(nonOrderedPoints, false) } + .forEach { (_, dataPoints) -> buildViolin(root, dataPoints, pos, coord, ctx) } buildHints(aesthetics, pos, coord, ctx, -1.0) buildHints(aesthetics, pos, coord, ctx, 1.0) } + private fun buildViolin( + root: SvgRoot, + dataPoints: Iterable, + pos: PositionAdjustment, + coord: CoordinateSystem, + ctx: GeomContext + ) { + val helper = LinesHelper(pos, coord, ctx) + val paths = helper.createBands(dataPoints, toLocationBound(-1.0, ctx), toLocationBound(1.0, ctx)) + paths.reverse() + appendNodes(paths, root) + + helper.setAlphaEnabled(false) + appendNodes(helper.createLines(dataPoints, toLocationBound(-1.0, ctx)), root) + appendNodes(helper.createLines(dataPoints, toLocationBound(1.0, ctx)), root) + } + private fun buildHints(aesthetics: Aesthetics, pos: PositionAdjustment, coord: CoordinateSystem, ctx: GeomContext, sign: Double) { val geomHelper = GeomHelper(pos, coord, ctx) val multiPointDataList = MultiPointDataConstructor.createMultiPointDataByGroup( - aesthetics.dataPoints(), + GeomUtil.withDefined(aesthetics.dataPoints(), Aes.X, Aes.Y, Aes.VIOLINWIDTH), MultiPointDataConstructor.singlePointAppender { p -> toClient(geomHelper, p, sign, ctx) }, MultiPointDataConstructor.reducer(0.999, false) ) @@ -67,7 +70,7 @@ class ViolinGeom : GeomBase() { targetCollector.addPath( multiPointData.points, multiPointData.localToGlobalIndex, - setupTooltipParams(multiPointData.aes), + TooltipParams.params().setColor(HintColorUtil.fromFill(multiPointData.aes)), if (ctx.flipped) { TipLayoutHint.Kind.VERTICAL_TOOLTIP } else { @@ -77,18 +80,14 @@ class ViolinGeom : GeomBase() { } } - protected open fun setupTooltipParams(aes: DataPointAesthetics): GeomTargetCollector.TooltipParams { - return GeomTargetCollector.TooltipParams.params().setColor(HintColorUtil.fromFill(aes)) - } - - private fun toClient(geomHelper: GeomHelper, p: DataPointAesthetics, sign: Double, ctx: GeomContext): DoubleVector? { + private fun toClient(geomHelper: GeomHelper, p: DataPointAesthetics, sign: Double, ctx: GeomContext): DoubleVector { val coord = toLocationBound(sign, ctx)(p) - return coord?.let { geomHelper.toClient(it, p) } + return coord.let { geomHelper.toClient(it, p) } } - private fun toLocationBound(sign: Double, ctx: GeomContext): (p: DataPointAesthetics) -> DoubleVector? { - return fun (p: DataPointAesthetics): DoubleVector? { - val x = p.x()!! + ctx.getResolution(Aes.X) / 2 * DEF_WIDTH * sign * p.violinwidth()!! + private fun toLocationBound(sign: Double, ctx: GeomContext): (p: DataPointAesthetics) -> DoubleVector { + return fun (p: DataPointAesthetics): DoubleVector { + val x = p.x()!! + ctx.getResolution(Aes.X) / 2 * WIDTH_SCALE * sign * p.violinwidth()!! val y = p.y()!! return DoubleVector(x, y) } @@ -96,7 +95,7 @@ class ViolinGeom : GeomBase() { companion object { const val HANDLES_GROUPS = true - const val DEF_WIDTH = 0.95 + const val WIDTH_SCALE = 0.95 } } \ No newline at end of file diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/BaseStat.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/BaseStat.kt index 79b48563d92..f4cafc98463 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/BaseStat.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/BaseStat.kt @@ -11,8 +11,8 @@ import jetbrains.datalore.plot.base.Stat import jetbrains.datalore.plot.base.data.TransformVar abstract class BaseStat(private val defaultMappings: Map, DataFrame.Variable>) : Stat { - override fun normalize(data: DataFrame): DataFrame { - return data + override fun normalize(dataAfterStat: DataFrame): DataFrame { + return dataAfterStat } override fun hasDefaultMapping(aes: Aes<*>): Boolean { diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/DensityStat.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/DensityStat.kt index c2bd1f9b59d..2dd7eaf93c7 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/DensityStat.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/DensityStat.kt @@ -16,7 +16,7 @@ import jetbrains.datalore.plot.common.data.SeriesUtil /** * Computes kernel density estimate for 'n' values evenly distributed throughout the range of the input series. * - * If size of the input series exceeds the 'fullScalMax' value, then the less accurate but more efficient computation replaces + * If size of the input series exceeds the 'fullScanMax' value, then the less accurate but more efficient computation replaces * highly inefficient 'full scan' computation. */ class DensityStat( @@ -25,7 +25,7 @@ class DensityStat( private val adjust: Double, private val kernel: Kernel, private val n: Int, - private val fullScalMax: Int + private val fullScanMax: Int ) : BaseStat(DEF_MAPPING) { init { @@ -74,7 +74,7 @@ class DensityStat( val statScaled = ArrayList() val densityFunction = DensityStatUtil.densityFunction( xs, weights, - bandWidth, bandWidthMethod, adjust, kernel, fullScalMax + bandWidth, bandWidthMethod, adjust, kernel, fullScanMax ) val nTotal = weights.sum() diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/Stats.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/Stats.kt index 348fd0bc4df..e11fc95470b 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/Stats.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/Stats.kt @@ -182,7 +182,7 @@ object Stats { adjust: Double = DensityStat.DEF_ADJUST, kernel: DensityStat.Kernel = DensityStat.DEF_KERNEL, n: Int = DensityStat.DEF_N, - fullScalMax: Int = DensityStat.DEF_FULL_SCAN_MAX + fullScanMax: Int = DensityStat.DEF_FULL_SCAN_MAX ): DensityStat { return DensityStat( bandWidth = bandWidth, @@ -190,7 +190,7 @@ object Stats { adjust = adjust, kernel = kernel, n = n, - fullScalMax = fullScalMax + fullScanMax = fullScanMax ) } diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/YDensityStat.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/YDensityStat.kt index 2c43ddeb200..3c869001f68 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/YDensityStat.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/YDensityStat.kt @@ -57,15 +57,12 @@ class YDensityStat( return builder.build() } - override fun normalize(data: DataFrame): DataFrame { - val statDensity = data.getNumeric(Stats.DENSITY).map { it!! } - val densityMax = statDensity.maxOrNull()!! - val builder = DataFrame.Builder() - for (variable in data.variables()) { - builder.put(variable, data[variable]) - } - builder.putNumeric(Stats.VIOLIN_WIDTH, statDensity.map { it / densityMax }) - return builder.build() + override fun normalize(dataAfterStat: DataFrame): DataFrame { + val statDensity = dataAfterStat.getNumeric(Stats.DENSITY).map { it!! } + val densityMax = statDensity.maxOrNull() ?: 1.0 + return dataAfterStat.builder() + .putNumeric(Stats.VIOLIN_WIDTH, statDensity.map { it / densityMax }) + .build() } private fun buildStat( diff --git a/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/data/DataProcessing.kt b/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/data/DataProcessing.kt index a550d720b45..3dadf50f9e6 100644 --- a/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/data/DataProcessing.kt +++ b/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/data/DataProcessing.kt @@ -134,7 +134,7 @@ object DataProcessing { groupSizeListAfterStat = groupMerger.getGroupSizes() } - val resultData = Builder().run { + val dataAfterStat = Builder().run { // put results for (variable in resultSeries.keys) { put(variable, resultSeries[variable]!!) @@ -149,7 +149,7 @@ object DataProcessing { // build DataFrame build() } - val dataAfterNormalizing = stat.normalize(resultData) + val dataAfterNormalizing = stat.normalize(dataAfterStat) val groupingContextAfterStat = GroupingContext.withOrderedGroups( dataAfterNormalizing, diff --git a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/GeomProto.kt b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/GeomProto.kt index cd3f5fa1218..634c01f8b4e 100644 --- a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/GeomProto.kt +++ b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/GeomProto.kt @@ -177,7 +177,7 @@ open class GeomProto constructor(val geomKind: GeomKind) { private fun violinDefaults(): Map { val defaults = HashMap() defaults["stat"] = "ydensity" - defaults["position"] = mapOf(Meta.NAME to "dodge", "width" to ViolinGeom.DEF_WIDTH) + defaults["position"] = mapOf(Meta.NAME to "dodge", "width" to ViolinGeom.WIDTH_SCALE) return defaults } diff --git a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/StatProto.kt b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/StatProto.kt index 7a954ff40be..b91733b7335 100644 --- a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/StatProto.kt +++ b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/StatProto.kt @@ -218,7 +218,7 @@ object StatProto { adjust = options.getDoubleDef(Density.ADJUST, DensityStat.DEF_ADJUST), kernel = kernel ?: DensityStat.DEF_KERNEL, n = options.getIntegerDef(Density.N, DensityStat.DEF_N), - fullScalMax = options.getIntegerDef(Density.FULL_SCAN_MAX, DensityStat.DEF_FULL_SCAN_MAX), + fullScanMax = options.getIntegerDef(Density.FULL_SCAN_MAX, DensityStat.DEF_FULL_SCAN_MAX), ) } From 34087a17d076992765323133f48efe23f978797a Mon Sep 17 00:00:00 2001 From: ASmirnov-HORIS Date: Fri, 17 Dec 2021 17:45:49 +0300 Subject: [PATCH 50/81] Update ViolinGeom for better performance. --- .../datalore/plot/base/geom/ViolinGeom.kt | 54 ++++++++++--------- 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt index e59ce80a6d8..c67d16ce296 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt @@ -35,9 +35,6 @@ class ViolinGeom : GeomBase() { .groupBy(DataPointAesthetics::x) .map { (x, nonOrderedPoints) -> x to GeomUtil.ordered_Y(nonOrderedPoints, false) } .forEach { (_, dataPoints) -> buildViolin(root, dataPoints, pos, coord, ctx) } - - buildHints(aesthetics, pos, coord, ctx, -1.0) - buildHints(aesthetics, pos, coord, ctx, 1.0) } private fun buildViolin( @@ -48,23 +45,45 @@ class ViolinGeom : GeomBase() { ctx: GeomContext ) { val helper = LinesHelper(pos, coord, ctx) - val paths = helper.createBands(dataPoints, toLocationBound(-1.0, ctx), toLocationBound(1.0, ctx)) + val leftBound = toLocationBound(-1.0, ctx) + val rightBound = toLocationBound(1.0, ctx) + + val paths = helper.createBands(dataPoints, leftBound, rightBound) paths.reverse() appendNodes(paths, root) helper.setAlphaEnabled(false) - appendNodes(helper.createLines(dataPoints, toLocationBound(-1.0, ctx)), root) - appendNodes(helper.createLines(dataPoints, toLocationBound(1.0, ctx)), root) + appendNodes(helper.createLines(dataPoints, leftBound), root) + appendNodes(helper.createLines(dataPoints, rightBound), root) + + buildHints(dataPoints, ctx, helper, leftBound) + buildHints(dataPoints, ctx, helper, rightBound) + } + + private fun toLocationBound( + sign: Double, + ctx: GeomContext + ): (p: DataPointAesthetics) -> DoubleVector { + return fun (p: DataPointAesthetics): DoubleVector { + val x = p.x()!! + ctx.getResolution(Aes.X) / 2 * WIDTH_SCALE * sign * p.violinwidth()!! + val y = p.y()!! + return DoubleVector(x, y) + } } - private fun buildHints(aesthetics: Aesthetics, pos: PositionAdjustment, coord: CoordinateSystem, ctx: GeomContext, sign: Double) { - val geomHelper = GeomHelper(pos, coord, ctx) + private fun buildHints( + dataPoints: Iterable, + ctx: GeomContext, + helper: GeomHelper, + bound: (p: DataPointAesthetics) -> DoubleVector + ) { val multiPointDataList = MultiPointDataConstructor.createMultiPointDataByGroup( - GeomUtil.withDefined(aesthetics.dataPoints(), Aes.X, Aes.Y, Aes.VIOLINWIDTH), - MultiPointDataConstructor.singlePointAppender { p -> toClient(geomHelper, p, sign, ctx) }, + dataPoints, + MultiPointDataConstructor.singlePointAppender { p -> + bound(p).let { helper.toClient(it, p) } + }, MultiPointDataConstructor.reducer(0.999, false) ) - val targetCollector = getGeomTargetCollector(ctx) for (multiPointData in multiPointDataList) { targetCollector.addPath( @@ -80,19 +99,6 @@ class ViolinGeom : GeomBase() { } } - private fun toClient(geomHelper: GeomHelper, p: DataPointAesthetics, sign: Double, ctx: GeomContext): DoubleVector { - val coord = toLocationBound(sign, ctx)(p) - return coord.let { geomHelper.toClient(it, p) } - } - - private fun toLocationBound(sign: Double, ctx: GeomContext): (p: DataPointAesthetics) -> DoubleVector { - return fun (p: DataPointAesthetics): DoubleVector { - val x = p.x()!! + ctx.getResolution(Aes.X) / 2 * WIDTH_SCALE * sign * p.violinwidth()!! - val y = p.y()!! - return DoubleVector(x, y) - } - } - companion object { const val HANDLES_GROUPS = true const val WIDTH_SCALE = 0.95 From 5810a8375c8753a205429cd66afeef38b2eb1838 Mon Sep 17 00:00:00 2001 From: ASmirnov-HORIS Date: Mon, 20 Dec 2021 13:41:50 +0300 Subject: [PATCH 51/81] Fix tests. --- .../datalore/plot/config/aes/TypedOptionConverterMap.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/aes/TypedOptionConverterMap.kt b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/aes/TypedOptionConverterMap.kt index a42f4a023e8..d8da3dc1bf9 100644 --- a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/aes/TypedOptionConverterMap.kt +++ b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/aes/TypedOptionConverterMap.kt @@ -29,6 +29,7 @@ import jetbrains.datalore.plot.base.Aes.Companion.SPEED import jetbrains.datalore.plot.base.Aes.Companion.SYM_X import jetbrains.datalore.plot.base.Aes.Companion.SYM_Y import jetbrains.datalore.plot.base.Aes.Companion.UPPER +import jetbrains.datalore.plot.base.Aes.Companion.VIOLINWIDTH import jetbrains.datalore.plot.base.Aes.Companion.VJUST import jetbrains.datalore.plot.base.Aes.Companion.WEIGHT import jetbrains.datalore.plot.base.Aes.Companion.WIDTH @@ -64,6 +65,7 @@ internal class TypedOptionConverterMap { this.put(SIZE, DOUBLE_CVT) this.put(WIDTH, DOUBLE_CVT) this.put(HEIGHT, DOUBLE_CVT) + this.put(VIOLINWIDTH, DOUBLE_CVT) this.put(WEIGHT, DOUBLE_CVT) this.put(INTERCEPT, DOUBLE_CVT) this.put(SLOPE, DOUBLE_CVT) From 219e4e4f67e348043b4c50dde7cc8fc303b95008 Mon Sep 17 00:00:00 2001 From: ASmirnov-HORIS Date: Mon, 20 Dec 2021 16:01:56 +0300 Subject: [PATCH 52/81] Small fixes. Mostly in names. --- .../datalore/plot/base/geom/ViolinGeom.kt | 19 +++++++++---------- .../plot/builder/data/DataProcessing.kt | 6 +++--- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt index c67d16ce296..5ed05c82a2d 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt @@ -45,19 +45,18 @@ class ViolinGeom : GeomBase() { ctx: GeomContext ) { val helper = LinesHelper(pos, coord, ctx) - val leftBound = toLocationBound(-1.0, ctx) - val rightBound = toLocationBound(1.0, ctx) + val leftBoundTransform = toLocationBound(-1.0, ctx) + val rightBoundTransform = toLocationBound(1.0, ctx) - val paths = helper.createBands(dataPoints, leftBound, rightBound) - paths.reverse() + val paths = helper.createBands(dataPoints, leftBoundTransform, rightBoundTransform) appendNodes(paths, root) helper.setAlphaEnabled(false) - appendNodes(helper.createLines(dataPoints, leftBound), root) - appendNodes(helper.createLines(dataPoints, rightBound), root) + appendNodes(helper.createLines(dataPoints, leftBoundTransform), root) + appendNodes(helper.createLines(dataPoints, rightBoundTransform), root) - buildHints(dataPoints, ctx, helper, leftBound) - buildHints(dataPoints, ctx, helper, rightBound) + buildHints(dataPoints, ctx, helper, leftBoundTransform) + buildHints(dataPoints, ctx, helper, rightBoundTransform) } private fun toLocationBound( @@ -75,12 +74,12 @@ class ViolinGeom : GeomBase() { dataPoints: Iterable, ctx: GeomContext, helper: GeomHelper, - bound: (p: DataPointAesthetics) -> DoubleVector + boundTransform: (p: DataPointAesthetics) -> DoubleVector ) { val multiPointDataList = MultiPointDataConstructor.createMultiPointDataByGroup( dataPoints, MultiPointDataConstructor.singlePointAppender { p -> - bound(p).let { helper.toClient(it, p) } + boundTransform(p).let { helper.toClient(it, p) } }, MultiPointDataConstructor.reducer(0.999, false) ) diff --git a/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/data/DataProcessing.kt b/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/data/DataProcessing.kt index 3dadf50f9e6..ba855192977 100644 --- a/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/data/DataProcessing.kt +++ b/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/data/DataProcessing.kt @@ -149,15 +149,15 @@ object DataProcessing { // build DataFrame build() } - val dataAfterNormalizing = stat.normalize(dataAfterStat) + val normalizedData = stat.normalize(dataAfterStat) val groupingContextAfterStat = GroupingContext.withOrderedGroups( - dataAfterNormalizing, + normalizedData, groupSizeListAfterStat ) return DataAndGroupingContext( - dataAfterNormalizing, + normalizedData, groupingContextAfterStat ) } From 761ccdba45401ceba5c8379c0e4677e1c487e427 Mon Sep 17 00:00:00 2001 From: ASmirnov-HORIS Date: Mon, 20 Dec 2021 16:57:44 +0300 Subject: [PATCH 53/81] Updates in YDensityStat: more clear normalization. --- .../jetbrains/datalore/plot/base/stat/YDensityStat.kt | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/YDensityStat.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/YDensityStat.kt index 3c869001f68..2c3559e4c6d 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/YDensityStat.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/YDensityStat.kt @@ -59,9 +59,14 @@ class YDensityStat( override fun normalize(dataAfterStat: DataFrame): DataFrame { val statDensity = dataAfterStat.getNumeric(Stats.DENSITY).map { it!! } - val densityMax = statDensity.maxOrNull() ?: 1.0 + val statViolinWidth = if (statDensity.isEmpty()) { + emptyList() + } else { + val densityMax = statDensity.maxOrNull()!! + statDensity.map { it / densityMax } + } return dataAfterStat.builder() - .putNumeric(Stats.VIOLIN_WIDTH, statDensity.map { it / densityMax }) + .putNumeric(Stats.VIOLIN_WIDTH, statViolinWidth) .build() } @@ -86,6 +91,7 @@ class YDensityStat( val (binY, binW) = (filteredY zip filteredW) .sortedBy { it.first } .unzip() + if (binY.isEmpty()) continue val ySummary = FiveNumberSummary(binY) val rangeY = ClosedRange(ySummary.min, ySummary.max) val binStatY = DensityStatUtil.createStepValues(rangeY, n) From 253d8c201c03d837b0a208c71319422ca5fba2cf Mon Sep 17 00:00:00 2001 From: ASmirnov-HORIS Date: Tue, 21 Dec 2021 13:10:55 +0300 Subject: [PATCH 54/81] Fix error for empty dataframe in normalize() for YDensityStat. --- .../kotlin/jetbrains/datalore/plot/base/stat/YDensityStat.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/YDensityStat.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/YDensityStat.kt index 2c3559e4c6d..c7158795f03 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/YDensityStat.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/YDensityStat.kt @@ -58,10 +58,10 @@ class YDensityStat( } override fun normalize(dataAfterStat: DataFrame): DataFrame { - val statDensity = dataAfterStat.getNumeric(Stats.DENSITY).map { it!! } - val statViolinWidth = if (statDensity.isEmpty()) { + val statViolinWidth = if (dataAfterStat.rowCount() == 0) { emptyList() } else { + val statDensity = dataAfterStat.getNumeric(Stats.DENSITY).map { it!! } val densityMax = statDensity.maxOrNull()!! statDensity.map { it / densityMax } } From 26019fd32f55bb140bd4d1f48658397b1992bb72 Mon Sep 17 00:00:00 2001 From: ASmirnov-HORIS Date: Tue, 18 Jan 2022 20:46:53 +0300 Subject: [PATCH 55/81] Add the draw_quantiles parameter to the geom_violin(). --- .../datalore/plot/base/geom/ViolinGeom.kt | 67 +++++++++++++++++++ .../builder/assemble/geom/GeomProvider.kt | 7 +- .../plot/config/GeomProtoClientSide.kt | 10 ++- .../jetbrains/datalore/plot/config/Option.kt | 4 ++ .../plotDemo/model/plotConfig/Violin.kt | 3 +- 5 files changed, 86 insertions(+), 5 deletions(-) diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt index 5ed05c82a2d..2e60a2d9f12 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt @@ -7,12 +7,18 @@ package jetbrains.datalore.plot.base.geom import jetbrains.datalore.base.geometry.DoubleVector import jetbrains.datalore.plot.base.* +import jetbrains.datalore.plot.base.aes.AestheticsBuilder import jetbrains.datalore.plot.base.geom.util.* import jetbrains.datalore.plot.base.interact.GeomTargetCollector.TooltipParams import jetbrains.datalore.plot.base.interact.TipLayoutHint import jetbrains.datalore.plot.base.render.SvgRoot class ViolinGeom : GeomBase() { + private var drawQuantiles = DEF_DRAW_QUANTILES + + fun setDrawQuantiles(quantiles: List<*>) { + drawQuantiles = quantiles.map { it.toString().toDouble() } + } override fun buildIntern( root: SvgRoot, @@ -55,10 +61,69 @@ class ViolinGeom : GeomBase() { appendNodes(helper.createLines(dataPoints, leftBoundTransform), root) appendNodes(helper.createLines(dataPoints, rightBoundTransform), root) + buildQuantiles(root, dataPoints, pos, coord, ctx) + buildHints(dataPoints, ctx, helper, leftBoundTransform) buildHints(dataPoints, ctx, helper, rightBoundTransform) } + private fun buildQuantiles( + root: SvgRoot, + dataPoints: Iterable, + pos: PositionAdjustment, + coord: CoordinateSystem, + ctx: GeomContext + ) { + if (drawQuantiles.isEmpty()) return + + // Calculate quantiles + val vws = dataPoints.map { it.violinwidth()!! } + val ys = dataPoints.map { it.y()!! } + val xsMin = dataPoints.map { toLocationBound(-1.0, ctx)(it) }.map { it.x } + val xsMax = dataPoints.map { toLocationBound(1.0, ctx)(it) }.map { it.x } + val vwsSum = vws.sum() + val dens = vws.runningReduce { cumSum, elem -> cumSum + elem }.map { it / vwsSum } + val quantY = drawQuantiles.map { pwLinInterp(dens, ys)(it) } + val quantXMin = quantY.map { pwLinInterp(ys, xsMin)(it) } + val quantXMax = quantY.map { pwLinInterp(ys, xsMax)(it) } + + // Construct dataPoints by quantiles + val quantilesColor = dataPoints.first().color() + val quantilesSize = dataPoints.first().size() + val quantileDataPoints = AestheticsBuilder(quantY.size) + .xmin(AestheticsBuilder.list(quantXMin)) + .xmax(AestheticsBuilder.list(quantXMax)) + .y(AestheticsBuilder.list(quantY)) + .color(AestheticsBuilder.constant(quantilesColor)) + .size(AestheticsBuilder.constant(quantilesSize)) + .build() + .dataPoints() + + // Draw quantiles + val geomHelper = GeomHelper(pos, coord, ctx) + val helper = geomHelper.createSvgElementHelper() + for (p in quantileDataPoints) { + val start = DoubleVector(p.xmin()!!, p.y()!!) + val end = DoubleVector(p.xmax()!!, p.y()!!) + val line = helper.createLine(start, end, p) + root.add(line) + } + + // TODO: Draw tooltips + } + + private fun pwLinInterp(x: List, y: List): (Double) -> Double { + // Returns (bounded) piecewise linear interpolation function + return fun (t: Double): Double { + val i = x.indexOfFirst { it >= t } + if (i == 0) return y.first() + if (i == -1) return y.last() + val a = (y[i] - y[i - 1]) / (x[i] - x[i - 1]) + val b = y[i - 1] - a * x[i - 1] + return a * t + b + } + } + private fun toLocationBound( sign: Double, ctx: GeomContext @@ -99,6 +164,8 @@ class ViolinGeom : GeomBase() { } companion object { + val DEF_DRAW_QUANTILES = emptyList() + const val HANDLES_GROUPS = true const val WIDTH_SCALE = 0.95 } diff --git a/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/assemble/geom/GeomProvider.kt b/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/assemble/geom/GeomProvider.kt index 78376ee15cd..e12bd82a1a7 100644 --- a/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/assemble/geom/GeomProvider.kt +++ b/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/assemble/geom/GeomProvider.kt @@ -228,12 +228,13 @@ abstract class GeomProvider private constructor(val geomKind: GeomKind) { ).build() } - fun violin(): GeomProvider { + fun violin(supplier: () -> Geom): GeomProvider { return GeomProviderBuilder( GeomKind.VIOLIN, AestheticsDefaults.violin(), - ViolinGeom.HANDLES_GROUPS - ) { ViolinGeom() }.build() + ViolinGeom.HANDLES_GROUPS, + supplier + ).build() } fun livemap( diff --git a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/GeomProtoClientSide.kt b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/GeomProtoClientSide.kt index 0eaae1bd6c2..b993b96e4e1 100644 --- a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/GeomProtoClientSide.kt +++ b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/GeomProtoClientSide.kt @@ -22,6 +22,7 @@ import jetbrains.datalore.plot.config.Option.Geom.PointRange import jetbrains.datalore.plot.config.Option.Geom.Segment import jetbrains.datalore.plot.config.Option.Geom.Step import jetbrains.datalore.plot.config.Option.Geom.Text +import jetbrains.datalore.plot.config.Option.Geom.Violin class GeomProtoClientSide(geomKind: GeomKind) : GeomProto(geomKind) { @@ -82,6 +83,14 @@ class GeomProtoClientSide(geomKind: GeomKind) : GeomProto(geomKind) { geom } + GeomKind.VIOLIN -> return GeomProvider.violin { + val geom = ViolinGeom() + if (opts.hasOwn(Violin.DRAW_QUANTILES)) { + geom.setDrawQuantiles(opts.getList(Violin.DRAW_QUANTILES)) + } + geom + } + GeomKind.LIVE_MAP -> { return GeomProvider.livemap(parseFromLayerOptions(opts)) } @@ -191,7 +200,6 @@ class GeomProtoClientSide(geomKind: GeomKind) : GeomProto(geomKind) { PROVIDER[GeomKind.H_LINE] = GeomProvider.hline() PROVIDER[GeomKind.V_LINE] = GeomProvider.vline() // boxplot - special case - PROVIDER[GeomKind.VIOLIN] = GeomProvider.violin() PROVIDER[GeomKind.RIBBON] = GeomProvider.ribbon() PROVIDER[GeomKind.AREA] = GeomProvider.area() PROVIDER[GeomKind.DENSITY] = GeomProvider.density() diff --git a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/Option.kt b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/Option.kt index e295387ac00..d67448c3e3d 100644 --- a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/Option.kt +++ b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/Option.kt @@ -138,6 +138,10 @@ object Option { const val SIZE = "outlier_size" } + object Violin { + const val DRAW_QUANTILES = "draw_quantiles" + } + object Jitter { const val WIDTH = "width" const val HEIGHT = "height" diff --git a/plot-demo-common/src/commonMain/kotlin/jetbrains/datalore/plotDemo/model/plotConfig/Violin.kt b/plot-demo-common/src/commonMain/kotlin/jetbrains/datalore/plotDemo/model/plotConfig/Violin.kt index b85661bfd26..5b07dfedd0a 100644 --- a/plot-demo-common/src/commonMain/kotlin/jetbrains/datalore/plotDemo/model/plotConfig/Violin.kt +++ b/plot-demo-common/src/commonMain/kotlin/jetbrains/datalore/plotDemo/model/plotConfig/Violin.kt @@ -27,7 +27,8 @@ class Violin { " 'layers': [" + " {" + " 'geom': 'violin'," + - " 'alpha': 0.7" + + " 'alpha': 0.7," + + " 'draw_quantiles': [0.25, 0.5, 0.75]" + " }" + " ]" + "}" From 4cdbe483bf3c4644405c1d467b36abd521ff92e8 Mon Sep 17 00:00:00 2001 From: ASmirnov-HORIS Date: Wed, 19 Jan 2022 16:40:01 +0300 Subject: [PATCH 56/81] Add hints for quantiles to the geom_violin(). --- .../datalore/plot/base/geom/ViolinGeom.kt | 44 ++++++++++++++++++- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt index 2e60a2d9f12..b945edd9ed3 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt @@ -5,6 +5,7 @@ package jetbrains.datalore.plot.base.geom +import jetbrains.datalore.base.geometry.DoubleRectangle import jetbrains.datalore.base.geometry.DoubleVector import jetbrains.datalore.plot.base.* import jetbrains.datalore.plot.base.aes.AestheticsBuilder @@ -107,9 +108,15 @@ class ViolinGeom : GeomBase() { val end = DoubleVector(p.xmax()!!, p.y()!!) val line = helper.createLine(start, end, p) root.add(line) - } - // TODO: Draw tooltips + // Draw quantile tooltips + buildQuantileHints( + DoubleRectangle(start.x, start.y, end.x - start.x, 0.0), + p, + ctx, + geomHelper + ) + } } private fun pwLinInterp(x: List, y: List): (Double) -> Double { @@ -163,6 +170,39 @@ class ViolinGeom : GeomBase() { } } + private fun buildQuantileHints( + rect: DoubleRectangle, + p: DataPointAesthetics, + ctx: GeomContext, + geomHelper: GeomHelper + ) { + val clientRect = geomHelper.toClient(rect, p) + val objectRadius = if (ctx.flipped) clientRect.height / 2.0 else clientRect.width / 2.0 + val tooltipKind = if (ctx.flipped) { + TipLayoutHint.Kind.ROTATED_TOOLTIP + } else { + TipLayoutHint.Kind.HORIZONTAL_TOOLTIP + } + + val hint = HintsCollection.HintConfigFactory() + .defaultObjectRadius(objectRadius) + .defaultX(rect.left) + .defaultKind(tooltipKind) + + val hints = HintsCollection(p, geomHelper) + .addHint(hint.create(Aes.Y)) + .hints + + ctx.targetCollector.addRectangle( + p.index(), + clientRect, + TooltipParams.params() + .setTipLayoutHints(hints) + .setColor(HintColorUtil.fromColor(p)), + tooltipKind = tooltipKind + ) + } + companion object { val DEF_DRAW_QUANTILES = emptyList() From 27dacc05c01da3320c43f00975c5493c93b6f87c Mon Sep 17 00:00:00 2001 From: ASmirnov-HORIS Date: Wed, 19 Jan 2022 18:55:37 +0300 Subject: [PATCH 57/81] Fix hint texts for quantiles to the geom_violin(). --- .../datalore/plot/base/geom/ViolinGeom.kt | 18 +++++++++--------- .../plot/config/GeomInteractionUtil.kt | 1 + 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt index b945edd9ed3..1fb3912be1c 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt @@ -103,16 +103,17 @@ class ViolinGeom : GeomBase() { // Draw quantiles val geomHelper = GeomHelper(pos, coord, ctx) val helper = geomHelper.createSvgElementHelper() - for (p in quantileDataPoints) { - val start = DoubleVector(p.xmin()!!, p.y()!!) - val end = DoubleVector(p.xmax()!!, p.y()!!) - val line = helper.createLine(start, end, p) + for (q in quantileDataPoints) { + val start = DoubleVector(q.xmin()!!, q.y()!!) + val end = DoubleVector(q.xmax()!!, q.y()!!) + val line = helper.createLine(start, end, q) root.add(line) - // Draw quantile tooltips + // Draw tooltips + val nearestDataPoint = dataPoints.first { p -> p.y()!! >= q.y()!! } buildQuantileHints( DoubleRectangle(start.x, start.y, end.x - start.x, 0.0), - p, + nearestDataPoint, ctx, geomHelper ) @@ -177,7 +178,6 @@ class ViolinGeom : GeomBase() { geomHelper: GeomHelper ) { val clientRect = geomHelper.toClient(rect, p) - val objectRadius = if (ctx.flipped) clientRect.height / 2.0 else clientRect.width / 2.0 val tooltipKind = if (ctx.flipped) { TipLayoutHint.Kind.ROTATED_TOOLTIP } else { @@ -185,8 +185,8 @@ class ViolinGeom : GeomBase() { } val hint = HintsCollection.HintConfigFactory() - .defaultObjectRadius(objectRadius) - .defaultX(rect.left) + .defaultObjectRadius(0.0) + .defaultX(rect.center.x) .defaultKind(tooltipKind) val hints = HintsCollection(p, geomHelper) diff --git a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/GeomInteractionUtil.kt b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/GeomInteractionUtil.kt index daf8e7cdf9e..4ba005e483a 100644 --- a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/GeomInteractionUtil.kt +++ b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/GeomInteractionUtil.kt @@ -171,6 +171,7 @@ object GeomInteractionUtil { GeomKind.POINT_RANGE, GeomKind.RIBBON -> listOf(Aes.YMAX, Aes.YMIN) GeomKind.BOX_PLOT -> listOf(Aes.YMAX, Aes.UPPER, Aes.MIDDLE, Aes.LOWER, Aes.YMIN) + GeomKind.VIOLIN -> listOf(Aes.Y) GeomKind.SMOOTH -> listOf(Aes.YMAX, Aes.YMIN, Aes.Y) else -> emptyList() } From 1c1ca39608558d8dcafbf4b1dd1026e6a791ba93 Mon Sep 17 00:00:00 2001 From: ASmirnov-HORIS Date: Thu, 20 Jan 2022 16:06:02 +0300 Subject: [PATCH 58/81] Add the quantile aesthetic. --- .../kotlin/jetbrains/datalore/plot/base/Aes.kt | 2 ++ .../jetbrains/datalore/plot/base/DataPointAesthetics.kt | 2 ++ .../jetbrains/datalore/plot/base/aes/AesInitValue.kt | 2 ++ .../jetbrains/datalore/plot/base/aes/AesVisitor.kt | 6 ++++++ .../datalore/plot/base/aes/AestheticsBuilder.kt | 9 +++++++++ .../jetbrains/datalore/plot/base/data/TransformVar.kt | 5 +++++ .../jetbrains/datalore/plot/base/geom/ViolinGeom.kt | 3 ++- .../plot/base/geom/util/DataPointAestheticsDelegate.kt | 4 ++++ .../datalore/plot/builder/scale/DefaultMapperProvider.kt | 2 ++ .../datalore/plot/builder/scale/DefaultNaValue.kt | 2 ++ .../datalore/plot/config/GeomInteractionUtil.kt | 2 +- .../datalore/plot/config/aes/TypedOptionConverterMap.kt | 2 ++ .../server/config/transform/bistro/util/LayerOptions.kt | 1 - 13 files changed, 39 insertions(+), 3 deletions(-) diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/Aes.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/Aes.kt index ec1a50fd742..e1998bbcca8 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/Aes.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/Aes.kt @@ -40,6 +40,7 @@ class Aes private constructor(val name: String, val isNumeric: Boolean = true val WIDTH: Aes = Aes("width") val HEIGHT: Aes = Aes("height") val VIOLINWIDTH: Aes = Aes("violinwidth") + val QUANTILE: Aes = Aes("quantile") val WEIGHT: Aes = Aes("weight") val INTERCEPT: Aes = Aes("intercept") val SLOPE: Aes = Aes("slope") @@ -155,6 +156,7 @@ class Aes private constructor(val name: String, val isNumeric: Boolean = true aes == WIDTH || aes == HEIGHT || aes == VIOLINWIDTH || + aes == QUANTILE || aes == HJUST || aes == VJUST || aes == ANGLE || diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/DataPointAesthetics.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/DataPointAesthetics.kt index 02d3c411177..b2286c14044 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/DataPointAesthetics.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/DataPointAesthetics.kt @@ -40,6 +40,8 @@ interface DataPointAesthetics { fun violinwidth(): Double? + fun quantile(): Double? + fun weight(): Double? fun intercept(): Double? diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/aes/AesInitValue.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/aes/AesInitValue.kt index fdcd75fa3f5..a8ed16340bf 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/aes/AesInitValue.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/aes/AesInitValue.kt @@ -25,6 +25,7 @@ import jetbrains.datalore.plot.base.Aes.Companion.LINETYPE import jetbrains.datalore.plot.base.Aes.Companion.LOWER import jetbrains.datalore.plot.base.Aes.Companion.MAP_ID import jetbrains.datalore.plot.base.Aes.Companion.MIDDLE +import jetbrains.datalore.plot.base.Aes.Companion.QUANTILE import jetbrains.datalore.plot.base.Aes.Companion.SHAPE import jetbrains.datalore.plot.base.Aes.Companion.SIZE import jetbrains.datalore.plot.base.Aes.Companion.SLOPE @@ -69,6 +70,7 @@ object AesInitValue { VALUE_MAP[WIDTH] = 1.0 VALUE_MAP[HEIGHT] = 1.0 VALUE_MAP[VIOLINWIDTH] = 0.0 + VALUE_MAP[QUANTILE] = Double.NaN VALUE_MAP[WEIGHT] = 1.0 VALUE_MAP[INTERCEPT] = 0.0 VALUE_MAP[SLOPE] = 1.0 diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/aes/AesVisitor.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/aes/AesVisitor.kt index e2135fb1b6c..574e3c6b817 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/aes/AesVisitor.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/aes/AesVisitor.kt @@ -22,6 +22,7 @@ import jetbrains.datalore.plot.base.Aes.Companion.LINETYPE import jetbrains.datalore.plot.base.Aes.Companion.LOWER import jetbrains.datalore.plot.base.Aes.Companion.MAP_ID import jetbrains.datalore.plot.base.Aes.Companion.MIDDLE +import jetbrains.datalore.plot.base.Aes.Companion.QUANTILE import jetbrains.datalore.plot.base.Aes.Companion.SHAPE import jetbrains.datalore.plot.base.Aes.Companion.SIZE import jetbrains.datalore.plot.base.Aes.Companion.SLOPE @@ -107,6 +108,9 @@ abstract class AesVisitor { if (aes == VIOLINWIDTH) { return violinwidth() } + if (aes == QUANTILE) { + return quantile() + } if (aes == WEIGHT) { return weight() } @@ -213,6 +217,8 @@ abstract class AesVisitor { protected abstract fun violinwidth(): T + protected abstract fun quantile(): T + protected abstract fun weight(): T protected abstract fun intercept(): T diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/aes/AestheticsBuilder.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/aes/AestheticsBuilder.kt index 00412ebb828..324709a157d 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/aes/AestheticsBuilder.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/aes/AestheticsBuilder.kt @@ -25,6 +25,7 @@ import jetbrains.datalore.plot.base.Aes.Companion.LINETYPE import jetbrains.datalore.plot.base.Aes.Companion.LOWER import jetbrains.datalore.plot.base.Aes.Companion.MAP_ID import jetbrains.datalore.plot.base.Aes.Companion.MIDDLE +import jetbrains.datalore.plot.base.Aes.Companion.QUANTILE import jetbrains.datalore.plot.base.Aes.Companion.SHAPE import jetbrains.datalore.plot.base.Aes.Companion.SIZE import jetbrains.datalore.plot.base.Aes.Companion.SLOPE @@ -185,6 +186,10 @@ class AestheticsBuilder @JvmOverloads constructor(private var myDataPointCount: return aes(SYM_Y, v) } + fun quantile(v: (Int) -> Double?): AestheticsBuilder { + return aes(QUANTILE, v) + } + fun constantAes(aes: Aes, v: T): AestheticsBuilder { myConstantAes.add(aes) myIndexFunctionMap[aes] = constant(v) @@ -403,6 +408,10 @@ class AestheticsBuilder @JvmOverloads constructor(private var myDataPointCount: return get(VIOLINWIDTH) } + override fun quantile(): Double { + return get(QUANTILE) + } + override fun weight(): Double { return get(WEIGHT) } diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/data/TransformVar.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/data/TransformVar.kt index 947a3ff84c4..a9900222a4a 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/data/TransformVar.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/data/TransformVar.kt @@ -25,6 +25,7 @@ object TransformVar { val WIDTH = DataFrame.Variable("transform.WIDTH", TRANSFORM) val HEIGHT = DataFrame.Variable("transform.HEIGHT", TRANSFORM) val VIOLINWIDTH = DataFrame.Variable("transform.VIOLINWIDTH", TRANSFORM) + val QUANTILE = DataFrame.Variable("transform.QUANTILE", TRANSFORM) val WEIGHT = DataFrame.Variable("transform.WEIGHT", TRANSFORM) val INTERCEPT = DataFrame.Variable("transform.INTERCEPT", TRANSFORM) val SLOPE = DataFrame.Variable("transform.SLOPE", TRANSFORM) @@ -134,6 +135,10 @@ object TransformVar { return VIOLINWIDTH } + override fun quantile(): DataFrame.Variable { + return QUANTILE + } + override fun weight(): DataFrame.Variable { return WEIGHT } diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt index 1fb3912be1c..e0fc4f94755 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt @@ -95,6 +95,7 @@ class ViolinGeom : GeomBase() { .xmin(AestheticsBuilder.list(quantXMin)) .xmax(AestheticsBuilder.list(quantXMax)) .y(AestheticsBuilder.list(quantY)) + .quantile(AestheticsBuilder.list(drawQuantiles)) .color(AestheticsBuilder.constant(quantilesColor)) .size(AestheticsBuilder.constant(quantilesSize)) .build() @@ -190,7 +191,7 @@ class ViolinGeom : GeomBase() { .defaultKind(tooltipKind) val hints = HintsCollection(p, geomHelper) - .addHint(hint.create(Aes.Y)) + .addHint(hint.create(Aes.QUANTILE)) .hints ctx.targetCollector.addRectangle( diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/util/DataPointAestheticsDelegate.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/util/DataPointAestheticsDelegate.kt index b070dee4cf1..cd25b5ecc03 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/util/DataPointAestheticsDelegate.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/util/DataPointAestheticsDelegate.kt @@ -74,6 +74,10 @@ open class DataPointAestheticsDelegate(private val p: DataPointAesthetics) : return p.violinwidth() } + override fun quantile(): Double? { + return p.quantile() + } + override fun weight(): Double? { return p.weight() } diff --git a/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/scale/DefaultMapperProvider.kt b/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/scale/DefaultMapperProvider.kt index ba2f184b7b9..53cb6d5f525 100644 --- a/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/scale/DefaultMapperProvider.kt +++ b/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/scale/DefaultMapperProvider.kt @@ -23,6 +23,7 @@ import jetbrains.datalore.plot.base.Aes.Companion.LINETYPE import jetbrains.datalore.plot.base.Aes.Companion.LOWER import jetbrains.datalore.plot.base.Aes.Companion.MAP_ID import jetbrains.datalore.plot.base.Aes.Companion.MIDDLE +import jetbrains.datalore.plot.base.Aes.Companion.QUANTILE import jetbrains.datalore.plot.base.Aes.Companion.SHAPE import jetbrains.datalore.plot.base.Aes.Companion.SIZE import jetbrains.datalore.plot.base.Aes.Companion.SLOPE @@ -99,6 +100,7 @@ object DefaultMapperProvider { this.put(HEIGHT, NUMERIC_IDENTITY) this.put(WEIGHT, NUMERIC_IDENTITY) this.put(VIOLINWIDTH, NUMERIC_IDENTITY) + this.put(QUANTILE, NUMERIC_IDENTITY) this.put(INTERCEPT, NUMERIC_IDENTITY) this.put(SLOPE, NUMERIC_IDENTITY) this.put(XINTERCEPT, NUMERIC_IDENTITY) diff --git a/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/scale/DefaultNaValue.kt b/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/scale/DefaultNaValue.kt index 31e993460f4..5771b50ee2f 100644 --- a/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/scale/DefaultNaValue.kt +++ b/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/scale/DefaultNaValue.kt @@ -25,6 +25,7 @@ import jetbrains.datalore.plot.base.Aes.Companion.LINETYPE import jetbrains.datalore.plot.base.Aes.Companion.LOWER import jetbrains.datalore.plot.base.Aes.Companion.MAP_ID import jetbrains.datalore.plot.base.Aes.Companion.MIDDLE +import jetbrains.datalore.plot.base.Aes.Companion.QUANTILE import jetbrains.datalore.plot.base.Aes.Companion.SHAPE import jetbrains.datalore.plot.base.Aes.Companion.SIZE import jetbrains.datalore.plot.base.Aes.Companion.SLOPE @@ -69,6 +70,7 @@ object DefaultNaValue { VALUE_MAP.put(WIDTH, 1.0) VALUE_MAP.put(HEIGHT, 1.0) VALUE_MAP.put(VIOLINWIDTH, 0.0) + VALUE_MAP.put(QUANTILE, 0.0) VALUE_MAP.put(WEIGHT, 1.0) VALUE_MAP.put(INTERCEPT, 0.0) VALUE_MAP.put(SLOPE, 1.0) diff --git a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/GeomInteractionUtil.kt b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/GeomInteractionUtil.kt index 4ba005e483a..53fa754a129 100644 --- a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/GeomInteractionUtil.kt +++ b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/GeomInteractionUtil.kt @@ -171,7 +171,7 @@ object GeomInteractionUtil { GeomKind.POINT_RANGE, GeomKind.RIBBON -> listOf(Aes.YMAX, Aes.YMIN) GeomKind.BOX_PLOT -> listOf(Aes.YMAX, Aes.UPPER, Aes.MIDDLE, Aes.LOWER, Aes.YMIN) - GeomKind.VIOLIN -> listOf(Aes.Y) + GeomKind.VIOLIN -> listOf(Aes.QUANTILE) GeomKind.SMOOTH -> listOf(Aes.YMAX, Aes.YMIN, Aes.Y) else -> emptyList() } diff --git a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/aes/TypedOptionConverterMap.kt b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/aes/TypedOptionConverterMap.kt index d8da3dc1bf9..55b172c2fc7 100644 --- a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/aes/TypedOptionConverterMap.kt +++ b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/aes/TypedOptionConverterMap.kt @@ -22,6 +22,7 @@ import jetbrains.datalore.plot.base.Aes.Companion.LINETYPE import jetbrains.datalore.plot.base.Aes.Companion.LOWER import jetbrains.datalore.plot.base.Aes.Companion.MAP_ID import jetbrains.datalore.plot.base.Aes.Companion.MIDDLE +import jetbrains.datalore.plot.base.Aes.Companion.QUANTILE import jetbrains.datalore.plot.base.Aes.Companion.SHAPE import jetbrains.datalore.plot.base.Aes.Companion.SIZE import jetbrains.datalore.plot.base.Aes.Companion.SLOPE @@ -66,6 +67,7 @@ internal class TypedOptionConverterMap { this.put(WIDTH, DOUBLE_CVT) this.put(HEIGHT, DOUBLE_CVT) this.put(VIOLINWIDTH, DOUBLE_CVT) + this.put(QUANTILE, DOUBLE_CVT) this.put(WEIGHT, DOUBLE_CVT) this.put(INTERCEPT, DOUBLE_CVT) this.put(SLOPE, DOUBLE_CVT) diff --git a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/server/config/transform/bistro/util/LayerOptions.kt b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/server/config/transform/bistro/util/LayerOptions.kt index 6529d4b3f11..06d4bb85df9 100644 --- a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/server/config/transform/bistro/util/LayerOptions.kt +++ b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/server/config/transform/bistro/util/LayerOptions.kt @@ -42,7 +42,6 @@ class LayerOptions : Options() { var size: Double? by map(Aes.SIZE) var width: Double? by map(Aes.WIDTH) var height: Double? by map(Aes.HEIGHT) - var violinwidth: Double? by map(Aes.VIOLINWIDTH) var weight: Double? by map(Aes.WEIGHT) var intercept: Double? by map(Aes.INTERCEPT) var slope: Double? by map(Aes.SLOPE) From 0c610a1a631451a6d684762ff9fa62e90fc4da6c Mon Sep 17 00:00:00 2001 From: ASmirnov-HORIS Date: Thu, 20 Jan 2022 18:54:48 +0300 Subject: [PATCH 59/81] Remove the quantile aesthetic. Remove all hints except the axis ones. --- .../jetbrains/datalore/plot/base/Aes.kt | 2 - .../datalore/plot/base/DataPointAesthetics.kt | 2 - .../datalore/plot/base/aes/AesInitValue.kt | 2 - .../datalore/plot/base/aes/AesVisitor.kt | 6 -- .../plot/base/aes/AestheticsBuilder.kt | 9 -- .../datalore/plot/base/data/TransformVar.kt | 5 -- .../datalore/plot/base/geom/ViolinGeom.kt | 82 ++++++++----------- .../geom/util/DataPointAestheticsDelegate.kt | 4 - .../builder/scale/DefaultMapperProvider.kt | 2 - .../plot/builder/scale/DefaultNaValue.kt | 2 - .../plot/config/GeomInteractionUtil.kt | 1 - .../config/aes/TypedOptionConverterMap.kt | 2 - 12 files changed, 35 insertions(+), 84 deletions(-) diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/Aes.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/Aes.kt index e1998bbcca8..ec1a50fd742 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/Aes.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/Aes.kt @@ -40,7 +40,6 @@ class Aes private constructor(val name: String, val isNumeric: Boolean = true val WIDTH: Aes = Aes("width") val HEIGHT: Aes = Aes("height") val VIOLINWIDTH: Aes = Aes("violinwidth") - val QUANTILE: Aes = Aes("quantile") val WEIGHT: Aes = Aes("weight") val INTERCEPT: Aes = Aes("intercept") val SLOPE: Aes = Aes("slope") @@ -156,7 +155,6 @@ class Aes private constructor(val name: String, val isNumeric: Boolean = true aes == WIDTH || aes == HEIGHT || aes == VIOLINWIDTH || - aes == QUANTILE || aes == HJUST || aes == VJUST || aes == ANGLE || diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/DataPointAesthetics.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/DataPointAesthetics.kt index b2286c14044..02d3c411177 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/DataPointAesthetics.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/DataPointAesthetics.kt @@ -40,8 +40,6 @@ interface DataPointAesthetics { fun violinwidth(): Double? - fun quantile(): Double? - fun weight(): Double? fun intercept(): Double? diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/aes/AesInitValue.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/aes/AesInitValue.kt index a8ed16340bf..fdcd75fa3f5 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/aes/AesInitValue.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/aes/AesInitValue.kt @@ -25,7 +25,6 @@ import jetbrains.datalore.plot.base.Aes.Companion.LINETYPE import jetbrains.datalore.plot.base.Aes.Companion.LOWER import jetbrains.datalore.plot.base.Aes.Companion.MAP_ID import jetbrains.datalore.plot.base.Aes.Companion.MIDDLE -import jetbrains.datalore.plot.base.Aes.Companion.QUANTILE import jetbrains.datalore.plot.base.Aes.Companion.SHAPE import jetbrains.datalore.plot.base.Aes.Companion.SIZE import jetbrains.datalore.plot.base.Aes.Companion.SLOPE @@ -70,7 +69,6 @@ object AesInitValue { VALUE_MAP[WIDTH] = 1.0 VALUE_MAP[HEIGHT] = 1.0 VALUE_MAP[VIOLINWIDTH] = 0.0 - VALUE_MAP[QUANTILE] = Double.NaN VALUE_MAP[WEIGHT] = 1.0 VALUE_MAP[INTERCEPT] = 0.0 VALUE_MAP[SLOPE] = 1.0 diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/aes/AesVisitor.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/aes/AesVisitor.kt index 574e3c6b817..e2135fb1b6c 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/aes/AesVisitor.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/aes/AesVisitor.kt @@ -22,7 +22,6 @@ import jetbrains.datalore.plot.base.Aes.Companion.LINETYPE import jetbrains.datalore.plot.base.Aes.Companion.LOWER import jetbrains.datalore.plot.base.Aes.Companion.MAP_ID import jetbrains.datalore.plot.base.Aes.Companion.MIDDLE -import jetbrains.datalore.plot.base.Aes.Companion.QUANTILE import jetbrains.datalore.plot.base.Aes.Companion.SHAPE import jetbrains.datalore.plot.base.Aes.Companion.SIZE import jetbrains.datalore.plot.base.Aes.Companion.SLOPE @@ -108,9 +107,6 @@ abstract class AesVisitor { if (aes == VIOLINWIDTH) { return violinwidth() } - if (aes == QUANTILE) { - return quantile() - } if (aes == WEIGHT) { return weight() } @@ -217,8 +213,6 @@ abstract class AesVisitor { protected abstract fun violinwidth(): T - protected abstract fun quantile(): T - protected abstract fun weight(): T protected abstract fun intercept(): T diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/aes/AestheticsBuilder.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/aes/AestheticsBuilder.kt index 324709a157d..00412ebb828 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/aes/AestheticsBuilder.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/aes/AestheticsBuilder.kt @@ -25,7 +25,6 @@ import jetbrains.datalore.plot.base.Aes.Companion.LINETYPE import jetbrains.datalore.plot.base.Aes.Companion.LOWER import jetbrains.datalore.plot.base.Aes.Companion.MAP_ID import jetbrains.datalore.plot.base.Aes.Companion.MIDDLE -import jetbrains.datalore.plot.base.Aes.Companion.QUANTILE import jetbrains.datalore.plot.base.Aes.Companion.SHAPE import jetbrains.datalore.plot.base.Aes.Companion.SIZE import jetbrains.datalore.plot.base.Aes.Companion.SLOPE @@ -186,10 +185,6 @@ class AestheticsBuilder @JvmOverloads constructor(private var myDataPointCount: return aes(SYM_Y, v) } - fun quantile(v: (Int) -> Double?): AestheticsBuilder { - return aes(QUANTILE, v) - } - fun constantAes(aes: Aes, v: T): AestheticsBuilder { myConstantAes.add(aes) myIndexFunctionMap[aes] = constant(v) @@ -408,10 +403,6 @@ class AestheticsBuilder @JvmOverloads constructor(private var myDataPointCount: return get(VIOLINWIDTH) } - override fun quantile(): Double { - return get(QUANTILE) - } - override fun weight(): Double { return get(WEIGHT) } diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/data/TransformVar.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/data/TransformVar.kt index a9900222a4a..947a3ff84c4 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/data/TransformVar.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/data/TransformVar.kt @@ -25,7 +25,6 @@ object TransformVar { val WIDTH = DataFrame.Variable("transform.WIDTH", TRANSFORM) val HEIGHT = DataFrame.Variable("transform.HEIGHT", TRANSFORM) val VIOLINWIDTH = DataFrame.Variable("transform.VIOLINWIDTH", TRANSFORM) - val QUANTILE = DataFrame.Variable("transform.QUANTILE", TRANSFORM) val WEIGHT = DataFrame.Variable("transform.WEIGHT", TRANSFORM) val INTERCEPT = DataFrame.Variable("transform.INTERCEPT", TRANSFORM) val SLOPE = DataFrame.Variable("transform.SLOPE", TRANSFORM) @@ -135,10 +134,6 @@ object TransformVar { return VIOLINWIDTH } - override fun quantile(): DataFrame.Variable { - return QUANTILE - } - override fun weight(): DataFrame.Variable { return WEIGHT } diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt index e0fc4f94755..d0e40cff049 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt @@ -7,12 +7,14 @@ package jetbrains.datalore.plot.base.geom import jetbrains.datalore.base.geometry.DoubleRectangle import jetbrains.datalore.base.geometry.DoubleVector +import jetbrains.datalore.base.values.Color import jetbrains.datalore.plot.base.* import jetbrains.datalore.plot.base.aes.AestheticsBuilder import jetbrains.datalore.plot.base.geom.util.* import jetbrains.datalore.plot.base.interact.GeomTargetCollector.TooltipParams import jetbrains.datalore.plot.base.interact.TipLayoutHint import jetbrains.datalore.plot.base.render.SvgRoot +import kotlin.math.abs class ViolinGeom : GeomBase() { private var drawQuantiles = DEF_DRAW_QUANTILES @@ -77,7 +79,27 @@ class ViolinGeom : GeomBase() { ) { if (drawQuantiles.isEmpty()) return - // Calculate quantiles + // Draw quantiles + val geomHelper = GeomHelper(pos, coord, ctx) + val helper = geomHelper.createSvgElementHelper() + val hintColor = HintColorUtil.fromFill(dataPoints.first()) + for (q in calculateQuantiles(dataPoints, ctx)) { + val start = DoubleVector(q.xmin()!!, q.y()!!) + val end = DoubleVector(q.xmax()!!, q.y()!!) + val line = helper.createLine(start, end, q) + root.add(line) + + // Add axis tooltip + val nearestDataPoint = dataPoints.minByOrNull { p -> abs(p.y()!! - q.y()!!) }!! + val rect = DoubleRectangle(start.x, start.y, end.x - start.x, 0.0) + buildQuantileHints(rect, nearestDataPoint, ctx, geomHelper, hintColor) + } + } + + private fun calculateQuantiles( + dataPoints: Iterable, + ctx: GeomContext + ): Iterable { val vws = dataPoints.map { it.violinwidth()!! } val ys = dataPoints.map { it.y()!! } val xsMin = dataPoints.map { toLocationBound(-1.0, ctx)(it) }.map { it.x } @@ -87,38 +109,17 @@ class ViolinGeom : GeomBase() { val quantY = drawQuantiles.map { pwLinInterp(dens, ys)(it) } val quantXMin = quantY.map { pwLinInterp(ys, xsMin)(it) } val quantXMax = quantY.map { pwLinInterp(ys, xsMax)(it) } - - // Construct dataPoints by quantiles val quantilesColor = dataPoints.first().color() val quantilesSize = dataPoints.first().size() - val quantileDataPoints = AestheticsBuilder(quantY.size) + + return AestheticsBuilder(quantY.size) + .y(AestheticsBuilder.list(quantY)) .xmin(AestheticsBuilder.list(quantXMin)) .xmax(AestheticsBuilder.list(quantXMax)) - .y(AestheticsBuilder.list(quantY)) - .quantile(AestheticsBuilder.list(drawQuantiles)) .color(AestheticsBuilder.constant(quantilesColor)) .size(AestheticsBuilder.constant(quantilesSize)) .build() .dataPoints() - - // Draw quantiles - val geomHelper = GeomHelper(pos, coord, ctx) - val helper = geomHelper.createSvgElementHelper() - for (q in quantileDataPoints) { - val start = DoubleVector(q.xmin()!!, q.y()!!) - val end = DoubleVector(q.xmax()!!, q.y()!!) - val line = helper.createLine(start, end, q) - root.add(line) - - // Draw tooltips - val nearestDataPoint = dataPoints.first { p -> p.y()!! >= q.y()!! } - buildQuantileHints( - DoubleRectangle(start.x, start.y, end.x - start.x, 0.0), - nearestDataPoint, - ctx, - geomHelper - ) - } } private fun pwLinInterp(x: List, y: List): (Double) -> Double { @@ -176,31 +177,18 @@ class ViolinGeom : GeomBase() { rect: DoubleRectangle, p: DataPointAesthetics, ctx: GeomContext, - geomHelper: GeomHelper + geomHelper: GeomHelper, + color: Color ) { - val clientRect = geomHelper.toClient(rect, p) - val tooltipKind = if (ctx.flipped) { - TipLayoutHint.Kind.ROTATED_TOOLTIP - } else { - TipLayoutHint.Kind.HORIZONTAL_TOOLTIP - } - - val hint = HintsCollection.HintConfigFactory() - .defaultObjectRadius(0.0) - .defaultX(rect.center.x) - .defaultKind(tooltipKind) - - val hints = HintsCollection(p, geomHelper) - .addHint(hint.create(Aes.QUANTILE)) - .hints - ctx.targetCollector.addRectangle( p.index(), - clientRect, - TooltipParams.params() - .setTipLayoutHints(hints) - .setColor(HintColorUtil.fromColor(p)), - tooltipKind = tooltipKind + geomHelper.toClient(rect, p), + TooltipParams.params().setColor(color), + tooltipKind = if (ctx.flipped) { + TipLayoutHint.Kind.VERTICAL_TOOLTIP + } else { + TipLayoutHint.Kind.HORIZONTAL_TOOLTIP + } ) } diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/util/DataPointAestheticsDelegate.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/util/DataPointAestheticsDelegate.kt index cd25b5ecc03..b070dee4cf1 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/util/DataPointAestheticsDelegate.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/util/DataPointAestheticsDelegate.kt @@ -74,10 +74,6 @@ open class DataPointAestheticsDelegate(private val p: DataPointAesthetics) : return p.violinwidth() } - override fun quantile(): Double? { - return p.quantile() - } - override fun weight(): Double? { return p.weight() } diff --git a/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/scale/DefaultMapperProvider.kt b/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/scale/DefaultMapperProvider.kt index 53cb6d5f525..ba2f184b7b9 100644 --- a/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/scale/DefaultMapperProvider.kt +++ b/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/scale/DefaultMapperProvider.kt @@ -23,7 +23,6 @@ import jetbrains.datalore.plot.base.Aes.Companion.LINETYPE import jetbrains.datalore.plot.base.Aes.Companion.LOWER import jetbrains.datalore.plot.base.Aes.Companion.MAP_ID import jetbrains.datalore.plot.base.Aes.Companion.MIDDLE -import jetbrains.datalore.plot.base.Aes.Companion.QUANTILE import jetbrains.datalore.plot.base.Aes.Companion.SHAPE import jetbrains.datalore.plot.base.Aes.Companion.SIZE import jetbrains.datalore.plot.base.Aes.Companion.SLOPE @@ -100,7 +99,6 @@ object DefaultMapperProvider { this.put(HEIGHT, NUMERIC_IDENTITY) this.put(WEIGHT, NUMERIC_IDENTITY) this.put(VIOLINWIDTH, NUMERIC_IDENTITY) - this.put(QUANTILE, NUMERIC_IDENTITY) this.put(INTERCEPT, NUMERIC_IDENTITY) this.put(SLOPE, NUMERIC_IDENTITY) this.put(XINTERCEPT, NUMERIC_IDENTITY) diff --git a/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/scale/DefaultNaValue.kt b/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/scale/DefaultNaValue.kt index 5771b50ee2f..31e993460f4 100644 --- a/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/scale/DefaultNaValue.kt +++ b/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/scale/DefaultNaValue.kt @@ -25,7 +25,6 @@ import jetbrains.datalore.plot.base.Aes.Companion.LINETYPE import jetbrains.datalore.plot.base.Aes.Companion.LOWER import jetbrains.datalore.plot.base.Aes.Companion.MAP_ID import jetbrains.datalore.plot.base.Aes.Companion.MIDDLE -import jetbrains.datalore.plot.base.Aes.Companion.QUANTILE import jetbrains.datalore.plot.base.Aes.Companion.SHAPE import jetbrains.datalore.plot.base.Aes.Companion.SIZE import jetbrains.datalore.plot.base.Aes.Companion.SLOPE @@ -70,7 +69,6 @@ object DefaultNaValue { VALUE_MAP.put(WIDTH, 1.0) VALUE_MAP.put(HEIGHT, 1.0) VALUE_MAP.put(VIOLINWIDTH, 0.0) - VALUE_MAP.put(QUANTILE, 0.0) VALUE_MAP.put(WEIGHT, 1.0) VALUE_MAP.put(INTERCEPT, 0.0) VALUE_MAP.put(SLOPE, 1.0) diff --git a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/GeomInteractionUtil.kt b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/GeomInteractionUtil.kt index 53fa754a129..daf8e7cdf9e 100644 --- a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/GeomInteractionUtil.kt +++ b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/GeomInteractionUtil.kt @@ -171,7 +171,6 @@ object GeomInteractionUtil { GeomKind.POINT_RANGE, GeomKind.RIBBON -> listOf(Aes.YMAX, Aes.YMIN) GeomKind.BOX_PLOT -> listOf(Aes.YMAX, Aes.UPPER, Aes.MIDDLE, Aes.LOWER, Aes.YMIN) - GeomKind.VIOLIN -> listOf(Aes.QUANTILE) GeomKind.SMOOTH -> listOf(Aes.YMAX, Aes.YMIN, Aes.Y) else -> emptyList() } diff --git a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/aes/TypedOptionConverterMap.kt b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/aes/TypedOptionConverterMap.kt index 55b172c2fc7..d8da3dc1bf9 100644 --- a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/aes/TypedOptionConverterMap.kt +++ b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/aes/TypedOptionConverterMap.kt @@ -22,7 +22,6 @@ import jetbrains.datalore.plot.base.Aes.Companion.LINETYPE import jetbrains.datalore.plot.base.Aes.Companion.LOWER import jetbrains.datalore.plot.base.Aes.Companion.MAP_ID import jetbrains.datalore.plot.base.Aes.Companion.MIDDLE -import jetbrains.datalore.plot.base.Aes.Companion.QUANTILE import jetbrains.datalore.plot.base.Aes.Companion.SHAPE import jetbrains.datalore.plot.base.Aes.Companion.SIZE import jetbrains.datalore.plot.base.Aes.Companion.SLOPE @@ -67,7 +66,6 @@ internal class TypedOptionConverterMap { this.put(WIDTH, DOUBLE_CVT) this.put(HEIGHT, DOUBLE_CVT) this.put(VIOLINWIDTH, DOUBLE_CVT) - this.put(QUANTILE, DOUBLE_CVT) this.put(WEIGHT, DOUBLE_CVT) this.put(INTERCEPT, DOUBLE_CVT) this.put(SLOPE, DOUBLE_CVT) From aa9d94e359a9cca585dcc67641561f988f6fd178 Mon Sep 17 00:00:00 2001 From: ASmirnov-HORIS Date: Fri, 21 Jan 2022 16:54:30 +0300 Subject: [PATCH 60/81] Remove the quantile hints from geom_violin(). --- .../datalore/plot/base/geom/ViolinGeom.kt | 37 ++----------------- 1 file changed, 4 insertions(+), 33 deletions(-) diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt index d0e40cff049..9eb8e032395 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt @@ -5,16 +5,13 @@ package jetbrains.datalore.plot.base.geom -import jetbrains.datalore.base.geometry.DoubleRectangle import jetbrains.datalore.base.geometry.DoubleVector -import jetbrains.datalore.base.values.Color import jetbrains.datalore.plot.base.* import jetbrains.datalore.plot.base.aes.AestheticsBuilder import jetbrains.datalore.plot.base.geom.util.* import jetbrains.datalore.plot.base.interact.GeomTargetCollector.TooltipParams import jetbrains.datalore.plot.base.interact.TipLayoutHint import jetbrains.datalore.plot.base.render.SvgRoot -import kotlin.math.abs class ViolinGeom : GeomBase() { private var drawQuantiles = DEF_DRAW_QUANTILES @@ -79,20 +76,13 @@ class ViolinGeom : GeomBase() { ) { if (drawQuantiles.isEmpty()) return - // Draw quantiles val geomHelper = GeomHelper(pos, coord, ctx) val helper = geomHelper.createSvgElementHelper() - val hintColor = HintColorUtil.fromFill(dataPoints.first()) - for (q in calculateQuantiles(dataPoints, ctx)) { - val start = DoubleVector(q.xmin()!!, q.y()!!) - val end = DoubleVector(q.xmax()!!, q.y()!!) - val line = helper.createLine(start, end, q) + for (p in calculateQuantiles(dataPoints, ctx)) { + val start = DoubleVector(p.xmin()!!, p.y()!!) + val end = DoubleVector(p.xmax()!!, p.y()!!) + val line = helper.createLine(start, end, p) root.add(line) - - // Add axis tooltip - val nearestDataPoint = dataPoints.minByOrNull { p -> abs(p.y()!! - q.y()!!) }!! - val rect = DoubleRectangle(start.x, start.y, end.x - start.x, 0.0) - buildQuantileHints(rect, nearestDataPoint, ctx, geomHelper, hintColor) } } @@ -173,25 +163,6 @@ class ViolinGeom : GeomBase() { } } - private fun buildQuantileHints( - rect: DoubleRectangle, - p: DataPointAesthetics, - ctx: GeomContext, - geomHelper: GeomHelper, - color: Color - ) { - ctx.targetCollector.addRectangle( - p.index(), - geomHelper.toClient(rect, p), - TooltipParams.params().setColor(color), - tooltipKind = if (ctx.flipped) { - TipLayoutHint.Kind.VERTICAL_TOOLTIP - } else { - TipLayoutHint.Kind.HORIZONTAL_TOOLTIP - } - ) - } - companion object { val DEF_DRAW_QUANTILES = emptyList() From 73cdbb9c95a4a6fccc1b6250c8dae67955640d50 Mon Sep 17 00:00:00 2001 From: ASmirnov-HORIS Date: Fri, 21 Jan 2022 19:09:11 +0300 Subject: [PATCH 61/81] Add the scale parameter to the geom_violin(). --- .../datalore/plot/base/stat/YDensityStat.kt | 31 +++++++++++++++++-- .../jetbrains/datalore/plot/config/Option.kt | 4 +++ .../datalore/plot/config/StatProto.kt | 14 +++++++++ 3 files changed, 46 insertions(+), 3 deletions(-) diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/YDensityStat.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/YDensityStat.kt index c7158795f03..ad40f5f32ee 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/YDensityStat.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/YDensityStat.kt @@ -13,6 +13,7 @@ import jetbrains.datalore.plot.base.data.TransformVar import jetbrains.datalore.plot.common.data.SeriesUtil class YDensityStat( + private val scale: Scale, private val bandWidth: Double?, private val bandWidthMethod: DensityStat.BandWidthMethod, private val adjust: Double, @@ -61,9 +62,26 @@ class YDensityStat( val statViolinWidth = if (dataAfterStat.rowCount() == 0) { emptyList() } else { - val statDensity = dataAfterStat.getNumeric(Stats.DENSITY).map { it!! } - val densityMax = statDensity.maxOrNull()!! - statDensity.map { it / densityMax } + when (scale) { + Scale.AREA -> { + val statDensity = dataAfterStat.getNumeric(Stats.DENSITY).map { it!! } + val densityMax = statDensity.maxOrNull()!! + statDensity.map { it / densityMax } + } + Scale.COUNT -> { + val statDensity = dataAfterStat.getNumeric(Stats.DENSITY).map { it!! } + val densityMax = statDensity.maxOrNull()!! + val statCount = dataAfterStat.getNumeric(Stats.COUNT).map { it!! } + val widthsSumMax = statDensity.mapIndexed { i, d -> + if (d > 0) statCount[i] / d else Double.NaN + }.maxOrNull()!! + val norm = densityMax * widthsSumMax + statCount.map { it / norm } + } + Scale.WIDTH -> { + dataAfterStat.getNumeric(Stats.SCALED).map { it!! } + } + } } return dataAfterStat.builder() .putNumeric(Stats.VIOLIN_WIDTH, statViolinWidth) @@ -119,11 +137,18 @@ class YDensityStat( ) } + enum class Scale { + AREA, + COUNT, + WIDTH + } + companion object { private val DEF_MAPPING: Map, DataFrame.Variable> = mapOf( Aes.X to Stats.X, Aes.Y to Stats.Y, Aes.VIOLINWIDTH to Stats.VIOLIN_WIDTH ) + val DEF_SCALE = Scale.AREA } } \ No newline at end of file diff --git a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/Option.kt b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/Option.kt index d67448c3e3d..b4caab1dba3 100644 --- a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/Option.kt +++ b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/Option.kt @@ -276,6 +276,10 @@ object Option { const val BINS = "bins" const val BINWIDTH = "binwidth" } + + object YDensity { + const val SCALE = "scale" + } } object Scale { diff --git a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/StatProto.kt b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/StatProto.kt index b91733b7335..cb53a6c2e97 100644 --- a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/StatProto.kt +++ b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/StatProto.kt @@ -16,6 +16,7 @@ import jetbrains.datalore.plot.config.Option.Stat.Corr import jetbrains.datalore.plot.config.Option.Stat.Density import jetbrains.datalore.plot.config.Option.Stat.Density2d import jetbrains.datalore.plot.config.Option.Stat.Smooth +import jetbrains.datalore.plot.config.Option.Stat.YDensity object StatProto { @@ -173,6 +174,18 @@ object StatProto { } private fun configureYDensityStat(options: OptionsAccessor): YDensityStat { + val scale = options.getString(YDensity.SCALE)?.let { + when (it.lowercase()) { + "area" -> YDensityStat.Scale.AREA + "count" -> YDensityStat.Scale.COUNT + "width" -> YDensityStat.Scale.WIDTH + else -> throw IllegalArgumentException( + "Unsupported scale: '$it'\n" + + "Use one of: area, count, width." + ) + } + } + var bwValue: Double? = null var bwMethod: DensityStat.BandWidthMethod = DensityStat.DEF_BW options[Density.BAND_WIDTH]?.run { @@ -188,6 +201,7 @@ object StatProto { } return YDensityStat( + scale = scale ?: YDensityStat.DEF_SCALE, bandWidth = bwValue, bandWidthMethod = bwMethod, adjust = options.getDoubleDef(Density.ADJUST, DensityStat.DEF_ADJUST), From d67f4246505e3bd6e451951aa7724f68ebe9f9f1 Mon Sep 17 00:00:00 2001 From: ASmirnov-HORIS Date: Mon, 24 Jan 2022 19:28:15 +0300 Subject: [PATCH 62/81] Use the new option accessor for the draw_quantiles parameter of the geom_violin(). --- .../jetbrains/datalore/plot/base/geom/ViolinGeom.kt | 6 +++--- .../jetbrains/datalore/plot/config/GeomProtoClientSide.kt | 2 +- .../jetbrains/datalore/plot/config/OptionsAccessor.kt | 8 ++++++++ 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt index 9eb8e032395..ed39c19fbdf 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt @@ -14,10 +14,10 @@ import jetbrains.datalore.plot.base.interact.TipLayoutHint import jetbrains.datalore.plot.base.render.SvgRoot class ViolinGeom : GeomBase() { - private var drawQuantiles = DEF_DRAW_QUANTILES + private var drawQuantiles: List = DEF_DRAW_QUANTILES - fun setDrawQuantiles(quantiles: List<*>) { - drawQuantiles = quantiles.map { it.toString().toDouble() } + fun setDrawQuantiles(quantiles: List) { + drawQuantiles = quantiles } override fun buildIntern( diff --git a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/GeomProtoClientSide.kt b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/GeomProtoClientSide.kt index b993b96e4e1..9a523f78d6b 100644 --- a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/GeomProtoClientSide.kt +++ b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/GeomProtoClientSide.kt @@ -86,7 +86,7 @@ class GeomProtoClientSide(geomKind: GeomKind) : GeomProto(geomKind) { GeomKind.VIOLIN -> return GeomProvider.violin { val geom = ViolinGeom() if (opts.hasOwn(Violin.DRAW_QUANTILES)) { - geom.setDrawQuantiles(opts.getList(Violin.DRAW_QUANTILES)) + geom.setDrawQuantiles(opts.getBoundedDoubleList(Violin.DRAW_QUANTILES, 0.0, 1.0)) } geom } diff --git a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/OptionsAccessor.kt b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/OptionsAccessor.kt index 5aac6f762f1..d6ba4b33bbd 100644 --- a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/OptionsAccessor.kt +++ b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/OptionsAccessor.kt @@ -71,6 +71,14 @@ open class OptionsAccessor( return list.map { it.toDouble() } } + fun getBoundedDoubleList(option: String, lowerBound: Double, upperBound: Double): List { + val list = getDoubleList(option) + list.forEach { + check(it in lowerBound..upperBound) { "Quantile $it is not in range [$lowerBound, $upperBound]" } + } + return list + } + fun getNumPair(option: String): Pair { val list = getNumList(option) { it is Number } @Suppress("UNCHECKED_CAST") From b3cfbb14bd069ddef78a1f5177882de9e2d4332b Mon Sep 17 00:00:00 2001 From: ASmirnov-HORIS Date: Tue, 25 Jan 2022 12:11:09 +0300 Subject: [PATCH 63/81] Update demo for Violin. --- .../plotDemo/model/plotConfig/Violin.kt | 31 +++++++++++++++++-- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/plot-demo-common/src/commonMain/kotlin/jetbrains/datalore/plotDemo/model/plotConfig/Violin.kt b/plot-demo-common/src/commonMain/kotlin/jetbrains/datalore/plotDemo/model/plotConfig/Violin.kt index 5b07dfedd0a..830ea6381bb 100644 --- a/plot-demo-common/src/commonMain/kotlin/jetbrains/datalore/plotDemo/model/plotConfig/Violin.kt +++ b/plot-demo-common/src/commonMain/kotlin/jetbrains/datalore/plotDemo/model/plotConfig/Violin.kt @@ -12,7 +12,8 @@ class Violin { fun plotSpecList(): List> { return listOf( basic(), - withNan() + withNan(), + withGroups(), ) } @@ -28,7 +29,7 @@ class Violin { " {" + " 'geom': 'violin'," + " 'alpha': 0.7," + - " 'draw_quantiles': [0.25, 0.5, 0.75]" + + " 'draw_quantiles': [0.1, 0.5, 0.9]" + " }" + " ]" + "}" @@ -42,7 +43,7 @@ class Violin { private fun withNan(): MutableMap { val spec = "{" + " 'kind': 'plot'," + - " 'data' : {'class': ['A', 'A', 'A', null, 'B', 'B', 'B', 'B']," + + " 'data' : {'class': [0, 0, 0, null, 1, 1, 1, 1]," + " 'value': [0, 0, 2, 2, 1, 1, 3, null]" + " }," + " 'mapping': {" + @@ -59,4 +60,28 @@ class Violin { return HashMap(parsePlotSpec(spec)) } + + private fun withGroups(): MutableMap { + val spec = "{" + + " 'kind': 'plot'," + + " 'data' : {'class': ['A', 'A', 'A', 'A', 'A', 'A', 'B', 'B', 'B', 'B', 'B', 'B']," + + " 'group': ['x', 'x', 'x', 'y', 'y', 'y', 'x', 'x', 'x', 'x', 'y', 'y']," + + " 'value': [0, 0, 2, 1, 1, 3, 1, 3, 3, 5, 2, 4]" + + " }," + + " 'mapping': {" + + " 'x': 'class'," + + " 'y': 'value'," + + " 'fill': 'group'" + + " }," + + " 'layers': [" + + " {" + + " 'geom': 'violin'," + + " 'draw_quantiles': [0.25, 0.5, 0.75]" + + " }" + + " ]" + + "}" + + return HashMap(parsePlotSpec(spec)) + + } } \ No newline at end of file From f404b16d9607d81e8640c8735a448001beb220d3 Mon Sep 17 00:00:00 2001 From: ASmirnov-HORIS Date: Tue, 25 Jan 2022 12:13:16 +0300 Subject: [PATCH 64/81] Hide quantiles for Violin. --- .../kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt index ed39c19fbdf..2ca11386e74 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt @@ -61,7 +61,7 @@ class ViolinGeom : GeomBase() { appendNodes(helper.createLines(dataPoints, leftBoundTransform), root) appendNodes(helper.createLines(dataPoints, rightBoundTransform), root) - buildQuantiles(root, dataPoints, pos, coord, ctx) + // buildQuantiles(root, dataPoints, pos, coord, ctx) buildHints(dataPoints, ctx, helper, leftBoundTransform) buildHints(dataPoints, ctx, helper, rightBoundTransform) From 90d92472048716c3ea97438a226015a32d4a3ce8 Mon Sep 17 00:00:00 2001 From: ASmirnov-HORIS Date: Tue, 25 Jan 2022 20:13:44 +0300 Subject: [PATCH 65/81] Special data for width error in Violin. --- .../plotDemo/model/plotConfig/Violin.kt | 47 +++++++++++++++++-- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/plot-demo-common/src/commonMain/kotlin/jetbrains/datalore/plotDemo/model/plotConfig/Violin.kt b/plot-demo-common/src/commonMain/kotlin/jetbrains/datalore/plotDemo/model/plotConfig/Violin.kt index 830ea6381bb..e953178c1d1 100644 --- a/plot-demo-common/src/commonMain/kotlin/jetbrains/datalore/plotDemo/model/plotConfig/Violin.kt +++ b/plot-demo-common/src/commonMain/kotlin/jetbrains/datalore/plotDemo/model/plotConfig/Violin.kt @@ -11,9 +11,8 @@ import jetbrains.datalore.plotDemo.data.Iris class Violin { fun plotSpecList(): List> { return listOf( - basic(), - withNan(), - withGroups(), + mpgCylHwy456(), + mpgCylHwy46(), ) } @@ -84,4 +83,46 @@ class Violin { return HashMap(parsePlotSpec(spec)) } + + private fun mpgCylHwy456(): MutableMap { + val spec = "{" + + " 'kind': 'plot'," + + " 'data' : {'x': [4, 4, 4, 4, 6, 6, 6, 4, 4, 4, 4, 6, 6, 6, 6, 6, 6, 4, 4, 6, 6, 6, 4, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 6, 6, 6, 4, 4, 4, 4, 6, 6, 6, 6, 6, 6, 6, 6, 4, 4, 4, 4, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 6, 6, 6, 4, 4, 4, 4, 6, 6, 6, 4, 4, 4, 4, 6, 6, 6, 4, 4, 4, 4, 4, 4, 4, 4, 6, 6, 6, 6, 4, 4, 4, 4, 6, 4, 4, 4, 4, 4, 5, 5, 6, 6, 4, 4, 4, 4, 5, 5, 4, 4, 4, 4, 6, 6, 6]," + + " 'y': [29, 29, 31, 30, 26, 26, 27, 26, 25, 28, 27, 25, 25, 25, 25, 24, 25, 27, 30, 26, 29, 26, 24, 24, 22, 22, 24, 24, 17, 22, 21, 23, 23, 19, 18, 17, 17, 17, 17, 19, 17, 19, 17, 17, 26, 25, 26, 24, 33, 32, 32, 29, 32, 34, 36, 36, 29, 26, 27, 30, 31, 26, 26, 28, 26, 29, 28, 27, 24, 24, 24, 22, 19, 20, 17, 19, 29, 27, 31, 32, 27, 26, 26, 25, 25, 17, 17, 20, 26, 26, 27, 28, 25, 24, 27, 25, 26, 23, 26, 26, 26, 26, 25, 27, 25, 27, 20, 20, 19, 17, 20, 29, 27, 31, 31, 26, 26, 28, 27, 29, 31, 31, 26, 26, 27, 30, 33, 35, 37, 35, 20, 20, 22, 17, 19, 18, 20, 29, 26, 29, 29, 24, 44, 29, 26, 29, 29, 29, 29, 23, 24, 44, 41, 29, 26, 28, 29, 29, 29, 28, 29, 26, 26, 26]" + + " }," + + " 'mapping': {" + + " 'x': 'x'," + + " 'y': 'y'" + + " }," + + " 'layers': [" + + " {" + + " 'geom': 'violin'" + + " }" + + " ]" + + "}" + + return HashMap(parsePlotSpec(spec)) + + } + + private fun mpgCylHwy46(): MutableMap { + val spec = "{" + + " 'kind': 'plot'," + + " 'data' : {'x': [4, 4, 4, 4, 6, 6, 6, 4, 4, 4, 4, 6, 6, 6, 6, 6, 6, 4, 4, 6, 6, 6, 4, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 6, 6, 6, 4, 4, 4, 4, 6, 6, 6, 6, 6, 6, 6, 6, 4, 4, 4, 4, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 6, 6, 6, 4, 4, 4, 4, 6, 6, 6, 4, 4, 4, 4, 6, 6, 6, 4, 4, 4, 4, 4, 4, 4, 4, 6, 6, 6, 6, 4, 4, 4, 4, 6, 4, 4, 4, 4, 4, 6, 6, 4, 4, 4, 4, 4, 4, 4, 4, 6, 6, 6]," + + " 'y': [29, 29, 31, 30, 26, 26, 27, 26, 25, 28, 27, 25, 25, 25, 25, 24, 25, 27, 30, 26, 29, 26, 24, 24, 22, 22, 24, 24, 17, 22, 21, 23, 23, 19, 18, 17, 17, 17, 17, 19, 17, 19, 17, 17, 26, 25, 26, 24, 33, 32, 32, 29, 32, 34, 36, 36, 29, 26, 27, 30, 31, 26, 26, 28, 26, 29, 28, 27, 24, 24, 24, 22, 19, 20, 17, 19, 29, 27, 31, 32, 27, 26, 26, 25, 25, 17, 17, 20, 26, 26, 27, 28, 25, 24, 27, 25, 26, 23, 26, 26, 26, 26, 25, 27, 25, 27, 20, 20, 19, 17, 20, 29, 27, 31, 31, 26, 26, 28, 27, 29, 31, 31, 26, 26, 27, 30, 33, 35, 37, 35, 20, 20, 22, 17, 19, 18, 20, 29, 26, 29, 29, 24, 44, 29, 26, 29, 29, 23, 24, 44, 41, 29, 26, 29, 29, 28, 29, 26, 26, 26]" + + " }," + + " 'mapping': {" + + " 'x': 'x'," + + " 'y': 'y'" + + " }," + + " 'layers': [" + + " {" + + " 'geom': 'violin'" + + " }" + + " ]" + + "}" + + return HashMap(parsePlotSpec(spec)) + + } } \ No newline at end of file From 514c2093d1b412dce33b6f49b3371f70c9c23c15 Mon Sep 17 00:00:00 2001 From: ASmirnov-HORIS Date: Wed, 26 Jan 2022 20:44:35 +0300 Subject: [PATCH 66/81] Violin: remove WIDTH_SCALE from ViolinGeom, add WIDTH aesthetic and update the demo. --- .../jetbrains/datalore/plot/base/GeomMeta.kt | 3 +- .../datalore/plot/base/geom/ViolinGeom.kt | 3 +- .../datalore/plot/base/stat/YDensityStat.kt | 2 + .../datalore/plot/config/GeomProto.kt | 3 +- .../plotDemo/model/plotConfig/Violin.kt | 38 +++++++++++++++---- 5 files changed, 36 insertions(+), 13 deletions(-) diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/GeomMeta.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/GeomMeta.kt index 981dbb71ef2..7952907e8fd 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/GeomMeta.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/GeomMeta.kt @@ -220,7 +220,8 @@ object GeomMeta { Aes.COLOR, Aes.FILL, Aes.LINETYPE, - Aes.SIZE + Aes.SIZE, + Aes.WIDTH ) GeomKind.RIBBON -> listOf( diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt index 2ca11386e74..3be1c47a5da 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt @@ -129,7 +129,7 @@ class ViolinGeom : GeomBase() { ctx: GeomContext ): (p: DataPointAesthetics) -> DoubleVector { return fun (p: DataPointAesthetics): DoubleVector { - val x = p.x()!! + ctx.getResolution(Aes.X) / 2 * WIDTH_SCALE * sign * p.violinwidth()!! + val x = p.x()!! + ctx.getResolution(Aes.X) / 2 * sign * p.violinwidth()!! val y = p.y()!! return DoubleVector(x, y) } @@ -167,7 +167,6 @@ class ViolinGeom : GeomBase() { val DEF_DRAW_QUANTILES = emptyList() const val HANDLES_GROUPS = true - const val WIDTH_SCALE = 0.95 } } \ No newline at end of file diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/YDensityStat.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/YDensityStat.kt index ad40f5f32ee..cb1d71b00b7 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/YDensityStat.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/YDensityStat.kt @@ -83,8 +83,10 @@ class YDensityStat( } } } + // TODO: Calculate WIDTH properly return dataAfterStat.builder() .putNumeric(Stats.VIOLIN_WIDTH, statViolinWidth) + .putNumeric(Stats.WIDTH, List(statViolinWidth.size) { 1.0 }) .build() } diff --git a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/GeomProto.kt b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/GeomProto.kt index 634c01f8b4e..b0ed28f308b 100644 --- a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/GeomProto.kt +++ b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/GeomProto.kt @@ -9,7 +9,6 @@ import jetbrains.datalore.plot.base.Aes import jetbrains.datalore.plot.base.GeomKind import jetbrains.datalore.plot.base.GeomKind.* import jetbrains.datalore.plot.base.GeomMeta -import jetbrains.datalore.plot.base.geom.ViolinGeom import jetbrains.datalore.plot.base.pos.PositionAdjustments import jetbrains.datalore.plot.builder.assemble.PosProvider import jetbrains.datalore.plot.builder.assemble.geom.DefaultSampling @@ -177,7 +176,7 @@ open class GeomProto constructor(val geomKind: GeomKind) { private fun violinDefaults(): Map { val defaults = HashMap() defaults["stat"] = "ydensity" - defaults["position"] = mapOf(Meta.NAME to "dodge", "width" to ViolinGeom.WIDTH_SCALE) + defaults["position"] = mapOf(Meta.NAME to "dodge", "width" to 0.95) return defaults } diff --git a/plot-demo-common/src/commonMain/kotlin/jetbrains/datalore/plotDemo/model/plotConfig/Violin.kt b/plot-demo-common/src/commonMain/kotlin/jetbrains/datalore/plotDemo/model/plotConfig/Violin.kt index e953178c1d1..b88ff01595c 100644 --- a/plot-demo-common/src/commonMain/kotlin/jetbrains/datalore/plotDemo/model/plotConfig/Violin.kt +++ b/plot-demo-common/src/commonMain/kotlin/jetbrains/datalore/plotDemo/model/plotConfig/Violin.kt @@ -11,8 +11,9 @@ import jetbrains.datalore.plotDemo.data.Iris class Violin { fun plotSpecList(): List> { return listOf( - mpgCylHwy456(), - mpgCylHwy46(), + data132Violin(), + data132Boxplot(), + data123Violin(), ) } @@ -84,11 +85,11 @@ class Violin { } - private fun mpgCylHwy456(): MutableMap { + private fun data132Violin(): MutableMap { val spec = "{" + " 'kind': 'plot'," + - " 'data' : {'x': [4, 4, 4, 4, 6, 6, 6, 4, 4, 4, 4, 6, 6, 6, 6, 6, 6, 4, 4, 6, 6, 6, 4, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 6, 6, 6, 4, 4, 4, 4, 6, 6, 6, 6, 6, 6, 6, 6, 4, 4, 4, 4, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 6, 6, 6, 4, 4, 4, 4, 6, 6, 6, 4, 4, 4, 4, 6, 6, 6, 4, 4, 4, 4, 4, 4, 4, 4, 6, 6, 6, 6, 4, 4, 4, 4, 6, 4, 4, 4, 4, 4, 5, 5, 6, 6, 4, 4, 4, 4, 5, 5, 4, 4, 4, 4, 6, 6, 6]," + - " 'y': [29, 29, 31, 30, 26, 26, 27, 26, 25, 28, 27, 25, 25, 25, 25, 24, 25, 27, 30, 26, 29, 26, 24, 24, 22, 22, 24, 24, 17, 22, 21, 23, 23, 19, 18, 17, 17, 17, 17, 19, 17, 19, 17, 17, 26, 25, 26, 24, 33, 32, 32, 29, 32, 34, 36, 36, 29, 26, 27, 30, 31, 26, 26, 28, 26, 29, 28, 27, 24, 24, 24, 22, 19, 20, 17, 19, 29, 27, 31, 32, 27, 26, 26, 25, 25, 17, 17, 20, 26, 26, 27, 28, 25, 24, 27, 25, 26, 23, 26, 26, 26, 26, 25, 27, 25, 27, 20, 20, 19, 17, 20, 29, 27, 31, 31, 26, 26, 28, 27, 29, 31, 31, 26, 26, 27, 30, 33, 35, 37, 35, 20, 20, 22, 17, 19, 18, 20, 29, 26, 29, 29, 24, 44, 29, 26, 29, 29, 29, 29, 23, 24, 44, 41, 29, 26, 28, 29, 29, 29, 28, 29, 26, 26, 26]" + + " 'data' : {'x': [1, 3, 2]," + + " 'y': [2, 0, 1]" + " }," + " 'mapping': {" + " 'x': 'x'," + @@ -105,11 +106,32 @@ class Violin { } - private fun mpgCylHwy46(): MutableMap { + private fun data132Boxplot(): MutableMap { val spec = "{" + " 'kind': 'plot'," + - " 'data' : {'x': [4, 4, 4, 4, 6, 6, 6, 4, 4, 4, 4, 6, 6, 6, 6, 6, 6, 4, 4, 6, 6, 6, 4, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 6, 6, 6, 4, 4, 4, 4, 6, 6, 6, 6, 6, 6, 6, 6, 4, 4, 4, 4, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 6, 6, 6, 4, 4, 4, 4, 6, 6, 6, 4, 4, 4, 4, 6, 6, 6, 4, 4, 4, 4, 4, 4, 4, 4, 6, 6, 6, 6, 4, 4, 4, 4, 6, 4, 4, 4, 4, 4, 6, 6, 4, 4, 4, 4, 4, 4, 4, 4, 6, 6, 6]," + - " 'y': [29, 29, 31, 30, 26, 26, 27, 26, 25, 28, 27, 25, 25, 25, 25, 24, 25, 27, 30, 26, 29, 26, 24, 24, 22, 22, 24, 24, 17, 22, 21, 23, 23, 19, 18, 17, 17, 17, 17, 19, 17, 19, 17, 17, 26, 25, 26, 24, 33, 32, 32, 29, 32, 34, 36, 36, 29, 26, 27, 30, 31, 26, 26, 28, 26, 29, 28, 27, 24, 24, 24, 22, 19, 20, 17, 19, 29, 27, 31, 32, 27, 26, 26, 25, 25, 17, 17, 20, 26, 26, 27, 28, 25, 24, 27, 25, 26, 23, 26, 26, 26, 26, 25, 27, 25, 27, 20, 20, 19, 17, 20, 29, 27, 31, 31, 26, 26, 28, 27, 29, 31, 31, 26, 26, 27, 30, 33, 35, 37, 35, 20, 20, 22, 17, 19, 18, 20, 29, 26, 29, 29, 24, 44, 29, 26, 29, 29, 23, 24, 44, 41, 29, 26, 29, 29, 28, 29, 26, 26, 26]" + + " 'data' : {'x': [1, 3, 2]," + + " 'y': [2, 0, 1]" + + " }," + + " 'mapping': {" + + " 'x': 'x'," + + " 'y': 'y'" + + " }," + + " 'layers': [" + + " {" + + " 'geom': 'boxplot'" + + " }" + + " ]" + + "}" + + return HashMap(parsePlotSpec(spec)) + + } + + private fun data123Violin(): MutableMap { + val spec = "{" + + " 'kind': 'plot'," + + " 'data' : {'x': [1, 2, 3]," + + " 'y': [2, 1, 0]" + " }," + " 'mapping': {" + " 'x': 'x'," + From 0eee9abf31a035eb4c696fd5c83ec31f52213eca Mon Sep 17 00:00:00 2001 From: ASmirnov-HORIS Date: Thu, 27 Jan 2022 13:02:46 +0300 Subject: [PATCH 67/81] Use width aesthetic in the ViolinGeom, and add new demo for debugging violins. --- .../datalore/plot/base/geom/ViolinGeom.kt | 4 ++-- .../plotDemo/model/plotConfig/Violin.kt | 24 +++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt index 3be1c47a5da..5a40e59db15 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt @@ -37,7 +37,7 @@ class ViolinGeom : GeomBase() { coord: CoordinateSystem, ctx: GeomContext ) { - GeomUtil.withDefined(aesthetics.dataPoints(), Aes.X, Aes.Y, Aes.VIOLINWIDTH) + GeomUtil.withDefined(aesthetics.dataPoints(), Aes.X, Aes.Y, Aes.VIOLINWIDTH, Aes.WIDTH) .groupBy(DataPointAesthetics::x) .map { (x, nonOrderedPoints) -> x to GeomUtil.ordered_Y(nonOrderedPoints, false) } .forEach { (_, dataPoints) -> buildViolin(root, dataPoints, pos, coord, ctx) } @@ -129,7 +129,7 @@ class ViolinGeom : GeomBase() { ctx: GeomContext ): (p: DataPointAesthetics) -> DoubleVector { return fun (p: DataPointAesthetics): DoubleVector { - val x = p.x()!! + ctx.getResolution(Aes.X) / 2 * sign * p.violinwidth()!! + val x = p.x()!! + ctx.getResolution(Aes.X) / 2 * sign * p.width()!! * p.violinwidth()!! val y = p.y()!! return DoubleVector(x, y) } diff --git a/plot-demo-common/src/commonMain/kotlin/jetbrains/datalore/plotDemo/model/plotConfig/Violin.kt b/plot-demo-common/src/commonMain/kotlin/jetbrains/datalore/plotDemo/model/plotConfig/Violin.kt index b88ff01595c..8467cf3ced1 100644 --- a/plot-demo-common/src/commonMain/kotlin/jetbrains/datalore/plotDemo/model/plotConfig/Violin.kt +++ b/plot-demo-common/src/commonMain/kotlin/jetbrains/datalore/plotDemo/model/plotConfig/Violin.kt @@ -12,6 +12,7 @@ class Violin { fun plotSpecList(): List> { return listOf( data132Violin(), + data132ViolinIdentity(), data132Boxplot(), data123Violin(), ) @@ -106,6 +107,29 @@ class Violin { } + private fun data132ViolinIdentity(): MutableMap { + val spec = "{" + + " 'kind': 'plot'," + + " 'data' : {'x': [1, 1, 1, 3, 3, 3, 2, 2, 2]," + + " 'y': [4, 3, 2, 5, 4, 3, 3, 2, 1]," + + " 'vw': [0, 1, 0, 0, 1, 0, 0, 1, 0]" + + " }," + + " 'mapping': {" + + " 'x': 'x'," + + " 'y': 'y'" + + " }," + + " 'layers': [" + + " {" + + " 'geom': 'violin'," + + " 'stat': 'identity'" + + " }" + + " ]" + + "}" + + return HashMap(parsePlotSpec(spec)) + + } + private fun data132Boxplot(): MutableMap { val spec = "{" + " 'kind': 'plot'," + From 606e5c8401651c33409fe4d4eaf4ade2e922af85 Mon Sep 17 00:00:00 2001 From: ASmirnov-HORIS Date: Thu, 27 Jan 2022 14:00:23 +0300 Subject: [PATCH 68/81] Update demo for debugging the violins. --- .../plotDemo/model/plotConfig/Violin.kt | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/plot-demo-common/src/commonMain/kotlin/jetbrains/datalore/plotDemo/model/plotConfig/Violin.kt b/plot-demo-common/src/commonMain/kotlin/jetbrains/datalore/plotDemo/model/plotConfig/Violin.kt index 8467cf3ced1..42d9c68f73c 100644 --- a/plot-demo-common/src/commonMain/kotlin/jetbrains/datalore/plotDemo/model/plotConfig/Violin.kt +++ b/plot-demo-common/src/commonMain/kotlin/jetbrains/datalore/plotDemo/model/plotConfig/Violin.kt @@ -11,7 +11,11 @@ import jetbrains.datalore.plotDemo.data.Iris class Violin { fun plotSpecList(): List> { return listOf( +// basic(), +// withNan(), +// withGroups(), data132Violin(), + data132ViolinDiscrete(), data132ViolinIdentity(), data132Boxplot(), data123Violin(), @@ -26,6 +30,9 @@ class Violin { " 'y': 'sepal length (cm)'," + " 'fill': 'target'" + " }," + + " 'ggtitle': {" + + " 'text': 'Basic demo'" + + " }," + " 'layers': [" + " {" + " 'geom': 'violin'," + @@ -51,6 +58,9 @@ class Violin { " 'x': 'class'," + " 'y': 'value'" + " }," + + " 'ggtitle': {" + + " 'text': 'NaNs in data'" + + " }," + " 'layers': [" + " {" + " 'geom': 'violin'" + @@ -74,6 +84,9 @@ class Violin { " 'y': 'value'," + " 'fill': 'group'" + " }," + + " 'ggtitle': {" + + " 'text': 'Additional grouping'" + + " }," + " 'layers': [" + " {" + " 'geom': 'violin'," + @@ -96,10 +109,43 @@ class Violin { " 'x': 'x'," + " 'y': 'y'" + " }," + + " 'ggtitle': {" + + " 'text': 'x=[1, 3, 2]'" + + " }," + + " 'layers': [" + + " {" + + " 'geom': 'violin'" + + " }" + + " ]" + + "}" + + return HashMap(parsePlotSpec(spec)) + + } + + private fun data132ViolinDiscrete(): MutableMap { + val spec = "{" + + " 'kind': 'plot'," + + " 'data' : {'x': [1, 3, 2]," + + " 'y': [2, 0, 1]" + + " }," + + " 'mapping': {" + + " 'x': 'x'," + + " 'y': 'y'" + + " }," + + " 'ggtitle': {" + + " 'text': 'x=[1, 3, 2] and discrete'" + + " }," + " 'layers': [" + " {" + " 'geom': 'violin'" + " }" + + " ]," + + " 'scales': [" + + " {" + + " 'aesthetic': 'x'," + + " 'discrete': true" + + " }" + " ]" + "}" @@ -118,6 +164,9 @@ class Violin { " 'x': 'x'," + " 'y': 'y'" + " }," + + " 'ggtitle': {" + + " 'text': 'x=[1, 3, 2], stat=identity'" + + " }," + " 'layers': [" + " {" + " 'geom': 'violin'," + @@ -140,6 +189,9 @@ class Violin { " 'x': 'x'," + " 'y': 'y'" + " }," + + " 'ggtitle': {" + + " 'text': 'x=[1, 3, 2], geom=boxplot'" + + " }," + " 'layers': [" + " {" + " 'geom': 'boxplot'" + @@ -161,6 +213,9 @@ class Violin { " 'x': 'x'," + " 'y': 'y'" + " }," + + " 'ggtitle': {" + + " 'text': 'x=[1, 2, 3]'" + + " }," + " 'layers': [" + " {" + " 'geom': 'violin'" + From e388e9553b20f8e92dce42c35fb0e4b6e77d3380 Mon Sep 17 00:00:00 2001 From: ASmirnov-HORIS Date: Thu, 27 Jan 2022 14:28:47 +0300 Subject: [PATCH 69/81] Fix conflicts after merging with master. --- .../datalore/plot/base/geom/ViolinGeom.kt | 2 +- .../plot/config/GeomProtoClientSide.kt | 2 +- .../datalore/plot/config/StatProto.kt | 25 ------------------- 3 files changed, 2 insertions(+), 27 deletions(-) diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt index 5a40e59db15..c9b4f74d1c9 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt @@ -153,7 +153,7 @@ class ViolinGeom : GeomBase() { targetCollector.addPath( multiPointData.points, multiPointData.localToGlobalIndex, - TooltipParams.params().setColor(HintColorUtil.fromFill(multiPointData.aes)), + TooltipParams.params().setColors(listOf(HintColorUtil.fromFill(multiPointData.aes))), if (ctx.flipped) { TipLayoutHint.Kind.VERTICAL_TOOLTIP } else { diff --git a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/GeomProtoClientSide.kt b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/GeomProtoClientSide.kt index 9def63a4c2a..460cff557a4 100644 --- a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/GeomProtoClientSide.kt +++ b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/GeomProtoClientSide.kt @@ -200,7 +200,7 @@ class GeomProtoClientSide(geomKind: GeomKind) : GeomProto(geomKind) { PROVIDER[GeomKind.H_LINE] = GeomProvider.hline() PROVIDER[GeomKind.V_LINE] = GeomProvider.vline() // boxplot - special case - PROVIDER[GeomKind.VIOLIN] = GeomProvider.violin() + // violin - special case PROVIDER[GeomKind.RIBBON] = GeomProvider.ribbon() PROVIDER[GeomKind.AREA] = GeomProvider.area() PROVIDER[GeomKind.DENSITY] = GeomProvider.density() diff --git a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/StatProto.kt b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/StatProto.kt index 61de9ea8fcc..9a002483dac 100644 --- a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/StatProto.kt +++ b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/StatProto.kt @@ -134,31 +134,6 @@ object StatProto { ) } - private fun configureYDensityStat(options: OptionsAccessor): YDensityStat { - var bwValue: Double? = null - var bwMethod: DensityStat.BandWidthMethod = DensityStat.DEF_BW - options[Density.BAND_WIDTH]?.run { - if (this is Number) { - bwValue = this.toDouble() - } else if (this is String) { - bwMethod = DensityStatUtil.toBandWidthMethod(this) - } - } - - val kernel = options.getString(Density.KERNEL)?.let { - DensityStatUtil.toKernel(it) - } - - return YDensityStat( - bandWidth = bwValue, - bandWidthMethod = bwMethod, - adjust = options.getDoubleDef(Density.ADJUST, DensityStat.DEF_ADJUST), - kernel = kernel ?: DensityStat.DEF_KERNEL, - n = options.getIntegerDef(Density.N, DensityStat.DEF_N), - fullScanMax = options.getIntegerDef(Density.FULL_SCAN_MAX, DensityStat.DEF_FULL_SCAN_MAX) - ) - } - private fun configureYDensityStat(options: OptionsAccessor): YDensityStat { val scale = options.getString(YDensity.SCALE)?.let { when (it.lowercase()) { From ecf42d0773c643192422b8f9c2c69bc6cfb3610a Mon Sep 17 00:00:00 2001 From: ASmirnov-HORIS Date: Thu, 27 Jan 2022 14:34:56 +0300 Subject: [PATCH 70/81] Fix another one conflict after merging with master. --- .../kotlin/jetbrains/datalore/plot/base/GeomMeta.kt | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/GeomMeta.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/GeomMeta.kt index 6fd8bf14f05..7952907e8fd 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/GeomMeta.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/GeomMeta.kt @@ -224,18 +224,6 @@ object GeomMeta { Aes.WIDTH ) - GeomKind.VIOLIN -> listOf( - Aes.X, - Aes.Y, - Aes.VIOLINWIDTH, - - Aes.ALPHA, - Aes.COLOR, - Aes.FILL, - Aes.LINETYPE, - Aes.SIZE - ) - GeomKind.RIBBON -> listOf( Aes.X, Aes.YMIN, Aes.YMAX, From b01377d5a2813e8a9c9792cefb11ccdea1ca4d4e Mon Sep 17 00:00:00 2001 From: ASmirnov-HORIS Date: Tue, 1 Feb 2022 17:21:20 +0300 Subject: [PATCH 71/81] Make width aesthetic to constant for violin geom. Fix the demo examples. --- .../datalore/plot/base/stat/YDensityStat.kt | 16 ++++++++++++---- .../datalore/plotDemo/model/plotConfig/Violin.kt | 10 +++++++--- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/YDensityStat.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/YDensityStat.kt index cb1d71b00b7..13a34b4b92a 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/YDensityStat.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/YDensityStat.kt @@ -43,18 +43,26 @@ class YDensityStat( } else { List(ys.size) { 0.0 } } - val ws = if (data.has(TransformVar.WEIGHT)) { + val weights = if (data.has(TransformVar.WEIGHT)) { data.getNumeric(TransformVar.WEIGHT) } else { List(ys.size) { 1.0 } } + // width is constant through all data + val width = if (data.has(TransformVar.WIDTH)) { + data.getNumeric(TransformVar.WIDTH)[0] ?: DEF_WIDTH + } else { + DEF_WIDTH + } - val statData = buildStat(xs, ys, ws) + val statData = buildStat(xs, ys, weights) + val widths = List(statData[Stats.X]!!.size) { width } val builder = DataFrame.Builder() for ((variable, series) in statData) { builder.putNumeric(variable, series) } + builder.putNumeric(Stats.WIDTH, widths) return builder.build() } @@ -83,10 +91,8 @@ class YDensityStat( } } } - // TODO: Calculate WIDTH properly return dataAfterStat.builder() .putNumeric(Stats.VIOLIN_WIDTH, statViolinWidth) - .putNumeric(Stats.WIDTH, List(statViolinWidth.size) { 1.0 }) .build() } @@ -146,6 +152,8 @@ class YDensityStat( } companion object { + val DEF_WIDTH = 1.0 + private val DEF_MAPPING: Map, DataFrame.Variable> = mapOf( Aes.X to Stats.X, Aes.Y to Stats.Y, diff --git a/plot-demo-common/src/commonMain/kotlin/jetbrains/datalore/plotDemo/model/plotConfig/Violin.kt b/plot-demo-common/src/commonMain/kotlin/jetbrains/datalore/plotDemo/model/plotConfig/Violin.kt index 42d9c68f73c..e44574f978f 100644 --- a/plot-demo-common/src/commonMain/kotlin/jetbrains/datalore/plotDemo/model/plotConfig/Violin.kt +++ b/plot-demo-common/src/commonMain/kotlin/jetbrains/datalore/plotDemo/model/plotConfig/Violin.kt @@ -14,6 +14,7 @@ class Violin { // basic(), // withNan(), // withGroups(), + data132Violin(), data132ViolinDiscrete(), data132ViolinIdentity(), @@ -114,7 +115,8 @@ class Violin { " }," + " 'layers': [" + " {" + - " 'geom': 'violin'" + + " 'geom': 'violin'," + + " 'n': 3" + " }" + " ]" + "}" @@ -138,7 +140,8 @@ class Violin { " }," + " 'layers': [" + " {" + - " 'geom': 'violin'" + + " 'geom': 'violin'," + + " 'n': 3" + " }" + " ]," + " 'scales': [" + @@ -218,7 +221,8 @@ class Violin { " }," + " 'layers': [" + " {" + - " 'geom': 'violin'" + + " 'geom': 'violin'," + + " 'n': 3" + " }" + " ]" + "}" From 207d035a2047c296391e211445a0e1066257419d Mon Sep 17 00:00:00 2001 From: ASmirnov-HORIS Date: Tue, 1 Feb 2022 17:41:57 +0300 Subject: [PATCH 72/81] Fix demo for the identity stat for violin. --- .../datalore/plot/base/stat/YDensityStat.kt | 2 +- .../plotDemo/model/plotConfig/Violin.kt | 21 +++++++++++-------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/YDensityStat.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/YDensityStat.kt index 13a34b4b92a..5d890d36123 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/YDensityStat.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/YDensityStat.kt @@ -48,7 +48,7 @@ class YDensityStat( } else { List(ys.size) { 1.0 } } - // width is constant through all data + // width should be constant through all data val width = if (data.has(TransformVar.WIDTH)) { data.getNumeric(TransformVar.WIDTH)[0] ?: DEF_WIDTH } else { diff --git a/plot-demo-common/src/commonMain/kotlin/jetbrains/datalore/plotDemo/model/plotConfig/Violin.kt b/plot-demo-common/src/commonMain/kotlin/jetbrains/datalore/plotDemo/model/plotConfig/Violin.kt index e44574f978f..c37f73c002a 100644 --- a/plot-demo-common/src/commonMain/kotlin/jetbrains/datalore/plotDemo/model/plotConfig/Violin.kt +++ b/plot-demo-common/src/commonMain/kotlin/jetbrains/datalore/plotDemo/model/plotConfig/Violin.kt @@ -11,15 +11,15 @@ import jetbrains.datalore.plotDemo.data.Iris class Violin { fun plotSpecList(): List> { return listOf( -// basic(), -// withNan(), -// withGroups(), - - data132Violin(), - data132ViolinDiscrete(), - data132ViolinIdentity(), - data132Boxplot(), - data123Violin(), + basic(), + withNan(), + withGroups(), + +// data132Violin(), +// data132ViolinDiscrete(), +// data132ViolinIdentity(), +// data132Boxplot(), +// data123Violin(), ) } @@ -173,6 +173,9 @@ class Violin { " 'layers': [" + " {" + " 'geom': 'violin'," + + " 'mapping': {" + + " 'violinwidth': 'vw'" + + " }," + " 'stat': 'identity'" + " }" + " ]" + From 260e0e6bb432a9e24957d2e49d4646eb76c28b12 Mon Sep 17 00:00:00 2001 From: ASmirnov-HORIS Date: Tue, 1 Feb 2022 19:33:13 +0300 Subject: [PATCH 73/81] Another demo for debugging violins. --- .../plotDemo/model/plotConfig/Violin.kt | 59 ++++++++++++++++++- 1 file changed, 56 insertions(+), 3 deletions(-) diff --git a/plot-demo-common/src/commonMain/kotlin/jetbrains/datalore/plotDemo/model/plotConfig/Violin.kt b/plot-demo-common/src/commonMain/kotlin/jetbrains/datalore/plotDemo/model/plotConfig/Violin.kt index c37f73c002a..8d3bb6fbc76 100644 --- a/plot-demo-common/src/commonMain/kotlin/jetbrains/datalore/plotDemo/model/plotConfig/Violin.kt +++ b/plot-demo-common/src/commonMain/kotlin/jetbrains/datalore/plotDemo/model/plotConfig/Violin.kt @@ -11,15 +11,18 @@ import jetbrains.datalore.plotDemo.data.Iris class Violin { fun plotSpecList(): List> { return listOf( - basic(), - withNan(), - withGroups(), +// basic(), +// withNan(), +// withGroups(), // data132Violin(), // data132ViolinDiscrete(), // data132ViolinIdentity(), // data132Boxplot(), // data123Violin(), + + data132ViolinN49(), + data132ViolinN50(), ) } @@ -233,4 +236,54 @@ class Violin { return HashMap(parsePlotSpec(spec)) } + + private fun data132ViolinN49(): MutableMap { + val spec = "{" + + " 'kind': 'plot'," + + " 'data' : {'x': [1, 3, 2]," + + " 'y': [2, 0, 1]" + + " }," + + " 'mapping': {" + + " 'x': 'x'," + + " 'y': 'y'" + + " }," + + " 'ggtitle': {" + + " 'text': 'x=[1, 3, 2], n=49'" + + " }," + + " 'layers': [" + + " {" + + " 'geom': 'violin'," + + " 'n': 49" + + " }" + + " ]" + + "}" + + return HashMap(parsePlotSpec(spec)) + + } + + private fun data132ViolinN50(): MutableMap { + val spec = "{" + + " 'kind': 'plot'," + + " 'data' : {'x': [1, 3, 2]," + + " 'y': [2, 0, 1]" + + " }," + + " 'mapping': {" + + " 'x': 'x'," + + " 'y': 'y'" + + " }," + + " 'ggtitle': {" + + " 'text': 'x=[1, 3, 2], n=50'" + + " }," + + " 'layers': [" + + " {" + + " 'geom': 'violin'," + + " 'n': 50" + + " }" + + " ]" + + "}" + + return HashMap(parsePlotSpec(spec)) + + } } \ No newline at end of file From b78b89384e1b13d971c9349cfcc0b4e6d899bece Mon Sep 17 00:00:00 2001 From: ASmirnov-HORIS Date: Wed, 2 Feb 2022 14:36:02 +0300 Subject: [PATCH 74/81] Update demo for debugging violins. --- .../plotDemo/model/plotConfig/Violin.kt | 76 ++++++------------- 1 file changed, 24 insertions(+), 52 deletions(-) diff --git a/plot-demo-common/src/commonMain/kotlin/jetbrains/datalore/plotDemo/model/plotConfig/Violin.kt b/plot-demo-common/src/commonMain/kotlin/jetbrains/datalore/plotDemo/model/plotConfig/Violin.kt index 8d3bb6fbc76..ef7da83bcbf 100644 --- a/plot-demo-common/src/commonMain/kotlin/jetbrains/datalore/plotDemo/model/plotConfig/Violin.kt +++ b/plot-demo-common/src/commonMain/kotlin/jetbrains/datalore/plotDemo/model/plotConfig/Violin.kt @@ -11,18 +11,16 @@ import jetbrains.datalore.plotDemo.data.Iris class Violin { fun plotSpecList(): List> { return listOf( -// basic(), -// withNan(), -// withGroups(), + basic(), + withNan(), + withGroups(), // data132Violin(), // data132ViolinDiscrete(), +// data132ViolinDefaultN(), // data132ViolinIdentity(), // data132Boxplot(), // data123Violin(), - - data132ViolinN49(), - data132ViolinN50(), ) } @@ -159,36 +157,7 @@ class Violin { } - private fun data132ViolinIdentity(): MutableMap { - val spec = "{" + - " 'kind': 'plot'," + - " 'data' : {'x': [1, 1, 1, 3, 3, 3, 2, 2, 2]," + - " 'y': [4, 3, 2, 5, 4, 3, 3, 2, 1]," + - " 'vw': [0, 1, 0, 0, 1, 0, 0, 1, 0]" + - " }," + - " 'mapping': {" + - " 'x': 'x'," + - " 'y': 'y'" + - " }," + - " 'ggtitle': {" + - " 'text': 'x=[1, 3, 2], stat=identity'" + - " }," + - " 'layers': [" + - " {" + - " 'geom': 'violin'," + - " 'mapping': {" + - " 'violinwidth': 'vw'" + - " }," + - " 'stat': 'identity'" + - " }" + - " ]" + - "}" - - return HashMap(parsePlotSpec(spec)) - - } - - private fun data132Boxplot(): MutableMap { + private fun data132ViolinDefaultN(): MutableMap { val spec = "{" + " 'kind': 'plot'," + " 'data' : {'x': [1, 3, 2]," + @@ -199,11 +168,11 @@ class Violin { " 'y': 'y'" + " }," + " 'ggtitle': {" + - " 'text': 'x=[1, 3, 2], geom=boxplot'" + + " 'text': 'x=[1, 3, 2], default n'" + " }," + " 'layers': [" + " {" + - " 'geom': 'boxplot'" + + " 'geom': 'violin'" + " }" + " ]" + "}" @@ -212,23 +181,27 @@ class Violin { } - private fun data123Violin(): MutableMap { + private fun data132ViolinIdentity(): MutableMap { val spec = "{" + " 'kind': 'plot'," + - " 'data' : {'x': [1, 2, 3]," + - " 'y': [2, 1, 0]" + + " 'data' : {'x': [1, 1, 1, 3, 3, 3, 2, 2, 2]," + + " 'y': [4, 3, 2, 5, 4, 3, 3, 2, 1]," + + " 'vw': [0, 1, 0, 0, 1, 0, 0, 1, 0]" + " }," + " 'mapping': {" + " 'x': 'x'," + " 'y': 'y'" + " }," + " 'ggtitle': {" + - " 'text': 'x=[1, 2, 3]'" + + " 'text': 'x=[1, 3, 2], stat=identity'" + " }," + " 'layers': [" + " {" + " 'geom': 'violin'," + - " 'n': 3" + + " 'mapping': {" + + " 'violinwidth': 'vw'" + + " }," + + " 'stat': 'identity'" + " }" + " ]" + "}" @@ -237,7 +210,7 @@ class Violin { } - private fun data132ViolinN49(): MutableMap { + private fun data132Boxplot(): MutableMap { val spec = "{" + " 'kind': 'plot'," + " 'data' : {'x': [1, 3, 2]," + @@ -248,12 +221,11 @@ class Violin { " 'y': 'y'" + " }," + " 'ggtitle': {" + - " 'text': 'x=[1, 3, 2], n=49'" + + " 'text': 'x=[1, 3, 2], geom=boxplot'" + " }," + " 'layers': [" + " {" + - " 'geom': 'violin'," + - " 'n': 49" + + " 'geom': 'boxplot'" + " }" + " ]" + "}" @@ -262,23 +234,23 @@ class Violin { } - private fun data132ViolinN50(): MutableMap { + private fun data123Violin(): MutableMap { val spec = "{" + " 'kind': 'plot'," + - " 'data' : {'x': [1, 3, 2]," + - " 'y': [2, 0, 1]" + + " 'data' : {'x': [1, 2, 3]," + + " 'y': [2, 1, 0]" + " }," + " 'mapping': {" + " 'x': 'x'," + " 'y': 'y'" + " }," + " 'ggtitle': {" + - " 'text': 'x=[1, 3, 2], n=50'" + + " 'text': 'x=[1, 2, 3]'" + " }," + " 'layers': [" + " {" + " 'geom': 'violin'," + - " 'n': 50" + + " 'n': 3" + " }" + " ]" + "}" From 86ef5322fce52d3ea298b1063042b7abe433126f Mon Sep 17 00:00:00 2001 From: ASmirnov-HORIS Date: Wed, 2 Feb 2022 15:13:05 +0300 Subject: [PATCH 75/81] Fix problem with grouped quantiles. --- .../plot/base/aes/AestheticsBuilder.kt | 4 +++ .../datalore/plot/base/geom/ViolinGeom.kt | 29 ++++++++++--------- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/aes/AestheticsBuilder.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/aes/AestheticsBuilder.kt index 61fade1b39d..4dc19ecc816 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/aes/AestheticsBuilder.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/aes/AestheticsBuilder.kt @@ -113,6 +113,10 @@ class AestheticsBuilder @JvmOverloads constructor(private var myDataPointCount: return aes(WIDTH, v) } + fun violinwidth(v: (Int) -> Double?): AestheticsBuilder { + return aes(VIOLINWIDTH, v) + } + fun weight(v: (Int) -> Double?): AestheticsBuilder { return aes(WEIGHT, v) } diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt index c9b4f74d1c9..cafc7be9885 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ViolinGeom.kt @@ -61,7 +61,7 @@ class ViolinGeom : GeomBase() { appendNodes(helper.createLines(dataPoints, leftBoundTransform), root) appendNodes(helper.createLines(dataPoints, rightBoundTransform), root) - // buildQuantiles(root, dataPoints, pos, coord, ctx) + buildQuantiles(root, dataPoints, pos, coord, ctx) buildHints(dataPoints, ctx, helper, leftBoundTransform) buildHints(dataPoints, ctx, helper, rightBoundTransform) @@ -78,34 +78,37 @@ class ViolinGeom : GeomBase() { val geomHelper = GeomHelper(pos, coord, ctx) val helper = geomHelper.createSvgElementHelper() - for (p in calculateQuantiles(dataPoints, ctx)) { - val start = DoubleVector(p.xmin()!!, p.y()!!) - val end = DoubleVector(p.xmax()!!, p.y()!!) - val line = helper.createLine(start, end, p) - root.add(line) + for ((group, dataPointsGroup) in dataPoints.groupBy { it.group() }) { + for (p in calculateQuantiles(dataPointsGroup, group)) { + val xmin = toLocationBound(-1.0, ctx)(p).x + val xmax = toLocationBound(1.0, ctx)(p).x + val start = DoubleVector(xmin, p.y()!!) + val end = DoubleVector(xmax, p.y()!!) + val line = helper.createLine(start, end, p) + root.add(line) + } } } private fun calculateQuantiles( dataPoints: Iterable, - ctx: GeomContext + group: Int? ): Iterable { + val x = dataPoints.first().x()!! val vws = dataPoints.map { it.violinwidth()!! } val ys = dataPoints.map { it.y()!! } - val xsMin = dataPoints.map { toLocationBound(-1.0, ctx)(it) }.map { it.x } - val xsMax = dataPoints.map { toLocationBound(1.0, ctx)(it) }.map { it.x } val vwsSum = vws.sum() val dens = vws.runningReduce { cumSum, elem -> cumSum + elem }.map { it / vwsSum } val quantY = drawQuantiles.map { pwLinInterp(dens, ys)(it) } - val quantXMin = quantY.map { pwLinInterp(ys, xsMin)(it) } - val quantXMax = quantY.map { pwLinInterp(ys, xsMax)(it) } + val quantViolinWidth = quantY.map { pwLinInterp(ys, vws)(it) } val quantilesColor = dataPoints.first().color() val quantilesSize = dataPoints.first().size() return AestheticsBuilder(quantY.size) + .x(AestheticsBuilder.constant(x)) .y(AestheticsBuilder.list(quantY)) - .xmin(AestheticsBuilder.list(quantXMin)) - .xmax(AestheticsBuilder.list(quantXMax)) + .violinwidth(AestheticsBuilder.list(quantViolinWidth)) + .group(AestheticsBuilder.constant(group ?: 0)) .color(AestheticsBuilder.constant(quantilesColor)) .size(AestheticsBuilder.constant(quantilesSize)) .build() From c34046cfbb3f695499a4a25a6aabb9f165873d75 Mon Sep 17 00:00:00 2001 From: ASmirnov-HORIS Date: Wed, 2 Feb 2022 18:53:18 +0300 Subject: [PATCH 76/81] Fix width in YDensityStat and update some comments. --- .../datalore/plot/base/stat/YDensityStat.kt | 14 ++++---------- .../datalore/plotDemo/model/plotConfig/Violin.kt | 1 + 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/YDensityStat.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/YDensityStat.kt index 5d890d36123..9b95f747b9c 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/YDensityStat.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/YDensityStat.kt @@ -48,27 +48,21 @@ class YDensityStat( } else { List(ys.size) { 1.0 } } - // width should be constant through all data - val width = if (data.has(TransformVar.WIDTH)) { - data.getNumeric(TransformVar.WIDTH)[0] ?: DEF_WIDTH - } else { - DEF_WIDTH - } val statData = buildStat(xs, ys, weights) - val widths = List(statData[Stats.X]!!.size) { width } + val statWidth = List(statData.getValue(Stats.X).size) { DEF_WIDTH } val builder = DataFrame.Builder() for ((variable, series) in statData) { builder.putNumeric(variable, series) } - builder.putNumeric(Stats.WIDTH, widths) + builder.putNumeric(Stats.WIDTH, statWidth) return builder.build() } override fun normalize(dataAfterStat: DataFrame): DataFrame { val statViolinWidth = if (dataAfterStat.rowCount() == 0) { - emptyList() + emptyList() } else { when (scale) { Scale.AREA -> { @@ -87,7 +81,7 @@ class YDensityStat( statCount.map { it / norm } } Scale.WIDTH -> { - dataAfterStat.getNumeric(Stats.SCALED).map { it!! } + dataAfterStat.getNumeric(Stats.SCALED) } } } diff --git a/plot-demo-common/src/commonMain/kotlin/jetbrains/datalore/plotDemo/model/plotConfig/Violin.kt b/plot-demo-common/src/commonMain/kotlin/jetbrains/datalore/plotDemo/model/plotConfig/Violin.kt index ef7da83bcbf..c160546d4ac 100644 --- a/plot-demo-common/src/commonMain/kotlin/jetbrains/datalore/plotDemo/model/plotConfig/Violin.kt +++ b/plot-demo-common/src/commonMain/kotlin/jetbrains/datalore/plotDemo/model/plotConfig/Violin.kt @@ -15,6 +15,7 @@ class Violin { withNan(), withGroups(), +// TODO: Move this to tests // data132Violin(), // data132ViolinDiscrete(), // data132ViolinDefaultN(), From c502f9b138c6318fbcf4aa7ed4c9e7bcf547a3e6 Mon Sep 17 00:00:00 2001 From: ASmirnov-HORIS Date: Thu, 3 Feb 2022 12:22:23 +0300 Subject: [PATCH 77/81] Remove extra WIDTH from the YDensityStat. --- .../kotlin/jetbrains/datalore/plot/base/stat/YDensityStat.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/YDensityStat.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/YDensityStat.kt index 9b95f747b9c..873f7c92f12 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/YDensityStat.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/YDensityStat.kt @@ -50,13 +50,11 @@ class YDensityStat( } val statData = buildStat(xs, ys, weights) - val statWidth = List(statData.getValue(Stats.X).size) { DEF_WIDTH } val builder = DataFrame.Builder() for ((variable, series) in statData) { builder.putNumeric(variable, series) } - builder.putNumeric(Stats.WIDTH, statWidth) return builder.build() } From 3b22588f7cad14d03595073a5940d6900138324f Mon Sep 17 00:00:00 2001 From: ASmirnov-HORIS Date: Thu, 3 Feb 2022 12:24:27 +0300 Subject: [PATCH 78/81] Tiny fix in naming. --- .../kotlin/jetbrains/datalore/plot/base/stat/YDensityStat.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/YDensityStat.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/YDensityStat.kt index 873f7c92f12..ade8e3d8e6e 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/YDensityStat.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/YDensityStat.kt @@ -43,13 +43,13 @@ class YDensityStat( } else { List(ys.size) { 0.0 } } - val weights = if (data.has(TransformVar.WEIGHT)) { + val ws = if (data.has(TransformVar.WEIGHT)) { data.getNumeric(TransformVar.WEIGHT) } else { List(ys.size) { 1.0 } } - val statData = buildStat(xs, ys, weights) + val statData = buildStat(xs, ys, ws) val builder = DataFrame.Builder() for ((variable, series) in statData) { From a2d32c5b0206d177fabfa4dab461b9028d898ec8 Mon Sep 17 00:00:00 2001 From: ASmirnov-HORIS Date: Thu, 3 Feb 2022 12:25:37 +0300 Subject: [PATCH 79/81] Remove extra constant from the YDensityStat. --- .../kotlin/jetbrains/datalore/plot/base/stat/YDensityStat.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/YDensityStat.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/YDensityStat.kt index ade8e3d8e6e..58e3c6c7373 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/YDensityStat.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/stat/YDensityStat.kt @@ -144,8 +144,6 @@ class YDensityStat( } companion object { - val DEF_WIDTH = 1.0 - private val DEF_MAPPING: Map, DataFrame.Variable> = mapOf( Aes.X to Stats.X, Aes.Y to Stats.Y, From aa283e3f8b7d1e0344b7d9da787f07c86edd30b1 Mon Sep 17 00:00:00 2001 From: ASmirnov-HORIS Date: Wed, 9 Feb 2022 11:51:13 +0300 Subject: [PATCH 80/81] Add docstring to the geom_violin() and update the demo notebook. --- docs/f-21-12/notebooks/geom_violin.ipynb | 1087 ++++++++++++---------- python-package/lets_plot/plot/geom.py | 131 +++ 2 files changed, 716 insertions(+), 502 deletions(-) diff --git a/docs/f-21-12/notebooks/geom_violin.ipynb b/docs/f-21-12/notebooks/geom_violin.ipynb index c1f244d0778..470d205d035 100644 --- a/docs/f-21-12/notebooks/geom_violin.ipynb +++ b/docs/f-21-12/notebooks/geom_violin.ipynb @@ -12,7 +12,7 @@ " \n", " \n", @@ -24,7 +24,6 @@ } ], "source": [ - "import numpy as np\n", "import pandas as pd\n", "\n", "from lets_plot import *\n", @@ -33,113 +32,32 @@ ] }, { - "cell_type": "markdown", + "cell_type": "code", + "execution_count": 2, "metadata": {}, + "outputs": [], "source": [ - "## Test datasets" + "DRAW_QUANTILES = [.25, .5, .75]" ] }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 3, "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
sepal_lengthsepal_widthpetal_lengthpetal_widthspecies
05.13.51.40.2setosa
14.93.01.40.2setosa
24.73.21.30.2setosa
34.63.11.50.2setosa
45.03.61.40.2setosa
\n", - "
" - ], - "text/plain": [ - " sepal_length sepal_width petal_length petal_width species\n", - "0 5.1 3.5 1.4 0.2 setosa\n", - "1 4.9 3.0 1.4 0.2 setosa\n", - "2 4.7 3.2 1.3 0.2 setosa\n", - "3 4.6 3.1 1.5 0.2 setosa\n", - "4 5.0 3.6 1.4 0.2 setosa" - ] - }, - "execution_count": 2, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ - "iris_df = pd.read_csv(\"https://raw.githubusercontent.com/JetBrains/lets-plot-docs/master/data/iris.csv\")\n", - "\n", - "iris_df.head()" + "def plot_matrix(plots=[], width=400, height=300, columns=2):\n", + " bunch = GGBunch()\n", + " for i in range(len(plots)):\n", + " row = int(i / columns)\n", + " column = i % columns\n", + " bunch.add_plot(plots[i], column * width, row * height, width, height)\n", + " return bunch.show()" ] }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 4, "metadata": {}, "outputs": [ { @@ -163,307 +81,361 @@ " \n", " \n", " \n", - " species\n", - " sepal_length\n", - " weight\n", + " Unnamed: 0\n", + " manufacturer\n", + " model\n", + " displ\n", + " year\n", + " cyl\n", + " trans\n", + " drv\n", + " cty\n", + " hwy\n", + " fl\n", + " class\n", " \n", " \n", " \n", " \n", " 0\n", - " setosa\n", - " 4.300000\n", - " 0.222676\n", + " 1\n", + " audi\n", + " a4\n", + " 1.8\n", + " 1999\n", + " 4\n", + " auto(l5)\n", + " f\n", + " 18\n", + " 29\n", + " p\n", + " compact\n", " \n", " \n", " 1\n", - " setosa\n", - " 4.302935\n", - " 0.228662\n", + " 2\n", + " audi\n", + " a4\n", + " 1.8\n", + " 1999\n", + " 4\n", + " manual(m5)\n", + " f\n", + " 21\n", + " 29\n", + " p\n", + " compact\n", " \n", " \n", " 2\n", - " setosa\n", - " 4.305871\n", - " 0.234639\n", + " 3\n", + " audi\n", + " a4\n", + " 2.0\n", + " 2008\n", + " 4\n", + " manual(m6)\n", + " f\n", + " 20\n", + " 31\n", + " p\n", + " compact\n", " \n", " \n", " 3\n", - " setosa\n", - " 4.308806\n", - " 0.240684\n", + " 4\n", + " audi\n", + " a4\n", + " 2.0\n", + " 2008\n", + " 4\n", + " auto(av)\n", + " f\n", + " 21\n", + " 30\n", + " p\n", + " compact\n", " \n", " \n", " 4\n", - " setosa\n", - " 4.311742\n", - " 0.246886\n", + " 5\n", + " audi\n", + " a4\n", + " 2.8\n", + " 1999\n", + " 6\n", + " auto(l5)\n", + " f\n", + " 16\n", + " 26\n", + " p\n", + " compact\n", " \n", " \n", "\n", "" ], "text/plain": [ - " species sepal_length weight\n", - "0 setosa 4.300000 0.222676\n", - "1 setosa 4.302935 0.228662\n", - "2 setosa 4.305871 0.234639\n", - "3 setosa 4.308806 0.240684\n", - "4 setosa 4.311742 0.246886" + " Unnamed: 0 manufacturer model displ year cyl trans drv cty hwy \\\n", + "0 1 audi a4 1.8 1999 4 auto(l5) f 18 29 \n", + "1 2 audi a4 1.8 1999 4 manual(m5) f 21 29 \n", + "2 3 audi a4 2.0 2008 4 manual(m6) f 20 31 \n", + "3 4 audi a4 2.0 2008 4 auto(av) f 21 30 \n", + "4 5 audi a4 2.8 1999 6 auto(l5) f 16 26 \n", + "\n", + " fl class \n", + "0 p compact \n", + "1 p compact \n", + "2 p compact \n", + "3 p compact \n", + "4 p compact " ] }, - "execution_count": 3, + "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "def construct_violin_df(df, xname, yname, n=512):\n", - " from functools import reduce\n", - "\n", - " from scipy.stats import gaussian_kde\n", + "mpg_df = pd.read_csv(\"https://raw.githubusercontent.com/JetBrains/lets-plot-docs/master/data/mpg.csv\")\n", "\n", - " def get_weights(values):\n", - " def nrd0_bw(kde):\n", - " iqr = np.quantile(kde.dataset, .75) - np.quantile(kde.dataset, .25)\n", - " std = np.std(kde.dataset)\n", - " size = kde.dataset.size\n", - " if iqr > 0:\n", - " return .9 * min(std, iqr / 1.34) * (size ** -.2)\n", - " if std > 0:\n", - " return .9 * std * (size ** -.2)\n", - "\n", - " yrange = np.linspace(values.min(), values.max(), n)\n", - "\n", - " return {yname: yrange, 'weight': gaussian_kde(values, bw_method=nrd0_bw)(yrange)}\n", - "\n", - " def reducer(agg_df, xval):\n", - " weights = get_weights(df[df[xname] == xval][yname])\n", - " y = weights[yname]\n", - " x = [xval] * y.size\n", - " w = weights['weight']\n", - "\n", - " return pd.concat([agg_df, pd.DataFrame({xname: x, yname: y, 'weight': w})], ignore_index=True)\n", - "\n", - " return reduce(reducer, df[xname], pd.DataFrame(columns=[xname, yname, 'weight']))\n", - "\n", - "violin_df = construct_violin_df(iris_df, 'species', 'sepal_length')\n", - "violin_df.head()" + "mpg_df.head()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Minimalistic example" ] }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
vc1c2
00.496714Ab
1-0.138264Bb
20.647689Aa
31.523030Aa
4-0.234153Ca
\n", - "
" + "
\n", + " " ], "text/plain": [ - " v c1 c2\n", - "0 0.496714 A b\n", - "1 -0.138264 B b\n", - "2 0.647689 A a\n", - "3 1.523030 A a\n", - "4 -0.234153 C a" + "" ] }, - "execution_count": 4, + "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "size = 100\n", - "np.random.seed(42)\n", - "random_df = pd.DataFrame({\n", - " 'v': np.random.normal(size=size),\n", - " 'c1': np.random.choice(['A', 'B', 'C'], size=size),\n", - " 'c2': np.random.choice(['a', 'b'], size=size)\n", - "})\n", - "\n", - "random_df.head()" + "ggplot(mpg_df, aes(y='hwy')) + geom_violin() + ggtitle(\"Simplest example\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Comparison of geoms" ] }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
vc1c2
00.496714Ab
1-0.138264NaNb
2NaNAa
31.523030ANaN
4-0.234153Ca
\n", - "
" - ], - "text/plain": [ - " v c1 c2\n", - "0 0.496714 A b\n", - "1 -0.138264 NaN b\n", - "2 NaN A a\n", - "3 1.523030 A NaN\n", - "4 -0.234153 C a" + "
\n", + " " ] }, - "execution_count": 5, "metadata": {}, - "output_type": "execute_result" + "output_type": "display_data" } ], "source": [ - "def mask(p=.1, seed=42):\n", - " np.random.seed(seed)\n", - " return np.random.choice([True, False], random_df.shape[0], p=[p, 1 - p])\n", - "\n", - "nullable_df = random_df.copy()\n", - "nullable_df.loc[mask(seed=1), 'v'] = np.nan\n", - "nullable_df.loc[mask(seed=2), 'c1'] = np.nan\n", - "nullable_df.loc[mask(seed=6), 'c2'] = np.nan\n", + "p_d = ggplot(mpg_df) + \\\n", + " geom_density(aes(x='hwy', fill='drv'), color='black', alpha=.5) + \\\n", + " facet_grid(x='drv') + \\\n", + " coord_flip() + \\\n", + " ggtitle(\"geom_density()\")\n", + "p_v = ggplot(mpg_df, aes(x=as_discrete('drv', order=1), y='hwy')) + \\\n", + " geom_violin(aes(fill='drv'), alpha=.5) + \\\n", + " ggtitle(\"geom_violin()\")\n", "\n", - "nullable_df.head()" + "plot_matrix([p_d, p_v])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Minimalistic example" + "## Original parameters" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### `draw_quantiles`" ] }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/html": [ - "
\n", + "
\n", " " ], "text/plain": [ - "" + "" ] }, - "execution_count": 6, + "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "ggplot(random_df, aes(y='v')) + geom_violin() + ggtitle(\"Simplest example\")" + "tests = [\n", + " {'draw_quantiles': None}, # default\n", + " {'draw_quantiles': [.05, .5, .95]}, # all correct\n", + " {'draw_quantiles': (1/3, .5, 2/3)}, # strange, but correct\n", + " {'draw_quantiles': [.25]}, # only one\n", + " {'draw_quantiles': []}, # empty\n", + " {'draw_quantiles': [0, .5, 1]}, # include borders\n", + " {'draw_quantiles': [-1, .5, 2], 'skip': True}, # beyond borders\n", + " {'draw_quantiles': ['0.25', '0.5', '0.75'], 'skip': True}, # invalid values\n", + " {'draw_quantiles': [True, False], 'skip': True}, # totally invalid values\n", + " {'draw_quantiles': 0.5, 'skip': True}, # wrong parameter type\n", + " {'draw_quantiles': True, 'skip': True}, # another wrong parameter type\n", + " {'draw_quantiles': '0.25', 'skip': True}, # even worse parameter type\n", + " {'draw_quantiles': object(), 'skip': True}, # totally wrong parameter type\n", + "]\n", + "\n", + "ggplot(mpg_df, aes('drv', 'hwy')) + \\\n", + " geom_violin(draw_quantiles=DRAW_QUANTILES) + \\\n", + " ggtitle(\"draw_quantiles={0}\".format(DRAW_QUANTILES))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Comparison of geoms" + "### `scale`" ] }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/html": [ - "
\n", + "
\n", " " ], "text/plain": [ - "" + "" ] }, - "execution_count": 9, + "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "ggplot(random_df, aes(x='c1', y='v')) + \\\n", - " geom_violin(aes(fill='c2'), tooltips=layer_tooltips().line('^x')\n", - " .line('category|@c2')\n", - " .line('v|@v')\n", - " .line('@|@..density..')\n", - " .line('count|@..count..')\n", - " .line('scaled|@..scaled..')) + \\\n", + "ggplot(mpg_df, aes(x='drv', y='hwy')) + \\\n", + " geom_violin(aes(group='year', fill=as_discrete('year')), \\\n", + " draw_quantiles=DRAW_QUANTILES, \\\n", + " tooltips=layer_tooltips().line('^x')\n", + " .line('year|@year')\n", + " .line('hwy|@hwy')\n", + " .line('violinwidth|@..violinwidth..')\n", + " .line('density|@..density..')\n", + " .line('count|@..count..')\n", + " .line('scaled|@..scaled..')) + \\\n", " ggtitle(\"Grouping and tooltips\")" ] }, @@ -894,52 +954,62 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## `coord_flip()`" + "## Facets" ] }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/html": [ - "
\n", + "
\n", " " ], "text/plain": [ - "" + "" ] }, - "execution_count": 10, + "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "ggplot(iris_df, aes('species', 'sepal_length')) + \\\n", - " geom_violin() + \\\n", - " coord_flip() + \\\n", - " ggtitle(\"Use coord_flip()\")" + "ggplot(mpg_df, aes(x='drv', y='hwy')) + \\\n", + " geom_violin(aes(fill=as_discrete('year')), draw_quantiles=DRAW_QUANTILES) + \\\n", + " facet_grid(y='year')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## \"identity\" statistic" + "## `coord_flip()`" ] }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/html": [ - "
\n", + "
\n", " " ], "text/plain": [ - "" + "" ] }, - "execution_count": 11, + "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "ggplot(violin_df, aes('species', 'sepal_length')) + \\\n", - " geom_violin(aes(weight='weight'), stat='identity') + \\\n", - " ggtitle(\"Use 'identity' statistic\")" + "ggplot(mpg_df, aes('drv', 'hwy')) + \\\n", + " geom_violin(draw_quantiles=DRAW_QUANTILES) + \\\n", + " coord_flip() + \\\n", + " ggtitle(\"Use coord_flip()\")" ] }, { @@ -1040,41 +1113,35 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "text/html": [ - "
\n", + "
\n", " " ], "text/plain": [ - "" + "" ] }, - "execution_count": 12, + "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "ggplot(random_df, aes(as_discrete('c1', order=-1), 'v')) + \\\n", - " geom_violin(aes(color='c1', fill='c1'), alpha=.5, size=2, \\\n", + "ggplot(mpg_df, aes(as_discrete('drv', order=-1), 'hwy')) + \\\n", + " geom_violin(aes(color='drv', fill='drv'), alpha=.5, size=2, \\\n", + " n=8, draw_quantiles=DRAW_QUANTILES,\n", " sampling=sampling_group_systematic(2)) + \\\n", - " facet_grid(x='c2') + \\\n", - " scale_y_continuous(breaks=list(np.linspace(-3, 3, 9))) + \\\n", + " scale_y_continuous(breaks=list(range(12, 29, 2))) + \\\n", " scale_color_brewer(type='qual', palette='Set1') + \\\n", " scale_fill_brewer(type='qual', palette='Set1') + \\\n", - " ylim(-3, 3) + \\\n", - " coord_fixed(ratio=.5) + \\\n", + " ylim(12, 28) + \\\n", + " coord_fixed(ratio=.2) + \\\n", " theme_grey() + \\\n", " ggtitle(\"Some additional aesthetics, parameters and layers\")" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Dataset with NaN's" - ] - }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "text/html": [ - "
\n", + "
\n", " " ], "text/plain": [ - "" + "" ] }, - "execution_count": 13, + "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "ggplot(nullable_df, aes('c1', 'v')) + geom_violin()" + "# Note: quartiles for violin need not to be equal to the quartiles for boxplot!\n", + "# See the last paragraph here: https://stackoverflow.com/a/36036821/11771414\n", + "quartiles = [1/4, 2/4, 3/4]\n", + "ggplot(mpg_df, aes(x='drv', y='hwy')) + \\\n", + " geom_violin(draw_quantiles=quartiles) + \\\n", + " geom_boxplot(width=.1)" ] } ], diff --git a/python-package/lets_plot/plot/geom.py b/python-package/lets_plot/plot/geom.py index da1783192a1..cea431a355a 100644 --- a/python-package/lets_plot/plot/geom.py +++ b/python-package/lets_plot/plot/geom.py @@ -3,6 +3,7 @@ # Use of this source code is governed by the MIT license that can be found in the LICENSE file. # from lets_plot.geo_data_internals.utils import is_geocoder + from .core import FeatureSpec, LayerSpec from .util import as_annotated_data, is_geo_data_frame, geo_data_frame_to_wgs84, get_geo_data_frame_meta @@ -2647,6 +2648,136 @@ def geom_boxplot(mapping=None, *, data=None, stat=None, position=None, show_lege def geom_violin(mapping=None, *, data=None, stat=None, position=None, show_legend=None, sampling=None, tooltips=None, **other_args): + """ + A violin plot is a mirrored density plot with an additional grouping as for a boxplot. + + Parameters + ---------- + mapping : `FeatureSpec` + Set of aesthetic mappings created by `aes()` function. + Aesthetic mappings describe the way that variables in the data are + mapped to plot "aesthetics". + data : dict or `DataFrame` + The data to be displayed in this layer. If None, the default, the data + is inherited from the plot data as specified in the call to ggplot. + stat : str, default='ydensity' + The statistical transformation to use on the data for this layer, as a string. + position : str or `FeatureSpec` + Position adjustment, either as a string ('identity', 'stack', 'dodge', ...), + or the result of a call to a position adjustment function. + show_legend : bool, default=True + False - do not show legend for this layer. + sampling : `FeatureSpec` + Result of the call to the `sampling_xxx()` function. + Value None (or 'none') will disable sampling for this layer. + tooltips : `layer_tooltips` + Result of the call to the `layer_tooltips()` function. + Specifies appearance, style and content. + draw_quantiles : list of float + Draw horizontal lines at the given quantiles of the density estimate. + scale : {'area', 'count', 'width'}, default='area' + If 'area', all violins have the same area. + If 'count', areas are scaled proportionally to the number of observations. + If 'width', all violins have the same maximum width. + other_args + Other arguments passed on to the layer. + These are often aesthetics settings used to set an aesthetic to a fixed value, + like color='red', fill='blue', size=3 or shape=21. + They may also be parameters to the paired geom/stat. + + Returns + ------- + `LayerSpec` + Geom object specification. + + Notes + ----- + Computed variables: + + - ..violinwidth.. : density scaled for the violin plot, according to area, counts or to a constant maximum width (mapped by default). + - ..density.. : density estimate. + - ..count.. : density * number of points. + - ..scaled.. : density estimate, scaled to maximum of 1. + + `geom_violin()` understands the following aesthetics mappings: + + - x : x-axis coordinates. + - y : y-axis coordinates. + - alpha : transparency level of a layer. Understands numbers between 0 and 1. + - color (colour) : color of a geometry lines. Can be continuous or discrete. For continuous value this will be a color gradient between two colors. + - fill : color of geometry filling. + - size : lines width. + - linetype : type of the line of border. Codes and names: 0 = 'blank', 1 = 'solid', 2 = 'dashed', 3 = 'dotted', 4 = 'dotdash', 5 = 'longdash', 6 = 'twodash'. + - weight : used by 'ydensity' stat to compute weighted density. + + Examples + -------- + .. jupyter-execute:: + :linenos: + :emphasize-lines: 9 + + import numpy as np + from lets_plot import * + LetsPlot.setup_html() + n = 100 + np.random.seed(42) + x = np.random.choice(['a', 'b', 'c'], size=n) + y = np.random.normal(size=n) + ggplot({'x': x, 'y': y}, aes(x='x', y='y')) + \\ + geom_violin() + + | + + .. jupyter-execute:: + :linenos: + :emphasize-lines: 9 + + import numpy as np + from lets_plot import * + LetsPlot.setup_html() + n = 100 + np.random.seed(42) + x = np.random.choice(['a', 'b', 'b', 'c'], size=n) + y = np.random.normal(size=n) + ggplot({'x': x, 'y': y}, aes('x', 'y')) + \\ + geom_violin(scale='count', draw_quantiles=[.25, .5, .75]) + + | + + .. jupyter-execute:: + :linenos: + :emphasize-lines: 10 + + import numpy as np + from lets_plot import * + LetsPlot.setup_html() + n = 3 + np.random.seed(42) + x = ['a'] * n + ['b'] * n + ['c'] * n + y = 3 * list(range(n)) + vw = np.random.uniform(size=3*n) + ggplot({'x': x, 'y': y, 'vw': vw}, aes('x', 'y')) + \\ + geom_violin(aes(violinwidth='vw', fill='x'), stat='identity') + + | + .. jupyter-execute:: + :linenos: + :emphasize-lines: 10-11 + + import numpy as np + import pandas as pd + from lets_plot import * + LetsPlot.setup_html() + n, m = 100, 5 + np.random.seed(42) + df = pd.DataFrame({'x%s' % i: np.random.normal(size=n) \\ + for i in range(1, m + 1)}) + ggplot(df.melt(), aes('variable', 'value')) + \\ + geom_violin(aes(color='variable', fill='variable'), \\ + size=2, alpha=.5, scale='width') + \\ + geom_boxplot(aes(fill='variable'), width=.2) + + """ return _geom('violin', mapping=mapping, data=data, From ea0fedaf33733383e6cc6fd639ea0252bab1ff3f Mon Sep 17 00:00:00 2001 From: ASmirnov-HORIS Date: Fri, 25 Feb 2022 11:40:49 +0300 Subject: [PATCH 81/81] Add YDensityStatTest. --- .../plot/base/stat/YDensityStatTest.kt | 166 ++++++++++++++++++ .../plotDemo/model/plotConfig/Violin.kt | 166 ------------------ 2 files changed, 166 insertions(+), 166 deletions(-) create mode 100644 plot-base-portable/src/commonTest/kotlin/jetbrains/datalore/plot/base/stat/YDensityStatTest.kt diff --git a/plot-base-portable/src/commonTest/kotlin/jetbrains/datalore/plot/base/stat/YDensityStatTest.kt b/plot-base-portable/src/commonTest/kotlin/jetbrains/datalore/plot/base/stat/YDensityStatTest.kt new file mode 100644 index 00000000000..0beb80ab2a8 --- /dev/null +++ b/plot-base-portable/src/commonTest/kotlin/jetbrains/datalore/plot/base/stat/YDensityStatTest.kt @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2022. JetBrains s.r.o. + * Use of this source code is governed by the MIT license that can be found in the LICENSE file. + */ + +package jetbrains.datalore.plot.base.stat + +import jetbrains.datalore.base.gcommon.collect.ClosedRange +import jetbrains.datalore.plot.base.DataFrame +import jetbrains.datalore.plot.base.StatContext +import jetbrains.datalore.plot.base.data.TransformVar +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +class YDensityStatTest { + private fun statContext(d: DataFrame): StatContext { + return SimpleStatContext(d) + } + + private fun dataFrame(dataMap: Map>): DataFrame { + val builder = DataFrame.Builder() + for (key in dataMap.keys) { + builder.put(key, dataMap.getValue(key)) + } + return builder.build() + } + + private fun filteredDataFrame(df: DataFrame, variable: DataFrame.Variable, filterFun: (Double?) -> Boolean): DataFrame { + val indices = df.getNumeric(variable) + .mapIndexed { index, v -> if (filterFun(v)) index else null } + .filterNotNull() + + return df.selectIndices(indices) + } + + private fun yDensityStat(scale: YDensityStat.Scale? = null): YDensityStat { + return YDensityStat( + scale = scale ?: YDensityStat.DEF_SCALE, + bandWidth = null, + bandWidthMethod = DensityStat.DEF_BW, + adjust = DensityStat.DEF_ADJUST, + kernel = DensityStat.DEF_KERNEL, + n = DensityStat.DEF_N, + fullScanMax = DensityStat.DEF_FULL_SCAN_MAX + ) + } + + private fun checkStatVar(statDf: DataFrame, variable: DataFrame.Variable) { + assertTrue(statDf.has(variable), "Has var " + variable.name) + } + + private fun checkStatVarAndValuesDomain(statDf: DataFrame, variable: DataFrame.Variable, expectedValuesDomain: Set) { + checkStatVar(statDf, variable) + assertEquals(statDf.getNumeric(variable).toSet(), expectedValuesDomain, "Unique values of var " + variable.name) + } + + private fun checkStatVarAndValuesRange(statDf: DataFrame, variable: DataFrame.Variable, expectedValuesRange: ClosedRange) { + checkStatVar(statDf, variable) + val actualMinValue = statDf.getNumeric(variable).minByOrNull { it!! }!! + assertEquals(expectedValuesRange.lowerEnd, actualMinValue, "Min value of var " + variable.name) + val actualMaxValue = statDf.getNumeric(variable).maxByOrNull { it!! }!! + assertEquals(expectedValuesRange.upperEnd, actualMaxValue, "Max value of var " + variable.name) + } + + private fun checkStatVarAndMaxValue(statDf: DataFrame, variable: DataFrame.Variable, expectedMaxValue: Double) { + checkStatVar(statDf, variable) + val actualMaxValue = statDf.getNumeric(variable).maxByOrNull { it!! }!! + assertEquals(expectedMaxValue, actualMaxValue, "Max value of var " + variable.name) + } + + private fun checkStatVarAndMaxLimit(statDf: DataFrame, variable: DataFrame.Variable, expectedMaxLimit: Double) { + checkStatVar(statDf, variable) + val actualMaxValue = statDf.getNumeric(variable).maxByOrNull { it!! }!! + assertTrue(expectedMaxLimit - actualMaxValue > 0, "Max value of var " + variable.name + " limited") + } + + @Test + fun emptyDataFrame() { + val df = dataFrame(emptyMap()) + val stat = yDensityStat() + val statDf = stat.normalize(stat.apply(df, statContext(df))) + + checkStatVarAndValuesDomain(statDf, Stats.X, emptySet()) + checkStatVarAndValuesDomain(statDf, Stats.Y, emptySet()) + checkStatVarAndValuesDomain(statDf, Stats.VIOLIN_WIDTH, emptySet()) + } + + @Test + fun oneElementDataFrame() { + val yValue = 3.14 + val df = dataFrame(mapOf( + TransformVar.Y to listOf(yValue) + )) + val stat = yDensityStat() + val statDf = stat.normalize(stat.apply(df, statContext(df))) + + checkStatVarAndValuesDomain(statDf, Stats.X, setOf(0.0)) + checkStatVarAndMaxValue(statDf, Stats.VIOLIN_WIDTH, 1.0) + } + + @Test + fun twoElementsInDataFrame() { + val y = listOf(2.71, 3.14) + val df = dataFrame(mapOf( + TransformVar.Y to y + )) + val stat = yDensityStat() + val statDf = stat.normalize(stat.apply(df, statContext(df))) + + checkStatVarAndValuesDomain(statDf, Stats.X, setOf(0.0)) + checkStatVarAndValuesRange(statDf, Stats.Y, ClosedRange(2.71, 3.14)) + checkStatVarAndMaxValue(statDf, Stats.VIOLIN_WIDTH, 1.0) + } + + @Test + fun withNanValues() { + val x = listOf(null, 4.0, 3.0, 3.0, 1.0, 1.0, 2.0, 2.0) + val y = listOf(3.0, null, 2.0, 3.0, 0.0, 1.0, 1.0, 2.0) + val df = dataFrame(mapOf( + TransformVar.X to x, + TransformVar.Y to y + )) + val stat = yDensityStat() + val statDf = stat.normalize(stat.apply(df, statContext(df))) + + checkStatVarAndValuesDomain(statDf, Stats.X, setOf(1.0, 2.0, 3.0)) + checkStatVarAndValuesRange(statDf, Stats.Y, ClosedRange(0.0, 3.0)) + checkStatVarAndMaxValue(statDf, Stats.VIOLIN_WIDTH, 1.0) + } + + @Test + fun changeScales() { + val x = listOf(0.0, 0.0, 0.0, 0.0, 1.0, 1.0) + val y = listOf(0.0, 1.0, 2.0, 3.0, 0.0, 1.0) + val df = dataFrame(mapOf( + TransformVar.X to x, + TransformVar.Y to y + )) + + for (scale in YDensityStat.Scale.values()) { + val stat = yDensityStat(scale = scale) + val statDf = stat.normalize(stat.apply(df, statContext(df))) + val statDf0 = filteredDataFrame(statDf, Stats.X) { it == 0.0 } + val statDf1 = filteredDataFrame(statDf, Stats.X) { it == 1.0 } + + checkStatVarAndValuesDomain(statDf, Stats.X, setOf(0.0, 1.0)) + checkStatVarAndValuesRange(statDf0, Stats.Y, ClosedRange(0.0, 3.0)) + checkStatVarAndValuesRange(statDf1, Stats.Y, ClosedRange(0.0, 1.0)) + when (scale) { + YDensityStat.Scale.AREA -> { + checkStatVarAndMaxLimit(statDf0, Stats.VIOLIN_WIDTH, 0.5) + checkStatVarAndMaxValue(statDf1, Stats.VIOLIN_WIDTH, 1.0) + } + YDensityStat.Scale.COUNT -> { + checkStatVarAndMaxLimit(statDf0, Stats.VIOLIN_WIDTH, 0.5) + checkStatVarAndMaxValue(statDf1, Stats.VIOLIN_WIDTH, 0.5) + } + YDensityStat.Scale.WIDTH -> { + checkStatVarAndMaxValue(statDf0, Stats.VIOLIN_WIDTH, 1.0) + checkStatVarAndMaxValue(statDf1, Stats.VIOLIN_WIDTH, 1.0) + } + } + } + } +} \ No newline at end of file diff --git a/plot-demo-common/src/commonMain/kotlin/jetbrains/datalore/plotDemo/model/plotConfig/Violin.kt b/plot-demo-common/src/commonMain/kotlin/jetbrains/datalore/plotDemo/model/plotConfig/Violin.kt index c160546d4ac..ba868a2d33b 100644 --- a/plot-demo-common/src/commonMain/kotlin/jetbrains/datalore/plotDemo/model/plotConfig/Violin.kt +++ b/plot-demo-common/src/commonMain/kotlin/jetbrains/datalore/plotDemo/model/plotConfig/Violin.kt @@ -14,14 +14,6 @@ class Violin { basic(), withNan(), withGroups(), - -// TODO: Move this to tests -// data132Violin(), -// data132ViolinDiscrete(), -// data132ViolinDefaultN(), -// data132ViolinIdentity(), -// data132Boxplot(), -// data123Violin(), ) } @@ -101,162 +93,4 @@ class Violin { return HashMap(parsePlotSpec(spec)) } - - private fun data132Violin(): MutableMap { - val spec = "{" + - " 'kind': 'plot'," + - " 'data' : {'x': [1, 3, 2]," + - " 'y': [2, 0, 1]" + - " }," + - " 'mapping': {" + - " 'x': 'x'," + - " 'y': 'y'" + - " }," + - " 'ggtitle': {" + - " 'text': 'x=[1, 3, 2]'" + - " }," + - " 'layers': [" + - " {" + - " 'geom': 'violin'," + - " 'n': 3" + - " }" + - " ]" + - "}" - - return HashMap(parsePlotSpec(spec)) - - } - - private fun data132ViolinDiscrete(): MutableMap { - val spec = "{" + - " 'kind': 'plot'," + - " 'data' : {'x': [1, 3, 2]," + - " 'y': [2, 0, 1]" + - " }," + - " 'mapping': {" + - " 'x': 'x'," + - " 'y': 'y'" + - " }," + - " 'ggtitle': {" + - " 'text': 'x=[1, 3, 2] and discrete'" + - " }," + - " 'layers': [" + - " {" + - " 'geom': 'violin'," + - " 'n': 3" + - " }" + - " ]," + - " 'scales': [" + - " {" + - " 'aesthetic': 'x'," + - " 'discrete': true" + - " }" + - " ]" + - "}" - - return HashMap(parsePlotSpec(spec)) - - } - - private fun data132ViolinDefaultN(): MutableMap { - val spec = "{" + - " 'kind': 'plot'," + - " 'data' : {'x': [1, 3, 2]," + - " 'y': [2, 0, 1]" + - " }," + - " 'mapping': {" + - " 'x': 'x'," + - " 'y': 'y'" + - " }," + - " 'ggtitle': {" + - " 'text': 'x=[1, 3, 2], default n'" + - " }," + - " 'layers': [" + - " {" + - " 'geom': 'violin'" + - " }" + - " ]" + - "}" - - return HashMap(parsePlotSpec(spec)) - - } - - private fun data132ViolinIdentity(): MutableMap { - val spec = "{" + - " 'kind': 'plot'," + - " 'data' : {'x': [1, 1, 1, 3, 3, 3, 2, 2, 2]," + - " 'y': [4, 3, 2, 5, 4, 3, 3, 2, 1]," + - " 'vw': [0, 1, 0, 0, 1, 0, 0, 1, 0]" + - " }," + - " 'mapping': {" + - " 'x': 'x'," + - " 'y': 'y'" + - " }," + - " 'ggtitle': {" + - " 'text': 'x=[1, 3, 2], stat=identity'" + - " }," + - " 'layers': [" + - " {" + - " 'geom': 'violin'," + - " 'mapping': {" + - " 'violinwidth': 'vw'" + - " }," + - " 'stat': 'identity'" + - " }" + - " ]" + - "}" - - return HashMap(parsePlotSpec(spec)) - - } - - private fun data132Boxplot(): MutableMap { - val spec = "{" + - " 'kind': 'plot'," + - " 'data' : {'x': [1, 3, 2]," + - " 'y': [2, 0, 1]" + - " }," + - " 'mapping': {" + - " 'x': 'x'," + - " 'y': 'y'" + - " }," + - " 'ggtitle': {" + - " 'text': 'x=[1, 3, 2], geom=boxplot'" + - " }," + - " 'layers': [" + - " {" + - " 'geom': 'boxplot'" + - " }" + - " ]" + - "}" - - return HashMap(parsePlotSpec(spec)) - - } - - private fun data123Violin(): MutableMap { - val spec = "{" + - " 'kind': 'plot'," + - " 'data' : {'x': [1, 2, 3]," + - " 'y': [2, 1, 0]" + - " }," + - " 'mapping': {" + - " 'x': 'x'," + - " 'y': 'y'" + - " }," + - " 'ggtitle': {" + - " 'text': 'x=[1, 2, 3]'" + - " }," + - " 'layers': [" + - " {" + - " 'geom': 'violin'," + - " 'n': 3" + - " }" + - " ]" + - "}" - - return HashMap(parsePlotSpec(spec)) - - } } \ No newline at end of file