// Copyright Epic Games, Inc. All Rights Reserved. "use strict"; import { ZenPage } from "./page.js" import { Friendly } from "../util/friendly.js" import { ProgressBar } from "../util/widgets.js" import { create_indexer } from "../indexer/indexer.js" //////////////////////////////////////////////////////////////////////////////// function squarify(weights, callback, area_threshold=-1) { const rect = [1.0, 1.0]; for (var start = 0; start < weights.length;) { const ri = +(rect[0] >= rect[1]); const length = rect[ri]; var end = start; var area = 0; var prev_rd = Infinity; for (; end < weights.length; ++end) { const w = (area + weights[end]) / length; const r = weights[end] / (w * w); const rd = Math.abs(1.0 - r); if (prev_rd < rd) break; prev_rd = rd; area += weights[end]; } const v = area / length; const tl = [1.0 - rect[0], 1.0 - rect[1]]; const wh = [undefined, undefined]; for (var i = start; i < end; ++i) { wh[ri ^ 0] = weights[i] / v; wh[ri ^ 1] = v; callback(i, tl[0], tl[1], wh[0], wh[1], ri); tl[ri] += wh[ri]; } start = end; rect[ri ^ 1] -= v; if (rect[0] * rect[1] < area_threshold) break; } } //////////////////////////////////////////////////////////////////////////////// export class Page extends ZenPage { main() { const project = this.get_param("project"); const oplog = this.get_param("oplog"); this._indexer = this._load_indexer(project, oplog); this.set_title("map"); const section = this.add_section(project + " - " + oplog); this._build(section); } async _load_indexer(project, oplog) { const progress_bar = this.add_widget(ProgressBar); progress_bar.set_progress("indexing"); var indexer = create_indexer(project, oplog, (...args) => { progress_bar.set_progress(...args); }); indexer = await indexer; progress_bar.destroy(); return indexer; } async _build(section) { const indexer = await this._indexer; var prefix = this.get_param("path", "/"); if (!prefix.endsWith("/")) prefix += "/"; var total_size = 0; var branch_size = 0; const new_nodes = new Object(); for (var [name, size] of indexer.enum_all()) { total_size += size; if (!name.startsWith(prefix)) continue; branch_size += size; name = name.substr(prefix.length); const slash = name.indexOf("/"); if (slash != -1) name = name.substr(0, slash + 1); if (new_nodes[name] !== undefined) new_nodes[name] += size; else new_nodes[name] = size; } const sorted_keys = Object.keys(new_nodes).sort((l, r) => { return new_nodes[r] - new_nodes[l]; }); const nodes = new Array(); for (const name of sorted_keys) nodes.push(new_nodes[name] / branch_size); var stats = Friendly.kib(branch_size); stats += " / "; stats += Friendly.kib(total_size); stats += " ("; stats += 0|((branch_size * 100) / total_size); stats += "%)"; section.tag().text(prefix + " : " + stats); const treemap = section.tag().id("treemap"); const canvas = treemap.tag("canvas").inner(); const width = canvas.offsetWidth; var height = window.visualViewport.height; height -= treemap.inner().getBoundingClientRect().top + window.scrollY; height -= 50; canvas.width = canvas.offsetWidth; canvas.height = height; const context = canvas.getContext("2d"); context.textBaseline = "top"; context.imageSmoothingEnabled = false; context.font = "13px sans-serif"; context.strokeStyle = "#666666"; const palette = [ "#8dd3c7", "#ffffb3", "#bebada", "#fb8072", "#80b1d3", "#fdb462", "#b3de69", "#fccde5", "#d9d9d9", "#bc80bd", "#ccebc5", ]; const callback = (i, x, y, w, h, d) => { const r = function(u,v) { return Math.floor(u * (v - 1e-7)); }; x = r(x, width); y = r(y, height); w = r(w, width); h = r(h, height); context.save(); context.beginPath(); context.rect(x, y, w, h); context.clip(); context.fillStyle = palette[(i * 0x493) % palette.length]; context.fill(); context.stroke(); context.fillStyle = "#000000"; context.fillText(sorted_keys[i], x + 4, y + 4); context.restore(); }; squarify(nodes, callback, 0.01); } }