Skip to content

Commit 29eee77

Browse files
Merge pull request #2614 from plotly/nocolor
px.NO_COLOR
2 parents b4cb434 + a226a5f commit 29eee77

File tree

11 files changed

+160
-10
lines changed

11 files changed

+160
-10
lines changed

CHANGELOG.md

+10
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,22 @@ This project adheres to [Semantic Versioning](http://semver.org/).
55

66
## [4.9.0] - unreleased
77

8+
### Added
9+
10+
- `px.NO_COLOR` constant to override wide-form color assignment in Plotly Express ([#2614](https://github.com/plotly/plotly.py/pull/2614))
11+
- `facet_row_spacing` and `facet_col_spacing` added to Plotly Express cartesian 2d functions ([#2614](https://github.com/plotly/plotly.py/pull/2614))
12+
13+
### Fixed
14+
15+
- trendline traces are now of type `scattergl` when `render_mode="webgl"` in Plotly Express ([#2614](https://github.com/plotly/plotly.py/pull/2614))
16+
817
### Updated
918

1019
- Added all cartesian-2d Plotly Express functions, plus `imshow` to Pandas backend with `kind` option
1120
- `plotly.express.imshow` now uses data frame index and columns names and values to populate axis parameters by default ([#2539](https://github.com/plotly/plotly.py/pull/2539))
1221

1322

23+
1424
## [4.8.2] - 2020-06-26
1525

1626
### Updated

doc/python/facet-plots.md

+21-4
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ jupyter:
66
extension: .md
77
format_name: markdown
88
format_version: '1.2'
9-
jupytext_version: 1.3.4
9+
jupytext_version: 1.4.2
1010
kernelspec:
1111
display_name: Python 3
1212
language: python
@@ -20,7 +20,7 @@ jupyter:
2020
name: python
2121
nbconvert_exporter: python
2222
pygments_lexer: ipython3
23-
version: 3.7.0
23+
version: 3.7.7
2424
plotly:
2525
description: How to make Facet and Trellis Plots in Python with Plotly.
2626
display_as: statistical
@@ -103,7 +103,7 @@ fig.show()
103103

104104
### Customize Subplot Figure Titles
105105

106-
Since subplot figure titles are [annotations](https://plotly.com/python/text-and-annotations/#simple-annotation), you can use the `for_each_annotation` function to customize them.
106+
Since subplot figure titles are [annotations](https://plotly.com/python/text-and-annotations/#simple-annotation), you can use the `for_each_annotation` function to customize them, for example to remove the equal-sign (`=`).
107107

108108
In the following example, we pass a lambda function to `for_each_annotation` in order to change the figure subplot titles from `smoker=No` and `smoker=Yes` to just `No` and `Yes`.
109109

@@ -115,8 +115,25 @@ fig.for_each_annotation(lambda a: a.update(text=a.text.split("=")[-1]))
115115
fig.show()
116116
```
117117

118+
### Controlling Facet Spacing
119+
120+
The `facet_row_spacing` and `facet_col_spacing` arguments can be used to control the spacing between rows and columns. These values are specified in fractions of the plotting area in paper coordinates and not in pixels, so they will grow or shrink with the `width` and `height` of the figure.
121+
122+
The defaults work well with 1-4 rows or columns at the default figure size with the default font size, but need to be reduced to around 0.01 for very large figures or figures with many rows or columns. Conversely, if activating tick labels on all facets, the spacing will need to be increased.
123+
118124
```python
125+
import plotly.express as px
119126

127+
df = px.data.gapminder().query("continent == 'Africa'")
128+
129+
fig = px.line(df, x="year", y="lifeExp", facet_col="country", facet_col_wrap=7,
130+
facet_row_spacing=0.04, # default is 0.07 when facet_col_wrap is used
131+
facet_col_spacing=0.04, # default is 0.03
132+
height=600, width=800,
133+
title="Life Expectancy in Africa")
134+
fig.for_each_annotation(lambda a: a.update(text=a.text.split("=")[-1]))
135+
fig.update_yaxes(showticklabels=True)
136+
fig.show()
120137
```
121138

122139
### Synchronizing axes in subplots with `matches`
@@ -138,4 +155,4 @@ for i in range(1, 4):
138155
fig.add_trace(go.Scatter(x=x, y=np.random.random(N)), 1, i)
139156
fig.update_xaxes(matches='x')
140157
fig.show()
141-
```
158+
```

doc/python/wide-form.md

+10
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,16 @@ fig = px.bar(wide_df, x="nation", y=["gold", "silver", "bronze"], facet_col="var
158158
fig.show()
159159
```
160160

161+
You can also prevent `color` from getting assigned if you're mapping `variable` to some other argument:
162+
163+
```python
164+
import plotly.express as px
165+
wide_df = px.data.medals_wide(indexed=False)
166+
167+
fig = px.bar(wide_df, x="nation", y=["gold", "silver", "bronze"], facet_col="variable", color=px.NO_COLOR)
168+
fig.show()
169+
```
170+
161171
If using a data frame's named indexes, either explicitly or relying on the defaults, the row-index references (i.e. `df.index`) or column-index names (i.e. the value of `df.columns.name`) must be used:
162172

163173
```python

packages/python/plotly/plotly/express/__init__.py

+2
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
set_mapbox_access_token,
5454
defaults,
5555
get_trendline_results,
56+
NO_COLOR,
5657
)
5758

5859
from ._special_inputs import IdentityMap, Constant, Range # noqa: F401
@@ -100,4 +101,5 @@
100101
"IdentityMap",
101102
"Constant",
102103
"Range",
104+
"NO_COLOR",
103105
]

packages/python/plotly/plotly/express/_chart_types.py

+22
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ def scatter(
2323
facet_row=None,
2424
facet_col=None,
2525
facet_col_wrap=0,
26+
facet_row_spacing=None,
27+
facet_col_spacing=None,
2628
error_x=None,
2729
error_x_minus=None,
2830
error_y=None,
@@ -74,6 +76,8 @@ def density_contour(
7476
facet_row=None,
7577
facet_col=None,
7678
facet_col_wrap=0,
79+
facet_row_spacing=None,
80+
facet_col_spacing=None,
7781
hover_name=None,
7882
hover_data=None,
7983
animation_frame=None,
@@ -141,6 +145,8 @@ def density_heatmap(
141145
facet_row=None,
142146
facet_col=None,
143147
facet_col_wrap=0,
148+
facet_row_spacing=None,
149+
facet_col_spacing=None,
144150
hover_name=None,
145151
hover_data=None,
146152
animation_frame=None,
@@ -213,6 +219,8 @@ def line(
213219
facet_row=None,
214220
facet_col=None,
215221
facet_col_wrap=0,
222+
facet_row_spacing=None,
223+
facet_col_spacing=None,
216224
error_x=None,
217225
error_x_minus=None,
218226
error_y=None,
@@ -260,6 +268,8 @@ def area(
260268
facet_row=None,
261269
facet_col=None,
262270
facet_col_wrap=0,
271+
facet_row_spacing=None,
272+
facet_col_spacing=None,
263273
animation_frame=None,
264274
animation_group=None,
265275
category_orders={},
@@ -301,6 +311,8 @@ def bar(
301311
facet_row=None,
302312
facet_col=None,
303313
facet_col_wrap=0,
314+
facet_row_spacing=None,
315+
facet_col_spacing=None,
304316
hover_name=None,
305317
hover_data=None,
306318
custom_data=None,
@@ -353,6 +365,8 @@ def histogram(
353365
facet_row=None,
354366
facet_col=None,
355367
facet_col_wrap=0,
368+
facet_row_spacing=None,
369+
facet_col_spacing=None,
356370
hover_name=None,
357371
hover_data=None,
358372
animation_frame=None,
@@ -417,6 +431,8 @@ def violin(
417431
facet_row=None,
418432
facet_col=None,
419433
facet_col_wrap=0,
434+
facet_row_spacing=None,
435+
facet_col_spacing=None,
420436
hover_name=None,
421437
hover_data=None,
422438
custom_data=None,
@@ -464,6 +480,8 @@ def box(
464480
facet_row=None,
465481
facet_col=None,
466482
facet_col_wrap=0,
483+
facet_row_spacing=None,
484+
facet_col_spacing=None,
467485
hover_name=None,
468486
hover_data=None,
469487
custom_data=None,
@@ -514,6 +532,8 @@ def strip(
514532
facet_row=None,
515533
facet_col=None,
516534
facet_col_wrap=0,
535+
facet_row_spacing=None,
536+
facet_col_spacing=None,
517537
hover_name=None,
518538
hover_data=None,
519539
custom_data=None,
@@ -1398,6 +1418,8 @@ def funnel(
13981418
facet_row=None,
13991419
facet_col=None,
14001420
facet_col_wrap=0,
1421+
facet_row_spacing=None,
1422+
facet_col_spacing=None,
14011423
hover_name=None,
14021424
hover_data=None,
14031425
custom_data=None,

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

+11-5
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
_subplot_type_for_trace_type,
1616
)
1717

18+
NO_COLOR = "px_no_color_constant"
1819

1920
# Declare all supported attributes, across all plot types
2021
direct_attrables = (
@@ -842,7 +843,7 @@ def make_trace_spec(args, constructor, attrs, trace_patch):
842843
# Add trendline trace specifications
843844
if "trendline" in args and args["trendline"]:
844845
trace_spec = TraceSpec(
845-
constructor=go.Scatter,
846+
constructor=go.Scattergl if constructor == go.Scattergl else go.Scatter,
846847
attrs=["trendline"],
847848
trace_patch=dict(mode="lines"),
848849
marginal=None,
@@ -1349,6 +1350,10 @@ def build_dataframe(args, constructor):
13491350
label=_escape_col_name(df_input, "index", [var_name, value_name])
13501351
)
13511352

1353+
no_color = False
1354+
if type(args.get("color", None)) == str and args["color"] == NO_COLOR:
1355+
no_color = True
1356+
args["color"] = None
13521357
# now that things have been prepped, we do the systematic rewriting of `args`
13531358

13541359
df_output, wide_id_vars = process_args_into_dataframe(
@@ -1440,7 +1445,8 @@ def build_dataframe(args, constructor):
14401445
args["x" if orient_v else "y"] = value_name
14411446
args["y" if orient_v else "x"] = wide_cross_name
14421447
args["color"] = args["color"] or var_name
1443-
1448+
if no_color:
1449+
args["color"] = None
14441450
args["data_frame"] = df_output
14451451
return args
14461452

@@ -2054,9 +2060,9 @@ def init_figure(args, subplot_type, frame_list, nrows, ncols, col_labels, row_la
20542060
row_heights = [main_size] * (nrows - 1) + [1 - main_size]
20552061
vertical_spacing = 0.01
20562062
elif args.get("facet_col_wrap", 0):
2057-
vertical_spacing = 0.07
2063+
vertical_spacing = args.get("facet_row_spacing", None) or 0.07
20582064
else:
2059-
vertical_spacing = 0.03
2065+
vertical_spacing = args.get("facet_row_spacing", None) or 0.03
20602066

20612067
if bool(args.get("marginal_y", False)):
20622068
if args["marginal_y"] == "histogram" or ("color" in args and args["color"]):
@@ -2067,7 +2073,7 @@ def init_figure(args, subplot_type, frame_list, nrows, ncols, col_labels, row_la
20672073
column_widths = [main_size] * (ncols - 1) + [1 - main_size]
20682074
horizontal_spacing = 0.005
20692075
else:
2070-
horizontal_spacing = 0.02
2076+
horizontal_spacing = args.get("facet_col_spacing", None) or 0.02
20712077
else:
20722078
# Other subplot types:
20732079
# 'scene', 'geo', 'polar', 'ternary', 'mapbox', 'domain', None

packages/python/plotly/plotly/express/_doc.py

+8
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,14 @@
224224
"Wraps the column variable at this width, so that the column facets span multiple rows.",
225225
"Ignored if 0, and forced to 0 if `facet_row` or a `marginal` is set.",
226226
],
227+
facet_row_spacing=[
228+
"float between 0 and 1",
229+
"Spacing between facet rows, in paper units. Default is 0.03 or 0.0.7 when facet_col_wrap is used.",
230+
],
231+
facet_col_spacing=[
232+
"float between 0 and 1",
233+
"Spacing between facet columns, in paper units Default is 0.02.",
234+
],
227235
animation_frame=[
228236
colref_type,
229237
colref_desc,

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

-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import plotly.express as px
2-
import numpy as np
32

43

54
def test_reversed_colorscale():
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import plotly.express as px
2+
from pytest import approx
3+
4+
5+
def test_facets():
6+
df = px.data.tips()
7+
fig = px.scatter(df, x="total_bill", y="tip")
8+
assert "xaxis2" not in fig.layout
9+
assert "yaxis2" not in fig.layout
10+
assert fig.layout.xaxis.domain == (0.0, 1.0)
11+
assert fig.layout.yaxis.domain == (0.0, 1.0)
12+
13+
fig = px.scatter(df, x="total_bill", y="tip", facet_row="sex", facet_col="smoker")
14+
assert fig.layout.xaxis4.domain[0] - fig.layout.xaxis.domain[1] == approx(0.02)
15+
assert fig.layout.yaxis4.domain[0] - fig.layout.yaxis.domain[1] == approx(0.03)
16+
17+
fig = px.scatter(df, x="total_bill", y="tip", facet_col="day", facet_col_wrap=2)
18+
assert fig.layout.xaxis4.domain[0] - fig.layout.xaxis.domain[1] == approx(0.02)
19+
assert fig.layout.yaxis4.domain[0] - fig.layout.yaxis.domain[1] == approx(0.07)
20+
21+
fig = px.scatter(
22+
df,
23+
x="total_bill",
24+
y="tip",
25+
facet_row="sex",
26+
facet_col="smoker",
27+
facet_col_spacing=0.09,
28+
facet_row_spacing=0.08,
29+
)
30+
assert fig.layout.xaxis4.domain[0] - fig.layout.xaxis.domain[1] == approx(0.09)
31+
assert fig.layout.yaxis4.domain[0] - fig.layout.yaxis.domain[1] == approx(0.08)
32+
33+
fig = px.scatter(
34+
df,
35+
x="total_bill",
36+
y="tip",
37+
facet_col="day",
38+
facet_col_wrap=2,
39+
facet_col_spacing=0.09,
40+
facet_row_spacing=0.08,
41+
)
42+
assert fig.layout.xaxis4.domain[0] - fig.layout.xaxis.domain[1] == approx(0.09)
43+
assert fig.layout.yaxis4.domain[0] - fig.layout.yaxis.domain[1] == approx(0.08)

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

+22
Original file line numberDiff line numberDiff line change
@@ -253,3 +253,25 @@ def test_marginal_ranges():
253253
)
254254
assert fig.layout.xaxis2.range is None
255255
assert fig.layout.yaxis3.range is None
256+
257+
258+
def test_render_mode():
259+
df = px.data.gapminder()
260+
df2007 = df.query("year == 2007")
261+
fig = px.scatter(df2007, x="gdpPercap", y="lifeExp", trendline="ols")
262+
assert fig.data[0].type == "scatter"
263+
assert fig.data[1].type == "scatter"
264+
fig = px.scatter(
265+
df2007, x="gdpPercap", y="lifeExp", trendline="ols", render_mode="webgl"
266+
)
267+
assert fig.data[0].type == "scattergl"
268+
assert fig.data[1].type == "scattergl"
269+
fig = px.scatter(df, x="gdpPercap", y="lifeExp", trendline="ols")
270+
assert fig.data[0].type == "scattergl"
271+
assert fig.data[1].type == "scattergl"
272+
fig = px.scatter(df, x="gdpPercap", y="lifeExp", trendline="ols", render_mode="svg")
273+
assert fig.data[0].type == "scatter"
274+
assert fig.data[1].type == "scatter"
275+
fig = px.density_contour(df, x="gdpPercap", y="lifeExp", trendline="ols")
276+
assert fig.data[0].type == "histogram2dcontour"
277+
assert fig.data[1].type == "scatter"

packages/python/plotly/plotly/tests/test_core/test_px/test_px_wide.py

+11
Original file line numberDiff line numberDiff line change
@@ -708,6 +708,17 @@ def append_special_case(df_in, args_in, args_expect, df_expect):
708708
),
709709
)
710710

711+
# NO_COLOR
712+
df = pd.DataFrame(dict(a=[1, 2], b=[3, 4]))
713+
append_special_case(
714+
df_in=df,
715+
args_in=dict(x=None, y=None, color=px.NO_COLOR),
716+
args_expect=dict(x="index", y="value", color=None, orientation="v",),
717+
df_expect=pd.DataFrame(
718+
dict(variable=["a", "a", "b", "b"], index=[0, 1, 0, 1], value=[1, 2, 3, 4])
719+
),
720+
)
721+
711722

712723
@pytest.mark.parametrize("df_in, args_in, args_expect, df_expect", special_cases)
713724
def test_wide_mode_internal_special_cases(df_in, args_in, args_expect, df_expect):

0 commit comments

Comments
 (0)