1168ff6f3Sric2016<?php 23976b470SGreg Roach 3168ff6f3Sric2016/** 4168ff6f3Sric2016 * webtrees: online genealogy 5d11be702SGreg Roach * Copyright (C) 2023 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; 26d35568b4SGreg Roachuse Fisharebest\Webtrees\Registry; 279219296aSGreg Roachuse Fisharebest\Webtrees\Statistics; 28b55cbc6bSGreg Roachuse Fisharebest\Webtrees\Validator; 296ccdf4f0SGreg Roachuse Psr\Http\Message\ResponseInterface; 306ccdf4f0SGreg Roachuse Psr\Http\Message\ServerRequestInterface; 313976b470SGreg Roach 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; 436bd19c8cSGreg Roachuse function intdiv; 446960d4a1SGreg Roachuse function is_numeric; 456960d4a1SGreg Roachuse function sprintf; 46168ff6f3Sric2016 47168ff6f3Sric2016/** 48168ff6f3Sric2016 * Class StatisticsChartModule 49168ff6f3Sric2016 */ 5037eb8894SGreg Roachclass StatisticsChartModule extends AbstractModule implements ModuleChartInterface 51c1010edaSGreg Roach{ 5249a243cbSGreg Roach use ModuleChartTrait; 5349a243cbSGreg Roach 54c6266a77SGreg Roach public const X_AXIS_INDIVIDUAL_MAP = 1; 55c6266a77SGreg Roach public const X_AXIS_BIRTH_MAP = 2; 56c6266a77SGreg Roach public const X_AXIS_DEATH_MAP = 3; 57c6266a77SGreg Roach public const X_AXIS_MARRIAGE_MAP = 4; 58c6266a77SGreg Roach public const X_AXIS_BIRTH_MONTH = 11; 59c6266a77SGreg Roach public const X_AXIS_DEATH_MONTH = 12; 60c6266a77SGreg Roach public const X_AXIS_MARRIAGE_MONTH = 13; 61c6266a77SGreg Roach public const X_AXIS_FIRST_CHILD_MONTH = 14; 62c6266a77SGreg Roach public const X_AXIS_FIRST_MARRIAGE_MONTH = 15; 63c6266a77SGreg Roach public const X_AXIS_AGE_AT_DEATH = 18; 64c6266a77SGreg Roach public const X_AXIS_AGE_AT_MARRIAGE = 19; 65c6266a77SGreg Roach public const X_AXIS_AGE_AT_FIRST_MARRIAGE = 20; 66c6266a77SGreg Roach public const X_AXIS_NUMBER_OF_CHILDREN = 21; 67c6266a77SGreg Roach 68c6266a77SGreg Roach public const Y_AXIS_NUMBERS = 201; 69c6266a77SGreg Roach public const Y_AXIS_PERCENT = 202; 70c6266a77SGreg Roach 71c6266a77SGreg Roach public const Z_AXIS_ALL = 300; 72c6266a77SGreg Roach public const Z_AXIS_SEX = 301; 73c6266a77SGreg Roach public const Z_AXIS_TIME = 302; 74c6266a77SGreg Roach 75c6266a77SGreg Roach // First two colors are blue/pink, to work with Z_AXIS_SEX. 76c6266a77SGreg Roach private const Z_AXIS_COLORS = ['0000FF', 'FFA0CB', '9F00FF', 'FF7000', '905030', 'FF0000', '00FF00', 'F0F000']; 77c6266a77SGreg Roach 78c6266a77SGreg Roach private const DAYS_IN_YEAR = 365.25; 79c6266a77SGreg Roach 80168ff6f3Sric2016 /** 810cfd6963SGreg Roach * How should this module be identified in the control panel, etc.? 82168ff6f3Sric2016 * 83168ff6f3Sric2016 * @return string 84168ff6f3Sric2016 */ 8549a243cbSGreg Roach public function title(): string 86c1010edaSGreg Roach { 87bbb76c12SGreg Roach /* I18N: Name of a module/chart */ 88bbb76c12SGreg Roach return I18N::translate('Statistics'); 89168ff6f3Sric2016 } 90168ff6f3Sric2016 9149a243cbSGreg Roach public function description(): string 92c1010edaSGreg Roach { 93bbb76c12SGreg Roach /* I18N: Description of the “StatisticsChart” module */ 94bbb76c12SGreg Roach return I18N::translate('Various statistics charts.'); 95168ff6f3Sric2016 } 96168ff6f3Sric2016 97168ff6f3Sric2016 /** 98377a2979SGreg Roach * CSS class for the URL. 99377a2979SGreg Roach * 100377a2979SGreg Roach * @return string 101377a2979SGreg Roach */ 102377a2979SGreg Roach public function chartMenuClass(): string 103377a2979SGreg Roach { 104377a2979SGreg Roach return 'menu-chart-statistics'; 105377a2979SGreg Roach } 106377a2979SGreg Roach 107377a2979SGreg Roach /** 108e6562982SGreg Roach * The URL for this chart. 109168ff6f3Sric2016 * 11060bc3e3fSGreg Roach * @param Individual $individual 11176d39c55SGreg Roach * @param array<bool|int|string|array<string>|null> $parameters 11260bc3e3fSGreg Roach * 113e6562982SGreg Roach * @return string 114168ff6f3Sric2016 */ 115e6562982SGreg Roach public function chartUrl(Individual $individual, array $parameters = []): string 116c1010edaSGreg Roach { 117c6266a77SGreg Roach return route('module', [ 118c6266a77SGreg Roach 'module' => $this->name(), 119c6266a77SGreg Roach 'action' => 'Chart', 120d72b284aSGreg Roach 'tree' => $individual->tree()->name(), 121e6562982SGreg Roach ] + $parameters); 122168ff6f3Sric2016 } 123c6266a77SGreg Roach 124c6266a77SGreg Roach /** 125c6266a77SGreg Roach * A form to request the chart parameters. 126c6266a77SGreg Roach * 12757ab2231SGreg Roach * @param ServerRequestInterface $request 128c6266a77SGreg Roach * 1296ccdf4f0SGreg Roach * @return ResponseInterface 130c6266a77SGreg Roach */ 13157ab2231SGreg Roach public function getChartAction(ServerRequestInterface $request): ResponseInterface 132c6266a77SGreg Roach { 133b55cbc6bSGreg Roach $tree = Validator::attributes($request)->tree(); 134b55cbc6bSGreg Roach $user = Validator::attributes($request)->user(); 13557ab2231SGreg Roach 136ef483801SGreg Roach Auth::checkComponentAccess($this, ModuleChartInterface::class, $tree, $user); 1379867b2f0SGreg Roach 138c6266a77SGreg Roach $tabs = [ 139c6266a77SGreg Roach I18N::translate('Individuals') => route('module', [ 140c6266a77SGreg Roach 'module' => $this->name(), 141c6266a77SGreg Roach 'action' => 'Individuals', 142d72b284aSGreg Roach 'tree' => $tree->name(), 143c6266a77SGreg Roach ]), 144c6266a77SGreg Roach I18N::translate('Families') => route('module', [ 145c6266a77SGreg Roach 'module' => $this->name(), 146c6266a77SGreg Roach 'action' => 'Families', 147d72b284aSGreg Roach 'tree' => $tree->name(), 148c6266a77SGreg Roach ]), 149c6266a77SGreg Roach I18N::translate('Other') => route('module', [ 150c6266a77SGreg Roach 'module' => $this->name(), 151c6266a77SGreg Roach 'action' => 'Other', 152d72b284aSGreg Roach 'tree' => $tree->name(), 153c6266a77SGreg Roach ]), 154c6266a77SGreg Roach I18N::translate('Custom') => route('module', [ 155c6266a77SGreg Roach 'module' => $this->name(), 156c6266a77SGreg Roach 'action' => 'Custom', 157d72b284aSGreg Roach 'tree' => $tree->name(), 158c6266a77SGreg Roach ]), 159c6266a77SGreg Roach ]; 160c6266a77SGreg Roach 1619b5537c3SGreg Roach return $this->viewResponse('modules/statistics-chart/page', [ 16271378461SGreg Roach 'module' => $this->name(), 163c6266a77SGreg Roach 'tabs' => $tabs, 164c6266a77SGreg Roach 'title' => $this->title(), 165ef5d23f1SGreg Roach 'tree' => $tree, 166c6266a77SGreg Roach ]); 167c6266a77SGreg Roach } 168c6266a77SGreg Roach 169c6266a77SGreg Roach /** 17057ab2231SGreg Roach * @param ServerRequestInterface $request 171c6266a77SGreg Roach * 1726ccdf4f0SGreg Roach * @return ResponseInterface 173c6266a77SGreg Roach */ 17449528f2bSGreg Roach public function getIndividualsAction(ServerRequestInterface $request): ResponseInterface 175c6266a77SGreg Roach { 176b6c326d8SGreg Roach $this->layout = 'layouts/ajax'; 177b6c326d8SGreg Roach 178b6c326d8SGreg Roach return $this->viewResponse('modules/statistics-chart/individuals', [ 179c6266a77SGreg Roach 'show_oldest_living' => Auth::check(), 180d35568b4SGreg Roach 'statistics' => Registry::container()->get(Statistics::class), 181c6266a77SGreg Roach ]); 182c6266a77SGreg Roach } 183c6266a77SGreg Roach 184c6266a77SGreg Roach /** 18557ab2231SGreg Roach * @param ServerRequestInterface $request 186c6266a77SGreg Roach * 1876ccdf4f0SGreg Roach * @return ResponseInterface 188c6266a77SGreg Roach */ 18949528f2bSGreg Roach public function getFamiliesAction(ServerRequestInterface $request): ResponseInterface 190c6266a77SGreg Roach { 191b6c326d8SGreg Roach $this->layout = 'layouts/ajax'; 192b6c326d8SGreg Roach 193b6c326d8SGreg Roach return $this->viewResponse('modules/statistics-chart/families', [ 194d35568b4SGreg Roach 'statistics' => Registry::container()->get(Statistics::class), 195c6266a77SGreg Roach ]); 196c6266a77SGreg Roach } 197c6266a77SGreg Roach 198c6266a77SGreg Roach /** 19957ab2231SGreg Roach * @param ServerRequestInterface $request 200c6266a77SGreg Roach * 2016ccdf4f0SGreg Roach * @return ResponseInterface 202c6266a77SGreg Roach */ 20349528f2bSGreg Roach public function getOtherAction(ServerRequestInterface $request): ResponseInterface 204c6266a77SGreg Roach { 205b6c326d8SGreg Roach $this->layout = 'layouts/ajax'; 206b6c326d8SGreg Roach 207b6c326d8SGreg Roach return $this->viewResponse('modules/statistics-chart/other', [ 208d35568b4SGreg Roach 'statistics' => Registry::container()->get(Statistics::class), 209c6266a77SGreg Roach ]); 210c6266a77SGreg Roach } 211c6266a77SGreg Roach 212c6266a77SGreg Roach /** 21357ab2231SGreg Roach * @param ServerRequestInterface $request 214c6266a77SGreg Roach * 2156ccdf4f0SGreg Roach * @return ResponseInterface 216c6266a77SGreg Roach */ 21757ab2231SGreg Roach public function getCustomAction(ServerRequestInterface $request): ResponseInterface 218c6266a77SGreg Roach { 219b6c326d8SGreg Roach $this->layout = 'layouts/ajax'; 220b6c326d8SGreg Roach 221b55cbc6bSGreg Roach $tree = Validator::attributes($request)->tree(); 22257ab2231SGreg Roach 223b6c326d8SGreg Roach return $this->viewResponse('modules/statistics-chart/custom', [ 224c6266a77SGreg Roach 'module' => $this, 225c6266a77SGreg Roach 'tree' => $tree, 226c6266a77SGreg Roach ]); 227c6266a77SGreg Roach } 228c6266a77SGreg Roach 229c6266a77SGreg Roach /** 2306ccdf4f0SGreg Roach * @param ServerRequestInterface $request 231c6266a77SGreg Roach * 2326ccdf4f0SGreg Roach * @return ResponseInterface 233c6266a77SGreg Roach */ 2343aa29b97SGreg Roach public function postCustomChartAction(ServerRequestInterface $request): ResponseInterface 235c6266a77SGreg Roach { 236d35568b4SGreg Roach $statistics = Registry::container()->get(Statistics::class); 237dca2dd78SGreg Roach assert($statistics instanceof Statistics); 23857ab2231SGreg Roach 239748dbe15SGreg Roach $x_axis_type = Validator::parsedBody($request)->integer('x-as'); 240748dbe15SGreg Roach $y_axis_type = Validator::parsedBody($request)->integer('y-as'); 241748dbe15SGreg Roach $z_axis_type = Validator::parsedBody($request)->integer('z-as'); 242c6266a77SGreg Roach $ydata = []; 243c6266a77SGreg Roach 244c6266a77SGreg Roach switch ($x_axis_type) { 245c6266a77SGreg Roach case self::X_AXIS_INDIVIDUAL_MAP: 2466ccdf4f0SGreg Roach return response($statistics->chartDistribution( 247748dbe15SGreg Roach Validator::parsedBody($request)->string('chart_shows'), 248748dbe15SGreg Roach Validator::parsedBody($request)->string('chart_type'), 249748dbe15SGreg Roach Validator::parsedBody($request)->string('SURN') 250c6266a77SGreg Roach )); 251c6266a77SGreg Roach 252c6266a77SGreg Roach case self::X_AXIS_BIRTH_MAP: 2536ccdf4f0SGreg Roach return response($statistics->chartDistribution( 254748dbe15SGreg Roach Validator::parsedBody($request)->string('chart_shows'), 255c6266a77SGreg Roach 'birth_distribution_chart' 256c6266a77SGreg Roach )); 257c6266a77SGreg Roach 258c6266a77SGreg Roach case self::X_AXIS_DEATH_MAP: 2596ccdf4f0SGreg Roach return response($statistics->chartDistribution( 260748dbe15SGreg Roach Validator::parsedBody($request)->string('chart_shows'), 261c6266a77SGreg Roach 'death_distribution_chart' 262c6266a77SGreg Roach )); 263c6266a77SGreg Roach 264c6266a77SGreg Roach case self::X_AXIS_MARRIAGE_MAP: 2656ccdf4f0SGreg Roach return response($statistics->chartDistribution( 266748dbe15SGreg Roach Validator::parsedBody($request)->string('chart_shows'), 267c6266a77SGreg Roach 'marriage_distribution_chart' 268c6266a77SGreg Roach )); 269c6266a77SGreg Roach 270c6266a77SGreg Roach case self::X_AXIS_BIRTH_MONTH: 271c6266a77SGreg Roach $chart_title = I18N::translate('Month of birth'); 272c6266a77SGreg Roach $x_axis_title = I18N::translate('Month'); 273c6266a77SGreg Roach $x_axis = $this->axisMonths(); 274c6266a77SGreg Roach 275c6266a77SGreg Roach switch ($y_axis_type) { 276c6266a77SGreg Roach case self::Y_AXIS_NUMBERS: 277c6266a77SGreg Roach $y_axis_title = I18N::translate('Individuals'); 278c6266a77SGreg Roach break; 279c6266a77SGreg Roach case self::Y_AXIS_PERCENT: 280c6266a77SGreg Roach $y_axis_title = '%'; 281c6266a77SGreg Roach break; 282c6266a77SGreg Roach default: 283d501c45dSGreg Roach throw new HttpNotFoundException(); 284c6266a77SGreg Roach } 285c6266a77SGreg Roach 286c6266a77SGreg Roach switch ($z_axis_type) { 287c6266a77SGreg Roach case self::Z_AXIS_ALL: 288c6266a77SGreg Roach $z_axis = $this->axisAll(); 289cde1d378SGreg Roach $rows = $statistics->statsBirthQuery()->get(); 290c6266a77SGreg Roach foreach ($rows as $row) { 291c6266a77SGreg Roach $this->fillYData($row->d_month, 0, $row->total, $x_axis, $z_axis, $ydata); 292c6266a77SGreg Roach } 293c6266a77SGreg Roach break; 294c6266a77SGreg Roach case self::Z_AXIS_SEX: 295c6266a77SGreg Roach $z_axis = $this->axisSexes(); 296cde1d378SGreg Roach $rows = $statistics->statsBirthBySexQuery()->get(); 297c6266a77SGreg Roach foreach ($rows as $row) { 298c6266a77SGreg Roach $this->fillYData($row->d_month, $row->i_sex, $row->total, $x_axis, $z_axis, $ydata); 299c6266a77SGreg Roach } 300c6266a77SGreg Roach break; 301c6266a77SGreg Roach case self::Z_AXIS_TIME: 302748dbe15SGreg Roach $boundaries_csv = Validator::parsedBody($request)->string('z-axis-boundaries-periods'); 303c6266a77SGreg Roach $z_axis = $this->axisYears($boundaries_csv); 304c6266a77SGreg Roach $prev_boundary = 0; 305c6266a77SGreg Roach foreach (array_keys($z_axis) as $boundary) { 306cde1d378SGreg Roach $rows = $statistics->statsBirthQuery($prev_boundary, $boundary)->get(); 307c6266a77SGreg Roach foreach ($rows as $row) { 308c6266a77SGreg Roach $this->fillYData($row->d_month, $boundary, $row->total, $x_axis, $z_axis, $ydata); 309c6266a77SGreg Roach } 310c6266a77SGreg Roach $prev_boundary = $boundary + 1; 311c6266a77SGreg Roach } 312c6266a77SGreg Roach break; 313c6266a77SGreg Roach default: 314d501c45dSGreg Roach throw new HttpNotFoundException(); 315c6266a77SGreg Roach } 316c6266a77SGreg Roach 3176ccdf4f0SGreg Roach return response($this->myPlot($chart_title, $x_axis, $x_axis_title, $ydata, $y_axis_title, $z_axis, $y_axis_type)); 318c6266a77SGreg Roach 319c6266a77SGreg Roach case self::X_AXIS_DEATH_MONTH: 320c6266a77SGreg Roach $chart_title = I18N::translate('Month of death'); 321c6266a77SGreg Roach $x_axis_title = I18N::translate('Month'); 322c6266a77SGreg Roach $x_axis = $this->axisMonths(); 323c6266a77SGreg Roach 324c6266a77SGreg Roach switch ($y_axis_type) { 325c6266a77SGreg Roach case self::Y_AXIS_NUMBERS: 326c6266a77SGreg Roach $y_axis_title = I18N::translate('Individuals'); 327c6266a77SGreg Roach break; 328c6266a77SGreg Roach case self::Y_AXIS_PERCENT: 329c6266a77SGreg Roach $y_axis_title = '%'; 330c6266a77SGreg Roach break; 331c6266a77SGreg Roach default: 332d501c45dSGreg Roach throw new HttpNotFoundException(); 333c6266a77SGreg Roach } 334c6266a77SGreg Roach 335c6266a77SGreg Roach switch ($z_axis_type) { 336c6266a77SGreg Roach case self::Z_AXIS_ALL: 337c6266a77SGreg Roach $z_axis = $this->axisAll(); 338cde1d378SGreg Roach $rows = $statistics->statsDeathQuery()->get(); 339c6266a77SGreg Roach foreach ($rows as $row) { 340c6266a77SGreg Roach $this->fillYData($row->d_month, 0, $row->total, $x_axis, $z_axis, $ydata); 341c6266a77SGreg Roach } 342c6266a77SGreg Roach break; 343c6266a77SGreg Roach case self::Z_AXIS_SEX: 344c6266a77SGreg Roach $z_axis = $this->axisSexes(); 345cde1d378SGreg Roach $rows = $statistics->statsDeathBySexQuery()->get(); 346c6266a77SGreg Roach foreach ($rows as $row) { 347c6266a77SGreg Roach $this->fillYData($row->d_month, $row->i_sex, $row->total, $x_axis, $z_axis, $ydata); 348c6266a77SGreg Roach } 349c6266a77SGreg Roach break; 350c6266a77SGreg Roach case self::Z_AXIS_TIME: 351748dbe15SGreg Roach $boundaries_csv = Validator::parsedBody($request)->string('z-axis-boundaries-periods'); 352c6266a77SGreg Roach $z_axis = $this->axisYears($boundaries_csv); 353c6266a77SGreg Roach $prev_boundary = 0; 354c6266a77SGreg Roach foreach (array_keys($z_axis) as $boundary) { 355cde1d378SGreg Roach $rows = $statistics->statsDeathQuery($prev_boundary, $boundary)->get(); 356c6266a77SGreg Roach foreach ($rows as $row) { 357c6266a77SGreg Roach $this->fillYData($row->d_month, $boundary, $row->total, $x_axis, $z_axis, $ydata); 358c6266a77SGreg Roach } 359c6266a77SGreg Roach $prev_boundary = $boundary + 1; 360c6266a77SGreg Roach } 361c6266a77SGreg Roach break; 362c6266a77SGreg Roach default: 363d501c45dSGreg Roach throw new HttpNotFoundException(); 364c6266a77SGreg Roach } 365c6266a77SGreg Roach 3666ccdf4f0SGreg Roach return response($this->myPlot($chart_title, $x_axis, $x_axis_title, $ydata, $y_axis_title, $z_axis, $y_axis_type)); 367c6266a77SGreg Roach 368c6266a77SGreg Roach case self::X_AXIS_MARRIAGE_MONTH: 369c6266a77SGreg Roach $chart_title = I18N::translate('Month of marriage'); 370c6266a77SGreg Roach $x_axis_title = I18N::translate('Month'); 371c6266a77SGreg Roach $x_axis = $this->axisMonths(); 372c6266a77SGreg Roach 373c6266a77SGreg Roach switch ($y_axis_type) { 374c6266a77SGreg Roach case self::Y_AXIS_NUMBERS: 375c6266a77SGreg Roach $y_axis_title = I18N::translate('Families'); 376c6266a77SGreg Roach break; 377c6266a77SGreg Roach case self::Y_AXIS_PERCENT: 378c6266a77SGreg Roach $y_axis_title = '%'; 379c6266a77SGreg Roach break; 380c6266a77SGreg Roach default: 381d501c45dSGreg Roach throw new HttpNotFoundException(); 382c6266a77SGreg Roach } 383c6266a77SGreg Roach 384c6266a77SGreg Roach switch ($z_axis_type) { 385c6266a77SGreg Roach case self::Z_AXIS_ALL: 386c6266a77SGreg Roach $z_axis = $this->axisAll(); 387e6f3d5e2SGreg Roach $rows = $statistics->statsMarriageQuery()->get(); 388c6266a77SGreg Roach foreach ($rows as $row) { 389c6266a77SGreg Roach $this->fillYData($row->d_month, 0, $row->total, $x_axis, $z_axis, $ydata); 390c6266a77SGreg Roach } 391c6266a77SGreg Roach break; 392c6266a77SGreg Roach case self::Z_AXIS_TIME: 393748dbe15SGreg Roach $boundaries_csv = Validator::parsedBody($request)->string('z-axis-boundaries-periods'); 394c6266a77SGreg Roach $z_axis = $this->axisYears($boundaries_csv); 395c6266a77SGreg Roach $prev_boundary = 0; 396c6266a77SGreg Roach foreach (array_keys($z_axis) as $boundary) { 397e6f3d5e2SGreg Roach $rows = $statistics->statsMarriageQuery($prev_boundary, $boundary)->get(); 398c6266a77SGreg Roach foreach ($rows as $row) { 399c6266a77SGreg Roach $this->fillYData($row->d_month, $boundary, $row->total, $x_axis, $z_axis, $ydata); 400c6266a77SGreg Roach } 401c6266a77SGreg Roach $prev_boundary = $boundary + 1; 402c6266a77SGreg Roach } 403c6266a77SGreg Roach break; 404c6266a77SGreg Roach default: 405d501c45dSGreg Roach throw new HttpNotFoundException(); 406c6266a77SGreg Roach } 407c6266a77SGreg Roach 4086ccdf4f0SGreg Roach return response($this->myPlot($chart_title, $x_axis, $x_axis_title, $ydata, $y_axis_title, $z_axis, $y_axis_type)); 409c6266a77SGreg Roach 410c6266a77SGreg Roach case self::X_AXIS_FIRST_CHILD_MONTH: 411c6266a77SGreg Roach $chart_title = I18N::translate('Month of birth of first child in a relation'); 412c6266a77SGreg Roach $x_axis_title = I18N::translate('Month'); 413c6266a77SGreg Roach $x_axis = $this->axisMonths(); 414c6266a77SGreg Roach 415c6266a77SGreg Roach switch ($y_axis_type) { 416c6266a77SGreg Roach case self::Y_AXIS_NUMBERS: 417c6266a77SGreg Roach $y_axis_title = I18N::translate('Children'); 418c6266a77SGreg Roach break; 419c6266a77SGreg Roach case self::Y_AXIS_PERCENT: 420c6266a77SGreg Roach $y_axis_title = '%'; 421c6266a77SGreg Roach break; 422c6266a77SGreg Roach default: 423d501c45dSGreg Roach throw new HttpNotFoundException(); 424c6266a77SGreg Roach } 425c6266a77SGreg Roach 426c6266a77SGreg Roach switch ($z_axis_type) { 427c6266a77SGreg Roach case self::Z_AXIS_ALL: 428c6266a77SGreg Roach $z_axis = $this->axisAll(); 429999da590SGreg Roach $rows = $statistics->monthFirstChildQuery()->get(); 430c6266a77SGreg Roach foreach ($rows as $row) { 431c6266a77SGreg Roach $this->fillYData($row->d_month, 0, $row->total, $x_axis, $z_axis, $ydata); 432c6266a77SGreg Roach } 433c6266a77SGreg Roach break; 434c6266a77SGreg Roach case self::Z_AXIS_SEX: 435c6266a77SGreg Roach $z_axis = $this->axisSexes(); 436999da590SGreg Roach $rows = $statistics->monthFirstChildBySexQuery()->get(); 437c6266a77SGreg Roach foreach ($rows as $row) { 438c6266a77SGreg Roach $this->fillYData($row->d_month, $row->i_sex, $row->total, $x_axis, $z_axis, $ydata); 439c6266a77SGreg Roach } 440c6266a77SGreg Roach break; 441c6266a77SGreg Roach case self::Z_AXIS_TIME: 442748dbe15SGreg Roach $boundaries_csv = Validator::parsedBody($request)->string('z-axis-boundaries-periods'); 443c6266a77SGreg Roach $z_axis = $this->axisYears($boundaries_csv); 444c6266a77SGreg Roach $prev_boundary = 0; 445c6266a77SGreg Roach foreach (array_keys($z_axis) as $boundary) { 446999da590SGreg Roach $rows = $statistics->monthFirstChildQuery($prev_boundary, $boundary)->get(); 447c6266a77SGreg Roach foreach ($rows as $row) { 448c6266a77SGreg Roach $this->fillYData($row->d_month, $boundary, $row->total, $x_axis, $z_axis, $ydata); 449c6266a77SGreg Roach } 450c6266a77SGreg Roach $prev_boundary = $boundary + 1; 451c6266a77SGreg Roach } 452c6266a77SGreg Roach break; 453c6266a77SGreg Roach default: 454d501c45dSGreg Roach throw new HttpNotFoundException(); 455c6266a77SGreg Roach } 456c6266a77SGreg Roach 4576ccdf4f0SGreg Roach return response($this->myPlot($chart_title, $x_axis, $x_axis_title, $ydata, $y_axis_title, $z_axis, $y_axis_type)); 458c6266a77SGreg Roach 459c6266a77SGreg Roach case self::X_AXIS_FIRST_MARRIAGE_MONTH: 460c6266a77SGreg Roach $chart_title = I18N::translate('Month of first marriage'); 461c6266a77SGreg Roach $x_axis_title = I18N::translate('Month'); 462c6266a77SGreg Roach $x_axis = $this->axisMonths(); 463c6266a77SGreg Roach 464c6266a77SGreg Roach switch ($y_axis_type) { 465c6266a77SGreg Roach case self::Y_AXIS_NUMBERS: 466c6266a77SGreg Roach $y_axis_title = I18N::translate('Families'); 467c6266a77SGreg Roach break; 468c6266a77SGreg Roach case self::Y_AXIS_PERCENT: 469c6266a77SGreg Roach $y_axis_title = '%'; 470c6266a77SGreg Roach break; 471c6266a77SGreg Roach default: 472d501c45dSGreg Roach throw new HttpNotFoundException(); 473c6266a77SGreg Roach } 474c6266a77SGreg Roach 475c6266a77SGreg Roach switch ($z_axis_type) { 476c6266a77SGreg Roach case self::Z_AXIS_ALL: 477c6266a77SGreg Roach $z_axis = $this->axisAll(); 478e6f3d5e2SGreg Roach $rows = $statistics->statsFirstMarriageQuery()->get(); 479c6266a77SGreg Roach $indi = []; 480c6266a77SGreg Roach foreach ($rows as $row) { 481dca2dd78SGreg Roach if (!in_array($row->f_husb, $indi, true) && !in_array($row->f_wife, $indi, true)) { 482c6266a77SGreg Roach $this->fillYData($row->month, 0, 1, $x_axis, $z_axis, $ydata); 483c6266a77SGreg Roach } 484dca2dd78SGreg Roach $indi[] = $row->f_husb; 485dca2dd78SGreg Roach $indi[] = $row->f_wife; 486c6266a77SGreg Roach } 487c6266a77SGreg Roach break; 488c6266a77SGreg Roach case self::Z_AXIS_TIME: 489748dbe15SGreg Roach $boundaries_csv = Validator::parsedBody($request)->string('z-axis-boundaries-periods'); 490c6266a77SGreg Roach $z_axis = $this->axisYears($boundaries_csv); 491c6266a77SGreg Roach $prev_boundary = 0; 492c6266a77SGreg Roach $indi = []; 493c6266a77SGreg Roach foreach (array_keys($z_axis) as $boundary) { 494e6f3d5e2SGreg Roach $rows = $statistics->statsFirstMarriageQuery($prev_boundary, $boundary)->get(); 495c6266a77SGreg Roach foreach ($rows as $row) { 496dca2dd78SGreg Roach if (!in_array($row->f_husb, $indi, true) && !in_array($row->f_wife, $indi, true)) { 497c6266a77SGreg Roach $this->fillYData($row->month, $boundary, 1, $x_axis, $z_axis, $ydata); 498c6266a77SGreg Roach } 499dca2dd78SGreg Roach $indi[] = $row->f_husb; 500dca2dd78SGreg Roach $indi[] = $row->f_wife; 501c6266a77SGreg Roach } 502c6266a77SGreg Roach $prev_boundary = $boundary + 1; 503c6266a77SGreg Roach } 504c6266a77SGreg Roach break; 505c6266a77SGreg Roach default: 506d501c45dSGreg Roach throw new HttpNotFoundException(); 507c6266a77SGreg Roach } 508c6266a77SGreg Roach 5096ccdf4f0SGreg Roach return response($this->myPlot($chart_title, $x_axis, $x_axis_title, $ydata, $y_axis_title, $z_axis, $y_axis_type)); 510c6266a77SGreg Roach 511c6266a77SGreg Roach case self::X_AXIS_AGE_AT_DEATH: 512c6266a77SGreg Roach $chart_title = I18N::translate('Average age at death'); 513c6266a77SGreg Roach $x_axis_title = I18N::translate('age'); 514748dbe15SGreg Roach $boundaries_csv = Validator::parsedBody($request)->string('x-axis-boundaries-ages'); 515c6266a77SGreg Roach $x_axis = $this->axisNumbers($boundaries_csv); 516c6266a77SGreg Roach 517c6266a77SGreg Roach switch ($y_axis_type) { 518c6266a77SGreg Roach case self::Y_AXIS_NUMBERS: 519c6266a77SGreg Roach $y_axis_title = I18N::translate('Individuals'); 520c6266a77SGreg Roach break; 521c6266a77SGreg Roach case self::Y_AXIS_PERCENT: 522c6266a77SGreg Roach $y_axis_title = '%'; 523c6266a77SGreg Roach break; 524c6266a77SGreg Roach default: 525d501c45dSGreg Roach throw new HttpNotFoundException(); 526c6266a77SGreg Roach } 527c6266a77SGreg Roach 528c6266a77SGreg Roach switch ($z_axis_type) { 529c6266a77SGreg Roach case self::Z_AXIS_ALL: 530c6266a77SGreg Roach $z_axis = $this->axisAll(); 5319219296aSGreg Roach $rows = $statistics->statsAgeQuery('DEAT'); 532c6266a77SGreg Roach foreach ($rows as $row) { 533c6266a77SGreg Roach foreach ($row as $age) { 534c6266a77SGreg Roach $years = (int) ($age / self::DAYS_IN_YEAR); 535c6266a77SGreg Roach $this->fillYData($years, 0, 1, $x_axis, $z_axis, $ydata); 536c6266a77SGreg Roach } 537c6266a77SGreg Roach } 538c6266a77SGreg Roach break; 539c6266a77SGreg Roach case self::Z_AXIS_SEX: 540c6266a77SGreg Roach $z_axis = $this->axisSexes(); 541c6266a77SGreg Roach foreach (array_keys($z_axis) as $sex) { 5429219296aSGreg Roach $rows = $statistics->statsAgeQuery('DEAT', $sex); 543c6266a77SGreg Roach foreach ($rows as $row) { 544c6266a77SGreg Roach foreach ($row as $age) { 545c6266a77SGreg Roach $years = (int) ($age / self::DAYS_IN_YEAR); 546c6266a77SGreg Roach $this->fillYData($years, $sex, 1, $x_axis, $z_axis, $ydata); 547c6266a77SGreg Roach } 548c6266a77SGreg Roach } 549c6266a77SGreg Roach } 550c6266a77SGreg Roach break; 551c6266a77SGreg Roach case self::Z_AXIS_TIME: 552748dbe15SGreg Roach $boundaries_csv = Validator::parsedBody($request)->string('z-axis-boundaries-periods'); 553c6266a77SGreg Roach $z_axis = $this->axisYears($boundaries_csv); 554c6266a77SGreg Roach $prev_boundary = 0; 555c6266a77SGreg Roach foreach (array_keys($z_axis) as $boundary) { 5569219296aSGreg Roach $rows = $statistics->statsAgeQuery('DEAT', 'BOTH', $prev_boundary, $boundary); 557c6266a77SGreg Roach foreach ($rows as $row) { 558c6266a77SGreg Roach foreach ($row as $age) { 559c6266a77SGreg Roach $years = (int) ($age / self::DAYS_IN_YEAR); 560c6266a77SGreg Roach $this->fillYData($years, $boundary, 1, $x_axis, $z_axis, $ydata); 561c6266a77SGreg Roach } 562c6266a77SGreg Roach } 563c6266a77SGreg Roach $prev_boundary = $boundary + 1; 564c6266a77SGreg Roach } 565c6266a77SGreg Roach 566c6266a77SGreg Roach break; 567c6266a77SGreg Roach default: 568d501c45dSGreg Roach throw new HttpNotFoundException(); 569c6266a77SGreg Roach } 570c6266a77SGreg Roach 5716ccdf4f0SGreg Roach return response($this->myPlot($chart_title, $x_axis, $x_axis_title, $ydata, $y_axis_title, $z_axis, $y_axis_type)); 572c6266a77SGreg Roach 573c6266a77SGreg Roach case self::X_AXIS_AGE_AT_MARRIAGE: 574c6266a77SGreg Roach $chart_title = I18N::translate('Age in year of marriage'); 575c6266a77SGreg Roach $x_axis_title = I18N::translate('age'); 576748dbe15SGreg Roach $boundaries_csv = Validator::parsedBody($request)->string('x-axis-boundaries-ages_m'); 577c6266a77SGreg Roach $x_axis = $this->axisNumbers($boundaries_csv); 578c6266a77SGreg Roach 579c6266a77SGreg Roach switch ($y_axis_type) { 580c6266a77SGreg Roach case self::Y_AXIS_NUMBERS: 581c6266a77SGreg Roach $y_axis_title = I18N::translate('Individuals'); 582c6266a77SGreg Roach break; 583c6266a77SGreg Roach case self::Y_AXIS_PERCENT: 584c6266a77SGreg Roach $y_axis_title = '%'; 585c6266a77SGreg Roach break; 586c6266a77SGreg Roach default: 587d501c45dSGreg Roach throw new HttpNotFoundException(); 588c6266a77SGreg Roach } 589c6266a77SGreg Roach 590c6266a77SGreg Roach switch ($z_axis_type) { 591c6266a77SGreg Roach case self::Z_AXIS_ALL: 592c6266a77SGreg Roach $z_axis = $this->axisAll(); 593afa8d404SGreg Roach // The stats query doesn't have an "all" function, so query M/F separately 594afa8d404SGreg Roach foreach (['M', 'F'] as $sex) { 5959219296aSGreg Roach $rows = $statistics->statsMarrAgeQuery($sex); 596c6266a77SGreg Roach foreach ($rows as $row) { 597c6266a77SGreg Roach $years = (int) ($row->age / self::DAYS_IN_YEAR); 598c6266a77SGreg Roach $this->fillYData($years, 0, 1, $x_axis, $z_axis, $ydata); 599c6266a77SGreg Roach } 600c6266a77SGreg Roach } 601c6266a77SGreg Roach break; 602c6266a77SGreg Roach case self::Z_AXIS_SEX: 603c6266a77SGreg Roach $z_axis = $this->axisSexes(); 604c6266a77SGreg Roach foreach (array_keys($z_axis) as $sex) { 6059219296aSGreg Roach $rows = $statistics->statsMarrAgeQuery($sex); 606c6266a77SGreg Roach foreach ($rows as $row) { 607c6266a77SGreg Roach $years = (int) ($row->age / self::DAYS_IN_YEAR); 608c6266a77SGreg Roach $this->fillYData($years, $sex, 1, $x_axis, $z_axis, $ydata); 609c6266a77SGreg Roach } 610c6266a77SGreg Roach } 611c6266a77SGreg Roach break; 612c6266a77SGreg Roach case self::Z_AXIS_TIME: 613748dbe15SGreg Roach $boundaries_csv = Validator::parsedBody($request)->string('z-axis-boundaries-periods'); 614c6266a77SGreg Roach $z_axis = $this->axisYears($boundaries_csv); 615afa8d404SGreg Roach // The stats query doesn't have an "all" function, so query M/F separately 616afa8d404SGreg Roach foreach (['M', 'F'] as $sex) { 617c6266a77SGreg Roach $prev_boundary = 0; 618c6266a77SGreg Roach foreach (array_keys($z_axis) as $boundary) { 6199219296aSGreg Roach $rows = $statistics->statsMarrAgeQuery($sex, $prev_boundary, $boundary); 620c6266a77SGreg Roach foreach ($rows as $row) { 621c6266a77SGreg Roach $years = (int) ($row->age / self::DAYS_IN_YEAR); 622c6266a77SGreg Roach $this->fillYData($years, $boundary, 1, $x_axis, $z_axis, $ydata); 623c6266a77SGreg Roach } 624c6266a77SGreg Roach $prev_boundary = $boundary + 1; 625c6266a77SGreg Roach } 626c6266a77SGreg Roach } 627c6266a77SGreg Roach break; 628c6266a77SGreg Roach default: 629d501c45dSGreg Roach throw new HttpNotFoundException(); 630c6266a77SGreg Roach } 631c6266a77SGreg Roach 6326ccdf4f0SGreg Roach return response($this->myPlot($chart_title, $x_axis, $x_axis_title, $ydata, $y_axis_title, $z_axis, $y_axis_type)); 633c6266a77SGreg Roach 634c6266a77SGreg Roach case self::X_AXIS_AGE_AT_FIRST_MARRIAGE: 635c6266a77SGreg Roach $chart_title = I18N::translate('Age in year of first marriage'); 636c6266a77SGreg Roach $x_axis_title = I18N::translate('age'); 637748dbe15SGreg Roach $boundaries_csv = Validator::parsedBody($request)->string('x-axis-boundaries-ages_m'); 638c6266a77SGreg Roach $x_axis = $this->axisNumbers($boundaries_csv); 639c6266a77SGreg Roach 640c6266a77SGreg Roach switch ($y_axis_type) { 641c6266a77SGreg Roach case self::Y_AXIS_NUMBERS: 642c6266a77SGreg Roach $y_axis_title = I18N::translate('Individuals'); 643c6266a77SGreg Roach break; 644c6266a77SGreg Roach case self::Y_AXIS_PERCENT: 645c6266a77SGreg Roach $y_axis_title = '%'; 646c6266a77SGreg Roach break; 647c6266a77SGreg Roach default: 648d501c45dSGreg Roach throw new HttpNotFoundException(); 649c6266a77SGreg Roach } 650c6266a77SGreg Roach 651c6266a77SGreg Roach switch ($z_axis_type) { 652c6266a77SGreg Roach case self::Z_AXIS_ALL: 653c6266a77SGreg Roach $z_axis = $this->axisAll(); 654afa8d404SGreg Roach // The stats query doesn't have an "all" function, so query M/F separately 655afa8d404SGreg Roach foreach (['M', 'F'] as $sex) { 6569219296aSGreg Roach $rows = $statistics->statsMarrAgeQuery($sex); 657c6266a77SGreg Roach $indi = []; 658c6266a77SGreg Roach foreach ($rows as $row) { 6596960d4a1SGreg Roach if (!in_array($row->d_gid, $indi, true)) { 660c6266a77SGreg Roach $years = (int) ($row->age / self::DAYS_IN_YEAR); 661c6266a77SGreg Roach $this->fillYData($years, 0, 1, $x_axis, $z_axis, $ydata); 662c6266a77SGreg Roach $indi[] = $row->d_gid; 663c6266a77SGreg Roach } 664c6266a77SGreg Roach } 665c6266a77SGreg Roach } 666c6266a77SGreg Roach break; 667c6266a77SGreg Roach case self::Z_AXIS_SEX: 668c6266a77SGreg Roach $z_axis = $this->axisSexes(); 669c6266a77SGreg Roach foreach (array_keys($z_axis) as $sex) { 6709219296aSGreg Roach $rows = $statistics->statsMarrAgeQuery($sex); 671c6266a77SGreg Roach $indi = []; 672c6266a77SGreg Roach foreach ($rows as $row) { 6736960d4a1SGreg Roach if (!in_array($row->d_gid, $indi, true)) { 674c6266a77SGreg Roach $years = (int) ($row->age / self::DAYS_IN_YEAR); 675c6266a77SGreg Roach $this->fillYData($years, $sex, 1, $x_axis, $z_axis, $ydata); 676c6266a77SGreg Roach $indi[] = $row->d_gid; 677c6266a77SGreg Roach } 678c6266a77SGreg Roach } 679c6266a77SGreg Roach } 680c6266a77SGreg Roach break; 681c6266a77SGreg Roach case self::Z_AXIS_TIME: 682748dbe15SGreg Roach $boundaries_csv = Validator::parsedBody($request)->string('z-axis-boundaries-periods'); 683c6266a77SGreg Roach $z_axis = $this->axisYears($boundaries_csv); 684afa8d404SGreg Roach // The stats query doesn't have an "all" function, so query M/F separately 685afa8d404SGreg Roach foreach (['M', 'F'] as $sex) { 686c6266a77SGreg Roach $prev_boundary = 0; 687c6266a77SGreg Roach $indi = []; 688c6266a77SGreg Roach foreach (array_keys($z_axis) as $boundary) { 6899219296aSGreg Roach $rows = $statistics->statsMarrAgeQuery($sex, $prev_boundary, $boundary); 690c6266a77SGreg Roach foreach ($rows as $row) { 6916960d4a1SGreg Roach if (!in_array($row->d_gid, $indi, true)) { 692c6266a77SGreg Roach $years = (int) ($row->age / self::DAYS_IN_YEAR); 693c6266a77SGreg Roach $this->fillYData($years, $boundary, 1, $x_axis, $z_axis, $ydata); 694c6266a77SGreg Roach $indi[] = $row->d_gid; 695c6266a77SGreg Roach } 696c6266a77SGreg Roach } 697c6266a77SGreg Roach $prev_boundary = $boundary + 1; 698c6266a77SGreg Roach } 699c6266a77SGreg Roach } 700c6266a77SGreg Roach break; 701c6266a77SGreg Roach default: 702d501c45dSGreg Roach throw new HttpNotFoundException(); 703c6266a77SGreg Roach } 704c6266a77SGreg Roach 7056ccdf4f0SGreg Roach return response($this->myPlot($chart_title, $x_axis, $x_axis_title, $ydata, $y_axis_title, $z_axis, $y_axis_type)); 706c6266a77SGreg Roach 707c6266a77SGreg Roach case self::X_AXIS_NUMBER_OF_CHILDREN: 708c6266a77SGreg Roach $chart_title = I18N::translate('Number of children'); 709c6266a77SGreg Roach $x_axis_title = I18N::translate('Children'); 7106960d4a1SGreg Roach $x_axis = $this->axisNumbers('0,1,2,3,4,5,6,7,8,9,10'); 711c6266a77SGreg Roach 712c6266a77SGreg Roach switch ($y_axis_type) { 713c6266a77SGreg Roach case self::Y_AXIS_NUMBERS: 714c6266a77SGreg Roach $y_axis_title = I18N::translate('Families'); 715c6266a77SGreg Roach break; 716c6266a77SGreg Roach case self::Y_AXIS_PERCENT: 717c6266a77SGreg Roach $y_axis_title = '%'; 718c6266a77SGreg Roach break; 719c6266a77SGreg Roach default: 720d501c45dSGreg Roach throw new HttpNotFoundException(); 721c6266a77SGreg Roach } 722c6266a77SGreg Roach 723c6266a77SGreg Roach switch ($z_axis_type) { 724c6266a77SGreg Roach case self::Z_AXIS_ALL: 725c6266a77SGreg Roach $z_axis = $this->axisAll(); 7269219296aSGreg Roach $rows = $statistics->statsChildrenQuery(); 727c6266a77SGreg Roach foreach ($rows as $row) { 728c6266a77SGreg Roach $this->fillYData($row->f_numchil, 0, $row->total, $x_axis, $z_axis, $ydata); 729c6266a77SGreg Roach } 730c6266a77SGreg Roach break; 731c6266a77SGreg Roach case self::Z_AXIS_TIME: 732748dbe15SGreg Roach $boundaries_csv = Validator::parsedBody($request)->string('z-axis-boundaries-periods'); 733c6266a77SGreg Roach $z_axis = $this->axisYears($boundaries_csv); 734c6266a77SGreg Roach $prev_boundary = 0; 735c6266a77SGreg Roach foreach (array_keys($z_axis) as $boundary) { 736b1126ab4SGreg Roach $rows = $statistics->statsChildrenQuery($prev_boundary, $boundary); 737c6266a77SGreg Roach foreach ($rows as $row) { 738c6266a77SGreg Roach $this->fillYData($row->f_numchil, $boundary, $row->total, $x_axis, $z_axis, $ydata); 739c6266a77SGreg Roach } 740c6266a77SGreg Roach $prev_boundary = $boundary + 1; 741c6266a77SGreg Roach } 742c6266a77SGreg Roach break; 743c6266a77SGreg Roach default: 744d501c45dSGreg Roach throw new HttpNotFoundException(); 745c6266a77SGreg Roach } 746c6266a77SGreg Roach 7476ccdf4f0SGreg Roach return response($this->myPlot($chart_title, $x_axis, $x_axis_title, $ydata, $y_axis_title, $z_axis, $y_axis_type)); 748c6266a77SGreg Roach 749c6266a77SGreg Roach default: 750d501c45dSGreg Roach throw new HttpNotFoundException(); 751c6266a77SGreg Roach } 752c6266a77SGreg Roach } 753c6266a77SGreg Roach 754c6266a77SGreg Roach /** 75524f2a3afSGreg Roach * @return array<string> 756c6266a77SGreg Roach */ 757c6266a77SGreg Roach private function axisAll(): array 758c6266a77SGreg Roach { 759c6266a77SGreg Roach return [ 760c6266a77SGreg Roach I18N::translate('Total'), 761c6266a77SGreg Roach ]; 762c6266a77SGreg Roach } 763c6266a77SGreg Roach 764c6266a77SGreg Roach /** 76524f2a3afSGreg Roach * @return array<string> 766c6266a77SGreg Roach */ 767c6266a77SGreg Roach private function axisSexes(): array 768c6266a77SGreg Roach { 769c6266a77SGreg Roach return [ 770c6266a77SGreg Roach 'M' => I18N::translate('Male'), 771c6266a77SGreg Roach 'F' => I18N::translate('Female'), 772c6266a77SGreg Roach ]; 773c6266a77SGreg Roach } 774c6266a77SGreg Roach 775c6266a77SGreg Roach /** 776c6266a77SGreg Roach * Labels for the X axis 777c6266a77SGreg Roach * 77824f2a3afSGreg Roach * @return array<string> 779c6266a77SGreg Roach */ 780c6266a77SGreg Roach private function axisMonths(): array 781c6266a77SGreg Roach { 782c6266a77SGreg Roach return [ 783c6266a77SGreg Roach 'JAN' => I18N::translateContext('NOMINATIVE', 'January'), 784c6266a77SGreg Roach 'FEB' => I18N::translateContext('NOMINATIVE', 'February'), 785c6266a77SGreg Roach 'MAR' => I18N::translateContext('NOMINATIVE', 'March'), 786c6266a77SGreg Roach 'APR' => I18N::translateContext('NOMINATIVE', 'April'), 787c6266a77SGreg Roach 'MAY' => I18N::translateContext('NOMINATIVE', 'May'), 788c6266a77SGreg Roach 'JUN' => I18N::translateContext('NOMINATIVE', 'June'), 789c6266a77SGreg Roach 'JUL' => I18N::translateContext('NOMINATIVE', 'July'), 790c6266a77SGreg Roach 'AUG' => I18N::translateContext('NOMINATIVE', 'August'), 791c6266a77SGreg Roach 'SEP' => I18N::translateContext('NOMINATIVE', 'September'), 792c6266a77SGreg Roach 'OCT' => I18N::translateContext('NOMINATIVE', 'October'), 793c6266a77SGreg Roach 'NOV' => I18N::translateContext('NOMINATIVE', 'November'), 794c6266a77SGreg Roach 'DEC' => I18N::translateContext('NOMINATIVE', 'December'), 795c6266a77SGreg Roach ]; 796c6266a77SGreg Roach } 797c6266a77SGreg Roach 798c6266a77SGreg Roach /** 799c6266a77SGreg Roach * Convert a list of N year-boundaries into N+1 year-ranges for the z-axis. 800c6266a77SGreg Roach * 801c6266a77SGreg Roach * @param string $boundaries_csv 802c6266a77SGreg Roach * 80324f2a3afSGreg Roach * @return array<string> 804c6266a77SGreg Roach */ 805c6266a77SGreg Roach private function axisYears(string $boundaries_csv): array 806c6266a77SGreg Roach { 807c6266a77SGreg Roach $boundaries = explode(',', $boundaries_csv); 808c6266a77SGreg Roach 809c6266a77SGreg Roach $axis = []; 810c6266a77SGreg Roach foreach ($boundaries as $n => $boundary) { 811c6266a77SGreg Roach if ($n === 0) { 81267ff9eb7SGreg Roach $axis[$boundary - 1] = '–' . I18N::digits($boundary); 813c6266a77SGreg Roach } else { 81467ff9eb7SGreg Roach $axis[$boundary - 1] = I18N::digits($boundaries[$n - 1]) . '–' . I18N::digits($boundary); 815c6266a77SGreg Roach } 816c6266a77SGreg Roach } 817c6266a77SGreg Roach 81867ff9eb7SGreg Roach $axis[PHP_INT_MAX] = I18N::digits($boundaries[count($boundaries) - 1]) . '–'; 819c6266a77SGreg Roach 820c6266a77SGreg Roach return $axis; 821c6266a77SGreg Roach } 822c6266a77SGreg Roach 823c6266a77SGreg Roach /** 824c6266a77SGreg Roach * Create the X axis. 825c6266a77SGreg Roach * 826c6266a77SGreg Roach * @param string $boundaries_csv 827c6266a77SGreg Roach * 828fc26b4f6SGreg Roach * @return array<string> 829c6266a77SGreg Roach */ 830c6266a77SGreg Roach private function axisNumbers(string $boundaries_csv): array 831c6266a77SGreg Roach { 832c6266a77SGreg Roach $boundaries = explode(',', $boundaries_csv); 833c6266a77SGreg Roach 834*f25fc0f9SGreg Roach $boundaries = array_map(static fn (string $x): int => (int) $x, $boundaries); 835c6266a77SGreg Roach 836c6266a77SGreg Roach $axis = []; 837c6266a77SGreg Roach foreach ($boundaries as $n => $boundary) { 838c6266a77SGreg Roach if ($n === 0) { 8396960d4a1SGreg Roach $prev_boundary = 0; 8406960d4a1SGreg Roach } else { 8416960d4a1SGreg Roach $prev_boundary = $boundaries[$n - 1] + 1; 8426960d4a1SGreg Roach } 8436960d4a1SGreg Roach 8446960d4a1SGreg Roach if ($prev_boundary === $boundary) { 845c6266a77SGreg Roach /* I18N: A range of numbers */ 8466960d4a1SGreg Roach $axis[$boundary] = I18N::number($boundary); 847c6266a77SGreg Roach } else { 848c6266a77SGreg Roach /* I18N: A range of numbers */ 8496960d4a1SGreg Roach $axis[$boundary] = I18N::translate('%1$s–%2$s', I18N::number($prev_boundary), I18N::number($boundary)); 850c6266a77SGreg Roach } 851c6266a77SGreg Roach } 852c6266a77SGreg Roach 853c6266a77SGreg Roach /* I18N: Label on a graph; 40+ means 40 or more */ 854c6266a77SGreg Roach $axis[PHP_INT_MAX] = I18N::translate('%s+', I18N::number($boundaries[count($boundaries) - 1])); 855c6266a77SGreg Roach 856c6266a77SGreg Roach return $axis; 857c6266a77SGreg Roach } 858c6266a77SGreg Roach 859c6266a77SGreg Roach /** 860c6266a77SGreg Roach * Calculate the Y axis. 861c6266a77SGreg Roach * 862c6266a77SGreg Roach * @param int|string $x 863c6266a77SGreg Roach * @param int|string $z 864c6266a77SGreg Roach * @param int|string $value 86576d39c55SGreg Roach * @param array<string> $x_axis 86676d39c55SGreg Roach * @param array<string> $z_axis 86709482a55SGreg Roach * @param array<array<int>> $ydata 868c6266a77SGreg Roach * 869c6266a77SGreg Roach * @return void 870c6266a77SGreg Roach */ 871e364afe4SGreg Roach private function fillYData($x, $z, $value, array $x_axis, array $z_axis, array &$ydata): void 872c6266a77SGreg Roach { 873c6266a77SGreg Roach $x = $this->findAxisEntry($x, $x_axis); 874c6266a77SGreg Roach $z = $this->findAxisEntry($z, $z_axis); 875c6266a77SGreg Roach 8766960d4a1SGreg Roach if (!array_key_exists($z, $z_axis)) { 877c6266a77SGreg Roach foreach (array_keys($z_axis) as $key) { 878c6266a77SGreg Roach if ($value <= $key) { 879c6266a77SGreg Roach $z = $key; 880c6266a77SGreg Roach break; 881c6266a77SGreg Roach } 882c6266a77SGreg Roach } 883c6266a77SGreg Roach } 884c6266a77SGreg Roach 885c6266a77SGreg Roach // Add the value to the appropriate data point. 886c6266a77SGreg Roach $ydata[$z][$x] = ($ydata[$z][$x] ?? 0) + $value; 887c6266a77SGreg Roach } 888c6266a77SGreg Roach 889c6266a77SGreg Roach /** 890c6266a77SGreg Roach * Find the axis entry for a given value. 891c6266a77SGreg Roach * Some are direct lookup (e.g. M/F, JAN/FEB/MAR). 892d823340dSGreg Roach * Others need to find the appropriate range. 893c6266a77SGreg Roach * 89476d39c55SGreg Roach * @param int|string $value 89509482a55SGreg Roach * @param array<string> $axis 896c6266a77SGreg Roach * 897c6266a77SGreg Roach * @return int|string 898c6266a77SGreg Roach */ 89924f2a3afSGreg Roach private function findAxisEntry($value, array $axis) 900c6266a77SGreg Roach { 901c6266a77SGreg Roach if (is_numeric($value)) { 902c6266a77SGreg Roach $value = (int) $value; 903c6266a77SGreg Roach 9046960d4a1SGreg Roach if (!array_key_exists($value, $axis)) { 905c6266a77SGreg Roach foreach (array_keys($axis) as $boundary) { 906c6266a77SGreg Roach if ($value <= $boundary) { 907c6266a77SGreg Roach $value = $boundary; 908c6266a77SGreg Roach break; 909c6266a77SGreg Roach } 910c6266a77SGreg Roach } 911c6266a77SGreg Roach } 912c6266a77SGreg Roach } 913c6266a77SGreg Roach 914c6266a77SGreg Roach return $value; 915c6266a77SGreg Roach } 916c6266a77SGreg Roach 917c6266a77SGreg Roach /** 918c6266a77SGreg Roach * Plot the data. 919c6266a77SGreg Roach * 920c6266a77SGreg Roach * @param string $chart_title 92109482a55SGreg Roach * @param array<string> $x_axis 922c6266a77SGreg Roach * @param string $x_axis_title 92309482a55SGreg Roach * @param array<array<int>> $ydata 924c6266a77SGreg Roach * @param string $y_axis_title 92509482a55SGreg Roach * @param array<string> $z_axis 926c6266a77SGreg Roach * @param int $y_axis_type 927c6266a77SGreg Roach * 928c6266a77SGreg Roach * @return string 929c6266a77SGreg Roach */ 930a81e5019SRico Sonntag private function myPlot( 931a81e5019SRico Sonntag string $chart_title, 932a81e5019SRico Sonntag array $x_axis, 933a81e5019SRico Sonntag string $x_axis_title, 934a81e5019SRico Sonntag array $ydata, 935a81e5019SRico Sonntag string $y_axis_title, 936a81e5019SRico Sonntag array $z_axis, 937a81e5019SRico Sonntag int $y_axis_type 938a81e5019SRico Sonntag ): string { 9396960d4a1SGreg Roach if (!count($ydata)) { 940a81e5019SRico Sonntag return I18N::translate('This information is not available.'); 941c6266a77SGreg Roach } 942c6266a77SGreg Roach 943c6266a77SGreg Roach // Colors for z-axis 944c6266a77SGreg Roach $colors = []; 945c6266a77SGreg Roach $index = 0; 9466960d4a1SGreg Roach while (count($colors) < count($ydata)) { 947c6266a77SGreg Roach $colors[] = self::Z_AXIS_COLORS[$index]; 9486960d4a1SGreg Roach $index = ($index + 1) % count(self::Z_AXIS_COLORS); 949c6266a77SGreg Roach } 950c6266a77SGreg Roach 951c6266a77SGreg Roach // Convert our sparse dataset into a fixed-size array 952c6266a77SGreg Roach $tmp = []; 953c6266a77SGreg Roach foreach (array_keys($z_axis) as $z) { 954c6266a77SGreg Roach foreach (array_keys($x_axis) as $x) { 955c6266a77SGreg Roach $tmp[$z][$x] = $ydata[$z][$x] ?? 0; 956c6266a77SGreg Roach } 957c6266a77SGreg Roach } 958c6266a77SGreg Roach $ydata = $tmp; 959c6266a77SGreg Roach 960a81e5019SRico Sonntag // Convert the chart data to percentage 961c6266a77SGreg Roach if ($y_axis_type === self::Y_AXIS_PERCENT) { 962c6266a77SGreg Roach // Normalise each (non-zero!) set of data to total 100% 9630b5fd0a6SGreg Roach array_walk($ydata, static function (array &$x) { 964c6266a77SGreg Roach $sum = array_sum($x); 965c6266a77SGreg Roach if ($sum > 0) { 9664c78e066SGreg Roach $x = array_map(static fn (float $y): float => $y * 100.0 / $sum, $x); 967c6266a77SGreg Roach } 968c6266a77SGreg Roach }); 969c6266a77SGreg Roach } 970c6266a77SGreg Roach 971a81e5019SRico Sonntag $data = [ 972a81e5019SRico Sonntag array_merge( 973a81e5019SRico Sonntag [I18N::translate('Century')], 974a81e5019SRico Sonntag array_values($z_axis) 97571378461SGreg Roach ), 976c6266a77SGreg Roach ]; 977c6266a77SGreg Roach 978a81e5019SRico Sonntag $intermediate = []; 979d85de00fSGreg Roach foreach ($ydata as $months) { 980a81e5019SRico Sonntag foreach ($months as $month => $value) { 981a81e5019SRico Sonntag $intermediate[$month][] = [ 982a81e5019SRico Sonntag 'v' => $value, 983d85de00fSGreg Roach 'f' => $y_axis_type === self::Y_AXIS_PERCENT ? sprintf('%.1f%%', $value) : $value, 984a81e5019SRico Sonntag ]; 985a81e5019SRico Sonntag } 986c6266a77SGreg Roach } 987c6266a77SGreg Roach 988a81e5019SRico Sonntag foreach ($intermediate as $key => $values) { 989a81e5019SRico Sonntag $data[] = array_merge( 990a81e5019SRico Sonntag [$x_axis[$key]], 991a81e5019SRico Sonntag $values 992a81e5019SRico Sonntag ); 993a81e5019SRico Sonntag } 994c6266a77SGreg Roach 995a81e5019SRico Sonntag $chart_options = [ 996a81e5019SRico Sonntag 'title' => '', 997a81e5019SRico Sonntag 'subtitle' => '', 998a81e5019SRico Sonntag 'height' => 400, 999a81e5019SRico Sonntag 'width' => '100%', 1000a81e5019SRico Sonntag 'legend' => [ 10016960d4a1SGreg Roach 'position' => count($z_axis) > 1 ? 'right' : 'none', 1002a81e5019SRico Sonntag 'alignment' => 'center', 1003a81e5019SRico Sonntag ], 1004a81e5019SRico Sonntag 'tooltip' => [ 1005a81e5019SRico Sonntag 'format' => '\'%\'', 1006a81e5019SRico Sonntag ], 1007a81e5019SRico Sonntag 'vAxis' => [ 100876d39c55SGreg Roach 'title' => $y_axis_title, 1009a81e5019SRico Sonntag ], 1010a81e5019SRico Sonntag 'hAxis' => [ 101176d39c55SGreg Roach 'title' => $x_axis_title, 1012a81e5019SRico Sonntag ], 1013a81e5019SRico Sonntag 'colors' => $colors, 1014a81e5019SRico Sonntag ]; 1015a81e5019SRico Sonntag 101690a2f718SGreg Roach return view('statistics/other/charts/custom', [ 1017a81e5019SRico Sonntag 'data' => $data, 1018a81e5019SRico Sonntag 'chart_options' => $chart_options, 1019a81e5019SRico Sonntag 'chart_title' => $chart_title, 102065cf5706SGreg Roach 'language' => I18N::languageTag(), 102190a2f718SGreg Roach ]); 1022c6266a77SGreg Roach } 1023168ff6f3Sric2016} 1024