OverviewFilesCommitsIssuesDocsReleasesPeers

hypercore-kb / opencog-kb / language-learning / mvp / viz / archive / index-hierarchy.html

366 lines · html ref:
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Language Learning - Hierarchical View</title>
  <style>
    * { margin: 0; padding: 0; box-sizing: border-box; }
    body {
      font-family: 'Monaco', 'Consolas', monospace;
      background: #0a0a0f;
      color: #e0e0e0;
      overflow: hidden;
    }
    #container {
      display: flex;
      height: 100vh;
    }
    #canvas-container {
      flex: 1;
      position: relative;
    }
    #sidebar {
      width: 280px;
      background: #12121a;
      padding: 20px;
      overflow-y: auto;
      border-left: 1px solid #333;
    }
    h1 { font-size: 14px; margin-bottom: 20px; color: #888; }
    h2 { font-size: 12px; margin: 15px 0 10px; color: #666; text-transform: uppercase; }
    .stat {
      display: flex;
      justify-content: space-between;
      padding: 5px 0;
      font-size: 12px;
    }
    .stat-value { color: #4ecdc4; }

    .view-toggle {
      display: flex;
      gap: 5px;
      margin-bottom: 15px;
    }
    .view-toggle a {
      flex: 1;
      padding: 8px;
      text-align: center;
      background: #1a1a25;
      color: #888;
      text-decoration: none;
      border-radius: 4px;
      font-size: 11px;
    }
    .view-toggle a.active {
      background: #2a2a35;
      color: #4ecdc4;
    }
    .view-toggle a:hover {
      background: #252535;
    }

    .info-box {
      background: #1a1a25;
      padding: 10px;
      border-radius: 4px;
      font-size: 11px;
      margin: 10px 0;
      color: #888;
    }
    .info-box strong {
      color: #4ecdc4;
    }

    #tooltip {
      position: absolute;
      background: rgba(0,0,0,0.9);
      border: 1px solid #4ecdc4;
      padding: 10px;
      font-size: 11px;
      pointer-events: none;
      display: none;
      max-width: 250px;
      z-index: 100;
    }
    #loading {
      position: absolute;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
      color: #4ecdc4;
    }
    canvas { display: block; }

    .controls {
      margin: 10px 0;
    }
    .controls button {
      padding: 6px 12px;
      margin: 2px;
      background: #1a1a25;
      color: #e0e0e0;
      border: 1px solid #333;
      border-radius: 4px;
      cursor: pointer;
      font-size: 11px;
    }
    .controls button:hover {
      background: #252535;
    }
    .slider-container {
      margin: 10px 0;
    }
    .slider-container label {
      display: block;
      font-size: 11px;
      color: #888;
      margin-bottom: 5px;
    }
    .hint {
      font-size: 9px;
      color: #555;
      margin-top: 4px;
    }
  </style>
</head>
<body>
  <div id="container">
    <div id="canvas-container">
      <canvas id="canvas"></canvas>
      <div id="tooltip"></div>
      <div id="loading">Loading hierarchy data...</div>
    </div>
    <div id="sidebar">
      <h1>HIERARCHICAL VISUALIZER</h1>

      <div class="view-toggle">
        <a href="index.html">3D Graph</a>
        <a href="index-hierarchy.html" class="active">Hierarchy</a>
      </div>

      <h2>Statistics</h2>
      <div class="stat"><span>Words:</span><span class="stat-value" id="stat-words">0</span></div>
      <div class="stat"><span>Pairs:</span><span class="stat-value" id="stat-pairs">0</span></div>
      <div class="stat"><span>Clusters:</span><span class="stat-value" id="stat-clusters">0</span></div>
      <div class="stat"><span>Level:</span><span class="stat-value" id="stat-level">0</span></div>
      <div class="stat"><span>Avg Surprise:</span><span class="stat-value" id="stat-surprise">0</span></div>

      <h2>Navigation</h2>
      <div class="controls">
        <button onclick="zoomOut()">Zoom Out</button>
        <button onclick="zoomTo(hierarchy)">Reset View</button>
      </div>

      <div class="info-box">
        <strong>Click</strong> a circle to zoom in<br>
        <strong>Right-click</strong> to zoom out<br>
        <strong>Scroll</strong> to zoom in/out<br>
        <strong>Hover</strong> for details
      </div>

      <h2>Recursive Structure</h2>
      <div class="info-box">
        The same learning process happens at each level:
        <br><br>
        <strong>Level 0:</strong> Words<br>
        <strong>Level 1:</strong> Word Clusters<br>
        <strong>Level 2:</strong> Super-Clusters<br>
        <strong>Level N:</strong> Meta-Structures
        <br><br>
        Each level applies: COUNT → STRUCTURE → CLUSTER
      </div>

      <h2>Time Evolution</h2>
      <div class="slider-container">
        <label>Iteration: <span id="iteration-value">All</span></label>
        <input type="range" id="iteration-slider" min="0" max="100" value="100" style="width: 100%;">
        <div class="hint">Slide to see hierarchy at different learning stages</div>
      </div>

      <h2>Current View</h2>
      <div id="current-view" class="info-box">
        Viewing: <strong>All Structures</strong><br>
        Level: <strong>-1</strong><br>
        Children: <strong>0</strong>
      </div>
    </div>
  </div>

  <script src="viz-hierarchy.js"></script>
  <script>
    // Update current view display
    const originalZoomTo = zoomTo;
    zoomTo = function(node) {
      originalZoomTo(node);
      updateCurrentView(node);
    };

    function updateCurrentView(node) {
      if (!node) return;
      const div = document.getElementById('current-view');
      let html = `Viewing: <strong>${node.name || node.id}</strong><br>`;
      html += `Level: <strong>${node.level}</strong><br>`;
      html += `Children: <strong>${node.children?.length || 0}</strong>`;

      if (node.data?.iteration !== undefined) {
        html += `<br>Formed at iteration: <strong>${node.data.iteration}</strong>`;
      }
      if (node.data?.similarity !== undefined && node.data.similarity > 0) {
        html += `<br>Similarity: <strong>${node.data.similarity.toFixed(3)}</strong>`;
      }

      div.innerHTML = html;
    }

    // Iteration timeline slider
    let maxIteration = 100;

    // Override loadData to set up timeline
    const originalLoadData = loadData;
    loadData = async function() {
      await originalLoadData();

      // Set max iteration from data
      if (hierarchy && hierarchy.data && hierarchy.data.totalIterations) {
        maxIteration = hierarchy.data.totalIterations;
      } else if (data && data.stats && data.stats.clusterIterations) {
        maxIteration = data.stats.clusterIterations;
      }

      const slider = document.getElementById('iteration-slider');
      slider.max = maxIteration;
      slider.value = maxIteration;
      document.getElementById('iteration-value').textContent = 'All';
    };

    document.getElementById('iteration-slider').addEventListener('input', (e) => {
      const iter = parseInt(e.target.value);
      if (iter >= maxIteration) {
        document.getElementById('iteration-value').textContent = 'All';
        // Show all clusters
        filterByIteration(Infinity);
      } else {
        document.getElementById('iteration-value').textContent = iter;
        // Filter to show only clusters formed by this iteration
        filterByIteration(iter);
      }
    });

    function filterByIteration(maxIter) {
      // Rebuild hierarchy with only clusters formed by maxIter
      if (!data) return;

      // Rebuild and repack
      hierarchy = buildHierarchyFiltered(data, maxIter);
      setParentRefs(hierarchy, null);

      const size = Math.min(canvas.width, canvas.height) * 0.45;
      packCircles(hierarchy, canvas.width / 2, canvas.height / 2, size);

      // Reset view
      viewX = canvas.width / 2;
      viewY = canvas.height / 2;
      viewScale = 1;
      targetX = viewX;
      targetY = viewY;
      targetScale = 1;
      currentView = hierarchy;
    }

    function buildHierarchyFiltered(data, maxIter) {
      // Similar to buildHierarchy but filters by iteration
      const root = {
        id: 'root',
        name: `Structures (iter ≤ ${maxIter === Infinity ? 'all' : maxIter})`,
        level: -1,
        children: [],
        data: { type: 'root' }
      };

      const words = data.words || {};
      const hierarchy = data.hierarchy || {};
      const merges = (hierarchy.merges || []).filter(m => m.iteration <= maxIter);

      // Get clusters that exist at this iteration
      const validClusterIds = new Set(merges.map(m => m.clusterId));

      // Create word nodes
      const wordNodes = new Map();
      for (const [word, info] of Object.entries(words)) {
        wordNodes.set(word, {
          id: `word_${word}`,
          name: word,
          level: 0,
          children: [],
          data: {
            type: 'word',
            word: word,
            surprise: info.surprise || 0,
            count: info.count || 1,
            degree: info.degree || 0,
            totalMI: info.totalMI || 0
          }
        });
      }

      // Build clusters from filtered merges
      const clusterNodes = new Map();
      const wordToCluster = new Map();

      for (const merge of merges) {
        const clusterId = merge.clusterId;

        if (!clusterNodes.has(clusterId)) {
          clusterNodes.set(clusterId, {
            id: `cluster_${clusterId}`,
            name: `${merge.entity1} + ${merge.entity2}`,
            level: 1,
            children: [],
            data: {
              type: 'cluster',
              clusterId: clusterId,
              iteration: merge.iteration,
              similarity: merge.similarity
            }
          });
        }

        // Add entities to cluster
        for (const entity of [merge.entity1, merge.entity2]) {
          if (wordNodes.has(entity) && !wordToCluster.has(entity)) {
            clusterNodes.get(clusterId).children.push(wordNodes.get(entity));
            wordToCluster.set(entity, clusterId);
          }
        }
      }

      // Add clusters
      const clusters = Array.from(clusterNodes.values());
      clusters.sort((a, b) => (a.data.iteration || 0) - (b.data.iteration || 0));
      root.children.push(...clusters);

      // Add unclustered words
      const orphans = [];
      for (const [word, node] of wordNodes) {
        if (!wordToCluster.has(word)) {
          orphans.push(node);
        }
      }

      if (orphans.length > 0) {
        root.children.push({
          id: 'unclustered',
          name: 'Unclustered',
          level: 1,
          children: orphans,
          data: { type: 'unclustered', memberCount: orphans.length }
        });
      }

      return root;
    }
  </script>
</body>
</html>