Skip to content

Commit 2bd5f33

Browse files
committed
Add hardcoded gantt plot for problems in summary tab
1 parent 0240945 commit 2bd5f33

2 files changed

Lines changed: 224 additions & 49 deletions

File tree

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
import React from "react";
2+
3+
import ReactECharts from "echarts-for-react";
4+
import * as echarts from "echarts";
5+
6+
const ProblemGanttPlot = () => {
7+
const problems = [
8+
"Problem 1",
9+
"Problem 2",
10+
"Problem 3",
11+
"Problem 4",
12+
"Problem 5",
13+
"Problem 6",
14+
"Problem 7",
15+
"Problem 8a",
16+
"Problem 8b",
17+
"Problem 8c",
18+
"Problem 9",
19+
"Problem 10",
20+
"Problem 11",
21+
"Problem 12",
22+
];
23+
24+
const timelineData = [
25+
{
26+
problemIndex: 0,
27+
startTime: "2026-04-14T10:00:00Z",
28+
endTime: "2026-04-14T10:00:15Z",
29+
label: "Session 1",
30+
},
31+
{
32+
problemIndex: 0,
33+
startTime: "2026-04-14T10:00:25Z",
34+
endTime: "2026-04-14T10:00:45Z",
35+
label: "Session 2",
36+
},
37+
{
38+
problemIndex: 1,
39+
startTime: "2026-04-14T10:00:05Z",
40+
endTime: "2026-04-14T10:00:30Z",
41+
label: "Session 3",
42+
},
43+
{
44+
problemIndex: 7,
45+
startTime: "2026-04-14T10:01:00Z",
46+
endTime: "2026-04-14T10:01:20Z",
47+
label: "Session 4",
48+
},
49+
{
50+
problemIndex: 8,
51+
startTime: "2026-04-14T10:01:05Z",
52+
endTime: "2026-04-14T10:01:40Z",
53+
label: "Session 5",
54+
},
55+
{
56+
problemIndex: 9,
57+
startTime: "2026-04-14T10:01:15Z",
58+
endTime: "2026-04-14T10:01:55Z",
59+
label: "Session 6",
60+
},
61+
];
62+
63+
// Map to ECharts internal format
64+
const data = timelineData.map((item) => ({
65+
name: item.label,
66+
value: [
67+
item.problemIndex,
68+
new Date(item.startTime).getTime(),
69+
new Date(item.endTime).getTime(),
70+
],
71+
itemStyle: { color: "#5470c6" },
72+
}));
73+
74+
const option = {
75+
tooltip: {
76+
formatter: (params) => {
77+
// value[1] is start, value[2] is end
78+
const start = echarts.time.format(
79+
params.value[1],
80+
"{HH}:{mm}:{ss}",
81+
false,
82+
);
83+
const end = echarts.time.format(
84+
params.value[2],
85+
"{HH}:{mm}:{ss}",
86+
false,
87+
);
88+
return `${params.name}<br/>${start} - ${end}`;
89+
},
90+
},
91+
title: { text: "Problem Timeline", left: "center" },
92+
// Enable zooming and panning for high-frequency data
93+
dataZoom: [
94+
{
95+
type: "slider",
96+
filterMode: "weakFilter",
97+
showDataShadow: false,
98+
bottom: 10,
99+
},
100+
],
101+
grid: {
102+
top: 80, // Space for the title and top X-axis
103+
left: 100, // Space for problem labels
104+
right: 50, // Padding on the right
105+
bottom: 80, // This creates the gap where the slider lives
106+
},
107+
xAxis: {
108+
type: "time",
109+
position: "top",
110+
splitLine: { show: true },
111+
axisLabel: { formatter: "{HH}:{mm}:{ss}" },
112+
},
113+
yAxis: {
114+
data: problems,
115+
splitLine: { show: true },
116+
axisLabel: {
117+
interval: 0, // Force show ALL problems
118+
},
119+
},
120+
series: [
121+
{
122+
type: "custom",
123+
renderItem: (params, api) => {
124+
const categoryIndex = api.value(0);
125+
const start = api.coord([api.value(1), categoryIndex]);
126+
const end = api.coord([api.value(2), categoryIndex]);
127+
const height = api.size([0, 1])[1] * 0.6; // Bar height is 60% of row height
128+
129+
const rectShape = echarts.graphic.clipRectByRect(
130+
{
131+
x: start[0],
132+
y: start[1] - height / 2,
133+
width: end[0] - start[0],
134+
height: height,
135+
},
136+
{
137+
x: params.coordSys.x,
138+
y: params.coordSys.y,
139+
width: params.coordSys.width,
140+
height: params.coordSys.height,
141+
},
142+
);
143+
144+
return (
145+
rectShape && {
146+
type: "rect",
147+
transition: ["shape"],
148+
shape: rectShape,
149+
style: {
150+
fill: api.visual("color"), // Gets the color from the itemStyle in your data
151+
opacity: 0.8,
152+
},
153+
}
154+
);
155+
},
156+
itemStyle: { opacity: 0.8 },
157+
encode: { x: [1, 2], y: 0 },
158+
data: data,
159+
},
160+
],
161+
};
162+
163+
return <ReactECharts option={option} />;
164+
};
165+
166+
export default ProblemGanttPlot;

src/snapshots-app/client/bundles/components/submission/tabs/summary/SummaryTab.jsx

Lines changed: 58 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,20 @@ import {
2323
import { useParams } from "react-router";
2424

2525
import StatisticsDashboard from "./StatisticsDashboard";
26+
import ProblemGanttPlot from "./ProblemGanttPlot";
27+
// import ProblemTimeline from "./ProblemTimeline";
28+
// import GanttPlot from "./GanttPlot";
2629
import InfoTooltip from "../../../common/InfoTooltip";
2730

2831
// TODO: move graphs from Submission Layout into here
2932
// TODO: lines added/removed rich git diff chart like encourse
3033

3134
// TODO: problem summaries [subtasks]
32-
// TODO: duration plots for problems (see slack)
3335
// TODO: number of backups for each problem
3436
// TODO: plot time spent on unlocking vs correctness tests for each problem
3537

3638
// TODO: radar plot
3739

38-
3940
// TODO: don't hardcode these options for just ants
4041
const SCORE_HISTOGRAM_OPTIONS = {
4142
histogram: {
@@ -207,55 +208,63 @@ function SummaryTab({}) {
207208
</div>
208209

209210
{menuItems.length > 0 ? (
210-
// TODO: generalize this left sidebar + main area into a component
211-
<Box sx={{ display: "flex", gap: 3, minHeight: "80vh" }}>
212-
{/* Left Sidebar */}
213-
<Paper
214-
elevation={2}
215-
sx={{
216-
width: 240,
217-
flexShrink: 0,
218-
borderRadius: 2,
219-
overflow: "hidden",
220-
}}
221-
>
222-
<List>
223-
{menuItems.map((item, index) => (
224-
<ListItem key={item.text} disablePadding>
225-
<ListItemButton
226-
selected={activeIndex === index}
227-
onClick={() => setActiveIndex(index)}
228-
>
229-
<ListItemIcon>{item.icon}</ListItemIcon>
230-
<ListItemText primary={item.text} />
231-
232-
<Box
233-
onClick={(e) => e.stopPropagation()}
234-
sx={{ ml: "auto", display: "flex", alignItems: "center" }}
211+
<>
212+
{/* TODO: generalize this left sidebar + main area into a component */}
213+
<Box sx={{ display: "flex", gap: 3, minHeight: "80vh" }}>
214+
{/* Left Sidebar */}
215+
<Paper
216+
elevation={2}
217+
sx={{
218+
width: 240,
219+
flexShrink: 0,
220+
borderRadius: 2,
221+
overflow: "hidden",
222+
}}
223+
>
224+
<List>
225+
{menuItems.map((item, index) => (
226+
<ListItem key={item.text} disablePadding>
227+
<ListItemButton
228+
selected={activeIndex === index}
229+
onClick={() => setActiveIndex(index)}
235230
>
236-
{item.tooltip ? (
237-
<InfoTooltip info={item.tooltip} />
238-
) : null}
239-
</Box>
240-
</ListItemButton>
241-
</ListItem>
242-
))}
243-
</List>
244-
</Paper>
231+
<ListItemIcon>{item.icon}</ListItemIcon>
232+
<ListItemText primary={item.text} />
233+
234+
<Box
235+
onClick={(e) => e.stopPropagation()}
236+
sx={{
237+
ml: "auto",
238+
display: "flex",
239+
alignItems: "center",
240+
}}
241+
>
242+
{item.tooltip ? (
243+
<InfoTooltip info={item.tooltip} />
244+
) : null}
245+
</Box>
246+
</ListItemButton>
247+
</ListItem>
248+
))}
249+
</List>
250+
</Paper>
251+
252+
{/* Main Content Area */}
253+
<Paper
254+
elevation={1}
255+
sx={{
256+
flexGrow: 1,
257+
p: 4,
258+
borderRadius: 2,
259+
backgroundColor: "#fafafa",
260+
}}
261+
>
262+
{menuItems[activeIndex].component}
263+
</Paper>
264+
</Box>
245265

246-
{/* Main Content Area */}
247-
<Paper
248-
elevation={1}
249-
sx={{
250-
flexGrow: 1,
251-
p: 4,
252-
borderRadius: 2,
253-
backgroundColor: "#fafafa",
254-
}}
255-
>
256-
{menuItems[activeIndex].component}
257-
</Paper>
258-
</Box>
266+
<ProblemGanttPlot />
267+
</>
259268
) : (
260269
<CircularProgress />
261270
)}

0 commit comments

Comments
 (0)