From ada2acce496e004741e90b1e901a11a3b534b7f5 Mon Sep 17 00:00:00 2001 From: ethanalvizo <55671206+ethanalvizo@users.noreply.github.com> Date: Sun, 27 Oct 2024 22:25:53 -0400 Subject: [PATCH 1/4] docs: number field (#932) Closes #830 --- plugins/ui/docs/components/number_field.md | 318 ++++++++++++++++++ .../deephaven/ui/components/number_field.py | 8 +- 2 files changed, 324 insertions(+), 2 deletions(-) create mode 100644 plugins/ui/docs/components/number_field.md diff --git a/plugins/ui/docs/components/number_field.md b/plugins/ui/docs/components/number_field.md new file mode 100644 index 000000000..195e7f2f1 --- /dev/null +++ b/plugins/ui/docs/components/number_field.md @@ -0,0 +1,318 @@ +# Number Field + +Number fields allow users to enter a number and increase or decrease the value using stepper buttons. + +## Example + +```python +from deephaven import ui + + +my_number_field = ui.number_field( + label="Width", + on_change=lambda value: print(f"Number changed to {value}"), + default_value=1024, +) +``` + +## Value + +A number field's value is empty by default. The `default_value` prop can set an initial, uncontrolled value, or the `value` prop can set a controlled value. + +```python +from deephaven import ui + + +@ui.component +def ui_number_field_value_examples(): + value, set_value = ui.use_state(5) + return [ + ui.number_field(label="Hours (Uncontrolled)", default_value=5), + ui.number_field( + label="Favorite animal (controlled)", value=value, on_change=set_value + ), + ] + + +my_number_field_value_examples = ui_number_field_value_examples() +``` + +## HTML Forms + +Number fields can support a `name` prop for integration with HTML forms, allowing for easy identification of a value on form submission. + +```python +from deephaven import ui + + +my_number_field_name_example = ui.form( + ui.number_field( + label="Withdrawal amount", + name="amount", + default_value=45, + format_options={"currency_sign": "standard"}, + ) +) +``` + +## Labeling + +To provide a visual label for the text field, use the `label` prop. To indicate that the text field is mandatory, use the `is_required` prop. + +```python +from deephaven import ui + + +@ui.component +def ui_number_field_is_required_examples(): + return [ + ui.number_field(label="Birth year"), + ui.number_field(label="Birth year", is_required=True), + ] + + +my_number_field_is_required_example = ui_number_field_is_required_examples() +``` + +By setting `is_required` to True, the `necessity_indicator` is set to "icon" by default, but this can be changed. The `necessity_indicator` can also be used independently to indicate that the text field is optional. + +When the `necessity_indicator` prop is set to "label", a localized string will be generated for "(required)" or "(optional)" automatically. + +```python +from deephaven import ui + + +@ui.component +def ui_number_field_necessity_indicator_examples(): + return [ + ui.number_field( + label="Birth year", is_required=True, necessity_indicator="label" + ), + ui.number_field(label="Birth year", necessity_indicator="label"), + ] + + +my_number_field_necessity_indicator_examples = ( + ui_number_field_necessity_indicator_examples() +) +``` + +## Events + +The `on_change` property is triggered whenever the value in the text field is edited. + +```python +from deephaven import ui + + +@ui.component +def ui_number_field_on_change_example(): + value, set_value = ui.use_state("") + return [ + ui.number_field(label="Your age", value=value, on_change=set_value), + ui.text(f"Age has been changed to: {value}"), + ] + + +my_number_field_on_change_example = ui_number_field_on_change_example() +``` + +## Format Options + +The `format_options` prop dictates how the value is displayed and which characters can be inputted. There are 3 styles supported by this parameter: Percentage, Currency, and Units. + +Note: This prop is compatible with the option parameter of [Intl.NumberFormat](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/NumberFormat). + +### Percentage + +```python +from deephaven import ui + + +@ui.component +def ui_number_field_percentage_example(): + + return [ + ui.number_field( + label="Percent", default_value="0.5", format_options={"style": "percent"} + ), + ] + + +my_number_field_percentage_example = ui_number_field_percentage_example() +``` + +### Currency + +When the style is set to `currency`, a specific currency must be defined through the `currency` prop. Possible values follow the [ISO 4217](https://en.wikipedia.org/wiki/ISO_4217#List_of_ISO_4217_currency_codes) currency codes. Examples include "USD" for the US dollar or "EUR" for the euro. + +```python +from deephaven import ui + + +@ui.component +def ui_number_field_currency_example(): + + return [ + ui.number_field( + label="Currency", + default_value="49.99", + format_options={"style": "currency", "currency": "USD"}, + ), + ] + + +my_number_field_currency_example = ui_number_field_currency_example() +``` + +### Units +When the style is set to `unit`, a specific unit must be defined through the `unit` prop. Possible values are defined in [UTS #35, Part 2 Section 6](https://unicode.org/reports/tr35/tr35-general.html#Unit_Elements). Examples include "degree", "inch", or "cup". + +```python +from deephaven import ui + + +@ui.component +def ui_number_field_unit_example(): + + return [ + ui.number_field( + label="Unit", + default_value="10", + format_options={"style": "unit", "unit": "inch"}, + ), + ] + + +my_number_field_unit_example = ui_number_field_unit_example() +``` + +## Quiet State + +The `is_quiet` prop makes number fields "quiet". This can be useful when the input area and its corresponding styling should not distract users from surrounding content. + +```python +from deephaven import ui + + +my_number_field_is_quiet_example = ui.number_field(label="Age", is_quiet=True) +``` + +## Disabled State + +The `is_disabled` prop disables text fields to prevent user interaction. This is useful when the number field should be visible but not available for input. + +```python +from deephaven import ui + + +my_number_field_is_disabled_example = ui.number_field(label="Age", is_disabled=True) +``` + +## Read only + +The `is_read_only` prop makes number fields read-only to prevent user interaction. This is different than setting the `is_disabled` prop since the number field remains focusable, and the contents of the number field remain visible. + +```python +from deephaven import ui + + +my_number_field_is_read_only_example = ui.number_field( + label="Age", default_value=25, is_read_only=True +) +``` + +## Label position + +By default, the position of a number field's label is above the number field, but it can be changed to the side using the `label_position` prop. + +While labels can be placed either on top or on the side of the number field, top labels are the default recommendation. Top labels work better with longer copy, localization, and responsive layouts. Side labels are more useful when vertical space is limited. + +```python +from deephaven import ui + + +@ui.component +def ui_number_field_label_position_examples(): + return [ + ui.number_field(label="Sample Label"), + ui.number_field( + label="Sample Label", label_position="side", label_align="start" + ), + ] + + +my_number_field_label_position_examples = ui_number_field_label_position_examples() +``` + +## Help text + +A number field can have both a `description` and an `error_message`. The description remains visible at all times, except when the `validation_state` is set to "invalid" and an error message is present. Use the error message to offer specific guidance on how to correct the input. + +```python +from deephaven import ui + + +@ui.component +def ui_number_field_help_number_examples(): + return [ + ui.number_field( + label="Comment", + default_value="Awesome!", + validation_state="valid", + description="Enter a comment.", + ), + ui.number_field( + label="Comment", + validation_state="invalid", + error_message="Empty input is not allowed.", + ), + ] + + +my_number_field_help_number_examples = ui_number_field_help_number_examples() +``` + +## Contextual Help + +Using the `contextual_help` prop, a `ui.contextual_help` can be placed next to the label to provide additional information about the number field. + +```python +from deephaven import ui + + +my_number_field_contextual_help_example = ui.number_field( + label="FPS", + contextual_help=ui.contextual_help( + ui.heading("What is FPS"), + ui.content( + "Frames Per Second (FPS) is a measure of how many individual frames (images) are displayed in one second of video or animation" + ), + ), +) +``` + +## Custom width + +The `width` prop adjusts the width of a number field, and the `max_width` prop enforces a maximum width. + +```python +from deephaven import ui + + +@ui.component +def ui_number_field_width_examples(): + return [ + ui.number_field(label="Birth year", width="size-3600"), + ui.number_field(label="Birth year", width="size-3600", max_width="100%"), + ] + + +my_number_field_width_examples = ui_number_field_width_examples() +``` + +## API Reference + +```{eval-rst} +.. dhautofunction:: deephaven.ui.number_field +``` diff --git a/plugins/ui/src/deephaven/ui/components/number_field.py b/plugins/ui/src/deephaven/ui/components/number_field.py index 271f6e3c0..92f4adaf1 100644 --- a/plugins/ui/src/deephaven/ui/components/number_field.py +++ b/plugins/ui/src/deephaven/ui/components/number_field.py @@ -107,6 +107,7 @@ def number_field( decrement_aria_label: The aria label for the decrement stepper button. If not provided, the default is "Decrement" increment_aria_label: The aria label for the increment stepper button. If not provided, the default is "Increment" is_wheel_disabled: Whether the input should change with scroll + format_options: Options for formatting the displayed value, which also restricts input characters. is_disabled: Whether the input should be disabled is_read_only: Whether the input scan be selected but not changed by the user is_required: Whether the input is required before form submission @@ -169,12 +170,15 @@ def number_field( is_hidden: Whether the element is hidden. id: The unique identifier of the element. aria_label: The label for the element. - aria_labelled_by: The id of the element that labels the current element. - aria_described_by: The id of the element that describes the current element. + aria_labelledby: The id of the element that labels the current element. + aria_describedby: The id of the element that describes the current element. aria_details: The id of the element that provides additional information about the current element. UNSAFE_class_name: A CSS class to apply to the element. UNSAFE_style: A CSS style to apply to the element. key: A unique identifier used by React to render elements in a list. + + Returns: + The rendered number field element. """ return component_element( From 3a448eafcfb62eddbc97af21d5e553ee609b83b3 Mon Sep 17 00:00:00 2001 From: Steven Wu Date: Mon, 28 Oct 2024 09:46:39 -0400 Subject: [PATCH 2/4] fix: slider input default value (#959) - Fixes #754 - Change slider to use `useDebouncedOnChange` --- .../ui/src/js/src/elements/RangeSlider.tsx | 31 +++++++------------ plugins/ui/src/js/src/elements/Slider.tsx | 31 +++++++------------ plugins/ui/src/js/src/elements/TextArea.tsx | 2 +- plugins/ui/src/js/src/elements/TextField.tsx | 2 +- .../src/elements/hooks/useTextInputProps.ts | 2 +- .../src/elements/model/SerializedPropTypes.ts | 12 +++---- 6 files changed, 31 insertions(+), 49 deletions(-) diff --git a/plugins/ui/src/js/src/elements/RangeSlider.tsx b/plugins/ui/src/js/src/elements/RangeSlider.tsx index bb7423ae5..fa4a92de6 100644 --- a/plugins/ui/src/js/src/elements/RangeSlider.tsx +++ b/plugins/ui/src/js/src/elements/RangeSlider.tsx @@ -1,35 +1,26 @@ -import React, { useCallback, useState } from 'react'; import { RangeSlider as DHCRangeSlider, RangeSliderProps as DHCRangeSliderProps, } from '@deephaven/components'; -import { useDebouncedCallback } from '@deephaven/react-hooks'; +import useDebouncedOnChange from './hooks/useDebouncedOnChange'; +import { SerializedInputElementProps } from './model'; -const VALUE_CHANGE_DEBOUNCE = 250; +export type SerializedRangeSliderProps = SerializedInputElementProps< + DHCRangeSliderProps, + { start: number; end: number } +>; -const EMPTY_FUNCTION = () => undefined; - -export function RangeSlider(props: DHCRangeSliderProps): JSX.Element { +export function RangeSlider(props: SerializedRangeSliderProps): JSX.Element { const { defaultValue = { start: 0, end: 0 }, value: propValue, - onChange: propOnChange = EMPTY_FUNCTION, + onChange: propOnChange, ...otherProps } = props; - const [value, setValue] = useState(propValue ?? defaultValue); - - const debouncedOnChange = useDebouncedCallback( - propOnChange, - VALUE_CHANGE_DEBOUNCE - ); - - const onChange = useCallback( - newValue => { - setValue(newValue); - debouncedOnChange(newValue); - }, - [debouncedOnChange] + const [value, onChange] = useDebouncedOnChange( + propValue ?? defaultValue, + propOnChange ); return ( diff --git a/plugins/ui/src/js/src/elements/Slider.tsx b/plugins/ui/src/js/src/elements/Slider.tsx index 005e28498..f9b3eeccd 100644 --- a/plugins/ui/src/js/src/elements/Slider.tsx +++ b/plugins/ui/src/js/src/elements/Slider.tsx @@ -1,35 +1,26 @@ -import React, { useCallback, useState } from 'react'; import { Slider as DHCSlider, SliderProps as DHCSliderProps, } from '@deephaven/components'; -import { useDebouncedCallback } from '@deephaven/react-hooks'; +import useDebouncedOnChange from './hooks/useDebouncedOnChange'; +import { SerializedInputElementProps } from './model'; -const VALUE_CHANGE_DEBOUNCE = 250; +export type SerializedSliderProps = SerializedInputElementProps< + DHCSliderProps, + number +>; -const EMPTY_FUNCTION = () => undefined; - -export function Slider(props: DHCSliderProps): JSX.Element { +export function Slider(props: SerializedSliderProps): JSX.Element { const { defaultValue = 0, value: propValue, - onChange: propOnChange = EMPTY_FUNCTION, + onChange: propOnChange, ...otherProps } = props; - const [value, setValue] = useState(propValue ?? defaultValue); - - const debouncedOnChange = useDebouncedCallback( - propOnChange, - VALUE_CHANGE_DEBOUNCE - ); - - const onChange = useCallback( - newValue => { - setValue(newValue); - debouncedOnChange(newValue); - }, - [debouncedOnChange] + const [value, onChange] = useDebouncedOnChange( + propValue ?? defaultValue, + propOnChange ); return ( diff --git a/plugins/ui/src/js/src/elements/TextArea.tsx b/plugins/ui/src/js/src/elements/TextArea.tsx index c2e88da3e..3926b011d 100644 --- a/plugins/ui/src/js/src/elements/TextArea.tsx +++ b/plugins/ui/src/js/src/elements/TextArea.tsx @@ -6,7 +6,7 @@ import useTextInputProps from './hooks/useTextInputProps'; import { SerializedTextInputEventProps } from './model'; export function TextArea( - props: SerializedTextInputEventProps + props: SerializedTextInputEventProps ): JSX.Element { const textAreaProps = useTextInputProps(props); diff --git a/plugins/ui/src/js/src/elements/TextField.tsx b/plugins/ui/src/js/src/elements/TextField.tsx index 512253e8d..469d44a5d 100644 --- a/plugins/ui/src/js/src/elements/TextField.tsx +++ b/plugins/ui/src/js/src/elements/TextField.tsx @@ -6,7 +6,7 @@ import useTextInputProps from './hooks/useTextInputProps'; import { SerializedTextInputEventProps } from './model'; export function TextField( - props: SerializedTextInputEventProps + props: SerializedTextInputEventProps ): JSX.Element { const textFieldProps = useTextInputProps(props); diff --git a/plugins/ui/src/js/src/elements/hooks/useTextInputProps.ts b/plugins/ui/src/js/src/elements/hooks/useTextInputProps.ts index e24b91ec5..31b727992 100644 --- a/plugins/ui/src/js/src/elements/hooks/useTextInputProps.ts +++ b/plugins/ui/src/js/src/elements/hooks/useTextInputProps.ts @@ -5,7 +5,7 @@ import useDebouncedOnChange from './useDebouncedOnChange'; // returns SpectrumTextAreaProps export function useTextInputProps( - props: SerializedTextInputEventProps + props: SerializedTextInputEventProps ): T { const { defaultValue = '', diff --git a/plugins/ui/src/js/src/elements/model/SerializedPropTypes.ts b/plugins/ui/src/js/src/elements/model/SerializedPropTypes.ts index b69e45ede..e0ef89df0 100644 --- a/plugins/ui/src/js/src/elements/model/SerializedPropTypes.ts +++ b/plugins/ui/src/js/src/elements/model/SerializedPropTypes.ts @@ -44,24 +44,24 @@ export type SerializedPressEventProps = Omit< onPressUp?: SerializedPressEventCallback; }; -export type SerializedInputElementProps = Omit< +export type SerializedInputElementProps = Omit< T, 'defaultValue' | 'value' | 'onChange' > & { /** The default value of the input */ - defaultValue?: string; + defaultValue?: V; /** The value of the input */ - value?: string; + value?: V; /** Handler that is called when the input value changes */ - onChange?: (value: string) => Promise; + onChange?: (value: V) => Promise; }; export type SerializedButtonEventProps = SerializedFocusEventProps< SerializedKeyboardEventProps> > & { children: React.ReactNode }; -export type SerializedTextInputEventProps = SerializedFocusEventProps< - SerializedKeyboardEventProps> +export type SerializedTextInputEventProps = SerializedFocusEventProps< + SerializedKeyboardEventProps> >; From 4114ecc5d731154a248c754e1638123d5fe123ce Mon Sep 17 00:00:00 2001 From: ethanalvizo <55671206+ethanalvizo@users.noreply.github.com> Date: Mon, 28 Oct 2024 14:44:15 -0400 Subject: [PATCH 3/4] docs: dashboard (#814) Closes #708 BREAKING CHANGE: Changed `activeItemIdx` to `active_item_index` in `stack.py` --- plugins/ui/docs/components/dashboard.md | 335 ++++++++++++++++++ .../ui/src/deephaven/ui/components/column.py | 8 +- .../src/deephaven/ui/components/dashboard.py | 5 +- .../ui/src/deephaven/ui/components/panel.py | 5 +- plugins/ui/src/deephaven/ui/components/row.py | 6 +- .../ui/src/deephaven/ui/components/stack.py | 13 +- 6 files changed, 363 insertions(+), 9 deletions(-) create mode 100644 plugins/ui/docs/components/dashboard.md diff --git a/plugins/ui/docs/components/dashboard.md b/plugins/ui/docs/components/dashboard.md new file mode 100644 index 000000000..f0af00435 --- /dev/null +++ b/plugins/ui/docs/components/dashboard.md @@ -0,0 +1,335 @@ +# Dashboard + +Dashboards allow you to create a page layout containing a collection of components. The user can move and resize panels within the dashboard. + +## Example + +```python +from deephaven import ui + +my_dash = ui.dashboard(ui.row(ui.panel("A"))) +``` + +## Rules + +1. Dashboards must be a child of the root script and not nested inside a `@ui.component`. Otherwise, the application cannot correctly determine the type of the component. +2. Dashboards must have one and only one child, typically a row or column. +3. Height and width of panels are summed to 100% within a row or column. + +## Key Components + +Four main children make up a dashboard: row, column, stack, and panels. + +- **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 a stack of tabs. Each element gets its own tab, with only one element visible at a time. +- **Panel**: A container used to group and label elements. + +## Layout Hierarchy + +### Top-Level + +Your dashboard must start with a row or column, which is the "top" of the layout tree. Columns should go inside rows and rows should go inside columns + +Note: Nesting rows within rows or columns within columns will sub-divide the row or column. + +### Bottom-Level + +Stacks and panels are considered the "bottom" of the layout tree. Once added, the layout in that section is considered complete. You can't further nest stacks within panels. For layouts within a panel, see [`tabs`](./tabs.md), [`flex`](./flex.md), and [`grid`](./grid.md). + +## Automatic Wrapping + +Children are implicitly wrapped when necessary, so the entire layout does not need to be explicitly defined. + +End to end example: `dashboard([t1, t2])` would become `dashboard(column(stack(panel(t1)), stack(panel(t2))))`. + +Automatic wrapping is applied by the following rules: + +1. Dashboard: wrap in row/column if no single node is the default (e.g., `[t1, t2]` as the child to the dashboard would become `row(t1, t2)`). +2. Row/Column: + - If there are children that are rows/columns, wrap the non-wrapped children with the same element (e.g., `row(col(t1), t2)` becomes `row(col(t1), col(t2))`). + - If none of the children are wrapped by rows/columns, they are wrapped in stacks (e.g., `row(col(t1), col(t2))` from above becomes `row(col(stack(t1)), col(stack(t2)))`). +3. Stacks: wrap non-panel children in panels (e.g., `row(col(stack(t1)), col(stack(t2)))` becomes `row(col(stack(panel(t1))), col(stack(panel(t2))))`). + +## Varying dimensions + +Rows can have a specified height, while columns can have a specified width. + +Stacks directly within a column can have a height or a width if they are within a row. Setting the other dimension will be ignored. + +### Varying row heights + +```python +from deephaven import ui + +dash_row_heights = ui.dashboard( + [ + ui.row(ui.stack(ui.panel("A", title="A")), ui.panel("B", title="B"), height=70), + ui.row(ui.stack(ui.panel("C", title="C")), ui.panel("D", title="D")), + ] +) +``` + +### Varying column widths + +```python +from deephaven import ui + +dash_column_widths = ui.dashboard( + [ + ui.column( + ui.stack(ui.panel("A", title="A")), ui.panel("C", title="C"), width=70 + ), + ui.column(ui.stack(ui.panel("B", title="B")), ui.panel("D", title="D")), + ] +) +``` + +### Varying stack widths + +```python +from deephaven import ui + +dash_stack_widths = ui.dashboard( + ui.row(ui.stack(ui.panel("A", title="A"), width=70), ui.panel("B", title="B")) +) +``` + +### Varying stack heights + +```python +from deephaven import ui + +dash_stack_heights = ui.dashboard( + ui.column(ui.stack(ui.panel("A", title="A"), height=70), ui.panel("B", title="B")) +) +``` + +## Layout Examples + +### Row split (2x1) + +```python +from deephaven import ui + +dash_2x1 = ui.dashboard(ui.row(ui.panel("A", title="A"), ui.panel("B", title="B"))) +``` + +### Column split (1x2) + +```python +from deephaven import ui + +dash_1x2 = ui.dashboard(ui.column(ui.panel("A", title="A"), ui.panel("B", title="B"))) +``` + +### 2x2 + +```python +from deephaven import ui + +dash_2x2 = ui.dashboard( + ui.row( + ui.column(ui.panel("A", title="A"), ui.panel("C", title="C")), + ui.column(ui.panel("B", title="B"), ui.panel("D", title="D")), + ) +) +``` + +### 3x1 + +```python +from deephaven import ui + +dash_3x1 = ui.dashboard( + ui.row(ui.panel("A", title="A"), ui.panel("B", title="B"), ui.panel("C", title="C")) +) +``` + +### Basic stack + +```python +from deephaven import ui + +dash_stack = ui.dashboard( + ui.stack( + ui.panel("A", title="A"), ui.panel("B", title="B"), ui.panel("C", title="C") + ) +) +``` + +### Stack in a layout + +```python +from deephaven import ui + +dash_layout_stack = ui.dashboard( + ui.row( + ui.stack( + ui.panel("A", title="A"), ui.panel("B", title="B"), ui.panel("C", title="C") + ), + ui.panel("D", title="D"), + ui.panel("E", title="E"), + ) +) +``` + +### Holy Grail + +```python +from deephaven import ui + +dash_holy_grail = ui.dashboard( + ui.column( + ui.panel("Header", title="Header"), + ui.row( + ui.panel("Left Sidebar", title="Left Sidebar"), + ui.stack(ui.panel("Main Content", title="Main Content"), width=70), + ui.panel("Right Sidebar", title="Right Sidebar"), + ), + ui.panel("Footer", title="Footer"), + ) +) +``` + +## Stateful Example + +By hoisting state management to the dashboard component, interacting and sharing data between the child components is much easier to maintain and debug. + +### Simple + +```python +from deephaven import ui + + +@ui.component +def layout(): + message, set_message = ui.use_state("Hello world!") + + return ui.row( + ui.panel(ui.text_field(value=message, on_change=set_message, width="100%")), + ui.panel(message), + ) + + +dash_simple_state = 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"), + active_item_index=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")), + ), + ) + + +dash_complex_state = ui.dashboard(multiwave()) +``` + +## API Reference + +```{eval-rst} +.. dhautofunction:: deephaven.ui.dashboard +``` + +### Row API Reference + +```{eval-rst} +.. dhautofunction:: deephaven.ui.row +``` + +### Column API Reference + +```{eval-rst} +.. dhautofunction:: deephaven.ui.column +``` + +### Stack API Reference + +```{eval-rst} +.. dhautofunction:: deephaven.ui.stack +``` diff --git a/plugins/ui/src/deephaven/ui/components/column.py b/plugins/ui/src/deephaven/ui/components/column.py index ef6868097..4dbeea67b 100644 --- a/plugins/ui/src/deephaven/ui/components/column.py +++ b/plugins/ui/src/deephaven/ui/components/column.py @@ -2,9 +2,12 @@ from typing import Any from .basic import component_element +from ..elements import Element -def column(*children: Any, width: float | None = None, key: str | None = None): +def column( + *children: Any, width: float | None = None, key: str | None = None +) -> Element: """ A column is a container that can be used to group elements. Each element will be placed below its prior sibling. @@ -13,5 +16,8 @@ def column(*children: Any, width: float | None = None, key: str | None = None): children: Elements to render in the column. width: The percent width of the column relative to other children of its parent. If not provided, the column will be sized automatically. key: A unique identifier used by React to render elements in a list. + + Returns: + The rendered column element. """ return component_element("Column", *children, width=width, key=key) diff --git a/plugins/ui/src/deephaven/ui/components/dashboard.py b/plugins/ui/src/deephaven/ui/components/dashboard.py index a6c4c0595..44ae34491 100644 --- a/plugins/ui/src/deephaven/ui/components/dashboard.py +++ b/plugins/ui/src/deephaven/ui/components/dashboard.py @@ -4,12 +4,15 @@ from ..elements import DashboardElement, FunctionElement -def dashboard(element: FunctionElement): +def dashboard(element: FunctionElement) -> DashboardElement: """ A dashboard is the container for an entire layout. Args: element: Element to render as the dashboard. The element should render a layout that contains 1 root column or row. + + Returns: + The rendered dashboard. """ return DashboardElement(element) diff --git a/plugins/ui/src/deephaven/ui/components/panel.py b/plugins/ui/src/deephaven/ui/components/panel.py index 283d35fbe..9e9f12429 100644 --- a/plugins/ui/src/deephaven/ui/components/panel.py +++ b/plugins/ui/src/deephaven/ui/components/panel.py @@ -12,6 +12,7 @@ DimensionValue, Overflow, ) +from ..elements import Element def panel( @@ -35,7 +36,7 @@ def panel( padding_y: DimensionValue | None = None, key: str | None = None, **props: Any, -): +) -> Element: """ A panel is a container that can be used to group elements. @@ -59,6 +60,8 @@ def panel( padding_y: The padding to apply to the top and bottom of the element. key: A unique identifier used by React to render elements in a list. + Returns: + The rendered panel element. """ children, props = create_props(locals()) diff --git a/plugins/ui/src/deephaven/ui/components/row.py b/plugins/ui/src/deephaven/ui/components/row.py index 801ef063d..d059beae1 100644 --- a/plugins/ui/src/deephaven/ui/components/row.py +++ b/plugins/ui/src/deephaven/ui/components/row.py @@ -2,9 +2,10 @@ from typing import Any from .basic import component_element +from ..elements import Element -def row(*children: Any, height: float | None = None, key: str | None = None): +def row(*children: Any, height: float | None = None, key: str | None = None) -> Element: """ A row is a container that can be used to group elements. Each element will be placed to the right of its prior sibling. @@ -13,5 +14,8 @@ def row(*children: Any, height: float | None = None, key: str | None = None): *children: Elements to render in the row. height: The percent height of the row relative to other children of its parent. If not provided, the row will be sized automatically. key: A unique identifier used by React to render elements in a list. + + Returns: + The rendered row element. """ return component_element("Row", *children, height=height, key=key) diff --git a/plugins/ui/src/deephaven/ui/components/stack.py b/plugins/ui/src/deephaven/ui/components/stack.py index b5cd93e40..52470f035 100644 --- a/plugins/ui/src/deephaven/ui/components/stack.py +++ b/plugins/ui/src/deephaven/ui/components/stack.py @@ -2,16 +2,16 @@ from typing import Any from .basic import component_element +from ..elements import Element def stack( *children: Any, height: float | None = None, width: float | None = None, - activeItemIndex: int | None = None, + active_item_index: int | None = None, key: str | None = None, - **kwargs: Any, -): +) -> Element: """ A stack is a container that can be used to group elements which creates a set of tabs. Each element will get a tab and only one element can be visible at a time. @@ -20,14 +20,17 @@ def stack( *children: Elements to render in the row. height: The percent height of the stack relative to other children of its parent. If not provided, the stack will be sized automatically. width: The percent width of the stack relative to other children of its parent. If not provided, the stack will be sized automatically. + active_item_index: The index of the active item in the stack. key: A unique identifier used by React to render elements in a list. + + Returns: + The rendered stack element. """ return component_element( "Stack", *children, height=height, width=width, - activeItemIndex=activeItemIndex, + active_item_index=active_item_index, key=key, - **kwargs, ) From 82f7125e04b2ae7c6d6ef7c4572aca7de77025ee Mon Sep 17 00:00:00 2001 From: Joe Date: Tue, 29 Oct 2024 10:32:03 -0500 Subject: [PATCH 4/4] fix: Incorrect output in autodoc (#960) Autodoc output was not as expected Additionally, description was still in the ParamTable object, but can have newlines, so rendering broke. --- sphinx_ext/deephaven_autodoc.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/sphinx_ext/deephaven_autodoc.py b/sphinx_ext/deephaven_autodoc.py index 5d7abc703..56e6e946b 100644 --- a/sphinx_ext/deephaven_autodoc.py +++ b/sphinx_ext/deephaven_autodoc.py @@ -33,14 +33,13 @@ class FunctionMetadata(TypedDict): name: str -# total is False to allow for popping some keys class SignatureData(TypedDict): parameters: Params - return_description: str - return_type: str + return_description: NotRequired[str] + return_type: NotRequired[str] module_name: str name: str - description: str + description: NotRequired[str] SignatureValue = Union[str, Params] @@ -306,15 +305,17 @@ def to_mdx(node: sphinx.addnodes.desc) -> docutils.nodes.comment: """ result = extract_desc_data(node) - dat = json.dumps(result) + # these need to be popped in case they have newlines which will break ParamTable rendering + return_description = result.pop("return_description") + return_type = result.pop("return_type") + description = result.pop("description") - return_description = result["return_description"] - return_type = result["return_type"] + dat = json.dumps(result) autofunction_markdown = ( f"{AUTOFUNCTION_COMMENT_PREFIX}" - rf"{return_description}

" - rf"**Returns:** {return_type}

" + rf"{description}

" + rf"**Returns:** `{return_type}` {return_description}

" rf"" )