UNPKG

gojs

Version:

Interactive diagrams, charts, and graphs, such as trees, flowcharts, orgcharts, UML, BPMN, or business diagrams

356 lines (330 loc) 13.6 kB
<!DOCTYPE html> <html> <head> <title>Radial Layout</title> <meta name="description" content="Radial layout of an arbitrary graph given a start node; selecting a node re-lays out using it as a new root node." /> <!-- Copyright 1998-2016 by Northwoods Software Corporation. --> <meta charset="UTF-8"> <script src="go.js"></script> <link href="../assets/css/goSamples.css" rel="stylesheet" type="text/css" /> <!-- you don't need to use this --> <script src="goSamples.js"></script> <!-- this is only for the GoJS Samples framework --> <script id="code"> var showCircles = true; // show a circle behind the nodes in each layer var rotateText = true; // whether to rotate the label with the angle of the node var maxLayers = 2; // how many concentric layers to show var layerThickness = 100; // how thick each ring should be function init() { if (window.goSamples) goSamples(); // init for these samples -- you don't need to call this var $ = go.GraphObject.make; // for conciseness in defining templates myDiagram = $(go.Diagram, "myDiagramDiv", // must be the ID or reference to div { initialAutoScale: go.Diagram.Uniform, initialContentAlignment: go.Spot.Center, padding: 10, isReadOnly: true, maxSelectionCount: 1, "animationManager.isEnabled": false }); // shows when hovering over a node var commonToolTip = $(go.Adornment, "Auto", { isShadowed: true }, $(go.Shape, { fill: "#FFFFCC" }), $(go.Panel, "Vertical", { margin: 3 }, $(go.TextBlock, // bound to node data {margin: 4, font: "bold 12pt sans-serif" }, new go.Binding("text")), $(go.TextBlock, // bound to node data new go.Binding("text", "color", function(c) { return "Color: " + c; })), $(go.TextBlock, // bound to Adornment because of call to Binding.ofObject new go.Binding("text", "", function(ad) { return "Connections: " + ad.adornedPart.linksConnected.count; }).ofObject()) ) // end Vertical Panel ); // end Adornment // define the Node template myDiagram.nodeTemplate = $(go.Node, "Spot", { locationSpot: go.Spot.Center, locationObjectName: "SHAPE", // Node.location is the center of the Shape selectionAdorned: false, selectionChanged: nodeSelectionChanged, toolTip: commonToolTip }, $(go.Shape, "Circle", { name: "SHAPE", fill: "lightgray", // default value, but also data-bound stroke: "transparent", strokeWidth: 2, desiredSize: new go.Size(20, 20), portId: "" // so links will go to the shape, not the whole node }, new go.Binding("fill", "color")), $(go.TextBlock, { name: "TEXTBLOCK", alignment: go.Spot.Right, alignmentFocus: go.Spot.Left }, new go.Binding("text")) ); // this is the root node, at the center of the circular layers myDiagram.nodeTemplateMap.add("Root", $(go.Node, "Auto", { locationSpot: go.Spot.Center, selectionAdorned: false, selectionChanged: nodeSelectionChanged, toolTip: commonToolTip }, $(go.Shape, "Circle", { fill: "white" }), $(go.TextBlock, { font: "bold 14pt sans-serif", margin: 10 }, new go.Binding("text")) )); // define the Link template myDiagram.linkTemplate = $(go.Link, { routing: go.Link.Normal, curve: go.Link.Bezier, selectionAdorned: false, layerName: "Background" }, $(go.Shape, { stroke: "black", // default value, but is data-bound strokeWidth: 1 }, new go.Binding("stroke", "color")) ); generateGraph(); } function generateGraph() { var names = [ "Joshua", "Daniel", "Robert", "Noah", "Anthony", "Elizabeth", "Addison", "Alexis", "Ella", "Samantha", "Joseph", "Scott", "James", "Ryan", "Benjamin", "Walter", "Gabriel", "Christian", "Nathan", "Simon", "Isabella", "Emma", "Olivia", "Sophia", "Ava", "Emily", "Madison", "Tina", "Elena", "Mia", "Jacob", "Ethan", "Michael", "Alexander", "William", "Natalie", "Grace", "Lily", "Alyssa", "Ashley", "Sarah", "Taylor", "Hannah", "Brianna", "Hailey", "Christopher", "Aiden", "Matthew", "David", "Andrew", "Kaylee", "Juliana", "Leah", "Anna", "Allison", "John", "Samuel", "Tyler", "Dylan", "Jonathan" ]; var nodeDataArray = []; for (var i = 0; i < names.length; i++) { nodeDataArray.push({ key: i, text: names[i], color: go.Brush.randomColor(128, 240) }); } var linkDataArray = []; var num = nodeDataArray.length; for (var i = 0; i < num * 2; i++) { var a = Math.floor(Math.random() * num); var b = Math.floor(Math.random() * num / 4) + 1; linkDataArray.push({ from: a, to: (a + b) % num, color: go.Brush.randomColor(0, 127) }); } myDiagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray); var someone = nodeDataArray[Math.floor(Math.random() * nodeDataArray.length)]; var somenode = myDiagram.findNodeForData(someone); myDiagram.select(somenode); } // called when "Set Max Layers" button is clicked function adjustMaxLayers() { var newMaxLayers = document.getElementById("maxLayersChanger").value; function IsNumeric(val) { return Number(parseFloat(val)) == val; } if (!IsNumeric(newMaxLayers)) alert("Please enter a number"); else { maxLayers = newMaxLayers; var root = myDiagram.findNodesByExample({ category: "Root" }).first(); myDiagram.clearSelection(); myDiagram.select(root); } } // when a new node is selected, adjust the radial layout around the new node function nodeSelectionChanged(node) { var diagram = node.diagram; if (diagram === null) return; if (node.isSelected) { // make this Node the root node.category = "Root"; // determine new distances from this new root node var results = findDistances(node); radialLayout(node, results); } } // returns a Map of Nodes with distance values function findDistances(source) { var diagram = source.diagram; // keep track of distances from the source node var distances = new go.Map(go.Node, "number"); diagram.nodes.each(function(n) { distances.add(n, Infinity); }); // the source node starts with distance 0 distances.add(source, 0); // keep track of nodes for we have set a non-Infinity distance, // but which we have not yet finished examining var seen = new go.Set(go.Node); seen.add(source); // local function for finding a Node with the smallest distance in a given collection function leastNode(coll, distances) { var bestdist = Infinity; var bestnode = null; var it = coll.iterator; while (it.next()) { var n = it.value; var dist = distances.getValue(n); if (dist < bestdist) { bestdist = dist; bestnode = n; } } return bestnode; } // keep track of nodes we have finished examining; // this avoids unnecessary traversals and helps keep the SEEN collection small var finished = new go.Set(go.Node); while (seen.count > 0) { // look at the unfinished node with the shortest distance so far var least = leastNode(seen, distances); var leastdist = distances.getValue(least); // by the end of this loop we will have finished examining this LEAST node seen.remove(least); finished.add(least); // look at all Links connected with this node least.linksConnected.each(function(link) { var neighbor = link.getOtherNode(least); // skip nodes that we have finished if (finished.contains(neighbor)) return; var neighbordist = distances.getValue(neighbor); // assume "distance" along a link is unitary, but could be any non-negative number. var dist = leastdist + 1; //Math.sqrt(least.location.distanceSquaredPoint(neighbor.location)); if (dist < neighbordist) { // if haven't seen that node before, add it to the SEEN collection if (neighbordist == Infinity) { seen.add(neighbor); } // record the new best distance so far to that node distances.add(neighbor, dist); } }); } return distances; } function radialLayout(root, distances) { root.diagram.startTransaction("radial layout"); // sort all results into Arrays of Nodes with the same distance var nodes = {}; var maxlayer = 0; var it = distances.iterator; while (it.next()) { var node = it.key; if (node !== root) node.category = ""; // remove "Root" category from all non-root nodes node._laid = false; var layer = it.value; if (layer === Infinity) continue; // Infinity used as init value (set in findDistances()) if (layer > maxlayer) maxlayer = layer; var layernodes = nodes[layer]; if (layernodes === undefined) { layernodes = []; nodes[layer] = layernodes; } layernodes.push(node); } // optional: add circles in the background // need to remove any old ones first var gridlayer = root.diagram.findLayer("Grid"); var circles = new go.Set(go.Part); gridlayer.parts.each(function(circle) { if (circle.name === "CIRCLE") circles.add(circle); }); circles.each(function(circle) { root.diagram.remove(circle); }); // add circles centered at the root if (showCircles) { var $ = go.GraphObject.make; // for conciseness in defining templates for (var lay = 1; lay <= maxLayers; lay++) { var radius = lay * layerThickness; var circle = $(go.Part, { name: "CIRCLE", layerName: "Grid" }, { locationSpot: go.Spot.Center, location: new go.Point(0, 0) }, $(go.Shape, "Circle", { width: radius * 2, height: radius * 2 }, { fill: "rgba(200,200,200,0.2)", stroke: null })); node.diagram.add(circle); } } // now recursively position nodes (using radlay1()), starting with the root root.location = new go.Point(0, 0); radlay1(root, 1, 0, 360, distances); // finally, hide nodes with distance > maxLayers it = distances.iterator; while (it.next()) { var node = it.key; node.visible = (it.value <= maxLayers); } root.diagram.commitTransaction("radial layout"); } // recursively position nodes in a radial layout function radlay1(node, layer, angle, sweep, distances) { if (layer > maxLayers) return; // no need to position nodes outside of maxLayers var nodes = []; // array of all Nodes connected to 'node' in layer 'layer' node.findNodesConnected().each(function(n) { if (n._laid) return; if (distances.getValue(n) === layer) nodes.push(n); }); var found = nodes.length; if (found === 0) return; var radius = layer * layerThickness; var separator = sweep / found; // distance between nodes in their sweep portion var start = angle - sweep / 2 + separator / 2; // for each node in this layer, place it in its correct layer and position for (var i = 0; i < found; i++) { var n = nodes[i]; var a = start + i * separator; // the angle to rotate the node to // the point to place the node at -- this corresponds with the layer the node is in // all nodes in the same layer are placed at a constant point, then rotated accordingly var p = new go.Point(radius, 0); p.rotate(a); n.location = p; n._laid = true; // rotates the node's textblock if (rotateText) { n.angle = a; var label = n.findObject("TEXTBLOCK"); if (label !== null) { label.angle = ((a > 90 && a < 270 || a < -90) ? 180 : 0); } } // keep going for all layers radlay1(n, layer + 1, a, sweep / found, distances); } } </script> </head> <body onload="init()"> <div id="sample"> <h3>GoJS Recentering Radial</h3> <div id="myDiagramDiv" style="border: solid 1px black; background: white; width: 100%; height: 600px"></div> <label for="maxLayersChanger">Max Layers</label><input type="text" id="maxLayersChanger" name="maxLayers" style="width: 50px" /> <button onclick="adjustMaxLayers()">Set Max Layers</button> <p> Click on a Node to center it and show its relationships. </p> <p> You can set some parameters in the JavaScript code to control how many layers to show, whether to draw the circles, and whether to rotate the text. It is also easy to add more information to each node, including pictures, or to put such information into <a href="../intro/toolTips.html" target="_blank">Tooltips</a>. </p> </div> </body> </html>