hi,
Is it possible to do virtualization if we are using TreeLayout with GraphLinksModel ?
If so, how to do it? the sample provided does not have TreeLayout with GraphLinksModel.
Thanks
Amey
hi,
Is it possible to do virtualization if we are using TreeLayout with GraphLinksModel ?
If so, how to do it? the sample provided does not have TreeLayout with GraphLinksModel.
Thanks
Amey
Layouts are completely independent of models. Copy the code from one of the other virtualization samples.
We Have tried to just change from your samples code but still it is not working.
I have attached the script file virtulization.js for your reference.
virtulization.js contains your sample code with change in just input data.
Can you please guide us were are we going wrong.
virtulization.js:
var nodeData = [{ “key”: “-1”, “name”: “START”, “color”: “green” }, { “key”: “0”, “name”: “End”, “color”: “red” }];
var nodeLinks = [{ “from”: “-1”, “to”: “0” }];
function init() {
var $ = go.GraphObject.make; // for conciseness in defining templates
// The Diagram just shows what should be visible in the viewport.
// Its model does NOT include node data for the whole graph, but only that
// which might be visible in the viewport.
myDiagram =
$(go.Diagram, "myDiagramDiv",
{
contentAlignment: go.Spot.Center,
initialDocumentSpot: go.Spot.Center,
initialViewportSpot: go.Spot.Center,
// Assume there's no Layout -- all data.bounds are provided
layout: $(go.Layout, { isInitial: false, isOngoing: false }), // never invalidates
// Define the template for Nodes, used by virtualization.
nodeTemplate:
$(go.Node, "Auto",
{ isLayoutPositioned: false }, // optimization
new go.Binding("position", "bounds", function(b) { return b.position; })
.makeTwoWay(function(p, d) { return new go.Rect(p.x, p.y, d.bounds.width, d.bounds.height); }),
{ width: 70, height: 20 }, // in cooperation with the load function, below
$(go.Shape, "Rectangle",
new go.Binding("fill", "color")),
$(go.TextBlock,
{ margin: 2 },
new go.Binding("text", "name")),
{
toolTip:
$("ToolTip",
$(go.TextBlock, { margin: 3 },
new go.Binding("text", "",
function(d) { return "key: " + d.key + "\nbounds: " + d.bounds.toString(); }))
)
}
),
groupTemplate:
$(go.Group, "Vertical",
{ isLayoutPositioned: false }, // optimization
{ locationSpot: go.Spot.TopLeft, locationObjectName: "SHAPE" },
new go.Binding("location", "bounds", function(b) { return b.position; })
.makeTwoWay(function(p, d) { return new go.Rect(p.x, p.y, d.bounds.width, d.bounds.height); }),
new go.Binding("isSubGraphExpanded").makeTwoWay(),
{
subGraphExpandedChanged: function(g) {
if (!g.isSubGraphExpanded) return; // only after expanding
// invalidate all member and external link routes
g.findSubGraphParts().each(function(n) { if (n instanceof go.Node) n.invalidateConnectedLinks(); });
}
},
$(go.Panel, "Horizontal",
$("SubGraphExpanderButton"),
$(go.TextBlock,
new go.Binding("text", "key"))
),
$(go.Shape,
{ fill: "lightgray", name: "SHAPE" },
new go.Binding("fill", "color"),
new go.Binding("desiredSize", "", function(d) { if (d.isSubGraphExpanded === false) return new go.Size(50, 50); else return d.bounds.size; })
)
),
// Define the template for Links
linkTemplate:
$(go.Link,
{ isLayoutPositioned: false }, // optimization
$(go.Shape),
$(go.Shape, { toArrow: "OpenTriangle" })
),
"animationManager.isEnabled": false
});
// This model includes all of the data
myWholeModel =
$(go.GraphLinksModel); // must match the model used by the Diagram, below
// Do not set myDiagram.model = myWholeModel -- that would create a zillion Nodes and Links!
// In the future Diagram may have built-in support for virtualization.
// For now, we have to implement virtualization ourselves by having the Diagram's model
// be different than the "real" model.
myDiagram.model = // this only holds nodes that should be in the viewport
$(go.GraphLinksModel); // must match the model, above
// for now, we have to implement virtualization ourselves
myDiagram.isVirtualized = true;
myDiagram.addDiagramListener("ViewportBoundsChanged", onViewportChanged);
myDiagram.addModelChangedListener(onModelChanged);
myDiagram.model.makeUniqueKeyFunction = virtualUniqueKey; // ensure uniqueness in myWholeModel
myDiagram.commandHandler.selectAll = function() { }; // make Select All command a no-op
// This is a status message
myLoading =
$(go.Part, // this has to set the location or position explicitly
{ location: new go.Point(0, 0) },
$(go.TextBlock, "loading...",
{ stroke: "red", font: "20pt sans-serif" }));
// temporarily add the status indicator
myDiagram.add(myLoading);
// Allow the myLoading indicator to be shown now,
// but allow objects added in load to also be considered part of the initial Diagram.
// If you are not going to add temporary initial Parts, don't call delayInitialization.
myDiagram.delayInitialization(load);
}
function load() {
// create a lot of data for the myWholeModel
var total = 123456;
var sqrt = Math.sqrt(total);
var data = [];
var ldata = [];
for (var i = 0; i < total; i++) {
data.push({
key: i, // this node data's key
color: go.Brush.randomColor(), // the node's color
//!!!???@@@ this needs to be customized to account for your chosen Node template
bounds: new go.Rect((i % sqrt) * 100, (i / sqrt) * 100, 70, 20)
});
if (i > 0) { // link sequential nodes
ldata.push({
from: i - 1,
to: i
});
}
}
// create some groups
for (var i = 0; i < total - 10; i++) {
if (Math.random() > 0.1) continue; // # groups ~10% of total
var key = total + i; // key for new group
var mems = Math.floor(Math.random() * 5) + 1; // number of member nodes in this group
var sgb = new go.Rect(); // Placeholder-like bounds of the member nodes
for (var j = 0; j < mems; j++) {
var d = data[i + j];
d.group = key; // assign membership
if (sgb.isEmpty()) sgb.set(d.bounds); else sgb.unionRect(d.bounds);
}
sgb.inflate(15, 15); // a bit of padding
data.push({
key: key,
isGroup: true,
color: "rgba(180,180,180, 0.2)",
bounds: sgb
});
i += mems; // avoid overlapping groups, except for groups that wrap to next row
}
myWholeModel.nodeDataArray = nodeData;
myWholeModel.linkDataArray = nodeLinks;
// can't depend on regular bounds computation that depends on all Nodes existing
myDiagram.fixedBounds = computeDocumentBounds(myWholeModel);
// remove the status indicator
myDiagram.remove(myLoading);
}
// The following functions implement virtualization of the Diagram
// Assume data.bounds is a Rect of the area occupied by the Node in document coordinates.
// It's not good enough to ensure keys are unique in the limited model that is myDiagram.model --
// need to ensure uniqueness in myWholeModel.
function virtualUniqueKey(model, data) {
myWholeModel.makeNodeDataKeyUnique(data);
return myWholeModel.getKeyForNodeData(data);
}
// The normal mechanism for determining the size of the document depends on all of the
// Nodes and Links existing, so we need to use a function that depends only on the model data.
function computeDocumentBounds(model) {
var b = new go.Rect();
var ndata = model.nodeDataArray;
for (var i = 0; i < ndata.length; i++) {
var d = ndata[i];
if (!d.bounds) continue;
if (b.isEmpty()) b.set(d.bounds); else b.unionRect(d.bounds);
}
return b;
}
// As the user scrolls or zooms, make sure the Parts (Nodes and Links) exist in the viewport.
function onViewportChanged(e) {
var diagram = e.diagram;
// make sure there are Nodes for each node data that is in the viewport
// or that is connected to such a Node
var viewb = diagram.viewportBounds; // the new viewportBounds
var model = diagram.model;
var oldskips = diagram.skipsUndoManager;
diagram.skipsUndoManager = true;
var b = new go.Rect();
var ndata = myWholeModel.nodeDataArray;
for (var i = 0; i < ndata.length; i++) {
var n = ndata[i];
if (!n.bounds) continue;
if (n.bounds.intersectsRect(viewb)) {
addNodeAndGroups(diagram, myWholeModel, n);
}
if (model instanceof go.TreeModel) {
// make sure links to all parent nodes appear
var parentkey = myWholeModel.getParentKeyForNodeData(n);
var parent = myWholeModel.findNodeDataForKey(parentkey);
if (parent !== null) {
if (n.bounds.intersectsRect(viewb)) { // N is inside viewport
// so that link to parent appears
addNodeAndGroups(diagram, myWholeModel, parent);
var node = diagram.findNodeForData(n);
if (node !== null) {
var link = node.findTreeParentLink();
if (link !== null) {
// do this now to avoid delayed routing outside of transaction
link.updateRoute();
}
}
} else { // N is outside of viewport
// see if there's a parent that is in the viewport,
// or if the link might cross over the viewport
b.set(n.bounds);
b.unionRect(parent.bounds);
if (b.intersectsRect(viewb)) {
// add N so that link to parent appears
addNodeAndGroups(diagram, myWholeModel, n);
var child = diagram.findNodeForData(n);
if (child !== null) {
var link = child.findTreeParentLink();
if (link !== null) {
// do this now to avoid delayed routing outside of transaction
link.updateRoute();
}
}
}
}
}
}
}
if (model instanceof go.GraphLinksModel) {
var ldata = myWholeModel.linkDataArray;
for (var i = 0; i < ldata.length; i++) {
var l = ldata[i];
var fromkey = myWholeModel.getFromKeyForLinkData(l);
if (fromkey === undefined) continue;
var from = myWholeModel.findNodeDataForKey(fromkey);
if (from === null || !from.bounds) continue;
var tokey = myWholeModel.getToKeyForLinkData(l);
if (tokey === undefined) continue;
var to = myWholeModel.findNodeDataForKey(tokey);
if (to === null || !to.bounds) continue;
b.set(from.bounds);
b.unionRect(to.bounds);
if (b.intersectsRect(viewb)) {
// also make sure both connected nodes are present,
// so that link routing is authentic
addNodeAndGroups(diagram, myWholeModel, from);
addNodeAndGroups(diagram, myWholeModel, to);
model.addLinkData(l);
var link = diagram.findLinkForData(l);
if (link !== null) {
// do this now to avoid delayed routing outside of transaction
link.updateRoute();
}
}
}
}
diagram.skipsUndoManager = oldskips;
if (myRemoveTimer === null) {
// only remove offscreen nodes after a delay
myRemoveTimer = setTimeout(function() { removeOffscreen(diagram); }, 3000);
}
}
function addNodeAndGroups(diagram, wholeModel, data) {
var model = diagram.model;
model.addNodeData(data);
var n = diagram.findNodeForData(data);
if (n !== null) n.ensureBounds();
var groupkey = wholeModel.getGroupKeyForNodeData(data);
while (groupkey !== undefined) {
var gd = wholeModel.findNodeDataForKey(groupkey);
if (gd !== null) { // is there a containing group data?
model.addNodeData(gd); // make sure it's added to the diagram
var g = diagram.findNodeForData(gd);
if (g !== null) g.ensureBounds();
}
// walk up chain of containing group data
groupkey = wholeModel.getGroupKeyForNodeData(gd);
}
}
function isPartOrGroupSelected(part) {
if (!part) return false;
if (part.isSelected) return true;
return isPartOrGroupSelected(part.containingGroup);
}
// occasionally remove Parts that are offscreen from the Diagram
var myRemoveTimer = null;
function removeOffscreen(diagram) {
myRemoveTimer = null;
var viewb = diagram.viewportBounds;
var model = diagram.model;
var remove = []; // collect for later removal
var removeLinks = new go.Set(); // links connected to a node data to remove
var it = diagram.nodes;
while (it.next()) {
var n = it.value;
var d = n.data;
if (d === null) continue;
if (!n.actualBounds.intersectsRect(viewb) && !isPartOrGroupSelected(n)) {
// even if the node is out of the viewport, keep it if it is selected or
// if any link connecting with the node is still in the viewport
if (!n.linksConnected.any(function(l) { return l.actualBounds.intersectsRect(viewb); })) {
remove.push(d);
if (model instanceof go.GraphLinksModel) {
removeLinks.addAll(n.linksConnected);
}
}
}
}
if (remove.length > 0) {
var oldskips = diagram.skipsUndoManager;
diagram.skipsUndoManager = true;
model.removeNodeDataCollection(remove);
if (model instanceof go.GraphLinksModel) {
removeLinks.each(function(l) { if (!isPartOrGroupSelected(l)) model.removeLinkData(l.data); });
}
diagram.skipsUndoManager = oldskips;
}
}
function onModelChanged(e) { // handle insertions and removals
if (e.model.skipsUndoManager) return;
if (e.change === go.ChangedEvent.Insert) {
if (e.propertyName === "nodeDataArray") {
myWholeModel.addNodeData(e.newValue);
} else if (e.propertyName === "linkDataArray") {
myWholeModel.addLinkData(e.newValue);
}
} else if (e.change === go.ChangedEvent.Remove && e.propertyName === "nodeDataArray") {
if (e.propertyName === "nodeDataArray") {
myWholeModel.removeNodeData(e.oldValue);
} else if (e.propertyName === "linkDataArray") {
myWholeModel.removeLinkData(e.oldValue);
}
}
}
But you haven’t incorporated the tree layout code from either
Virtualized Tree with custom layout (custom code) or Virtualized Tree with TreeLayout (uses TreeLayout).
Can virtualization be possible without providing bounds?
We are using Tree Layout and graph links model which itself routes the location of each node.
And as per our understanding bounds is the collection of location and view-port’s width and height.
And we don’t have the location of any nodes. So will it be possible to do virtualization with out bounds?
If so how?
The answer is no. Without knowing for each node data what area it will occupy, the virtualizing code cannot decide when to create the corresponding Node, and it cannot decide whether connecting Links need to be created.
Yet all regular Layouts require the Nodes and Links to exist. So the problem is how to get the bounds
property on all node data objects to have the correct information. The virtualized versions of the layouts do that, with some restrictions.