diff --git a/plugins/plotly-express/docs/indicator.md b/plugins/plotly-express/docs/indicator.md new file mode 100644 index 000000000..c7e52f4d3 --- /dev/null +++ b/plugins/plotly-express/docs/indicator.md @@ -0,0 +1,273 @@ +# Indicator Plot + +An indicator plot is a type of plot that highlights a collection of numeric values. + +### What are indicator plots useful for? + +- **Highlight specific metrics**: Indicator plots are useful when you want to highlight specific numeric metrics in a visually appealing way. +- **Compare metrics to a reference value**: Indicator plots are useful to compare metrics to a reference value, such as a starting value or a target value. +- **Compare metrics to each other**: Indicator plots are useful to compare multiple metrics to each other by highlighting where they fall relative to each other. + +## Examples + +### A basic indicator plot + +Visualize a single numeric value by passing the column name to the `value` argument. The table should contain only one row. + +```python +import deephaven.plot.express as dx +from deephaven import agg as agg + +my_table = dx.data.stocks() + +# subset data and aggregate for DOG prices +dog_avg = my_table.where("Sym = `DOG`").agg_by([agg.avg(cols="Price")]) + +indicator_plot = dx.indicator(dog_avg, value="Price") +``` + +### A delta indicator plot + +Visualize a single numeric value with a delta to a reference value by passing the reference column name to the `reference` argument. + +```python order=indicator_plot,my_table +import deephaven.plot.express as dx +from deephaven import agg as agg +my_table = dx.data.stocks() + +# subset data and aggregate for DOG prices +dog_agg = my_table.where("Sym = `DOG`").agg_by([agg.avg(cols="Price"), agg.first(cols="StartingPrice = Price")]) + +indicator_plot = dx.indicator(dog_agg, value="Price", reference="StartingPrice") +``` + +## Indicator plots from variables + +Pass variables into a table to create an indicator plot. + +```python order=indicator_plot,my_table +import deephaven.plot.express as dx +from deephaven import new_table +from deephaven.column import int_col + +my_value = 10 +my_reference = 5 + +my_table = new_table([ + int_col("MyValue", [my_value]), + int_col("MyReference", [my_reference]) +]) + +indicator_plot = dx.indicator(my_table, value="MyValue", reference="MyReference") +``` + +# Delta only indicator plot + +Visualize only the delta to a reference value by passing `number=False`. + +```python order=indicator_plot,my_table +import deephaven.plot.express as dx +from deephaven import agg as agg +my_table = dx.data.stocks() + +# subset data and aggregate for DOG prices +dog_agg = my_table.where("Sym = `DOG`").agg_by([agg.avg(cols="Price"), agg.first(cols="StartingPrice = Price")]) + +indicator_plot = dx.indicator(dog_agg, value="Price", reference="StartingPrice", number=False) +``` + +### An angular indicator plot + +Visualize a single numeric value with an angular gauge by passing `gauge="angular"`. + +```python order=indicator_plot,my_table +import deephaven.plot.express as dx +from deephaven import agg as agg +my_table = dx.data.stocks() + +# subset data and aggregate for DOG prices +dog_avg = my_table.where("Sym = `DOG`").agg_by([agg.avg(cols="Price")]) + +indicator_plot = dx.indicator(dog_avg, value="Price", gauge="angular") +``` + +### A hidden axis bullet indicator plot + +Visualize a single numeric value with a bullet gauge by passing `gauge="bullet"`. Hide the axis by passing `axis=False`. + +```python order=indicator_plot,my_table +import deephaven.plot.express as dx +from deephaven import agg as agg +my_table = dx.data.stocks() + +# subset data and aggregate for DOG prices +dog_avg = my_table.where("Sym = `DOG`").agg_by([agg.avg(cols="Price")]) + +indicator_plot = dx.indicator(dog_avg, value="Price", gauge="bullet", axis=False) +``` + +### Prefixes and suffixes + +Add a prefix and suffix to the numeric value by passing `prefix` and `suffix`. + +```python order=indicator_plot,my_table +import deephaven.plot.express as dx +from deephaven import agg as agg +my_table = dx.data.stocks() + +# subset data and aggregate for DOG prices +dog_avg = my_table.where("Sym = `DOG`").agg_by([agg.avg(cols="Price")]) + +indicator_plot = dx.indicator(dog_avg, value="Price", prefix="$", suffix="USD") +``` + +### Delta Symbols + +Modify the symbol before the delta value by passing `increasing_text` and `decreasing_text`. + +```python order=indicator_plot,my_table +import deephaven.plot.express as dx +from deephaven import agg as agg +my_table = dx.data.stocks() + +# subset data and aggregate for DOG prices +dog_avg = my_table.where("Sym = `DOG`").agg_by([agg.avg(cols="Price")]) + +indicator_plot = dx.indicator(dog_avg, value="Price", increasing_text="Up: ", decreasing_text="Down: ") +``` + +### Indicator with text + +Add text to the indicator by passing the text column name to the `text` argument. + +```python order=indicator_plot,my_table +import deephaven.plot.express as dx +from deephaven import agg as agg +my_table = dx.data.stocks() + +# subset data and aggregate prices, keeping the Sym +dog_avg = my_table.where("Sym = `DOG`").agg_by([agg.avg(cols="Price")]) + +indicator_plot = dx.indicator(dog_avg, value="Price", text="Sym") +``` + +### Multiple indicators + +Visualize multiple numeric values by passing in a table with multiple rows. By default, a square grid of indicators is created. + +```python order=indicator_plot,my_table +import deephaven.plot.express as dx +my_table = dx.data.stocks() + +# aggregate for average prices by Sym +sym_avg = my_table.agg_by([agg.avg(cols="Price")], by="Sym") + +indicator_plot = dx.indicator(sym_avg, value="Price") +``` + +### Multiple rows + +By default, a grid of indicators is created. To create a specific amount of rows with a dynamic number of columns, pass the number of rows to the `rows` argument. + +```python order=indicator_plot,my_table +import deephaven.plot.express as dx +my_table = dx.data.stocks() + +# aggregate for average prices by Sym +sym_avg = my_table.agg_by([agg.avg(cols="Price")], by="Sym") + +indicator_plot = dx.indicator(sym_avg, value="Price", rows=2) +``` + +### Multiple columns + +By default, a grid of indicators is created. To create a specific amount of columns with a dynamic number of rows, pass the number of columns to the `columns` argument. + +```python order=indicator_plot,my_table +import deephaven.plot.express as dx +my_table = dx.data.stocks() + +# aggregate for average prices by Sym +sym_avg = my_table.agg_by([agg.avg(cols="Price")], by="Sym") + +indicator_plot = dx.indicator(sym_avg, value="Price", columns=2) +``` + +### Delta colors + +Change the color of the delta value based on whether it is increasing or decreasing by passing `increasing_color_sequence` and `decreasing_color_sequence`. +These colors are applied sequentially to the indicators and looped if there are more indicators than colors. + +```python +import deephaven.plot.express as dx +from deephaven import agg as agg + +my_table = dx.data.stocks() + +# subset data and aggregate for DOG prices +sym_agg = my_table.agg_by( + [agg.avg(cols="Price"), agg.first(cols="StartingPrice = Price")] +) + +indicator_plot = dx.indicator( + sym_agg, + value="Price", + reference="Starting Price", + increasing_color_sequence=["green", "darkgreen"], + decreasing_color_sequence=["red", "darkred"], +) +``` + +### Gauge colors + +Change the color of the gauge based on the value by passing `gauge_color_sequence`. +These colors are applied sequentially to the indicators and looped if there are more indicators than colors. + +```python +import deephaven.plot.express as dx +from deephaven import agg as agg + +my_table = dx.data.stocks() + +# subset data and aggregate for DOG prices +sym_agg = my_table.agg_by([agg.avg(cols="Price")]) + +indicator_plot = dx.indicator( + sym_agg, value="Price", gauge_color_sequence=["green", "darkgreen"] +) +``` + +### Plot by + +Create groups of styled indicators by passing the grouping categorical column name to the `by` argument. +`increasing_color_map` and `decreasing_color_map` can be used to style the indicators based on the group. + +```python +import deephaven.plot.express as dx +from deephaven import agg as agg + +my_table = dx.data.stocks() + +# subset data and aggregate prices, keeping the Sym +sym_agg = my_table.agg_by( + [ + agg.avg(cols="Price"), + agg.first(cols="StartingPrice = Price"), + agg.last(cols="Sym"), + ] +) + +indicator_plot = dx.indicator( + sym_agg, + value="Price", + reference="StartingPrice", + by="Sym", + increasing_color_map={"DOG": "darkgreen"}, + decreasing_color_map={"DOG": "darkred"}, +) +``` + +## API Reference +```{eval-rst} +.. dhautofunction:: deephaven.plot.express.indicator +``` diff --git a/plugins/plotly-express/src/deephaven/plot/express/plots/indicator.py b/plugins/plotly-express/src/deephaven/plot/express/plots/indicator.py new file mode 100644 index 000000000..16a54117e --- /dev/null +++ b/plugins/plotly-express/src/deephaven/plot/express/plots/indicator.py @@ -0,0 +1,105 @@ +from __future__ import annotations + +from typing import Callable + +from ..shared import default_callback +from ..deephaven_figure import DeephavenFigure +from ..types import PartitionableTableLike, Gauge, StyleDict + + +def indicator( + table: PartitionableTableLike, + value: str | None, + reference: str | None = None, + text: str | None = None, + by: str | list[str] | None = None, + by_vars: str | tuple[str, ...] = "gauge_color", + increasing_color: str | list[str] | None = None, + decreasing_color: str | list[str] | None = None, + gauge_color: str | list[str] | None = None, + increasing_color_sequence: list[str] | None = None, + increasing_color_map: StyleDict | None = None, + decreasing_color_sequence: list[str] | None = None, + decreasing_color_map: StyleDict | None = None, + gauge_color_sequence: list[str] | None = None, + gauge_color_map: StyleDict | None = None, + number: bool = True, + gauge: Gauge | None = None, + axis: bool = True, + prefix: str | None = None, + suffix: str | None = None, + increasing_text: str | None = "▲", + decreasing_text: str | None = "▼", + rows: int | None = None, + columns: int | None = None, + unsafe_update_figure: Callable = default_callback, +) -> DeephavenFigure: + """ + Create an indicator chart. + + Args: + table: A table to pull data from. + value: The column to use as the value. + reference: The column to use as the reference value. + by: 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 increasing_color are specified + by_vars: A string or list of string that contain design elements to plot by. + Can contain increasing_color and decreasing_color + If associated maps or sequences are specified, they are used to map by column values + to designs. Otherwise, default values are used. + increasing_color: A column or list of columns used for a plot by on delta increasing color. + Only valid if reference is not None. + See increasing_color_map for additional behaviors. + decreasing_color: A column or list of columns used for a plot by on delta increasing color. + Only valid if reference is not None. + See decreasing_color_map for additional behaviors. + gauge_color: A column or list of columns used for a plot by on color. + Only valid if gauge is not None. + See gauge_color_map for additional behaviors. + text: A column that contains text annotations. + increasing_color_sequence: A list of colors to sequentially apply to + the series. The colors loop, so if there are more series than colors, + colors are reused. + increasing_color_map: A dict with keys that are strings of the column values (or a tuple + of combinations of column values) which map to colors. + decreasing_color_sequence: A list of colors to sequentially apply to + the series. The colors loop, so if there are more series than colors, + colors are reused. + decreasing_color_map: A dict with keys that are strings of the column values (or a tuple + of combinations of column values) which map to colors. + gauge_color_sequence: A list of colors to sequentially apply to + the series. The colors loop, so if there are more series than colors, + colors are reused. + gauge_color_map: A dict with keys that are strings of the column values (or a tuple + of combinations of column values) which map to colors. + number: True to show the number, False to hide it. + gauge: Specifies the type of gauge to use. + Set to "angular" for a half-circle gauge and "bullet" for a horizontal gauge. + axis: True to show the axis. Only valid if gauge is set. + prefix: A string to prepend to the number value. + suffix: A string to append to the number value. + increasing_text: The text to display before the delta if the number value + is greater than the reference value. + decreasing_text: The text to display before the delta if the number value + is less than the reference value. + rows: The number of rows of indicators to create. + If None, the number of rows is determined by the number of columns. + If both rows and columns are None, a square grid is created. + columns: The number of columns of indicators to create. + If None, the number of columns is determined by the number of rows. + If both rows and columns are None, a square grid is created. + unsafe_update_figure: 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: + A DeephavenFigure that contains the indicator chart + + """ + raise NotImplementedError diff --git a/plugins/plotly-express/src/deephaven/plot/express/types/__init__.py b/plugins/plotly-express/src/deephaven/plot/express/types/__init__.py index 0417cd135..3f89c08c0 100644 --- a/plugins/plotly-express/src/deephaven/plot/express/types/__init__.py +++ b/plugins/plotly-express/src/deephaven/plot/express/types/__init__.py @@ -1 +1 @@ -from .plots import PartitionableTableLike, TableLike +from .plots import PartitionableTableLike, TableLike, Gauge, StyleDict, StyleMap diff --git a/plugins/plotly-express/src/deephaven/plot/express/types/plots.py b/plugins/plotly-express/src/deephaven/plot/express/types/plots.py index 25cc51aa9..172d8d220 100644 --- a/plugins/plotly-express/src/deephaven/plot/express/types/plots.py +++ b/plugins/plotly-express/src/deephaven/plot/express/types/plots.py @@ -1,6 +1,22 @@ -from typing import Union +from __future__ import annotations + +from typing import Union, Literal, Tuple, Dict from pandas import DataFrame from deephaven.table import Table, PartitionedTable TableLike = Union[Table, DataFrame] PartitionableTableLike = Union[PartitionedTable, TableLike] +Gauge = Literal["shape", "bullet"] + +# StyleDict is a dictionary that maps column values to style values. +StyleDict = Dict[Union[str, Tuple[str]], str] + +# In addition to StyleDict, StyleMap can also be a string literal "identity" or "by" +# that specifies how to map column values to style values. +# If "identity", the column values are taken as literal style values. +# If "by", the column values are used to map to style values. +# "by" is only used to override parameters that default to numeric mapping on a continuous scale, such as scatter color. +# Providing a tuple of "by" and a StyleDict is equivalent to providing a StyleDict. +StyleMap = Union[ + Literal["identity"], Literal["by"], Tuple[Literal["by"], StyleDict], StyleDict +]