Upload folder using huggingface_hub
Browse files
app.py
CHANGED
|
@@ -1389,6 +1389,61 @@ def evaluation_tab():
|
|
| 1389 |
], fluid=True, className="pt-3")
|
| 1390 |
|
| 1391 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1392 |
def contact_tab():
|
| 1393 |
return dbc.Container([
|
| 1394 |
html.H3("Research Team", className="mt-3 mb-4"),
|
|
@@ -1748,8 +1803,9 @@ app.layout = dbc.Container([
|
|
| 1748 |
dbc.Tabs([
|
| 1749 |
dbc.Tab(label="Extraction Data", tab_id="tab-proteins"),
|
| 1750 |
dbc.Tab(label="Clustering Explorer",tab_id="tab-clustering"),
|
| 1751 |
-
dbc.Tab(label="Grid Metrics",
|
| 1752 |
-
dbc.Tab(label="
|
|
|
|
| 1753 |
dbc.Tab(label="Extraction Pipeline Instructions", tab_id="tab-pipeline"),
|
| 1754 |
dbc.Tab(label="README", tab_id="tab-readme"),
|
| 1755 |
dbc.Tab(label="Contact", tab_id="tab-contact"),
|
|
@@ -1773,6 +1829,8 @@ def render_tab(tab):
|
|
| 1773 |
return clustering_tab()
|
| 1774 |
elif tab == "tab-grid-metrics":
|
| 1775 |
return grid_metrics_tab()
|
|
|
|
|
|
|
| 1776 |
elif tab == "tab-evaluation":
|
| 1777 |
return evaluation_tab()
|
| 1778 |
elif tab == "tab-pipeline":
|
|
@@ -2734,6 +2792,217 @@ def update_metrics_table(model, min_val, threshold):
|
|
| 2734 |
)
|
| 2735 |
|
| 2736 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2737 |
@app.callback(
|
| 2738 |
Output("metrics-heatmap", "figure"),
|
| 2739 |
Output("metrics-model-compare", "figure"),
|
|
|
|
| 1389 |
], fluid=True, className="pt-3")
|
| 1390 |
|
| 1391 |
|
| 1392 |
+
def protocol_configs_tab():
|
| 1393 |
+
_PC_FIELDS = ["expression_strain", "inducer", "medium_name",
|
| 1394 |
+
"plasmid", "lysis_buffer", "elution_buffer"]
|
| 1395 |
+
_PC_LABELS = {
|
| 1396 |
+
"expression_strain": "Expression host",
|
| 1397 |
+
"inducer": "Inducer",
|
| 1398 |
+
"medium_name": "Growth medium",
|
| 1399 |
+
"plasmid": "Plasmid",
|
| 1400 |
+
"lysis_buffer": "Lysis buffer",
|
| 1401 |
+
"elution_buffer": "Elution buffer",
|
| 1402 |
+
}
|
| 1403 |
+
topn_opts = [{"label": f"Top {n} clusters / field", "value": n}
|
| 1404 |
+
for n in [10, 20, 30, 50]]
|
| 1405 |
+
return dbc.Container([
|
| 1406 |
+
html.H3("Protocol Configurations", className="mt-3 mb-1"),
|
| 1407 |
+
html.P(
|
| 1408 |
+
"Most common multi-field protocol combinations derived from BioSimCSE clusters. "
|
| 1409 |
+
"Each line in the flow diagram represents proteins sharing the same cluster "
|
| 1410 |
+
"assignments across fields.",
|
| 1411 |
+
className="text-muted mb-3",
|
| 1412 |
+
),
|
| 1413 |
+
dbc.Row([
|
| 1414 |
+
make_dropdown("Model", "pc-dd-model", MODELS,
|
| 1415 |
+
value="kamalkraj__BioSimCSE-BioLinkBERT-BASE"
|
| 1416 |
+
if "kamalkraj__BioSimCSE-BioLinkBERT-BASE" in MODELS
|
| 1417 |
+
else (MODELS[0] if MODELS else None)),
|
| 1418 |
+
make_dropdown("Min community size", "pc-dd-min", MINS,
|
| 1419 |
+
value="2" if "2" in MINS else (MINS[0] if MINS else None)),
|
| 1420 |
+
make_dropdown("Threshold", "pc-dd-threshold", THRESHOLDS,
|
| 1421 |
+
value="0.9" if "0.9" in THRESHOLDS else (THRESHOLDS[0] if THRESHOLDS else None)),
|
| 1422 |
+
dbc.Col([
|
| 1423 |
+
html.Label("Clusters per field", className="fw-semibold small mb-1"),
|
| 1424 |
+
dcc.Dropdown(
|
| 1425 |
+
id="pc-dd-topn",
|
| 1426 |
+
options=topn_opts,
|
| 1427 |
+
value=20,
|
| 1428 |
+
clearable=False,
|
| 1429 |
+
style={"fontSize": "13px"},
|
| 1430 |
+
),
|
| 1431 |
+
]),
|
| 1432 |
+
], className="mb-3 g-2"),
|
| 1433 |
+
dcc.Loading(
|
| 1434 |
+
dcc.Graph(id="pc-parcats-graph", style={"height": "560px"}),
|
| 1435 |
+
type="circle", color="#1a73e8",
|
| 1436 |
+
),
|
| 1437 |
+
html.H5("Top configurations", className="mt-4 mb-2"),
|
| 1438 |
+
html.P(
|
| 1439 |
+
"Cluster labels are grouped into top-level categories (BL21, IPTG, LB, pETβ¦) "
|
| 1440 |
+
"to aggregate across fine-grained t=0.9 clusters. % is relative to all proteins with 4 core fields.",
|
| 1441 |
+
className="text-muted small mb-2",
|
| 1442 |
+
),
|
| 1443 |
+
dcc.Loading(html.Div(id="pc-config-table"), type="circle", color="#1a73e8"),
|
| 1444 |
+
], fluid=True, className="pt-3")
|
| 1445 |
+
|
| 1446 |
+
|
| 1447 |
def contact_tab():
|
| 1448 |
return dbc.Container([
|
| 1449 |
html.H3("Research Team", className="mt-3 mb-4"),
|
|
|
|
| 1803 |
dbc.Tabs([
|
| 1804 |
dbc.Tab(label="Extraction Data", tab_id="tab-proteins"),
|
| 1805 |
dbc.Tab(label="Clustering Explorer",tab_id="tab-clustering"),
|
| 1806 |
+
dbc.Tab(label="Grid Metrics", tab_id="tab-grid-metrics"),
|
| 1807 |
+
dbc.Tab(label="Protocol Configurations",tab_id="tab-protocol-configs"),
|
| 1808 |
+
dbc.Tab(label="Evaluation Results", tab_id="tab-evaluation"),
|
| 1809 |
dbc.Tab(label="Extraction Pipeline Instructions", tab_id="tab-pipeline"),
|
| 1810 |
dbc.Tab(label="README", tab_id="tab-readme"),
|
| 1811 |
dbc.Tab(label="Contact", tab_id="tab-contact"),
|
|
|
|
| 1829 |
return clustering_tab()
|
| 1830 |
elif tab == "tab-grid-metrics":
|
| 1831 |
return grid_metrics_tab()
|
| 1832 |
+
elif tab == "tab-protocol-configs":
|
| 1833 |
+
return protocol_configs_tab()
|
| 1834 |
elif tab == "tab-evaluation":
|
| 1835 |
return evaluation_tab()
|
| 1836 |
elif tab == "tab-pipeline":
|
|
|
|
| 2792 |
)
|
| 2793 |
|
| 2794 |
|
| 2795 |
+
_PC_PROTOCOL_FIELDS = ["expression_strain", "inducer", "medium_name",
|
| 2796 |
+
"plasmid", "lysis_buffer", "elution_buffer"]
|
| 2797 |
+
_PC_FIELD_LABELS = {
|
| 2798 |
+
"expression_strain": "Expression host",
|
| 2799 |
+
"inducer": "Inducer",
|
| 2800 |
+
"medium_name": "Growth medium",
|
| 2801 |
+
"plasmid": "Plasmid",
|
| 2802 |
+
"lysis_buffer": "Lysis buffer",
|
| 2803 |
+
"elution_buffer": "Elution buffer",
|
| 2804 |
+
}
|
| 2805 |
+
_PC_COLORS = [
|
| 2806 |
+
"#1a73e8", "#4db8ff", "#80ccff", "#1aa85c", "#f4a55a",
|
| 2807 |
+
"#e8711a", "#c0392b", "#8e1ae8", "#2ecc71", "#adb5bd",
|
| 2808 |
+
"#dee2e6",
|
| 2809 |
+
]
|
| 2810 |
+
|
| 2811 |
+
|
| 2812 |
+
def _pc_build_wide(all_fields_df, top_n):
|
| 2813 |
+
"""Pivot ALL_FIELDS into wide format, keeping top_n clusters per field."""
|
| 2814 |
+
sub = all_fields_df[all_fields_df["field"].isin(_PC_PROTOCOL_FIELDS)].copy()
|
| 2815 |
+
rows = []
|
| 2816 |
+
for field in _PC_PROTOCOL_FIELDS:
|
| 2817 |
+
fd = sub[sub["field"] == field]
|
| 2818 |
+
clustered = fd[fd["cluster_id"] != -1]
|
| 2819 |
+
# Top N clusters by size
|
| 2820 |
+
top_ids = (
|
| 2821 |
+
clustered.groupby("cluster_id").size()
|
| 2822 |
+
.sort_values(ascending=False)
|
| 2823 |
+
.head(top_n).index
|
| 2824 |
+
)
|
| 2825 |
+
def label_row(r):
|
| 2826 |
+
if r["cluster_id"] == -1:
|
| 2827 |
+
return None
|
| 2828 |
+
if r["cluster_id"] in top_ids:
|
| 2829 |
+
lbl = r["cluster_label_short"]
|
| 2830 |
+
return lbl[:40] + "β¦" if len(lbl) > 40 else lbl
|
| 2831 |
+
return "Other clusters"
|
| 2832 |
+
fd = fd.copy()
|
| 2833 |
+
fd["cat"] = fd.apply(label_row, axis=1)
|
| 2834 |
+
rows.append(fd[["key", "protein_index", "cat"]].rename(columns={"cat": field}))
|
| 2835 |
+
|
| 2836 |
+
wide = rows[0]
|
| 2837 |
+
for r in rows[1:]:
|
| 2838 |
+
wide = wide.merge(r, on=["key", "protein_index"], how="outer")
|
| 2839 |
+
return wide
|
| 2840 |
+
|
| 2841 |
+
|
| 2842 |
+
@app.callback(
|
| 2843 |
+
Output("pc-parcats-graph", "figure"),
|
| 2844 |
+
Output("pc-config-table", "children"),
|
| 2845 |
+
Input("pc-dd-model", "value"),
|
| 2846 |
+
Input("pc-dd-min", "value"),
|
| 2847 |
+
Input("pc-dd-threshold", "value"),
|
| 2848 |
+
Input("pc-dd-topn", "value"),
|
| 2849 |
+
)
|
| 2850 |
+
def update_protocol_configs(model, min_val, threshold, top_n):
|
| 2851 |
+
if not all([model, min_val, threshold, top_n]):
|
| 2852 |
+
empty = go.Figure()
|
| 2853 |
+
empty.update_layout(paper_bgcolor="white",
|
| 2854 |
+
annotations=[dict(text="Select model, min and threshold.",
|
| 2855 |
+
showarrow=False, font=dict(size=14))])
|
| 2856 |
+
return empty, html.Div()
|
| 2857 |
+
|
| 2858 |
+
adf = _load_cluster_csv(model, min_val, threshold)
|
| 2859 |
+
if adf is None:
|
| 2860 |
+
empty = go.Figure()
|
| 2861 |
+
empty.update_layout(paper_bgcolor="white",
|
| 2862 |
+
annotations=[dict(text="Data file not found.",
|
| 2863 |
+
showarrow=False, font=dict(size=14))])
|
| 2864 |
+
return empty, html.Div("Data file not found.", className="text-muted small")
|
| 2865 |
+
|
| 2866 |
+
wide = _pc_build_wide(adf, top_n)
|
| 2867 |
+
core = _PC_PROTOCOL_FIELDS[:4] # host, inducer, medium, plasmid
|
| 2868 |
+
w4 = wide.dropna(subset=core)
|
| 2869 |
+
|
| 2870 |
+
# ββ Parcats figure ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 2871 |
+
dims = []
|
| 2872 |
+
for field in _PC_PROTOCOL_FIELDS:
|
| 2873 |
+
if field not in wide.columns:
|
| 2874 |
+
continue
|
| 2875 |
+
col = w4[field].fillna("N/A") if field in w4.columns else None
|
| 2876 |
+
if col is None:
|
| 2877 |
+
continue
|
| 2878 |
+
from collections import Counter as _Ctr
|
| 2879 |
+
cnt = _Ctr(col.tolist())
|
| 2880 |
+
cat_order = [c for c, _ in cnt.most_common() if c != "N/A"] + ["N/A"]
|
| 2881 |
+
dims.append(go.parcats.Dimension(
|
| 2882 |
+
values=col.tolist(),
|
| 2883 |
+
label=_PC_FIELD_LABELS.get(field, field),
|
| 2884 |
+
categoryorder="array",
|
| 2885 |
+
categoryarray=cat_order,
|
| 2886 |
+
))
|
| 2887 |
+
|
| 2888 |
+
# Color by expression host category index
|
| 2889 |
+
host_col = w4["expression_strain"].fillna("N/A").tolist()
|
| 2890 |
+
all_hosts = list(dict.fromkeys(host_col))
|
| 2891 |
+
host_idx = {h: i for i, h in enumerate(all_hosts)}
|
| 2892 |
+
color_vals = [host_idx.get(h, 0) for h in host_col]
|
| 2893 |
+
n_hosts = max(len(all_hosts), 1)
|
| 2894 |
+
colorscale = [[i / max(n_hosts - 1, 1), _PC_COLORS[i % len(_PC_COLORS)]]
|
| 2895 |
+
for i in range(n_hosts)]
|
| 2896 |
+
|
| 2897 |
+
fig = go.Figure(go.Parcats(
|
| 2898 |
+
dimensions=dims,
|
| 2899 |
+
line=dict(color=color_vals, colorscale=colorscale, shape="hspline"),
|
| 2900 |
+
labelfont=dict(size=12, family="Arial"),
|
| 2901 |
+
tickfont=dict(size=10, family="Arial"),
|
| 2902 |
+
arrangement="freeform",
|
| 2903 |
+
hoverinfo="count+probability",
|
| 2904 |
+
))
|
| 2905 |
+
short_model = model.split("__")[-1] if "__" in model else model
|
| 2906 |
+
fig.update_layout(
|
| 2907 |
+
title=dict(
|
| 2908 |
+
text=f"Protocol configuration flows β {short_model} t={threshold} min={min_val}",
|
| 2909 |
+
font=dict(size=13, family="Arial"), x=0.5,
|
| 2910 |
+
),
|
| 2911 |
+
paper_bgcolor="white",
|
| 2912 |
+
font=dict(family="Arial", size=11),
|
| 2913 |
+
margin=dict(l=60, r=60, t=60, b=40),
|
| 2914 |
+
)
|
| 2915 |
+
|
| 2916 |
+
# ββ Top-config table β category-level grouping ββββββββββββββββββββββββββ
|
| 2917 |
+
# Map fine-grained cluster labels to readable top-level categories so
|
| 2918 |
+
# combinations survive the specificity of t=0.9 clusters.
|
| 2919 |
+
def _grp(field, label):
|
| 2920 |
+
if not label or label == "Other clusters":
|
| 2921 |
+
return None
|
| 2922 |
+
sl = str(label).lower()
|
| 2923 |
+
if field == "expression_strain":
|
| 2924 |
+
if "bl21" in sl: return "BL21(DE3)"
|
| 2925 |
+
if "rosetta" in sl: return "Rosetta"
|
| 2926 |
+
if "c41" in sl or "c43" in sl: return "C41/C43"
|
| 2927 |
+
if "hek" in sl or "cho" in sl or "293" in sl: return "Human/CHO"
|
| 2928 |
+
if "sf9" in sl or "sf21" in sl: return "Insect (Sf9)"
|
| 2929 |
+
if "yeast" in sl or "pichia" in sl: return "Yeast"
|
| 2930 |
+
return "Other E. coli"
|
| 2931 |
+
if field == "inducer":
|
| 2932 |
+
if "not mentioned" in sl: return "IPTG (implied)"
|
| 2933 |
+
if "iptg" in sl: return "IPTG"
|
| 2934 |
+
if "arabinose" in sl: return "Arabinose"
|
| 2935 |
+
return "Other inducer"
|
| 2936 |
+
if field == "medium_name":
|
| 2937 |
+
if re.search(r"\blb\b|luria.bertani|luria broth", sl): return "LB"
|
| 2938 |
+
if "terrific" in sl: return "TB"
|
| 2939 |
+
if "2xyt" in sl or "2x yt" in sl: return "2xYT"
|
| 2940 |
+
if "minimal" in sl or "m9" in sl: return "Minimal"
|
| 2941 |
+
return "Other medium"
|
| 2942 |
+
if field == "plasmid":
|
| 2943 |
+
if re.search(r"\bpet", sl): return "pET"
|
| 2944 |
+
if "pgex" in sl: return "pGEX"
|
| 2945 |
+
if "pqe" in sl: return "pQE"
|
| 2946 |
+
if "pmal" in sl: return "pMAL"
|
| 2947 |
+
return "Other plasmid"
|
| 2948 |
+
if field == "lysis_buffer":
|
| 2949 |
+
if re.search(r"\btris\b", sl): return "Tris"
|
| 2950 |
+
if "pbs" in sl: return "PBS"
|
| 2951 |
+
if "hepes" in sl: return "HEPES"
|
| 2952 |
+
if re.search(r"\bphosphate\b", sl): return "Phosphate"
|
| 2953 |
+
return "Other lysis"
|
| 2954 |
+
if field == "elution_buffer":
|
| 2955 |
+
if "imidazole" in sl: return "Imidazole"
|
| 2956 |
+
if "glutathione" in sl: return "Glutathione"
|
| 2957 |
+
if "maltose" in sl: return "Maltose"
|
| 2958 |
+
return "Other elution"
|
| 2959 |
+
return label
|
| 2960 |
+
|
| 2961 |
+
# Build category table from all clustered entries (not just top-N)
|
| 2962 |
+
# to get meaningful combination counts.
|
| 2963 |
+
sub_all = adf[adf["field"].isin(core) & (adf["cluster_id"] != -1)].copy()
|
| 2964 |
+
sub_all["cat"] = sub_all.apply(
|
| 2965 |
+
lambda r: _grp(r["field"], r["cluster_label_short"]), axis=1
|
| 2966 |
+
)
|
| 2967 |
+
sub_all = sub_all[sub_all["cat"].notna()]
|
| 2968 |
+
wide_cat = sub_all.pivot_table(
|
| 2969 |
+
index=["key", "protein_index"], columns="field",
|
| 2970 |
+
values="cat", aggfunc="first"
|
| 2971 |
+
).reset_index()
|
| 2972 |
+
w4_grp = wide_cat.dropna(subset=core)
|
| 2973 |
+
from collections import Counter as _Ctr
|
| 2974 |
+
combos = _Ctr(tuple(r) for r in w4_grp[core].itertuples(index=False))
|
| 2975 |
+
total = len(w4_grp) # proteins with all 4 fields in a named cluster
|
| 2976 |
+
|
| 2977 |
+
table_rows = []
|
| 2978 |
+
for rank, (combo, cnt) in enumerate(combos.most_common(20), 1):
|
| 2979 |
+
row = {"Rank": rank, "Count": cnt, "%": f"{cnt/total*100:.1f}%"}
|
| 2980 |
+
for field, val in zip(core, combo):
|
| 2981 |
+
row[_PC_FIELD_LABELS[field]] = val
|
| 2982 |
+
table_rows.append(row)
|
| 2983 |
+
|
| 2984 |
+
cols = ["Rank", "Count", "%"] + [_PC_FIELD_LABELS[f] for f in core]
|
| 2985 |
+
table = dash_table.DataTable(
|
| 2986 |
+
columns=[{"name": c, "id": c} for c in cols],
|
| 2987 |
+
data=table_rows,
|
| 2988 |
+
sort_action="native",
|
| 2989 |
+
style_table={"overflowX": "auto"},
|
| 2990 |
+
style_cell={"fontSize": "12px", "padding": "5px 10px", "textAlign": "left",
|
| 2991 |
+
"maxWidth": "220px", "overflow": "hidden", "textOverflow": "ellipsis"},
|
| 2992 |
+
style_header={"fontWeight": "bold", "backgroundColor": "#f8f9fa"},
|
| 2993 |
+
style_data_conditional=[
|
| 2994 |
+
{"if": {"row_index": "odd"}, "backgroundColor": "#f8f9fa"},
|
| 2995 |
+
{"if": {"row_index": 0}, "backgroundColor": "#e8f0fe", "fontWeight": "600"},
|
| 2996 |
+
],
|
| 2997 |
+
tooltip_data=[
|
| 2998 |
+
{c: {"value": str(row.get(c, "")), "type": "markdown"} for c in cols}
|
| 2999 |
+
for row in table_rows
|
| 3000 |
+
],
|
| 3001 |
+
tooltip_duration=None,
|
| 3002 |
+
)
|
| 3003 |
+
return fig, table
|
| 3004 |
+
|
| 3005 |
+
|
| 3006 |
@app.callback(
|
| 3007 |
Output("metrics-heatmap", "figure"),
|
| 3008 |
Output("metrics-model-compare", "figure"),
|