From 10a7b8e6e99f41fe7b4b78dfdc0dd544174916e6 Mon Sep 17 00:00:00 2001 From: Emmanuelle Gouillart Date: Mon, 3 Feb 2020 08:44:34 -0500 Subject: [PATCH 01/20] xarray and imshow --- .../python/plotly/plotly/express/_imshow.py | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/packages/python/plotly/plotly/express/_imshow.py b/packages/python/plotly/plotly/express/_imshow.py index 6f1bf77e877..a24f87bfcdb 100644 --- a/packages/python/plotly/plotly/express/_imshow.py +++ b/packages/python/plotly/plotly/express/_imshow.py @@ -2,6 +2,11 @@ from _plotly_utils.basevalidators import ColorscaleValidator from ._core import apply_default_cascade import numpy as np +try: + import xarray + xarray_imported = True +except ImportError: + xarray_imported = False _float_types = [] @@ -140,7 +145,15 @@ def imshow( """ args = locals() apply_default_cascade(args) - + img_is_xarray = False + if xarray_imported: + if isinstance(img, xarray.DataArray): + y_label, x_label = img.dims + x = img.coords[x_label] + y = img.coords[y_label] + x_range = (img.coords[x_label][0], img.coords[x_label][-1]) + y_range = (img.coords[y_label][0], img.coords[y_label][-1]) + img_is_xarray = True img = np.asanyarray(img) # Cast bools to uint8 (also one byte) if img.dtype == np.bool: @@ -185,12 +198,16 @@ def imshow( ) layout_patch = dict() - for v in ["title", "height", "width"]: - if args[v]: - layout_patch[v] = args[v] + for attr_name in ["title", "height", "width"]: + if args[attr_name]: + layout_patch[attr_name] = args[attr_name] if "title" not in layout_patch and args["template"].layout.margin.t is None: layout_patch["margin"] = {"t": 60} fig = go.Figure(data=trace, layout=layout) fig.update_layout(layout_patch) + if img_is_xarray: + fig.update_traces(x=x, y=y) + fig.update_xaxes(title_text=x_label) + fig.update_yaxes(title_text=y_label) fig.update_layout(template=args["template"], overwrite=True) return fig From f449c0430acc2f09e8237c5f47e4f92326731d6c Mon Sep 17 00:00:00 2001 From: Emmanuelle Gouillart Date: Wed, 5 Feb 2020 20:49:52 -0500 Subject: [PATCH 02/20] add xarray support in imshow --- packages/python/plotly/plotly/express/_imshow.py | 11 ++++++++--- .../plotly/tests/test_core/test_px/test_imshow.py | 13 ++++++++++++- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/packages/python/plotly/plotly/express/_imshow.py b/packages/python/plotly/plotly/express/_imshow.py index a24f87bfcdb..121c7a18280 100644 --- a/packages/python/plotly/plotly/express/_imshow.py +++ b/packages/python/plotly/plotly/express/_imshow.py @@ -2,8 +2,10 @@ from _plotly_utils.basevalidators import ColorscaleValidator from ._core import apply_default_cascade import numpy as np + try: import xarray + xarray_imported = True except ImportError: xarray_imported = False @@ -148,13 +150,15 @@ def imshow( img_is_xarray = False if xarray_imported: if isinstance(img, xarray.DataArray): - y_label, x_label = img.dims + y_label, x_label = img.dims[0], img.dims[1] x = img.coords[x_label] y = img.coords[y_label] - x_range = (img.coords[x_label][0], img.coords[x_label][-1]) + x_range = (img.coords[x_label][0], img.coords[x_label][-1]) y_range = (img.coords[y_label][0], img.coords[y_label][-1]) img_is_xarray = True + img = np.asanyarray(img) + # Cast bools to uint8 (also one byte) if img.dtype == np.bool: img = 255 * img.astype(np.uint8) @@ -206,7 +210,8 @@ def imshow( fig = go.Figure(data=trace, layout=layout) fig.update_layout(layout_patch) if img_is_xarray: - fig.update_traces(x=x, y=y) + if img.ndim <= 2: + fig.update_traces(x=x, y=y) fig.update_xaxes(title_text=x_label) fig.update_yaxes(title_text=y_label) fig.update_layout(template=args["template"], overwrite=True) diff --git a/packages/python/plotly/plotly/tests/test_core/test_px/test_imshow.py b/packages/python/plotly/plotly/tests/test_core/test_px/test_imshow.py index e296a958f3a..43e4748f486 100644 --- a/packages/python/plotly/plotly/tests/test_core/test_px/test_imshow.py +++ b/packages/python/plotly/plotly/tests/test_core/test_px/test_imshow.py @@ -1,6 +1,7 @@ import plotly.express as px import numpy as np import pytest +import xarray as xr img_rgb = np.array([[[255, 0, 0], [0, 255, 0], [0, 0, 255]]], dtype=np.uint8) img_gray = np.arange(100).reshape((10, 10)) @@ -58,8 +59,9 @@ def test_colorscale(): def test_wrong_dimensions(): imgs = [1, np.ones((5,) * 3), np.ones((5,) * 4)] + msg = "px.imshow only accepts 2D single-channel, RGB or RGBA images." for img in imgs: - with pytest.raises(ValueError) as err_msg: + with pytest.raises(ValueError, match=msg): fig = px.imshow(img) @@ -114,3 +116,12 @@ def test_zmin_zmax_range_color(): fig = px.imshow(img, zmax=0.8) assert fig.layout.coloraxis.cmin == 0.0 assert fig.layout.coloraxis.cmax == 0.8 + + +def test_imshow_xarray(): + img = np.random.random((20, 30)) + da = xr.DataArray(img, dims=["dim_rows", "dim_cols"]) + fig = px.imshow(da) + assert fig.layout.xaxis.title.text == "dim_cols" + assert fig.layout.yaxis.title.text == "dim_rows" + assert np.all(np.array(fig.data[0].x) == np.array(da.coords["dim_cols"])) From 495466373d2eea55045f619276e578fea9027457 Mon Sep 17 00:00:00 2001 From: Emmanuelle Gouillart Date: Mon, 10 Feb 2020 09:54:59 -0500 Subject: [PATCH 03/20] aspect ratio: pixels are not square by default for xarrays, plus timedate handling --- .../python/plotly/plotly/express/_imshow.py | 29 +++++++++++++++---- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/packages/python/plotly/plotly/express/_imshow.py b/packages/python/plotly/plotly/express/_imshow.py index 121c7a18280..264fe61dcaf 100644 --- a/packages/python/plotly/plotly/express/_imshow.py +++ b/packages/python/plotly/plotly/express/_imshow.py @@ -75,6 +75,7 @@ def imshow( template=None, width=None, height=None, + aspect=None ): """ Display an image, i.e. data on a 2D regular raster. @@ -129,6 +130,14 @@ def imshow( height: number The figure height in pixels, defaults to 600. + aspect: 'equal', 'auto', or None + - 'equal': Ensures an aspect ratio of 1 or pixels (square pixels) + - 'auto': The axes is kept fixed and the aspect ratio of pixels is + adjusted so that the data fit in the axes. In general, this will + result in non-square pixels. + - if None, 'equal' is used for numpy arrays and 'auto' for xarrays + (which have typically heterogeneous coordinates) + Returns ------- fig : graph_objects.Figure containing the displayed image @@ -151,11 +160,19 @@ def imshow( if xarray_imported: if isinstance(img, xarray.DataArray): y_label, x_label = img.dims[0], img.dims[1] + # np.datetime64 is not handled correctly by go.Heatmap + for ax in [x_label, y_label]: + if np.issubdtype(img.coords[ax].dtype, np.datetime64): + img.coords[ax] = img.coords[ax].astype(str) x = img.coords[x_label] y = img.coords[y_label] - x_range = (img.coords[x_label][0], img.coords[x_label][-1]) - y_range = (img.coords[y_label][0], img.coords[y_label][-1]) img_is_xarray = True + if aspect is None: + aspect = 'auto' + + if not img_is_xarray: + if aspect is None: + aspect = 'equal' img = np.asanyarray(img) @@ -167,10 +184,10 @@ def imshow( if img.ndim == 2: trace = go.Heatmap(z=img, coloraxis="coloraxis1") autorange = True if origin == "lower" else "reversed" - layout = dict( - xaxis=dict(scaleanchor="y", constrain="domain"), - yaxis=dict(autorange=autorange, constrain="domain"), - ) + layout = dict(yaxis=dict(autorange=autorange)) + if aspect == 'equal': + layout["xaxis"] = dict(scaleanchor="y", constrain="domain") + layout["yaxis"]["constrain"] = "domain" colorscale_validator = ColorscaleValidator("colorscale", "imshow") if zmin is not None and zmax is None: zmax = img.max() From b2ee80999bcd3a7d8caa5d6f5804c2651a687c96 Mon Sep 17 00:00:00 2001 From: Emmanuelle Gouillart Date: Mon, 10 Feb 2020 09:59:09 -0500 Subject: [PATCH 04/20] imshow tutorial: add xarray example --- doc/python/imshow.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/doc/python/imshow.md b/doc/python/imshow.md index 349732e82a7..93864b87a98 100644 --- a/doc/python/imshow.md +++ b/doc/python/imshow.md @@ -110,6 +110,32 @@ fig.update_layout(coloraxis_showscale=False) fig.show() ``` +### Display an xarray image with px.imshow + +[xarrays](http://xarray.pydata.org/en/stable/) are labeled arrays (with labeled axes and coordinates). If you pass an xarray image to `px.imshow`, its axes labels and coordinates will be used for ticks. (If you don't want this behavior, just pass `img.values` which is a NumPy array if `img` is an xarray). + +```python +import plotly.express as px +import xarray as xr +# Load xarray from dataset included in the xarray tutorial +# We remove 273.5 to display Celsius degrees instead of Kelvin degrees +airtemps = xr.tutorial.open_dataset('air_temperature').air.isel(lon=20) - 273.5 +fig = px.imshow(airtemps.T, color_continuous_scale='RdBu_r', origin='lower') +fig.show() +``` + +### Display an xarray image with square pixels + +For xarrays, by default `px.imshow` does not constrain pixels to be square, since axes often correspond to different physical quantities (e.g. time and space), contrary to a plain camera image where pixels are square (most of the time). If you want to impose square pixels, set the parameter `aspect` to "equal" as below. + +```python +import plotly.express as px +import xarray as xr +airtemps = xr.tutorial.open_dataset('air_temperature').air.isel(time=500) - 273.5 +fig = px.imshow(airtemps, color_continuous_scale='RdBu_r', aspect='equal') +fig.show() +``` + ### Display multichannel image data with go.Image It is also possible to use the `go.Image` trace from the low-level `graph_objects` API in order to display image data. Note that `go.Image` only accepts multichannel images. For single images, use [`go.Heatmap`](/python/heatmaps). From 8282e0ab2eb22dec9c3d20ee24ca2ac235ff4919 Mon Sep 17 00:00:00 2001 From: Emmanuelle Gouillart Date: Mon, 10 Feb 2020 13:36:30 -0500 Subject: [PATCH 05/20] update tutorials + use long_name --- doc/python/datashader.md | 35 ++++++------------- doc/python/imshow.md | 2 ++ .../python/plotly/plotly/express/_imshow.py | 24 +++++++++---- 3 files changed, 30 insertions(+), 31 deletions(-) diff --git a/doc/python/datashader.md b/doc/python/datashader.md index 9a9a3ba9f52..32f11c57fe6 100644 --- a/doc/python/datashader.md +++ b/doc/python/datashader.md @@ -5,8 +5,8 @@ jupyter: text_representation: extension: .md format_name: markdown - format_version: "1.2" - jupytext_version: 1.3.1 + format_version: '1.2' + jupytext_version: 1.3.0 kernelspec: display_name: Python 3 language: python @@ -20,10 +20,9 @@ jupyter: name: python nbconvert_exporter: python pygments_lexer: ipython3 - version: 3.6.8 + version: 3.7.3 plotly: - description: - How to use datashader to rasterize large datasets, and visualize + description: How to use datashader to rasterize large datasets, and visualize the generated raster data with plotly. display_as: scientific language: python @@ -98,7 +97,7 @@ fig.show() ``` ```python -import plotly.graph_objects as go +import plotly.express as px import pandas as pd import numpy as np import datashader as ds @@ -106,24 +105,10 @@ df = pd.read_parquet('https://raw.githubusercontent.com/plotly/datasets/master/2 cvs = ds.Canvas(plot_width=100, plot_height=100) agg = cvs.points(df, 'SCHEDULED_DEPARTURE', 'DEPARTURE_DELAY') -x = np.array(agg.coords['SCHEDULED_DEPARTURE']) -y = np.array(agg.coords['DEPARTURE_DELAY']) - -# Assign nan to zero values so that the corresponding pixels are transparent -agg = np.array(agg.values, dtype=np.float) -agg[agg<1] = np.nan - -fig = go.Figure(go.Heatmap( - z=np.log10(agg), x=x, y=y, - hoverongaps=False, - hovertemplate='Scheduled departure: %{x:.1f}h
Depature delay: %{y}
Log10(Count): %{z}', - colorbar=dict(title='Count (Log)', tickprefix='1.e'))) -fig.update_xaxes(title_text='Scheduled departure') -fig.update_yaxes(title_text='Departure delay') +agg.values = np.log10(agg.values) +agg.attrs['long_name'] = 'Log10(count)' +fig = px.imshow(agg, origin='lower') +fig.update_traces(hoverongaps=False) +fig.update_layout(coloraxis_colorbar=dict(title='Count (Log)', tickprefix='1.e')) fig.show() - -``` - -```python - ``` diff --git a/doc/python/imshow.md b/doc/python/imshow.md index d887765f4c1..17d38d5ea33 100644 --- a/doc/python/imshow.md +++ b/doc/python/imshow.md @@ -120,6 +120,7 @@ import xarray as xr # Load xarray from dataset included in the xarray tutorial # We remove 273.5 to display Celsius degrees instead of Kelvin degrees airtemps = xr.tutorial.open_dataset('air_temperature').air.isel(lon=20) - 273.5 +airtemps.attrs['long_name'] = 'Temperature' # used for hover fig = px.imshow(airtemps.T, color_continuous_scale='RdBu_r', origin='lower') fig.show() ``` @@ -132,6 +133,7 @@ For xarrays, by default `px.imshow` does not constrain pixels to be square, sinc import plotly.express as px import xarray as xr airtemps = xr.tutorial.open_dataset('air_temperature').air.isel(time=500) - 273.5 +airtemps.attrs['long_name'] = 'Temperature' # used for hover fig = px.imshow(airtemps, color_continuous_scale='RdBu_r', aspect='equal') fig.show() ``` diff --git a/packages/python/plotly/plotly/express/_imshow.py b/packages/python/plotly/plotly/express/_imshow.py index 264fe61dcaf..5e497f1af55 100644 --- a/packages/python/plotly/plotly/express/_imshow.py +++ b/packages/python/plotly/plotly/express/_imshow.py @@ -75,7 +75,7 @@ def imshow( template=None, width=None, height=None, - aspect=None + aspect=None, ): """ Display an image, i.e. data on a 2D regular raster. @@ -83,7 +83,7 @@ def imshow( Parameters ---------- - img: array-like image + img: array-like image, or xarray The image data. Supported array shapes are - (M, N): an image with scalar data. The data is visualized @@ -153,6 +153,9 @@ def imshow( In order to update and customize the returned figure, use `go.Figure.update_traces` or `go.Figure.update_layout`. + + If an xarray is passed, dimensions names and coordinates are used for + axes labels and ticks. """ args = locals() apply_default_cascade(args) @@ -168,11 +171,12 @@ def imshow( y = img.coords[y_label] img_is_xarray = True if aspect is None: - aspect = 'auto' + aspect = "auto" + z_name = img.attrs["long_name"] if "long_name" in img.attrs else "z" if not img_is_xarray: if aspect is None: - aspect = 'equal' + aspect = "equal" img = np.asanyarray(img) @@ -185,7 +189,7 @@ def imshow( trace = go.Heatmap(z=img, coloraxis="coloraxis1") autorange = True if origin == "lower" else "reversed" layout = dict(yaxis=dict(autorange=autorange)) - if aspect == 'equal': + if aspect == "equal": layout["xaxis"] = dict(scaleanchor="y", constrain="domain") layout["yaxis"]["constrain"] = "domain" colorscale_validator = ColorscaleValidator("colorscale", "imshow") @@ -228,7 +232,15 @@ def imshow( fig.update_layout(layout_patch) if img_is_xarray: if img.ndim <= 2: - fig.update_traces(x=x, y=y) + hovertemplate = ( + x_label + + ": %{x}
" + + y_label + + ": %{y}
" + + z_name + + " : %{z}" + ) + fig.update_traces(x=x, y=y, hovertemplate=hovertemplate) fig.update_xaxes(title_text=x_label) fig.update_yaxes(title_text=y_label) fig.update_layout(template=args["template"], overwrite=True) From 568d1c167877b3bfe78fd1a738a607fcf2702aab Mon Sep 17 00:00:00 2001 From: Emmanuelle Gouillart Date: Mon, 10 Feb 2020 13:44:46 -0500 Subject: [PATCH 06/20] change for CI --- packages/python/plotly/tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/python/plotly/tox.ini b/packages/python/plotly/tox.ini index df41b7e1df1..23fd1e30a99 100644 --- a/packages/python/plotly/tox.ini +++ b/packages/python/plotly/tox.ini @@ -60,6 +60,7 @@ deps= retrying==1.3.3 pytest==3.5.1 pandas==0.24.2 + xarray==0.10.9 backports.tempfile==1.0 optional: --editable=file:///{toxinidir}/../plotly-geo optional: numpy==1.16.5 @@ -73,7 +74,6 @@ deps= optional: pyshp==1.2.10 optional: pillow==5.2.0 optional: matplotlib==2.2.3 - optional: xarray==0.10.9 optional: scikit-image==0.14.4 ; CORE ENVIRONMENTS From d63a76a2c5c6a264e96e8aa81c414bd0f24715df Mon Sep 17 00:00:00 2001 From: Emmanuelle Gouillart Date: Thu, 13 Feb 2020 16:05:32 -0500 Subject: [PATCH 07/20] comment --- .../python/plotly/plotly/tests/test_core/test_px/test_imshow.py | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/python/plotly/plotly/tests/test_core/test_px/test_imshow.py b/packages/python/plotly/plotly/tests/test_core/test_px/test_imshow.py index 43e4748f486..783a0f0d4b3 100644 --- a/packages/python/plotly/plotly/tests/test_core/test_px/test_imshow.py +++ b/packages/python/plotly/plotly/tests/test_core/test_px/test_imshow.py @@ -122,6 +122,7 @@ def test_imshow_xarray(): img = np.random.random((20, 30)) da = xr.DataArray(img, dims=["dim_rows", "dim_cols"]) fig = px.imshow(da) + # Dimensions are used for axis labels and coordinates assert fig.layout.xaxis.title.text == "dim_cols" assert fig.layout.yaxis.title.text == "dim_rows" assert np.all(np.array(fig.data[0].x) == np.array(da.coords["dim_cols"])) From 70def8e30a4add72af5523cc2bdc5538bf9fad95 Mon Sep 17 00:00:00 2001 From: Emmanuelle Gouillart Date: Thu, 13 Feb 2020 16:18:14 -0500 Subject: [PATCH 08/20] try to regenerate cache --- .circleci/create_conda_optional_env.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/create_conda_optional_env.sh b/.circleci/create_conda_optional_env.sh index ebd907688a5..d1d48f7e998 100755 --- a/.circleci/create_conda_optional_env.sh +++ b/.circleci/create_conda_optional_env.sh @@ -14,7 +14,7 @@ if [ ! -d $HOME/miniconda/envs/circle_optional ]; then ./miniconda.sh -b -p $HOME/miniconda # Create environment - # PYTHON_VERSION=3.6 + # 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 From 16212717de667ee6131d6e221b07edbf37da8ef6 Mon Sep 17 00:00:00 2001 From: Emmanuelle Gouillart Date: Thu, 19 Mar 2020 09:46:00 -0400 Subject: [PATCH 09/20] tmp --- doc/python/imshow.md | 4 ++-- packages/python/plotly/plotly/express/_imshow.py | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/doc/python/imshow.md b/doc/python/imshow.md index 17d38d5ea33..c7b27426eab 100644 --- a/doc/python/imshow.md +++ b/doc/python/imshow.md @@ -119,8 +119,8 @@ import plotly.express as px import xarray as xr # Load xarray from dataset included in the xarray tutorial # We remove 273.5 to display Celsius degrees instead of Kelvin degrees -airtemps = xr.tutorial.open_dataset('air_temperature').air.isel(lon=20) - 273.5 -airtemps.attrs['long_name'] = 'Temperature' # used for hover +airtemps = xr.tutorial.open_dataset('air_temperature').air.sel(lon=250.0) +#airtemps.attrs['long_name'] = 'Temperature' # used for hover fig = px.imshow(airtemps.T, color_continuous_scale='RdBu_r', origin='lower') fig.show() ``` diff --git a/packages/python/plotly/plotly/express/_imshow.py b/packages/python/plotly/plotly/express/_imshow.py index 5e497f1af55..54fd4ec58e4 100644 --- a/packages/python/plotly/plotly/express/_imshow.py +++ b/packages/python/plotly/plotly/express/_imshow.py @@ -68,6 +68,7 @@ def imshow( zmin=None, zmax=None, origin=None, + labels=None, color_continuous_scale=None, color_continuous_midpoint=None, range_color=None, @@ -173,6 +174,7 @@ def imshow( if aspect is None: aspect = "auto" z_name = img.attrs["long_name"] if "long_name" in img.attrs else "z" + #TODO if ... if not img_is_xarray: if aspect is None: From ae984b0cac66dfbbc625edf0df8140bccd72e28f Mon Sep 17 00:00:00 2001 From: Emmanuelle Gouillart Date: Thu, 19 Mar 2020 13:52:39 -0400 Subject: [PATCH 10/20] added labels to imshow --- doc/python/imshow.md | 14 +++++----- .../python/plotly/plotly/express/_imshow.py | 26 ++++++++++++++++--- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/doc/python/imshow.md b/doc/python/imshow.md index c7b27426eab..6f0ca07eb37 100644 --- a/doc/python/imshow.md +++ b/doc/python/imshow.md @@ -112,16 +112,15 @@ fig.show() ### Display an xarray image with px.imshow -[xarrays](http://xarray.pydata.org/en/stable/) are labeled arrays (with labeled axes and coordinates). If you pass an xarray image to `px.imshow`, its axes labels and coordinates will be used for ticks. (If you don't want this behavior, just pass `img.values` which is a NumPy array if `img` is an xarray). +[xarrays](http://xarray.pydata.org/en/stable/) are labeled arrays (with labeled axes and coordinates). If you pass an xarray image to `px.imshow`, its axes labels and coordinates will be used for axis titles. If you don't want this behavior, you can pass `img.values` which is a NumPy array if `img` is an xarray. Alternatively, you can override axis titles hover labels and colorbar title using the `labels` attribute, as below. ```python import plotly.express as px import xarray as xr # Load xarray from dataset included in the xarray tutorial -# We remove 273.5 to display Celsius degrees instead of Kelvin degrees airtemps = xr.tutorial.open_dataset('air_temperature').air.sel(lon=250.0) -#airtemps.attrs['long_name'] = 'Temperature' # used for hover -fig = px.imshow(airtemps.T, color_continuous_scale='RdBu_r', origin='lower') +fig = px.imshow(airtemps.T, color_continuous_scale='RdBu_r', origin='lower', + labels={'colorbar':airtemps.attrs['var_desc']}) fig.show() ``` @@ -132,9 +131,10 @@ For xarrays, by default `px.imshow` does not constrain pixels to be square, sinc ```python import plotly.express as px import xarray as xr -airtemps = xr.tutorial.open_dataset('air_temperature').air.isel(time=500) - 273.5 -airtemps.attrs['long_name'] = 'Temperature' # used for hover -fig = px.imshow(airtemps, color_continuous_scale='RdBu_r', aspect='equal') +airtemps = xr.tutorial.open_dataset('air_temperature').air.isel(time=500) +colorbar_title = airtemps.attrs['var_desc'] + '
(%s)'%airtemps.attrs['units'] +fig = px.imshow(airtemps, color_continuous_scale='RdBu_r', aspect='equal', + labels={'colorbar':colorbar_title}) fig.show() ``` diff --git a/packages/python/plotly/plotly/express/_imshow.py b/packages/python/plotly/plotly/express/_imshow.py index 54fd4ec58e4..b2007949967 100644 --- a/packages/python/plotly/plotly/express/_imshow.py +++ b/packages/python/plotly/plotly/express/_imshow.py @@ -68,7 +68,7 @@ def imshow( zmin=None, zmax=None, origin=None, - labels=None, + labels={}, color_continuous_scale=None, color_continuous_midpoint=None, range_color=None, @@ -104,6 +104,14 @@ def imshow( position of the [0, 0] pixel of the image array, in the upper left or lower left corner. The convention 'upper' is typically used for matrices and images. + labels : dict with str keys and str values (default `{}`) + Overrides names used in the figure for axis titles (keys ``x`` and ``y``), + colorbar title (key ``colorbar``) and hover (key ``color``). The values + should correspond to the desired label to be displayed. If ``img`` is an + xarray, dimension names are used for axis titles, and long name for the + colorbar title (unless overridden in ``labels``). Possible keys are: + x, y, color and colorbar. + color_continuous_scale : str or list of str colormap used to map scalar data to colors (for a 2D image). This parameter is not used for RGB or RGBA images. If a string is provided, it should be the name @@ -161,6 +169,7 @@ def imshow( args = locals() apply_default_cascade(args) img_is_xarray = False + colorbar_title = '' if xarray_imported: if isinstance(img, xarray.DataArray): y_label, x_label = img.dims[0], img.dims[1] @@ -173,8 +182,18 @@ def imshow( img_is_xarray = True if aspect is None: aspect = "auto" - z_name = img.attrs["long_name"] if "long_name" in img.attrs else "z" - #TODO if ... + z_name = img.attrs["long_name"] if "long_name" in img.attrs else "" + colorbar_title = z_name + + if labels is not None: + if 'x' in labels: + y_label = labels['x'] + if 'y' in labels: + y_label = labels['y'] + if 'color' in labels: + z_name = labels['color'] + if 'colorbar' in labels: + colorbar_title = labels['colorbar'] if not img_is_xarray: if aspect is None: @@ -207,6 +226,7 @@ def imshow( cmid=color_continuous_midpoint, cmin=range_color[0], cmax=range_color[1], + colorbar=dict(title=colorbar_title) ) # For 2D+RGB data, use Image trace From 503e962bb80c467ff2c57b62312cefdcecb484cd Mon Sep 17 00:00:00 2001 From: Emmanuelle Gouillart Date: Thu, 19 Mar 2020 14:20:56 -0400 Subject: [PATCH 11/20] blacken --- .../python/plotly/plotly/express/_imshow.py | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/python/plotly/plotly/express/_imshow.py b/packages/python/plotly/plotly/express/_imshow.py index b2007949967..036039ea7da 100644 --- a/packages/python/plotly/plotly/express/_imshow.py +++ b/packages/python/plotly/plotly/express/_imshow.py @@ -169,7 +169,7 @@ def imshow( args = locals() apply_default_cascade(args) img_is_xarray = False - colorbar_title = '' + colorbar_title = "" if xarray_imported: if isinstance(img, xarray.DataArray): y_label, x_label = img.dims[0], img.dims[1] @@ -186,14 +186,14 @@ def imshow( colorbar_title = z_name if labels is not None: - if 'x' in labels: - y_label = labels['x'] - if 'y' in labels: - y_label = labels['y'] - if 'color' in labels: - z_name = labels['color'] - if 'colorbar' in labels: - colorbar_title = labels['colorbar'] + if "x" in labels: + y_label = labels["x"] + if "y" in labels: + y_label = labels["y"] + if "color" in labels: + z_name = labels["color"] + if "colorbar" in labels: + colorbar_title = labels["colorbar"] if not img_is_xarray: if aspect is None: @@ -226,7 +226,7 @@ def imshow( cmid=color_continuous_midpoint, cmin=range_color[0], cmax=range_color[1], - colorbar=dict(title=colorbar_title) + colorbar=dict(title=colorbar_title), ) # For 2D+RGB data, use Image trace From 11414e0da3b3e0a695072ed8827143da318ff9af Mon Sep 17 00:00:00 2001 From: Emmanuelle Gouillart Date: Thu, 19 Mar 2020 09:47:25 -0400 Subject: [PATCH 12/20] pinning orca --- .circleci/create_conda_optional_env.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/create_conda_optional_env.sh b/.circleci/create_conda_optional_env.sh index d1d48f7e998..78c79940828 100755 --- a/.circleci/create_conda_optional_env.sh +++ b/.circleci/create_conda_optional_env.sh @@ -19,5 +19,5 @@ if [ ! -d $HOME/miniconda/envs/circle_optional ]; then 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 + $HOME/miniconda/bin/conda install --yes -n circle_optional -c plotly plotly-orca=1.2.1 fi From e64b05a512048a2296ff09351b93b5e56e9a8a38 Mon Sep 17 00:00:00 2001 From: Emmanuelle Gouillart Date: Thu, 19 Mar 2020 18:44:06 -0400 Subject: [PATCH 13/20] label --- doc/python/imshow.md | 5 +++-- packages/python/plotly/plotly/express/_imshow.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/doc/python/imshow.md b/doc/python/imshow.md index 6f0ca07eb37..6adf15770b0 100644 --- a/doc/python/imshow.md +++ b/doc/python/imshow.md @@ -112,7 +112,7 @@ fig.show() ### Display an xarray image with px.imshow -[xarrays](http://xarray.pydata.org/en/stable/) are labeled arrays (with labeled axes and coordinates). If you pass an xarray image to `px.imshow`, its axes labels and coordinates will be used for axis titles. If you don't want this behavior, you can pass `img.values` which is a NumPy array if `img` is an xarray. Alternatively, you can override axis titles hover labels and colorbar title using the `labels` attribute, as below. +[xarrays](http://xarray.pydata.org/en/stable/) are labeled arrays (with labeled axes and coordinates). If you pass an xarray image to `px.imshow`, its axes labels and coordinates will be used for axis titles. If you don't want this behavior, you can pass `img.values` which is a NumPy array if `img` is an xarray. Alternatively, you can override axis titles hover labels and colorbar title using the `labels` attribute, as in the next example. ```python import plotly.express as px @@ -120,7 +120,8 @@ import xarray as xr # Load xarray from dataset included in the xarray tutorial airtemps = xr.tutorial.open_dataset('air_temperature').air.sel(lon=250.0) fig = px.imshow(airtemps.T, color_continuous_scale='RdBu_r', origin='lower', - labels={'colorbar':airtemps.attrs['var_desc']}) + #labels={'colorbar':airtemps.attrs['var_desc']} + ) fig.show() ``` diff --git a/packages/python/plotly/plotly/express/_imshow.py b/packages/python/plotly/plotly/express/_imshow.py index 036039ea7da..db06b87e410 100644 --- a/packages/python/plotly/plotly/express/_imshow.py +++ b/packages/python/plotly/plotly/express/_imshow.py @@ -182,7 +182,7 @@ def imshow( img_is_xarray = True if aspect is None: aspect = "auto" - z_name = img.attrs["long_name"] if "long_name" in img.attrs else "" + z_name = xarray.plot.utils.label_from_attrs(img).replace("\n", "
") colorbar_title = z_name if labels is not None: From 33890df9a289fe47de4a6622b1561505751ca2f0 Mon Sep 17 00:00:00 2001 From: Emmanuelle Gouillart Date: Thu, 19 Mar 2020 19:27:18 -0400 Subject: [PATCH 14/20] removed colorbar key --- packages/python/plotly/plotly/express/_imshow.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/packages/python/plotly/plotly/express/_imshow.py b/packages/python/plotly/plotly/express/_imshow.py index db06b87e410..75bbb585385 100644 --- a/packages/python/plotly/plotly/express/_imshow.py +++ b/packages/python/plotly/plotly/express/_imshow.py @@ -106,11 +106,10 @@ def imshow( labels : dict with str keys and str values (default `{}`) Overrides names used in the figure for axis titles (keys ``x`` and ``y``), - colorbar title (key ``colorbar``) and hover (key ``color``). The values - should correspond to the desired label to be displayed. If ``img`` is an - xarray, dimension names are used for axis titles, and long name for the - colorbar title (unless overridden in ``labels``). Possible keys are: - x, y, color and colorbar. + colorbar title and hover (key ``color``). The values should correspond + to the desired label to be displayed. If ``img`` is an xarray, dimension + names are used for axis titles, and long name for the colorbar title + (unless overridden in ``labels``). Possible keys are: x, y, and color. color_continuous_scale : str or list of str colormap used to map scalar data to colors (for a 2D image). This parameter is @@ -169,7 +168,6 @@ def imshow( args = locals() apply_default_cascade(args) img_is_xarray = False - colorbar_title = "" if xarray_imported: if isinstance(img, xarray.DataArray): y_label, x_label = img.dims[0], img.dims[1] @@ -183,7 +181,6 @@ def imshow( if aspect is None: aspect = "auto" z_name = xarray.plot.utils.label_from_attrs(img).replace("\n", "
") - colorbar_title = z_name if labels is not None: if "x" in labels: @@ -192,8 +189,6 @@ def imshow( y_label = labels["y"] if "color" in labels: z_name = labels["color"] - if "colorbar" in labels: - colorbar_title = labels["colorbar"] if not img_is_xarray: if aspect is None: @@ -226,7 +221,7 @@ def imshow( cmid=color_continuous_midpoint, cmin=range_color[0], cmax=range_color[1], - colorbar=dict(title=colorbar_title), + colorbar=dict(title=z_name), ) # For 2D+RGB data, use Image trace From e3ec430fbf463454ac6a0b9c36ea4755b06ea5f3 Mon Sep 17 00:00:00 2001 From: Emmanuelle Gouillart Date: Fri, 20 Mar 2020 10:02:31 -0400 Subject: [PATCH 15/20] corrected bug --- packages/python/plotly/plotly/express/_imshow.py | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/python/plotly/plotly/express/_imshow.py b/packages/python/plotly/plotly/express/_imshow.py index 75bbb585385..0dfe5ff47a0 100644 --- a/packages/python/plotly/plotly/express/_imshow.py +++ b/packages/python/plotly/plotly/express/_imshow.py @@ -168,6 +168,7 @@ def imshow( args = locals() apply_default_cascade(args) img_is_xarray = False + z_name = "" if xarray_imported: if isinstance(img, xarray.DataArray): y_label, x_label = img.dims[0], img.dims[1] From d70fe1402c784f722c001f3928044a913e3ee6d1 Mon Sep 17 00:00:00 2001 From: Emmanuelle Gouillart Date: Fri, 20 Mar 2020 10:21:51 -0400 Subject: [PATCH 16/20] datashader example --- doc/python/datashader.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/doc/python/datashader.md b/doc/python/datashader.md index e7fd2bdea03..aa2c963c966 100644 --- a/doc/python/datashader.md +++ b/doc/python/datashader.md @@ -5,8 +5,8 @@ jupyter: text_representation: extension: .md format_name: markdown - format_version: "1.2" - jupytext_version: 1.3.1 + format_version: '1.2' + jupytext_version: 1.3.0 kernelspec: display_name: Python 3 language: python @@ -22,8 +22,7 @@ jupyter: pygments_lexer: ipython3 version: 3.7.3 plotly: - description: - How to use datashader to rasterize large datasets, and visualize + description: How to use datashader to rasterize large datasets, and visualize the generated raster data with plotly. display_as: scientific language: python @@ -107,9 +106,12 @@ df = pd.read_parquet('https://raw.githubusercontent.com/plotly/datasets/master/2 cvs = ds.Canvas(plot_width=100, plot_height=100) agg = cvs.points(df, 'SCHEDULED_DEPARTURE', 'DEPARTURE_DELAY') agg.values = np.log10(agg.values) -agg.attrs['long_name'] = 'Log10(count)' -fig = px.imshow(agg, origin='lower') +fig = px.imshow(agg, origin='lower', labels={'color':'Log10(count)'}) fig.update_traces(hoverongaps=False) -fig.update_layout(coloraxis_colorbar=dict(title='Count (Log)', tickprefix='1.e')) +fig.update_layout(coloraxis_colorbar=dict(title='Count', tickprefix='1.e')) fig.show() ``` + +```python + +``` From b051468cbad0948bf634b5c589d9280d0978875a Mon Sep 17 00:00:00 2001 From: Emmanuelle Gouillart Date: Fri, 20 Mar 2020 10:47:19 -0400 Subject: [PATCH 17/20] Update packages/python/plotly/plotly/express/_imshow.py Co-Authored-By: Nicolas Kruchten --- packages/python/plotly/plotly/express/_imshow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/python/plotly/plotly/express/_imshow.py b/packages/python/plotly/plotly/express/_imshow.py index 0dfe5ff47a0..efeada90e41 100644 --- a/packages/python/plotly/plotly/express/_imshow.py +++ b/packages/python/plotly/plotly/express/_imshow.py @@ -185,7 +185,7 @@ def imshow( if labels is not None: if "x" in labels: - y_label = labels["x"] + x_label = labels["x"] if "y" in labels: y_label = labels["y"] if "color" in labels: From e8302d15977cffdf99c05f9a5a84d2abb2eeb941 Mon Sep 17 00:00:00 2001 From: Emmanuelle Gouillart Date: Fri, 20 Mar 2020 11:16:39 -0400 Subject: [PATCH 18/20] hover --- .../python/plotly/plotly/express/_imshow.py | 37 ++++++++++--------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/packages/python/plotly/plotly/express/_imshow.py b/packages/python/plotly/plotly/express/_imshow.py index 0dfe5ff47a0..b92f0762fd8 100644 --- a/packages/python/plotly/plotly/express/_imshow.py +++ b/packages/python/plotly/plotly/express/_imshow.py @@ -168,7 +168,9 @@ def imshow( args = locals() apply_default_cascade(args) img_is_xarray = False - z_name = "" + x_label = "x" + y_label = "y" + z_label = "" if xarray_imported: if isinstance(img, xarray.DataArray): y_label, x_label = img.dims[0], img.dims[1] @@ -181,15 +183,15 @@ def imshow( img_is_xarray = True if aspect is None: aspect = "auto" - z_name = xarray.plot.utils.label_from_attrs(img).replace("\n", "
") + z_label = xarray.plot.utils.label_from_attrs(img).replace("\n", "
") if labels is not None: if "x" in labels: - y_label = labels["x"] + x_label = labels["x"] if "y" in labels: y_label = labels["y"] if "color" in labels: - z_name = labels["color"] + z_label = labels["color"] if not img_is_xarray: if aspect is None: @@ -222,7 +224,7 @@ def imshow( cmid=color_continuous_midpoint, cmin=range_color[0], cmax=range_color[1], - colorbar=dict(title=z_name), + colorbar=dict(title=z_label), ) # For 2D+RGB data, use Image trace @@ -248,18 +250,19 @@ def imshow( layout_patch["margin"] = {"t": 60} fig = go.Figure(data=trace, layout=layout) fig.update_layout(layout_patch) + if img.ndim <= 2: + hovertemplate = ( + x_label + + ": %{x}
" + + y_label + + ": %{y}
" + + z_label + + " : %{z}" + ) + fig.update_traces(hovertemplate=hovertemplate) if img_is_xarray: - if img.ndim <= 2: - hovertemplate = ( - x_label - + ": %{x}
" - + y_label - + ": %{y}
" - + z_name - + " : %{z}" - ) - fig.update_traces(x=x, y=y, hovertemplate=hovertemplate) - fig.update_xaxes(title_text=x_label) - fig.update_yaxes(title_text=y_label) + fig.update_traces(x=x, y=y) + fig.update_xaxes(title_text=x_label) + fig.update_yaxes(title_text=y_label) fig.update_layout(template=args["template"], overwrite=True) return fig From 941779c43742c8459e8e6b881589d7a57f5becbc Mon Sep 17 00:00:00 2001 From: Nicolas Kruchten Date: Fri, 20 Mar 2020 21:35:22 -0400 Subject: [PATCH 19/20] generalizing imshow(labels, x, y) --- doc/python/imshow.md | 17 ++-- .../python/plotly/plotly/express/_imshow.py | 79 +++++++++---------- 2 files changed, 48 insertions(+), 48 deletions(-) diff --git a/doc/python/imshow.md b/doc/python/imshow.md index 6adf15770b0..99f5214498a 100644 --- a/doc/python/imshow.md +++ b/doc/python/imshow.md @@ -6,7 +6,7 @@ jupyter: extension: .md format_name: markdown format_version: '1.2' - jupytext_version: 1.3.0 + jupytext_version: 1.3.1 kernelspec: display_name: Python 3 language: python @@ -20,7 +20,7 @@ jupyter: name: python nbconvert_exporter: python pygments_lexer: ipython3 - version: 3.7.3 + version: 3.6.8 plotly: description: How to display image data in Python with Plotly. display_as: scientific @@ -93,7 +93,11 @@ fig.show() import plotly.express as px import numpy as np img = np.arange(100).reshape((10, 10)) -fig = px.imshow(img, color_continuous_scale='gray') +fig = px.imshow(img, color_continuous_scale='gray', labels=dict(x="yoo", y="yaa", color="hey"), + width=600, height=600, + x=["a","b","c","d","e","f","g","h","i","j"], + y=["a","b","c","d","e","f","g","h","i","j"] + ) fig.show() ``` @@ -120,7 +124,7 @@ import xarray as xr # Load xarray from dataset included in the xarray tutorial airtemps = xr.tutorial.open_dataset('air_temperature').air.sel(lon=250.0) fig = px.imshow(airtemps.T, color_continuous_scale='RdBu_r', origin='lower', - #labels={'colorbar':airtemps.attrs['var_desc']} + labels={'color':airtemps.attrs['var_desc']} ) fig.show() ``` @@ -133,9 +137,8 @@ For xarrays, by default `px.imshow` does not constrain pixels to be square, sinc import plotly.express as px import xarray as xr airtemps = xr.tutorial.open_dataset('air_temperature').air.isel(time=500) -colorbar_title = airtemps.attrs['var_desc'] + '
(%s)'%airtemps.attrs['units'] -fig = px.imshow(airtemps, color_continuous_scale='RdBu_r', aspect='equal', - labels={'colorbar':colorbar_title}) +colorbar_title = airtemps.attrs['var_desc'] + '
(%s)'%airtemps.attrs['units'] +fig = px.imshow(airtemps, color_continuous_scale='RdBu_r', aspect='equal') fig.show() ``` diff --git a/packages/python/plotly/plotly/express/_imshow.py b/packages/python/plotly/plotly/express/_imshow.py index b92f0762fd8..6dfcf489b06 100644 --- a/packages/python/plotly/plotly/express/_imshow.py +++ b/packages/python/plotly/plotly/express/_imshow.py @@ -69,6 +69,8 @@ def imshow( zmax=None, origin=None, labels={}, + x=None, + y=None, color_continuous_scale=None, color_continuous_midpoint=None, range_color=None, @@ -167,33 +169,33 @@ def imshow( """ args = locals() apply_default_cascade(args) - img_is_xarray = False - x_label = "x" - y_label = "y" - z_label = "" - if xarray_imported: - if isinstance(img, xarray.DataArray): - y_label, x_label = img.dims[0], img.dims[1] - # np.datetime64 is not handled correctly by go.Heatmap - for ax in [x_label, y_label]: - if np.issubdtype(img.coords[ax].dtype, np.datetime64): - img.coords[ax] = img.coords[ax].astype(str) + labels = labels.copy() + if xarray_imported and isinstance(img, xarray.DataArray): + y_label, x_label = img.dims[0], img.dims[1] + # np.datetime64 is not handled correctly by go.Heatmap + for ax in [x_label, y_label]: + if np.issubdtype(img.coords[ax].dtype, np.datetime64): + img.coords[ax] = img.coords[ax].astype(str) + if x is None: x = img.coords[x_label] + if y is None: y = img.coords[y_label] - img_is_xarray = True - if aspect is None: - aspect = "auto" - z_label = xarray.plot.utils.label_from_attrs(img).replace("\n", "
") - - if labels is not None: - if "x" in labels: - x_label = labels["x"] - if "y" in labels: - y_label = labels["y"] - if "color" in labels: - z_label = labels["color"] - - if not img_is_xarray: + if aspect is None: + aspect = "auto" + if labels.get("x", None) is None: + labels["x"] = x_label + if labels.get("y", None) is None: + labels["y"] = y_label + if labels.get("color", None) is None: + labels["color"] = xarray.plot.utils.label_from_attrs(img) + labels["color"] = labels["color"].replace("\n", "
") + else: + if labels.get("x", None) is None: + labels["x"] = "" + if labels.get("y", None) is None: + labels["y"] = "" + if labels.get("color", None) is None: + labels["color"] = "" if aspect is None: aspect = "equal" @@ -205,7 +207,7 @@ def imshow( # For 2d data, use Heatmap trace if img.ndim == 2: - trace = go.Heatmap(z=img, coloraxis="coloraxis1") + trace = go.Heatmap(x=x, y=y, z=img, coloraxis="coloraxis1") autorange = True if origin == "lower" else "reversed" layout = dict(yaxis=dict(autorange=autorange)) if aspect == "equal": @@ -224,8 +226,9 @@ def imshow( cmid=color_continuous_midpoint, cmin=range_color[0], cmax=range_color[1], - colorbar=dict(title=z_label), ) + if labels["color"]: + layout["coloraxis1"]["colorbar"] = dict(title=labels["color"]) # For 2D+RGB data, use Image trace elif img.ndim == 3 and img.shape[-1] in [3, 4]: @@ -250,19 +253,13 @@ def imshow( layout_patch["margin"] = {"t": 60} fig = go.Figure(data=trace, layout=layout) fig.update_layout(layout_patch) - if img.ndim <= 2: - hovertemplate = ( - x_label - + ": %{x}
" - + y_label - + ": %{y}
" - + z_label - + " : %{z}" - ) - fig.update_traces(hovertemplate=hovertemplate) - if img_is_xarray: - fig.update_traces(x=x, y=y) - fig.update_xaxes(title_text=x_label) - fig.update_yaxes(title_text=y_label) + fig.update_traces( + hovertemplate="%s: %%{x}
%s: %%{y}
%s: %%{z}" + % (labels["x"] or "x", labels["y"] or "y", labels["color"] or "color",) + ) + if labels["x"]: + fig.update_xaxes(title_text=labels["x"]) + if labels["y"]: + fig.update_yaxes(title_text=labels["y"]) fig.update_layout(template=args["template"], overwrite=True) return fig From 21df0305e61a7ff012f7abf9c2df2f6d0683dc40 Mon Sep 17 00:00:00 2001 From: Nicolas Kruchten Date: Sat, 21 Mar 2020 21:11:51 -0400 Subject: [PATCH 20/20] docs and tests for imshow changes --- doc/python/heatmaps.md | 22 +++++++-- doc/python/imshow.md | 49 ++++++++++++++----- packages/python/plotly/plotly/express/_doc.py | 13 ++--- .../python/plotly/plotly/express/_imshow.py | 24 +++++++-- .../tests/test_core/test_px/test_imshow.py | 24 +++++++++ 5 files changed, 101 insertions(+), 31 deletions(-) diff --git a/doc/python/heatmaps.md b/doc/python/heatmaps.md index ec2735bf725..bc54043ec1a 100644 --- a/doc/python/heatmaps.md +++ b/doc/python/heatmaps.md @@ -36,9 +36,7 @@ jupyter: ### Heatmap with `plotly.express` and `px.imshow` -[Plotly Express](/python/plotly-express/) is the easy-to-use, high-level interface to Plotly. With `px.imshow`, each value of the input array is represented as a heatmap pixel. - -`px.imshow` makes opiniated choices for representing heatmaps, such as using square pixels. To override this behaviour, you can use `fig.update_layout` or use the `go.Heatmap` trace from `plotly.graph_objects` as described below. +[Plotly Express](/python/plotly-express/) is the easy-to-use, high-level interface to Plotly, which [operates on "tidy" data](/python/px-arguments/) and produces [easy-to-style figures](/python/styling-plotly-express/). With `px.imshow`, each value of the input array is represented as a heatmap pixel. For more examples using `px.imshow`, see the [tutorial on displaying image data with plotly](/python/imshow). @@ -51,6 +49,22 @@ fig = px.imshow([[1, 20, 30], fig.show() ``` +### Customizing the axes and labels on a heatmap + +You can use the `x`, `y` and `labels` arguments to customize the display of a heatmap, and use `.update_xaxes()` to move the x axis tick labels to the top: + +```python +import plotly.express as px +data=[[1, 25, 30, 50, 1], [20, 1, 60, 80, 30], [30, 60, 1, 5, 20]] +fig = px.imshow(data, + labels=dict(x="Day of Week", y="Time of Day", color="Productivity"), + x=['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'], + y=['Morning', 'Afternoon', 'Evening'] + ) +fig.update_xaxes(side="top") +fig.show() +``` + ### Basic Heatmap with `plotly.graph_objects` If Plotly Express does not provide a good starting point, it is also possible to use the more generic `go.Heatmap` function from `plotly.graph_objects`. @@ -67,7 +81,7 @@ fig.show() ### Heatmap with Categorical Axis Labels -In this example we also show how to ignore [hovertext](https://plot.ly/python/hover-text-and-formatting/) when we have [missing values](https://plot.ly/python/missing_values) in the data by setting the [hoverongaps](https://plot.ly/python/reference/#heatmap-hoverongaps) to False. +In this example we also show how to ignore [hovertext](https://plot.ly/python/hover-text-and-formatting/) when we have [missing values](https://plot.ly/python/missing_values) in the data by setting the [hoverongaps](https://plot.ly/python/reference/#heatmap-hoverongaps) to False. ```python import plotly.graph_objects as go diff --git a/doc/python/imshow.md b/doc/python/imshow.md index 99f5214498a..3fbcb98a7e5 100644 --- a/doc/python/imshow.md +++ b/doc/python/imshow.md @@ -74,7 +74,7 @@ fig = px.imshow(img) fig.show() ``` -### Display single-channel 2D image as grayscale +### Display single-channel 2D data as a heatmap For a 2D image, `px.imshow` uses a colorscale to map scalar data to colors. The default colorscale is the one of the active template (see [the tutorial on templates](/python/templates/)). @@ -88,22 +88,29 @@ fig.show() ### Choose the colorscale to display a single-channel image +You can customize the [continuous color scale](/python/colorscales/) just like with any other Plotly Express function: ```python import plotly.express as px import numpy as np img = np.arange(100).reshape((10, 10)) -fig = px.imshow(img, color_continuous_scale='gray', labels=dict(x="yoo", y="yaa", color="hey"), - width=600, height=600, - x=["a","b","c","d","e","f","g","h","i","j"], - y=["a","b","c","d","e","f","g","h","i","j"] - ) +fig = px.imshow(img, color_continuous_scale='Viridis') +fig.show() +``` + +You can use this to make the image grayscale as well: + +```python +import plotly.express as px +import numpy as np +img = np.arange(100).reshape((10, 10)) +fig = px.imshow(img, color_continuous_scale='gray') fig.show() ``` -### Hiding the colorbar when displaying a single-channel image +### Hiding the colorbar and axis labels -See [the tutorial on coloraxis](/python/colorscales/#share-color-axis) for more details on coloraxis. +See the [continuous color](/python/colorscales/) and [cartesian axes](/python/axes/) pages for more details. ```python import plotly.express as px @@ -111,21 +118,37 @@ from skimage import data img = data.camera() fig = px.imshow(img, color_continuous_scale='gray') fig.update_layout(coloraxis_showscale=False) +fig.update_xaxes(showticklabels=False) +fig.update_yaxes(showticklabels=False) +fig.show() +``` + +### Customizing the axes and labels on a single-channel image + +You can use the `x`, `y` and `labels` arguments to customize the display of a heatmap, and use `.update_xaxes()` to move the x axis tick labels to the top: + +```python +import plotly.express as px +data=[[1, 25, 30, 50, 1], [20, 1, 60, 80, 30], [30, 60, 1, 5, 20]] +fig = px.imshow(data, + labels=dict(x="Day of Week", y="Time of Day", color="Productivity"), + x=['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'], + y=['Morning', 'Afternoon', 'Evening'] + ) +fig.update_xaxes(side="top") fig.show() ``` ### Display an xarray image with px.imshow -[xarrays](http://xarray.pydata.org/en/stable/) are labeled arrays (with labeled axes and coordinates). If you pass an xarray image to `px.imshow`, its axes labels and coordinates will be used for axis titles. If you don't want this behavior, you can pass `img.values` which is a NumPy array if `img` is an xarray. Alternatively, you can override axis titles hover labels and colorbar title using the `labels` attribute, as in the next example. +[xarrays](http://xarray.pydata.org/en/stable/) are labeled arrays (with labeled axes and coordinates). If you pass an xarray image to `px.imshow`, its axes labels and coordinates will be used for axis titles. If you don't want this behavior, you can pass `img.values` which is a NumPy array if `img` is an xarray. Alternatively, you can override axis titles hover labels and colorbar title using the `labels` attribute, as above. ```python import plotly.express as px import xarray as xr # Load xarray from dataset included in the xarray tutorial airtemps = xr.tutorial.open_dataset('air_temperature').air.sel(lon=250.0) -fig = px.imshow(airtemps.T, color_continuous_scale='RdBu_r', origin='lower', - labels={'color':airtemps.attrs['var_desc']} - ) +fig = px.imshow(airtemps.T, color_continuous_scale='RdBu_r', origin='lower') fig.show() ``` @@ -233,7 +256,7 @@ fig.show() ### imshow and datashader Arrays of rasterized values build by datashader can be visualized using -imshow. See the [plotly and datashader tutorial](/python/datashader/) for +imshow. See the [plotly and datashader tutorial](/python/datashader/) for examples on how to use plotly and datashader. diff --git a/packages/python/plotly/plotly/express/_doc.py b/packages/python/plotly/plotly/express/_doc.py index 3b153b0587a..41ea9e2f23d 100644 --- a/packages/python/plotly/plotly/express/_doc.py +++ b/packages/python/plotly/plotly/express/_doc.py @@ -6,13 +6,6 @@ except AttributeError: # python 2 getfullargspec = inspect.getargspec -# TODO contents of columns -# TODO explain categorical -# TODO handle color -# TODO handle details of box/violin/histogram -# TODO handle details of column selection with `dimensions` -# TODO document "or `None`, default `None`" in various places -# TODO standardize positioning and casing of 'default' colref_type = "str or int or Series or array-like" colref_desc = "Either a name of a column in `data_frame`, or a pandas Series or array_like object." @@ -325,11 +318,11 @@ ], title=["str", "The figure title."], template=[ - "or dict or plotly.graph_objects.layout.Template instance", - "The figure template name or definition.", + "str or dict or plotly.graph_objects.layout.Template instance", + "The figure template name (must be a key in plotly.io.templates) or definition.", ], width=["int (default `None`)", "The figure width in pixels."], - height=["int (default `600`)", "The figure height in pixels."], + height=["int (default `None`)", "The figure height in pixels."], labels=[ "dict with str keys and str values (default `{}`)", "By default, column names are used in the figure for axis titles, legend entries and hovers.", diff --git a/packages/python/plotly/plotly/express/_imshow.py b/packages/python/plotly/plotly/express/_imshow.py index 6dfcf489b06..9ffe6800676 100644 --- a/packages/python/plotly/plotly/express/_imshow.py +++ b/packages/python/plotly/plotly/express/_imshow.py @@ -101,18 +101,24 @@ def imshow( a multichannel image of floats, the max of the image is computed and zmax is the smallest power of 256 (1, 255, 65535) greater than this max value, with a 5% tolerance. For a single-channel image, the max of the image is used. + Overridden by range_color. origin : str, 'upper' or 'lower' (default 'upper') position of the [0, 0] pixel of the image array, in the upper left or lower left corner. The convention 'upper' is typically used for matrices and images. labels : dict with str keys and str values (default `{}`) - Overrides names used in the figure for axis titles (keys ``x`` and ``y``), - colorbar title and hover (key ``color``). The values should correspond + Sets names used in the figure for axis titles (keys ``x`` and ``y``), + colorbar title and hoverlabel (key ``color``). The values should correspond to the desired label to be displayed. If ``img`` is an xarray, dimension names are used for axis titles, and long name for the colorbar title (unless overridden in ``labels``). Possible keys are: x, y, and color. + x, y: list-like, optional + x and y are used to label the axes of single-channel heatmap visualizations and + their lengths must match the lengths of the second and first dimensions of the + img argument. They are auto-populated if the input is an xarray. + color_continuous_scale : str or list of str colormap used to map scalar data to colors (for a 2D image). This parameter is not used for RGB or RGBA images. If a string is provided, it should be the name @@ -121,7 +127,7 @@ def imshow( color_continuous_midpoint : number If set, computes the bounds of the continuous color scale to have the desired - midpoint. + midpoint. Overridden by range_color or zmin and zmax. range_color : list of two numbers If provided, overrides auto-scaling on the continuous color scale, including @@ -138,7 +144,7 @@ def imshow( The figure width in pixels. height: number - The figure height in pixels, defaults to 600. + The figure height in pixels. aspect: 'equal', 'auto', or None - 'equal': Ensures an aspect ratio of 1 or pixels (square pixels) @@ -207,6 +213,16 @@ def imshow( # For 2d data, use Heatmap trace if img.ndim == 2: + if y is not None and img.shape[0] != len(y): + raise ValueError( + "The length of the y vector must match the length of the first " + + "dimension of the img matrix." + ) + if x is not None and img.shape[1] != len(x): + raise ValueError( + "The length of the x vector must match the length of the second " + + "dimension of the img matrix." + ) trace = go.Heatmap(x=x, y=y, z=img, coloraxis="coloraxis1") autorange = True if origin == "lower" else "reversed" layout = dict(yaxis=dict(autorange=autorange)) diff --git a/packages/python/plotly/plotly/tests/test_core/test_px/test_imshow.py b/packages/python/plotly/plotly/tests/test_core/test_px/test_imshow.py index 783a0f0d4b3..7f8c2afd48b 100644 --- a/packages/python/plotly/plotly/tests/test_core/test_px/test_imshow.py +++ b/packages/python/plotly/plotly/tests/test_core/test_px/test_imshow.py @@ -126,3 +126,27 @@ def test_imshow_xarray(): assert fig.layout.xaxis.title.text == "dim_cols" assert fig.layout.yaxis.title.text == "dim_rows" assert np.all(np.array(fig.data[0].x) == np.array(da.coords["dim_cols"])) + + +def test_imshow_labels_and_ranges(): + fig = px.imshow([[1, 2], [3, 4], [5, 6]],) + assert fig.layout.xaxis.title.text is None + assert fig.layout.yaxis.title.text is None + assert fig.layout.coloraxis.colorbar.title.text is None + assert fig.data[0].x is None + assert fig.data[0].y is None + fig = px.imshow( + [[1, 2], [3, 4], [5, 6]], + x=["a", "b"], + y=["c", "d", "e"], + labels=dict(x="the x", y="the y", color="the color"), + ) + # Dimensions are used for axis labels and coordinates + assert fig.layout.xaxis.title.text == "the x" + assert fig.layout.yaxis.title.text == "the y" + assert fig.layout.coloraxis.colorbar.title.text == "the color" + assert fig.data[0].x[0] == "a" + assert fig.data[0].y[0] == "c" + + with pytest.raises(ValueError): + fig = px.imshow([[1, 2], [3, 4], [5, 6]], x=["a"])