gojs
Version:
Interactive diagrams, charts, and graphs, such as trees, flowcharts, orgcharts, UML, BPMN, or business diagrams
243 lines (217 loc) • 10.1 kB
HTML
<html>
<head>
<meta charset="UTF-8">
<title>GoJS Packed Class Hierarchy</title>
<meta name="description" content="The JavaScript class hierarchy defined by the GoJS library, arranged in nested circles." />
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
<script src="../samples/assets/require.js"></script>
<script src="../assets/js/goSamples.js"></script> <!-- this is only for the GoJS Samples framework -->
<script id="code">
function init() {
if (window.goSamples) window.goSamples(); // init for these samples -- you don't need to call this
require(["../extensionsTS/PackedLayout"], function(app) {
var PackedLayout = app.PackedLayout;
// subclass PackedLayout to change default properties and override commitLayout function
function HierarchyLayout() {
PackedLayout.call(this);
this.packShape = PackedLayout.Spiral;
this.hasCircularNodes = true;
this.sortMode = PackedLayout.Area,
this.comparer = function(na, nb) {
// ensure label is placed last
if (na.data.isLabel) {
return 1;
}
if (nb.data.isLabel) {
return -1;
}
// otherwise sort in ascending order by size (all nodes are circular, so using width or height here doesn't matter)
return (na.actualBounds.width - nb.actualBounds.width);
}
}
go.Diagram.inherit(HierarchyLayout, PackedLayout);
/* after each group has had its layout applied, size and position it according
* to the smallest enclosing circle which goes around all of its nodes */
HierarchyLayout.prototype.commitLayout = function() {
if (this.group !== null) {
var groupData = keyToNodeDataMap.get(this.group.key);
var enclosingCircle = this.enclosingCircle;
var actualBounds = this.actualBounds;
var size = new go.Size(enclosingCircle.width, enclosingCircle.width);
this.diagram.model.setDataProperty(groupData, "size", size);
var dx = enclosingCircle.centerX - actualBounds.centerX;
var dy = enclosingCircle.centerY - actualBounds.centerY;
var position = new go.Point((actualBounds.width - enclosingCircle.width) / 2 + dx, (actualBounds.height - enclosingCircle.height) / 2 + dy);
this.diagram.model.setDataProperty(groupData, "position", position);
}
}
var $ = go.GraphObject.make; // for conciseness in defining templates
var myDiagram =
$(go.Diagram, "myDiagramDiv", // must be the ID or reference to div
{
layout: $(HierarchyLayout), // defined above
"animationManager.isEnabled": false,
isReadOnly: true,
initialAutoScale: go.Diagram.Uniform
});
// common definitions for both Nodes and Groups
var toolTipTemplate =
$(go.Adornment, "Auto",
$(go.Shape, { fill: "white" }),
$(go.TextBlock, { margin: 4 },
new go.Binding("text", "tooltip"))
);
var selectionAdornmentTemplate =
$(go.Adornment, "Auto",
$(go.Shape, "Circle",
{ fill: null, stroke: "dodgerblue", strokeWidth: 3,
spot1: go.Spot.TopLeft, spot2: go.Spot.BottomRight }),
$(go.Placeholder)
);
function commonStyle() {
return [
{
toolTip: toolTipTemplate,
selectionAdornmentTemplate: selectionAdornmentTemplate,
doubleClick: function(e, node) {
var url = "../../api/symbols/" + node.data.key + ".html";
window.open(url, "_blank");
}
}
];
}
myDiagram.nodeTemplate =
$(go.Node, "Auto", commonStyle(),
new go.Binding("width", "diameter"),
new go.Binding("height", "diameter"),
$(go.Shape,
{ figure: "Circle", fill: "#1F4963", strokeWidth: 0, spot1: go.Spot.TopLeft, spot2: go.Spot.BottomRight },
new go.Binding("fill")),
$(go.TextBlock,
{ font: "12px Helvetica, Arial, sans-serif", stroke: "white", maxLines: 1 },
new go.Binding("text"),
new go.Binding("font"),
new go.Binding("stroke", "fill", function(f) { return go.Brush.isDark(f) ? "white" : "black"; }))
);
myDiagram.groupTemplate =
$(go.Group, commonStyle(),
{ layout: $(HierarchyLayout) }, // defined above
$(go.Shape, "Circle",
{ fill: "rgba(128,128,128,0.33)" },
new go.Binding("desiredSize", "size"),
new go.Binding("position")),
$(go.Placeholder) // represents area for all member parts
);
// Collect all of the data for the model of the class hierarchy
var nodeDataArray = [
{ key: "GoJS", text: "GoJS", children: [] },
// large label to be placed at the very end
{
key: "GoJS label",
group: "GoJS",
text: "GoJS",
tooltip: "GoJS",
fill: null,
font: "64px Helvetica, bold Arial, sans-serif",
isLabel: true,
children: []
}
];
var keyToNodeDataMap = new go.Map();
keyToNodeDataMap.add("GoJS", nodeDataArray[0]);
// Iterate over all of the classes in "go"
for (var k in go) {
var cls = go[k];
if (!cls) continue;
var proto = cls.prototype;
if (!proto) continue;
proto.constructor.className = k; // remember name
var propCount = 0; // count number of properties on the class
for (var prop in proto) {
if (proto.hasOwnProperty(prop)) {
propCount++;
}
}
var data = { key: k, text: k, propCount: propCount, children: [] };
if (keyToNodeDataMap.has(k)) {
data.children = keyToNodeDataMap.get(k).children;
}
keyToNodeDataMap.add(k, data); // will replace existing key if there is one
// find base class constructor
var base = Object.getPrototypeOf(proto).constructor;
if (base === Object) { // "root" node?
data.group = "GoJS";
keyToNodeDataMap.get("GoJS").children.push(data);
nodeDataArray.push(data);
} else {
// add a node for this class and set its group to its parent
data.group = base.className;
if (keyToNodeDataMap.has(base.className)) {
keyToNodeDataMap.get(base.className).children.push(data);
} else {
keyToNodeDataMap.add(base.className, {children: [data]});
}
nodeDataArray.push(data);
}
}
// create groups and add labels to groups with only 1 child
for (var i = nodeDataArray.length - 1; i >= 0; i--) {
var n = nodeDataArray[i];
if (n.children.length > 0) {
n.isGroup = true;
}
// add tooltip and/or size node using the total number of properties it has and has inherited
var totalCount = n.propCount;
var parentKey = n.group;
var parentData;
while ((parentData = keyToNodeDataMap.get(parentKey)) !== null && parentData.propCount !== undefined) {
totalCount += parentData.propCount;
parentKey = parentData.group;
}
if (totalCount === undefined) { // applies to the root GoJS group only
n.tooltip = n.text;
} else {
n.tooltip = n.text + ": " + totalCount; // add tooltip
}
if (!n.isGroup && !n.isLabel) { // only set size of node if it is not a group
// calculate size by scaling totalCount logarithmically to produce more visually appealing results
n.diameter = 20 * Math.log(0.5 * (totalCount + 5.5));
}
// add label to groups that only have one child
if (n.children.length === 1) {
nodeDataArray.push({
text: n.text,
tooltip: n.tooltip,
group: n.key,
fill: null,
font: "20px Helvetica, bold Arial, sans-serif"
});
}
delete n.children;
}
myDiagram.model = new go.GraphLinksModel(nodeDataArray);
});
}
</script>
</head>
<body onload="init()">
<div id="sample">
<div id="myDiagramDiv" style="border: solid 1px black; width:100%; height:700px"></div>
<p>
Circle packing can be a useful way to visualize hierarchical data, as demonstrated here
with a visualization of the class hierarchy of the GoJS library. This layout is performed
automatically by the <a href="../extensions/PackedLayout.html">PackedLayout</a> extension. Nodes
are sized according to how many properties their corresponding class has, or has inherited.
As a result, larger nodes generally represent more complex classes. Mouse over nodes to see
their full name and the number of properties on their corresponding class.
</p>
<p>
This sample is very similar to the <a href="../samples/classHierarchy.html" target="_blank">Class Hierarchy</a> sample,
except that instead of showing the class hierarchy as a tree, it is displayed using nested circles.
Opening the API page is achieved by double-clicking on a node, rather than using a "HyperlinkText".
</p>
</div>
</body>
</html>