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