Aucune description
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

family-tree-layout.ts 2.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. import { Injectable } from '@angular/core';
  2. import {
  3. FamilyTreeNode
  4. } from './family-tree-builder';
  5. import { LayoutNode } from '../models/layoutnode';
  6. import { Family } from '../models/family';
  7. @Injectable({
  8. providedIn: 'root'
  9. })
  10. export class FamilyTreeLayoutService {
  11. private readonly NODE_WIDTH = 140;
  12. private readonly NODE_HEIGHT = 70;
  13. private readonly HORIZONTAL_GAP = 200;
  14. private readonly VERTICAL_GAP = 200;
  15. private readonly SPOUSE_GAP = 180;
  16. private processedCouples = new Set<string>();
  17. private currentX = 0;
  18. buildLayout(
  19. roots: FamilyTreeNode[]
  20. ): LayoutNode[] {
  21. this.currentX = 0;
  22. this.processedCouples.clear();
  23. const result: LayoutNode[] = [];
  24. const visited = new Set<string>();
  25. roots.forEach(root => {
  26. this.layoutNode(
  27. root,
  28. 0,
  29. result,
  30. visited
  31. );
  32. this.currentX +=
  33. this.HORIZONTAL_GAP;
  34. });
  35. return result;
  36. }
  37. private layoutNode(
  38. node: FamilyTreeNode,
  39. depth: number,
  40. result: LayoutNode[],
  41. visited: Set<string>
  42. ): number {
  43. if (visited.has(node.family.id)) {
  44. const existing = result.find(
  45. x => x.familyId === node.family.id
  46. );
  47. return existing?.x ?? 0;
  48. }
  49. visited.add(node.family.id);
  50. const children = node.children;
  51. let x: number;
  52. if (children.length === 0) {
  53. x = this.currentX;
  54. this.currentX +=
  55. this.HORIZONTAL_GAP;
  56. } else {
  57. const childXs =
  58. children.map(child =>
  59. this.layoutNode(
  60. child,
  61. depth + 1,
  62. result,
  63. visited
  64. )
  65. );
  66. x =
  67. (Math.min(...childXs) +
  68. Math.max(...childXs))
  69. / 2;
  70. }
  71. const y =
  72. depth *
  73. this.VERTICAL_GAP;
  74. let spouseX: number | undefined;
  75. let spouse: Family | undefined;
  76. if (node.spouses.length > 0) {
  77. const spouseNode =
  78. node.spouses[0];
  79. const coupleKey =
  80. [
  81. node.family.id,
  82. spouseNode.family.id
  83. ]
  84. .sort()
  85. .join('-');
  86. if (
  87. !this.processedCouples.has(
  88. coupleKey
  89. )
  90. ) {
  91. this.processedCouples.add(
  92. coupleKey
  93. );
  94. spouse =
  95. spouseNode.family;
  96. spouseX =
  97. x + this.SPOUSE_GAP;
  98. }
  99. }
  100. result.push({
  101. familyId: node.family.id,
  102. family: node.family,
  103. spouse,
  104. depth,
  105. x,
  106. y,
  107. spouseX,
  108. width: this.NODE_WIDTH,
  109. height: this.NODE_HEIGHT
  110. });
  111. return x;
  112. }
  113. }