GODRICH
#cssアニメーション#ボタン#ローディング#トランジション#スクロール#ダッシュボード#実用ui#デザイントレンド

css 組織図 9パターン — 枝の幅を計算して親を子の真ん中に置くツリーの場面

組織図は箱と線を描けば済みそうだが、枝の幅を測らないと兄弟の箱が重なり、枝がたたまれると残りの箱が跳ね、親が変わると別々の親の横線が重なる — 01番・03番・07番が一つずつ直す。下の css 組織図九つの場面はキャンバスに計算して描いたもので、ファイルごと貼ればそのまま動きます。

組織図九つの場面をこの画面で見る

下のグリッドは、ページを開いた時点から自動で動いています。同じ11人の組織を、配置の計算・線・たたみ方・拡大・扇形・付け替え・検索・人数の集計と、場面ごとに別の角度から動かします。

自動再生 · タイルを押すとその項目へ · すべて1つのzip

css 組織図 9パターン — 枝の幅を先に測らないと箱が重なる

九つの場面が使う組織は同じ11個の箱です。代表の下に開発・デザイン・運営が並び、開発の下にフロント・サーバー・データ、デザインの下にブランド・製品、運営の下に営業・サポートが付きます。キャンバスの内側は480×300(W・H)で、箱も文字も線も2Dコンテキストで描き、CSSは同じ16:10の比率に引き伸ばして見せるだけです。

要になるのは配置を決める二つの関数です。measure が一番下の葉から幅を足しながら上へ進み、place がその幅のとおりに子の集まりを左から右へ敷いて、親を最初と最後の子の真ん中に置きます。01がこの骨組みを見せ、02〜09は同じ関数を呼んだまま、その上に線を引く・たたむ・経路を光らせる・拡大する・扇に開く・付け替える・検索する・集計する、を一つずつ重ねます。この木には力を掛け合うシミュレーションがなく、同じ入力なら必ず同じ位置が出る決定的な計算なので、ノード同士が押し合って落ち着くネットワークグラフ9種 — 力で押し引きし自ら並ぶキャンバスとは性格が違います。

時間は requestAnimationFrame のタイムスタンプを16ミリ秒(FIXED_DT)単位でため、フレームの間隔が160ミリ秒を超えたら160に切って、タブを開き直したときに場面が一気に跳ばないようにしています。乱数・Date・fetch・保存領域・ライブラリ・外部アドレスは使わないので、同じ時刻なら同じ絵になります。幅が決まったサイドバー型のツリーは、ファイルツリー 作成 9選 — フォルダが実際に開くツリービューが扱っています。

技法別コードで見る木の配置

以下の断片はデモのソースから連続した行をそのまま切り出したもので、完結したファイルは暗号化zipに入っています。箱の大きさと間隔は NODE_W 56・GAP_X 8・LEVEL_DY 72・TOP_Y 44 で、九つの場面に共通です。

01枝の幅を測って親を子の真ん中に置く基本配置

measure が葉の幅56から始めて子の幅の合計に間隔8を足しながら親の幅へ上げ、place が親を最初と最後の子の真ん中にすえます。基本の木は葉7個の56×7に間隔8×6を足した幅440なので、開発184・デザイン120・運営120が並び、この幅が決まってから親が最初の子の上から本来の位置へ移ります。測定は累積面積10.9%・平均強度121.0・22/23フレームです。

枝の幅を測って親を子の真ん中に置く基本配置
新しいタブで開く

01 · 枝の幅を測って親を子の真ん中に置く基本配置 — 計測 累積面積 10.878% · 動いたフレーム 22/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

GAP_XNODE_WMID_START_MSROOT_START_MSSETTLE_MS
      function measure(n) {
          const v = vis(n);
          if (v.length === 0) {
              n.w = n.bw;
              return n.w;
          }
          let sum = GAP_X * (v.length - 1);
          for (let i = 0; i < v.length; i += 1) {
              sum += measure(v[i]);
          }
          n.w = Math.max(n.bw, sum);
          return n.w;
      }
      function place(n, left) {
          const v = vis(n);
          n.left = left;
          if (v.length === 0) {
              n.tx = left + n.w / 2;
              return;
          }
          let span = GAP_X * (v.length - 1);
          for (let i = 0; i < v.length; i += 1) {
              span += v[i].w;
          }
          let cur = left + (n.w - span) / 2;
          for (let i = 0; i < v.length; i += 1) {
              place(v[i], cur);
              cur += v[i].w + GAP_X;
          }
          n.tx = (v[0].tx + v[v.length - 1].tx) / 2;
      }

02親から子へ折れて描かれる連結線

親の下で横に分かれて子へ降りる折れ線が、長さの割合の分だけ引かれ、先端には白い点と進み具合のパーセントが付きます。SVGの stroke-dasharray と同じ考え方ですが、getTotalLengthは使わず、この線は軸に平行な線分だけなので polyLen が縦横の長さを直接足して測ります。測定は累積面積13.6%・平均強度99.6・22/23フレームです。

親から子へ折れて描かれる連結線
新しいタブで開く

02 · 親から子へ折れて描かれる連結線 — 計測 累積面積 13.637% · 動いたフレーム 22/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

DRAW_MSKID_GAP_MSRETRACT_START_MSRETRACT_MS
      function strokeLine(q, ratio, col, lw) {
          if (ratio <= 0) {
              return;
          }
          const total = polyLen(q);
          ctx.beginPath();
          ctx.moveTo(q[0], q[1]);
          for (let i = 2; i < q.length; i += 2) {
              ctx.lineTo(q[i], q[i + 1]);
          }
          ctx.lineWidth = lw;
          ctx.strokeStyle = col;
          ctx.lineCap = 'round';
          ctx.lineJoin = 'round';
          ctx.setLineDash(ratio >= 1 ? [] : [total * ratio, total * 2]);
          ctx.stroke();
          ctx.setLineDash([]);
      }
      function elbow(p, c) {
          const ay = p.y + p.bh / 2;
          const dy = c.y - c.bh / 2;
          const my = (ay + dy) / 2;
          return [p.x, ay, p.x, my, c.x, my, c.x, dy];
      }

03枝がたたまれたとき残りの箱が新しい位置へ滑る再配置

枝を一つたたむと layout が見える枝の幅を測り直して新しい目標位置を求め、箱は一歩ごとに残りの距離の MOVE_RATE の割合ずつ近づきます。たたまれたノードには +3 のような子の数のバッジ、開いたノードには - のバッジが付き、矢印キーで選んだノードをEnterかSpaceでたたんだり開いたりできます。測定は累積面積21.6%・平均強度82.8・23/23フレームです。

枝がたたまれたとき残りの箱が新しい位置へ滑る再配置
新しいタブで開く

03 · 枝がたたまれたとき残りの箱が新しい位置へ滑る再配置 — 計測 累積面積 21.601% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

MOVE_RATEFADE_RATELEVEL_DY
      function settleNodes(rate) {
          for (let i = 0; i < nodes.length; i += 1) {
              const n = nodes[i];
              n.x += (n.tx - n.x) * rate;
              n.y += (n.ty - n.y) * rate;
              n.alpha += (n.talpha - n.alpha) * FADE_RATE;
          }
      function fold(n, open) {
          if (n.kids.length === 0) {
              return;
          }
          n.open = open;
          cursor = n;
          showCursor = true;
          layout();
          setStatus(n.label + ': ' + (open ? '展開' : 'たたみ'));
      }

04指したノードから最上位までの経路だけが点く強調

ノードを指すとそれが0番、親が1番と数えられ、k番目の祖先は時間が k×LIT_STEP_MS(220)を超えて初めて点くので、明かりが下から上へ登ります。点いていない箱と線は透明度0.3(DIM_ALPHA)まで薄くなり、状態の文字には 代表 > 開発 > フロント のような経路が出ます。測定は累積面積13.8%・平均強度74.8・22/23フレームです。

指したノードから最上位までの経路だけが点く強調
新しいタブで開く

04 · 指したノードから最上位までの経路だけが点く強調 — 計測 累積面積 13.762% · 動いたフレーム 22/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

LIT_STEP_MSLIT_RATEDIM_ALPHA
      function litTarget() {
          for (let i = 0; i < nodes.length; i += 1) {
              const k = chainIndex(nodes[i]);
              nodes[i].tval = (focus && k >= 0 && litMs >= k * LIT_STEP_MS) ? 1 : 0;
          }
      }
      stepFn = function (dt) {
          litMs += dt;
          litTarget();
          for (let i = 0; i < nodes.length; i += 1) {
              nodes[i].lit += (nodes[i].tval - nodes[i].lit) * LIT_RATE;
          }
          dimNow += ((focus ? DIM_ALPHA : 1) - dimNow) * LIT_RATE;
      };

05ポインタの位置をつかんだまま拡大する移動とズーム

拡大のとき zoomAt が新しい倍率と移動量をいっしょに解くので、ポインタの下の点は動かず、手を離した後は速度が一歩ごとに0.94倍へ減りながら滑ります。倍率は1〜3.2に収まり、移動できる範囲は横90・縦70の余白までで、右上の小さな地図が今見ている範囲を示します。測定は累積面積45.6%・平均強度106.0・23/23フレームです。

ポインタの位置をつかんだまま拡大する移動とズーム
新しいタブで開く

05 · ポインタの位置をつかんだまま拡大する移動とズーム — 計測 累積面積 45.608% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

ZOOM_RATEZOOM_MAXFRICTIONPAD_X
      function clampCam() {
          cam.ox = clamp(cam.ox, W - W * cam.s - PAD_X, PAD_X);
          cam.oy = clamp(cam.oy, H - H * cam.s - PAD_Y, PAD_Y);
      }
      // 拡大率が k 倍になっても (px, py) の下の世界の点が動かないよう、移動量を解く
      function zoomAt(px, py, k) {
          const ns = clamp(cam.s * k, ZOOM_MIN, ZOOM_MAX);
          const real = ns / cam.s;
          cam.ox = px - (px - cam.ox) * real;
          cam.oy = py - (py - cam.oy) * real;
          cam.s = ns;
          clampCam();
      }

06同じ木を扇形に広げる極座標配置

どのノードも、キャンバス上端の中央にある原点から見た角度と半径の組を持ちます。横配置は Math.atan2 で、扇形は葉7個に FAN_SPAN の2.62ラジアン(約150°)を等分して角度を出します。角度と半径を別々に補間するので、ノードは直線ではなく原点を回る弧を描いて移ります。測定は累積面積21.8%・平均強度98.5・23/23フレームです。

同じ木を扇形に広げる極座標配置
新しいタブで開く

06 · 同じ木を扇形に広げる極座標配置 — 計測 累積面積 21.756% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

FAN_SPANRINGFAN_RATE
      function assignAngle(n) {
          if (n.kids.length === 0) {
              fanAngle[n.id] = -FAN_SPAN / 2 + (leafNo + 0.5) * FAN_SPAN / leafCount;
              leafNo += 1;
          }
          else {
              for (let i = 0; i < n.kids.length; i += 1) {
                  assignAngle(n.kids[i]);
              }
              fanAngle[n.id] = (fanAngle[n.kids[0].id] + fanAngle[n.kids[n.kids.length - 1].id]) / 2;
          }
          fanRadius[n.id] = n.depth * RING;
      }
      // 横配置も同じ原点から見た (角度, 半径) に直しておくと、二つの配置の間を弧でつなげる
      for (let i = 0; i < nodes.length; i += 1) {
          const dx = nodes[i].tx - ORIGIN_X, dy = nodes[i].ty - ORIGIN_Y;
          flatRadius[i] = Math.sqrt(dx * dx + dy * dy);
          flatAngle[i] = Math.atan2(dx, dy);
      }
      function polarX(r, a) { return ORIGIN_X + r * Math.sin(a); }
      function polarY(r, a) { return ORIGIN_Y + r * Math.cos(a); }

07親が変わるとき線と箱がいっしょに並び直る移動

ノードを別の親の下へ移すと reparent が連結だけを先に付け替え、箱は元の位置に残すので、別々の親の同じ高さの横線が重なる長さを measureOverlap が測ります。650ミリ秒後に resolve が幅を測り直すと重なりは0になり、デモでは営業を開発へ移すとき64px、フロントを運営へ移すとき192px、データをデザインへ移すとき64pxが、それぞれ0へ解けます。測定は累積面積14.6%・平均強度58.2・22/23フレームです。

親が変わるとき線と箱がいっしょに並び直る移動
新しいタブで開く

07 · 親が変わるとき線と箱がいっしょに並び直る移動 — 計測 累積面積 14.557% · 動いたフレーム 22/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

TANGLE_MSREFLOW_RATEGAP_X
      function measureOverlap(useTarget) {
          overlaps = [];
          overlapPx = 0;
          const spans = [];
          for (let i = 0; i < nodes.length; i += 1) {
              const p = nodes[i];
              const v = vis(p);
              if (v.length === 0) {
                  continue;
              }
              const px0 = useTarget ? p.tx : p.x;
              let lo = px0, hi = px0;
              for (let j = 0; j < v.length; j += 1) {
                  const vx0 = useTarget ? v[j].tx : v[j].x;
                  lo = Math.min(lo, vx0);
                  hi = Math.max(hi, vx0);
              }
              const py0 = useTarget ? p.ty : p.y;
              const vy0 = useTarget ? v[0].ty : v[0].y;
              spans.push([lo, hi, ((py0 + p.bh / 2) + (vy0 - v[0].bh / 2)) / 2]);
          }
          for (let a = 0; a < spans.length; a += 1) {
              for (let b = a + 1; b < spans.length; b += 1) {
                  if (Math.abs(spans[a][2] - spans[b][2]) > 1) {
                      continue;
                  }
                  const lo = Math.max(spans[a][0], spans[b][0]);
                  const hi = Math.min(spans[a][1], spans[b][1]);
                  if (hi - lo > 0.5) {
                      overlaps.push([lo, hi, spans[a][2]]);
                      overlapPx += hi - lo;
                  }
              }
          }
      }
      function resolve() {
          const before = overlapPx;
          layout();
          tangled = false;
          pendingLayout = false;
          // 幅を測り直した目標位置を基準に、同じ区間をもう一度測る
          measureOverlap(true);
          setStatus('線の重なり ' + Math.round(before) + 'px > ' + Math.round(overlapPx) + 'px');
          overlaps = [];
      }

08見つけたノードへ画面が近づく検索フォーカス

入力欄に文字を打つとラベルにその文字を含むノードが当たりになり、当たりがあればカメラが一歩ごとに0.08の割合で1.9倍の拡大点へ近づき、残りのノードは透明度0.25まで薄くなります。当たった文字の下には measureText で前の部分の幅を測って2pxの線を引き、検索語が空に戻れば元の倍率と位置へ帰ります。測定は累積面積53.9%・平均強度58.9・22/23フレームです。

見つけたノードへ画面が近づく検索フォーカス
新しいタブで開く

08 · 見つけたノードへ画面が近づく検索フォーカス — 計測 累積面積 53.938% · 動いたフレーム 22/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

CAM_RATEFOCUS_ZOOMDIM_ALPHA
      function applyQuery(q) {
          query = q;
          matchFirst = null;
          matchCount = 0;
          for (let i = 0; i < nodes.length; i += 1) {
              if (matches(nodes[i])) {
                  matchCount += 1;
                  if (!matchFirst) {
                      matchFirst = nodes[i];
                  }
              }
          }
          if (matchFirst) {
              camS = FOCUS_ZOOM;
              camOx = W / 2 - matchFirst.tx * FOCUS_ZOOM;
              camOy = H / 2 - matchFirst.ty * FOCUS_ZOOM;
              setStatus('見つかった: ' + matchCount);
          }
          else {
              camS = 1;
              camOx = 0;
              camOy = 0;
              setStatus(query === '' ? '検索語なし' : '一致なし');
          }
      }

09人数の合計を上へ集めて箱の大きさに反映するヒーロー場面

葉ごとに人数を持ち、rollUp が下から上へ親の合計を一歩ごとに数え直し、その合計が箱の幅・高さ・線の太さを決めます。人数が変わるたびに幅を測り直すので、箱が大きくなって兄弟が横へ押され、幅は86を上限にそれ以上は伸びません。測定は累積面積11.9%・平均強度45.8・23/23フレームです。

人数の合計を上へ集めて箱の大きさに反映するヒーロー場面
新しいタブで開く

09 · 人数の合計を上へ集めて箱の大きさに反映するヒーロー場面 — 計測 累積面積 11.908% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

COUNT_RATEWIDTH_PERHEIGHT_PERLINE_PERWIDTH_MAX
      function rollUp() {
          for (let i = nodes.length - 1; i >= 0; i -= 1) {
              const n = nodes[i];
              if (n.kids.length === 0) {
                  n.total = n.val;
              }
              else {
                  let s = 0;
                  for (let j = 0; j < n.kids.length; j += 1) {
                      s += n.kids[j].total;
                  }
                  n.total = s;
              }
              n.bw = Math.min(WIDTH_MAX, WIDTH_BASE + n.total * WIDTH_PER);
              n.bh = HEIGHT_BASE + n.total * HEIGHT_PER;
          }
      }

値が場面を変えるところ — 枝の間隔・動く割合・扇の角度

手を入れると画面が変わる値は三つです。兄弟の間隔(01の GAP_X)、箱が新しい位置へ近づく割合(03の MOVE_RATE)、扇形が占める角度(06の FAN_SPAN)で、ソースの定数を書き換えればそのまま計算に入ります。

間隔を変えると木全体の幅がついてくる

GAP_X が8のとき、いちばん上の枝の幅は440です。3に狭めると410になって葉が詰まり、16に広げると488になって480のキャンバスをはみ出します。間隔を一つ広げるごとに、兄弟の多い枝がいちばん大きく膨らみます。

割合を変えると滑る時間が変わる

MOVE_RATE の0.09は一歩ごとに残りの距離の9%を縮めるので、新しい位置の90%に届くまで約24歩(390ミリ秒)かかります。0.2に上げると約10歩(165ミリ秒)になり、同じたたみ方が短く速く終わります。

九つの場面の測定は下の表のとおりです。05と08だけが累積面積30%を超え、残りは箱がキャンバスの小さな一部なので10〜22%(03と06は21%台)にとどまります。01は一フレームで変わるのが棒の数本と親一つだけなので、面積の最大が3.5%と最も小さくなります。

項目 累積面積 平均強度 動いたフレーム
01 枝の幅の基本配置 10.9% 121.0 22/23
02 折れ線の描画 13.6% 99.6 22/23
03 たたんで再配置 21.6% 82.8 23/23
04 祖先の経路強調 13.8% 74.8 22/23
05 ポインタ拡大 45.6% 106.0 23/23
06 極座標の扇形 21.8% 98.5 23/23
07 親の付け替え 14.6% 58.2 22/23
08 検索カメラ 53.9% 58.9 22/23
09 人数の集計 11.9% 45.8 23/23

どこで壊れるか — 深い枝・片側に偏った木・読めない名前

幅がキャンバスをはみ出す場合

葉が7個の基本の木は440なので480に収まりますが、葉が8個になると56×8に間隔8×7を足した504になって外へ出ます。GAP_X を16に広げても同じことが起こるので、箱の幅と間隔はキャンバスの幅から逆算して決めます。

親が枝の幅の真ん中にならない場合

代表は最初の子(開発、中心112)と最後の子(運営、中心400)の真ん中の256に置かれます。いちばん上の枝の幅の真ん中は240なので代表は16だけ右にあり、開発の枝が184と広く、デザインと運営が120と狭いためです。親は枝の幅ではなく子の箱の位置を基準に置かれるので、片側が重い木は上が偏って見えます。

割合で縮める移動の終わりが長引く場合

03は一歩ごとに残りの距離の一定割合だけ縮めるため、90%までは速くても最後の1pxに届くまでには歩数がかかります。たたむ操作を続けると前の移動が終わる前に次の目標ができ、箱が目標を追って揺れるように見えることがあります。

二つの状態が一つの画面で重なる場合

04で指すノードを変えると、前の経路が消える動きと新しい経路が登る動きが重なり、短い間だけ二つの経路が同時に点いて見えます。07が測るのは同じ高さの横線が重なる長さだけで、デモの三回の付け替え(64px・192px・64px)はどれも幅を測り直すと0になります。深さの違う親へ移したときに線がどう見えるかまでは測っていません。

検索語が複数のノードに当たる場合

08で日本語の デ を打つとデザインとデータの二つのノードが当たりますが、カメラは最初のノードだけを追います。一文字の検索語は意図しないノードまで拾い、日本語の変換中は文字が確定する前にも input イベントが届くので、当たるノードがちらつくことがあります。定数を書き換えながらこの境目を比べるなら、下のzipをパスワード jksvbezb で開いてください。

バリエーション三つとアクセシビリティ

名前 値 感じ方
枝の間隔を変える 01 GAP_X 8 → 3 兄弟の箱の間隔 GAP_X を 8 から 3 に狭めると、いちばん上の枝全体の幅が 440 から 410 になり、葉の箱が詰まる。逆に 16 まで広げると 488 になって 480 のキャンバスをはみ出す。
動く割合を調整する 03 MOVE_RATE 0.09 → 0.2 16ms の一歩ごとに残りの距離を縮める割合 MOVE_RATE を 0.09 から 0.2 に上げると、箱が新しい位置の 90% に届くまでが約 24 歩(390ms)から約 10 歩(165ms)に縮み、滑りが短く速い終わり方になる。
扇の角度を狭める 06 FAN_SPAN 2.62 → 1.6 扇が占める角度 FAN_SPAN を 2.62 ラジアン(約 150°)から 1.6 ラジアン(約 92°)に狭めると、いちばん外側の葉の間の弧の長さが約 58px から約 36px に縮み、箱の幅 56 より狭くなる。角度を狭めるときは RING も一緒に大きくする。

prefers-reduced-motion が有効なときは、九つの場面すべてで requestAnimationFrame のループを回さず、最初の場面をすぐに描きます。この設定ではボタンの名前が「次の場面」に変わり、押すたびにデモの予定の次の出来事を実行して、補間なしの最終配置をその場で描きます。キャンバスは tabindex が0で、状態の文字は aria-live="polite" として九つの場面すべてにあります。キーの動きは場面ごとに違います。01と02は操作のない時間の関数なので、キャンバスのクリック・Enter・Spaceが再生と停止で、ノードは選べません。矢印キーでノードを選べるのは03・04・07・09だけで、03はEnterかSpaceでたたみ・開き、04と07のEscapeは選択や持ち上げの解除、09の +・- は人数の増減です。05の矢印キーは画面の移動、+・- は拡大・縮小、0・Enter・Spaceは最初の位置へ戻す操作です。08はEnterで入力欄にフォーカスが移り、Escapeで検索語が消えます。06はキーでノードを選べません。キャンバスの中のノードはDOM要素ではないため role="tree" は使わず、代わりに選んだノードの名前を状態の文字で読み上げさせています。スクリーンリーダーの利用者が木の構造を項目ごとにたどるところまでは対応していないので、実サービスでは同じデータから作ったアクセシブルな一覧を横に置いてください。

css 組織図は flex と線だけで足りるのか?

絵が固定なら足ります。html css 組織図をいちばん簡単に作るには、箱を display: flex で並べて線を擬似要素で引く方法があり、たたむ操作も項目の入れ替えもない紹介ページなら、そちらのほうが軽くなります。ただし、たたむと兄弟がなめらかに寄り、親を変えると線が引き直され、ポインタをつかんで拡大する画面では、幅を先に測って位置を計算する必要があるので、この記事の方法の出番になります。

css ツリー 構造を数値で扱うこうした計算は、社内の名簿やプロジェクトの報告ラインを見せる画面で使われます。メンバーが数十人ならたたむ操作(03)と経路の強調(04)が、部署の異動が多いなら付け替え(07)と検索(08)が先に必要です。線と箱を数値で描く別の方法は、css グラフ をSVGで描く9種 — ライブラリなしとJavaScript グラフ描画 9選 — ライブラリなしのCanvasアニメーションにあり、場面が呼び出す線・弧・文字の描画はCanvas APIのドキュメントとrequestAnimationFrameのドキュメントで読めます。

css 組織図を作る前によくある質問

ツリー表示を自分のページに貼るには何から変えますか?

zipから01を開き、ORG 配列の部署名と親の番号を書き換えます。葉が7個を超えるなら、箱の幅と GAP_X を先に縮めて、木全体の幅がキャンバスに収まるかを確かめてください。

外部のライブラリや画像は必要ですか?

必要ありません。組織図の箱・文字・線はすべてキャンバスの2Dで描き、ライブラリ・画像・外部アドレスの呼び出しがないので、場面一つのファイルだけを移してもそのまま動きます。

箱が重なったり枝が画面の外へ出たりしたらどこを直しますか?

重なるなら measure が足した幅が合っているかを先に見て、外へ出るなら NODE_W と GAP_X を縮めて、いちばん上の枝の幅をキャンバスの幅に戻します。親が片側へ偏って見えるのは誤りではなく、子の位置を基準に置く計算の結果です。

解凍パスワードを入力してください

パスワードはこの記事の本文の中にあります。読み進めると出てきます。