def build_nested_dropdown_plot_response(
charts_by_primary_key: Mapping[str, Mapping[str, ChartLike]],
*,
primary_label: str,
secondary_label: str,
default_primary_key: str | None = None,
default_secondary_key: str | None = None,
height: str = "420px",
) -> PlotResponse:
"""Build an HTML response with dependent primary and secondary dropdowns."""
if not charts_by_primary_key:
raise ValueError("At least one chart is required to build a dropdown plot.")
selected_primary_key = default_primary_key or next(iter(charts_by_primary_key))
secondary_charts = charts_by_primary_key[selected_primary_key]
if not secondary_charts:
raise ValueError("Each primary dropdown option must contain at least one chart.")
selected_secondary_key = default_secondary_key or next(iter(secondary_charts))
chart_id = f"owi_results_chart_{uuid4().hex}"
primary_select_id = f"owi_results_primary_select_{uuid4().hex}"
secondary_select_id = f"owi_results_secondary_select_{uuid4().hex}"
render_function = f"render_{chart_id}"
load_callback_name = f"renderWhenReady_{chart_id}"
for charts_by_secondary_key in charts_by_primary_key.values():
for chart in charts_by_secondary_key.values():
_apply_monospace_theme(chart)
dependencies = list(
dict.fromkeys(
dependency
for charts_by_secondary_key in charts_by_primary_key.values()
for chart in charts_by_secondary_key.values()
for dependency in chart.js_dependencies.items
)
)
options_map = (
"{\n"
+ ",\n".join(
f"{json.dumps(primary_key)}: {{\n"
+ ",\n".join(
f"{json.dumps(secondary_key)}: {chart.dump_options()}"
for secondary_key, chart in charts_by_secondary_key.items()
)
+ "\n}"
for primary_key, charts_by_secondary_key in charts_by_primary_key.items()
)
+ "\n}"
)
primary_items = [(primary_key, primary_key) for primary_key in charts_by_primary_key]
secondary_items_by_primary = {
primary_key: [(secondary_key, secondary_key) for secondary_key in charts_by_secondary_key]
for primary_key, charts_by_secondary_key in charts_by_primary_key.items()
}
primary_dropdown = _custom_dropdown_markup(
root_id=primary_select_id,
label=primary_label,
items=primary_items,
selected_value=selected_primary_key,
)
secondary_dropdown = _custom_dropdown_markup(
root_id=secondary_select_id,
label=secondary_label,
items=secondary_items_by_primary[selected_primary_key],
selected_value=selected_secondary_key,
)
html = f"""
<div class="owi-results-dropdown-plot" style="font-family:{MONOSPACE_FONT_FAMILY}; border-radius: 0.5rem; padding: 1rem; border: 1px solid #ddd;">
<style>
.owi-results-control {{
min-width: 160px;
}}
.owi-results-dropdown {{
position: relative;
}}
.owi-results-dropdown [data-role="toggle"] {{
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 4px 8px;
border: 1px solid #d0d0d0;
border-radius: 0.25rem;
background: #ffffff;
color: inherit;
font-family: {MONOSPACE_FONT_FAMILY};
cursor: pointer;
}}
.owi-results-dropdown [data-role="menu"] {{
position: absolute;
top: calc(100% + 4px);
left: 0;
z-index: 20;
min-width: 100%;
overflow: hidden;
border: 1px solid #d0d0d0;
border-radius: 0.25rem;
background: #ffffff;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.12);
}}
.owi-results-dropdown-item {{
width: 100%;
display: block;
padding: 6px 8px;
border: 0;
background: transparent;
color: inherit;
font-family: {MONOSPACE_FONT_FAMILY};
text-align: left;
cursor: pointer;
}}
.owi-results-dropdown-item:hover,
.owi-results-dropdown-item.is-selected {{
background: #f2f4f7;
}}
</style>
<div style="display:flex; gap:12px; flex-wrap:wrap; margin-bottom:12px;">
{primary_dropdown}
{secondary_dropdown}
</div>
<div id="{chart_id}" style="width:100%;height:{height};"></div>
<script>
(function() {{
function {load_callback_name}() {{
var optionsByPrimaryKey = {options_map};
var primaryItems = {json.dumps([{"value": key, "label": key} for key in charts_by_primary_key])};
var secondaryItemsByPrimary = {json.dumps({key: [{"value": secondary_key, "label": secondary_key} for secondary_key in charts_by_secondary_key] for key, charts_by_secondary_key in charts_by_primary_key.items()})};
var container = document.getElementById('{chart_id}');
{_dropdown_script_helpers()}
var primarySelector = createDropdown('{primary_select_id}', primaryItems, {json.dumps(selected_primary_key)}, function(primaryKey) {{
secondarySelector.setItems(secondaryItemsByPrimary[primaryKey], secondarySelector.getValue());
{render_function}(primaryKey, secondarySelector.getValue());
}});
var secondarySelector = createDropdown('{secondary_select_id}', secondaryItemsByPrimary[{json.dumps(selected_primary_key)}], {json.dumps(selected_secondary_key)}, function(secondaryKey) {{
{render_function}(primarySelector.getValue(), secondaryKey);
}});
var chart = null;
function ensureChart() {{
if (chart) {{
return chart;
}}
chart = echarts.init(container);
return chart;
}}
function resetChart() {{
if (chart) {{
chart.dispose();
chart = null;
}}
}}
function {render_function}(primaryKey, secondaryKey) {{
resetChart();
var activeChart = ensureChart();
activeChart.clear();
activeChart.setOption(optionsByPrimaryKey[primaryKey][secondaryKey], true);
activeChart.resize();
}}
window.addEventListener('resize', function() {{ if (chart) {{ chart.resize(); }} }});
{_initial_render_script(f"{render_function}({json.dumps(selected_primary_key)}, {json.dumps(selected_secondary_key)})")}
}}
{_loader_script(dependencies, load_callback_name)}
}})();
</script>
</div>
""".strip()
widget_notebook = _build_nested_widget_dropdown(
charts_by_primary_key,
primary_label=primary_label,
secondary_label=secondary_label,
default_primary_key=selected_primary_key,
default_secondary_key=selected_secondary_key,
frame_height=_parse_pixel_height(height, default=420) + 120,
)
notebook = _select_notebook_dropdown_renderer(
widget_renderer=widget_notebook,
html=html,
frame_height=_parse_pixel_height(height, default=420) + 120,
)
return PlotResponse(
chart=charts_by_primary_key[selected_primary_key][selected_secondary_key],
notebook=notebook,
html=html,
json_options=json.dumps(
{
primary_key: {
secondary_key: json.loads(
chart.dump_options_with_quotes()
if callable(getattr(chart, "dump_options_with_quotes", None))
else chart.dump_options()
)
for secondary_key, chart in charts_by_secondary_key.items()
}
for primary_key, charts_by_secondary_key in charts_by_primary_key.items()
}
),
)