/// <summary> /// Layout nodes based on their depth within the network. /// /// Note 1. /// Input nodes are defined as being at layer zero and we position them in their own layer at the /// top of the layout area. Any other type of node (hidden or output) node not connected to is also /// defined as being at layer zero, if such nodes exist then we place them into their own layout /// layer to visually separate them from the input nodes. /// /// Note 2. /// Output nodes can be at a range of depths, but for clarity we position them all in their own layer /// at the bottom of the layout area. A hidden node can have a depth greater than or equal to one or /// more of the output nodes, to handle this case neatly we ensure that the output nodes are always /// in a layer by themselves by creating an additional layer in the layout if necessary. /// /// Note 3. /// Hidden nodes are positioned into layers between the inputs and outputs based on their depth. /// /// Note 4. /// Empty layers are not possible in the underlying network because for there to be a layer N node it /// must have a connection from a layer N-1 node. However, in cyclic networks the output nodes can be /// source nodes, but we also always paint output nodes in their own layout layer at the bottom of the /// layout area. Therefore if the only node at a given depth is an output node then the layout layer /// can be empty. To handle this neatly we check for empty layout layers before invoking the layout logic. /// </summary> /// <param name="graph">The network/graph structure to be layed out.</param> /// <param name="layoutArea">The area the structure is to be layed out on.</param> public void Layout(IOGraph graph, Size layoutArea) { int inputCount = graph.InputNodeList.Count; int outputCount = graph.OutputNodeList.Count; int hiddenCount = graph.HiddenNodeList.Count; //=== Stratify all nodes into layout layers, each layer keyed by depth value. SortedDictionary<int,List<GraphNode>> layersByDepth = new SortedDictionary<int,List<GraphNode>>(); // Special case. Key all input nodes by depth -1. This places them as the first layer and distinct // from all other nodes. layersByDepth.Add(-1, graph.InputNodeList); // Stratify hidden nodes into layers. // Count hidden nodes in each layer. int[] hiddenNodeCountByLayerArr = new int[graph.Depth]; for(int i=0; i<hiddenCount; i++) { hiddenNodeCountByLayerArr[graph.HiddenNodeList[i].Depth]++; } // Allocate storage per layer. for(int i=0; i<graph.Depth; i++) { int nodeCount = hiddenNodeCountByLayerArr[i]; if(0 != nodeCount) { layersByDepth.Add(i, new List<GraphNode>(nodeCount)); } } // Put hidden nodes into layer lists. for(int i=0; i<hiddenCount; i++) { GraphNode node = graph.HiddenNodeList[i]; layersByDepth[node.Depth].Add(node); } // Special case 2. Place output nodes in their own distinct layer at a depth one more than the max // depth of the network. This places them as the last layer and distinct from all other nodes. layersByDepth.Add(graph.Depth, graph.OutputNodeList); //=== Layout. // Calculate height of each layer. We divide the layout area height by the number of layers, but also // define margins at the top and bottom of the view. To make best use of available height the margins // are defined as a proportion of the layer height. Hence we have: // // ============================ // d - network depth. // l - layer height. // m - margin height (same height used for top and bottom margins). // p - proportion of layer height that gives margin height. // H - layout areas height // ============================ // // Derivation: // ============================ // 1) l = (H-2m) / (d-1) // // 2) m = lp, therefore // // 3) l = (H-2pl) / (d-1), solve for l // // 4) l = H/(d-1) - 2pl/(d-1) // // 5) 1 = H/(d-1)l - 2p/(d-1) // // 6) H / (d-1)l = 1 + 2p/(d-1), inverting both sides gives // // 7) (d-1)l / H = 1/[ 1 + 2p/(d-1) ] // // 8) = (d-1) / ((d-1) + 2p) // // 9) = (d-1) / (d + 2p - 1), rearranging for l gives // // 10) l = H / (d + 2p - 1) const float p = 0.33f; const float p2 = 2f * p; // Rounding will produce 'off by one' errors, e.g. all heights may not total heinght of layout area, // but the effect is probably negligible on the quality of the layout. int layoutLayerCount = layersByDepth.Count; int l = (int)Math.Round((float)layoutArea.Height / ((float)layoutLayerCount + p2 - 1f)); int m = (int)Math.Round(l * p); // Size struct to keep track of the area that bounds all nodes. Size bounds = new Size(0,0); // Assign a position to each node, one layer at a time. int yCurrent = m; foreach(List<GraphNode> layerNodeList in layersByDepth.Values) { // Calculate inter-node gap and margin width (we use the same principle as with the vertical layout of layers, see notes above). int nodeCount = layerNodeList.Count; float xIncr = (float)layoutArea.Width / ((float)nodeCount + p2 - 1f); int xMargin = (int)Math.Round(xIncr * p); // Loop nodes in layer; Assign position to each. float xCurrent = xMargin; for(int nodeIdx=0; nodeIdx < nodeCount; nodeIdx++, xCurrent += xIncr) { layerNodeList[nodeIdx].Position = new Point((int)xCurrent, yCurrent); UpdateModelBounds(layerNodeList[nodeIdx], ref bounds); } // Increament y coord for next layer. yCurrent += l; } // Store the bounds of the graph elements. Useful when drawing the graph. graph.Bounds = bounds; }
/// <summary> /// Paints the provided IOGraph onto the provided GDI+ Graphics drawing surface. /// </summary> protected override void PaintNetwork(IOGraph graph, PaintState state) { // Invoke the base painting routine. base.PaintNetwork(graph, state); // Paint a legend for the activation functions. PaintLegend(state); }
/// <summary> /// Paints the provided IOGraph onto the provided GDI+ Graphics drawing surface. /// </summary> public void PaintNetwork(IOGraph graph, Graphics g, Rectangle viewportArea, float zoomFactor) { // Create a PaintState object. This holds all temporary state info for the painting routines. // Pass the call on to the virtual PaintNetwork. This allows us to override a version of PaintNetwork // that has access to a PaintState object. PaintState state = new PaintState(g, viewportArea, zoomFactor, graph.ConnectionWeightRange); PaintNetwork(graph, state); }
/// <summary> /// Paints the provided IOGraph onto the current GDI+ Graphics drawing surface. /// </summary> protected virtual void PaintNetwork(IOGraph graph, PaintState state) { // Create per-node state info. int hiddenNodeCount = graph.HiddenNodeList.Count; int inputNodeCount = graph.InputNodeList.Count; int outputNodeCount = graph.OutputNodeList.Count; state._nodeStateDict = new Dictionary<GraphNode,ConnectionPointInfo>(hiddenNodeCount + inputNodeCount + outputNodeCount); // Paint all connections. We do this first and paint nodes on top of the connections. This allows the // slightly messy ends of the connections to be painted over by the nodes. PaintConnections(graph.InputNodeList, state); PaintConnections(graph.HiddenNodeList, state); PaintConnections(graph.OutputNodeList, state); // Paint all nodes. Painted over the top of connection endpoints. PaintNodes(graph.InputNodeList, state); PaintNodes(graph.HiddenNodeList, state); PaintNodes(graph.OutputNodeList, state); }
/// <summary> /// Paints the provided IOGraph onto the current GDI+ Graphics drawing surface. /// </summary> protected virtual void PaintNetwork(IOGraph graph, PaintState state) { // Create per-node state info. int hiddenNodeCount = graph.HiddenNodeList.Count; int inputNodeCount = graph.InputNodeList.Count; int outputNodeCount = graph.OutputNodeList.Count; state._nodeStateDict = new Dictionary <GraphNode, ConnectionPointInfo>(hiddenNodeCount + inputNodeCount + outputNodeCount); // Paint all connections. We do this first and paint nodes on top of the connections. This allows the // slightly messy ends of the connections to be painted over by the nodes. PaintConnections(graph.InputNodeList, state); PaintConnections(graph.HiddenNodeList, state); PaintConnections(graph.OutputNodeList, state); // Paint all nodes. Painted over the top of connection endpoints. PaintNodes(graph.InputNodeList, state); PaintNodes(graph.HiddenNodeList, state); PaintNodes(graph.OutputNodeList, state); }
/// <summary> /// Constructs with the provided graph painter and layout manager. /// </summary> public IOGraphViewportPainter(IOGraphPainter graphPainter, ILayoutManager layoutManager) { _graph = null; _layoutManager = layoutManager; _graphPainter = graphPainter; }
/// <summary> /// Constructs with the provided graph painter and a default layout manager. /// </summary> public IOGraphViewportPainter(IOGraphPainter graphPainter) { _graph = null; _layoutManager = new DepthLayoutManager(); _graphPainter = graphPainter; }
/// <summary> /// Create an IOGraph that represents the structure described by the provided INetworkDefinition. /// </summary> public IOGraph CreateGraph(INetworkDefinition networkDef) { // Perform depth analysis of network. NetworkDepthInfo depthInfo; if (networkDef.IsAcyclic) { AcyclicNetworkDepthAnalysis depthAnalysis = new AcyclicNetworkDepthAnalysis(); depthInfo = depthAnalysis.CalculateNodeDepths(networkDef); } else { CyclicNetworkDepthAnalysis depthAnalysis = new CyclicNetworkDepthAnalysis(); depthInfo = depthAnalysis.CalculateNodeDepths(networkDef); } // Create an IOGraph, allocating storage for the node lists. INodeList nodeList = networkDef.NodeList; int nodeCount = nodeList.Count; int inputCount = networkDef.InputNodeCount + 1; // + to count bias as an input layer node. int outputCount = networkDef.OutputNodeCount; int hiddenCount = nodeCount - (inputCount + outputCount); IOGraph ioGraph = new IOGraph(inputCount, outputCount, hiddenCount, 0f, depthInfo._networkDepth); // We also build a dictionary of nodes keyed by innovation ID. This is used later // to assign connections to nodes. Dictionary <uint, GraphNode> nodeDict = new Dictionary <uint, GraphNode>(nodeCount); // Loop input nodes. int idx = 0; for (int i = 0; i < inputCount; i++, idx++) { // Create node, assign it a tag and add it to the node dictionary and the // input node list of the IOGraph. uint innovationId = nodeList[idx].Id; GraphNode node = new GraphNode(innovationId.ToString()); node.AuxData = CreateGraphNodeAuxData(nodeList[idx]); node.Depth = depthInfo._nodeDepthArr[idx]; nodeDict.Add(innovationId, node); ioGraph.InputNodeList.Add(node); } // Loop output nodes. for (int i = 0; i < outputCount; i++, idx++) { // Create node, assign it a tag and add it to the node dictionary and the // output node list of the IOGraph. uint innovationId = nodeList[idx].Id; GraphNode node = new GraphNode(innovationId.ToString()); node.AuxData = CreateGraphNodeAuxData(nodeList[idx]); node.Depth = depthInfo._nodeDepthArr[idx]; nodeDict.Add(innovationId, node); ioGraph.OutputNodeList.Add(node); } // Loop hidden nodes. for (; idx < nodeCount; idx++) { // Create node, assign it a tag and add it to the node dictionary and the // hidden node list of the IOGraph. uint innovationId = nodeList[idx].Id; GraphNode node = new GraphNode(innovationId.ToString()); node.AuxData = CreateGraphNodeAuxData(nodeList[idx]); node.Depth = depthInfo._nodeDepthArr[idx]; nodeDict.Add(innovationId, node); ioGraph.HiddenNodeList.Add(node); } // Loop connections. Build GraphConnection objects and connect them to their source // and target nodes. double maxAbsWeight = 0.1; // Start at a non-zero value to prevent possibility of a divide by zero occuring. IConnectionList connectionList = networkDef.ConnectionList; int connCount = connectionList.Count; for (int i = 0; i < connCount; i++) { // Create connection object and assign its source and target nodes. INetworkConnection connection = connectionList[i]; GraphNode sourceNode = nodeDict[connection.SourceNodeId]; GraphNode targetNode = nodeDict[connection.TargetNodeId]; GraphConnection conn = new GraphConnection(sourceNode, targetNode, (float)connection.Weight); // Add the connection to the connection lists on the source and target nodes. sourceNode.OutConnectionList.Add(conn); targetNode.InConnectionList.Add(conn); // Track weight range oevr all connections. double absWeight = Math.Abs(connection.Weight); if (absWeight > maxAbsWeight) { maxAbsWeight = absWeight; } } ioGraph.ConnectionWeightRange = (float)maxAbsWeight; return(ioGraph); }
/// <summary> /// Layout nodes based on their depth within the network. /// /// Note 1. /// Input nodes are defined as being at layer zero and we position them in their own layer at the /// top of the layout area. Any other type of node (hidden or output) node not connected to is also /// defined as being at layer zero, if such nodes exist then we place them into their own layout /// layer to visually separate them from the input nodes. /// /// Note 2. /// Output nodes can be at a range of depths, but for clarity we position them all in their own layer /// at the bottom of the layout area. A hidden node can have a depth greater than or equal to one or /// more of the output nodes, to handle this case neatly we ensure that the output nodes are always /// in a layer by themselves by creating an additional layer in the layout if necessary. /// /// Note 3. /// Hidden nodes are positioned into layers between the inputs and outputs based on their depth. /// /// Note 4. /// Empty layers are not possible in the underlying network because for there to be a layer N node it /// must have a connection from a layer N-1 node. However, in cyclic networks the output nodes can be /// source nodes, but we also always paint output nodes in their own layout layer at the bottom of the /// layout area. Therefore if the only node at a given depth is an output node then the layout layer /// can be empty. To handle this neatly we check for empty layout layers before invoking the layout logic. /// </summary> /// <param name="graph">The network/graph structure to be laid out.</param> /// <param name="layoutArea">The area the structure is to be laid out on.</param> public void Layout(IOGraph graph, Size layoutArea) { int inputCount = graph.InputNodeList.Count; int outputCount = graph.OutputNodeList.Count; int hiddenCount = graph.HiddenNodeList.Count; //=== Stratify all nodes into layout layers, each layer keyed by depth value. SortedDictionary <int, List <GraphNode> > layersByDepth = new SortedDictionary <int, List <GraphNode> >(); // Special case. Key all input nodes by depth -1. This places them as the first layer and distinct // from all other nodes. layersByDepth.Add(-1, graph.InputNodeList); // Stratify hidden nodes into layers. // Count hidden nodes in each layer. int[] hiddenNodeCountByLayerArr = new int[graph.Depth]; for (int i = 0; i < hiddenCount; i++) { hiddenNodeCountByLayerArr[graph.HiddenNodeList[i].Depth]++; } // Allocate storage per layer. for (int i = 0; i < graph.Depth; i++) { int nodeCount = hiddenNodeCountByLayerArr[i]; if (0 != nodeCount) { layersByDepth.Add(i, new List <GraphNode>(nodeCount)); } } // Put hidden nodes into layer lists. for (int i = 0; i < hiddenCount; i++) { GraphNode node = graph.HiddenNodeList[i]; layersByDepth[node.Depth].Add(node); } // Special case 2. Place output nodes in their own distinct layer at a depth one more than the max // depth of the network. This places them as the last layer and distinct from all other nodes. layersByDepth.Add(graph.Depth, graph.OutputNodeList); //=== Layout. // Calculate height of each layer. We divide the layout area height by the number of layers, but also // define margins at the top and bottom of the view. To make best use of available height the margins // are defined as a proportion of the layer height. Hence we have: // // ============================ // d - network depth. // l - layer height. // m - margin height (same height used for top and bottom margins). // p - proportion of layer height that gives margin height. // H - layout areas height // ============================ // // Derivation: // ============================ // 1) l = (H-2m) / (d-1) // // 2) m = lp, therefore // // 3) l = (H-2pl) / (d-1), solve for l // // 4) l = H/(d-1) - 2pl/(d-1) // // 5) 1 = H/(d-1)l - 2p/(d-1) // // 6) H / (d-1)l = 1 + 2p/(d-1), inverting both sides gives // // 7) (d-1)l / H = 1/[ 1 + 2p/(d-1) ] // // 8) = (d-1) / ((d-1) + 2p) // // 9) = (d-1) / (d + 2p - 1), rearranging for l gives // // 10) l = H / (d + 2p - 1) const float p = 0.9f; const float p2 = 2f * p; // Rounding will produce 'off by one' errors, e.g. all heights may not total height of layout area, // but the effect is probably negligible on the quality of the layout. int layoutLayerCount = layersByDepth.Count; int l = (int)Math.Round((float)layoutArea.Height / ((float)layoutLayerCount + p2 - 1f)); int m = (int)Math.Round(l * p); // Size struct to keep track of the area that bounds all nodes. Size bounds = new Size(0, 0); // Assign a position to each node, one layer at a time. int yCurrent = m; foreach (List <GraphNode> layerNodeList in layersByDepth.Values) { // Calculate inter-node gap and margin width (we use the same principle as with the vertical layout of layers, see notes above). int nodeCount = layerNodeList.Count; float xIncr = (float)layoutArea.Width / ((float)nodeCount + p2 - 1f); int xMargin = (int)Math.Round(xIncr * p); // Loop nodes in layer; Assign position to each. float xCurrent = xMargin; for (int nodeIdx = 0; nodeIdx < nodeCount; nodeIdx++, xCurrent += xIncr) { layerNodeList[nodeIdx].Position = new Point((int)xCurrent, yCurrent); UpdateModelBounds(layerNodeList[nodeIdx], ref bounds); } // Increment y coord for next layer. yCurrent += l; } // Store the bounds of the graph elements. Useful when drawing the graph. graph.Bounds = bounds; }
/// <summary> /// Layout nodes evenly spaced out on a grid. /// </summary> /// <param name="graph">The network/graph structure to be layed out.</param> /// <param name="layoutArea">The area the structrue is to be layed out on.</param> public void Layout(IOGraph graph, Size layoutArea) { // Size struct to keep track of the area that bounds all nodes. Size bounds = new Size(0,0); // Some other useful variables. int inputCount = graph.InputNodeList.Count; int outputCount = graph.OutputNodeList.Count; int hiddenCount = graph.HiddenNodeList.Count; double heightWidthRatio = (double)layoutArea.Height/(double)layoutArea.Width; // Determine how many layers/rows to arrange the nodes into. // Note. we always show the inputs and outputs in their own rows, therefore this just // handles how many additional rows are requried for the hidden nodes. int numLayersHidden=0; if(hiddenCount>0) { // Arrange nodes in a sqare and adjust for height/width ratio of layout area. double sqrtHidden = Math.Sqrt(hiddenCount); numLayersHidden = (int)Math.Floor(sqrtHidden * heightWidthRatio); // Must be at least 1 layer if we have nodes. numLayersHidden = Math.Max(1, numLayersHidden); } // Arrange the nodes. int yMarginTop = (int)(layoutArea.Height * MarginProportionYTop); int yMarginBottom = (int)(layoutArea.Height * MarginProportionYBottom); int layoutHeight = layoutArea.Height - (yMarginTop + yMarginBottom); int yCurrent = yMarginTop; int yIncrement; if(0 == numLayersHidden) { // Just two layers (input and output). yIncrement = layoutHeight; } else { yIncrement = layoutHeight / (numLayersHidden+1); } // Input layer. Place all input nodes in one layer. int layoutWidth = layoutArea.Width - (2 * MarginX); int xIncrement = layoutWidth / (inputCount+1); int xCurrent = MarginX + xIncrement; // Loop input nodes. for(int i=0; i<inputCount; i++) { GraphNode node = graph.InputNodeList[i]; node.Position = new Point(xCurrent, yCurrent); UpdateModelBounds(node, ref bounds); xCurrent += xIncrement; } // Increment yCurrent, ready for the next layer. yCurrent += yIncrement; // Layout hidden layers. if(0 != numLayersHidden) { // Calculate the max number of nodes in any hidden layer. int layerNodesMax = (int)Math.Ceiling((double)hiddenCount / (double)numLayersHidden); // Keep track of how many nodes remain to be positioned. The last layer will have fewer // than layerNodesMax if the number of nodes isn't a square number. int nodesRemaining = hiddenCount; int nodeIdx=0; // Loop layers. for(int i=0; i<numLayersHidden; i++) { // Calculate the number of nodes in this layer. int layerNodeCount = Math.Min(nodesRemaining, layerNodesMax); // Position nodes in this layer. xIncrement = layoutWidth / (layerNodeCount+1); xCurrent = MarginX + xIncrement; // Loop nodes in this layer. for(int j=0; j<layerNodeCount; j++, nodeIdx++) { GraphNode node = graph.HiddenNodeList[nodeIdx]; node.Position = new Point(xCurrent, yCurrent); UpdateModelBounds(node, ref bounds); xCurrent += xIncrement; } // Increment yCurrent, ready for the next layer. yCurrent += yIncrement; nodesRemaining -= layerNodeCount; } } // Output layer. Place all output nodes in one layer. xIncrement = layoutWidth / (outputCount+1); xCurrent = MarginX + xIncrement; // Loop output nodes. for(int i=0; i<outputCount; i++) { GraphNode node = graph.OutputNodeList[i]; node.Position = new Point(xCurrent, yCurrent); UpdateModelBounds(node, ref bounds); xCurrent += xIncrement; } // Store the bounds of the graph elements. Useful when drawing the graph. graph.Bounds = bounds; }
/// <summary> /// Create an IOGraph that represents the structure described by the provided INetworkDefinition. /// </summary> public IOGraph CreateGraph(INetworkDefinition networkDef) { // Perform depth analysis of network. NetworkDepthInfo depthInfo; if(networkDef.IsAcyclic) { AcyclicNetworkDepthAnalysis depthAnalysis = new AcyclicNetworkDepthAnalysis(); depthInfo = depthAnalysis.CalculateNodeDepths(networkDef); } else { CyclicNetworkDepthAnalysis depthAnalysis = new CyclicNetworkDepthAnalysis(); depthInfo = depthAnalysis.CalculateNodeDepths(networkDef); } // Create an IOGraph, allocating storage for the node lists. INodeList nodeList = networkDef.NodeList; int nodeCount = nodeList.Count; int inputCount = networkDef.InputNodeCount + 1; // + to count bias as an input layer node. int outputCount = networkDef.OutputNodeCount; int hiddenCount = nodeCount - (inputCount + outputCount); IOGraph ioGraph = new IOGraph(inputCount, outputCount, hiddenCount, 0f, depthInfo._networkDepth); // We also build a dictionary of nodes keyed by innovation ID. This is used later // to assign connections to nodes. Dictionary<uint, GraphNode> nodeDict = new Dictionary<uint,GraphNode>(nodeCount); // Loop input nodes. int idx = 0; for(int i=0; i<inputCount; i++, idx++) { // Create node, assign it a tag and add it to the node dictionary and the // input node list of the IOGraph. uint innovationId = nodeList[idx].Id; GraphNode node = new GraphNode(innovationId.ToString()); node.AuxData = CreateGraphNodeAuxData(nodeList[idx]); node.Depth = depthInfo._nodeDepthArr[idx]; nodeDict.Add(innovationId, node); ioGraph.InputNodeList.Add(node); } // Loop output nodes. for(int i=0; i<outputCount; i++, idx++) { // Create node, assign it a tag and add it to the node dictionary and the // output node list of the IOGraph. uint innovationId = nodeList[idx].Id; GraphNode node = new GraphNode(innovationId.ToString()); node.AuxData = CreateGraphNodeAuxData(nodeList[idx]); node.Depth = depthInfo._nodeDepthArr[idx]; nodeDict.Add(innovationId, node); ioGraph.OutputNodeList.Add(node); } // Loop hidden nodes. for(; idx<nodeCount; idx++) { // Create node, assign it a tag and add it to the node dictionary and the // hidden node list of the IOGraph. uint innovationId = nodeList[idx].Id; GraphNode node = new GraphNode(innovationId.ToString()); node.AuxData = CreateGraphNodeAuxData(nodeList[idx]); node.Depth = depthInfo._nodeDepthArr[idx]; nodeDict.Add(innovationId, node); ioGraph.HiddenNodeList.Add(node); } // Loop connections. Build GraphConnection objects and connect them to their source // and target nodes. double maxAbsWeight = 0.1; // Start at a non-zero value to prevent possibility of a divide by zero occurring. IConnectionList connectionList = networkDef.ConnectionList; int connCount = connectionList.Count; for(int i=0; i<connCount; i++) { // Create connection object and assign its source and target nodes. INetworkConnection connection = connectionList[i]; GraphNode sourceNode = nodeDict[connection.SourceNodeId]; GraphNode targetNode = nodeDict[connection.TargetNodeId]; GraphConnection conn = new GraphConnection(sourceNode, targetNode, (float)connection.Weight); // Add the connection to the connection lists on the source and target nodes. sourceNode.OutConnectionList.Add(conn); targetNode.InConnectionList.Add(conn); // Track weight range over all connections. double absWeight = Math.Abs(connection.Weight); if(absWeight > maxAbsWeight) { maxAbsWeight = absWeight; } } ioGraph.ConnectionWeightRange = (float)maxAbsWeight; return ioGraph; }
/// <summary> /// Layout nodes evenly spaced out on a grid. /// </summary> /// <param name="graph">The network/graph structure to be layed out.</param> /// <param name="layoutArea">The area the structrue is to be layed out on.</param> public void Layout(IOGraph graph, Size layoutArea) { // Size struct to keep track of the area that bounds all nodes. Size bounds = new Size(0, 0); // Some other useful variables. int inputCount = graph.InputNodeList.Count; int outputCount = graph.OutputNodeList.Count; int hiddenCount = graph.HiddenNodeList.Count; double heightWidthRatio = (double)layoutArea.Height / (double)layoutArea.Width; // Determine how many layers/rows to arrange the nodes into. // Note. we always show the inputs and outputs in their own rows, therefore this just // handles how many additional rows are requried for the hidden nodes. int numLayersHidden = 0; if (hiddenCount > 0) { // Arrange nodes in a sqare and adjust for height/width ratio of layout area. double sqrtHidden = Math.Sqrt(hiddenCount); numLayersHidden = (int)Math.Floor(sqrtHidden * heightWidthRatio); // Must be at least 1 layer if we have nodes. numLayersHidden = Math.Max(1, numLayersHidden); } // Arrange the nodes. int yMarginTop = (int)(layoutArea.Height * MarginProportionYTop); int yMarginBottom = (int)(layoutArea.Height * MarginProportionYBottom); int layoutHeight = layoutArea.Height - (yMarginTop + yMarginBottom); int yCurrent = yMarginTop; int yIncrement; if (0 == numLayersHidden) { // Just two layers (input and output). yIncrement = layoutHeight; } else { yIncrement = layoutHeight / (numLayersHidden + 1); } // Input layer. Place all input nodes in one layer. int layoutWidth = layoutArea.Width - (2 * MarginX); int xIncrement = layoutWidth / (inputCount + 1); int xCurrent = MarginX + xIncrement; // Loop input nodes. for (int i = 0; i < inputCount; i++) { GraphNode node = graph.InputNodeList[i]; node.Position = new Point(xCurrent, yCurrent); UpdateModelBounds(node, ref bounds); xCurrent += xIncrement; } // Increment yCurrent, ready for the next layer. yCurrent += yIncrement; // Layout hidden layers. if (0 != numLayersHidden) { // Calculate the max number of nodes in any hidden layer. int layerNodesMax = (int)Math.Ceiling((double)hiddenCount / (double)numLayersHidden); // Keep track of how many nodes remain to be positioned. The last layer will have fewer // than layerNodesMax if the number of nodes isn't a square number. int nodesRemaining = hiddenCount; int nodeIdx = 0; // Loop layers. for (int i = 0; i < numLayersHidden; i++) { // Calculate the number of nodes in this layer. int layerNodeCount = Math.Min(nodesRemaining, layerNodesMax); // Position nodes in this layer. xIncrement = layoutWidth / (layerNodeCount + 1); xCurrent = MarginX + xIncrement; // Loop nodes in this layer. for (int j = 0; j < layerNodeCount; j++, nodeIdx++) { GraphNode node = graph.HiddenNodeList[nodeIdx]; node.Position = new Point(xCurrent, yCurrent); UpdateModelBounds(node, ref bounds); xCurrent += xIncrement; } // Increment yCurrent, ready for the next layer. yCurrent += yIncrement; nodesRemaining -= layerNodeCount; } } // Output layer. Place all output nodes in one layer. xIncrement = layoutWidth / (outputCount + 1); xCurrent = MarginX + xIncrement; // Loop output nodes. for (int i = 0; i < outputCount; i++) { GraphNode node = graph.OutputNodeList[i]; node.Position = new Point(xCurrent, yCurrent); UpdateModelBounds(node, ref bounds); xCurrent += xIncrement; } // Store the bounds of the graph elements. Useful when drawing the graph. graph.Bounds = bounds; }