Cyotek Development Bloghttps://devblog.cyotek.com/tag/trigonometry/atom.xml2020-04-06T17:00:09ZArranging items radially around a central point using C#urn:uuid:86f1fb70-2193-4df7-8ff4-79ee17d814c62020-04-06T17:00:09Z2017-11-05T10:33:39Z<p>Recently I was looking for a way of display hierarchical
information in a more compact form than the previous
horizontal/vertical trees I was using. One of the concepts I
looked into during this was the idea of arranging children
radially around a central node.</p>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/radial-tree-1a.png" class="gallery" title="The demonstration program with a few child nodes" ><img src="https://images.cyotek.com/image/thumbnail/devblog/radial-tree-1a.png" alt="The demonstration program with a few child nodes" decoding="async" loading="lazy" /></a><figcaption>The demonstration program with a few child nodes</figcaption></figure>
<p>This post discusses the sample project I created to explore the
first part of this concept.</p>
<blockquote>
<p>Caveat emptor. This post is out of my usual comfort zone as
its dealing with trigonometry. Errors and misunderstandings
may abound.</p>
</blockquote>
<h2 id="calculating-the-angle">Calculating the angle</h2>
<p>The first thing we need to do is calculate the angle between one
node and the next. The following formula will calculate the
number of <del>degrees</del> radians in the angle.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="comment">// count is the number of children we&#39;ll be placing around the center</span>
<span class="keyword">double</span> angle <span class="symbol">=</span> <span class="number">360.0</span> <span class="symbol">/</span> count <span class="symbol">*</span> Math<span class="symbol">.</span>PI <span class="symbol">/</span> <span class="number">180.0</span><span class="symbol">;</span>
</pre>
</figure>
<h2 id="calculating-a-default-distance">Calculating a default distance</h2>
<p>All the nodes in this diagram are going to be rendered as
circles. This makes some of the layout work easier due to there
being no corners, as the shapes can be closer to together
without overlapping. To start with, I'll simply try and place
the new nodes right next to the centre node.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="comment">// technically as I&#39;m using circles the Width and Height should always be the same, but this may change in future</span>
distance <span class="symbol">=</span> Math<span class="symbol">.</span>Max<span class="symbol">(</span>node<span class="symbol">.</span>Width<span class="symbol">,</span> node<span class="symbol">.</span>Height<span class="symbol">)</span><span class="symbol">;</span>
</pre>
</figure>
<h2 id="positioning-each-node">Positioning each node</h2>
<p>Once we have the angle, we loop through each child node and
using the angle and distance values we can calculate the centre
point of the child using the sine and cosine mathematical
functions. Once we've got the centre, we offset it by half the
nodes width and height to get the upper left corner.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">for</span> <span class="symbol">(</span><span class="keyword">int</span> i <span class="symbol">=</span> <span class="number">0</span><span class="symbol">;</span> i <span class="symbol">&lt;</span> nodes<span class="symbol">.</span>Count<span class="symbol">;</span> i<span class="symbol">++</span><span class="symbol">)</span>
<span class="symbol">{</span>
 DiagramNode child<span class="symbol">;</span>
 <span class="keyword">int</span> x<span class="symbol">;</span>
 <span class="keyword">int</span> y<span class="symbol">;</span>

 child <span class="symbol">=</span> nodes<span class="symbol">[</span>i<span class="symbol">]</span><span class="symbol">;</span>

 <span class="comment">// calculate the center of the child node offset from</span>
 <span class="comment">// the central node</span>
 x <span class="symbol">=</span> cx <span class="symbol">+</span> Convert<span class="symbol">.</span>ToInt<span class="number">32</span><span class="symbol">(</span>Math<span class="symbol">.</span>Cos<span class="symbol">(</span>angle <span class="symbol">*</span> i<span class="symbol">)</span> <span class="symbol">*</span> distance<span class="symbol">)</span><span class="symbol">;</span>
 y <span class="symbol">=</span> cy <span class="symbol">+</span> Convert<span class="symbol">.</span>ToInt<span class="number">32</span><span class="symbol">(</span>Math<span class="symbol">.</span>Sin<span class="symbol">(</span>angle <span class="symbol">*</span> i<span class="symbol">)</span> <span class="symbol">*</span> distance<span class="symbol">)</span><span class="symbol">;</span>

 <span class="comment">// adjust the final location to be the top left instead of the center</span>
 child<span class="symbol">.</span>Location <span class="symbol">=</span> <span class="keyword">new</span> Point<span class="symbol">(</span>x <span class="symbol">-</span> <span class="symbol">(</span>child<span class="symbol">.</span>Width <span class="symbol">/</span> <span class="number">2</span><span class="symbol">)</span><span class="symbol">,</span> y <span class="symbol">-</span> <span class="symbol">(</span>child<span class="symbol">.</span>Height <span class="symbol">/</span> <span class="number">2</span><span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/radial-tree-1b.png" class="gallery" title="Overlapping nodes make for pretty patterns but otherwise aren't much use" ><img src="https://images.cyotek.com/image/thumbnail/devblog/radial-tree-1b.png" alt="Overlapping nodes make for pretty patterns but otherwise aren't much use" decoding="async" loading="lazy" /></a><figcaption>Overlapping nodes make for pretty patterns but otherwise aren't much use</figcaption></figure>
<p>With this code in place, our diagram is taking shape - at least
until we add enough nodes that they start to overlap with each
other. If this happens, we need to detect if the nodes overlap
using Euclidean geometry.</p>
<h2 id="testing-if-two-circles-overlap">Testing if two circles overlap</h2>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/radial-tree-1d.png" class="gallery" title="Without corners, nodes can overlap each other without having an impact" ><img src="https://images.cyotek.com/image/thumbnail/devblog/radial-tree-1d.png" alt="Without corners, nodes can overlap each other without having an impact" decoding="async" loading="lazy" /></a><figcaption>Without corners, nodes can overlap each other without having an impact</figcaption></figure>
<p>To test if circles overlap, we calculate the distance between
the centre of the first circle and the second. If the distance
is less than the radius of the two circles, then they intersect.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">private</span> <span class="keyword">bool</span> DoCirclesInsersect<span class="symbol">(</span><span class="keyword">int</span> cx<span class="number">1</span><span class="symbol">,</span> <span class="keyword">int</span> cy<span class="number">1</span><span class="symbol">,</span> <span class="keyword">int</span> r<span class="number">1</span><span class="symbol">,</span> <span class="keyword">int</span> cx<span class="number">2</span><span class="symbol">,</span> <span class="keyword">int</span> cy<span class="number">2</span><span class="symbol">,</span> <span class="keyword">int</span> r<span class="number">2</span><span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">int</span> dx<span class="symbol">;</span>
 <span class="keyword">int</span> dy<span class="symbol">;</span>
 <span class="keyword">double</span> distance<span class="symbol">;</span>

 <span class="comment">// Find the distance between the centers</span>
 dx <span class="symbol">=</span> cx<span class="number">1</span> <span class="symbol">-</span> cx<span class="number">2</span><span class="symbol">;</span>
 dy <span class="symbol">=</span> cy<span class="number">1</span> <span class="symbol">-</span> cy<span class="number">2</span><span class="symbol">;</span>
 distance <span class="symbol">=</span> Math<span class="symbol">.</span>Sqrt<span class="symbol">(</span>dx <span class="symbol">*</span> dx <span class="symbol">+</span> dy <span class="symbol">*</span> dy<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">return</span> distance <span class="symbol">&lt;</span> r<span class="number">1</span> <span class="symbol">+</span> r<span class="number">2</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<blockquote>
<p>This is almost exactly the same method used in my previous
post of <a href="/post/finding-nearest-colors-using-euclidean-distance">finding nearest colours</a>.</p>
</blockquote>
<p>For a much more detailed version of this function which can also
determine at which points on the edges of the circles intersect
see this <a href="http://csharphelper.com/blog/2014/09/determine-where-two-circles-intersect-in-c/" rel="external nofollow noopener">C# Helper post</a>. It also describes the math,
something I'm not even going to try and do.</p>
<h2 id="brute-forcing-the-intersection">Brute forcing the intersection</h2>
<figure class="screenshot" ><a href="https://www.cyotek.com/files/articleimages/radial-tree-1c.png" class="gallery" title="Increasing the distance between the centre node and its children mean they can fit without overlapping" ><img src="https://www.cyotek.com/files/articleimages/radial-tree-1c-thumbnail.png" alt="Increasing the distance between the centre node and its children mean they can fit without overlapping" decoding="async" loading="lazy" /></a><figcaption>Increasing the distance between the centre node and its children mean they can fit without overlapping</figcaption></figure>
<p>I'm sure there's probably a much better way of doing this, but
as I don't know of one I resorted to brute forcing - after
positioning the children, I check them to see if there's any
overlap. If there are intersections, I increment the distance,
re-position the nodes and test again. To avoid nodes being
placed excessively far from the centre node, once the distance
is above a defined maximum I abort the testing and use the
maximum value, regardless of overlap.</p>
<blockquote>
<p>The below code only considers the intersection of one child
node with an adjacent child node. However, if the central node
is smaller than the children, then it is possible for the
child nodes to overlap the parent. The full demonstration
program tests for intersection with both the parent node and
the next child node to avoid this.</p>
</blockquote>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">private</span> <span class="keyword">bool</span> TestNodes<span class="symbol">(</span>List<span class="symbol">&lt;</span>DiagramNode<span class="symbol">&gt;</span> nodes<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">bool</span> result<span class="symbol">;</span>
 <span class="keyword">int</span> count<span class="symbol">;</span>

 result <span class="symbol">=</span> <span class="keyword">true</span><span class="symbol">;</span>
 count <span class="symbol">=</span> nodes<span class="symbol">.</span>Count<span class="symbol">;</span>

 <span class="keyword">for</span> <span class="symbol">(</span><span class="keyword">int</span> i <span class="symbol">=</span> <span class="number">0</span><span class="symbol">;</span> i <span class="symbol">&lt;</span> count<span class="symbol">;</span> i<span class="symbol">++</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">int</span> cx<span class="number">1</span><span class="symbol">;</span>
 <span class="keyword">int</span> cy<span class="number">1</span><span class="symbol">;</span>
 <span class="keyword">int</span> cx<span class="number">2</span><span class="symbol">;</span>
 <span class="keyword">int</span> cy<span class="number">2</span><span class="symbol">;</span>
 <span class="keyword">int</span> r<span class="number">1</span><span class="symbol">;</span>
 <span class="keyword">int</span> r<span class="number">2</span><span class="symbol">;</span>
 <span class="keyword">int</span> next<span class="symbol">;</span>
 Point c<span class="number">1</span><span class="symbol">;</span>
 Point c<span class="number">2</span><span class="symbol">;</span>

 next <span class="symbol">=</span> i <span class="symbol">&lt;</span> count <span class="symbol">-</span> <span class="number">1</span> <span class="symbol">?</span> i <span class="symbol">+</span> <span class="number">1</span> <span class="symbol">:</span> <span class="number">0</span><span class="symbol">;</span>

 c<span class="number">1</span> <span class="symbol">=</span> nodes<span class="symbol">[</span>i<span class="symbol">]</span><span class="symbol">.</span>Center<span class="symbol">;</span>
 c<span class="number">2</span> <span class="symbol">=</span> nodes<span class="symbol">[</span>next<span class="symbol">]</span><span class="symbol">.</span>Center<span class="symbol">;</span>

 cx<span class="number">1</span> <span class="symbol">=</span> c<span class="number">1</span><span class="symbol">.</span>X<span class="symbol">;</span>
 cy<span class="number">1</span> <span class="symbol">=</span> c<span class="number">1</span><span class="symbol">.</span>Y<span class="symbol">;</span>
 r<span class="number">1</span> <span class="symbol">=</span> nodes<span class="symbol">[</span>i<span class="symbol">]</span><span class="symbol">.</span>Width <span class="symbol">/</span> <span class="number">2</span><span class="symbol">;</span>

 cx<span class="number">2</span> <span class="symbol">=</span> c<span class="number">2</span><span class="symbol">.</span>X<span class="symbol">;</span>
 cy<span class="number">2</span> <span class="symbol">=</span> c<span class="number">2</span><span class="symbol">.</span>Y<span class="symbol">;</span>
 r<span class="number">2</span> <span class="symbol">=</span> nodes<span class="symbol">[</span>next<span class="symbol">]</span><span class="symbol">.</span>Width <span class="symbol">/</span> <span class="number">2</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>DoCirclesInsersect<span class="symbol">(</span>cx<span class="number">1</span><span class="symbol">,</span> cy<span class="number">1</span><span class="symbol">,</span> r<span class="number">1</span><span class="symbol">,</span> cx<span class="number">2</span><span class="symbol">,</span> cy<span class="number">2</span><span class="symbol">,</span> r<span class="number">2</span><span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 result <span class="symbol">=</span> <span class="keyword">false</span><span class="symbol">;</span>
 <span class="keyword">break</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>

 <span class="keyword">return</span> result<span class="symbol">;</span>
<span class="symbol">}</span>

<span class="keyword">private</span> <span class="keyword">void</span> PositionDiagram<span class="symbol">(</span>DiagramNode node<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">int</span> count<span class="symbol">;</span>
 <span class="keyword">double</span> angle<span class="symbol">;</span>
 <span class="keyword">int</span> distance<span class="symbol">;</span>
 <span class="keyword">int</span> cx<span class="symbol">;</span>
 <span class="keyword">int</span> cy<span class="symbol">;</span>
 List<span class="symbol">&lt;</span>DiagramNode<span class="symbol">&gt;</span> childNodes<span class="symbol">;</span>
 Point center<span class="symbol">;</span>

 childNodes <span class="symbol">=</span> node<span class="symbol">.</span>ChildNodes<span class="symbol">;</span>

 count <span class="symbol">=</span> childNodes<span class="symbol">.</span>Count<span class="symbol">;</span>

 angle <span class="symbol">=</span> <span class="number">360.0</span> <span class="symbol">/</span> count <span class="symbol">*</span> Math<span class="symbol">.</span>PI <span class="symbol">/</span> <span class="number">180.0</span><span class="symbol">;</span>

 <span class="comment">// if we were using squares we&#39;d need some extra padding</span>
 <span class="comment">// but as I&#39;m using ellipsis we can use use the largest axis</span>
 distance <span class="symbol">=</span> Math<span class="symbol">.</span>Max<span class="symbol">(</span>node<span class="symbol">.</span>Width<span class="symbol">,</span> node<span class="symbol">.</span>Height<span class="symbol">)</span><span class="symbol">;</span>

 <span class="comment">// need to use the centerpoint of our node</span>
 <span class="comment">// to ensure all other nodes are an equal distance away</span>
 center <span class="symbol">=</span> node<span class="symbol">.</span>Center<span class="symbol">;</span>
 cx <span class="symbol">=</span> center<span class="symbol">.</span>X<span class="symbol">;</span>
 cy <span class="symbol">=</span> center<span class="symbol">.</span>Y<span class="symbol">;</span>

 <span class="comment">// position the children</span>
 <span class="keyword">this</span><span class="symbol">.</span>ArrangeNodes<span class="symbol">(</span>childNodes<span class="symbol">,</span> cx<span class="symbol">,</span> cy<span class="symbol">,</span> angle<span class="symbol">,</span> distance<span class="symbol">)</span><span class="symbol">;</span>

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

<span class="keyword">private</span> <span class="keyword">void</span> BruteForceNodeLayout<span class="symbol">(</span>List<span class="symbol">&lt;</span>DiagramNode<span class="symbol">&gt;</span> childNodes<span class="symbol">,</span> <span class="keyword">double</span> angle<span class="symbol">,</span> <span class="keyword">int</span> cx<span class="symbol">,</span> <span class="keyword">int</span> cy<span class="symbol">,</span> <span class="keyword">int</span> distance<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">bool</span> success<span class="symbol">;</span>

 <span class="keyword">do</span>
 <span class="symbol">{</span>
 <span class="comment">// increment the distance</span>
 distance <span class="symbol">+=</span> childNodes<span class="symbol">[</span><span class="number">0</span><span class="symbol">]</span><span class="symbol">.</span>Width <span class="symbol">/</span> <span class="number">4</span><span class="symbol">;</span>
 <span class="keyword">if</span> <span class="symbol">(</span>distance <span class="symbol">&gt;</span> _maximumDistance<span class="symbol">)</span>
 <span class="symbol">{</span>
 distance <span class="symbol">=</span> _maximumDistance<span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="comment">// first arrange all the nodes around the central node with a minimum distance</span>
 <span class="keyword">this</span><span class="symbol">.</span>ArrangeNodes<span class="symbol">(</span>childNodes<span class="symbol">,</span> cx<span class="symbol">,</span> cy<span class="symbol">,</span> angle<span class="symbol">,</span> distance<span class="symbol">)</span><span class="symbol">;</span>

 success <span class="symbol">=</span> distance <span class="symbol">&gt;=</span> _maximumDistance <span class="symbol">||</span> <span class="keyword">this</span><span class="symbol">.</span>TestNodes<span class="symbol">(</span>childNodes<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span> <span class="keyword">while</span> <span class="symbol">(</span><span class="symbol">!</span>success<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<blockquote>
<p>Once the child nodes have moved sufficiently far enough away
from the centre you could try staggering the nodes to make
better use of the free space; this may allow for a closer
grouping.</p>
</blockquote>
<p>Although the demonstration program doesn't show this, this code
works perfectly well if the child nodes are of varying sizes -
it will try and position according to the largest child it
finds.</p>
<h2 id="initial-starting-position">Initial starting position</h2>
<p>If you consider a clock face, the painting of the first node in
this example always occurs at 3 o'clock. This is actually
perfect for my needs, but if you wanted it to start from
somewhere else (for example 12 o'clock), you'd need to adapt the
code in <code>ArrangeNodes</code>.</p>
<h2 id="final-thoughts">Final thoughts</h2>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/radial-tree-1.gif" class="gallery" title="An animated example of the demonstration program" ><img src="https://images.cyotek.com/image/thumbnail/devblog/radial-tree-1.gif" alt="An animated example of the demonstration program" decoding="async" loading="lazy" /></a><figcaption>An animated example of the demonstration program</figcaption></figure>
<p>The technique in this article can be useful in other
circumstances, for example I first used code similar to this to
create a 12 node colour wheel as part of another concept
program. But the general principle could be used for other
things, such as dials, gauges and clock faces.</p>
<p>Due to the brute forcing for positioning, this code is nowhere
near as optimal as I'd otherwise like it - if anyone has ideas
for solving this I'd love to <a href="https://cyotek.com/contact">hear them</a>!</p>
<p>I cut out a lot of the code from this article and just focused
on the core functionality, a fully functional sample can be
downloaded from the link below.</p>
<h2 id="update-history">Update History</h2>
<ul>
<li>2017-11-05 - First published</li>
<li>2020-11-22 - Updated formatting</li>
</ul>

<p><small>
All content <a href="https://devblog.cyotek.com/copyright-and-trademarks">Copyright (c) by Cyotek Ltd</a> or its respective writers. Permission to reproduce news and web log entries and other RSS feed content in unmodified form without notice is granted provided they are not used to endorse or promote any products or opinions (other than what was expressed by the author) and without taking them out of context. Written permission from the copyright owner must be obtained for everything else.<br />Original URL of this content is https://devblog.cyotek.com/post/arranging-items-radially-around-a-central-point-using-csharp .
</small></p>Richard Mosshttps://www.cyotek.com/richard.moss@cyotek.com