public static void Initialize()
        {
            // populate all nodes
            Forest = new List <Node>(DefDatabase <ResearchProjectDef> .AllDefsListForReading
                                     // exclude hidden projects (prereq of itself is a common trick to hide research).
                                     .Where(def => def.prerequisites.NullOrEmpty() || !def.prerequisites.Contains(def))
                                     .Select(def => new Node(def)));

            // mark, but do not remove redundant prerequisites.
            foreach (Node node in Forest)
            {
                if (!node.Research.prerequisites.NullOrEmpty())
                {
                    var ancestors = node.Research.prerequisites.SelectMany(r => r.GetPrerequisitesRecursive()).ToList();
                    if (!ancestors.NullOrEmpty() &&
                        (!node.Research.prerequisites?.Intersect(ancestors).ToList().NullOrEmpty() ?? false))
                    {
                        Log.Warning("ResearchPal :: redundant prerequisites for " + node.Research.LabelCap + " the following research: " +
                                    string.Join(", ", node.Research.prerequisites.Intersect(ancestors).Select(r => r.LabelCap).ToArray()));
                    }
                }
            }

            // create links between nodes
            foreach (Node node in Forest)
            {
                node.CreateLinks();
            }

            // calculate Depth of each node
            foreach (Node node in Forest)
            {
                node.SetDepth();
            }

            // get the main 'Trees', looping over all defs, find strings of Research named similarly.
            // We're aiming for finding things like Construction I/II/III/IV/V here.
            Dictionary <string, List <Node> > trunks = new Dictionary <string, List <Node> > ();
            List <Node> orphans = new List <Node> (); // temp

            foreach (Node node in Forest)
            {
                // try to remove the amount of random hits by requiring Trees to be directly linked.
                if (node.Parents.Any(parent => parent.Genus == node.Genus) ||
                    node.Children.Any(child => child.Genus == node.Genus))
                {
                    if (!trunks.ContainsKey(node.Genus))
                    {
                        trunks.Add(node.Genus, new List <Node> ());
                    }
                    trunks [node.Genus].Add(node);
                }
                else
                {
                    orphans.Add(node);
                }
            }

            // Assign the working dictionary to Tree objects, culling stumps.
            Trees = trunks.Where(trunk => trunk.Value.Count >= Settings.MinTrunkSize)
                    .Select(trunk => new Tree(trunk.Key, trunk.Value))
                    .ToList();

            // add too small Trees back into orphan list
            orphans.AddRange(trunks.Where(trunk => trunk.Value.Count < Settings.MinTrunkSize).SelectMany(trunk => trunk.Value));

            // The order in which Trees should appear; ideally we want Trees with lots of cross-references to appear together.
            OrderTrunks();

            // Attach orphan nodes to the nearest Trunk, or the orphanage trunk
            Orphans = new Tree("orphans", new List <Node>())
            {
                Color = Color.grey
            };
            foreach (Node orphan in orphans)
            {
                Tree closest = orphan.ClosestTree() ?? Orphans;
                closest.AddLeaf(orphan);
            }

            // Assign colors to trunks
            int n = Trees.Count;

            for (int i = 1; i <= Trees.Count; i++)
            {
                Trees[i - 1].Color = ColorHelper.HSVtoRGB((float)i / n, 1, 1);
            }

            // update nodes with position info
            FixPositions();

            // Done!
            Initialized = true;
        }
        public void DrawTree(Rect canvas)
        {
            // get total size of Research Tree
            int maxDepth = 0, totalWidth = 0;

            if (ResearchTree.Trees.Any())
            {
                maxDepth   = ResearchTree.Trees.Max(tree => tree.MaxDepth);
                totalWidth = ResearchTree.Trees.Sum(tree => tree.Width);
            }

            maxDepth    = Math.Max(maxDepth, ResearchTree.Orphans.MaxDepth);
            totalWidth += ResearchTree.Orphans.Width;

            float width  = (maxDepth + 1) * (Settings.NodeSize.x + Settings.NodeMargins.x);    // zero based
            float height = (totalWidth - 1) * (Settings.NodeSize.y + Settings.NodeMargins.y);

            // main view rect
            Rect view = new Rect(0f, 0f, width, height);

            // create the scroll area below the search box (plus a small margin) so it stays on top
            Widgets.BeginScrollView(new Rect(canvas.x,
                                             canvas.y + filterManager.Height + Settings.NodeMargins.y,
                                             canvas.width, canvas.height - filterManager.Height - Settings.NodeMargins.y),
                                    ref _scrollPosition, view);
            GUI.BeginGroup(view);
            Text.Anchor = TextAnchor.MiddleCenter;

            // draw regular connections, not done first to better highlight done.
            foreach (Pair <Node, Node> connection in connections.Where(pair => !pair.Second.Research.IsFinished))
            {
                ResearchTree.DrawLine(connection,
                                      filterManager.FilterPhrase.NullOrEmpty() ? connection.First.Tree.GreyedColor : ColorHelper.AdjustAlpha(connection.First.Tree.GreyedColor, Settings.FilterNonMatchAlpha));
            }

            // draw connections from completed nodes
            foreach (Pair <Node, Node> connection in connections.Where(pair => pair.Second.Research.IsFinished))
            {
                if (!filterManager.FilterPhrase.NullOrEmpty())
                {
                    ResearchTree.DrawLine(connection, ColorHelper.AdjustAlpha(connection.First.Tree.GreyedColor, Settings.FilterNonMatchAlpha));
                }
                else
                {
                    ResearchTree.DrawLine(connection, connection.First.Tree.MediumColor);
                }
            }
            connections.Clear();

            // draw highlight connections on top
            foreach (Pair <Node, Node> connection in highlightedConnections)
            {
                ResearchTree.DrawLine(connection, GenUI.MouseoverColor, true);
            }
            highlightedConnections.Clear();

            // draw nodes on top of lines
            bool reqScroll    = true;
            Node scrollToNode = null;

            foreach (Node node in nodes)
            {
                // draw the node
                bool visible = node.Draw();

                // ensure that at least one matching node is visible, prioritize highest on the screen
                if (filterManager.FilterDirty)
                {
                    if (node.FilterMatch.IsValidMatch())
                    {
                        if (!reqScroll)
                        {
                            continue;
                        }

                        // this node is a match and is currently visible, we don't need to scroll
                        if (visible)
                        {
                            reqScroll    = false;
                            scrollToNode = null;
                        }
                        else
                        {
                            // this node is a match, but isn't visible. if it's the highest node then we'll scroll to it
                            if (scrollToNode == null || node.Pos.z < scrollToNode.Pos.z)
                            {
                                scrollToNode = node;
                            }
                        }
                    }
                }
            }

            if (filterManager.FilterDirty)
            {
                // scroll to a matching node if necessary
                if (scrollToNode != null)
                {
                    // scale the focus area to ensure it all fits on the screen
                    Rect r = scrollToNode.Rect.ScaledBy(2.0f);
                    _scrollPosition = new Vector2(r.xMin, r.yMin);
                }
                else if (filterManager.FilterPhrase == "")
                {
                    _scrollPosition = Vector2.zero;
                }
            }
            nodes.Clear();

            // register hub tooltips
            foreach (KeyValuePair <Rect, List <string> > pair in hubTips)
            {
                string text = string.Join("\n", pair.Value.ToArray());
                TooltipHandler.TipRegion(pair.Key, text);
            }
            hubTips.Clear();

            // draw Queue labels
            Queue.DrawLabels();

            // reset anchor
            Text.Anchor = TextAnchor.UpperLeft;

            GUI.EndGroup();
            Widgets.EndScrollView();
        }