|
1 | 1 | /* ── PIR Engineering Notes — charts.js ──────────────────────────────── |
2 | 2 | * |
3 | 3 | * Main visualization engine for the "Benching PIR (WIP)" site. |
4 | | - * Renders all Plotly charts from ../data/reported.json (v2 schema). |
| 4 | + * Renders all Plotly charts from data/reported.json (v2 schema). |
5 | 5 | * |
6 | 6 | * ── Architecture Overview ────────────────────────────────────────── |
7 | 7 | * |
|
175 | 175 | return STORAGE_NOTE_IDS[s.id] ? STORAGE_NOTE_TEXT : ''; |
176 | 176 | } |
177 | 177 |
|
| 178 | + // Returns a Plotly annotation explaining † (Tier 2) and * (Tier 3) badges, |
| 179 | + // or null if all items are Tier 1. |
| 180 | + function tierBadgeLegend(items, t) { |
| 181 | + var has2 = items.some(function (s) { return s.data_tier === 2; }); |
| 182 | + var has3 = items.some(function (s) { return s.data_tier === 3; }); |
| 183 | + if (!has2 && !has3) return null; |
| 184 | + var parts = []; |
| 185 | + if (has2) parts.push('\u2020 from figures/analytics'); |
| 186 | + if (has3) parts.push('* from asymptotics'); |
| 187 | + return { |
| 188 | + text: '<i>' + parts.join(' ') + '</i>', |
| 189 | + xref: 'paper', yref: 'paper', x: 0, y: 0, yanchor: 'top', yshift: -60, |
| 190 | + showarrow: false, font: { size: 11, color: t.muted } |
| 191 | + }; |
| 192 | + } |
| 193 | + |
178 | 194 | function isDark() { |
179 | 195 | return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches; |
180 | 196 | } |
|
893 | 909 | hoverinfo: 'text' |
894 | 910 | }); |
895 | 911 |
|
896 | | - Plotly.newPlot(el, traces, baseLayout('Server Time (ms)', { |
| 912 | + var layout = baseLayout('Server Time (ms)', { |
897 | 913 | yaxis: { autorange: 'reversed', tickfont: { size: 11 }, gridcolor: t.grid }, |
898 | 914 | xaxis: { title: { text: 'Server Time (ms)', standoff: 20 }, type: 'log', gridcolor: t.grid }, |
899 | 915 | margin: { l: barLeftMargin(), r: 60, t: 48, b: 48 }, |
900 | 916 | height: Math.max(350, items.length * 26 + 120) |
901 | | - }), plotConfig()); |
| 917 | + }); |
| 918 | + var badge = tierBadgeLegend(items, t); |
| 919 | + if (badge) { layout.annotations = (layout.annotations || []).concat(badge); layout.margin.b = 80; } |
| 920 | + Plotly.newPlot(el, traces, layout, plotConfig()); |
902 | 921 | } |
903 | 922 |
|
904 | 923 | // ── 4. Client Compute Bars ─────────────────────────────── |
|
912 | 931 | var items = data.filter(function (s) { return isPos(getVal(s, 'client_time_ms')); }); |
913 | 932 | items.sort(function (a, b) { return getVal(a, 'client_time_ms') - getVal(b, 'client_time_ms'); }); |
914 | 933 |
|
915 | | - Plotly.newPlot(el, [{ |
| 934 | + var traces = [{ |
916 | 935 | y: items.map(function (s) { return (TIER_BADGE[s.data_tier] ? TIER_BADGE[s.data_tier] + ' ' : '') + consolidatedName(s); }), |
917 | 936 | x: items.map(function (s) { return getVal(s, 'client_time_ms'); }), |
918 | 937 | type: 'bar', orientation: 'h', |
|
928 | 947 | return consolidatedName(s) + (entrySizeLabel(s) ? ' (' + entrySizeLabel(s) + ' entries)' : '') + '<br>Client Time: ' + formatNum(getVal(s, 'client_time_ms')) + ' ms<br>Source: ' + (s.source_ref || 'N/A') + consolidatedHoverSuffix(s); |
929 | 948 | }), |
930 | 949 | hoverinfo: 'text' |
931 | | - }], baseLayout('Client Computation Time (ms)', { |
| 950 | + }]; |
| 951 | + var layout = baseLayout('Client Computation Time (ms)', { |
932 | 952 | yaxis: { tickfont: { size: 11 }, gridcolor: t.grid }, |
933 | 953 | xaxis: { title: 'Client Time (ms)', type: 'log', gridcolor: t.grid }, |
934 | 954 | margin: { l: barLeftMargin(), r: 60, t: 48, b: 48 }, |
935 | 955 | height: Math.max(300, items.length * 30 + 100) |
936 | | - }), plotConfig()); |
| 956 | + }); |
| 957 | + var badge = tierBadgeLegend(items, t); |
| 958 | + if (badge) { layout.annotations = (layout.annotations || []).concat(badge); layout.margin.b = 80; } |
| 959 | + Plotly.newPlot(el, traces, layout, plotConfig()); |
937 | 960 | } |
938 | 961 |
|
939 | 962 | // ── 5. Offline & Storage Bar Chart ─────────────────── |
|
1003 | 1026 | hoverinfo: 'text' |
1004 | 1027 | }); |
1005 | 1028 |
|
1006 | | - Plotly.newPlot(el, traces, baseLayout('Offline & Client Storage', { |
| 1029 | + var layout = baseLayout('Offline & Client Storage', { |
1007 | 1030 | barmode: 'group', |
1008 | 1031 | xaxis: { title: 'Size (MB)', gridcolor: t.grid }, |
1009 | 1032 | yaxis: { tickfont: { size: 11 }, gridcolor: t.grid }, |
1010 | 1033 | legend: { orientation: 'h', x: 0, y: -0.12, font: { size: 11 } }, |
1011 | 1034 | margin: { l: barLeftMargin(), r: 60, t: 48, b: 100 }, |
1012 | 1035 | height: Math.max(400, items.length * 40 + 160) |
1013 | | - }), plotConfig()); |
| 1036 | + }); |
| 1037 | + var badge = tierBadgeLegend(items, t); |
| 1038 | + if (badge) { badge.yshift = -70; layout.annotations = (layout.annotations || []).concat(badge); } |
| 1039 | + Plotly.newPlot(el, traces, layout, plotConfig()); |
1014 | 1040 | } |
1015 | 1041 |
|
1016 | 1042 | // ── 5b. Preprocessing Time Bar Chart ────────────────── |
|
1106 | 1132 | var seenGroups = {}; |
1107 | 1133 | items.forEach(function (s) { seenGroups[s.group] = true; }); |
1108 | 1134 |
|
1109 | | - Plotly.newPlot(el, traces, baseLayout('Preprocessing / Offline Computation Time', { |
| 1135 | + var layout = baseLayout('Preprocessing / Offline Computation Time', { |
1110 | 1136 | barmode: 'group', |
1111 | 1137 | showlegend: false, |
1112 | 1138 | yaxis: { tickfont: { size: 11 }, gridcolor: t.grid }, |
1113 | 1139 | xaxis: { title: { text: 'Time (ms)', standoff: 20 }, type: 'log', gridcolor: t.grid }, |
1114 | 1140 | margin: { l: isMobile() ? 140 : 160, r: 80, t: 48, b: 50 }, |
1115 | 1141 | height: Math.max(300, items.length * 30 + 80) |
1116 | | - }), plotConfig()); |
| 1142 | + }); |
| 1143 | + var badge = tierBadgeLegend(items, t); |
| 1144 | + if (badge) { layout.annotations = (layout.annotations || []).concat(badge); layout.margin.b = 80; } |
| 1145 | + Plotly.newPlot(el, traces, layout, plotConfig()); |
1117 | 1146 |
|
1118 | 1147 | // HTML legend below the chart |
1119 | 1148 | var legendId = 'preproc-legend'; |
|
2458 | 2487 | var items = data.filter(function (s) { return isPos(getVal(s, 'rate')); }); |
2459 | 2488 | items.sort(function (a, b) { return getVal(b, 'rate') - getVal(a, 'rate'); }); |
2460 | 2489 |
|
2461 | | - Plotly.newPlot(el, [{ |
| 2490 | + var traces = [{ |
2462 | 2491 | y: items.map(function (s) { return (TIER_BADGE[s.data_tier] ? TIER_BADGE[s.data_tier] + ' ' : '') + consolidatedName(s); }), |
2463 | 2492 | x: items.map(function (s) { return getVal(s, 'rate'); }), |
2464 | 2493 | type: 'bar', orientation: 'h', |
|
2477 | 2506 | '<br>Source: ' + (s.source_ref || 'N/A') + consolidatedHoverSuffix(s); |
2478 | 2507 | }), |
2479 | 2508 | hoverinfo: 'text' |
2480 | | - }], baseLayout('Communication Rate (plaintext / ciphertext)', { |
| 2509 | + }]; |
| 2510 | + var layout = baseLayout('Communication Rate (plaintext / ciphertext)', { |
2481 | 2511 | yaxis: { autorange: 'reversed', tickfont: { size: 11 }, gridcolor: t.grid }, |
2482 | 2512 | xaxis: { title: { text: 'Rate (higher = better, 1.0 = optimal)', standoff: 20 }, range: [0, 1.05], gridcolor: t.grid }, |
2483 | 2513 | margin: { l: barLeftMargin(), r: 60, t: 48, b: 48 }, |
2484 | 2514 | height: Math.max(350, items.length * 30 + 120) |
2485 | | - }), plotConfig()); |
| 2515 | + }); |
| 2516 | + var badge = tierBadgeLegend(items, t); |
| 2517 | + if (badge) { layout.annotations = (layout.annotations || []).concat(badge); layout.margin.b = 80; } |
| 2518 | + Plotly.newPlot(el, traces, layout, plotConfig()); |
2486 | 2519 | } |
2487 | 2520 |
|
2488 | 2521 | // ── Misc: Amortized Offline Communication Bars ───────── |
|
2496 | 2529 | var items = data.filter(function (s) { return isPos(getVal(s, 'amortized_offline_comm_kb')); }); |
2497 | 2530 | items.sort(function (a, b) { return getVal(a, 'amortized_offline_comm_kb') - getVal(b, 'amortized_offline_comm_kb'); }); |
2498 | 2531 |
|
2499 | | - Plotly.newPlot(el, [{ |
| 2532 | + var traces = [{ |
2500 | 2533 | y: items.map(function (s) { return (TIER_BADGE[s.data_tier] ? TIER_BADGE[s.data_tier] + ' ' : '') + consolidatedName(s); }), |
2501 | 2534 | x: items.map(function (s) { return getVal(s, 'amortized_offline_comm_kb'); }), |
2502 | 2535 | type: 'bar', orientation: 'h', |
|
2515 | 2548 | '<br>Source: ' + (s.source_ref || 'N/A') + consolidatedHoverSuffix(s); |
2516 | 2549 | }), |
2517 | 2550 | hoverinfo: 'text' |
2518 | | - }], baseLayout('Amortized Offline Communication (KB/query)', { |
| 2551 | + }]; |
| 2552 | + var layout = baseLayout('Amortized Offline Communication (KB/query)', { |
2519 | 2553 | yaxis: { autorange: 'reversed', tickfont: { size: 11 }, gridcolor: t.grid }, |
2520 | 2554 | xaxis: { title: { text: 'Amortized Offline Comm (KB/query)', standoff: 20 }, type: 'log', gridcolor: t.grid }, |
2521 | 2555 | margin: { l: barLeftMargin(), r: 60, t: 48, b: 48 }, |
2522 | 2556 | height: Math.max(350, items.length * 30 + 120) |
2523 | | - }), plotConfig()); |
| 2557 | + }); |
| 2558 | + var badge = tierBadgeLegend(items, t); |
| 2559 | + if (badge) { layout.annotations = (layout.annotations || []).concat(badge); layout.margin.b = 80; } |
| 2560 | + Plotly.newPlot(el, traces, layout, plotConfig()); |
2524 | 2561 | } |
2525 | 2562 |
|
2526 | 2563 | // ── Misc: Amortized Offline Time Bars ────────────────── |
|
2534 | 2571 | var items = data.filter(function (s) { return isPos(getVal(s, 'amortized_offline_time_ms')); }); |
2535 | 2572 | items.sort(function (a, b) { return getVal(a, 'amortized_offline_time_ms') - getVal(b, 'amortized_offline_time_ms'); }); |
2536 | 2573 |
|
2537 | | - Plotly.newPlot(el, [{ |
| 2574 | + var traces = [{ |
2538 | 2575 | y: items.map(function (s) { return (TIER_BADGE[s.data_tier] ? TIER_BADGE[s.data_tier] + ' ' : '') + consolidatedName(s); }), |
2539 | 2576 | x: items.map(function (s) { return getVal(s, 'amortized_offline_time_ms'); }), |
2540 | 2577 | type: 'bar', orientation: 'h', |
|
2553 | 2590 | '<br>Source: ' + (s.source_ref || 'N/A') + consolidatedHoverSuffix(s); |
2554 | 2591 | }), |
2555 | 2592 | hoverinfo: 'text' |
2556 | | - }], baseLayout('Amortized Offline Time (ms/query)', { |
| 2593 | + }]; |
| 2594 | + var layout = baseLayout('Amortized Offline Time (ms/query)', { |
2557 | 2595 | yaxis: { autorange: 'reversed', tickfont: { size: 11 }, gridcolor: t.grid }, |
2558 | 2596 | xaxis: { title: { text: 'Amortized Offline Time (ms/query)', standoff: 20 }, type: 'log', gridcolor: t.grid }, |
2559 | 2597 | margin: { l: barLeftMargin(), r: 60, t: 48, b: 48 }, |
2560 | 2598 | height: Math.max(350, items.length * 30 + 120) |
2561 | | - }), plotConfig()); |
| 2599 | + }); |
| 2600 | + var badge = tierBadgeLegend(items, t); |
| 2601 | + if (badge) { layout.annotations = (layout.annotations || []).concat(badge); layout.margin.b = 80; } |
| 2602 | + Plotly.newPlot(el, traces, layout, plotConfig()); |
2562 | 2603 | } |
2563 | 2604 |
|
2564 | 2605 | var _activeDbTier = 'tiny'; |
|
2776 | 2817 | // Resolve data path: charts.js lives in reported/, data in data/ |
2777 | 2818 | var scriptEl = document.querySelector('script[src$="charts.js"]'); |
2778 | 2819 | var prefix = scriptEl ? scriptEl.getAttribute('src').replace('charts.js', '') : ''; |
2779 | | - fetch(prefix + '../data/reported.json') |
| 2820 | + fetch(prefix + 'data/reported.json') |
2780 | 2821 | .then(function (r) { return r.json(); }) |
2781 | 2822 | .then(function (raw) { |
2782 | 2823 | // clear loading indicators |
|
2806 | 2847 | .catch(function (err) { |
2807 | 2848 | console.error('Failed to load PIR data:', err); |
2808 | 2849 | var el = document.getElementById('chart-heatmap'); |
2809 | | - if (el) el.innerHTML = '<p style="color:red;padding:20px">Failed to load data. Ensure data/reported.json exists.</p>'; |
| 2850 | + if (el) el.innerHTML = '<p style="color:red;padding:20px">Failed to load data. Ensure reported/data/reported.json exists.</p>'; |
2810 | 2851 | }); |
2811 | 2852 | } |
2812 | 2853 |
|
|
0 commit comments