TableLayout 中的动态节点

Dynamic nodes in TableLayout

我在 table 布局中有一个节点图。

如果我在单元格中有一些节点,我会得到这个 table:

图片: https://ibb.co/554y9ck

(Q2后面有Q0和Q1..它们是重叠的)

怎样才能把它们排好? :)

这是我的节点模板:

 var nodeSimpleTemplate =
        $(go.Node, "Auto",mouseEventHandlers(),
            new go.Binding("row").makeTwoWay(),
            new go.Binding("column", "col").makeTwoWay(),
            new go.Binding("alignment", "align", go.Spot.parse).makeTwoWay(go.Spot.stringify),
            new go.Binding("layerName", "isSelected", function(s) { return s ? "Foreground" : ""; }).ofObject(),
            {
                //locationSpot: go.Spot.Center,
                // when the user clicks on a Node, highlight all Links coming out of the node
                // and all of the Nodes at the other ends of those Links.
                click: function (e, node) {
                    var diagram = node.diagram;
                    diagram.startTransaction("Click simple node");
                    diagram.clearHighlighteds();
                    // @ts-ignore
                    node.findLinksOutOf().each(function (l) {
                        changeLinkCategory(e, l);
                        l.isHighlighted = true;
                    });
                    // @ts-ignore
                    node.findNodesOutOf().each(function (n) {
                        n.isHighlighted = true;
                    });
                    changeNodeCategory(e, node);
                    diagram.commitTransaction("Click simple node");
                }
            },
            $(go.Shape, "Ellipse",
                {
                    fill: $(go.Brush, "Linear", {0: "white", 1: "lightblue"}),
                    stroke: "darkblue", strokeWidth: 2
                }),
            $(go.Panel, "Table",
                {defaultAlignment: go.Spot.Left, margin: 4},
                $(go.RowColumnDefinition, {column: 1, width: 4}),
                $(go.TextBlock,
                    {row: 0, column: 0, columnSpan: 3, alignment: go.Spot.Center},
                    {font: "bold 14pt sans-serif"},
                    new go.Binding("text", "key"))
            ));

    var nodeDetailedTemplate =
        $(go.Node, "Auto",mouseEventHandlers(),
            new go.Binding("row").makeTwoWay(),
            new go.Binding("column", "col").makeTwoWay(),
            new go.Binding("alignment", "align", go.Spot.parse).makeTwoWay(go.Spot.stringify),
            new go.Binding("layerName", "isSelected", function(s) { return s ? "Foreground" : ""; }).ofObject(),
            {
                //locationSpot: go.Spot.Center,
                // when the user clicks on a Node, highlight all Links coming out of the node
                // and all of the Nodes at the other ends of those Links.
                click: function (e, node) {
                    var diagram = node.diagram;
                    diagram.startTransaction("Click Details node");
                    diagram.clearHighlighteds();
                    // @ts-ignore
                    node.findLinksOutOf().each(function (l) {
                        changeLinkCategory(e, l);
                        l.isHighlighted = true;
                    });
                    // @ts-ignore
                    node.findNodesOutOf().each(function (n) {
                        n.isHighlighted = true;
                    });
                    changeNodeCategory(e, node);
                    diagram.commitTransaction("Click Details node");
                }
            },

            $(go.Shape, "Ellipse",
                {
                    fill: $(go.Brush, "Linear", {0: "white", 1: "lightblue"}),
                    stroke: "darkblue", strokeWidth: 2
                }),
            $(go.Panel, "Table",
                {defaultAlignment: go.Spot.Left, margin: 4},
                $(go.RowColumnDefinition, {column: 1, width: 4}),
                $(go.TextBlock,
                    {row: 0, column: 0, columnSpan: 3, alignment: go.Spot.Center},
                    {font: "bold 14pt sans-serif"},
                    new go.Binding("text", "key")),
                $(go.TextBlock, "Time: ",
                    {row: 1, column: 0}, {font: "bold 10pt sans-serif"}),
                $(go.TextBlock,
                    {row: 1, column: 2},
                    new go.Binding("text", "time")),
                $(go.TextBlock, "Parameters: ",
                    {row: 2, column: 0}, {font: "bold 10pt sans-serif"}),
                $(go.TextBlock,
                    {row: 2, column: 2},
                    new go.Binding("text", "parameters"))
            )
        );

    // for each of the node categories, specify which template to use
    dia.nodeTemplateMap.add("simple", nodeSimpleTemplate);
    dia.nodeTemplateMap.add("detailed", nodeDetailedTemplate);

这是图表定义:

 public initDiagram(): go.Diagram {
    // define a custom ResizingTool to limit how far one can shrink a row or column
    function LaneResizingTool() {
        go.ResizingTool.call(this);
    }
    go.Diagram.inherit(LaneResizingTool, go.ResizingTool);

    LaneResizingTool.prototype.computeMinSize = function() {
        var diagram = this.diagram;
        var lane = this.adornedObject.part;  // might be row or column
        var horiz = (lane.rowSpan >= 9999);  // column header
        var margin = diagram.nodeTemplate.margin;
        var bounds = new go.Rect();
        diagram.findTopLevelGroups().each(function(g) {
            if (horiz ? (g.column === lane.column) : (g.row === lane.row)) {
                var b = diagram.computePartsBounds(g.memberParts);
                if (b.isEmpty()) return;  // nothing in there?  ignore it
                b.unionPoint(g.location);  // keep any empty space on the left and top
                b.addMargin(margin);  // assume the same node margin applies to all nodes
                if (bounds.isEmpty()) {
                    bounds = b;
                } else {
                    bounds.unionRect(b);
                }
            }
        });

        // limit the result by the standard value of computeMinSize
        var msz = go.ResizingTool.prototype.computeMinSize.call(this);
        if (bounds.isEmpty()) return msz;
        return new go.Size(Math.max(msz.width, bounds.width), Math.max(msz.height, bounds.height));
    };

    LaneResizingTool.prototype.resize = function(newr) {
        var lane = this.adornedObject.part;
        var horiz = (lane.rowSpan >= 9999);
        var lay = this.diagram.layout;  // the TableLayout
        if (horiz) {
            var col = lane.column;
            var coldef = lay.getColumnDefinition(col);
            coldef.width = newr.width;
        } else {
            var row = lane.row;
            var rowdef = lay.getRowDefinition(row);
            rowdef.height = newr.height;
        }
        lay.invalidateLayout();
    };
    // end LaneResizingTool class

    function AlignmentDraggingTool() {
        go.DraggingTool.call(this);
    }
    go.Diagram.inherit(AlignmentDraggingTool, go.DraggingTool);

    AlignmentDraggingTool.prototype.moveParts = function(parts, offset, check) {
        go.DraggingTool.prototype.moveParts.call(this, parts, offset, check);
        var tool = this;
        parts.iteratorKeys.each(function(part) {
            if (part instanceof go.Link) return;
            var col = part.column;
            var row = part.row;
            if (typeof col === "number" && typeof row === "number") {
                var b = computeCellBounds(col, row);
                part.alignment = new go.Spot(0.5, 0.5, b.centerX, b.centerY);  // offset from center of cell
            }
        })
    }
    // end AlignmentDraggingTool

    // Utility functions, assuming the Diagram.layout is a TableLayout,
    // and that the rows and columns are implemented as Groups

    function computeCellBounds(col, row) {  // this is only valid after a layout
        //@ts-ignore
        var coldef = dia.layout.getColumnDefinition(col);
        //@ts-ignore
        var rowdef = dia.layout.getRowDefinition(row);
        return new go.Rect(coldef.position, rowdef.position, coldef.total, rowdef.total);
    }

    function findColumnGroup(col) {
        var it = dia.findTopLevelGroups();
        while (it.next()) {
            var g = it.value;
            if (g.column === col && g.rowSpan >= 9999) return g;
        }
        return null;
    }

    function findRowGroup(row) {
        var it = dia.findTopLevelGroups();
        while (it.next()) {
            var g = it.value;
            if (g.row === row && g.columnSpan >= 9999) return g;
        }
        return null;
    }

    function mouseEventHandlers() {  // standard mouse drag-and-drop event handlers
        return {
            mouseDragEnter: function(e) { mouseInCell(e, true); },
            mouseDragLeave: function(e) { mouseInCell(e, false); },
            mouseDrop: function(e) { mouseDropInCell(e, e.diagram.selection); }
        };
    }

    function mouseInCell(e, highlight) {
        e.diagram.clearHighlighteds();

        var col = e.diagram.layout.findColumnForDocumentX(e.documentPoint.x);
        if (col < 1) col = 1;  // disallow dropping in headers
        var g = findColumnGroup(col);
        if (g !== null) g.isHighlighted = highlight;

        var row = e.diagram.layout.findRowForDocumentY(e.documentPoint.y);
        if (row < 1) row = 1;
        g = findRowGroup(row);
        if (g !== null) g.isHighlighted = highlight;
    }

    function mouseDropInCell(e, coll) {
        var col = e.diagram.layout.findColumnForDocumentX(e.documentPoint.x);
        if (col < 1) col = 1;  // disallow dropping in headers
        var row = e.diagram.layout.findRowForDocumentY(e.documentPoint.y);
        if (row < 1) row = 1;
        coll.each(function(node) {
            if (node instanceof go.Node) {
                node.column = col;
                node.row = row;
                // adjust the alignment to the new cell's center point
                var cb = computeCellBounds(col, row);
                var ab = node.actualBounds.copy();
                //@ts-ignore
                if (ab.right > cb.right-node.margin.right) ab.x -= (ab.right - cb.right + node.margin.right);
                //@ts-ignore
                if (ab.left < cb.left+node.margin.left) ab.x = cb.left + node.margin.left;
                //@ts-ignore
                if (ab.bottom > cb.bottom-node.margin.bottom) ab.y -= (ab.bottom - cb.bottom + node.margin.bottom);
                //@ts-ignore
                if (ab.top < cb.top+node.margin.top) ab.y = cb.top + node.margin.top;
                var off = ab.center.subtract(cb.center);
                node.alignment = new go.Spot(0.5, 0.5, off.x, off.y);
            }
        });
        dia.layoutDiagram(true);
    }

    const $ = go.GraphObject.make;
    const dia = $(go.Diagram,{
        layout: $(TableLayout,
            $(go.RowColumnDefinition, { row: 0, height: 50, minimum: 50 }),
            $(go.RowColumnDefinition, { column: 0, width: 100, minimum: 100 }),
          // defaultStretch: go.GraphObject.Horizontal,
        ),

        'initialContentAlignment': go.Spot.Center,
        'undoManager.isEnabled': true,
        resizingTool: new LaneResizingTool(),
        model: $(go.GraphLinksModel,
            {
                linkToPortIdProperty: 'toPort',
                linkFromPortIdProperty: 'fromPort',
                linkKeyProperty: 'key' // IMPORTANT! must be defined for merges and data sync when using GraphLinksModel
            }
        ),

    });

如果您从 Table 布局示例开始,您只需在组模板中添加以下行:

    {
      layout: $(go.GridLayout, { wrappingColumn: 1 })
    }

根据需要调整 GridLayout,或将其替换为不同的布局。