YeCanming commited on
Commit
f598a7d
·
1 Parent(s): 1e63386

feat: 初步实现

Browse files
Files changed (2) hide show
  1. src/streamlit_app.py +227 -77
  2. src/test.py +37 -0
src/streamlit_app.py CHANGED
@@ -1,12 +1,15 @@
1
  import streamlit as st
2
  from pathlib import Path
3
- import altair as alt
 
 
 
 
4
 
5
  # 导入重构后的模块
6
- # 这些文件需要您在与此 app.py 同级的目录下或Python路径中创建
7
  try:
8
  from utils import DATA_ROOT_PATH, AppMode
9
- from data_models import Study, Trial
10
  from data_loader import discover_studies_cached, ensure_data_directory_exists
11
  except ImportError as e:
12
  st.error(
@@ -20,10 +23,22 @@ if "selected_study_name" not in st.session_state:
20
  st.session_state.selected_study_name = None
21
  if "selected_trial_name" not in st.session_state:
22
  st.session_state.selected_trial_name = None
23
- if "studies_data" not in st.session_state: # To store loaded Study objects
24
- st.session_state.studies_data = {}
25
  if "app_mode" not in st.session_state:
26
- st.session_state.app_mode = AppMode.VIEWING # Default mode
 
 
 
 
 
 
 
 
 
 
 
 
27
 
28
  # --- Page Configuration ---
29
  st.set_page_config(layout="wide", page_title="柳暗花明 (flowillower)")
@@ -37,20 +52,15 @@ with header_cols[0]:
37
  st.markdown("## 柳暗花明")
38
  st.caption("flowillower")
39
 
40
- # 确保数据目录存在
41
  ensure_data_directory_exists(DATA_ROOT_PATH)
42
-
43
- # 加载 Studies
44
- all_study_objects = discover_studies_cached(DATA_ROOT_PATH) # Returns Dict[str, Study]
45
  study_names = list(all_study_objects.keys())
46
 
47
  if not study_names:
48
  st.warning(f"在 {DATA_ROOT_PATH} 未找到任何 Study。请确保您的数据结构正确或使用 flowillower API 开始记录实验。")
49
 
50
- # Study 选择
51
  if study_names:
52
  with header_cols[1]:
53
- # 如果 session_state 中的 study_name 不在当前发现的 study_names 中,重置它
54
  if st.session_state.selected_study_name not in study_names:
55
  st.session_state.selected_study_name = study_names[0] if study_names else None
56
 
@@ -59,15 +69,14 @@ if study_names:
59
  study_names,
60
  index=study_names.index(st.session_state.selected_study_name) if st.session_state.selected_study_name in study_names else 0,
61
  label_visibility="collapsed",
62
- key="study_selector_ui" # Use a different key to avoid conflict if direct assignment is used
63
  )
64
- # Update session state if selection changes
65
  if selected_study_name_from_ui != st.session_state.selected_study_name:
66
  st.session_state.selected_study_name = selected_study_name_from_ui
67
- st.session_state.selected_trial_name = None # Reset trial when study changes
 
68
  st.rerun()
69
 
70
-
71
  with header_cols[2]:
72
  if st.session_state.selected_study_name:
73
  st.write(f"当前 Study: **{st.session_state.selected_study_name}**")
@@ -75,53 +84,35 @@ else:
75
  with header_cols[1]:
76
  st.info("没有可用的 Study。")
77
 
78
-
79
- # Placeholder for right-side icons
80
- with header_cols[3]:
81
- st.button("➕", help="添加 (Add)", disabled=True)
82
- with header_cols[4]:
83
- st.button("⚙️", help="设置 (Settings)", disabled=True)
84
- with header_cols[5]:
85
- st.button("👤", help="用户 (User)", disabled=True)
86
-
87
  st.markdown("---")
88
 
89
-
90
  # --- Sidebar ---
91
  current_study: Study | None = None
92
  if st.session_state.selected_study_name and st.session_state.selected_study_name in all_study_objects:
93
  current_study = all_study_objects[st.session_state.selected_study_name]
94
- # Ensure trials are discovered for the current study
95
- if not current_study.trials: # Discover trials if not already done
96
  current_study.discover_trials_cached()
97
 
98
-
99
- trial_names = []
100
- if current_study:
101
- trial_names = list(current_study.trials.keys())
102
-
103
 
104
  with st.sidebar:
105
  st.markdown("### Study")
106
  if current_study:
107
  st.markdown(f"##### {current_study.name}")
108
  if st.button("刷新 Study 数据 (Refresh Study Data)", use_container_width=True):
109
- current_study.clear_cache() # Clear specific study cache
110
  st.rerun()
111
-
112
- # These buttons can be linked to specific views or functionalities later
113
- if st.button("概览 (Overview)", use_container_width=True, disabled=True):
114
- st.toast("功能待实现 (Feature to be implemented)")
115
- if st.button("图表对比视图 (Chart Comparison View)", use_container_width=True, disabled=True):
116
- st.toast("功能待实现 (Feature to be implemented)")
117
  else:
118
- st.markdown("未选择 Study (No Study Selected)")
119
 
120
  st.markdown("---")
121
  st.markdown("### Trial")
122
-
123
  if current_study and trial_names:
124
- # 如果 session_state 中的 trial_name 不在当前发现的 trial_names 中,重置它
125
  if st.session_state.selected_trial_name not in trial_names:
126
  st.session_state.selected_trial_name = trial_names[0] if trial_names else None
127
 
@@ -130,34 +121,28 @@ with st.sidebar:
130
  trial_names,
131
  index=trial_names.index(st.session_state.selected_trial_name) if st.session_state.selected_trial_name in trial_names else 0,
132
  label_visibility="collapsed",
133
- key="trial_selector_ui"
134
  )
135
  if selected_trial_name_from_ui != st.session_state.selected_trial_name:
136
  st.session_state.selected_trial_name = selected_trial_name_from_ui
 
137
  st.rerun()
138
-
139
  if st.session_state.selected_trial_name:
140
  st.markdown(f"当前选择: **{st.session_state.selected_trial_name}**")
141
-
142
  elif current_study:
143
  st.info(f"Study '{current_study.name}' 中没有 Trial。")
144
  else:
145
  st.info("请先选择一个 Study。")
146
-
147
  st.markdown("---")
148
- if st.button("⚙️ App 设置 (App Settings)", use_container_width=True, disabled=True):
149
- st.toast("功能待实现 (Feature to be implemented)")
150
-
151
 
152
  # --- Main Content Area ---
153
  current_trial: Trial | None = None
154
  if current_study and st.session_state.selected_trial_name and st.session_state.selected_trial_name in current_study.trials:
155
  current_trial = current_study.trials[st.session_state.selected_trial_name]
156
- # Load data for the current trial if not already loaded (methods are cached)
157
  current_trial.load_input_variables_cached()
158
  current_trial.load_metrics_cached()
159
 
160
-
161
  if current_study and current_trial:
162
  main_title_cols = st.columns([3,1, 0.5])
163
  with main_title_cols[0]:
@@ -167,8 +152,96 @@ if current_study and current_trial:
167
  if st.button("刷新 Trial 数据 (Refresh Trial Data)", type="secondary"):
168
  current_trial.clear_cache()
169
  st.rerun()
170
- with main_title_cols[2]:
171
- st.button("...", help="更多选项 (More Options)", disabled=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
172
 
173
  tab_titles = ["图表 (Charts)", "参数 (Parameters)", "系统 (System)", "日志 (Logs)", "环境 (Environment)"]
174
  tab_charts, tab_params, tab_system, tab_logs, tab_env = st.tabs(tab_titles)
@@ -178,11 +251,14 @@ if current_study and current_trial:
178
  st.markdown("---")
179
 
180
  if not current_trial.metrics_data:
181
- st.info("当前 Trial 没有可显示的指标数据。请检查 `logs/scalar` 文件夹和 TOML 文件。")
182
  else:
183
  num_metrics = len(current_trial.metrics_data)
184
- cols_per_row = st.slider("每行图表数量 (Charts per row)", 1, 4, min(2, num_metrics) if num_metrics > 0 else 1, key=f"cols_slider_{current_trial.name}")
185
-
 
 
 
186
  metric_names = sorted(list(current_trial.metrics_data.keys()))
187
 
188
  for i in range(0, num_metrics, cols_per_row):
@@ -198,41 +274,115 @@ if current_study and current_trial:
198
  with st.container(border=True):
199
  st.subheader(metric_name)
200
  try:
201
- chart = alt.Chart(df_metric).mark_line(point=alt.MarkDef(size=20)).encode(
202
- x=alt.X('global_step:Q', title='全局步骤 (Global Step)'),
203
- y=alt.Y('value:Q', title=metric_name, scale=alt.Scale(zero=False)),
204
- color='track:N',
205
- tooltip=['global_step', 'value', 'track']
206
- ).interactive()
207
- st.altair_chart(chart, use_container_width=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
208
  except Exception as e:
209
  st.error(f"为指标 '{metric_name}' 生成图表时出错: {e}")
210
  st.dataframe(df_metric)
 
 
211
 
212
  with tab_params:
213
  st.header("输入参数 (Input Parameters)")
214
- if current_trial.input_variables:
215
- st.json(current_trial.input_variables)
216
- else:
217
- st.info("未找到 `input_variables.toml` 或文件为空。")
218
 
219
- # Placeholder tabs
220
- for tab_content, name in [(tab_system, "系统监控 (System Monitoring)"),
221
- (tab_logs, "日志 (Logs)"),
222
- (tab_env, "环境 (Environment)")]:
223
  with tab_content:
224
  st.header(name)
225
  st.info("此功能待您的 `flowillower` API 提供相关数据后实现。")
226
 
227
  elif not st.session_state.selected_study_name:
228
- st.info("👈 请从顶部选择一个 Study 开始。(Please select a Study from the top to begin.)")
229
  elif not st.session_state.selected_trial_name:
230
- st.info("👈 请从侧边栏选择一个 Trial。(Please select a Trial from the sidebar.)")
231
  else:
232
  st.info("请选择 Study 和 Trial 以查看数据。")
233
 
234
-
235
- # --- Footer (Optional) ---
236
  st.markdown("---")
237
- st.caption("柳暗花明 (flowillower) - 数据可视化App (Data Visualization App)")
238
 
 
 
 
 
 
1
  import streamlit as st
2
  from pathlib import Path
3
+ import plotly.graph_objects as go
4
+ import plotly.express as px
5
+ from plotly.subplots import make_subplots
6
+ import pandas as pd
7
+ import time
8
 
9
  # 导入重构后的模块
 
10
  try:
11
  from utils import DATA_ROOT_PATH, AppMode
12
+ from data_models import Study, Trial # Study, Trial will be used
13
  from data_loader import discover_studies_cached, ensure_data_directory_exists
14
  except ImportError as e:
15
  st.error(
 
23
  st.session_state.selected_study_name = None
24
  if "selected_trial_name" not in st.session_state:
25
  st.session_state.selected_trial_name = None
26
+ # if "studies_data" not in st.session_state: # Not directly used, discover_studies_cached returns objects
27
+ # st.session_state.studies_data = {}
28
  if "app_mode" not in st.session_state:
29
+ st.session_state.app_mode = AppMode.VIEWING
30
+
31
+ # 新增: 用于跨图表共享选中的 global_step
32
+ if "shared_selected_global_step" not in st.session_state:
33
+ st.session_state.shared_selected_global_step = None
34
+
35
+ # 新增: 自动播放相关状态
36
+ if "is_auto_playing" not in st.session_state:
37
+ st.session_state.is_auto_playing = False
38
+ if "auto_play_speed" not in st.session_state:
39
+ st.session_state.auto_play_speed = 1.0
40
+ if "auto_play_needs_rerun" not in st.session_state:
41
+ st.session_state.auto_play_needs_rerun = False
42
 
43
  # --- Page Configuration ---
44
  st.set_page_config(layout="wide", page_title="柳暗花明 (flowillower)")
 
52
  st.markdown("## 柳暗花明")
53
  st.caption("flowillower")
54
 
 
55
  ensure_data_directory_exists(DATA_ROOT_PATH)
56
+ all_study_objects = discover_studies_cached(DATA_ROOT_PATH)
 
 
57
  study_names = list(all_study_objects.keys())
58
 
59
  if not study_names:
60
  st.warning(f"在 {DATA_ROOT_PATH} 未找到任何 Study。请确保您的数据结构正确或使用 flowillower API 开始记录实验。")
61
 
 
62
  if study_names:
63
  with header_cols[1]:
 
64
  if st.session_state.selected_study_name not in study_names:
65
  st.session_state.selected_study_name = study_names[0] if study_names else None
66
 
 
69
  study_names,
70
  index=study_names.index(st.session_state.selected_study_name) if st.session_state.selected_study_name in study_names else 0,
71
  label_visibility="collapsed",
72
+ key="study_selector_main_ui"
73
  )
 
74
  if selected_study_name_from_ui != st.session_state.selected_study_name:
75
  st.session_state.selected_study_name = selected_study_name_from_ui
76
+ st.session_state.selected_trial_name = None
77
+ st.session_state.shared_selected_global_step = None # Study 变化时清除高亮
78
  st.rerun()
79
 
 
80
  with header_cols[2]:
81
  if st.session_state.selected_study_name:
82
  st.write(f"当前 Study: **{st.session_state.selected_study_name}**")
 
84
  with header_cols[1]:
85
  st.info("没有可用的 Study。")
86
 
87
+ with header_cols[3]: st.button("➕", help="添加 (Add)", disabled=True)
88
+ with header_cols[4]: st.button("⚙️", help="设置 (Settings)", disabled=True)
89
+ with header_cols[5]: st.button("👤", help="用户 (User)", disabled=True)
 
 
 
 
 
 
90
  st.markdown("---")
91
 
 
92
  # --- Sidebar ---
93
  current_study: Study | None = None
94
  if st.session_state.selected_study_name and st.session_state.selected_study_name in all_study_objects:
95
  current_study = all_study_objects[st.session_state.selected_study_name]
96
+ if not current_study.trials:
 
97
  current_study.discover_trials_cached()
98
 
99
+ trial_names = list(current_study.trials.keys()) if current_study else []
 
 
 
 
100
 
101
  with st.sidebar:
102
  st.markdown("### Study")
103
  if current_study:
104
  st.markdown(f"##### {current_study.name}")
105
  if st.button("刷新 Study 数据 (Refresh Study Data)", use_container_width=True):
106
+ current_study.clear_cache()
107
  st.rerun()
108
+ if st.button("概览 (Overview)", use_container_width=True, disabled=True): st.toast("功能待实现")
109
+ if st.button("图表对比视图 (Chart Comparison View)", use_container_width=True, disabled=True): st.toast("功能待实现")
 
 
 
 
110
  else:
111
+ st.markdown("未选择 Study")
112
 
113
  st.markdown("---")
114
  st.markdown("### Trial")
 
115
  if current_study and trial_names:
 
116
  if st.session_state.selected_trial_name not in trial_names:
117
  st.session_state.selected_trial_name = trial_names[0] if trial_names else None
118
 
 
121
  trial_names,
122
  index=trial_names.index(st.session_state.selected_trial_name) if st.session_state.selected_trial_name in trial_names else 0,
123
  label_visibility="collapsed",
124
+ key="trial_selector_sidebar_ui"
125
  )
126
  if selected_trial_name_from_ui != st.session_state.selected_trial_name:
127
  st.session_state.selected_trial_name = selected_trial_name_from_ui
128
+ st.session_state.shared_selected_global_step = None # Trial 变化时清除高亮
129
  st.rerun()
 
130
  if st.session_state.selected_trial_name:
131
  st.markdown(f"当前选择: **{st.session_state.selected_trial_name}**")
 
132
  elif current_study:
133
  st.info(f"Study '{current_study.name}' 中没有 Trial。")
134
  else:
135
  st.info("请先选择一个 Study。")
 
136
  st.markdown("---")
137
+ if st.button("⚙️ App 设置 (App Settings)", use_container_width=True, disabled=True): st.toast("功能待实现")
 
 
138
 
139
  # --- Main Content Area ---
140
  current_trial: Trial | None = None
141
  if current_study and st.session_state.selected_trial_name and st.session_state.selected_trial_name in current_study.trials:
142
  current_trial = current_study.trials[st.session_state.selected_trial_name]
 
143
  current_trial.load_input_variables_cached()
144
  current_trial.load_metrics_cached()
145
 
 
146
  if current_study and current_trial:
147
  main_title_cols = st.columns([3,1, 0.5])
148
  with main_title_cols[0]:
 
152
  if st.button("刷新 Trial 数据 (Refresh Trial Data)", type="secondary"):
153
  current_trial.clear_cache()
154
  st.rerun()
155
+ with main_title_cols[2]: st.button("...", help="更多选项 (More Options)", disabled=True)
156
+
157
+ # 添加全局步骤控制器
158
+ if current_trial.metrics_data:
159
+ st.markdown("### 全局步骤控制 (Global Step Control)")
160
+
161
+ # 获取所有指标的全局步骤范围
162
+ all_global_steps = set()
163
+ for metric_name in current_trial.metrics_data.keys():
164
+ df_metric = current_trial.get_metric_dataframe(metric_name)
165
+ if df_metric is not None and not df_metric.empty and 'global_step' in df_metric.columns:
166
+ all_global_steps.update(df_metric['global_step'].tolist())
167
+
168
+ if all_global_steps:
169
+ all_global_steps = sorted(list(all_global_steps))
170
+ min_step, max_step = min(all_global_steps), max(all_global_steps)
171
+
172
+ # 控制器布局
173
+ control_cols = st.columns([3, 1, 1, 1])
174
+
175
+ with control_cols[0]:
176
+ # 滑动条
177
+ if st.session_state.shared_selected_global_step is None:
178
+ st.session_state.shared_selected_global_step = min_step
179
+
180
+ # 确保当前选中的步骤在有效范围内
181
+ if st.session_state.shared_selected_global_step not in all_global_steps:
182
+ # 找到最接近的有效步骤
183
+ closest_step = min(all_global_steps, key=lambda x: abs(x - st.session_state.shared_selected_global_step))
184
+ st.session_state.shared_selected_global_step = closest_step
185
+
186
+ selected_step = st.select_slider(
187
+ "选择全局步骤",
188
+ options=all_global_steps,
189
+ value=st.session_state.shared_selected_global_step,
190
+ format_func=lambda x: f"Step {x}",
191
+ key="global_step_slider"
192
+ )
193
+
194
+ if selected_step != st.session_state.shared_selected_global_step:
195
+ st.session_state.shared_selected_global_step = selected_step
196
+ st.rerun()
197
+
198
+ with control_cols[1]:
199
+ # 播放/暂停按钮
200
+ if st.session_state.is_auto_playing:
201
+ if st.button("⏸️ 暂停", type="primary", use_container_width=True):
202
+ st.session_state.is_auto_playing = False
203
+ st.rerun()
204
+ else:
205
+ if st.button("▶️ 播放", type="primary", use_container_width=True):
206
+ st.session_state.is_auto_playing = True
207
+ st.rerun()
208
+
209
+ with control_cols[2]:
210
+ # 速度控制
211
+ speed = st.selectbox(
212
+ "播放速度",
213
+ options=[0.5, 1.0, 2.0, 4.0],
214
+ index=[0.5, 1.0, 2.0, 4.0].index(st.session_state.auto_play_speed),
215
+ format_func=lambda x: f"{x}x",
216
+ key="speed_selector"
217
+ )
218
+ if speed != st.session_state.auto_play_speed:
219
+ st.session_state.auto_play_speed = speed
220
+
221
+ with control_cols[3]:
222
+ # 重置按钮
223
+ if st.button("🔄 重置", use_container_width=True):
224
+ st.session_state.shared_selected_global_step = min_step
225
+ st.session_state.is_auto_playing = False
226
+ st.rerun()
227
+
228
+ # 自动播放逻辑 - 设置标志但不立即rerun
229
+ if st.session_state.is_auto_playing:
230
+ current_index = all_global_steps.index(st.session_state.shared_selected_global_step)
231
+ if current_index < len(all_global_steps) - 1:
232
+ # 等待指定时间后移动到下一步
233
+ time.sleep(1.0 / st.session_state.auto_play_speed)
234
+ st.session_state.shared_selected_global_step = all_global_steps[current_index + 1]
235
+ st.session_state.auto_play_needs_rerun = True
236
+ else:
237
+ # 到达末尾,停止播放
238
+ st.session_state.is_auto_playing = False
239
+ st.session_state.auto_play_needs_rerun = True
240
+
241
+ # 显示当前步骤信息
242
+ st.info(f"当前选中步骤: **{st.session_state.shared_selected_global_step}** / {max_step}")
243
+
244
+ st.markdown("---")
245
 
246
  tab_titles = ["图表 (Charts)", "参数 (Parameters)", "系统 (System)", "日志 (Logs)", "环境 (Environment)"]
247
  tab_charts, tab_params, tab_system, tab_logs, tab_env = st.tabs(tab_titles)
 
251
  st.markdown("---")
252
 
253
  if not current_trial.metrics_data:
254
+ st.info("当前 Trial 没有可显示的指标数据。")
255
  else:
256
  num_metrics = len(current_trial.metrics_data)
257
+ cols_per_row = st.slider(
258
+ "每行图表数量 (Charts per row)", 1, 4,
259
+ min(2, num_metrics) if num_metrics > 0 else 1,
260
+ key=f"cols_slider_{current_study.name}_{current_trial.name}"
261
+ )
262
  metric_names = sorted(list(current_trial.metrics_data.keys()))
263
 
264
  for i in range(0, num_metrics, cols_per_row):
 
274
  with st.container(border=True):
275
  st.subheader(metric_name)
276
  try:
277
+ # 创建 Plotly 图表
278
+ fig = go.Figure()
279
+
280
+ # 按track分组绘制线条
281
+ if 'track' in df_metric.columns:
282
+ tracks = df_metric['track'].unique()
283
+ colors = px.colors.qualitative.Set1[:len(tracks)]
284
+
285
+ for k, track in enumerate(tracks):
286
+ track_data = df_metric[df_metric['track'] == track]
287
+ fig.add_trace(go.Scatter(
288
+ x=track_data['global_step'],
289
+ y=track_data['value'],
290
+ mode='lines+markers',
291
+ name=track,
292
+ line=dict(color=colors[k % len(colors)]),
293
+ marker=dict(size=6, color=colors[k % len(colors)],
294
+ line=dict(width=1, color='white')),
295
+ customdata=track_data[['global_step', 'value', 'track']],
296
+ hovertemplate='<b>%{fullData.name}</b><br>' +
297
+ 'Global Step: %{x}<br>' +
298
+ 'Value: %{y}<br>' +
299
+ '<extra></extra>'
300
+ ))
301
+ else:
302
+ # 如果没有track列,绘制单条线
303
+ fig.add_trace(go.Scatter(
304
+ x=df_metric['global_step'],
305
+ y=df_metric['value'],
306
+ mode='lines+markers',
307
+ name=metric_name,
308
+ marker=dict(size=6, line=dict(width=1, color='white')),
309
+ customdata=df_metric[['global_step', 'value']],
310
+ hovertemplate='Global Step: %{x}<br>' +
311
+ 'Value: %{y}<br>' +
312
+ '<extra></extra>'
313
+ ))
314
+
315
+ # 如果有共享的选中步骤,添加高亮线
316
+ if st.session_state.shared_selected_global_step is not None:
317
+ fig.add_vline(
318
+ x=st.session_state.shared_selected_global_step,
319
+ line_width=2,
320
+ line_dash="solid",
321
+ line_color="firebrick",
322
+ opacity=0.9
323
+ )
324
+
325
+ # 设置图表布局
326
+ fig.update_layout(
327
+ title=None,
328
+ xaxis_title='全局步骤 (Global Step)',
329
+ yaxis_title=metric_name,
330
+ height=400,
331
+ margin=dict(l=0, r=0, t=0, b=0),
332
+ showlegend=True if 'track' in df_metric.columns and len(df_metric['track'].unique()) > 1 else False,
333
+ hovermode='closest'
334
+ )
335
+
336
+ # 显示图表并处理点击事件
337
+ chart_key = f"chart_metric_{current_study.name}_{current_trial.name}_{metric_name}"
338
+ clicked_points = st.plotly_chart(
339
+ fig,
340
+ use_container_width=True,
341
+ key=chart_key,
342
+ on_select="rerun"
343
+ )
344
+
345
+ # 处理点击事件
346
+ if clicked_points and 'selection' in clicked_points:
347
+ selection = clicked_points['selection']
348
+ if 'points' in selection and len(selection['points']) > 0:
349
+ # 获取第一个点击点的 x 坐标 (global_step)
350
+ clicked_x = selection['points'][0]['x']
351
+ if clicked_x is not None:
352
+ new_step = int(clicked_x)
353
+ if st.session_state.get("shared_selected_global_step") != new_step:
354
+ st.session_state.shared_selected_global_step = new_step
355
+ # 点击图表时停止自动播放
356
+ st.session_state.is_auto_playing = False
357
+ st.rerun()
358
+
359
  except Exception as e:
360
  st.error(f"为指标 '{metric_name}' 生成图表时出错: {e}")
361
  st.dataframe(df_metric)
362
+ # raise e
363
+
364
 
365
  with tab_params:
366
  st.header("输入参数 (Input Parameters)")
367
+ if current_trial.input_variables: st.json(current_trial.input_variables)
368
+ else: st.info("未找到 `input_variables.toml` 或文件为空。")
 
 
369
 
370
+ for tab_content, name in [(tab_system, "系统监控"), (tab_logs, "日志"), (tab_env, "环境")]:
 
 
 
371
  with tab_content:
372
  st.header(name)
373
  st.info("此功能待您的 `flowillower` API 提供相关数据后实现。")
374
 
375
  elif not st.session_state.selected_study_name:
376
+ st.info("👈 请从顶部选择一个 Study 开始。")
377
  elif not st.session_state.selected_trial_name:
378
+ st.info("👈 请从侧边栏选择一个 Trial。")
379
  else:
380
  st.info("请选择 Study 和 Trial 以查看数据。")
381
 
 
 
382
  st.markdown("---")
383
+ st.caption("柳暗花明 (flowillower) - 数据可视化App")
384
 
385
+ # 在页面最后处理自动播放的rerun
386
+ if st.session_state.get("auto_play_needs_rerun", False):
387
+ st.session_state.auto_play_needs_rerun = False
388
+ st.rerun()
src/test.py ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import plotly.graph_objects as go
2
+ from plotly.subplots import make_subplots
3
+ import numpy as np
4
+
5
+ # 创建示例数据
6
+ x = np.linspace(0, 10, 100)
7
+ y1 = np.sin(x)
8
+ y2 = np.cos(x)
9
+
10
+ # 创建两个 FigureWidget 图表
11
+ fig1 = go.FigureWidget(data=[go.Scatter(x=x, y=y1, mode='lines', name='sin(x)')])
12
+ fig2 = go.FigureWidget(data=[go.Scatter(x=x, y=y2, mode='lines', name='cos(x)')])
13
+
14
+ # 定义悬停事件的回调函数
15
+ def hover_fn(trace, points, state):
16
+ if points.xs:
17
+ hover_x = points.xs[0]
18
+ with fig2.batch_update():
19
+ # 在第二个图表上添加垂直线
20
+ fig2.layout.shapes = [dict(
21
+ type='line',
22
+ x0=hover_x,
23
+ x1=hover_x,
24
+ y0=min(y2),
25
+ y1=max(y2),
26
+ line=dict(color='red', dash='dot')
27
+ )]
28
+
29
+ # 为第一个图表的第一个 trace 注册悬停事件
30
+ fig1.data[0].on_hover(hover_fn)
31
+
32
+ # 显示图表
33
+ import streamlit as st
34
+ st.plotly_chart(fig1, use_container_width=True)
35
+ st.plotly_chart(fig2, use_container_width=True)
36
+
37
+ # %%