Download RadialDiagramDemoPart1.zip, last updated 05/11/2017 (11.38 KB)

Download
  • md5: 980240e651a010b143b573f01f46ab02
  • sha1: c26f00aaec5f56e9a27fcab7e796b8abe0b33e29
  • sha256: c209600f7368ebe7a0b63de229b0a5bde7efbe547f2387a6b61a34e35c034c66
// Arranging items radially around a central point using C#
// http://www.cyotek.com/blog/arranging-items-radially-around-a-central-point-using-csharp
// Copyright © 2017 Cyotek Ltd. All Rights Reserved.

// This work is licensed under the Creative Commons Attribution 4.0 International License.
// To view a copy of this license, visit http://creativecommons.org/licenses/by/4.0/.

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;

namespace Cyotek.Demo.RadialDiagram
{
  public partial class MainForm : Form
  {
    #region Fields

    private int _autoIncrement;

    private int _childSize;

    private bool _drawBounds;

    private int _maximumDistance;

    private DiagramNode _root;

    private int _rootSize;

    #endregion

    #region Constructors

    public MainForm()
    {
      this.InitializeComponent();
    }

    #endregion

    #region Methods

    protected override void OnShown(EventArgs e)
    {
      base.OnShown(e);

      _drawBounds = false;
      _maximumDistance = (int)maximumDistanceNumericUpDown.Value;
      _rootSize = (int)rootSizeNumericUpDown.Value;
      _childSize = (int)childSizeNumericUpDown.Value;

      this.CreateDiagram();

      rootTrackBar.Focus();

      //_autoIncrement = 1;
      //rootTrackBar.Maximum = 18;
      //rootTrackBar.Value = 0;
      //incrementTimer.Enabled = true;
    }

    private void ArrangeNodes(List<DiagramNode> nodes, int cx, int cy, double angle, int distance)
    {
      for (int i = 0; i < nodes.Count; i++)
      {
        DiagramNode child;
        int x;
        int y;

        child = nodes[i];

        x = cx + Convert.ToInt32(Math.Cos(angle * i) * distance);
        y = cy + Convert.ToInt32(Math.Sin(angle * i) * distance);

        // adjust the final location to be the top left instead of the center
        child.Location = new Point(x - child.Width / 2, y - child.Height / 2);
      }
    }

    private void BruteForceNodeLayout(DiagramNode parent, List<DiagramNode> childNodes, double angle, int cx, int cy, int distance)
    {
      bool success;

      do
      {
        // increment the distance
        distance += childNodes[0].Width / 4;
        if (distance > _maximumDistance)
        {
          distance = _maximumDistance;
        }

        // first arrange all the nodes around the central node with a minimum distance
        this.ArrangeNodes(childNodes, cx, cy, angle, distance);

        success = distance >= _maximumDistance || this.TestNodes(parent, childNodes);
      } while (!success);
    }

    private void childSizeNumericUpDown_ValueChanged(object sender, EventArgs e)
    {
      _childSize = (int)childSizeNumericUpDown.Value;

      this.CreateDiagram();
    }

    private void CreateDiagram()
    {
      _root = new DiagramNode("Root", new Size(_rootSize, _rootSize));

      for (int i = 0; i < rootTrackBar.Value; i++)
      {
        _root.ChildNodes.Add(new DiagramNode("Page " + (i + 1), new Size(_childSize, _childSize)));
      }

      this.PositionDiagram(_root);
      diagramPanel.Invalidate();
    }

    private void diagramPanel_Paint(object sender, PaintEventArgs e)
    {
      int x;
      int y;
      Size size;

      e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;

      // find the center of the panel, we'll draw our diagram there
      size = diagramPanel.ClientSize;
      x = (size.Width - _root.Width) / 2;
      y = (size.Height - _root.Height) / 2;

      this.DrawNode(e.Graphics, _root, x, y);
    }

    private bool DoCirclesInsersect(int cx1, int cy1, int r1, int cx2, int cy2, int r2)
    {
      int dx;
      int dy;
      double distance;

      // Find the distance between the centers.
      //Derived from  http://csharphelper.com/blog/2014/09/determine-where-two-circles-intersect-in-c/
      dx = cx1 - cx2;
      dy = cy1 - cy2;
      distance = Math.Sqrt(dx * dx + dy * dy);

      return distance < r1 + r2;
    }

    private void drawBoundariesCheckBox_CheckedChanged(object sender, EventArgs e)
    {
      _drawBounds = drawBoundariesCheckBox.Checked;

      diagramPanel.Invalidate();
    }

    private void DrawChildNodes(Graphics g, DiagramNode node, int offsetX, int offsetY, int cx, int cy)
    {
      foreach (DiagramNode child in node.ChildNodes)
      {
        Point center;

        center = child.Center;

        // draw the connector between the parent node and this child
        g.DrawLine(Pens.Gray, cx, cy, offsetX + center.X, offsetY + center.Y);

        // draw the child node (and any children of its own)
        this.DrawNode(g, child, offsetX, offsetY);
      }
    }

    private void DrawNode(Graphics g, DiagramNode node, int offsetX, int offsetY)
    {
      int x;
      int y;
      int w;
      int h;
      int cx;
      int cy;

      // get the bounds of the source node
      // and apply the drawing offset
      x = node.Left + offsetX;
      y = node.Top + offsetY;
      w = node.Width;
      h = node.Height;

      // define the center of the source node
      cx = x + w / 2;
      cy = y + h / 2;

      // draw the child nodes first
      // so that the connectors don't overwrite the node
      this.DrawChildNodes(g, node, offsetX, offsetY, cx, cy);

      // node draw the center node
      g.FillEllipse(Brushes.PapayaWhip, x, y, w, h);
      g.DrawEllipse(Pens.DimGray, x, y, w, h);

      TextRenderer.DrawText(g, node.Text, this.Font, new Rectangle(x, y, w, h), Color.DarkSlateGray, Color.PapayaWhip, TextFormatFlags.NoPadding | TextFormatFlags.SingleLine | TextFormatFlags.VerticalCenter | TextFormatFlags.HorizontalCenter | TextFormatFlags.NoPrefix | TextFormatFlags.EndEllipsis);

      if (_drawBounds)
      {
        g.DrawRectangle(Pens.Red, x, y, w, h);
      }
    }

    private void incrementTimer_Tick(object sender, EventArgs e)
    {
      int value;

      value = rootTrackBar.Value + _autoIncrement;

      if (value > rootTrackBar.Maximum)
      {
        _autoIncrement = -1;
      }
      else if (value < rootTrackBar.Minimum)
      {
        _autoIncrement = 1;
      }
      else
      {
        rootTrackBar.Value = value;
      }
    }

    private void maximumDistanceNumericUpDown_ValueChanged(object sender, EventArgs e)
    {
      _maximumDistance = (int)maximumDistanceNumericUpDown.Value;

      this.PositionDiagram(_root);
      diagramPanel.Invalidate();
    }

    private void PositionDiagram(DiagramNode node)
    {
      int count;
      double angle;
      int distance;
      int cx;
      int cy;
      List<DiagramNode> childNodes;
      Point center;

      childNodes = node.ChildNodes;

      count = childNodes.Count;

      angle = 360.0 / count * Math.PI / 180.0;

      // if we were using squares we'd need some extra padding
      // but as I'm using ellipsis we can use use the largest axis
      distance = Math.Max(node.Width, node.Height);

      // need to use the centerpoint of our node
      // to ensure all other nodes are an equal distance away
      center = node.Center;
      cx = center.X;
      cy = center.Y;

      // position the children
      this.ArrangeNodes(childNodes, cx, cy, angle, distance);

      // if there is more than one child node, check to see if any intersect with each 
      // other. if they do, and the distance is within a given maximum, increase the distance
      // and try again. I've no doubt there's a much better way of doing this
      // than brute forcing!
      if (count > 1 && !this.TestNodes(node, childNodes))
      {
        this.BruteForceNodeLayout(node, childNodes, angle, cx, cy, distance);
      }
    }

    private void rootSizeNumericUpDown_ValueChanged(object sender, EventArgs e)
    {
      _rootSize = (int)rootSizeNumericUpDown.Value;

      this.CreateDiagram();
    }

    private void rootTrackBar_Scroll(object sender, EventArgs e)
    {
      this.CreateDiagram();
    }

    private bool TestNodes(DiagramNode parent, List<DiagramNode> nodes)
    {
      bool result;
      int count;
      int cx3;
      int cy3;
      int r3;
      Point c3;

      result = true;
      count = nodes.Count;

      c3 = parent.Center;
      cx3 = c3.X;
      cy3 = c3.Y;
      r3 = parent.Width / 2;

      for (int i = 0; i < count; i++)
      {
        int cx1;
        int cy1;
        int cx2;
        int cy2;
        int r1;
        int r2;
        int next;
        Point c1;
        Point c2;

        // find the next node. if the current
        // node is the last node in the collection, 
        // the next node is therefore the first node
        next = i < count - 1 ? i + 1 : 0;

        // get the center and radius of the current node
        c1 = nodes[i].Center;
        cx1 = c1.X;
        cy1 = c1.Y;
        r1 = nodes[i].Width / 2;

        // get the center and radius of the next node
        c2 = nodes[next].Center;
        cx2 = c2.X;
        cy2 = c2.Y;
        r2 = nodes[next].Width / 2;

        // check to see if the first node (c1) intersects with the root node (c3)
        // or if the first node intersecds with the last node (c2)
        if (this.DoCirclesInsersect(cx1, cy1, r1, cx3, cy3, r3) || this.DoCirclesInsersect(cx1, cy1, r1, cx2, cy2, r2))
        {
          result = false;
          break;
        }
      }

      return result;
    }

    #endregion
  }
}

Donate

Donate