barad-dur 0.17.3

The all-seeing repository analyzer
Documentation
pub const JS: &str = r#"
  /* ---- Treemap layout ---- */

  function buildFileTree(files) {
    var root = { name: '/', children: {}, files: [], totalLoc: 0 };
    files.forEach(function(f) {
      var parts = f.path.split('/');
      var fname = parts.pop();
      var node = root;
      parts.forEach(function(p) {
        if (!node.children[p]) {
          node.children[p] = { name: p, children: {}, files: [], totalLoc: 0 };
        }
        node = node.children[p];
      });
      node.files.push({ name: fname, path: f.path, loc: f.loc });
    });
    function computeLoc(node) {
      var sum = 0;
      node.files.forEach(function(f) { sum += f.loc; });
      var keys = Object.keys(node.children);
      keys.forEach(function(k) { sum += computeLoc(node.children[k]); });
      node.totalLoc = sum;
      return sum;
    }
    computeLoc(root);
    function squashSingle(node) {
      var keys = Object.keys(node.children);
      keys.forEach(function(k) { squashSingle(node.children[k]); });
      keys = Object.keys(node.children);
      if (keys.length === 1 && node.files.length === 0 && node.name !== '/') {
        var child = node.children[keys[0]];
        node.name = node.name + '/' + child.name;
        node.children = child.children;
        node.files = child.files;
      }
    }
    squashSingle(root);
    return root;
  }

  function squarify(items, x, y, w, h) {
    if (items.length === 0) return [];
    var results = [];
    var remaining = items.slice().sort(function(a, b) { return b.size - a.size; });
    var totalArea = w * h;
    var totalSize = 0;
    remaining.forEach(function(it) { totalSize += it.size; });
    if (totalSize <= 0) return [];

    function layoutRow(row, rowSize, rx, ry, rw, rh) {
      var short = Math.min(rw, rh);
      var rowArea = (rowSize / totalSize) * totalArea;
      var rowLen = short > 0 ? rowArea / short : 0;
      var offset = 0;
      var horizontal = rw >= rh;
      row.forEach(function(it) {
        var frac = rowSize > 0 ? it.size / rowSize : 0;
        var itemLen = frac * short;
        if (horizontal) {
          results.push({ x: rx, y: ry + offset, w: rowLen, h: itemLen, data: it.data });
        } else {
          results.push({ x: rx + offset, y: ry, w: itemLen, h: rowLen, data: it.data });
        }
        offset += itemLen;
      });
      if (horizontal) {
        return { x: rx + rowLen, y: ry, w: rw - rowLen, h: rh };
      } else {
        return { x: rx, y: ry + rowLen, w: rw, h: rh - rowLen };
      }
    }

    function worstRatio(row, rowSize, short) {
      if (row.length === 0 || short <= 0) return Infinity;
      var rowArea = (rowSize / totalSize) * totalArea;
      var worst = 0;
      row.forEach(function(it) {
        var frac = it.size / rowSize;
        var itemArea = frac * rowArea;
        var itemLen = short > 0 ? frac * short : 0;
        var itemWidth = itemLen > 0 ? itemArea / itemLen : 0;
        var r = itemWidth > itemLen ? itemWidth / itemLen : itemLen / itemWidth;
        if (r > worst) worst = r;
      });
      return worst;
    }

    var rx = x, ry = y, rw = w, rh = h;
    while (remaining.length > 0) {
      var short = Math.min(rw, rh);
      if (short <= 0) break;
      var row = [remaining[0]];
      var rowSize = remaining[0].size;
      remaining.splice(0, 1);
      var currentWorst = worstRatio(row, rowSize, short);

      while (remaining.length > 0) {
        var next = remaining[0];
        var newSize = rowSize + next.size;
        var newRow = row.concat([next]);
        var newWorst = worstRatio(newRow, newSize, short);
        if (newWorst <= currentWorst) {
          row = newRow;
          rowSize = newSize;
          currentWorst = newWorst;
          remaining.splice(0, 1);
        } else {
          break;
        }
      }
      var rest = layoutRow(row, rowSize, rx, ry, rw, rh);
      rx = rest.x; ry = rest.y; rw = rest.w; rh = rest.h;
    }
    return results;
  }

  function circlePack(items, cx, cy, r) {
    if (items.length === 0) return [];
    var sorted = items.slice().sort(function(a, b) { return b.size - a.size; });
    var totalSize = 0;
    sorted.forEach(function(it) { totalSize += it.size; });
    if (totalSize <= 0) return [];

    // Assign radii proportional to sqrt(size) so area ~ size
    var radii = [];
    var sumR2 = 0;
    sorted.forEach(function(it) {
      var ri = Math.sqrt(it.size / totalSize);
      radii.push(ri);
      sumR2 += ri * ri;
    });
    // Scale so circles fit inside parent radius with padding
    var scale = r * 0.85 / Math.sqrt(sumR2);
    radii = radii.map(function(ri) { return ri * scale; });

    // Place circles using simple greedy front-chain approach
    var placed = [];
    for (var i = 0; i < sorted.length; i++) {
      var ri = Math.max(radii[i], 2);
      if (i === 0) {
        placed.push({ cx: cx, cy: cy, r: ri, data: sorted[i].data });
      } else if (i === 1) {
        placed.push({ cx: cx + placed[0].r + ri + 1, cy: cy, r: ri, data: sorted[i].data });
      } else {
        // Find position that doesn't overlap existing circles, closest to center
        var bestX = cx, bestY = cy, bestDist = Infinity;
        for (var j = 0; j < placed.length; j++) {
          for (var k = j + 1; k < placed.length; k++) {
            // Try placing tangent to circles j and k
            var candidates = tangentPositions(placed[j], placed[k], ri);
            for (var c = 0; c < candidates.length; c++) {
              var px = candidates[c].x, py = candidates[c].y;
              var dist = Math.sqrt((px - cx) * (px - cx) + (py - cy) * (py - cy));
              if (dist + ri > r * 0.95) continue; // outside parent
              var overlaps = false;
              for (var m = 0; m < placed.length; m++) {
                var dx = px - placed[m].cx, dy = py - placed[m].cy;
                if (Math.sqrt(dx * dx + dy * dy) < ri + placed[m].r - 0.5) {
                  overlaps = true;
                  break;
                }
              }
              if (!overlaps && dist < bestDist) {
                bestDist = dist;
                bestX = px;
                bestY = py;
              }
            }
          }
        }
        placed.push({ cx: bestX, cy: bestY, r: ri, data: sorted[i].data });
      }
    }

    // Center the packed circles within the parent
    if (placed.length > 0) {
      var avgX = 0, avgY = 0;
      placed.forEach(function(p) { avgX += p.cx; avgY += p.cy; });
      avgX /= placed.length;
      avgY /= placed.length;
      var shiftX = cx - avgX, shiftY = cy - avgY;
      placed.forEach(function(p) { p.cx += shiftX; p.cy += shiftY; });
    }

    return placed;
  }

  function tangentPositions(c1, c2, r) {
    var dx = c2.cx - c1.cx, dy = c2.cy - c1.cy;
    var d = Math.sqrt(dx * dx + dy * dy);
    if (d < 0.001) return [{ x: c1.cx + c1.r + r, y: c1.cy }];
    var d1 = c1.r + r, d2 = c2.r + r;
    if (d > d1 + d2) return [];
    var a = (d1 * d1 - d2 * d2 + d * d) / (2 * d);
    var h2 = d1 * d1 - a * a;
    if (h2 < 0) h2 = 0;
    var h = Math.sqrt(h2);
    var mx = c1.cx + a * dx / d, my = c1.cy + a * dy / d;
    return [
      { x: mx + h * dy / d, y: my - h * dx / d },
      { x: mx - h * dy / d, y: my + h * dx / d }
    ];
  }

  var metricScales = {
    hotspot: {
      label: 'Hotspot Score',
      color: function(f) {
        var s = f.hotspot_score || 0;
        var t = Math.min(s, 100) / 100;
        var h = (1 - t) * 120;
        return 'hsl(' + h + ',80%,' + (35 + t * 15) + '%)';
      },
      legend: function() {
        return [
          { label: 'Low', color: 'hsl(120,80%,35%)' },
          { label: 'Medium', color: 'hsl(60,80%,42%)' },
          { label: 'High', color: 'hsl(0,80%,50%)' }
        ];
      }
    },
    complexity: {
      label: 'Cyclomatic Complexity',
      color: function(f, maxCC) {
        var t = maxCC > 0 ? Math.min(f.cyclomatic_complexity || 0, maxCC) / maxCC : 0;
        return 'hsl(0,70%,' + (75 - t * 40) + '%)';
      },
      legend: function() {
        return [
          { label: 'Low', color: 'hsl(0,70%,75%)' },
          { label: 'High', color: 'hsl(0,70%,35%)' }
        ];
      }
    },
    churn: {
      label: 'Churn Count',
      color: function(f, _mc, maxChurn) {
        var t = maxChurn > 0 ? Math.min(f.churn_count || 0, maxChurn) / maxChurn : 0;
        return 'hsl(30,80%,' + (75 - t * 40) + '%)';
      },
      legend: function() {
        return [
          { label: 'Low', color: 'hsl(30,80%,75%)' },
          { label: 'High', color: 'hsl(30,80%,35%)' }
        ];
      }
    },
    age: {
      label: 'File Age (days)',
      color: function(f, _mc, _mch, ageMap, maxAge) {
        var a = ageMap[f.path];
        var days = a ? a.days_since_modified : 0;
        var t = maxAge > 0 ? Math.min(days, maxAge) / maxAge : 0;
        return 'hsl(220,70%,' + (75 - t * 40) + '%)';
      },
      legend: function() {
        return [
          { label: 'Recent', color: 'hsl(220,70%,75%)' },
          { label: 'Old', color: 'hsl(220,70%,35%)' }
        ];
      }
    },
    owner: {
      label: 'Top Contributor',
      color: function(f, _mc, _mch, _am, _ma, ownerMap, authorIndex) {
        var own = ownerMap[f.path];
        if (own && own.authors && own.authors[0]) {
          var idx = authorIndex[own.authors[0].name];
          return PALETTE[idx != null ? idx % PALETTE.length : 0];
        }
        return '#334155';
      },
      legend: function(authorIndex) {
        var items = [];
        for (var name in authorIndex) {
          items.push({ label: name, color: PALETTE[authorIndex[name] % PALETTE.length] });
        }
        return items;
      }
    }
  };
"#;