kuni 3 tygodni temu
rodzic
commit
7a6e6a394a

+ 60
- 38
src/app/pages/danka-detail/danka-detail.html Wyświetl plik

@@ -320,7 +320,8 @@
320 320
             <div class="family-tree-diagram">
321 321
               <div class="family-tree-svg-container">
322 322
 
323
-                <svg #familyTreeSvg width="1000" height="1000">
323
+                <svg #familyTreeSvg width="100%" height="100%" [attr.viewBox]="viewBox"
324
+                  preserveAspectRatio="xMidYMid meet">
324 325
 
325 326
                   @for (
326 327
                   layout of unitLayouts;
@@ -332,27 +333,19 @@
332 333
                   track child.unit.id
333 334
                   ) {
334 335
 
335
-                  @if (
336
-                  getUnitLayout(
337
-                  child.unit.id
338
-                  );
339
-                  as childLayout
340
-                  ) {
336
+                  @if (getUnitLayout(child.unit.id); as childLayout) {
341 337
 
342 338
                   <!-- 親から中間点 -->
343
-
344 339
                   <line [attr.x1]="getCenterX(layout)" [attr.y1]="getBottomCenterY(layout)"
345 340
                     [attr.x2]="getCenterX(layout)" [attr.y2]="getMiddleY(layout, childLayout)" stroke="black"
346 341
                     stroke-width="2" />
347 342
 
348 343
                   <!-- 横線 -->
349
-
350 344
                   <line [attr.x1]="getCenterX(layout)" [attr.y1]="getMiddleY(layout, childLayout)"
351 345
                     [attr.x2]="getCenterX(childLayout)" [attr.y2]="getMiddleY(layout, childLayout)" stroke="black"
352 346
                     stroke-width="2" />
353 347
 
354 348
                   <!-- 子へ -->
355
-
356 349
                   <line [attr.x1]="getCenterX(childLayout)" [attr.y1]="getMiddleY(layout, childLayout)"
357 350
                     [attr.x2]="getCenterX(childLayout)" [attr.y2]="getTopCenterY(childLayout)" stroke="black"
358 351
                     stroke-width="2" />
@@ -363,68 +356,97 @@
363 356
 
364 357
                   }
365 358
 
366
-                  @for (
367
-                  layout of unitLayouts;
368
-                  track layout.node.unit.id
369
-                  ) {
359
+                  @for (layout of unitLayouts; track layout.node.unit.id) {
370 360
 
371 361
                   <!-- 夫婦線 -->
372
-
373
-                  @if (
374
-                  layout.node.unit.husband &&
375
-                  layout.node.unit.wife
376
-                  ) {
377
-
362
+                  @if (layout.node.unit.husband && layout.node.unit.wife) {
378 363
                   <line [attr.x1]="layout.x + PERSON_WIDTH" [attr.y1]="layout.y + PERSON_HEIGHT / 2"
379 364
                     [attr.x2]="layout.x + PERSON_WIDTH + SPOUSE_GAP" [attr.y2]="layout.y + PERSON_HEIGHT / 2"
380 365
                     stroke="red" stroke-width="2" />
381
-
382 366
                   }
383 367
 
368
+                  <!-- ========================= -->
384 369
                   <!-- 夫 -->
385
-
386
-                  @if (
387
-                  layout.node.unit.husband
388
-                  ) {
370
+                  <!-- ========================= -->
371
+                  @if (layout.node.unit.husband) {
389 372
 
390 373
                   <rect [attr.x]="getHusbandX(layout)" [attr.y]="layout.y" [attr.width]="PERSON_WIDTH"
391 374
                     [attr.height]="PERSON_HEIGHT" fill="#dbeafe"
392 375
                     [attr.stroke]="selectedFamily?.id === layout.node.unit.husband?.id ? '#2563eb' : 'black'"
393 376
                     class="family-node" (click)="selectFamily(layout.node.unit.husband!)" />
394 377
 
395
-                  <text [attr.x]="getHusbandTextX(layout)" [attr.y]="layout.y + 20"
396
-                    style="writing-mode: vertical-rl;"
397
-                    [attr.stroke-width]="selectedFamily?.id === layout.node.unit.husband?.id ? 3 : 1"
398
-                    class="family-text" (click)="selectFamily(layout.node.unit.husband!)">
399
-
378
+                  <!-- 右:名前(基準) -->
379
+                  <text [attr.x]="getHusbandTextX(layout)" [attr.y]="layout.y + 15"
380
+                    style="writing-mode: vertical-rl; text-orientation: upright;" class="family-text"
381
+                    (click)="selectFamily(layout.node.unit.husband!)">
400 382
                     {{ layout.node.unit.husband.name }}
383
+                  </text>
401 384
 
385
+                  <!-- 中央:没年月日(名前の左) -->
386
+                  @if (getDeathWareki(layout.node.unit.husband)) {
387
+
388
+                  <text [attr.x]="getHusbandTextX(layout) - 26" [attr.y]="getDeathTextY(layout, getDeathWareki(layout.node.unit.husband))"
389
+                    dominant-baseline="text-after-edge" style="writing-mode: vertical-rl; text-orientation: upright;"
390
+                    [attr.font-size]="DEATH_FONT_SIZE">
391
+                    {{ getDeathWareki(layout.node.unit.husband) }}
402 392
                   </text>
403 393
 
404 394
                   }
405 395
 
406
-                  <!-- 妻 -->
396
+                  <!-- 左:享年 -->
397
+                  @if (getAgeAtDeathText(layout.node.unit.husband)) {
407 398
 
408
-                  @if (
409
-                  layout.node.unit.wife
410
-                  ) {
399
+                  <text [attr.x]="getHusbandTextX(layout) - 40" [attr.y]="getDeathTextY(layout, getAgeAtDeathText(layout.node.unit.husband))"
400
+                    dominant-baseline="text-after-edge" style="writing-mode: vertical-rl; text-orientation: upright;"
401
+                    [attr.font-size]="DEATH_FONT_SIZE">
402
+                    {{ getAgeAtDeathText(layout.node.unit.husband) }}
403
+                  </text>
404
+
405
+                  }
406
+
407
+                  }
408
+
409
+                  <!-- ========================= -->
410
+                  <!-- 妻 -->
411
+                  <!-- ========================= -->
412
+                  @if (layout.node.unit.wife) {
411 413
 
412 414
                   <rect [attr.x]="getWifeX(layout)" [attr.y]="layout.y" [attr.width]="PERSON_WIDTH"
413 415
                     [attr.height]="PERSON_HEIGHT" fill="#fde2e2"
414 416
                     [attr.stroke]="selectedFamily?.id === layout.node.unit.wife?.id ? '#dc2626' : 'black'"
415 417
                     class="family-node" (click)="selectFamily(layout.node.unit.wife!)" />
416 418
 
417
-                  <text [attr.x]="getWifeTextX(layout)" [attr.y]="layout.y + 20"
418
-                    style="writing-mode: vertical-rl;"
419
-                    [attr.stroke-width]="selectedFamily?.id === layout.node.unit.wife?.id ? 3 : 1" class="family-text"
419
+                  <!-- 右:名前 -->
420
+                  <text [attr.x]="getWifeTextX(layout)" [attr.y]="layout.y + 15"
421
+                    style="writing-mode: vertical-rl; text-orientation: upright;" class="family-text"
420 422
                     (click)="selectFamily(layout.node.unit.wife!)">
421
-
422 423
                     {{ layout.node.unit.wife.name }}
424
+                  </text>
425
+
426
+                  <!-- 中央:没年月日 -->
427
+                  @if (getDeathWareki(layout.node.unit.wife)) {
423 428
 
429
+                  <text [attr.x]="getWifeTextX(layout) - 26" [attr.y]="getDeathTextY(layout, getDeathWareki(layout.node.unit.wife))"
430
+                    dominant-baseline="text-after-edge" style="writing-mode: vertical-rl; text-orientation: upright;"
431
+                    [attr.font-size]="DEATH_FONT_SIZE">
432
+                    {{ getDeathWareki(layout.node.unit.wife) }}
424 433
                   </text>
425 434
 
426 435
                   }
427 436
 
437
+                  <!-- 左:享年 -->
438
+                  @if (getAgeAtDeathText(layout.node.unit.wife)) {
439
+
440
+                  <text [attr.x]="getWifeTextX(layout) - 40" [attr.y]="getDeathTextY(layout, getAgeAtDeathText(layout.node.unit.wife))"
441
+                    dominant-baseline="text-after-edge" style="writing-mode: vertical-rl; text-orientation: upright;"
442
+                    [attr.font-size]="DEATH_FONT_SIZE">
443
+                    {{ getAgeAtDeathText(layout.node.unit.wife) }}
444
+                  </text>
445
+
446
+                  }
447
+
448
+                  }
449
+
428 450
                   }
429 451
 
430 452
                 </svg>

+ 10
- 0
src/app/pages/danka-detail/danka-detail.scss Wyświetl plik

@@ -1120,4 +1120,14 @@
1120 1120
 
1121 1121
   cursor: pointer;
1122 1122
 
1123
+}
1124
+
1125
+.family-tree-svg-container svg {
1126
+  width: 100%;
1127
+  height: 100%;
1128
+}
1129
+
1130
+.family-text {
1131
+  writing-mode: vertical-rl;
1132
+  text-orientation: upright;
1123 1133
 }

+ 145
- 13
src/app/pages/danka-detail/danka-detail.ts Wyświetl plik

@@ -55,6 +55,8 @@ export class DankaDetail implements AfterViewInit {
55 55
   layoutNodeMap = new Map<string, LayoutNode>();
56 56
   unitLayouts: FamilyUnitLayout[] = [];
57 57
   unitLayoutMap = new Map<string, FamilyUnitLayout>();
58
+  deathDateMap = new Map<string, Kakocho>();
59
+  private kakochoByNameMap = new Map<string, Kakocho>();
58 60
 
59 61
   selectedTab: 'basic' | 'family' | 'kakocho' | 'familyTree' = 'basic';
60 62
   selectedFamily: Family | undefined = undefined;
@@ -63,9 +65,13 @@ export class DankaDetail implements AfterViewInit {
63 65
   familyTreeSvg?: ElementRef<SVGSVGElement>;
64 66
   private panZoomInstance: any;
65 67
 
66
-  readonly PERSON_WIDTH = 70;
68
+  readonly PERSON_WIDTH = 90;
67 69
   readonly PERSON_HEIGHT = 140;
68
-  readonly SPOUSE_GAP = 20;
70
+  readonly SPOUSE_GAP = 30;
71
+
72
+  viewBox = '0 0 6000 6000';
73
+  readonly DEATH_FONT_SIZE = 10;
74
+  readonly DEATH_LINE_HEIGHT = 10; // 少し余白込み
69 75
 
70 76
   constructor(
71 77
     private dankaService: DankaService,
@@ -140,6 +146,29 @@ export class DankaDetail implements AfterViewInit {
140 146
 
141 147
       this.rebuildUnitLayoutMap();
142 148
 
149
+      this.calculateViewBox();
150
+
151
+      this.kakocholist.forEach(kakocho => {
152
+
153
+        if (kakocho.familyId) {
154
+
155
+          this.deathDateMap.set(
156
+            kakocho.familyId,
157
+            kakocho
158
+          );
159
+
160
+        }
161
+
162
+      });
163
+
164
+      this.kakocholist.forEach(k => {
165
+
166
+        const key = this.normalizeName(k.name) + '_' + k.dankaId;
167
+
168
+        this.kakochoByNameMap.set(key, k);
169
+
170
+      });
171
+
143 172
     }
144 173
   }
145 174
   ngAfterViewInit(): void {
@@ -147,22 +176,16 @@ export class DankaDetail implements AfterViewInit {
147 176
       return;
148 177
     }
149 178
 
150
-    svgPanZoom(
179
+    this.panZoomInstance = svgPanZoom(
151 180
       this.familyTreeSvg.nativeElement,
152 181
       {
153
-        zoomEnabled: true,
154
-        panEnabled: true,
155
-
156 182
         fit: true,
157 183
         center: true,
184
+        zoomEnabled: true,
185
+        panEnabled: true,
158 186
 
159
-        minZoom: 0.0001,
160
-        maxZoom: 500,
161
-
162
-        mouseWheelZoomEnabled: true,
163
-        dblClickZoomEnabled: true,
164
-
165
-        zoomScaleSensitivity: 0.15
187
+        minZoom: 0.1,
188
+        maxZoom: 50
166 189
       }
167 190
     );
168 191
   }
@@ -561,7 +584,116 @@ export class DankaDetail implements AfterViewInit {
561 584
       this.getWifeX(layout)
562 585
       + this.PERSON_WIDTH / 2
563 586
     );
587
+  }
588
+
589
+  private calculateViewBox(): void {
590
+
591
+    if (this.unitLayouts.length === 0) {
592
+      return;
593
+    }
594
+
595
+    const minX = Math.min(
596
+      ...this.unitLayouts.map(x => x.x)
597
+    );
598
+
599
+    const minY = Math.min(
600
+      ...this.unitLayouts.map(x => x.y)
601
+    );
602
+
603
+    const maxX = Math.max(
604
+      ...this.unitLayouts.map(
605
+        x => x.x + this.PERSON_WIDTH * 2 + this.SPOUSE_GAP
606
+      )
607
+    );
608
+
609
+    const maxY = Math.max(
610
+      ...this.unitLayouts.map(
611
+        x => x.y + this.PERSON_HEIGHT
612
+      )
613
+    );
614
+
615
+    const padding = 100;
616
+
617
+    this.viewBox =
618
+      `${minX - padding}
619
+     ${minY - padding}
620
+     ${maxX - minX + padding * 2}
621
+     ${maxY - minY + padding * 2}`;
622
+  }
564 623
 
624
+  getKakochoByFamilyId(
625
+    familyId: string
626
+  ): Kakocho | undefined {
627
+
628
+    return this.deathDateMap.get(
629
+      familyId
630
+    );
631
+
632
+  }
633
+
634
+  getKakochoByFamily(family: Family): Kakocho | undefined {
635
+
636
+    const key =
637
+      this.normalizeName(family.name) + '_' + family.dankaId;
638
+
639
+    return this.kakochoByNameMap.get(key);
640
+
641
+  }
642
+
643
+  getDeathWareki(family: Family): string {
644
+
645
+    const kakocho = this.getKakochoByFamily(family);
646
+
647
+    if (!kakocho?.deathDate) return '';
648
+
649
+    const date = new Date(kakocho.deathDate);
650
+
651
+    const wareki = new Intl.DateTimeFormat('ja-JP-u-ca-japanese', {
652
+      era: 'long',
653
+      year: 'numeric',
654
+    }).format(date);
655
+
656
+    return `${wareki}${date.getMonth() + 1}月${date.getDate()}日没`;
657
+  }
658
+
659
+  getAgeAtDeathText(family: Family): string {
660
+
661
+    const kakocho = this.getKakochoByFamily(family);
662
+
663
+    if (!kakocho?.ageAtDeath) return '';
664
+
665
+    return `享年${kakocho.ageAtDeath}歳`;
666
+  }
667
+
668
+  getDeathDate(family: Family): string {
669
+
670
+    const kakocho = this.getKakochoByFamily(family);
671
+
672
+    return kakocho?.deathDate ?? '';
673
+
674
+  }
675
+
676
+  /**
677
+   * 縦書きテキストの高さを概算する
678
+   */
679
+  getVerticalTextHeight(text: string | null | undefined): number {
680
+    if (!text) return 0;
681
+
682
+    // 縦書きは基本「1文字=1行」
683
+    return text.length * this.DEATH_LINE_HEIGHT;
684
+  }
685
+
686
+  /**
687
+   * テキストをカード下に揃えるためのY座標
688
+   */
689
+  getDeathTextY(layout: FamilyUnitLayout, text: string | null | undefined): number {
690
+
691
+    const height = this.getVerticalTextHeight(text);
692
+
693
+    return (
694
+      layout.y +
695
+      this.PERSON_HEIGHT - 5 - height
696
+    );
565 697
   }
566 698
 
567 699
 }

Ładowanie…
Anuluj
Zapisz