From 7cf2ad00b4335a87b29cf2d2d65f59dfa61022c8 Mon Sep 17 00:00:00 2001 From: Emmanuelle Gouillart Date: Sat, 4 Apr 2020 11:10:29 +0200 Subject: [PATCH 01/20] added statsmodels to dependencies for CI --- .circleci/create_conda_optional_env.sh | 2 +- packages/python/plotly/tox.ini | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.circleci/create_conda_optional_env.sh b/.circleci/create_conda_optional_env.sh index c27cd43db3d..17f3c999af8 100755 --- a/.circleci/create_conda_optional_env.sh +++ b/.circleci/create_conda_optional_env.sh @@ -16,7 +16,7 @@ if [ ! -d $HOME/miniconda/envs/circle_optional ]; then # Create environment # PYTHON_VERSION=2.7 or 3.5 $HOME/miniconda/bin/conda create -n circle_optional --yes python=$PYTHON_VERSION \ -requests nbformat six retrying psutil pandas decorator pytest mock nose poppler xarray scikit-image ipython jupyter ipykernel ipywidgets +requests nbformat six retrying psutil pandas decorator pytest mock nose poppler xarray scikit-image ipython jupyter ipykernel ipywidgets statsmodels # Install orca into environment $HOME/miniconda/bin/conda install --yes -n circle_optional -c plotly plotly-orca==1.3.1 diff --git a/packages/python/plotly/tox.ini b/packages/python/plotly/tox.ini index 6297713789c..12f9089a147 100644 --- a/packages/python/plotly/tox.ini +++ b/packages/python/plotly/tox.ini @@ -59,6 +59,7 @@ deps= pytest==3.5.1 pandas==0.24.2 xarray==0.10.9 + statsmodels==0.11.1 backports.tempfile==1.0 optional: --editable=file:///{toxinidir}/../plotly-geo optional: numpy==1.16.5 From 73dba81c8837c7d6a497b0b82716842addf7a69b Mon Sep 17 00:00:00 2001 From: Emmanuelle Gouillart Date: Mon, 13 Apr 2020 15:55:30 +0200 Subject: [PATCH 02/20] hover can be a dict for skipping or formatting hover --- .../python/plotly/plotly/express/_core.py | 20 ++++++++++- packages/python/plotly/plotly/express/_doc.py | 8 +++-- .../tests/test_core/test_px/test_px_hover.py | 33 +++++++++++++++++++ 3 files changed, 58 insertions(+), 3 deletions(-) create mode 100644 packages/python/plotly/plotly/tests/test_core/test_px/test_px_hover.py diff --git a/packages/python/plotly/plotly/express/_core.py b/packages/python/plotly/plotly/express/_core.py index 613920d05fa..0080ecf1c0a 100644 --- a/packages/python/plotly/plotly/express/_core.py +++ b/packages/python/plotly/plotly/express/_core.py @@ -290,7 +290,10 @@ def make_trace_kwargs(args, trace_spec, trace_data, mapping_labels, sizeref): go.Histogram2d, go.Histogram2dContour, ]: + hover_is_dict = isinstance(attr_value, dict) for col in attr_value: + if hover_is_dict and not attr_value[col]: + continue try: position = args["custom_data"].index(col) except (ValueError, AttributeError, KeyError): @@ -387,7 +390,20 @@ def make_trace_kwargs(args, trace_spec, trace_data, mapping_labels, sizeref): go.Parcoords, go.Parcats, ]: - hover_lines = [k + "=" + v for k, v in mapping_labels.items()] + # Modify mapping_labels according to hover_data keys + # if hover_data is a dict + mapping_labels_copy = OrderedDict(mapping_labels) + if args["hover_data"] and isinstance(args["hover_data"], dict): + for k, v in mapping_labels.items(): + if k in args["hover_data"]: + if args["hover_data"][k]: + if isinstance(args["hover_data"][k], str): + mapping_labels_copy[k] = v.replace( + "}", ":%s}" % args["hover_data"][k] + ) + else: + _ = mapping_labels_copy.pop(k) + hover_lines = [k + "=" + v for k, v in mapping_labels_copy.items()] trace_patch["hovertemplate"] = hover_header + "
".join(hover_lines) trace_patch["hovertemplate"] += "" return trace_patch, fit_results @@ -1029,6 +1045,8 @@ def build_dataframe(args, attrables, array_attrables): # Finally, update argument with column name now that column exists if field_name not in array_attrables: args[field_name] = str(col_name) + elif isinstance(args[field_name], dict): + pass else: args[field_name][i] = str(col_name) diff --git a/packages/python/plotly/plotly/express/_doc.py b/packages/python/plotly/plotly/express/_doc.py index 4f17af74756..de68aec4044 100644 --- a/packages/python/plotly/plotly/express/_doc.py +++ b/packages/python/plotly/plotly/express/_doc.py @@ -180,8 +180,12 @@ "Values from this column or array_like appear in bold in the hover tooltip.", ], hover_data=[ - colref_list_type, - colref_list_desc, + "list of str or int, or Series or array-like, or dict", + "Either a list of names of columns in `data_frame`, or pandas Series,", + "or array_like objects", + "or a dict with column names as keys, with values True (for default formatting)", + "False (in order to remove this column from hover information),", + "or a formatting string, for example '.3f'." "Values from these columns appear as extra data in the hover tooltip.", ], custom_data=[ diff --git a/packages/python/plotly/plotly/tests/test_core/test_px/test_px_hover.py b/packages/python/plotly/plotly/tests/test_core/test_px/test_px_hover.py new file mode 100644 index 00000000000..d2622a87ba0 --- /dev/null +++ b/packages/python/plotly/plotly/tests/test_core/test_px/test_px_hover.py @@ -0,0 +1,33 @@ +import plotly.express as px +import numpy as np +import pandas as pd +import pytest +import plotly.graph_objects as go + + +def test_skip_hover(): + df = px.data.iris() + fig = px.scatter( + df, + x="petal_length", + y="petal_width", + size="species_id", + hover_data={"petal_length": None, "petal_width": None}, + ) + assert fig.data[0].hovertemplate == "species_id=%{marker.size}" + + +def test_composite_hover(): + df = px.data.tips() + fig = px.scatter( + df, + x="tip", + y="total_bill", + color="day", + facet_row="time", + hover_data={"day": False, "sex": True, "time": False, "total_bill": ".1f"}, + ) + assert ( + fig.data[0].hovertemplate + == "tip=%{x}
total_bill=%{customdata[1]:.1f}
sex=%{customdata[0]}" + ) From 659e2b99805f0e743de9c521377f0388d86a475c Mon Sep 17 00:00:00 2001 From: Emmanuelle Gouillart Date: Mon, 13 Apr 2020 16:02:19 +0200 Subject: [PATCH 03/20] Revert "added statsmodels to dependencies for CI" This reverts commit 7cf2ad00b4335a87b29cf2d2d65f59dfa61022c8. --- .circleci/create_conda_optional_env.sh | 2 +- packages/python/plotly/tox.ini | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.circleci/create_conda_optional_env.sh b/.circleci/create_conda_optional_env.sh index 17f3c999af8..c27cd43db3d 100755 --- a/.circleci/create_conda_optional_env.sh +++ b/.circleci/create_conda_optional_env.sh @@ -16,7 +16,7 @@ if [ ! -d $HOME/miniconda/envs/circle_optional ]; then # Create environment # PYTHON_VERSION=2.7 or 3.5 $HOME/miniconda/bin/conda create -n circle_optional --yes python=$PYTHON_VERSION \ -requests nbformat six retrying psutil pandas decorator pytest mock nose poppler xarray scikit-image ipython jupyter ipykernel ipywidgets statsmodels +requests nbformat six retrying psutil pandas decorator pytest mock nose poppler xarray scikit-image ipython jupyter ipykernel ipywidgets # Install orca into environment $HOME/miniconda/bin/conda install --yes -n circle_optional -c plotly plotly-orca==1.3.1 diff --git a/packages/python/plotly/tox.ini b/packages/python/plotly/tox.ini index 12f9089a147..6297713789c 100644 --- a/packages/python/plotly/tox.ini +++ b/packages/python/plotly/tox.ini @@ -59,7 +59,6 @@ deps= pytest==3.5.1 pandas==0.24.2 xarray==0.10.9 - statsmodels==0.11.1 backports.tempfile==1.0 optional: --editable=file:///{toxinidir}/../plotly-geo optional: numpy==1.16.5 From ed4ba16aeb0fa5187d91c0d6debf79eda4571c04 Mon Sep 17 00:00:00 2001 From: Emmanuelle Gouillart Date: Tue, 14 Apr 2020 15:07:43 +0200 Subject: [PATCH 04/20] ordereddict for py2 --- .../plotly/plotly/tests/test_core/test_px/test_px_hover.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/python/plotly/plotly/tests/test_core/test_px/test_px_hover.py b/packages/python/plotly/plotly/tests/test_core/test_px/test_px_hover.py index d2622a87ba0..6baa3d62288 100644 --- a/packages/python/plotly/plotly/tests/test_core/test_px/test_px_hover.py +++ b/packages/python/plotly/plotly/tests/test_core/test_px/test_px_hover.py @@ -3,6 +3,7 @@ import pandas as pd import pytest import plotly.graph_objects as go +from collections import OrderedDict # an OrderedDict is needed for Python 2 def test_skip_hover(): @@ -19,13 +20,16 @@ def test_skip_hover(): def test_composite_hover(): df = px.data.tips() + hover_dict = OrderedDict( + {"day": False, "sex": True, "time": False, "total_bill": ".1f"} + ) fig = px.scatter( df, x="tip", y="total_bill", color="day", facet_row="time", - hover_data={"day": False, "sex": True, "time": False, "total_bill": ".1f"}, + hover_data=hover_dict, ) assert ( fig.data[0].hovertemplate From 0bb6493b40d8d11ce492efc78a4aa4e477c03e6a Mon Sep 17 00:00:00 2001 From: Emmanuelle Gouillart Date: Tue, 14 Apr 2020 15:28:27 +0200 Subject: [PATCH 05/20] delimiter --- packages/python/plotly/plotly/express/_core.py | 2 +- packages/python/plotly/plotly/express/_doc.py | 2 +- .../plotly/plotly/tests/test_core/test_px/test_px_hover.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/python/plotly/plotly/express/_core.py b/packages/python/plotly/plotly/express/_core.py index 0080ecf1c0a..a09976d54ed 100644 --- a/packages/python/plotly/plotly/express/_core.py +++ b/packages/python/plotly/plotly/express/_core.py @@ -399,7 +399,7 @@ def make_trace_kwargs(args, trace_spec, trace_data, mapping_labels, sizeref): if args["hover_data"][k]: if isinstance(args["hover_data"][k], str): mapping_labels_copy[k] = v.replace( - "}", ":%s}" % args["hover_data"][k] + "}", "%s}" % args["hover_data"][k] ) else: _ = mapping_labels_copy.pop(k) diff --git a/packages/python/plotly/plotly/express/_doc.py b/packages/python/plotly/plotly/express/_doc.py index de68aec4044..b146b651eb7 100644 --- a/packages/python/plotly/plotly/express/_doc.py +++ b/packages/python/plotly/plotly/express/_doc.py @@ -185,7 +185,7 @@ "or array_like objects", "or a dict with column names as keys, with values True (for default formatting)", "False (in order to remove this column from hover information),", - "or a formatting string, for example '.3f'." + "or a formatting string, for example ':.3f' or '|%a'." "Values from these columns appear as extra data in the hover tooltip.", ], custom_data=[ diff --git a/packages/python/plotly/plotly/tests/test_core/test_px/test_px_hover.py b/packages/python/plotly/plotly/tests/test_core/test_px/test_px_hover.py index 6baa3d62288..ef8d8cebce7 100644 --- a/packages/python/plotly/plotly/tests/test_core/test_px/test_px_hover.py +++ b/packages/python/plotly/plotly/tests/test_core/test_px/test_px_hover.py @@ -21,7 +21,7 @@ def test_skip_hover(): def test_composite_hover(): df = px.data.tips() hover_dict = OrderedDict( - {"day": False, "sex": True, "time": False, "total_bill": ".1f"} + {"day": False, "sex": True, "time": False, "total_bill": ":.1f"} ) fig = px.scatter( df, From e3ec1e254135b52baa3ecd60ec39cd36cb841bf0 Mon Sep 17 00:00:00 2001 From: Emmanuelle Gouillart Date: Wed, 15 Apr 2020 10:26:50 +0200 Subject: [PATCH 06/20] ordereddict --- 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 a09976d54ed..557a9f8a6e4 100644 --- a/packages/python/plotly/plotly/express/_core.py +++ b/packages/python/plotly/plotly/express/_core.py @@ -906,7 +906,7 @@ def build_dataframe(args, attrables, array_attrables): for field in args: if field in array_attrables and args[field] is not None: args[field] = ( - dict(args[field]) + OrderedDict(args[field]) if isinstance(args[field], dict) else list(args[field]) ) From 2f44bf8069f94817f9d973e2f4f3722723117d52 Mon Sep 17 00:00:00 2001 From: Emmanuelle Gouillart Date: Thu, 16 Apr 2020 14:25:32 +0200 Subject: [PATCH 07/20] tuple possible in hover_data dict --- .../python/plotly/plotly/express/_core.py | 37 +++++++++++++++---- .../tests/test_core/test_px/test_px_hover.py | 23 +++++++++++- 2 files changed, 51 insertions(+), 9 deletions(-) diff --git a/packages/python/plotly/plotly/express/_core.py b/packages/python/plotly/plotly/express/_core.py index 557a9f8a6e4..b900245e25b 100644 --- a/packages/python/plotly/plotly/express/_core.py +++ b/packages/python/plotly/plotly/express/_core.py @@ -396,10 +396,10 @@ def make_trace_kwargs(args, trace_spec, trace_data, mapping_labels, sizeref): if args["hover_data"] and isinstance(args["hover_data"], dict): for k, v in mapping_labels.items(): if k in args["hover_data"]: - if args["hover_data"][k]: - if isinstance(args["hover_data"][k], str): + if args["hover_data"][k][0]: + if isinstance(args["hover_data"][k][0], str): mapping_labels_copy[k] = v.replace( - "}", "%s}" % args["hover_data"][k] + "}", "%s}" % args["hover_data"][k][0] ) else: _ = mapping_labels_copy.pop(k) @@ -935,6 +935,17 @@ def build_dataframe(args, attrables, array_attrables): else: df_output[df_input.columns] = df_input[df_input.columns] + # hover_data is a dict + hover_data_is_dict = ( + "hover_data" in args + and args["hover_data"] + and isinstance(args["hover_data"], dict) + ) + if hover_data_is_dict: + for k in args["hover_data"]: + if not isinstance(args["hover_data"][k], tuple): + args["hover_data"][k] = (args["hover_data"][k], None) + # Loop over possible arguments for field_name in attrables: # Massaging variables @@ -970,7 +981,12 @@ def build_dataframe(args, attrables, array_attrables): if isinstance(argument, str) or isinstance( argument, int ): # just a column name given as str or int - if not df_provided: + bypass_warnings = ( + hover_data_is_dict + and argument in args["hover_data"] + and args["hover_data"][argument][1] + ) + if not df_provided and not bypass_warnings: raise ValueError( "String or int arguments are only possible when a " "DataFrame or an array is provided in the `data_frame` " @@ -978,7 +994,7 @@ def build_dataframe(args, attrables, array_attrables): "'%s' is of type str or int." % field ) # Check validity of column name - if argument not in df_input.columns: + if not bypass_warnings and argument not in df_input.columns: err_msg = ( "Value of '%s' is not the name of a column in 'data_frame'. " "Expected one of %s but received: %s" @@ -989,7 +1005,7 @@ def build_dataframe(args, attrables, array_attrables): "\n To use the index, pass it in directly as `df.index`." ) raise ValueError(err_msg) - if length and len(df_input[argument]) != length: + if not bypass_warnings and length and len(df_input[argument]) != length: raise ValueError( "All arguments should have the same length. " "The length of column argument `df[%s]` is %d, whereas the " @@ -1002,7 +1018,14 @@ def build_dataframe(args, attrables, array_attrables): ) ) col_name = str(argument) - df_output[col_name] = df_input[argument].values + if ( + field_name == "hover_data" + and hover_data_is_dict + and args["hover_data"][col_name][1] + ): + df_output[col_name] = args["hover_data"][col_name][1] + else: + df_output[col_name] = df_input[argument].values # ----------------- argument is a column / array / list.... ------- else: is_index = isinstance(argument, pd.RangeIndex) diff --git a/packages/python/plotly/plotly/tests/test_core/test_px/test_px_hover.py b/packages/python/plotly/plotly/tests/test_core/test_px/test_px_hover.py index ef8d8cebce7..0f8c83c2042 100644 --- a/packages/python/plotly/plotly/tests/test_core/test_px/test_px_hover.py +++ b/packages/python/plotly/plotly/tests/test_core/test_px/test_px_hover.py @@ -21,7 +21,7 @@ def test_skip_hover(): def test_composite_hover(): df = px.data.tips() hover_dict = OrderedDict( - {"day": False, "sex": True, "time": False, "total_bill": ":.1f"} + {"day": False, "sex": True, "time": False, "total_bill": ":.1f"} ) fig = px.scatter( df, @@ -33,5 +33,24 @@ def test_composite_hover(): ) assert ( fig.data[0].hovertemplate - == "tip=%{x}
total_bill=%{customdata[1]:.1f}
sex=%{customdata[0]}" + == "tip=%{x}
total_bill=%{customdata[3]:.1f}
sex=%{customdata[1]}" + ) + + +def test_tuple_hover_data(): + fig = px.scatter( + x=[1, 2, 3], y=[3, 4, 5], hover_data={"comment": (True, ["a", "b", "c"])} + ) + assert ( + fig.data[0].hovertemplate + == "x=%{x}
y=%{y}
comment=%{customdata[0]}" + ) + fig = px.scatter( + x=[1, 2, 3], + y=[3, 4, 5], + hover_data={"comment": (":.1f", [1.234, 45.3455, 5666.234])}, + ) + assert ( + fig.data[0].hovertemplate + == "x=%{x}
y=%{y}
comment=%{customdata[0]:.1f}" ) From b799e1e7b18d652cfebfecd83dbda05e968a65b5 Mon Sep 17 00:00:00 2001 From: Emmanuelle Gouillart Date: Thu, 16 Apr 2020 14:26:56 +0200 Subject: [PATCH 08/20] comment --- packages/python/plotly/plotly/express/_core.py | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/python/plotly/plotly/express/_core.py b/packages/python/plotly/plotly/express/_core.py index b900245e25b..7ebf37547ac 100644 --- a/packages/python/plotly/plotly/express/_core.py +++ b/packages/python/plotly/plotly/express/_core.py @@ -941,6 +941,7 @@ def build_dataframe(args, attrables, array_attrables): and args["hover_data"] and isinstance(args["hover_data"], dict) ) + # If dict, convert all values of hover_data to tuples to simplify processing if hover_data_is_dict: for k in args["hover_data"]: if not isinstance(args["hover_data"][k], tuple): From 003d447a175fb42ae5e63b548900720f552afdb0 Mon Sep 17 00:00:00 2001 From: Emmanuelle Gouillart Date: Thu, 16 Apr 2020 15:55:47 +0200 Subject: [PATCH 09/20] py2 --- .../plotly/plotly/tests/test_core/test_px/test_px_hover.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/python/plotly/plotly/tests/test_core/test_px/test_px_hover.py b/packages/python/plotly/plotly/tests/test_core/test_px/test_px_hover.py index 0f8c83c2042..694fee949bf 100644 --- a/packages/python/plotly/plotly/tests/test_core/test_px/test_px_hover.py +++ b/packages/python/plotly/plotly/tests/test_core/test_px/test_px_hover.py @@ -33,7 +33,9 @@ def test_composite_hover(): ) assert ( fig.data[0].hovertemplate - == "tip=%{x}
total_bill=%{customdata[3]:.1f}
sex=%{customdata[1]}" + == "tip=%{x}
total_bill=%{customdata[3]:.1f}
sex=%{customdata[1]}" or + fig.data[0].hovertemplate + == "tip=%{x}
total_bill=%{customdata[1]:.1f}
sex=%{customdata[3]}" ) From fae0caa441acec194f44216356a12edb79389f4f Mon Sep 17 00:00:00 2001 From: Emmanuelle Gouillart Date: Thu, 16 Apr 2020 15:56:18 +0200 Subject: [PATCH 10/20] black --- .../plotly/plotly/tests/test_core/test_px/test_px_hover.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/python/plotly/plotly/tests/test_core/test_px/test_px_hover.py b/packages/python/plotly/plotly/tests/test_core/test_px/test_px_hover.py index 694fee949bf..0823bdec468 100644 --- a/packages/python/plotly/plotly/tests/test_core/test_px/test_px_hover.py +++ b/packages/python/plotly/plotly/tests/test_core/test_px/test_px_hover.py @@ -33,8 +33,8 @@ def test_composite_hover(): ) assert ( fig.data[0].hovertemplate - == "tip=%{x}
total_bill=%{customdata[3]:.1f}
sex=%{customdata[1]}" or - fig.data[0].hovertemplate + == "tip=%{x}
total_bill=%{customdata[3]:.1f}
sex=%{customdata[1]}" + or fig.data[0].hovertemplate == "tip=%{x}
total_bill=%{customdata[1]:.1f}
sex=%{customdata[3]}" ) From 9ec0f263c033781ee09f872dbc97ecd75c5ccfaf Mon Sep 17 00:00:00 2001 From: Emmanuelle Gouillart Date: Thu, 16 Apr 2020 19:29:00 +0200 Subject: [PATCH 11/20] debug --- .../plotly/tests/test_core/test_px/test_px_hover.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/python/plotly/plotly/tests/test_core/test_px/test_px_hover.py b/packages/python/plotly/plotly/tests/test_core/test_px/test_px_hover.py index 0823bdec468..053778feefc 100644 --- a/packages/python/plotly/plotly/tests/test_core/test_px/test_px_hover.py +++ b/packages/python/plotly/plotly/tests/test_core/test_px/test_px_hover.py @@ -20,9 +20,7 @@ def test_skip_hover(): def test_composite_hover(): df = px.data.tips() - hover_dict = OrderedDict( - {"day": False, "sex": True, "time": False, "total_bill": ":.1f"} - ) + hover_dict = OrderedDict({"day": False, "sex": True, "total_bill": ":.1f"}) fig = px.scatter( df, x="tip", @@ -33,9 +31,9 @@ def test_composite_hover(): ) assert ( fig.data[0].hovertemplate - == "tip=%{x}
total_bill=%{customdata[3]:.1f}
sex=%{customdata[1]}" + == "time=Dinner
tip=%{x}
total_bill=%{customdata[2]:.1f}
sex=%{customdata[1]}" or fig.data[0].hovertemplate - == "tip=%{x}
total_bill=%{customdata[1]:.1f}
sex=%{customdata[3]}" + == "time=Dinner
tip=%{x}
total_bill=%{customdata[1]:.1f}
sex=%{customdata[2]}" ) From 9a4cb55429e6d495e1822e664e0df3fa8e2b3f09 Mon Sep 17 00:00:00 2001 From: Emmanuelle Gouillart Date: Thu, 16 Apr 2020 19:58:13 +0200 Subject: [PATCH 12/20] debug --- .../plotly/plotly/tests/test_core/test_px/test_px_hover.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/python/plotly/plotly/tests/test_core/test_px/test_px_hover.py b/packages/python/plotly/plotly/tests/test_core/test_px/test_px_hover.py index 053778feefc..472644136b3 100644 --- a/packages/python/plotly/plotly/tests/test_core/test_px/test_px_hover.py +++ b/packages/python/plotly/plotly/tests/test_core/test_px/test_px_hover.py @@ -33,7 +33,7 @@ def test_composite_hover(): fig.data[0].hovertemplate == "time=Dinner
tip=%{x}
total_bill=%{customdata[2]:.1f}
sex=%{customdata[1]}" or fig.data[0].hovertemplate - == "time=Dinner
tip=%{x}
total_bill=%{customdata[1]:.1f}
sex=%{customdata[2]}" + == "time=Dinner
tip=%{x}
total_bill=%{customdata[1]:.1f}
sex=%{customdata[0]}" ) From 546dd1a91da836922e1183523ce4ec1d50e6d9aa Mon Sep 17 00:00:00 2001 From: Emmanuelle Gouillart Date: Thu, 16 Apr 2020 22:34:16 +0200 Subject: [PATCH 13/20] doc, more tests --- doc/python/hover-text-and-formatting.md | 35 ++++++++++- .../python/plotly/plotly/express/_core.py | 4 +- packages/python/plotly/plotly/express/_doc.py | 4 +- .../tests/test_core/test_px/test_px_hover.py | 59 ++++++++++++++----- 4 files changed, 80 insertions(+), 22 deletions(-) diff --git a/doc/python/hover-text-and-formatting.md b/doc/python/hover-text-and-formatting.md index 755c5bb5845..468dcced1d8 100644 --- a/doc/python/hover-text-and-formatting.md +++ b/doc/python/hover-text-and-formatting.md @@ -6,7 +6,7 @@ jupyter: extension: .md format_name: markdown format_version: '1.2' - jupytext_version: 1.3.1 + 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.6.8 + version: 3.7.3 plotly: description: How to use hover text and formatting in Python with Plotly. display_as: file_settings @@ -102,7 +102,7 @@ fig.show() ### Customizing Hover text with Plotly Express -Plotly Express functions automatically add all the data being plotted (x, y, color etc) to the hover label. Many Plotly Express functions also support configurable hover text. The `hover_data` argument accepts a list of column names to be added to the hover tooltip. The `hover_name` property controls which column is displayed in bold as the tooltip title. +Plotly Express functions automatically add all the data being plotted (x, y, color etc) to the hover label. Many Plotly Express functions also support configurable hover text. The `hover_data` argument accepts a list of column names to be added to the hover tooltip, or a dictionary for advanced formatting (see the next section). The `hover_name` property controls which column is displayed in bold as the tooltip title. Here is an example that creates a scatter plot using Plotly Express with custom hover data and a custom hover name. @@ -117,6 +117,35 @@ fig = px.scatter(df_2007, x="gdpPercap", y="lifeExp", log_x=True, fig.show() ``` +### Disabling or customizing hover of columns in plotly express + +`hover_data` can also be a dictionary. Its keys are existing columns of the `dataframe` argument, or new labels. For an existing column, the values can be +* `False` to remove the column from the hover data (for example, if one wishes to remove the column of the `x` argument) +* `True` to add a different column, with default formatting +* a formatting string starting with `:` for numbers [d3-format's syntax](https://github.com/d3/d3-3.x-api-reference/blob/master/Formatting.md#d3_forma), and `|` for dates in [d3-time-format's syntax](https://github.com/d3/d3-3.x-api-reference/blob/master/Time-Formatting.md#format), for example `:.3f`, `|%a`. + +For passing new data, the value is a tuple, which first element is one of the possible values described above for existing columns, and the second element correspond to the hover values, for example `(True, [1, 2, 3])` or `(':.1f', [1.54, 2.345])`. + +These different cases are illustrated in the following example. + +```python +import plotly.express as px +import numpy as np +df = px.data.iris() +fig = px.scatter(df, x='petal_length', y='sepal_length', facet_col='species', color='species', + hover_data={'species':False, # remove species from hover data + 'sepal_length':':.2f', # customize hover for column of y attribute + 'petal_width':True, # add other column, default formatting + 'sepal_width':':.2f', # add other column, customized formatting + # data not in dataframe, default formatting + 'suppl_1': (True, np.random.random(len(df))), + # data not in dataframe, customized formatting + 'suppl_2': (':.3f', np.random.random(len(df))) + }) +fig.update_layout(height=300) +fig.show() +``` + ### Customizing hover text with a hovertemplate To customize the tooltip on your graph you can use [hovertemplate](https://plotly.com/python/reference/#pie-hovertemplate), which is a template string used for rendering the information that appear on hoverbox. diff --git a/packages/python/plotly/plotly/express/_core.py b/packages/python/plotly/plotly/express/_core.py index 7ebf37547ac..e5b028c9e05 100644 --- a/packages/python/plotly/plotly/express/_core.py +++ b/packages/python/plotly/plotly/express/_core.py @@ -985,7 +985,7 @@ def build_dataframe(args, attrables, array_attrables): bypass_warnings = ( hover_data_is_dict and argument in args["hover_data"] - and args["hover_data"][argument][1] + and args["hover_data"][argument][1] is not None ) if not df_provided and not bypass_warnings: raise ValueError( @@ -1022,7 +1022,7 @@ def build_dataframe(args, attrables, array_attrables): if ( field_name == "hover_data" and hover_data_is_dict - and args["hover_data"][col_name][1] + and args["hover_data"][col_name][1] is not None ): df_output[col_name] = args["hover_data"][col_name][1] else: diff --git a/packages/python/plotly/plotly/express/_doc.py b/packages/python/plotly/plotly/express/_doc.py index b146b651eb7..d6f4007018e 100644 --- a/packages/python/plotly/plotly/express/_doc.py +++ b/packages/python/plotly/plotly/express/_doc.py @@ -185,7 +185,9 @@ "or array_like objects", "or a dict with column names as keys, with values True (for default formatting)", "False (in order to remove this column from hover information),", - "or a formatting string, for example ':.3f' or '|%a'." + "or a formatting string, for example ':.3f' or '|%a'", + "or tuples with a bool or formatting string as first element,", + "and list-like data to appear in hover as second element", "Values from these columns appear as extra data in the hover tooltip.", ], custom_data=[ diff --git a/packages/python/plotly/plotly/tests/test_core/test_px/test_px_hover.py b/packages/python/plotly/plotly/tests/test_core/test_px/test_px_hover.py index 472644136b3..cd1fb52b626 100644 --- a/packages/python/plotly/plotly/tests/test_core/test_px/test_px_hover.py +++ b/packages/python/plotly/plotly/tests/test_core/test_px/test_px_hover.py @@ -20,7 +20,9 @@ def test_skip_hover(): def test_composite_hover(): df = px.data.tips() - hover_dict = OrderedDict({"day": False, "sex": True, "total_bill": ":.1f"}) + hover_dict = OrderedDict( + {"day": False, "time": False, "sex": True, "total_bill": ":.1f"} + ) fig = px.scatter( df, x="tip", @@ -29,12 +31,11 @@ def test_composite_hover(): facet_row="time", hover_data=hover_dict, ) - assert ( - fig.data[0].hovertemplate - == "time=Dinner
tip=%{x}
total_bill=%{customdata[2]:.1f}
sex=%{customdata[1]}" - or fig.data[0].hovertemplate - == "time=Dinner
tip=%{x}
total_bill=%{customdata[1]:.1f}
sex=%{customdata[0]}" - ) + for el in ["tip", "total_bill", "sex"]: + assert el in fig.data[0].hovertemplate + for el in ["day", "time"]: + assert el not in fig.data[0].hovertemplate + assert ":.1f" in fig.data[0].hovertemplate def test_tuple_hover_data(): @@ -45,12 +46,38 @@ def test_tuple_hover_data(): fig.data[0].hovertemplate == "x=%{x}
y=%{y}
comment=%{customdata[0]}" ) - fig = px.scatter( - x=[1, 2, 3], - y=[3, 4, 5], - hover_data={"comment": (":.1f", [1.234, 45.3455, 5666.234])}, - ) - assert ( - fig.data[0].hovertemplate - == "x=%{x}
y=%{y}
comment=%{customdata[0]:.1f}" - ) + hover_dicts = [ + {"comment": (":.1f", [1.234, 45.3455, 5666.234])}, + {"comment": (":.1f", np.array([1.234, 45.3455, 5666.234]))}, + {"comment": (":.1f", pd.Series([1.234, 45.3455, 5666.234]))}, + ] + for hover_dict in hover_dicts: + fig = px.scatter(x=[1, 2, 3], y=[3, 4, 5], hover_data=hover_dict,) + assert ( + fig.data[0].hovertemplate + == "x=%{x}
y=%{y}
comment=%{customdata[0]:.1f}" + ) + + +def test_fail_wrong_column(): + with pytest.raises(ValueError): + fig = px.scatter( + {"a": [1, 2], "b": [3, 4], "c": [2, 1]}, + x="a", + y="b", + hover_data={"d": True}, + ) + with pytest.raises(ValueError): + fig = px.scatter( + {"a": [1, 2], "b": [3, 4], "c": [2, 1]}, + x="a", + y="b", + hover_data={"d": ":.1f"}, + ) + with pytest.raises(ValueError): + fig = px.scatter( + {"a": [1, 2], "b": [3, 4], "c": [2, 1]}, + x="a", + y="b", + hover_data={"d": (True, [3, 4, 5])}, + ) From 7170a527430a041459b897e5916eb53c5db42c18 Mon Sep 17 00:00:00 2001 From: Emmanuelle Gouillart Date: Sun, 19 Apr 2020 23:20:34 +0200 Subject: [PATCH 14/20] address review comments --- doc/python/hover-text-and-formatting.md | 4 ++-- packages/python/plotly/plotly/express/_core.py | 6 ++++++ packages/python/plotly/plotly/express/_doc.py | 1 + .../plotly/plotly/tests/test_core/test_px/test_px_hover.py | 7 ++++++- 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/doc/python/hover-text-and-formatting.md b/doc/python/hover-text-and-formatting.md index 468dcced1d8..e47f1762822 100644 --- a/doc/python/hover-text-and-formatting.md +++ b/doc/python/hover-text-and-formatting.md @@ -124,7 +124,7 @@ fig.show() * `True` to add a different column, with default formatting * a formatting string starting with `:` for numbers [d3-format's syntax](https://github.com/d3/d3-3.x-api-reference/blob/master/Formatting.md#d3_forma), and `|` for dates in [d3-time-format's syntax](https://github.com/d3/d3-3.x-api-reference/blob/master/Time-Formatting.md#format), for example `:.3f`, `|%a`. -For passing new data, the value is a tuple, which first element is one of the possible values described above for existing columns, and the second element correspond to the hover values, for example `(True, [1, 2, 3])` or `(':.1f', [1.54, 2.345])`. +It is also possible to pass new data as values of the `hover_data` dict, either as list-like data, or inside a tuple, which first element is one of the possible values described above for existing columns, and the second element correspond to the list-like data, for example `(True, [1, 2, 3])` or `(':.1f', [1.54, 2.345])`. These different cases are illustrated in the following example. @@ -138,7 +138,7 @@ fig = px.scatter(df, x='petal_length', y='sepal_length', facet_col='species', co 'petal_width':True, # add other column, default formatting 'sepal_width':':.2f', # add other column, customized formatting # data not in dataframe, default formatting - 'suppl_1': (True, np.random.random(len(df))), + 'suppl_1': np.random.random(len(df)), # data not in dataframe, customized formatting 'suppl_2': (':.3f', np.random.random(len(df))) }) diff --git a/packages/python/plotly/plotly/express/_core.py b/packages/python/plotly/plotly/express/_core.py index e5b028c9e05..63981636add 100644 --- a/packages/python/plotly/plotly/express/_core.py +++ b/packages/python/plotly/plotly/express/_core.py @@ -885,6 +885,10 @@ def _get_reserved_col_names(args, attrables, array_attrables): return reserved_names +def _isinstance_listlike(x): + return isinstance(x, list) or isinstance(x, np.ndarray) or isinstance(x, pd.Series) + + def build_dataframe(args, attrables, array_attrables): """ Constructs a dataframe and modifies `args` in-place. @@ -944,6 +948,8 @@ def build_dataframe(args, attrables, array_attrables): # If dict, convert all values of hover_data to tuples to simplify processing if hover_data_is_dict: for k in args["hover_data"]: + if _isinstance_listlike(args["hover_data"][k]): + args["hover_data"][k] = (True, args["hover_data"][k]) if not isinstance(args["hover_data"][k], tuple): args["hover_data"][k] = (args["hover_data"][k], None) diff --git a/packages/python/plotly/plotly/express/_doc.py b/packages/python/plotly/plotly/express/_doc.py index d6f4007018e..05a5b214cad 100644 --- a/packages/python/plotly/plotly/express/_doc.py +++ b/packages/python/plotly/plotly/express/_doc.py @@ -186,6 +186,7 @@ "or a dict with column names as keys, with values True (for default formatting)", "False (in order to remove this column from hover information),", "or a formatting string, for example ':.3f' or '|%a'", + "or list-like data to appear in the hover tooltip", "or tuples with a bool or formatting string as first element,", "and list-like data to appear in hover as second element", "Values from these columns appear as extra data in the hover tooltip.", diff --git a/packages/python/plotly/plotly/tests/test_core/test_px/test_px_hover.py b/packages/python/plotly/plotly/tests/test_core/test_px/test_px_hover.py index cd1fb52b626..c72b89cfa5e 100644 --- a/packages/python/plotly/plotly/tests/test_core/test_px/test_px_hover.py +++ b/packages/python/plotly/plotly/tests/test_core/test_px/test_px_hover.py @@ -38,7 +38,12 @@ def test_composite_hover(): assert ":.1f" in fig.data[0].hovertemplate -def test_tuple_hover_data(): +def test_newdatain_hover_data(): + fig = px.scatter(x=[1, 2, 3], y=[3, 4, 5], hover_data={"comment": ["a", "b", "c"]}) + assert ( + fig.data[0].hovertemplate + == "x=%{x}
y=%{y}
comment=%{customdata[0]}" + ) fig = px.scatter( x=[1, 2, 3], y=[3, 4, 5], hover_data={"comment": (True, ["a", "b", "c"])} ) From f1ec098778f7f215a7a1f0e23de48740d81d4c82 Mon Sep 17 00:00:00 2001 From: Nicolas Kruchten Date: Mon, 20 Apr 2020 09:30:57 -0400 Subject: [PATCH 15/20] short-circuit instead of bypass flag --- .../python/plotly/plotly/express/_core.py | 30 +++++++++---------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/packages/python/plotly/plotly/express/_core.py b/packages/python/plotly/plotly/express/_core.py index 63981636add..72feb60f425 100644 --- a/packages/python/plotly/plotly/express/_core.py +++ b/packages/python/plotly/plotly/express/_core.py @@ -988,12 +988,17 @@ def build_dataframe(args, attrables, array_attrables): if isinstance(argument, str) or isinstance( argument, int ): # just a column name given as str or int - bypass_warnings = ( - hover_data_is_dict - and argument in args["hover_data"] - and args["hover_data"][argument][1] is not None - ) - if not df_provided and not bypass_warnings: + + if ( + field_name == "hover_data" + and hover_data_is_dict + and args["hover_data"][str(argument)][1] is not None + ): + col_name = str(argument) + df_output[col_name] = args["hover_data"][col_name][1] + continue + + if not df_provided: raise ValueError( "String or int arguments are only possible when a " "DataFrame or an array is provided in the `data_frame` " @@ -1001,7 +1006,7 @@ def build_dataframe(args, attrables, array_attrables): "'%s' is of type str or int." % field ) # Check validity of column name - if not bypass_warnings and argument not in df_input.columns: + if argument not in df_input.columns: err_msg = ( "Value of '%s' is not the name of a column in 'data_frame'. " "Expected one of %s but received: %s" @@ -1012,7 +1017,7 @@ def build_dataframe(args, attrables, array_attrables): "\n To use the index, pass it in directly as `df.index`." ) raise ValueError(err_msg) - if not bypass_warnings and length and len(df_input[argument]) != length: + if length and len(df_input[argument]) != length: raise ValueError( "All arguments should have the same length. " "The length of column argument `df[%s]` is %d, whereas the " @@ -1025,14 +1030,7 @@ def build_dataframe(args, attrables, array_attrables): ) ) col_name = str(argument) - if ( - field_name == "hover_data" - and hover_data_is_dict - and args["hover_data"][col_name][1] is not None - ): - df_output[col_name] = args["hover_data"][col_name][1] - else: - df_output[col_name] = df_input[argument].values + df_output[col_name] = df_input[argument].values # ----------------- argument is a column / array / list.... ------- else: is_index = isinstance(argument, pd.RangeIndex) From a05679ef3178d0ae40f81316370bf3b1c3138383 Mon Sep 17 00:00:00 2001 From: Emmanuelle Gouillart Date: Tue, 21 Apr 2020 17:46:08 +0200 Subject: [PATCH 16/20] be more permissive when accepting data in hover dict --- .../python/plotly/plotly/express/_core.py | 12 ++++++++++-- .../tests/test_core/test_px/test_px_hover.py | 19 ++++++++++++++----- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/packages/python/plotly/plotly/express/_core.py b/packages/python/plotly/plotly/express/_core.py index 72feb60f425..97563273ea2 100644 --- a/packages/python/plotly/plotly/express/_core.py +++ b/packages/python/plotly/plotly/express/_core.py @@ -886,7 +886,15 @@ def _get_reserved_col_names(args, attrables, array_attrables): def _isinstance_listlike(x): - return isinstance(x, list) or isinstance(x, np.ndarray) or isinstance(x, pd.Series) + if ( + isinstance(x, str) + or (isinstance(x, tuple) and len(x) == 2) + or isinstance(x, bool) + or x is None + ): + return False + else: + return True def build_dataframe(args, attrables, array_attrables): @@ -952,7 +960,7 @@ def build_dataframe(args, attrables, array_attrables): args["hover_data"][k] = (True, args["hover_data"][k]) if not isinstance(args["hover_data"][k], tuple): args["hover_data"][k] = (args["hover_data"][k], None) - + print(args["hover_data"]) # Loop over possible arguments for field_name in attrables: # Massaging variables diff --git a/packages/python/plotly/plotly/tests/test_core/test_px/test_px_hover.py b/packages/python/plotly/plotly/tests/test_core/test_px/test_px_hover.py index c72b89cfa5e..509f48d1991 100644 --- a/packages/python/plotly/plotly/tests/test_core/test_px/test_px_hover.py +++ b/packages/python/plotly/plotly/tests/test_core/test_px/test_px_hover.py @@ -39,11 +39,19 @@ def test_composite_hover(): def test_newdatain_hover_data(): - fig = px.scatter(x=[1, 2, 3], y=[3, 4, 5], hover_data={"comment": ["a", "b", "c"]}) - assert ( - fig.data[0].hovertemplate - == "x=%{x}
y=%{y}
comment=%{customdata[0]}" - ) + hover_dicts = [ + {"comment": ["a", "b", "c"]}, + {"comment": (1.234, 45.3455, 5666.234)}, + {"comment": [1.234, 45.3455, 5666.234]}, + {"comment": np.array([1.234, 45.3455, 5666.234])}, + {"comment": pd.Series([1.234, 45.3455, 5666.234])}, + ] + for hover_dict in hover_dicts: + fig = px.scatter(x=[1, 2, 3], y=[3, 4, 5], hover_data=hover_dict) + assert ( + fig.data[0].hovertemplate + == "x=%{x}
y=%{y}
comment=%{customdata[0]}" + ) fig = px.scatter( x=[1, 2, 3], y=[3, 4, 5], hover_data={"comment": (True, ["a", "b", "c"])} ) @@ -52,6 +60,7 @@ def test_newdatain_hover_data(): == "x=%{x}
y=%{y}
comment=%{customdata[0]}" ) hover_dicts = [ + {"comment": (":.1f", (1.234, 45.3455, 5666.234))}, {"comment": (":.1f", [1.234, 45.3455, 5666.234])}, {"comment": (":.1f", np.array([1.234, 45.3455, 5666.234]))}, {"comment": (":.1f", pd.Series([1.234, 45.3455, 5666.234]))}, From b1c2cc3a90779f7b935d48d599042725d4320bc1 Mon Sep 17 00:00:00 2001 From: Emmanuelle Gouillart Date: Tue, 21 Apr 2020 17:52:16 +0200 Subject: [PATCH 17/20] remove print --- packages/python/plotly/plotly/express/_core.py | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/python/plotly/plotly/express/_core.py b/packages/python/plotly/plotly/express/_core.py index 97563273ea2..0ccbe8d09f3 100644 --- a/packages/python/plotly/plotly/express/_core.py +++ b/packages/python/plotly/plotly/express/_core.py @@ -960,7 +960,6 @@ def build_dataframe(args, attrables, array_attrables): args["hover_data"][k] = (True, args["hover_data"][k]) if not isinstance(args["hover_data"][k], tuple): args["hover_data"][k] = (args["hover_data"][k], None) - print(args["hover_data"]) # Loop over possible arguments for field_name in attrables: # Massaging variables From 6f1a148e82be141f6bc2f8a0ae67ea0749d98d19 Mon Sep 17 00:00:00 2001 From: Emmanuelle Gouillart Date: Tue, 21 Apr 2020 18:39:01 +0200 Subject: [PATCH 18/20] update tutorial --- doc/python/hover-text-and-formatting.md | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/doc/python/hover-text-and-formatting.md b/doc/python/hover-text-and-formatting.md index 391cc0e415a..eb5957cb71a 100644 --- a/doc/python/hover-text-and-formatting.md +++ b/doc/python/hover-text-and-formatting.md @@ -169,13 +169,11 @@ fig.show() ### Customizing hover text with a hovertemplate -To customize the tooltip on your graph you can use [hovertemplate](https://plotly.com/python/reference/#pie-hovertemplate), which is a template string used for rendering the information that appear on hoverbox. +To customize the tooltip on your graph you can use the [hovertemplate](https://plotly.com/python/reference/#pie-hovertemplate) attribute of `graph_objects` tracces, which is a template string used for rendering the information that appear on hoverbox. This template string can include `variables` in %{variable} format, `numbers` in [d3-format's syntax](https://github.com/d3/d3-3.x-api-reference/blob/master/Formatting.md#d3_forma), and `date` in [d3-time-format's syntax](https://github.com/d3/d3-3.x-api-reference/blob/master/Time-Formatting.md#format). Hovertemplate customize the tooltip text vs. [texttemplate](https://plotly.com/python/reference/#pie-texttemplate) which customizes the text that appears on your chart.
Set the horizontal alignment of the text within tooltip with [hoverlabel.align](https://plotly.com/python/reference/#layout-hoverlabel-align). -Plotly Express automatically sets the `hovertemplate`, but you can set it manually when using `graph_objects`. - ```python import plotly.graph_objects as go @@ -216,6 +214,24 @@ fig = go.Figure(go.Pie( fig.show() ``` +### Modifying the hovertemplate of a plotly express figure + +`plotly.express` automatically sets the hovertemplate but you can modify it using the `update_traces` method of the generated figure. It helps to print the hovertemplate generated by `plotly.express` in order to be able to modify it. One can also revert to the default hover information of traces by setting the hovertemplate to `None`. + +```python +import plotly.express as px + +df_2007 = px.data.gapminder().query("year==2007") + +fig = px.scatter(df_2007, x="gdpPercap", y="lifeExp", log_x=True, color='continent' + ) +print("plotly express hovertemplate:", fig.data[0].hovertemplate) +fig.update_traces(hovertemplate='GDP: %{x}
Life Expectany: %{y}') # +fig.update_traces(hovertemplate=None, selector={'name':'Europe'}) # revert to default hover +print("user_defined hovertemplate:", fig.data[0].hovertemplate) +fig.show() +``` + ### Advanced Hover Template The following example shows how to format hover template. [Here](https://plotly.com/python/v3/hover-text-and-formatting/#dash-example) is an example to see how to format hovertemplate in Dash. From 6fc67104d1a5e999ffb2e7a798762e20ffcccb53 Mon Sep 17 00:00:00 2001 From: Emmanuelle Gouillart Date: Tue, 21 Apr 2020 21:33:15 +0200 Subject: [PATCH 19/20] add docstring to helper function --- packages/python/plotly/plotly/express/_core.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/python/plotly/plotly/express/_core.py b/packages/python/plotly/plotly/express/_core.py index 0ccbe8d09f3..89b4ffce5e3 100644 --- a/packages/python/plotly/plotly/express/_core.py +++ b/packages/python/plotly/plotly/express/_core.py @@ -886,6 +886,10 @@ def _get_reserved_col_names(args, attrables, array_attrables): def _isinstance_listlike(x): + """Returns True if x is an iterable which can be transformed into a pandas Series, + False for the other types of possible values of a `hover_data` dict. + A tuple of length 2 is a special case corresponding to a (format, data) tuple. + """ if ( isinstance(x, str) or (isinstance(x, tuple) and len(x) == 2) From 3c2a60775de7d1590392075ddb35555b292dca9d Mon Sep 17 00:00:00 2001 From: Emmanuelle Gouillart Date: Tue, 21 Apr 2020 23:35:42 +0200 Subject: [PATCH 20/20] changelog --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b2b05a9094f..ab4811baae3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). +## Dev version + +### Added + +- The `hover_data` parameter of `px` functions can now be a dictionary. This + makes it possible to skip hover information for some arguments or to change + the formatting of hover informatiom [#2377](https://github.com/plotly/plotly.py/pull/2377). + ## [4.6] - 2020-03-31 ### Updated