{"id":984,"date":"2014-10-29T09:07:50","date_gmt":"2014-10-29T09:07:50","guid":{"rendered":"http:\/\/mindfusion.eu\/blog\/?p=984"},"modified":"2021-01-14T11:44:45","modified_gmt":"2021-01-14T11:44:45","slug":"combine-layout-algorithms-2","status":"publish","type":"post","link":"https:\/\/mindfusion.dev\/blog\/combine-layout-algorithms-2\/","title":{"rendered":"Combine layout algorithms"},"content":{"rendered":"<p><strong>Apply TreeLayout twice to arrange a genealogy tree<\/strong><\/p>\n<p>In a series of posts we\u2019ll explore ways to combine graph layout algorithms for various purposes, such as improving layout speed or achieving specific layout constraints.<\/p>\n<p>In this example we\u2019ll show how to apply two TreeLayout instances with different settings to arrange a genealogy tree. The genealogy tree is focused on specific person, with several levels of ancestors drawn above and descendants drawn below. A Visual Studio sample project containing the code from this post is available for download here:<\/p>\n<p><a title=\"GenealogyLayout.zip\" href=\"https:\/\/mindfusion.dev\/_samples\/GenealogyLayout.zip\">GenealogyLayout.zip<\/a><\/p>\n<p>As a start, let&#8217;s define a new node class that will draw a person&#8217;s photo and name inside a frame, along with their partners&#8217;. This will simplify our layout code since we won&#8217;t have to take care of keeping partner nodes close to each other:<\/p>\n<pre>class GenealogyNode : DiagramNode\n{\n\tpublic List Partners { get; set; }\n\n\tpublic override void DrawLocal(IGraphics graphics, RenderOptions options)\n\t{\n\t\tfloat relationLinkLen = Bounds.Width \/ 7;\n\t\tint relations = Partners.Count - 1;\n\t\tfloat personViewWidth = Bounds.Width - relations * relationLinkLen;\n\t\tpersonViewWidth \/= Partners.Count;\n\n\t\tvar rect = GetLocalBounds();\n\t\trect.Width = personViewWidth;\n\t\tfor (int i = 0; i &lt; Partners.Count; i++)\n\t\t{\n\t\t\tDrawPerson(Partners[i], graphics, rect);\n\n\t\t\tif (i &lt; Partners.Count - 1)\n\t\t\t{\n\t\t\t\tfloat rx = rect.Right;\n\t\t\t\tfloat ry = rect.Y + 4 * rect.Height \/ 5;\n\t\t\t\trect.X += personViewWidth + relationLinkLen;\n\t\t\t\tgraphics.DrawLine(Pens.Gray, rx, ry, rect.X, ry);\n\t\t\t}\n\t\t}\n\t}\n\n\tpublic override void DrawShadowLocal(IGraphics graphics, RenderOptions options)\n\t{\n\t}\n\n\tvoid DrawPerson(Person person, IGraphics graphics, RectangleF rect)\n\t{\n\t\tconst float labelHeight = 5;\n\t\tconst float padding = 3;\n\n\t\t\/\/ draw name\n\t\tvar labelRect = RectangleF.FromLTRB(\n\t\t\trect.Left,\n\t\t\trect.Bottom - labelHeight,\n\t\t\trect.Right,\n\t\t\trect.Bottom);\n\n\t\tgraphics.DrawString(person.Name,\n\t\t\tEffectiveFont, Brushes.Black, labelRect,\n\t\t\tnew StringFormat { Alignment = StringAlignment.Center });\n\n\t\t\/\/ draw image\n\t\tvar imageRect = rect;\n\t\timageRect.Height -= labelHeight + padding;\n\n\t\tUtilities.DrawImage(graphics, person.Image, imageRect, ImageAlign.Fit);\n\n\t\t\/\/ draw frame\n\t\tvar frameColor = person.Gender == Gender.Female ?\n\t\t\tColor.Red : Color.BlueViolet;\n\t\tvar framePen = new System.Drawing.Pen(frameColor, 0);\n\t\tgraphics.DrawRectangle(framePen, rect);\n\t\tframePen.Dispose();\n\t}\n}\n<\/pre>\n<p>Alternatively, we could draw a single person per node instead, placing partners&#8217; nodes close to each other, grouping them using AttachTo method, and later running TreeLayout with its KeepGroupLayout property enabled.<\/p>\n<p>Now to generate a sample tree, we&#8217;ll define recursive methods that will create specified number of ancestor pairs (GenerateAncestors) and create random number of descendants (GenerateDescendants):<\/p>\n<pre>void GenerateAncestors(GenealogyNode node, int levels)\n{\n\tif (levels == 0)\n\t\treturn;\n\tfor (int i = 0; i &lt; 2; i++)\n\t{\n\t\tvar p = AddPair();\n\t\tvar link = diagram.Factory.CreateDiagramLink(p, node);\n\t\tlink.DestinationAnchor = i;\n\t\tlink.OriginAnchor = 2;\n\t\tGenerateAncestors(p, levels - 1);\n\t}\n}\n\nvoid GenerateDescendants(GenealogyNode node, int levels)\n{\n\tif (levels == 0)\n\t\treturn;\n\tint children = random.Next(1, 5);\n\tfor (int i = 0; i &lt; children; i++)\n\t{\n\t\tint r = random.Next(0, 3);\n\t\tif (r == 2)\n\t\t{\n\t\t\tvar p = AddPair();\n\t\t\tvar link = diagram.Factory.CreateDiagramLink(node, p);\n\t\t\tlink.OriginAnchor = 2;\n\t\t\tlink.DestinationAnchor = 0;\n\t\t\tGenerateDescendants(p, levels - 1);\n\t\t}\n\t\telse if (r == 1)\n\t\t{\n\t\t\tvar p = new Person { Name = \"daughter\", Gender = Gender.Female, Image = fImage };\n\t\t\tvar childNode = AddNode(p);\n\t\t\tdiagram.Factory.CreateDiagramLink(node, childNode);\n\t\t}\n\t\telse if (r == 0)\n\t\t{\n\t\t\tvar p = new Person { Name = \"son\", Gender = Gender.Male, Image = mImage };\n\t\t\tvar childNode = AddNode(p);\n\t\t\tdiagram.Factory.CreateDiagramLink(node, childNode);\n\t\t}\n\t}\n}\n\nGenealogyNode AddPair()\n{\n\tvar p1 = new Person { Name = \"mom\", Gender = Gender.Female, Image = fImage };\n\tvar p2 = new Person { Name = \"dad\", Gender = Gender.Male, Image = mImage };\n\treturn AddNode(p1, p2);\n}\n\nGenealogyNode AddNode(Person p)\n{\n\tvar bounds = new RectangleF(0, 0, 30, 40);\n\n\tvar node = new GenealogyNode();\n\tnode.Bounds = bounds;\n\tnode.Partners = new List { p };\n\tnode.AnchorPattern = AnchorPattern.TopInBottomOut;\n\tdiagram.Nodes.Add(node);\n\treturn node;\n}\n\nGenealogyNode AddNode(Person p1, Person p2)\n{\n\tvar bounds = new RectangleF(0, 0, 70, 40);\n\n\tvar node = new GenealogyNode();\n\tnode.Bounds = bounds;\n\tnode.Partners = new List { p1, p2 };\n\tnode.AnchorPattern = PairPattern;\n\tdiagram.Nodes.Add(node);\n\treturn node;\n}\n<\/pre>\n<p>Finally we run TreeLayout twice with specified root node, arranging ancestor nodes above the root and descendant nodes below it, creating the genealogy drawing shown below:<\/p>\n<pre>private void GenealogyForm_Load(object sender, EventArgs e)\n{\n\tvar root = AddPair();\n\tGenerateAncestors(root, 2);\n\tGenerateDescendants(root, 3);\n\n\tvar l1 = new TreeLayout();\n\tl1.ReversedLinks = true;\n\tl1.Direction = TreeLayoutDirections.BottomToTop;\n\tl1.Anchoring = Anchoring.Keep;\n\tl1.LevelDistance *= 2;\n\tl1.NodeDistance *= 1.4f;\n\tl1.LinkStyle = TreeLayoutLinkType.Cascading3;\n\tl1.Arrange(diagram);\n\n\tvar l2 = new TreeLayout();\n\tl2.Root = root;\n\tl2.KeepRootPosition = true;\n\tl2.Anchoring = Anchoring.Keep;\n\tl2.LevelDistance *= 2;\n\tl2.NodeDistance *= 1.4f;\n\tl2.LinkStyle = TreeLayoutLinkType.Cascading3;\n\tl2.Arrange(diagram);\n\n\tdiagram.ResizeToFitItems(5);\n\t\/\/diagramView.ZoomToFit();\n}\n<\/pre>\n<p><img decoding=\"async\" src=\"http:\/\/mindfusion.dev\/_samples\/genealogy_tree.png\" alt=\"genealogy tree layout\" \/><\/p>\n<p>The code above uses MindFusion\u2019s .NET API and can be used with Windows Forms, WPF, Silverlight and ASP.NET diagramming components. The Java API for Android and desktop Swing application will look similar, with setter method calls instead of property assignments.<\/p>\n<p>You can download the trial version of any MindFusion.Diagramming component from <a href=\"http:\/\/mindfusion.dev\/download-diagramming-pack.html\">this page.<\/a><\/p>\n<p>Enjoy!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Apply TreeLayout twice to arrange a genealogy tree In a series of posts we\u2019ll explore ways to combine graph layout algorithms for various purposes, such as improving layout speed or achieving specific layout constraints. In this example we\u2019ll show how &hellip; <a href=\"https:\/\/mindfusion.dev\/blog\/combine-layout-algorithms-2\/\">Continue reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","enabled":false},"version":2}},"categories":[95,74],"tags":[264,262,6,263],"class_list":["post-984","post","type-post","status-publish","format-standard","hentry","category-diagramming-2","category-sample-code","tag-c","tag-genealogy","tag-layout","tag-tree"],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_shortlink":"https:\/\/wp.me\/p3RlKs-fS","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/mindfusion.dev\/blog\/wp-json\/wp\/v2\/posts\/984","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/mindfusion.dev\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/mindfusion.dev\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/mindfusion.dev\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/mindfusion.dev\/blog\/wp-json\/wp\/v2\/comments?post=984"}],"version-history":[{"count":4,"href":"https:\/\/mindfusion.dev\/blog\/wp-json\/wp\/v2\/posts\/984\/revisions"}],"predecessor-version":[{"id":2518,"href":"https:\/\/mindfusion.dev\/blog\/wp-json\/wp\/v2\/posts\/984\/revisions\/2518"}],"wp:attachment":[{"href":"https:\/\/mindfusion.dev\/blog\/wp-json\/wp\/v2\/media?parent=984"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/mindfusion.dev\/blog\/wp-json\/wp\/v2\/categories?post=984"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/mindfusion.dev\/blog\/wp-json\/wp\/v2\/tags?post=984"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}