/ concept-collection / ephys_compression_tests
Sign in
concept-collection / ephys_compression_tests
ephys_compression_tests / web-ui / src / components / benchmark / charts / BenchmarkCharts.tsx
253 lines · 8.1 KBCodeBlameHistory
552a4baadd web-uiJeremy Magland 1import { useState } from "react";
2import Plot from "react-plotly.js";
4interface BenchmarkBarChartProps {
5 title: string;
6 data: ChartData[];
7 dataKey: keyof Pick<
8 ChartData,
721e8d8error metricJeremy Magland 9 "compression_ratio" | "encode_speed" | "decode_speed" | "rmse" | "max_error"
552a4baadd web-uiJeremy Magland 10 >;
11 color: string;
12 xAxisTitle: string;
13 normalize?: boolean;
16function BenchmarkBarChart({
17 title,
18 data,
19 dataKey,
20 color,
21 xAxisTitle,
22 normalize,
23}: BenchmarkBarChartProps) {
24 const normalizedData =
25 normalize && dataKey === "compression_ratio"
26 ? data.map((d) => ({
27 ...d,
28 compression_ratio: d.reference_compression_ratio
29 ? d.compression_ratio / d.reference_compression_ratio
30 : d.compression_ratio,
31 reference_compression_ratio: d.reference_compression_ratio ? 1 : null,
32 }))
33 : data;
35 return (
36 <div style={{ margin: "0 20px 20px 0" }}>
37 <h3 style={{ marginBottom: "10px" }}>{title}</h3>
38 <Plot
39 data={[
40 {
41 type: "bar",
42 orientation: "h",
43 y: normalizedData.map((d) => d.algorithmOrDataset),
5be77d1dev lossyJeremy Magland 44 x: normalizedData.map((d) => {
45 const value = d[dataKey];
46 return value !== undefined ? value : 0;
47 }),
552a4baadd web-uiJeremy Magland 48 marker: { color },
49 name: title,
50 hovertemplate:
51 normalize && dataKey === "compression_ratio"
52 ? "%{x:.3f}×<extra></extra>"
53 : "%{x:.2f}<extra></extra>",
54 },
55 ...(dataKey === "compression_ratio" &&
56 normalizedData.some((d) => d.reference_compression_ratio !== null)
57 ? [
58 ...normalizedData
59 .filter((d) => d.reference_compression_ratio !== null)
60 .flatMap((d) => [
61 {
62 type: "scatter" as const,
63 mode: "lines" as const,
64 y: [d.algorithmOrDataset, d.algorithmOrDataset],
65 x: [0, d.reference_compression_ratio],
66 line: { color, width: 1 },
67 showlegend: false,
68 hoverinfo: "skip" as const,
69 },
70 {
71 type: "scatter" as const,
72 mode: "markers" as const,
73 y: [d.algorithmOrDataset],
74 x: [d.reference_compression_ratio],
75 marker: { color: "#aaaaaa", size: 8 },
76 name: "Best Compression",
77 hovertemplate: normalize
78 ? "Best: 1.000×<extra></extra>"
79 : "Best: %{x:.2f}<extra></extra>",
80 showlegend:
81 d.algorithmOrDataset ===
82 normalizedData[0].algorithmOrDataset,
83 },
84 ]),
85 ]
86 : []),
87 ]}
88 layout={{
89 width: 700,
90 height: Math.max(300, data.length * 23 + 40),
91 margin: { t: 5, r: 30, l: 200, b: 30 },
92 xaxis: { title: xAxisTitle },
5be77d1dev lossyJeremy Magland 93 yaxis: {
94 automargin: true,
95 ticksuffix: " ",
96 tickmode: "array",
97 tickvals: normalizedData.map((d) => d.algorithmOrDataset),
98 ticktext: normalizedData.map((d) =>
99 d.tags.includes("lossy")
100 ? `<span style="color: red;">${d.algorithmOrDataset}*</span>`
101 : d.algorithmOrDataset
102 ),
103 },
552a4baadd web-uiJeremy Magland 104 dragmode: false,
105 }}
106 config={{ displayModeBar: false }}
107 />
108 </div>
109 );
112interface ChartData {
113 algorithmOrDataset: string;
114 compression_ratio: number;
115 reference_compression_ratio: number | null; // the highest compression ratio for the dataset (if algorithmOrDataset is a dataset)
116 encode_speed: number;
117 decode_speed: number;
5be77d1dev lossyJeremy Magland 118 rmse?: number;
721e8d8error metricJeremy Magland 119 max_error?: number;
5be77d1dev lossyJeremy Magland 120 tags: string[];
552a4baadd web-uiJeremy Magland 121}
123interface BenchmarkChartsProps {
124 chartData: ChartData[];
125 showSortByCompressionRatio?: boolean;
126 showNormalizeByReference?: boolean;
129export function BenchmarkCharts({
130 chartData,
131 showSortByCompressionRatio,
132 showNormalizeByReference,
133}: BenchmarkChartsProps) {
134 const [sortByRatio, setSortByRatio] = useState(
135 showSortByCompressionRatio ? true : false,
136 );
137 const [normalize, setNormalize] = useState(false);
5be77d1dev lossyJeremy Magland 138 const [showLossyAlgs, setShowLossyAlgs] = useState(true);
721e8d8error metricJeremy Magland 139 const [errorMetric, setErrorMetric] = useState<"rmse" | "max_error">("rmse");
552a4baadd web-uiJeremy Magland 140
141 if (!chartData.length) return null;
5be77d1dev lossyJeremy Magland 143 // Filter data based on showLossyAlgs
144 // If showLossyAlgs is true, show all algorithms (both lossy and lossless)
145 // If showLossyAlgs is false, only show lossless algorithms
146 const filteredData = showLossyAlgs
147 ? chartData
148 : chartData.filter((d) => !d.tags.includes("lossy"));
552a4baadd web-uiJeremy Magland 150 const sortedData = sortByRatio
5be77d1dev lossyJeremy Magland 151 ? [...filteredData].sort((a, b) => a.compression_ratio - b.compression_ratio)
152 : filteredData;
721e8d8error metricJeremy Magland 154 // For Error chart, only show lossy algorithms with error values
155 const lossyData = chartData.filter(
156 (d) => d.tags.includes("lossy") &&
157 (errorMetric === "rmse" ? d.rmse !== undefined : d.max_error !== undefined)
158 );
5be77d1dev lossyJeremy Magland 159 const sortedLossyData = sortByRatio
160 ? [...lossyData].sort((a, b) => a.compression_ratio - b.compression_ratio)
161 : lossyData;
552a4baadd web-uiJeremy Magland 162
163 return (
164 <div>
165 {showSortByCompressionRatio && (
5be77d1dev lossyJeremy Magland 166 <div style={{ marginBottom: "10px", display: "flex", gap: "16px" }}>
552a4baadd web-uiJeremy Magland 167 <label style={{ display: "flex", alignItems: "center", gap: "8px" }}>
168 <input
169 type="checkbox"
170 checked={sortByRatio}
171 onChange={(e) => setSortByRatio(e.target.checked)}
172 />
173 Sort by compression ratio
174 </label>
5be77d1dev lossyJeremy Magland 175 <label style={{ display: "flex", alignItems: "center", gap: "8px" }}>
176 <input
177 type="checkbox"
178 checked={showLossyAlgs}
179 onChange={(e) => setShowLossyAlgs(e.target.checked)}
180 />
181 Show lossy algs
182 </label>
552a4baadd web-uiJeremy Magland 183 </div>
184 )}
185 {showNormalizeByReference && (
186 <div style={{ marginBottom: "10px" }}>
187 <label style={{ display: "flex", alignItems: "center", gap: "8px" }}>
188 <input
189 type="checkbox"
190 checked={normalize}
191 onChange={(e) => setNormalize(e.target.checked)}
192 />
193 Normalize to best compression
194 </label>
195 </div>
196 )}
197 <div
198 style={{
199 display: "flex",
200 flexWrap: "wrap",
201 gap: "20px",
202 }}
203 >
204 <BenchmarkBarChart
205 title="Compression Ratio"
206 data={sortedData}
207 dataKey="compression_ratio"
208 color="#8884d8"
209 xAxisTitle={normalize ? "Fraction of Best Compression" : "Ratio"}
210 normalize={normalize}
211 />
212 <BenchmarkBarChart
213 title="Encode Speed (MB/s)"
214 data={sortedData}
215 dataKey="encode_speed"
216 color="#82ca9d"
217 xAxisTitle="MB/s"
218 />
219 <BenchmarkBarChart
220 title="Decode Speed (MB/s)"
221 data={sortedData}
222 dataKey="decode_speed"
223 color="#ff7300"
224 xAxisTitle="MB/s"
225 />
5be77d1dev lossyJeremy Magland 226 {sortedLossyData.length > 0 && (
721e8d8error metricJeremy Magland 227 <div>
228 <div style={{ marginBottom: "10px" }}>
229 <label style={{ display: "flex", alignItems: "center", gap: "8px" }}>
230 Error Metric:
231 <select
232 value={errorMetric}
233 onChange={(e) => setErrorMetric(e.target.value as "rmse" | "max_error")}
234 style={{ marginLeft: "8px", padding: "4px 8px" }}
235 >
236 <option value="rmse">RMSE</option>
237 <option value="max_error">Maximum</option>
238 </select>
239 </label>
240 </div>
241 <BenchmarkBarChart
242 title="Error (Lossy Algs)"
243 data={sortedLossyData}
244 dataKey={errorMetric}
245 color="#d62728"
246 xAxisTitle={errorMetric === "rmse" ? "RMSE" : "Maximum Error"}
247 />
248 </div>
5be77d1dev lossyJeremy Magland 249 )}
552a4baadd web-uiJeremy Magland 250 </div>
251 </div>
252 );
moveopenescclose