1import { useEffect, useState, useMemo } from 'react';
2import axios from 'axios';
3import Plot from 'react-plotly.js';
4import {
5 createColumnHelper,
6 flexRender,
7 getCoreRowModel,
8 useReactTable,
9 getSortedRowModel,
10} from '@tanstack/react-table';
11import { BenchmarkResult } from '../types';
13const columnHelper = createColumnHelper<BenchmarkResult>();
15const formatNumber = (num: number, decimals = 2) => {
16 return new Intl.NumberFormat('en-US', {
17 minimumFractionDigits: decimals,
18 maximumFractionDigits: decimals,
19 }).format(num);
20};
22const formatSize = (bytes: number) => {
23 const mb = bytes / (1024 * 1024);
24 return `${formatNumber(mb)} MB`;
25};
27const columns = [
28 columnHelper.accessor('dataset', {
29 header: 'Dataset',
30 cell: info => info.getValue(),
31 }),
32 columnHelper.accessor('algorithm', {
33 header: 'Algorithm',
34 cell: info => info.getValue(),
35 }),
36 columnHelper.accessor('compression_ratio', {
37 header: 'Compression Ratio',
38 cell: info => `${formatNumber(info.getValue())}x`,
39 sortingFn: (rowA, rowB) => {
40 const a = rowA.original.compression_ratio;
41 const b = rowB.original.compression_ratio;
42 return a - b;
43 },
44 }),
45 columnHelper.accessor('encode_time', {
46 header: 'Encode Time (s)',
47 cell: info => formatNumber(info.getValue(), 4),
48 sortingFn: (rowA, rowB) => {
49 const a = rowA.original.encode_time;
50 const b = rowB.original.encode_time;
51 return a - b;
52 },
53 }),
54 columnHelper.accessor('decode_time', {
55 header: 'Decode Time (s)',
56 cell: info => formatNumber(info.getValue(), 4),
57 sortingFn: (rowA, rowB) => {
58 const a = rowA.original.decode_time;
59 const b = rowB.original.decode_time;
60 return a - b;
61 },
62 }),
63 columnHelper.accessor('encode_mb_per_sec', {
64 header: 'Encode Speed (MB/s)',
65 cell: info => formatNumber(info.getValue()),
66 sortingFn: (rowA, rowB) => {
67 const a = rowA.original.encode_mb_per_sec;
68 const b = rowB.original.encode_mb_per_sec;
69 return a - b;
70 },
71 }),
72 columnHelper.accessor('decode_mb_per_sec', {
73 header: 'Decode Speed (MB/s)',
74 cell: info => formatNumber(info.getValue()),
75 sortingFn: (rowA, rowB) => {
76 const a = rowA.original.decode_mb_per_sec;
77 const b = rowB.original.decode_mb_per_sec;
78 return a - b;
79 },
80 }),
81 columnHelper.accessor('original_size', {
82 header: 'Original Size',
83 cell: info => formatSize(info.getValue()),
84 sortingFn: (rowA, rowB) => {
85 const a = rowA.original.original_size;
86 const b = rowB.original.original_size;
87 return a - b;
88 },
89 }),
90 columnHelper.accessor('compressed_size', {
91 header: 'Compressed Size',
92 cell: info => formatSize(info.getValue()),
93 sortingFn: (rowA, rowB) => {
94 const a = rowA.original.compressed_size;
95 const b = rowB.original.compressed_size;
96 return a - b;
97 },
98 }),
99];
101export function BenchmarkTable() {
102 const [data, setData] = useState<BenchmarkResult[]>([]);
103 const [selectedDataset, setSelectedDataset] = useState<string>('');
104 const [availableDatasets, setAvailableDatasets] = useState<string[]>([]);
105 const [isLoading, setIsLoading] = useState(true);
106 const [error, setError] = useState<string | null>(null);
108 useEffect(() => {
109 const fetchData = async () => {
110 try {
111 setIsLoading(true);
112 setError(null);
113 const response = await axios.get(
114 'https://raw.githubusercontent.com/magland/zia/benchmark-results/benchmark_results/results.json'
115 );
116 const results = response.data.results;
117 setData(results);
118 // Extract unique dataset names with proper typing
119 const datasets = Array.from(new Set(results.map((result: BenchmarkResult) => result.dataset))).sort() as string[];
120 setAvailableDatasets(datasets);
121 } catch (error) {
122 const message = error instanceof Error ? error.message : 'Failed to fetch data';
123 setError(message);
124 console.error('Error fetching benchmark data:', error);
125 } finally {
126 setIsLoading(false);
127 }
128 };
130 fetchData();
131 }, []);
133 // Memoize filtered data to prevent unnecessary recalculations
134 const filteredData = useMemo(() => {
135 if (!selectedDataset) return data;
136 return data.filter(row => row.dataset === selectedDataset);
137 }, [data, selectedDataset]);
139 const table = useReactTable({
140 data: filteredData || [],
141 columns,
142 getCoreRowModel: getCoreRowModel(),
143 getSortedRowModel: getSortedRowModel(),
144 });
146 // Prepare data for bar charts when a dataset is selected
147 const chartData = useMemo(() => {
148 if (!selectedDataset) return [];
149 return data
150 .filter(row => row.dataset === selectedDataset)
151 .map(row => ({
152 algorithm: row.algorithm,
153 compression_ratio: row.compression_ratio,
154 encode_speed: row.encode_mb_per_sec,
155 decode_speed: row.decode_mb_per_sec
156 }));
157 }, [data, selectedDataset]);
159 if (isLoading) {
160 return <div>Loading benchmark data...</div>;
161 }
163 if (error) {
164 return <div>Error: {error}</div>;
165 }
167 return (
168 <div className="table-container">
169 <div style={{ marginBottom: '20px', display: 'flex', alignItems: 'center', gap: '10px' }}>
170 <label htmlFor="dataset-select">Filter by Dataset:</label>
171 <select
172 id="dataset-select"
173 value={selectedDataset}
174 onChange={(e) => setSelectedDataset(e.target.value)}
175 style={{
176 padding: '8px',
177 borderRadius: '4px',
178 border: '1px solid #ccc',
179 minWidth: '200px',
180 backgroundColor: '#fff'
181 }}
182 >
183 <option value="">All Datasets</option>
184 {availableDatasets.map(dataset => (
185 <option key={dataset} value={dataset}>
186 {dataset}
187 </option>
188 ))}
189 </select>
190 </div>
192 {selectedDataset && chartData.length > 0 && (
193 <div style={{ marginBottom: '30px' }}>
194 <div style={{ marginBottom: '20px' }}>
195 <h3 style={{ marginBottom: '10px' }}>Compression Ratio</h3>
196 <Plot
197 data={[{
198 type: 'bar',
199 x: chartData.map(d => d.algorithm),
200 y: chartData.map(d => d.compression_ratio),
201 marker: { color: '#8884d8' }
202 }]}
203 layout={{
204 width: 800,
205 height: 300,
206 margin: { t: 5, r: 30, l: 50, b: 30 },
207 yaxis: { title: 'Ratio' }
208 }}
209 config={{ displayModeBar: false }}
210 />
211 </div>
213 <div style={{ marginBottom: '20px' }}>
214 <h3 style={{ marginBottom: '10px' }}>Encode Speed (MB/s)</h3>
215 <Plot
216 data={[{
217 type: 'bar',
218 x: chartData.map(d => d.algorithm),
219 y: chartData.map(d => d.encode_speed),
220 marker: { color: '#82ca9d' }
221 }]}
222 layout={{
223 width: 800,
224 height: 300,
225 margin: { t: 5, r: 30, l: 50, b: 30 },
226 yaxis: { title: 'MB/s' }
227 }}
228 config={{ displayModeBar: false }}
229 />
230 </div>
232 <div style={{ marginBottom: '20px' }}>
233 <h3 style={{ marginBottom: '10px' }}>Decode Speed (MB/s)</h3>
234 <Plot
235 data={[{
236 type: 'bar',
237 x: chartData.map(d => d.algorithm),
238 y: chartData.map(d => d.decode_speed),
239 marker: { color: '#ff7300' }
240 }]}
241 layout={{
242 width: 800,
243 height: 300,
244 margin: { t: 5, r: 30, l: 50, b: 30 },
245 yaxis: { title: 'MB/s' }
246 }}
247 config={{ displayModeBar: false }}
248 />
249 </div>
250 </div>
251 )}
252 <table>
253 <thead>
254 {table.getHeaderGroups().map(headerGroup => (
255 <tr key={headerGroup.id}>
256 {headerGroup.headers.map(header => (
257 <th
258 key={header.id}
259 onClick={header.column.getToggleSortingHandler()}
260 style={{ cursor: 'pointer' }}
261 >
262 {flexRender(
263 header.column.columnDef.header,
264 header.getContext()
265 )}
266 {header.column.getIsSorted() && (
267 <span style={{ marginLeft: '4px' }}>
268 {header.column.getIsSorted() === 'asc' ? '↑' : '↓'}
269 </span>
270 )}
271 </th>
272 ))}
273 </tr>
274 ))}
275 </thead>
276 <tbody>
277 {table.getRowModel().rows.map(row => (
278 <tr key={row.id}>
279 {row.getVisibleCells().map(cell => (
280 <td key={cell.id}>
281 {flexRender(cell.column.columnDef.cell, cell.getContext())}
282 </td>
283 ))}
284 </tr>
285 ))}
286 </tbody>
287 </table>
288 </div>
289 );
290}