diff --git a/packages/python/plotly/plotly/express/__init__.py b/packages/python/plotly/plotly/express/__init__.py index c825233aca..eec2fd9da1 100644 --- a/packages/python/plotly/plotly/express/__init__.py +++ b/packages/python/plotly/plotly/express/__init__.py @@ -4,6 +4,7 @@ """ from __future__ import absolute_import from plotly import optional_imports +from ._imshow import imshow pd = optional_imports.get_module("pandas") if pd is None: @@ -12,7 +13,6 @@ Plotly express requires pandas to be installed.""" ) - from ._chart_types import ( # noqa: F401 scatter, scatter_3d, @@ -44,9 +44,10 @@ treemap, funnel, funnel_area, + choropleth_mapbox, + density_mapbox, ) -from ._imshow import imshow from ._core import ( # noqa: F401 set_mapbox_access_token, @@ -66,6 +67,7 @@ "scatter_matrix", "density_contour", "density_heatmap", + "density_mapbox", "line", "line_3d", "line_polar", @@ -82,6 +84,7 @@ "strip", "histogram", "choropleth", + "choropleth_mapbox", "pie", "sunburst", "treemap", diff --git a/packages/python/plotly/plotly/express/_chart_types.py b/packages/python/plotly/plotly/express/_chart_types.py index 8cbd4d85b6..7644a63819 100644 --- a/packages/python/plotly/plotly/express/_chart_types.py +++ b/packages/python/plotly/plotly/express/_chart_types.py @@ -842,7 +842,6 @@ def choropleth( hover_name=None, hover_data=None, custom_data=None, - size=None, animation_frame=None, animation_group=None, category_orders={}, @@ -850,7 +849,6 @@ def choropleth( color_continuous_scale=None, range_color=None, color_continuous_midpoint=None, - size_max=None, projection=None, scope=None, center=None, @@ -983,6 +981,8 @@ def scatter_mapbox( opacity=None, size_max=None, zoom=8, + center=None, + mapbox_style=None, title=None, template=None, width=None, @@ -998,6 +998,85 @@ def scatter_mapbox( scatter_mapbox.__doc__ = make_docstring(scatter_mapbox) +def choropleth_mapbox( + data_frame=None, + geojson=None, + locations=None, + color=None, + hover_name=None, + hover_data=None, + custom_data=None, + animation_frame=None, + animation_group=None, + category_orders={}, + labels={}, + color_continuous_scale=None, + range_color=None, + color_continuous_midpoint=None, + opacity=None, + zoom=8, + center=None, + mapbox_style=None, + title=None, + template=None, + width=None, + height=None, +): + """ + In a Mapbox choropleth map, each row of `data_frame` is represented by a + colored region on a Mapbox map. + """ + return make_figure( + args=locals(), + constructor=go.Choroplethmapbox, + trace_patch=dict( + geojson=geojson + if not hasattr(geojson, "__geo_interface__") + else geojson.__geo_interface__ + ), + ) + + +choropleth_mapbox.__doc__ = make_docstring(choropleth_mapbox) + + +def density_mapbox( + data_frame=None, + lat=None, + lon=None, + z=None, + hover_name=None, + hover_data=None, + custom_data=None, + animation_frame=None, + animation_group=None, + category_orders={}, + labels={}, + color_continuous_scale=None, + range_color=None, + color_continuous_midpoint=None, + opacity=None, + zoom=8, + center=None, + mapbox_style=None, + radius=None, + title=None, + template=None, + width=None, + height=None, +): + """ + In a Mapbox density map, each row of `data_frame` contributes to the intensity of + the color of the region around the corresponding point on the map + """ + return make_figure( + args=locals(), constructor=go.Densitymapbox, trace_patch=dict(radius=radius) + ) + + +density_mapbox.__doc__ = make_docstring(density_mapbox) + + def line_mapbox( data_frame=None, lat=None, @@ -1015,6 +1094,8 @@ def line_mapbox( color_discrete_sequence=None, color_discrete_map={}, zoom=8, + center=None, + mapbox_style=None, title=None, template=None, width=None, diff --git a/packages/python/plotly/plotly/express/_core.py b/packages/python/plotly/plotly/express/_core.py index 459168ea02..1cecba9ba8 100644 --- a/packages/python/plotly/plotly/express/_core.py +++ b/packages/python/plotly/plotly/express/_core.py @@ -287,7 +287,7 @@ def make_trace_kwargs(args, trace_spec, g, mapping_labels, sizeref): v_label_col = get_decorated_label(args, col, None) mapping_labels[v_label_col] = "%%{customdata[%d]}" % (position) elif k == "color": - if trace_spec.constructor == go.Choropleth: + if trace_spec.constructor in [go.Choropleth, go.Choroplethmapbox]: result["z"] = g[v] result["coloraxis"] = "coloraxis1" mapping_labels[v_label] = "%{z}" @@ -380,6 +380,8 @@ def configure_axes(args, constructor, fig, orders): go.Scatterpolargl: configure_polar_axes, go.Barpolar: configure_polar_axes, go.Scattermapbox: configure_mapbox, + go.Choroplethmapbox: configure_mapbox, + go.Densitymapbox: configure_mapbox, go.Scattergeo: configure_geo, go.Choropleth: configure_geo, } @@ -502,13 +504,11 @@ def configure_cartesian_axes(args, fig, orders): def configure_ternary_axes(args, fig, orders): - fig.update( - layout=dict( - ternary=dict( - aaxis=dict(title=get_label(args, args["a"])), - baxis=dict(title=get_label(args, args["b"])), - caxis=dict(title=get_label(args, args["c"])), - ) + fig.update_layout( + ternary=dict( + aaxis=dict(title=get_label(args, args["a"])), + baxis=dict(title=get_label(args, args["b"])), + caxis=dict(title=get_label(args, args["c"])), ) ) @@ -562,28 +562,28 @@ def configure_3d_axes(args, fig, orders): def configure_mapbox(args, fig, orders): - fig.update( - layout=dict( - mapbox=dict( - accesstoken=MAPBOX_TOKEN, - center=dict( - lat=args["data_frame"][args["lat"]].mean(), - lon=args["data_frame"][args["lon"]].mean(), - ), - zoom=args["zoom"], - ) + center = args["center"] + if not center and "lat" in args and "lon" in args: + center = dict( + lat=args["data_frame"][args["lat"]].mean(), + lon=args["data_frame"][args["lon"]].mean(), + ) + fig.update_layout( + mapbox=dict( + accesstoken=MAPBOX_TOKEN, + center=center, + zoom=args["zoom"], + style=args["mapbox_style"], ) ) def configure_geo(args, fig, orders): - fig.update( - layout=dict( - geo=dict( - center=args["center"], - scope=args["scope"], - projection=dict(type=args["projection"]), - ) + fig.update_layout( + geo=dict( + center=args["center"], + scope=args["scope"], + projection=dict(type=args["projection"]), ) ) @@ -1083,7 +1083,7 @@ def infer_config(args, constructor, trace_patch): # Compute final trace patch trace_patch = trace_patch.copy() - if constructor == go.Histogram2d: + if constructor in [go.Histogram2d, go.Densitymapbox]: show_colorbar = True trace_patch["coloraxis"] = "coloraxis1" @@ -1221,6 +1221,8 @@ def make_figure(args, constructor, trace_patch={}, layout_patch={}): go.Parcats, go.Parcoords, go.Choropleth, + go.Choroplethmapbox, + go.Densitymapbox, go.Histogram2d, go.Sunburst, go.Treemap, @@ -1321,7 +1323,7 @@ def make_figure(args, constructor, trace_patch={}, layout_patch={}): ) layout_patch = layout_patch.copy() if show_colorbar: - colorvar = "z" if constructor == go.Histogram2d else "color" + colorvar = "z" if constructor in [go.Histogram2d, go.Densitymapbox] else "color" range_color = args["range_color"] or [None, None] colorscale_validator = ColorscaleValidator("colorscale", "make_figure") diff --git a/packages/python/plotly/plotly/express/_doc.py b/packages/python/plotly/plotly/express/_doc.py index c1d015cbe3..ae9d42b229 100644 --- a/packages/python/plotly/plotly/express/_doc.py +++ b/packages/python/plotly/plotly/express/_doc.py @@ -164,6 +164,7 @@ colref_desc, "Values from this column or array_like are used to assign mark sizes.", ], + radius=["int (default is 30)", "Sets the radius of influence of each point.",], hover_name=[ colref_type, colref_desc, @@ -445,6 +446,12 @@ "Dict keys are `'lat'` and `'lon'`", "Sets the center point of the map.", ], + mapbox_style=[ + "str (default `'basic'`, needs Mapbox API token)", + "Identifier of base map style, some of which require a Mapbox API token to be set using `plotly.express.set_mapbox_access_token()`.", + "Allowed values which do not require a Mapbox API token are `'open-street-map'`, `'white-bg'`, `'carto-positron'`, `'carto-darkmatter'`, `'stamen-terrain'`, `'stamen-toner'`, `'stamen-watercolor'`.", + "Allowed values which do require a Mapbox API token are `'basic'`, `'streets'`, `'outdoors'`, `'light'`, `'dark'`, `'satellite'`, `'satellite-streets'`.", + ], points=[ "str or boolean (default `'outliers'`)", "One of `'outliers'`, `'suspectedoutliers'`, `'all'`, or `False`.", @@ -456,6 +463,10 @@ ], box=["boolean (default `False`)", "If `True`, boxes are drawn inside the violins."], notched=["boolean (default `False`)", "If `True`, boxes are drawn with notches."], + geojson=[ + "GeoJSON-formatted dict", + "Must contain a Polygon feature collection, with IDs, which are references from `locations`.", + ], cumulative=[ "boolean (default `False`)", "If `True`, histogram values are cumulative.", diff --git a/packages/python/plotly/plotly/package_data/datasets/election.csv.gz b/packages/python/plotly/plotly/package_data/datasets/election.csv.gz index 710a53a535..9038c29a41 100644 Binary files a/packages/python/plotly/plotly/package_data/datasets/election.csv.gz and b/packages/python/plotly/plotly/package_data/datasets/election.csv.gz differ diff --git a/test/percy/plotly-express.py b/test/percy/plotly-express.py index 0342f33394..854e2d2369 100644 --- a/test/percy/plotly-express.py +++ b/test/percy/plotly-express.py @@ -435,6 +435,37 @@ import plotly.express as px +sample_geojson = { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "id": "the_polygon", + "geometry": { + "type": "Polygon", + "coordinates": [ + [[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0], [0.0, 0.0]] + ], + }, + } + ], +} +fig = px.choropleth_mapbox( + geojson=sample_geojson, locations=["the_polygon"], color=[10], zoom=6, +) +fig.write_html(os.path.join(dir_name, "choropleth_mapbox.html"), auto_play=False) + +import plotly.express as px + +carshare = px.data.carshare() +fig = px.density_mapbox( + carshare, lat="centroid_lat", lon="centroid_lon", z="peak_hour", +) +fig.write_html(os.path.join(dir_name, "density_mapbox.html"), auto_play=False) + +import plotly.express as px + + gapminder = px.data.gapminder() fig = px.scatter_geo( gapminder, @@ -470,3 +501,42 @@ range_color=[20, 80], ) fig.write_html(os.path.join(dir_name, "choropleth.html"), auto_play=False) + +import plotly.express as px + +tips = px.data.tips() +fig = px.pie(tips, names="smoker", values="total_bill") +fig.write_html(os.path.join(dir_name, "pie.html"), auto_play=False) + +import plotly.express as px + +tips = px.data.tips() +fig = px.funnel_area(tips, names="smoker", values="total_bill") +fig.write_html(os.path.join(dir_name, "funnel_area.html"), auto_play=False) + +import plotly.express as px + +fig = px.treemap( + names=["Eve", "Cain", "Seth", "Enos", "Noam", "Abel", "Awan", "Enoch", "Azura"], + parents=["", "Eve", "Eve", "Seth", "Seth", "Eve", "Eve", "Awan", "Eve"], + values=[10, 14, 12, 10, 2, 6, 6, 4, 4], +) +fig.write_html(os.path.join(dir_name, "treemap.html"), auto_play=False) + + +import plotly.express as px + +fig = px.sunburst( + names=["Eve", "Cain", "Seth", "Enos", "Noam", "Abel", "Awan", "Enoch", "Azura"], + parents=["", "Eve", "Eve", "Seth", "Seth", "Eve", "Eve", "Awan", "Eve"], + values=[10, 14, 12, 10, 2, 6, 6, 4, 4], +) +fig.write_html(os.path.join(dir_name, "sunburst.html"), auto_play=False) + + +import plotly.express as px + +fig = px.funnel( + y=["first", "second", "first", "second"], x=[3, 1, 4, 2], color=["A", "A", "B", "B"] +) +fig.write_html(os.path.join(dir_name, "funnel.html"), auto_play=False)