How to add scrollbar beside Table inside Node

Hi,

I want to know how to add scrollbar beside Table inside Node.

We now currently use GoJS 2.3, and don’t want to change (upgrade) version if possible.
(GoJS 3.0 supports overflow property in go.Panel, but we can’t use it)

After some research, adding custom css property (like overflowY = "auto“) could be the solution, but I haven’t succeeded yet.

I attached some part of current code.

I’m greatly appreciated if anyone help me.
Thanks in advance.

Takuya

'use strict';

import * as go from "gojs";
import { ReactDiagram } from "gojs-react";
import * as React from "react";
import * as U from "./DiagramUtils";

import "./App.css";

// ノードデータ(nodeDataArrayに格納する要素の型)
export interface INexNodeData {
  key: string;
  text: string;
  loc: string;
  size: string;
  color: string;
  bShowInferMon: boolean  // 推論モニターの表示フラグ
  nodeLevels: INodeLevel[];
}

// ノード状態(水準とその確率値)
export interface INodeLevel {
  // 各行のカテゴリ指定を追加(GoJSノード上のテーブル表示のため)
  category?: string;   // ヘッダ行のとき"Header"、データ本体のとき"Data"

  // 以下はcategory == "Data"のときのみ意味を持つ
  level?: string; // 状態名(水準名)
  prior?: number; // 事前確率
  posterior?: number; // 事後確率
}

// ネットワークデータ
const INIT_nodeDataArray: INexNodeData[] = [
  {
    key: "A",
    text: "Polution?",
    loc: "-150 -200",
    size: "250 150",
    color: "green",
    bShowInferMon: true,
    nodeLevels: [
      { category: "Header" },
      {
        category: "Data",
        level: "Low",
        prior: 0.9,
        posterior: 0,
      },
      {
        category: "Data",
        level: "High", prior: 0.1, posterior: 1
      },
    ],
  },
  {
    key: "B",
    text: "smoker?",
    loc: "150 -200",
    size: "250 200",
    color: "green",
    bShowInferMon: true,
    nodeLevels: [
      { category: "Header" },
      { category: "Data", level: "Yes", prior: 0.2, posterior: 0.2 },
      { category: "Data", level: "No", prior: 0.5, posterior: 0.3 },
      { category: "Data", level: "Sometimes", prior: 0.2, posterior: 0.4 },
      { category: "Data", level: "Quit", prior: 0.1, posterior: 0.1 },
    ],
  },
  {
    key: "C",
    text: "μs",
    loc: "30 40",
    size: "60 50",
    color: "red",
    bShowInferMon: false,
    nodeLevels: [
      { category: "Header" },
      { category: "Data", level: "Honoka", prior: 0.1, posterior: 0.1 },
      { category: "Data", level: "Kotori", prior: 0.1, posterior: 0.1 },
      { category: "Data", level: "Umi", prior: 0.1, posterior: 0.1 },
      { category: "Data", level: "Maki", prior: 0.1, posterior: 0.1 },
      { category: "Data", level: "Rin", prior: 0.1, posterior: 0.1 },
      { category: "Data", level: "Pana", prior: 0.1, posterior: 0.1 },
      { category: "Data", level: "Eli", prior: 0.1, posterior: 0.1 },
      { category: "Data", level: "Nozomi", prior: 0.1, posterior: 0.1 },
      { category: "Data", level: "Nico", prior: 0.1, posterior: 0.1 },
    ],
  },
  {
    key: "D",
    text: "rentgen?",
    loc: "-150 150",
    size: "150 50",
    color: "orange",
    bShowInferMon: false,
    nodeLevels: [
      { category: "Header" },
      { category: "Data", level: "Pos", prior: 0.2, posterior: 1.0 },
      { category: "Data", level: "Neg", prior: 0.8, posterior: 0 },
    ],
  },
  {
    key: "E",
    text: "LungCancer?",
    loc: "150 150",
    size: "250 150",
    color: "orange",
    bShowInferMon: true,
    nodeLevels: [
      { category: "Header" },
      { category: "Data", level: "Yes", prior: 0.3, posterior: 0.4 },
      { category: "Data", level: "No", prior: 0.7, posterior: 0.6 },
    ],
  },
];

const INIT_linkDataArray = [
  { from: "A", to: "C" },
  { from: "B", to: "C" },
  { from: "C", to: "D" },
  { from: "C", to: "E" },
];

const MyDiagram = (): any => {
  const [nodeDataArray, /* setNodeDataArray */] = React.useState(INIT_nodeDataArray);
  const [linkDataArray, /* setLinkDataArray */] = React.useState(INIT_linkDataArray);

  const initDiagram = () => {
    // if ((window as any).goSamples) (window as any).goSamples();  // init for these samples -- you don't need to call this

    const $ = go.GraphObject.make;  // for conciseness in defining templates

    const diagram = $(go.Diagram, /*'myDiagramDiv',*/  // create a Diagram for the DIV HTML element
      {
        'undoManager.isEnabled': true,  // enable undo & redo

        model: $(go.GraphLinksModel, {
          linkKeyProperty: "key", // IMPORTANT! must be defined for merges and data sync when using GraphLinksModel
        })
      });

    // テンプレートマップを作成
    // キー名はデータ中の"category"に対応する
    //
    // typeScript版はgo.Map作成時に型を指定しないとコンパイルエラーになる(2024/5/14)
    // <参考>
    // https://forum.nwoods.com/t/tooltip-and-template-with-table/14759/6
    var itemTemplateMap = new go.Map<string, go.Panel>();

    itemTemplateMap.add(
      "Data", // デフォルトのテンプレート(テーブルのヘッダ以外の部分に対応)
      $(
        go.Panel,
        "TableRow",
        $(go.TextBlock,   // 1列目(状態名)
          {
            column: 0,
            margin: 2,
            font: "bold 10pt sans-serif",
          },
          new go.Binding("text", "level")
        ),
        $(go.Panel, // 2列目(事前確率)
          {
            width: 50,
            column: 1,
            margin: new go.Margin(2, 2, 0, 2),
          },
          $(go.TextBlock, new go.Binding("text", "prior"))
        ),
        $(go.Panel, // 3列目(事後確率)
          {
            width: 50,
            column: 2,
            margin: new go.Margin(2, 2, 0, 2),
          },
          $(go.TextBlock, new go.Binding("text", "posterior"))
        ),
      )
    );
    itemTemplateMap.add(
      "Header", // テーブルヘッダ用
      $(
        go.Panel,
        "TableRow",
        $(go.TextBlock, "State", {
          column: 0,
          margin: 2,
          font: "bold 10pt sans-serif",
        }),
        $(go.TextBlock, "Prior", {
          column: 1,
          margin: 2,
          font: "bold 10pt sans-serif",
        }),
        $(go.TextBlock, "Posterior", {
          column: 2,
          margin: 2,
          font: "bold 10pt sans-serif",
        }),
      )
    );

    let scrollableHTML = new go.HTMLInfo();
    scrollableHTML.show = function (obj: go.GraphObject, diagram: go.Diagram, tool: go.Tool) {
      const div = document.createElement("div");

      // JavaScriptでスタイル指定
      div.style.width = "200px";
      div.style.height = "100px";
      div.style.overflowY = "auto";
      div.style.border = "1px solid lightgray";
      div.style.padding = "5px"; // 内側の余白を追加

      return div;
    }

    // define a simple Node template
    diagram.nodeTemplate = $(
      go.Node,
      "Auto", // the Shape will go around the TextBlock
      {
        resizable: true
      },
      new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
      new go.Binding("desiredSize", "size", go.Size.parse).makeTwoWay(go.Size.stringify),
      $(
        go.Shape,
        "RoundedRectangle",
        {
          name: "SHAPE",
          fill: $(go.Brush, "Linear", { 0: "white", 1: "lightblue" }),
          strokeWidth: 1,
        },
        // Shape.fill is bound to Node.data.color
        new go.Binding("fill", "color"),
      ),
      $(
        go.Panel,
        "Vertical",
        $(
          go.Panel,
          "Horizontal",
          $(
            go.TextBlock,
            {
              name: "KeyText",
              margin: 2,
              editable: false,
              font: "bold 18pt sans-serif",
              stroke: "blue",
            },
            new go.Binding("text", "text"),
          ),
          // この辺りのコードはsamples/IVRtree.htmlを参考にした(2022/3/10)
          $(
            "PanelExpanderButton",
            {
              // name: "NodePanelExpandButton",
              column: 1,
              alignment: go.Spot.Right,
              click: (e: go.InputEvent, button: go.GraphObject) => {
                if (button.part) {
                  const nodeData = button.part.data;
                  const key = nodeData.key;
                  if (key) {
                    const nodeObj = button.diagram?.findNodeForKey(key);
                    if (nodeObj) {
                      const visible = U.isVisibleInferMon(nodeObj);
                      const bNewVisible = !visible;

                      // ノードのサイズ計算
                      const newSize = U.getNodeSize(nodeObj, bNewVisible, false, true);
                      button.diagram?.commit((d: go.Diagram) => {
                        const model = d.model as go.GraphLinksModel;

                        model.setDataProperty(nodeData, "bShowInferMon", bNewVisible);
                        model.setDataProperty(nodeData, "size", `${newSize.width} ${newSize.height}`);  // 更新後の値をnodeDataArrayにセット
                      })
                    }
                  }
                }
              }
            }
          ),
        ),
        $(go.Panel,
          "Vertical",
          // "Auto",
          {
            name: "COLLAPSIBLE",  // ★
            // stretch: go.GraphObject.Horizontal // available after GoJS 3.0
            // height: 200, // ここで高さ指定してもスクロールバーは出ない
          },
          new go.Binding("visible", "bShowInferMon"),
          $(
            go.Panel,
            "Table",
            new go.Binding("itemArray", "nodeLevels"),  // Table.itemArrayを、nodeDataArrayの各要素のnodeLevelsに結びつけよ、の意
            new go.Binding("desiredSize", "", (nodeData: INexNodeData, node: go.GraphObject) => {
              if (node && node.diagram) {
                const nodeObj: go.Node | null = node.diagram.findNodeForKey(nodeData.key);
                if (nodeObj) {
                  // ノードのグリッドのサイズ計算
                  const visible = U.isVisibleInferMon(nodeObj);
                  const newSize = U.getNodeSize(nodeObj, visible, true, false);  // 第3引数true = グリッド部分のみのサイズ、false = ノード全体サイズ
                  return newSize;
                }
              }
              return undefined;
            }),
            {
              // height: 100,  // 効かない
              // overflow: go.Panel.Vertical, // available after GoJS 3.0
              defaultAlignment: go.Spot.Left,
              defaultColumnSeparatorStroke: "black",
              background: "white",
              itemTemplateMap: itemTemplateMap,
            },
            $(go.RowColumnDefinition, { row: 0, background: "lightgray" }),
            $(go.RowColumnDefinition, { row: 1, separatorStroke: "black" })
          ),
        ),
      )
    );

    return diagram;
  }

  return (
    <ReactDiagram
      // ref={this.diagramRef}
      divClassName="diagram-component"
      initDiagram={initDiagram}
      nodeDataArray={nodeDataArray}
      linkDataArray={linkDataArray}
    />
  );
}

export { MyDiagram };

For performance and rendering flexibility reasons, GoJS has never supported HTML layout within Nodes or Links, and thus has never supported CSS layout properties.

If you want to let the user scroll a “Table” Panel, use a “ScrollingTable” instead. Example: Scrolling Table Panels with ScrollBars | GoJS Diagramming Library
It’s defined at extensionsJSM/ScrollingTable.ts.

Thank you for valuable information!
it seems ScrollingTable works fine!

Takuya.