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

Nested tabs do not work #1000

Open
chipkent opened this issue Nov 8, 2024 · 1 comment
Open

Nested tabs do not work #1000

chipkent opened this issue Nov 8, 2024 · 1 comment
Labels
bug Something isn't working
Milestone

Comments

@chipkent
Copy link
Member

chipkent commented Nov 8, 2024

Nesting tabs do not work. I suspect they are unsupported, but instead provide an error message that does not say this.

  1. Nesting tabs should be supported.
  2. If they are not supported, a clear error message to this effect should be returned.
# TODO: pydoc
import logging
from typing import Tuple, Callable, Any, Union
from deephaven import ui, dtypes
from deephaven.table import Table, PartitionedTable
from deephaven.plot.figure import Figure


# This function is to avoid UI bugs in rendering tables with non-primitive columns
# TODO: https://github.com/deephaven/deephaven-plugins/issues/701
# TODO: https://github.com/deephaven/deephaven-core/issues/5708
def _retain_primitive_cols(name: str, table: Table) -> Table:
    """Return a new table with only primitive columns.

    This function is to avoid UI bugs in rendering tables with non-primitive columns.

    Args:
        name (str): The name of the table.
        table (Table): The table to filter.
    """
    def retain_col(col):
        retain = col.data_type.is_primitive or col.data_type in [dtypes.string, dtypes.bool_, dtypes.Instant]

        if not retain:
            logging.warning(f"REMOVING COLUMN FOR VISUALIZATION: table={name} column={col.name} type={col.data_type}.")

        return retain

    return table.view([col.name for col in table.columns if retain_col(col)])


def null_note(t: Table, note: str, func: Callable[[], Any]) -> Any:
    """ Return a note if the table is empty; otherwise, return the element from the function. """
    return func() if t else ui.text(note)


# TODO: this is commented out because it fails if it is not
# @ui.component
def _symbol_picker(table: PartitionedTable, label: str = "Symbol") -> Tuple[ui.Element, str]:
    symbol_table = ui.use_memo(lambda: table.table.view("symbol").select_distinct().sort("symbol"), [])
    symbol, set_symbol = ui.use_state("")

    pick_table = ui.picker(
        symbol_table,
        label=label,  # TODO: the type hint is bad
        on_change=set_symbol,
        selected_key=symbol,
    )

    text = ui.text_field(
        value=symbol,
        on_change=set_symbol,
        label=label,
    )

    return ui.flex(pick_table, text), symbol


@ui.component
def _tab_table_view(tables: dict[str, PartitionedTable], symbol: str) -> ui.BaseElement:

    def make_component(table):
        t = table.get_constituent([symbol])
        return null_note(t, f"No data for {symbol}", lambda: t)

    return ui.stack([ui.panel(make_component(table), title=name) for name, table in tables.items()])


@ui.component
def plot_shares(shares: Table) -> Figure:
    p = Figure()

    for col in shares.columns:
        if "shares_" in col.name and "dilution_factor" not in col.name:
            p = p.plot_xy(col.name, shares, x="timestamp", y=col.name)

    return p.show()

@ui.component
def plot_prices(prices: Table) -> Figure:
    return (
        Figure()
        .plot_xy("close_split_div_adj", prices, x="timestamp", y="close_split_div_adj")
        .y_axis(log=True)
        .show()
    )

@ui.component
def plot_fundamentals(fundamentals: Table, series: str):
    return (
        Figure()
        .plot_xy(series, fundamentals, x="timestamp", y=series)
        .show()
    )


@ui.component
def plot_model_preds(model_preds: Table, symbol: str) -> Union[Figure, ui.Element]:

    if model_preds.size == 0:
        return ui.text(f"No model predictions for {symbol}")

    model_preds = model_preds.update_view([
                "pred_price_sdp = pred_price + pred_price_stdev_uncorr",  # TODO: which stdev?
                "pred_price_sdm = pred_price - pred_price_stdev_uncorr",  # TODO: which stdev?
            ])

    return (
        Figure()
        .plot_xy("Pred", t=model_preds, x="pred_time", y="pred_price",
                      y_low="pred_price_sdm", y_high="pred_price_sdp", by=["full_name"])
        .y_axis(log=True)
        .show()
    )


@ui.component
def plot_meta_preds(meta_preds: Table, symbol: str) -> Union[Figure, ui.Element]:

    if meta_preds.size == 0:
        return ui.text(f"No meta predictions for {symbol}")

    meta = (
        meta_preds
        .update_view([
            "full_name = stdev_calc + `_` + meta_weights_func",
            "pred_price_sdp = pred_price + pred_price_stdev",
            "pred_price_sdm = pred_price - pred_price_stdev",
        ])
    )

    return (
        Figure()
        .plot_xy("Meta", t=meta, x="pred_time", y="pred_price", y_low="pred_price_sdm", y_high="pred_price_sdp", by=["full_name"])
        .y_axis(log=True)
        .show()
    )


@ui.component
def plot_meta_returns(meta_preds: Table, symbol: str) -> Union[Figure, ui.Element]:

    if meta_preds.size == 0:
        return ui.text(f"No meta predictions for {symbol}")

    meta = (
        meta_preds
        .where("pred_time > 0.4")
        .update_view([
            "full_name = stdev_calc + `_` + meta_weights_func",
            "pred_return_ann_sdp = pred_return_ann + pred_return_ann_stdev",
            "pred_return_ann_sdm = pred_return_ann - pred_return_ann_stdev",
        ])
    )

    return (
        Figure()
        .plot_xy("Meta", t=meta, x="pred_time", y="pred_return_ann", y_low="pred_return_ann_sdm", y_high="pred_return_ann_sdp", by=["full_name"])
        .show()
    )


@ui.component
def plot_meta_z(meta_preds: Table, symbol: str) -> Union[Figure, ui.Element]:

    if meta_preds.size == 0:
        return ui.text(f"No meta predictions for {symbol}")

    meta = (
        meta_preds
        .where("pred_time > 0.4")
        .update_view([
            "full_name = stdev_calc + `_` + meta_weights_func",
        ])
    )

    return (
        Figure()
        .plot_xy("Z", t=meta, x="pred_time", y="pred_z", by=["full_name"])
        .show()
    )


@ui.component
def _input_plots(tables: dict[str, PartitionedTable], symbol: str) -> ui.BaseElement:

    # TODO: this doesn't work --> need to have calls to the same number of hooks
    # if not symbol:
    #     return ui.panel(ui.text("Select a symbol"))

    # TODO: filter on date

    shares = tables["shares"].get_constituent([symbol])
    prices = tables["prices"].get_constituent([symbol])
    fundamentals = tables["fundamentals"].get_constituent([symbol])
    model_preds = tables["model_preds"].get_constituent([symbol])
    meta_preds = tables["meta_preds"].get_constituent([symbol])

    error_msg = f"No data for {symbol}"
    outputs = [
        ui.panel(null_note(shares, error_msg, lambda: plot_shares(shares)), title="shares"),
        ui.panel(null_note(prices, error_msg, lambda: plot_prices(prices)), title="prices"),
    ]

    for col in tables["fundamentals"].constituent_table_columns:
        if col.name in ["date", "timestamp", "symbol"]:
            continue

        outputs.append(ui.panel(null_note(fundamentals, error_msg, lambda: plot_fundamentals(fundamentals, col.name)), title=col.name))

    outputs = outputs + [
        ui.panel(null_note(model_preds, error_msg, lambda: plot_model_preds(model_preds.where("active"), symbol)),
                     title="model_preds_active"),
        ui.panel(null_note(model_preds, error_msg, lambda: plot_model_preds(model_preds.where("!active"), symbol)),
                     title="model_preds_inactive"),
        ui.panel(null_note(meta_preds, error_msg, lambda: plot_meta_preds(meta_preds, symbol)), title="meta_preds"),
        ui.panel(null_note(meta_preds, error_msg, lambda: plot_meta_returns(meta_preds, symbol)),
                                title="meta_returns"),
        ui.panel(null_note(meta_preds, error_msg, lambda: plot_meta_z(meta_preds, symbol)), title="meta_z"),
    ]

    return ui.stack(
        *outputs,
    )


@ui.component
def my_panel(tables: dict[str, PartitionedTable]) -> ui.BaseElement:
    model_preds = tables["model_preds"]
    symbol_picker, symbol = _symbol_picker(model_preds, label="Symbol")

    # TODO: thinks this should be fine --> Doesn't work because new stuff is garbage at the bottom
    # if not symbol:
    #     return ui.column(symbol_picker)

    ttv = _tab_table_view(tables, symbol)
    ip = _input_plots(tables, symbol)

    # The ui.stack below is what is failing.  If it is removed and the elements are left in the column, everything works
    return ui.column(
        symbol_picker,
        ui.stack(
            ttv,
            ip,
        )
    )


def my_dashboard(tables: dict[str, Table]) -> ui.DashboardElement:
    part_tables = {name: _retain_primitive_cols(name, table).partition_by("symbol") for name, table in tables.items()}
    return ui.dashboard(my_panel(part_tables)) # TODO: Bad type hint or should fail


if __name__ == "__main__":
    from cecropia.lynchmunger import cli
    globals().update(cli.debug_values)
    dashboard = my_dashboard(cli.debug_values)
Error

ui.panel must be a top-level component or used within a dashboard layout.

Error: ui.panel must be a top-level component or used within a dashboard layout.
    at ReactPanel (eval at <anonymous> (http://localhost:8080/ide/assets/vendor-808b021e.js:1:1), <anonymous>:7800:11)
    at gue (http://localhost:8080/ide/assets/vendor-808b021e.js:37:53329)
    at t5e (http://localhost:8080/ide/assets/vendor-808b021e.js:41:8798)
    at X2e (http://localhost:8080/ide/assets/vendor-808b021e.js:41:965)
    at YQe (http://localhost:8080/ide/assets/vendor-808b021e.js:41:888)
    at Ey (http://localhost:8080/ide/assets/vendor-808b021e.js:41:737)
    at NP (http://localhost:8080/ide/assets/vendor-808b021e.js:39:10894)
    at http://localhost:8080/ide/assets/vendor-808b021e.js:37:39105
    at e.unstable_runWithPriority (http://localhost:8080/ide/assets/vendor-808b021e.js:26:3824)
    at Rv (http://localhost:8080/ide/assets/vendor-808b021e.js:37:38878)
    at D2e (http://localhost:8080/ide/assets/vendor-808b021e.js:37:39051)
    at ip (http://localhost:8080/ide/assets/vendor-808b021e.js:37:38984)
    at mm (http://localhost:8080/ide/assets/vendor-808b021e.js:39:8477)
    at $ue (http://localhost:8080/ide/assets/vendor-808b021e.js:37:58674)
    at eval (eval at <anonymous> (http://localhost:8080/ide/assets/vendor-808b021e.js:1:1), <anonymous>:11227:11)
    at eval (eval at <anonymous> (http://localhost:8080/ide/assets/vendor-808b021e.js:1:1), <anonymous>:7063:24)
    at callMethod (eval at <anonymous> (http://localhost:8080/ide/assets/vendor-808b021e.js:1:1), <anonymous>:7167:18)
    at noopMiddleware (eval at <anonymous> (http://localhost:8080/ide/assets/vendor-808b021e.js:1:1), <anonymous>:7199:10)
    at JSONRPCServer2.callMethod (eval at <anonymous> (http://localhost:8080/ide/assets/vendor-808b021e.js:1:1), <anonymous>:7179:51)
    at JSONRPCServer2.eval (eval at <anonymous> (http://localhost:8080/ide/assets/vendor-808b021e.js:1:1), <anonymous>:7128:31)
    at step (eval at <anonymous> (http://localhost:8080/ide/assets/vendor-808b021e.js:1:1), <anonymous>:7006:17)
    at Object.eval [as next] (eval at <anonymous> (http://localhost:8080/ide/assets/vendor-808b021e.js:1:1), <anonymous>:6958:14)
    at eval (eval at <anonymous> (http://localhost:8080/ide/assets/vendor-808b021e.js:1:1), <anonymous>:6945:67)
    at new Promise (<anonymous>)
    at __awaiter$1 (eval at <anonymous> (http://localhost:8080/ide/assets/vendor-808b021e.js:1:1), <anonymous>:6927:10)
    at JSONRPCServer2.receiveSingle (eval at <anonymous> (http://localhost:8080/ide/assets/vendor-808b021e.js:1:1), <anonymous>:7119:14)
    at JSONRPCServer2.receive (eval at <anonymous> (http://localhost:8080/ide/assets/vendor-808b021e.js:1:1), <anonymous>:7092:21)
    at JSONRPCServerAndClient2.eval (eval at <anonymous> (http://localhost:8080/ide/assets/vendor-808b021e.js:1:1), <anonymous>:7386:38)
    at step (eval at <anonymous> (http://localhost:8080/ide/assets/vendor-808b021e.js:1:1), <anonymous>:7314:17)
    at Object.eval [as next] (eval at <anonymous> (http://localhost:8080/ide/assets/vendor-808b021e.js:1:1), <anonymous>:7266:14)
    at eval (eval at <anonymous> (http://localhost:8080/ide/assets/vendor-808b021e.js:1:1), <anonymous>:7253:67)
    at new Promise (<anonymous>)
    at __awaiter (eval at <anonymous> (http://localhost:8080/ide/assets/vendor-808b021e.js:1:1), <anonymous>:7235:10)
    at JSONRPCServerAndClient2.receiveAndSend (eval at <anonymous> (http://localhost:8080/ide/assets/vendor-808b021e.js:1:1), <anonymous>:7376:14)
    at receiveData (eval at <anonymous> (http://localhost:8080/ide/assets/vendor-808b021e.js:1:1), <anonymous>:11270:32)
    at eval (eval at <anonymous> (http://localhost:8080/ide/assets/vendor-808b021e.js:1:1), <anonymous>:11282:11)
    at Object.$lambda$3 (http://localhost:8080/jsapi/dh-core.js:5399:5)
    at Function.onInvoke_9 (http://localhost:8080/jsapi/dh-core.js:6066:16)
    at lambda (http://localhost:8080/jsapi/dh-core.js:174:22)
    at Array.forEach (<anonymous>)
    at Object.$fireEvent_0 (http://localhost:8080/jsapi/dh-core.js:5375:15)
    at Object.$lambda$6_4 (http://localhost:8080/jsapi/dh-core.js:25658:11)
    at Function.apply_167 (http://localhost:8080/jsapi/dh-core.js:25885:10)
    at lambda (http://localhost:8080/jsapi/dh-core.js:174:22)
    at http://localhost:8080/jsapi/dh-internal.js:1:792286
    at Array.forEach (<anonymous>)
    at http://localhost:8080/jsapi/dh-internal.js:1:792265
    at http://localhost:8080/jsapi/dh-internal.js:1:43529
    at Array.forEach (<anonymous>)
    at e.rawOnMessage (http://localhost:8080/jsapi/dh-internal.js:1:43491)
    at http://localhost:8080/jsapi/dh-internal.js:1:41304
    at Array.forEach (<anonymous>)
    at e.onTransportChunk (http://localhost:8080/jsapi/dh-internal.js:1:41179)
    at Object.$onMessage (http://localhost:8080/jsapi/dh-core.js:17888:35)
    at MultiplexedWebsocketTransport$3methodref$onMessage$Type.handleEvent_2 [as handleEvent] (http://localhost:8080/jsapi/dh-core.js:18032:10)
image
@chipkent chipkent added bug Something isn't working triage labels Nov 8, 2024
@vbabich
Copy link
Contributor

vbabich commented Nov 12, 2024

@chipkent You can use ui.tabs for nested tabs - https://github.com/deephaven/deephaven-plugins/blob/main/plugins/ui/docs/components/tabs.md
This doc explains the rules for dashboards and panels - https://github.com/deephaven/deephaven-plugins/blob/main/plugins/ui/docs/components/dashboard.md

We'll look into how to make this simpler as it is frustrating from the end user perspective.

@vbabich vbabich added this to the December 2024 milestone Nov 12, 2024
@vbabich vbabich removed the triage label Nov 12, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants