From ff4df858e5c1bb9f06df082ec5217239a6ab4328 Mon Sep 17 00:00:00 2001 From: Emmanuelle Gouillart Date: Fri, 24 Jan 2020 13:54:15 -0500 Subject: [PATCH 01/21] added some items in release process notes --- contributing.md | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/contributing.md b/contributing.md index 09989e2296e..1373deec4e9 100644 --- a/contributing.md +++ b/contributing.md @@ -213,7 +213,7 @@ This is the release process for releasing `plotly.py` version `X.Y.Z` with `plotlywidget` version `A.B.C`. Note: The `plotlywidget` instructions must be followed if any change -has been made in the `js/` directory source code, OR if the version of +has been made in the `packages/javascript` directory source code, OR if the version of plotly.js has been updated. If neither of these is the case, there's no need to increment the `plotlywidget` version or to publish a new version to npm. @@ -297,17 +297,19 @@ And, you'll need the credentials file `~/.pypirc`. Request access from (plotly_dev) $ twine upload dist/plotly-X.Y.Zrc1* ``` -### Publish release candidate of `plotlywidget` to NPM +### Publish release candidate of `plotlywidget` and `jupyterlab-plotly` to NPM Now, publish the release candidate of the `plotlywidget` NPM package. ```bash -cd ./js +cd ./packages/javascript/plotlywidget npm publish --access public --tag next ``` The `--tag next` part ensures that users won't install this version unless they explicitly ask for the version or for the version wtih the `next` tag. +Do the same in the `jupyterlab-plotly` directory. + ### Publish release candidate to plotly anaconda channel To publish package to the plotly anaconda channel you'll need to have the anaconda or miniconda distribution installed, and you'll need to have the @@ -361,7 +363,9 @@ release candidate suffix from the following version strings: - `plotly/_widget_version.py`: + Update `__frontend_version__` to `^A.B.C` (Note the `^` prefix) - - `js/package.json` + - `packages/javascript/plotlywidget/package.json` + + Update `"version"` to `A.B.C` + - `packages/javascript/jupyterlab-plotly/package.json` + Update `"version"` to `A.B.C` Commit and push to the release branch. @@ -431,6 +435,19 @@ Make "Release title" the same string as the tag. Copy changelog section for this version as the "Describe this release" +### Upgrade doc requirements and API doc + +Files to be updated: +- `doc/apidoc/conf.py` with new version number +- `doc/requirements.txt` +- `binder/requirements.txt` + +### Synchronize master and doc-prod branches + +doc-prod should already have been merged on a regular basis into master, but +start doing it first. Then merge master into doc-prod to deploy the doc related +to features in the release. + ### Post announcement Post a simple announcement to the Plotly Python forum, with links to the README installation instructions and to the CHANGELOG. From 2b06173d04e3534604ff8a6c200a9c4f522b9de8 Mon Sep 17 00:00:00 2001 From: Emmanuelle Gouillart Date: Fri, 24 Jan 2020 16:12:38 -0500 Subject: [PATCH 02/21] mention getting-started --- contributing.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/contributing.md b/contributing.md index 1373deec4e9..82ed16b20ba 100644 --- a/contributing.md +++ b/contributing.md @@ -253,7 +253,9 @@ Note that the conda installation instructions must include "-c plotly/lable/test" rather than "-c plotly" in order to install the release candidate version. -Commit Changelog and README updates. +Update the `doc/python/getting-started.md` file with the same version numbers. + +Commit Changelog, README and getting-started updates. ### Bump to release candidate version 1) Manually update the plotlywidget version to `A.B.C-rc.1` in the files From 7483fbbdcfc371824f29f3d8fae855037c01c218 Mon Sep 17 00:00:00 2001 From: Emmanuelle Gouillart Date: Mon, 27 Jan 2020 14:17:22 -0500 Subject: [PATCH 03/21] changed order of imports for better error messages (#2132) --- packages/python/plotly/plotly/express/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/python/plotly/plotly/express/__init__.py b/packages/python/plotly/plotly/express/__init__.py index 1d7c10d8cf2..fb334c1b973 100644 --- a/packages/python/plotly/plotly/express/__init__.py +++ b/packages/python/plotly/plotly/express/__init__.py @@ -4,7 +4,6 @@ """ from __future__ import absolute_import from plotly import optional_imports -from ._imshow import imshow pd = optional_imports.get_module("pandas") if pd is None: @@ -13,6 +12,7 @@ Plotly express requires pandas to be installed.""" ) +from ._imshow import imshow from ._chart_types import ( # noqa: F401 scatter, scatter_3d, From d7fc1bc967f67edb101c05eb62f632d4c9275515 Mon Sep 17 00:00:00 2001 From: Nicolas Kruchten Date: Tue, 28 Jan 2020 15:19:42 -0500 Subject: [PATCH 04/21] tweaks to make flake8 happy --- packages/python/plotly/plotly/express/_core.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/python/plotly/plotly/express/_core.py b/packages/python/plotly/plotly/express/_core.py index e94a79d3954..a327f10231e 100644 --- a/packages/python/plotly/plotly/express/_core.py +++ b/packages/python/plotly/plotly/express/_core.py @@ -1015,8 +1015,8 @@ def _check_dataframe_all_leaves(df): null_indices = np.nonzero(null_mask.any(axis=1).values)[0] for null_row_index in null_indices: row = null_mask.iloc[null_row_index] - indices = np.nonzero(row.values)[0] - if not row[indices[0] :].all(): + i = np.nonzero(row.values)[0][0] + if not row[i:].all(): raise ValueError( "None entries cannot have not-None children", df_sorted.iloc[null_row_index], @@ -1058,6 +1058,7 @@ def process_dataframe_hierarchy(args): path = [new_col_name if x == col_name else x for x in path] df[new_col_name] = series_to_copy # ------------ Define aggregation functions -------------------------------- + def aggfunc_discrete(x): uniques = x.unique() if len(uniques) == 1: From e9fa00de208fe3fda34289b2152ac5de4058c3c9 Mon Sep 17 00:00:00 2001 From: Nicolas Kruchten Date: Tue, 28 Jan 2020 15:24:27 -0500 Subject: [PATCH 05/21] g -> trace_data --- .../python/plotly/plotly/express/_core.py | 60 +++++++++++-------- 1 file changed, 34 insertions(+), 26 deletions(-) diff --git a/packages/python/plotly/plotly/express/_core.py b/packages/python/plotly/plotly/express/_core.py index a327f10231e..8e8d2b2ecb6 100644 --- a/packages/python/plotly/plotly/express/_core.py +++ b/packages/python/plotly/plotly/express/_core.py @@ -137,7 +137,7 @@ def make_mapping(args, variable): ) -def make_trace_kwargs(args, trace_spec, g, mapping_labels, sizeref): +def make_trace_kwargs(args, trace_spec, trace_data, mapping_labels, sizeref): """Populates a dict with arguments to update trace Parameters @@ -147,7 +147,7 @@ def make_trace_kwargs(args, trace_spec, g, mapping_labels, sizeref): trace_spec : NamedTuple which kind of trace to be used (has constructor, marginal etc. attributes) - g : pandas DataFrame + trace_data : pandas DataFrame data mapping_labels : dict to be used for hovertemplate @@ -162,7 +162,7 @@ def make_trace_kwargs(args, trace_spec, g, mapping_labels, sizeref): fit information to be used for trendlines """ if "line_close" in args and args["line_close"]: - g = g.append(g.iloc[0]) + trace_data = trace_data.append(trace_data.iloc[0]) result = trace_spec.trace_patch.copy() or {} fit_results = None hover_header = "" @@ -173,7 +173,7 @@ def make_trace_kwargs(args, trace_spec, g, mapping_labels, sizeref): if k == "dimensions": dims = [ (name, column) - for (name, column) in g.iteritems() + for (name, column) in trace_data.iteritems() if ((not v) or (name in v)) and ( trace_spec.constructor != go.Parcoords @@ -207,7 +207,7 @@ def make_trace_kwargs(args, trace_spec, g, mapping_labels, sizeref): if k == "size": if "marker" not in result: result["marker"] = dict() - result["marker"]["size"] = g[v] + result["marker"]["size"] = trace_data[v] result["marker"]["sizemode"] = "area" result["marker"]["sizeref"] = sizeref mapping_labels[v_label] = "%{marker.size}" @@ -218,13 +218,18 @@ def make_trace_kwargs(args, trace_spec, g, mapping_labels, sizeref): if trace_spec.constructor == go.Histogram: mapping_labels["count"] = "%{x}" elif k == "trendline": - if v in ["ols", "lowess"] and args["x"] and args["y"] and len(g) > 1: + if ( + v in ["ols", "lowess"] + and args["x"] + and args["y"] + and len(trace_data) > 1 + ): import statsmodels.api as sm # sorting is bad but trace_specs with "trendline" have no other attrs - g2 = g.sort_values(by=args["x"]) - y = g2[args["y"]] - x = g2[args["x"]] + sorted_trace_data = trace_data.sort_values(by=args["x"]) + y = sorted_trace_data[args["y"]] + x = sorted_trace_data[args["x"]] result["x"] = x if x.dtype.type == np.datetime64: @@ -255,9 +260,9 @@ def make_trace_kwargs(args, trace_spec, g, mapping_labels, sizeref): arr = "arrayminus" if k.endswith("minus") else "array" if error_xy not in result: result[error_xy] = {} - result[error_xy][arr] = g[v] + result[error_xy][arr] = trace_data[v] elif k == "custom_data": - result["customdata"] = g[v].values + result["customdata"] = trace_data[v].values custom_data_len = len(v) # number of custom data columns elif k == "hover_name": if trace_spec.constructor not in [ @@ -265,7 +270,7 @@ def make_trace_kwargs(args, trace_spec, g, mapping_labels, sizeref): go.Histogram2d, go.Histogram2dContour, ]: - result["hovertext"] = g[v] + result["hovertext"] = trace_data[v] if hover_header == "": hover_header = "%{hovertext}

" elif k == "hover_data": @@ -282,15 +287,18 @@ def make_trace_kwargs(args, trace_spec, g, mapping_labels, sizeref): custom_data_len += 1 if "customdata" in result: result["customdata"] = np.hstack( - (result["customdata"], g[col].values[:, None]) + ( + result["customdata"], + trace_data[col].values[:, None], + ) ) else: - result["customdata"] = g[col].values[:, None] + result["customdata"] = trace_data[col].values[:, None] v_label_col = get_decorated_label(args, col, None) mapping_labels[v_label_col] = "%%{customdata[%d]}" % (position) elif k == "color": if trace_spec.constructor in [go.Choropleth, go.Choroplethmapbox]: - result["z"] = g[v] + result["z"] = trace_data[v] result["coloraxis"] = "coloraxis1" mapping_labels[v_label] = "%{z}" elif trace_spec.constructor in [ @@ -303,13 +311,13 @@ def make_trace_kwargs(args, trace_spec, g, mapping_labels, sizeref): result["marker"] = dict() if args.get("color_is_continuous"): - result["marker"]["colors"] = g[v] + result["marker"]["colors"] = trace_data[v] result["marker"]["coloraxis"] = "coloraxis1" mapping_labels[v_label] = "%{color}" else: result["marker"]["colors"] = [] mapping = {} - for cat in g[v]: + for cat in trace_data[v]: if mapping.get(cat) is None: mapping[cat] = args["color_discrete_sequence"][ len(mapping) % len(args["color_discrete_sequence"]) @@ -321,24 +329,24 @@ def make_trace_kwargs(args, trace_spec, g, mapping_labels, sizeref): colorable = "line" if colorable not in result: result[colorable] = dict() - result[colorable]["color"] = g[v] + result[colorable]["color"] = trace_data[v] result[colorable]["coloraxis"] = "coloraxis1" mapping_labels[v_label] = "%%{%s.color}" % colorable elif k == "animation_group": - result["ids"] = g[v] + result["ids"] = trace_data[v] elif k == "locations": - result[k] = g[v] + result[k] = trace_data[v] mapping_labels[v_label] = "%{location}" elif k == "values": - result[k] = g[v] + result[k] = trace_data[v] _label = "value" if v_label == "values" else v_label mapping_labels[_label] = "%{value}" elif k == "parents": - result[k] = g[v] + result[k] = trace_data[v] _label = "parent" if v_label == "parents" else v_label mapping_labels[_label] = "%{parent}" elif k == "ids": - result[k] = g[v] + result[k] = trace_data[v] _label = "id" if v_label == "ids" else v_label mapping_labels[_label] = "%{id}" elif k == "names": @@ -348,14 +356,14 @@ def make_trace_kwargs(args, trace_spec, g, mapping_labels, sizeref): go.Pie, go.Funnelarea, ]: - result["labels"] = g[v] + result["labels"] = trace_data[v] _label = "label" if v_label == "names" else v_label mapping_labels[_label] = "%{label}" else: - result[k] = g[v] + result[k] = trace_data[v] else: if v: - result[k] = g[v] + result[k] = trace_data[v] mapping_labels[v_label] = "%%{%s}" % k if trace_spec.constructor not in [ go.Parcoords, From ca8735394ae79c21b88c1fca2989a15af5d4bbad Mon Sep 17 00:00:00 2001 From: Nicolas Kruchten Date: Tue, 28 Jan 2020 15:26:01 -0500 Subject: [PATCH 06/21] k -> attr_name --- .../python/plotly/plotly/express/_core.py | 60 +++++++++---------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/packages/python/plotly/plotly/express/_core.py b/packages/python/plotly/plotly/express/_core.py index 8e8d2b2ecb6..0340002908f 100644 --- a/packages/python/plotly/plotly/express/_core.py +++ b/packages/python/plotly/plotly/express/_core.py @@ -167,10 +167,10 @@ def make_trace_kwargs(args, trace_spec, trace_data, mapping_labels, sizeref): fit_results = None hover_header = "" custom_data_len = 0 - for k in trace_spec.attrs: - v = args[k] - v_label = get_decorated_label(args, v, k) - if k == "dimensions": + for attr_name in trace_spec.attrs: + v = args[attr_name] + v_label = get_decorated_label(args, v, attr_name) + if attr_name == "dimensions": dims = [ (name, column) for (name, column) in trace_data.iteritems() @@ -198,26 +198,26 @@ def make_trace_kwargs(args, trace_spec, trace_data, mapping_labels, sizeref): elif ( v is not None - or (trace_spec.constructor == go.Histogram and k in ["x", "y"]) + or (trace_spec.constructor == go.Histogram and attr_name in ["x", "y"]) or ( trace_spec.constructor in [go.Histogram2d, go.Histogram2dContour] - and k == "z" + and attr_name == "z" ) ): - if k == "size": + if attr_name == "size": if "marker" not in result: result["marker"] = dict() result["marker"]["size"] = trace_data[v] result["marker"]["sizemode"] = "area" result["marker"]["sizeref"] = sizeref mapping_labels[v_label] = "%{marker.size}" - elif k == "marginal_x": + elif attr_name == "marginal_x": if trace_spec.constructor == go.Histogram: mapping_labels["count"] = "%{y}" - elif k == "marginal_y": + elif attr_name == "marginal_y": if trace_spec.constructor == go.Histogram: mapping_labels["count"] = "%{x}" - elif k == "trendline": + elif attr_name == "trendline": if ( v in ["ols", "lowess"] and args["x"] @@ -255,16 +255,16 @@ def make_trace_kwargs(args, trace_spec, trace_data, mapping_labels, sizeref): mapping_labels[get_label(args, args["x"])] = "%{x}" mapping_labels[get_label(args, args["y"])] = "%{y} (trend)" - elif k.startswith("error"): - error_xy = k[:7] - arr = "arrayminus" if k.endswith("minus") else "array" + elif attr_name.startswith("error"): + error_xy = attr_name[:7] + arr = "arrayminus" if attr_name.endswith("minus") else "array" if error_xy not in result: result[error_xy] = {} result[error_xy][arr] = trace_data[v] - elif k == "custom_data": + elif attr_name == "custom_data": result["customdata"] = trace_data[v].values custom_data_len = len(v) # number of custom data columns - elif k == "hover_name": + elif attr_name == "hover_name": if trace_spec.constructor not in [ go.Histogram, go.Histogram2d, @@ -273,7 +273,7 @@ def make_trace_kwargs(args, trace_spec, trace_data, mapping_labels, sizeref): result["hovertext"] = trace_data[v] if hover_header == "": hover_header = "%{hovertext}

" - elif k == "hover_data": + elif attr_name == "hover_data": if trace_spec.constructor not in [ go.Histogram, go.Histogram2d, @@ -296,7 +296,7 @@ def make_trace_kwargs(args, trace_spec, trace_data, mapping_labels, sizeref): result["customdata"] = trace_data[col].values[:, None] v_label_col = get_decorated_label(args, col, None) mapping_labels[v_label_col] = "%%{customdata[%d]}" % (position) - elif k == "color": + elif attr_name == "color": if trace_spec.constructor in [go.Choropleth, go.Choroplethmapbox]: result["z"] = trace_data[v] result["coloraxis"] = "coloraxis1" @@ -332,24 +332,24 @@ def make_trace_kwargs(args, trace_spec, trace_data, mapping_labels, sizeref): result[colorable]["color"] = trace_data[v] result[colorable]["coloraxis"] = "coloraxis1" mapping_labels[v_label] = "%%{%s.color}" % colorable - elif k == "animation_group": + elif attr_name == "animation_group": result["ids"] = trace_data[v] - elif k == "locations": - result[k] = trace_data[v] + elif attr_name == "locations": + result[attr_name] = trace_data[v] mapping_labels[v_label] = "%{location}" - elif k == "values": - result[k] = trace_data[v] + elif attr_name == "values": + result[attr_name] = trace_data[v] _label = "value" if v_label == "values" else v_label mapping_labels[_label] = "%{value}" - elif k == "parents": - result[k] = trace_data[v] + elif attr_name == "parents": + result[attr_name] = trace_data[v] _label = "parent" if v_label == "parents" else v_label mapping_labels[_label] = "%{parent}" - elif k == "ids": - result[k] = trace_data[v] + elif attr_name == "ids": + result[attr_name] = trace_data[v] _label = "id" if v_label == "ids" else v_label mapping_labels[_label] = "%{id}" - elif k == "names": + elif attr_name == "names": if trace_spec.constructor in [ go.Sunburst, go.Treemap, @@ -360,11 +360,11 @@ def make_trace_kwargs(args, trace_spec, trace_data, mapping_labels, sizeref): _label = "label" if v_label == "names" else v_label mapping_labels[_label] = "%{label}" else: - result[k] = trace_data[v] + result[attr_name] = trace_data[v] else: if v: - result[k] = trace_data[v] - mapping_labels[v_label] = "%%{%s}" % k + result[attr_name] = trace_data[v] + mapping_labels[v_label] = "%%{%s}" % attr_name if trace_spec.constructor not in [ go.Parcoords, go.Parcats, From e7ced12d10be20c7d61516798722dbb23d43cb41 Mon Sep 17 00:00:00 2001 From: Nicolas Kruchten Date: Tue, 28 Jan 2020 15:27:53 -0500 Subject: [PATCH 07/21] v -> attr_value --- .../python/plotly/plotly/express/_core.py | 54 +++++++++---------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/packages/python/plotly/plotly/express/_core.py b/packages/python/plotly/plotly/express/_core.py index 0340002908f..1e0b2690878 100644 --- a/packages/python/plotly/plotly/express/_core.py +++ b/packages/python/plotly/plotly/express/_core.py @@ -168,20 +168,20 @@ def make_trace_kwargs(args, trace_spec, trace_data, mapping_labels, sizeref): hover_header = "" custom_data_len = 0 for attr_name in trace_spec.attrs: - v = args[attr_name] - v_label = get_decorated_label(args, v, attr_name) + attr_value = args[attr_name] + v_label = get_decorated_label(args, attr_value, attr_name) if attr_name == "dimensions": dims = [ (name, column) for (name, column) in trace_data.iteritems() - if ((not v) or (name in v)) + if ((not attr_value) or (name in attr_value)) and ( trace_spec.constructor != go.Parcoords or args["data_frame"][name].dtype.kind in "bifc" ) and ( trace_spec.constructor != go.Parcats - or (v is not None and name in v) + or (attr_value is not None and name in attr_value) or len(args["data_frame"][name].unique()) <= args["dimensions_max_cardinality"] ) @@ -197,7 +197,7 @@ def make_trace_kwargs(args, trace_spec, trace_data, mapping_labels, sizeref): mapping_labels["%{yaxis.title.text}"] = "%{y}" elif ( - v is not None + attr_value is not None or (trace_spec.constructor == go.Histogram and attr_name in ["x", "y"]) or ( trace_spec.constructor in [go.Histogram2d, go.Histogram2dContour] @@ -207,7 +207,7 @@ def make_trace_kwargs(args, trace_spec, trace_data, mapping_labels, sizeref): if attr_name == "size": if "marker" not in result: result["marker"] = dict() - result["marker"]["size"] = trace_data[v] + result["marker"]["size"] = trace_data[attr_value] result["marker"]["sizemode"] = "area" result["marker"]["sizeref"] = sizeref mapping_labels[v_label] = "%{marker.size}" @@ -219,7 +219,7 @@ def make_trace_kwargs(args, trace_spec, trace_data, mapping_labels, sizeref): mapping_labels["count"] = "%{x}" elif attr_name == "trendline": if ( - v in ["ols", "lowess"] + attr_value in ["ols", "lowess"] and args["x"] and args["y"] and len(trace_data) > 1 @@ -235,11 +235,11 @@ def make_trace_kwargs(args, trace_spec, trace_data, mapping_labels, sizeref): if x.dtype.type == np.datetime64: x = x.astype(int) / 10 ** 9 # convert to unix epoch seconds - if v == "lowess": + if attr_value == "lowess": trendline = sm.nonparametric.lowess(y, x) result["y"] = trendline[:, 1] hover_header = "LOWESS trendline

" - elif v == "ols": + elif attr_value == "ols": fit_results = sm.OLS(y.values, sm.add_constant(x.values)).fit() result["y"] = fit_results.predict() hover_header = "OLS trendline
" @@ -260,17 +260,17 @@ def make_trace_kwargs(args, trace_spec, trace_data, mapping_labels, sizeref): arr = "arrayminus" if attr_name.endswith("minus") else "array" if error_xy not in result: result[error_xy] = {} - result[error_xy][arr] = trace_data[v] + result[error_xy][arr] = trace_data[attr_value] elif attr_name == "custom_data": - result["customdata"] = trace_data[v].values - custom_data_len = len(v) # number of custom data columns + result["customdata"] = trace_data[attr_value].values + custom_data_len = len(attr_value) # number of custom data columns elif attr_name == "hover_name": if trace_spec.constructor not in [ go.Histogram, go.Histogram2d, go.Histogram2dContour, ]: - result["hovertext"] = trace_data[v] + result["hovertext"] = trace_data[attr_value] if hover_header == "": hover_header = "%{hovertext}

" elif attr_name == "hover_data": @@ -279,7 +279,7 @@ def make_trace_kwargs(args, trace_spec, trace_data, mapping_labels, sizeref): go.Histogram2d, go.Histogram2dContour, ]: - for col in v: + for col in attr_value: try: position = args["custom_data"].index(col) except (ValueError, AttributeError, KeyError): @@ -298,7 +298,7 @@ def make_trace_kwargs(args, trace_spec, trace_data, mapping_labels, sizeref): mapping_labels[v_label_col] = "%%{customdata[%d]}" % (position) elif attr_name == "color": if trace_spec.constructor in [go.Choropleth, go.Choroplethmapbox]: - result["z"] = trace_data[v] + result["z"] = trace_data[attr_value] result["coloraxis"] = "coloraxis1" mapping_labels[v_label] = "%{z}" elif trace_spec.constructor in [ @@ -311,13 +311,13 @@ def make_trace_kwargs(args, trace_spec, trace_data, mapping_labels, sizeref): result["marker"] = dict() if args.get("color_is_continuous"): - result["marker"]["colors"] = trace_data[v] + result["marker"]["colors"] = trace_data[attr_value] result["marker"]["coloraxis"] = "coloraxis1" mapping_labels[v_label] = "%{color}" else: result["marker"]["colors"] = [] mapping = {} - for cat in trace_data[v]: + for cat in trace_data[attr_value]: if mapping.get(cat) is None: mapping[cat] = args["color_discrete_sequence"][ len(mapping) % len(args["color_discrete_sequence"]) @@ -329,24 +329,24 @@ def make_trace_kwargs(args, trace_spec, trace_data, mapping_labels, sizeref): colorable = "line" if colorable not in result: result[colorable] = dict() - result[colorable]["color"] = trace_data[v] + result[colorable]["color"] = trace_data[attr_value] result[colorable]["coloraxis"] = "coloraxis1" mapping_labels[v_label] = "%%{%s.color}" % colorable elif attr_name == "animation_group": - result["ids"] = trace_data[v] + result["ids"] = trace_data[attr_value] elif attr_name == "locations": - result[attr_name] = trace_data[v] + result[attr_name] = trace_data[attr_value] mapping_labels[v_label] = "%{location}" elif attr_name == "values": - result[attr_name] = trace_data[v] + result[attr_name] = trace_data[attr_value] _label = "value" if v_label == "values" else v_label mapping_labels[_label] = "%{value}" elif attr_name == "parents": - result[attr_name] = trace_data[v] + result[attr_name] = trace_data[attr_value] _label = "parent" if v_label == "parents" else v_label mapping_labels[_label] = "%{parent}" elif attr_name == "ids": - result[attr_name] = trace_data[v] + result[attr_name] = trace_data[attr_value] _label = "id" if v_label == "ids" else v_label mapping_labels[_label] = "%{id}" elif attr_name == "names": @@ -356,14 +356,14 @@ def make_trace_kwargs(args, trace_spec, trace_data, mapping_labels, sizeref): go.Pie, go.Funnelarea, ]: - result["labels"] = trace_data[v] + result["labels"] = trace_data[attr_value] _label = "label" if v_label == "names" else v_label mapping_labels[_label] = "%{label}" else: - result[attr_name] = trace_data[v] + result[attr_name] = trace_data[attr_value] else: - if v: - result[attr_name] = trace_data[v] + if attr_value: + result[attr_name] = trace_data[attr_value] mapping_labels[v_label] = "%%{%s}" % attr_name if trace_spec.constructor not in [ go.Parcoords, From 94303bcab58fa14cfa3298cc44cbd5ef4d3089b8 Mon Sep 17 00:00:00 2001 From: Nicolas Kruchten Date: Tue, 28 Jan 2020 15:28:43 -0500 Subject: [PATCH 08/21] v_label -> attr_label --- .../python/plotly/plotly/express/_core.py | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/packages/python/plotly/plotly/express/_core.py b/packages/python/plotly/plotly/express/_core.py index 1e0b2690878..ec332e6d6bd 100644 --- a/packages/python/plotly/plotly/express/_core.py +++ b/packages/python/plotly/plotly/express/_core.py @@ -169,7 +169,7 @@ def make_trace_kwargs(args, trace_spec, trace_data, mapping_labels, sizeref): custom_data_len = 0 for attr_name in trace_spec.attrs: attr_value = args[attr_name] - v_label = get_decorated_label(args, attr_value, attr_name) + attr_label = get_decorated_label(args, attr_value, attr_name) if attr_name == "dimensions": dims = [ (name, column) @@ -210,7 +210,7 @@ def make_trace_kwargs(args, trace_spec, trace_data, mapping_labels, sizeref): result["marker"]["size"] = trace_data[attr_value] result["marker"]["sizemode"] = "area" result["marker"]["sizeref"] = sizeref - mapping_labels[v_label] = "%{marker.size}" + mapping_labels[attr_label] = "%{marker.size}" elif attr_name == "marginal_x": if trace_spec.constructor == go.Histogram: mapping_labels["count"] = "%{y}" @@ -294,13 +294,15 @@ def make_trace_kwargs(args, trace_spec, trace_data, mapping_labels, sizeref): ) else: result["customdata"] = trace_data[col].values[:, None] - v_label_col = get_decorated_label(args, col, None) - mapping_labels[v_label_col] = "%%{customdata[%d]}" % (position) + attr_label_col = get_decorated_label(args, col, None) + mapping_labels[attr_label_col] = "%%{customdata[%d]}" % ( + position + ) elif attr_name == "color": if trace_spec.constructor in [go.Choropleth, go.Choroplethmapbox]: result["z"] = trace_data[attr_value] result["coloraxis"] = "coloraxis1" - mapping_labels[v_label] = "%{z}" + mapping_labels[attr_label] = "%{z}" elif trace_spec.constructor in [ go.Sunburst, go.Treemap, @@ -313,7 +315,7 @@ def make_trace_kwargs(args, trace_spec, trace_data, mapping_labels, sizeref): if args.get("color_is_continuous"): result["marker"]["colors"] = trace_data[attr_value] result["marker"]["coloraxis"] = "coloraxis1" - mapping_labels[v_label] = "%{color}" + mapping_labels[attr_label] = "%{color}" else: result["marker"]["colors"] = [] mapping = {} @@ -331,23 +333,23 @@ def make_trace_kwargs(args, trace_spec, trace_data, mapping_labels, sizeref): result[colorable] = dict() result[colorable]["color"] = trace_data[attr_value] result[colorable]["coloraxis"] = "coloraxis1" - mapping_labels[v_label] = "%%{%s.color}" % colorable + mapping_labels[attr_label] = "%%{%s.color}" % colorable elif attr_name == "animation_group": result["ids"] = trace_data[attr_value] elif attr_name == "locations": result[attr_name] = trace_data[attr_value] - mapping_labels[v_label] = "%{location}" + mapping_labels[attr_label] = "%{location}" elif attr_name == "values": result[attr_name] = trace_data[attr_value] - _label = "value" if v_label == "values" else v_label + _label = "value" if attr_label == "values" else attr_label mapping_labels[_label] = "%{value}" elif attr_name == "parents": result[attr_name] = trace_data[attr_value] - _label = "parent" if v_label == "parents" else v_label + _label = "parent" if attr_label == "parents" else attr_label mapping_labels[_label] = "%{parent}" elif attr_name == "ids": result[attr_name] = trace_data[attr_value] - _label = "id" if v_label == "ids" else v_label + _label = "id" if attr_label == "ids" else attr_label mapping_labels[_label] = "%{id}" elif attr_name == "names": if trace_spec.constructor in [ @@ -357,14 +359,14 @@ def make_trace_kwargs(args, trace_spec, trace_data, mapping_labels, sizeref): go.Funnelarea, ]: result["labels"] = trace_data[attr_value] - _label = "label" if v_label == "names" else v_label + _label = "label" if attr_label == "names" else attr_label mapping_labels[_label] = "%{label}" else: result[attr_name] = trace_data[attr_value] else: if attr_value: result[attr_name] = trace_data[attr_value] - mapping_labels[v_label] = "%%{%s}" % attr_name + mapping_labels[attr_label] = "%%{%s}" % attr_name if trace_spec.constructor not in [ go.Parcoords, go.Parcats, From 95f78c2053cd1594e9dc7569558cf7addd3b2467 Mon Sep 17 00:00:00 2001 From: Nicolas Kruchten Date: Tue, 28 Jan 2020 15:30:56 -0500 Subject: [PATCH 09/21] result -> trace_patch --- .../python/plotly/plotly/express/_core.py | 88 ++++++++++--------- 1 file changed, 45 insertions(+), 43 deletions(-) diff --git a/packages/python/plotly/plotly/express/_core.py b/packages/python/plotly/plotly/express/_core.py index ec332e6d6bd..dd670c26d40 100644 --- a/packages/python/plotly/plotly/express/_core.py +++ b/packages/python/plotly/plotly/express/_core.py @@ -156,14 +156,14 @@ def make_trace_kwargs(args, trace_spec, trace_data, mapping_labels, sizeref): Returns ------- - result : dict + trace_patch : dict dict to be used to update trace fit_results : dict fit information to be used for trendlines """ if "line_close" in args and args["line_close"]: trace_data = trace_data.append(trace_data.iloc[0]) - result = trace_spec.trace_patch.copy() or {} + trace_patch = trace_spec.trace_patch.copy() or {} fit_results = None hover_header = "" custom_data_len = 0 @@ -186,12 +186,12 @@ def make_trace_kwargs(args, trace_spec, trace_data, mapping_labels, sizeref): <= args["dimensions_max_cardinality"] ) ] - result["dimensions"] = [ + trace_patch["dimensions"] = [ dict(label=get_label(args, name), values=column.values) for (name, column) in dims ] if trace_spec.constructor == go.Splom: - for d in result["dimensions"]: + for d in trace_patch["dimensions"]: d["axis"] = dict(matches=True) mapping_labels["%{xaxis.title.text}"] = "%{x}" mapping_labels["%{yaxis.title.text}"] = "%{y}" @@ -205,11 +205,11 @@ def make_trace_kwargs(args, trace_spec, trace_data, mapping_labels, sizeref): ) ): if attr_name == "size": - if "marker" not in result: - result["marker"] = dict() - result["marker"]["size"] = trace_data[attr_value] - result["marker"]["sizemode"] = "area" - result["marker"]["sizeref"] = sizeref + if "marker" not in trace_patch: + trace_patch["marker"] = dict() + trace_patch["marker"]["size"] = trace_data[attr_value] + trace_patch["marker"]["sizemode"] = "area" + trace_patch["marker"]["sizeref"] = sizeref mapping_labels[attr_label] = "%{marker.size}" elif attr_name == "marginal_x": if trace_spec.constructor == go.Histogram: @@ -230,18 +230,18 @@ def make_trace_kwargs(args, trace_spec, trace_data, mapping_labels, sizeref): sorted_trace_data = trace_data.sort_values(by=args["x"]) y = sorted_trace_data[args["y"]] x = sorted_trace_data[args["x"]] - result["x"] = x + trace_patch["x"] = x if x.dtype.type == np.datetime64: x = x.astype(int) / 10 ** 9 # convert to unix epoch seconds if attr_value == "lowess": trendline = sm.nonparametric.lowess(y, x) - result["y"] = trendline[:, 1] + trace_patch["y"] = trendline[:, 1] hover_header = "LOWESS trendline

" elif attr_value == "ols": fit_results = sm.OLS(y.values, sm.add_constant(x.values)).fit() - result["y"] = fit_results.predict() + trace_patch["y"] = fit_results.predict() hover_header = "OLS trendline
" hover_header += "%s = %g * %s + %g
" % ( args["y"], @@ -258,11 +258,11 @@ def make_trace_kwargs(args, trace_spec, trace_data, mapping_labels, sizeref): elif attr_name.startswith("error"): error_xy = attr_name[:7] arr = "arrayminus" if attr_name.endswith("minus") else "array" - if error_xy not in result: - result[error_xy] = {} - result[error_xy][arr] = trace_data[attr_value] + if error_xy not in trace_patch: + trace_patch[error_xy] = {} + trace_patch[error_xy][arr] = trace_data[attr_value] elif attr_name == "custom_data": - result["customdata"] = trace_data[attr_value].values + trace_patch["customdata"] = trace_data[attr_value].values custom_data_len = len(attr_value) # number of custom data columns elif attr_name == "hover_name": if trace_spec.constructor not in [ @@ -270,7 +270,7 @@ def make_trace_kwargs(args, trace_spec, trace_data, mapping_labels, sizeref): go.Histogram2d, go.Histogram2dContour, ]: - result["hovertext"] = trace_data[attr_value] + trace_patch["hovertext"] = trace_data[attr_value] if hover_header == "": hover_header = "%{hovertext}

" elif attr_name == "hover_data": @@ -285,23 +285,25 @@ def make_trace_kwargs(args, trace_spec, trace_data, mapping_labels, sizeref): except (ValueError, AttributeError, KeyError): position = custom_data_len custom_data_len += 1 - if "customdata" in result: - result["customdata"] = np.hstack( + if "customdata" in trace_patch: + trace_patch["customdata"] = np.hstack( ( - result["customdata"], + trace_patch["customdata"], trace_data[col].values[:, None], ) ) else: - result["customdata"] = trace_data[col].values[:, None] + trace_patch["customdata"] = trace_data[col].values[ + :, None + ] attr_label_col = get_decorated_label(args, col, None) mapping_labels[attr_label_col] = "%%{customdata[%d]}" % ( position ) elif attr_name == "color": if trace_spec.constructor in [go.Choropleth, go.Choroplethmapbox]: - result["z"] = trace_data[attr_value] - result["coloraxis"] = "coloraxis1" + trace_patch["z"] = trace_data[attr_value] + trace_patch["coloraxis"] = "coloraxis1" mapping_labels[attr_label] = "%{z}" elif trace_spec.constructor in [ go.Sunburst, @@ -309,46 +311,46 @@ def make_trace_kwargs(args, trace_spec, trace_data, mapping_labels, sizeref): go.Pie, go.Funnelarea, ]: - if "marker" not in result: - result["marker"] = dict() + if "marker" not in trace_patch: + trace_patch["marker"] = dict() if args.get("color_is_continuous"): - result["marker"]["colors"] = trace_data[attr_value] - result["marker"]["coloraxis"] = "coloraxis1" + trace_patch["marker"]["colors"] = trace_data[attr_value] + trace_patch["marker"]["coloraxis"] = "coloraxis1" mapping_labels[attr_label] = "%{color}" else: - result["marker"]["colors"] = [] + trace_patch["marker"]["colors"] = [] mapping = {} for cat in trace_data[attr_value]: if mapping.get(cat) is None: mapping[cat] = args["color_discrete_sequence"][ len(mapping) % len(args["color_discrete_sequence"]) ] - result["marker"]["colors"].append(mapping[cat]) + trace_patch["marker"]["colors"].append(mapping[cat]) else: colorable = "marker" if trace_spec.constructor in [go.Parcats, go.Parcoords]: colorable = "line" - if colorable not in result: - result[colorable] = dict() - result[colorable]["color"] = trace_data[attr_value] - result[colorable]["coloraxis"] = "coloraxis1" + if colorable not in trace_patch: + trace_patch[colorable] = dict() + trace_patch[colorable]["color"] = trace_data[attr_value] + trace_patch[colorable]["coloraxis"] = "coloraxis1" mapping_labels[attr_label] = "%%{%s.color}" % colorable elif attr_name == "animation_group": - result["ids"] = trace_data[attr_value] + trace_patch["ids"] = trace_data[attr_value] elif attr_name == "locations": - result[attr_name] = trace_data[attr_value] + trace_patch[attr_name] = trace_data[attr_value] mapping_labels[attr_label] = "%{location}" elif attr_name == "values": - result[attr_name] = trace_data[attr_value] + trace_patch[attr_name] = trace_data[attr_value] _label = "value" if attr_label == "values" else attr_label mapping_labels[_label] = "%{value}" elif attr_name == "parents": - result[attr_name] = trace_data[attr_value] + trace_patch[attr_name] = trace_data[attr_value] _label = "parent" if attr_label == "parents" else attr_label mapping_labels[_label] = "%{parent}" elif attr_name == "ids": - result[attr_name] = trace_data[attr_value] + trace_patch[attr_name] = trace_data[attr_value] _label = "id" if attr_label == "ids" else attr_label mapping_labels[_label] = "%{id}" elif attr_name == "names": @@ -358,22 +360,22 @@ def make_trace_kwargs(args, trace_spec, trace_data, mapping_labels, sizeref): go.Pie, go.Funnelarea, ]: - result["labels"] = trace_data[attr_value] + trace_patch["labels"] = trace_data[attr_value] _label = "label" if attr_label == "names" else attr_label mapping_labels[_label] = "%{label}" else: - result[attr_name] = trace_data[attr_value] + trace_patch[attr_name] = trace_data[attr_value] else: if attr_value: - result[attr_name] = trace_data[attr_value] + trace_patch[attr_name] = trace_data[attr_value] mapping_labels[attr_label] = "%%{%s}" % attr_name if trace_spec.constructor not in [ go.Parcoords, go.Parcats, ]: hover_lines = [k + "=" + v for k, v in mapping_labels.items()] - result["hovertemplate"] = hover_header + "
".join(hover_lines) - return result, fit_results + trace_patch["hovertemplate"] = hover_header + "
".join(hover_lines) + return trace_patch, fit_results def configure_axes(args, constructor, fig, orders): From f7dc2bee8e668459b2f8c3d9a01ac4c80e9ecd63 Mon Sep 17 00:00:00 2001 From: Emmanuelle Gouillart Date: Mon, 3 Feb 2020 22:45:01 -0500 Subject: [PATCH 10/21] Sunburst improvements (#2133) * color column now appears in hover * corrected bug: path column can be numeric --- packages/python/plotly/plotly/express/_core.py | 11 +++++++++-- .../tests/test_core/test_px/test_px_functions.py | 14 +++++++++++--- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/packages/python/plotly/plotly/express/_core.py b/packages/python/plotly/plotly/express/_core.py index dd670c26d40..db8f002c2c1 100644 --- a/packages/python/plotly/plotly/express/_core.py +++ b/packages/python/plotly/plotly/express/_core.py @@ -1024,6 +1024,7 @@ def build_dataframe(args, attrables, array_attrables): def _check_dataframe_all_leaves(df): df_sorted = df.sort_values(by=list(df.columns)) null_mask = df_sorted.isnull() + df_sorted = df_sorted.astype(str) null_indices = np.nonzero(null_mask.any(axis=1).values)[0] for null_row_index in null_indices: row = null_mask.iloc[null_row_index] @@ -1055,8 +1056,9 @@ def process_dataframe_hierarchy(args): if args["color"] and args["color"] in path: series_to_copy = df[args["color"]] - args["color"] = str(args["color"]) + "additional_col_for_px" - df[args["color"]] = series_to_copy + new_col_name = args["color"] + "additional_col_for_color" + path = [new_col_name if x == args["color"] else x for x in path] + df[new_col_name] = series_to_copy if args["hover_data"]: for col_name in args["hover_data"]: if col_name == args["color"]: @@ -1160,6 +1162,11 @@ def aggfunc_continuous(x): args["ids"] = "id" args["names"] = "labels" args["parents"] = "parent" + if args["color"]: + if not args["hover_data"]: + args["hover_data"] = [args["color"]] + else: + args["hover_data"].append(args["color"]) return args diff --git a/packages/python/plotly/plotly/tests/test_core/test_px/test_px_functions.py b/packages/python/plotly/plotly/tests/test_core/test_px/test_px_functions.py index 0393931af11..0fc38c94d4d 100644 --- a/packages/python/plotly/plotly/tests/test_core/test_px/test_px_functions.py +++ b/packages/python/plotly/plotly/tests/test_core/test_px/test_px_functions.py @@ -209,14 +209,22 @@ def test_sunburst_treemap_with_path_color(): # Hover info df["hover"] = [el.lower() for el in vendors] fig = px.sunburst(df, path=path, color="calls", hover_data=["hover"]) - custom = fig.data[0].customdata.ravel() - assert np.all(custom[:8] == df["hover"]) - assert np.all(custom[8:] == "(?)") + custom = fig.data[0].customdata + assert np.all(custom[:8, 0] == df["hover"]) + assert np.all(custom[8:, 0] == "(?)") + assert np.all(custom[:8, 1] == df["calls"]) # Discrete color fig = px.sunburst(df, path=path, color="vendors") assert len(np.unique(fig.data[0].marker.colors)) == 9 + # Numerical column in path + df["regions"] = df["regions"].map({"North": 1, "South": 2}) + path = ["total", "regions", "sectors", "vendors"] + fig = px.sunburst(df, path=path, values="values", color="calls") + colors = fig.data[0].marker.colors + assert np.all(np.array(colors[:8]) == np.array(calls)) + def test_sunburst_treemap_with_path_non_rectangular(): vendors = ["A", "B", "C", "D", None, "E", "F", "G", "H", None] From 9fb88c16e2f0993da35a34ebc8663f545bcaf605 Mon Sep 17 00:00:00 2001 From: Emmanuelle Gouillart Date: Tue, 11 Feb 2020 14:52:17 -0500 Subject: [PATCH 11/21] Merge doc prod2 (#2179) * switch thumbnail for ternary plots doc to ternary scatter plot * Actually display the tidy table (#2136) * target the new graphing-library-docs repo * finally: how to style px * Datashader tutorial (#2154) * datashader tutorial * added requirements * CI fixup * icon + links Co-authored-by: Nicolas Kruchten * cross-link px styling * more links Co-authored-by: Joseph Damiba Co-authored-by: Nicolas Kruchten Co-authored-by: Nikolas Havrikov --- .circleci/config.yml | 10 +- binder/requirements.txt | 2 + doc/python/3d-scatter-plots.md | 2 +- doc/python/bar-charts.md | 4 +- doc/python/box-plots.md | 29 ++-- doc/python/bubble-charts.md | 2 +- doc/python/bubble-maps.md | 2 +- doc/python/choropleth-maps.md | 2 +- doc/python/datashader.md | 129 +++++++++++++++++ doc/python/distplot.md | 2 +- doc/python/dot-plots.md | 7 +- doc/python/error-bars.md | 2 +- doc/python/filled-area-plots.md | 2 +- doc/python/funnel-charts.md | 12 +- doc/python/heatmaps.md | 5 + doc/python/histograms.md | 2 +- doc/python/horizontal-bar-charts.md | 2 +- doc/python/imshow.md | 7 + doc/python/line-and-scatter.md | 2 +- doc/python/line-charts.md | 2 +- doc/python/linear-fits.md | 2 +- doc/python/lines-on-maps.md | 2 +- doc/python/mapbox-county-choropleth.md | 2 +- doc/python/mapbox-density-heatmaps.md | 13 +- doc/python/mapbox-layers.md | 4 + doc/python/parallel-coordinates-plot.md | 2 +- doc/python/pie-charts.md | 16 +-- doc/python/plotly-express.md | 45 ++---- doc/python/polar-chart.md | 2 +- doc/python/px-arguments.md | 1 + doc/python/radar-chart.md | 8 +- doc/python/scatter-plots-on-maps.md | 2 +- doc/python/scattermapbox.md | 2 +- doc/python/splom.md | 2 +- doc/python/styling-plotly-express.md | 183 ++++++++++++++++++++++++ doc/python/sunburst-charts.md | 23 +-- doc/python/ternary-plots.md | 2 +- doc/python/treemaps.md | 29 ++-- doc/python/violin.md | 2 +- doc/python/webgl-vs-svg.md | 4 + doc/python/wind-rose-charts.md | 2 +- doc/requirements.txt | 2 + 42 files changed, 442 insertions(+), 135 deletions(-) create mode 100644 doc/python/datashader.md create mode 100644 doc/python/styling-plotly-express.md diff --git a/.circleci/config.yml b/.circleci/config.yml index 151471f0f3a..5e58717cc4a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -425,8 +425,8 @@ jobs: . venv/bin/activate echo ${mapbox_token} > python/.mapbox_token make -kj8 || make -kj8 - curl https://raw.githubusercontent.com/plotly/documentation/source-design-merge/front-matter-ci.py > front-matter-ci.py - curl https://raw.githubusercontent.com/plotly/documentation/source-design-merge/check-or-enforce-order.py > check-or-enforce-order.py + curl https://raw.githubusercontent.com/plotly/graphing-library-docs/master/front-matter-ci.py > front-matter-ci.py + curl https://raw.githubusercontent.com/plotly/graphing-library-docs/master/check-or-enforce-order.py > check-or-enforce-order.py python front-matter-ci.py build/html python check-or-enforce-order.py build/html if [ "${CIRCLE_BRANCH}" == "doc-prod" ]; then @@ -448,14 +448,14 @@ jobs: name: trigger doc build command: | if [ "${CIRCLE_BRANCH}" == "doc-prod" ]; then - git clone --depth=1 --branch=source-design-merge https://github.com/plotly/documentation.git - cd documentation + git clone --depth=1 https://github.com/plotly/graphing-library-docs.git + cd graphing-library-docs git config user.name plotlydocbot git config user.email accounts@plot.ly git commit --allow-empty -m "deploying https://github.com/plotly/plotly.py/commit/${CIRCLE_SHA1}" git push cd .. - rm -rf documentation + rm -rf graphing-library-docs fi - run: diff --git a/binder/requirements.txt b/binder/requirements.txt index 52868c42d97..d59fb34283f 100644 --- a/binder/requirements.txt +++ b/binder/requirements.txt @@ -12,3 +12,5 @@ psutil requests networkx scikit-image +datashader +pyarrow diff --git a/doc/python/3d-scatter-plots.md b/doc/python/3d-scatter-plots.md index 0d5beba1bb8..e102cac7fe4 100644 --- a/doc/python/3d-scatter-plots.md +++ b/doc/python/3d-scatter-plots.md @@ -35,7 +35,7 @@ jupyter: ## 3D scatter plot with Plotly Express -[Plotly Express](/python/plotly-express/) is the easy-to-use, high-level interface to Plotly, which [operates on "tidy" data](/python/px-arguments/). +[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/). Like the [2D scatter plot](https://plot.ly/python/line-and-scatter/) `px.scatter`, the 3D function `px.scatter_3d` plots individual data in three-dimensional space. diff --git a/doc/python/bar-charts.md b/doc/python/bar-charts.md index 883140cb7e3..d4302dac5d0 100644 --- a/doc/python/bar-charts.md +++ b/doc/python/bar-charts.md @@ -5,7 +5,7 @@ jupyter: text_representation: extension: .md format_name: markdown - format_version: '1.2' + format_version: "1.2" jupytext_version: 1.3.0 kernelspec: display_name: Python 3 @@ -35,7 +35,7 @@ jupyter: ### Bar chart with Plotly Express -[Plotly Express](/python/plotly-express/) is the easy-to-use, high-level interface to Plotly, which [operates on "tidy" data](/python/px-arguments/). +[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.bar`, each row of the DataFrame is represented as a rectangular mark. diff --git a/doc/python/box-plots.md b/doc/python/box-plots.md index bcb3ea81466..9a4fddd4dac 100644 --- a/doc/python/box-plots.md +++ b/doc/python/box-plots.md @@ -5,7 +5,7 @@ jupyter: text_representation: extension: .md format_name: markdown - format_version: '1.2' + format_version: "1.2" jupytext_version: 1.3.1 kernelspec: display_name: Python 3 @@ -31,8 +31,8 @@ jupyter: page_type: example_index permalink: python/box-plots/ redirect_from: - - /python/box/ - - /python/basic_statistics/ + - /python/box/ + - /python/basic_statistics/ thumbnail: thumbnail/box.jpg --- @@ -40,7 +40,7 @@ A [box plot](https://en.wikipedia.org/wiki/Box_plot) is a statistical representa ## Box Plot with `plotly.express` -[Plotly Express](/python/plotly-express/) is the easy-to-use, high-level interface to Plotly, which [operates on "tidy" data](/python/px-arguments/). +[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/). In a box plot created by `px.box`, the distribution of the column given as `y` argument is represented. @@ -73,13 +73,13 @@ fig.show() ### Choosing The Algorithm For Computing Quartiles -By default, quartiles for box plots are computed using the `linear` method (for more about linear interpolation, see #10 listed on [http://www.amstat.org/publications/jse/v14n3/langford.html](http://www.amstat.org/publications/jse/v14n3/langford.html) and [https://en.wikipedia.org/wiki/Quartile](https://en.wikipedia.org/wiki/Quartile) for more details). +By default, quartiles for box plots are computed using the `linear` method (for more about linear interpolation, see #10 listed on [http://www.amstat.org/publications/jse/v14n3/langford.html](http://www.amstat.org/publications/jse/v14n3/langford.html) and [https://en.wikipedia.org/wiki/Quartile](https://en.wikipedia.org/wiki/Quartile) for more details). -However, you can also choose to use an `exclusive` or an `inclusive` algorithm to compute quartiles. +However, you can also choose to use an `exclusive` or an `inclusive` algorithm to compute quartiles. -The *exclusive* algorithm uses the median to divide the ordered dataset into two halves. If the sample is odd, it does not include the median in either half. Q1 is then the median of the lower half and Q3 is the median of the upper half. +The _exclusive_ algorithm uses the median to divide the ordered dataset into two halves. If the sample is odd, it does not include the median in either half. Q1 is then the median of the lower half and Q3 is the median of the upper half. -The *inclusive* algorithm also uses the median to divide the ordered dataset into two halves, but if the sample is odd, it includes the median in both halves. Q1 is then the median of the lower half and Q3 the median of the upper half. +The _inclusive_ algorithm also uses the median to divide the ordered dataset into two halves, but if the sample is odd, it includes the median in both halves. Q1 is then the median of the lower half and Q3 the median of the upper half. ```python import plotly.express as px @@ -92,7 +92,8 @@ fig.show() ``` #### Difference Between Quartile Algorithms -It can sometimes be difficult to see the difference between the linear, inclusive, and exclusive algorithms for computing quartiles. In the following example, the same dataset is visualized using each of the three different quartile computation algorithms. + +It can sometimes be difficult to see the difference between the linear, inclusive, and exclusive algorithms for computing quartiles. In the following example, the same dataset is visualized using each of the three different quartile computation algorithms. ```python import plotly.express as px @@ -103,7 +104,7 @@ df = pd.DataFrame(dict( linear=data, inclusive=data, exclusive=data -)).melt(var_name="quartilemethod") +)).melt(var_name="quartilemethod") fig = px.box(df, y="value", facet_col="quartilemethod", color="quartilemethod", @@ -204,7 +205,7 @@ fig.show() You can specify precomputed quartile attributes rather than using a built-in quartile computation algorithm. -This could be useful if you have already pre-computed those values or if you need to use a different algorithm than the ones provided. +This could be useful if you have already pre-computed those values or if you need to use a different algorithm than the ones provided. ```python import plotly.graph_objects as go @@ -217,9 +218,9 @@ fig.add_trace(go.Box(y=[ [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ] ], name="Precompiled Quartiles")) -fig.update_traces(q1=[ 1, 2, 3 ], median=[ 4, 5, 6 ], - q3=[ 7, 8, 9 ], lowerfence=[-1, 0, 1], - upperfence=[5, 6, 7], mean=[ 2.2, 2.8, 3.2 ], +fig.update_traces(q1=[ 1, 2, 3 ], median=[ 4, 5, 6 ], + q3=[ 7, 8, 9 ], lowerfence=[-1, 0, 1], + upperfence=[5, 6, 7], mean=[ 2.2, 2.8, 3.2 ], sd=[ 0.2, 0.4, 0.6 ], notchspan=[ 0.2, 0.4, 0.6 ] ) fig.show() diff --git a/doc/python/bubble-charts.md b/doc/python/bubble-charts.md index d9df36022db..4018c782b7d 100644 --- a/doc/python/bubble-charts.md +++ b/doc/python/bubble-charts.md @@ -38,7 +38,7 @@ jupyter: A [bubble chart](https://en.wikipedia.org/wiki/Bubble_chart) is a scatter plot in which a third dimension of the data is shown through the size of markers. For other types of scatter plot, see the [line and scatter page](https://plot.ly/python/line-and-scatter/). -We first show a bubble chart example using Plotly Express. [Plotly Express](/python/plotly-express/) is the easy-to-use, high-level interface to Plotly, which [operates on "tidy" data](/python/px-arguments/). The size of markers is set from the dataframe column given as the `size` parameter. +We first show a bubble chart example using Plotly Express. [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/). The size of markers is set from the dataframe column given as the `size` parameter. ```python import plotly.express as px diff --git a/doc/python/bubble-maps.md b/doc/python/bubble-maps.md index 6563221d923..864312a6a1e 100644 --- a/doc/python/bubble-maps.md +++ b/doc/python/bubble-maps.md @@ -39,7 +39,7 @@ Plotly figures made with `px.scatter_geo`, `px.line_geo` or `px.choropleth` func ### Bubble map with Plotly Express -[Plotly Express](/python/plotly-express/) is the easy-to-use, high-level interface to Plotly, which [operates on "tidy" data](/python/px-arguments/). With `px.scatter_geo`, each line of the dataframe is represented as a marker point. The column set as the `size` argument gives the size of markers. +[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.scatter_geo`, each line of the dataframe is represented as a marker point. The column set as the `size` argument gives the size of markers. ```python import plotly.express as px diff --git a/doc/python/choropleth-maps.md b/doc/python/choropleth-maps.md index ca2b1383f58..b847966b30c 100644 --- a/doc/python/choropleth-maps.md +++ b/doc/python/choropleth-maps.md @@ -56,7 +56,7 @@ The GeoJSON data is passed to the `geojson` argument, and the data is passed int ### Choropleth Map with plotly.express -[Plotly Express](/python/plotly-express/) is the easy-to-use, high-level interface to Plotly, which [operates on "tidy" data](/python/px-arguments/). +[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/). #### GeoJSON with `feature.id` diff --git a/doc/python/datashader.md b/doc/python/datashader.md new file mode 100644 index 00000000000..9a9a3ba9f52 --- /dev/null +++ b/doc/python/datashader.md @@ -0,0 +1,129 @@ +--- +jupyter: + jupytext: + notebook_metadata_filter: all + text_representation: + extension: .md + format_name: markdown + format_version: "1.2" + jupytext_version: 1.3.1 + kernelspec: + display_name: Python 3 + language: python + name: python3 + language_info: + codemirror_mode: + name: ipython + version: 3 + file_extension: .py + mimetype: text/x-python + name: python + nbconvert_exporter: python + pygments_lexer: ipython3 + version: 3.6.8 + plotly: + description: + How to use datashader to rasterize large datasets, and visualize + the generated raster data with plotly. + display_as: scientific + language: python + layout: base + name: Plotly and Datashader + order: 21 + page_type: u-guide + permalink: python/datashader/ + thumbnail: thumbnail/datashader.jpg +--- + +[datashader](https://datashader.org/) creates rasterized representations of large datasets for easier visualization, with a pipeline approach consisting of several steps: projecting the data on a regular grid, creating a color representation of the grid, etc. + +### Passing datashader rasters as a mabox image layer + +We visualize here the spatial distribution of taxi rides in New York City. A higher density +is observed on major avenues. For more details about mapbox charts, see [the mapbox layers tutorial](/python/mapbox-layers). No mapbox token is needed here. + +```python +import pandas as pd +df = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/uber-rides-data1.csv') +dff = df.query('Lat < 40.82').query('Lat > 40.70').query('Lon > -74.02').query('Lon < -73.91') + +import datashader as ds +cvs = ds.Canvas(plot_width=1000, plot_height=1000) +agg = cvs.points(dff, x='Lon', y='Lat') +# agg is an xarray object, see http://xarray.pydata.org/en/stable/ for more details +coords_lat, coords_lon = agg.coords['Lat'].values, agg.coords['Lon'].values +# Corners of the image, which need to be passed to mapbox +coordinates = [[coords_lon[0], coords_lat[0]], + [coords_lon[-1], coords_lat[0]], + [coords_lon[-1], coords_lat[-1]], + [coords_lon[0], coords_lat[-1]]] + +from colorcet import fire +import datashader.transfer_functions as tf +img = tf.shade(agg, cmap=fire)[::-1].to_pil() + +import plotly.express as px +# Trick to create rapidly a figure with mapbox axes +fig = px.scatter_mapbox(dff[:1], lat='Lat', lon='Lon', zoom=12) +# Add the datashader image as a mapbox layer image +fig.update_layout(mapbox_style="carto-darkmatter", + mapbox_layers = [ + { + "sourcetype": "image", + "source": img, + "coordinates": coordinates + }] +) +fig.show() +``` + +### Exploring correlations of a large dataset + +Here we explore the flight delay dataset from https://www.kaggle.com/usdot/flight-delays. In order to get a visual impression of the correlation between features, we generate a datashader rasterized array which we plot using a `Heatmap` trace. It creates a much clearer visualization than a scatter plot of (even a fraction of) the data points, as shown below. + +Note that instead of datashader it would theoretically be possible to create a [2d histogram](/python/2d-histogram-contour/) with plotly but this is not recommended here because you would need to load the whole dataset (5M rows !) in the browser for plotly.js to compute the heatmap, which is practically not tractable. Datashader offers the possibility to reduce the size of the dataset before passing it to the browser. + +```python +import plotly.graph_objects as go +import pandas as pd +import numpy as np +import datashader as ds +df = pd.read_parquet('https://raw.githubusercontent.com/plotly/datasets/master/2015_flights.parquet') +fig = go.Figure(go.Scattergl(x=df['SCHEDULED_DEPARTURE'][::200], + y=df['DEPARTURE_DELAY'][::200], + mode='markers') +) +fig.update_layout(title_text='A busy plot') +fig.show() +``` + +```python +import plotly.graph_objects as go +import pandas as pd +import numpy as np +import datashader as ds +df = pd.read_parquet('https://raw.githubusercontent.com/plotly/datasets/master/2015_flights.parquet') + +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') +fig.show() + +``` + +```python + +``` diff --git a/doc/python/distplot.md b/doc/python/distplot.md index bf1987a77c9..b2bedfeffbe 100644 --- a/doc/python/distplot.md +++ b/doc/python/distplot.md @@ -37,7 +37,7 @@ jupyter: Several representations of statistical distributions are available in plotly, such as [histograms](https://plot.ly/python/histograms/), [violin plots](https://plot.ly/python/violin/), [box plots](https://plot.ly/python/box-plots/) (see [the complete list here](https://plot.ly/python/statistical-charts/)). It is also possible to combine several representations in the same plot. -For example, the `plotly.express` function `px.histogram` can add a subplot with a different statistical representation than the histogram, given by the parameter `marginal`. [Plotly Express](/python/plotly-express/) is the easy-to-use, high-level interface to Plotly, which [operates on "tidy" data](/python/px-arguments/). +For example, the `plotly.express` function `px.histogram` can add a subplot with a different statistical representation than the histogram, given by the parameter `marginal`. [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/). ```python import plotly.express as px diff --git a/doc/python/dot-plots.md b/doc/python/dot-plots.md index dffe1d9b8ac..a5aaa6997ee 100644 --- a/doc/python/dot-plots.md +++ b/doc/python/dot-plots.md @@ -5,7 +5,7 @@ jupyter: text_representation: extension: .md format_name: markdown - format_version: '1.1' + format_version: "1.1" jupytext_version: 1.1.1 kernelspec: display_name: Python 3 @@ -35,11 +35,11 @@ jupyter: #### Basic Dot Plot -Dot plots (also known as [Cleveland dot plots](https://en.wikipedia.org/wiki/Dot_plot_(statistics))) show changes between two (or more) points in time or between two (or more) conditions. Compared to a [bar chart](/python/bar-charts/), dot plots can be less cluttered and allow for an easier comparison between conditions. +Dot plots (also known as [Cleveland dot plots]()) show changes between two (or more) points in time or between two (or more) conditions. Compared to a [bar chart](/python/bar-charts/), dot plots can be less cluttered and allow for an easier comparison between conditions. For the same data, we show below how to create a dot plot using either `px.scatter` (for a tidy pandas DataFrame) or `go.Scatter`. -[Plotly Express](/python/plotly-express/) is the easy-to-use, high-level interface to Plotly, which [operates on "tidy" data](/python/px-arguments/). +[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/). ```python import plotly.express as px @@ -158,5 +158,4 @@ fig.show() ### Reference - See https://plot.ly/python/reference/#scatter for more information and chart attribute options! diff --git a/doc/python/error-bars.md b/doc/python/error-bars.md index e4870b7a223..37867c149f6 100644 --- a/doc/python/error-bars.md +++ b/doc/python/error-bars.md @@ -35,7 +35,7 @@ jupyter: ### Error Bars with Plotly Express -[Plotly Express](/python/plotly-express/) is the easy-to-use, high-level interface to Plotly, which [operates on "tidy" data](/python/px-arguments/). For functions representing 2D data points such as [`px.scatter`](https://plot.ly/python/line-and-scatter/), [`px.line`](https://plot.ly/python/line-charts/), [`px.bar`](https://plot.ly/python/bar-charts/) etc., error bars are given as a column name which is the value of the `error_x` (for the error on x position) and `error_y` (for the error on y position). +[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/). For functions representing 2D data points such as [`px.scatter`](https://plot.ly/python/line-and-scatter/), [`px.line`](https://plot.ly/python/line-charts/), [`px.bar`](https://plot.ly/python/bar-charts/) etc., error bars are given as a column name which is the value of the `error_x` (for the error on x position) and `error_y` (for the error on y position). ```python import plotly.express as px diff --git a/doc/python/filled-area-plots.md b/doc/python/filled-area-plots.md index b32c7915110..f45ca14d143 100644 --- a/doc/python/filled-area-plots.md +++ b/doc/python/filled-area-plots.md @@ -37,7 +37,7 @@ This example shows how to fill the area enclosed by traces. ## Filled area plot with plotly.express -[Plotly Express](/python/plotly-express/) is the easy-to-use, high-level interface to Plotly, which [operates on "tidy" data](/python/px-arguments/). +[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/). `px.area` creates a stacked area plot. Each filled area corresponds to one value of the column given by the `line_group` parameter. diff --git a/doc/python/funnel-charts.md b/doc/python/funnel-charts.md index e786eca4fbc..669b3221f3a 100644 --- a/doc/python/funnel-charts.md +++ b/doc/python/funnel-charts.md @@ -5,7 +5,7 @@ jupyter: text_representation: extension: .md format_name: markdown - format_version: '1.2' + format_version: "1.2" jupytext_version: 1.3.0 kernelspec: display_name: Python 3 @@ -24,23 +24,20 @@ jupyter: thumbnail: thumbnail/funnel.jpg --- - ### Introduction Funnel charts are often used to represent data in different stages of a business process. It’s an important mechanism in Business Intelligence to identify potential problem areas of a process. For example, it’s used to observe the revenue or loss in a sales process for each stage, and displays values that are decreasing progressively. Each stage is illustrated as a percentage of the total of all values. - ### Basic Funnel Plot with plotly.express -[Plotly Express](/python/plotly-express/) is the easy-to-use, high-level interface to Plotly, which [operates on "tidy" data](/python/px-arguments/). +[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.funnel`, each row of the DataFrame is represented as a stage of the funnel. - ```python import plotly.express as px data = dict( - number=[39, 27.4, 20.6, 11, 2], + number=[39, 27.4, 20.6, 11, 2], stage=["Website visit", "Downloads", "Potential customers", "Requested price", "invoice sent"]) fig = px.funnel(data, x='number', y='stage') fig.show() @@ -76,6 +73,7 @@ fig.show() ``` ### Setting Marker Size and Color + This example uses [textposition](https://plot.ly/python/reference/#scatter-textposition) and [textinfo](https://plot.ly/python/reference/#funnel-textinfo) to determine information apears on the graph, and shows how to customize the bars. ```python @@ -167,7 +165,6 @@ fig.show() #### Multiple Area Funnels - ```python from plotly import graph_objects as go @@ -204,4 +201,5 @@ fig.show() ``` #### Reference + See https://plot.ly/python/reference/#funnel and https://plot.ly/python/reference/#funnelarea for more information and chart attribute options! diff --git a/doc/python/heatmaps.md b/doc/python/heatmaps.md index aac6a570455..ec2735bf725 100644 --- a/doc/python/heatmaps.md +++ b/doc/python/heatmaps.md @@ -162,5 +162,10 @@ fig.update_layout( fig.show() ``` +### Heatmap and datashader + +Arrays of rasterized values build by datashader can be visualized using +plotly's heatmaps, as shown in the [plotly and datashader tutorial](/python/datashader/). + #### Reference See https://plot.ly/python/reference/#heatmap for more information and chart attribute options! diff --git a/doc/python/histograms.md b/doc/python/histograms.md index 7ec7f6b2d1c..76bc94d1d7d 100644 --- a/doc/python/histograms.md +++ b/doc/python/histograms.md @@ -41,7 +41,7 @@ bar, go to the [Bar Chart tutorial](/python/bar-charts/). ## Histogram with Plotly Express -[Plotly Express](/python/plotly-express/) is the easy-to-use, high-level interface to Plotly, which [operates on "tidy" data](/python/px-arguments/). +[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/). ```python import plotly.express as px diff --git a/doc/python/horizontal-bar-charts.md b/doc/python/horizontal-bar-charts.md index c5e9de9b188..b9daa15e407 100644 --- a/doc/python/horizontal-bar-charts.md +++ b/doc/python/horizontal-bar-charts.md @@ -37,7 +37,7 @@ See more examples of bar charts (including vertical bar charts) and styling opti ### Horizontal Bar Chart with Plotly Express -[Plotly Express](/python/plotly-express/) is the easy-to-use, high-level interface to Plotly, which [operates on "tidy" data](/python/px-arguments/). For a horizontal bar char, use the `px.bar` function with `orientation='h'`. +[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/). For a horizontal bar char, use the `px.bar` function with `orientation='h'`. #### Basic Horizontal Bar Chart with Plotly Express diff --git a/doc/python/imshow.md b/doc/python/imshow.md index 349732e82a7..ba5b0c20cb7 100644 --- a/doc/python/imshow.md +++ b/doc/python/imshow.md @@ -198,6 +198,13 @@ fig.update_layout(height=400) 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 +examples on how to use plotly and datashader. + + #### Reference See https://plot.ly/python/reference/#image for more information and chart attribute options! diff --git a/doc/python/line-and-scatter.md b/doc/python/line-and-scatter.md index 9479bb63f50..42c46606d9f 100644 --- a/doc/python/line-and-scatter.md +++ b/doc/python/line-and-scatter.md @@ -36,7 +36,7 @@ jupyter: ## Scatter plot with Plotly Express -[Plotly Express](/python/plotly-express/) is the easy-to-use, high-level interface to Plotly, which [operates on "tidy" data](/python/px-arguments/). +[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.scatter`, each data point is represented as a marker point, which location is given by the `x` and `y` columns. diff --git a/doc/python/line-charts.md b/doc/python/line-charts.md index 13d151494e7..1de7699bfa0 100644 --- a/doc/python/line-charts.md +++ b/doc/python/line-charts.md @@ -37,7 +37,7 @@ jupyter: ### Line Plot with plotly.express -[Plotly Express](/python/plotly-express/) is the easy-to-use, high-level interface to Plotly, which [operates on "tidy" data](/python/px-arguments/). With `px.line`, each data point is represented as a vertex (which location is given by the `x` and `y` columns) of a **polyline mark** in 2D space. +[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.line`, each data point is represented as a vertex (which location is given by the `x` and `y` columns) of a **polyline mark** in 2D space. For more examples of line plots, see the [line and scatter notebook](https://plot.ly/python/line-and-scatter/). diff --git a/doc/python/linear-fits.md b/doc/python/linear-fits.md index 795a334a7a6..7d1c970efca 100644 --- a/doc/python/linear-fits.md +++ b/doc/python/linear-fits.md @@ -37,7 +37,7 @@ jupyter: ### Linear fit trendlines with Plotly Express -[Plotly Express](/python/plotly-express/) is the easy-to-use, high-level interface to Plotly, which [operates on "tidy" data](/python/px-arguments/). +[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/). Plotly Express allows you to add [Ordinary Least](https://en.wikipedia.org/wiki/Ordinary_least_squares) Squares regression trendline to scatterplots with the `trendline` argument. In order to do so, you will need to install `statsmodels` and its dependencies. Hovering over the trendline will show the equation of the line and its R-squared value. diff --git a/doc/python/lines-on-maps.md b/doc/python/lines-on-maps.md index 514a6a51e1a..55cb9294396 100644 --- a/doc/python/lines-on-maps.md +++ b/doc/python/lines-on-maps.md @@ -41,7 +41,7 @@ Plotly figures made with `px.scatter_geo`, `px.line_geo` or `px.choropleth` func ## Lines on Maps with Plotly Express -[Plotly Express](/python/plotly-express/) is the easy-to-use, high-level interface to Plotly, which [operates on "tidy" data](/python/px-arguments/). +[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/). ```python import plotly.express as px diff --git a/doc/python/mapbox-county-choropleth.md b/doc/python/mapbox-county-choropleth.md index dd5cbebd978..10b26e914b1 100644 --- a/doc/python/mapbox-county-choropleth.md +++ b/doc/python/mapbox-county-choropleth.md @@ -80,7 +80,7 @@ df.head() ### Choropleth map using plotly.express and carto base map (no token needed) -[Plotly Express](/python/plotly-express/) is the easy-to-use, high-level interface to Plotly, which [operates on "tidy" data](/python/px-arguments/). +[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.choropleth_mapbox`, each row of the DataFrame is represented as a region of the choropleth. diff --git a/doc/python/mapbox-density-heatmaps.md b/doc/python/mapbox-density-heatmaps.md index e17a0ae5ef8..7c4535e22b4 100644 --- a/doc/python/mapbox-density-heatmaps.md +++ b/doc/python/mapbox-density-heatmaps.md @@ -5,7 +5,7 @@ jupyter: text_representation: extension: .md format_name: markdown - format_version: '1.2' + format_version: "1.2" jupytext_version: 1.3.0 kernelspec: display_name: Python 3 @@ -33,17 +33,13 @@ jupyter: thumbnail: thumbnail/mapbox-density.png --- - #### Mapbox Access Token -To plot on Mapbox maps with Plotly you *may* need a Mapbox account and a public [Mapbox Access Token](https://www.mapbox.com/studio). See our [Mapbox Map Layers](/python/mapbox-layers/) documentation for more information. - - - +To plot on Mapbox maps with Plotly you _may_ need a Mapbox account and a public [Mapbox Access Token](https://www.mapbox.com/studio). See our [Mapbox Map Layers](/python/mapbox-layers/) documentation for more information. ### Stamen Terrain base map (no token needed): density mapbox with `plotly.express` -[Plotly Express](/python/plotly-express/) is the easy-to-use, high-level interface to Plotly, which [operates on "tidy" data](/python/px-arguments/). +[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.density_mapbox`, each row of the DataFrame is represented as a point smoothed with a given radius of influence. @@ -67,7 +63,7 @@ import pandas as pd quakes = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/earthquakes-23k.csv') import plotly.graph_objects as go -fig = go.Figure(go.Densitymapbox(lat=quakes.Latitude, lon=quakes.Longitude, z=quakes.Magnitude, +fig = go.Figure(go.Densitymapbox(lat=quakes.Latitude, lon=quakes.Longitude, z=quakes.Magnitude, radius=10)) fig.update_layout(mapbox_style="stamen-terrain", mapbox_center_lon=180) fig.update_layout(margin={"r":0,"t":0,"l":0,"b":0}) @@ -75,4 +71,5 @@ fig.show() ``` #### Reference + See https://plot.ly/python/reference/#densitymapbox for more information about mapbox and their attribute options. diff --git a/doc/python/mapbox-layers.md b/doc/python/mapbox-layers.md index 0aac4981c1a..ecb68d5cf3b 100644 --- a/doc/python/mapbox-layers.md +++ b/doc/python/mapbox-layers.md @@ -186,6 +186,10 @@ fig.update_layout(margin={"r":0,"t":0,"l":0,"b":0}) fig.show() ``` +#### Using a mapbox image layer to display a datashader raster image + +See the example in the [plotly and datashader tutorial](/python/datashader). + #### Reference See https://plot.ly/python/reference/#layout-mapbox for more information and options! diff --git a/doc/python/parallel-coordinates-plot.md b/doc/python/parallel-coordinates-plot.md index ae9b9349ec4..b33b410ab42 100644 --- a/doc/python/parallel-coordinates-plot.md +++ b/doc/python/parallel-coordinates-plot.md @@ -37,7 +37,7 @@ jupyter: ## Parallel Coordinates plot with Plotly Express -[Plotly Express](/python/plotly-express/) is the easy-to-use, high-level interface to Plotly, which [operates on "tidy" data](/python/px-arguments/). In a parallel coordinates plot with `px.parallel_coordinates`, each row of the DataFrame is represented by a polyline mark which traverses a set of parallel axes, one for each of the dimensions. For other representations of multivariate data, also see [parallel categories](/python/parallel-categories-diagram/), [radar charts](/python/radar-chart/) and [scatterplot matrix (SPLOM)](/python/splom/). +[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/). In a parallel coordinates plot with `px.parallel_coordinates`, each row of the DataFrame is represented by a polyline mark which traverses a set of parallel axes, one for each of the dimensions. For other representations of multivariate data, also see [parallel categories](/python/parallel-categories-diagram/), [radar charts](/python/radar-chart/) and [scatterplot matrix (SPLOM)](/python/splom/). ```python import plotly.express as px diff --git a/doc/python/pie-charts.md b/doc/python/pie-charts.md index a3fe1160c8f..75905b990cf 100644 --- a/doc/python/pie-charts.md +++ b/doc/python/pie-charts.md @@ -5,7 +5,7 @@ jupyter: text_representation: extension: .md format_name: markdown - format_version: '1.2' + format_version: "1.2" jupytext_version: 1.3.0 kernelspec: display_name: Python 3 @@ -33,14 +33,14 @@ jupyter: thumbnail: thumbnail/pie-chart.jpg --- -A pie chart is a circular statistical chart, which is divided into sectors to illustrate numerical proportion. +A pie chart is a circular statistical chart, which is divided into sectors to illustrate numerical proportion. If you're looking instead for a multilevel hierarchical pie-like chart, go to the [Sunburst tutorial](/python/sunburst-charts/). ### Pie chart with plotly express -[Plotly Express](/python/plotly-express/) is the easy-to-use, high-level interface to Plotly, which [operates on "tidy" data](/python/px-arguments/). +[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/). In `px.pie`, data visualized by the sectors of the pie is set in `values`. The sector labels are set in `names`. @@ -80,7 +80,7 @@ In the example below, we first create a pie chart with `px,pie`, using some of i ```python import plotly.express as px df = px.data.gapminder().query("year == 2007").query("continent == 'Americas'") -fig = px.pie(df, values='pop', names='country', +fig = px.pie(df, values='pop', names='country', title='Population of American continent', hover_data=['lifeExp'], labels={'lifeExp':'life expectancy'}) fig.update_traces(textposition='inside', textinfo='percent+label') @@ -91,8 +91,7 @@ fig.show() If Plotly Express does not provide a good starting point, it is also possible to use the more generic `go.Pie` function from `plotly.graph_objects`. - -In `go.Pie`, data visualized by the sectors of the pie is set in `values`. The sector labels are set in `labels`. The sector colors are set in `marker.colors`. +In `go.Pie`, data visualized by the sectors of the pie is set in `values`. The sector labels are set in `labels`. The sector colors are set in `marker.colors`. If you're looking instead for a multilevel hierarchical pie-like chart, go to the [Sunburst tutorial](/python/sunburst-charts/). @@ -141,7 +140,7 @@ fig.show() The `insidetextorientation` attribute controls the orientation of text inside sectors. With "auto" the texts may automatically be rotated to fit with the maximum size inside the slice. Using "horizontal" (resp. "radial", "tangential") forces text to be horizontal (resp. radial or tangential) -For a figure `fig` created with plotly express, use `fig.update_traces(insidetextorientation='...')` to change the text orientation. +For a figure `fig` created with plotly express, use `fig.update_traces(insidetextorientation='...')` to change the text orientation. ```python import plotly.graph_objects as go @@ -157,7 +156,6 @@ fig.show() ### Donut Chart - ```python import plotly.graph_objects as go @@ -211,7 +209,6 @@ fig.update_layout( fig.show() ``` - ```python import plotly.graph_objects as go from plotly.subplots import make_subplots @@ -291,4 +288,5 @@ fig.show() ``` #### Reference + See https://plot.ly/python/reference/#pie for more information and chart attribute options! diff --git a/doc/python/plotly-express.md b/doc/python/plotly-express.md index bdee32bb0c2..b5584b757b9 100644 --- a/doc/python/plotly-express.md +++ b/doc/python/plotly-express.md @@ -5,7 +5,7 @@ jupyter: text_representation: extension: .md format_name: markdown - format_version: '1.2' + format_version: "1.2" jupytext_version: 1.3.1 kernelspec: display_name: Python 3 @@ -22,7 +22,8 @@ jupyter: pygments_lexer: ipython3 version: 3.6.8 plotly: - description: Plotly Express is a terse, consistent, high-level API for rapid data + description: + Plotly Express is a terse, consistent, high-level API for rapid data exploration and figure generation. display_as: file_settings language: python @@ -36,11 +37,11 @@ jupyter: ### Plotly Express -Plotly Express is a terse, consistent, high-level wrapper around `plotly.graph_objects` for rapid data exploration and figure generation. +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/). Every Plotly Express function returns a `graph_objects.Figure` object whose `data` and `layout` has been pre-populated according to the provided arguments. -**Note**: Plotly Express was previously its own separately-installed `plotly_express` package but is now part of `plotly`! +> **Note**: Plotly Express was previously its own separately-installed `plotly_express` package but is now part of `plotly` and importable via `import plotly.express as px`. -This notebook demonstrates various `plotly.express` features. [Reference documentation](https://plotly.github.io/plotly_express/plotly_express/) is also available, as well as a [tutorial on input argument types](/python/px-arguments). +This notebook demonstrates various `plotly.express` features. [Reference documentation](https://plot.ly/python-api-reference/plotly.express.html) is also available, as well as a [tutorial on input argument types](/python/px-arguments) and one on how to [style figures made with Plotly Express](/python/styling-plotly-express/). You can also read our original [Medium announcement article](https://medium.com/@plotlygraphs/introducing-plotly-express-808df010143d) for more information on this library. @@ -54,6 +55,8 @@ px.data.iris().head() #### Scatter and Line plots +Refer to the main [scatter and line plot page](/python/line-and-scatter/) for full documentation. + ```python import plotly.express as px df = px.data.iris() @@ -172,6 +175,8 @@ fig.show() #### Visualize Distributions +Refer to the main [statistical graphs page](/python/statistical-charts/) for full documentation. + ```python import plotly.express as px df = px.data.iris() @@ -357,33 +362,3 @@ df = px.data.gapminder() fig = px.choropleth(df, locations="iso_alpha", color="lifeExp", hover_name="country", animation_frame="year", range_color=[20,80]) fig.show() ``` - -#### Built-in Color Scales and Sequences (and a way to see them!) - -```python -px.colors.qualitative.swatches() -``` - -```python -px.colors.sequential.swatches() -``` - -```python -px.colors.diverging.swatches() -``` - -```python -px.colors.cyclical.swatches() -``` - -```python -px.colors.colorbrewer.swatches() -``` - -```python -px.colors.cmocean.swatches() -``` - -```python -px.colors.carto.swatches() -``` diff --git a/doc/python/polar-chart.md b/doc/python/polar-chart.md index 7043cc73f13..ce6ac9aee0b 100644 --- a/doc/python/polar-chart.md +++ b/doc/python/polar-chart.md @@ -37,7 +37,7 @@ jupyter: A polar chart represents data along radial and angular axes. With Plotly Express, it is possible to represent polar data as scatter markers with `px.scatter_polar`, and as lines with `px.line_polar`. -[Plotly Express](/python/plotly-express/) is the easy-to-use, high-level interface to Plotly, which [operates on "tidy" data](/python/px-arguments/). +[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/). For other types of arguments, see the section below using `go.Scatterpolar`. diff --git a/doc/python/px-arguments.md b/doc/python/px-arguments.md index add81956987..9cb7827738e 100644 --- a/doc/python/px-arguments.md +++ b/doc/python/px-arguments.md @@ -51,6 +51,7 @@ import pandas as pd print("This is the same data in 'long' format, ready for Plotly Express:") wide_df = pd.DataFrame(dict(Month=["Jan", "Feb", "Mar"], London=[1,2,3], Paris=[3,1,2])) tidy_df = wide_df.melt(id_vars="Month") +tidy_df ``` ```python diff --git a/doc/python/radar-chart.md b/doc/python/radar-chart.md index a180a8ee74e..6ffa8c5fbfb 100644 --- a/doc/python/radar-chart.md +++ b/doc/python/radar-chart.md @@ -5,7 +5,7 @@ jupyter: text_representation: extension: .md format_name: markdown - format_version: '1.1' + format_version: "1.1" jupytext_version: 1.1.1 kernelspec: display_name: Python 3 @@ -33,13 +33,13 @@ jupyter: thumbnail: thumbnail/radar.gif --- -A [Radar Chart](https://en.wikipedia.org/wiki/Radar_chart) (also known as a spider plot or star plot) displays multivariate data in the form of a two-dimensional chart of quantitative variables represented on axes originating from the center. The relative position and angle of the axes is typically uninformative. It is equivalent to a [parallel coordinates plot](/python/parallel-coordinates-plot/) with the axes arranged radially. +A [Radar Chart](https://en.wikipedia.org/wiki/Radar_chart) (also known as a spider plot or star plot) displays multivariate data in the form of a two-dimensional chart of quantitative variables represented on axes originating from the center. The relative position and angle of the axes is typically uninformative. It is equivalent to a [parallel coordinates plot](/python/parallel-coordinates-plot/) with the axes arranged radially. For a Radar Chart, use a [polar chart](/python/polar-chart/) with categorical angular variables, with `px.line_polar` for data available as a tidy pandas DataFrame, or with `go.Scatterpolar` in the general case. See more examples of [polar charts here](/python/polar-chart/). #### Radar Chart with Plotly Express -[Plotly Express](/python/plotly-express/) is the easy-to-use, high-level interface to Plotly, which [operates on "tidy" data](/python/px-arguments/). +[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/). Use `line_close=True` for closed lines. @@ -70,7 +70,6 @@ fig.show() ### Basic Radar Chart with go.Scatterpolar - ```python import plotly.graph_objects as go @@ -129,4 +128,5 @@ fig.show() ``` #### Reference + See https://plot.ly/python/reference/#scatterpolar for more information and chart attribute options! diff --git a/doc/python/scatter-plots-on-maps.md b/doc/python/scatter-plots-on-maps.md index be23088be84..debe6c4771b 100644 --- a/doc/python/scatter-plots-on-maps.md +++ b/doc/python/scatter-plots-on-maps.md @@ -43,7 +43,7 @@ Plotly figures made with `px.scatter_geo`, `px.line_geo` or `px.choropleth` func Here we show the [Plotly Express](/python/plotly-express/) function `px.scatter_geo` for a geographical scatter plot. The `size` argument is used to set the size of markers from a given column of the DataFrame. -[Plotly Express](/python/plotly-express/) is the easy-to-use, high-level interface to Plotly, which [operates on "tidy" data](/python/px-arguments/). +[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/). ```python import plotly.express as px diff --git a/doc/python/scattermapbox.md b/doc/python/scattermapbox.md index 0f9a32aea2e..d8383dd3171 100644 --- a/doc/python/scattermapbox.md +++ b/doc/python/scattermapbox.md @@ -41,7 +41,7 @@ To plot on Mapbox maps with Plotly you _may_ need a Mapbox account and a public Here we show the [Plotly Express](/python/plotly-express/) function `px.scatter_mapbox` for a scatter plot on a tile map. -[Plotly Express](/python/plotly-express/) is the easy-to-use, high-level interface to Plotly, which [operates on "tidy" data](/python/px-arguments/). +[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/). ```python import plotly.express as px diff --git a/doc/python/splom.md b/doc/python/splom.md index 0101ebfb094..e46d8147d3e 100644 --- a/doc/python/splom.md +++ b/doc/python/splom.md @@ -42,7 +42,7 @@ A scatterplot matrix is a matrix associated to n numerical arrays (data variable Here we show the Plotly Express function `px.scatter_matrix` to plot the scatter matrix for the columns of the dataframe. By default, all columns are considered. -[Plotly Express](/python/plotly-express/) is the easy-to-use, high-level interface to Plotly, which [operates on "tidy" data](/python/px-arguments/). +[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/). ```python import plotly.express as px diff --git a/doc/python/styling-plotly-express.md b/doc/python/styling-plotly-express.md new file mode 100644 index 00000000000..3151b09f15d --- /dev/null +++ b/doc/python/styling-plotly-express.md @@ -0,0 +1,183 @@ +--- +jupyter: + jupytext: + notebook_metadata_filter: all + text_representation: + extension: .md + format_name: markdown + format_version: "1.2" + jupytext_version: 1.3.1 + kernelspec: + display_name: Python 3 + language: python + name: python3 + language_info: + codemirror_mode: + name: ipython + version: 3 + file_extension: .py + mimetype: text/x-python + name: python + nbconvert_exporter: python + pygments_lexer: ipython3 + version: 3.6.8 + plotly: + description: Figures made with Plotly Express can be customized in all the same ways as figures made with graph objects, as well as with PX-specific function arguments. + display_as: file_settings + language: python + layout: base + name: Styling Plotly Express Figures + order: 29 + page_type: u-guide + permalink: python/styling-plotly-express/ + thumbnail: thumbnail/plotly-express.png +--- + +### Styling Figures made with Plotly Express + +[Plotly Express](/python/plotly-express/) is the easy-to-use, high-level interface to Plotly, which [operates on "tidy" data](/python/px-arguments/). Every Plotly Express function returns a `graph_objects.Figure` object whose `data` and `layout` has been pre-populated according to the provided arguments. + +> You can style and customize figures made with Plotly Express _in all the same ways_ as you can style figures made more manually by explicitly assembling `graph_objects` into a figure. + +More specifically, here are the 4 ways you can style and customize figures made with Plotly Express: + +1. Control common parameters like width & height, titles, labeling and colors using built-in Plotly Express function arguments +2. Updating the figure attributes using [update methods or by directly setting attributes](/python/creating-and-updating-figures/) +3. Using Plotly's [theming/templating mechanism](/python/templates/) via the `template` argument to every Plotly Express function +4. Setting default values for common parameters using `px.defaults` + +### Built-in Plotly Express Styling Arguments + +Many common styling options can be set directly in the `px` function call. Every Plotly Express function accepts the following arguments: + +- `title` to set the figure title +- `width` and `height` to set the figure dimensions +- `labels` to override the default axis and legend labels behaviour, which is to use the data frame column name if available, and otherwise to use the label name itself like "x", "y", "color" etc. `labels` accepts a `dict` whose keys are the label to rename and whose values are the desired labels. +- `category_orders` to override the default category ordering behaviour, which is to use the order in which the data appears in the input. `category_orders` accepts a `dict` whose keys are the column name to reorder and whose values are a `list` of values in the desired order. +- [Various color-related attributes](/python/colorscales/) such as `color_continuous_scale`, `color_range`, `color_discrete_sequence` and/or `color_discrete_map` set the colors used in the figure. `color_discrete_map` accepts a dict whose keys are values mapped to `color` and whose values are the desired CSS colors. +- `template` to [set many styling parameters at once](/python/templates/) (see below for more details) + +To illustrate each of these, here is a simple, default figure made with Plotly Express. Note the default orderings for the x-axis categories and the usage of lowercase & snake_case data frame columns for axis labelling. + +```python +import plotly.express as px +df = px.data.tips() +fig = px.histogram(df, x="day", y="total_bill", color="sex", histfunc="sum") +fig.show() +``` + +Here is the same figure, restyled by adding some extra parameters to the initial Plotly Express call: + +```python +import plotly.express as px +df = px.data.tips() +fig = px.histogram(df, x="day", y="total_bill", color="sex", histfunc="sum", + title="Receipts by Payer Gender and Day of Week", + width=600, height=400, + labels={ # replaces default labels by column name + "sex": "Payer Gender", "day": "Day of Week", "total_bill": "Receipts" + }, + category_orders={ # replaces default order by column name + "day": ["Thur", "Fri", "Sat", "Sun"], "sex": ["Male", "Female"] + }, + color_discrete_map={ # replaces default color mapping by value + "Male": "RebeccaPurple", "Female": "MediumPurple" + }, + template="simple_white" + ) +fig.show() +``` + +### Updating or Modifying Figures made with Plotly Express + +If none of the built-in Plotly Express arguments allow you to customize the figure the way you need to, you can use [the `update_*` and `add_*` methods](/python/creating-and-updating-figures/) on the `graph_objects.Figure` object returned by the PX function to make any further modifications to the figure. This approach is the one used throughout the Plotly.py documentation to [customize axes](/python/axes/), control [legends](/python/legend/) and [colorbars](/python/colorscales/), add [shapes](/python/shapes/) and [annotations](/python/text-and-annotations/) etc. + +Here is the same figure as above, with some additional customizations to the axes and legend via `.update_yaxes()`, and `.update_layout()`, as well as some annotations added via `.add_shape()` and `.add_annotation()`. + +```python +import plotly.express as px +df = px.data.tips() +fig = px.histogram(df, x="day", y="total_bill", color="sex", histfunc="sum", + title="Receipts by Payer Gender and Day of Week vs Target", + width=600, height=400, + labels={"sex": "Payer Gender", "day": "Day of Week", "total_bill": "Receipts"}, + category_orders={"day": ["Thur", "Fri", "Sat", "Sun"], "sex": ["Male", "Female"]}, + color_discrete_map={"Male": "RebeccaPurple", "Female": "MediumPurple"}, + template="simple_white" + ) + +fig.update_yaxes( # the y-axis is in dollars + tickprefix="$", showgrid=True +) + +fig.update_layout( # customize font and legend orientation & position + font_family="Rockwell", + legend=dict( + title=None, orientation="h", y=1, yanchor="bottom", x=0.5, xanchor="center" + ) +) + +fig.add_shape( # add a horizontal "target" line + type="line", line_color="salmon", line_width=3, opacity=1, line_dash="dot", + x0=0, x1=1, xref="paper", y0=950, y1=950, yref="y" +) + +fig.add_annotation( # add a text callout with arrow + text="below target!", x="Fri", y=400, arrowhead=1, showarrow=True +) + +fig.show() +``` + +### How Plotly Express Works with Templates + +Plotly has a [theming system based on templates](/python/templates/) and figures created with Plotly Express interact smoothly with this system: + +- Plotly Express methods will use the default template if one is set in `plotly.io` (by default, this is set to `plotly`) or in `plotly.express.defaults` (see below) +- The template in use can always be overridden via the `template` argument to every PX function +- The default `color_continuous_scale` will be the value of `layout.colorscales.sequential` in the template in use, unless it is overridden via the corresponding function argument or via `plotly.express.defaults` (see below) +- The default `color_discrete_sequence` will be the value of `layout.colorway` in the template in use, unless it is overridden via the corresponding function argument or via `plotly.express.defaults` (see below) + +By way of example, in the following figure, simply setting the `template` argument will automatically change the default continuous color scale, even though we have not specified `color_continuous_scale` directly. + +```python +import plotly.express as px +df = px.data.iris() +fig = px.density_heatmap(df, x="sepal_width", y="sepal_length", template="seaborn") +fig.show() +``` + +### Setting Plotly Express Styling Defaults + +Plotly Express supports a simple default-configuration system via the `plotly.express.defaults` singleton object. The values of the properties set on this object are used for the rest of the active session in place of `None` as the default values for any argument to a PX function with a matching name: + +- `width` and `height` can be set once globally for all Plotly Express functions +- `template` can override the setting of `plotly.io.templates.default` for all Plotly Express functions +- `color_continuous_scale` and `color_discrete_scale` can override the contents of the template in use for all Plotly Express functions that accept these arguments +- `line_dash_sequence`, `symbol_sequence` and `size_max` can be set once globally for all Plotly Express functions that accept these arguments + +To illustrate this "defaults hierarchy", in the following example: + +- we set the Plotly-wide default template to `simple_white`, but +- we override the default template for Plotly Express to be `ggplot2`, but +- we also set the default `color_continuous_scale`, and +- we set the default `height` and `width` to 400 by 600, but +- we override the default `width` to 400 via the function argument. + +As a result, any figure produced with Plotly Express thereafter uses the `ggplot2` settings for all attributes except for the continuous color scale (visible because `simple_white` doesn't set a plot background, and neither the `simple_white` nor `ggplot2` template uses `Blackbody` as a color scale), and uses the Plotly Express defaults for height but not width (visible because the figure height is the same as the figure width, despite the default). + +```python +import plotly.express as px +import plotly.io as pio + +pio.templates.default = "simple_white" + +px.defaults.template = "ggplot2" +px.defaults.color_continuous_scale = px.colors.sequential.Blackbody +px.defaults.width = 600 +px.defaults.height = 400 + +df = px.data.iris() +fig = px.scatter(df, x="sepal_width", y="sepal_length", color="sepal_length", width=400) +fig.show() +``` diff --git a/doc/python/sunburst-charts.md b/doc/python/sunburst-charts.md index cb9bc8f8cdd..69f89133c35 100644 --- a/doc/python/sunburst-charts.md +++ b/doc/python/sunburst-charts.md @@ -5,7 +5,7 @@ jupyter: text_representation: extension: .md format_name: markdown - format_version: '1.2' + format_version: "1.2" jupytext_version: 1.3.0 kernelspec: display_name: Python 3 @@ -36,13 +36,14 @@ jupyter: Sunburst plots visualize hierarchical data spanning outwards radially from root to leaves. The sunburst sector hierarchy is determined by the entries in `labels` (`names` in `px.sunburst`) and in `parents`. The root starts from the center and children are added to the outer rings. Main arguments: + 1. `labels` (`names` in `px.sunburst` since `labels` is reserved for overriding columns names): sets the labels of sunburst sectors. 2. `parents`: sets the parent sectors of sunburst sectors. An empty string `''` is used for the root node in the hierarchy. In this example, the root is "Eve". 3. `values`: sets the values associated with sunburst sectors, determining their width (See the `branchvalues` section below for different modes for setting the width). ### Basic Sunburst Plot with plotly.express -[Plotly Express](/python/plotly-express/) is the easy-to-use, high-level interface to Plotly, which [operates on "tidy" data](/python/px-arguments/). +[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.sunburst`, each row of the DataFrame is represented as a sector of the sunburst. @@ -75,15 +76,15 @@ fig.show() ### Sunburst of a rectangular DataFrame with continuous color argument in px.sunburst -If a `color` argument is passed, the color of a node is computed as the average of the color values of its children, weighted by their values. +If a `color` argument is passed, the color of a node is computed as the average of the color values of its children, weighted by their values. ```python import plotly.express as px import numpy as np df = px.data.gapminder().query("year == 2007") -fig = px.sunburst(df, path=['continent', 'country'], values='pop', +fig = px.sunburst(df, path=['continent', 'country'], values='pop', color='lifeExp', hover_data=['iso_alpha'], - color_continuous_scale='RdBu', + color_continuous_scale='RdBu', color_continuous_midpoint=np.average(df['lifeExp'], weights=df['pop'])) fig.show() ``` @@ -133,7 +134,6 @@ fig.show() If Plotly Express does not provide a good starting point, it is also possible to use the more generic `go.Sunburst` function from `plotly.graph_objects`. - ```python import plotly.graph_objects as go @@ -242,7 +242,7 @@ fig.show() The `insidetextorientation` attribute controls the orientation of text inside sectors. With "auto" the texts may automatically be rotated to fit with the maximum size inside the slice. Using "horizontal" (resp. "radial", "tangential") forces text to be horizontal (resp. radial or tangential). Note that `plotly` may reduce the font size in order to fit the text with the requested orientation. -For a figure `fig` created with plotly express, use `fig.update_traces(insidetextorientation='...')` to change the text orientation. +For a figure `fig` created with plotly express, use `fig.update_traces(insidetextorientation='...')` to change the text orientation. ```python import plotly.graph_objects as go @@ -288,7 +288,7 @@ fig.show() ### Sunburst chart with a continuous colorscale -The example below visualizes a breakdown of sales (corresponding to sector width) and call success rate (corresponding to sector color) by region, county and salesperson level. For example, when exploring the data you can see that although the East region is behaving poorly, the Tyler county is still above average -- however, its performance is reduced by the poor success rate of salesperson GT. +The example below visualizes a breakdown of sales (corresponding to sector width) and call success rate (corresponding to sector color) by region, county and salesperson level. For example, when exploring the data you can see that although the East region is behaving poorly, the Tyler county is still above average -- however, its performance is reduced by the poor success rate of salesperson GT. In the right subplot which has a `maxdepth` of two levels, click on a sector to see its breakdown to lower levels. @@ -308,7 +308,7 @@ def build_hierarchical_dataframe(df, levels, value_column, color_columns=None): """ Build a hierarchy of levels for Sunburst or Treemap charts. - Levels are given starting from the bottom to the top of the hierarchy, + Levels are given starting from the bottom to the top of the hierarchy, ie the last level corresponds to the root. """ df_all_trees = pd.DataFrame(columns=['id', 'parent', 'value', 'color']) @@ -324,7 +324,7 @@ def build_hierarchical_dataframe(df, levels, value_column, color_columns=None): df_tree['value'] = dfg[value_column] df_tree['color'] = dfg[color_columns[0]] / dfg[color_columns[1]] df_all_trees = df_all_trees.append(df_tree, ignore_index=True) - total = pd.Series(dict(id='total', parent='', + total = pd.Series(dict(id='total', parent='', value=df[value_column].sum(), color=df[color_columns[0]].sum() / df[color_columns[1]].sum())) df_all_trees = df_all_trees.append(total, ignore_index=True) @@ -335,7 +335,7 @@ df_all_trees = build_hierarchical_dataframe(df, levels, value_column, color_colu average_score = df['sales'].sum() / df['calls'].sum() fig = make_subplots(1, 2, specs=[[{"type": "domain"}, {"type": "domain"}]],) - + fig.add_trace(go.Sunburst( labels=df_all_trees['id'], parents=df_all_trees['parent'], @@ -367,4 +367,5 @@ fig.show() ``` #### Reference + See https://plot.ly/python/reference/#sunburst for more information and chart attribute options! diff --git a/doc/python/ternary-plots.md b/doc/python/ternary-plots.md index f26fbd532fb..195b028b5b1 100644 --- a/doc/python/ternary-plots.md +++ b/doc/python/ternary-plots.md @@ -30,7 +30,7 @@ jupyter: order: 4 page_type: example_index permalink: python/ternary-plots/ - thumbnail: thumbnail/ternary.jpg + thumbnail: thumbnail/v4-migration.png --- ### Basic Ternary Plot with Markers diff --git a/doc/python/treemaps.md b/doc/python/treemaps.md index 3d55ae316a6..36dd866e008 100644 --- a/doc/python/treemaps.md +++ b/doc/python/treemaps.md @@ -5,7 +5,7 @@ jupyter: text_representation: extension: .md format_name: markdown - format_version: '1.2' + format_version: "1.2" jupytext_version: 1.3.1 kernelspec: display_name: Python 3 @@ -33,12 +33,11 @@ jupyter: thumbnail: thumbnail/treemap.png --- - [Treemap charts](https://en.wikipedia.org/wiki/Treemapping) visualize hierarchical data using nested rectangles. Same as [Sunburst](https://plot.ly/python/sunburst-charts/) the hierarchy is defined by [labels](https://plot.ly/python/reference/#treemap-labels) (`names` for `px.treemap`) and [parents](https://plot.ly/python/reference/#treemap-parents) attributes. Click on one sector to zoom in/out, which also displays a pathbar in the upper-left corner of your treemap. To zoom out you can use the path bar as well. ### Basic Treemap with plotly.express -[Plotly Express](/python/plotly-express/) is the easy-to-use, high-level interface to Plotly, which [operates on "tidy" data](/python/px-arguments/). +[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.treemap`, each row of the DataFrame is represented as a sector of the treemap. @@ -123,11 +122,11 @@ print(df) fig = px.treemap(df, path=['all', 'regions', 'sectors', 'vendors'], values='sales') fig.show() ``` + ### Basic Treemap with go.Treemap If Plotly Express does not provide a good starting point, it is also possible to use the more generic `go.Treemap` function from `plotly.graph_objects`. - ```python import plotly.graph_objects as go @@ -143,11 +142,11 @@ fig.show() This example uses the following attributes: - 1. [values](https://plot.ly/python/reference/#treemap-values): sets the values associated with each of the sectors. - 2. [textinfo](https://plot.ly/python/reference/#treemap-textinfo): determines which trace information appear on the graph that can be 'text', 'value', 'current path', 'percent root', 'percent entry', and 'percent parent', or any combination of them. - 3. [pathbar](https://plot.ly/python/reference/#treemap-pathbar): a main extra feature of treemap to display the current path of the visible portion of the hierarchical map. It may also be useful for zooming out of the graph. - 4. [branchvalues](https://plot.ly/python/reference/#treemap-branchvalues): determines how the items in `values` are summed. When set to "total", items in `values` are taken to be value of all its descendants. In the example below Eva = 65, which is equal to 14 + 12 + 10 + 2 + 6 + 6 + 1 + 4. -When set to "remainder", items in `values` corresponding to the root and the branches sectors are taken to be the extra part not part of the sum of the values at their leaves. +1. [values](https://plot.ly/python/reference/#treemap-values): sets the values associated with each of the sectors. +2. [textinfo](https://plot.ly/python/reference/#treemap-textinfo): determines which trace information appear on the graph that can be 'text', 'value', 'current path', 'percent root', 'percent entry', and 'percent parent', or any combination of them. +3. [pathbar](https://plot.ly/python/reference/#treemap-pathbar): a main extra feature of treemap to display the current path of the visible portion of the hierarchical map. It may also be useful for zooming out of the graph. +4. [branchvalues](https://plot.ly/python/reference/#treemap-branchvalues): determines how the items in `values` are summed. When set to "total", items in `values` are taken to be value of all its descendants. In the example below Eva = 65, which is equal to 14 + 12 + 10 + 2 + 6 + 6 + 1 + 4. + When set to "remainder", items in `values` corresponding to the root and the branches sectors are taken to be the extra part not part of the sum of the values at their leaves. ```python import plotly.graph_objects as go @@ -188,7 +187,8 @@ fig.show() ### Set Color of Treemap Sectors There are three different ways to change the color of the sectors in Treemap: - 1) [marker.colors](https://plot.ly/python/reference/#treemap-marker-colors), 2) [colorway](https://plot.ly/python/reference/#treemap-colorway), 3) [colorscale](https://plot.ly/python/reference/#treemap-colorscale). The following examples show how to use each of them. + +1. [marker.colors](https://plot.ly/python/reference/#treemap-marker-colors), 2) [colorway](https://plot.ly/python/reference/#treemap-colorway), 3) [colorscale](https://plot.ly/python/reference/#treemap-colorscale). The following examples show how to use each of them. ```python import plotly.graph_objects as go @@ -240,7 +240,7 @@ fig.show() ### Treemap chart with a continuous colorscale -The example below visualizes a breakdown of sales (corresponding to sector width) and call success rate (corresponding to sector color) by region, county and salesperson level. For example, when exploring the data you can see that although the East region is behaving poorly, the Tyler county is still above average -- however, its performance is reduced by the poor success rate of salesperson GT. +The example below visualizes a breakdown of sales (corresponding to sector width) and call success rate (corresponding to sector color) by region, county and salesperson level. For example, when exploring the data you can see that although the East region is behaving poorly, the Tyler county is still above average -- however, its performance is reduced by the poor success rate of salesperson GT. In the right subplot which has a `maxdepth` of two levels, click on a sector to see its breakdown to lower levels. @@ -260,7 +260,7 @@ def build_hierarchical_dataframe(df, levels, value_column, color_columns=None): """ Build a hierarchy of levels for Sunburst or Treemap charts. - Levels are given starting from the bottom to the top of the hierarchy, + Levels are given starting from the bottom to the top of the hierarchy, ie the last level corresponds to the root. """ df_all_trees = pd.DataFrame(columns=['id', 'parent', 'value', 'color']) @@ -276,7 +276,7 @@ def build_hierarchical_dataframe(df, levels, value_column, color_columns=None): df_tree['value'] = dfg[value_column] df_tree['color'] = dfg[color_columns[0]] / dfg[color_columns[1]] df_all_trees = df_all_trees.append(df_tree, ignore_index=True) - total = pd.Series(dict(id='total', parent='', + total = pd.Series(dict(id='total', parent='', value=df[value_column].sum(), color=df[color_columns[0]].sum() / df[color_columns[1]].sum())) df_all_trees = df_all_trees.append(total, ignore_index=True) @@ -287,7 +287,7 @@ df_all_trees = build_hierarchical_dataframe(df, levels, value_column, color_colu average_score = df['sales'].sum() / df['calls'].sum() fig = make_subplots(1, 2, specs=[[{"type": "domain"}, {"type": "domain"}]],) - + fig.add_trace(go.Treemap( labels=df_all_trees['id'], parents=df_all_trees['parent'], @@ -378,4 +378,5 @@ fig.show() ``` #### Reference + See https://plot.ly/python/reference/#treemap for more information and chart attribute options! diff --git a/doc/python/violin.md b/doc/python/violin.md index 9665755b5ed..149f7f49c9c 100644 --- a/doc/python/violin.md +++ b/doc/python/violin.md @@ -42,7 +42,7 @@ See also the [list of other statistical charts](https://plot.ly/python/statistic ### Basic Violin Plot with Plotly Express -[Plotly Express](/python/plotly-express/) is the easy-to-use, high-level interface to Plotly, which [operates on "tidy" data](/python/px-arguments/). +[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/). ```python import plotly.express as px diff --git a/doc/python/webgl-vs-svg.md b/doc/python/webgl-vs-svg.md index 7b30d51527c..ff11ace247f 100644 --- a/doc/python/webgl-vs-svg.md +++ b/doc/python/webgl-vs-svg.md @@ -33,6 +33,10 @@ jupyter: thumbnail: thumbnail/webgl.jpg --- +Here we show that it is possible to represent millions of points with WebGL. +For larger datasets, or for a clearer visualization of the density of points, +it is also possible to use [datashader](/python/datashader/). + #### Compare WebGL and SVG Checkout [this notebook](https://plot.ly/python/compare-webgl-svg) to compare WebGL and SVG scatter plots with 75,000 random data points diff --git a/doc/python/wind-rose-charts.md b/doc/python/wind-rose-charts.md index 7aa46ea7b4a..f2596fa4120 100644 --- a/doc/python/wind-rose-charts.md +++ b/doc/python/wind-rose-charts.md @@ -39,7 +39,7 @@ jupyter: A [wind rose chart](https://en.wikipedia.org/wiki/Wind_rose) (also known as a polar bar chart) is a graphical tool used to visualize how wind speed and direction are typically distributed at a given location. You can use the `px.bar_polar` function from Plotly Express as below, otherwise use `go.Barpolar` as explained in the next section. -[Plotly Express](/python/plotly-express/) is the easy-to-use, high-level interface to Plotly, which [operates on "tidy" data](/python/px-arguments/). +[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/). ```python import plotly.express as px diff --git a/doc/requirements.txt b/doc/requirements.txt index 988f05efdbb..37d0944f8f2 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -22,3 +22,5 @@ sphinx_bootstrap_theme recommonmark pathlib python-frontmatter +datashader +pyarrow From 00c3238b3fd1bda6c39b72897dc42b671b9b2d1b Mon Sep 17 00:00:00 2001 From: Joseph Damiba Date: Tue, 11 Feb 2020 17:50:40 -0500 Subject: [PATCH 12/21] prevent creation of new px.default properties at run time --- app.py | 10 ++++++++++ packages/python/plotly/plotly/express/_core.py | 6 ++++-- 2 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 app.py diff --git a/app.py b/app.py new file mode 100644 index 00000000000..2df9fe3bc9b --- /dev/null +++ b/app.py @@ -0,0 +1,10 @@ +import plotly.express as px +px.defaults.template = "ggplot2" +px.defaults.color_continuous_scale = px.colors.sequential.Blackbody +px.defaults.width = 600 +px.defaults.height = 400 +px.defaults.blerd = "hlkjhlkjhjh" + +df = px.data.iris() +fig = px.scatter(df, x="sepal_width", y="sepal_length", color="sepal_length", width=400) +fig.show() \ No newline at end of file diff --git a/packages/python/plotly/plotly/express/_core.py b/packages/python/plotly/plotly/express/_core.py index db8f002c2c1..2ab68e63c6f 100644 --- a/packages/python/plotly/plotly/express/_core.py +++ b/packages/python/plotly/plotly/express/_core.py @@ -16,6 +16,8 @@ class PxDefaults(object): + __slots__ = ['template', 'width', 'height', 'color_discrete_sequence', 'color_continuous_scale', 'symbol_sequence', 'line_dash_sequence', 'size_max'] + def __init__(self): self.template = None self.width = None @@ -25,8 +27,7 @@ def __init__(self): self.symbol_sequence = None self.line_dash_sequence = None self.size_max = 20 - - + defaults = PxDefaults() del PxDefaults @@ -740,6 +741,7 @@ def one_group(x): def apply_default_cascade(args): # first we apply px.defaults to unspecified args + for param in ( ["color_discrete_sequence", "color_continuous_scale"] From 0eda9f828253eed40c821a03609e5c20fa56a89e Mon Sep 17 00:00:00 2001 From: Joseph Damiba Date: Tue, 11 Feb 2020 17:52:29 -0500 Subject: [PATCH 13/21] prevent creation of new px.default properties at run time --- app.py | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 app.py diff --git a/app.py b/app.py deleted file mode 100644 index 2df9fe3bc9b..00000000000 --- a/app.py +++ /dev/null @@ -1,10 +0,0 @@ -import plotly.express as px -px.defaults.template = "ggplot2" -px.defaults.color_continuous_scale = px.colors.sequential.Blackbody -px.defaults.width = 600 -px.defaults.height = 400 -px.defaults.blerd = "hlkjhlkjhjh" - -df = px.data.iris() -fig = px.scatter(df, x="sepal_width", y="sepal_length", color="sepal_length", width=400) -fig.show() \ No newline at end of file From 633c2da5fd861086df4e5ff8581b5617f05e71a1 Mon Sep 17 00:00:00 2001 From: Joseph Damiba Date: Tue, 11 Feb 2020 17:59:33 -0500 Subject: [PATCH 14/21] run black --- packages/python/plotly/plotly/express/_core.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/packages/python/plotly/plotly/express/_core.py b/packages/python/plotly/plotly/express/_core.py index 2ab68e63c6f..1f9e2706af7 100644 --- a/packages/python/plotly/plotly/express/_core.py +++ b/packages/python/plotly/plotly/express/_core.py @@ -16,8 +16,17 @@ class PxDefaults(object): - __slots__ = ['template', 'width', 'height', 'color_discrete_sequence', 'color_continuous_scale', 'symbol_sequence', 'line_dash_sequence', 'size_max'] - + __slots__ = [ + "template", + "width", + "height", + "color_discrete_sequence", + "color_continuous_scale", + "symbol_sequence", + "line_dash_sequence", + "size_max", + ] + def __init__(self): self.template = None self.width = None @@ -27,7 +36,8 @@ def __init__(self): self.symbol_sequence = None self.line_dash_sequence = None self.size_max = 20 - + + defaults = PxDefaults() del PxDefaults @@ -741,7 +751,6 @@ def one_group(x): def apply_default_cascade(args): # first we apply px.defaults to unspecified args - for param in ( ["color_discrete_sequence", "color_continuous_scale"] From bae04837dfda371c407c9617ad151b4aac7f3c27 Mon Sep 17 00:00:00 2001 From: Joseph Damiba Date: Tue, 11 Feb 2020 20:33:28 -0500 Subject: [PATCH 15/21] add test for permissive defaults --- .../plotly/plotly/tests/test_core/test_px/test_px.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/python/plotly/plotly/tests/test_core/test_px/test_px.py b/packages/python/plotly/plotly/tests/test_core/test_px/test_px.py index 9ace6a7b4a9..6a283ede030 100644 --- a/packages/python/plotly/plotly/tests/test_core/test_px/test_px.py +++ b/packages/python/plotly/plotly/tests/test_core/test_px/test_px.py @@ -1,5 +1,7 @@ import plotly.express as px import numpy as np +import pytest + def test_scatter(): @@ -232,3 +234,9 @@ def assert_orderings(days_order, days_check, times_order, times_check): for days in permutations(df["day"].unique()): for times in permutations(df["time"].unique()): assert_orderings(days, days, times, times) + +def check_permissive_defaults(): + msg = "AttributeError: 'PxDefaults' object has no attribute 'should_not_work'" + with pytest.raises(AttributeError, match=msg): + px.defaults.should_not_work = "test" + \ No newline at end of file From 58aeb32627669340a9e7b01ae83973c109d260c7 Mon Sep 17 00:00:00 2001 From: Joseph Damiba Date: Tue, 11 Feb 2020 20:34:43 -0500 Subject: [PATCH 16/21] formatting --- .../python/plotly/plotly/tests/test_core/test_px/test_px.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/python/plotly/plotly/tests/test_core/test_px/test_px.py b/packages/python/plotly/plotly/tests/test_core/test_px/test_px.py index 6a283ede030..a681442d885 100644 --- a/packages/python/plotly/plotly/tests/test_core/test_px/test_px.py +++ b/packages/python/plotly/plotly/tests/test_core/test_px/test_px.py @@ -3,7 +3,6 @@ import pytest - def test_scatter(): iris = px.data.iris() fig = px.scatter(iris, x="sepal_width", y="sepal_length") @@ -238,5 +237,4 @@ def assert_orderings(days_order, days_check, times_order, times_check): def check_permissive_defaults(): msg = "AttributeError: 'PxDefaults' object has no attribute 'should_not_work'" with pytest.raises(AttributeError, match=msg): - px.defaults.should_not_work = "test" - \ No newline at end of file + px.defaults.should_not_work = "test" \ No newline at end of file From 756f09bd3419713910ff27ce192039307dd646db Mon Sep 17 00:00:00 2001 From: Joseph Damiba Date: Tue, 11 Feb 2020 20:35:32 -0500 Subject: [PATCH 17/21] formatting --- .../python/plotly/plotly/tests/test_core/test_px/test_px.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/python/plotly/plotly/tests/test_core/test_px/test_px.py b/packages/python/plotly/plotly/tests/test_core/test_px/test_px.py index a681442d885..d04e97809f6 100644 --- a/packages/python/plotly/plotly/tests/test_core/test_px/test_px.py +++ b/packages/python/plotly/plotly/tests/test_core/test_px/test_px.py @@ -234,7 +234,8 @@ def assert_orderings(days_order, days_check, times_order, times_check): for times in permutations(df["time"].unique()): assert_orderings(days, days, times, times) + def check_permissive_defaults(): msg = "AttributeError: 'PxDefaults' object has no attribute 'should_not_work'" with pytest.raises(AttributeError, match=msg): - px.defaults.should_not_work = "test" \ No newline at end of file + px.defaults.should_not_work = "test" From bdda85167e0bde72bda70d0f5f18ec9a6554fd78 Mon Sep 17 00:00:00 2001 From: Joseph Damiba Date: Wed, 12 Feb 2020 10:59:25 -0500 Subject: [PATCH 18/21] make sure new test is picked up by pytest --- .../python/plotly/plotly/tests/test_core/test_px/test_px.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/python/plotly/plotly/tests/test_core/test_px/test_px.py b/packages/python/plotly/plotly/tests/test_core/test_px/test_px.py index d04e97809f6..b4880899e95 100644 --- a/packages/python/plotly/plotly/tests/test_core/test_px/test_px.py +++ b/packages/python/plotly/plotly/tests/test_core/test_px/test_px.py @@ -235,7 +235,7 @@ def assert_orderings(days_order, days_check, times_order, times_check): assert_orderings(days, days, times, times) -def check_permissive_defaults(): - msg = "AttributeError: 'PxDefaults' object has no attribute 'should_not_work'" +def test_permissive_defaults(): + msg = "'PxDefaults' object has no attribute 'should_not_work'" with pytest.raises(AttributeError, match=msg): px.defaults.should_not_work = "test" From ca903a48034412fcf474843a3587052b91fe263c Mon Sep 17 00:00:00 2001 From: Joseph Damiba Date: Wed, 12 Feb 2020 13:34:25 -0500 Subject: [PATCH 19/21] add marker symbol example --- doc/python/marker-style.md | 111 ++++++++++++++++++++++++++++++++++++- 1 file changed, 108 insertions(+), 3 deletions(-) diff --git a/doc/python/marker-style.md b/doc/python/marker-style.md index e456d7f41f9..2c8f8522062 100644 --- a/doc/python/marker-style.md +++ b/doc/python/marker-style.md @@ -5,8 +5,8 @@ jupyter: text_representation: extension: .md format_name: markdown - format_version: "1.1" - jupytext_version: 1.1.7 + format_version: '1.2' + jupytext_version: 1.3.2 kernelspec: display_name: Python 3 language: python @@ -20,7 +20,7 @@ jupyter: name: python nbconvert_exporter: python pygments_lexer: ipython3 - version: 3.6.5 + version: 3.7.0 plotly: description: How to style markers in Python with Plotly. display_as: file_settings @@ -305,6 +305,111 @@ fig.show() ``` +### Custom Marker Symbols + +The `marker_symbol` attribute allows you to customize the symbol used to represent markers. + +In the following figure, hover over a symbol to see its name. Set the `marker_symbol` attribute equal to that name to change the marker symbol in your figure to use it. + +```python +import plotly.graph_objects as go + +symbols = [0, 'circle', 100, 'circle-open', 200, 'circle-dot', 300, + 'circle-open-dot', 1, 'square', 101, 'square-open', 201, + 'square-dot', 301, 'square-open-dot', 2, 'diamond', 102, + 'diamond-open', 202, 'diamond-dot', 302, + 'diamond-open-dot', 3, 'cross', 103, 'cross-open', 203, + 'cross-dot', 303, 'cross-open-dot', 4, 'x', 104, 'x-open', + 204, 'x-dot', 304, 'x-open-dot', 5, 'triangle-up', 105, + 'triangle-up-open', 205, 'triangle-up-dot', 305, + 'triangle-up-open-dot', 6, 'triangle-down', 106, + 'triangle-down-open', 206, 'triangle-down-dot', 306, + 'triangle-down-open-dot', 7, 'triangle-left', 107, + 'triangle-left-open', 207, 'triangle-left-dot', 307, + 'triangle-left-open-dot', 8, 'triangle-right', 108, + 'triangle-right-open', 208, 'triangle-right-dot', 308, + 'triangle-right-open-dot', 9, 'triangle-ne', 109, + 'triangle-ne-open', 209, 'triangle-ne-dot', 309, + 'triangle-ne-open-dot', 10, 'triangle-se', 110, + 'triangle-se-open', 210, 'triangle-se-dot', 310, + 'triangle-se-open-dot', 11, 'triangle-sw', 111, + 'triangle-sw-open', 211, 'triangle-sw-dot', 311, + 'triangle-sw-open-dot', 12, 'triangle-nw', 112, + 'triangle-nw-open', 212, 'triangle-nw-dot', 312, + 'triangle-nw-open-dot', 13, 'pentagon', 113, + 'pentagon-open', 213, 'pentagon-dot', 313, + 'pentagon-open-dot', 14, 'hexagon', 114, 'hexagon-open', + 214, 'hexagon-dot', 314, 'hexagon-open-dot', 15, + 'hexagon2', 115, 'hexagon2-open', 215, 'hexagon2-dot', + 315, 'hexagon2-open-dot', 16, 'octagon', 116, + 'octagon-open', 216, 'octagon-dot', 316, + 'octagon-open-dot', 17, 'star', 117, 'star-open', 217, + 'star-dot', 317, 'star-open-dot', 18, 'hexagram', 118, + 'hexagram-open', 218, 'hexagram-dot', 318, + 'hexagram-open-dot', 19, 'star-triangle-up', 119, + 'star-triangle-up-open', 219, 'star-triangle-up-dot', 319, + 'star-triangle-up-open-dot', 20, 'star-triangle-down', + 120, 'star-triangle-down-open', 220, + 'star-triangle-down-dot', 320, + 'star-triangle-down-open-dot', 21, 'star-square', 121, + 'star-square-open', 221, 'star-square-dot', 321, + 'star-square-open-dot', 22, 'star-diamond', 122, + 'star-diamond-open', 222, 'star-diamond-dot', 322, + 'star-diamond-open-dot', 23, 'diamond-tall', 123, + 'diamond-tall-open', 223, 'diamond-tall-dot', 323, + 'diamond-tall-open-dot', 24, 'diamond-wide', 124, + 'diamond-wide-open', 224, 'diamond-wide-dot', 324, + 'diamond-wide-open-dot', 25, 'hourglass', 125, + 'hourglass-open', 26, 'bowtie', 126, 'bowtie-open', 27, + 'circle-cross', 127, 'circle-cross-open', 28, 'circle-x', + 128, 'circle-x-open', 29, 'square-cross', 129, + 'square-cross-open', 30, 'square-x', 130, 'square-x-open', + 31, 'diamond-cross', 131, 'diamond-cross-open', 32, + 'diamond-x', 132, 'diamond-x-open', 33, 'cross-thin', 133, + 'cross-thin-open', 34, 'x-thin', 134, 'x-thin-open', 35, + 'asterisk', 135, 'asterisk-open', 36, 'hash', 136, + 'hash-open', 236, 'hash-dot', 336, 'hash-open-dot', 37, + 'y-up', 137, 'y-up-open', 38, 'y-down', 138, + 'y-down-open', 39, 'y-left', 139, 'y-left-open', 40, + 'y-right', 140, 'y-right-open', 41, 'line-ew', 141, + 'line-ew-open', 42, 'line-ns', 142, 'line-ns-open', 43, + 'line-ne', 143, 'line-ne-open', 44, 'line-nw', 144, + 'line-nw-open'] + + + +fig = go.Figure() +fig.update_layout( + title="Custom Marker Symbols", +) + +x_axis_offset = 0; +for index, symbol in enumerate(symbols): + if index >= 0 and index <= 60: + fig.add_trace(go.Scatter(x=[index + x_axis_offset], y=[1], + marker_symbol=symbol, marker_color='black', marker_size=10, showlegend=False, hovertext=symbols[index])) + + if index >= 61 and index <= 120: + fig.add_trace(go.Scatter(x=[(index-244) + x_axis_offset], y=[1.2], + marker_symbol=symbol, marker_color='black', marker_size=10, hoverinfo="text", showlegend=False, hovertext=symbols[index])) + if index >= 121 and index <= 180: + fig.add_trace(go.Scatter(x=[(index-484) + x_axis_offset], y=[1.4], + marker_symbol=symbol, marker_color='black', marker_size=10, hoverinfo="text", showlegend=False, hovertext=symbols[index])) + + if index >= 181 and index <= 240: + fig.add_trace(go.Scatter(x=[(index-724) + x_axis_offset], y=[1.6], + marker_symbol=symbol, marker_color='black', marker_size=10, showlegend=False, hovertext=symbols[index])) + if index >= 241 and index <= 300: + fig.add_trace(go.Scatter(x=[(index-964) + x_axis_offset], y=[1.8], + marker_symbol=symbol, marker_color='black', marker_size=10, showlegend=False, hovertext=symbols[index])) + + x_axis_offset += 3 + + +fig.show() +``` + + ### Reference See https://plot.ly/python/reference/ for more information and chart attribute options! From 061afb5534749d4ab11ae9049ce8e3ba0caabb83 Mon Sep 17 00:00:00 2001 From: Joseph Damiba Date: Wed, 12 Feb 2020 13:37:26 -0500 Subject: [PATCH 20/21] fix typo --- doc/python/marker-style.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/python/marker-style.md b/doc/python/marker-style.md index 2c8f8522062..2d6a19a0385 100644 --- a/doc/python/marker-style.md +++ b/doc/python/marker-style.md @@ -309,7 +309,7 @@ fig.show() The `marker_symbol` attribute allows you to customize the symbol used to represent markers. -In the following figure, hover over a symbol to see its name. Set the `marker_symbol` attribute equal to that name to change the marker symbol in your figure to use it. +In the following figure, hover over a symbol to see its name. Set the `marker_symbol` attribute equal to that name to change the marker symbol in your figure. ```python import plotly.graph_objects as go From fe369aa7658a0b22c9e5f8d9d469630e5684ffdf Mon Sep 17 00:00:00 2001 From: Joseph Damiba Date: Thu, 13 Feb 2020 17:37:10 -0500 Subject: [PATCH 21/21] be more explicit about basic/advanced shapes --- doc/python/marker-style.md | 62 ++++++++++++++++++++------------------ 1 file changed, 33 insertions(+), 29 deletions(-) diff --git a/doc/python/marker-style.md b/doc/python/marker-style.md index 2d6a19a0385..57f80b57fcc 100644 --- a/doc/python/marker-style.md +++ b/doc/python/marker-style.md @@ -307,13 +307,35 @@ fig.show() ### Custom Marker Symbols -The `marker_symbol` attribute allows you to customize the symbol used to represent markers. +The `marker_symbol` attribute allows you to choose from a wide array of symbols to represent markers in your figures. -In the following figure, hover over a symbol to see its name. Set the `marker_symbol` attribute equal to that name to change the marker symbol in your figure. +The basic symbols are: `circle`, `square`, `diamond`, `cross`, `x`, `triangle`, `pentagon`, `hexagram`, `star`, `diamond`, `hourglass`, `bowtie`, `asterisk`, `hash`, `y`, and `line`. + +Each basic symbol is also represented by a number. Adding 100 to that number is equivalent to appending the suffix "-open" to a symbol name. Adding 200 is equivalent to appending "-dot" to a symbol name. Adding 300 is equivalent to appending "-open-dot" or "dot-open" to a symbol name. + +In the following figures, hover over a symbol to see its name or number. Set the `marker_symbol` attribute equal to that name or number to change the marker symbol in your figure. + +#### Basic Symbols ```python import plotly.graph_objects as go +fig = go.Figure() +fig.update_layout(title="Basic Symbols") +fig.update_xaxes(showticklabels=False) +fig.update_yaxes(showticklabels=False) + +for index in range(27): + fig.add_trace(go.Scatter(x=[(index % 30)], y=[index // 30], + marker_symbol=index, marker_color='black', + marker_size=10, showlegend=False, hovertext=index)) + +fig.show() +``` + +#### Custom Symbols +```python +import plotly.graph_objects as go symbols = [0, 'circle', 100, 'circle-open', 200, 'circle-dot', 300, 'circle-open-dot', 1, 'square', 101, 'square-open', 201, 'square-dot', 301, 'square-open-dot', 2, 'diamond', 102, @@ -376,36 +398,18 @@ symbols = [0, 'circle', 100, 'circle-open', 200, 'circle-dot', 300, 'line-ne', 143, 'line-ne-open', 44, 'line-nw', 144, 'line-nw-open'] - - fig = go.Figure() -fig.update_layout( - title="Custom Marker Symbols", -) +fig.update_layout(title="Custom Marker Symbols") +fig.update_xaxes(showticklabels=False) +fig.update_yaxes(showticklabels=False) -x_axis_offset = 0; -for index, symbol in enumerate(symbols): - if index >= 0 and index <= 60: - fig.add_trace(go.Scatter(x=[index + x_axis_offset], y=[1], - marker_symbol=symbol, marker_color='black', marker_size=10, showlegend=False, hovertext=symbols[index])) - - if index >= 61 and index <= 120: - fig.add_trace(go.Scatter(x=[(index-244) + x_axis_offset], y=[1.2], - marker_symbol=symbol, marker_color='black', marker_size=10, hoverinfo="text", showlegend=False, hovertext=symbols[index])) - if index >= 121 and index <= 180: - fig.add_trace(go.Scatter(x=[(index-484) + x_axis_offset], y=[1.4], - marker_symbol=symbol, marker_color='black', marker_size=10, hoverinfo="text", showlegend=False, hovertext=symbols[index])) - - if index >= 181 and index <= 240: - fig.add_trace(go.Scatter(x=[(index-724) + x_axis_offset], y=[1.6], - marker_symbol=symbol, marker_color='black', marker_size=10, showlegend=False, hovertext=symbols[index])) - if index >= 241 and index <= 300: - fig.add_trace(go.Scatter(x=[(index-964) + x_axis_offset], y=[1.8], - marker_symbol=symbol, marker_color='black', marker_size=10, showlegend=False, hovertext=symbols[index])) - - x_axis_offset += 3 - +for index, symbol in enumerate(symbols[::2]): + fig.add_trace(go.Scatter(x=[(index % 30)], y=[index // 30], + marker_symbol=symbol, marker_color='black', + marker_size=10, showlegend=False, hovertext=symbols[2*index + 1], + name='')) + fig.show() ```