Data Model / Groups

Groups

Groups are elements in your UI that act as a container for zero or more Nodes. In the UI, these can be collapsed, and Edges to/from the Nodes inside the Group are then temporarily displaced to the Group container.

Groups have in common with Nodes, Edges and Ports the two concepts of id and of type, and as with the other graph objects, type will be set to "default" if it cannot be determined. Group IDs and types either follow the default rules (ie. they are given by the id and type parameters, respectively, in the Group's data), or they are derived by applying the current idFunction and typeFunction.

Groups, as with the other graph objects, can have arbitrary JSON data associated with them.


The Toolkit's default JSON parser can parse information about groups, and about node membership in groups. You can do this in two ways:

  • Explicitly declared Groups
var t = jsPlumbToolkit.newInstance();
t.load({
  data:{
    groups:[
      { id:"g1", type:"groupType1" },
      { id:"g2", type:"groupType1" }
    ],
    nodes:[
      { id:"1", group:"g1" },
      { id:"2", group:"g1" },
      { id:"3", group:"g2" },
      { id:"4", group:"g2" }
    ],
    edges:[
      { "source":"1", target:"2" },
      { "source":"1", target:"3" },
      { "source":"4", target:"3" },
      { "source":"4", target:"2" }
    ]
  }
});

Here, we have a groups section which lists out all the groups in the dataset. Then in the nodes we map each Node to a Group, which is expected to be declared in the payload. The default behaviour of the Toolkit is to not create Groups that are referenced by a Node but which do not exist. You can set that on a flag though:

  • Implicitly declared Groups
var t = jsPlumbToolkit.newInstance({
  createMissingGroups:true
});

t.load({
  data:{
    nodes:[
      { id:"1", group:"g1" },
      { id:"2", group:"g1" },
      { id:"3", group:"g2" },
      { id:"4", group:"g2" }
    ],
    edges:[
      { "source":"1", target:"2" },
      { "source":"1", target:"3" },
      { "source":"4", target:"3" },
      { "source":"4", target:"2" }
    ]
  }
});

By setting createMissingGroups:true in the Toolkit factory method, we no longer need to declare the Groups in the JSON. This approach has the drawback that you cannot specify the type of the Groups that are created: they will be given a type of "default". Also, you cannot associate arbitrary data with these automatically created groups, meaning in your UI you will not be able to render anything unique to each Group. For some use cases, of course, this is perfectly acceptable.

TOP


Groups are rendered using client side templates just as Nodes are. They are declared inside the view alongside Nodes, Edges and Ports. A simple example to start:

toolkit.render({
  ...
  view:{
    nodes:{
        "default":{
            template:"tmplNode",
            events:{
                click:function(p) {
                    alert("You clicked on node " + p.node.id);
                }
            }
        }
    },
    groups:{
        "groupType1":{
            template:"tmplGroupType1",
            constrain:true
        }
    }
  }
});

Here we declare that Groups of type groupType1 are to be rendered using the template tmplGroupType1, and that members of the Group are constrained to the Group element (that is, they cannot be dragged outside of the Group's bounds).

There are several flags available to control the drag behaviour of members of a Group:

  • droppable True by default - indicates that Nodes may be dropped onto the Group.
  • constrain False by default - Nodes may be dragged outside of the bounds of their Group.
  • revert True by default - a Node dragged outside of its Group will, unless dropped on another Group, revert back to its position inside the Group.
  • prune False by default. If true, Nodes dropped outside of the Group (and not dropped onto another Group) are removed from the dataset (not just the Group...the entire dataset).
  • orphan False by default. If true, Nodes dropped outside of the Group (and not dropped onto another Group) are removed from the Group (but remain in the dataset)
  • dropOverride False by default. If true, Nodes dropped onto other Groups first have any rules established by this Group applied. For instance, if the Groups stated prune:true, then the Node would be removed from the dataset rather than be dropped onto another Group.

You can collapse/expand a group using the collapseGroup and expandGroup methods on the Surface widget. When you collapse a Group, any Edges from any of the member Nodes in the Group to Nodes outside of the Group are relocated to the Group's container, and a CSS class is applied to the Group's container, indicating the collapsed state. When you subsequently expand the Group, the Edges are placed back onto their appropriate Nodes.

It is important to note that when a Group is collapsed, the jsPlumb Toolkit does not hide the member Nodes automatically for you. But a CSS class of jtk-group-collapsed is added to the Group's container, for you to handle this in your CSS.

The Endpoint and Anchor to be used in the collapsed state can be specified in the Group definition in the view:

toolkit.render({
  ...
  view:{    
    groups:{
      "groupType1":{
          template:"tmplGroupType1",
          endpoint:"Blank",
          anchor:"Continuous"
      }
    }
  }
  ...
});

Any valid Anchor/Endpoint (including custom Endpoints) can be used here.

Specifying the Node canvas

It is not necessarily the case that you wish to use your entire Group template as the parent of the Group's members. You can set a jtk-group-content attribute on the element that you wish to have acting as the parent for the members:

<script type="jtk" id="tmplMyGroup">
    <div class="aGroup">
        <h1>${title}</h1>
        <div jtk-group-content="true" class="aGroupInner">
            <!-- Nodes go here -->
        </div>
    </div>
</script>

TOP


You can drag Groups from a palette onto a Surface using the Drop Manager used to drag Nodes onto a Surface, with two differences:

  • you need to set jtk-is-group="true" as an attribute on an element that you wish to drag on to the Surface as a Group
  • the current GroupFactory will be called to prepare a dataset for your new Group, rather than the current NodeFactory.

Here's an example from the Groups demo that ships with the Toolkit (showing both Nodes and Groups, but we're just going to focus on the Groups setup):

<div class="sidebar node-palette">
    <div title="Drag Node to canvas" data-node-type="node" class="sidebar-item">
        <i class="icon-tablet"></i>Drag Node
    </div>
    <div title="Drag Group to canvas" jtk-is-group="true" data-node-type="group" class="sidebar-item">
        <i class="icon-tablet"></i>Drag Group
    </div>
</div>

The Toolkit instance is configured with both a nodeFactory and a groupFactory:

var toolkit = window.toolkit = jsPlumbToolkit.newInstance({
    groupFactory:function(type, data, callback) {
        data.title = "Group " + (toolkit.getGroupCount() + 1);
        callback(data);
    },
    nodeFactory:function(type, data, callback) {
        data.name = (toolkit.getNodeCount() + 1);
        callback(data);
    }
});

...and the call to configure the drag/drop is no different to that which you'd make just for Nodes:

new SurfaceDropManager({
    surface:renderer,
    source:document.querySelector(".node-palette"),
    selector:"[data-node-type]",
    dataGenerator:function(e) {
        return {
            type:"default"
        };
    }
});

TOP


Groups can be configured to automatically resize themselves to encompass the extents of their child Nodes, via the autoSize flag on a Group definition in a View:

 view:{
     groups:{
         "groupType1":{
             template:"tmplGroupTypeOne",
             autoSize:true
         },
         "groupType2":{
             template:"tmplGroupTypeOne",
             autoSize:true,
             maxSize:[600,600]
         }
     }
 }

In this example, both Group types are declared to auto size, but groupType2 will grow to a maximum of 600 pixels in each axis.

Autosizing is run after a data load (ie. you call toolkit.load(...)), or when data exists in a Toolkit instance and you call toolkit.render(...).

You can run auto sizing on demand (on all groups):


surface.autoSizeGroups();

or on a single group:


surface.sizeGroupToFit(group:Group);

By default, every group has an Absolute layout assigned to it. If your node data has left/top properties in it, these values will automatically be used to place nodes inside of their parent groups.

Specifying in the view

To specify the layout for a specific type of group, set it in that group's entry in your view:

view:{

   ...

   groups:{
     "someGroupType":{
       ...
       layout:{
           type:"Hierarchical",
           parameters:{
               orientation:"vertical"
           }
       }
     }
   }

   ...
}

The format of the layout parameter is identical to the layout parameter in the root of the view.

Layout on demand

You can force a layout in a group like this:

 surface.relayoutGroup(group:String|Group)

This will cause a layout to be run immediately on the given group (which may be passed in as the Group object, or just its id)

Relationship to group size

By default, a layout in a group will cause the auto size routine to be run for the group immediately afterwards.