Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: dashboard #814

Merged
merged 17 commits into from
Oct 28, 2024
212 changes: 212 additions & 0 deletions plugins/ui/docs/components/dashboard.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
# Dashboard

Dashboards allow you to layout a collection of ui components as panels as individuals pages.
ethanalvizo marked this conversation as resolved.
Show resolved Hide resolved

ethanalvizo marked this conversation as resolved.
Show resolved Hide resolved
## Rules
1. Dashboards must be a child of the root script and not nested inside a `@ui.component`. Otherwise the application is unable to correctly determine the type of the component
ethanalvizo marked this conversation as resolved.
Show resolved Hide resolved
2. Dashboards must have one and only one child, typically a row or column.
3. Height and width of panels are summed to 100%
ethanalvizo marked this conversation as resolved.
Show resolved Hide resolved

## Key Components
There are 3 main children that make up dashboard: row, column, and stack.

- **Row**: A container used to group elements horizontally. Each element is placed to the right of the previous one.
- **Column**: A container used to group elements vertically. Each element is placed below the previous one.
- **Stack**: A container used to group elements into tabs. Each element gets its own tab, with only one element visible at a time.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Panel should probably be mentioned here as well.

We should mention something about row/column being the "top" of the layout trees. Columns should go inside of rows and rows should go inside of columns when making a layout. You can put rows as children of rows and columns as children of columns, but it's basically a no-op I believe in terms of actual layout. Would need to double-check it doesn't do anything special. row(row(column, column), column) is the same visually as row(column, column, column)

Stacks/panels are the "bottom" of the tree. Once you add a stack or panel, the layout in that section is effectively done. If you put a column inside a row, you'll get a flex column within the panel. That detail probably isn't necessary for dashboard though as it is more a column/row/panel detail.

We also implicitly wrap the children when necessary, so you don't have to be explicit about the entire layout if you don't need the extra props of the wrappers.

If you have other row/column siblings, the node will be wrapped in an appropriate row/column. If there are no row/column children, then nodes will be wrapped in stacks if needed.

row(component, component) will become row(stack(component), stack(component)) while row(column(comp), comp) will become row(column(comp), column(comp)) and finally row(column(stack(comp)), column(stack(comp))). They will actually wrap the comp in a panel as well, but I left that out of my example.

The rules are basically as follows for automatic wrapping

  1. Dashboard - Wrap in row/column if no single node is the default (e.g. you can provide [col, col] as the child to dashboard)
  2. Row/column
    • If there are children that are rows/columns, wrap non-row/non-column children in row/column. Row elements wrap children in columns and vice-versa.
    • If there are no children that are rows/columns, wrap non-stack children in stacks
  3. Stack
    • Wrap non-panel children in panels

A final example on this. dashboard([t1, t2]) would become dashboard(column(stack(panel(t1)), stack(panel(t2)))). It would follow rule 1, then 2b, then 3.

Copy link
Contributor Author

@ethanalvizo ethanalvizo Sep 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The row(row(column, column), column) actually impacts the width of the panels. The two children of the outer row are given the same width so this results in the inner row splitting 50% of the width in two for each of its inner columns while the column not in the inner row fills up the remaining 50% of the outer row.

So rather than AAA|BBB|CCC it's AA|BB|CCCC

from deephaven import ui

my_dash = ui.dashboard(ui.row(ui.row(ui.column("A"), ui.column("B")), ui.column("C")))

I do still think we should push users to put columns in rows and vice versa to make defining their layouts simpler but I don't think we should say it's visually the same thing. Unless that behaviour I discovered above is unintended^

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tried condensing these points into two main sections: Layout Hierarchy and Automatic Wrapping

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AA|BB|CCCC

I've put rows in rows for that exact reason recently, so I assume that is both intended and useful?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is intentional AFAIK in golden-layout. It's just something we normally don't do when normalizing layouts because we set width/height instead

These 2 should be equivalent

row(row(col(comp), col(comp)), col(comp))
row(col(comp, width=25), col(comp, width=25), col(comp))

ethanalvizo marked this conversation as resolved.
Show resolved Hide resolved

## Layout Examples
ethanalvizo marked this conversation as resolved.
Show resolved Hide resolved
### Row split (2x1)
```python
from deephaven import ui

my_dash = ui.dashboard(ui.row(ui.panel("A"), ui.panel("B")))
```

### Column split (1x2)
```python
from deephaven import ui

my_dash = ui.dashboard(ui.column(ui.panel("A"), ui.panel("B")))
```

### 2x2
```python
from deephaven import ui

my_dash = ui.dashboard(
ui.row(
ui.column(ui.panel("A"), ui.panel("C")), ui.column(ui.panel("B"), ui.panel("D"))
)
)
```

### 3x1
```python
from deephaven import ui

my_dash = ui.dashboard(ui.row(ui.panel("A"), ui.panel("B"), ui.panel("C")))
```

### Basic stack
```python
from deephaven import ui

my_dash = ui.dashboard(ui.stack(ui.panel("A"), ui.panel("B"), ui.panel("C")))
```

### Stack in a layout
```python
from deephaven import ui

my_dash = ui.dashboard(
ui.row(
ui.stack(ui.panel("A"), ui.panel("B"), ui.panel("C")),
ui.panel("D"),
ui.panel("E"),
)
)
```

### Varying widths
```python
from deephaven import ui

my_dash = ui.dashboard(ui.row(ui.stack(ui.panel("A"), width=70), ui.panel("B")))
```

### Varying height
```python
from deephaven import ui

my_dash = ui.dashboard(ui.column(ui.stack(ui.panel("A"), height=70), ui.panel("B")))
```

### Holy Grail
```python
from deephaven import ui

my_dash = ui.dashboard(
ui.column(
ui.panel("Header"),
ui.row(
ui.panel("Left Sidebar"),
ui.stack(ui.panel("Main Content"), width=70),
ui.panel("Right Sidebar"),
),
ui.panel("Footer"),
)
)
```

## Stateful Example
### Simple
```python
from deephaven import ui


@ui.component
def layout():
message, set_message = ui.use_state("Hello! How are you doing today?")
ethanalvizo marked this conversation as resolved.
Show resolved Hide resolved

return ui.row(
ui.panel(ui.text_field(value=message, on_change=set_message, width="100%")),
ui.panel(message),
)


my_dash = ui.dashboard(layout())
```

### Complex
```python
from deephaven import ui, time_table
from deephaven.ui import use_memo, use_state
from deephaven.plot.figure import Figure


def use_wave_input():
"""
Demonstrating a custom hook.
Creates an input panel that controls the amplitude, frequency, and phase for a wave
"""
amplitude, set_amplitude = use_state(1.0)
frequency, set_frequency = use_state(1.0)
phase, set_phase = use_state(1.0)

input_panel = ui.flex(
ui.slider(
label="Amplitude",
default_value=amplitude,
min_value=-100.0,
max_value=100.0,
on_change=set_amplitude,
step=0.1,
),
ui.slider(
label="Frequency",
default_value=frequency,
min_value=-100.0,
max_value=100.0,
on_change=set_frequency,
step=0.1,
),
ui.slider(
label="Phase",
default_value=phase,
min_value=-100.0,
max_value=100.0,
on_change=set_phase,
step=0.1,
),
direction="column",
)

return amplitude, frequency, phase, input_panel


@ui.component
def multiwave():
amplitude, frequency, phase, wave_input = use_wave_input()

tt = use_memo(lambda: time_table("PT1s").update("x=i"), [])
t = use_memo(
lambda: tt.update(
[
f"y_sin={amplitude}*Math.sin({frequency}*x+{phase})",
f"y_cos={amplitude}*Math.cos({frequency}*x+{phase})",
f"y_tan={amplitude}*Math.tan({frequency}*x+{phase})",
]
),
[amplitude, frequency, phase],
)
p_sin = use_memo(
lambda: Figure().plot_xy(series_name="Sine", t=t, x="x", y="y_sin").show(), [t]
)
p_cos = use_memo(
lambda: Figure().plot_xy(series_name="Cosine", t=t, x="x", y="y_cos").show(),
[t],
)
p_tan = use_memo(
lambda: Figure().plot_xy(series_name="Tangent", t=t, x="x", y="y_tan").show(),
[t],
)

return ui.column(
ui.row(
ui.stack(
ui.panel(wave_input, title="Wave Input"),
ui.panel(t, title="Wave Table"),
activeItemIndex=0,
),
height=25,
),
ui.row(
ui.stack(ui.panel(p_sin, title="Sine"), width=50),
ui.stack(ui.panel(p_cos, title="Cosine"), width=30),
ui.stack(ui.panel(p_tan, title="Tangent")),
),
)


mw = ui.dashboard(multiwave())
```
Loading