deepboard 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- deepboard/__init__.py +1 -0
- deepboard/__version__.py +4 -0
- deepboard/gui/THEME.yml +28 -0
- deepboard/gui/__init__.py +0 -0
- deepboard/gui/assets/artefacts.css +108 -0
- deepboard/gui/assets/base.css +208 -0
- deepboard/gui/assets/base.js +77 -0
- deepboard/gui/assets/charts.css +188 -0
- deepboard/gui/assets/compare.css +90 -0
- deepboard/gui/assets/datagrid.css +120 -0
- deepboard/gui/assets/fileview.css +13 -0
- deepboard/gui/assets/right_panel.css +227 -0
- deepboard/gui/assets/theme.css +85 -0
- deepboard/gui/components/__init__.py +8 -0
- deepboard/gui/components/artefact_group.py +12 -0
- deepboard/gui/components/chart_type.py +22 -0
- deepboard/gui/components/legend.py +34 -0
- deepboard/gui/components/log_selector.py +22 -0
- deepboard/gui/components/modal.py +20 -0
- deepboard/gui/components/smoother.py +21 -0
- deepboard/gui/components/split_selector.py +21 -0
- deepboard/gui/components/stat_line.py +8 -0
- deepboard/gui/entry.py +21 -0
- deepboard/gui/main.py +93 -0
- deepboard/gui/pages/__init__.py +1 -0
- deepboard/gui/pages/compare_page/__init__.py +6 -0
- deepboard/gui/pages/compare_page/compare_page.py +22 -0
- deepboard/gui/pages/compare_page/components/__init__.py +4 -0
- deepboard/gui/pages/compare_page/components/card_list.py +19 -0
- deepboard/gui/pages/compare_page/components/chart.py +54 -0
- deepboard/gui/pages/compare_page/components/compare_setup.py +30 -0
- deepboard/gui/pages/compare_page/components/split_card.py +51 -0
- deepboard/gui/pages/compare_page/components/utils.py +20 -0
- deepboard/gui/pages/compare_page/routes.py +58 -0
- deepboard/gui/pages/main_page/__init__.py +4 -0
- deepboard/gui/pages/main_page/datagrid/__init__.py +5 -0
- deepboard/gui/pages/main_page/datagrid/compare_button.py +21 -0
- deepboard/gui/pages/main_page/datagrid/datagrid.py +67 -0
- deepboard/gui/pages/main_page/datagrid/handlers.py +54 -0
- deepboard/gui/pages/main_page/datagrid/header.py +43 -0
- deepboard/gui/pages/main_page/datagrid/routes.py +112 -0
- deepboard/gui/pages/main_page/datagrid/row.py +20 -0
- deepboard/gui/pages/main_page/datagrid/sortable_column_js.py +45 -0
- deepboard/gui/pages/main_page/datagrid/utils.py +9 -0
- deepboard/gui/pages/main_page/handlers.py +16 -0
- deepboard/gui/pages/main_page/main_page.py +21 -0
- deepboard/gui/pages/main_page/right_panel/__init__.py +12 -0
- deepboard/gui/pages/main_page/right_panel/config.py +57 -0
- deepboard/gui/pages/main_page/right_panel/fragments.py +133 -0
- deepboard/gui/pages/main_page/right_panel/hparams.py +25 -0
- deepboard/gui/pages/main_page/right_panel/images.py +358 -0
- deepboard/gui/pages/main_page/right_panel/run_info.py +86 -0
- deepboard/gui/pages/main_page/right_panel/scalars.py +251 -0
- deepboard/gui/pages/main_page/right_panel/template.py +151 -0
- deepboard/gui/pages/main_page/routes.py +25 -0
- deepboard/gui/pages/not_found.py +3 -0
- deepboard/gui/requirements.txt +5 -0
- deepboard/gui/utils.py +267 -0
- deepboard/resultTable/__init__.py +2 -0
- deepboard/resultTable/cursor.py +20 -0
- deepboard/resultTable/logwritter.py +667 -0
- deepboard/resultTable/resultTable.py +529 -0
- deepboard/resultTable/scalar.py +29 -0
- deepboard/resultTable/table_schema.py +135 -0
- deepboard/resultTable/utils.py +50 -0
- deepboard-0.2.0.dist-info/METADATA +164 -0
- deepboard-0.2.0.dist-info/RECORD +69 -0
- deepboard-0.2.0.dist-info/WHEEL +4 -0
- deepboard-0.2.0.dist-info/entry_points.txt +2 -0
deepboard/gui/main.py
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
import os
|
2
|
+
import sys
|
3
|
+
sys.path.append(os.getcwd())
|
4
|
+
from fasthtml.common import *
|
5
|
+
from deepboard.gui.pages.main_page.datagrid import SortableColumnsJs, right_click_handler_row, right_click_handler
|
6
|
+
from deepboard.gui.pages.main_page import MainPage, build_main_page_endpoints
|
7
|
+
from deepboard.gui.utils import prepare_db, Config, initiate_files, get_table_path_from_cli, verify_runids
|
8
|
+
from deepboard.gui.pages.compare_page import build_compare_routes, ComparePage
|
9
|
+
from deepboard.gui.pages import _not_found
|
10
|
+
from deepboard.gui.components import Modal
|
11
|
+
from deepboard.resultTable import ResultTable
|
12
|
+
from fh_plotly import plotly_headers
|
13
|
+
|
14
|
+
DEBUG = False
|
15
|
+
# Create config files to customize the UI
|
16
|
+
initiate_files()
|
17
|
+
|
18
|
+
# Load config and DB
|
19
|
+
CONFIG = Config.FromFile(os.path.expanduser('~/.config/deepboard/THEME.yml'))
|
20
|
+
DATABASE = get_table_path_from_cli()
|
21
|
+
if not os.path.exists(DATABASE):
|
22
|
+
raise RuntimeError(f"ResultTable {DATABASE} does not exist")
|
23
|
+
prepare_db()
|
24
|
+
|
25
|
+
# Load the result Table
|
26
|
+
rTable = ResultTable(DATABASE)
|
27
|
+
|
28
|
+
cls = FastHTMLWithLiveReload if DEBUG else FastHTML
|
29
|
+
app = cls(
|
30
|
+
exception_handlers={404: _not_found},
|
31
|
+
hdrs=(
|
32
|
+
Link(rel='stylesheet', href='assets/base.css', type='text/css'),
|
33
|
+
Link(rel='stylesheet', href='assets/datagrid.css', type='text/css'),
|
34
|
+
Link(rel='stylesheet', href='assets/right_panel.css', type='text/css'),
|
35
|
+
Link(rel='stylesheet', href='assets/charts.css', type='text/css'),
|
36
|
+
Link(rel='stylesheet', href='assets/fileview.css', type='text/css'),
|
37
|
+
Link(rel='stylesheet', href='assets/compare.css', type='text/css'),
|
38
|
+
Link(rel='stylesheet', href='assets/artefacts.css', type='text/css'),
|
39
|
+
Link(href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css", rel="stylesheet"),
|
40
|
+
plotly_headers,
|
41
|
+
Script(src="assets/base.js"),
|
42
|
+
SortableColumnsJs(),
|
43
|
+
),
|
44
|
+
static_path='assets'
|
45
|
+
)
|
46
|
+
|
47
|
+
rt = app.route
|
48
|
+
@rt("/assets/{fname:path}.{ext:static}")
|
49
|
+
async def get(fname:str, ext:str):
|
50
|
+
if fname == "theme" and ext == "css" and not DEBUG:
|
51
|
+
if os.path.exists(os.path.expanduser('~/.config/deepboard/theme.css')):
|
52
|
+
return FileResponse(os.path.expanduser('~/.config/deepboard/THEME.css'))
|
53
|
+
root = os.path.dirname(os.path.abspath(__file__))
|
54
|
+
return FileResponse(f'{root}/assets/{fname}.{ext}')
|
55
|
+
|
56
|
+
|
57
|
+
@rt("/")
|
58
|
+
def get(session):
|
59
|
+
if "show_hidden" not in session:
|
60
|
+
session["show_hidden"] = False
|
61
|
+
|
62
|
+
# Check if row_selected exists.
|
63
|
+
verify_runids(session, rTable)
|
64
|
+
|
65
|
+
return (Title("Table"),
|
66
|
+
Div(id="custom-menu"),
|
67
|
+
Modal(P("Hellp world"), active=False),
|
68
|
+
MainPage(session),
|
69
|
+
)
|
70
|
+
|
71
|
+
|
72
|
+
@rt("/compare")
|
73
|
+
def get(session, run_ids: str):
|
74
|
+
return ComparePage(session, run_ids)
|
75
|
+
|
76
|
+
|
77
|
+
# Dropdown menu when right-clicked
|
78
|
+
@rt("/get-context-menu")
|
79
|
+
def get(session, elementIds: str, top: int, left: int):
|
80
|
+
elementIds = elementIds.split(",")
|
81
|
+
if any(elementId.startswith("grid-header") for elementId in elementIds):
|
82
|
+
return right_click_handler(elementIds, top, left)
|
83
|
+
elif any(elementId.startswith("grid-row") for elementId in elementIds):
|
84
|
+
return right_click_handler_row(session, elementIds, top, left)
|
85
|
+
else:
|
86
|
+
return Div(
|
87
|
+
id='custom-menu',
|
88
|
+
style=f'visibility: visible; top: {top}px; left: {left}px;',
|
89
|
+
)
|
90
|
+
|
91
|
+
build_main_page_endpoints(rt)
|
92
|
+
build_compare_routes(rt)
|
93
|
+
serve(reload=DEBUG)
|
@@ -0,0 +1 @@
|
|
1
|
+
from .not_found import _not_found
|
@@ -0,0 +1,6 @@
|
|
1
|
+
from .components.card_list import ChartCardList
|
2
|
+
from .components.compare_setup import CompareSetup
|
3
|
+
from .components.split_card import SplitCard
|
4
|
+
from .components.chart import Chart, LoadingChart
|
5
|
+
from .compare_page import ComparePage
|
6
|
+
from .routes import build_compare_routes
|
@@ -0,0 +1,22 @@
|
|
1
|
+
from typing import *
|
2
|
+
from fasthtml.common import *
|
3
|
+
from .components import CompareSetup, ChartCardList
|
4
|
+
|
5
|
+
|
6
|
+
def ComparePage(session, run_ids: str):
|
7
|
+
run_ids = run_ids.split(",")
|
8
|
+
session["compare"] = {"selected-rows": run_ids}
|
9
|
+
return (Title("Compare"),
|
10
|
+
Div(id="custom-menu"),
|
11
|
+
Div(
|
12
|
+
Div(
|
13
|
+
CompareSetup(session),
|
14
|
+
cls="compare-setup-container"
|
15
|
+
),
|
16
|
+
Div(
|
17
|
+
ChartCardList(session),
|
18
|
+
cls="cards-list-container"
|
19
|
+
),
|
20
|
+
cls="compare-container"
|
21
|
+
)
|
22
|
+
)
|
@@ -0,0 +1,19 @@
|
|
1
|
+
from typing import *
|
2
|
+
from fasthtml.common import *
|
3
|
+
from .split_card import SplitCard
|
4
|
+
|
5
|
+
def ChartCardList(session, swap: bool = False):
|
6
|
+
runIDs = sorted([int(rid) for rid in session["compare"]["selected-rows"]])
|
7
|
+
from __main__ import rTable
|
8
|
+
sockets = [rTable.load_run(runID) for runID in runIDs]
|
9
|
+
keys = {key for socket in sockets for key in socket.formatted_scalars}
|
10
|
+
splits = {split for split, metric in keys}
|
11
|
+
splits = sorted(splits)
|
12
|
+
metrics = {split: [metric for sp, metric in keys if sp == split] for split in splits}
|
13
|
+
|
14
|
+
return Ul(
|
15
|
+
*[SplitCard(session, split, metrics[split]) for split in splits],
|
16
|
+
cls="comparison-list",
|
17
|
+
id="chart-card-list",
|
18
|
+
hx_swap_oob="true" if swap else None
|
19
|
+
)
|
@@ -0,0 +1,54 @@
|
|
1
|
+
from typing import *
|
2
|
+
from fasthtml.common import *
|
3
|
+
from fh_plotly import plotly2fasthtml
|
4
|
+
from .utils import make_lines
|
5
|
+
from deepboard.gui.utils import make_fig
|
6
|
+
|
7
|
+
def Chart(session, split: str, metric: str, type: Literal["step", "duration"], running: bool, logscale: bool):
|
8
|
+
from __main__ import rTable
|
9
|
+
runIDs = [int(txt) for txt in session["compare"]["selected-rows"]]
|
10
|
+
runIDs.sort()
|
11
|
+
sockets = [rTable.load_run(runID) for runID in runIDs]
|
12
|
+
hidden_lines = session["compare"]["hidden_lines"] if "hidden_lines" in session["compare"] else []
|
13
|
+
smoothness = session["compare"]["smoother_value"] - 1 if "smoother_value" in session["compare"] else 0
|
14
|
+
lines = make_lines(sockets, split, metric, runIDs, type)
|
15
|
+
|
16
|
+
# # Sort lines by label
|
17
|
+
lines.sort(key=lambda x: x[0])
|
18
|
+
# Hide lines if needed
|
19
|
+
lines = [line for line in lines if line[0] not in hidden_lines]
|
20
|
+
fig = make_fig(lines, type=type, smoothness=smoothness, log_scale=logscale)
|
21
|
+
|
22
|
+
if running:
|
23
|
+
update_params = dict(
|
24
|
+
hx_get=f"/compare/chart?split={split}&metric={metric}&type={type}&running={running}&logscale={logscale}",
|
25
|
+
hx_target=f"#chart-container-{split}-{metric}",
|
26
|
+
hx_trigger="every 10s",
|
27
|
+
hx_swap="outerHTML",
|
28
|
+
)
|
29
|
+
else:
|
30
|
+
update_params = {}
|
31
|
+
return Div(
|
32
|
+
plotly2fasthtml(fig, js_options=dict(responsive=True)),
|
33
|
+
cls="chart-container",
|
34
|
+
id=f"chart-container-{split}-{metric}",
|
35
|
+
**update_params
|
36
|
+
),
|
37
|
+
|
38
|
+
def LoadingChart(session, split: str, metric: str, type: Literal["step", "duration"], running: bool = False, logscale: bool = False):
|
39
|
+
return Div(
|
40
|
+
Div(
|
41
|
+
H1(metric, cls="chart-title"),
|
42
|
+
cls="chart-header",
|
43
|
+
id=f"chart-header-{split}-{metric}"
|
44
|
+
),
|
45
|
+
Div(
|
46
|
+
cls="chart-container",
|
47
|
+
id=f"chart-container-{split}-{metric}",
|
48
|
+
hx_get=f"/compare/chart?split={split}&metric={metric}&type={type}&running={running}&logscale={logscale}",
|
49
|
+
hx_target=f"#chart-container-{split}-{metric}",
|
50
|
+
hx_trigger="load",
|
51
|
+
),
|
52
|
+
cls = "chart",
|
53
|
+
id = f"chart-{split}-{metric}",
|
54
|
+
)
|
@@ -0,0 +1,30 @@
|
|
1
|
+
from typing import *
|
2
|
+
from fasthtml.common import *
|
3
|
+
from deepboard.gui.components import Legend, ChartType, Smoother, LogSelector
|
4
|
+
|
5
|
+
def CompareSetup(session, swap: bool = False):
|
6
|
+
from __main__ import CONFIG
|
7
|
+
from __main__ import rTable
|
8
|
+
if "hidden_lines" in session["compare"]:
|
9
|
+
hidden_lines = session["compare"]["hidden_lines"]
|
10
|
+
else:
|
11
|
+
hidden_lines = []
|
12
|
+
raw_labels = [int(txt) for txt in session["compare"]["selected-rows"]]
|
13
|
+
raw_labels = sorted(raw_labels)
|
14
|
+
sockets = [rTable.load_run(runID) for runID in raw_labels]
|
15
|
+
repetitions = [socket.get_repetitions() for socket in sockets]
|
16
|
+
if any(len(rep) > 1 for rep in repetitions):
|
17
|
+
labels = [(f"{label}.{rep}", CONFIG.COLORS[i % len(CONFIG.COLORS)], f"{label}.{rep}" in hidden_lines) for i, label in enumerate(raw_labels) for rep in sockets[i].get_repetitions()]
|
18
|
+
else:
|
19
|
+
labels = [(f"{label}", CONFIG.COLORS[i % len(CONFIG.COLORS)], f"{label}" in hidden_lines) for
|
20
|
+
i, label in enumerate(raw_labels)]
|
21
|
+
return Div(
|
22
|
+
H1("Setup", cls="chart-scalar-title"),
|
23
|
+
Legend(session, labels, path="/compare", selected_rows_key="compare"),
|
24
|
+
ChartType(session, path="/compare", selected_rows_key="compare", session_path="compare"),
|
25
|
+
LogSelector(session, path="/compare", selected_rows_key="compare", session_path="compare"),
|
26
|
+
Smoother(session, path="/compare", selected_rows_key="compare", session_path="compare"),
|
27
|
+
cls="setup-card",
|
28
|
+
id="setup-card",
|
29
|
+
hx_swap_oob="true" if swap else None,
|
30
|
+
)
|
@@ -0,0 +1,51 @@
|
|
1
|
+
from typing import *
|
2
|
+
from fasthtml.common import *
|
3
|
+
from .chart import LoadingChart
|
4
|
+
|
5
|
+
def SplitCard(session, split: str, metrics: List[str]):
|
6
|
+
from __main__ import rTable
|
7
|
+
runIDs = sorted([int(rid) for rid in session["compare"]["selected-rows"]])
|
8
|
+
sockets = [rTable.load_run(runID) for runID in runIDs]
|
9
|
+
running = any([socket.status == "running" for socket in sockets])
|
10
|
+
metrics = sorted(metrics)
|
11
|
+
chart_type = session["compare"]["chart_type"] if "chart_type" in session["compare"] else "step"
|
12
|
+
logscale = session["compare"]["chart_scale"] == "log" if "chart_scale" in session["compare"] else False
|
13
|
+
|
14
|
+
opened = session["compare"]["cards-state"][split] if "cards-state" in session["compare"] and split in session["compare"]["cards-state"] else True
|
15
|
+
if opened:
|
16
|
+
return Li(
|
17
|
+
Div(
|
18
|
+
H1(split, cls="split-card-title"),
|
19
|
+
Button(
|
20
|
+
I(cls="fas fa-chevron-down"),
|
21
|
+
hx_get=f"/compare/toggle_accordion?split={split}&metrics={','.join(metrics)}&open=false",
|
22
|
+
hx_target=f"#split-card-{split}",
|
23
|
+
hx_swap="outerHTML",
|
24
|
+
cls="accordion-toggle"
|
25
|
+
),
|
26
|
+
cls="split-card-header"
|
27
|
+
),
|
28
|
+
Div(
|
29
|
+
*[LoadingChart(session, split, metric, type=chart_type, running=running, logscale=logscale) for metric in metrics],
|
30
|
+
cls="multi-charts-container"
|
31
|
+
),
|
32
|
+
cls="split-card",
|
33
|
+
id=f"split-card-{split}",
|
34
|
+
)
|
35
|
+
else:
|
36
|
+
return Li(
|
37
|
+
Div(
|
38
|
+
H1(split, cls=".split-card-title"),
|
39
|
+
Button(
|
40
|
+
I(cls="fas fa-chevron-down"),
|
41
|
+
hx_get=f"/compare/toggle_accordion?split={split}&metrics={','.join(metrics)}&open=true",
|
42
|
+
hx_target=f"#split-card-{split}",
|
43
|
+
hx_swap="outerHTML",
|
44
|
+
cls="accordion-toggle rotated"
|
45
|
+
),
|
46
|
+
|
47
|
+
cls="split-card-header"
|
48
|
+
),
|
49
|
+
cls="split-card closed",
|
50
|
+
id=f"split-card-{split}",
|
51
|
+
)
|
@@ -0,0 +1,20 @@
|
|
1
|
+
from deepboard.gui.utils import get_lines
|
2
|
+
from typing import *
|
3
|
+
|
4
|
+
def make_lines(sockets, split: str, metric: str, runIDs: List[int], type: Literal["step", "duration"]):
|
5
|
+
from __main__ import CONFIG
|
6
|
+
lines = []
|
7
|
+
all_reps = [socket.get_repetitions() for socket in sockets]
|
8
|
+
multi_rep = any(len(rep) > 1 for rep in all_reps)
|
9
|
+
for i, runID in enumerate(runIDs):
|
10
|
+
reps = get_lines(sockets[i], split, metric, key=type)
|
11
|
+
|
12
|
+
for rep_idx, rep in enumerate(reps):
|
13
|
+
lines.append((
|
14
|
+
f'{runID}.{rep_idx}' if multi_rep else f'{runID}',
|
15
|
+
rep["index"],
|
16
|
+
rep["value"],
|
17
|
+
CONFIG.COLORS[i % len(CONFIG.COLORS)],
|
18
|
+
rep["epoch"],
|
19
|
+
))
|
20
|
+
return lines
|
@@ -0,0 +1,58 @@
|
|
1
|
+
from typing import *
|
2
|
+
from fasthtml.common import *
|
3
|
+
from deepboard.gui.components import Legend, ChartType, Smoother, LogSelector
|
4
|
+
from .components import SplitCard, ChartCardList, CompareSetup, Chart
|
5
|
+
from .compare_page import ComparePage
|
6
|
+
|
7
|
+
def build_compare_routes(rt):
|
8
|
+
rt("/compare")(ComparePage)
|
9
|
+
rt("/compare/toggle_accordion")(toggle_accordion)
|
10
|
+
rt("/compare/change_chart")(change_chart_type)
|
11
|
+
rt("/compare/change_scale")(change_chart_scale)
|
12
|
+
rt("/compare/hide_line")(hide_line)
|
13
|
+
rt("/compare/show_line")(show_line)
|
14
|
+
rt("/compare/change_smoother")(change_smoother)
|
15
|
+
rt("/compare/chart")(load_chart)
|
16
|
+
|
17
|
+
# Routes
|
18
|
+
def toggle_accordion(session, split: str, metrics: str, open: bool):
|
19
|
+
if "cards-state" not in session["compare"]:
|
20
|
+
session["compare"]["cards-state"] = {}
|
21
|
+
session["compare"]["cards-state"][split] = open
|
22
|
+
return SplitCard(session, split, metrics=metrics.split(","))
|
23
|
+
|
24
|
+
def change_chart_type(session, runIDs: str, step: bool):
|
25
|
+
new_type = "time" if step else "step"
|
26
|
+
session["compare"]["chart_type"] = new_type
|
27
|
+
return (ChartType(session, path="/compare", session_path="compare", selected_rows_key="compare"), # We want to toggle it
|
28
|
+
ChartCardList(session, swap=True)
|
29
|
+
)
|
30
|
+
|
31
|
+
def change_chart_scale(session, runIDs: str, log: bool):
|
32
|
+
new_scale = "default" if log else "log"
|
33
|
+
session["compare"]["chart_scale"] = new_scale
|
34
|
+
return (LogSelector(session, path="/compare", session_path="compare", selected_rows_key="compare"), # We want to toggle it
|
35
|
+
ChartCardList(session, swap=True)
|
36
|
+
)
|
37
|
+
|
38
|
+
def hide_line(session, runIDs: str, label: str):
|
39
|
+
if 'hidden_lines' not in session["compare"]:
|
40
|
+
session["compare"]['hidden_lines'] = []
|
41
|
+
session["compare"]['hidden_lines'].append(label)
|
42
|
+
return CompareSetup(session, swap=True), ChartCardList(session, swap=True)
|
43
|
+
|
44
|
+
|
45
|
+
def show_line(session, runIDs: str, label: str):
|
46
|
+
if 'hidden_lines' not in session["compare"]:
|
47
|
+
session["compare"]['hidden_lines'] = []
|
48
|
+
if label in session["compare"]['hidden_lines']:
|
49
|
+
session["compare"]['hidden_lines'].remove(label)
|
50
|
+
|
51
|
+
return CompareSetup(session, swap=True), ChartCardList(session, swap=True)
|
52
|
+
|
53
|
+
def change_smoother(session, runIDs: str, smoother: int):
|
54
|
+
session["compare"]["smoother_value"] = smoother
|
55
|
+
return CompareSetup(session, swap=True), ChartCardList(session, swap=True)
|
56
|
+
|
57
|
+
def load_chart(session, split: str, metric: str, type: str, running: bool, logscale: bool = False):
|
58
|
+
return Chart(session, split, metric, type, running, logscale=logscale)
|
@@ -0,0 +1,21 @@
|
|
1
|
+
from typing import *
|
2
|
+
from fasthtml.common import *
|
3
|
+
|
4
|
+
def CompareButton(session, swap: bool = False):
|
5
|
+
show = "datagrid" in session and "selected-rows" in session["datagrid"] and len(session["datagrid"]["selected-rows"]) > 1
|
6
|
+
run_ids = session["datagrid"].get("selected-rows") or []
|
7
|
+
run_ids_formatted = ','.join([str(i) for i in run_ids])
|
8
|
+
return Div(
|
9
|
+
Button(
|
10
|
+
"Compare",
|
11
|
+
cls="compare-button",
|
12
|
+
style="display: block;" if show else "display: none;",
|
13
|
+
# onclick=f"window.open('{url}', '_blank')",
|
14
|
+
hx_get=f"/compare_action?run_ids={run_ids_formatted}",
|
15
|
+
hx_target="#container",
|
16
|
+
data_new_tab="true"
|
17
|
+
),
|
18
|
+
cls="compare-button-container",
|
19
|
+
id="compare-button-container",
|
20
|
+
hx_swap_oob="true" if swap else "false",
|
21
|
+
)
|
@@ -0,0 +1,67 @@
|
|
1
|
+
from typing import *
|
2
|
+
from fasthtml.common import *
|
3
|
+
from .row import Row
|
4
|
+
from .header import Header, HeaderRename
|
5
|
+
|
6
|
+
def DataGrid(session, rename_col: str = None, wrapincontainer: bool = False, fullscreen: bool = False):
|
7
|
+
"""
|
8
|
+
Note that fullscreen only work if the container is requested because it applies on the container
|
9
|
+
"""
|
10
|
+
from __main__ import rTable, CONFIG
|
11
|
+
|
12
|
+
if "datagrid" not in session:
|
13
|
+
session["datagrid"] = dict()
|
14
|
+
show_hidden = session.get("show_hidden", False)
|
15
|
+
rows_selected = session["datagrid"].get("selected-rows") or []
|
16
|
+
sort_by: Optional[str] = session["datagrid"].get("sort_by", None)
|
17
|
+
sort_order: Optional[str] = session["datagrid"].get("sort_order", None)
|
18
|
+
columns, col_ids, data = rTable.get_results(show_hidden=show_hidden)
|
19
|
+
# If the columns to sort by is hidden, we reset it
|
20
|
+
if sort_by is not None and sort_by not in col_ids:
|
21
|
+
session["datagrid"]["sort_by"] = sort_by = None
|
22
|
+
session["datagrid"]["sort_order"] = sort_order = None
|
23
|
+
|
24
|
+
if sort_by is not None and sort_order is not None:
|
25
|
+
data = sorted(
|
26
|
+
data,
|
27
|
+
key=lambda x: (
|
28
|
+
x[col_ids.index(sort_by)] is None, # True = 1, False = 0 — Nones last
|
29
|
+
x[col_ids.index(sort_by)]
|
30
|
+
),
|
31
|
+
reverse=(sort_order == "desc")
|
32
|
+
)
|
33
|
+
|
34
|
+
run_ids = [row[col_ids.index("run_id")] for row in data]
|
35
|
+
rows_hidden = rTable.get_hidden_runs() if show_hidden else []
|
36
|
+
table = Table(
|
37
|
+
# We put the headers in a form so that we can sort them using htmx
|
38
|
+
Thead(
|
39
|
+
Tr(
|
40
|
+
*[
|
41
|
+
HeaderRename(col_name, col_id) if col_id == rename_col else Header(
|
42
|
+
col_name,
|
43
|
+
col_id,
|
44
|
+
sort_order if col_id == sort_by else None)
|
45
|
+
for col_name, col_id in zip(columns, col_ids)],
|
46
|
+
id="column-header-row"
|
47
|
+
)
|
48
|
+
),
|
49
|
+
Tbody(
|
50
|
+
*[Row(row,
|
51
|
+
run_id,
|
52
|
+
max_decimals=CONFIG.MAX_DEC,
|
53
|
+
selected=run_id in rows_selected,
|
54
|
+
hidden=run_id in rows_hidden,
|
55
|
+
fullscreen=fullscreen) for row, run_id in zip(data, run_ids)],
|
56
|
+
),
|
57
|
+
cls="data-grid"
|
58
|
+
),
|
59
|
+
|
60
|
+
if wrapincontainer:
|
61
|
+
return Div(
|
62
|
+
table,
|
63
|
+
cls="scroll-container" if not fullscreen else "scroll-container fullscreen",
|
64
|
+
id="experiment-table",
|
65
|
+
),
|
66
|
+
else:
|
67
|
+
return table
|
@@ -0,0 +1,54 @@
|
|
1
|
+
from typing import *
|
2
|
+
from fasthtml.common import *
|
3
|
+
|
4
|
+
def right_click_handler(elementIds: List[str], top: int, left: int):
|
5
|
+
from __main__ import rTable
|
6
|
+
elementId = [elem for elem in elementIds if elem.startswith("grid-header-")][0]
|
7
|
+
clicked_col = elementId.replace("grid-header-", "")
|
8
|
+
hidden_columns = [(key, alias) for key, (order, alias) in rTable.result_columns.items() if order is None]
|
9
|
+
return Div(
|
10
|
+
Ul(
|
11
|
+
Li('Hide', hx_get=f"/hide?col={clicked_col}", hx_target='#experiment-table', hx_swap="innerHTML", cls="menu-item"),
|
12
|
+
Li('Rename', hx_get=f'/rename_col_datagrid?col={clicked_col}', hx_target='#experiment-table', hx_swap="innerHTML", cls="menu-item"),
|
13
|
+
Li(
|
14
|
+
Div(A('Add', href="#", cls="has-submenu"), Span("►"),
|
15
|
+
style="display: flex; flex-direction: row; justify-content: space-between;"),
|
16
|
+
Ul(
|
17
|
+
*[Li(alias, hx_get=f"/show?col={col_name}&after={clicked_col}", hx_target='#experiment-table', hx_swap="innerHTML", cls="menu-item")
|
18
|
+
for col_name, alias in hidden_columns],
|
19
|
+
cls="submenu"
|
20
|
+
),
|
21
|
+
cls="menu-item has-submenu-wrapper"
|
22
|
+
),
|
23
|
+
cls='dropdown-menu'
|
24
|
+
),
|
25
|
+
id='custom-menu',
|
26
|
+
style=f'visibility: visible; top: {top}px; left: {left}px;',
|
27
|
+
)
|
28
|
+
def right_click_handler_row(session, elementIds: List[str], top: int, left: int):
|
29
|
+
from __main__ import rTable
|
30
|
+
elementId = [elem for elem in elementIds if elem.startswith("grid-row-")][0]
|
31
|
+
clicked_row = int(elementId.replace("grid-row-", ""))
|
32
|
+
hidden_runs = rTable.get_hidden_runs()
|
33
|
+
if clicked_row in hidden_runs:
|
34
|
+
hideshow_button = Li('Show', hx_get=f"/show_run?run_id={clicked_row}", hx_target='#experiment-table',
|
35
|
+
hx_swap="innerHTML", cls="menu-item")
|
36
|
+
else:
|
37
|
+
hideshow_button = Li('Hide', hx_get=f"/hide_run?run_id={clicked_row}", hx_target='#experiment-table',
|
38
|
+
hx_swap="innerHTML",cls="menu-item")
|
39
|
+
print(session)
|
40
|
+
if session.get("show_hidden", False):
|
41
|
+
toggle_visibility_button = Li('Hide Hidden', hx_get=f"/hide_hidden", hx_target='#experiment-table',
|
42
|
+
hx_swap="innerHTML", cls="menu-item")
|
43
|
+
else:
|
44
|
+
toggle_visibility_button = Li('Show Hidden', hx_get=f"/show_hidden", hx_target='#experiment-table',
|
45
|
+
hx_swap="innerHTML", cls="menu-item")
|
46
|
+
return Div(
|
47
|
+
Ul(
|
48
|
+
hideshow_button,
|
49
|
+
toggle_visibility_button,
|
50
|
+
cls='dropdown-menu'
|
51
|
+
),
|
52
|
+
id='custom-menu',
|
53
|
+
style=f'visibility: visible; top: {top}px; left: {left}px;',
|
54
|
+
)
|
@@ -0,0 +1,43 @@
|
|
1
|
+
from typing import *
|
2
|
+
from fasthtml.common import *
|
3
|
+
|
4
|
+
def Header(name: str, col_id: str, sort_dir: str = None):
|
5
|
+
if sort_dir == "asc":
|
6
|
+
return Th(
|
7
|
+
Div(name, Span("↑", cls='sort-icon'), hx_get=f'/sort?by={col_id}&order=desc', target_id='experiment-table',
|
8
|
+
hx_swap='innerHTML',
|
9
|
+
cls='sortable',
|
10
|
+
id=f"grid-header-{col_id}"
|
11
|
+
),
|
12
|
+
data_col=col_id
|
13
|
+
),
|
14
|
+
elif sort_dir == "desc":
|
15
|
+
return Th(
|
16
|
+
Div(name, Span("↓", cls='sort-icon'), hx_get=f'/sort?by={col_id}&order=', target_id='experiment-table',
|
17
|
+
hx_swap='innerHTML',
|
18
|
+
cls='sortable',
|
19
|
+
id=f"grid-header-{col_id}"),
|
20
|
+
data_col=col_id
|
21
|
+
),
|
22
|
+
else:
|
23
|
+
return Th(
|
24
|
+
Div(name, Span('⇅', cls='sort-icon'), hx_get=f'/sort?by={col_id}&order=asc', target_id='experiment-table',
|
25
|
+
hx_swap='innerHTML',
|
26
|
+
cls='sortable',
|
27
|
+
id=f"grid-header-{col_id}"),
|
28
|
+
data_col=col_id
|
29
|
+
),
|
30
|
+
|
31
|
+
def HeaderRename(name: str, col_id: str):
|
32
|
+
return Th(
|
33
|
+
Input(
|
34
|
+
type="text",
|
35
|
+
value=name,
|
36
|
+
name="new_name",
|
37
|
+
hx_post=f'/rename_col?col_id={col_id}',
|
38
|
+
hx_target='#experiment-table',
|
39
|
+
hx_swap='innerHTML',
|
40
|
+
id=f"grid-header-{col_id}",
|
41
|
+
cls="rename-input"
|
42
|
+
)
|
43
|
+
),
|