xref: /webtrees/app/Module/StatisticsChartModule.php (revision 81b729d3a9a6e0a0e8b96285d1ad7955d2d0c659)
1168ff6f3Sric2016<?php
23976b470SGreg Roach
3168ff6f3Sric2016/**
4168ff6f3Sric2016 * webtrees: online genealogy
589f7189bSGreg Roach * Copyright (C) 2021 webtrees development team
6168ff6f3Sric2016 * This program is free software: you can redistribute it and/or modify
7168ff6f3Sric2016 * it under the terms of the GNU General Public License as published by
8168ff6f3Sric2016 * the Free Software Foundation, either version 3 of the License, or
9168ff6f3Sric2016 * (at your option) any later version.
10168ff6f3Sric2016 * This program is distributed in the hope that it will be useful,
11168ff6f3Sric2016 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12168ff6f3Sric2016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13168ff6f3Sric2016 * GNU General Public License for more details.
14168ff6f3Sric2016 * You should have received a copy of the GNU General Public License
1589f7189bSGreg Roach * along with this program. If not, see <https://www.gnu.org/licenses/>.
16168ff6f3Sric2016 */
17fcfa147eSGreg Roach
18e7f56f2aSGreg Roachdeclare(strict_types=1);
19e7f56f2aSGreg Roach
20168ff6f3Sric2016namespace Fisharebest\Webtrees\Module;
21168ff6f3Sric2016
22c6266a77SGreg Roachuse Fisharebest\Webtrees\Auth;
23*81b729d3SGreg Roachuse Fisharebest\Webtrees\Http\Exceptions\HttpNotFoundException;
24168ff6f3Sric2016use Fisharebest\Webtrees\I18N;
25168ff6f3Sric2016use Fisharebest\Webtrees\Individual;
269219296aSGreg Roachuse Fisharebest\Webtrees\Statistics;
275229eadeSGreg Roachuse Fisharebest\Webtrees\Tree;
286ccdf4f0SGreg Roachuse Psr\Http\Message\ResponseInterface;
296ccdf4f0SGreg Roachuse Psr\Http\Message\ServerRequestInterface;
303976b470SGreg Roach
3157ab2231SGreg Roachuse function app;
326960d4a1SGreg Roachuse function array_key_exists;
336960d4a1SGreg Roachuse function array_keys;
346960d4a1SGreg Roachuse function array_map;
356960d4a1SGreg Roachuse function array_merge;
366960d4a1SGreg Roachuse function array_sum;
376960d4a1SGreg Roachuse function array_values;
386960d4a1SGreg Roachuse function array_walk;
395229eadeSGreg Roachuse function assert;
406960d4a1SGreg Roachuse function count;
416960d4a1SGreg Roachuse function explode;
426960d4a1SGreg Roachuse function in_array;
436960d4a1SGreg Roachuse function is_numeric;
446960d4a1SGreg Roachuse function sprintf;
45168ff6f3Sric2016
46168ff6f3Sric2016/**
47168ff6f3Sric2016 * Class StatisticsChartModule
48168ff6f3Sric2016 */
4937eb8894SGreg Roachclass StatisticsChartModule extends AbstractModule implements ModuleChartInterface
50c1010edaSGreg Roach{
5149a243cbSGreg Roach    use ModuleChartTrait;
5249a243cbSGreg Roach
53c6266a77SGreg Roach    public const X_AXIS_INDIVIDUAL_MAP        = 1;
54c6266a77SGreg Roach    public const X_AXIS_BIRTH_MAP             = 2;
55c6266a77SGreg Roach    public const X_AXIS_DEATH_MAP             = 3;
56c6266a77SGreg Roach    public const X_AXIS_MARRIAGE_MAP          = 4;
57c6266a77SGreg Roach    public const X_AXIS_BIRTH_MONTH           = 11;
58c6266a77SGreg Roach    public const X_AXIS_DEATH_MONTH           = 12;
59c6266a77SGreg Roach    public const X_AXIS_MARRIAGE_MONTH        = 13;
60c6266a77SGreg Roach    public const X_AXIS_FIRST_CHILD_MONTH     = 14;
61c6266a77SGreg Roach    public const X_AXIS_FIRST_MARRIAGE_MONTH  = 15;
62c6266a77SGreg Roach    public const X_AXIS_AGE_AT_DEATH          = 18;
63c6266a77SGreg Roach    public const X_AXIS_AGE_AT_MARRIAGE       = 19;
64c6266a77SGreg Roach    public const X_AXIS_AGE_AT_FIRST_MARRIAGE = 20;
65c6266a77SGreg Roach    public const X_AXIS_NUMBER_OF_CHILDREN    = 21;
66c6266a77SGreg Roach
67c6266a77SGreg Roach    public const Y_AXIS_NUMBERS = 201;
68c6266a77SGreg Roach    public const Y_AXIS_PERCENT = 202;
69c6266a77SGreg Roach
70c6266a77SGreg Roach    public const Z_AXIS_ALL  = 300;
71c6266a77SGreg Roach    public const Z_AXIS_SEX  = 301;
72c6266a77SGreg Roach    public const Z_AXIS_TIME = 302;
73c6266a77SGreg Roach
74c6266a77SGreg Roach    // First two colors are blue/pink, to work with Z_AXIS_SEX.
75c6266a77SGreg Roach    private const Z_AXIS_COLORS = ['0000FF', 'FFA0CB', '9F00FF', 'FF7000', '905030', 'FF0000', '00FF00', 'F0F000'];
76c6266a77SGreg Roach
77c6266a77SGreg Roach    private const DAYS_IN_YEAR = 365.25;
78c6266a77SGreg Roach
79168ff6f3Sric2016    /**
800cfd6963SGreg Roach     * How should this module be identified in the control panel, etc.?
81168ff6f3Sric2016     *
82168ff6f3Sric2016     * @return string
83168ff6f3Sric2016     */
8449a243cbSGreg Roach    public function title(): string
85c1010edaSGreg Roach    {
86bbb76c12SGreg Roach        /* I18N: Name of a module/chart */
87bbb76c12SGreg Roach        return I18N::translate('Statistics');
88168ff6f3Sric2016    }
89168ff6f3Sric2016
90168ff6f3Sric2016    /**
91168ff6f3Sric2016     * A sentence describing what this module does.
92168ff6f3Sric2016     *
93168ff6f3Sric2016     * @return string
94168ff6f3Sric2016     */
9549a243cbSGreg Roach    public function description(): string
96c1010edaSGreg Roach    {
97bbb76c12SGreg Roach        /* I18N: Description of the “StatisticsChart” module */
98bbb76c12SGreg Roach        return I18N::translate('Various statistics charts.');
99168ff6f3Sric2016    }
100168ff6f3Sric2016
101168ff6f3Sric2016    /**
102377a2979SGreg Roach     * CSS class for the URL.
103377a2979SGreg Roach     *
104377a2979SGreg Roach     * @return string
105377a2979SGreg Roach     */
106377a2979SGreg Roach    public function chartMenuClass(): string
107377a2979SGreg Roach    {
108377a2979SGreg Roach        return 'menu-chart-statistics';
109377a2979SGreg Roach    }
110377a2979SGreg Roach
111377a2979SGreg Roach    /**
112e6562982SGreg Roach     * The URL for this chart.
113168ff6f3Sric2016     *
11460bc3e3fSGreg Roach     * @param Individual $individual
11559597b37SGreg Roach     * @param mixed[]    $parameters
11660bc3e3fSGreg Roach     *
117e6562982SGreg Roach     * @return string
118168ff6f3Sric2016     */
119e6562982SGreg Roach    public function chartUrl(Individual $individual, array $parameters = []): string
120c1010edaSGreg Roach    {
121c6266a77SGreg Roach        return route('module', [
122c6266a77SGreg Roach                'module' => $this->name(),
123c6266a77SGreg Roach                'action' => 'Chart',
124d72b284aSGreg Roach                'tree'    => $individual->tree()->name(),
125e6562982SGreg Roach            ] + $parameters);
126168ff6f3Sric2016    }
127c6266a77SGreg Roach
128c6266a77SGreg Roach    /**
129c6266a77SGreg Roach     * A form to request the chart parameters.
130c6266a77SGreg Roach     *
13157ab2231SGreg Roach     * @param ServerRequestInterface $request
132c6266a77SGreg Roach     *
1336ccdf4f0SGreg Roach     * @return ResponseInterface
134c6266a77SGreg Roach     */
13557ab2231SGreg Roach    public function getChartAction(ServerRequestInterface $request): ResponseInterface
136c6266a77SGreg Roach    {
13757ab2231SGreg Roach        $tree = $request->getAttribute('tree');
13875964c75SGreg Roach        assert($tree instanceof Tree);
1395229eadeSGreg Roach
14057ab2231SGreg Roach        $user = $request->getAttribute('user');
14157ab2231SGreg Roach
142ef483801SGreg Roach        Auth::checkComponentAccess($this, ModuleChartInterface::class, $tree, $user);
1439867b2f0SGreg Roach
144c6266a77SGreg Roach        $tabs = [
145c6266a77SGreg Roach            I18N::translate('Individuals') => route('module', [
146c6266a77SGreg Roach                'module' => $this->name(),
147c6266a77SGreg Roach                'action' => 'Individuals',
148d72b284aSGreg Roach                'tree'    => $tree->name(),
149c6266a77SGreg Roach            ]),
150c6266a77SGreg Roach            I18N::translate('Families')    => route('module', [
151c6266a77SGreg Roach                'module' => $this->name(),
152c6266a77SGreg Roach                'action' => 'Families',
153d72b284aSGreg Roach                'tree'    => $tree->name(),
154c6266a77SGreg Roach            ]),
155c6266a77SGreg Roach            I18N::translate('Other')       => route('module', [
156c6266a77SGreg Roach                'module' => $this->name(),
157c6266a77SGreg Roach                'action' => 'Other',
158d72b284aSGreg Roach                'tree'    => $tree->name(),
159c6266a77SGreg Roach            ]),
160c6266a77SGreg Roach            I18N::translate('Custom')      => route('module', [
161c6266a77SGreg Roach                'module' => $this->name(),
162c6266a77SGreg Roach                'action' => 'Custom',
163d72b284aSGreg Roach                'tree'    => $tree->name(),
164c6266a77SGreg Roach            ]),
165c6266a77SGreg Roach        ];
166c6266a77SGreg Roach
1679b5537c3SGreg Roach        return $this->viewResponse('modules/statistics-chart/page', [
16871378461SGreg Roach            'module' => $this->name(),
169c6266a77SGreg Roach            'tabs'   => $tabs,
170c6266a77SGreg Roach            'title'  => $this->title(),
171ef5d23f1SGreg Roach            'tree'   => $tree,
172c6266a77SGreg Roach        ]);
173c6266a77SGreg Roach    }
174c6266a77SGreg Roach
175c6266a77SGreg Roach    /**
17657ab2231SGreg Roach     * @param ServerRequestInterface $request
177c6266a77SGreg Roach     *
1786ccdf4f0SGreg Roach     * @return ResponseInterface
179c6266a77SGreg Roach     */
18057ab2231SGreg Roach    public function getIndividualsAction(ServerRequestInterface $request): ResponseInterface
181c6266a77SGreg Roach    {
182b6c326d8SGreg Roach        $this->layout = 'layouts/ajax';
183b6c326d8SGreg Roach
184b6c326d8SGreg Roach        return $this->viewResponse('modules/statistics-chart/individuals', [
185c6266a77SGreg Roach            'show_oldest_living' => Auth::check(),
18657ab2231SGreg Roach            'stats'              => app(Statistics::class),
187c6266a77SGreg Roach        ]);
188c6266a77SGreg Roach    }
189c6266a77SGreg Roach
190c6266a77SGreg Roach    /**
19157ab2231SGreg Roach     * @param ServerRequestInterface $request
192c6266a77SGreg Roach     *
1936ccdf4f0SGreg Roach     * @return ResponseInterface
194c6266a77SGreg Roach     */
19557ab2231SGreg Roach    public function getFamiliesAction(ServerRequestInterface $request): ResponseInterface
196c6266a77SGreg Roach    {
197b6c326d8SGreg Roach        $this->layout = 'layouts/ajax';
198b6c326d8SGreg Roach
199b6c326d8SGreg Roach        return $this->viewResponse('modules/statistics-chart/families', [
20057ab2231SGreg Roach            'stats' => app(Statistics::class),
201c6266a77SGreg Roach        ]);
202c6266a77SGreg Roach    }
203c6266a77SGreg Roach
204c6266a77SGreg Roach    /**
20557ab2231SGreg Roach     * @param ServerRequestInterface $request
206c6266a77SGreg Roach     *
2076ccdf4f0SGreg Roach     * @return ResponseInterface
208c6266a77SGreg Roach     */
20957ab2231SGreg Roach    public function getOtherAction(ServerRequestInterface $request): ResponseInterface
210c6266a77SGreg Roach    {
211b6c326d8SGreg Roach        $this->layout = 'layouts/ajax';
212b6c326d8SGreg Roach
213b6c326d8SGreg Roach        return $this->viewResponse('modules/statistics-chart/other', [
21457ab2231SGreg Roach            'stats' => app(Statistics::class),
215c6266a77SGreg Roach        ]);
216c6266a77SGreg Roach    }
217c6266a77SGreg Roach
218c6266a77SGreg Roach    /**
21957ab2231SGreg Roach     * @param ServerRequestInterface $request
220c6266a77SGreg Roach     *
2216ccdf4f0SGreg Roach     * @return ResponseInterface
222c6266a77SGreg Roach     */
22357ab2231SGreg Roach    public function getCustomAction(ServerRequestInterface $request): ResponseInterface
224c6266a77SGreg Roach    {
225b6c326d8SGreg Roach        $this->layout = 'layouts/ajax';
226b6c326d8SGreg Roach
22757ab2231SGreg Roach        $tree = $request->getAttribute('tree');
22875964c75SGreg Roach        assert($tree instanceof Tree);
22957ab2231SGreg Roach
230b6c326d8SGreg Roach        return $this->viewResponse('modules/statistics-chart/custom', [
231c6266a77SGreg Roach            'module' => $this,
232c6266a77SGreg Roach            'tree'   => $tree,
233c6266a77SGreg Roach        ]);
234c6266a77SGreg Roach    }
235c6266a77SGreg Roach
236c6266a77SGreg Roach    /**
2376ccdf4f0SGreg Roach     * @param ServerRequestInterface $request
238c6266a77SGreg Roach     *
2396ccdf4f0SGreg Roach     * @return ResponseInterface
240c6266a77SGreg Roach     */
2413aa29b97SGreg Roach    public function postCustomChartAction(ServerRequestInterface $request): ResponseInterface
242c6266a77SGreg Roach    {
24357ab2231SGreg Roach        $statistics = app(Statistics::class);
244dca2dd78SGreg Roach        assert($statistics instanceof Statistics);
24557ab2231SGreg Roach
246b46c87bdSGreg Roach        $params = (array) $request->getParsedBody();
247b6b9dcc9SGreg Roach
248b6b9dcc9SGreg Roach        $x_axis_type = (int) $params['x-as'];
249b6b9dcc9SGreg Roach        $y_axis_type = (int) $params['y-as'];
250b6b9dcc9SGreg Roach        $z_axis_type = (int) $params['z-as'];
251c6266a77SGreg Roach        $ydata       = [];
252c6266a77SGreg Roach
253c6266a77SGreg Roach        switch ($x_axis_type) {
254c6266a77SGreg Roach            case self::X_AXIS_INDIVIDUAL_MAP:
2556ccdf4f0SGreg Roach                return response($statistics->chartDistribution(
256b6b9dcc9SGreg Roach                    $params['chart_shows'],
257b6b9dcc9SGreg Roach                    $params['chart_type'],
258b6b9dcc9SGreg Roach                    $params['SURN']
259c6266a77SGreg Roach                ));
260c6266a77SGreg Roach
261c6266a77SGreg Roach            case self::X_AXIS_BIRTH_MAP:
2626ccdf4f0SGreg Roach                return response($statistics->chartDistribution(
263b6b9dcc9SGreg Roach                    $params['chart_shows'],
264c6266a77SGreg Roach                    'birth_distribution_chart'
265c6266a77SGreg Roach                ));
266c6266a77SGreg Roach
267c6266a77SGreg Roach            case self::X_AXIS_DEATH_MAP:
2686ccdf4f0SGreg Roach                return response($statistics->chartDistribution(
269b6b9dcc9SGreg Roach                    $params['chart_shows'],
270c6266a77SGreg Roach                    'death_distribution_chart'
271c6266a77SGreg Roach                ));
272c6266a77SGreg Roach
273c6266a77SGreg Roach            case self::X_AXIS_MARRIAGE_MAP:
2746ccdf4f0SGreg Roach                return response($statistics->chartDistribution(
275b6b9dcc9SGreg Roach                    $params['chart_shows'],
276c6266a77SGreg Roach                    'marriage_distribution_chart'
277c6266a77SGreg Roach                ));
278c6266a77SGreg Roach
279c6266a77SGreg Roach            case self::X_AXIS_BIRTH_MONTH:
280c6266a77SGreg Roach                $chart_title  = I18N::translate('Month of birth');
281c6266a77SGreg Roach                $x_axis_title = I18N::translate('Month');
282c6266a77SGreg Roach                $x_axis       = $this->axisMonths();
283c6266a77SGreg Roach
284c6266a77SGreg Roach                switch ($y_axis_type) {
285c6266a77SGreg Roach                    case self::Y_AXIS_NUMBERS:
286c6266a77SGreg Roach                        $y_axis_title = I18N::translate('Individuals');
287c6266a77SGreg Roach                        break;
288c6266a77SGreg Roach                    case self::Y_AXIS_PERCENT:
289c6266a77SGreg Roach                        $y_axis_title = '%';
290c6266a77SGreg Roach                        break;
291c6266a77SGreg Roach                    default:
292d501c45dSGreg Roach                        throw new HttpNotFoundException();
293c6266a77SGreg Roach                }
294c6266a77SGreg Roach
295c6266a77SGreg Roach                switch ($z_axis_type) {
296c6266a77SGreg Roach                    case self::Z_AXIS_ALL:
297c6266a77SGreg Roach                        $z_axis = $this->axisAll();
298cde1d378SGreg Roach                        $rows   = $statistics->statsBirthQuery()->get();
299c6266a77SGreg Roach                        foreach ($rows as $row) {
300c6266a77SGreg Roach                            $this->fillYData($row->d_month, 0, $row->total, $x_axis, $z_axis, $ydata);
301c6266a77SGreg Roach                        }
302c6266a77SGreg Roach                        break;
303c6266a77SGreg Roach                    case self::Z_AXIS_SEX:
304c6266a77SGreg Roach                        $z_axis = $this->axisSexes();
305cde1d378SGreg Roach                        $rows   = $statistics->statsBirthBySexQuery()->get();
306c6266a77SGreg Roach                        foreach ($rows as $row) {
307c6266a77SGreg Roach                            $this->fillYData($row->d_month, $row->i_sex, $row->total, $x_axis, $z_axis, $ydata);
308c6266a77SGreg Roach                        }
309c6266a77SGreg Roach                        break;
310c6266a77SGreg Roach                    case self::Z_AXIS_TIME:
311b6b9dcc9SGreg Roach                        $boundaries_csv = $params['z-axis-boundaries-periods'];
312c6266a77SGreg Roach                        $z_axis         = $this->axisYears($boundaries_csv);
313c6266a77SGreg Roach                        $prev_boundary  = 0;
314c6266a77SGreg Roach                        foreach (array_keys($z_axis) as $boundary) {
315cde1d378SGreg Roach                            $rows = $statistics->statsBirthQuery($prev_boundary, $boundary)->get();
316c6266a77SGreg Roach                            foreach ($rows as $row) {
317c6266a77SGreg Roach                                $this->fillYData($row->d_month, $boundary, $row->total, $x_axis, $z_axis, $ydata);
318c6266a77SGreg Roach                            }
319c6266a77SGreg Roach                            $prev_boundary = $boundary + 1;
320c6266a77SGreg Roach                        }
321c6266a77SGreg Roach                        break;
322c6266a77SGreg Roach                    default:
323d501c45dSGreg Roach                        throw new HttpNotFoundException();
324c6266a77SGreg Roach                }
325c6266a77SGreg Roach
3266ccdf4f0SGreg Roach                return response($this->myPlot($chart_title, $x_axis, $x_axis_title, $ydata, $y_axis_title, $z_axis, $y_axis_type));
327c6266a77SGreg Roach
328c6266a77SGreg Roach            case self::X_AXIS_DEATH_MONTH:
329c6266a77SGreg Roach                $chart_title  = I18N::translate('Month of death');
330c6266a77SGreg Roach                $x_axis_title = I18N::translate('Month');
331c6266a77SGreg Roach                $x_axis       = $this->axisMonths();
332c6266a77SGreg Roach
333c6266a77SGreg Roach                switch ($y_axis_type) {
334c6266a77SGreg Roach                    case self::Y_AXIS_NUMBERS:
335c6266a77SGreg Roach                        $y_axis_title = I18N::translate('Individuals');
336c6266a77SGreg Roach                        break;
337c6266a77SGreg Roach                    case self::Y_AXIS_PERCENT:
338c6266a77SGreg Roach                        $y_axis_title = '%';
339c6266a77SGreg Roach                        break;
340c6266a77SGreg Roach                    default:
341d501c45dSGreg Roach                        throw new HttpNotFoundException();
342c6266a77SGreg Roach                }
343c6266a77SGreg Roach
344c6266a77SGreg Roach                switch ($z_axis_type) {
345c6266a77SGreg Roach                    case self::Z_AXIS_ALL:
346c6266a77SGreg Roach                        $z_axis = $this->axisAll();
347cde1d378SGreg Roach                        $rows   = $statistics->statsDeathQuery()->get();
348c6266a77SGreg Roach                        foreach ($rows as $row) {
349c6266a77SGreg Roach                            $this->fillYData($row->d_month, 0, $row->total, $x_axis, $z_axis, $ydata);
350c6266a77SGreg Roach                        }
351c6266a77SGreg Roach                        break;
352c6266a77SGreg Roach                    case self::Z_AXIS_SEX:
353c6266a77SGreg Roach                        $z_axis = $this->axisSexes();
354cde1d378SGreg Roach                        $rows   = $statistics->statsDeathBySexQuery()->get();
355c6266a77SGreg Roach                        foreach ($rows as $row) {
356c6266a77SGreg Roach                            $this->fillYData($row->d_month, $row->i_sex, $row->total, $x_axis, $z_axis, $ydata);
357c6266a77SGreg Roach                        }
358c6266a77SGreg Roach                        break;
359c6266a77SGreg Roach                    case self::Z_AXIS_TIME:
360b6b9dcc9SGreg Roach                        $boundaries_csv = $params['z-axis-boundaries-periods'];
361c6266a77SGreg Roach                        $z_axis         = $this->axisYears($boundaries_csv);
362c6266a77SGreg Roach                        $prev_boundary  = 0;
363c6266a77SGreg Roach                        foreach (array_keys($z_axis) as $boundary) {
364cde1d378SGreg Roach                            $rows = $statistics->statsDeathQuery($prev_boundary, $boundary)->get();
365c6266a77SGreg Roach                            foreach ($rows as $row) {
366c6266a77SGreg Roach                                $this->fillYData($row->d_month, $boundary, $row->total, $x_axis, $z_axis, $ydata);
367c6266a77SGreg Roach                            }
368c6266a77SGreg Roach                            $prev_boundary = $boundary + 1;
369c6266a77SGreg Roach                        }
370c6266a77SGreg Roach                        break;
371c6266a77SGreg Roach                    default:
372d501c45dSGreg Roach                        throw new HttpNotFoundException();
373c6266a77SGreg Roach                }
374c6266a77SGreg Roach
3756ccdf4f0SGreg Roach                return response($this->myPlot($chart_title, $x_axis, $x_axis_title, $ydata, $y_axis_title, $z_axis, $y_axis_type));
376c6266a77SGreg Roach
377c6266a77SGreg Roach            case self::X_AXIS_MARRIAGE_MONTH:
378c6266a77SGreg Roach                $chart_title  = I18N::translate('Month of marriage');
379c6266a77SGreg Roach                $x_axis_title = I18N::translate('Month');
380c6266a77SGreg Roach                $x_axis       = $this->axisMonths();
381c6266a77SGreg Roach
382c6266a77SGreg Roach                switch ($y_axis_type) {
383c6266a77SGreg Roach                    case self::Y_AXIS_NUMBERS:
384c6266a77SGreg Roach                        $y_axis_title = I18N::translate('Families');
385c6266a77SGreg Roach                        break;
386c6266a77SGreg Roach                    case self::Y_AXIS_PERCENT:
387c6266a77SGreg Roach                        $y_axis_title = '%';
388c6266a77SGreg Roach                        break;
389c6266a77SGreg Roach                    default:
390d501c45dSGreg Roach                        throw new HttpNotFoundException();
391c6266a77SGreg Roach                }
392c6266a77SGreg Roach
393c6266a77SGreg Roach                switch ($z_axis_type) {
394c6266a77SGreg Roach                    case self::Z_AXIS_ALL:
395c6266a77SGreg Roach                        $z_axis = $this->axisAll();
396e6f3d5e2SGreg Roach                        $rows   = $statistics->statsMarriageQuery()->get();
397c6266a77SGreg Roach                        foreach ($rows as $row) {
398c6266a77SGreg Roach                            $this->fillYData($row->d_month, 0, $row->total, $x_axis, $z_axis, $ydata);
399c6266a77SGreg Roach                        }
400c6266a77SGreg Roach                        break;
401c6266a77SGreg Roach                    case self::Z_AXIS_TIME:
402b6b9dcc9SGreg Roach                        $boundaries_csv = $params['z-axis-boundaries-periods'];
403c6266a77SGreg Roach                        $z_axis         = $this->axisYears($boundaries_csv);
404c6266a77SGreg Roach                        $prev_boundary  = 0;
405c6266a77SGreg Roach                        foreach (array_keys($z_axis) as $boundary) {
406e6f3d5e2SGreg Roach                            $rows = $statistics->statsMarriageQuery($prev_boundary, $boundary)->get();
407c6266a77SGreg Roach                            foreach ($rows as $row) {
408c6266a77SGreg Roach                                $this->fillYData($row->d_month, $boundary, $row->total, $x_axis, $z_axis, $ydata);
409c6266a77SGreg Roach                            }
410c6266a77SGreg Roach                            $prev_boundary = $boundary + 1;
411c6266a77SGreg Roach                        }
412c6266a77SGreg Roach                        break;
413c6266a77SGreg Roach                    default:
414d501c45dSGreg Roach                        throw new HttpNotFoundException();
415c6266a77SGreg Roach                }
416c6266a77SGreg Roach
4176ccdf4f0SGreg Roach                return response($this->myPlot($chart_title, $x_axis, $x_axis_title, $ydata, $y_axis_title, $z_axis, $y_axis_type));
418c6266a77SGreg Roach
419c6266a77SGreg Roach            case self::X_AXIS_FIRST_CHILD_MONTH:
420c6266a77SGreg Roach                $chart_title  = I18N::translate('Month of birth of first child in a relation');
421c6266a77SGreg Roach                $x_axis_title = I18N::translate('Month');
422c6266a77SGreg Roach                $x_axis       = $this->axisMonths();
423c6266a77SGreg Roach
424c6266a77SGreg Roach                switch ($y_axis_type) {
425c6266a77SGreg Roach                    case self::Y_AXIS_NUMBERS:
426c6266a77SGreg Roach                        $y_axis_title = I18N::translate('Children');
427c6266a77SGreg Roach                        break;
428c6266a77SGreg Roach                    case self::Y_AXIS_PERCENT:
429c6266a77SGreg Roach                        $y_axis_title = '%';
430c6266a77SGreg Roach                        break;
431c6266a77SGreg Roach                    default:
432d501c45dSGreg Roach                        throw new HttpNotFoundException();
433c6266a77SGreg Roach                }
434c6266a77SGreg Roach
435c6266a77SGreg Roach                switch ($z_axis_type) {
436c6266a77SGreg Roach                    case self::Z_AXIS_ALL:
437c6266a77SGreg Roach                        $z_axis = $this->axisAll();
438999da590SGreg Roach                        $rows   = $statistics->monthFirstChildQuery()->get();
439c6266a77SGreg Roach                        foreach ($rows as $row) {
440c6266a77SGreg Roach                            $this->fillYData($row->d_month, 0, $row->total, $x_axis, $z_axis, $ydata);
441c6266a77SGreg Roach                        }
442c6266a77SGreg Roach                        break;
443c6266a77SGreg Roach                    case self::Z_AXIS_SEX:
444c6266a77SGreg Roach                        $z_axis = $this->axisSexes();
445999da590SGreg Roach                        $rows   = $statistics->monthFirstChildBySexQuery()->get();
446c6266a77SGreg Roach                        foreach ($rows as $row) {
447c6266a77SGreg Roach                            $this->fillYData($row->d_month, $row->i_sex, $row->total, $x_axis, $z_axis, $ydata);
448c6266a77SGreg Roach                        }
449c6266a77SGreg Roach                        break;
450c6266a77SGreg Roach                    case self::Z_AXIS_TIME:
451b6b9dcc9SGreg Roach                        $boundaries_csv = $params['z-axis-boundaries-periods'];
452c6266a77SGreg Roach                        $z_axis         = $this->axisYears($boundaries_csv);
453c6266a77SGreg Roach                        $prev_boundary  = 0;
454c6266a77SGreg Roach                        foreach (array_keys($z_axis) as $boundary) {
455999da590SGreg Roach                            $rows = $statistics->monthFirstChildQuery($prev_boundary, $boundary)->get();
456c6266a77SGreg Roach                            foreach ($rows as $row) {
457c6266a77SGreg Roach                                $this->fillYData($row->d_month, $boundary, $row->total, $x_axis, $z_axis, $ydata);
458c6266a77SGreg Roach                            }
459c6266a77SGreg Roach                            $prev_boundary = $boundary + 1;
460c6266a77SGreg Roach                        }
461c6266a77SGreg Roach                        break;
462c6266a77SGreg Roach                    default:
463d501c45dSGreg Roach                        throw new HttpNotFoundException();
464c6266a77SGreg Roach                }
465c6266a77SGreg Roach
4666ccdf4f0SGreg Roach                return response($this->myPlot($chart_title, $x_axis, $x_axis_title, $ydata, $y_axis_title, $z_axis, $y_axis_type));
467c6266a77SGreg Roach
468c6266a77SGreg Roach            case self::X_AXIS_FIRST_MARRIAGE_MONTH:
469c6266a77SGreg Roach                $chart_title  = I18N::translate('Month of first marriage');
470c6266a77SGreg Roach                $x_axis_title = I18N::translate('Month');
471c6266a77SGreg Roach                $x_axis       = $this->axisMonths();
472c6266a77SGreg Roach
473c6266a77SGreg Roach                switch ($y_axis_type) {
474c6266a77SGreg Roach                    case self::Y_AXIS_NUMBERS:
475c6266a77SGreg Roach                        $y_axis_title = I18N::translate('Families');
476c6266a77SGreg Roach                        break;
477c6266a77SGreg Roach                    case self::Y_AXIS_PERCENT:
478c6266a77SGreg Roach                        $y_axis_title = '%';
479c6266a77SGreg Roach                        break;
480c6266a77SGreg Roach                    default:
481d501c45dSGreg Roach                        throw new HttpNotFoundException();
482c6266a77SGreg Roach                }
483c6266a77SGreg Roach
484c6266a77SGreg Roach                switch ($z_axis_type) {
485c6266a77SGreg Roach                    case self::Z_AXIS_ALL:
486c6266a77SGreg Roach                        $z_axis = $this->axisAll();
487e6f3d5e2SGreg Roach                        $rows   = $statistics->statsFirstMarriageQuery()->get();
488c6266a77SGreg Roach                        $indi   = [];
489c6266a77SGreg Roach                        foreach ($rows as $row) {
490dca2dd78SGreg Roach                            if (!in_array($row->f_husb, $indi, true) && !in_array($row->f_wife, $indi, true)) {
491c6266a77SGreg Roach                                $this->fillYData($row->month, 0, 1, $x_axis, $z_axis, $ydata);
492c6266a77SGreg Roach                            }
493dca2dd78SGreg Roach                            $indi[]  = $row->f_husb;
494dca2dd78SGreg Roach                            $indi[]  = $row->f_wife;
495c6266a77SGreg Roach                        }
496c6266a77SGreg Roach                        break;
497c6266a77SGreg Roach                    case self::Z_AXIS_TIME:
498b6b9dcc9SGreg Roach                        $boundaries_csv = $params['z-axis-boundaries-periods'];
499c6266a77SGreg Roach                        $z_axis         = $this->axisYears($boundaries_csv);
500c6266a77SGreg Roach                        $prev_boundary  = 0;
501c6266a77SGreg Roach                        $indi           = [];
502c6266a77SGreg Roach                        foreach (array_keys($z_axis) as $boundary) {
503e6f3d5e2SGreg Roach                            $rows = $statistics->statsFirstMarriageQuery($prev_boundary, $boundary)->get();
504c6266a77SGreg Roach                            foreach ($rows as $row) {
505dca2dd78SGreg Roach                                if (!in_array($row->f_husb, $indi, true) && !in_array($row->f_wife, $indi, true)) {
506c6266a77SGreg Roach                                    $this->fillYData($row->month, $boundary, 1, $x_axis, $z_axis, $ydata);
507c6266a77SGreg Roach                                }
508dca2dd78SGreg Roach                                $indi[]  = $row->f_husb;
509dca2dd78SGreg Roach                                $indi[]  = $row->f_wife;
510c6266a77SGreg Roach                            }
511c6266a77SGreg Roach                            $prev_boundary = $boundary + 1;
512c6266a77SGreg Roach                        }
513c6266a77SGreg Roach                        break;
514c6266a77SGreg Roach                    default:
515d501c45dSGreg Roach                        throw new HttpNotFoundException();
516c6266a77SGreg Roach                }
517c6266a77SGreg Roach
5186ccdf4f0SGreg Roach                return response($this->myPlot($chart_title, $x_axis, $x_axis_title, $ydata, $y_axis_title, $z_axis, $y_axis_type));
519c6266a77SGreg Roach
520c6266a77SGreg Roach            case self::X_AXIS_AGE_AT_DEATH:
521c6266a77SGreg Roach                $chart_title    = I18N::translate('Average age at death');
522c6266a77SGreg Roach                $x_axis_title   = I18N::translate('age');
523b6b9dcc9SGreg Roach                $boundaries_csv = $params['x-axis-boundaries-ages'];
524c6266a77SGreg Roach                $x_axis         = $this->axisNumbers($boundaries_csv);
525c6266a77SGreg Roach
526c6266a77SGreg Roach                switch ($y_axis_type) {
527c6266a77SGreg Roach                    case self::Y_AXIS_NUMBERS:
528c6266a77SGreg Roach                        $y_axis_title = I18N::translate('Individuals');
529c6266a77SGreg Roach                        break;
530c6266a77SGreg Roach                    case self::Y_AXIS_PERCENT:
531c6266a77SGreg Roach                        $y_axis_title = '%';
532c6266a77SGreg Roach                        break;
533c6266a77SGreg Roach                    default:
534d501c45dSGreg Roach                        throw new HttpNotFoundException();
535c6266a77SGreg Roach                }
536c6266a77SGreg Roach
537c6266a77SGreg Roach                switch ($z_axis_type) {
538c6266a77SGreg Roach                    case self::Z_AXIS_ALL:
539c6266a77SGreg Roach                        $z_axis = $this->axisAll();
5409219296aSGreg Roach                        $rows   = $statistics->statsAgeQuery('DEAT');
541c6266a77SGreg Roach                        foreach ($rows as $row) {
542c6266a77SGreg Roach                            foreach ($row as $age) {
543c6266a77SGreg Roach                                $years = (int) ($age / self::DAYS_IN_YEAR);
544c6266a77SGreg Roach                                $this->fillYData($years, 0, 1, $x_axis, $z_axis, $ydata);
545c6266a77SGreg Roach                            }
546c6266a77SGreg Roach                        }
547c6266a77SGreg Roach                        break;
548c6266a77SGreg Roach                    case self::Z_AXIS_SEX:
549c6266a77SGreg Roach                        $z_axis = $this->axisSexes();
550c6266a77SGreg Roach                        foreach (array_keys($z_axis) as $sex) {
5519219296aSGreg Roach                            $rows = $statistics->statsAgeQuery('DEAT', $sex);
552c6266a77SGreg Roach                            foreach ($rows as $row) {
553c6266a77SGreg Roach                                foreach ($row as $age) {
554c6266a77SGreg Roach                                    $years = (int) ($age / self::DAYS_IN_YEAR);
555c6266a77SGreg Roach                                    $this->fillYData($years, $sex, 1, $x_axis, $z_axis, $ydata);
556c6266a77SGreg Roach                                }
557c6266a77SGreg Roach                            }
558c6266a77SGreg Roach                        }
559c6266a77SGreg Roach                        break;
560c6266a77SGreg Roach                    case self::Z_AXIS_TIME:
561b6b9dcc9SGreg Roach                        $boundaries_csv = $params['z-axis-boundaries-periods'];
562c6266a77SGreg Roach                        $z_axis         = $this->axisYears($boundaries_csv);
563c6266a77SGreg Roach                        $prev_boundary  = 0;
564c6266a77SGreg Roach                        foreach (array_keys($z_axis) as $boundary) {
5659219296aSGreg Roach                            $rows = $statistics->statsAgeQuery('DEAT', 'BOTH', $prev_boundary, $boundary);
566c6266a77SGreg Roach                            foreach ($rows as $row) {
567c6266a77SGreg Roach                                foreach ($row as $age) {
568c6266a77SGreg Roach                                    $years = (int) ($age / self::DAYS_IN_YEAR);
569c6266a77SGreg Roach                                    $this->fillYData($years, $boundary, 1, $x_axis, $z_axis, $ydata);
570c6266a77SGreg Roach                                }
571c6266a77SGreg Roach                            }
572c6266a77SGreg Roach                            $prev_boundary = $boundary + 1;
573c6266a77SGreg Roach                        }
574c6266a77SGreg Roach
575c6266a77SGreg Roach                        break;
576c6266a77SGreg Roach                    default:
577d501c45dSGreg Roach                        throw new HttpNotFoundException();
578c6266a77SGreg Roach                }
579c6266a77SGreg Roach
5806ccdf4f0SGreg Roach                return response($this->myPlot($chart_title, $x_axis, $x_axis_title, $ydata, $y_axis_title, $z_axis, $y_axis_type));
581c6266a77SGreg Roach
582c6266a77SGreg Roach            case self::X_AXIS_AGE_AT_MARRIAGE:
583c6266a77SGreg Roach                $chart_title    = I18N::translate('Age in year of marriage');
584c6266a77SGreg Roach                $x_axis_title   = I18N::translate('age');
585b6b9dcc9SGreg Roach                $boundaries_csv = $params['x-axis-boundaries-ages_m'];
586c6266a77SGreg Roach                $x_axis         = $this->axisNumbers($boundaries_csv);
587c6266a77SGreg Roach
588c6266a77SGreg Roach                switch ($y_axis_type) {
589c6266a77SGreg Roach                    case self::Y_AXIS_NUMBERS:
590c6266a77SGreg Roach                        $y_axis_title = I18N::translate('Individuals');
591c6266a77SGreg Roach                        break;
592c6266a77SGreg Roach                    case self::Y_AXIS_PERCENT:
593c6266a77SGreg Roach                        $y_axis_title = '%';
594c6266a77SGreg Roach                        break;
595c6266a77SGreg Roach                    default:
596d501c45dSGreg Roach                        throw new HttpNotFoundException();
597c6266a77SGreg Roach                }
598c6266a77SGreg Roach
599c6266a77SGreg Roach                switch ($z_axis_type) {
600c6266a77SGreg Roach                    case self::Z_AXIS_ALL:
601c6266a77SGreg Roach                        $z_axis = $this->axisAll();
602afa8d404SGreg Roach                        // The stats query doesn't have an "all" function, so query M/F separately
603afa8d404SGreg Roach                        foreach (['M', 'F'] as $sex) {
6049219296aSGreg Roach                            $rows = $statistics->statsMarrAgeQuery($sex);
605c6266a77SGreg Roach                            foreach ($rows as $row) {
606c6266a77SGreg Roach                                $years = (int) ($row->age / self::DAYS_IN_YEAR);
607c6266a77SGreg Roach                                $this->fillYData($years, 0, 1, $x_axis, $z_axis, $ydata);
608c6266a77SGreg Roach                            }
609c6266a77SGreg Roach                        }
610c6266a77SGreg Roach                        break;
611c6266a77SGreg Roach                    case self::Z_AXIS_SEX:
612c6266a77SGreg Roach                        $z_axis = $this->axisSexes();
613c6266a77SGreg Roach                        foreach (array_keys($z_axis) as $sex) {
6149219296aSGreg Roach                            $rows = $statistics->statsMarrAgeQuery($sex);
615c6266a77SGreg Roach                            foreach ($rows as $row) {
616c6266a77SGreg Roach                                $years = (int) ($row->age / self::DAYS_IN_YEAR);
617c6266a77SGreg Roach                                $this->fillYData($years, $sex, 1, $x_axis, $z_axis, $ydata);
618c6266a77SGreg Roach                            }
619c6266a77SGreg Roach                        }
620c6266a77SGreg Roach                        break;
621c6266a77SGreg Roach                    case self::Z_AXIS_TIME:
622b6b9dcc9SGreg Roach                        $boundaries_csv = $params['z-axis-boundaries-periods'];
623c6266a77SGreg Roach                        $z_axis         = $this->axisYears($boundaries_csv);
624afa8d404SGreg Roach                        // The stats query doesn't have an "all" function, so query M/F separately
625afa8d404SGreg Roach                        foreach (['M', 'F'] as $sex) {
626c6266a77SGreg Roach                            $prev_boundary = 0;
627c6266a77SGreg Roach                            foreach (array_keys($z_axis) as $boundary) {
6289219296aSGreg Roach                                $rows = $statistics->statsMarrAgeQuery($sex, $prev_boundary, $boundary);
629c6266a77SGreg Roach                                foreach ($rows as $row) {
630c6266a77SGreg Roach                                    $years = (int) ($row->age / self::DAYS_IN_YEAR);
631c6266a77SGreg Roach                                    $this->fillYData($years, $boundary, 1, $x_axis, $z_axis, $ydata);
632c6266a77SGreg Roach                                }
633c6266a77SGreg Roach                                $prev_boundary = $boundary + 1;
634c6266a77SGreg Roach                            }
635c6266a77SGreg Roach                        }
636c6266a77SGreg Roach                        break;
637c6266a77SGreg Roach                    default:
638d501c45dSGreg Roach                        throw new HttpNotFoundException();
639c6266a77SGreg Roach                }
640c6266a77SGreg Roach
6416ccdf4f0SGreg Roach                return response($this->myPlot($chart_title, $x_axis, $x_axis_title, $ydata, $y_axis_title, $z_axis, $y_axis_type));
642c6266a77SGreg Roach
643c6266a77SGreg Roach            case self::X_AXIS_AGE_AT_FIRST_MARRIAGE:
644c6266a77SGreg Roach                $chart_title    = I18N::translate('Age in year of first marriage');
645c6266a77SGreg Roach                $x_axis_title   = I18N::translate('age');
646b6b9dcc9SGreg Roach                $boundaries_csv = $params['x-axis-boundaries-ages_m'];
647c6266a77SGreg Roach                $x_axis         = $this->axisNumbers($boundaries_csv);
648c6266a77SGreg Roach
649c6266a77SGreg Roach                switch ($y_axis_type) {
650c6266a77SGreg Roach                    case self::Y_AXIS_NUMBERS:
651c6266a77SGreg Roach                        $y_axis_title = I18N::translate('Individuals');
652c6266a77SGreg Roach                        break;
653c6266a77SGreg Roach                    case self::Y_AXIS_PERCENT:
654c6266a77SGreg Roach                        $y_axis_title = '%';
655c6266a77SGreg Roach                        break;
656c6266a77SGreg Roach                    default:
657d501c45dSGreg Roach                        throw new HttpNotFoundException();
658c6266a77SGreg Roach                }
659c6266a77SGreg Roach
660c6266a77SGreg Roach                switch ($z_axis_type) {
661c6266a77SGreg Roach                    case self::Z_AXIS_ALL:
662c6266a77SGreg Roach                        $z_axis = $this->axisAll();
663afa8d404SGreg Roach                        // The stats query doesn't have an "all" function, so query M/F separately
664afa8d404SGreg Roach                        foreach (['M', 'F'] as $sex) {
6659219296aSGreg Roach                            $rows = $statistics->statsMarrAgeQuery($sex);
666c6266a77SGreg Roach                            $indi = [];
667c6266a77SGreg Roach                            foreach ($rows as $row) {
6686960d4a1SGreg Roach                                if (!in_array($row->d_gid, $indi, true)) {
669c6266a77SGreg Roach                                    $years = (int) ($row->age / self::DAYS_IN_YEAR);
670c6266a77SGreg Roach                                    $this->fillYData($years, 0, 1, $x_axis, $z_axis, $ydata);
671c6266a77SGreg Roach                                    $indi[] = $row->d_gid;
672c6266a77SGreg Roach                                }
673c6266a77SGreg Roach                            }
674c6266a77SGreg Roach                        }
675c6266a77SGreg Roach                        break;
676c6266a77SGreg Roach                    case self::Z_AXIS_SEX:
677c6266a77SGreg Roach                        $z_axis = $this->axisSexes();
678c6266a77SGreg Roach                        foreach (array_keys($z_axis) as $sex) {
6799219296aSGreg Roach                            $rows = $statistics->statsMarrAgeQuery($sex);
680c6266a77SGreg Roach                            $indi = [];
681c6266a77SGreg Roach                            foreach ($rows as $row) {
6826960d4a1SGreg Roach                                if (!in_array($row->d_gid, $indi, true)) {
683c6266a77SGreg Roach                                    $years = (int) ($row->age / self::DAYS_IN_YEAR);
684c6266a77SGreg Roach                                    $this->fillYData($years, $sex, 1, $x_axis, $z_axis, $ydata);
685c6266a77SGreg Roach                                    $indi[] = $row->d_gid;
686c6266a77SGreg Roach                                }
687c6266a77SGreg Roach                            }
688c6266a77SGreg Roach                        }
689c6266a77SGreg Roach                        break;
690c6266a77SGreg Roach                    case self::Z_AXIS_TIME:
691b6b9dcc9SGreg Roach                        $boundaries_csv = $params['z-axis-boundaries-periods'];
692c6266a77SGreg Roach                        $z_axis         = $this->axisYears($boundaries_csv);
693afa8d404SGreg Roach                        // The stats query doesn't have an "all" function, so query M/F separately
694afa8d404SGreg Roach                        foreach (['M', 'F'] as $sex) {
695c6266a77SGreg Roach                            $prev_boundary = 0;
696c6266a77SGreg Roach                            $indi          = [];
697c6266a77SGreg Roach                            foreach (array_keys($z_axis) as $boundary) {
6989219296aSGreg Roach                                $rows = $statistics->statsMarrAgeQuery($sex, $prev_boundary, $boundary);
699c6266a77SGreg Roach                                foreach ($rows as $row) {
7006960d4a1SGreg Roach                                    if (!in_array($row->d_gid, $indi, true)) {
701c6266a77SGreg Roach                                        $years = (int) ($row->age / self::DAYS_IN_YEAR);
702c6266a77SGreg Roach                                        $this->fillYData($years, $boundary, 1, $x_axis, $z_axis, $ydata);
703c6266a77SGreg Roach                                        $indi[] = $row->d_gid;
704c6266a77SGreg Roach                                    }
705c6266a77SGreg Roach                                }
706c6266a77SGreg Roach                                $prev_boundary = $boundary + 1;
707c6266a77SGreg Roach                            }
708c6266a77SGreg Roach                        }
709c6266a77SGreg Roach                        break;
710c6266a77SGreg Roach                    default:
711d501c45dSGreg Roach                        throw new HttpNotFoundException();
712c6266a77SGreg Roach                }
713c6266a77SGreg Roach
7146ccdf4f0SGreg Roach                return response($this->myPlot($chart_title, $x_axis, $x_axis_title, $ydata, $y_axis_title, $z_axis, $y_axis_type));
715c6266a77SGreg Roach
716c6266a77SGreg Roach            case self::X_AXIS_NUMBER_OF_CHILDREN:
717c6266a77SGreg Roach                $chart_title  = I18N::translate('Number of children');
718c6266a77SGreg Roach                $x_axis_title = I18N::translate('Children');
7196960d4a1SGreg Roach                $x_axis       = $this->axisNumbers('0,1,2,3,4,5,6,7,8,9,10');
720c6266a77SGreg Roach
721c6266a77SGreg Roach                switch ($y_axis_type) {
722c6266a77SGreg Roach                    case self::Y_AXIS_NUMBERS:
723c6266a77SGreg Roach                        $y_axis_title = I18N::translate('Families');
724c6266a77SGreg Roach                        break;
725c6266a77SGreg Roach                    case self::Y_AXIS_PERCENT:
726c6266a77SGreg Roach                        $y_axis_title = '%';
727c6266a77SGreg Roach                        break;
728c6266a77SGreg Roach                    default:
729d501c45dSGreg Roach                        throw new HttpNotFoundException();
730c6266a77SGreg Roach                }
731c6266a77SGreg Roach
732c6266a77SGreg Roach                switch ($z_axis_type) {
733c6266a77SGreg Roach                    case self::Z_AXIS_ALL:
734c6266a77SGreg Roach                        $z_axis = $this->axisAll();
7359219296aSGreg Roach                        $rows   = $statistics->statsChildrenQuery();
736c6266a77SGreg Roach                        foreach ($rows as $row) {
737c6266a77SGreg Roach                            $this->fillYData($row->f_numchil, 0, $row->total, $x_axis, $z_axis, $ydata);
738c6266a77SGreg Roach                        }
739c6266a77SGreg Roach                        break;
740c6266a77SGreg Roach                    case self::Z_AXIS_TIME:
741b6b9dcc9SGreg Roach                        $boundaries_csv = $params['z-axis-boundaries-periods'];
742c6266a77SGreg Roach                        $z_axis         = $this->axisYears($boundaries_csv);
743c6266a77SGreg Roach                        $prev_boundary  = 0;
744c6266a77SGreg Roach                        foreach (array_keys($z_axis) as $boundary) {
745b1126ab4SGreg Roach                            $rows = $statistics->statsChildrenQuery($prev_boundary, $boundary);
746c6266a77SGreg Roach                            foreach ($rows as $row) {
747c6266a77SGreg Roach                                $this->fillYData($row->f_numchil, $boundary, $row->total, $x_axis, $z_axis, $ydata);
748c6266a77SGreg Roach                            }
749c6266a77SGreg Roach                            $prev_boundary = $boundary + 1;
750c6266a77SGreg Roach                        }
751c6266a77SGreg Roach                        break;
752c6266a77SGreg Roach                    default:
753d501c45dSGreg Roach                        throw new HttpNotFoundException();
754c6266a77SGreg Roach                }
755c6266a77SGreg Roach
7566ccdf4f0SGreg Roach                return response($this->myPlot($chart_title, $x_axis, $x_axis_title, $ydata, $y_axis_title, $z_axis, $y_axis_type));
757c6266a77SGreg Roach
758c6266a77SGreg Roach            default:
759d501c45dSGreg Roach                throw new HttpNotFoundException();
760c6266a77SGreg Roach        }
761c6266a77SGreg Roach    }
762c6266a77SGreg Roach
763c6266a77SGreg Roach    /**
76424f2a3afSGreg Roach     * @return array<string>
765c6266a77SGreg Roach     */
766c6266a77SGreg Roach    private function axisAll(): array
767c6266a77SGreg Roach    {
768c6266a77SGreg Roach        return [
769c6266a77SGreg Roach            I18N::translate('Total'),
770c6266a77SGreg Roach        ];
771c6266a77SGreg Roach    }
772c6266a77SGreg Roach
773c6266a77SGreg Roach    /**
77424f2a3afSGreg Roach     * @return array<string>
775c6266a77SGreg Roach     */
776c6266a77SGreg Roach    private function axisSexes(): array
777c6266a77SGreg Roach    {
778c6266a77SGreg Roach        return [
779c6266a77SGreg Roach            'M' => I18N::translate('Male'),
780c6266a77SGreg Roach            'F' => I18N::translate('Female'),
781c6266a77SGreg Roach        ];
782c6266a77SGreg Roach    }
783c6266a77SGreg Roach
784c6266a77SGreg Roach    /**
785c6266a77SGreg Roach     * Labels for the X axis
786c6266a77SGreg Roach     *
78724f2a3afSGreg Roach     * @return array<string>
788c6266a77SGreg Roach     */
789c6266a77SGreg Roach    private function axisMonths(): array
790c6266a77SGreg Roach    {
791c6266a77SGreg Roach        return [
792c6266a77SGreg Roach            'JAN' => I18N::translateContext('NOMINATIVE', 'January'),
793c6266a77SGreg Roach            'FEB' => I18N::translateContext('NOMINATIVE', 'February'),
794c6266a77SGreg Roach            'MAR' => I18N::translateContext('NOMINATIVE', 'March'),
795c6266a77SGreg Roach            'APR' => I18N::translateContext('NOMINATIVE', 'April'),
796c6266a77SGreg Roach            'MAY' => I18N::translateContext('NOMINATIVE', 'May'),
797c6266a77SGreg Roach            'JUN' => I18N::translateContext('NOMINATIVE', 'June'),
798c6266a77SGreg Roach            'JUL' => I18N::translateContext('NOMINATIVE', 'July'),
799c6266a77SGreg Roach            'AUG' => I18N::translateContext('NOMINATIVE', 'August'),
800c6266a77SGreg Roach            'SEP' => I18N::translateContext('NOMINATIVE', 'September'),
801c6266a77SGreg Roach            'OCT' => I18N::translateContext('NOMINATIVE', 'October'),
802c6266a77SGreg Roach            'NOV' => I18N::translateContext('NOMINATIVE', 'November'),
803c6266a77SGreg Roach            'DEC' => I18N::translateContext('NOMINATIVE', 'December'),
804c6266a77SGreg Roach        ];
805c6266a77SGreg Roach    }
806c6266a77SGreg Roach
807c6266a77SGreg Roach    /**
808c6266a77SGreg Roach     * Convert a list of N year-boundaries into N+1 year-ranges for the z-axis.
809c6266a77SGreg Roach     *
810c6266a77SGreg Roach     * @param string $boundaries_csv
811c6266a77SGreg Roach     *
81224f2a3afSGreg Roach     * @return array<string>
813c6266a77SGreg Roach     */
814c6266a77SGreg Roach    private function axisYears(string $boundaries_csv): array
815c6266a77SGreg Roach    {
816c6266a77SGreg Roach        $boundaries = explode(',', $boundaries_csv);
817c6266a77SGreg Roach
818c6266a77SGreg Roach        $axis = [];
819c6266a77SGreg Roach        foreach ($boundaries as $n => $boundary) {
820c6266a77SGreg Roach            if ($n === 0) {
82167ff9eb7SGreg Roach                $axis[$boundary - 1] = '–' . I18N::digits($boundary);
822c6266a77SGreg Roach            } else {
82367ff9eb7SGreg Roach                $axis[$boundary - 1] = I18N::digits($boundaries[$n - 1]) . '–' . I18N::digits($boundary);
824c6266a77SGreg Roach            }
825c6266a77SGreg Roach        }
826c6266a77SGreg Roach
82767ff9eb7SGreg Roach        $axis[PHP_INT_MAX] = I18N::digits($boundaries[count($boundaries) - 1]) . '–';
828c6266a77SGreg Roach
829c6266a77SGreg Roach        return $axis;
830c6266a77SGreg Roach    }
831c6266a77SGreg Roach
832c6266a77SGreg Roach    /**
833c6266a77SGreg Roach     * Create the X axis.
834c6266a77SGreg Roach     *
835c6266a77SGreg Roach     * @param string $boundaries_csv
836c6266a77SGreg Roach     *
837fc26b4f6SGreg Roach     * @return array<string>
838c6266a77SGreg Roach     */
839c6266a77SGreg Roach    private function axisNumbers(string $boundaries_csv): array
840c6266a77SGreg Roach    {
841c6266a77SGreg Roach        $boundaries = explode(',', $boundaries_csv);
842c6266a77SGreg Roach
8430b5fd0a6SGreg Roach        $boundaries = array_map(static function (string $x): int {
844c6266a77SGreg Roach            return (int) $x;
845c6266a77SGreg Roach        }, $boundaries);
846c6266a77SGreg Roach
847c6266a77SGreg Roach        $axis = [];
848c6266a77SGreg Roach        foreach ($boundaries as $n => $boundary) {
849c6266a77SGreg Roach            if ($n === 0) {
8506960d4a1SGreg Roach                $prev_boundary = 0;
8516960d4a1SGreg Roach            } else {
8526960d4a1SGreg Roach                $prev_boundary = $boundaries[$n - 1] + 1;
8536960d4a1SGreg Roach            }
8546960d4a1SGreg Roach
8556960d4a1SGreg Roach            if ($prev_boundary === $boundary) {
856c6266a77SGreg Roach                /* I18N: A range of numbers */
8576960d4a1SGreg Roach                $axis[$boundary] = I18N::number($boundary);
858c6266a77SGreg Roach            } else {
859c6266a77SGreg Roach                /* I18N: A range of numbers */
8606960d4a1SGreg Roach                $axis[$boundary] = I18N::translate('%1$s–%2$s', I18N::number($prev_boundary), I18N::number($boundary));
861c6266a77SGreg Roach            }
862c6266a77SGreg Roach        }
863c6266a77SGreg Roach
864c6266a77SGreg Roach        /* I18N: Label on a graph; 40+ means 40 or more */
865c6266a77SGreg Roach        $axis[PHP_INT_MAX] = I18N::translate('%s+', I18N::number($boundaries[count($boundaries) - 1]));
866c6266a77SGreg Roach
867c6266a77SGreg Roach        return $axis;
868c6266a77SGreg Roach    }
869c6266a77SGreg Roach
870c6266a77SGreg Roach    /**
871c6266a77SGreg Roach     * Calculate the Y axis.
872c6266a77SGreg Roach     *
873c6266a77SGreg Roach     * @param int|string $x
874c6266a77SGreg Roach     * @param int|string $z
875c6266a77SGreg Roach     * @param int|string $value
876c6266a77SGreg Roach     * @param array      $x_axis
877c6266a77SGreg Roach     * @param array      $z_axis
878c6266a77SGreg Roach     * @param int[][]    $ydata
879c6266a77SGreg Roach     *
880c6266a77SGreg Roach     * @return void
881c6266a77SGreg Roach     */
882e364afe4SGreg Roach    private function fillYData($x, $z, $value, array $x_axis, array $z_axis, array &$ydata): void
883c6266a77SGreg Roach    {
884c6266a77SGreg Roach        $x = $this->findAxisEntry($x, $x_axis);
885c6266a77SGreg Roach        $z = $this->findAxisEntry($z, $z_axis);
886c6266a77SGreg Roach
8876960d4a1SGreg Roach        if (!array_key_exists($z, $z_axis)) {
888c6266a77SGreg Roach            foreach (array_keys($z_axis) as $key) {
889c6266a77SGreg Roach                if ($value <= $key) {
890c6266a77SGreg Roach                    $z = $key;
891c6266a77SGreg Roach                    break;
892c6266a77SGreg Roach                }
893c6266a77SGreg Roach            }
894c6266a77SGreg Roach        }
895c6266a77SGreg Roach
896c6266a77SGreg Roach        // Add the value to the appropriate data point.
897c6266a77SGreg Roach        $ydata[$z][$x] = ($ydata[$z][$x] ?? 0) + $value;
898c6266a77SGreg Roach    }
899c6266a77SGreg Roach
900c6266a77SGreg Roach    /**
901c6266a77SGreg Roach     * Find the axis entry for a given value.
902c6266a77SGreg Roach     * Some are direct lookup (e.g. M/F, JAN/FEB/MAR).
903d823340dSGreg Roach     * Others need to find the appropriate range.
904c6266a77SGreg Roach     *
905c6266a77SGreg Roach     * @param int|float|string $value
906c6266a77SGreg Roach     * @param string[]         $axis
907c6266a77SGreg Roach     *
908c6266a77SGreg Roach     * @return int|string
909c6266a77SGreg Roach     */
91024f2a3afSGreg Roach    private function findAxisEntry($value, array $axis)
911c6266a77SGreg Roach    {
912c6266a77SGreg Roach        if (is_numeric($value)) {
913c6266a77SGreg Roach            $value = (int) $value;
914c6266a77SGreg Roach
9156960d4a1SGreg Roach            if (!array_key_exists($value, $axis)) {
916c6266a77SGreg Roach                foreach (array_keys($axis) as $boundary) {
917c6266a77SGreg Roach                    if ($value <= $boundary) {
918c6266a77SGreg Roach                        $value = $boundary;
919c6266a77SGreg Roach                        break;
920c6266a77SGreg Roach                    }
921c6266a77SGreg Roach                }
922c6266a77SGreg Roach            }
923c6266a77SGreg Roach        }
924c6266a77SGreg Roach
925c6266a77SGreg Roach        return $value;
926c6266a77SGreg Roach    }
927c6266a77SGreg Roach
928c6266a77SGreg Roach    /**
929c6266a77SGreg Roach     * Plot the data.
930c6266a77SGreg Roach     *
931c6266a77SGreg Roach     * @param string   $chart_title
932c6266a77SGreg Roach     * @param string[] $x_axis
933c6266a77SGreg Roach     * @param string   $x_axis_title
934c6266a77SGreg Roach     * @param int[][]  $ydata
935c6266a77SGreg Roach     * @param string   $y_axis_title
936c6266a77SGreg Roach     * @param string[] $z_axis
937c6266a77SGreg Roach     * @param int      $y_axis_type
938c6266a77SGreg Roach     *
939c6266a77SGreg Roach     * @return string
940c6266a77SGreg Roach     */
941a81e5019SRico Sonntag    private function myPlot(
942a81e5019SRico Sonntag        string $chart_title,
943a81e5019SRico Sonntag        array $x_axis,
944a81e5019SRico Sonntag        string $x_axis_title,
945a81e5019SRico Sonntag        array $ydata,
946a81e5019SRico Sonntag        string $y_axis_title,
947a81e5019SRico Sonntag        array $z_axis,
948a81e5019SRico Sonntag        int $y_axis_type
949a81e5019SRico Sonntag    ): string {
9506960d4a1SGreg Roach        if (!count($ydata)) {
951a81e5019SRico Sonntag            return I18N::translate('This information is not available.');
952c6266a77SGreg Roach        }
953c6266a77SGreg Roach
954c6266a77SGreg Roach        // Colors for z-axis
955c6266a77SGreg Roach        $colors = [];
956c6266a77SGreg Roach        $index  = 0;
9576960d4a1SGreg Roach        while (count($colors) < count($ydata)) {
958c6266a77SGreg Roach            $colors[] = self::Z_AXIS_COLORS[$index];
9596960d4a1SGreg Roach            $index    = ($index + 1) % count(self::Z_AXIS_COLORS);
960c6266a77SGreg Roach        }
961c6266a77SGreg Roach
962c6266a77SGreg Roach        // Convert our sparse dataset into a fixed-size array
963c6266a77SGreg Roach        $tmp = [];
964c6266a77SGreg Roach        foreach (array_keys($z_axis) as $z) {
965c6266a77SGreg Roach            foreach (array_keys($x_axis) as $x) {
966c6266a77SGreg Roach                $tmp[$z][$x] = $ydata[$z][$x] ?? 0;
967c6266a77SGreg Roach            }
968c6266a77SGreg Roach        }
969c6266a77SGreg Roach        $ydata = $tmp;
970c6266a77SGreg Roach
971a81e5019SRico Sonntag        // Convert the chart data to percentage
972c6266a77SGreg Roach        if ($y_axis_type === self::Y_AXIS_PERCENT) {
973c6266a77SGreg Roach            // Normalise each (non-zero!) set of data to total 100%
9740b5fd0a6SGreg Roach            array_walk($ydata, static function (array &$x) {
975c6266a77SGreg Roach                $sum = array_sum($x);
976c6266a77SGreg Roach                if ($sum > 0) {
9770b5fd0a6SGreg Roach                    $x = array_map(static function ($y) use ($sum) {
978c6266a77SGreg Roach                        return $y * 100.0 / $sum;
979c6266a77SGreg Roach                    }, $x);
980c6266a77SGreg Roach                }
981c6266a77SGreg Roach            });
982c6266a77SGreg Roach        }
983c6266a77SGreg Roach
984a81e5019SRico Sonntag        $data = [
985a81e5019SRico Sonntag            array_merge(
986a81e5019SRico Sonntag                [I18N::translate('Century')],
987a81e5019SRico Sonntag                array_values($z_axis)
98871378461SGreg Roach            ),
989c6266a77SGreg Roach        ];
990c6266a77SGreg Roach
991a81e5019SRico Sonntag        $intermediate = [];
992a81e5019SRico Sonntag        foreach ($ydata as $century => $months) {
993a81e5019SRico Sonntag            foreach ($months as $month => $value) {
994a81e5019SRico Sonntag                $intermediate[$month][] = [
995a81e5019SRico Sonntag                    'v' => $value,
996a81e5019SRico Sonntag                    'f' => ($y_axis_type === self::Y_AXIS_PERCENT) ? sprintf('%.1f%%', $value) : $value,
997a81e5019SRico Sonntag                ];
998a81e5019SRico Sonntag            }
999c6266a77SGreg Roach        }
1000c6266a77SGreg Roach
1001a81e5019SRico Sonntag        foreach ($intermediate as $key => $values) {
1002a81e5019SRico Sonntag            $data[] = array_merge(
1003a81e5019SRico Sonntag                [$x_axis[$key]],
1004a81e5019SRico Sonntag                $values
1005a81e5019SRico Sonntag            );
1006a81e5019SRico Sonntag        }
1007c6266a77SGreg Roach
1008a81e5019SRico Sonntag        $chart_options = [
1009a81e5019SRico Sonntag            'title'    => '',
1010a81e5019SRico Sonntag            'subtitle' => '',
1011a81e5019SRico Sonntag            'height'   => 400,
1012a81e5019SRico Sonntag            'width'    => '100%',
1013a81e5019SRico Sonntag            'legend'   => [
10146960d4a1SGreg Roach                'position'  => count($z_axis) > 1 ? 'right' : 'none',
1015a81e5019SRico Sonntag                'alignment' => 'center',
1016a81e5019SRico Sonntag            ],
1017a81e5019SRico Sonntag            'tooltip'  => [
1018a81e5019SRico Sonntag                'format' => '\'%\'',
1019a81e5019SRico Sonntag            ],
1020a81e5019SRico Sonntag            'vAxis'    => [
1021a81e5019SRico Sonntag                'title' => $y_axis_title ?? '',
1022a81e5019SRico Sonntag            ],
1023a81e5019SRico Sonntag            'hAxis'    => [
1024a81e5019SRico Sonntag                'title' => $x_axis_title ?? '',
1025a81e5019SRico Sonntag            ],
1026a81e5019SRico Sonntag            'colors'   => $colors,
1027a81e5019SRico Sonntag        ];
1028a81e5019SRico Sonntag
102990a2f718SGreg Roach        return view('statistics/other/charts/custom', [
1030a81e5019SRico Sonntag            'data'          => $data,
1031a81e5019SRico Sonntag            'chart_options' => $chart_options,
1032a81e5019SRico Sonntag            'chart_title'   => $chart_title,
103365cf5706SGreg Roach            'language'      => I18N::languageTag(),
103490a2f718SGreg Roach        ]);
1035c6266a77SGreg Roach    }
1036168ff6f3Sric2016}
1037