Skip to content

Commit 96a9430

Browse files
committed
Merge branch 'master' into update-plotly-js
2 parents 04f682d + 216fca2 commit 96a9430

File tree

8 files changed

+97
-8
lines changed

8 files changed

+97
-8
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,13 @@ This project adheres to [Semantic Versioning](http://semver.org/).
1818

1919
### Fixed
2020
- Fixed another compatibility issue with Pandas 2.0, just affecting `px.*(line_close=True)` [[#4190](https://github.com/plotly/plotly.py/pull/4190)]
21+
- Empty pandas dataframe with facet row/column set no longer fails [[#4038](https://github.com/plotly/plotly.py/pull/4038)]
2122
- Added some rounding to the `make_subplots` function to handle situations where the user-input specs cause the domain to exceed 1 by small amounts [[#4153](https://github.com/plotly/plotly.py/pull/4153)]
2223
- Sanitize JSON output to prevent an XSS vector when graphs are inserted directly into HTML [[#4196](https://github.com/plotly/plotly.py/pull/4196)]
2324
- Fixed issue with shapes and annotations plotting on the wrong y axis when supplied with a specific axis in the `yref` parameter [[#4177](https://github.com/plotly/plotly.py/pull/4177)]
2425
- Remove `use_2to3` setuptools arg, which is invalid in the latest Python and setuptools versions [[#4206](https://github.com/plotly/plotly.py/pull/4206)]
2526
- Fix [#4066](https://github.com/plotly/plotly.py/issues/4066) JupyterLab v4 giving tiny default graph height [[#4227](https://github.com/plotly/plotly.py/pull/4227)]
27+
- Fixed issue with `colors.n_colors` where generated RGB color values were not being constrained to stay between 0 and 255 [[#4110](https://github.com/plotly/plotly.py/pull/4110)]
2628

2729
## [5.14.1] - 2023-04-05
2830

packages/python/plotly/_plotly_utils/colors/__init__.py

+11-3
Original file line numberDiff line numberDiff line change
@@ -694,11 +694,19 @@ def n_colors(lowcolor, highcolor, n_colors, colortype="tuple"):
694694
incr_2 = diff_2 / (n_colors - 1)
695695
list_of_colors = []
696696

697+
def _constrain_color(c):
698+
if c > 255.0:
699+
return 255.0
700+
elif c < 0.0:
701+
return 0.0
702+
else:
703+
return c
704+
697705
for index in range(n_colors):
698706
new_tuple = (
699-
lowcolor[0] + (index * incr_0),
700-
lowcolor[1] + (index * incr_1),
701-
lowcolor[2] + (index * incr_2),
707+
_constrain_color(lowcolor[0] + (index * incr_0)),
708+
_constrain_color(lowcolor[1] + (index * incr_1)),
709+
_constrain_color(lowcolor[2] + (index * incr_2)),
702710
)
703711
list_of_colors.append(new_tuple)
704712

packages/python/plotly/_plotly_utils/tests/validators/test_integer_validator.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ def test_acceptance_min(val, validator_min):
7474
assert validator_min.validate_coerce(val) == approx(val)
7575

7676

77-
@pytest.mark.parametrize("val", [-2, -123, np.iinfo(np.int).min])
77+
@pytest.mark.parametrize("val", [-2, -123, np.iinfo(int).min])
7878
def test_rejection_min(val, validator_min):
7979
with pytest.raises(ValueError) as validation_failure:
8080
validator_min.validate_coerce(val)

packages/python/plotly/plotly/express/_core.py

+4
Original file line numberDiff line numberDiff line change
@@ -1880,6 +1880,10 @@ def infer_config(args, constructor, trace_patch, layout_patch):
18801880
args[position] = args["marginal"]
18811881
args[other_position] = None
18821882

1883+
# Ignore facet rows and columns when data frame is empty so as to prevent nrows/ncols equaling 0
1884+
if len(args["data_frame"]) == 0:
1885+
args["facet_row"] = args["facet_col"] = None
1886+
18831887
# If both marginals and faceting are specified, faceting wins
18841888
if args.get("facet_col") is not None and args.get("marginal_y") is not None:
18851889
args["marginal_y"] = None

packages/python/plotly/plotly/io/_renderers.py

+6-4
Original file line numberDiff line numberDiff line change
@@ -525,13 +525,15 @@ def show(fig, renderer=None, validate=True, **kwargs):
525525
else:
526526
# If ipython isn't available, try to display figures in the default
527527
# browser
528-
import webbrowser
529-
530528
try:
529+
import webbrowser
530+
531531
webbrowser.get()
532532
default_renderer = "browser"
533-
except webbrowser.Error:
534-
# Default browser could not be loaded
533+
except Exception:
534+
# Many things could have gone wrong
535+
# There could not be a webbrowser Python module,
536+
# or the module may be a dumb placeholder
535537
pass
536538

537539
renderers.render_on_display = True

packages/python/plotly/plotly/tests/test_core/test_colors/test_colors.py

+27
Original file line numberDiff line numberDiff line change
@@ -210,3 +210,30 @@ def test_sample_colorscale(self):
210210
colors.sample_colorscale("TuRbId_r", 12),
211211
colors.sequential.turbid_r,
212212
)
213+
214+
def test_n_colors(self):
215+
# test that n_colors constrains values to between 0 and 255
216+
generated_colorscale = colors.n_colors(
217+
lowcolor="rgb(255,0,0)",
218+
highcolor="rgb(0,255,0)",
219+
n_colors=14,
220+
colortype="rgb",
221+
)
222+
expected_colorscale = [
223+
"rgb(255.0, 0.0, 0.0)",
224+
"rgb(235.3846153846154, 19.615384615384617, 0.0)",
225+
"rgb(215.76923076923077, 39.23076923076923, 0.0)",
226+
"rgb(196.15384615384613, 58.846153846153854, 0.0)",
227+
"rgb(176.53846153846155, 78.46153846153847, 0.0)",
228+
"rgb(156.9230769230769, 98.07692307692308, 0.0)",
229+
"rgb(137.3076923076923, 117.69230769230771, 0.0)",
230+
"rgb(117.69230769230768, 137.30769230769232, 0.0)",
231+
"rgb(98.07692307692307, 156.92307692307693, 0.0)",
232+
"rgb(78.46153846153845, 176.53846153846155, 0.0)",
233+
"rgb(58.84615384615384, 196.15384615384616, 0.0)",
234+
"rgb(39.230769230769226, 215.76923076923077, 0.0)",
235+
"rgb(19.615384615384585, 235.38461538461542, 0.0)",
236+
"rgb(0.0, 255.0, 0.0)",
237+
]
238+
239+
self.assertEqual(generated_colorscale, expected_colorscale)

packages/python/plotly/plotly/tests/test_io/test_renderers.py

+43
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ def test_plotly_mimetype_renderer_show(fig1, renderer):
126126
# ------------
127127
# See plotly/tests/test_orca/test_image_renderers.py
128128

129+
129130
# HTML
130131
# ----
131132
def assert_full_html(html):
@@ -381,3 +382,45 @@ def test_repr_mimebundle_mixed_renderer(fig1):
381382
assert set(fig1._repr_mimebundle_().keys()) == set(
382383
{"application/vnd.plotly.v1+json", "text/html"}
383384
)
385+
386+
387+
def test_missing_webbrowser_module(fig1):
388+
"""
389+
Assert that no errors occur if the webbrowser module is absent
390+
"""
391+
try:
392+
import builtins
393+
except ImportError:
394+
import __builtin__ as builtins
395+
realimport = builtins.__import__
396+
397+
def webbrowser_absent_import(name, globals, locals, fromlist, level):
398+
"""
399+
Mimick an absent webbrowser module
400+
"""
401+
if name == "webbrowser":
402+
raise ImportError
403+
return realimport(name, globals, locals, fromlist, level)
404+
405+
with mock.patch("builtins.__import__", webbrowser_absent_import):
406+
# 1: check whether importing webbrowser actually results in an ImportError
407+
with pytest.raises(ImportError):
408+
import webbrowser
409+
410+
# 2: check whether the _repr_html_ can handle it regardless
411+
fig1._repr_html_()
412+
413+
414+
def test_missing_webbrowser_methods(fig1):
415+
"""
416+
Assert that no errors occur if the webbrowser module does not contain some methods
417+
"""
418+
import webbrowser
419+
420+
removed_webbrowser_get_method = webbrowser.get
421+
try:
422+
del webbrowser.get
423+
fig1._repr_html_()
424+
finally:
425+
# restore everything after this test
426+
webbrowser.get = removed_webbrowser_get_method

test/percy/plotly-express.py

+3
Original file line numberDiff line numberDiff line change
@@ -543,3 +543,6 @@
543543
)
544544
fig = px.timeline(df, x_start="Start", x_end="Finish", y="Task", color="Task")
545545
fig.write_html(os.path.join(dir_name, "timeline.html"), auto_play=False)
546+
547+
548+
px.bar(pd.DataFrame(columns=["A", "B", "X"]), x="A", y="X", facet_col="B")

0 commit comments

Comments
 (0)