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