xref: /webtrees/app/Module/PedigreeChartModule.php (revision 9b5537c37ce2b6d3b5c1a28c56f73d7ff9b9355c)
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