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