From 296e6303389caacd0f80b15d09c53d9aa3a33dae Mon Sep 17 00:00:00 2001 From: Joe Numainville Date: Tue, 26 Sep 2023 16:52:40 -0500 Subject: [PATCH 1/4] adding maps --- .../src/deephaven/plot/express/__init__.py | 5 + .../plot/express/deephaven_figure/generate.py | 3 + .../deephaven/plot/express/plots/__init__.py | 1 + .../src/deephaven/plot/express/plots/maps.py | 734 ++++++++++++++++++ .../deephaven/plot/express/plots/test_maps.py | 227 ++++++ 5 files changed, 970 insertions(+) create mode 100644 plugins/plotly-express/src/deephaven/plot/express/plots/maps.py create mode 100644 plugins/plotly-express/test/deephaven/plot/express/plots/test_maps.py diff --git a/plugins/plotly-express/src/deephaven/plot/express/__init__.py b/plugins/plotly-express/src/deephaven/plot/express/__init__.py index 53e952d92..b9d486f11 100644 --- a/plugins/plotly-express/src/deephaven/plot/express/__init__.py +++ b/plugins/plotly-express/src/deephaven/plot/express/__init__.py @@ -30,6 +30,11 @@ pie, layer, make_subplots, + scatter_geo, + scatter_mapbox, + density_mapbox, + line_geo, + line_mapbox, ) from .data import data_generators diff --git a/plugins/plotly-express/src/deephaven/plot/express/deephaven_figure/generate.py b/plugins/plotly-express/src/deephaven/plot/express/deephaven_figure/generate.py index 635bde9b3..e7a2c7715 100644 --- a/plugins/plotly-express/src/deephaven/plot/express/deephaven_figure/generate.py +++ b/plugins/plotly-express/src/deephaven/plot/express/deephaven_figure/generate.py @@ -56,6 +56,9 @@ # in other cases, the color would already be calculated so this argument # would not be set "color", + "lat", + "lon", + "locations", } DATA_ARGS.update(DATA_LIST_ARGS) diff --git a/plugins/plotly-express/src/deephaven/plot/express/plots/__init__.py b/plugins/plotly-express/src/deephaven/plot/express/plots/__init__.py index 64aa21b48..8558d8a14 100644 --- a/plugins/plotly-express/src/deephaven/plot/express/plots/__init__.py +++ b/plugins/plotly-express/src/deephaven/plot/express/plots/__init__.py @@ -8,3 +8,4 @@ from .pie import pie from ._layer import layer from .subplots import make_subplots +from .maps import scatter_geo, scatter_mapbox, density_mapbox, line_geo, line_mapbox diff --git a/plugins/plotly-express/src/deephaven/plot/express/plots/maps.py b/plugins/plotly-express/src/deephaven/plot/express/plots/maps.py new file mode 100644 index 000000000..0aa7b8b01 --- /dev/null +++ b/plugins/plotly-express/src/deephaven/plot/express/plots/maps.py @@ -0,0 +1,734 @@ +from __future__ import annotations + +from numbers import Number + +from plotly import express as px + +from deephaven.table import Table + +from ._private_utils import process_args +from ._update_wrapper import default_callback + + +def scatter_geo( + table: Table = None, + lat: str = None, + lon: str = None, + locations: str = None, + locationmode: str = None, + geojson: str | dict = None, + featureidkey: str = "id", + by: str | list[str] = None, + by_vars: str | list[str] = "color", + color: str | list[str] = None, + symbol: str | list[str] = None, + size: str | list[str] = None, + text: str = None, + hover_name: str = None, + labels: dict[str, str] = None, + color_discrete_sequence: list[str] = None, + color_discrete_map: str + | tuple[str, dict[str | tuple[str], str]] + | dict[str | tuple[str], str] = None, + symbol_sequence: list[str] = None, + symbol_map: dict[str | tuple[str], str] = None, + size_sequence: list[int] = None, + size_map: str + | tuple[str, dict[str | tuple[str], str]] + | dict[str | tuple[str], str] = None, + color_continuous_scale: list[str] = None, + range_color: list[Number] = None, + color_continuous_midpoint: Number = None, + opacity: float = None, + projection: str = None, + scope: str = None, + center: dict[str, float] = None, + fitbounds: str = False, + basemap_visible: bool = None, + title: str = None, + template: str = None, + unsafe_update_figure: callable = default_callback, +): + """ + Create a scatter_geo plot + + Args: + table: Table: (Default value = None) + A table to pull data from. + lat: str: (Default value = None) + A column name to use for latitude values. + lon: str: (Default value = None) + A column name to use for longitude values. + locations: str: (Default value = None) + A column name to use for location values. + locationmode: str: (Default value = None) + A location mode to use. + One of ‘ISO-3’, ‘USA-states’, or ‘country names’. + These map locations to predefined geographic regions. + geojson: str | dict: (Default value = None) + GeoJSON data to use for geographic regions. + featureidkey: (Default value = "id") + The feature ID key to use for geographic regions. + by: str | list[str]: (Default value = None) + A column or list of columns that contain values to plot the figure traces by. + All values or combination of values map to a unique design. The variable + by_vars specifies which design elements are used. + This is overriden if any specialized design variables such as color are specified + by_vars: str | list[str]: (Default value = "color") + A string or list of string that contain design elements to plot by. + Can contain size, color, and symbol. + If associated maps or sequences are specified, they are used to map by column values + to designs. Otherwise, default values are used. + color: str | list[str]: (Default value = None) + A column or list of columns that contain color values. + If only one column is passed, and it contains numeric values, the value + is used as a value on a continuous color scale. Otherwise, the value is + used for a plot by on color. + See color_discrete_map for additional behaviors. + symbol: str | list[str]: (Default value = None) + A column or list of columns that contain symbol values. + The value is used for a plot by on symbol. + See color_discrete_map for additional behaviors. + size: str | list[str]: (Default value = None) + A column or list of columns that contain size values. + If only one column is passed, and it contains numeric values, the value + is used as a size. Otherwise, the value is used for a plot by on size. + See size_map for additional behaviors. + text: str: (Default value = None) + A column that contains text annotations. + hover_name: str: (Default value = None) + A column that contains names to bold in the hover tooltip. + labels: dict[str, str]: (Default value = None) + A dictionary of labels mapping columns to new labels. + color_discrete_sequence: list[str]: (Default value = None) + A list of colors to sequentially apply to + the series. The colors loop, so if there are more series than colors, + colors will be reused. This is overriden if "color" is specified. + color_discrete_map: + str | tuple[str, dict[str | tuple[str], str]] + | dict[str | tuple[str], str] (Default value = None) + If dict, the keys should be strings of the column values (or a tuple + of combinations of column values) which map to colors. + If "identity", the values are taken as literal colors. + If "by" or ("by", dict) where dict is as described above, the colors are forced to by + symbol_sequence: list[str]: (Default value = None) + A list of symbols to sequentially apply to the + markers in the series. The symbols loop, so if there are more series than + symbols, symbols will be reused. This is overriden if "symbol" is specified. + symbol_map: dict[str | tuple[str], str] (Default value = None) + If dict, the keys should be strings of the column values (or a tuple + of combinations of column values) which map to symbols. + size_sequence: list[str]: (Default value = None) + A list of sizes to sequentially apply to the + markers in the series. The sizes loop, so if there are more series than + symbols, sizes will be reused. This is overriden if "size" is specified. + size_map: + str | tuple[str, dict[str | tuple[str], str]] + | dict[str | tuple[str], str] (Default value = None) + If dict, the keys should be strings of the column values (or a tuple + of combinations of column values) which map to sizes. + If "identity", the values are taken as literal sizes. + If "by" or ("by", dict) where dict is as described above, the sizes are forced to by + color_continuous_scale: list[str]: (Default value = None) + A list of colors for a continuous scale + range_color: list[Number]: (Default value = None) + A list of two numbers that form the endpoints of the color axis + color_continuous_midpoint: Number: (Default value = None) + A number that is the midpoint of the color axis + opacity: float: (Default value = None) + Opacity to apply to all markers. 0 is completely transparent + and 1 is completely opaque. + projection: str: (Default value = None) + The projection type to use. + Default depends on scope. + One of 'equirectangular', 'mercator', 'orthographic', 'natural earth', + 'kavrayskiy7', 'miller', 'robinson', 'eckert4', 'azimuthal equal area', + 'azimuthal equidistant', 'conic equal area', 'conic conformal', + 'conic equidistant', 'gnomonic', 'stereographic', 'mollweide', 'hammer', + 'transverse mercator', 'albers usa', 'winkel tripel', 'aitoff', or + 'sinusoidal' + scope: str: (Default value = None) + The scope of the map. + Default of 'world', but forced to 'usa' if projection is 'albers usa' + One of 'world', 'usa', 'europe', 'asia', 'africa', 'north america', or + 'south america' + center: dict[str, float]: (Default value = None) + A dictionary of center coordinates. + The keys should be 'lat' and 'lon' and the values should be floats + that represent the lat and lon of the center of the map. + fitbounds: str: (Default value = False) + One of False, 'locations', or 'geojson' + If 'locations' or 'geojson', the map will zoom to the extent of the + locations or geojson bounds respectively. + basemap_visible: bool: (Default value = None) + If True, the basemap layer is visible. + title: str: (Default value = None) + The title of the chart + template: str: (Default value = None) + The template for the chart. + unsafe_update_figure: callable: (Default value = default_callback) + An update function that takes a plotly figure + as an argument and optionally returns a plotly figure. If a figure is + not returned, the plotly figure passed will be assumed to be the return + value. Used to add any custom changes to the underlying plotly figure. + Note that the existing data traces should not be removed. This may lead + to unexpected behavior if traces are modified in a way that break data + mappings. + + Returns: + DeephavenFigure: A DeephavenFigure that contains the scatter_geo figure + """ + args = locals() + + return process_args(args, {"scatter"}, px_func=px.scatter_geo) + + +def scatter_mapbox( + table: Table = None, + lat: str = None, + lon: str = None, + by: str | list[str] = None, + by_vars: str | list[str] = "color", + color: str | list[str] = None, + symbol: str | list[str] = None, + size: str | list[str] = None, + text: str = None, + hover_name: str = None, + labels: dict[str, str] = None, + color_discrete_sequence: list[str] = None, + color_discrete_map: str + | tuple[str, dict[str | tuple[str], str]] + | dict[str | tuple[str], str] = None, + symbol_sequence: list[str] = None, + symbol_map: dict[str | tuple[str], str] = None, + size_sequence: list[int] = None, + size_map: str + | tuple[str, dict[str | tuple[str], str]] + | dict[str | tuple[str], str] = None, + color_continuous_scale: list[str] = None, + range_color: list[Number] = None, + color_continuous_midpoint: Number = None, + opacity: float = None, + zoom: float = None, + center: dict[str, float] = None, + mapbox_style: str = "open-street-map", + title: str = None, + template: str = None, + unsafe_update_figure: callable = default_callback, +): + """ + Create a scatter_mapbox plot + + Args: + table: Table: (Default value = None) + A table to pull data from. + lat: str: (Default value = None) + A column name to use for latitude values. + lon: str: (Default value = None) + A column name to use for longitude values. + by: str | list[str]: (Default value = None) + A column or list of columns that contain values to plot the figure traces by. + All values or combination of values map to a unique design. The variable + by_vars specifies which design elements are used. + This is overriden if any specialized design variables such as color are specified + by_vars: str | list[str]: (Default value = "color") + A string or list of string that contain design elements to plot by. + Can contain size, color, and symbol. + If associated maps or sequences are specified, they are used to map by column values + to designs. Otherwise, default values are used. + color: str | list[str]: (Default value = None) + A column or list of columns that contain color values. + If only one column is passed, and it contains numeric values, the value + is used as a value on a continuous color scale. Otherwise, the value is + used for a plot by on color. + See color_discrete_map for additional behaviors. + symbol: str | list[str]: (Default value = None) + A column or list of columns that contain symbol values. + The value is used for a plot by on symbol. + See color_discrete_map for additional behaviors. + size: str | list[str]: (Default value = None) + A column or list of columns that contain size values. + If only one column is passed, and it contains numeric values, the value + is used as a size. Otherwise, the value is used for a plot by on size. + See size_map for additional behaviors. + text: str: (Default value = None) + A column that contains text annotations. + hover_name: str: (Default value = None) + A column that contains names to bold in the hover tooltip. + labels: dict[str, str]: (Default value = None) + A dictionary of labels mapping columns to new labels. + color_discrete_sequence: list[str]: (Default value = None) + A list of colors to sequentially apply to + the series. The colors loop, so if there are more series than colors, + colors will be reused. + color_discrete_map: + str | tuple[str, dict[str | tuple[str], str]] + | dict[str | tuple[str], str] (Default value = None) + If dict, the keys should be strings of the column values (or a tuple + of combinations of column values) which map to colors. + If "identity", the values are taken as literal colors. + If "by" or ("by", dict) where dict is as described above, the colors are forced to by + symbol_sequence: dict[str | tuple[str], str]: (Default value = None) + A list of symbols to sequentially apply to the + markers in the series. The symbols loop, so if there are more series than + symbols, symbols will be reused. + symbol_map: dict[str | tuple[str], str] (Default value = None) + If dict, the keys should be strings of the column values (or a tuple + of combinations of column values) which map to symbols. + size_sequence: list[str]: (Default value = None) + A list of sizes to sequentially apply to the + markers in the series. The sizes loop, so if there are more series than + symbols, sizes will be reused. This is overriden is "size" is specified. + size_map: + str | tuple[str, dict[str | tuple[str], str]] + | dict[str | tuple[str], str] (Default value = None) + If dict, the keys should be strings of the column values (or a tuple + of combinations of column values) which map to sizes. + If "identity", the values are taken as literal sizes. + If "by" or ("by", dict) where dict is as described above, the sizes are forced to by + color_continuous_scale: list[str]: (Default value = None) + A list of colors for a continuous scale + range_color: list[Number]: (Default value = None) + A list of two numbers that form the endpoints of the color axis + color_continuous_midpoint: Number: (Default value = None) + A number that is the midpoint of the color axis + opacity: float: (Default value = None) + Opacity to apply to all markers. 0 is completely transparent + and 1 is completely opaque. + zoom: float: (Default value = None) + The zoom level of the map. + center: dict[str, float]: (Default value = None) + A dictionary of center coordinates. + The keys should be 'lat' and 'lon' and the values should be floats + that represent the lat and lon of the center of the map. + mapbox_style: str: (Default value = "open-street-map") + The style of the map. + One of 'open-street-map', 'white-bg', 'carto-positron', 'carto-darkmatter', + and 'stamen-terrain', 'stamen-toner', 'stamen-watercolor' + title: str: (Default value = None) + The title of the chart + template: str: (Default value = None) + The template for the chart. + unsafe_update_figure: callable: (Default value = default_callback) + An update function that takes a plotly figure + as an argument and optionally returns a plotly figure. If a figure is + not returned, the plotly figure passed will be assumed to be the return + value. Used to add any custom changes to the underlying plotly figure. + Note that the existing data traces should not be removed. This may lead + to unexpected behavior if traces are modified in a way that break data + mappings. + + Returns: + DeephavenFigure: A DeephavenFigure that contains the scatter_mapbox figure + """ + args = locals() + + return process_args(args, {"line"}, px_func=px.scatter_mapbox) + + +def line_geo( + table: Table = None, + lat: str = None, + lon: str = None, + locations: str = None, + locationmode: str = None, + geojson: str | dict = None, + featureidkey: str = "id", + by: str | list[str] = None, + by_vars: str | list[str] = "color", + color: str | list[str] = None, + symbol: str | list[str] = None, + size: str | list[str] = None, + width: str = None, + line_dash: str = None, + text: str = None, + hover_name: str = None, + labels: dict[str, str] = None, + color_discrete_sequence: list[str] = None, + color_discrete_map: dict[str | tuple[str], str] = None, + symbol_sequence: list[str] = None, + symbol_map: dict[str | tuple[str], str] = None, + size_sequence: list[float] = None, + size_map: dict[str | tuple[str], str] = None, + width_sequence: list[float] = None, + width_map: dict[str | tuple[str], str] = None, + line_dash_sequence: list[str] = None, + line_dash_map: dict[str | tuple[str], str] = None, + markers: bool = False, + projection: str = None, + scope: str = None, + center: dict[str, float] = None, + fitbounds: str = False, + basemap_visible: bool = None, + title: str = None, + template: str = None, + unsafe_update_figure: callable = default_callback, +): + """ + Create a line_geo plot + + Args: + table: Table: (Default value = None) + A table to pull data from. + lat: str: (Default value = None) + A column name to use for latitude values. + lon: str: (Default value = None) + A column name to use for longitude values. + locations: str: (Default value = None) + A column name to use for location values. + locationmode: str: (Default value = None) + A location mode to use. + One of ‘ISO-3’, ‘USA-states’, or ‘country names’. + These map locations to predefined geographic regions. + geojson: str | dict: (Default value = None) + GeoJSON data to use for geographic regions. + featureidkey: (Default value = "id") + The feature ID key to use for geographic regions. + by: str | list[str]: (Default value = None) + A column or list of columns that contain values to plot the figure traces by. + All values or combination of values map to a unique design. The variable + by_vars specifies which design elements are used. + This is overriden if any specialized design variables such as color are specified + by_vars: str | list[str]: (Default value = "color") + A string or list of string that contain design elements to plot by. + Can contain size, line_dash, width, color, and symbol. + If associated maps or sequences are specified, they are used to map by column values + to designs. Otherwise, default values are used. + color: str | list[str]: (Default value = None) + A column or list of columns that contain color values. + If only one column is passed, and it contains numeric values, the value + is used as a value on a continuous color scale. Otherwise, the value is + used for a plot by on color. + See color_discrete_map for additional behaviors. + symbol: str | list[str]: (Default value = None) + A column or list of columns that contain symbol values. + The value is used for a plot by on symbol. + See color_discrete_map for additional behaviors. + size: str | list[str]: (Default value = None) + A column or list of columns that contain size values. + If only one column is passed, and it contains numeric values, the value + is used as a size. Otherwise, the value is used for a plot by on size. + See size_map for additional behaviors. + width: + A column or list of columns that contain width values. + If only one column is passed, and it contains numeric values, the value + is used as a width. Otherwise, the value is used for a plot by on width. + See width_map for additional behaviors. + line_dash: + A column or list of columns that contain line_dash values. + If only one column is passed, and it contains numeric values, the value + is used as a line_dash. Otherwise, the value is used for a plot by on line_dash. + See line_dash_map for additional behaviors. + text: + A column that contains text annotations. + hover_name: + A column that contains names to bold in the hover tooltip. + labels: dict[str, str]: (Default value = None) + A dictionary of labels mapping columns to new labels. + color_discrete_sequence: list[str]: (Default value = None) + A list of colors to sequentially apply to + the series. The colors loop, so if there are more series than colors, + colors will be reused. This is overriden if "color" is specified. + color_discrete_map: dict[str | tuple[str], str] (Default value = None) + If dict, the keys should be strings of the column values (or a tuple + of combinations of column values) which map to colors. + symbol_sequence: list[str]: (Default value = None) + A list of symbols to sequentially apply to the + markers in the series. The symbols loop, so if there are more series than + symbols, symbols will be reused. This is overriden if "symbol" is specified. + symbol_map: dict[str | tuple[str], str] (Default value = None) + If dict, the keys should be strings of the column values (or a tuple + of combinations of column values) which map to symbols. + size_sequence: list[str]: (Default value = None) + A list of sizes to sequentially apply to the + markers in the series. The sizes loop, so if there are more series than + sizes, sizes will be reused. This is overriden if "size" is specified. + size_map: dict[str | tuple[str], str] (Default value = None) + If dict, the keys should be strings of the column values (or a tuple + of combinations of column values) which map to symbols. + width_sequence: list[str]: (Default value = None) + A list of widths to sequentially apply to the + markers in the series. The widths loop, so if there are more series than + widths, widths will be reused. This is overriden if "width" is specified. + width_map: dict[str | tuple[str], str] (Default value = None) + If dict, the keys should be strings of the column values (or a tuple + of combinations of column values) which map to symbols. + line_dash_sequence: list[str]: (Default value = None) + A list of line_dashes to sequentially apply to the + markers in the series. The widths loop, so if there are more series than + widths, widths will be reused. This is overriden if "line_dash" is specified. + line_dash_map: dict[str | tuple[str], str] (Default value = None) + If dict, the keys should be strings of the column values (or a tuple + of combinations of column values) which map to line_dash. + markers: bool: (Default value = False) + If True, markers are shown. + projection: str: (Default value = None) + The projection type to use. + Default depends on scope. + One of 'equirectangular', 'mercator', 'orthographic', 'natural earth', + 'kavrayskiy7', 'miller', 'robinson', 'eckert4', 'azimuthal equal area', + 'azimuthal equidistant', 'conic equal area', 'conic conformal', + 'conic equidistant', 'gnomonic', 'stereographic', 'mollweide', 'hammer', + 'transverse mercator', 'albers usa', 'winkel tripel', 'aitoff', or + 'sinusoidal' + scope: str: (Default value = None) + The scope of the map. + Default of 'world', but forced to 'usa' if projection is 'albers usa' + One of 'world', 'usa', 'europe', 'asia', 'africa', 'north america', or + 'south america' + center: dict[str, float]: (Default value = None) + A dictionary of center coordinates. + The keys should be 'lat' and 'lon' and the values should be floats + that represent the lat and lon of the center of the map. + fitbounds: str: (Default value = False) + One of False, 'locations', or 'geojson' + If 'locations' or 'geojson', the map will zoom to the extent of the + locations or geojson bounds respectively. + basemap_visible: bool: (Default value = None) + If True, the basemap layer is visible. + title: str: (Default value = None) + The title of the chart + template: str: (Default value = None) + The template for the chart. + unsafe_update_figure: callable: (Default value = default_callback) + An update function that takes a plotly figure + as an argument and optionally returns a plotly figure. If a figure is + not returned, the plotly figure passed will be assumed to be the return + value. Used to add any custom changes to the underlying plotly figure. + Note that the existing data traces should not be removed. This may lead + to unexpected behavior if traces are modified in a way that break data + mappings. + + Returns: + DeephavenFigure: A DeephavenFigure that contains the line_geo figure + + """ + args = locals() + + return process_args(args, {"line"}, px_func=px.line_geo) + + +def line_mapbox( + table: Table = None, + lat: str = None, + lon: str = None, + by: str | list[str] = None, + by_vars: str | list[str] = "color", + color: str = None, + text: str = None, + size: str = None, + symbol: str = None, + width: str = None, + line_dash: str = None, + hover_name: str = None, + labels: dict[str, str] = None, + color_discrete_sequence: list[str] = None, + color_discrete_map: dict[str | tuple[str], str] = None, + line_dash_sequence: list[str] = None, + line_dash_map: dict[str | tuple[str], str] = None, + symbol_sequence: list[str] = None, + symbol_map: dict[str | tuple[str], str] = None, + size_sequence: list[int] = None, + size_map: dict[str | tuple[str], str] = None, + width_sequence: list[int] = None, + width_map: dict[str | tuple[str], str] = None, + zoom: float = None, + mapbox_style: str = "open-street-map", + center: dict[str, float] = None, + title: str = None, + template: str = None, + unsafe_update_figure: callable = default_callback, +): + """ + Create a line_mapbox plot + + Args: + table: Table: (Default value = None) + A table to pull data from. + lat: str: (Default value = None) + A column name to use for latitude values. + lon: str: (Default value = None) + A column name to use for longitude values. + by: str | list[str]: (Default value = None) + A column or list of columns that contain values to plot the figure traces by. + All values or combination of values map to a unique design. The variable + by_vars specifies which design elements are used. + This is overriden if any specialized design variables such as color are specified + by_vars: str | list[str]: (Default value = "color") + A string or list of string that contain design elements to plot by. + Can contain size, line_dash, width, color, and symbol. + If associated maps or sequences are specified, they are used to map by column values + to designs. Otherwise, default values are used. + color: str | list[str]: (Default value = None) + A column or list of columns that contain color values. + If only one column is passed, and it contains numeric values, the value + is used as a value on a continuous color scale. Otherwise, the value is + used for a plot by on color. + See color_discrete_map for additional behaviors. + symbol: str | list[str]: (Default value = None) + A column or list of columns that contain symbol values. + The value is used for a plot by on symbol. + See color_discrete_map for additional behaviors. + size: str | list[str]: (Default value = None) + A column or list of columns that contain size values. + If only one column is passed, and it contains numeric values, the value + is used as a size. Otherwise, the value is used for a plot by on size. + See size_map for additional behaviors. + width: + A column or list of columns that contain width values. + If only one column is passed, and it contains numeric values, the value + is used as a width. Otherwise, the value is used for a plot by on width. + See width_map for additional behaviors. + line_dash: + A column or list of columns that contain line_dash values. + If only one column is passed, and it contains numeric values, the value + is used as a line_dash. Otherwise, the value is used for a plot by on line_dash. + See line_dash_map for additional behaviors. + text: + A column that contains text annotations. + hover_name: + A column that contains names to bold in the hover tooltip. + labels: dict[str, str]: (Default value = None) + A dictionary of labels mapping columns to new labels. + color_discrete_sequence: list[str]: (Default value = None) + A list of colors to sequentially apply to + the series. The colors loop, so if there are more series than colors, + colors will be reused. This is overriden if "color" is specified. + color_discrete_map: dict[str | tuple[str], str] (Default value = None) + If dict, the keys should be strings of the column values (or a tuple + of combinations of column values) which map to colors. + symbol_sequence: list[str]: (Default value = None) + A list of symbols to sequentially apply to the + markers in the series. The symbols loop, so if there are more series than + symbols, symbols will be reused. This is overriden if "symbol" is specified. + symbol_map: dict[str | tuple[str], str] (Default value = None) + If dict, the keys should be strings of the column values (or a tuple + of combinations of column values) which map to symbols. + size_sequence: list[str]: (Default value = None) + A list of sizes to sequentially apply to the + markers in the series. The sizes loop, so if there are more series than + sizes, sizes will be reused. This is overriden if "size" is specified. + size_map: dict[str | tuple[str], str] (Default value = None) + If dict, the keys should be strings of the column values (or a tuple + of combinations of column values) which map to symbols. + width_sequence: list[str]: (Default value = None) + A list of widths to sequentially apply to the + markers in the series. The widths loop, so if there are more series than + widths, widths will be reused. This is overriden if "width" is specified. + width_map: dict[str | tuple[str], str] (Default value = None) + If dict, the keys should be strings of the column values (or a tuple + of combinations of column values) which map to symbols. + line_dash_sequence: list[str]: (Default value = None) + A list of line_dashes to sequentially apply to the + markers in the series. The widths loop, so if there are more series than + widths, widths will be reused. This is overriden if "line_dash" is specified. + line_dash_map: dict[str | tuple[str], str] (Default value = None) + If dict, the keys should be strings of the column values (or a tuple + of combinations of column values) which map to line_dash. + zoom: float: (Default value = None) + The zoom level of the map. + center: dict[str, float]: (Default value = None) + A dictionary of center coordinates. + The keys should be 'lat' and 'lon' and the values should be floats + that represent the lat and lon of the center of the map. + mapbox_style: str: (Default value = "open-street-map") + The style of the map. + One of 'open-street-map', 'white-bg', 'carto-positron', 'carto-darkmatter', + and 'stamen-terrain', 'stamen-toner', 'stamen-watercolor' + title: str: (Default value = None) + The title of the chart + template: str: (Default value = None) + The template for the chart. + unsafe_update_figure: callable: (Default value = default_callback) + An update function that takes a plotly figure + as an argument and optionally returns a plotly figure. If a figure is + not returned, the plotly figure passed will be assumed to be the return + value. Used to add any custom changes to the underlying plotly figure. + Note that the existing data traces should not be removed. This may lead + to unexpected behavior if traces are modified in a way that break data + mappings. + + Returns: + DeephavenFigure: A DeephavenFigure that contains the line_mapbox figure + + """ + args = locals() + + return process_args(args, {"line"}, px_func=px.line_mapbox) + + +def density_mapbox( + table: Table = None, + lat: str = None, + lon: str = None, + z: str = None, + hover_name: str = None, + color_continuous_scale: list[str] = None, + range_color: list[float] = None, + color_continuous_midpoint: float = None, + labels: dict[str, str] = None, + radius: int = 30, + opacity: float = None, + zoom: float = None, + center: dict[str, float] = None, + mapbox_style: str = "open-street-map", + title: str = None, + template: str = None, + unsafe_update_figure: callable = default_callback, +): + """ + Create a density_mapbox plot + + Args: + table: Table: (Default value = None) + A table to pull data from. + lat: str: (Default value = None) + A column name to use for latitude values. + lon: str: (Default value = None) + A column name to use for longitude values. + z: str: (Default value = None) + A column name to use for z values. + hover_name: str: (Default value = None) + A column that contains names to bold in the hover tooltip. + labels: dict[str, str]: (Default value = None) + A dictionary of labels mapping columns to new labels. + color_continuous_scale: list[str]: (Default value = None) + A list of colors for a continuous scale + range_color: list[Number]: (Default value = None) + A list of two numbers that form the endpoints of the color axis + color_continuous_midpoint: Number: (Default value = None) + A number that is the midpoint of the color axis + radius: int: (Default value = 30) + The radius of each point. + opacity: float: (Default value = None) + Opacity to apply to all markers. 0 is completely transparent + and 1 is completely opaque. + zoom: float: (Default value = None) + The zoom level of the map. + center: dict[str, float]: (Default value = None) + A dictionary of center coordinates. + The keys should be 'lat' and 'lon' and the values should be floats + that represent the lat and lon of the center of the map. + mapbox_style: str: (Default value = "open-street-map") + The style of the map. + One of 'open-street-map', 'white-bg', 'carto-positron', 'carto-darkmatter', + and 'stamen-terrain', 'stamen-toner', 'stamen-watercolor' + title: str: (Default value = None) + The title of the chart + template: str: (Default value = None) + The template for the chart. + unsafe_update_figure: callable: (Default value = default_callback) + An update function that takes a plotly figure + as an argument and optionally returns a plotly figure. If a figure is + not returned, the plotly figure passed will be assumed to be the return + value. Used to add any custom changes to the underlying plotly figure. + Note that the existing data traces should not be removed. This may lead + to unexpected behavior if traces are modified in a way that break data + mappings. + + Returns: + DeephavenFigure: A DeephavenFigure that contains the density_mapbox figure + """ + args = locals() + + return process_args(args, set(), px_func=px.density_mapbox) diff --git a/plugins/plotly-express/test/deephaven/plot/express/plots/test_maps.py b/plugins/plotly-express/test/deephaven/plot/express/plots/test_maps.py new file mode 100644 index 000000000..9c5bec78a --- /dev/null +++ b/plugins/plotly-express/test/deephaven/plot/express/plots/test_maps.py @@ -0,0 +1,227 @@ +import unittest + +from ..BaseTest import BaseTestCase + + +class AreaTestCase(BaseTestCase): + def setUp(self) -> None: + from deephaven import new_table + from deephaven.column import int_col + + self.source = new_table( + [ + int_col("lat", [1, 2, 2, 3, 3, 3, 4, 4, 5]), + int_col("lon", [1, 2, 2, 3, 3, 3, 4, 4, 5]), + int_col("z", [1, 2, 2, 3, 3, 3, 4, 4, 5]), + ] + ) + + def test_basic_scatter_geo(self): + import src.deephaven.plot.express as dx + + chart = dx.scatter_geo(self.source, lat="lat", lon="lon").to_dict(self.exporter) + plotly, deephaven = chart["plotly"], chart["deephaven"] + + # pop template as we currently do not modify it + plotly["layout"].pop("template") + + expected_data = [ + { + "featureidkey": "id", + "geo": "geo", + "hovertemplate": "lat=%{lat}
lon=%{lon}", + "lat": [-2147483648], + "legendgroup": "", + "lon": [-2147483648], + "marker": {"color": "#636efa", "symbol": "circle"}, + "mode": "markers", + "name": "", + "showlegend": False, + "type": "scattergeo", + } + ] + + self.assertEqual(plotly["data"], expected_data) + + expected_layout = { + "geo": {"domain": {"x": [0.0, 1.0], "y": [0.0, 1.0]}, "fitbounds": False}, + "legend": {"tracegroupgap": 0}, + "margin": {"t": 60}, + } + + self.assertEqual(plotly["layout"], expected_layout) + + def test_basic_scatter_mapbox(self): + import src.deephaven.plot.express as dx + + chart = dx.scatter_mapbox(self.source, lat="lat", lon="lon").to_dict( + self.exporter + ) + plotly, deephaven = chart["plotly"], chart["deephaven"] + + # pop template as we currently do not modify it + plotly["layout"].pop("template") + + expected_data = [ + { + "hovertemplate": "lat=%{lat}
lon=%{lon}", + "lat": [-2147483648], + "legendgroup": "", + "lon": [-2147483648], + "marker": {"color": "#636efa"}, + "mode": "lines", + "name": "", + "showlegend": False, + "subplot": "mapbox", + "type": "scattermapbox", + } + ] + + self.assertEqual(plotly["data"], expected_data) + + expected_layout = { + "legend": {"tracegroupgap": 0}, + "mapbox": { + "center": {"lat": -2147483648.0, "lon": -2147483648.0}, + "domain": {"x": [0.0, 1.0], "y": [0.0, 1.0]}, + "style": "open-street-map", + "zoom": 8, + }, + "margin": {"t": 60}, + } + + self.assertEqual(plotly["layout"], expected_layout) + + def test_basic_line_geo(self): + import src.deephaven.plot.express as dx + + chart = dx.line_geo(self.source, lat="lat", lon="lon").to_dict(self.exporter) + plotly, deephaven = chart["plotly"], chart["deephaven"] + + # pop template as we currently do not modify it + plotly["layout"].pop("template") + + expected_data = [ + { + "featureidkey": "id", + "geo": "geo", + "hovertemplate": "lat=%{lat}
lon=%{lon}", + "lat": [-2147483648], + "legendgroup": "", + "line": {"color": "#636efa", "dash": "solid"}, + "lon": [-2147483648], + "marker": {"symbol": "circle"}, + "mode": "lines", + "name": "", + "showlegend": False, + "type": "scattergeo", + } + ] + + self.assertEqual(plotly["data"], expected_data) + + expected_layout = { + "geo": {"domain": {"x": [0.0, 1.0], "y": [0.0, 1.0]}, "fitbounds": False}, + "legend": {"tracegroupgap": 0}, + "margin": {"t": 60}, + } + + self.assertEqual(plotly["layout"], expected_layout) + + def test_basic_line_mapbox(self): + import src.deephaven.plot.express as dx + + chart = dx.line_mapbox(self.source, lat="lat", lon="lon").to_dict(self.exporter) + plotly, deephaven = chart["plotly"], chart["deephaven"] + + # pop template as we currently do not modify it + plotly["layout"].pop("template") + + expected_data = [ + { + "hovertemplate": "lat=%{lat}
lon=%{lon}", + "lat": [-2147483648], + "legendgroup": "", + "line": {"color": "#636efa"}, + "lon": [-2147483648], + "mode": "lines", + "name": "", + "showlegend": False, + "subplot": "mapbox", + "type": "scattermapbox", + } + ] + + self.assertEqual(plotly["data"], expected_data) + + expected_layout = { + "legend": {"tracegroupgap": 0}, + "mapbox": { + "center": {"lat": -2147483648.0, "lon": -2147483648.0}, + "domain": {"x": [0.0, 1.0], "y": [0.0, 1.0]}, + "style": "open-street-map", + "zoom": 8, + }, + "margin": {"t": 60}, + } + + self.assertEqual(plotly["layout"], expected_layout) + + def test_basic_density_mapbox(self): + import src.deephaven.plot.express as dx + + chart = dx.density_mapbox(self.source, lat="lat", lon="lon", z="z").to_dict( + self.exporter + ) + plotly, deephaven = chart["plotly"], chart["deephaven"] + + # pop template as we currently do not modify it + plotly["layout"].pop("template") + + expected_data = [ + { + "coloraxis": "coloraxis", + "hovertemplate": "lat=%{lat}
lon=%{lon}
z=%{z}", + "lat": [-2147483648], + "lon": [-2147483648], + "name": "", + "radius": 30, + "subplot": "mapbox", + "z": [-2147483648], + "type": "densitymapbox", + } + ] + + self.assertEqual(plotly["data"], expected_data) + + expected_layout = { + "coloraxis": { + "colorbar": {"title": {"text": "z"}}, + "colorscale": [ + [0.0, "#0d0887"], + [0.1111111111111111, "#46039f"], + [0.2222222222222222, "#7201a8"], + [0.3333333333333333, "#9c179e"], + [0.4444444444444444, "#bd3786"], + [0.5555555555555556, "#d8576b"], + [0.6666666666666666, "#ed7953"], + [0.7777777777777778, "#fb9f3a"], + [0.8888888888888888, "#fdca26"], + [1.0, "#f0f921"], + ], + }, + "legend": {"tracegroupgap": 0}, + "mapbox": { + "center": {"lat": -2147483648.0, "lon": -2147483648.0}, + "domain": {"x": [0.0, 1.0], "y": [0.0, 1.0]}, + "style": "open-street-map", + "zoom": 8, + }, + "margin": {"t": 60}, + } + + self.assertEqual(plotly["layout"], expected_layout) + + +if __name__ == "__main__": + unittest.main() From 0ef444b13165199fd7626723e6875741d4d1bc3f Mon Sep 17 00:00:00 2001 From: Joe Numainville Date: Thu, 28 Sep 2023 14:22:33 -0500 Subject: [PATCH 2/4] fixing type --- plugins/plotly-express/src/deephaven/plot/express/plots/maps.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/plotly-express/src/deephaven/plot/express/plots/maps.py b/plugins/plotly-express/src/deephaven/plot/express/plots/maps.py index 0aa7b8b01..17b1731ce 100644 --- a/plugins/plotly-express/src/deephaven/plot/express/plots/maps.py +++ b/plugins/plotly-express/src/deephaven/plot/express/plots/maps.py @@ -323,7 +323,7 @@ def scatter_mapbox( """ args = locals() - return process_args(args, {"line"}, px_func=px.scatter_mapbox) + return process_args(args, {"scatter"}, px_func=px.scatter_mapbox) def line_geo( From 43e10b72b6e183503480f5955952d50b4cb6b2e3 Mon Sep 17 00:00:00 2001 From: Joe Numainville Date: Thu, 28 Sep 2023 15:27:07 -0500 Subject: [PATCH 3/4] fixing tests --- .../deephaven/plot/express/plots/test_maps.py | 35 +++++++++++-------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/plugins/plotly-express/test/deephaven/plot/express/plots/test_maps.py b/plugins/plotly-express/test/deephaven/plot/express/plots/test_maps.py index 9c5bec78a..28f218d0b 100644 --- a/plugins/plotly-express/test/deephaven/plot/express/plots/test_maps.py +++ b/plugins/plotly-express/test/deephaven/plot/express/plots/test_maps.py @@ -3,7 +3,7 @@ from ..BaseTest import BaseTestCase -class AreaTestCase(BaseTestCase): +class MapTestCase(BaseTestCase): def setUp(self) -> None: from deephaven import new_table from deephaven.column import int_col @@ -18,6 +18,7 @@ def setUp(self) -> None: def test_basic_scatter_geo(self): import src.deephaven.plot.express as dx + from deephaven.constants import NULL_INT chart = dx.scatter_geo(self.source, lat="lat", lon="lon").to_dict(self.exporter) plotly, deephaven = chart["plotly"], chart["deephaven"] @@ -30,9 +31,9 @@ def test_basic_scatter_geo(self): "featureidkey": "id", "geo": "geo", "hovertemplate": "lat=%{lat}
lon=%{lon}", - "lat": [-2147483648], + "lat": [NULL_INT], "legendgroup": "", - "lon": [-2147483648], + "lon": [NULL_INT], "marker": {"color": "#636efa", "symbol": "circle"}, "mode": "markers", "name": "", @@ -53,6 +54,7 @@ def test_basic_scatter_geo(self): def test_basic_scatter_mapbox(self): import src.deephaven.plot.express as dx + from deephaven.constants import NULL_INT chart = dx.scatter_mapbox(self.source, lat="lat", lon="lon").to_dict( self.exporter @@ -65,11 +67,11 @@ def test_basic_scatter_mapbox(self): expected_data = [ { "hovertemplate": "lat=%{lat}
lon=%{lon}", - "lat": [-2147483648], + "lat": [NULL_INT], "legendgroup": "", - "lon": [-2147483648], + "lon": [NULL_INT], "marker": {"color": "#636efa"}, - "mode": "lines", + "mode": "markers", "name": "", "showlegend": False, "subplot": "mapbox", @@ -82,7 +84,7 @@ def test_basic_scatter_mapbox(self): expected_layout = { "legend": {"tracegroupgap": 0}, "mapbox": { - "center": {"lat": -2147483648.0, "lon": -2147483648.0}, + "center": {"lat": NULL_INT, "lon": NULL_INT}, "domain": {"x": [0.0, 1.0], "y": [0.0, 1.0]}, "style": "open-street-map", "zoom": 8, @@ -94,6 +96,7 @@ def test_basic_scatter_mapbox(self): def test_basic_line_geo(self): import src.deephaven.plot.express as dx + from deephaven.constants import NULL_INT chart = dx.line_geo(self.source, lat="lat", lon="lon").to_dict(self.exporter) plotly, deephaven = chart["plotly"], chart["deephaven"] @@ -106,10 +109,10 @@ def test_basic_line_geo(self): "featureidkey": "id", "geo": "geo", "hovertemplate": "lat=%{lat}
lon=%{lon}", - "lat": [-2147483648], + "lat": [NULL_INT], "legendgroup": "", "line": {"color": "#636efa", "dash": "solid"}, - "lon": [-2147483648], + "lon": [NULL_INT], "marker": {"symbol": "circle"}, "mode": "lines", "name": "", @@ -130,6 +133,7 @@ def test_basic_line_geo(self): def test_basic_line_mapbox(self): import src.deephaven.plot.express as dx + from deephaven.constants import NULL_INT chart = dx.line_mapbox(self.source, lat="lat", lon="lon").to_dict(self.exporter) plotly, deephaven = chart["plotly"], chart["deephaven"] @@ -140,10 +144,10 @@ def test_basic_line_mapbox(self): expected_data = [ { "hovertemplate": "lat=%{lat}
lon=%{lon}", - "lat": [-2147483648], + "lat": [NULL_INT], "legendgroup": "", "line": {"color": "#636efa"}, - "lon": [-2147483648], + "lon": [NULL_INT], "mode": "lines", "name": "", "showlegend": False, @@ -157,7 +161,7 @@ def test_basic_line_mapbox(self): expected_layout = { "legend": {"tracegroupgap": 0}, "mapbox": { - "center": {"lat": -2147483648.0, "lon": -2147483648.0}, + "center": {"lat": NULL_INT, "lon": NULL_INT}, "domain": {"x": [0.0, 1.0], "y": [0.0, 1.0]}, "style": "open-street-map", "zoom": 8, @@ -169,6 +173,7 @@ def test_basic_line_mapbox(self): def test_basic_density_mapbox(self): import src.deephaven.plot.express as dx + from deephaven.constants import NULL_INT chart = dx.density_mapbox(self.source, lat="lat", lon="lon", z="z").to_dict( self.exporter @@ -182,8 +187,8 @@ def test_basic_density_mapbox(self): { "coloraxis": "coloraxis", "hovertemplate": "lat=%{lat}
lon=%{lon}
z=%{z}", - "lat": [-2147483648], - "lon": [-2147483648], + "lat": [NULL_INT], + "lon": [NULL_INT], "name": "", "radius": 30, "subplot": "mapbox", @@ -212,7 +217,7 @@ def test_basic_density_mapbox(self): }, "legend": {"tracegroupgap": 0}, "mapbox": { - "center": {"lat": -2147483648.0, "lon": -2147483648.0}, + "center": {"lat": NULL_INT, "lon": NULL_INT}, "domain": {"x": [0.0, 1.0], "y": [0.0, 1.0]}, "style": "open-street-map", "zoom": 8, From 57581f41ab8883144c42d17cd5fa51feb1e1d2f1 Mon Sep 17 00:00:00 2001 From: Matthew Runyon Date: Mon, 2 Oct 2023 17:52:37 -0500 Subject: [PATCH 4/4] Pause on user interactions for maps and polar --- .../src/js/src/PlotlyExpressChartModel.ts | 22 +++++++++++++++++-- .../src/js/src/PlotlyExpressChartPanel.tsx | 10 +++++++-- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/plugins/plotly-express/src/js/src/PlotlyExpressChartModel.ts b/plugins/plotly-express/src/js/src/PlotlyExpressChartModel.ts index 959ee6d30..4b5af57a8 100644 --- a/plugins/plotly-express/src/js/src/PlotlyExpressChartModel.ts +++ b/plugins/plotly-express/src/js/src/PlotlyExpressChartModel.ts @@ -193,8 +193,26 @@ export class PlotlyExpressChartModel extends ChartModel { } } - has3D(): boolean { - return this.data.some(({ type }) => type != null && type.includes('3d')); + shouldPauseOnUserInteraction(): boolean { + return ( + this.hasScene() || this.hasGeo() || this.hasMapbox() || this.hasPolar() + ); + } + + hasScene(): boolean { + return this.data.some(d => 'scene' in d && d.scene != null); + } + + hasGeo(): boolean { + return this.data.some(d => 'geo' in d && d.geo != null); + } + + hasMapbox(): boolean { + return this.data.some(({ type }) => type?.includes('mapbox')); + } + + hasPolar(): boolean { + return this.data.some(({ type }) => type?.includes('polar')); } getPlotWidth(): number { diff --git a/plugins/plotly-express/src/js/src/PlotlyExpressChartPanel.tsx b/plugins/plotly-express/src/js/src/PlotlyExpressChartPanel.tsx index 8de2acd1d..f452e7bee 100644 --- a/plugins/plotly-express/src/js/src/PlotlyExpressChartPanel.tsx +++ b/plugins/plotly-express/src/js/src/PlotlyExpressChartPanel.tsx @@ -42,8 +42,14 @@ function PlotlyExpressChartPanel(props: PlotlyExpressChartPanelProps) { }, [dh, fetch]); useEffect( - function handle3DTicks() { - if (!model || !containerRef.current || !model.has3D()) { + function handleSceneTicks() { + // Plotly scenes and geo views reset when our data ticks + // Pause rendering data updates when the user is manipulating a scene + if ( + !model || + !containerRef.current || + !model.shouldPauseOnUserInteraction() + ) { return; }