From ffc29e6ec63e6fe97841ee602b80b74a755471a5 Mon Sep 17 00:00:00 2001 From: Nicolas Kruchten Date: Fri, 3 Jul 2020 22:03:47 -0400 Subject: [PATCH 1/8] px.NO_COLOR --- CHANGELOG.md | 7 +++++++ packages/python/plotly/plotly/express/__init__.py | 2 ++ packages/python/plotly/plotly/express/_core.py | 8 +++++++- .../plotly/tests/test_core/test_px/test_px_wide.py | 11 +++++++++++ 4 files changed, 27 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e087fcb07..f882274975 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). +## [4.9.0] - unreleased + +### Added + +- `px.NO_COLOR` constant to override wide-form color assignment + + ## [4.8.2] - 2020-06-26 ### Updated diff --git a/packages/python/plotly/plotly/express/__init__.py b/packages/python/plotly/plotly/express/__init__.py index 72d0b44554..bec7e915cc 100644 --- a/packages/python/plotly/plotly/express/__init__.py +++ b/packages/python/plotly/plotly/express/__init__.py @@ -53,6 +53,7 @@ set_mapbox_access_token, defaults, get_trendline_results, + NO_COLOR, ) from ._special_inputs import IdentityMap, Constant, Range # noqa: F401 @@ -100,4 +101,5 @@ "IdentityMap", "Constant", "Range", + "NO_COLOR", ] diff --git a/packages/python/plotly/plotly/express/_core.py b/packages/python/plotly/plotly/express/_core.py index 82da1ea7be..1285d57bfe 100644 --- a/packages/python/plotly/plotly/express/_core.py +++ b/packages/python/plotly/plotly/express/_core.py @@ -15,6 +15,7 @@ _subplot_type_for_trace_type, ) +NO_COLOR = "px_no_color_constant" # Declare all supported attributes, across all plot types direct_attrables = ( @@ -1349,6 +1350,10 @@ def build_dataframe(args, constructor): label=_escape_col_name(df_input, "index", [var_name, value_name]) ) + no_color = False + if args.get("color", None) == NO_COLOR: + no_color = True + args["color"] = None # now that things have been prepped, we do the systematic rewriting of `args` df_output, wide_id_vars = process_args_into_dataframe( @@ -1440,7 +1445,8 @@ def build_dataframe(args, constructor): args["x" if orient_v else "y"] = value_name args["y" if orient_v else "x"] = wide_cross_name args["color"] = args["color"] or var_name - + if no_color: + args["color"] = None args["data_frame"] = df_output return args diff --git a/packages/python/plotly/plotly/tests/test_core/test_px/test_px_wide.py b/packages/python/plotly/plotly/tests/test_core/test_px/test_px_wide.py index ebd650371f..e5ac5640ec 100644 --- a/packages/python/plotly/plotly/tests/test_core/test_px/test_px_wide.py +++ b/packages/python/plotly/plotly/tests/test_core/test_px/test_px_wide.py @@ -708,6 +708,17 @@ def append_special_case(df_in, args_in, args_expect, df_expect): ), ) +# NO_COLOR +df = pd.DataFrame(dict(a=[1, 2], b=[3, 4])) +append_special_case( + df_in=df, + args_in=dict(x=None, y=None, color=px.NO_COLOR), + args_expect=dict(x="index", y="value", color=None, orientation="v",), + df_expect=pd.DataFrame( + dict(variable=["a", "a", "b", "b"], index=[0, 1, 0, 1], value=[1, 2, 3, 4]) + ), +) + @pytest.mark.parametrize("df_in, args_in, args_expect, df_expect", special_cases) def test_wide_mode_internal_special_cases(df_in, args_in, args_expect, df_expect): From bb52dbc35c7d6b61e2918747b1a7c65629f51fb0 Mon Sep 17 00:00:00 2001 From: Nicolas Kruchten Date: Fri, 3 Jul 2020 22:06:42 -0400 Subject: [PATCH 2/8] document px.NO_COLOR --- doc/python/wide-form.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/doc/python/wide-form.md b/doc/python/wide-form.md index d95ea383fe..59ed3b8dda 100644 --- a/doc/python/wide-form.md +++ b/doc/python/wide-form.md @@ -158,6 +158,16 @@ fig = px.bar(wide_df, x="nation", y=["gold", "silver", "bronze"], facet_col="var fig.show() ``` +You can also prevent `color` from getting assigned if you're mapping `variable` to some other argument: + +```python +import plotly.express as px +wide_df = px.data.medals_wide(indexed=False) + +fig = px.bar(wide_df, x="nation", y=["gold", "silver", "bronze"], facet_col="variable", color=px.NO_COLOR) +fig.show() +``` + If using a data frame's named indexes, either explicitly or relying on the defaults, the row-index references (i.e. `df.index`) or column-index names (i.e. the value of `df.columns.name`) must be used: ```python @@ -302,4 +312,4 @@ fig.show() fig = px.box(wide_df, orientation="h") fig.show() -``` \ No newline at end of file +``` From 1490acecb14d4e647be35ac477e604ed6a82ed9f Mon Sep 17 00:00:00 2001 From: Nicolas Kruchten Date: Sat, 4 Jul 2020 21:20:52 -0400 Subject: [PATCH 3/8] webgl trendlines --- CHANGELOG.md | 3 ++- .../python/plotly/plotly/express/_core.py | 2 +- .../plotly/tests/test_core/test_px/test_px.py | 22 +++++++++++++++++++ 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f882274975..80284a65f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,8 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Added -- `px.NO_COLOR` constant to override wide-form color assignment +- `px.NO_COLOR` constant to override wide-form color assignment in Plotly Express +- trendline traces are now of type `scattergl` when `render_mode="webgl"` in Plotly Express ## [4.8.2] - 2020-06-26 diff --git a/packages/python/plotly/plotly/express/_core.py b/packages/python/plotly/plotly/express/_core.py index 1285d57bfe..78a6583ca4 100644 --- a/packages/python/plotly/plotly/express/_core.py +++ b/packages/python/plotly/plotly/express/_core.py @@ -843,7 +843,7 @@ def make_trace_spec(args, constructor, attrs, trace_patch): # Add trendline trace specifications if "trendline" in args and args["trendline"]: trace_spec = TraceSpec( - constructor=go.Scatter, + constructor=go.Scattergl if constructor == go.Scattergl else go.Scatter, attrs=["trendline"], trace_patch=dict(mode="lines"), marginal=None, diff --git a/packages/python/plotly/plotly/tests/test_core/test_px/test_px.py b/packages/python/plotly/plotly/tests/test_core/test_px/test_px.py index d037bc10b5..1a298eb484 100644 --- a/packages/python/plotly/plotly/tests/test_core/test_px/test_px.py +++ b/packages/python/plotly/plotly/tests/test_core/test_px/test_px.py @@ -253,3 +253,25 @@ def test_marginal_ranges(): ) assert fig.layout.xaxis2.range is None assert fig.layout.yaxis3.range is None + + +def test_render_mode(): + df = px.data.gapminder() + df2007 = df.query("year == 2007") + fig = px.scatter(df2007, x="gdpPercap", y="lifeExp", trendline="ols") + assert fig.data[0].type == "scatter" + assert fig.data[1].type == "scatter" + fig = px.scatter( + df2007, x="gdpPercap", y="lifeExp", trendline="ols", render_mode="webgl" + ) + assert fig.data[0].type == "scattergl" + assert fig.data[1].type == "scattergl" + fig = px.scatter(df, x="gdpPercap", y="lifeExp", trendline="ols") + assert fig.data[0].type == "scattergl" + assert fig.data[1].type == "scattergl" + fig = px.scatter(df, x="gdpPercap", y="lifeExp", trendline="ols", render_mode="svg") + assert fig.data[0].type == "scatter" + assert fig.data[1].type == "scatter" + fig = px.density_contour(df, x="gdpPercap", y="lifeExp", trendline="ols") + assert fig.data[0].type == "histogram2dcontour" + assert fig.data[1].type == "scatter" From a4da9998136e03c51056469799b0e8eff28e5520 Mon Sep 17 00:00:00 2001 From: Nicolas Kruchten Date: Sat, 4 Jul 2020 22:20:52 -0400 Subject: [PATCH 4/8] fix tests --- packages/python/plotly/plotly/express/_core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/python/plotly/plotly/express/_core.py b/packages/python/plotly/plotly/express/_core.py index 78a6583ca4..ddca5dfa13 100644 --- a/packages/python/plotly/plotly/express/_core.py +++ b/packages/python/plotly/plotly/express/_core.py @@ -1351,7 +1351,7 @@ def build_dataframe(args, constructor): ) no_color = False - if args.get("color", None) == NO_COLOR: + if args.get("color", None) is not None and args["color"] == NO_COLOR: no_color = True args["color"] = None # now that things have been prepped, we do the systematic rewriting of `args` From abf1950231b4257ed71fb4276521f5990b12ad6d Mon Sep 17 00:00:00 2001 From: Nicolas Kruchten Date: Sat, 4 Jul 2020 23:01:46 -0400 Subject: [PATCH 5/8] facet spacing, closes #2584 --- CHANGELOG.md | 8 +++- .../plotly/plotly/express/_chart_types.py | 22 ++++++++++ .../python/plotly/plotly/express/_core.py | 6 +-- packages/python/plotly/plotly/express/_doc.py | 5 +++ .../tests/test_core/test_px/test_colors.py | 1 - .../tests/test_core/test_px/test_facets.py | 43 +++++++++++++++++++ 6 files changed, 79 insertions(+), 6 deletions(-) create mode 100644 packages/python/plotly/plotly/tests/test_core/test_px/test_facets.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 80284a65f5..4fc3b9cdb8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,8 +6,12 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Added -- `px.NO_COLOR` constant to override wide-form color assignment in Plotly Express -- trendline traces are now of type `scattergl` when `render_mode="webgl"` in Plotly Express +- `px.NO_COLOR` constant to override wide-form color assignment in Plotly Express ([#2614](https://github.com/plotly/plotly.py/pull/2614)) +- `facet_row_spacing` and `facet_col_spacing` added to Plotly Express cartesian 2d functions ([#2614](https://github.com/plotly/plotly.py/pull/2614)) + +### Fixed + +- trendline traces are now of type `scattergl` when `render_mode="webgl"` in Plotly Express ([#2614](https://github.com/plotly/plotly.py/pull/2614)) ## [4.8.2] - 2020-06-26 diff --git a/packages/python/plotly/plotly/express/_chart_types.py b/packages/python/plotly/plotly/express/_chart_types.py index 2d41c40590..cb56f127d0 100644 --- a/packages/python/plotly/plotly/express/_chart_types.py +++ b/packages/python/plotly/plotly/express/_chart_types.py @@ -23,6 +23,8 @@ def scatter( facet_row=None, facet_col=None, facet_col_wrap=0, + facet_row_spacing=None, + facet_col_spacing=None, error_x=None, error_x_minus=None, error_y=None, @@ -74,6 +76,8 @@ def density_contour( facet_row=None, facet_col=None, facet_col_wrap=0, + facet_row_spacing=None, + facet_col_spacing=None, hover_name=None, hover_data=None, animation_frame=None, @@ -141,6 +145,8 @@ def density_heatmap( facet_row=None, facet_col=None, facet_col_wrap=0, + facet_row_spacing=None, + facet_col_spacing=None, hover_name=None, hover_data=None, animation_frame=None, @@ -213,6 +219,8 @@ def line( facet_row=None, facet_col=None, facet_col_wrap=0, + facet_row_spacing=None, + facet_col_spacing=None, error_x=None, error_x_minus=None, error_y=None, @@ -260,6 +268,8 @@ def area( facet_row=None, facet_col=None, facet_col_wrap=0, + facet_row_spacing=None, + facet_col_spacing=None, animation_frame=None, animation_group=None, category_orders={}, @@ -301,6 +311,8 @@ def bar( facet_row=None, facet_col=None, facet_col_wrap=0, + facet_row_spacing=None, + facet_col_spacing=None, hover_name=None, hover_data=None, custom_data=None, @@ -353,6 +365,8 @@ def histogram( facet_row=None, facet_col=None, facet_col_wrap=0, + facet_row_spacing=None, + facet_col_spacing=None, hover_name=None, hover_data=None, animation_frame=None, @@ -417,6 +431,8 @@ def violin( facet_row=None, facet_col=None, facet_col_wrap=0, + facet_row_spacing=None, + facet_col_spacing=None, hover_name=None, hover_data=None, custom_data=None, @@ -464,6 +480,8 @@ def box( facet_row=None, facet_col=None, facet_col_wrap=0, + facet_row_spacing=None, + facet_col_spacing=None, hover_name=None, hover_data=None, custom_data=None, @@ -514,6 +532,8 @@ def strip( facet_row=None, facet_col=None, facet_col_wrap=0, + facet_row_spacing=None, + facet_col_spacing=None, hover_name=None, hover_data=None, custom_data=None, @@ -1398,6 +1418,8 @@ def funnel( facet_row=None, facet_col=None, facet_col_wrap=0, + facet_row_spacing=None, + facet_col_spacing=None, hover_name=None, hover_data=None, custom_data=None, diff --git a/packages/python/plotly/plotly/express/_core.py b/packages/python/plotly/plotly/express/_core.py index ddca5dfa13..c524a6b83d 100644 --- a/packages/python/plotly/plotly/express/_core.py +++ b/packages/python/plotly/plotly/express/_core.py @@ -2060,9 +2060,9 @@ def init_figure(args, subplot_type, frame_list, nrows, ncols, col_labels, row_la row_heights = [main_size] * (nrows - 1) + [1 - main_size] vertical_spacing = 0.01 elif args.get("facet_col_wrap", 0): - vertical_spacing = 0.07 + vertical_spacing = args.get("facet_row_spacing", None) or 0.07 else: - vertical_spacing = 0.03 + vertical_spacing = args.get("facet_row_spacing", None) or 0.03 if bool(args.get("marginal_y", False)): if args["marginal_y"] == "histogram" or ("color" in args and args["color"]): @@ -2073,7 +2073,7 @@ def init_figure(args, subplot_type, frame_list, nrows, ncols, col_labels, row_la column_widths = [main_size] * (ncols - 1) + [1 - main_size] horizontal_spacing = 0.005 else: - horizontal_spacing = 0.02 + horizontal_spacing = args.get("facet_col_spacing", None) or 0.02 else: # Other subplot types: # 'scene', 'geo', 'polar', 'ternary', 'mapbox', 'domain', None diff --git a/packages/python/plotly/plotly/express/_doc.py b/packages/python/plotly/plotly/express/_doc.py index 4c7b591f78..a678a805ca 100644 --- a/packages/python/plotly/plotly/express/_doc.py +++ b/packages/python/plotly/plotly/express/_doc.py @@ -224,6 +224,11 @@ "Wraps the column variable at this width, so that the column facets span multiple rows.", "Ignored if 0, and forced to 0 if `facet_row` or a `marginal` is set.", ], + facet_row_spacing=[ + "float between 0 and 1", + "Spacing between facet rows, in paper units.", + ], + facet_col_spacing=["int", "Spacing between facet columns, in paper units",], animation_frame=[ colref_type, colref_desc, diff --git a/packages/python/plotly/plotly/tests/test_core/test_px/test_colors.py b/packages/python/plotly/plotly/tests/test_core/test_px/test_colors.py index 5ca41f9311..36bde27d1f 100644 --- a/packages/python/plotly/plotly/tests/test_core/test_px/test_colors.py +++ b/packages/python/plotly/plotly/tests/test_core/test_px/test_colors.py @@ -1,5 +1,4 @@ import plotly.express as px -import numpy as np def test_reversed_colorscale(): diff --git a/packages/python/plotly/plotly/tests/test_core/test_px/test_facets.py b/packages/python/plotly/plotly/tests/test_core/test_px/test_facets.py new file mode 100644 index 0000000000..eeac32853f --- /dev/null +++ b/packages/python/plotly/plotly/tests/test_core/test_px/test_facets.py @@ -0,0 +1,43 @@ +import plotly.express as px +from pytest import approx + + +def test_facets(): + df = px.data.tips() + fig = px.scatter(df, x="total_bill", y="tip") + assert "xaxis2" not in fig.layout + assert "yaxis2" not in fig.layout + assert fig.layout.xaxis.domain == (0.0, 1.0) + assert fig.layout.yaxis.domain == (0.0, 1.0) + + fig = px.scatter(df, x="total_bill", y="tip", facet_row="sex", facet_col="smoker") + assert fig.layout.xaxis4.domain[0] - fig.layout.xaxis.domain[1] == approx(0.02) + assert fig.layout.yaxis4.domain[0] - fig.layout.yaxis.domain[1] == approx(0.03) + + fig = px.scatter(df, x="total_bill", y="tip", facet_col="day", facet_col_wrap=2) + assert fig.layout.xaxis4.domain[0] - fig.layout.xaxis.domain[1] == approx(0.02) + assert fig.layout.yaxis4.domain[0] - fig.layout.yaxis.domain[1] == approx(0.07) + + fig = px.scatter( + df, + x="total_bill", + y="tip", + facet_row="sex", + facet_col="smoker", + facet_col_spacing=0.09, + facet_row_spacing=0.08, + ) + assert fig.layout.xaxis4.domain[0] - fig.layout.xaxis.domain[1] == approx(0.09) + assert fig.layout.yaxis4.domain[0] - fig.layout.yaxis.domain[1] == approx(0.08) + + fig = px.scatter( + df, + x="total_bill", + y="tip", + facet_col="day", + facet_col_wrap=2, + facet_col_spacing=0.09, + facet_row_spacing=0.08, + ) + assert fig.layout.xaxis4.domain[0] - fig.layout.xaxis.domain[1] == approx(0.09) + assert fig.layout.yaxis4.domain[0] - fig.layout.yaxis.domain[1] == approx(0.08) From c93201a57f0089b2fe84a947089b93013748d006 Mon Sep 17 00:00:00 2001 From: Nicolas Kruchten Date: Sat, 4 Jul 2020 23:04:10 -0400 Subject: [PATCH 6/8] fix tests --- packages/python/plotly/plotly/express/_core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/python/plotly/plotly/express/_core.py b/packages/python/plotly/plotly/express/_core.py index c524a6b83d..6ccc5e7782 100644 --- a/packages/python/plotly/plotly/express/_core.py +++ b/packages/python/plotly/plotly/express/_core.py @@ -1351,7 +1351,7 @@ def build_dataframe(args, constructor): ) no_color = False - if args.get("color", None) is not None and args["color"] == NO_COLOR: + if type(args["color"]) == str and args["color"] == NO_COLOR: no_color = True args["color"] = None # now that things have been prepped, we do the systematic rewriting of `args` From 868da9d39caf0f661a92431cbe4b2d262b929f2f Mon Sep 17 00:00:00 2001 From: Nicolas Kruchten Date: Wed, 8 Jul 2020 15:29:46 -0400 Subject: [PATCH 7/8] fix tests for real --- packages/python/plotly/plotly/express/_core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/python/plotly/plotly/express/_core.py b/packages/python/plotly/plotly/express/_core.py index 6ccc5e7782..fcbac2202d 100644 --- a/packages/python/plotly/plotly/express/_core.py +++ b/packages/python/plotly/plotly/express/_core.py @@ -1351,7 +1351,7 @@ def build_dataframe(args, constructor): ) no_color = False - if type(args["color"]) == str and args["color"] == NO_COLOR: + if type(args.get("color", None)) == str and args["color"] == NO_COLOR: no_color = True args["color"] = None # now that things have been prepped, we do the systematic rewriting of `args` From a226a5f6d8f18037f79af02c90bd756902e9deb7 Mon Sep 17 00:00:00 2001 From: Nicolas Kruchten Date: Wed, 8 Jul 2020 16:48:09 -0400 Subject: [PATCH 8/8] documenting facet_*_spacing --- doc/python/facet-plots.md | 25 ++++++++++++++++--- packages/python/plotly/plotly/express/_doc.py | 7 ++++-- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/doc/python/facet-plots.md b/doc/python/facet-plots.md index 2178eb7c33..2de009c0ef 100644 --- a/doc/python/facet-plots.md +++ b/doc/python/facet-plots.md @@ -6,7 +6,7 @@ jupyter: extension: .md format_name: markdown format_version: '1.2' - jupytext_version: 1.3.4 + jupytext_version: 1.4.2 kernelspec: display_name: Python 3 language: python @@ -20,7 +20,7 @@ jupyter: name: python nbconvert_exporter: python pygments_lexer: ipython3 - version: 3.7.0 + version: 3.7.7 plotly: description: How to make Facet and Trellis Plots in Python with Plotly. display_as: statistical @@ -103,7 +103,7 @@ fig.show() ### Customize Subplot Figure Titles -Since subplot figure titles are [annotations](https://plotly.com/python/text-and-annotations/#simple-annotation), you can use the `for_each_annotation` function to customize them. +Since subplot figure titles are [annotations](https://plotly.com/python/text-and-annotations/#simple-annotation), you can use the `for_each_annotation` function to customize them, for example to remove the equal-sign (`=`). In the following example, we pass a lambda function to `for_each_annotation` in order to change the figure subplot titles from `smoker=No` and `smoker=Yes` to just `No` and `Yes`. @@ -115,8 +115,25 @@ fig.for_each_annotation(lambda a: a.update(text=a.text.split("=")[-1])) fig.show() ``` +### Controlling Facet Spacing + +The `facet_row_spacing` and `facet_col_spacing` arguments can be used to control the spacing between rows and columns. These values are specified in fractions of the plotting area in paper coordinates and not in pixels, so they will grow or shrink with the `width` and `height` of the figure. + +The defaults work well with 1-4 rows or columns at the default figure size with the default font size, but need to be reduced to around 0.01 for very large figures or figures with many rows or columns. Conversely, if activating tick labels on all facets, the spacing will need to be increased. + ```python +import plotly.express as px +df = px.data.gapminder().query("continent == 'Africa'") + +fig = px.line(df, x="year", y="lifeExp", facet_col="country", facet_col_wrap=7, + facet_row_spacing=0.04, # default is 0.07 when facet_col_wrap is used + facet_col_spacing=0.04, # default is 0.03 + height=600, width=800, + title="Life Expectancy in Africa") +fig.for_each_annotation(lambda a: a.update(text=a.text.split("=")[-1])) +fig.update_yaxes(showticklabels=True) +fig.show() ``` ### Synchronizing axes in subplots with `matches` @@ -138,4 +155,4 @@ for i in range(1, 4): fig.add_trace(go.Scatter(x=x, y=np.random.random(N)), 1, i) fig.update_xaxes(matches='x') fig.show() -``` \ No newline at end of file +``` diff --git a/packages/python/plotly/plotly/express/_doc.py b/packages/python/plotly/plotly/express/_doc.py index a678a805ca..f1b892695d 100644 --- a/packages/python/plotly/plotly/express/_doc.py +++ b/packages/python/plotly/plotly/express/_doc.py @@ -226,9 +226,12 @@ ], facet_row_spacing=[ "float between 0 and 1", - "Spacing between facet rows, in paper units.", + "Spacing between facet rows, in paper units. Default is 0.03 or 0.0.7 when facet_col_wrap is used.", + ], + facet_col_spacing=[ + "float between 0 and 1", + "Spacing between facet columns, in paper units Default is 0.02.", ], - facet_col_spacing=["int", "Spacing between facet columns, in paper units",], animation_frame=[ colref_type, colref_desc,