1<?php 2 3declare(strict_types=1); 4 5use Fisharebest\Webtrees\Carbon; 6use Fisharebest\Webtrees\Date; 7use Fisharebest\Webtrees\Gedcom; 8use Fisharebest\Webtrees\GedcomTag; 9use Fisharebest\Webtrees\I18N; 10use Fisharebest\Webtrees\Individual; 11use Fisharebest\Webtrees\View; 12use Ramsey\Uuid\Uuid; 13 14$table_id = 'table-fam-' . Uuid::uuid4()->toString(); // lists requires a unique ID in case there are multiple lists per page 15 16$hundred_years_ago = Carbon::now()->subYears(100)->julianDay(); 17 18?> 19 20<?php View::push('javascript') ?> 21<script> 22 23$("#<?= e($table_id) ?>").dataTable({ 24 dom: '<"H"<"filtersH_<?= e($table_id) ?>"><"dt-clear">pf<"dt-clear">irl>t<"F"pl<"dt-clear"><"filtersF_<?= e($table_id) ?>">>', 25 autoWidth: false, 26 processing: true, 27 retrieve: true, 28 columns: [ 29 /* Given names */ { type: "text" }, 30 /* Surnames */ { type: "text" }, 31 /* Age */ { type: "num" }, 32 /* Given names */ { type: "text" }, 33 /* Surnames */ { type: "text" }, 34 /* Age */ { type: "num" }, 35 /* Marriage date */ { type: "num" }, 36 /* Anniversary */ { type: "num" }, 37 /* Marriage place */ { type: "text" }, 38 /* Children */ { type: "num" }, 39 /* Last change */ { visible: <?= json_encode((bool) $tree->getPreference('SHOW_LAST_CHANGE')) ?> }, 40 /* Filter marriage */ { sortable: false }, 41 /* Filter alive/dead */ { sortable: false }, 42 /* Filter tree */ { sortable: false } 43 ], 44 sorting: [ 45 [1, "asc"] 46 ] 47}); 48 49$("#<?= e($table_id) ?>") 50 /* Hide/show parents */ 51 .on("click", ".btn-toggle-parents", function() { 52 $(this).toggleClass("ui-state-active"); 53 $(".parents", $(this).closest("table").DataTable().rows().nodes()).slideToggle(); 54 }) 55 /* Hide/show statistics */ 56 .on("click", ".btn-toggle-statistics", function() { 57 $(this).toggleClass("ui-state-active"); 58 $("#family-charts-<?= e($table_id) ?>").slideToggle({ 59 complete: function () { 60 // Trigger resize to redraw the chart 61 $('div[id^="google-chart-"]').resize(); 62 } 63 }); 64 }) 65 /* Filter buttons in table header */ 66 .on("click", "button[data-filter-column]", function() { 67 var btn = $(this); 68 // De-activate the other buttons in this button group 69 btn.siblings().removeClass("active"); 70 // Apply (or clear) this filter 71 var col = $("#<?= e($table_id) ?>").DataTable().column(btn.data("filter-column")); 72 if (btn.hasClass("active")) { 73 col.search("").draw(); 74 } else { 75 col.search(btn.data("filter-value")).draw(); 76 } 77 }); 78 79</script> 80<?php View::endpush() ?> 81 82<?php 83$max_age = (int) $tree->getPreference('MAX_ALIVE_AGE'); 84 85// init chart data 86$marr_by_age = []; 87for ($age = 0; $age <= $max_age; $age++) { 88 $marr_by_age[$age]['M'] = 0; 89 $marr_by_age[$age]['F'] = 0; 90 $marr_by_age[$age]['U'] = 0; 91} 92$birt_by_decade = []; 93$marr_by_decade = []; 94for ($year = 1400; $year < 2050; $year += 10) { 95 $birt_by_decade[$year]['M'] = 0; 96 $birt_by_decade[$year]['F'] = 0; 97 $birt_by_decade[$year]['U'] = 0; 98 $marr_by_decade[$year]['M'] = 0; 99 $marr_by_decade[$year]['F'] = 0; 100 $marr_by_decade[$year]['U'] = 0; 101} 102 103$birthData = [ 104 [ 105 [ 106 'label' => I18N::translate('Century'), 107 'type' => 'date', 108 ], [ 109 'label' => I18N::translate('Males'), 110 'type' => 'number', 111 ], [ 112 'label' => I18N::translate('Females'), 113 'type' => 'number', 114 ], 115 ] 116]; 117 118$marriageData = [ 119 [ 120 [ 121 'label' => I18N::translate('Century'), 122 'type' => 'date', 123 ], [ 124 'label' => I18N::translate('Males'), 125 'type' => 'number', 126 ], [ 127 'label' => I18N::translate('Females'), 128 'type' => 'number', 129 ], 130 ] 131]; 132 133$marriageAgeData = [ 134 [ 135 I18N::translate('Age'), 136 I18N::translate('Males'), 137 I18N::translate('Females'), 138 I18N::translate('Average age'), 139 ] 140]; 141 142?> 143 144<div class="fam-list"> 145 <table id="<?= e($table_id) ?>" class="table-bordered table-sm" 146 <?= view('lists/datatables-attributes') ?> 147 > 148 <thead> 149 <tr> 150 <th colspan="14"> 151 <div class="btn-toolbar d-flex justify-content-between mb-2"> 152 <div class="btn-group" data-toggle="buttons"> 153 <button 154 class="btn btn-secondary" 155 data-filter-column="12" 156 data-filter-value="N" 157 title="' . I18N::translate('Show individuals who are alive or couples where both partners are alive.') ?>" 158 > 159 <?= I18N::translate('Both alive') ?> 160 </button> 161 <button 162 class="btn btn-secondary" 163 data-filter-column="12" 164 data-filter-value="W" 165 title="<?= I18N::translate('Show couples where only the female partner is dead.') ?>" 166 > 167 <?= I18N::translate('Widower') ?> 168 </button> 169 <button 170 class="btn btn-secondary" 171 data-filter-column="12" 172 data-filter-value="H" 173 title="<?= I18N::translate('Show couples where only the male partner is dead.') ?>" 174 > 175 <?= I18N::translate('Widow') ?> 176 </button> 177 <button 178 class="btn btn-secondary" 179 data-filter-column="12" 180 data-filter-value="Y" 181 title="<?= I18N::translate('Show individuals who are dead or couples where both partners are dead.') ?>" 182 > 183 <?= I18N::translate('Both dead') ?> 184 </button> 185 </div> 186 <div class="btn-group" data-toggle="buttons"> 187 <button 188 class="btn btn-secondary" 189 data-filter-column="13" 190 data-filter-value="R" 191 title="<?= I18N::translate('Show “roots” couples or individuals. These individuals may also be called “patriarchs”. They are individuals who have no parents recorded in the database.') ?>" 192 > 193 <?= I18N::translate('Roots') ?> 194 </button> 195 <button 196 class="btn btn-secondary" 197 data-filter-column="13" 198 data-filter-value="L" 199 title="<?= I18N::translate('Show “leaves” couples or individuals. These are individuals who are alive but have no children recorded in the database.') ?>" 200 > 201 <?= I18N::translate('Leaves') ?> 202 </button> 203 </div> 204 <div class="btn-group" data-toggle="buttons"> 205 <button 206 class="btn btn-secondary" 207 data-filter-column="11" 208 data-filter-value="U" 209 title="<?= I18N::translate('Show couples with an unknown marriage date.') ?>" 210 > 211 <?= I18N::translate('Marriage') ?> 212 </button> 213 <button 214 class="btn btn-secondary" 215 data-filter-column="11" 216 data-filter-value="YES" 217 title="<?= I18N::translate('Show couples who married more than 100 years ago.') ?>" 218 > 219 <?= I18N::translate('Marriage') ?>>100 220 </button> 221 <button 222 class="btn btn-secondary" 223 data-filter-column="11" 224 data-filter-value="Y100" 225 title="<?= I18N::translate('Show couples who married within the last 100 years.') ?>" 226 > 227 <?= I18N::translate('Marriage') ?><=100 228 </button> 229 <button 230 class="btn btn-secondary" 231 data-filter-column="11" 232 data-filter-value="D" 233 title="<?= I18N::translate('Show divorced couples.') ?>" 234 > 235 <?= I18N::translate('Divorce') ?> 236 </button> 237 <button 238 class="btn btn-secondary" 239 data-filter-column="11" 240 data-filter-value="M" 241 title="<?= I18N::translate('Show couples where either partner married more than once.') ?>" 242 > 243 <?= I18N::translate('Multiple marriages') ?> 244 </button> 245 </div> 246 </div> 247 </th> 248 </tr> 249 <tr> 250 <th><?= I18N::translate('Given names') ?></th> 251 <th><?= I18N::translate('Surname') ?></th> 252 <th><?= I18N::translate('Age') ?></th> 253 <th><?= I18N::translate('Given names') ?></th> 254 <th><?= I18N::translate('Surname') ?></th> 255 <th><?= I18N::translate('Age') ?></th> 256 <th><?= I18N::translate('Marriage') ?></th> 257 <th> 258 <span title="<?= I18N::translate('Anniversary') ?>"> 259 <?= view('icons/anniversary') ?> 260 </span> 261 </th> 262 <th><?= I18N::translate('Place') ?></th> 263 <th><i class="icon-children" title="<?= I18N::translate('Children') ?>"></i></th> 264 <th><?= I18N::translate('Last change') ?></th> 265 <th hidden></th> 266 <th hidden></th> 267 <th hidden></th> 268 </tr> 269 </thead> 270 271 <tfoot> 272 <tr> 273 <th colspan="14"> 274 <div class="btn-toolbar"> 275 <div class="btn-group"> 276 <button class="ui-state-default btn-toggle-parents"> 277 <?= I18N::translate('Show parents') ?> 278 </button> 279 <button class="ui-state-default btn-toggle-statistics"> 280 <?= I18N::translate('Show statistics charts') ?> 281 </button> 282 </div> 283 </div> 284 </th> 285 </tr> 286 </tfoot> 287 <tbody> 288 289 <?php foreach ($families as $family) : ?> 290 <?php $husb = $family->husband() ?? new Individual('H', '0 @H@ INDI', null, $family->tree()) ?> 291 <?php $wife = $family->wife() ?? new Individual('W', '0 @W@ INDI', null, $family->tree()) ?> 292 293 <tr class="<?= $family->isPendingDeletion() ? 'wt-old' : ($family->isPendingAddition() ? 'wt-new' : '') ?>"> 294 <!-- Husband name --> 295 <td colspan="2" data-sort="<?= e(str_replace([',', '@P.N.', '@N.N.'], 'AAAA', implode(',', array_reverse(explode(',', $husb->sortName()))))) ?>"> 296 <?php foreach ($husb->getAllNames() as $num => $name) : ?> 297 <?php if ($name['type'] !== '_MARNM' || $num == $husb->getPrimaryName()) : ?> 298 <a title="<?= $name['type'] === 'NAME' ? '' : GedcomTag::getLabel($name['type'], $husb) ?>" href="<?= e($family->url()) ?>" class="<?= $num === $husb->getPrimaryName() ? 'name2' : '' ?>"> 299 <?= $name['full'] ?> 300 </a> 301 <?php if ($num === $husb->getPrimaryName()) : ?> 302 <small><?= view('icons/sex', ['sex' => $husb->sex()]) ?></small> 303 <?php endif ?> 304 <br> 305 <?php endif ?> 306 <?php endforeach ?> 307 <?= $husb->getPrimaryParentsNames('parents details1', 'none') ?> 308 </td> 309 310 <td hidden data-sort="<?= e(str_replace([',', '@P.N.', '@N.N.'], 'AAAA', $husb->sortName())) ?>"></td> 311 312 <!-- Husband age --> 313 <?php 314 $mdate = $family->getMarriageDate(); 315 $hdate = $husb->getBirthDate(); 316 if ($hdate->isOK() && $mdate->isOK()) { 317 if ($hdate->gregorianYear() >= 1550 && $hdate->gregorianYear() < 2030) { 318 ++$birt_by_decade[(int) ($hdate->gregorianYear() / 10) * 10][$husb->sex()]; 319 } 320 $hage = Date::getAgeYears($hdate, $mdate); 321 if ($hage >= 0 && $hage <= $max_age) { 322 ++$marr_by_age[$hage][$husb->sex()]; 323 } 324 } 325 ?> 326 <td class="text-center" data-sort="<?= Date::getAgeDays($hdate, $mdate) ?>"> 327 <?= Date::getAge($hdate, $mdate) ?> 328 </td> 329 330 <!-- Wife name --> 331 <td colspan="2" data-sort="<?= e(str_replace([',', '@P.N.', '@N.N.'], 'AAAA', implode(',', array_reverse(explode(',', $wife->sortName()))))) ?>"> 332 <?php foreach ($wife->getAllNames() as $num => $name) : ?> 333 <?php if ($name['type'] !== '_MARNM' || $num == $wife->getPrimaryName()) : ?> 334 <a title="<?= $name['type'] === 'NAME' ? '' : GedcomTag::getLabel($name['type'], $wife) ?>" href="<?= e($family->url()) ?>" class="<?= $num === $wife->getPrimaryName() ? 'name2' : '' ?>"> 335 <?= $name['full'] ?> 336 </a> 337 <?php if ($num === $wife->getPrimaryName()) : ?> 338 <small><?= view('icons/sex', ['sex' => $wife->sex()]) ?></small> 339 <?php endif ?> 340 <br> 341 <?php endif ?> 342 <?php endforeach ?> 343 <?= $wife->getPrimaryParentsNames('parents details1', 'none') ?> 344 </td> 345 346 <td hidden data-sort="<?= e(str_replace([',', '@P.N.', '@N.N.'], 'AAAA', $wife->sortName())) ?>"></td> 347 348 <!-- Wife age --> 349 <?php 350 $wdate = $wife->getBirthDate(); 351 if ($wdate->isOK() && $mdate->isOK()) { 352 if ($wdate->gregorianYear() >= 1550 && $wdate->gregorianYear() < 2030) { 353 ++$birt_by_decade[(int) ($wdate->gregorianYear() / 10) * 10][$wife->sex()]; 354 } 355 $wage = Date::getAgeYears($wdate, $mdate); 356 if ($wage >= 0 && $wage <= $max_age) { 357 ++$marr_by_age[$wage][$wife->sex()]; 358 } 359 } 360 ?> 361 362 <td class="text-center" data-sort="<?= Date::getAgeDays($wdate, $mdate) ?>"> 363 <?= Date::getAge($wdate, $mdate) ?> 364 </td> 365 366 <!-- Marriage date --> 367 <td data-sort="<?= $family->getMarriageDate()->julianDay() ?>"> 368 <?php if ($marriage_dates = $family->getAllMarriageDates()) : ?> 369 <?php foreach ($marriage_dates as $n => $marriage_date) : ?> 370 <div><?= $marriage_date->display(true) ?></div> 371 <?php endforeach ?> 372 <?php if ($marriage_dates[0]->gregorianYear() >= 1550 && $marriage_dates[0]->gregorianYear() < 2030) : ?> 373 <?php 374 ++$marr_by_decade[(int) ($marriage_dates[0]->gregorianYear() / 10) * 10][$husb->sex()]; 375 ++$marr_by_decade[(int) ($marriage_dates[0]->gregorianYear() / 10) * 10][$wife->sex()]; 376 ?> 377 <?php endif ?> 378 <?php elseif ($family->facts(['_NMR'])->isNotEmpty()) : ?> 379 <?= I18N::translate('no') ?> 380 <?php elseif ($family->facts(['MARR'])->isNotEmpty()) : ?> 381 <?= I18N::translate('yes') ?> 382 <?php endif ?> 383 </td> 384 385 <!-- Marriage anniversary --> 386 <td class="text-center" data-sort="<?= - $family->getMarriageDate()->julianDay() ?>"> 387 <?= Date::getAge($family->getMarriageDate(), null) ?> 388 </td> 389 390 <!-- Marriage place --> 391 <td> 392 <?php foreach ($family->getAllMarriagePlaces() as $n => $marriage_place) : ?> 393 <?= $marriage_place->shortName(true) ?> 394 <br> 395 <?php endforeach ?> 396 </td> 397 398 <!-- Number of children --> 399 <td class="text-center" data-sort="<?= $family->numberOfChildren() ?>"> 400 <?= I18N::number($family->numberOfChildren()) ?> 401 </td> 402 403 <!-- Last change --> 404 <td data-sort="<?= $family->lastChangeTimestamp()->unix() ?>"> 405 <?= view('components/datetime', ['timestamp' => $family->lastChangeTimestamp()]) ?> 406 </td> 407 408 <!-- Filter by marriage date --> 409 <td hidden> 410 <?php if (!$family->canShow() || !$mdate->isOK()) : ?> 411 U 412 <?php elseif ($mdate->maximumJulianDay() > $hundred_years_ago) : ?> 413 Y100 414 <?php else : ?> 415 YES 416 <?php endif ?> 417 <?php if ($family->facts(Gedcom::DIVORCE_EVENTS)->isNotEmpty()) : ?> 418 D 419 <?php endif ?> 420 <?php if (count($husb->spouseFamilies()) > 1 || count($wife->spouseFamilies()) > 1) : ?> 421 M 422 <?php endif ?> 423 </td> 424 425 <!-- Filter by alive/dead --> 426 <td hidden> 427 <?php if ($husb->isDead() && $wife->isDead()) : ?> 428 Y 429 <?php endif ?> 430 <?php if ($husb->isDead() && !$wife->isDead()) : ?> 431 <?php if ($wife->sex() === 'F') : ?> 432 H 433 <?php endif ?> 434 <?php if ($wife->sex() === 'M') : ?> 435 W 436 <?php endif ?> 437 <?php endif ?> 438 <?php if (!$husb->isDead() && $wife->isDead()) : ?> 439 <?php if ($husb->sex() === 'M') : ?> 440 W 441 <?php endif ?> 442 <?php if ($husb->sex() === 'F') : ?> 443 H 444 <?php endif ?> 445 <?php endif ?> 446 <?php if (!$husb->isDead() && !$wife->isDead()) : ?> 447 N 448 <?php endif ?> 449 </td> 450 451 <!-- Filter by roots/leaves --> 452 <td hidden> 453 <?php if (!$husb->childFamilies() && !$wife->childFamilies()) : ?> 454 R 455 <?php elseif (!$husb->isDead() && !$wife->isDead() && $family->numberOfChildren() === 0) : ?> 456 L 457 <?php endif ?> 458 </td> 459 </tr> 460 <?php endforeach ?> 461 </tbody> 462 </table> 463</div> 464 465<div id="family-charts-<?= e($table_id) ?>" style="display: none;"> 466 <div class="mb-3"> 467 <div class="card-deck"> 468 <div class="col-lg-12 col-md-12 mb-3"> 469 <div class="card m-0"> 470 <div class="card-header"> 471 <?= I18N::translate('Decade of birth') ?> 472 </div><div class="card-body"> 473 <?php 474 foreach ($birt_by_decade as $century => $values) { 475 if (($values['M'] + $values['F']) > 0) { 476 $birthData[] = [ 477 [ 478 'v' => 'Date(' . $century . ', 0, 1)', 479 'f' => $century, 480 ], 481 $values['M'], 482 $values['F'], 483 ]; 484 } 485 } 486 ?> 487 <?= view('lists/chart-by-decade', ['data' => $birthData, 'title' => I18N::translate('Decade of birth')]) ?> 488 </div> 489 </div> 490 </div> 491 </div> 492 <div class="card-deck"> 493 <div class="col-lg-12 col-md-12 mb-3"> 494 <div class="card m-0"> 495 <div class="card-header"> 496 <?= I18N::translate('Decade of marriage') ?> 497 </div><div class="card-body"> 498 <?php 499 foreach ($marr_by_decade as $century => $values) { 500 if (($values['M'] + $values['F']) > 0) { 501 $marriageData[] = [ 502 [ 503 'v' => 'Date(' . $century . ', 0, 1)', 504 'f' => $century, 505 ], 506 $values['M'], 507 $values['F'], 508 ]; 509 } 510 } 511 ?> 512 <?= view('lists/chart-by-decade', ['data' => $marriageData, 'title' => I18N::translate('Decade of marriage')]) ?> 513 </div> 514 </div> 515 </div> 516 </div> 517 <div class="card-deck"> 518 <div class="col-lg-12 col-md-12 mb-3"> 519 <div class="card m-0"> 520 <div class="card-header"> 521 <?= I18N::translate('Age in year of marriage') ?> 522 </div> 523 <div class="card-body"> 524 <?php 525 $totalAge = 0; 526 $totalSum = 0; 527 $max = 0; 528 529 foreach ($marr_by_age as $age => $values) { 530 if (($values['M'] + $values['F']) > 0) { 531 if (($values['M'] + $values['F']) > $max) { 532 $max = $values['M'] + $values['F']; 533 } 534 535 $totalAge += $age * ($values['M'] + $values['F']); 536 $totalSum += $values['M'] + $values['F']; 537 538 $marriageAgeData[] = [ 539 $age, 540 $values['M'], 541 $values['F'], 542 null, 543 ]; 544 } 545 } 546 547 if ($totalSum > 0) { 548 $marriageAgeData[] = [ 549 round($totalAge / $totalSum, 1), 550 null, 551 null, 552 0, 553 ]; 554 555 $marriageAgeData[] = [ 556 round($totalAge / $totalSum, 1), 557 null, 558 null, 559 $max, 560 ]; 561 } 562 ?> 563 <?= view('lists/chart-by-age', ['data' => $marriageAgeData, 'title' => I18N::translate('Age in year of marriage')]) ?> 564 </div> 565 </div> 566 </div> 567 </div> 568 </div> 569</div> 570