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; 2381b729d3SGreg 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 115*09482a55SGreg Roach * @param array<bool|int|string|array|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 { 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 878*09482a55SGreg Roach * @param array<array<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 906*09482a55SGreg Roach * @param array<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 932*09482a55SGreg Roach * @param array<string> $x_axis 933c6266a77SGreg Roach * @param string $x_axis_title 934*09482a55SGreg Roach * @param array<array<int>> $ydata 935c6266a77SGreg Roach * @param string $y_axis_title 936*09482a55SGreg Roach * @param array<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