summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGreg Roach <fisharebest@webtrees.net>2019-01-22 17:51:15 +0000
committerGreg Roach <fisharebest@webtrees.net>2019-01-22 17:51:15 +0000
commit241a1636dee0487eb56818cbf3b6a79ceaf02090 (patch)
tree2d1b5d6966c78eabe9cd047bb623db4e285686c8
parent26684e686fb5ab50ecb57e7e6c6a0a55852d2203 (diff)
downloadwebtrees-241a1636dee0487eb56818cbf3b6a79ceaf02090.tar.gz
webtrees-241a1636dee0487eb56818cbf3b6a79ceaf02090.tar.bz2
webtrees-241a1636dee0487eb56818cbf3b6a79ceaf02090.zip
Merge chart controllers into chart modules
-rw-r--r--app/Http/Controllers/PedigreeChartController.php475
-rw-r--r--app/Module/PedigreeChartModule.php413
-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.php2
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',