Skip to content

Commit 2c5ba66

Browse files
dgodinez-dhdsmmckenmofojedmargaretkennedy
authored
docs: update plotting (#1101)
https://deephaven.atlassian.net/browse/DOC-376 Update to an existing document: - renamed - added to sidebar - updated old content - added new content --------- Co-authored-by: Don <dsmmcken@gmail.com> Co-authored-by: Mike Bender <mikebender@deephaven.io> Co-authored-by: margaretkennedy <82049573+margaretkennedy@users.noreply.github.com>
1 parent 024811d commit 2c5ba66

File tree

3 files changed

+211
-112
lines changed

3 files changed

+211
-112
lines changed

plugins/ui/docs/Plotting.md

Lines changed: 0 additions & 112 deletions
This file was deleted.
Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
# Plotting
2+
3+
In addition to tables, Deephaven supports [dynamic plots](/core/docs/how-to-guides/plotting/) as an excellent way to visualize real-time data. You can add plots to your `deephaven.ui` components. Like tables, plots will update in real time and react to changes in the UI.
4+
5+
The `deephaven.ui` module provides a simple interface for creating interactive plots using the `deephaven-express` library. This guide will show you how to create plots that update based on user input.
6+
7+
## Memoize plots
8+
9+
Just as you should memoize table operations, it’s important to memoize plots based on the table used to create them and any arguments that may change. This process of memoization prevents the plot from being recreated during every re-render. Instead, the plot will only be recreated when an argument related to `plot `changes.
10+
11+
```python
12+
from deephaven import time_table, ui
13+
import deephaven.plot.express as dx
14+
15+
16+
@ui.component
17+
def ui_memo_plot_app():
18+
n, set_n = ui.use_state(1)
19+
20+
result_table = ui.use_memo(
21+
lambda: time_table("PT1s").update(f"y=i*{n}").reverse(), [n]
22+
)
23+
24+
# memoize the plot
25+
plot = ui.use_memo(
26+
lambda: dx.line(result_table, x="Timestamp", y="y"), [result_table]
27+
)
28+
29+
return ui.view(
30+
ui.flex(
31+
ui.slider(value=n, min_value=1, max_value=10, on_change=set_n, label="n"),
32+
plot,
33+
direction="column",
34+
height="100%",
35+
),
36+
align_self="stretch",
37+
flex_grow=1,
38+
)
39+
40+
41+
memo_plot_app = ui_memo_plot_app()
42+
```
43+
44+
## Plot a filtered table
45+
46+
This example demonstrates how to create a simple line plot that updates based on user input. The plot will display the price of a stock filtered based on the stock symbol entered by the user. Here, we have used a `ui.text_field` to get the value, but it could be driven by any deephaven.ui input, including double clicking on a value from a `ui.table`. We've previously referred to this sort of behavior as a "one-click" component in Enterprise, as the plot updates as soon as the user enters a filter.
47+
48+
```python
49+
import deephaven.plot.express as dx
50+
import deephaven.ui as ui
51+
52+
_stocks = dx.data.stocks()
53+
54+
55+
@ui.component
56+
def plot_filtered_table(table, initial_value):
57+
text, set_text = ui.use_state(initial_value)
58+
# the filter is memoized so that it is only recalculated when the text changes
59+
filtered_table = ui.use_memo(
60+
lambda: table.where(f"Sym = `{text.upper()}`"), [table, text]
61+
)
62+
plot = ui.use_memo(
63+
lambda: dx.line(
64+
filtered_table, x="Timestamp", y="Price", title=f"Filtered by: {text}"
65+
),
66+
[filtered_table, text],
67+
)
68+
return [ui.text_field(value=text, on_change=set_text), plot]
69+
70+
71+
p = plot_filtered_table(_stocks, "DOG")
72+
```
73+
74+
## Plot a partitioned table
75+
76+
Using a partitioned table, as opposed to a `where` statement, can be more efficient if you filter the same table multiple times with different values. This is because the partitioning is only done once, and then the key is selected from the partitioned table. Compared to using `where`, it can be faster to return results, but at the expense of the query engine using more memory. Depending on the size of your table and the number of unique values in the partition key, this trade-off can be worthwhile.
77+
78+
```python
79+
import deephaven.plot.express as dx
80+
import deephaven.ui as ui
81+
82+
_stocks = dx.data.stocks()
83+
84+
85+
@ui.component
86+
def plot_partitioned_table(table, initial_value):
87+
text, set_text = ui.use_state(initial_value)
88+
# memoize the partition by so that it only performed once
89+
partitioned_table = ui.use_memo(lambda: table.partition_by(["Sym"]), [table])
90+
constituent_table = ui.use_memo(
91+
lambda: partitioned_table.get_constituent(text.upper()) if text != "" else None,
92+
[partitioned_table, text],
93+
)
94+
# only attempt to plot valid partition keys
95+
plot = ui.use_memo(
96+
lambda: dx.line(
97+
constituent_table, x="Timestamp", y="Price", title=f"partition key: {text}"
98+
)
99+
if constituent_table != None
100+
else ui.text("Please enter a valid partition."),
101+
[constituent_table, text],
102+
)
103+
return [
104+
ui.text_field(value=text, on_change=set_text),
105+
plot,
106+
]
107+
108+
109+
p = plot_partitioned_table(_stocks, "DOG")
110+
```
111+
112+
## Combine a filter and a partition by
113+
114+
Deephaven Plotly Express allows you to plot by a partition and assign unique colors to each key. Sometimes, as a user, you may also want to filter the data in addition to partitioning it. We've previously referred to this as "one-click plot by" behavior in Enterprise. This can be done by either filtering the table first and then partitioning it, or partitioning it first and then filtering it. The choice of which to use depends on the size of the table and the number of unique values in the partition key. The first example is more like a traditional "one-click" component, and the second is more like a parameterized query. Both will give you the same result, but the first one may return results faster, whereas the second one may be more memory efficient.
115+
116+
```python
117+
import deephaven.plot.express as dx
118+
import deephaven.ui as ui
119+
120+
_stocks = dx.data.stocks()
121+
122+
123+
@ui.component
124+
def partition_then_filter(table, by, initial_value):
125+
"""
126+
Partition the table by both passed columns, then filter it by the value entered by the user
127+
"""
128+
text, set_text = ui.use_state(initial_value)
129+
partitioned_table = ui.use_memo(lambda: table.partition_by(by), [table, by])
130+
filtered = ui.use_memo(
131+
lambda: partitioned_table.filter(f"{by[0]} = `{text.upper()}`"),
132+
[text, partitioned_table],
133+
)
134+
plot = ui.use_memo(
135+
lambda: dx.line(filtered, x="Timestamp", y="Price", by=[f"{by[1]}"]),
136+
[filtered, by],
137+
)
138+
return [
139+
ui.text_field(value=text, on_change=set_text),
140+
plot,
141+
]
142+
143+
144+
@ui.component
145+
def where_then_partition(table, by, initial_value):
146+
"""
147+
Filter the table by the value entered by the user, then re-partition it by the second passed column
148+
"""
149+
text, set_text = ui.use_state(initial_value)
150+
filtered = ui.use_memo(
151+
lambda: table.where(f"{by[0]} = `{text.upper()}`"), [text, table]
152+
)
153+
plot = ui.use_memo(
154+
lambda: dx.line(filtered, x="Timestamp", y="Price", by=[f"{by[1]}"]),
155+
[filtered, by],
156+
)
157+
return [ui.text_field(value=text, on_change=set_text), plot]
158+
159+
160+
# outputs the same thing, done two different ways depending on how you want the work done
161+
ptf = partition_then_filter(_stocks, ["Sym", "Exchange"], "DOG")
162+
wtp = where_then_partition(_stocks, ["Sym", "Exchange"], "DOG")
163+
```
164+
165+
## Change a plot
166+
167+
In response to user events, you can change data for a plot and you can change the plot itself. In this example, the plot type changes by selecting it from a picker.
168+
169+
```python
170+
import deephaven.plot.express as dx
171+
import deephaven.ui as ui
172+
173+
_stocks = dx.data.stocks().where("Sym = `DOG`")
174+
175+
plot_types = ["Line", "Scatter", "Area"]
176+
177+
178+
@ui.component
179+
def change_plot_type(table):
180+
plot_type, set_plot_type = ui.use_state("Line")
181+
182+
def create_plot(t, pt):
183+
match pt:
184+
case "Line":
185+
return dx.line(t, x="Timestamp", y="Price")
186+
case "Scatter":
187+
return dx.scatter(t, x="Timestamp", y="Price")
188+
case "Area":
189+
return dx.area(t, x="Timestamp", y="Price")
190+
case _:
191+
return ui.text(f"Unknown plot type {pt}")
192+
193+
plot = ui.use_memo(lambda: create_plot(table, plot_type), [table, plot_type])
194+
return [
195+
ui.picker(plot_types, selected_key=plot_type, on_change=set_plot_type),
196+
plot,
197+
]
198+
199+
200+
change_plot_type_example = change_plot_type(_stocks)
201+
```
202+
203+
## Plots and Liveness
204+
205+
While you may need to use a liveness scope for Deephaven tables, you do not for Deephaven Express plots.
206+
207+
Deephaven Express tracks liveness internally for the tables used by the plot. It cleans up when the figure is deleted or cleaned up by garbage collection. You should not need to explicitly use liveness scope for Deephaven Express.

plugins/ui/docs/sidebar.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,10 @@
9898
{
9999
"label": "Update Lists in State",
100100
"path": "add-interactivity/update-lists-in-state.md"
101+
},
102+
{
103+
"label": "Plotting",
104+
"path": "add-interactivity/plot-with-components.md"
101105
}
102106
]
103107
},

0 commit comments

Comments
 (0)