需要一种算法来调整组织结构图的大小以适应 window

Need an algorithm to resize an organization chart to fit window

上下文:

我需要能够将任何组织层次结构树呈现到任何给定尺寸的网页上,然后使用无头浏览器将页面转换为 PDF,同时保持文本清晰,以便大公司可以打印以大字体打印或放大查看。

最终目标:

这使他们能够拥有单一的事实来源并直观地表示其组织的树。一些组织希望定义较大的 pdf 尺寸(例如,24" x 36"),并能够将其闪存驱动器上的 PDF 带到 Kinkos 并打印出来并在他们的办公室展示。

注意:组织的深度或宽度没有上限。目前拥有超过 300 家公司和 5 个层次的公司。所以这就是为什么我需要一种可以扩展的方法。

当前方法:

目前,我正在通过缩放父容器的字体大小来采用蛮力方法。

大图表需要更精细的增量,否则它们根本不会呈现。

例如,假设字体大小增量为 0.5。如果图表太大 0.5px 溢出,当前脚本将递增 - 看到它溢出 - 然后恢复为 0,这意味着它不显示。

如果我降低增量,那么大多数较小的图表渲染时间将急剧增加。

数据结构:

Seat: {
   name: 'Seat Name',
   children: Array<Seat>,
}

原型:

https://github.com/danfoust/org-chart-prototype

期望的解决方案

我可以将递归数据结构和 window 维度提供给函数,它可以有效地确定最佳字体大小以适应 window 中生成的 DOM。

如果 CSS 可以做到这一点,那就太好了。我正在使用 Puppeteer 并扩展为 Chromium,所以如果有一些最新和最伟大的 CSS 魔法,那就太棒了。

我按照@user3386109 的建议找到了上限,然后进行了二分查找。对于 300 个席位,这将查找时间缩短了一半。

'use strict';

const $ = (selector) => document.querySelector(selector);
const $$ = (selector) => document.querySelectorAll(selector);

// Prevent loop from blocking render for 3 ms
async function isOutOfBounds(chart, w, h) {
  return new Promise((res) => {
    setTimeout(() => res(chart.scrollWidth > w || chart.scrollHeight > h), 3);
  });
}

///////////////////////////////////////////////////////////////////////////////////

const scrollbarPX = 16; // Prevent empty last page

const screenWidth = window.innerWidth - 1;
const screenHeight = window.innerHeight - scrollbarPX;

const headHeight = $('.header').scrollHeight;
const footHeight = $('.footer').scrollHeight;

const maxChartHeight = screenHeight - headHeight - footHeight;

(async () => {
  // Set each page to window dimensions
  const pages = $$('.page');
  for (const page of pages) {
    page.style.width = `${screenWidth}px`;
    page.style.height = `${screenHeight}px`;
  }

  //////////////////////////////////////////////////////////////////////
  
  const cache = {};

  const charts = $$('.chart');
  for (const chart of charts) {
      let cacheKey = `${chart.scrollWidth}x${chart.scrollHeight}`;
      if (cache[cacheKey]) {
          chart.style.fontSize = `${cache[cacheKey]}px`;

          // Handle rare case where same sized charts render differently at same font-size
          if (!await isOutOfBounds(chart, screenWidth, maxChartHeight)) {
             continue;
          }
      }

      //////////////////////////////////////////////////////////////////

      let max = 1;

      while (1) { // Get upper bound
          chart.style.fontSize = `${max}px`;

          if (await isOutOfBounds(chart, screenWidth, maxChartHeight)) break;

          max *= 2;
      }

      let mid;

      while (1) { // Use binary search
          mid = max / 2;

          chart.style.fontSize = `${mid}px`;

          if (!(await isOutOfBounds(chart, screenWidth, maxChartHeight))) break;
      
          max = mid;
      }

      // The binary search takes the first number that works
      // which may not be the best number.  Let's try scaling 
      // up by a fraction of the working number.
      let incr = mid / 10;
      let size = mid + incr;
      while (1) {
         chart.style.fontSize = `${size}px`;

          if (await isOutOfBounds(chart, screenWidth, maxChartHeight)) {
              chart.style.fontSize = `${size - incr}px`;
              cache[cacheKey] = size - incr;
              break;
          }

          size += incr;
      }
    }

    // Using waitForSelector with Puppeteer
    $('body').classList.add('complete');
})();