/ concept-collection / benchcompress
concept-collection / benchcompress
improve layout
Jeremy Magland <jmagland@flatironinstitute.org> committed commit 104f44274e47 parent bde0876 Browse files
11 changed files+1024−764
web-ui/public/favicon.svgmodified+13−24View file
@@ -1,26 +1,15 @@
11 <?xml version="1.0" encoding="UTF-8"?>
2-<svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
3- <!-- Background circle -->
4- <circle cx="16" cy="16" r="15" fill="#f0f0f0"/>
5-
6- <!-- Compression arrows -->
7- <g transform="translate(16 16) scale(0.0625)">
8- <!-- Left arrows -->
9- <path d="M-180 -60 L-100 -60 L-100 -90 L-20 -30 L-100 30 L-100 0 L-180 0 Z" fill="#2563eb"/>
10- <!-- Right arrows -->
11- <path d="M180 60 L100 60 L100 90 L20 30 L100 -30 L100 0 L180 0 Z" fill="#2563eb"/>
12-
13- <!-- Central data points/graph -->
14- <g fill="#1e40af">
15- <circle cx="-60" cy="40" r="8"/>
16- <circle cx="-20" cy="-30" r="8"/>
17- <circle cx="20" cy="10" r="8"/>
18- <circle cx="60" cy="-40" r="8"/>
19- <path d="M-60 40 L-20 -30 L20 10 L60 -40"
20- fill="none"
21- stroke="#1e40af"
22- stroke-width="4"
23- stroke-linecap="round"/>
24- </g>
25- </g>
2+<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
3+ <!-- Outer box -->
4+ <rect x="4" y="4" width="24" height="24" stroke="#777777" stroke-width="1.5" fill="none"/>
5+ <!-- Inner box (compressed) -->
6+ <rect x="10" y="10" width="12" height="12" stroke="#2c2c2c" stroke-width="1.5" stroke-dasharray="2" fill="none"/>
7+ <!-- Left arrow (pointing right) -->
8+ <path d="M7 16 L13 16 L10 13 M13 16 L10 19" stroke="#2c2c2c" stroke-width="2" fill="none"/>
9+ <!-- Right arrow (pointing left) -->
10+ <path d="M25 16 L19 16 L22 13 M19 16 L22 19" stroke="#2c2c2c" stroke-width="2" fill="none"/>
11+ <!-- Top arrow (pointing down) -->
12+ <path d="M16 7 L16 13 L13 10 M16 13 L19 10" stroke="#2c2c2c" stroke-width="2" fill="none"/>
13+ <!-- Bottom arrow (pointing up) -->
14+ <path d="M16 25 L16 19 L13 22 M16 19 L19 22" stroke="#2c2c2c" stroke-width="2" fill="none"/>
2615 </svg>
web-ui/public/logo.svgmodified+13−25View file
@@ -1,27 +1,15 @@
11 <?xml version="1.0" encoding="UTF-8"?>
2-<svg width="512" height="512" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg">
3- <!-- Background circle -->
4- <circle cx="256" cy="256" r="240" fill="#f0f0f0"/>
5-
6- <!-- Compression arrows -->
7- <g transform="translate(256 256)">
8- <!-- Left arrows -->
9- <path d="M-180 -60 L-100 -60 L-100 -90 L-20 -30 L-100 30 L-100 0 L-180 0 Z" fill="#2563eb"/>
10- <!-- Right arrows -->
11- <path d="M180 60 L100 60 L100 90 L20 30 L100 -30 L100 0 L180 0 Z" fill="#2563eb"/>
12-
13- <!-- Central data points/graph -->
14- <g fill="#1e40af">
15- <circle cx="-60" cy="40" r="8"/>
16- <circle cx="-20" cy="-30" r="8"/>
17- <circle cx="20" cy="10" r="8"/>
18- <circle cx="60" cy="-40" r="8"/>
19- <!-- Connecting lines -->
20- <path d="M-60 40 L-20 -30 L20 10 L60 -40"
21- fill="none"
22- stroke="#1e40af"
23- stroke-width="4"
24- stroke-linecap="round"/>
25- </g>
26- </g>
2+<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
3+ <!-- Outer box -->
4+ <rect x="4" y="4" width="24" height="24" stroke="#777777" stroke-width="1.5" fill="none"/>
5+ <!-- Inner box (compressed) -->
6+ <rect x="10" y="10" width="12" height="12" stroke="#2c2c2c" stroke-width="1.5" stroke-dasharray="2" fill="none"/>
7+ <!-- Left arrow (pointing right) -->
8+ <path d="M7 16 L13 16 L10 13 M13 16 L10 19" stroke="#2c2c2c" stroke-width="2" fill="none"/>
9+ <!-- Right arrow (pointing left) -->
10+ <path d="M25 16 L19 16 L22 13 M19 16 L22 19" stroke="#2c2c2c" stroke-width="2" fill="none"/>
11+ <!-- Top arrow (pointing down) -->
12+ <path d="M16 7 L16 13 L13 10 M16 13 L19 10" stroke="#2c2c2c" stroke-width="2" fill="none"/>
13+ <!-- Bottom arrow (pointing up) -->
14+ <path d="M16 25 L16 19 L13 22 M16 19 L19 22" stroke="#2c2c2c" stroke-width="2" fill="none"/>
2715 </svg>
web-ui/src/App.tsxmodified+59−8View file
@@ -2,6 +2,7 @@ import { BrowserRouter, Routes, Route, Link, Navigate } from "react-router-dom";
22 import { useEffect, useState } from "react";
33 import axios from "axios";
44 import { ScrollToTop } from "./components/ScrollToTop";
5+import "./components/AppHeader.css";
56 import Home from "./pages/Home";
67 import About from "./pages/About";
78 import { BenchmarkData } from "./types";
@@ -40,10 +41,8 @@ function App() {
4041 <ScrollToTop />
4142 <div
4243 style={{
43- paddingTop: "5rem",
44- paddingLeft: "2rem",
45- paddingRight: "2rem",
46- paddingBottom: "2rem",
44+ paddingTop: "3rem",
45+ padding: "3rem 2rem 2rem 2rem",
4746 }}
4847 >
4948 <nav
@@ -52,14 +51,66 @@ function App() {
5251 top: 0,
5352 left: 0,
5453 right: 0,
55- padding: "1rem 2rem",
54+ padding: "0.35rem min(2rem, 4%)",
5655 backgroundColor: "white",
57- borderBottom: "1px solid #eaeaea",
56+ // borderBottom: "1px solid #eaeaea",
5857 zIndex: 1000,
59- boxShadow: "0 2px 4px rgba(0,0,0,0.1)",
58+ // boxShadow: "0 2px 4px rgba(0,0,0,0.1)",
6059 }}
6160 >
62- <div style={{ display: "flex", justifyContent: "flex-end" }}>
61+ <div
62+ style={{
63+ display: "flex",
64+ justifyContent: "space-between",
65+ alignItems: "center",
66+ minHeight: "32px",
67+ }}
68+ >
69+ <Link
70+ to="/datasets"
71+ style={{
72+ display: "flex",
73+ alignItems: "center",
74+ textDecoration: "none",
75+ minWidth: 0,
76+ maxWidth: "calc(100% - 80px)",
77+ }}
78+ >
79+ <img
80+ src="/benchcompress/logo.svg"
81+ alt="Benchcompress Logo"
82+ style={{
83+ width: "28px",
84+ height: "28px",
85+ marginRight: "10px",
86+ flexShrink: 0,
87+ }}
88+ />
89+ <span
90+ style={{
91+ minWidth: 0,
92+ whiteSpace: "nowrap",
93+ overflow: "hidden",
94+ textOverflow: "ellipsis",
95+ }}
96+ >
97+ <span
98+ style={{
99+ fontSize: "1rem",
100+ fontWeight: "500",
101+ color: "#2c2c2c",
102+ }}
103+ >
104+ Benchcompress
105+ </span>
106+ <span className="app-header-subtitle">
107+ {" · "}
108+ <span style={{ fontSize: "1rem", color: "#777" }}>
109+ Comparing compression algorithms for numeric arrays
110+ </span>
111+ </span>
112+ </span>
113+ </Link>
63114 <Link
64115 to="/about"
65116 style={{
web-ui/src/components/AppHeader.cssadded+9−0View file
@@ -0,0 +1,9 @@
1+.app-header-subtitle {
2+ display: none;
3+}
4+
5+@media (min-width: 640px) {
6+ .app-header-subtitle {
7+ display: inline;
8+ }
9+}
web-ui/src/components/algorithm/AlgorithmContent.tsxadded+110−0View file
@@ -0,0 +1,110 @@
1+import { Algorithm, BenchmarkData } from "../../types";
2+import { BenchmarkCharts } from "../benchmark/charts/BenchmarkCharts";
3+import { BenchmarkTable } from "../benchmark/table/BenchmarkTable";
4+
5+type ChartData = Array<{
6+ algorithm: string;
7+ compression_ratio: number;
8+ encode_speed: number;
9+ decode_speed: number;
10+}>;
11+
12+interface AlgorithmContentProps {
13+ algorithm: Algorithm;
14+ benchmarkData: BenchmarkData | null;
15+ chartData: ChartData;
16+}
17+
18+export const AlgorithmContent = ({
19+ algorithm,
20+ benchmarkData,
21+ chartData,
22+}: AlgorithmContentProps) => {
23+ return (
24+ <div>
25+ <div style={{ marginBottom: "1.5rem" }}>
26+ <p style={{ fontSize: "0.9rem", lineHeight: "1.5" }}>
27+ {algorithm.description}
28+ </p>
29+ </div>
30+ <div
31+ style={{
32+ marginBottom: "1.5rem",
33+ display: "flex",
34+ gap: "2rem",
35+ flexWrap: "wrap",
36+ }}
37+ >
38+ <div>
39+ <span style={{ fontWeight: "bold", fontSize: "0.9rem" }}>
40+ Version:{" "}
41+ </span>
42+ <span style={{ fontSize: "0.9rem" }}>{algorithm.version}</span>
43+ </div>
44+ <div>
45+ <span style={{ fontWeight: "bold", fontSize: "0.9rem" }}>Tags: </span>
46+ {algorithm.tags.map((tag) => (
47+ <span
48+ key={tag}
49+ style={{
50+ display: "inline-block",
51+ backgroundColor: "#e1e1e1",
52+ padding: "2px 6px",
53+ borderRadius: "3px",
54+ margin: "2px",
55+ fontSize: "0.8rem",
56+ }}
57+ >
58+ {tag}
59+ </span>
60+ ))}
61+ </div>
62+ {algorithm.source_file && (
63+ <div>
64+ <span style={{ fontWeight: "bold", fontSize: "0.9rem" }}>
65+ Source:{" "}
66+ </span>
67+ <a
68+ href={algorithm.source_file}
69+ target="_blank"
70+ rel="noopener noreferrer"
71+ style={{
72+ color: "#0066cc",
73+ textDecoration: "none",
74+ padding: "2px 6px",
75+ backgroundColor: "#f0f0f0",
76+ borderRadius: "4px",
77+ fontSize: "0.9rem",
78+ }}
79+ >
80+ View
81+ </a>
82+ </div>
83+ )}
84+ </div>
85+ {benchmarkData && (
86+ <>
87+ <div style={{ marginBottom: "1.5rem" }}>
88+ <h2
89+ style={{
90+ fontSize: "1.2rem",
91+ fontWeight: "bold",
92+ marginBottom: "0.5rem",
93+ }}
94+ >
95+ Benchmark Results
96+ </h2>
97+ <BenchmarkCharts chartData={chartData} />
98+ </div>
99+ <div style={{ marginBottom: "1.5rem" }}>
100+ <BenchmarkTable
101+ results={benchmarkData.results.filter(
102+ (result) => result.algorithm === algorithm.name,
103+ )}
104+ />
105+ </div>
106+ </>
107+ )}
108+ </div>
109+ );
110+};
web-ui/src/components/dataset/DatasetContent.tsxadded+187−0View file
@@ -0,0 +1,187 @@
1+import { Dataset, BenchmarkData } from "../../types";
2+import TimeseriesView from "./TimeseriesView";
3+import { BenchmarkCharts } from "../benchmark/charts/BenchmarkCharts";
4+import { BenchmarkTable } from "../benchmark/table/BenchmarkTable";
5+import { useEffect, useRef, useState } from "react";
6+type ChartData = Array<{
7+ algorithm: string;
8+ compression_ratio: number;
9+ encode_speed: number;
10+ decode_speed: number;
11+}>;
12+
13+interface DatasetContentProps {
14+ dataset: Dataset;
15+ benchmarkData: BenchmarkData | null;
16+ chartData: ChartData;
17+}
18+
19+export const DatasetContent = ({
20+ dataset,
21+ benchmarkData,
22+ chartData,
23+}: DatasetContentProps) => {
24+ const containerRef = useRef<HTMLDivElement>(null);
25+ const [containerWidth, setContainerWidth] = useState(1200);
26+
27+ useEffect(() => {
28+ if (!containerRef.current) return;
29+
30+ const resizeObserver = new ResizeObserver((entries) => {
31+ for (const entry of entries) {
32+ setContainerWidth(entry.contentRect.width - 32);
33+ }
34+ });
35+
36+ resizeObserver.observe(containerRef.current);
37+
38+ return () => {
39+ resizeObserver.disconnect();
40+ };
41+ }, []);
42+
43+ return (
44+ <div>
45+ <div style={{ marginBottom: "1.5rem" }}>
46+ <p style={{ fontSize: "0.9rem", lineHeight: "1.5" }}>
47+ {dataset.description}
48+ </p>
49+ </div>
50+ <div
51+ style={{
52+ marginBottom: "1.5rem",
53+ display: "flex",
54+ gap: "2rem",
55+ flexWrap: "wrap",
56+ }}
57+ >
58+ <div>
59+ <span style={{ fontWeight: "bold", fontSize: "0.9rem" }}>
60+ Version:{" "}
61+ </span>
62+ <span style={{ fontSize: "0.9rem" }}>{dataset.version}</span>
63+ </div>
64+ <div>
65+ <span style={{ fontWeight: "bold", fontSize: "0.9rem" }}>Tags: </span>
66+ {dataset.tags.map((tag) => (
67+ <span
68+ key={tag}
69+ style={{
70+ display: "inline-block",
71+ backgroundColor: "#e1e1e1",
72+ padding: "2px 6px",
73+ borderRadius: "3px",
74+ margin: "2px",
75+ fontSize: "0.8rem",
76+ }}
77+ >
78+ {tag}
79+ </span>
80+ ))}
81+ </div>
82+ <div>
83+ <span style={{ fontWeight: "bold", fontSize: "0.9rem" }}>
84+ Download:{" "}
85+ </span>
86+ <span style={{ display: "inline-flex", gap: "0.5rem" }}>
87+ {dataset.data_url_npy && (
88+ <a
89+ href={dataset.data_url_npy}
90+ download={`${dataset.name}-${dataset.version}.npy`}
91+ style={{
92+ color: "#0066cc",
93+ textDecoration: "none",
94+ padding: "2px 6px",
95+ backgroundColor: "#f0f0f0",
96+ borderRadius: "4px",
97+ fontSize: "0.9rem",
98+ }}
99+ >
100+ NPY
101+ </a>
102+ )}
103+ {dataset.data_url_raw && (
104+ <a
105+ href={dataset.data_url_raw}
106+ download={`${dataset.name}-${dataset.version}.dat`}
107+ style={{
108+ color: "#0066cc",
109+ textDecoration: "none",
110+ padding: "2px 6px",
111+ backgroundColor: "#f0f0f0",
112+ borderRadius: "4px",
113+ fontSize: "0.9rem",
114+ }}
115+ >
116+ RAW
117+ </a>
118+ )}
119+ </span>
120+ </div>
121+ {dataset.source_file && (
122+ <div>
123+ <span style={{ fontWeight: "bold", fontSize: "0.9rem" }}>
124+ Source:{" "}
125+ </span>
126+ <a
127+ href={dataset.source_file}
128+ target="_blank"
129+ rel="noopener noreferrer"
130+ style={{
131+ color: "#0066cc",
132+ textDecoration: "none",
133+ padding: "2px 6px",
134+ backgroundColor: "#f0f0f0",
135+ borderRadius: "4px",
136+ fontSize: "0.9rem",
137+ }}
138+ >
139+ View
140+ </a>
141+ </div>
142+ )}
143+ </div>
144+ <div style={{ marginBottom: "1.5rem" }}>
145+ <div
146+ ref={containerRef}
147+ style={{
148+ width: "100%",
149+ height: "300px",
150+ backgroundColor: "#f5f5f5",
151+ borderRadius: "4px",
152+ padding: "1rem",
153+ }}
154+ >
155+ <TimeseriesView
156+ width={containerWidth}
157+ height={250}
158+ dataset={dataset}
159+ />
160+ </div>
161+ </div>
162+ {benchmarkData && (
163+ <>
164+ <div style={{ marginBottom: "1.5rem" }}>
165+ <h2
166+ style={{
167+ fontSize: "1.2rem",
168+ fontWeight: "bold",
169+ marginBottom: "0.5rem",
170+ }}
171+ >
172+ Benchmark Results
173+ </h2>
174+ <BenchmarkCharts chartData={chartData} />
175+ </div>
176+ <div style={{ marginBottom: "1.5rem" }}>
177+ <BenchmarkTable
178+ results={benchmarkData.results.filter(
179+ (result) => result.dataset === dataset.name,
180+ )}
181+ />
182+ </div>
183+ </>
184+ )}
185+ </div>
186+ );
187+};
web-ui/src/components/tables/DatasetAlgorithmTables.tsxadded+375−0View file
@@ -0,0 +1,375 @@
1+import { Link } from "react-router-dom";
2+import { TagFilter } from "../TagFilter";
3+import { Dataset, Algorithm } from "../../types";
4+
5+interface DatasetTableProps {
6+ filteredDatasets: Dataset[];
7+ availableDatasetTags: string[];
8+ datasetTags: string[];
9+ toggleDatasetTag: (tag: string) => void;
10+}
11+
12+interface AlgorithmTableProps {
13+ filteredAlgorithms: Algorithm[];
14+ availableAlgorithmTags: string[];
15+ algorithmTags: string[];
16+ toggleAlgorithmTag: (tag: string) => void;
17+}
18+
19+export const DatasetTable = ({
20+ filteredDatasets,
21+ availableDatasetTags,
22+ datasetTags,
23+ toggleDatasetTag,
24+}: DatasetTableProps) => (
25+ <div style={{ overflowX: "auto" }}>
26+ <div style={{ marginBottom: "1rem" }}>
27+ <TagFilter
28+ availableTags={availableDatasetTags}
29+ selectedTags={datasetTags}
30+ onTagToggle={toggleDatasetTag}
31+ label="Filter datasets"
32+ />
33+ </div>
34+ <table style={{ width: "100%", borderCollapse: "collapse" }}>
35+ <thead>
36+ <tr style={{ backgroundColor: "#f5f5f5" }}>
37+ <th
38+ style={{
39+ padding: "8px 12px",
40+ textAlign: "left",
41+ borderBottom: "1px solid #ddd",
42+ fontSize: "0.9rem",
43+ whiteSpace: "nowrap",
44+ }}
45+ >
46+ Name
47+ </th>
48+ <th
49+ style={{
50+ padding: "8px 12px",
51+ textAlign: "left",
52+ borderBottom: "1px solid #ddd",
53+ fontSize: "0.9rem",
54+ whiteSpace: "nowrap",
55+ }}
56+ >
57+ Version
58+ </th>
59+ <th
60+ style={{
61+ padding: "8px 12px",
62+ textAlign: "left",
63+ borderBottom: "1px solid #ddd",
64+ fontSize: "0.9rem",
65+ whiteSpace: "nowrap",
66+ }}
67+ >
68+ Description
69+ </th>
70+ <th
71+ style={{
72+ padding: "8px 12px",
73+ textAlign: "left",
74+ borderBottom: "1px solid #ddd",
75+ fontSize: "0.9rem",
76+ whiteSpace: "nowrap",
77+ }}
78+ >
79+ Tags
80+ </th>
81+ <th
82+ style={{
83+ padding: "8px 12px",
84+ textAlign: "left",
85+ borderBottom: "1px solid #ddd",
86+ fontSize: "0.9rem",
87+ whiteSpace: "nowrap",
88+ }}
89+ >
90+ Source
91+ </th>
92+ <th
93+ style={{
94+ padding: "8px 12px",
95+ textAlign: "left",
96+ borderBottom: "1px solid #ddd",
97+ fontSize: "0.9rem",
98+ whiteSpace: "nowrap",
99+ }}
100+ >
101+ Data
102+ </th>
103+ </tr>
104+ </thead>
105+ <tbody>
106+ {filteredDatasets.map((dataset, index) => (
107+ <tr
108+ key={`${dataset.name}-${dataset.version}`}
109+ style={{ backgroundColor: index % 2 === 0 ? "white" : "#fafafa" }}
110+ >
111+ <td
112+ style={{
113+ padding: "6px 12px",
114+ borderBottom: "1px solid #ddd",
115+ fontSize: "0.9rem",
116+ }}
117+ >
118+ <Link
119+ to={`/dataset/${encodeURIComponent(dataset.name)}`}
120+ style={{
121+ color: "#0066cc",
122+ textDecoration: "none",
123+ fontWeight: "500",
124+ }}
125+ >
126+ {dataset.name}
127+ </Link>
128+ </td>
129+ <td
130+ style={{
131+ padding: "6px 12px",
132+ borderBottom: "1px solid #ddd",
133+ fontSize: "0.9rem",
134+ }}
135+ >
136+ {dataset.version}
137+ </td>
138+ <td
139+ style={{
140+ padding: "6px 12px",
141+ borderBottom: "1px solid #ddd",
142+ fontSize: "0.9rem",
143+ }}
144+ >
145+ {dataset.description}
146+ </td>
147+ <td
148+ style={{
149+ padding: "6px 12px",
150+ borderBottom: "1px solid #ddd",
151+ fontSize: "0.9rem",
152+ }}
153+ >
154+ {dataset.tags.map((tag) => (
155+ <span
156+ key={tag}
157+ style={{
158+ display: "inline-block",
159+ backgroundColor: "#e1e1e1",
160+ padding: "2px 6px",
161+ borderRadius: "3px",
162+ margin: "1px",
163+ fontSize: "0.8rem",
164+ }}
165+ >
166+ {tag}
167+ </span>
168+ ))}
169+ </td>
170+ <td
171+ style={{
172+ padding: "6px 12px",
173+ borderBottom: "1px solid #ddd",
174+ fontSize: "0.9rem",
175+ }}
176+ >
177+ {dataset.source_file && (
178+ <a
179+ href={dataset.source_file}
180+ target="_blank"
181+ rel="noopener noreferrer"
182+ style={{ color: "#0066cc", textDecoration: "none" }}
183+ >
184+ View Source
185+ </a>
186+ )}
187+ </td>
188+ <td
189+ style={{
190+ padding: "6px 12px",
191+ borderBottom: "1px solid #ddd",
192+ fontSize: "0.9rem",
193+ }}
194+ >
195+ {dataset.data_url_npy && (
196+ <a
197+ href={dataset.data_url_npy}
198+ download={`${dataset.name}-${dataset.version}.npy`}
199+ style={{ color: "#0066cc", textDecoration: "none" }}
200+ >
201+ Download
202+ </a>
203+ )}
204+ </td>
205+ </tr>
206+ ))}
207+ </tbody>
208+ </table>
209+ </div>
210+);
211+
212+export const AlgorithmTable = ({
213+ filteredAlgorithms,
214+ availableAlgorithmTags,
215+ algorithmTags,
216+ toggleAlgorithmTag,
217+}: AlgorithmTableProps) => (
218+ <div style={{ overflowX: "auto" }}>
219+ <div style={{ marginBottom: "1rem" }}>
220+ <TagFilter
221+ availableTags={availableAlgorithmTags}
222+ selectedTags={algorithmTags}
223+ onTagToggle={toggleAlgorithmTag}
224+ label="Filter algorithms"
225+ />
226+ </div>
227+ <table style={{ width: "100%", borderCollapse: "collapse" }}>
228+ <thead>
229+ <tr style={{ backgroundColor: "#f5f5f5" }}>
230+ <th
231+ style={{
232+ padding: "8px 12px",
233+ textAlign: "left",
234+ borderBottom: "1px solid #ddd",
235+ fontSize: "0.9rem",
236+ whiteSpace: "nowrap",
237+ }}
238+ >
239+ Name
240+ </th>
241+ <th
242+ style={{
243+ padding: "8px 12px",
244+ textAlign: "left",
245+ borderBottom: "1px solid #ddd",
246+ fontSize: "0.9rem",
247+ whiteSpace: "nowrap",
248+ }}
249+ >
250+ Version
251+ </th>
252+ <th
253+ style={{
254+ padding: "8px 12px",
255+ textAlign: "left",
256+ borderBottom: "1px solid #ddd",
257+ fontSize: "0.9rem",
258+ whiteSpace: "nowrap",
259+ }}
260+ >
261+ Description
262+ </th>
263+ <th
264+ style={{
265+ padding: "8px 12px",
266+ textAlign: "left",
267+ borderBottom: "1px solid #ddd",
268+ fontSize: "0.9rem",
269+ whiteSpace: "nowrap",
270+ }}
271+ >
272+ Tags
273+ </th>
274+ <th
275+ style={{
276+ padding: "8px 12px",
277+ textAlign: "left",
278+ borderBottom: "1px solid #ddd",
279+ fontSize: "0.9rem",
280+ whiteSpace: "nowrap",
281+ }}
282+ >
283+ Source
284+ </th>
285+ </tr>
286+ </thead>
287+ <tbody>
288+ {filteredAlgorithms.map((algorithm, index) => (
289+ <tr
290+ key={`${algorithm.name}-${algorithm.version}`}
291+ style={{ backgroundColor: index % 2 === 0 ? "white" : "#fafafa" }}
292+ >
293+ <td
294+ style={{
295+ padding: "6px 12px",
296+ borderBottom: "1px solid #ddd",
297+ fontSize: "0.9rem",
298+ }}
299+ >
300+ <Link
301+ to={`/algorithm/${encodeURIComponent(algorithm.name)}`}
302+ style={{
303+ color: "#0066cc",
304+ textDecoration: "none",
305+ fontWeight: "500",
306+ }}
307+ >
308+ {algorithm.name}
309+ </Link>
310+ </td>
311+ <td
312+ style={{
313+ padding: "6px 12px",
314+ borderBottom: "1px solid #ddd",
315+ fontSize: "0.9rem",
316+ }}
317+ >
318+ {algorithm.version}
319+ </td>
320+ <td
321+ style={{
322+ padding: "6px 12px",
323+ borderBottom: "1px solid #ddd",
324+ fontSize: "0.9rem",
325+ }}
326+ >
327+ {algorithm.description}
328+ </td>
329+ <td
330+ style={{
331+ padding: "6px 12px",
332+ borderBottom: "1px solid #ddd",
333+ fontSize: "0.9rem",
334+ }}
335+ >
336+ {algorithm.tags.map((tag) => (
337+ <span
338+ key={tag}
339+ style={{
340+ display: "inline-block",
341+ backgroundColor: "#e1e1e1",
342+ padding: "2px 6px",
343+ borderRadius: "3px",
344+ margin: "1px",
345+ fontSize: "0.8rem",
346+ }}
347+ >
348+ {tag}
349+ </span>
350+ ))}
351+ </td>
352+ <td
353+ style={{
354+ padding: "6px 12px",
355+ borderBottom: "1px solid #ddd",
356+ fontSize: "0.9rem",
357+ }}
358+ >
359+ {algorithm.source_file && (
360+ <a
361+ href={algorithm.source_file}
362+ target="_blank"
363+ rel="noopener noreferrer"
364+ style={{ color: "#0066cc", textDecoration: "none" }}
365+ >
366+ View Source
367+ </a>
368+ )}
369+ </td>
370+ </tr>
371+ ))}
372+ </tbody>
373+ </table>
374+ </div>
375+);
web-ui/src/pages/Home.tsxmodified+156−707View file
@@ -1,10 +1,13 @@
1-import { Link, useLocation, useParams } from "react-router-dom";
2-import { BenchmarkTable } from "../components/benchmark/table/BenchmarkTable";
3-import { useEffect, useRef, useState } from "react";
4-import TimeseriesView from "../components/dataset/TimeseriesView";
5-import { BenchmarkCharts } from "../components/benchmark/charts/BenchmarkCharts";
1+import { useLocation, useNavigate, useParams } from "react-router-dom";
2+import { useReducer, useEffect } from "react";
3+import { tabsReducer } from "../reducers/tabsReducer";
4+import { AlgorithmContent } from "../components/algorithm/AlgorithmContent";
5+import { DatasetContent } from "../components/dataset/DatasetContent";
6+import {
7+ AlgorithmTable,
8+ DatasetTable,
9+} from "../components/tables/DatasetAlgorithmTables";
610 import { useBenchmarkChartData } from "../hooks/useBenchmarkChartData";
7-import { TagFilter } from "../components/TagFilter";
811 import { useTagFilter } from "../hooks/useTagFilter";
912 import { BenchmarkData } from "../types";
1013
@@ -14,39 +17,53 @@ interface HomeProps {
1417
1518 export default function Home({ benchmarkData }: HomeProps) {
1619 const location = useLocation();
20+ const navigate = useNavigate();
1721 const { datasetName, algorithmName } = useParams<{
1822 datasetName?: string;
1923 algorithmName?: string;
2024 }>();
21- const containerRef = useRef<HTMLDivElement>(null);
22- const [containerWidth, setContainerWidth] = useState(1200);
2325
24- useEffect(() => {
25- if (!containerRef.current) return;
26-
27- const resizeObserver = new ResizeObserver((entries) => {
28- for (const entry of entries) {
29- setContainerWidth(entry.contentRect.width - 32);
30- }
31- });
32-
33- resizeObserver.observe(containerRef.current);
26+ const [tabsState, dispatch] = useReducer(tabsReducer, {
27+ tabs: [
28+ { id: "datasets", label: "Datasets", route: "/datasets" },
29+ { id: "algorithms", label: "Algorithms", route: "/algorithms" },
30+ ],
31+ activeTabId: "datasets",
32+ });
3433
35- return () => {
36- resizeObserver.disconnect();
37- };
38- }, []);
39-
40- // Determine active tab based on URL
41- const getActiveTab = () => {
42- if (datasetName) return `dataset-${datasetName}`;
43- if (algorithmName) return `algorithm-${algorithmName}`;
44- if (location.pathname.includes("/algorithms")) return "algorithms";
45- return "datasets";
34+ // Effect to handle URL changes and update tabs
35+ useEffect(() => {
36+ if (datasetName) {
37+ dispatch({
38+ type: "ADD_TAB",
39+ payload: {
40+ id: `dataset-${datasetName}`,
41+ label: datasetName,
42+ route: `/dataset/${datasetName}`,
43+ },
44+ });
45+ } else if (algorithmName) {
46+ dispatch({
47+ type: "ADD_TAB",
48+ payload: {
49+ id: `algorithm-${algorithmName}`,
50+ label: algorithmName,
51+ route: `/algorithm/${algorithmName}`,
52+ },
53+ });
54+ } else if (location.pathname.includes("/algorithms")) {
55+ dispatch({ type: "SET_ACTIVE_TAB", payload: "algorithms" });
56+ } else if (location.pathname.includes("/datasets")) {
57+ dispatch({ type: "SET_ACTIVE_TAB", payload: "datasets" });
58+ }
59+ }, [datasetName, algorithmName, location.pathname]);
60+
61+ // Handle tab click
62+ const handleTabClick = (tabId: string, route: string) => {
63+ dispatch({ type: "SET_ACTIVE_TAB", payload: tabId });
64+ navigate(route);
4665 };
4766
48- const activeTab = getActiveTab();
49-
5067 // Get specific dataset or algorithm if viewing one
5168 const dataset = datasetName
5269 ? benchmarkData?.datasets.find((d) => d.name === datasetName)
@@ -78,703 +95,135 @@ export default function Home({ benchmarkData }: HomeProps) {
7895 toggleTag: toggleAlgorithmTag,
7996 } = useTagFilter(benchmarkData?.algorithms || []);
8097
81- const renderDatasets = () => (
82- <div style={{ overflowX: "auto" }}>
83- <div style={{ marginBottom: "1rem" }}>
84- <TagFilter
85- availableTags={availableDatasetTags}
86- selectedTags={datasetTags}
87- onTagToggle={toggleDatasetTag}
88- label="Filter datasets"
89- />
90- </div>
91- <table style={{ width: "100%", borderCollapse: "collapse" }}>
92- <thead>
93- <tr style={{ backgroundColor: "#f5f5f5" }}>
94- <th
95- style={{
96- padding: "8px 12px",
97- textAlign: "left",
98- borderBottom: "1px solid #ddd",
99- fontSize: "0.9rem",
100- whiteSpace: "nowrap",
101- }}
102- >
103- Name
104- </th>
105- <th
106- style={{
107- padding: "8px 12px",
108- textAlign: "left",
109- borderBottom: "1px solid #ddd",
110- fontSize: "0.9rem",
111- whiteSpace: "nowrap",
112- }}
113- >
114- Version
115- </th>
116- <th
117- style={{
118- padding: "8px 12px",
119- textAlign: "left",
120- borderBottom: "1px solid #ddd",
121- fontSize: "0.9rem",
122- whiteSpace: "nowrap",
123- }}
124- >
125- Description
126- </th>
127- <th
128- style={{
129- padding: "8px 12px",
130- textAlign: "left",
131- borderBottom: "1px solid #ddd",
132- fontSize: "0.9rem",
133- whiteSpace: "nowrap",
134- }}
135- >
136- Tags
137- </th>
138- <th
139- style={{
140- padding: "8px 12px",
141- textAlign: "left",
142- borderBottom: "1px solid #ddd",
143- fontSize: "0.9rem",
144- whiteSpace: "nowrap",
145- }}
146- >
147- Source
148- </th>
149- <th
150- style={{
151- padding: "8px 12px",
152- textAlign: "left",
153- borderBottom: "1px solid #ddd",
154- fontSize: "0.9rem",
155- whiteSpace: "nowrap",
156- }}
157- >
158- Data
159- </th>
160- </tr>
161- </thead>
162- <tbody>
163- {filteredDatasets.map((dataset, index) => (
164- <tr
165- key={`${dataset.name}-${dataset.version}`}
166- style={{ backgroundColor: index % 2 === 0 ? "white" : "#fafafa" }}
167- >
168- <td
169- style={{
170- padding: "6px 12px",
171- borderBottom: "1px solid #ddd",
172- fontSize: "0.9rem",
173- }}
174- >
175- <Link
176- to={`/dataset/${encodeURIComponent(dataset.name)}`}
177- style={{
178- color: "#0066cc",
179- textDecoration: "none",
180- fontWeight: "500",
181- }}
182- >
183- {dataset.name}
184- </Link>
185- </td>
186- <td
187- style={{
188- padding: "6px 12px",
189- borderBottom: "1px solid #ddd",
190- fontSize: "0.9rem",
191- }}
192- >
193- {dataset.version}
194- </td>
195- <td
196- style={{
197- padding: "6px 12px",
198- borderBottom: "1px solid #ddd",
199- fontSize: "0.9rem",
200- }}
201- >
202- {dataset.description}
203- </td>
204- <td
205- style={{
206- padding: "6px 12px",
207- borderBottom: "1px solid #ddd",
208- fontSize: "0.9rem",
209- }}
210- >
211- {dataset.tags.map((tag) => (
212- <span
213- key={tag}
214- style={{
215- display: "inline-block",
216- backgroundColor: "#e1e1e1",
217- padding: "2px 6px",
218- borderRadius: "3px",
219- margin: "1px",
220- fontSize: "0.8rem",
221- }}
222- >
223- {tag}
224- </span>
225- ))}
226- </td>
227- <td
228- style={{
229- padding: "6px 12px",
230- borderBottom: "1px solid #ddd",
231- fontSize: "0.9rem",
232- }}
233- >
234- {dataset.source_file && (
235- <a
236- href={dataset.source_file}
237- target="_blank"
238- rel="noopener noreferrer"
239- style={{ color: "#0066cc", textDecoration: "none" }}
240- >
241- View Source
242- </a>
243- )}
244- </td>
245- <td
246- style={{
247- padding: "6px 12px",
248- borderBottom: "1px solid #ddd",
249- fontSize: "0.9rem",
250- }}
251- >
252- {dataset.data_url_npy && (
253- <a
254- href={dataset.data_url_npy}
255- download={`${dataset.name}-${dataset.version}.npy`}
256- style={{ color: "#0066cc", textDecoration: "none" }}
257- >
258- Download
259- </a>
260- )}
261- </td>
262- </tr>
263- ))}
264- </tbody>
265- </table>
266- </div>
267- );
268-
269- const renderAlgorithms = () => (
270- <div style={{ overflowX: "auto" }}>
271- <div style={{ marginBottom: "1rem" }}>
272- <TagFilter
273- availableTags={availableAlgorithmTags}
274- selectedTags={algorithmTags}
275- onTagToggle={toggleAlgorithmTag}
276- label="Filter algorithms"
277- />
278- </div>
279- <table style={{ width: "100%", borderCollapse: "collapse" }}>
280- <thead>
281- <tr style={{ backgroundColor: "#f5f5f5" }}>
282- <th
283- style={{
284- padding: "8px 12px",
285- textAlign: "left",
286- borderBottom: "1px solid #ddd",
287- fontSize: "0.9rem",
288- whiteSpace: "nowrap",
289- }}
290- >
291- Name
292- </th>
293- <th
294- style={{
295- padding: "8px 12px",
296- textAlign: "left",
297- borderBottom: "1px solid #ddd",
298- fontSize: "0.9rem",
299- whiteSpace: "nowrap",
300- }}
301- >
302- Version
303- </th>
304- <th
305- style={{
306- padding: "8px 12px",
307- textAlign: "left",
308- borderBottom: "1px solid #ddd",
309- fontSize: "0.9rem",
310- whiteSpace: "nowrap",
311- }}
312- >
313- Description
314- </th>
315- <th
316- style={{
317- padding: "8px 12px",
318- textAlign: "left",
319- borderBottom: "1px solid #ddd",
320- fontSize: "0.9rem",
321- whiteSpace: "nowrap",
322- }}
323- >
324- Tags
325- </th>
326- <th
327- style={{
328- padding: "8px 12px",
329- textAlign: "left",
330- borderBottom: "1px solid #ddd",
331- fontSize: "0.9rem",
332- whiteSpace: "nowrap",
333- }}
334- >
335- Source
336- </th>
337- </tr>
338- </thead>
339- <tbody>
340- {filteredAlgorithms.map((algorithm, index) => (
341- <tr
342- key={`${algorithm.name}-${algorithm.version}`}
343- style={{ backgroundColor: index % 2 === 0 ? "white" : "#fafafa" }}
344- >
345- <td
346- style={{
347- padding: "6px 12px",
348- borderBottom: "1px solid #ddd",
349- fontSize: "0.9rem",
350- }}
351- >
352- <Link
353- to={`/algorithm/${encodeURIComponent(algorithm.name)}`}
354- style={{
355- color: "#0066cc",
356- textDecoration: "none",
357- fontWeight: "500",
358- }}
359- >
360- {algorithm.name}
361- </Link>
362- </td>
363- <td
364- style={{
365- padding: "6px 12px",
366- borderBottom: "1px solid #ddd",
367- fontSize: "0.9rem",
368- }}
369- >
370- {algorithm.version}
371- </td>
372- <td
373- style={{
374- padding: "6px 12px",
375- borderBottom: "1px solid #ddd",
376- fontSize: "0.9rem",
377- }}
378- >
379- {algorithm.description}
380- </td>
381- <td
382- style={{
383- padding: "6px 12px",
384- borderBottom: "1px solid #ddd",
385- fontSize: "0.9rem",
386- }}
387- >
388- {algorithm.tags.map((tag) => (
389- <span
390- key={tag}
391- style={{
392- display: "inline-block",
393- backgroundColor: "#e1e1e1",
394- padding: "2px 6px",
395- borderRadius: "3px",
396- margin: "1px",
397- fontSize: "0.8rem",
398- }}
399- >
400- {tag}
401- </span>
402- ))}
403- </td>
404- <td
405- style={{
406- padding: "6px 12px",
407- borderBottom: "1px solid #ddd",
408- fontSize: "0.9rem",
409- }}
410- >
411- {algorithm.source_file && (
412- <a
413- href={algorithm.source_file}
414- target="_blank"
415- rel="noopener noreferrer"
416- style={{ color: "#0066cc", textDecoration: "none" }}
417- >
418- View Source
419- </a>
420- )}
421- </td>
422- </tr>
423- ))}
424- </tbody>
425- </table>
426- </div>
427- );
428-
42998 return (
43099 <div>
431- <header style={{ marginBottom: "2rem" }}>
432- <h1 style={{ fontSize: "2rem", fontWeight: "bold", color: "#333" }}>
433- Benchcompress
434- </h1>
435- <p style={{ color: "#666", marginTop: "0.5rem" }}>
436- Comparing compression algorithms for numeric arrays
437- </p>
438- </header>
439100 <main>
440- <div style={{ marginBottom: "1rem" }}>
101+ <div
102+ style={{
103+ position: "fixed",
104+ top: "3rem",
105+ left: 0,
106+ right: 0,
107+ backgroundColor: "white",
108+ zIndex: 999,
109+ padding: "0 2rem 0 2rem",
110+ marginTop: "-4px",
111+ boxShadow: "0 2px 4px rgba(0,0,0,0.1)",
112+ borderBottom: "1px solid #eaeaea",
113+ }}
114+ >
441115 <div
442116 style={{
443- borderBottom: "1px solid #ddd",
117+ paddingBottom: "2px",
444118 display: "flex",
445119 gap: "4px",
446120 overflowX: "auto",
121+ width: "100%",
122+ backgroundColor: "white",
447123 }}
448124 >
449- <Link
450- to="/datasets"
451- style={{
452- padding: "8px 16px",
453- border: "none",
454- background: "none",
455- borderBottom:
456- activeTab === "datasets" ? "2px solid #0066cc" : "none",
457- color: activeTab === "datasets" ? "#0066cc" : "#666",
458- fontWeight: activeTab === "datasets" ? "600" : "normal",
459- cursor: "pointer",
460- textDecoration: "none",
461- whiteSpace: "nowrap",
462- }}
463- >
464- Datasets
465- </Link>
466- <Link
467- to="/algorithms"
468- style={{
469- padding: "8px 16px",
470- border: "none",
471- background: "none",
472- borderBottom:
473- activeTab === "algorithms" ? "2px solid #0066cc" : "none",
474- color: activeTab === "algorithms" ? "#0066cc" : "#666",
475- fontWeight: activeTab === "algorithms" ? "600" : "normal",
476- cursor: "pointer",
477- textDecoration: "none",
478- whiteSpace: "nowrap",
479- }}
480- >
481- Algorithms
482- </Link>
483- {dataset && (
484- <Link
485- to={`/dataset/${dataset.name}`}
486- style={{
487- padding: "8px 16px",
488- border: "none",
489- background: "none",
490- borderBottom:
491- activeTab === `dataset-${dataset.name}`
492- ? "2px solid #0066cc"
493- : "none",
494- color:
495- activeTab === `dataset-${dataset.name}`
496- ? "#0066cc"
497- : "#666",
498- fontWeight:
499- activeTab === `dataset-${dataset.name}` ? "600" : "normal",
500- cursor: "pointer",
501- textDecoration: "none",
502- whiteSpace: "nowrap",
503- }}
504- >
505- {dataset.name}
506- </Link>
507- )}
508- {algorithm && (
509- <Link
510- to={`/algorithm/${algorithm.name}`}
511- style={{
512- padding: "8px 16px",
513- border: "none",
514- background: "none",
515- borderBottom:
516- activeTab === `algorithm-${algorithm.name}`
517- ? "2px solid #0066cc"
518- : "none",
519- color:
520- activeTab === `algorithm-${algorithm.name}`
521- ? "#0066cc"
522- : "#666",
523- fontWeight:
524- activeTab === `algorithm-${algorithm.name}`
525- ? "600"
526- : "normal",
527- cursor: "pointer",
528- textDecoration: "none",
529- whiteSpace: "nowrap",
530- }}
531- >
532- {algorithm.name}
533- </Link>
534- )}
535- </div>
536- </div>
537-
538- <div style={{ padding: "1rem 0" }}>
539- {dataset ? (
540- <div>
541- <div style={{ marginBottom: "1.5rem" }}>
542- <p style={{ fontSize: "0.9rem", lineHeight: "1.5" }}>
543- {dataset.description}
544- </p>
545- </div>
125+ {tabsState.tabs.map((tab) => (
546126 <div
127+ key={tab.id}
547128 style={{
548- marginBottom: "1.5rem",
549129 display: "flex",
550- gap: "2rem",
551- flexWrap: "wrap",
130+ alignItems: "center",
131+ gap: "4px",
552132 }}
553133 >
554- <div>
555- <span style={{ fontWeight: "bold", fontSize: "0.9rem" }}>
556- Version:{" "}
557- </span>
558- <span style={{ fontSize: "0.9rem" }}>{dataset.version}</span>
559- </div>
560- <div>
561- <span style={{ fontWeight: "bold", fontSize: "0.9rem" }}>
562- Tags:{" "}
563- </span>
564- {dataset.tags.map((tag) => (
565- <span
566- key={tag}
567- style={{
568- display: "inline-block",
569- backgroundColor: "#e1e1e1",
570- padding: "2px 6px",
571- borderRadius: "3px",
572- margin: "2px",
573- fontSize: "0.8rem",
574- }}
575- >
576- {tag}
577- </span>
578- ))}
579- </div>
580- <div>
581- <span style={{ fontWeight: "bold", fontSize: "0.9rem" }}>
582- Download:{" "}
583- </span>
584- <span style={{ display: "inline-flex", gap: "0.5rem" }}>
585- {dataset.data_url_npy && (
586- <a
587- href={dataset.data_url_npy}
588- download={`${dataset.name}-${dataset.version}.npy`}
589- style={{
590- color: "#0066cc",
591- textDecoration: "none",
592- padding: "2px 6px",
593- backgroundColor: "#f0f0f0",
594- borderRadius: "4px",
595- fontSize: "0.9rem",
596- }}
597- >
598- NPY
599- </a>
600- )}
601- {dataset.data_url_raw && (
602- <a
603- href={dataset.data_url_raw}
604- download={`${dataset.name}-${dataset.version}.dat`}
605- style={{
606- color: "#0066cc",
607- textDecoration: "none",
608- padding: "2px 6px",
609- backgroundColor: "#f0f0f0",
610- borderRadius: "4px",
611- fontSize: "0.9rem",
612- }}
613- >
614- RAW
615- </a>
616- )}
617- </span>
618- </div>
619- {dataset.source_file && (
620- <div>
621- <span style={{ fontWeight: "bold", fontSize: "0.9rem" }}>
622- Source:{" "}
623- </span>
624- <a
625- href={dataset.source_file}
626- target="_blank"
627- rel="noopener noreferrer"
628- style={{
629- color: "#0066cc",
630- textDecoration: "none",
631- padding: "2px 6px",
632- backgroundColor: "#f0f0f0",
633- borderRadius: "4px",
634- fontSize: "0.9rem",
635- }}
636- >
637- View
638- </a>
639- </div>
640- )}
641- </div>
642- <div style={{ marginBottom: "1.5rem" }}>
643- <div
644- ref={containerRef}
134+ <button
135+ onClick={() => handleTabClick(tab.id, tab.route)}
645136 style={{
646- width: "100%",
647- height: "300px",
648- backgroundColor: "#f5f5f5",
649- borderRadius: "4px",
650- padding: "1rem",
137+ padding: "8px 16px",
138+ border: "none",
139+ background: "none",
140+ borderBottom:
141+ tabsState.activeTabId === tab.id
142+ ? "2px solid #0066cc"
143+ : "none",
144+ color:
145+ tabsState.activeTabId === tab.id ? "#0066cc" : "#666",
146+ fontWeight:
147+ tabsState.activeTabId === tab.id ? "600" : "normal",
148+ cursor: "pointer",
149+ textDecoration: "none",
150+ whiteSpace: "nowrap",
651151 }}
652152 >
653- <TimeseriesView
654- width={containerWidth}
655- height={250}
656- dataset={dataset}
657- />
658- </div>
659- </div>
660- {benchmarkData && (
661- <>
662- <div style={{ marginBottom: "1.5rem" }}>
663- <h2
664- style={{
665- fontSize: "1.2rem",
666- fontWeight: "bold",
667- marginBottom: "0.5rem",
668- }}
669- >
670- Benchmark Results
671- </h2>
672- <BenchmarkCharts chartData={chartData} />
673- </div>
674- <div style={{ marginBottom: "1.5rem" }}>
675- <BenchmarkTable
676- results={benchmarkData.results.filter(
677- (result) => result.dataset === dataset.name,
678- )}
679- />
680- </div>
681- </>
682- )}
683- </div>
684- ) : algorithm ? (
685- <div>
686- <div style={{ marginBottom: "1.5rem" }}>
687- <p style={{ fontSize: "0.9rem", lineHeight: "1.5" }}>
688- {algorithm.description}
689- </p>
690- </div>
691- <div
692- style={{
693- marginBottom: "1.5rem",
694- display: "flex",
695- gap: "2rem",
696- flexWrap: "wrap",
697- }}
698- >
699- <div>
700- <span style={{ fontWeight: "bold", fontSize: "0.9rem" }}>
701- Version:{" "}
702- </span>
703- <span style={{ fontSize: "0.9rem" }}>
704- {algorithm.version}
705- </span>
706- </div>
707- <div>
708- <span style={{ fontWeight: "bold", fontSize: "0.9rem" }}>
709- Tags:{" "}
710- </span>
711- {algorithm.tags.map((tag) => (
712- <span
713- key={tag}
714- style={{
715- display: "inline-block",
716- backgroundColor: "#e1e1e1",
717- padding: "2px 6px",
718- borderRadius: "3px",
719- margin: "2px",
720- fontSize: "0.8rem",
721- }}
722- >
723- {tag}
724- </span>
725- ))}
726- </div>
727- {algorithm.source_file && (
728- <div>
729- <span style={{ fontWeight: "bold", fontSize: "0.9rem" }}>
730- Source:{" "}
731- </span>
732- <a
733- href={algorithm.source_file}
734- target="_blank"
735- rel="noopener noreferrer"
736- style={{
737- color: "#0066cc",
738- textDecoration: "none",
739- padding: "2px 6px",
740- backgroundColor: "#f0f0f0",
741- borderRadius: "4px",
742- fontSize: "0.9rem",
743- }}
744- >
745- View
746- </a>
747- </div>
153+ {tab.label}
154+ </button>
155+ {tab.id !== "datasets" && tab.id !== "algorithms" && (
156+ <button
157+ onClick={(e) => {
158+ e.stopPropagation();
159+ const newActiveTab =
160+ tab.id === tabsState.activeTabId
161+ ? tabsState.tabs[0].id // Default to first tab if closing active
162+ : tabsState.activeTabId;
163+ dispatch({ type: "CLOSE_TAB", payload: tab.id });
164+ // Navigate if closing active tab
165+ if (tab.id === tabsState.activeTabId) {
166+ const defaultTab = tabsState.tabs.find(
167+ (t) => t.id === newActiveTab,
168+ );
169+ if (defaultTab) {
170+ navigate(defaultTab.route);
171+ }
172+ }
173+ }}
174+ style={{
175+ padding: "4px",
176+ border: "none",
177+ background: "none",
178+ color: "#666",
179+ cursor: "pointer",
180+ fontSize: "12px",
181+ display: "flex",
182+ alignItems: "center",
183+ justifyContent: "center",
184+ width: "20px",
185+ height: "20px",
186+ borderRadius: "50%",
187+ marginRight: "4px",
188+ marginLeft: "-4px",
189+ }}
190+ aria-label="Close tab"
191+ >
192+ ×
193+ </button>
748194 )}
749195 </div>
750- {benchmarkData && (
751- <>
752- <div style={{ marginBottom: "1.5rem" }}>
753- <h2
754- style={{
755- fontSize: "1.2rem",
756- fontWeight: "bold",
757- marginBottom: "0.5rem",
758- }}
759- >
760- Benchmark Results
761- </h2>
762- <BenchmarkCharts chartData={chartData} />
763- </div>
764- <div style={{ marginBottom: "1.5rem" }}>
765- <BenchmarkTable
766- results={benchmarkData.results.filter(
767- (result) => result.algorithm === algorithm.name,
768- )}
769- />
770- </div>
771- </>
772- )}
773- </div>
774- ) : activeTab === "datasets" ? (
775- renderDatasets()
196+ ))}
197+ </div>
198+ </div>
199+
200+ <div style={{ padding: "3rem 0 1rem 0" }}>
201+ {dataset ? (
202+ <DatasetContent
203+ dataset={dataset}
204+ benchmarkData={benchmarkData}
205+ chartData={chartData}
206+ />
207+ ) : algorithm ? (
208+ <AlgorithmContent
209+ algorithm={algorithm}
210+ benchmarkData={benchmarkData}
211+ chartData={chartData}
212+ />
213+ ) : tabsState.activeTabId === "datasets" ? (
214+ <DatasetTable
215+ filteredDatasets={filteredDatasets}
216+ availableDatasetTags={availableDatasetTags}
217+ datasetTags={datasetTags}
218+ toggleDatasetTag={toggleDatasetTag}
219+ />
776220 ) : (
777- renderAlgorithms()
221+ <AlgorithmTable
222+ filteredAlgorithms={filteredAlgorithms}
223+ availableAlgorithmTags={availableAlgorithmTags}
224+ algorithmTags={algorithmTags}
225+ toggleAlgorithmTag={toggleAlgorithmTag}
226+ />
778227 )}
779228 </div>
780229 </main>
web-ui/src/reducers/tabsReducer.tsadded+85−0View file
@@ -0,0 +1,85 @@
1+export type TabAction =
2+ | { type: "ADD_TAB"; payload: { id: string; label: string; route: string } }
3+ | { type: "SET_ACTIVE_TAB"; payload: string }
4+ | { type: "CLOSE_TAB"; payload: string }
5+ | { type: "REORDER_TABS"; payload: { fromIndex: number; toIndex: number } };
6+
7+export interface TabItem {
8+ id: string;
9+ label: string;
10+ route: string;
11+}
12+
13+export interface TabsState {
14+ tabs: TabItem[];
15+ activeTabId: string;
16+}
17+
18+const initialState: TabsState = {
19+ tabs: [
20+ { id: "datasets", label: "Datasets", route: "/datasets" },
21+ { id: "algorithms", label: "Algorithms", route: "/algorithms" },
22+ ],
23+ activeTabId: "datasets",
24+};
25+
26+export function tabsReducer(
27+ state: TabsState = initialState,
28+ action: TabAction,
29+): TabsState {
30+ switch (action.type) {
31+ case "ADD_TAB": {
32+ // If tab already exists, just set it as active
33+ if (state.tabs.some((tab) => tab.id === action.payload.id)) {
34+ return {
35+ ...state,
36+ activeTabId: action.payload.id,
37+ };
38+ }
39+
40+ // Otherwise add new tab
41+ return {
42+ ...state,
43+ tabs: [...state.tabs, action.payload],
44+ activeTabId: action.payload.id,
45+ };
46+ }
47+ case "SET_ACTIVE_TAB":
48+ return {
49+ ...state,
50+ activeTabId: action.payload,
51+ };
52+ case "REORDER_TABS": {
53+ const newTabs = [...state.tabs];
54+ const [movedTab] = newTabs.splice(action.payload.fromIndex, 1);
55+ newTabs.splice(action.payload.toIndex, 0, movedTab);
56+ return {
57+ ...state,
58+ tabs: newTabs,
59+ };
60+ }
61+ case "CLOSE_TAB": {
62+ // Don't allow closing of datasets or algorithms tabs
63+ if (action.payload === "datasets" || action.payload === "algorithms") {
64+ return state;
65+ }
66+
67+ const newTabs = state.tabs.filter((tab) => tab.id !== action.payload);
68+
69+ // If we're closing the active tab, activate the last tab in the list
70+ if (state.activeTabId === action.payload) {
71+ return {
72+ tabs: newTabs,
73+ activeTabId: newTabs[newTabs.length - 1].id,
74+ };
75+ }
76+
77+ return {
78+ ...state,
79+ tabs: newTabs,
80+ };
81+ }
82+ default:
83+ return state;
84+ }
85+}
web-ui/src/types.tsmodified+16−0View file
@@ -40,3 +40,19 @@ export interface BenchmarkData {
4040 algorithms: Algorithm[];
4141 datasets: Dataset[];
4242 }
43+
44+export interface TabItem {
45+ id: string;
46+ label: string;
47+ route: string;
48+}
49+
50+export interface TabsState {
51+ tabs: TabItem[];
52+ activeTabId: string;
53+}
54+
55+export type TabAction =
56+ | { type: "ADD_TAB"; payload: { id: string; label: string; route: string } }
57+ | { type: "SET_ACTIVE_TAB"; payload: string }
58+ | { type: "REORDER_TABS"; payload: { fromIndex: number; toIndex: number } };
web-ui/tsconfig.app.tsbuildinfoadded+1−0View file
@@ -0,0 +1 @@
1+{"root":["./src/App.tsx","./src/main.tsx","./src/types.ts","./src/vite-env.d.ts","./src/components/BenchmarkTable.tsx","./src/components/TagFilter.tsx","./src/components/benchmark/charts/BenchmarkCharts.tsx","./src/components/benchmark/export/csvExport.ts","./src/components/benchmark/table/BenchmarkTable.tsx","./src/components/benchmark/table/columns.tsx","./src/components/benchmark/utils/formatters.ts","./src/components/dataset/TimeseriesView.tsx","./src/components/dataset/TimeseriesViewWorker.ts","./src/components/dataset/WorkerTypes.ts","./src/components/dataset/timeseriesViewReducer.ts","./src/hooks/TimeseriesDataClient.ts","./src/hooks/useMarkdownContent.ts","./src/hooks/useMarkdownPosts.ts","./src/hooks/useTagFilter.ts","./src/hooks/useTimeseriesData.ts","./src/hooks/useTimeseriesDataClient.ts","./src/pages/About.tsx","./src/pages/Algorithm.tsx","./src/pages/Algorithms.tsx","./src/pages/Dataset.tsx","./src/pages/Datasets.tsx","./src/pages/Home.tsx"],"version":"5.6.3"}
\ No newline at end of file