|
1 | 1 | {% extends "browse/base_generic.html" %} {% block title %}Focal Plane |
2 | 2 | UI{%endblock %} {% block body %} |
3 | | -<h1>Focal Plane UI</h1> |
4 | | -<p>This page displays LiteBIRD Focal Plane interactive visualization tool.</p> |
5 | | - |
6 | | -<div id="plotly-div"></div> |
7 | | - |
8 | | -<script src="https://cdn.plot.ly/plotly-latest.min.js"></script> |
9 | | - |
10 | | -<body> |
11 | | - <div id="myPlot" style="width: 100%; height: 100%"></div> |
12 | | - |
13 | | - <script> |
14 | | - document.addEventListener('DOMContentLoaded', function() { |
15 | | - const xValues = {{ x_values|safe }}; |
16 | | - const yValues = {{ y_values|safe }}; |
17 | | - const uuids = {{ uuids|safe }}; |
18 | | - const channel = {{ channel|safe }}; |
19 | | - const size = {{ size|safe }}; |
20 | | - const pol = {{ pol|safe }}; |
21 | | - const colors = channel.map(val => |
22 | | - val.slice(0, 2) == "L1" ? 'blue' : |
23 | | - val.slice(0, 2) == "L2" ? 'orange' : |
24 | | - val.slice(0, 2) == "M1" ? 'red' : |
25 | | - val.slice(0, 2) == "M2" ? 'black' : |
26 | | - val.slice(0, 2) == "H1" ? 'purple' : |
27 | | - val.slice(0, 2) == "H2" ? 'pink' : |
28 | | - 'gray' |
29 | | - ); |
30 | | - const pols = pol.map(val => |
31 | | - val == 0 ? 'circle-cross': |
32 | | - val == 90 ? 'circle-cross' : |
33 | | - val == 45 ? 'circle-x' : |
34 | | - val == 135 ? 'circle-x' : |
35 | | - 27 |
36 | | - ); |
37 | | - |
38 | | - |
39 | | - |
40 | | - const data = [ |
41 | | - { |
42 | | - x: xValues, |
43 | | - y: yValues, |
44 | | - type: 'scatter', |
45 | | - mode: 'markers', |
46 | | - marker: { color: colors, symbol: pols, size: size}, |
47 | | - customdata: uuids, |
48 | | - } |
49 | | - ]; |
50 | | - const layout = { |
51 | | - xaxis: { title: 'X_sky' }, |
52 | | - yaxis: { title: 'Y_sky' }, |
53 | | - margin: { t: 8, b: 40, l: 40, r: 8 }, |
54 | | - autosize: true |
55 | | - }; |
56 | | - |
57 | | - Plotly.newPlot('myPlot', data, layout); |
58 | | - |
59 | | - const plotDiv = document.getElementById('myPlot'); |
60 | | - plotDiv.on('plotly_click', function(data) { |
61 | | - |
62 | | - const point = data.points[0]; |
63 | | - if (point) { |
64 | | - const this_uuid = point.customdata; |
65 | | - |
66 | | - if (this_uuid) { |
67 | | - const url = `http://127.0.0.1:8000/browse/data_files/${this_uuid}`; |
68 | | - window.open(url, '_blank'); |
69 | | - } |
| 3 | +<!--<h1>Focal Plane UI</h1> |
| 4 | +<p>This page displays a LiteBIRD Focal Plane interactive visualization tool.</p>--> |
| 5 | + |
| 6 | +<!-- Container for the two-column layout --> |
| 7 | +<div style="display: flex; width: 100%"> |
| 8 | + <!-- Left Column --> |
| 9 | + <div |
| 10 | + style=" |
| 11 | + flex: 1; |
| 12 | + padding: 10px; |
| 13 | + background-color: #f8f9fa; |
| 14 | + border: 1px solid #dee2e6; |
| 15 | + " |
| 16 | + > |
| 17 | + <!-- Container for the Plotly plot --> |
| 18 | + <div id="myPlot" style="width: 150%; height: 500px"></div> |
| 19 | + |
| 20 | + <!-- Load Plotly.js from CDN --> |
| 21 | + <script src="https://cdn.plot.ly/plotly-latest.min.js"></script> |
| 22 | + |
| 23 | + <!-- JavaScript to create the plot --> |
| 24 | + <script> |
| 25 | + document.addEventListener('DOMContentLoaded', function() { |
| 26 | + const xValues = {{ x_values|safe }}; |
| 27 | + const yValues = {{ y_values|safe }}; |
| 28 | + const uuids = {{ uuids|safe }}; |
| 29 | + const channel = {{ channel|safe }}; |
| 30 | + const size = {{ size|safe }}; |
| 31 | + const pol = {{ pol|safe }}; |
| 32 | + const colors = channel.map(val => |
| 33 | + val.slice(0, 2) == "L1" ? 'blue' : |
| 34 | + val.slice(0, 2) == "L2" ? 'orange' : |
| 35 | + val.slice(0, 2) == "M1" ? 'red' : |
| 36 | + val.slice(0, 2) == "M2" ? 'black' : |
| 37 | + val.slice(0, 2) == "H1" ? 'purple' : |
| 38 | + val.slice(0, 2) == "H2" ? 'pink' : |
| 39 | + 'gray' |
| 40 | + ); |
| 41 | + const pols = pol.map(val => |
| 42 | + val == 0 ? 'circle-cross': |
| 43 | + val == 90 ? 'circle-cross' : |
| 44 | + val == 45 ? 'circle-x' : |
| 45 | + val == 135 ? 'circle-x' : |
| 46 | + 27 |
| 47 | + ); |
| 48 | + |
| 49 | + |
| 50 | + const data = [ |
| 51 | + { |
| 52 | + x: xValues, |
| 53 | + y: yValues, |
| 54 | + type: 'scatter', |
| 55 | + mode: 'markers', |
| 56 | + marker: { color: colors, symbol: pols, size: size}, |
| 57 | + customdata: uuids |
| 58 | + } |
| 59 | + ]; |
| 60 | + const layout = { |
| 61 | + xaxis: { title: 'X_sky' }, |
| 62 | + yaxis: { title: 'Y_sky' }, |
| 63 | + margin: { t: 8, b: 40, l: 40, r: 8 }, |
| 64 | + autosize: true, |
| 65 | + }; |
| 66 | + |
| 67 | + const config = { |
| 68 | + |
| 69 | + doubleClickDelay: 0, |
| 70 | + responsive: false |
| 71 | + }; |
| 72 | + |
| 73 | + Plotly.newPlot('myPlot', data, layout, config) |
| 74 | + |
| 75 | + const plotDiv = document.getElementById('myPlot'); |
| 76 | + |
| 77 | + plotDiv.on('plotly_click', function(data) { |
| 78 | + const point = data.points[0]; |
| 79 | + if (point) { |
| 80 | + const this_uuid = point.customdata; |
| 81 | + |
| 82 | + if (this_uuid) { |
| 83 | + const url = new URL("{% url 'handle-plot-click' %}", window.location.origin); |
| 84 | + url.searchParams.append('uuid', this_uuid); |
| 85 | + |
| 86 | + fetch(url, { |
| 87 | + headers:{ |
| 88 | + 'Accept': 'application/json', |
| 89 | + 'X-Requested-With': 'XMLHttpRequest', |
| 90 | + }, |
| 91 | + }) |
| 92 | + .then(response => response.json()) |
| 93 | + .then(responseData => { |
| 94 | + const paramsElement = document.getElementById('params'); |
| 95 | + |
| 96 | + // Store data globally |
| 97 | + window.detectorData = responseData; |
| 98 | + |
| 99 | + // Create release selector and buttons |
| 100 | + let buttonsHTML = '<h3>Select a detector:</h3>'; |
| 101 | + |
| 102 | + // Add release selectbox with warning |
| 103 | + buttonsHTML += '<div style="margin-bottom: 15px; padding: 12px; background-color: #fff3cd; border: 1px solid #ffc107; border-radius: 4px;">'; |
| 104 | + buttonsHTML += '<label for="releaseSelect" style="margin-right: 10px; font-weight: bold;">Release (required to copy path):</label>'; |
| 105 | + buttonsHTML += '<select id="releaseSelect" style="padding: 6px 10px; border-radius: 4px; border: 1px solid #ccc;">'; |
| 106 | + buttonsHTML += '<option value="">-- Select a release --</option>'; |
| 107 | + buttonsHTML += '<option value="IMo_vPostKDP2">v2.1 (PostKDP2 Fixed Feb 2026)</option>'; |
| 108 | + buttonsHTML += '</select>'; |
| 109 | + buttonsHTML += '</div>'; |
| 110 | + |
| 111 | + buttonsHTML += '<div style="display: flex; gap: 10px; flex-wrap: wrap;">'; |
| 112 | + |
| 113 | + Object.keys(responseData).forEach(uuid => { |
| 114 | + // Get the detector name from the metadata |
| 115 | + const metadata = responseData[uuid]; |
| 116 | + const detectorName = metadata.name || metadata.detector || metadata.id || uuid.substring(0, 8); |
| 117 | + |
| 118 | + buttonsHTML += `<div style="display: flex; gap: 5px; align-items: center;"> |
| 119 | + <button onclick="openDetectorInfo('${uuid}')" style="padding: 8px 12px; cursor: pointer; background-color: #007bff; color: white; border: none; border-radius: 4px;"> |
| 120 | + ${detectorName} |
| 121 | + </button> |
| 122 | + <button onclick="copyToClipboard('${uuid}')" style="padding: 8px 10px; cursor: pointer; background-color: #6c757d; color: white; border: none; border-radius: 4px; font-size: 14px;" title="Copy UUID"> |
| 123 | + 📋 |
| 124 | + </button> |
| 125 | + </div>`; |
| 126 | + }); |
| 127 | + |
| 128 | + |
| 129 | + buttonsHTML += '</div>'; |
| 130 | + buttonsHTML += '<div id="dataDisplay" style="margin-top: 20px;"></div>'; |
| 131 | + |
| 132 | + paramsElement.innerHTML = buttonsHTML; |
| 133 | + }) |
| 134 | + |
| 135 | + .catch(error => console.error('Error:', error)) |
| 136 | + } |
| 137 | + } |
| 138 | + }); |
| 139 | + |
| 140 | + |
| 141 | + |
| 142 | + }); |
| 143 | + function openDetectorInfo(uuid) { |
| 144 | + const url = `http://127.0.0.1:8000/browse/data_files/${uuid}`; |
| 145 | + window.open(url, '_blank'); |
70 | 146 | } |
71 | | - }); |
72 | | - }); |
73 | | - </script> |
74 | | -</body> |
| 147 | + |
| 148 | + function copyToClipboard(uuid) { |
| 149 | + // Get the selected release |
| 150 | + const releaseSelect = document.getElementById('releaseSelect'); |
| 151 | + const selectedRelease = releaseSelect ? releaseSelect.value : ''; |
| 152 | + |
| 153 | + // Check if a release is selected |
| 154 | + if (!selectedRelease) { |
| 155 | + alert('⚠️ Please select a release before copying.'); |
| 156 | + return; |
| 157 | + } |
| 158 | + |
| 159 | + const url = new URL("{% url 'get-full-path' %}", window.location.origin); |
| 160 | + url.searchParams.append('uuid', uuid); |
| 161 | + |
| 162 | + fetch(url, { |
| 163 | + headers:{ |
| 164 | + 'Accept': 'application/json', |
| 165 | + 'X-Requested-With': 'XMLHttpRequest', |
| 166 | + }, |
| 167 | + }) |
| 168 | + .then(response => response.json()) |
| 169 | + .then(responseData => { |
| 170 | + // Append release to the path |
| 171 | + const fullPath = `${responseData}/${selectedRelease}`; |
| 172 | + |
| 173 | + navigator.clipboard.writeText(fullPath).then(() => { |
| 174 | + alert('Full path copied to clipboard!'); |
| 175 | + }).catch(err => { |
| 176 | + console.error('Failed to copy:', err); |
| 177 | + }); |
| 178 | + }) |
| 179 | + |
| 180 | + .catch(error => console.error('Error:', error)) |
| 181 | + } |
| 182 | + </script> |
| 183 | + </div> |
| 184 | + |
| 185 | + <!-- Right Column --> |
| 186 | + <div |
| 187 | + style=" |
| 188 | + flex: 1; |
| 189 | + padding: 10px; |
| 190 | + background-color: #f8f9fa; |
| 191 | + border: 1px solid #dee2e6; |
| 192 | + margin-left: 0; |
| 193 | + margin-right: 0; |
| 194 | + " |
| 195 | + > |
| 196 | + <p id="params">Select a pixel on the plot.</p> |
| 197 | + </div> |
| 198 | +</div> |
75 | 199 | {% endblock %} |
0 commit comments