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
diff --git a/doc/python/datashader.md b/doc/python/datashader.md
index 9a9a3ba9f52..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
@@ -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,22 +105,11 @@ 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)
+fig = px.imshow(agg, origin='lower', labels={'color':'Log10(count)'})
+fig.update_traces(hoverongaps=False)
+fig.update_layout(coloraxis_colorbar=dict(title='Count', tickprefix='1.e'))
fig.show()
-
```
```python
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 ba5b0c20cb7..3fbcb98a7e5 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
@@ -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,6 +88,17 @@ 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='Viridis')
+fig.show()
+```
+
+You can use this to make the image grayscale as well:
```python
import plotly.express as px
@@ -97,9 +108,9 @@ 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
@@ -107,6 +118,50 @@ 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 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')
+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)
+colorbar_title = airtemps.attrs['var_desc'] + '
(%s)'%airtemps.attrs['units']
+fig = px.imshow(airtemps, color_continuous_scale='RdBu_r', aspect='equal')
fig.show()
```
@@ -201,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 6f1bf77e877..9ffe6800676 100644
--- a/packages/python/plotly/plotly/express/_imshow.py
+++ b/packages/python/plotly/plotly/express/_imshow.py
@@ -3,6 +3,13 @@
from ._core import apply_default_cascade
import numpy as np
+try:
+ import xarray
+
+ xarray_imported = True
+except ImportError:
+ xarray_imported = False
+
_float_types = []
# Adapted from skimage.util.dtype
@@ -61,6 +68,9 @@ def imshow(
zmin=None,
zmax=None,
origin=None,
+ labels={},
+ x=None,
+ y=None,
color_continuous_scale=None,
color_continuous_midpoint=None,
range_color=None,
@@ -68,6 +78,7 @@ def imshow(
template=None,
width=None,
height=None,
+ aspect=None,
):
"""
Display an image, i.e. data on a 2D regular raster.
@@ -75,7 +86,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
@@ -90,11 +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 `{}`)
+ 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
@@ -103,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
@@ -120,7 +144,15 @@ 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)
+ - '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
-------
@@ -137,23 +169,66 @@ 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)
+ 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]
+ 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"
img = np.asanyarray(img)
+
# Cast bools to uint8 (also one byte)
if img.dtype == np.bool:
img = 255 * img.astype(np.uint8)
# For 2d data, use Heatmap trace
if img.ndim == 2:
- trace = go.Heatmap(z=img, coloraxis="coloraxis1")
+ 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(
- 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()
@@ -168,6 +243,8 @@ def imshow(
cmin=range_color[0],
cmax=range_color[1],
)
+ 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]:
@@ -185,12 +262,20 @@ 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)
+ 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
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..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
@@ -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,37 @@ 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)
+ # 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"]))
+
+
+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"])
diff --git a/packages/python/plotly/tox.ini b/packages/python/plotly/tox.ini
index b1f1cca2f71..095ec2fd3e9 100644
--- a/packages/python/plotly/tox.ini
+++ b/packages/python/plotly/tox.ini
@@ -58,6 +58,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
@@ -71,7 +72,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