コンテンツへスキップ

グラフのレイアウト ​

Pivotick は、ノードを配置するための複数のレイアウト戦略をサポートしています。 layout オプションまたは simulation.layout を使用してレイアウトを構成できます。両方がセットされている場合は、layout が勝ちます。

オプション種類デフォルト説明
type'force' | 'tree' | 'structured''force'使用するレイアウト アルゴリズム。 'force' は、物理ベースの力のレイアウトを適用します。 'tree' はノードをツリー構造に配置します。 'structured' は、人間が行うような方法で小さなグラフを描画します。「 構造化レイアウト」を参照してください。

ツリーレイアウトのオプション ​

type: 'tree' を選択すると、次の追加オプションが利用可能になります。

オプション種類デフォルト説明
rootIdstringundefinedルートとして使用するノードの ID を指定します。指定しない場合、Pivotick はrootIdAlgorithmFinder に基づいてルートを自動的に選択します。
rootIdAlgorithmFinder-MaxReachabilityルート ノードを自動的に検索するために使用されるアルゴリズム。
parentKeystringundefinedノードの親の ID を保持する node.data キーの名前。設定されていないと、親子関係は端から生まれます。 階層の宣言を参照してください。
depthKeystringundefined0 から数えて、ノードが存在する行を保持する node.data キーの名前。設定を解除すると、ノードは親の 1 行下に位置します。
strengthnumber0.1ツリー構造を維持する力の強さ。
horizontalbooleanfalseノードを垂直ではなく水平に配置します。
radialbooleanfalseノードを垂直ではなく放射状に配置します。
radialGapnumber750放射状レイアウトのレイヤー間のギャップ。
spacing'auto' | 'manual''auto'以下の 2 つの乗算器がノードのサイズとツリーの形状から自動的に調整されるかどうか。どちらかを明示的に設定すると、'manual' が暗黙的に指定されます。
levelSpacingnumber1連続するレベル間の距離 (radial の場合、リング間) を乗算します。 1 は、前と同じように木をキャンバスにフィットさせます。
siblingSpacingnumber1* レベル内のノード間の距離 * を乗算します。 radial の場合、レベルは常に完全な円にまたがる場合は無視されます。

サイクルと切り離されたグラフ ​

階層は、生のツリーからではなく、rootId をルートとする幅優先のスパニング ツリー から構築されます。 エッジなので、どちらもツリー レイアウトを停止しません。

  • Cycles. ノードに到達する最初のエッジがそのノードの親になります。に到達するエッジ すでに配置されているノードは単に階層の一部ではなく、他のノードと同様にレベルをまたいで描画されます。 もう一方の端。 2 つの親を持つノードも同様に、そのうちの 1 つによって要求されます。
  • 複数のコンポーネント。 ルートが到達できない各コンポーネントは、ノードを優先して独自のルートを取得します。 入力エッジがないため、is 階層であるコンポーネントは 1 つとして描画されます。 並んで配置されています。
  • エッジがまったくないノード。 これらは階層内に場所がないため、むしろ パーク になります。 与えられたものよりも、それらは浅いレベルの横のデッドスペースを埋めます。 木が下に行くにつれて広がるため、空のままにするレイアウト。放射状レイアウトでは、 最後の外側に自分たちのリング。何もポイントしないとをポイントするノードのみが存在します。 駐車しているので、木から駐車エリアまで伸びる端が残されることはありません。

rootIdAlgorithmFinder: 'MinMaxDistance' と 'MinHeight' は同じ検索、つまりノードであることに注意してください。 最も長いパスが最も短いものです。両方ともトポロジカルソートに基づいているため、サイクルのあるグラフ上で使用されます。 警告を発し、最初のノードにフォールバックします。デフォルトの 'MaxReachability' にはそのような制限はありません。

木の根ファインダー ​

ツリーベースのレイアウトでは、1 つのノードをルートとして指定する必要があります。 rootId オプションを使用してルート ノードを指定できます。

どのノードをルートにすべきかわからない場合でも、心配する必要はありません。 Pivotick は、rootIdAlgorithmFinder で定義されたアルゴリズムを使用して自動的に選択します。

名前付きの rootId はエッジの方向** を無視して ** 歩くので、どのノードもツリー全体をルート化できます。 リーフとグラフはその下に再びぶら下がり、元の親は 1 レベル下になります。 (矢印に沿って進むと 葉からは何も到達せず、グラフの残りの部分はその横に 2 番目のグラフとして配置されます。 ) エッジ自体はそのまま描画されるため、リンクが逆に使用されます。 レベルを上向きの矢印としてレンダリングします。 rootIdAlgorithmFinderが選んだ根は歩き続ける 矢印: それは最初から読み取られていました。

矢印が階層ではない場合。 収束 のデータ — すべてのリーフがソース、それらすべて いくつかのハブを指しており、これがほとんどの出所と「見られる」データの形状です。どのノードも到達しません。 どちらの発見者が質問しても、矢印に沿ってグラフが表示されます。有向ウォークはほぼすべてのノードを離れます 独自の根として、木は切り株の櫛のように出てきて、ほとんどの端が枝を横切って飛んでいます。 レイアウト。したがって、どのノードもが矢印に従って自身のコンポーネントの半分をカバーできる場合、 レイアウトは方向の読み取りを停止します。すべてのエッジを両方向に歩き、グラフの中央で再ルートします。 エッジはそのまま 描画 のままです。これは、エッジの矢印が木の上を向いていることを意味します。 root — その方向を示すデータの正直なレンダリング。

これはルールではなくフォールバックであり、テストはルートではなくグラフに関するものです。 選んだ:

  • 矢印が階層を形成する場合、それを基準に配置するのが最適であり、すべてのファインダーがそれを維持します。 独自の答え — 木上の答えは常に 葉 である 'MinHeight' を含み、 故意ではありません。グラフのほとんどをカバーしないルートは、他に何もない場合にのみ無効になります。 もっとカバーできたかもしれない。
  • いくつかの個別の階層のグラフがフォレストとして表示されます。カバレッジは以下に基づいて測定されます。 ルート自体のコンポーネントであるため、完全に適切な複数コンポーネント階層がルートと間違われることはありません。 失敗。
  • 固定された rootId はテストを完全にスキップします。すでに方向を無視して歩いています。

グラフにないノードに名前を付ける rootId — フィルタリングされ、削除され、折りたたまれたクラスター内で — それが真実である限り無視され、ファインダーは代わりにルートを選択します。オプションはそのままで、 したがって、戻ってきたノードはツリーを再ルートします。

ルートは実行時に変更することもできます。物理フライアウトでは、Root 行のメニューとしてルートが提供されます。 ツリー レイアウトがアクティブなときはいつでも。メニューには各ファインダーの名前があり、その機能が説明されています。最初のエントリ は「選択したノード」であり、ノードが 1 つだけ選択されるまでグレー表示されます。ノードが確立されると、 固定すると、行にはファインダーの名前ではなく、そのノードのラベルが表示されます。

ts
graph.simulation.setTreeRoot({ rootId: 'node-42' })          // pin the tree to a node
graph.simulation.setTreeRoot({ algorithm: 'MinHeight' })     // drop the pin, let the finder choose
graph.simulation.getTreeRoot() // { rootId: undefined, algorithm: 'MinHeight' }

階層の宣言 ​

通常、Pivotick はそれ自体で階層を作成します。つまり、スパニング ツリーからの親関係が、 エッジ、および各ノードはその親の 1 行下にあります。 2 つのオプションでは、代わりにデータにそれを伝えます。

  • parentKey は、ノードの親 ID を保持する node.data キーに名前を付けます。
  • depthKey は、0 から数えて、その行を保持する node.data キーに名前を付けます。 一番浅い根元。

どちらもデフォルトでは設定されておらず、キーに名前を付けるまでは何も読み取られません。つまり、データが含まれるグラフ たまたま depth または parent フィールドが含まれる場合は、要求するまで影響を受けません。

いずれかのキーが個々のノードで欠落している可能性があります。宣言された親のないノードは、 いつものようにエッジ。行が宣言されていないノードは、その親の 1 つ下に位置します。どちらも宣言していないグラフ いつもと同じように配置されています。

さらに下からツリーを開始する ​

depthKey の主な目的はこれです。いくつかの別々の木のグラフは 森 であり、その根は すべてが最も浅い行を共有します。以前は、そのうちの 1 つがさらに深い行に属していると判断する方法はありませんでした。 ダウン。そのルートに行を追加します。

ts
const data = {
    nodes: [
        { id: 'a' }, { id: 'a1' }, { id: 'a2' },
        { id: 'b', data: { level: 2 } }, { id: 'b1' }, { id: 'b2' },
    ],
    edges: [
        { from: 'a', to: 'a1' }, { from: 'a', to: 'a2' },
        { from: 'b', to: 'b1' }, { from: 'b', to: 'b2' },
    ],
}

const graph = new Pivotick(container, data, {
    layout: { type: 'tree', depthKey: 'level' },
})
row 0     a
row 1   a1  a2
row 2                b          <- b asked for row 2
row 3              b1  b2

木全体がその根とともに動きます。b の子供たちはそれを追いかけます。 radial レイアウトでは、行は ring つまり、木が 2 つ鳴るという同じ宣言が始まります。

行の意味 ​

行はノード をさらに下 に押すことしかできません。整然としたツリーは子をちょうど 1 行下に配置します その親であるため、親の行より下にない行は描画できません — parent + 1 に固定されています そして警告にはその数が表示されます。別の方法では、ノードをその行から切り離すことで行を尊重します。 親の場合、間違った数値を入力すると、ツリーが余分な部分に分割されてしまいます。

データがスキップする行は ** 空のままで、実空間** を占有します。これが、宣言された行になります。 行には何か意味があります。ツリーはキャンバスに合わせて拡大縮小されるため、浅い行 50 を宣言します。 グラフは高さを 51 行にし、実際のすべての行をゼロに圧縮します。 levelSpacingと 自動間隔 は元に戻す方法です。

宣言された親が行うこと ​

宣言された親は、エッジがペア ** に参加するかどうかに関係なく、 ** として受け入れられます。それによって次のことが言えるようになります エッジに含まれない階層 — しかし、それはまた、そのような枝が線に沿って描画されないことを意味します そこには描くエッジがないからです。宣言は階層の由来です。の エッジはそのまま描画されるだけです。

2 つの有益な結果: 複数の入力エッジを持つノードは、どれが本当の親であるかを知ることができます。 (そうでない場合、スパニング ツリーは最初に到達した方を採用します)、およびエッジがまったくないノード これは、親が駐車エリアではなくツリー内の適切な場所を取得することを宣言します。

使用できない宣言はそのノードでは削除され、エッジにフォールバックされます。 1 つの警告がすべてをまとめます。

  • レイアウトされていないノードに名前を付ける親 - フィルタリング、削除、または折りたたまれたノードの内部 クラスター — 名前を付けたノードが見つからない場合と同じ方法で rootId がフォールバックします。
  • サイクルを閉じる親 (サイクルを閉じるリンクがドロップされたリンクです)。
  • 0 以上の数値ではない行。
優先順位 ​

rootId は parentKey を上回ります。ルートを固定する — オプションで、または物理演算でルートを選択します レール - そのノードからツリーを再度吊り下げるリクエストであるため、親関係はエッジから再導出されます。 そして宣言された両親はそのパスのために確保されます。宣言された ** 行** は影響を受けません: 深さを示します ノードはぶら下がっているものではなく、座っています。

egoTree は両方のオプションを無視します。ルート自体の近隣からスターを構築します。

間隔 ​

木はキャンバスにぴったりになるようにレイアウトされているため、自然な間隔はすでにキャンバスのサイズに従っており、 木の深さと幅はどれくらいですか。ジオメトリに適合するlevelSpacing および siblingSpacing スケール: 2 は、奥行き軸と幅軸でそれぞれ「フィットしたレイアウトの 2 倍の距離」を意味します。両方 デフォルトは 1 で、これは適合レイアウト自体であり、どちらも 0.5 から 10 を受け入れます。

これらは実行時に変更することもできます。ツリーを作成するたびに、物理フライアウトがスライダーとしてそれらを提供します。 レイアウトがアクティブです (シミュレーション ノブの代わりに、ツリー レイアウトは無視します)。

ts
graph.simulation.setTreeSpacing({ levelSpacing: 2 })
graph.simulation.getTreeSpacing() // { levelSpacing: 2, siblingSpacing: 1 }

自動間隔 ​

デフォルトでは、ツリーは独自の間隔をあけます。ツリー レイアウトのサイズはキャンバスから決まります。 ノードの大きさを確認するため、小さな点のツリーと大きなラベル付きノードのツリーが配置されます。 2 つ目は同じように出力され、2 つ目は重複します。 spacing: 'auto' は最もきついペアを測定します。 各軸上の隣接要素は、その半径に必要なものを計算します (矢印の余地を加えたもの) レベル間)、それに合わせて乗数を調整します。グラフが変化するたびにそれらを再導出します。

意図的に行わない 2 つのこと:

  • 適合したレイアウトよりもきつくツリーを詰め込むことはありません。自動は乗数をそれ以上に上げるだけです。 1 なので、混雑することのなかったグラフが、いつもとまったく同じようにレイアウトされます。
  • 選択した内容が上書きされることはありません。 levelSpacing または siblingSpacing を設定するツリーは、 決心したとみなされ、どちらかのスライダーを手で動かすと、自動のままになります。を手渡します マルチプライヤーはsimulation.enableAutoTreeSpacing()に戻ります。

radial レイアウトでは、レベルが常に円全体に広がり、リング内に密集することは可能です。 リングをさらに外側に押し出すと緩和されます。そのため、両方の測定値が levelSpacing になります。

構造化されたレイアウト ​

type: 'structured' は、人間が描くような方法で小さなグラフをレイアウトします。 一目で判読できる必要があるビューア。シミュレーションではなく計算されます。すべてのノードは 配置して固定するため、同じグラフでは常に同じ画像が表示され、ノードをドラッグすると移動します そのノードのみ。

  • コンポーネントを並べて表示します。 接続された各コンポーネントは独自にレイアウトされ、 コンポーネントが行に詰め込まれています。行の幅は、レイアウト全体を行の幅に合わせたものになります。 キャンバスを最大ズームで表示します。
  • 扇子が並びます。 同じ種類の葉が 3 枚以上、同じ 1 枚または 2 枚から垂れ下がっています。 ノード (葉には最大 2 つの隣接ノードがあります) は 1 つの等間隔の行または列に立っています。 ノードはその片側にぶら下がっています。グループ化は Simplify のものです 同じ隣接 ルールが使用され、UI.simplify.typeOf によって入力され、それ以外の場合 render.nodeTypeAccessor。何も折りたたまれていません。
  • 距離はエッジに従います。 他のすべては応力メジャー化によって配置されます: ノード 1 エッジを離すと、エッジの長さは 1 つ離れて配置され、2 つのエッジはその 2 倍離れて配置されます。ハブの 隣人はその周りに出てきますし、鎖はまっすぐに出てきますが、どちらにも特別なケースはありません。
  • ルーズなノード用のトレイ。 エッジがまったくないノードは、残りの下にグリッドで配置されるか、そのノードに沿って配置されます。 そうです。
  • フリー側のラベル。 フローティングラベル (textVerticalShift または textHorizontalShift) 1 以上) は、エッジが自由なままになっているノードの側面に、 スタイルでそれを置きます。ファンのラベルはすべて、ファンがぶら下がっているノードの反対側を向いています。

表示されるグラフが変更されるたびにレイアウトが再実行され、最初の描画後にもう一度再実行されます。 ノードの実際のサイズがわかっている場合。ノードは getLayoutRadius() 間隔で配置されているため、ノード スタイル layoutSize を宣言しているものは、何を描画してもそのサイズでレイアウトされます。のグラフを対象としています。 最大数百ノード。

オプション種類デフォルト説明
groupMinnumber3タイプを共有する最も少ない葉と、扇状に並ぶその隣の葉。 1 つずつ配置されます。
direction'auto' | 'row' | 'column''auto'ファンが並ぶ様子。 'auto' は、ぶら下がっているノードへの線を越えて立っています。
gapnumber302 つのノード間のスペースを空けます。エッジはこの 2 倍の長さで描画されます。コンポーネントとトレイはこの 3 倍離れて設置されます。
trayPosition'bottom' | 'right''bottom'エッジのないノードがどこに行くか。
labelSidesbooleantrueフローティングされた各ラベルを端が空いている側に移動します。 false は、スタイルが配置する場所にラベルを残します。
ts
new Pivotick(container, data, {
    layout: { type: 'structured', trayPosition: 'right' },
    render: {
        nodeTypeAccessor: (node) => node.getData().type,
        defaultNodeStyle: { text: (node) => node.getData().label, textVerticalShift: -1.4 },
    },
})

graph.simulation.getLabelSide(id) は、ノードに選択された側を報告します: 'top'、'bottom'、 他のレイアウトでは、'left'、'right'、または undefined。

ビューはラベルではなくノードに適合するため、端のノードの横に長いラベルが表示されます。 レイアウトの一部がキャンバスを越えて実行される可能性があります。

危険

2 つのレイアウトの例を追加します。

垂直ツリー ​

js
const options = {
    layout: {
        type: 'tree'
    },
    UI: {
        mode: 'static'
    }
}

垂直ツリー ​

js
const options = {
    layout: {
        type: 'tree',
        radial: true,
        radialGap: 300,
    },
    UI: {
        mode: 'static'
    }
}