diff options
| author | Greg Roach <fisharebest@webtrees.net> | 2019-01-22 17:51:15 +0000 |
|---|---|---|
| committer | Greg Roach <fisharebest@webtrees.net> | 2019-01-22 17:51:15 +0000 |
| commit | 241a1636dee0487eb56818cbf3b6a79ceaf02090 (patch) | |
| tree | 2d1b5d6966c78eabe9cd047bb623db4e285686c8 | |
| parent | 26684e686fb5ab50ecb57e7e6c6a0a55852d2203 (diff) | |
| download | webtrees-241a1636dee0487eb56818cbf3b6a79ceaf02090.tar.gz webtrees-241a1636dee0487eb56818cbf3b6a79ceaf02090.tar.bz2 webtrees-241a1636dee0487eb56818cbf3b6a79ceaf02090.zip | |
Merge chart controllers into chart modules
| -rw-r--r-- | app/Http/Controllers/PedigreeChartController.php | 475 | ||||
| -rw-r--r-- | app/Module/PedigreeChartModule.php | 413 | ||||
| -rw-r--r-- | resources/views/modules/pedigree-chart/chart-page.phtml (renamed from resources/views/pedigree-page.phtml) | 6 | ||||
| -rw-r--r-- | resources/views/modules/pedigree-chart/chart.phtml (renamed from resources/views/pedigree-chart.phtml) | 0 | ||||
| -rw-r--r-- | routes/web.php | 2 |
5 files changed, 409 insertions, 487 deletions
diff --git a/app/Http/Controllers/PedigreeChartController.php b/app/Http/Controllers/PedigreeChartController.php deleted file mode 100644 index ae0fff73ea..0000000000 --- a/app/Http/Controllers/PedigreeChartController.php +++ /dev/null @@ -1,475 +0,0 @@ -<?php -/** - * webtrees: online genealogy - * Copyright (C) 2019 webtrees development team - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ -declare(strict_types=1); - -namespace Fisharebest\Webtrees\Http\Controllers; - -use DomainException; -use Fisharebest\Webtrees\Auth; -use Fisharebest\Webtrees\Functions\FunctionsEdit; -use Fisharebest\Webtrees\I18N; -use Fisharebest\Webtrees\Individual; -use Fisharebest\Webtrees\Module\PedigreeChartModule; -use Fisharebest\Webtrees\Services\ChartService; -use Fisharebest\Webtrees\Theme; -use Fisharebest\Webtrees\Tree; -use stdClass; -use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpFoundation\Response; - -/** - * A chart of direct-line ancestors in a tree layout. - */ -class PedigreeChartController extends AbstractBaseController -{ - // With more than 8 generations, we run out of pixels on the <canvas> - private const MAX_GENERATIONS = 8; - private const MIN_GENERATIONS = 2; - - private const DEFAULT_GENERATIONS = 4; - - /** - * Chart orientation codes - * Dont change them! the offset calculations rely on this order - */ - public const PORTRAIT = 0; - public const LANDSCAPE = 1; - public const OLDEST_AT_TOP = 2; - public const OLDEST_AT_BOTTOM = 3; - - private const DEFAULT_ORIENTATION = self::LANDSCAPE; - - /** @var int Selected chart layout */ - private $orientation; - - /** @var int Number of generation to display */ - private $generations; - - /** @var array data pertaining to each chart node */ - private $nodes = []; - - /** @var int Number of nodes in the chart */ - private $treesize; - - /** @var bool Are there ancestors beyond the bounds of this chart */ - private $chartHasAncestors = false; - - /** @var stdClass Determine which arrows to use for each of the chart orientations */ - private $arrows; - - /** @var Individual */ - private $root; - - /** - * Next and previous generation arrow size - */ - private const ARROW_SIZE = 22; //pixels - - /** - * A form to request the chart parameters. - * - * @param Request $request - * @param Tree $tree - * - * @return Response - */ - public function page(Request $request, Tree $tree): Response - { - $this->checkModuleIsActive($tree, PedigreeChartModule::class); - - $xref = $request->get('xref', ''); - $individual = Individual::getInstance($xref, $tree); - - Auth::checkIndividualAccess($individual); - - $orientation = (int) $request->get('orientation', self::DEFAULT_ORIENTATION); - $generations = (int) $request->get('generations', self::DEFAULT_GENERATIONS); - - $generations = min(self::MAX_GENERATIONS, $generations); - $generations = max(self::MIN_GENERATIONS, $generations); - - $orientations = $this->orientations(); - $generation_options = $this->generationOptions(); - - /* I18N: %s is an individual’s name */ - $title = I18N::translate('Pedigree tree of %s', $individual->getFullName()); - - return $this->viewResponse('pedigree-page', [ - 'generations' => $generations, - 'generation_options' => $generation_options, - 'individual' => $individual, - 'orientation' => $orientation, - 'orientations' => $orientations, - 'title' => $title, - ]); - } - - /** - * @param Request $request - * @param Tree $tree - * @param ChartService $chart_service - * - * @return Response - */ - public function chart(Request $request, Tree $tree, ChartService $chart_service): Response - { - $this->checkModuleIsActive($tree, PedigreeChartModule::class); - - $xref = $request->get('xref', ''); - $individual = Individual::getInstance($xref, $tree); - - Auth::checkIndividualAccess($individual); - - $this->orientation = (int) $request->get('orientation'); - $this->generations = (int) $request->get('generations'); - $bxspacing = Theme::theme()->parameter('chart-spacing-x'); - $byspacing = Theme::theme()->parameter('chart-spacing-y'); - $curgen = 1; // -- track which generation the algorithm is currently working on - $addoffset = []; - - // With more than 8 generations, we run out of pixels on the <canvas> - if ($this->generations > 8) { - $this->generations = 8; - } - - $this->root = $individual; - - $this->treesize = (2 ** $this->generations) - 1; - - $this->nodes = []; - - $ancestors = $chart_service->sosaStradonitzAncestors($individual, $this->generations); - - // $ancestors starts array at index 1 we need to start at 0 - for ($i = 0; $i < $this->treesize; ++$i) { - $this->nodes[$i] = [ - 'indi' => $ancestors->get($i+1), - 'x' => 0, - 'y' => 0, - ]; - } - - //check earliest generation for any ancestors - for ($i = (int) ($this->treesize / 2); $i < $this->treesize; $i++) { - $this->chartHasAncestors = $this->chartHasAncestors || ($this->nodes[$i]['indi'] && $this->nodes[$i]['indi']->getChildFamilies()); - } - - $this->arrows = new stdClass(); - switch ($this->orientation) { - case self::PORTRAIT: - case self::LANDSCAPE: - $this->arrows->prevGen = 'fas fa-arrow-end wt-icon-arrow-end'; - $this->arrows->menu = 'fas fa-arrow-start wt-icon-arrow-start'; - $addoffset['x'] = $this->chartHasAncestors ? self::ARROW_SIZE : 0; - $addoffset['y'] = 0; - break; - - case self::OLDEST_AT_TOP: - $this->arrows->prevGen = 'fas fa-arrow-up wt-icon-arrow-up'; - $this->arrows->menu = 'fas fa-arrow-down wt-icon-arrow-down'; - $addoffset['x'] = 0; - $addoffset['y'] = $this->root->getSpouseFamilies() ? self::ARROW_SIZE : 0; - break; - - case self::OLDEST_AT_BOTTOM: - $this->arrows->prevGen = 'fas fa-arrow-down wt-icon-arrow-down'; - $this->arrows->menu = 'fas fa-arrow-up wt-icon-arrow-up'; - $addoffset['x'] = 0; - $addoffset['y'] = $this->chartHasAncestors ? self::ARROW_SIZE : 0; - break; - - default: - throw new DomainException('Invalid orientation: ' . $this->orientation); - } - - // -- this next section will create and position the DIV layers for the pedigree tree - // -- loop through all of IDs in the array from last to first - // -- calculating the box positions - - for ($i = ($this->treesize - 1); $i >= 0; $i--) { - // -- check to see if we have moved to the next generation - if ($i < (int) ($this->treesize / (2 ** $curgen))) { - $curgen++; - } - - // -- box position in current generation - $boxpos = $i - (2 ** ($this->generations - $curgen)); - // -- offset multiple for current generation - if ($this->orientation < self::OLDEST_AT_TOP) { - $genoffset = 2 ** ($curgen - $this->orientation); - $boxspacing = Theme::theme()->parameter('chart-box-y') + $byspacing; - } else { - $genoffset = 2 ** ($curgen - 1); - $boxspacing = Theme::theme()->parameter('chart-box-x') + $byspacing; - } - // -- calculate the yoffset position in the generation put child between parents - $yoffset = ($boxpos * ($boxspacing * $genoffset)) + (($boxspacing / 2) * $genoffset) + ($boxspacing * $genoffset); - - // -- calculate the xoffset - switch ($this->orientation) { - case self::PORTRAIT: - $xoffset = ($this->generations - $curgen) * ((Theme::theme()->parameter('chart-box-x') + $bxspacing) / 1.8); - if (!$i && $this->root->getSpouseFamilies()) { - $xoffset -= self::ARROW_SIZE; - } - // -- compact the tree - if ($curgen < $this->generations) { - if ($i % 2 == 0) { - $yoffset = $yoffset - (($boxspacing / 2) * ($curgen - 1)); - } else { - $yoffset = $yoffset + (($boxspacing / 2) * ($curgen - 1)); - } - $parent = (int) (($i - 1) / 2); - $pgen = $curgen; - while ($parent > 0) { - if ($parent % 2 == 0) { - $yoffset = $yoffset - (($boxspacing / 2) * $pgen); - } else { - $yoffset = $yoffset + (($boxspacing / 2) * $pgen); - } - $pgen++; - if ($pgen > 3) { - $temp = 0; - for ($j = 1; $j < ($pgen - 2); $j++) { - $temp += ((2 ** $j) - 1); - } - if ($parent % 2 == 0) { - $yoffset = $yoffset - (($boxspacing / 2) * $temp); - } else { - $yoffset = $yoffset + (($boxspacing / 2) * $temp); - } - } - $parent = (int) (($parent - 1) / 2); - } - if ($curgen > 3) { - $temp = 0; - for ($j = 1; $j < ($curgen - 2); $j++) { - $temp += ((2 ** $j) - 1); - } - if ($i % 2 == 0) { - $yoffset = $yoffset - (($boxspacing / 2) * $temp); - } else { - $yoffset = $yoffset + (($boxspacing / 2) * $temp); - } - } - } - $yoffset -= (($boxspacing / 2) * (2 ** ($this->generations - 2)) - ($boxspacing / 2)); - break; - case self::LANDSCAPE: - $xoffset = ($this->generations - $curgen) * (Theme::theme()->parameter('chart-box-x') + $bxspacing); - if ($curgen == 1) { - $xoffset += 10; - } - break; - case self::OLDEST_AT_TOP: - //swap x & y offsets as chart is rotated - $xoffset = $yoffset; - $yoffset = $curgen * (Theme::theme()->parameter('chart-box-y') + ($byspacing * 4)); - break; - case self::OLDEST_AT_BOTTOM: - //swap x & y offsets as chart is rotated - $xoffset = $yoffset; - $yoffset = ($this->generations - $curgen) * (Theme::theme()->parameter('chart-box-y') + ($byspacing * 2)); - if ($i && $this->root->getSpouseFamilies()) { - $yoffset += self::ARROW_SIZE; - } - break; - - default: - throw new DomainException('Invalid orientation: ' . $this->orientation); - } - $this->nodes[$i]['x'] = (int) $xoffset; - $this->nodes[$i]['y'] = (int) $yoffset; - } - - // find the minimum x & y offsets and deduct that number from - // each value in the array so that offsets start from zero - - $min_xoffset = min(array_map(function (array $item): int { - return $item['x']; - }, $this->nodes)); - $min_yoffset = min(array_map(function (array $item): int { - return $item['y']; - }, $this->nodes)); - - array_walk($this->nodes, function (&$item) use ($min_xoffset, $min_yoffset) { - $item['x'] -= $min_xoffset; - $item['y'] -= $min_yoffset; - }); - - // calculate chart & canvas dimensions - $max_xoffset = max(array_map(function ($item) { - return $item['x']; - }, $this->nodes)); - $max_yoffset = max(array_map(function ($item) { - return $item['y']; - }, $this->nodes)); - - $canvas_width = $max_xoffset + $bxspacing + Theme::theme()->parameter('chart-box-x') + $addoffset['x']; - $canvas_height = $max_yoffset + $byspacing + Theme::theme()->parameter('chart-box-y') + $addoffset['y']; - $posn = I18N::direction() === 'rtl' ? 'right' : 'left'; - $last_gen_start = (int) floor($this->treesize / 2); - if ($this->orientation === self::OLDEST_AT_TOP || $this->orientation === self::OLDEST_AT_BOTTOM) { - $flex_direction = ' flex-column'; - } else { - $flex_direction = ''; - } - - foreach ($this->nodes as $n => $node) { - if ($n >= $last_gen_start) { - $this->nodes[$n]['previous_gen'] = $this->gotoPreviousGen($n); - } else { - $this->nodes[$n]['previous_gen'] = ''; - } - } - - - $html = view('pedigree-chart', [ - 'canvas_height' => $canvas_height, - 'canvas_width' => $canvas_width, - 'child_menu' => $this->getMenu(), - 'flex_direction' => $flex_direction, - 'last_gen_start' => $last_gen_start, - 'orientation' => $this->orientation, - 'nodes' => $this->nodes, - 'landscape' => self::LANDSCAPE, - 'oldest_at_top' => self::OLDEST_AT_TOP, - 'oldest_at_bottom' => self::OLDEST_AT_BOTTOM, - 'portrait' => self::PORTRAIT, - 'posn' => $posn, - ]); - - return new Response($html); - } - - /** - * Function get_menu - * - * Build a menu for the chart root individual - * - * @return string - */ - public function getMenu(): string - { - $families = $this->root->getSpouseFamilies(); - $html = ''; - if (!empty($families)) { - $html = sprintf('<div id="childarrow"><a href="#" class="menuselect %s"></a><div id="childbox-pedigree">', $this->arrows->menu); - - foreach ($families as $family) { - $html .= '<span class="name1">' . I18N::translate('Family') . '</span>'; - $spouse = $family->getSpouse($this->root); - if ($spouse) { - $html .= '<a class="name1" href="' . e(route('pedigree', [ - 'xref' => $spouse->xref(), - 'ged' => $spouse->tree()->name(), - 'generations' => $this->generations, - 'orientation' => $this->orientation, - ])) . '">' . $spouse->getFullName() . '</a>'; - } - $children = $family->getChildren(); - foreach ($children as $sibling) { - $html .= '<a class="name1" href="' . e(route('pedigree', [ - 'xref' => $sibling->xref(), - 'ged' => $sibling->tree()->name(), - 'generations' => $this->generations, - 'orientation' => $this->orientation, - ])) . '">' . $sibling->getFullName() . '</a>'; - } - } - //-- echo the siblings - foreach ($this->root->getChildFamilies() as $family) { - $siblings = array_filter($family->getChildren(), function (Individual $item): bool { - return $this->root->xref() !== $item->xref(); - }); - if (!empty($siblings)) { - $html .= '<span class="name1">'; - $html .= count($siblings) > 1 ? I18N::translate('Siblings') : I18N::translate('Sibling'); - $html .= '</span>'; - foreach ($siblings as $sibling) { - $html .= '<a class="name1" href="' . e(route('pedigree', [ - 'xref' => $sibling->xref(), - 'ged' => $sibling->tree()->name(), - 'generations' => $this->generations, - 'orientation' => $this->orientation, - ])) . '">' . $sibling->getFullName() . '</a>'; - } - } - } - $html .= - '</div>' . // #childbox-pedigree - '</div>'; // #childarrow - } - - return $html; - } - - /** - * Function gotoPreviousGen - * - * Create a link to generate a new chart based on the correct parent of the individual with this index - * - * @param int $index - * - * @return string - */ - public function gotoPreviousGen($index): string - { - $html = ''; - if ($this->chartHasAncestors) { - if ($this->nodes[$index]['indi'] && $this->nodes[$index]['indi']->getChildFamilies()) { - $html .= '<div class="ancestorarrow">'; - $rootParentId = 1; - if ($index > (int) ($this->treesize / 2) + (int) ($this->treesize / 4)) { - $rootParentId++; - } - $html .= '<a class="' . $this->arrows->prevGen . '" href="' . e(route('pedigree', [ - 'xref' => $this->nodes[$rootParentId]['indi']->xref(), - 'ged' => $this->nodes[$rootParentId]['indi']->tree()->name(), - 'generations' => $this->generations, - 'orientation' => $this->orientation, - ])) . '"></a>'; - $html .= '</div>'; - } else { - $html .= '<div class="spacer"></div>'; - } - } - - return $html; - } - - - /** - * @return string[] - */ - private function generationOptions(): array - { - return FunctionsEdit::numericOptions(range(self::MIN_GENERATIONS, self::MAX_GENERATIONS)); - } - - /** - * @return string[] - */ - private function orientations(): array - { - return [ - 0 => I18N::translate('Portrait'), - 1 => I18N::translate('Landscape'), - 2 => I18N::translate('Oldest at top'), - 3 => I18N::translate('Oldest at bottom'), - ]; - } -} diff --git a/app/Module/PedigreeChartModule.php b/app/Module/PedigreeChartModule.php index 31a454f718..23eeb2bce9 100644 --- a/app/Module/PedigreeChartModule.php +++ b/app/Module/PedigreeChartModule.php @@ -17,9 +17,17 @@ declare(strict_types=1); namespace Fisharebest\Webtrees\Module; +use Fisharebest\Webtrees\Auth; +use Fisharebest\Webtrees\Functions\FunctionsEdit; use Fisharebest\Webtrees\I18N; use Fisharebest\Webtrees\Individual; use Fisharebest\Webtrees\Menu; +use Fisharebest\Webtrees\Services\ChartService; +use Fisharebest\Webtrees\Theme; +use Fisharebest\Webtrees\Tree; +use stdClass; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; /** * Class PedigreeChartModule @@ -28,6 +36,43 @@ class PedigreeChartModule extends AbstractModule implements ModuleInterface, Mod { use ModuleChartTrait; + // With more than 8 generations, we run out of pixels on the <canvas> + protected const MAX_GENERATIONS = 8; + protected const MIN_GENERATIONS = 2; + + protected const DEFAULT_GENERATIONS = '4'; + + /** + * Chart orientation codes + * Dont change them! the offset calculations rely on this order + */ + protected const PORTRAIT = 0; + protected const LANDSCAPE = 1; + protected const OLDEST_AT_TOP = 2; + protected const OLDEST_AT_BOTTOM = 3; + + protected const DEFAULT_ORIENTATION = self::LANDSCAPE; + + /** @var int Number of generation to display */ + protected $generations; + + /** @var array data pertaining to each chart node */ + protected $nodes = []; + + /** @var int Number of nodes in the chart */ + protected $treesize; + + /** @var stdClass Determine which arrows to use for each of the chart orientations */ + protected $arrows; + + /** @var Individual */ + protected $root; + + /** + * Next and previous generation arrow size in pixels. + */ + protected const ARROW_SIZE = 22; + /** * How should this module be labelled on tabs, menus, etc.? * @@ -86,18 +131,370 @@ class PedigreeChartModule extends AbstractModule implements ModuleInterface, Mod } /** - * The URL for this chart. + * A form to request the chart parameters. * - * @param Individual $individual - * @param string[] $parameters + * @param Request $request + * @param Tree $tree + * @param ChartService $chart_service + * + * @return Response + */ + public function getChartAction(Request $request, Tree $tree, ChartService $chart_service): Response + { + $ajax = $request->get('ajax', ''); + $xref = $request->get('xref', ''); + $individual = Individual::getInstance($xref, $tree); + + Auth::checkIndividualAccess($individual); + + $orientation = (int) $request->get('orientation', self::DEFAULT_ORIENTATION); + $generations = (int) $request->get('generations', self::DEFAULT_GENERATIONS); + + $generations = min(self::MAX_GENERATIONS, $generations); + $generations = max(self::MIN_GENERATIONS, $generations); + + $generation_options = $this->generationOptions(); + + if ($ajax === '1') { + return $this->chart($individual, $generations, $orientation, $chart_service); + } + + $ajax_url = $this->chartUrl($individual, [ + 'ajax' => '1', + 'generations' => $generations, + 'orientation' => $orientation, + ]); + + return $this->viewResponse('modules/pedigree-chart/chart-page', [ + 'ajax_url' => $ajax_url, + 'generations' => $generations, + 'generation_options' => $generation_options, + 'individual' => $individual, + 'module_name' => $this->name(), + 'orientation' => $orientation, + 'orientations' => $this->orientations(), + 'title' => $this->chartTitle($individual), + ]); + } + + /** + * @param Individual $individual + * @param int $generations + * @param int $orientation + * @param ChartService $chart_service + * + * @return Response + */ + public function chart(Individual $individual, int $generations, int $orientation, ChartService $chart_service): Response + { + $bxspacing = Theme::theme()->parameter('chart-spacing-x'); + $byspacing = Theme::theme()->parameter('chart-spacing-y'); + $curgen = 1; // Track which generation the algorithm is currently working on + $addoffset = []; + + $this->root = $individual; + + $this->treesize = (2 ** $generations) - 1; + + $this->nodes = []; + + $ancestors = $chart_service->sosaStradonitzAncestors($individual, $generations); + + // $ancestors starts array at index 1 we need to start at 0 + for ($i = 0; $i < $this->treesize; ++$i) { + $this->nodes[$i] = [ + 'indi' => $ancestors->get($i + 1), + 'x' => 0, + 'y' => 0, + ]; + } + + // Are there ancestors beyond the bounds of this chart + $chart_has_ancestors = false; + + // Check earliest generation for any ancestors + for ($i = (int) ($this->treesize / 2); $i < $this->treesize; $i++) { + $chart_has_ancestors = $chart_has_ancestors || ($this->nodes[$i]['indi'] && $this->nodes[$i]['indi']->getChildFamilies()); + } + + $this->arrows = new stdClass(); + switch ($orientation) { + default: + case self::PORTRAIT: + case self::LANDSCAPE: + $this->arrows->prevGen = 'fas fa-arrow-end wt-icon-arrow-end'; + $this->arrows->menu = 'fas fa-arrow-start wt-icon-arrow-start'; + $addoffset['x'] = $chart_has_ancestors ? self::ARROW_SIZE : 0; + $addoffset['y'] = 0; + break; + + case self::OLDEST_AT_TOP: + $this->arrows->prevGen = 'fas fa-arrow-up wt-icon-arrow-up'; + $this->arrows->menu = 'fas fa-arrow-down wt-icon-arrow-down'; + $addoffset['x'] = 0; + $addoffset['y'] = $this->root->getSpouseFamilies() ? self::ARROW_SIZE : 0; + break; + + case self::OLDEST_AT_BOTTOM: + $this->arrows->prevGen = 'fas fa-arrow-down wt-icon-arrow-down'; + $this->arrows->menu = 'fas fa-arrow-up wt-icon-arrow-up'; + $addoffset['x'] = 0; + $addoffset['y'] = $chart_has_ancestors ? self::ARROW_SIZE : 0; + break; + } + + // Create and position the DIV layers for the pedigree tree + for ($i = ($this->treesize - 1); $i >= 0; $i--) { + // Check to see if we have moved to the next generation + if ($i < (int) ($this->treesize / (2 ** $curgen))) { + $curgen++; + } + + // Box position in current generation + $boxpos = $i - (2 ** ($this->generations - $curgen)); + // Offset multiple for current generation + if ($orientation < self::OLDEST_AT_TOP) { + $genoffset = 2 ** ($curgen - $orientation); + $boxspacing = Theme::theme()->parameter('chart-box-y') + $byspacing; + } else { + $genoffset = 2 ** ($curgen - 1); + $boxspacing = Theme::theme()->parameter('chart-box-x') + $byspacing; + } + // Calculate the yoffset position in the generation put child between parents + $yoffset = ($boxpos * ($boxspacing * $genoffset)) + (($boxspacing / 2) * $genoffset) + ($boxspacing * $genoffset); + + // Calculate the xoffset + switch ($orientation) { + default: + case self::PORTRAIT: + $xoffset = ($this->generations - $curgen) * ((Theme::theme()->parameter('chart-box-x') + $bxspacing) / 1.8); + if (!$i && $this->root->getSpouseFamilies()) { + $xoffset -= self::ARROW_SIZE; + } + // Compact the tree + if ($curgen < $this->generations) { + if ($i % 2 == 0) { + $yoffset = $yoffset - (($boxspacing / 2) * ($curgen - 1)); + } else { + $yoffset = $yoffset + (($boxspacing / 2) * ($curgen - 1)); + } + $parent = (int) (($i - 1) / 2); + $pgen = $curgen; + while ($parent > 0) { + if ($parent % 2 == 0) { + $yoffset = $yoffset - (($boxspacing / 2) * $pgen); + } else { + $yoffset = $yoffset + (($boxspacing / 2) * $pgen); + } + $pgen++; + if ($pgen > 3) { + $temp = 0; + for ($j = 1; $j < ($pgen - 2); $j++) { + $temp += ((2 ** $j) - 1); + } + if ($parent % 2 == 0) { + $yoffset = $yoffset - (($boxspacing / 2) * $temp); + } else { + $yoffset = $yoffset + (($boxspacing / 2) * $temp); + } + } + $parent = (int) (($parent - 1) / 2); + } + if ($curgen > 3) { + $temp = 0; + for ($j = 1; $j < ($curgen - 2); $j++) { + $temp += ((2 ** $j) - 1); + } + if ($i % 2 == 0) { + $yoffset = $yoffset - (($boxspacing / 2) * $temp); + } else { + $yoffset = $yoffset + (($boxspacing / 2) * $temp); + } + } + } + $yoffset -= (($boxspacing / 2) * (2 ** ($this->generations - 2)) - ($boxspacing / 2)); + break; + + case self::LANDSCAPE: + $xoffset = ($this->generations - $curgen) * (Theme::theme()->parameter('chart-box-x') + $bxspacing); + if ($curgen == 1) { + $xoffset += 10; + } + break; + + case self::OLDEST_AT_TOP: + // Swap x & y offsets as chart is rotated + $xoffset = $yoffset; + $yoffset = $curgen * (Theme::theme()->parameter('chart-box-y') + ($byspacing * 4)); + break; + + case self::OLDEST_AT_BOTTOM: + // Swap x & y offsets as chart is rotated + $xoffset = $yoffset; + $yoffset = ($this->generations - $curgen) * (Theme::theme()->parameter('chart-box-y') + ($byspacing * 2)); + if ($i && $this->root->getSpouseFamilies()) { + $yoffset += self::ARROW_SIZE; + } + break; + } + $this->nodes[$i]['x'] = (int) $xoffset; + $this->nodes[$i]['y'] = (int) $yoffset; + } + + // Find the minimum x & y offsets and deduct that number from + // each value in the array so that offsets start from zero + $min_xoffset = min(array_map(function (array $item): int { + return $item['x']; + }, $this->nodes)); + $min_yoffset = min(array_map(function (array $item): int { + return $item['y']; + }, $this->nodes)); + + array_walk($this->nodes, function (&$item) use ($min_xoffset, $min_yoffset) { + $item['x'] -= $min_xoffset; + $item['y'] -= $min_yoffset; + }); + + // Calculate chart & canvas dimensions + $max_xoffset = max(array_map(function ($item) { + return $item['x']; + }, $this->nodes)); + $max_yoffset = max(array_map(function ($item) { + return $item['y']; + }, $this->nodes)); + + $canvas_width = $max_xoffset + $bxspacing + Theme::theme()->parameter('chart-box-x') + $addoffset['x']; + $canvas_height = $max_yoffset + $byspacing + Theme::theme()->parameter('chart-box-y') + $addoffset['y']; + $posn = I18N::direction() === 'rtl' ? 'right' : 'left'; + $last_gen_start = (int) floor($this->treesize / 2); + if ($orientation === self::OLDEST_AT_TOP || $orientation === self::OLDEST_AT_BOTTOM) { + $flex_direction = ' flex-column'; + } else { + $flex_direction = ''; + } + + foreach ($this->nodes as $n => $node) { + if ($n >= $last_gen_start) { + $this->nodes[$n]['previous_gen'] = $this->gotoPreviousGen($n, $generations, $orientation, $chart_has_ancestors); + } else { + $this->nodes[$n]['previous_gen'] = ''; + } + } + + $html = view('modules/pedigree-chart/chart', [ + 'canvas_height' => $canvas_height, + 'canvas_width' => $canvas_width, + 'child_menu' => $this->getMenu($individual, $generations, $orientation), + 'flex_direction' => $flex_direction, + 'last_gen_start' => $last_gen_start, + 'orientation' => $orientation, + 'nodes' => $this->nodes, + 'landscape' => self::LANDSCAPE, + 'oldest_at_top' => self::OLDEST_AT_TOP, + 'oldest_at_bottom' => self::OLDEST_AT_BOTTOM, + 'portrait' => self::PORTRAIT, + 'posn' => $posn, + ]); + + return new Response($html); + } + + /** + * Build a menu for the chart root individual + * + * @param Individual $root + * @param int $generations + * @param int $orientation + * + * @return string + */ + public function getMenu(Individual $root, int $generations, int $orientation): string + { + $families = $root->getSpouseFamilies(); + $html = ''; + if (!empty($families)) { + $html = sprintf('<div id="childarrow"><a href="#" class="menuselect %s"></a><div id="childbox-pedigree">', $this->arrows->menu); + + foreach ($families as $family) { + $html .= '<span class="name1">' . I18N::translate('Family') . '</span>'; + $spouse = $family->getSpouse($root); + if ($spouse) { + $html .= '<a class="name1" href="' . e($this->chartUrl($spouse, ['generations' => $generations, 'orientation' => $orientation])) . '">' . $spouse->getFullName() . '</a>'; + } + $children = $family->getChildren(); + foreach ($children as $sibling) { + $html .= '<a class="name1" href="' . e($this->chartUrl($sibling, ['generations' => $generations, 'orientation' => $orientation])) . '">' . $sibling->getFullName() . '</a>'; + } + } + + foreach ($root->getChildFamilies() as $family) { + $siblings = array_filter($family->getChildren(), function (Individual $item) use ($root): bool { + return $root->xref() !== $item->xref(); + }); + if (!empty($siblings)) { + $html .= '<span class="name1">'; + $html .= count($siblings) > 1 ? I18N::translate('Siblings') : I18N::translate('Sibling'); + $html .= '</span>'; + foreach ($siblings as $sibling) { + $html .= '<a class="name1" href="' . e($this->chartUrl($sibling, ['generations' => $generations, 'orientation' => $orientation])) . '">' . $sibling->getFullName() . '</a>'; + } + } + } + $html .= '</div></div>'; + } + + return $html; + } + + /** + * Function gotoPreviousGen + * Create a link to generate a new chart based on the correct parent of the individual with this index + * + * @param int $index + * @param int $generations + * @param int $orientation + * @param bool $chart_has_ancestors * * @return string */ - public function chartUrl(Individual $individual, array $parameters = []): string + public function gotoPreviousGen(int $index, int $generations, int $orientation, bool $chart_has_ancestors): string + { + $html = ''; + if ($chart_has_ancestors) { + if ($this->nodes[$index]['indi'] && $this->nodes[$index]['indi']->getChildFamilies()) { + $html .= '<div class="ancestorarrow">'; + $rootParentId = 1; + if ($index > (int) ($this->treesize / 2) + (int) ($this->treesize / 4)) { + $rootParentId++; + } + $html .= '<a class="' . $this->arrows->prevGen . '" href="' . e($this->chartUrl($this->nodes[$rootParentId]['indi'], ['generations' => $generations, 'orientation' => $orientation])) . '"></a>'; + $html .= '</div>'; + } else { + $html .= '<div class="spacer"></div>'; + } + } + + return $html; + } + + /** + * @return string[] + */ + protected function generationOptions(): array + { + return FunctionsEdit::numericOptions(range(self::MIN_GENERATIONS, self::MAX_GENERATIONS)); + } + + /** + * @return string[] + */ + protected function orientations(): array { - return route('pedigree', [ - 'xref' => $individual->xref(), - 'ged' => $individual->tree()->name(), - ] + $parameters); + return [ + 0 => I18N::translate('Portrait'), + 1 => I18N::translate('Landscape'), + 2 => I18N::translate('Oldest at top'), + 3 => I18N::translate('Oldest at bottom'), + ]; } } diff --git a/resources/views/pedigree-page.phtml b/resources/views/modules/pedigree-chart/chart-page.phtml index 0859e8ad1b..f71b3f1c68 100644 --- a/resources/views/pedigree-page.phtml +++ b/resources/views/modules/pedigree-chart/chart-page.phtml @@ -7,7 +7,9 @@ </h2> <form class="wt-page-options wt-page-options-pedigree-chart d-print-none"> - <input type="hidden" name="route" value="pedigree"> + <input type="hidden" name="route" value="module"> + <input type="hidden" name="module" value="<?= e($module_name) ?>"> + <input type="hidden" name="action" value="Chart"> <input type="hidden" name="ged" value="<?= e($tree->name()) ?>"> <div class="row form-group"> @@ -45,4 +47,4 @@ </div> </form> -<div class="wt-ajax-load wt-page-content wt-chart wt-pedigree-chart" data-ajax-url="<?= e(route('pedigree-chart', ['xref' => $individual->xref(), 'ged' => $individual->tree()->name(), 'generations' => $generations, 'orientation' => $orientation])) ?>"></div> +<div class="wt-ajax-load wt-page-content wt-chart wt-pedigree-chart" data-ajax-url="<?= e($ajax_url) ?>"></div> diff --git a/resources/views/pedigree-chart.phtml b/resources/views/modules/pedigree-chart/chart.phtml index b181650922..b181650922 100644 --- a/resources/views/pedigree-chart.phtml +++ b/resources/views/modules/pedigree-chart/chart.phtml diff --git a/routes/web.php b/routes/web.php index 1b98f7a987..b25604edc2 100644 --- a/routes/web.php +++ b/routes/web.php @@ -278,8 +278,6 @@ if ($tree instanceof Tree && $tree->getPreference('imported') === '1') { 'GET:interactive-chart' => 'InteractiveChartController@chart', 'GET:lifespans' => 'LifespansChartController@page', 'GET:lifespans-chart' => 'LifespansChartController@chart', - 'GET:pedigree' => 'PedigreeChartController@page', - 'GET:pedigree-chart' => 'PedigreeChartController@chart', 'GET:relationships' => 'RelationshipsChartController@page', 'GET:relationships-chart' => 'RelationshipsChartController@chart', 'GET:statistics' => 'StatisticsChartController@page', |
