1168ff6f3Sric2016<?php 2168ff6f3Sric2016/** 3168ff6f3Sric2016 * webtrees: online genealogy 48fcd0d32SGreg Roach * Copyright (C) 2019 webtrees development team 5168ff6f3Sric2016 * This program is free software: you can redistribute it and/or modify 6168ff6f3Sric2016 * it under the terms of the GNU General Public License as published by 7168ff6f3Sric2016 * the Free Software Foundation, either version 3 of the License, or 8168ff6f3Sric2016 * (at your option) any later version. 9168ff6f3Sric2016 * This program is distributed in the hope that it will be useful, 10168ff6f3Sric2016 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11168ff6f3Sric2016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12168ff6f3Sric2016 * GNU General Public License for more details. 13168ff6f3Sric2016 * You should have received a copy of the GNU General Public License 14168ff6f3Sric2016 * along with this program. If not, see <http://www.gnu.org/licenses/>. 15168ff6f3Sric2016 */ 16e7f56f2aSGreg Roachdeclare(strict_types=1); 17e7f56f2aSGreg Roach 18168ff6f3Sric2016namespace Fisharebest\Webtrees\Module; 19168ff6f3Sric2016 20241a1636SGreg Roachuse Fisharebest\Webtrees\Auth; 21241a1636SGreg Roachuse Fisharebest\Webtrees\Functions\FunctionsEdit; 22168ff6f3Sric2016use Fisharebest\Webtrees\I18N; 23168ff6f3Sric2016use Fisharebest\Webtrees\Individual; 241f918143SScrutinizer Auto-Fixeruse Fisharebest\Webtrees\Menu; 25241a1636SGreg Roachuse Fisharebest\Webtrees\Services\ChartService; 26241a1636SGreg Roachuse Fisharebest\Webtrees\Theme; 27241a1636SGreg Roachuse Fisharebest\Webtrees\Tree; 28241a1636SGreg Roachuse stdClass; 29241a1636SGreg Roachuse Symfony\Component\HttpFoundation\Request; 30241a1636SGreg Roachuse Symfony\Component\HttpFoundation\Response; 31168ff6f3Sric2016 32168ff6f3Sric2016/** 33168ff6f3Sric2016 * Class PedigreeChartModule 34168ff6f3Sric2016 */ 3549a243cbSGreg Roachclass PedigreeChartModule extends AbstractModule implements ModuleInterface, ModuleChartInterface 36c1010edaSGreg Roach{ 3749a243cbSGreg Roach use ModuleChartTrait; 3849a243cbSGreg Roach 39241a1636SGreg Roach // With more than 8 generations, we run out of pixels on the <canvas> 40241a1636SGreg Roach protected const MAX_GENERATIONS = 8; 41241a1636SGreg Roach protected const MIN_GENERATIONS = 2; 42241a1636SGreg Roach 43241a1636SGreg Roach protected const DEFAULT_GENERATIONS = '4'; 44241a1636SGreg Roach 45241a1636SGreg Roach /** 46241a1636SGreg Roach * Chart orientation codes 47241a1636SGreg Roach * Dont change them! the offset calculations rely on this order 48241a1636SGreg Roach */ 49677aaceaSGreg Roach public const PORTRAIT = 0; 50677aaceaSGreg Roach public const LANDSCAPE = 1; 51677aaceaSGreg Roach public const OLDEST_AT_TOP = 2; 52677aaceaSGreg Roach public const OLDEST_AT_BOTTOM = 3; 53241a1636SGreg Roach 54241a1636SGreg Roach protected const DEFAULT_ORIENTATION = self::LANDSCAPE; 55241a1636SGreg Roach 56241a1636SGreg Roach /** @var int Number of generation to display */ 57241a1636SGreg Roach protected $generations; 58241a1636SGreg Roach 59241a1636SGreg Roach /** @var array data pertaining to each chart node */ 60241a1636SGreg Roach protected $nodes = []; 61241a1636SGreg Roach 62241a1636SGreg Roach /** @var int Number of nodes in the chart */ 63241a1636SGreg Roach protected $treesize; 64241a1636SGreg Roach 65241a1636SGreg Roach /** @var stdClass Determine which arrows to use for each of the chart orientations */ 66241a1636SGreg Roach protected $arrows; 67241a1636SGreg Roach 68241a1636SGreg Roach /** @var Individual */ 69241a1636SGreg Roach protected $root; 70241a1636SGreg Roach 71241a1636SGreg Roach /** 72241a1636SGreg Roach * Next and previous generation arrow size in pixels. 73241a1636SGreg Roach */ 74241a1636SGreg Roach protected const ARROW_SIZE = 22; 75241a1636SGreg Roach 76168ff6f3Sric2016 /** 77168ff6f3Sric2016 * How should this module be labelled on tabs, menus, etc.? 78168ff6f3Sric2016 * 79168ff6f3Sric2016 * @return string 80168ff6f3Sric2016 */ 8149a243cbSGreg Roach public function title(): string 82c1010edaSGreg Roach { 83bbb76c12SGreg Roach /* I18N: Name of a module/chart */ 84bbb76c12SGreg Roach return I18N::translate('Pedigree'); 85168ff6f3Sric2016 } 86168ff6f3Sric2016 87168ff6f3Sric2016 /** 88168ff6f3Sric2016 * A sentence describing what this module does. 89168ff6f3Sric2016 * 90168ff6f3Sric2016 * @return string 91168ff6f3Sric2016 */ 9249a243cbSGreg Roach public function description(): string 93c1010edaSGreg Roach { 94bbb76c12SGreg Roach /* I18N: Description of the “PedigreeChart” module */ 95bbb76c12SGreg Roach return I18N::translate('A chart of an individual’s ancestors, formatted as a tree.'); 96168ff6f3Sric2016 } 97168ff6f3Sric2016 98168ff6f3Sric2016 /** 99377a2979SGreg Roach * CSS class for the URL. 100377a2979SGreg Roach * 101377a2979SGreg Roach * @return string 102377a2979SGreg Roach */ 103377a2979SGreg Roach public function chartMenuClass(): string 104377a2979SGreg Roach { 105377a2979SGreg Roach return 'menu-chart-pedigree'; 106377a2979SGreg Roach } 107377a2979SGreg Roach 108377a2979SGreg Roach /** 1094eb71cfaSGreg Roach * Return a menu item for this chart - for use in individual boxes. 1104eb71cfaSGreg Roach * 11160bc3e3fSGreg Roach * @param Individual $individual 11260bc3e3fSGreg Roach * 1134eb71cfaSGreg Roach * @return Menu|null 1144eb71cfaSGreg Roach */ 115377a2979SGreg Roach public function chartBoxMenu(Individual $individual): ?Menu 116c1010edaSGreg Roach { 117e6562982SGreg Roach return $this->chartMenu($individual); 118e6562982SGreg Roach } 119e6562982SGreg Roach 120e6562982SGreg Roach /** 121e6562982SGreg Roach * The title for a specific instance of this chart. 122e6562982SGreg Roach * 123e6562982SGreg Roach * @param Individual $individual 124e6562982SGreg Roach * 125e6562982SGreg Roach * @return string 126e6562982SGreg Roach */ 127e6562982SGreg Roach public function chartTitle(Individual $individual): string 128e6562982SGreg Roach { 129e6562982SGreg Roach /* I18N: %s is an individual’s name */ 130e6562982SGreg Roach return I18N::translate('Pedigree tree of %s', $individual->getFullName()); 131e6562982SGreg Roach } 132e6562982SGreg Roach 133e6562982SGreg Roach /** 134241a1636SGreg Roach * A form to request the chart parameters. 135e6562982SGreg Roach * 136241a1636SGreg Roach * @param Request $request 137241a1636SGreg Roach * @param Tree $tree 138241a1636SGreg Roach * @param ChartService $chart_service 139241a1636SGreg Roach * 140241a1636SGreg Roach * @return Response 141241a1636SGreg Roach */ 142241a1636SGreg Roach public function getChartAction(Request $request, Tree $tree, ChartService $chart_service): Response 143241a1636SGreg Roach { 144*9b5537c3SGreg Roach $ajax = (bool) $request->get('ajax'); 145241a1636SGreg Roach $xref = $request->get('xref', ''); 146241a1636SGreg Roach $individual = Individual::getInstance($xref, $tree); 147241a1636SGreg Roach 148241a1636SGreg Roach Auth::checkIndividualAccess($individual); 149241a1636SGreg Roach 150f88af04cSGreg Roach $orientation = (int) $request->get('orientation', static::DEFAULT_ORIENTATION); 151f88af04cSGreg Roach $generations = (int) $request->get('generations', static::DEFAULT_GENERATIONS); 152241a1636SGreg Roach 153f88af04cSGreg Roach $generations = min(static::MAX_GENERATIONS, $generations); 154f88af04cSGreg Roach $generations = max(static::MIN_GENERATIONS, $generations); 155241a1636SGreg Roach 156241a1636SGreg Roach $generation_options = $this->generationOptions(); 157241a1636SGreg Roach 158*9b5537c3SGreg Roach if ($ajax) { 159241a1636SGreg Roach return $this->chart($individual, $generations, $orientation, $chart_service); 160241a1636SGreg Roach } 161241a1636SGreg Roach 162241a1636SGreg Roach $ajax_url = $this->chartUrl($individual, [ 163*9b5537c3SGreg Roach 'ajax' => true, 164241a1636SGreg Roach 'generations' => $generations, 165241a1636SGreg Roach 'orientation' => $orientation, 166241a1636SGreg Roach ]); 167241a1636SGreg Roach 168*9b5537c3SGreg Roach return $this->viewResponse('modules/pedigree-chart/page', [ 169241a1636SGreg Roach 'ajax_url' => $ajax_url, 170241a1636SGreg Roach 'generations' => $generations, 171241a1636SGreg Roach 'generation_options' => $generation_options, 172241a1636SGreg Roach 'individual' => $individual, 173241a1636SGreg Roach 'module_name' => $this->name(), 174241a1636SGreg Roach 'orientation' => $orientation, 175241a1636SGreg Roach 'orientations' => $this->orientations(), 176241a1636SGreg Roach 'title' => $this->chartTitle($individual), 177241a1636SGreg Roach ]); 178241a1636SGreg Roach } 179241a1636SGreg Roach 180241a1636SGreg Roach /** 181e6562982SGreg Roach * @param Individual $individual 182241a1636SGreg Roach * @param int $generations 183241a1636SGreg Roach * @param int $orientation 184241a1636SGreg Roach * @param ChartService $chart_service 185241a1636SGreg Roach * 186241a1636SGreg Roach * @return Response 187241a1636SGreg Roach */ 188241a1636SGreg Roach public function chart(Individual $individual, int $generations, int $orientation, ChartService $chart_service): Response 189241a1636SGreg Roach { 190241a1636SGreg Roach $bxspacing = Theme::theme()->parameter('chart-spacing-x'); 191241a1636SGreg Roach $byspacing = Theme::theme()->parameter('chart-spacing-y'); 192241a1636SGreg Roach $curgen = 1; // Track which generation the algorithm is currently working on 193241a1636SGreg Roach $addoffset = []; 194241a1636SGreg Roach 195241a1636SGreg Roach $this->root = $individual; 196241a1636SGreg Roach 197241a1636SGreg Roach $this->treesize = (2 ** $generations) - 1; 198241a1636SGreg Roach 199241a1636SGreg Roach $this->nodes = []; 200241a1636SGreg Roach 201241a1636SGreg Roach $ancestors = $chart_service->sosaStradonitzAncestors($individual, $generations); 202241a1636SGreg Roach 203241a1636SGreg Roach // $ancestors starts array at index 1 we need to start at 0 204241a1636SGreg Roach for ($i = 0; $i < $this->treesize; ++$i) { 205241a1636SGreg Roach $this->nodes[$i] = [ 206241a1636SGreg Roach 'indi' => $ancestors->get($i + 1), 207241a1636SGreg Roach 'x' => 0, 208241a1636SGreg Roach 'y' => 0, 209241a1636SGreg Roach ]; 210241a1636SGreg Roach } 211241a1636SGreg Roach 212241a1636SGreg Roach // Are there ancestors beyond the bounds of this chart 213241a1636SGreg Roach $chart_has_ancestors = false; 214241a1636SGreg Roach 215241a1636SGreg Roach // Check earliest generation for any ancestors 216241a1636SGreg Roach for ($i = (int) ($this->treesize / 2); $i < $this->treesize; $i++) { 217241a1636SGreg Roach $chart_has_ancestors = $chart_has_ancestors || ($this->nodes[$i]['indi'] && $this->nodes[$i]['indi']->getChildFamilies()); 218241a1636SGreg Roach } 219241a1636SGreg Roach 220241a1636SGreg Roach $this->arrows = new stdClass(); 221241a1636SGreg Roach switch ($orientation) { 222241a1636SGreg Roach default: 223f88af04cSGreg Roach case static::PORTRAIT: 224f88af04cSGreg Roach case static::LANDSCAPE: 225241a1636SGreg Roach $this->arrows->prevGen = 'fas fa-arrow-end wt-icon-arrow-end'; 226241a1636SGreg Roach $this->arrows->menu = 'fas fa-arrow-start wt-icon-arrow-start'; 227f88af04cSGreg Roach $addoffset['x'] = $chart_has_ancestors ? static::ARROW_SIZE : 0; 228241a1636SGreg Roach $addoffset['y'] = 0; 229241a1636SGreg Roach break; 230241a1636SGreg Roach 231f88af04cSGreg Roach case static::OLDEST_AT_TOP: 232241a1636SGreg Roach $this->arrows->prevGen = 'fas fa-arrow-up wt-icon-arrow-up'; 233241a1636SGreg Roach $this->arrows->menu = 'fas fa-arrow-down wt-icon-arrow-down'; 234241a1636SGreg Roach $addoffset['x'] = 0; 235f88af04cSGreg Roach $addoffset['y'] = $this->root->getSpouseFamilies() ? static::ARROW_SIZE : 0; 236241a1636SGreg Roach break; 237241a1636SGreg Roach 238f88af04cSGreg Roach case static::OLDEST_AT_BOTTOM: 239241a1636SGreg Roach $this->arrows->prevGen = 'fas fa-arrow-down wt-icon-arrow-down'; 240241a1636SGreg Roach $this->arrows->menu = 'fas fa-arrow-up wt-icon-arrow-up'; 241241a1636SGreg Roach $addoffset['x'] = 0; 242f88af04cSGreg Roach $addoffset['y'] = $chart_has_ancestors ? static::ARROW_SIZE : 0; 243241a1636SGreg Roach break; 244241a1636SGreg Roach } 245241a1636SGreg Roach 246241a1636SGreg Roach // Create and position the DIV layers for the pedigree tree 247241a1636SGreg Roach for ($i = ($this->treesize - 1); $i >= 0; $i--) { 248241a1636SGreg Roach // Check to see if we have moved to the next generation 249241a1636SGreg Roach if ($i < (int) ($this->treesize / (2 ** $curgen))) { 250241a1636SGreg Roach $curgen++; 251241a1636SGreg Roach } 252241a1636SGreg Roach 253241a1636SGreg Roach // Box position in current generation 254241a1636SGreg Roach $boxpos = $i - (2 ** ($this->generations - $curgen)); 255241a1636SGreg Roach // Offset multiple for current generation 256f88af04cSGreg Roach if ($orientation < static::OLDEST_AT_TOP) { 257241a1636SGreg Roach $genoffset = 2 ** ($curgen - $orientation); 258241a1636SGreg Roach $boxspacing = Theme::theme()->parameter('chart-box-y') + $byspacing; 259241a1636SGreg Roach } else { 260241a1636SGreg Roach $genoffset = 2 ** ($curgen - 1); 261241a1636SGreg Roach $boxspacing = Theme::theme()->parameter('chart-box-x') + $byspacing; 262241a1636SGreg Roach } 263241a1636SGreg Roach // Calculate the yoffset position in the generation put child between parents 264241a1636SGreg Roach $yoffset = ($boxpos * ($boxspacing * $genoffset)) + (($boxspacing / 2) * $genoffset) + ($boxspacing * $genoffset); 265241a1636SGreg Roach 266241a1636SGreg Roach // Calculate the xoffset 267241a1636SGreg Roach switch ($orientation) { 268241a1636SGreg Roach default: 269f88af04cSGreg Roach case static::PORTRAIT: 270241a1636SGreg Roach $xoffset = ($this->generations - $curgen) * ((Theme::theme()->parameter('chart-box-x') + $bxspacing) / 1.8); 271241a1636SGreg Roach if (!$i && $this->root->getSpouseFamilies()) { 272f88af04cSGreg Roach $xoffset -= static::ARROW_SIZE; 273241a1636SGreg Roach } 274241a1636SGreg Roach // Compact the tree 275241a1636SGreg Roach if ($curgen < $this->generations) { 276241a1636SGreg Roach if ($i % 2 == 0) { 277241a1636SGreg Roach $yoffset = $yoffset - (($boxspacing / 2) * ($curgen - 1)); 278241a1636SGreg Roach } else { 279241a1636SGreg Roach $yoffset = $yoffset + (($boxspacing / 2) * ($curgen - 1)); 280241a1636SGreg Roach } 281241a1636SGreg Roach $parent = (int) (($i - 1) / 2); 282241a1636SGreg Roach $pgen = $curgen; 283241a1636SGreg Roach while ($parent > 0) { 284241a1636SGreg Roach if ($parent % 2 == 0) { 285241a1636SGreg Roach $yoffset = $yoffset - (($boxspacing / 2) * $pgen); 286241a1636SGreg Roach } else { 287241a1636SGreg Roach $yoffset = $yoffset + (($boxspacing / 2) * $pgen); 288241a1636SGreg Roach } 289241a1636SGreg Roach $pgen++; 290241a1636SGreg Roach if ($pgen > 3) { 291241a1636SGreg Roach $temp = 0; 292241a1636SGreg Roach for ($j = 1; $j < ($pgen - 2); $j++) { 293241a1636SGreg Roach $temp += ((2 ** $j) - 1); 294241a1636SGreg Roach } 295241a1636SGreg Roach if ($parent % 2 == 0) { 296241a1636SGreg Roach $yoffset = $yoffset - (($boxspacing / 2) * $temp); 297241a1636SGreg Roach } else { 298241a1636SGreg Roach $yoffset = $yoffset + (($boxspacing / 2) * $temp); 299241a1636SGreg Roach } 300241a1636SGreg Roach } 301241a1636SGreg Roach $parent = (int) (($parent - 1) / 2); 302241a1636SGreg Roach } 303241a1636SGreg Roach if ($curgen > 3) { 304241a1636SGreg Roach $temp = 0; 305241a1636SGreg Roach for ($j = 1; $j < ($curgen - 2); $j++) { 306241a1636SGreg Roach $temp += ((2 ** $j) - 1); 307241a1636SGreg Roach } 308241a1636SGreg Roach if ($i % 2 == 0) { 309241a1636SGreg Roach $yoffset = $yoffset - (($boxspacing / 2) * $temp); 310241a1636SGreg Roach } else { 311241a1636SGreg Roach $yoffset = $yoffset + (($boxspacing / 2) * $temp); 312241a1636SGreg Roach } 313241a1636SGreg Roach } 314241a1636SGreg Roach } 315241a1636SGreg Roach $yoffset -= (($boxspacing / 2) * (2 ** ($this->generations - 2)) - ($boxspacing / 2)); 316241a1636SGreg Roach break; 317241a1636SGreg Roach 318f88af04cSGreg Roach case static::LANDSCAPE: 319241a1636SGreg Roach $xoffset = ($this->generations - $curgen) * (Theme::theme()->parameter('chart-box-x') + $bxspacing); 320241a1636SGreg Roach if ($curgen == 1) { 321241a1636SGreg Roach $xoffset += 10; 322241a1636SGreg Roach } 323241a1636SGreg Roach break; 324241a1636SGreg Roach 325f88af04cSGreg Roach case static::OLDEST_AT_TOP: 326241a1636SGreg Roach // Swap x & y offsets as chart is rotated 327241a1636SGreg Roach $xoffset = $yoffset; 328241a1636SGreg Roach $yoffset = $curgen * (Theme::theme()->parameter('chart-box-y') + ($byspacing * 4)); 329241a1636SGreg Roach break; 330241a1636SGreg Roach 331f88af04cSGreg Roach case static::OLDEST_AT_BOTTOM: 332241a1636SGreg Roach // Swap x & y offsets as chart is rotated 333241a1636SGreg Roach $xoffset = $yoffset; 334241a1636SGreg Roach $yoffset = ($this->generations - $curgen) * (Theme::theme()->parameter('chart-box-y') + ($byspacing * 2)); 335241a1636SGreg Roach if ($i && $this->root->getSpouseFamilies()) { 336f88af04cSGreg Roach $yoffset += static::ARROW_SIZE; 337241a1636SGreg Roach } 338241a1636SGreg Roach break; 339241a1636SGreg Roach } 340241a1636SGreg Roach $this->nodes[$i]['x'] = (int) $xoffset; 341241a1636SGreg Roach $this->nodes[$i]['y'] = (int) $yoffset; 342241a1636SGreg Roach } 343241a1636SGreg Roach 344241a1636SGreg Roach // Find the minimum x & y offsets and deduct that number from 345241a1636SGreg Roach // each value in the array so that offsets start from zero 346241a1636SGreg Roach $min_xoffset = min(array_map(function (array $item): int { 347241a1636SGreg Roach return $item['x']; 348241a1636SGreg Roach }, $this->nodes)); 349241a1636SGreg Roach $min_yoffset = min(array_map(function (array $item): int { 350241a1636SGreg Roach return $item['y']; 351241a1636SGreg Roach }, $this->nodes)); 352241a1636SGreg Roach 353241a1636SGreg Roach array_walk($this->nodes, function (&$item) use ($min_xoffset, $min_yoffset) { 354241a1636SGreg Roach $item['x'] -= $min_xoffset; 355241a1636SGreg Roach $item['y'] -= $min_yoffset; 356241a1636SGreg Roach }); 357241a1636SGreg Roach 358241a1636SGreg Roach // Calculate chart & canvas dimensions 359241a1636SGreg Roach $max_xoffset = max(array_map(function ($item) { 360241a1636SGreg Roach return $item['x']; 361241a1636SGreg Roach }, $this->nodes)); 362241a1636SGreg Roach $max_yoffset = max(array_map(function ($item) { 363241a1636SGreg Roach return $item['y']; 364241a1636SGreg Roach }, $this->nodes)); 365241a1636SGreg Roach 366241a1636SGreg Roach $canvas_width = $max_xoffset + $bxspacing + Theme::theme()->parameter('chart-box-x') + $addoffset['x']; 367241a1636SGreg Roach $canvas_height = $max_yoffset + $byspacing + Theme::theme()->parameter('chart-box-y') + $addoffset['y']; 368241a1636SGreg Roach $posn = I18N::direction() === 'rtl' ? 'right' : 'left'; 369241a1636SGreg Roach $last_gen_start = (int) floor($this->treesize / 2); 370f88af04cSGreg Roach if ($orientation === static::OLDEST_AT_TOP || $orientation === static::OLDEST_AT_BOTTOM) { 371241a1636SGreg Roach $flex_direction = ' flex-column'; 372241a1636SGreg Roach } else { 373241a1636SGreg Roach $flex_direction = ''; 374241a1636SGreg Roach } 375241a1636SGreg Roach 376241a1636SGreg Roach foreach ($this->nodes as $n => $node) { 377241a1636SGreg Roach if ($n >= $last_gen_start) { 378241a1636SGreg Roach $this->nodes[$n]['previous_gen'] = $this->gotoPreviousGen($n, $generations, $orientation, $chart_has_ancestors); 379241a1636SGreg Roach } else { 380241a1636SGreg Roach $this->nodes[$n]['previous_gen'] = ''; 381241a1636SGreg Roach } 382241a1636SGreg Roach } 383241a1636SGreg Roach 384241a1636SGreg Roach $html = view('modules/pedigree-chart/chart', [ 385241a1636SGreg Roach 'canvas_height' => $canvas_height, 386241a1636SGreg Roach 'canvas_width' => $canvas_width, 387241a1636SGreg Roach 'child_menu' => $this->getMenu($individual, $generations, $orientation), 388241a1636SGreg Roach 'flex_direction' => $flex_direction, 389241a1636SGreg Roach 'last_gen_start' => $last_gen_start, 390241a1636SGreg Roach 'orientation' => $orientation, 391241a1636SGreg Roach 'nodes' => $this->nodes, 392f88af04cSGreg Roach 'landscape' => static::LANDSCAPE, 393f88af04cSGreg Roach 'oldest_at_top' => static::OLDEST_AT_TOP, 394f88af04cSGreg Roach 'oldest_at_bottom' => static::OLDEST_AT_BOTTOM, 395f88af04cSGreg Roach 'portrait' => static::PORTRAIT, 396241a1636SGreg Roach 'posn' => $posn, 397241a1636SGreg Roach ]); 398241a1636SGreg Roach 399241a1636SGreg Roach return new Response($html); 400241a1636SGreg Roach } 401241a1636SGreg Roach 402241a1636SGreg Roach /** 403241a1636SGreg Roach * Build a menu for the chart root individual 404241a1636SGreg Roach * 405241a1636SGreg Roach * @param Individual $root 406241a1636SGreg Roach * @param int $generations 407241a1636SGreg Roach * @param int $orientation 408e6562982SGreg Roach * 409e6562982SGreg Roach * @return string 410e6562982SGreg Roach */ 411241a1636SGreg Roach public function getMenu(Individual $root, int $generations, int $orientation): string 412e6562982SGreg Roach { 413241a1636SGreg Roach $families = $root->getSpouseFamilies(); 414241a1636SGreg Roach $html = ''; 415241a1636SGreg Roach if (!empty($families)) { 416241a1636SGreg Roach $html = sprintf('<div id="childarrow"><a href="#" class="menuselect %s"></a><div id="childbox-pedigree">', $this->arrows->menu); 417241a1636SGreg Roach 418241a1636SGreg Roach foreach ($families as $family) { 419241a1636SGreg Roach $html .= '<span class="name1">' . I18N::translate('Family') . '</span>'; 420241a1636SGreg Roach $spouse = $family->getSpouse($root); 421241a1636SGreg Roach if ($spouse) { 422241a1636SGreg Roach $html .= '<a class="name1" href="' . e($this->chartUrl($spouse, ['generations' => $generations, 'orientation' => $orientation])) . '">' . $spouse->getFullName() . '</a>'; 423241a1636SGreg Roach } 424241a1636SGreg Roach $children = $family->getChildren(); 425241a1636SGreg Roach foreach ($children as $sibling) { 426241a1636SGreg Roach $html .= '<a class="name1" href="' . e($this->chartUrl($sibling, ['generations' => $generations, 'orientation' => $orientation])) . '">' . $sibling->getFullName() . '</a>'; 427241a1636SGreg Roach } 428241a1636SGreg Roach } 429241a1636SGreg Roach 430241a1636SGreg Roach foreach ($root->getChildFamilies() as $family) { 431241a1636SGreg Roach $siblings = array_filter($family->getChildren(), function (Individual $item) use ($root): bool { 432241a1636SGreg Roach return $root->xref() !== $item->xref(); 433241a1636SGreg Roach }); 434241a1636SGreg Roach if (!empty($siblings)) { 435241a1636SGreg Roach $html .= '<span class="name1">'; 436241a1636SGreg Roach $html .= count($siblings) > 1 ? I18N::translate('Siblings') : I18N::translate('Sibling'); 437241a1636SGreg Roach $html .= '</span>'; 438241a1636SGreg Roach foreach ($siblings as $sibling) { 439241a1636SGreg Roach $html .= '<a class="name1" href="' . e($this->chartUrl($sibling, ['generations' => $generations, 'orientation' => $orientation])) . '">' . $sibling->getFullName() . '</a>'; 440241a1636SGreg Roach } 441241a1636SGreg Roach } 442241a1636SGreg Roach } 443241a1636SGreg Roach $html .= '</div></div>'; 444241a1636SGreg Roach } 445241a1636SGreg Roach 446241a1636SGreg Roach return $html; 447241a1636SGreg Roach } 448241a1636SGreg Roach 449241a1636SGreg Roach /** 450241a1636SGreg Roach * Function gotoPreviousGen 451241a1636SGreg Roach * Create a link to generate a new chart based on the correct parent of the individual with this index 452241a1636SGreg Roach * 453241a1636SGreg Roach * @param int $index 454241a1636SGreg Roach * @param int $generations 455241a1636SGreg Roach * @param int $orientation 456241a1636SGreg Roach * @param bool $chart_has_ancestors 457241a1636SGreg Roach * 458241a1636SGreg Roach * @return string 459241a1636SGreg Roach */ 460241a1636SGreg Roach public function gotoPreviousGen(int $index, int $generations, int $orientation, bool $chart_has_ancestors): string 461241a1636SGreg Roach { 462241a1636SGreg Roach $html = ''; 463241a1636SGreg Roach if ($chart_has_ancestors) { 464241a1636SGreg Roach if ($this->nodes[$index]['indi'] && $this->nodes[$index]['indi']->getChildFamilies()) { 465241a1636SGreg Roach $html .= '<div class="ancestorarrow">'; 466241a1636SGreg Roach $rootParentId = 1; 467241a1636SGreg Roach if ($index > (int) ($this->treesize / 2) + (int) ($this->treesize / 4)) { 468241a1636SGreg Roach $rootParentId++; 469241a1636SGreg Roach } 470241a1636SGreg Roach $html .= '<a class="' . $this->arrows->prevGen . '" href="' . e($this->chartUrl($this->nodes[$rootParentId]['indi'], ['generations' => $generations, 'orientation' => $orientation])) . '"></a>'; 471241a1636SGreg Roach $html .= '</div>'; 472241a1636SGreg Roach } else { 473241a1636SGreg Roach $html .= '<div class="spacer"></div>'; 474241a1636SGreg Roach } 475241a1636SGreg Roach } 476241a1636SGreg Roach 477241a1636SGreg Roach return $html; 478241a1636SGreg Roach } 479241a1636SGreg Roach 480241a1636SGreg Roach /** 481241a1636SGreg Roach * @return string[] 482241a1636SGreg Roach */ 483241a1636SGreg Roach protected function generationOptions(): array 484241a1636SGreg Roach { 485f88af04cSGreg Roach return FunctionsEdit::numericOptions(range(static::MIN_GENERATIONS, static::MAX_GENERATIONS)); 486241a1636SGreg Roach } 487241a1636SGreg Roach 488241a1636SGreg Roach /** 489241a1636SGreg Roach * @return string[] 490241a1636SGreg Roach */ 491241a1636SGreg Roach protected function orientations(): array 492241a1636SGreg Roach { 493241a1636SGreg Roach return [ 494241a1636SGreg Roach 0 => I18N::translate('Portrait'), 495241a1636SGreg Roach 1 => I18N::translate('Landscape'), 496241a1636SGreg Roach 2 => I18N::translate('Oldest at top'), 497241a1636SGreg Roach 3 => I18N::translate('Oldest at bottom'), 498241a1636SGreg Roach ]; 499e6562982SGreg Roach } 500168ff6f3Sric2016} 501