1import { useState } from "react";
2import Plot from "react-plotly.js";
4interface BenchmarkBarChartProps {
5 title: string;
6 data: ChartData[];
7 dataKey: keyof Pick<
8 ChartData,
9 "compression_ratio" | "encode_speed" | "decode_speed" | "rmse"
10 >;
11 color: string;
12 xAxisTitle: string;
13 normalize?: boolean;
14}
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),
44 x: normalizedData.map((d) => {
45 const value = d[dataKey];
46 return value !== undefined ? value : 0;
47 }),
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 },
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 },
104 dragmode: false,
105 }}
106 config={{ displayModeBar: false }}
107 />
108 </div>
109 );
110}
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;
118 rmse?: number;
119 tags: string[];
120}
122interface BenchmarkChartsProps {
123 chartData: ChartData[];
124 showSortByCompressionRatio?: boolean;
125 showNormalizeByReference?: boolean;
126}
128export function BenchmarkCharts({
129 chartData,
130 showSortByCompressionRatio,
131 showNormalizeByReference,
132}: BenchmarkChartsProps) {
133 const [sortByRatio, setSortByRatio] = useState(
134 showSortByCompressionRatio ? true : false,
135 );
136 const [normalize, setNormalize] = useState(false);
137 const [showLossyAlgs, setShowLossyAlgs] = useState(true);
139 if (!chartData.length) return null;
141 // Filter data based on showLossyAlgs
142 // If showLossyAlgs is true, show all algorithms (both lossy and lossless)
143 // If showLossyAlgs is false, only show lossless algorithms
144 const filteredData = showLossyAlgs
145 ? chartData
146 : chartData.filter((d) => !d.tags.includes("lossy"));
148 const sortedData = sortByRatio
149 ? [...filteredData].sort((a, b) => a.compression_ratio - b.compression_ratio)
150 : filteredData;
152 // For RMSE chart, only show lossy algorithms with rmse values
153 const lossyData = chartData.filter((d) => d.tags.includes("lossy") && d.rmse !== undefined);
154 const sortedLossyData = sortByRatio
155 ? [...lossyData].sort((a, b) => a.compression_ratio - b.compression_ratio)
156 : lossyData;
158 return (
159 <div>
160 {showSortByCompressionRatio && (
161 <div style={{ marginBottom: "10px", display: "flex", gap: "16px" }}>
162 <label style={{ display: "flex", alignItems: "center", gap: "8px" }}>
163 <input
164 type="checkbox"
165 checked={sortByRatio}
166 onChange={(e) => setSortByRatio(e.target.checked)}
167 />
168 Sort by compression ratio
169 </label>
170 <label style={{ display: "flex", alignItems: "center", gap: "8px" }}>
171 <input
172 type="checkbox"
173 checked={showLossyAlgs}
174 onChange={(e) => setShowLossyAlgs(e.target.checked)}
175 />
176 Show lossy algs
177 </label>
178 </div>
179 )}
180 {showNormalizeByReference && (
181 <div style={{ marginBottom: "10px" }}>
182 <label style={{ display: "flex", alignItems: "center", gap: "8px" }}>
183 <input
184 type="checkbox"
185 checked={normalize}
186 onChange={(e) => setNormalize(e.target.checked)}
187 />
188 Normalize to best compression
189 </label>
190 </div>
191 )}
192 <div
193 style={{
194 display: "flex",
195 flexWrap: "wrap",
196 gap: "20px",
197 }}
198 >
199 <BenchmarkBarChart
200 title="Compression Ratio"
201 data={sortedData}
202 dataKey="compression_ratio"
203 color="#8884d8"
204 xAxisTitle={normalize ? "Fraction of Best Compression" : "Ratio"}
205 normalize={normalize}
206 />
207 <BenchmarkBarChart
208 title="Encode Speed (MB/s)"
209 data={sortedData}
210 dataKey="encode_speed"
211 color="#82ca9d"
212 xAxisTitle="MB/s"
213 />
214 <BenchmarkBarChart
215 title="Decode Speed (MB/s)"
216 data={sortedData}
217 dataKey="decode_speed"
218 color="#ff7300"
219 xAxisTitle="MB/s"
220 />
221 {sortedLossyData.length > 0 && (
222 <BenchmarkBarChart
223 title="RMSE (Lossy Algs)"
224 data={sortedLossyData}
225 dataKey="rmse"
226 color="#d62728"
227 xAxisTitle="RMSE"
228 />
229 )}
230 </div>
231 </div>
232 );
233}