Repository: plotly/plotly.py

Issue:
[BUG] zmin and zmax not working in imshow for 2d data
See for example
```
import plotly.express as px
import numpy as np
img = np.random.random((10, 10))
px.imshow(img, zmin=0.3, zmax=0.7)
```
I think this is because the Heatmap trace uses instead the `range_color` argument which was quite introduced quite late in the process. It's easy to fix, we just have to decide first if the API is ok as it is. 


Discussion:
Conclusion from discussion: we should continue to use `range_color` if provided, but if it isn't, we insert `zmin` and `zmax` in its place.
So of course it's a bit more complicated than I originally thought because in the coloraxis if you give `cmin` you must also give `cmax` (I don't see why, btw). Working on it!

Resolving patch:
diff --git a/packages/python/plotly/plotly/express/_imshow.py b/packages/python/plotly/plotly/express/_imshow.py
--- a/packages/python/plotly/plotly/express/_imshow.py
+++ b/packages/python/plotly/plotly/express/_imshow.py
@@ -1,7 +1,7 @@
 import plotly.graph_objs as go
 from _plotly_utils.basevalidators import ColorscaleValidator
 from ._core import apply_default_cascade
-import numpy as np  # is it fine to depend on np here?
+import numpy as np
 
 _float_types = []
 
@@ -107,7 +107,8 @@ def imshow(
 
     range_color : list of two numbers
         If provided, overrides auto-scaling on the continuous color scale, including
-        overriding `color_continuous_midpoint`.
+        overriding `color_continuous_midpoint`. Also overrides zmin and zmax. Used only
+        for single-channel images.
 
     title : str
         The figure title.
@@ -147,14 +148,18 @@ def imshow(
 
     # For 2d data, use Heatmap trace
     if img.ndim == 2:
-        trace = go.Heatmap(z=img, zmin=zmin, zmax=zmax, coloraxis="coloraxis1")
+        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"),
         )
         colorscale_validator = ColorscaleValidator("colorscale", "imshow")
-        range_color = range_color or [None, None]
+        if zmin is not None and zmax is None:
+            zmax = img.max()
+        if zmax is not None and zmin is None:
+            zmin = img.min()
+        range_color = range_color or [zmin, zmax]
         layout["coloraxis1"] = dict(
             colorscale=colorscale_validator.validate_coerce(
                 args["color_continuous_scale"]


Test patch:
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
--- 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
@@ -97,3 +97,20 @@ def test_zmax_floats():
         fig = px.imshow(img)
         print(fig.data[0]["zmax"], zmax)
         assert fig.data[0]["zmax"] == None
+
+
+def test_zmin_zmax_range_color():
+    img = img_gray / 100.0
+    fig = px.imshow(img)
+    assert not (fig.layout.coloraxis.cmin or fig.layout.coloraxis.cmax)
+    fig1 = px.imshow(img, zmin=0.2, zmax=0.8)
+    fig2 = px.imshow(img, range_color=[0.2, 0.8])
+    assert fig1 == fig2
+    # color_range overrides zmin and zmax
+    fig = px.imshow(img, zmin=0.3, zmax=0.9, range_color=[0.2, 0.8])
+    assert fig.layout.coloraxis.cmin == 0.2
+    assert fig.layout.coloraxis.cmax == 0.8
+    # It's possible to pass only zmin OR zmax
+    fig = px.imshow(img, zmax=0.8)
+    assert fig.layout.coloraxis.cmin == 0.0
+    assert fig.layout.coloraxis.cmax == 0.8