Commit 978a592e authored by Archit Tamarapu's avatar Archit Tamarapu
Browse files

add more plots and a plot dashboard to browse plots

parent f97ea578
Loading
Loading
Loading
Loading
+336 −0
Original line number Diff line number Diff line
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <title>Loudness Dashboard</title>
  <style>
    :root {
      --bg: #f7f8fa;
      --card: #ffffff;
      --text: #0f172a;
      --muted: #475569;
      --border: #e2e8f0;
      --header-bg: linear-gradient(120deg, #ecfeff, #f8fafc);
    }
    * { box-sizing: border-box; }
    body { margin: 0; font-family: ui-sans-serif, -apple-system, Segoe UI, Helvetica, Arial, sans-serif; color: var(--text); background: var(--bg); }
    header { padding: 16px 24px; border-bottom: 1px solid var(--border); background: var(--header-bg); }
    h1 { margin: 0; font-size: 22px; }
    main { padding: 16px 24px 28px; max-width: 1400px; margin: 0 auto; }
    .card { background: var(--card); border: 1px solid var(--border); border-radius: 12px; padding: 12px; margin-bottom: 14px; }
    .controls { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; margin-bottom: 12px; }
    label { font-weight: 600; }
    select { border: 1px solid var(--border); border-radius: 8px; padding: 6px 10px; font-size: 14px; min-width: 260px; background: var(--card); color: var(--text); }
    .plot-title { margin: 0 0 8px 0; font-size: 15px; color: var(--muted); }
    .image-wrap { width: 100%; border: 1px dashed var(--border); border-radius: 8px; padding: 8px; background: var(--card); }
    .zoom-bar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 8px; }
    .zoom-btn { border: 1px solid var(--border); border-radius: 6px; background: #fff; color: var(--text); cursor: pointer; padding: 4px 10px; }
    .zoom-label { color: var(--muted); font-size: 13px; min-width: 54px; }
    .viewport { width: 100%; max-height: 78vh; overflow: auto; border: 1px solid var(--border); border-radius: 6px; background: #fff; }
    .viewport img { display: block; width: auto; max-width: none; height: auto; }
    .empty { color: var(--muted); font-size: 14px; padding: 10px; }
  </style>
</head>
<body>
  <header>
    <h1 id="dashboardTitle">Loudness Dashboard @ custom</h1>
  </header>

  <main>
    <div class="card">
      <div class="controls">
        <label for="inputSelector">Input format:</label>
        <select id="inputSelector"></select>
      </div>
      <p class="plot-title">Heatmap</p>
      <div class="image-wrap">
        <div class="zoom-bar">
          <button class="zoom-btn" id="heatmapZoomOut" type="button">-</button>
          <button class="zoom-btn" id="heatmapZoomIn" type="button">+</button>
          <button class="zoom-btn" id="heatmapZoomReset" type="button">Fit</button>
          <span class="zoom-label" id="heatmapZoomLabel">100%</span>
        </div>
        <div class="viewport" id="heatmapViewport">
          <img id="heatmapImg" alt="Heatmap" />
          <div class="empty" id="heatmapEmpty">No image found for this selection.</div>
        </div>
      </div>
    </div>

    <div class="card">
      <div class="controls">
        <label for="outputSelector">Output format:</label>
        <select id="outputSelector"></select>
      </div>
      <p class="plot-title" id="drilldownTitle">Drilldown plot</p>
      <div class="image-wrap">
        <div class="zoom-bar">
          <button class="zoom-btn" id="drilldownZoomOut" type="button">-</button>
          <button class="zoom-btn" id="drilldownZoomIn" type="button">+</button>
          <button class="zoom-btn" id="drilldownZoomReset" type="button">Fit</button>
          <span class="zoom-label" id="drilldownZoomLabel">100%</span>
        </div>
        <div class="viewport" id="drilldownViewport">
          <img id="drilldownImg" alt="Drilldown" />
          <div class="empty" id="drilldownEmpty">No image found for this selection.</div>
        </div>
      </div>
    </div>
  </main>

  <script src="loudness_manifest.js"></script>
  <script>
    const manifest = window.__LOUDNESS_MANIFEST__ || {
      inputFormats: [],
      outputOptionsByInput: {},
      heatmaps: {},
      drilldown: {},
    };

    const inputSelector = document.getElementById('inputSelector');
    const outputSelector = document.getElementById('outputSelector');
    const drilldownTitle = document.getElementById('drilldownTitle');
    const dashboardTitle = document.getElementById('dashboardTitle');

    function normalizeSha(value) {
      const text = String(value || '').trim();
      if (!text) {
        return '';
      }
      const match = text.match(/[0-9a-fA-F]{7,40}/);
      return match ? match[0].toLowerCase() : '';
    }

    async function readShaViaScript(path) {
      return new Promise((resolve) => {
        const prior = window.__GIT_SHA__;
        const script = document.createElement('script');
        script.src = path;
        script.async = true;
        script.onload = () => {
          const sha = normalizeSha(window.__GIT_SHA__);
          if (typeof prior === 'undefined') {
            delete window.__GIT_SHA__;
          } else {
            window.__GIT_SHA__ = prior;
          }
          script.remove();
          resolve(sha);
        };
        script.onerror = () => {
          if (typeof prior === 'undefined') {
            delete window.__GIT_SHA__;
          } else {
            window.__GIT_SHA__ = prior;
          }
          script.remove();
          resolve('');
        };
        document.head.appendChild(script);
      });
    }

    async function updateDashboardTitle() {
      if (!dashboardTitle) {
        return;
      }

      let sha = '';
      try {
        const resp = await fetch('git-sha.txt', { cache: 'no-store' });
        if (resp.ok) {
          sha = normalizeSha(await resp.text());
        }
      } catch (_) {
        // Keep fallback title when file is unavailable in custom/moved bundles.
      }

      if (!sha) {
        sha = await readShaViaScript('git-sha.txt');
      }

      const shortSha = sha ? sha.slice(0, 8) : '';
      dashboardTitle.textContent = shortSha
        ? 'Loudness Dashboard @ ' + shortSha
        : 'Loudness Dashboard @ custom';
    }

    const zoomState = {
      heatmap: { scale: 1.0, fitScale: 1.0, isFit: true },
      drilldown: { scale: 1.0, fitScale: 1.0, isFit: true },
    };

    const ZOOM_MIN = 0.05;
    const ZOOM_MAX = 8.0;
    const ZOOM_STEP = 0.2;

    function clampZoom(scale) {
      return Math.max(ZOOM_MIN, Math.min(ZOOM_MAX, scale));
    }

    function applyZoom(kind) {
      const img = document.getElementById(kind + 'Img');
      const label = document.getElementById(kind + 'ZoomLabel');
      const scale = zoomState[kind].scale;
      if (img.naturalWidth) {
        img.style.width = Math.round(img.naturalWidth * scale) + 'px';
      } else {
        img.style.width = 'auto';
      }
      label.textContent = Math.round(scale * 100) + '%';
    }

    function computeFitScale(kind) {
      const viewport = document.getElementById(kind + 'Viewport');
      const img = document.getElementById(kind + 'Img');
      const naturalWidth = img.naturalWidth || 0;
      const naturalHeight = img.naturalHeight || 0;
      if (!naturalWidth || !naturalHeight) {
        return 1.0;
      }
      const fitWidth = viewport.clientWidth / naturalWidth;
      const fitHeight = viewport.clientHeight / naturalHeight;
      return clampZoom(Math.min(fitWidth, fitHeight, 1.0));
    }

    function fitToViewport(kind) {
      const fitScale = computeFitScale(kind);
      zoomState[kind].fitScale = fitScale;
      zoomState[kind].scale = fitScale;
      zoomState[kind].isFit = true;
      applyZoom(kind);
    }

    function zoomBy(kind, delta) {
      zoomState[kind].scale = clampZoom(zoomState[kind].scale + delta);
      zoomState[kind].isFit = false;
      applyZoom(kind);
    }

    function zoomReset(kind) {
      fitToViewport(kind);
    }

    function wireZoom(kind) {
      const viewport = document.getElementById(kind + 'Viewport');
      document.getElementById(kind + 'ZoomIn').addEventListener('click', () => zoomBy(kind, ZOOM_STEP));
      document.getElementById(kind + 'ZoomOut').addEventListener('click', () => zoomBy(kind, -ZOOM_STEP));
      document.getElementById(kind + 'ZoomReset').addEventListener('click', () => zoomReset(kind));

      viewport.addEventListener('wheel', (event) => {
        if (!(event.ctrlKey || event.metaKey)) {
          return;
        }
        event.preventDefault();
        zoomBy(kind, event.deltaY < 0 ? ZOOM_STEP : -ZOOM_STEP);
      }, { passive: false });

      applyZoom(kind);
    }

    function setImage(kind, src, altText) {
      const img = document.getElementById(kind + 'Img');
      const empty = document.getElementById(kind + 'Empty');
      if (!src) {
        img.style.display = 'none';
        img.onload = null;
        img.onerror = null;
        img.removeAttribute('src');
        img.style.width = 'auto';
        empty.textContent = 'No image found for this selection.';
        empty.style.display = 'block';
        return;
      }

      img.onload = null;
      img.onerror = null;
      img.src = src;
      img.alt = altText;
      img.style.display = 'block';
      empty.style.display = 'none';
      img.onload = () => fitToViewport(kind);
      img.onerror = () => {
        img.style.display = 'none';
        img.style.width = 'auto';
        empty.textContent = 'Plot file is not available in this artifact.';
        empty.style.display = 'block';
      };
    }

    function refreshHeatmap() {
      const inputFmt = inputSelector.value;
      const src = manifest.heatmaps[inputFmt] || null;
      setImage('heatmap', src, 'Heatmap for ' + inputFmt);
    }

    function refreshDrilldown() {
      const inputFmt = inputSelector.value;
      const outputValue = outputSelector.value;
      const outputLabel = outputSelector.options.length
        ? outputSelector.options[outputSelector.selectedIndex].textContent
        : outputValue;
      const inputMap = manifest.drilldown[inputFmt] || {};
      const src = inputMap[outputValue] || null;
      drilldownTitle.textContent = 'Drilldown plot: ' + inputFmt + ' in -> ' + outputLabel + ' out';
      setImage('drilldown', src, 'Drilldown for ' + inputFmt + ' to ' + outputLabel);
    }

    function refreshOutputOptionsForInput() {
      const inputFmt = inputSelector.value;
      const options = manifest.outputOptionsByInput[inputFmt] || [];
      const previousValue = outputSelector.value;

      outputSelector.innerHTML = '';
      options.forEach((entry) => {
        const opt = document.createElement('option');
        opt.value = entry.value;
        opt.textContent = entry.label;
        outputSelector.appendChild(opt);
      });

      if (options.some((entry) => entry.value === previousValue)) {
        outputSelector.value = previousValue;
      } else if (options.length > 0) {
        outputSelector.selectedIndex = 0;
      }
    }

    function populateSelectors() {
      manifest.inputFormats.forEach((inputFmt, idx) => {
        const opt = document.createElement('option');
        opt.value = inputFmt;
        opt.textContent = inputFmt;
        if (idx === 0) {
          opt.selected = true;
        }
        inputSelector.appendChild(opt);
      });

      refreshOutputOptionsForInput();
    }

    inputSelector.addEventListener('change', () => {
      refreshOutputOptionsForInput();
      refreshHeatmap();
      refreshDrilldown();
    });
    outputSelector.addEventListener('change', refreshDrilldown);

    wireZoom('heatmap');
    wireZoom('drilldown');
    window.addEventListener('resize', () => {
      if (zoomState.heatmap.isFit) {
        fitToViewport('heatmap');
      }
      if (zoomState.drilldown.isFit) {
        fitToViewport('drilldown');
      }
    });
    updateDashboardTitle();
    populateSelectors();
    refreshHeatmap();
    refreshDrilldown();
  </script>
</body>
</html>

scripts/parse_loudness_data.py

100755 → 100644
+615 −151

File changed.File mode changed from 100755 to 100644.

Preview size limit exceeded, changes collapsed.