1 /*
2  *  "GEDKeeper", the personal genealogical database editor.
3  *  Copyright (C) 2009-2021 by Sergey V. Zhdanovskih.
4  *
5  *  This file is part of "GEDKeeper".
6  *
7  *  This program is free software: you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License as published by
9  *  the Free Software Foundation, either version 3 of the License, or
10  *  (at your option) any later version.
11  *
12  *  This program is distributed in the hope that it will be useful,
13  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  *  GNU General Public License for more details.
16  *
17  *  You should have received a copy of the GNU General Public License
18  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
19  */
20 
21 using System;
22 using System.Collections.Generic;
23 using BSLib;
24 using BSLib.Design.Graphics;
25 using GDModel;
26 using GKCore.Interfaces;
27 using GKCore.Options;
28 
29 namespace GKCore.Charts
30 {
ARootChangedEventHandler(object sender, GDMIndividualRecord person)31     public delegate void ARootChangedEventHandler(object sender, GDMIndividualRecord person);
32 
33     public enum CircleChartType { Ancestors, Descendants }
34 
35     public abstract class CircleSegment : BaseObject
36     {
37         public int Gen;
38         public GDMIndividualRecord IRec;
39         public IGfxPath Path;
40 
41         public float Rad; // ?
42         public float IntRad; // Internal radius
43         public float ExtRad; // External radius
44 
45         public float StartAngle;
46         public float WedgeAngle;
47 
CircleSegment(int generation)48         protected CircleSegment(int generation)
49         {
50             Gen = generation;
51             IRec = null;
52             Path = AppHost.GfxProvider.CreatePath();
53         }
54 
Dispose(bool disposing)55         protected override void Dispose(bool disposing)
56         {
57             if (disposing) {
58                 if (Path != null) Path.Dispose();
59             }
60             base.Dispose(disposing);
61         }
62     }
63 
64     public sealed class AncPersonSegment : CircleSegment
65     {
66         public int GroupIndex;
67         public AncPersonSegment FatherSegment;
68         public AncPersonSegment MotherSegment;
69 
AncPersonSegment(int generation)70         public AncPersonSegment(int generation) : base(generation)
71         {
72             GroupIndex = -1;
73             FatherSegment = null;
74             MotherSegment = null;
75         }
76     }
77 
78     public sealed class DescPersonSegment : CircleSegment
79     {
80         public readonly List<DescPersonSegment> ChildSegments;
81         public int TotalSubSegments;
82 
DescPersonSegment(int generation)83         public DescPersonSegment(int generation) : base(generation)
84         {
85             ChildSegments = new List<DescPersonSegment>();
86             TotalSubSegments = 0;
87         }
88     }
89 
90     /// <summary>
91     ///
92     /// </summary>
93     public sealed class CircleChartModel : ChartModel
94     {
95         public const int CENTRAL_INDEX = 9;
96         public const float CENTER_RAD = 90;
97         public const float DEFAULT_GEN_WIDTH = 60;
98         public const int MAX_GENERATIONS = 8;
99 
100         private readonly IBrush[] fCircleBrushes;
101         private readonly IBrush[] fDarkBrushes;
102 
103         private IBaseWindow fBase;
104         private ExtRectF fBounds;
105         private float fGenWidth;
106         private int fIndividualsCount;
107         private CircleChartOptions fOptions;
108         private IPen fPen;
109         private GDMIndividualRecord fRootPerson;
110         private readonly List<CircleSegment> fSegments;
111         private CircleSegment fSelected;
112         private int fVisibleGenerations;
113 
114         #region Only ancestors circle
115         private int fGroupCount;
116         private bool fGroupsMode;
117         #endregion
118 
119         public IFont Font;
120 
121 
122         public IBaseWindow Base
123         {
124             get { return fBase; }
125             set { fBase = value; }
126         }
127 
128         public ExtRectF Bounds
129         {
130             get { return fBounds; }
131         }
132 
133         public float GenWidth
134         {
135             get {
136                 return fGenWidth;
137             }
138             set {
139                 if (value < 20 || value > 100) return;
140 
141                 fGenWidth = value;
142             }
143         }
144 
145         public bool GroupsMode
146         {
147             get {
148                 return fGroupsMode;
149             }
150             set {
151                 fGroupsMode = value;
152                 if (value) {
153                     fVisibleGenerations = MAX_GENERATIONS;
154                     GenWidth = 60;
155                 } else {
156                     fVisibleGenerations = MAX_GENERATIONS;
157                     GenWidth = CircleChartModel.DEFAULT_GEN_WIDTH;
158                 }
159             }
160         }
161 
162         public int IndividualsCount
163         {
164             get { return fIndividualsCount; }
165         }
166 
167         public CircleChartOptions Options
168         {
169             get { return fOptions; }
170             set { fOptions = value; }
171         }
172 
173         public float PenWidth
174         {
175             get { return fPen.Width; }
176         }
177 
178         public GDMIndividualRecord RootPerson
179         {
180             get {
181                 return fRootPerson;
182             }
183             set {
184                 if (fRootPerson != value) {
185                     fRootPerson = value;
186                 }
187             }
188         }
189 
190         public List<CircleSegment> Segments
191         {
192             get { return fSegments; }
193         }
194 
195         public CircleSegment Selected
196         {
197             get { return fSelected; }
198             set { fSelected = value; }
199         }
200 
201         public int VisibleGenerations
202         {
203             get { return fVisibleGenerations; }
204             set {
205                 if (value >= 1 && value <= MAX_GENERATIONS) {
206                     fVisibleGenerations = value;
207                 }
208             }
209         }
210 
211 
CircleChartModel()212         public CircleChartModel()
213         {
214             fCircleBrushes = new IBrush[CircleChartOptions.MAX_BRUSHES];
215             fDarkBrushes = new IBrush[CircleChartOptions.MAX_BRUSHES];
216 
217             fBounds = new ExtRectF();
218             fGenWidth = CircleChartModel.DEFAULT_GEN_WIDTH;
219             fSegments = new List<CircleSegment>();
220             fSelected = null;
221             fVisibleGenerations = MAX_GENERATIONS;
222         }
223 
Dispose(bool disposing)224         protected override void Dispose(bool disposing)
225         {
226             if (disposing) {
227                 DisposeBrushes();
228             }
229             base.Dispose(disposing);
230         }
231 
CreateBrushes()232         public void CreateBrushes()
233         {
234             for (int i = 0; i < fOptions.BrushColor.Length; i++)
235             {
236                 IColor col = fOptions.BrushColor[i];
237 
238                 fCircleBrushes[i] = fRenderer.CreateSolidBrush(col);
239                 fDarkBrushes[i] = fRenderer.CreateSolidBrush(col.Darker(0.2f));
240             }
241 
242             fPen = fRenderer.CreatePen(fOptions.BrushColor[10], 1.0f);
243         }
244 
DisposeBrushes()245         public void DisposeBrushes()
246         {
247             for (int i = 0; i < fOptions.BrushColor.Length; i++)
248             {
249                 if (fCircleBrushes[i] != null) fCircleBrushes[i].Dispose();
250                 if (fDarkBrushes[i] != null) fDarkBrushes[i].Dispose();
251             }
252 
253             if (fPen != null) fPen.Dispose();
254         }
255 
AdjustBounds()256         public void AdjustBounds()
257         {
258             /* Update scrolling area. */
259             fBounds.Left = 0.0f;
260             fBounds.Top = 0.0f;
261             fBounds.Right = 0.0f;
262             fBounds.Bottom = 0.0f;
263             foreach (var segment in fSegments) {
264                 ExtRectF bound = segment.Path.GetBounds();
265                 fBounds.Left = Math.Min(fBounds.Left, bound.Left);
266                 fBounds.Top = Math.Min(fBounds.Top, bound.Top);
267                 fBounds.Right = Math.Max(fBounds.Right, bound.Right);
268                 fBounds.Bottom = Math.Max(fBounds.Bottom, bound.Bottom);
269             }
270 
271             /* Add double width of the pen -- adjust both sides. */
272             fImageHeight = (int)(fBounds.GetHeight() + fPen.Width * 2);
273             fImageWidth = (int)(fBounds.GetWidth() + fPen.Width * 2);
274         }
275 
FindSegmentByRec(GDMIndividualRecord iRec)276         public CircleSegment FindSegmentByRec(GDMIndividualRecord iRec)
277         {
278             CircleSegment result = null;
279 
280             int numberOfSegments = fSegments.Count;
281             for (int i = 0; i < numberOfSegments; i++) {
282                 CircleSegment segment = fSegments[i];
283 
284                 if (segment.IRec == iRec) {
285                     result = segment;
286                     break;
287                 }
288             }
289 
290             return result;
291         }
292 
FindSegment(float dX, float dY)293         public CircleSegment FindSegment(float dX, float dY)
294         {
295             double rad = Math.Sqrt(dX * dX + dY * dY);
296             double angle = MathHelper.RadiansToDegrees(Math.Atan2(dY, dX));
297             if (angle <= -90) angle += 360.0f;
298 
299             CircleSegment result = null;
300 
301             int numberOfSegments = fSegments.Count;
302             for (int i = 0; i < numberOfSegments; i++)
303             {
304                 CircleSegment segment = fSegments[i];
305                 double startAng = segment.StartAngle;
306                 double endAng = startAng + segment.WedgeAngle;
307 
308                 if ((segment.IntRad <= rad && rad < segment.ExtRad) &&
309                     (startAng <= angle && angle < endAng))
310                 {
311                     result = segment;
312                     break;
313                 }
314             }
315 
316             return result;
317         }
318 
319         /// <summary>
320         /// Renders a specified <paramref name="segment"/>'s person name within
321         /// the segment.
322         /// </summary>
323         /// <param name="segment">Source segment to be drawn on `gfx`.</param>
DrawPersonName(CircleSegment segment)324         private void DrawPersonName(CircleSegment segment)
325         {
326             int gen = segment.Gen;
327             GDMIndividualRecord iRec = segment.IRec;
328 
329             string surn, givn;
330             if (iRec == null) {
331                 if (gen == 0) {
332                     givn = "Choose";
333                     surn = "subject";
334                 } else {
335                     return;
336                 }
337             } else {
338                 var parts = GKUtils.GetNameParts(fBase.Context.Tree, iRec);
339                 surn = parts.Surname;
340                 givn = parts.Name;
341             }
342 
343             var brush = fCircleBrushes[8];
344             ExtSizeF size;
345             float rad = segment.Rad - 20;
346             float angle = segment.StartAngle + 90.0f + segment.WedgeAngle / 2;
347             float wedgeAngle = segment.WedgeAngle;
348 
349             bool isNarrow = IsNarrowSegment(givn, rad, wedgeAngle, Font);
350 
351             fRenderer.SaveTransform();
352 
353             if (gen == 0) {
354 
355                 // central circle
356                 size = fRenderer.GetTextSize(surn, Font);
357                 fRenderer.DrawString(surn, Font, brush, -size.Width / 2f, -size.Height / 2f - size.Height / 2f);
358                 size = fRenderer.GetTextSize(givn, Font);
359                 fRenderer.DrawString(givn, Font, brush, -size.Width / 2f, 0f);
360 
361             } else {
362 
363                 if (isNarrow) {
364                     //var debugBrush = fRenderer.CreateSolidBrush(ChartRenderer.Red);
365 
366                     // narrow segments of 6-8 generations, radial text
367                     float dx = (float)Math.Sin(Math.PI * angle / 180.0f) * rad;
368                     float dy = (float)Math.Cos(Math.PI * angle / 180.0f) * rad;
369                     fRenderer.TranslateTransform(dx, -dy);
370 
371                     if (fOptions.LTRCorrection && (angle >= 180 && angle < 360)) {
372                         angle -= 180.0f;
373                     }
374 
375                     fRenderer.RotateTransform(angle - 90.0f);
376 
377                     size = fRenderer.GetTextSize(givn, Font);
378                     fRenderer.DrawString(givn, Font, brush, -size.Width / 2f, -size.Height / 2f);
379 
380                 } else {
381 
382                     if (wedgeAngle < 20) {
383 
384                         float dx = (float)Math.Sin(Math.PI * angle / 180.0f) * rad;
385                         float dy = (float)Math.Cos(Math.PI * angle / 180.0f) * rad;
386                         fRenderer.TranslateTransform(dx, -dy);
387                         fRenderer.RotateTransform(angle);
388 
389                         size = fRenderer.GetTextSize(givn, Font);
390                         fRenderer.DrawString(givn, Font, brush, -size.Width / 2f, -size.Height / 2f);
391 
392                     } else if (wedgeAngle < 180) {
393 
394                         if (fOptions.ArcText) {
395                             if (gen == 2) {
396                                 size = fRenderer.GetTextSize(surn, Font);
397                                 fRenderer.DrawArcText(surn, 0.0f, 0.0f, rad + size.Height / 2f,
398                                                       segment.StartAngle, segment.WedgeAngle, true, true, Font, brush);
399 
400                                 size = fRenderer.GetTextSize(givn, Font);
401                                 fRenderer.DrawArcText(givn, 0.0f, 0.0f, rad - size.Height / 2f,
402                                                       segment.StartAngle, segment.WedgeAngle, true, true, Font, brush);
403                             } else {
404                                 fRenderer.DrawArcText(givn, 0.0f, 0.0f, rad,
405                                                       segment.StartAngle, segment.WedgeAngle, true, true, Font, brush);
406                             }
407                         } else {
408                             float dx = (float)Math.Sin(Math.PI * angle / 180.0f) * rad;
409                             float dy = (float)Math.Cos(Math.PI * angle / 180.0f) * rad;
410                             fRenderer.TranslateTransform(dx, -dy);
411                             fRenderer.RotateTransform(angle);
412 
413                             size = fRenderer.GetTextSize(surn, Font);
414                             fRenderer.DrawString(surn, Font, brush, -size.Width / 2f, -size.Height / 2f);
415 
416                             size = fRenderer.GetTextSize(givn, Font);
417                             dx = (float)Math.Sin(Math.PI * angle / 180.0f) * (rad - size.Height);
418                             dy = (float)Math.Cos(Math.PI * angle / 180.0f) * (rad - size.Height);
419 
420                             fRenderer.RestoreTransform();
421                             fRenderer.SaveTransform();
422 
423                             fRenderer.TranslateTransform(dx, -dy);
424                             fRenderer.RotateTransform(angle);
425 
426                             fRenderer.DrawString(givn, Font, brush, -size.Width / 2f, -size.Height / 2f);
427                         }
428 
429                     } else if (wedgeAngle < 361) {
430 
431                         if (fOptions.ArcText) {
432                             size = fRenderer.GetTextSize(surn, Font);
433                             fRenderer.DrawArcText(surn, 0.0f, 0.0f, rad + size.Height / 2f,
434                                                   segment.StartAngle, segment.WedgeAngle, true, true, Font, brush);
435 
436                             size = fRenderer.GetTextSize(givn, Font);
437                             fRenderer.DrawArcText(givn, 0.0f, 0.0f, rad - size.Height / 2f,
438                                                   segment.StartAngle, segment.WedgeAngle, true, true, Font, brush);
439                         } else {
440                             float dx = (float)Math.Sin(Math.PI * angle / 180.0f) * rad;
441                             float dy = (float)Math.Cos(Math.PI * angle / 180.0f) * rad;
442                             fRenderer.TranslateTransform(dx, -dy);
443                             fRenderer.RotateTransform(angle);
444 
445                             size = fRenderer.GetTextSize(surn, Font);
446                             fRenderer.DrawString(surn, Font, brush, -size.Width / 2f, -size.Height / 2f);
447                             size = fRenderer.GetTextSize(givn, Font);
448                             fRenderer.DrawString(givn, Font, brush, -size.Width / 2f, -size.Height / 2f + size.Height);
449                         }
450 
451                     }
452                 }
453             }
454 
455             fRenderer.RestoreTransform();
456         }
457 
IsNarrowSegment(string text, float radius, float wedgeAngle, IFont font)458         private bool IsNarrowSegment(string text, float radius, float wedgeAngle, IFont font)
459         {
460             ExtSizeF size = fRenderer.GetTextSize(text, font);
461             radius = radius + size.Height / 2.0f;
462 
463             float wedgeL = radius * (float)MathHelper.DegreesToRadians(wedgeAngle);
464 
465             return (wedgeL / size.Width <= 0.9f);
466         }
467 
DefineSegment(CircleSegment segment, float rad, float inRad, float extRad, float startAngle, float wedgeAngle)468         private void DefineSegment(CircleSegment segment,
469                                    float rad, float inRad, float extRad,
470                                    float startAngle, float wedgeAngle)
471         {
472             segment.StartAngle = startAngle;
473             segment.WedgeAngle = wedgeAngle;
474             segment.Rad = rad;
475             segment.IntRad = inRad;
476             segment.ExtRad = extRad;
477 
478             if (wedgeAngle == 360.0f) {
479                 segment.Path = AppHost.GfxProvider.CreateCirclePath(-extRad, -extRad, extRad * 2.0f, extRad * 2.0f);
480             } else {
481                 segment.Path = AppHost.GfxProvider.CreateCircleSegmentPath(inRad, extRad, wedgeAngle, startAngle, startAngle + wedgeAngle);
482             }
483         }
484 
DrawSegment(CircleSegment segment, IPen pen, IBrush brush)485         private void DrawSegment(CircleSegment segment, IPen pen, IBrush brush)
486         {
487             fRenderer.DrawPath(pen, brush, segment.Path);
488             DrawPersonName(segment);
489         }
490 
491         #region Ancestors Circle
492 
BuildAncTree()493         public void BuildAncTree()
494         {
495             fSegments.Clear();
496 
497             const float startRad = CircleChartModel.CENTER_RAD - 50;
498             float inRad = startRad;
499 
500             AncPersonSegment segment = new AncPersonSegment(0);
501             DefineSegment(segment, 0, 0, inRad, 0 - 90.0f, 360.0f);
502             fSegments.Add(segment);
503 
504             int maxSteps = 1;
505             for (int gen = 1; gen <= fVisibleGenerations; gen++) {
506                 inRad = startRad + ((gen - 1) * fGenWidth);
507                 float extRad = inRad + fGenWidth;
508 
509                 maxSteps *= 2;
510                 float wedgeAngle = (360.0f / maxSteps);
511 
512                 for (int step = 0; step < maxSteps; step++) {
513                     float startAngle = (step * wedgeAngle) - 90.0f;
514 
515                     segment = new AncPersonSegment(gen);
516                     DefineSegment(segment, 0, inRad, extRad, startAngle, wedgeAngle);
517                     fSegments.Add(segment);
518                 }
519             }
520 
521             // traverse tree
522             fGroupCount = -1;
523             fIndividualsCount = 0;
524             if (fRootPerson == null) return;
525 
526             fIndividualsCount++;
527             AncPersonSegment rootSegment = SetSegmentParams(0, fRootPerson, 0, -1);
528             if (rootSegment == null) return;
529 
530             rootSegment.WedgeAngle = 360.0f;
531 
532             GDMIndividualRecord father = null, mother = null;
533             GDMFamilyRecord fam = fBase.Context.Tree.GetParentsFamily(fRootPerson);
534             if (fam != null && fBase.Context.IsRecordAccess(fam.Restriction)) {
535                 fBase.Context.Tree.GetSpouses(fam, out father, out mother);
536             }
537 
538             if (mother != null) {
539                 rootSegment.MotherSegment = TraverseAncestors(mother, 90f, 1, CircleChartModel.CENTER_RAD, 90.0f, 1, -1);
540             }
541 
542             if (father != null) {
543                 rootSegment.FatherSegment = TraverseAncestors(father, 270.0f, 1, CircleChartModel.CENTER_RAD, 90.0f, 1, -1);
544             }
545         }
546 
SetSegmentParams(int index, GDMIndividualRecord rec, float rad, int groupIndex)547         private AncPersonSegment SetSegmentParams(int index, GDMIndividualRecord rec, float rad, int groupIndex)
548         {
549             if (index < 0 || index >= fSegments.Count) {
550                 return null;
551             }
552 
553             AncPersonSegment segment = (AncPersonSegment)fSegments[index];
554             segment.IRec = rec;
555             segment.Rad = rad;
556             segment.GroupIndex = groupIndex;
557             return segment;
558         }
559 
TraverseAncestors(GDMIndividualRecord iRec, float v, int gen, float rad, float ro, int prevSteps, int groupIndex)560         private AncPersonSegment TraverseAncestors(GDMIndividualRecord iRec, float v, int gen, float rad, float ro, int prevSteps, int groupIndex)
561         {
562             try
563             {
564                 fIndividualsCount++;
565 
566                 if (fGroupsMode && groupIndex == -1) {
567                     AncPersonSegment otherSegment = (AncPersonSegment)FindSegmentByRec(iRec);
568                     if (otherSegment != null) {
569                         fGroupCount++;
570                         groupIndex = fGroupCount;
571                         TraverseGroups(otherSegment, groupIndex);
572                     }
573                 }
574 
575                 int genSize = 1 << gen;
576                 float ang = (360.0f / genSize);
577 
578                 int idx = prevSteps + (int)(v / ang);
579                 AncPersonSegment segment = SetSegmentParams(idx, iRec, rad, groupIndex);
580 
581                 if (segment != null && gen < fVisibleGenerations)
582                 {
583                     float inRad = rad;
584                     float extRad = rad + fGenWidth;
585 
586                     segment.IntRad = inRad - 50;
587                     segment.ExtRad = extRad - 50;
588 
589                     GDMIndividualRecord father = null, mother = null;
590                     GDMFamilyRecord fam = fBase.Context.Tree.GetParentsFamily(iRec);
591                     if (fam != null && fBase.Context.IsRecordAccess(fam.Restriction)) {
592                         fBase.Context.Tree.GetSpouses(fam, out father, out mother);
593                     }
594 
595                     int ps = prevSteps + genSize;
596 
597                     if (father != null) {
598                         v -= (Math.Abs(ang - ro) / 2.0f);
599                         segment.FatherSegment = TraverseAncestors(father, v, gen + 1, rad + fGenWidth, ro / 2.0f, ps, groupIndex);
600                     }
601 
602                     if (mother != null) {
603                         v += (ang / 2.0f);
604                         segment.MotherSegment = TraverseAncestors(mother, v, gen + 1, rad + fGenWidth, ro / 2.0f, ps, groupIndex);
605                     }
606                 }
607 
608                 return segment;
609             }
610             catch
611             {
612                 return null;
613             }
614         }
615 
TraverseGroups(AncPersonSegment segment, int groupIndex)616         private static void TraverseGroups(AncPersonSegment segment, int groupIndex)
617         {
618             if (segment == null) return;
619 
620             segment.GroupIndex = groupIndex;
621             if (segment.FatherSegment != null) TraverseGroups(segment.FatherSegment, groupIndex);
622             if (segment.MotherSegment != null) TraverseGroups(segment.MotherSegment, groupIndex);
623         }
624 
DrawAncestors()625         public void DrawAncestors()
626         {
627             int numberOfSegments = fSegments.Count;
628             for (int i = 0; i < numberOfSegments; i++) {
629                 AncPersonSegment segment = (AncPersonSegment)fSegments[i];
630 
631                 bool draw = (!fOptions.HideEmptySegments || segment.IRec != null);
632 
633                 if (draw) {
634                     int brIndex;
635                     if (fGroupsMode) {
636                         brIndex = (segment.GroupIndex == -1) ? 11 : segment.GroupIndex;
637                     } else {
638                         brIndex = (segment.Gen == 0) ? CircleChartModel.CENTRAL_INDEX : segment.Gen - 1;
639                     }
640                     IBrush brush = (fSelected == segment) ? fDarkBrushes[brIndex] : fCircleBrushes[brIndex];
641 
642                     DrawSegment(segment, fPen, brush);
643                 }
644             }
645         }
646 
647         #endregion
648 
649         #region Descendants Circle
650 
BuildDescTree()651         public void BuildDescTree()
652         {
653             fSegments.Clear();
654             fIndividualsCount = 0;
655             if (fRootPerson == null) return;
656 
657             // traverse tree
658             DescPersonSegment rootSegment = TraverseDescendants(fRootPerson, 0);
659             if (rootSegment == null) return;
660 
661             const float inRad = CircleChartModel.CENTER_RAD - 50;
662             float stepAngle = (360.0f / rootSegment.TotalSubSegments);
663 
664             CalcDescendants(rootSegment, inRad, -90.0f, stepAngle);
665         }
666 
CalcDescendants(DescPersonSegment segment, float inRad, float startAngle, float stepAngle)667         private void CalcDescendants(DescPersonSegment segment, float inRad, float startAngle, float stepAngle)
668         {
669             float extRad;
670             if (segment.Gen == 0) {
671                 DefineSegment(segment, 0, 0, inRad, startAngle, 360.0f);
672 
673                 extRad = inRad;
674             } else {
675                 extRad = inRad + fGenWidth;
676 
677                 int size = Math.Max(1, segment.TotalSubSegments);
678                 float wedgeAngle = stepAngle * size;
679 
680                 // in Eto.Drawings 360 degrees for the segments
681                 // leads to a crash of drawing
682                 if (wedgeAngle == 360.0f) {
683                     wedgeAngle -= 0.1f;
684                 }
685 
686                 DefineSegment(segment, inRad + 50, inRad, extRad, startAngle, wedgeAngle);
687             }
688 
689             for (int i = 0; i < segment.ChildSegments.Count; i++) {
690                 DescPersonSegment childSegment = segment.ChildSegments[i];
691 
692                 CalcDescendants(childSegment, extRad, startAngle, stepAngle);
693 
694                 int steps = Math.Max(1, childSegment.TotalSubSegments);
695                 startAngle += stepAngle * steps;
696             }
697         }
698 
TraverseDescendants(GDMIndividualRecord iRec, int gen)699         private DescPersonSegment TraverseDescendants(GDMIndividualRecord iRec, int gen)
700         {
701             if (iRec == null) return null;
702 
703             try {
704                 fIndividualsCount++;
705 
706                 DescPersonSegment resultSegment = new DescPersonSegment(gen);
707                 resultSegment.IRec = iRec;
708                 fSegments.Add(resultSegment);
709 
710                 if (gen < fVisibleGenerations) {
711                     var tree = fBase.Context.Tree;
712                     int numberOfFamilyLinks = iRec.SpouseToFamilyLinks.Count;
713                     for (int j = 0; j < numberOfFamilyLinks; j++) {
714                         GDMFamilyRecord family = tree.GetPtrValue(iRec.SpouseToFamilyLinks[j]);
715                         if (!fBase.Context.IsRecordAccess(family.Restriction)) continue;
716 
717                         fBase.Context.ProcessFamily(family);
718 
719                         int numberOfChildren = family.Children.Count;
720                         for (int i = 0; i < numberOfChildren; i++) {
721                             GDMIndividualRecord child = tree.GetPtrValue(family.Children[i]);
722                             DescPersonSegment childSegment = TraverseDescendants(child, gen + 1);
723 
724                             int size = Math.Max(1, childSegment.TotalSubSegments);
725                             resultSegment.TotalSubSegments += size;
726 
727                             resultSegment.ChildSegments.Add(childSegment);
728                         }
729                     }
730                 }
731 
732                 return resultSegment;
733             } catch {
734                 return null;
735             }
736         }
737 
DrawDescendants()738         public void DrawDescendants()
739         {
740             int numberOfSegments = fSegments.Count;
741             for (int i = 0; i < numberOfSegments; i++) {
742                 DescPersonSegment segment = (DescPersonSegment)fSegments[i];
743                 if (segment.IRec == null) continue;
744 
745                 int brIndex = (segment.Gen == 0) ? CircleChartModel.CENTRAL_INDEX : segment.Gen - 1;
746                 IBrush brush = (fSelected == segment) ? fDarkBrushes[brIndex] : fCircleBrushes[brIndex];
747 
748                 DrawSegment(segment, fPen, brush);
749             }
750         }
751 
752         #endregion
753     }
754 }
755