Discharge Homogeneity Analysis — Dash Dashboard.


Imports the data pipeline and figure builders from lensing_analysis, then wraps them in an interactive Dash application with live filter controls.

Run with:

python examples/lensing_analysis_dash.py

Then open http://127.0.0.1:8052 in your browser.

For a standalone (non-Dash) Plotly chart instead:

python examples/lensing_analysis.py
import sys
from pathlib import Path
from typing import Any, cast

sys.path.insert(0, str(Path(__file__).parent))

from dash import Dash, Input, Output, dcc, html  # noqa: E402
from lensing_analysis import build_dataframes, build_lab_figure  # noqa: E402

# ── Build the data once at startup ───────────────────────────────────────────

df_all, df_ep = build_dataframes()

ALL_TESTS_IN_DATA = sorted(df_all["Test"].unique().tolist())
ALL_GENS_IN_DATA = sorted(df_all["generator"].unique().tolist())
_gaps_numeric = sorted(
    [g for g in df_all["gap_mm"].dropna().unique().tolist() if g != "—"],
    key=lambda x: float(x),
)
_gaps_ep = ["—"] if "—" in df_all["gap_mm"].values else []
ALL_GAPS_IN_DATA = _gaps_numeric + _gaps_ep

# ── Layout helpers ────────────────────────────────────────────────────────────

_BTN_STYLE = {"fontSize": "11px", "padding": "2px 7px", "cursor": "pointer"}

_SECTION_HEADER = {
    "marginTop": "14px",
    "marginBottom": "6px",
    "fontSize": "13px",
    "fontWeight": "bold",
    "color": "#333",
}


def _filter_section(
    title: str, btn_all_id: str, btn_none_id: str, checklist_id: str, options: list[str]
) -> html.Div:
    return html.Div(
        [
            html.Div(title, style=_SECTION_HEADER),
            html.Span(
                [
                    html.Button(
                        "All",
                        id=btn_all_id,
                        n_clicks=0,
                        style={**_BTN_STYLE, "marginRight": "4px"},
                    ),
                    html.Button("None", id=btn_none_id, n_clicks=0, style=_BTN_STYLE),
                ]
            ),
            html.Hr(style={"margin": "8px 0"}),
            dcc.Checklist(
                id=checklist_id,
                options=cast(
                    Any,
                    [
                        {"label": f" {v} (EP)" if v == "—" else f" {v}", "value": v}
                        for v in options
                    ],
                ),
                value=options,
                labelStyle={
                    "display": "block",
                    "fontSize": "12px",
                    "marginBottom": "3px",
                },
                inputStyle={"marginRight": "5px"},
            ),
        ]
    )


# ── Dash application ──────────────────────────────────────────────────────────

app = Dash(__name__, title="Discharge Homogeneity")

app.layout = html.Div(
    style={"display": "flex", "fontFamily": "sans-serif", "height": "100vh"},
    children=[
        # ── Left panel ───────────────────────────────────────────────────────
        html.Div(
            style={
                "width": "200px",
                "minWidth": "200px",
                "padding": "14px 14px 14px 12px",
                "borderRight": "1px solid #ddd",
                "overflowY": "auto",
                "background": "#fafafa",
            },
            children=[
                html.H4(
                    "Filters",
                    style={"marginTop": 0, "marginBottom": "4px", "fontSize": "15px"},
                ),
                _filter_section(
                    "Tests",
                    "btn-test-all",
                    "btn-test-none",
                    "test-checklist",
                    ALL_TESTS_IN_DATA,
                ),
                _filter_section(
                    "Generator",
                    "btn-gen-all",
                    "btn-gen-none",
                    "gen-checklist",
                    ALL_GENS_IN_DATA,
                ),
                _filter_section(
                    "Gap (mm)",
                    "btn-gap-all",
                    "btn-gap-none",
                    "gap-checklist",
                    ALL_GAPS_IN_DATA,
                ),
            ],
        ),
        # ── Main panel ───────────────────────────────────────────────────────
        html.Div(
            style={"flex": "1", "padding": "16px", "overflowY": "auto"},
            children=[
                dcc.Graph(
                    id="lab-graph",
                    figure=build_lab_figure(
                        df_all, ALL_TESTS_IN_DATA, ALL_GENS_IN_DATA, ALL_GAPS_IN_DATA
                    ),
                    style={"height": "calc(100vh - 40px)"},
                    config={"displayModeBar": True, "scrollZoom": True},
                ),
            ],
        ),
    ],
)


@app.callback(
    Output("test-checklist", "value"),
    Input("btn-test-all", "n_clicks"),
    Input("btn-test-none", "n_clicks"),
    prevent_initial_call=True,
)
def _toggle_tests(n_all, n_none):
    from dash import ctx

    return ALL_TESTS_IN_DATA if ctx.triggered_id == "btn-test-all" else []


@app.callback(
    Output("gen-checklist", "value"),
    Input("btn-gen-all", "n_clicks"),
    Input("btn-gen-none", "n_clicks"),
    prevent_initial_call=True,
)
def _toggle_gens(n_all, n_none):
    from dash import ctx

    return ALL_GENS_IN_DATA if ctx.triggered_id == "btn-gen-all" else []


@app.callback(
    Output("gap-checklist", "value"),
    Input("btn-gap-all", "n_clicks"),
    Input("btn-gap-none", "n_clicks"),
    prevent_initial_call=True,
)
def _toggle_gaps(n_all, n_none):
    from dash import ctx

    return ALL_GAPS_IN_DATA if ctx.triggered_id == "btn-gap-all" else []


@app.callback(
    Output("lab-graph", "figure"),
    Input("test-checklist", "value"),
    Input("gen-checklist", "value"),
    Input("gap-checklist", "value"),
)
def _update_lab_graph(selected_tests, selected_gens, selected_gaps):
    return build_lab_figure(
        df_all, selected_tests or [], selected_gens or [], selected_gaps or []
    )


if __name__ == "__main__":
    print("\nStarting Dash app at http://127.0.0.1:8052")
    app.run(debug=False, port=8052)