Skip to content

Commit 4a47112

Browse files
Saba9gradio-pr-botabidlabs
authored
feat: allow hiding section header accordions (#435)
Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> Co-authored-by: Abubakar Abid <abubakar@huggingface.co> Co-authored-by: Abubakar Abid <islamrealm@gmail.com>
1 parent 4193223 commit 4a47112

7 files changed

Lines changed: 166 additions & 11 deletions

File tree

.changeset/clear-clocks-pull.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"trackio": minor
3+
---
4+
5+
feat:feat: allow hiding section header accordions

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,7 @@ Supported query parameters:
200200
- `xmin`: (number) Set the initial minimum value for the x-axis limits across all metric plots.
201201
- `xmax`: (number) Set the initial maximum value for the x-axis limits across all metric plots.
202202
- `smoothing`: (number) Set the initial value of the smoothing slider (0-20, where 0 = no smoothing).
203+
- `accordion`: (string: "hidden"). When set to "hidden", hides the section header accordions around metric groups. By default, section headers are visible.
203204

204205
## Alerts
205206

docs/source/deploy_embed.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ You can also filter the dashboard to display only specific projects or metrics u
4242
* `xmin` (number): Set the initial minimum value for the x-axis limits across all metrics plots.
4343
* `xmax` (number): Set the initial maximum value for the x-axis limits across all metrics plots.
4444
* `smoothing` (number): Set the initial value of the smoothing slider (0-20, where 0 = no smoothing).
45+
* `accordion` (string, `"hidden"`): When set to `"hidden"`, hides the section header accordions around metric groups. By default, section headers are visible.
4546

4647
You can customize your `<iframe>` using standard attributes such as `width`, `height`, and `style`. For more details, see [MDN Web Docs: `<iframe>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/iframe). For example:
4748

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ requires-python = ">=3.10"
1515
dependencies = [
1616
"pandas<3.0.0",
1717
"huggingface-hub<2.0.0",
18-
"gradio[oauth]>=6.6.0,<7.0.0",
18+
"gradio[oauth]>=6.8.0,<7.0.0",
1919
"numpy<3.0.0",
2020
"pillow<12.0.0",
2121
"orjson>=3.0,<4.0.0",
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import gradio as gr
2+
3+
4+
class HTMLAccordion(gr.HTML):
5+
def __init__(
6+
self,
7+
label: str = "",
8+
*,
9+
open: bool = True,
10+
hidden: bool = False,
11+
**kwargs,
12+
):
13+
html_template = """
14+
<div class="accordion-header">
15+
<button class="accordion-toggle" type="button">
16+
<span class="accordion-arrow">&#9654;</span>
17+
<span class="accordion-label">${label}</span>
18+
</button>
19+
</div>
20+
@children
21+
"""
22+
23+
css_template = """
24+
border: ${hidden ? 'none' : '1px solid var(--border-color-primary, #e5e7eb)'};
25+
border-radius: 8px;
26+
overflow: hidden;
27+
28+
.accordion-header {
29+
display: ${hidden ? 'none' : 'flex'};
30+
align-items: center;
31+
cursor: pointer;
32+
user-select: none;
33+
background: var(--background-fill-secondary, transparent);
34+
}
35+
36+
.accordion-toggle {
37+
display: flex;
38+
align-items: center;
39+
gap: 8px;
40+
background: none;
41+
border: none;
42+
cursor: pointer;
43+
padding: 10px 12px;
44+
width: 100%;
45+
text-align: left;
46+
color: var(--body-text-color);
47+
font-size: var(--text-md);
48+
font-weight: 600;
49+
}
50+
51+
.accordion-toggle:hover {
52+
opacity: 0.8;
53+
}
54+
55+
.accordion-arrow {
56+
font-size: 0.65em;
57+
transition: transform 0.2s ease;
58+
display: inline-block;
59+
}
60+
"""
61+
62+
js_on_load = """
63+
const header = element.querySelector('.accordion-header');
64+
const arrow = element.querySelector('.accordion-arrow');
65+
const headerParent = header ? header.parentElement : null;
66+
let isOpen = props.open !== undefined ? props.open : true;
67+
68+
function getContentNodes() {
69+
return Array.from(element.children).filter((node) => {
70+
if (node === headerParent) return false;
71+
if (node.tagName === 'STYLE') return false;
72+
return true;
73+
});
74+
}
75+
76+
function updateView() {
77+
if (arrow) {
78+
arrow.style.transform = isOpen ? 'rotate(90deg)' : 'rotate(0deg)';
79+
}
80+
if (header) {
81+
header.style.borderBottom = isOpen
82+
? '1px solid var(--border-color-primary, #e5e7eb)'
83+
: 'none';
84+
}
85+
for (const node of getContentNodes()) {
86+
node.style.display = isOpen ? '' : 'none';
87+
node.style.padding = '0 6px';
88+
}
89+
}
90+
91+
updateView();
92+
93+
if (header) {
94+
header.addEventListener('click', () => {
95+
isOpen = !isOpen;
96+
props.open = isOpen;
97+
updateView();
98+
});
99+
}
100+
"""
101+
102+
super().__init__(
103+
html_template=html_template,
104+
css_template=css_template,
105+
js_on_load=js_on_load,
106+
label=label,
107+
open=open,
108+
hidden=hidden,
109+
apply_default_css=False,
110+
**kwargs,
111+
)

trackio/ui/main.py

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from trackio.typehints import AlertEntry, LogEntry, SystemLogEntry, UploadEntry
1717
from trackio.ui import fns
1818
from trackio.ui.components.colored_checkbox import ColoredCheckboxGroup
19+
from trackio.ui.components.html_accordion import HTMLAccordion
1920
from trackio.ui.files import files_page
2021
from trackio.ui.helpers.run_selection import RunSelection
2122
from trackio.ui.media_page import media_page
@@ -241,8 +242,12 @@ def refresh_runs(
241242
)
242243

243244

244-
def generate_embed(project: str, metrics: str, selection: RunSelection) -> str:
245-
return utils.generate_embed_code(project, metrics, selection.selected)
245+
def generate_embed(
246+
project: str, metrics: str, selection: RunSelection, show_headers: bool = True
247+
) -> str:
248+
return utils.generate_embed_code(
249+
project, metrics, selection.selected, not show_headers
250+
)
246251

247252

248253
def update_x_axis_choices(project, selection, current_x_axis="step"):
@@ -571,6 +576,10 @@ def configure(request: gr.Request):
571576
case _:
572577
navbar = gr.Navbar(visible=True)
573578

579+
show_headers_cb = gr.Checkbox(
580+
value=request.query_params.get("accordion") != "hidden"
581+
)
582+
574583
return (
575584
[],
576585
sidebar,
@@ -579,6 +588,7 @@ def configure(request: gr.Request):
579588
navbar,
580589
[x_min, x_max],
581590
smoothing_value,
591+
show_headers_cb,
582592
)
583593

584594

@@ -765,6 +775,10 @@ def configure(request: gr.Request):
765775
)
766776
log_scale_x_cb = gr.Checkbox(label="Log scale X-axis", value=False)
767777
log_scale_y_cb = gr.Checkbox(label="Log scale Y-axis", value=False)
778+
show_headers_cb = gr.Checkbox(
779+
label="Show section headers",
780+
value=True,
781+
)
768782
metric_filter_tb = gr.Textbox(
769783
label="Metric Filter (regex)",
770784
placeholder="e.g., loss|ndcg@10|gpu",
@@ -791,6 +805,7 @@ def configure(request: gr.Request):
791805
navbar,
792806
x_lim,
793807
smoothing_slider,
808+
show_headers_cb,
794809
],
795810
queue=False,
796811
api_visibility="private",
@@ -835,7 +850,7 @@ def configure(request: gr.Request):
835850
api_visibility="private",
836851
).then(
837852
fn=generate_embed,
838-
inputs=[project_dd, metric_filter_tb, run_selection_state],
853+
inputs=[project_dd, metric_filter_tb, run_selection_state, show_headers_cb],
839854
outputs=[embed_code],
840855
show_progress="hidden",
841856
api_visibility="private",
@@ -868,7 +883,7 @@ def configure(request: gr.Request):
868883
gr.on(
869884
[metric_filter_tb.change, run_cb.change],
870885
fn=generate_embed,
871-
inputs=[project_dd, metric_filter_tb, run_selection_state],
886+
inputs=[project_dd, metric_filter_tb, run_selection_state, show_headers_cb],
872887
outputs=embed_code,
873888
show_progress="hidden",
874889
api_visibility="private",
@@ -906,7 +921,7 @@ def toggle_group_view(group_by_dd):
906921
queue=False,
907922
).then(
908923
fn=generate_embed,
909-
inputs=[project_dd, metric_filter_tb, run_selection_state],
924+
inputs=[project_dd, metric_filter_tb, run_selection_state, show_headers_cb],
910925
outputs=embed_code,
911926
show_progress="hidden",
912927
api_visibility="private",
@@ -921,6 +936,15 @@ def toggle_group_view(group_by_dd):
921936
show_progress="hidden",
922937
)
923938

939+
show_headers_cb.change(
940+
fn=generate_embed,
941+
inputs=[project_dd, metric_filter_tb, run_selection_state, show_headers_cb],
942+
outputs=embed_code,
943+
show_progress="hidden",
944+
api_visibility="private",
945+
queue=False,
946+
)
947+
924948
gr.api(
925949
fn=upload_db_to_space,
926950
api_name="upload_db_to_space",
@@ -1017,6 +1041,7 @@ def update_last_steps(project):
10171041
log_scale_x_cb.change,
10181042
log_scale_y_cb.change,
10191043
metric_filter_tb.change,
1044+
show_headers_cb.change,
10201045
],
10211046
inputs=[
10221047
project_dd,
@@ -1029,6 +1054,7 @@ def update_last_steps(project):
10291054
log_scale_y_cb,
10301055
metric_filter_tb,
10311056
run_selection_state,
1057+
show_headers_cb,
10321058
],
10331059
show_progress="hidden",
10341060
queue=False,
@@ -1044,6 +1070,7 @@ def update_dashboard(
10441070
log_scale_y,
10451071
metric_filter,
10461072
selection,
1073+
show_headers,
10471074
):
10481075
dfs = []
10491076
original_runs = runs.copy()
@@ -1126,11 +1153,12 @@ def update_dashboard(
11261153
else group_name
11271154
)
11281155

1129-
with gr.Accordion(
1156+
with HTMLAccordion(
11301157
label=group_label,
11311158
open=True,
1159+
hidden=not show_headers,
11321160
key=f"accordion-{group_name}",
1133-
preserved_by_key=["value", "open"],
1161+
preserved_by_key=["open"],
11341162
):
11351163
if group_data["direct_metrics"]:
11361164
with gr.Draggable(
@@ -1196,11 +1224,12 @@ def update_dashboard(
11961224
else subgroup_name
11971225
)
11981226

1199-
with gr.Accordion(
1227+
with HTMLAccordion(
12001228
label=subgroup_label,
12011229
open=True,
1230+
hidden=not show_headers,
12021231
key=f"accordion-{group_name}-{subgroup_name}",
1203-
preserved_by_key=["value", "open"],
1232+
preserved_by_key=["open"],
12041233
):
12051234
with gr.Draggable(
12061235
key=f"row-{group_name}-{subgroup_name}",

trackio/utils.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -736,7 +736,12 @@ def get_sync_status(scheduler: "CommitScheduler | DummyCommitScheduler") -> int
736736
return None
737737

738738

739-
def generate_embed_code(project: str, metrics: str, selected_runs: list = None) -> str:
739+
def generate_embed_code(
740+
project: str,
741+
metrics: str,
742+
selected_runs: list = None,
743+
hide_headers: bool = False,
744+
) -> str:
740745
"""Generate the embed iframe code based on current settings."""
741746
space_host = os.environ.get("SPACE_HOST", "")
742747
if not space_host:
@@ -754,6 +759,9 @@ def generate_embed_code(project: str, metrics: str, selected_runs: list = None)
754759
runs_param = ",".join(selected_runs)
755760
params.append(f"runs={runs_param}")
756761

762+
if hide_headers:
763+
params.append("accordion=hidden")
764+
757765
params.append("sidebar=hidden")
758766
params.append("navbar=hidden")
759767

0 commit comments

Comments
 (0)