1168ff6f3Sric2016<?php 23976b470SGreg Roach 3168ff6f3Sric2016/** 4168ff6f3Sric2016 * webtrees: online genealogy 55bfc6897SGreg Roach * Copyright (C) 2022 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; 2381b729d3SGreg Roachuse Fisharebest\Webtrees\Http\Exceptions\HttpNotFoundException; 24168ff6f3Sric2016use Fisharebest\Webtrees\I18N; 25168ff6f3Sric2016use Fisharebest\Webtrees\Individual; 269219296aSGreg Roachuse Fisharebest\Webtrees\Statistics; 27b55cbc6bSGreg Roachuse Fisharebest\Webtrees\Validator; 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 11576d39c55SGreg Roach * @param array<bool|int|string|array<string>|null> $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 { 137b55cbc6bSGreg Roach $tree = Validator::attributes($request)->tree(); 138b55cbc6bSGreg Roach $user = Validator::attributes($request)->user(); 13957ab2231SGreg Roach 140ef483801SGreg Roach Auth::checkComponentAccess($this, ModuleChartInterface::class, $tree, $user); 1419867b2f0SGreg Roach 142c6266a77SGreg Roach $tabs = [ 143c6266a77SGreg Roach I18N::translate('Individuals') => route('module', [ 144c6266a77SGreg Roach 'module' => $this->name(), 145c6266a77SGreg Roach 'action' => 'Individuals', 146d72b284aSGreg Roach 'tree' => $tree->name(), 147c6266a77SGreg Roach ]), 148c6266a77SGreg Roach I18N::translate('Families') => route('module', [ 149c6266a77SGreg Roach 'module' => $this->name(), 150c6266a77SGreg Roach 'action' => 'Families', 151d72b284aSGreg Roach 'tree' => $tree->name(), 152c6266a77SGreg Roach ]), 153c6266a77SGreg Roach I18N::translate('Other') => route('module', [ 154c6266a77SGreg Roach 'module' => $this->name(), 155c6266a77SGreg Roach 'action' => 'Other', 156d72b284aSGreg Roach 'tree' => $tree->name(), 157c6266a77SGreg Roach ]), 158c6266a77SGreg Roach I18N::translate('Custom') => route('module', [ 159c6266a77SGreg Roach 'module' => $this->name(), 160c6266a77SGreg Roach 'action' => 'Custom', 161d72b284aSGreg Roach 'tree' => $tree->name(), 162c6266a77SGreg Roach ]), 163c6266a77SGreg Roach ]; 164c6266a77SGreg Roach 1659b5537c3SGreg Roach return $this->viewResponse('modules/statistics-chart/page', [ 16671378461SGreg Roach 'module' => $this->name(), 167c6266a77SGreg Roach 'tabs' => $tabs, 168c6266a77SGreg Roach 'title' => $this->title(), 169ef5d23f1SGreg Roach 'tree' => $tree, 170c6266a77SGreg Roach ]); 171c6266a77SGreg Roach } 172c6266a77SGreg Roach 173c6266a77SGreg Roach /** 17457ab2231SGreg Roach * @param ServerRequestInterface $request 175c6266a77SGreg Roach * 1766ccdf4f0SGreg Roach * @return ResponseInterface 177c6266a77SGreg Roach */ 178a09ea7ccSGreg Roach public function getIndividualsAction(/** @scrutinizer ignore-unused */ ServerRequestInterface $request): ResponseInterface 179c6266a77SGreg Roach { 180b6c326d8SGreg Roach $this->layout = 'layouts/ajax'; 181b6c326d8SGreg Roach 182b6c326d8SGreg Roach return $this->viewResponse('modules/statistics-chart/individuals', [ 183c6266a77SGreg Roach 'show_oldest_living' => Auth::check(), 184*559d5fa0SGreg Roach 'statistics' => app(Statistics::class), 185c6266a77SGreg Roach ]); 186c6266a77SGreg Roach } 187c6266a77SGreg Roach 188c6266a77SGreg Roach /** 18957ab2231SGreg Roach * @param ServerRequestInterface $request 190c6266a77SGreg Roach * 1916ccdf4f0SGreg Roach * @return ResponseInterface 192c6266a77SGreg Roach */ 193a09ea7ccSGreg Roach public function getFamiliesAction(/** @scrutinizer ignore-unused */ ServerRequestInterface $request): ResponseInterface 194c6266a77SGreg Roach { 195b6c326d8SGreg Roach $this->layout = 'layouts/ajax'; 196b6c326d8SGreg Roach 197b6c326d8SGreg Roach return $this->viewResponse('modules/statistics-chart/families', [ 198*559d5fa0SGreg Roach 'statistics' => app(Statistics::class), 199c6266a77SGreg Roach ]); 200c6266a77SGreg Roach } 201c6266a77SGreg Roach 202c6266a77SGreg Roach /** 20357ab2231SGreg Roach * @param ServerRequestInterface $request 204c6266a77SGreg Roach * 2056ccdf4f0SGreg Roach * @return ResponseInterface 206c6266a77SGreg Roach */ 207a09ea7ccSGreg Roach public function getOtherAction(/** @scrutinizer ignore-unused */ ServerRequestInterface $request): ResponseInterface 208c6266a77SGreg Roach { 209b6c326d8SGreg Roach $this->layout = 'layouts/ajax'; 210b6c326d8SGreg Roach 211b6c326d8SGreg Roach return $this->viewResponse('modules/statistics-chart/other', [ 212*559d5fa0SGreg Roach 'statistics' => app(Statistics::class), 213c6266a77SGreg Roach ]); 214c6266a77SGreg Roach } 215c6266a77SGreg Roach 216c6266a77SGreg Roach /** 21757ab2231SGreg Roach * @param ServerRequestInterface $request 218c6266a77SGreg Roach * 2196ccdf4f0SGreg Roach * @return ResponseInterface 220c6266a77SGreg Roach */ 22157ab2231SGreg Roach public function getCustomAction(ServerRequestInterface $request): ResponseInterface 222c6266a77SGreg Roach { 223b6c326d8SGreg Roach $this->layout = 'layouts/ajax'; 224b6c326d8SGreg Roach 225b55cbc6bSGreg Roach $tree = Validator::attributes($request)->tree(); 22657ab2231SGreg Roach 227b6c326d8SGreg Roach return $this->viewResponse('modules/statistics-chart/custom', [ 228c6266a77SGreg Roach 'module' => $this, 229c6266a77SGreg Roach 'tree' => $tree, 230c6266a77SGreg Roach ]); 231c6266a77SGreg Roach } 232c6266a77SGreg Roach 233c6266a77SGreg Roach /** 2346ccdf4f0SGreg Roach * @param ServerRequestInterface $request 235c6266a77SGreg Roach * 2366ccdf4f0SGreg Roach * @return ResponseInterface 237c6266a77SGreg Roach */ 2383aa29b97SGreg Roach public function postCustomChartAction(ServerRequestInterface $request): ResponseInterface 239c6266a77SGreg Roach { 24057ab2231SGreg Roach $statistics = app(Statistics::class); 241dca2dd78SGreg Roach assert($statistics instanceof Statistics); 24257ab2231SGreg Roach 243b46c87bdSGreg Roach $params = (array) $request->getParsedBody(); 244b6b9dcc9SGreg Roach 245b6b9dcc9SGreg Roach $x_axis_type = (int) $params['x-as']; 246b6b9dcc9SGreg Roach $y_axis_type = (int) $params['y-as']; 247b6b9dcc9SGreg Roach $z_axis_type = (int) $params['z-as']; 248c6266a77SGreg Roach $ydata = []; 249c6266a77SGreg Roach 250c6266a77SGreg Roach switch ($x_axis_type) { 251c6266a77SGreg Roach case self::X_AXIS_INDIVIDUAL_MAP: 2526ccdf4f0SGreg Roach return response($statistics->chartDistribution( 253b6b9dcc9SGreg Roach $params['chart_shows'], 254b6b9dcc9SGreg Roach $params['chart_type'], 255b6b9dcc9SGreg Roach $params['SURN'] 256c6266a77SGreg Roach )); 257c6266a77SGreg Roach 258c6266a77SGreg Roach case self::X_AXIS_BIRTH_MAP: 2596ccdf4f0SGreg Roach return response($statistics->chartDistribution( 260b6b9dcc9SGreg Roach $params['chart_shows'], 261c6266a77SGreg Roach 'birth_distribution_chart' 262c6266a77SGreg Roach )); 263c6266a77SGreg Roach 264c6266a77SGreg Roach case self::X_AXIS_DEATH_MAP: 2656ccdf4f0SGreg Roach return response($statistics->chartDistribution( 266b6b9dcc9SGreg Roach $params['chart_shows'], 267c6266a77SGreg Roach 'death_distribution_chart' 268c6266a77SGreg Roach )); 269c6266a77SGreg Roach 270c6266a77SGreg Roach case self::X_AXIS_MARRIAGE_MAP: 2716ccdf4f0SGreg Roach return response($statistics->chartDistribution( 272b6b9dcc9SGreg Roach $params['chart_shows'], 273c6266a77SGreg Roach 'marriage_distribution_chart' 274c6266a77SGreg Roach )); 275c6266a77SGreg Roach 276c6266a77SGreg Roach case self::X_AXIS_BIRTH_MONTH: 277c6266a77SGreg Roach $chart_title = I18N::translate('Month of birth'); 278c6266a77SGreg Roach $x_axis_title = I18N::translate('Month'); 279c6266a77SGreg Roach $x_axis = $this->axisMonths(); 280c6266a77SGreg Roach 281c6266a77SGreg Roach switch ($y_axis_type) { 282c6266a77SGreg Roach case self::Y_AXIS_NUMBERS: 283c6266a77SGreg Roach $y_axis_title = I18N::translate('Individuals'); 284c6266a77SGreg Roach break; 285c6266a77SGreg Roach case self::Y_AXIS_PERCENT: 286c6266a77SGreg Roach $y_axis_title = '%'; 287c6266a77SGreg Roach break; 288c6266a77SGreg Roach default: 289d501c45dSGreg Roach throw new HttpNotFoundException(); 290c6266a77SGreg Roach } 291c6266a77SGreg Roach 292c6266a77SGreg Roach switch ($z_axis_type) { 293c6266a77SGreg Roach case self::Z_AXIS_ALL: 294c6266a77SGreg Roach $z_axis = $this->axisAll(); 295cde1d378SGreg Roach $rows = $statistics->statsBirthQuery()->get(); 296c6266a77SGreg Roach foreach ($rows as $row) { 297c6266a77SGreg Roach $this->fillYData($row->d_month, 0, $row->total, $x_axis, $z_axis, $ydata); 298c6266a77SGreg Roach } 299c6266a77SGreg Roach break; 300c6266a77SGreg Roach case self::Z_AXIS_SEX: 301c6266a77SGreg Roach $z_axis = $this->axisSexes(); 302cde1d378SGreg Roach $rows = $statistics->statsBirthBySexQuery()->get(); 303c6266a77SGreg Roach foreach ($rows as $row) { 304c6266a77SGreg Roach $this->fillYData($row->d_month, $row->i_sex, $row->total, $x_axis, $z_axis, $ydata); 305c6266a77SGreg Roach } 306c6266a77SGreg Roach break; 307c6266a77SGreg Roach case self::Z_AXIS_TIME: 308b6b9dcc9SGreg Roach $boundaries_csv = $params['z-axis-boundaries-periods']; 309c6266a77SGreg Roach $z_axis = $this->axisYears($boundaries_csv); 310c6266a77SGreg Roach $prev_boundary = 0; 311c6266a77SGreg Roach foreach (array_keys($z_axis) as $boundary) { 312cde1d378SGreg Roach $rows = $statistics->statsBirthQuery($prev_boundary, $boundary)->get(); 313c6266a77SGreg Roach foreach ($rows as $row) { 314c6266a77SGreg Roach $this->fillYData($row->d_month, $boundary, $row->total, $x_axis, $z_axis, $ydata); 315c6266a77SGreg Roach } 316c6266a77SGreg Roach $prev_boundary = $boundary + 1; 317c6266a77SGreg Roach } 318c6266a77SGreg Roach break; 319c6266a77SGreg Roach default: 320d501c45dSGreg Roach throw new HttpNotFoundException(); 321c6266a77SGreg Roach } 322c6266a77SGreg Roach 3236ccdf4f0SGreg Roach return response($this->myPlot($chart_title, $x_axis, $x_axis_title, $ydata, $y_axis_title, $z_axis, $y_axis_type)); 324c6266a77SGreg Roach 325c6266a77SGreg Roach case self::X_AXIS_DEATH_MONTH: 326c6266a77SGreg Roach $chart_title = I18N::translate('Month of death'); 327c6266a77SGreg Roach $x_axis_title = I18N::translate('Month'); 328c6266a77SGreg Roach $x_axis = $this->axisMonths(); 329c6266a77SGreg Roach 330c6266a77SGreg Roach switch ($y_axis_type) { 331c6266a77SGreg Roach case self::Y_AXIS_NUMBERS: 332c6266a77SGreg Roach $y_axis_title = I18N::translate('Individuals'); 333c6266a77SGreg Roach break; 334c6266a77SGreg Roach case self::Y_AXIS_PERCENT: 335c6266a77SGreg Roach $y_axis_title = '%'; 336c6266a77SGreg Roach break; 337c6266a77SGreg Roach default: 338d501c45dSGreg Roach throw new HttpNotFoundException(); 339c6266a77SGreg Roach } 340c6266a77SGreg Roach 341c6266a77SGreg Roach switch ($z_axis_type) { 342c6266a77SGreg Roach case self::Z_AXIS_ALL: 343c6266a77SGreg Roach $z_axis = $this->axisAll(); 344cde1d378SGreg Roach $rows = $statistics->statsDeathQuery()->get(); 345c6266a77SGreg Roach foreach ($rows as $row) { 346c6266a77SGreg Roach $this->fillYData($row->d_month, 0, $row->total, $x_axis, $z_axis, $ydata); 347c6266a77SGreg Roach } 348c6266a77SGreg Roach break; 349c6266a77SGreg Roach case self::Z_AXIS_SEX: 350c6266a77SGreg Roach $z_axis = $this->axisSexes(); 351cde1d378SGreg Roach $rows = $statistics->statsDeathBySexQuery()->get(); 352c6266a77SGreg Roach foreach ($rows as $row) { 353c6266a77SGreg Roach $this->fillYData($row->d_month, $row->i_sex, $row->total, $x_axis, $z_axis, $ydata); 354c6266a77SGreg Roach } 355c6266a77SGreg Roach break; 356c6266a77SGreg Roach case self::Z_AXIS_TIME: 357b6b9dcc9SGreg Roach $boundaries_csv = $params['z-axis-boundaries-periods']; 358c6266a77SGreg Roach $z_axis = $this->axisYears($boundaries_csv); 359c6266a77SGreg Roach $prev_boundary = 0; 360c6266a77SGreg Roach foreach (array_keys($z_axis) as $boundary) { 361cde1d378SGreg Roach $rows = $statistics->statsDeathQuery($prev_boundary, $boundary)->get(); 362c6266a77SGreg Roach foreach ($rows as $row) { 363c6266a77SGreg Roach $this->fillYData($row->d_month, $boundary, $row->total, $x_axis, $z_axis, $ydata); 364c6266a77SGreg Roach } 365c6266a77SGreg Roach $prev_boundary = $boundary + 1; 366c6266a77SGreg Roach } 367c6266a77SGreg Roach break; 368c6266a77SGreg Roach default: 369d501c45dSGreg Roach throw new HttpNotFoundException(); 370c6266a77SGreg Roach } 371c6266a77SGreg Roach 3726ccdf4f0SGreg Roach return response($this->myPlot($chart_title, $x_axis, $x_axis_title, $ydata, $y_axis_title, $z_axis, $y_axis_type)); 373c6266a77SGreg Roach 374c6266a77SGreg Roach case self::X_AXIS_MARRIAGE_MONTH: 375c6266a77SGreg Roach $chart_title = I18N::translate('Month of marriage'); 376c6266a77SGreg Roach $x_axis_title = I18N::translate('Month'); 377c6266a77SGreg Roach $x_axis = $this->axisMonths(); 378c6266a77SGreg Roach 379c6266a77SGreg Roach switch ($y_axis_type) { 380c6266a77SGreg Roach case self::Y_AXIS_NUMBERS: 381c6266a77SGreg Roach $y_axis_title = I18N::translate('Families'); 382c6266a77SGreg Roach break; 383c6266a77SGreg Roach case self::Y_AXIS_PERCENT: 384c6266a77SGreg Roach $y_axis_title = '%'; 385c6266a77SGreg Roach break; 386c6266a77SGreg Roach default: 387d501c45dSGreg Roach throw new HttpNotFoundException(); 388c6266a77SGreg Roach } 389c6266a77SGreg Roach 390c6266a77SGreg Roach switch ($z_axis_type) { 391c6266a77SGreg Roach case self::Z_AXIS_ALL: 392c6266a77SGreg Roach $z_axis = $this->axisAll(); 393e6f3d5e2SGreg Roach $rows = $statistics->statsMarriageQuery()->get(); 394c6266a77SGreg Roach foreach ($rows as $row) { 395c6266a77SGreg Roach $this->fillYData($row->d_month, 0, $row->total, $x_axis, $z_axis, $ydata); 396c6266a77SGreg Roach } 397c6266a77SGreg Roach break; 398c6266a77SGreg Roach case self::Z_AXIS_TIME: 399b6b9dcc9SGreg Roach $boundaries_csv = $params['z-axis-boundaries-periods']; 400c6266a77SGreg Roach $z_axis = $this->axisYears($boundaries_csv); 401c6266a77SGreg Roach $prev_boundary = 0; 402c6266a77SGreg Roach foreach (array_keys($z_axis) as $boundary) { 403e6f3d5e2SGreg Roach $rows = $statistics->statsMarriageQuery($prev_boundary, $boundary)->get(); 404c6266a77SGreg Roach foreach ($rows as $row) { 405c6266a77SGreg Roach $this->fillYData($row->d_month, $boundary, $row->total, $x_axis, $z_axis, $ydata); 406c6266a77SGreg Roach } 407c6266a77SGreg Roach $prev_boundary = $boundary + 1; 408c6266a77SGreg Roach } 409c6266a77SGreg Roach break; 410c6266a77SGreg Roach default: 411d501c45dSGreg Roach throw new HttpNotFoundException(); 412c6266a77SGreg Roach } 413c6266a77SGreg Roach 4146ccdf4f0SGreg Roach return response($this->myPlot($chart_title, $x_axis, $x_axis_title, $ydata, $y_axis_title, $z_axis, $y_axis_type)); 415c6266a77SGreg Roach 416c6266a77SGreg Roach case self::X_AXIS_FIRST_CHILD_MONTH: 417c6266a77SGreg Roach $chart_title = I18N::translate('Month of birth of first child in a relation'); 418c6266a77SGreg Roach $x_axis_title = I18N::translate('Month'); 419c6266a77SGreg Roach $x_axis = $this->axisMonths(); 420c6266a77SGreg Roach 421c6266a77SGreg Roach switch ($y_axis_type) { 422c6266a77SGreg Roach case self::Y_AXIS_NUMBERS: 423c6266a77SGreg Roach $y_axis_title = I18N::translate('Children'); 424c6266a77SGreg Roach break; 425c6266a77SGreg Roach case self::Y_AXIS_PERCENT: 426c6266a77SGreg Roach $y_axis_title = '%'; 427c6266a77SGreg Roach break; 428c6266a77SGreg Roach default: 429d501c45dSGreg Roach throw new HttpNotFoundException(); 430c6266a77SGreg Roach } 431c6266a77SGreg Roach 432c6266a77SGreg Roach switch ($z_axis_type) { 433c6266a77SGreg Roach case self::Z_AXIS_ALL: 434c6266a77SGreg Roach $z_axis = $this->axisAll(); 435999da590SGreg Roach $rows = $statistics->monthFirstChildQuery()->get(); 436c6266a77SGreg Roach foreach ($rows as $row) { 437c6266a77SGreg Roach $this->fillYData($row->d_month, 0, $row->total, $x_axis, $z_axis, $ydata); 438c6266a77SGreg Roach } 439c6266a77SGreg Roach break; 440c6266a77SGreg Roach case self::Z_AXIS_SEX: 441c6266a77SGreg Roach $z_axis = $this->axisSexes(); 442999da590SGreg Roach $rows = $statistics->monthFirstChildBySexQuery()->get(); 443c6266a77SGreg Roach foreach ($rows as $row) { 444c6266a77SGreg Roach $this->fillYData($row->d_month, $row->i_sex, $row->total, $x_axis, $z_axis, $ydata); 445c6266a77SGreg Roach } 446c6266a77SGreg Roach break; 447c6266a77SGreg Roach case self::Z_AXIS_TIME: 448b6b9dcc9SGreg Roach $boundaries_csv = $params['z-axis-boundaries-periods']; 449c6266a77SGreg Roach $z_axis = $this->axisYears($boundaries_csv); 450c6266a77SGreg Roach $prev_boundary = 0; 451c6266a77SGreg Roach foreach (array_keys($z_axis) as $boundary) { 452999da590SGreg Roach $rows = $statistics->monthFirstChildQuery($prev_boundary, $boundary)->get(); 453c6266a77SGreg Roach foreach ($rows as $row) { 454c6266a77SGreg Roach $this->fillYData($row->d_month, $boundary, $row->total, $x_axis, $z_axis, $ydata); 455c6266a77SGreg Roach } 456c6266a77SGreg Roach $prev_boundary = $boundary + 1; 457c6266a77SGreg Roach } 458c6266a77SGreg Roach break; 459c6266a77SGreg Roach default: 460d501c45dSGreg Roach throw new HttpNotFoundException(); 461c6266a77SGreg Roach } 462c6266a77SGreg Roach 4636ccdf4f0SGreg Roach return response($this->myPlot($chart_title, $x_axis, $x_axis_title, $ydata, $y_axis_title, $z_axis, $y_axis_type)); 464c6266a77SGreg Roach 465c6266a77SGreg Roach case self::X_AXIS_FIRST_MARRIAGE_MONTH: 466c6266a77SGreg Roach $chart_title = I18N::translate('Month of first marriage'); 467c6266a77SGreg Roach $x_axis_title = I18N::translate('Month'); 468c6266a77SGreg Roach $x_axis = $this->axisMonths(); 469c6266a77SGreg Roach 470c6266a77SGreg Roach switch ($y_axis_type) { 471c6266a77SGreg Roach case self::Y_AXIS_NUMBERS: 472c6266a77SGreg Roach $y_axis_title = I18N::translate('Families'); 473c6266a77SGreg Roach break; 474c6266a77SGreg Roach case self::Y_AXIS_PERCENT: 475c6266a77SGreg Roach $y_axis_title = '%'; 476c6266a77SGreg Roach break; 477c6266a77SGreg Roach default: 478d501c45dSGreg Roach throw new HttpNotFoundException(); 479c6266a77SGreg Roach } 480c6266a77SGreg Roach 481c6266a77SGreg Roach switch ($z_axis_type) { 482c6266a77SGreg Roach case self::Z_AXIS_ALL: 483c6266a77SGreg Roach $z_axis = $this->axisAll(); 484e6f3d5e2SGreg Roach $rows = $statistics->statsFirstMarriageQuery()->get(); 485c6266a77SGreg Roach $indi = []; 486c6266a77SGreg Roach foreach ($rows as $row) { 487dca2dd78SGreg Roach if (!in_array($row->f_husb, $indi, true) && !in_array($row->f_wife, $indi, true)) { 488c6266a77SGreg Roach $this->fillYData($row->month, 0, 1, $x_axis, $z_axis, $ydata); 489c6266a77SGreg Roach } 490dca2dd78SGreg Roach $indi[] = $row->f_husb; 491dca2dd78SGreg Roach $indi[] = $row->f_wife; 492c6266a77SGreg Roach } 493c6266a77SGreg Roach break; 494c6266a77SGreg Roach case self::Z_AXIS_TIME: 495b6b9dcc9SGreg Roach $boundaries_csv = $params['z-axis-boundaries-periods']; 496c6266a77SGreg Roach $z_axis = $this->axisYears($boundaries_csv); 497c6266a77SGreg Roach $prev_boundary = 0; 498c6266a77SGreg Roach $indi = []; 499c6266a77SGreg Roach foreach (array_keys($z_axis) as $boundary) { 500e6f3d5e2SGreg Roach $rows = $statistics->statsFirstMarriageQuery($prev_boundary, $boundary)->get(); 501c6266a77SGreg Roach foreach ($rows as $row) { 502dca2dd78SGreg Roach if (!in_array($row->f_husb, $indi, true) && !in_array($row->f_wife, $indi, true)) { 503c6266a77SGreg Roach $this->fillYData($row->month, $boundary, 1, $x_axis, $z_axis, $ydata); 504c6266a77SGreg Roach } 505dca2dd78SGreg Roach $indi[] = $row->f_husb; 506dca2dd78SGreg Roach $indi[] = $row->f_wife; 507c6266a77SGreg Roach } 508c6266a77SGreg Roach $prev_boundary = $boundary + 1; 509c6266a77SGreg Roach } 510c6266a77SGreg Roach break; 511c6266a77SGreg Roach default: 512d501c45dSGreg Roach throw new HttpNotFoundException(); 513c6266a77SGreg Roach } 514c6266a77SGreg Roach 5156ccdf4f0SGreg Roach return response($this->myPlot($chart_title, $x_axis, $x_axis_title, $ydata, $y_axis_title, $z_axis, $y_axis_type)); 516c6266a77SGreg Roach 517c6266a77SGreg Roach case self::X_AXIS_AGE_AT_DEATH: 518c6266a77SGreg Roach $chart_title = I18N::translate('Average age at death'); 519c6266a77SGreg Roach $x_axis_title = I18N::translate('age'); 520b6b9dcc9SGreg Roach $boundaries_csv = $params['x-axis-boundaries-ages']; 521c6266a77SGreg Roach $x_axis = $this->axisNumbers($boundaries_csv); 522c6266a77SGreg Roach 523c6266a77SGreg Roach switch ($y_axis_type) { 524c6266a77SGreg Roach case self::Y_AXIS_NUMBERS: 525c6266a77SGreg Roach $y_axis_title = I18N::translate('Individuals'); 526c6266a77SGreg Roach break; 527c6266a77SGreg Roach case self::Y_AXIS_PERCENT: 528c6266a77SGreg Roach $y_axis_title = '%'; 529c6266a77SGreg Roach break; 530c6266a77SGreg Roach default: 531d501c45dSGreg Roach throw new HttpNotFoundException(); 532c6266a77SGreg Roach } 533c6266a77SGreg Roach 534c6266a77SGreg Roach switch ($z_axis_type) { 535c6266a77SGreg Roach case self::Z_AXIS_ALL: 536c6266a77SGreg Roach $z_axis = $this->axisAll(); 5379219296aSGreg Roach $rows = $statistics->statsAgeQuery('DEAT'); 538c6266a77SGreg Roach foreach ($rows as $row) { 539c6266a77SGreg Roach foreach ($row as $age) { 540c6266a77SGreg Roach $years = (int) ($age / self::DAYS_IN_YEAR); 541c6266a77SGreg Roach $this->fillYData($years, 0, 1, $x_axis, $z_axis, $ydata); 542c6266a77SGreg Roach } 543c6266a77SGreg Roach } 544c6266a77SGreg Roach break; 545c6266a77SGreg Roach case self::Z_AXIS_SEX: 546c6266a77SGreg Roach $z_axis = $this->axisSexes(); 547c6266a77SGreg Roach foreach (array_keys($z_axis) as $sex) { 5489219296aSGreg Roach $rows = $statistics->statsAgeQuery('DEAT', $sex); 549c6266a77SGreg Roach foreach ($rows as $row) { 550c6266a77SGreg Roach foreach ($row as $age) { 551c6266a77SGreg Roach $years = (int) ($age / self::DAYS_IN_YEAR); 552c6266a77SGreg Roach $this->fillYData($years, $sex, 1, $x_axis, $z_axis, $ydata); 553c6266a77SGreg Roach } 554c6266a77SGreg Roach } 555c6266a77SGreg Roach } 556c6266a77SGreg Roach break; 557c6266a77SGreg Roach case self::Z_AXIS_TIME: 558b6b9dcc9SGreg Roach $boundaries_csv = $params['z-axis-boundaries-periods']; 559c6266a77SGreg Roach $z_axis = $this->axisYears($boundaries_csv); 560c6266a77SGreg Roach $prev_boundary = 0; 561c6266a77SGreg Roach foreach (array_keys($z_axis) as $boundary) { 5629219296aSGreg Roach $rows = $statistics->statsAgeQuery('DEAT', 'BOTH', $prev_boundary, $boundary); 563c6266a77SGreg Roach foreach ($rows as $row) { 564c6266a77SGreg Roach foreach ($row as $age) { 565c6266a77SGreg Roach $years = (int) ($age / self::DAYS_IN_YEAR); 566c6266a77SGreg Roach $this->fillYData($years, $boundary, 1, $x_axis, $z_axis, $ydata); 567c6266a77SGreg Roach } 568c6266a77SGreg Roach } 569c6266a77SGreg Roach $prev_boundary = $boundary + 1; 570c6266a77SGreg Roach } 571c6266a77SGreg Roach 572c6266a77SGreg Roach break; 573c6266a77SGreg Roach default: 574d501c45dSGreg Roach throw new HttpNotFoundException(); 575c6266a77SGreg Roach } 576c6266a77SGreg Roach 5776ccdf4f0SGreg Roach return response($this->myPlot($chart_title, $x_axis, $x_axis_title, $ydata, $y_axis_title, $z_axis, $y_axis_type)); 578c6266a77SGreg Roach 579c6266a77SGreg Roach case self::X_AXIS_AGE_AT_MARRIAGE: 580c6266a77SGreg Roach $chart_title = I18N::translate('Age in year of marriage'); 581c6266a77SGreg Roach $x_axis_title = I18N::translate('age'); 582b6b9dcc9SGreg Roach $boundaries_csv = $params['x-axis-boundaries-ages_m']; 583c6266a77SGreg Roach $x_axis = $this->axisNumbers($boundaries_csv); 584c6266a77SGreg Roach 585c6266a77SGreg Roach switch ($y_axis_type) { 586c6266a77SGreg Roach case self::Y_AXIS_NUMBERS: 587c6266a77SGreg Roach $y_axis_title = I18N::translate('Individuals'); 588c6266a77SGreg Roach break; 589c6266a77SGreg Roach case self::Y_AXIS_PERCENT: 590c6266a77SGreg Roach $y_axis_title = '%'; 591c6266a77SGreg Roach break; 592c6266a77SGreg Roach default: 593d501c45dSGreg Roach throw new HttpNotFoundException(); 594c6266a77SGreg Roach } 595c6266a77SGreg Roach 596c6266a77SGreg Roach switch ($z_axis_type) { 597c6266a77SGreg Roach case self::Z_AXIS_ALL: 598c6266a77SGreg Roach $z_axis = $this->axisAll(); 599afa8d404SGreg Roach // The stats query doesn't have an "all" function, so query M/F separately 600afa8d404SGreg Roach foreach (['M', 'F'] as $sex) { 6019219296aSGreg Roach $rows = $statistics->statsMarrAgeQuery($sex); 602c6266a77SGreg Roach foreach ($rows as $row) { 603c6266a77SGreg Roach $years = (int) ($row->age / self::DAYS_IN_YEAR); 604c6266a77SGreg Roach $this->fillYData($years, 0, 1, $x_axis, $z_axis, $ydata); 605c6266a77SGreg Roach } 606c6266a77SGreg Roach } 607c6266a77SGreg Roach break; 608c6266a77SGreg Roach case self::Z_AXIS_SEX: 609c6266a77SGreg Roach $z_axis = $this->axisSexes(); 610c6266a77SGreg Roach foreach (array_keys($z_axis) as $sex) { 6119219296aSGreg Roach $rows = $statistics->statsMarrAgeQuery($sex); 612c6266a77SGreg Roach foreach ($rows as $row) { 613c6266a77SGreg Roach $years = (int) ($row->age / self::DAYS_IN_YEAR); 614c6266a77SGreg Roach $this->fillYData($years, $sex, 1, $x_axis, $z_axis, $ydata); 615c6266a77SGreg Roach } 616c6266a77SGreg Roach } 617c6266a77SGreg Roach break; 618c6266a77SGreg Roach case self::Z_AXIS_TIME: 619b6b9dcc9SGreg Roach $boundaries_csv = $params['z-axis-boundaries-periods']; 620c6266a77SGreg Roach $z_axis = $this->axisYears($boundaries_csv); 621afa8d404SGreg Roach // The stats query doesn't have an "all" function, so query M/F separately 622afa8d404SGreg Roach foreach (['M', 'F'] as $sex) { 623c6266a77SGreg Roach $prev_boundary = 0; 624c6266a77SGreg Roach foreach (array_keys($z_axis) as $boundary) { 6259219296aSGreg Roach $rows = $statistics->statsMarrAgeQuery($sex, $prev_boundary, $boundary); 626c6266a77SGreg Roach foreach ($rows as $row) { 627c6266a77SGreg Roach $years = (int) ($row->age / self::DAYS_IN_YEAR); 628c6266a77SGreg Roach $this->fillYData($years, $boundary, 1, $x_axis, $z_axis, $ydata); 629c6266a77SGreg Roach } 630c6266a77SGreg Roach $prev_boundary = $boundary + 1; 631c6266a77SGreg Roach } 632c6266a77SGreg Roach } 633c6266a77SGreg Roach break; 634c6266a77SGreg Roach default: 635d501c45dSGreg Roach throw new HttpNotFoundException(); 636c6266a77SGreg Roach } 637c6266a77SGreg Roach 6386ccdf4f0SGreg Roach return response($this->myPlot($chart_title, $x_axis, $x_axis_title, $ydata, $y_axis_title, $z_axis, $y_axis_type)); 639c6266a77SGreg Roach 640c6266a77SGreg Roach case self::X_AXIS_AGE_AT_FIRST_MARRIAGE: 641c6266a77SGreg Roach $chart_title = I18N::translate('Age in year of first marriage'); 642c6266a77SGreg Roach $x_axis_title = I18N::translate('age'); 643b6b9dcc9SGreg Roach $boundaries_csv = $params['x-axis-boundaries-ages_m']; 644c6266a77SGreg Roach $x_axis = $this->axisNumbers($boundaries_csv); 645c6266a77SGreg Roach 646c6266a77SGreg Roach switch ($y_axis_type) { 647c6266a77SGreg Roach case self::Y_AXIS_NUMBERS: 648c6266a77SGreg Roach $y_axis_title = I18N::translate('Individuals'); 649c6266a77SGreg Roach break; 650c6266a77SGreg Roach case self::Y_AXIS_PERCENT: 651c6266a77SGreg Roach $y_axis_title = '%'; 652c6266a77SGreg Roach break; 653c6266a77SGreg Roach default: 654d501c45dSGreg Roach throw new HttpNotFoundException(); 655c6266a77SGreg Roach } 656c6266a77SGreg Roach 657c6266a77SGreg Roach switch ($z_axis_type) { 658c6266a77SGreg Roach case self::Z_AXIS_ALL: 659c6266a77SGreg Roach $z_axis = $this->axisAll(); 660afa8d404SGreg Roach // The stats query doesn't have an "all" function, so query M/F separately 661afa8d404SGreg Roach foreach (['M', 'F'] as $sex) { 6629219296aSGreg Roach $rows = $statistics->statsMarrAgeQuery($sex); 663c6266a77SGreg Roach $indi = []; 664c6266a77SGreg Roach foreach ($rows as $row) { 6656960d4a1SGreg Roach if (!in_array($row->d_gid, $indi, true)) { 666c6266a77SGreg Roach $years = (int) ($row->age / self::DAYS_IN_YEAR); 667c6266a77SGreg Roach $this->fillYData($years, 0, 1, $x_axis, $z_axis, $ydata); 668c6266a77SGreg Roach $indi[] = $row->d_gid; 669c6266a77SGreg Roach } 670c6266a77SGreg Roach } 671c6266a77SGreg Roach } 672c6266a77SGreg Roach break; 673c6266a77SGreg Roach case self::Z_AXIS_SEX: 674c6266a77SGreg Roach $z_axis = $this->axisSexes(); 675c6266a77SGreg Roach foreach (array_keys($z_axis) as $sex) { 6769219296aSGreg Roach $rows = $statistics->statsMarrAgeQuery($sex); 677c6266a77SGreg Roach $indi = []; 678c6266a77SGreg Roach foreach ($rows as $row) { 6796960d4a1SGreg Roach if (!in_array($row->d_gid, $indi, true)) { 680c6266a77SGreg Roach $years = (int) ($row->age / self::DAYS_IN_YEAR); 681c6266a77SGreg Roach $this->fillYData($years, $sex, 1, $x_axis, $z_axis, $ydata); 682c6266a77SGreg Roach $indi[] = $row->d_gid; 683c6266a77SGreg Roach } 684c6266a77SGreg Roach } 685c6266a77SGreg Roach } 686c6266a77SGreg Roach break; 687c6266a77SGreg Roach case self::Z_AXIS_TIME: 688b6b9dcc9SGreg Roach $boundaries_csv = $params['z-axis-boundaries-periods']; 689c6266a77SGreg Roach $z_axis = $this->axisYears($boundaries_csv); 690afa8d404SGreg Roach // The stats query doesn't have an "all" function, so query M/F separately 691afa8d404SGreg Roach foreach (['M', 'F'] as $sex) { 692c6266a77SGreg Roach $prev_boundary = 0; 693c6266a77SGreg Roach $indi = []; 694c6266a77SGreg Roach foreach (array_keys($z_axis) as $boundary) { 6959219296aSGreg Roach $rows = $statistics->statsMarrAgeQuery($sex, $prev_boundary, $boundary); 696c6266a77SGreg Roach foreach ($rows as $row) { 6976960d4a1SGreg Roach if (!in_array($row->d_gid, $indi, true)) { 698c6266a77SGreg Roach $years = (int) ($row->age / self::DAYS_IN_YEAR); 699c6266a77SGreg Roach $this->fillYData($years, $boundary, 1, $x_axis, $z_axis, $ydata); 700c6266a77SGreg Roach $indi[] = $row->d_gid; 701c6266a77SGreg Roach } 702c6266a77SGreg Roach } 703c6266a77SGreg Roach $prev_boundary = $boundary + 1; 704c6266a77SGreg Roach } 705c6266a77SGreg Roach } 706c6266a77SGreg Roach break; 707c6266a77SGreg Roach default: 708d501c45dSGreg Roach throw new HttpNotFoundException(); 709c6266a77SGreg Roach } 710c6266a77SGreg Roach 7116ccdf4f0SGreg Roach return response($this->myPlot($chart_title, $x_axis, $x_axis_title, $ydata, $y_axis_title, $z_axis, $y_axis_type)); 712c6266a77SGreg Roach 713c6266a77SGreg Roach case self::X_AXIS_NUMBER_OF_CHILDREN: 714c6266a77SGreg Roach $chart_title = I18N::translate('Number of children'); 715c6266a77SGreg Roach $x_axis_title = I18N::translate('Children'); 7166960d4a1SGreg Roach $x_axis = $this->axisNumbers('0,1,2,3,4,5,6,7,8,9,10'); 717c6266a77SGreg Roach 718c6266a77SGreg Roach switch ($y_axis_type) { 719c6266a77SGreg Roach case self::Y_AXIS_NUMBERS: 720c6266a77SGreg Roach $y_axis_title = I18N::translate('Families'); 721c6266a77SGreg Roach break; 722c6266a77SGreg Roach case self::Y_AXIS_PERCENT: 723c6266a77SGreg Roach $y_axis_title = '%'; 724c6266a77SGreg Roach break; 725c6266a77SGreg Roach default: 726d501c45dSGreg Roach throw new HttpNotFoundException(); 727c6266a77SGreg Roach } 728c6266a77SGreg Roach 729c6266a77SGreg Roach switch ($z_axis_type) { 730c6266a77SGreg Roach case self::Z_AXIS_ALL: 731c6266a77SGreg Roach $z_axis = $this->axisAll(); 7329219296aSGreg Roach $rows = $statistics->statsChildrenQuery(); 733c6266a77SGreg Roach foreach ($rows as $row) { 734c6266a77SGreg Roach $this->fillYData($row->f_numchil, 0, $row->total, $x_axis, $z_axis, $ydata); 735c6266a77SGreg Roach } 736c6266a77SGreg Roach break; 737c6266a77SGreg Roach case self::Z_AXIS_TIME: 738b6b9dcc9SGreg Roach $boundaries_csv = $params['z-axis-boundaries-periods']; 739c6266a77SGreg Roach $z_axis = $this->axisYears($boundaries_csv); 740c6266a77SGreg Roach $prev_boundary = 0; 741c6266a77SGreg Roach foreach (array_keys($z_axis) as $boundary) { 742b1126ab4SGreg Roach $rows = $statistics->statsChildrenQuery($prev_boundary, $boundary); 743c6266a77SGreg Roach foreach ($rows as $row) { 744c6266a77SGreg Roach $this->fillYData($row->f_numchil, $boundary, $row->total, $x_axis, $z_axis, $ydata); 745c6266a77SGreg Roach } 746c6266a77SGreg Roach $prev_boundary = $boundary + 1; 747c6266a77SGreg Roach } 748c6266a77SGreg Roach break; 749c6266a77SGreg Roach default: 750d501c45dSGreg Roach throw new HttpNotFoundException(); 751c6266a77SGreg Roach } 752c6266a77SGreg Roach 7536ccdf4f0SGreg Roach return response($this->myPlot($chart_title, $x_axis, $x_axis_title, $ydata, $y_axis_title, $z_axis, $y_axis_type)); 754c6266a77SGreg Roach 755c6266a77SGreg Roach default: 756d501c45dSGreg Roach throw new HttpNotFoundException(); 757c6266a77SGreg Roach } 758c6266a77SGreg Roach } 759c6266a77SGreg Roach 760c6266a77SGreg Roach /** 76124f2a3afSGreg Roach * @return array<string> 762c6266a77SGreg Roach */ 763c6266a77SGreg Roach private function axisAll(): array 764c6266a77SGreg Roach { 765c6266a77SGreg Roach return [ 766c6266a77SGreg Roach I18N::translate('Total'), 767c6266a77SGreg Roach ]; 768c6266a77SGreg Roach } 769c6266a77SGreg Roach 770c6266a77SGreg Roach /** 77124f2a3afSGreg Roach * @return array<string> 772c6266a77SGreg Roach */ 773c6266a77SGreg Roach private function axisSexes(): array 774c6266a77SGreg Roach { 775c6266a77SGreg Roach return [ 776c6266a77SGreg Roach 'M' => I18N::translate('Male'), 777c6266a77SGreg Roach 'F' => I18N::translate('Female'), 778c6266a77SGreg Roach ]; 779c6266a77SGreg Roach } 780c6266a77SGreg Roach 781c6266a77SGreg Roach /** 782c6266a77SGreg Roach * Labels for the X axis 783c6266a77SGreg Roach * 78424f2a3afSGreg Roach * @return array<string> 785c6266a77SGreg Roach */ 786c6266a77SGreg Roach private function axisMonths(): array 787c6266a77SGreg Roach { 788c6266a77SGreg Roach return [ 789c6266a77SGreg Roach 'JAN' => I18N::translateContext('NOMINATIVE', 'January'), 790c6266a77SGreg Roach 'FEB' => I18N::translateContext('NOMINATIVE', 'February'), 791c6266a77SGreg Roach 'MAR' => I18N::translateContext('NOMINATIVE', 'March'), 792c6266a77SGreg Roach 'APR' => I18N::translateContext('NOMINATIVE', 'April'), 793c6266a77SGreg Roach 'MAY' => I18N::translateContext('NOMINATIVE', 'May'), 794c6266a77SGreg Roach 'JUN' => I18N::translateContext('NOMINATIVE', 'June'), 795c6266a77SGreg Roach 'JUL' => I18N::translateContext('NOMINATIVE', 'July'), 796c6266a77SGreg Roach 'AUG' => I18N::translateContext('NOMINATIVE', 'August'), 797c6266a77SGreg Roach 'SEP' => I18N::translateContext('NOMINATIVE', 'September'), 798c6266a77SGreg Roach 'OCT' => I18N::translateContext('NOMINATIVE', 'October'), 799c6266a77SGreg Roach 'NOV' => I18N::translateContext('NOMINATIVE', 'November'), 800c6266a77SGreg Roach 'DEC' => I18N::translateContext('NOMINATIVE', 'December'), 801c6266a77SGreg Roach ]; 802c6266a77SGreg Roach } 803c6266a77SGreg Roach 804c6266a77SGreg Roach /** 805c6266a77SGreg Roach * Convert a list of N year-boundaries into N+1 year-ranges for the z-axis. 806c6266a77SGreg Roach * 807c6266a77SGreg Roach * @param string $boundaries_csv 808c6266a77SGreg Roach * 80924f2a3afSGreg Roach * @return array<string> 810c6266a77SGreg Roach */ 811c6266a77SGreg Roach private function axisYears(string $boundaries_csv): array 812c6266a77SGreg Roach { 813c6266a77SGreg Roach $boundaries = explode(',', $boundaries_csv); 814c6266a77SGreg Roach 815c6266a77SGreg Roach $axis = []; 816c6266a77SGreg Roach foreach ($boundaries as $n => $boundary) { 817c6266a77SGreg Roach if ($n === 0) { 81867ff9eb7SGreg Roach $axis[$boundary - 1] = '–' . I18N::digits($boundary); 819c6266a77SGreg Roach } else { 82067ff9eb7SGreg Roach $axis[$boundary - 1] = I18N::digits($boundaries[$n - 1]) . '–' . I18N::digits($boundary); 821c6266a77SGreg Roach } 822c6266a77SGreg Roach } 823c6266a77SGreg Roach 82467ff9eb7SGreg Roach $axis[PHP_INT_MAX] = I18N::digits($boundaries[count($boundaries) - 1]) . '–'; 825c6266a77SGreg Roach 826c6266a77SGreg Roach return $axis; 827c6266a77SGreg Roach } 828c6266a77SGreg Roach 829c6266a77SGreg Roach /** 830c6266a77SGreg Roach * Create the X axis. 831c6266a77SGreg Roach * 832c6266a77SGreg Roach * @param string $boundaries_csv 833c6266a77SGreg Roach * 834fc26b4f6SGreg Roach * @return array<string> 835c6266a77SGreg Roach */ 836c6266a77SGreg Roach private function axisNumbers(string $boundaries_csv): array 837c6266a77SGreg Roach { 838c6266a77SGreg Roach $boundaries = explode(',', $boundaries_csv); 839c6266a77SGreg Roach 8400b5fd0a6SGreg Roach $boundaries = array_map(static function (string $x): int { 841c6266a77SGreg Roach return (int) $x; 842c6266a77SGreg Roach }, $boundaries); 843c6266a77SGreg Roach 844c6266a77SGreg Roach $axis = []; 845c6266a77SGreg Roach foreach ($boundaries as $n => $boundary) { 846c6266a77SGreg Roach if ($n === 0) { 8476960d4a1SGreg Roach $prev_boundary = 0; 8486960d4a1SGreg Roach } else { 8496960d4a1SGreg Roach $prev_boundary = $boundaries[$n - 1] + 1; 8506960d4a1SGreg Roach } 8516960d4a1SGreg Roach 8526960d4a1SGreg Roach if ($prev_boundary === $boundary) { 853c6266a77SGreg Roach /* I18N: A range of numbers */ 8546960d4a1SGreg Roach $axis[$boundary] = I18N::number($boundary); 855c6266a77SGreg Roach } else { 856c6266a77SGreg Roach /* I18N: A range of numbers */ 8576960d4a1SGreg Roach $axis[$boundary] = I18N::translate('%1$s–%2$s', I18N::number($prev_boundary), I18N::number($boundary)); 858c6266a77SGreg Roach } 859c6266a77SGreg Roach } 860c6266a77SGreg Roach 861c6266a77SGreg Roach /* I18N: Label on a graph; 40+ means 40 or more */ 862c6266a77SGreg Roach $axis[PHP_INT_MAX] = I18N::translate('%s+', I18N::number($boundaries[count($boundaries) - 1])); 863c6266a77SGreg Roach 864c6266a77SGreg Roach return $axis; 865c6266a77SGreg Roach } 866c6266a77SGreg Roach 867c6266a77SGreg Roach /** 868c6266a77SGreg Roach * Calculate the Y axis. 869c6266a77SGreg Roach * 870c6266a77SGreg Roach * @param int|string $x 871c6266a77SGreg Roach * @param int|string $z 872c6266a77SGreg Roach * @param int|string $value 87376d39c55SGreg Roach * @param array<string> $x_axis 87476d39c55SGreg Roach * @param array<string> $z_axis 87509482a55SGreg Roach * @param array<array<int>> $ydata 876c6266a77SGreg Roach * 877c6266a77SGreg Roach * @return void 878c6266a77SGreg Roach */ 879e364afe4SGreg Roach private function fillYData($x, $z, $value, array $x_axis, array $z_axis, array &$ydata): void 880c6266a77SGreg Roach { 881c6266a77SGreg Roach $x = $this->findAxisEntry($x, $x_axis); 882c6266a77SGreg Roach $z = $this->findAxisEntry($z, $z_axis); 883c6266a77SGreg Roach 8846960d4a1SGreg Roach if (!array_key_exists($z, $z_axis)) { 885c6266a77SGreg Roach foreach (array_keys($z_axis) as $key) { 886c6266a77SGreg Roach if ($value <= $key) { 887c6266a77SGreg Roach $z = $key; 888c6266a77SGreg Roach break; 889c6266a77SGreg Roach } 890c6266a77SGreg Roach } 891c6266a77SGreg Roach } 892c6266a77SGreg Roach 893c6266a77SGreg Roach // Add the value to the appropriate data point. 894c6266a77SGreg Roach $ydata[$z][$x] = ($ydata[$z][$x] ?? 0) + $value; 895c6266a77SGreg Roach } 896c6266a77SGreg Roach 897c6266a77SGreg Roach /** 898c6266a77SGreg Roach * Find the axis entry for a given value. 899c6266a77SGreg Roach * Some are direct lookup (e.g. M/F, JAN/FEB/MAR). 900d823340dSGreg Roach * Others need to find the appropriate range. 901c6266a77SGreg Roach * 90276d39c55SGreg Roach * @param int|string $value 90309482a55SGreg Roach * @param array<string> $axis 904c6266a77SGreg Roach * 905c6266a77SGreg Roach * @return int|string 906c6266a77SGreg Roach */ 90724f2a3afSGreg Roach private function findAxisEntry($value, array $axis) 908c6266a77SGreg Roach { 909c6266a77SGreg Roach if (is_numeric($value)) { 910c6266a77SGreg Roach $value = (int) $value; 911c6266a77SGreg Roach 9126960d4a1SGreg Roach if (!array_key_exists($value, $axis)) { 913c6266a77SGreg Roach foreach (array_keys($axis) as $boundary) { 914c6266a77SGreg Roach if ($value <= $boundary) { 915c6266a77SGreg Roach $value = $boundary; 916c6266a77SGreg Roach break; 917c6266a77SGreg Roach } 918c6266a77SGreg Roach } 919c6266a77SGreg Roach } 920c6266a77SGreg Roach } 921c6266a77SGreg Roach 922c6266a77SGreg Roach return $value; 923c6266a77SGreg Roach } 924c6266a77SGreg Roach 925c6266a77SGreg Roach /** 926c6266a77SGreg Roach * Plot the data. 927c6266a77SGreg Roach * 928c6266a77SGreg Roach * @param string $chart_title 92909482a55SGreg Roach * @param array<string> $x_axis 930c6266a77SGreg Roach * @param string $x_axis_title 93109482a55SGreg Roach * @param array<array<int>> $ydata 932c6266a77SGreg Roach * @param string $y_axis_title 93309482a55SGreg Roach * @param array<string> $z_axis 934c6266a77SGreg Roach * @param int $y_axis_type 935c6266a77SGreg Roach * 936c6266a77SGreg Roach * @return string 937c6266a77SGreg Roach */ 938a81e5019SRico Sonntag private function myPlot( 939a81e5019SRico Sonntag string $chart_title, 940a81e5019SRico Sonntag array $x_axis, 941a81e5019SRico Sonntag string $x_axis_title, 942a81e5019SRico Sonntag array $ydata, 943a81e5019SRico Sonntag string $y_axis_title, 944a81e5019SRico Sonntag array $z_axis, 945a81e5019SRico Sonntag int $y_axis_type 946a81e5019SRico Sonntag ): string { 9476960d4a1SGreg Roach if (!count($ydata)) { 948a81e5019SRico Sonntag return I18N::translate('This information is not available.'); 949c6266a77SGreg Roach } 950c6266a77SGreg Roach 951c6266a77SGreg Roach // Colors for z-axis 952c6266a77SGreg Roach $colors = []; 953c6266a77SGreg Roach $index = 0; 9546960d4a1SGreg Roach while (count($colors) < count($ydata)) { 955c6266a77SGreg Roach $colors[] = self::Z_AXIS_COLORS[$index]; 9566960d4a1SGreg Roach $index = ($index + 1) % count(self::Z_AXIS_COLORS); 957c6266a77SGreg Roach } 958c6266a77SGreg Roach 959c6266a77SGreg Roach // Convert our sparse dataset into a fixed-size array 960c6266a77SGreg Roach $tmp = []; 961c6266a77SGreg Roach foreach (array_keys($z_axis) as $z) { 962c6266a77SGreg Roach foreach (array_keys($x_axis) as $x) { 963c6266a77SGreg Roach $tmp[$z][$x] = $ydata[$z][$x] ?? 0; 964c6266a77SGreg Roach } 965c6266a77SGreg Roach } 966c6266a77SGreg Roach $ydata = $tmp; 967c6266a77SGreg Roach 968a81e5019SRico Sonntag // Convert the chart data to percentage 969c6266a77SGreg Roach if ($y_axis_type === self::Y_AXIS_PERCENT) { 970c6266a77SGreg Roach // Normalise each (non-zero!) set of data to total 100% 9710b5fd0a6SGreg Roach array_walk($ydata, static function (array &$x) { 972c6266a77SGreg Roach $sum = array_sum($x); 973c6266a77SGreg Roach if ($sum > 0) { 9744c78e066SGreg Roach $x = array_map(static fn (float $y): float => $y * 100.0 / $sum, $x); 975c6266a77SGreg Roach } 976c6266a77SGreg Roach }); 977c6266a77SGreg Roach } 978c6266a77SGreg Roach 979a81e5019SRico Sonntag $data = [ 980a81e5019SRico Sonntag array_merge( 981a81e5019SRico Sonntag [I18N::translate('Century')], 982a81e5019SRico Sonntag array_values($z_axis) 98371378461SGreg Roach ), 984c6266a77SGreg Roach ]; 985c6266a77SGreg Roach 986a81e5019SRico Sonntag $intermediate = []; 987d85de00fSGreg Roach foreach ($ydata as $months) { 988a81e5019SRico Sonntag foreach ($months as $month => $value) { 989a81e5019SRico Sonntag $intermediate[$month][] = [ 990a81e5019SRico Sonntag 'v' => $value, 991d85de00fSGreg Roach 'f' => $y_axis_type === self::Y_AXIS_PERCENT ? sprintf('%.1f%%', $value) : $value, 992a81e5019SRico Sonntag ]; 993a81e5019SRico Sonntag } 994c6266a77SGreg Roach } 995c6266a77SGreg Roach 996a81e5019SRico Sonntag foreach ($intermediate as $key => $values) { 997a81e5019SRico Sonntag $data[] = array_merge( 998a81e5019SRico Sonntag [$x_axis[$key]], 999a81e5019SRico Sonntag $values 1000a81e5019SRico Sonntag ); 1001a81e5019SRico Sonntag } 1002c6266a77SGreg Roach 1003a81e5019SRico Sonntag $chart_options = [ 1004a81e5019SRico Sonntag 'title' => '', 1005a81e5019SRico Sonntag 'subtitle' => '', 1006a81e5019SRico Sonntag 'height' => 400, 1007a81e5019SRico Sonntag 'width' => '100%', 1008a81e5019SRico Sonntag 'legend' => [ 10096960d4a1SGreg Roach 'position' => count($z_axis) > 1 ? 'right' : 'none', 1010a81e5019SRico Sonntag 'alignment' => 'center', 1011a81e5019SRico Sonntag ], 1012a81e5019SRico Sonntag 'tooltip' => [ 1013a81e5019SRico Sonntag 'format' => '\'%\'', 1014a81e5019SRico Sonntag ], 1015a81e5019SRico Sonntag 'vAxis' => [ 101676d39c55SGreg Roach 'title' => $y_axis_title, 1017a81e5019SRico Sonntag ], 1018a81e5019SRico Sonntag 'hAxis' => [ 101976d39c55SGreg Roach 'title' => $x_axis_title, 1020a81e5019SRico Sonntag ], 1021a81e5019SRico Sonntag 'colors' => $colors, 1022a81e5019SRico Sonntag ]; 1023a81e5019SRico Sonntag 102490a2f718SGreg Roach return view('statistics/other/charts/custom', [ 1025a81e5019SRico Sonntag 'data' => $data, 1026a81e5019SRico Sonntag 'chart_options' => $chart_options, 1027a81e5019SRico Sonntag 'chart_title' => $chart_title, 102865cf5706SGreg Roach 'language' => I18N::languageTag(), 102990a2f718SGreg Roach ]); 1030c6266a77SGreg Roach } 1031168ff6f3Sric2016} 1032