Archive Browser
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
This software may be used free of charge, but as with all free software there are costs involved to develop and maintain.
If this site or its services have saved you time, please consider a donation to help with running costs and timely updates.
Donate