1<?php 2/** 3 * webtrees: online genealogy 4 * Copyright (C) 2019 webtrees development team 5 * This program is free software: you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation, either version 3 of the License, or 8 * (at your option) any later version. 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * You should have received a copy of the GNU General Public License 14 * along with this program. If not, see <http://www.gnu.org/licenses/>. 15 */ 16declare(strict_types=1); 17 18namespace Fisharebest\Webtrees\Statistics\Repository; 19 20use Carbon\Carbon; 21use Fisharebest\Webtrees\Auth; 22use Fisharebest\Webtrees\Functions\FunctionsDate; 23use Fisharebest\Webtrees\Functions\FunctionsPrintLists; 24use Fisharebest\Webtrees\Gedcom; 25use Fisharebest\Webtrees\GedcomRecord; 26use Fisharebest\Webtrees\I18N; 27use Fisharebest\Webtrees\Individual; 28use Fisharebest\Webtrees\Module\IndividualListModule; 29use Fisharebest\Webtrees\Module\ModuleInterface; 30use Fisharebest\Webtrees\Module\ModuleListInterface; 31use Fisharebest\Webtrees\Services\ModuleService; 32use Fisharebest\Webtrees\Statistics\Google\ChartAge; 33use Fisharebest\Webtrees\Statistics\Google\ChartBirth; 34use Fisharebest\Webtrees\Statistics\Google\ChartCommonGiven; 35use Fisharebest\Webtrees\Statistics\Google\ChartCommonSurname; 36use Fisharebest\Webtrees\Statistics\Google\ChartDeath; 37use Fisharebest\Webtrees\Statistics\Google\ChartFamilyWithSources; 38use Fisharebest\Webtrees\Statistics\Google\ChartIndividualWithSources; 39use Fisharebest\Webtrees\Statistics\Google\ChartMortality; 40use Fisharebest\Webtrees\Statistics\Google\ChartSex; 41use Fisharebest\Webtrees\Statistics\Repository\Interfaces\IndividualRepositoryInterface; 42use Fisharebest\Webtrees\Tree; 43use Illuminate\Database\Capsule\Manager as DB; 44use Illuminate\Database\Query\Builder; 45use Illuminate\Database\Query\JoinClause; 46 47/** 48 * 49 */ 50class IndividualRepository implements IndividualRepositoryInterface 51{ 52 /** 53 * @var Tree 54 */ 55 private $tree; 56 57 /** 58 * Constructor. 59 * 60 * @param Tree $tree 61 */ 62 public function __construct(Tree $tree) 63 { 64 $this->tree = $tree; 65 } 66 67 /** 68 * Find common given names. 69 * 70 * @param string $sex 71 * @param string $type 72 * @param bool $show_tot 73 * @param int $threshold 74 * @param int $maxtoshow 75 * 76 * @return string|int[] 77 */ 78 private function commonGivenQuery(string $sex, string $type, bool $show_tot, int $threshold, int $maxtoshow) 79 { 80 $query = DB::table('name') 81 ->join('individuals', function (JoinClause $join): void { 82 $join 83 ->on('i_file', '=', 'n_file') 84 ->on('i_id', '=', 'n_id'); 85 }) 86 ->where('n_file', '=', $this->tree->id()) 87 ->where('n_type', '<>', '_MARNM') 88 ->where('n_givn', '<>', '@P.N.') 89 ->where(DB::raw('LENGTH(n_givn)'), '>', 1); 90 91 switch ($sex) { 92 case 'M': 93 case 'F': 94 case 'U': 95 $query->where('i_sex', '=', $sex); 96 break; 97 98 case 'B': 99 default: 100 $query->where('i_sex', '<>', 'U'); 101 break; 102 } 103 104 $rows = $query 105 ->groupBy(['n_givn']) 106 ->select(['n_givn', DB::raw('COUNT(distinct n_id) AS count')]) 107 ->pluck('count', 'n_givn'); 108 109 $nameList = []; 110 111 foreach ($rows as $n_givn => $count) { 112 // Split “John Thomas” into “John” and “Thomas” and count against both totals 113 foreach (explode(' ', $n_givn) as $given) { 114 // Exclude initials and particles. 115 if (!preg_match('/^([A-Z]|[a-z]{1,3})$/', $given)) { 116 if (\array_key_exists($given, $nameList)) { 117 $nameList[$given] += (int) $count; 118 } else { 119 $nameList[$given] = (int) $count; 120 } 121 } 122 } 123 } 124 arsort($nameList); 125 $nameList = \array_slice($nameList, 0, $maxtoshow); 126 127 foreach ($nameList as $given => $total) { 128 if ($total < $threshold) { 129 unset($nameList[$given]); 130 } 131 } 132 133 switch ($type) { 134 case 'chart': 135 return $nameList; 136 137 case 'table': 138 return view('lists/given-names-table', [ 139 'given_names' => $nameList, 140 ]); 141 142 case 'list': 143 return view('lists/given-names-list', [ 144 'given_names' => $nameList, 145 'show_totals' => $show_tot, 146 ]); 147 148 case 'nolist': 149 default: 150 array_walk($nameList, function (int &$value, string $key) use ($show_tot): void { 151 if ($show_tot) { 152 $value = '<span dir="auto">' . e($key); 153 } else { 154 $value = '<span dir="auto">' . e($key) . ' (' . I18N::number($value) . ')'; 155 } 156 }); 157 158 return implode(I18N::$list_separator, $nameList); 159 } 160 } 161 162 /** 163 * Find common give names. 164 * 165 * @param int $threshold 166 * @param int $maxtoshow 167 * 168 * @return string 169 */ 170 public function commonGiven(int $threshold = 1, int $maxtoshow = 10): string 171 { 172 return $this->commonGivenQuery('B', 'nolist', false, $threshold, $maxtoshow); 173 } 174 175 /** 176 * Find common give names. 177 * 178 * @param int $threshold 179 * @param int $maxtoshow 180 * 181 * @return string 182 */ 183 public function commonGivenTotals(int $threshold = 1, int $maxtoshow = 10): string 184 { 185 return $this->commonGivenQuery('B', 'nolist', true, $threshold, $maxtoshow); 186 } 187 188 /** 189 * Find common give names. 190 * 191 * @param int $threshold 192 * @param int $maxtoshow 193 * 194 * @return string 195 */ 196 public function commonGivenList(int $threshold = 1, int $maxtoshow = 10): string 197 { 198 return $this->commonGivenQuery('B', 'list', false, $threshold, $maxtoshow); 199 } 200 201 /** 202 * Find common give names. 203 * 204 * @param int $threshold 205 * @param int $maxtoshow 206 * 207 * @return string 208 */ 209 public function commonGivenListTotals(int $threshold = 1, int $maxtoshow = 10): string 210 { 211 return $this->commonGivenQuery('B', 'list', true, $threshold, $maxtoshow); 212 } 213 214 /** 215 * Find common give names. 216 * 217 * @param int $threshold 218 * @param int $maxtoshow 219 * 220 * @return string 221 */ 222 public function commonGivenTable(int $threshold = 1, int $maxtoshow = 10): string 223 { 224 return $this->commonGivenQuery('B', 'table', false, $threshold, $maxtoshow); 225 } 226 227 /** 228 * Find common give names of females. 229 * 230 * @param int $threshold 231 * @param int $maxtoshow 232 * 233 * @return string 234 */ 235 public function commonGivenFemale(int $threshold = 1, int $maxtoshow = 10): string 236 { 237 return $this->commonGivenQuery('F', 'nolist', false, $threshold, $maxtoshow); 238 } 239 240 /** 241 * Find common give names of females. 242 * 243 * @param int $threshold 244 * @param int $maxtoshow 245 * 246 * @return string 247 */ 248 public function commonGivenFemaleTotals(int $threshold = 1, int $maxtoshow = 10): string 249 { 250 return $this->commonGivenQuery('F', 'nolist', true, $threshold, $maxtoshow); 251 } 252 253 /** 254 * Find common give names of females. 255 * 256 * @param int $threshold 257 * @param int $maxtoshow 258 * 259 * @return string 260 */ 261 public function commonGivenFemaleList(int $threshold = 1, int $maxtoshow = 10): string 262 { 263 return $this->commonGivenQuery('F', 'list', false, $threshold, $maxtoshow); 264 } 265 266 /** 267 * Find common give names of females. 268 * 269 * @param int $threshold 270 * @param int $maxtoshow 271 * 272 * @return string 273 */ 274 public function commonGivenFemaleListTotals(int $threshold = 1, int $maxtoshow = 10): string 275 { 276 return $this->commonGivenQuery('F', 'list', true, $threshold, $maxtoshow); 277 } 278 279 /** 280 * Find common give names of females. 281 * 282 * @param int $threshold 283 * @param int $maxtoshow 284 * 285 * @return string 286 */ 287 public function commonGivenFemaleTable(int $threshold = 1, int $maxtoshow = 10): string 288 { 289 return $this->commonGivenQuery('F', 'table', false, $threshold, $maxtoshow); 290 } 291 292 /** 293 * Find common give names of males. 294 * 295 * @param int $threshold 296 * @param int $maxtoshow 297 * 298 * @return string 299 */ 300 public function commonGivenMale(int $threshold = 1, int $maxtoshow = 10): string 301 { 302 return $this->commonGivenQuery('M', 'nolist', false, $threshold, $maxtoshow); 303 } 304 305 /** 306 * Find common give names of males. 307 * 308 * @param int $threshold 309 * @param int $maxtoshow 310 * 311 * @return string 312 */ 313 public function commonGivenMaleTotals(int $threshold = 1, int $maxtoshow = 10): string 314 { 315 return $this->commonGivenQuery('M', 'nolist', true, $threshold, $maxtoshow); 316 } 317 318 /** 319 * Find common give names of males. 320 * 321 * @param int $threshold 322 * @param int $maxtoshow 323 * 324 * @return string 325 */ 326 public function commonGivenMaleList(int $threshold = 1, int $maxtoshow = 10): string 327 { 328 return $this->commonGivenQuery('M', 'list', false, $threshold, $maxtoshow); 329 } 330 331 /** 332 * Find common give names of males. 333 * 334 * @param int $threshold 335 * @param int $maxtoshow 336 * 337 * @return string 338 */ 339 public function commonGivenMaleListTotals(int $threshold = 1, int $maxtoshow = 10): string 340 { 341 return $this->commonGivenQuery('M', 'list', true, $threshold, $maxtoshow); 342 } 343 344 /** 345 * Find common give names of males. 346 * 347 * @param int $threshold 348 * @param int $maxtoshow 349 * 350 * @return string 351 */ 352 public function commonGivenMaleTable(int $threshold = 1, int $maxtoshow = 10): string 353 { 354 return $this->commonGivenQuery('M', 'table', false, $threshold, $maxtoshow); 355 } 356 357 /** 358 * Find common give names of unknown sexes. 359 * 360 * @param int $threshold 361 * @param int $maxtoshow 362 * 363 * @return string 364 */ 365 public function commonGivenUnknown(int $threshold = 1, int $maxtoshow = 10): string 366 { 367 return $this->commonGivenQuery('U', 'nolist', false, $threshold, $maxtoshow); 368 } 369 370 /** 371 * Find common give names of unknown sexes. 372 * 373 * @param int $threshold 374 * @param int $maxtoshow 375 * 376 * @return string 377 */ 378 public function commonGivenUnknownTotals(int $threshold = 1, int $maxtoshow = 10): string 379 { 380 return $this->commonGivenQuery('U', 'nolist', true, $threshold, $maxtoshow); 381 } 382 383 /** 384 * Find common give names of unknown sexes. 385 * 386 * @param int $threshold 387 * @param int $maxtoshow 388 * 389 * @return string 390 */ 391 public function commonGivenUnknownList(int $threshold = 1, int $maxtoshow = 10): string 392 { 393 return $this->commonGivenQuery('U', 'list', false, $threshold, $maxtoshow); 394 } 395 396 /** 397 * Find common give names of unknown sexes. 398 * 399 * @param int $threshold 400 * @param int $maxtoshow 401 * 402 * @return string 403 */ 404 public function commonGivenUnknownListTotals(int $threshold = 1, int $maxtoshow = 10): string 405 { 406 return $this->commonGivenQuery('U', 'list', true, $threshold, $maxtoshow); 407 } 408 409 /** 410 * Find common give names of unknown sexes. 411 * 412 * @param int $threshold 413 * @param int $maxtoshow 414 * 415 * @return string 416 */ 417 public function commonGivenUnknownTable(int $threshold = 1, int $maxtoshow = 10): string 418 { 419 return $this->commonGivenQuery('U', 'table', false, $threshold, $maxtoshow); 420 } 421 422 /** 423 * Count the number of distinct given names (or the number of occurences of specific given names). 424 * 425 * @param string[] ...$params 426 * 427 * @return string 428 */ 429 public function totalGivennames(...$params): string 430 { 431 $query = DB::table('name') 432 ->where('n_file', '=', $this->tree->id()); 433 434 if (empty($params)) { 435 // Count number of distinct given names. 436 $query 437 ->distinct() 438 ->where('n_givn', '<>', '@P.N.') 439 ->whereNotNull('n_givn'); 440 } else { 441 // Count number of occurences of specific given names. 442 $query->whereIn('n_givn', $params); 443 } 444 445 $count = $query->count('n_givn'); 446 447 return I18N::number($count); 448 } 449 450 /** 451 * Count the number of distinct surnames (or the number of occurences of specific surnames). 452 * 453 * @param string[] ...$params 454 * 455 * @return string 456 */ 457 public function totalSurnames(...$params): string 458 { 459 $query = DB::table('name') 460 ->where('n_file', '=', $this->tree->id()); 461 462 if (empty($params)) { 463 // Count number of distinct surnames 464 $query->distinct() 465 ->whereNotNull('n_surn'); 466 } else { 467 // Count number of occurences of specific surnames. 468 $query->whereIn('n_surn', $params); 469 } 470 471 $count = $query->count('n_surn'); 472 473 return I18N::number($count); 474 } 475 476 /** 477 * @param int $number_of_surnames 478 * @param int $threshold 479 * 480 * @return \stdClass[] 481 */ 482 private function topSurnames(int $number_of_surnames, int $threshold): array 483 { 484 // Use the count of base surnames. 485 $top_surnames = DB::table('name') 486 ->where('n_file', '=', $this->tree->id()) 487 ->where('n_type', '<>', '_MARNM') 488 ->whereNotIn('n_surn', ['', '@N.N.']) 489 ->select('n_surn') 490 ->groupBy('n_surn') 491 ->orderByRaw('count(n_surn) desc') 492 ->take($number_of_surnames) 493 ->get() 494 ->pluck('n_surn') 495 ->all(); 496 497 $surnames = []; 498 foreach ($top_surnames as $top_surname) { 499 $variants = DB::table('name') 500 ->where('n_file', '=', $this->tree->id()) 501 ->where(DB::raw('n_surn /* COLLATE ' . I18N::collation() . ' */'), '=', $top_surname) 502 ->select('n_surn', DB::raw('COUNT(*) AS count')) 503 ->groupBy('n_surn') 504 ->get() 505 ->pluck('count', 'n_surn') 506 ->all(); 507 508 if (array_sum($variants) > $threshold) { 509 $surnames[$top_surname] = $variants; 510 } 511 } 512 513 return $surnames; 514 } 515 516 /** 517 * Find common surnames. 518 * 519 * @return string 520 */ 521 public function getCommonSurname(): string 522 { 523 $top_surname = $this->topSurnames(1, 0); 524 525 return $top_surname 526 ? implode(', ', array_keys(array_shift($top_surname)) ?? []) 527 : ''; 528 } 529 530 /** 531 * Find common surnames. 532 * 533 * @param string $type 534 * @param bool $show_tot 535 * @param int $threshold 536 * @param int $number_of_surnames 537 * @param string $sorting 538 * 539 * @return string 540 */ 541 private function commonSurnamesQuery( 542 string $type, 543 bool $show_tot, 544 int $threshold, 545 int $number_of_surnames, 546 string $sorting 547 ): string { 548 $surnames = $this->topSurnames($number_of_surnames, $threshold); 549 550 switch ($sorting) { 551 default: 552 case 'alpha': 553 uksort($surnames, [I18N::class, 'strcasecmp']); 554 break; 555 case 'count': 556 break; 557 case 'rcount': 558 $surnames = array_reverse($surnames, true); 559 break; 560 } 561 562 //find a module providing individual lists 563 $module = app(ModuleService::class)->findByComponent(ModuleListInterface::class, $this->tree, Auth::user())->first(function (ModuleInterface $module) { 564 return $module instanceof IndividualListModule; 565 }); 566 567 return FunctionsPrintLists::surnameList( 568 $surnames, 569 ($type === 'list' ? 1 : 2), 570 $show_tot, 571 $module, 572 $this->tree 573 ); 574 } 575 576 /** 577 * Find common surnames. 578 * 579 * @param int $threshold 580 * @param int $number_of_surnames 581 * @param string $sorting 582 * 583 * @return string 584 */ 585 public function commonSurnames( 586 int $threshold = 1, 587 int $number_of_surnames = 10, 588 string $sorting = 'alpha' 589 ): string { 590 return $this->commonSurnamesQuery('nolist', false, $threshold, $number_of_surnames, $sorting); 591 } 592 593 /** 594 * Find common surnames. 595 * 596 * @param int $threshold 597 * @param int $number_of_surnames 598 * @param string $sorting 599 * 600 * @return string 601 */ 602 public function commonSurnamesTotals( 603 int $threshold = 1, 604 int $number_of_surnames = 10, 605 string $sorting = 'rcount' 606 ): string { 607 return $this->commonSurnamesQuery('nolist', true, $threshold, $number_of_surnames, $sorting); 608 } 609 610 /** 611 * Find common surnames. 612 * 613 * @param int $threshold 614 * @param int $number_of_surnames 615 * @param string $sorting 616 * 617 * @return string 618 */ 619 public function commonSurnamesList( 620 int $threshold = 1, 621 int $number_of_surnames = 10, 622 string $sorting = 'alpha' 623 ): string { 624 return $this->commonSurnamesQuery('list', false, $threshold, $number_of_surnames, $sorting); 625 } 626 627 /** 628 * Find common surnames. 629 * 630 * @param int $threshold 631 * @param int $number_of_surnames 632 * @param string $sorting 633 * 634 * @return string 635 */ 636 public function commonSurnamesListTotals( 637 int $threshold = 1, 638 int $number_of_surnames = 10, 639 string $sorting = 'rcount' 640 ): string { 641 return $this->commonSurnamesQuery('list', true, $threshold, $number_of_surnames, $sorting); 642 } 643 644 /** 645 * Get a count of births by month. 646 * 647 * @param int $year1 648 * @param int $year2 649 * 650 * @return Builder 651 */ 652 public function statsBirthQuery(int $year1 = -1, int $year2 = -1): Builder 653 { 654 $query = DB::table('dates') 655 ->select(['d_month', DB::raw('COUNT(*) AS total')]) 656 ->where('d_file', '=', $this->tree->id()) 657 ->where('d_fact', '=', 'BIRT') 658 ->whereIn('d_type', ['@#DGREGORIAN@', '@#DJULIAN@']) 659 ->groupBy('d_month'); 660 661 if ($year1 >= 0 && $year2 >= 0) { 662 $query->whereBetween('d_year', [$year1, $year2]); 663 } 664 665 return $query; 666 } 667 668 /** 669 * Get a count of births by month. 670 * 671 * @param int $year1 672 * @param int $year2 673 * 674 * @return Builder 675 */ 676 public function statsBirthBySexQuery(int $year1 = -1, int $year2 = -1): Builder 677 { 678 return $this->statsBirthQuery($year1, $year2) 679 ->select(['d_month', 'i_sex', DB::raw('COUNT(*) AS total')]) 680 ->join('individuals', function (JoinClause $join): void { 681 $join 682 ->on('i_id', '=', 'd_gid') 683 ->on('i_file', '=', 'd_file'); 684 }) 685 ->groupBy('i_sex'); 686 } 687 688 /** 689 * General query on births. 690 * 691 * @param string|null $color_from 692 * @param string|null $color_to 693 * 694 * @return string 695 */ 696 public function statsBirth(string $color_from = null, string $color_to = null): string 697 { 698 return (new ChartBirth($this->tree)) 699 ->chartBirth($color_from, $color_to); 700 } 701 702 /** 703 * Get a list of death dates. 704 * 705 * @param int $year1 706 * @param int $year2 707 * 708 * @return Builder 709 */ 710 public function statsDeathQuery(int $year1 = -1, int $year2 = -1): Builder 711 { 712 $query = DB::table('dates') 713 ->select(['d_month', DB::raw('COUNT(*) AS total')]) 714 ->where('d_file', '=', $this->tree->id()) 715 ->where('d_fact', '=', 'DEAT') 716 ->whereIn('d_type', ['@#DGREGORIAN@', '@#DJULIAN@']) 717 ->groupBy('d_month'); 718 719 if ($year1 >= 0 && $year2 >= 0) { 720 $query->whereBetween('d_year', [$year1, $year2]); 721 } 722 723 return $query; 724 } 725 726 /** 727 * Get a list of death dates. 728 * 729 * @param int $year1 730 * @param int $year2 731 * 732 * @return Builder 733 */ 734 public function statsDeathBySexQuery(int $year1 = -1, int $year2 = -1): Builder 735 { 736 return $this->statsDeathQuery($year1, $year2) 737 ->select(['d_month', 'i_sex', DB::raw('COUNT(*) AS total')]) 738 ->join('individuals', function (JoinClause $join): void { 739 $join 740 ->on('i_id', '=', 'd_gid') 741 ->on('i_file', '=', 'd_file'); 742 }) 743 ->groupBy('i_sex'); 744 } 745 746 /** 747 * General query on deaths. 748 * 749 * @param string|null $color_from 750 * @param string|null $color_to 751 * 752 * @return string 753 */ 754 public function statsDeath(string $color_from = null, string $color_to = null): string 755 { 756 return (new ChartDeath($this->tree)) 757 ->chartDeath($color_from, $color_to); 758 } 759 760 /** 761 * General query on ages. 762 * 763 * @param string $related 764 * @param string $sex 765 * @param int $year1 766 * @param int $year2 767 * 768 * @return array|string 769 */ 770 public function statsAgeQuery(string $related = 'BIRT', string $sex = 'BOTH', int $year1 = -1, int $year2 = -1) 771 { 772 $prefix = DB::connection()->getTablePrefix(); 773 774 $query = $this->birthAndDeathQuery($sex); 775 776 if ($year1 >= 0 && $year2 >= 0) { 777 $query 778 ->whereIn('birth.d_type', ['@#DGREGORIAN@', '@#DJULIAN@']) 779 ->whereIn('death.d_type', ['@#DGREGORIAN@', '@#DJULIAN@']); 780 781 if ($related === 'BIRT') { 782 $query->whereBetween('birth.d_year', [$year1, $year2]); 783 } elseif ($related === 'DEAT') { 784 $query->whereBetween('death.d_year', [$year1, $year2]); 785 } 786 } 787 788 return $query 789 ->select(DB::raw($prefix . 'death.d_julianday2 - ' . $prefix . 'birth.d_julianday1 AS days')) 790 ->orderBy('days', 'desc') 791 ->get() 792 ->all(); 793 } 794 795 /** 796 * General query on ages. 797 * 798 * @return string 799 */ 800 public function statsAge(): string 801 { 802 return (new ChartAge($this->tree))->chartAge(); 803 } 804 805 /** 806 * Lifespan 807 * 808 * @param string $type 809 * @param string $sex 810 * 811 * @return string 812 */ 813 private function longlifeQuery(string $type, string $sex): string 814 { 815 $prefix = DB::connection()->getTablePrefix(); 816 817 $row = $this->birthAndDeathQuery($sex) 818 ->orderBy('days', 'desc') 819 ->select(['individuals.*', DB::raw($prefix . 'death.d_julianday2 - ' . $prefix . 'birth.d_julianday1 AS days')]) 820 ->first(); 821 822 if ($row === null) { 823 return ''; 824 } 825 826 /** @var Individual $individual */ 827 $individual = Individual::rowMapper()($row); 828 829 if (!$individual->canShow()) { 830 return I18N::translate('This information is private and cannot be shown.'); 831 } 832 833 switch ($type) { 834 default: 835 case 'full': 836 return $individual->formatList(); 837 838 case 'age': 839 return I18N::number((int) ($row->days / 365.25)); 840 841 case 'name': 842 return '<a href="' . e($individual->url()) . '">' . $individual->fullName() . '</a>'; 843 } 844 } 845 846 /** 847 * Find the longest lived individual. 848 * 849 * @return string 850 */ 851 public function longestLife(): string 852 { 853 return $this->longlifeQuery('full', 'BOTH'); 854 } 855 856 /** 857 * Find the age of the longest lived individual. 858 * 859 * @return string 860 */ 861 public function longestLifeAge(): string 862 { 863 return $this->longlifeQuery('age', 'BOTH'); 864 } 865 866 /** 867 * Find the name of the longest lived individual. 868 * 869 * @return string 870 */ 871 public function longestLifeName(): string 872 { 873 return $this->longlifeQuery('name', 'BOTH'); 874 } 875 876 /** 877 * Find the longest lived female. 878 * 879 * @return string 880 */ 881 public function longestLifeFemale(): string 882 { 883 return $this->longlifeQuery('full', 'F'); 884 } 885 886 /** 887 * Find the age of the longest lived female. 888 * 889 * @return string 890 */ 891 public function longestLifeFemaleAge(): string 892 { 893 return $this->longlifeQuery('age', 'F'); 894 } 895 896 /** 897 * Find the name of the longest lived female. 898 * 899 * @return string 900 */ 901 public function longestLifeFemaleName(): string 902 { 903 return $this->longlifeQuery('name', 'F'); 904 } 905 906 /** 907 * Find the longest lived male. 908 * 909 * @return string 910 */ 911 public function longestLifeMale(): string 912 { 913 return $this->longlifeQuery('full', 'M'); 914 } 915 916 /** 917 * Find the age of the longest lived male. 918 * 919 * @return string 920 */ 921 public function longestLifeMaleAge(): string 922 { 923 return $this->longlifeQuery('age', 'M'); 924 } 925 926 /** 927 * Find the name of the longest lived male. 928 * 929 * @return string 930 */ 931 public function longestLifeMaleName(): string 932 { 933 return $this->longlifeQuery('name', 'M'); 934 } 935 936 /** 937 * Returns the calculated age the time of event. 938 * 939 * @param int $age The age from the database record 940 * 941 * @return string 942 */ 943 private function calculateAge(int $age): string 944 { 945 if ((int) ($age / 365.25) > 0) { 946 $result = (int) ($age / 365.25) . 'y'; 947 } elseif ((int) ($age / 30.4375) > 0) { 948 $result = (int) ($age / 30.4375) . 'm'; 949 } else { 950 $result = $age . 'd'; 951 } 952 953 return FunctionsDate::getAgeAtEvent($result); 954 } 955 956 /** 957 * Find the oldest individuals. 958 * 959 * @param string $sex 960 * @param int $total 961 * 962 * @return array 963 */ 964 private function topTenOldestQuery(string $sex, int $total): array 965 { 966 $prefix = DB::connection()->getTablePrefix(); 967 968 $rows = $this->birthAndDeathQuery($sex) 969 ->groupBy(['i_id', 'i_file']) 970 ->orderBy('days', 'desc') 971 ->select(['individuals.*', DB::raw('MAX(' . $prefix . 'death.d_julianday2 - ' . $prefix . 'birth.d_julianday1) AS days')]) 972 ->take($total) 973 ->get(); 974 975 $top10 = []; 976 foreach ($rows as $row) { 977 /** @var Individual $individual */ 978 $individual = Individual::rowMapper()($row); 979 980 if ($individual->canShow()) { 981 $top10[] = [ 982 'person' => $individual, 983 'age' => $this->calculateAge((int) $row->days), 984 ]; 985 } 986 } 987 988 return $top10; 989 } 990 991 /** 992 * Find the oldest individuals. 993 * 994 * @param int $total 995 * 996 * @return string 997 */ 998 public function topTenOldest(int $total = 10): string 999 { 1000 $records = $this->topTenOldestQuery('BOTH', $total); 1001 1002 return view('statistics/individuals/top10-nolist', [ 1003 'records' => $records, 1004 ]); 1005 } 1006 1007 /** 1008 * Find the oldest living individuals. 1009 * 1010 * @param int $total 1011 * 1012 * @return string 1013 */ 1014 public function topTenOldestList(int $total = 10): string 1015 { 1016 $records = $this->topTenOldestQuery('BOTH', $total); 1017 1018 return view('statistics/individuals/top10-list', [ 1019 'records' => $records, 1020 ]); 1021 } 1022 1023 /** 1024 * Find the oldest females. 1025 * 1026 * @param int $total 1027 * 1028 * @return string 1029 */ 1030 public function topTenOldestFemale(int $total = 10): string 1031 { 1032 $records = $this->topTenOldestQuery('F', $total); 1033 1034 return view('statistics/individuals/top10-nolist', [ 1035 'records' => $records, 1036 ]); 1037 } 1038 1039 /** 1040 * Find the oldest living females. 1041 * 1042 * @param int $total 1043 * 1044 * @return string 1045 */ 1046 public function topTenOldestFemaleList(int $total = 10): string 1047 { 1048 $records = $this->topTenOldestQuery('F', $total); 1049 1050 return view('statistics/individuals/top10-list', [ 1051 'records' => $records, 1052 ]); 1053 } 1054 1055 /** 1056 * Find the longest lived males. 1057 * 1058 * @param int $total 1059 * 1060 * @return string 1061 */ 1062 public function topTenOldestMale(int $total = 10): string 1063 { 1064 $records = $this->topTenOldestQuery('M', $total); 1065 1066 return view('statistics/individuals/top10-nolist', [ 1067 'records' => $records, 1068 ]); 1069 } 1070 1071 /** 1072 * Find the longest lived males. 1073 * 1074 * @param int $total 1075 * 1076 * @return string 1077 */ 1078 public function topTenOldestMaleList(int $total = 10): string 1079 { 1080 $records = $this->topTenOldestQuery('M', $total); 1081 1082 return view('statistics/individuals/top10-list', [ 1083 'records' => $records, 1084 ]); 1085 } 1086 1087 /** 1088 * Find the oldest living individuals. 1089 * 1090 * @param string $sex "M", "F" or "BOTH" 1091 * @param int $total 1092 * 1093 * @return array 1094 */ 1095 private function topTenOldestAliveQuery(string $sex, int $total): array 1096 { 1097 $query = DB::table('dates') 1098 ->join('individuals', function (JoinClause $join): void { 1099 $join 1100 ->on('i_id', '=', 'd_gid') 1101 ->on('i_file', '=', 'd_file'); 1102 }) 1103 ->where('d_file', '=', $this->tree->id()) 1104 ->where('d_julianday1', '<>', 0) 1105 ->where('d_fact', '=', 'BIRT') 1106 ->where('i_gedcom', 'NOT LIKE', "%\n1 DEAT%") 1107 ->where('i_gedcom', 'NOT LIKE', "%\n1 BURI%") 1108 ->where('i_gedcom', 'NOT LIKE', "%\n1 CREM%"); 1109 1110 if ($sex === 'F' || $sex === 'M') { 1111 $query->where('i_sex', '=', $sex); 1112 } 1113 1114 return $query 1115 ->groupBy(['i_id', 'i_file']) 1116 ->orderBy(DB::raw('MIN(d_julianday1)')) 1117 ->select('individuals.*') 1118 ->take($total) 1119 ->get() 1120 ->map(Individual::rowMapper()) 1121 ->filter(GedcomRecord::accessFilter()) 1122 ->map(function (Individual $individual): array { 1123 $birth_jd = $individual->getBirthDate()->minimumJulianDay(); 1124 1125 return [ 1126 'person' => $individual, 1127 'age' => $this->calculateAge(unixtojd(Carbon::now()->timestamp) - $birth_jd), 1128 ]; 1129 }) 1130 ->all(); 1131 } 1132 1133 /** 1134 * Find the oldest living individuals. 1135 * 1136 * @param int $total 1137 * 1138 * @return string 1139 */ 1140 public function topTenOldestAlive(int $total = 10): string 1141 { 1142 if (!Auth::isMember($this->tree)) { 1143 return I18N::translate('This information is private and cannot be shown.'); 1144 } 1145 1146 $records = $this->topTenOldestAliveQuery('BOTH', $total); 1147 1148 return view('statistics/individuals/top10-nolist', [ 1149 'records' => $records, 1150 ]); 1151 } 1152 1153 /** 1154 * Find the oldest living individuals. 1155 * 1156 * @param int $total 1157 * 1158 * @return string 1159 */ 1160 public function topTenOldestListAlive(int $total = 10): string 1161 { 1162 if (!Auth::isMember($this->tree)) { 1163 return I18N::translate('This information is private and cannot be shown.'); 1164 } 1165 1166 $records = $this->topTenOldestAliveQuery('BOTH', $total); 1167 1168 return view('statistics/individuals/top10-list', [ 1169 'records' => $records, 1170 ]); 1171 } 1172 1173 /** 1174 * Find the oldest living females. 1175 * 1176 * @param int $total 1177 * 1178 * @return string 1179 */ 1180 public function topTenOldestFemaleAlive(int $total = 10): string 1181 { 1182 if (!Auth::isMember($this->tree)) { 1183 return I18N::translate('This information is private and cannot be shown.'); 1184 } 1185 1186 $records = $this->topTenOldestAliveQuery('F', $total); 1187 1188 return view('statistics/individuals/top10-nolist', [ 1189 'records' => $records, 1190 ]); 1191 } 1192 1193 /** 1194 * Find the oldest living females. 1195 * 1196 * @param int $total 1197 * 1198 * @return string 1199 */ 1200 public function topTenOldestFemaleListAlive(int $total = 10): string 1201 { 1202 if (!Auth::isMember($this->tree)) { 1203 return I18N::translate('This information is private and cannot be shown.'); 1204 } 1205 1206 $records = $this->topTenOldestAliveQuery('F', $total); 1207 1208 return view('statistics/individuals/top10-list', [ 1209 'records' => $records, 1210 ]); 1211 } 1212 1213 /** 1214 * Find the longest lived living males. 1215 * 1216 * @param int $total 1217 * 1218 * @return string 1219 */ 1220 public function topTenOldestMaleAlive(int $total = 10): string 1221 { 1222 if (!Auth::isMember($this->tree)) { 1223 return I18N::translate('This information is private and cannot be shown.'); 1224 } 1225 1226 $records = $this->topTenOldestAliveQuery('M', $total); 1227 1228 return view('statistics/individuals/top10-nolist', [ 1229 'records' => $records, 1230 ]); 1231 } 1232 1233 /** 1234 * Find the longest lived living males. 1235 * 1236 * @param int $total 1237 * 1238 * @return string 1239 */ 1240 public function topTenOldestMaleListAlive(int $total = 10): string 1241 { 1242 if (!Auth::isMember($this->tree)) { 1243 return I18N::translate('This information is private and cannot be shown.'); 1244 } 1245 1246 $records = $this->topTenOldestAliveQuery('M', $total); 1247 1248 return view('statistics/individuals/top10-list', [ 1249 'records' => $records, 1250 ]); 1251 } 1252 1253 /** 1254 * Find the average lifespan. 1255 * 1256 * @param string $sex "M", "F" or "BOTH" 1257 * @param bool $show_years 1258 * 1259 * @return string 1260 */ 1261 private function averageLifespanQuery(string $sex, bool $show_years): string 1262 { 1263 $prefix = DB::connection()->getTablePrefix(); 1264 1265 $days = (int) $this->birthAndDeathQuery($sex) 1266 ->select(DB::raw('AVG(' . $prefix . 'death.d_julianday2 - ' . $prefix . 'birth.d_julianday1) AS days')) 1267 ->value('days'); 1268 1269 if ($show_years) { 1270 return $this->calculateAge($days); 1271 } 1272 1273 return I18N::number((int) ($days / 365.25)); 1274 } 1275 1276 /** 1277 * Find the average lifespan. 1278 * 1279 * @param bool $show_years 1280 * 1281 * @return string 1282 */ 1283 public function averageLifespan($show_years = false): string 1284 { 1285 return $this->averageLifespanQuery('BOTH', $show_years); 1286 } 1287 1288 /** 1289 * Find the average lifespan of females. 1290 * 1291 * @param bool $show_years 1292 * 1293 * @return string 1294 */ 1295 public function averageLifespanFemale($show_years = false): string 1296 { 1297 return $this->averageLifespanQuery('F', $show_years); 1298 } 1299 1300 /** 1301 * Find the average male lifespan. 1302 * 1303 * @param bool $show_years 1304 * 1305 * @return string 1306 */ 1307 public function averageLifespanMale($show_years = false): string 1308 { 1309 return $this->averageLifespanQuery('M', $show_years); 1310 } 1311 1312 /** 1313 * Convert totals into percentages. 1314 * 1315 * @param int $count 1316 * @param int $total 1317 * 1318 * @return string 1319 */ 1320 private function getPercentage(int $count, int $total): string 1321 { 1322 return ($total !== 0) ? I18N::percentage($count / $total, 1) : ''; 1323 } 1324 1325 /** 1326 * Returns how many individuals exist in the tree. 1327 * 1328 * @return int 1329 */ 1330 private function totalIndividualsQuery(): int 1331 { 1332 return DB::table('individuals') 1333 ->where('i_file', '=', $this->tree->id()) 1334 ->count(); 1335 } 1336 1337 /** 1338 * Count the number of living individuals. 1339 * 1340 * The totalLiving/totalDeceased queries assume that every dead person will 1341 * have a DEAT record. It will not include individuals who were born more 1342 * than MAX_ALIVE_AGE years ago, and who have no DEAT record. 1343 * A good reason to run the “Add missing DEAT records” batch-update! 1344 * 1345 * @return int 1346 */ 1347 private function totalLivingQuery(): int 1348 { 1349 $query = DB::table('individuals') 1350 ->where('i_file', '=', $this->tree->id()); 1351 1352 foreach (Gedcom::DEATH_EVENTS as $death_event) { 1353 $query->where('i_gedcom', 'NOT LIKE', "%\n1 " . $death_event . '%'); 1354 } 1355 1356 return $query->count(); 1357 } 1358 1359 /** 1360 * Count the number of dead individuals. 1361 * 1362 * @return int 1363 */ 1364 private function totalDeceasedQuery(): int 1365 { 1366 return DB::table('individuals') 1367 ->where('i_file', '=', $this->tree->id()) 1368 ->where(function (Builder $query): void { 1369 foreach (Gedcom::DEATH_EVENTS as $death_event) { 1370 $query->orWhere('i_gedcom', 'LIKE', "%\n1 " . $death_event . '%'); 1371 } 1372 }) 1373 ->count(); 1374 } 1375 1376 /** 1377 * Returns the total count of a specific sex. 1378 * 1379 * @param string $sex The sex to query 1380 * 1381 * @return int 1382 */ 1383 private function getTotalSexQuery(string $sex): int 1384 { 1385 return DB::table('individuals') 1386 ->where('i_file', '=', $this->tree->id()) 1387 ->where('i_sex', '=', $sex) 1388 ->count(); 1389 } 1390 1391 /** 1392 * Returns the total number of males. 1393 * 1394 * @return int 1395 */ 1396 private function totalSexMalesQuery(): int 1397 { 1398 return $this->getTotalSexQuery('M'); 1399 } 1400 1401 /** 1402 * Returns the total number of females. 1403 * 1404 * @return int 1405 */ 1406 private function totalSexFemalesQuery(): int 1407 { 1408 return $this->getTotalSexQuery('F'); 1409 } 1410 1411 /** 1412 * Returns the total number of individuals with unknown sex. 1413 * 1414 * @return int 1415 */ 1416 private function totalSexUnknownQuery(): int 1417 { 1418 return $this->getTotalSexQuery('U'); 1419 } 1420 1421 /** 1422 * Count the total families. 1423 * 1424 * @return int 1425 */ 1426 private function totalFamiliesQuery(): int 1427 { 1428 return DB::table('families') 1429 ->where('f_file', '=', $this->tree->id()) 1430 ->count(); 1431 } 1432 1433 /** 1434 * How many individuals have one or more sources. 1435 * 1436 * @return int 1437 */ 1438 private function totalIndisWithSourcesQuery(): int 1439 { 1440 return DB::table('individuals') 1441 ->select(['i_id']) 1442 ->distinct() 1443 ->join('link', function (JoinClause $join): void { 1444 $join->on('i_id', '=', 'l_from') 1445 ->on('i_file', '=', 'l_file'); 1446 }) 1447 ->where('l_file', '=', $this->tree->id()) 1448 ->where('l_type', '=', 'SOUR') 1449 ->count('i_id'); 1450 } 1451 1452 /** 1453 * Count the families with source records. 1454 * 1455 * @return int 1456 */ 1457 private function totalFamsWithSourcesQuery(): int 1458 { 1459 return DB::table('families') 1460 ->select(['f_id']) 1461 ->distinct() 1462 ->join('link', function (JoinClause $join): void { 1463 $join->on('f_id', '=', 'l_from') 1464 ->on('f_file', '=', 'l_file'); 1465 }) 1466 ->where('l_file', '=', $this->tree->id()) 1467 ->where('l_type', '=', 'SOUR') 1468 ->count('f_id'); 1469 } 1470 1471 /** 1472 * Count the number of repositories. 1473 * 1474 * @return int 1475 */ 1476 private function totalRepositoriesQuery(): int 1477 { 1478 return DB::table('other') 1479 ->where('o_file', '=', $this->tree->id()) 1480 ->where('o_type', '=', 'REPO') 1481 ->count(); 1482 } 1483 1484 /** 1485 * Count the total number of sources. 1486 * 1487 * @return int 1488 */ 1489 private function totalSourcesQuery(): int 1490 { 1491 return DB::table('sources') 1492 ->where('s_file', '=', $this->tree->id()) 1493 ->count(); 1494 } 1495 1496 /** 1497 * Count the number of notes. 1498 * 1499 * @return int 1500 */ 1501 private function totalNotesQuery(): int 1502 { 1503 return DB::table('other') 1504 ->where('o_file', '=', $this->tree->id()) 1505 ->where('o_type', '=', 'NOTE') 1506 ->count(); 1507 } 1508 1509 /** 1510 * Returns the total number of records. 1511 * 1512 * @return int 1513 */ 1514 private function totalRecordsQuery(): int 1515 { 1516 return $this->totalIndividualsQuery() 1517 + $this->totalFamiliesQuery() 1518 + $this->totalNotesQuery() 1519 + $this->totalRepositoriesQuery() 1520 + $this->totalSourcesQuery(); 1521 } 1522 1523 /** 1524 * @inheritDoc 1525 */ 1526 public function totalRecords(): string 1527 { 1528 return I18N::number($this->totalRecordsQuery()); 1529 } 1530 1531 /** 1532 * @inheritDoc 1533 */ 1534 public function totalIndividuals(): string 1535 { 1536 return I18N::number($this->totalIndividualsQuery()); 1537 } 1538 1539 /** 1540 * Count the number of living individuals. 1541 * 1542 * @return string 1543 */ 1544 public function totalLiving(): string 1545 { 1546 return I18N::number($this->totalLivingQuery()); 1547 } 1548 1549 /** 1550 * Count the number of dead individuals. 1551 * 1552 * @return string 1553 */ 1554 public function totalDeceased(): string 1555 { 1556 return I18N::number($this->totalDeceasedQuery()); 1557 } 1558 1559 /** 1560 * @inheritDoc 1561 */ 1562 public function totalSexMales(): string 1563 { 1564 return I18N::number($this->totalSexMalesQuery()); 1565 } 1566 1567 /** 1568 * @inheritDoc 1569 */ 1570 public function totalSexFemales(): string 1571 { 1572 return I18N::number($this->totalSexFemalesQuery()); 1573 } 1574 1575 /** 1576 * @inheritDoc 1577 */ 1578 public function totalSexUnknown(): string 1579 { 1580 return I18N::number($this->totalSexUnknownQuery()); 1581 } 1582 1583 /** 1584 * @inheritDoc 1585 */ 1586 public function totalFamilies(): string 1587 { 1588 return I18N::number($this->totalFamiliesQuery()); 1589 } 1590 1591 /** 1592 * How many individuals have one or more sources. 1593 * 1594 * @return string 1595 */ 1596 public function totalIndisWithSources(): string 1597 { 1598 return I18N::number($this->totalIndisWithSourcesQuery()); 1599 } 1600 1601 /** 1602 * Count the families with with source records. 1603 * 1604 * @return string 1605 */ 1606 public function totalFamsWithSources(): string 1607 { 1608 return I18N::number($this->totalFamsWithSourcesQuery()); 1609 } 1610 1611 /** 1612 * @inheritDoc 1613 */ 1614 public function totalRepositories(): string 1615 { 1616 return I18N::number($this->totalRepositoriesQuery()); 1617 } 1618 1619 /** 1620 * @inheritDoc 1621 */ 1622 public function totalSources(): string 1623 { 1624 return I18N::number($this->totalSourcesQuery()); 1625 } 1626 1627 /** 1628 * @inheritDoc 1629 */ 1630 public function totalNotes(): string 1631 { 1632 return I18N::number($this->totalNotesQuery()); 1633 } 1634 1635 /** 1636 * @inheritDoc 1637 */ 1638 public function totalIndividualsPercentage(): string 1639 { 1640 return $this->getPercentage( 1641 $this->totalIndividualsQuery(), 1642 $this->totalRecordsQuery() 1643 ); 1644 } 1645 1646 /** 1647 * @inheritDoc 1648 */ 1649 public function totalFamiliesPercentage(): string 1650 { 1651 return $this->getPercentage( 1652 $this->totalFamiliesQuery(), 1653 $this->totalRecordsQuery() 1654 ); 1655 } 1656 1657 /** 1658 * @inheritDoc 1659 */ 1660 public function totalRepositoriesPercentage(): string 1661 { 1662 return $this->getPercentage( 1663 $this->totalRepositoriesQuery(), 1664 $this->totalRecordsQuery() 1665 ); 1666 } 1667 1668 /** 1669 * @inheritDoc 1670 */ 1671 public function totalSourcesPercentage(): string 1672 { 1673 return $this->getPercentage( 1674 $this->totalSourcesQuery(), 1675 $this->totalRecordsQuery() 1676 ); 1677 } 1678 1679 /** 1680 * @inheritDoc 1681 */ 1682 public function totalNotesPercentage(): string 1683 { 1684 return $this->getPercentage( 1685 $this->totalNotesQuery(), 1686 $this->totalRecordsQuery() 1687 ); 1688 } 1689 1690 /** 1691 * @inheritDoc 1692 */ 1693 public function totalLivingPercentage(): string 1694 { 1695 return $this->getPercentage( 1696 $this->totalLivingQuery(), 1697 $this->totalIndividualsQuery() 1698 ); 1699 } 1700 1701 /** 1702 * @inheritDoc 1703 */ 1704 public function totalDeceasedPercentage(): string 1705 { 1706 return $this->getPercentage( 1707 $this->totalDeceasedQuery(), 1708 $this->totalIndividualsQuery() 1709 ); 1710 } 1711 1712 /** 1713 * @inheritDoc 1714 */ 1715 public function totalSexMalesPercentage(): string 1716 { 1717 return $this->getPercentage( 1718 $this->totalSexMalesQuery(), 1719 $this->totalIndividualsQuery() 1720 ); 1721 } 1722 1723 /** 1724 * @inheritDoc 1725 */ 1726 public function totalSexFemalesPercentage(): string 1727 { 1728 return $this->getPercentage( 1729 $this->totalSexFemalesQuery(), 1730 $this->totalIndividualsQuery() 1731 ); 1732 } 1733 1734 /** 1735 * @inheritDoc 1736 */ 1737 public function totalSexUnknownPercentage(): string 1738 { 1739 return $this->getPercentage( 1740 $this->totalSexUnknownQuery(), 1741 $this->totalIndividualsQuery() 1742 ); 1743 } 1744 1745 /** 1746 * Create a chart of common given names. 1747 * 1748 * @param string|null $color_from 1749 * @param string|null $color_to 1750 * @param int $maxtoshow 1751 * 1752 * @return string 1753 */ 1754 public function chartCommonGiven( 1755 string $color_from = null, 1756 string $color_to = null, 1757 int $maxtoshow = 7 1758 ): string { 1759 $tot_indi = $this->totalIndividualsQuery(); 1760 $given = $this->commonGivenQuery('B', 'chart', false, 1, $maxtoshow); 1761 1762 if (empty($given)) { 1763 return I18N::translate('This information is not available.'); 1764 } 1765 1766 return (new ChartCommonGiven()) 1767 ->chartCommonGiven($tot_indi, $given, $color_from, $color_to); 1768 } 1769 1770 /** 1771 * Create a chart of common surnames. 1772 * 1773 * @param string|null $color_from 1774 * @param string|null $color_to 1775 * @param int $number_of_surnames 1776 * 1777 * @return string 1778 */ 1779 public function chartCommonSurnames( 1780 string $color_from = null, 1781 string $color_to = null, 1782 int $number_of_surnames = 10 1783 ): string { 1784 $tot_indi = $this->totalIndividualsQuery(); 1785 $all_surnames = $this->topSurnames($number_of_surnames, 0); 1786 1787 if (empty($all_surnames)) { 1788 return I18N::translate('This information is not available.'); 1789 } 1790 1791 return (new ChartCommonSurname($this->tree)) 1792 ->chartCommonSurnames($tot_indi, $all_surnames, $color_from, $color_to); 1793 } 1794 1795 /** 1796 * Create a chart showing mortality. 1797 * 1798 * @param string|null $color_living 1799 * @param string|null $color_dead 1800 * 1801 * @return string 1802 */ 1803 public function chartMortality(string $color_living = null, string $color_dead = null): string 1804 { 1805 $tot_l = $this->totalLivingQuery(); 1806 $tot_d = $this->totalDeceasedQuery(); 1807 1808 return (new ChartMortality()) 1809 ->chartMortality($tot_l, $tot_d, $color_living, $color_dead); 1810 } 1811 1812 /** 1813 * Create a chart showing individuals with/without sources. 1814 * 1815 * @param string|null $color_from 1816 * @param string|null $color_to 1817 * 1818 * @return string 1819 */ 1820 public function chartIndisWithSources( 1821 string $color_from = null, 1822 string $color_to = null 1823 ): string { 1824 $tot_indi = $this->totalIndividualsQuery(); 1825 $tot_indi_source = $this->totalIndisWithSourcesQuery(); 1826 1827 return (new ChartIndividualWithSources()) 1828 ->chartIndisWithSources($tot_indi, $tot_indi_source, $color_from, $color_to); 1829 } 1830 1831 /** 1832 * Create a chart of individuals with/without sources. 1833 * 1834 * @param string|null $color_from 1835 * @param string|null $color_to 1836 * 1837 * @return string 1838 */ 1839 public function chartFamsWithSources( 1840 string $color_from = null, 1841 string $color_to = null 1842 ): string { 1843 $tot_fam = $this->totalFamiliesQuery(); 1844 $tot_fam_source = $this->totalFamsWithSourcesQuery(); 1845 1846 return (new ChartFamilyWithSources()) 1847 ->chartFamsWithSources($tot_fam, $tot_fam_source, $color_from, $color_to); 1848 } 1849 1850 /** 1851 * @inheritDoc 1852 */ 1853 public function chartSex( 1854 string $color_female = null, 1855 string $color_male = null, 1856 string $color_unknown = null 1857 ): string { 1858 $tot_m = $this->totalSexMalesQuery(); 1859 $tot_f = $this->totalSexFemalesQuery(); 1860 $tot_u = $this->totalSexUnknownQuery(); 1861 1862 return (new ChartSex()) 1863 ->chartSex($tot_m, $tot_f, $tot_u, $color_female, $color_male, $color_unknown); 1864 } 1865 1866 /** 1867 * Query individuals, with their births and deaths. 1868 * 1869 * @param string $sex 1870 * 1871 * @return Builder 1872 */ 1873 private function birthAndDeathQuery(string $sex): Builder 1874 { 1875 $query = DB::table('individuals') 1876 ->where('i_file', '=', $this->tree->id()) 1877 ->join('dates AS birth', function (JoinClause $join): void { 1878 $join 1879 ->on('birth.d_file', '=', 'i_file') 1880 ->on('birth.d_gid', '=', 'i_id'); 1881 }) 1882 ->join('dates AS death', function (JoinClause $join): void { 1883 $join 1884 ->on('death.d_file', '=', 'i_file') 1885 ->on('death.d_gid', '=', 'i_id'); 1886 }) 1887 ->where('birth.d_fact', '=', 'BIRT') 1888 ->where('death.d_fact', '=', 'DEAT') 1889 ->whereColumn('death.d_julianday1', '>=', 'birth.d_julianday2') 1890 ->where('birth.d_julianday2', '<>', 0); 1891 1892 if ($sex === 'M' || $sex === 'F') { 1893 $query->where('i_sex', '=', $sex); 1894 } 1895 1896 return $query; 1897 } 1898} 1899