1168ff6f3Sric2016<?php 23976b470SGreg Roach 3168ff6f3Sric2016/** 4168ff6f3Sric2016 * webtrees: online genealogy 5*d11be702SGreg 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; 269219296aSGreg Roachuse Fisharebest\Webtrees\Statistics; 27b55cbc6bSGreg Roachuse Fisharebest\Webtrees\Validator; 286ccdf4f0SGreg Roachuse Psr\Http\Message\ResponseInterface; 296ccdf4f0SGreg Roachuse Psr\Http\Message\ServerRequestInterface; 303976b470SGreg Roach 3157ab2231SGreg Roachuse function app; 326960d4a1SGreg Roachuse function array_key_exists; 336960d4a1SGreg Roachuse function array_keys; 346960d4a1SGreg Roachuse function array_map; 356960d4a1SGreg Roachuse function array_merge; 366960d4a1SGreg Roachuse function array_sum; 376960d4a1SGreg Roachuse function array_values; 386960d4a1SGreg Roachuse function array_walk; 395229eadeSGreg Roachuse function assert; 406960d4a1SGreg Roachuse function count; 416960d4a1SGreg Roachuse function explode; 426960d4a1SGreg Roachuse function in_array; 436960d4a1SGreg Roachuse function is_numeric; 446960d4a1SGreg Roachuse function sprintf; 45168ff6f3Sric2016 46168ff6f3Sric2016/** 47168ff6f3Sric2016 * Class StatisticsChartModule 48168ff6f3Sric2016 */ 4937eb8894SGreg Roachclass StatisticsChartModule extends AbstractModule implements ModuleChartInterface 50c1010edaSGreg Roach{ 5149a243cbSGreg Roach use ModuleChartTrait; 5249a243cbSGreg Roach 53c6266a77SGreg Roach public const X_AXIS_INDIVIDUAL_MAP = 1; 54c6266a77SGreg Roach public const X_AXIS_BIRTH_MAP = 2; 55c6266a77SGreg Roach public const X_AXIS_DEATH_MAP = 3; 56c6266a77SGreg Roach public const X_AXIS_MARRIAGE_MAP = 4; 57c6266a77SGreg Roach public const X_AXIS_BIRTH_MONTH = 11; 58c6266a77SGreg Roach public const X_AXIS_DEATH_MONTH = 12; 59c6266a77SGreg Roach public const X_AXIS_MARRIAGE_MONTH = 13; 60c6266a77SGreg Roach public const X_AXIS_FIRST_CHILD_MONTH = 14; 61c6266a77SGreg Roach public const X_AXIS_FIRST_MARRIAGE_MONTH = 15; 62c6266a77SGreg Roach public const X_AXIS_AGE_AT_DEATH = 18; 63c6266a77SGreg Roach public const X_AXIS_AGE_AT_MARRIAGE = 19; 64c6266a77SGreg Roach public const X_AXIS_AGE_AT_FIRST_MARRIAGE = 20; 65c6266a77SGreg Roach public const X_AXIS_NUMBER_OF_CHILDREN = 21; 66c6266a77SGreg Roach 67c6266a77SGreg Roach public const Y_AXIS_NUMBERS = 201; 68c6266a77SGreg Roach public const Y_AXIS_PERCENT = 202; 69c6266a77SGreg Roach 70c6266a77SGreg Roach public const Z_AXIS_ALL = 300; 71c6266a77SGreg Roach public const Z_AXIS_SEX = 301; 72c6266a77SGreg Roach public const Z_AXIS_TIME = 302; 73c6266a77SGreg Roach 74c6266a77SGreg Roach // First two colors are blue/pink, to work with Z_AXIS_SEX. 75c6266a77SGreg Roach private const Z_AXIS_COLORS = ['0000FF', 'FFA0CB', '9F00FF', 'FF7000', '905030', 'FF0000', '00FF00', 'F0F000']; 76c6266a77SGreg Roach 77c6266a77SGreg Roach private const DAYS_IN_YEAR = 365.25; 78c6266a77SGreg Roach 79168ff6f3Sric2016 /** 800cfd6963SGreg Roach * How should this module be identified in the control panel, etc.? 81168ff6f3Sric2016 * 82168ff6f3Sric2016 * @return string 83168ff6f3Sric2016 */ 8449a243cbSGreg Roach public function title(): string 85c1010edaSGreg Roach { 86bbb76c12SGreg Roach /* I18N: Name of a module/chart */ 87bbb76c12SGreg Roach return I18N::translate('Statistics'); 88168ff6f3Sric2016 } 89168ff6f3Sric2016 90168ff6f3Sric2016 /** 91168ff6f3Sric2016 * A sentence describing what this module does. 92168ff6f3Sric2016 * 93168ff6f3Sric2016 * @return string 94168ff6f3Sric2016 */ 9549a243cbSGreg Roach public function description(): string 96c1010edaSGreg Roach { 97bbb76c12SGreg Roach /* I18N: Description of the “StatisticsChart” module */ 98bbb76c12SGreg Roach return I18N::translate('Various statistics charts.'); 99168ff6f3Sric2016 } 100168ff6f3Sric2016 101168ff6f3Sric2016 /** 102377a2979SGreg Roach * CSS class for the URL. 103377a2979SGreg Roach * 104377a2979SGreg Roach * @return string 105377a2979SGreg Roach */ 106377a2979SGreg Roach public function chartMenuClass(): string 107377a2979SGreg Roach { 108377a2979SGreg Roach return 'menu-chart-statistics'; 109377a2979SGreg Roach } 110377a2979SGreg Roach 111377a2979SGreg Roach /** 112e6562982SGreg Roach * The URL for this chart. 113168ff6f3Sric2016 * 11460bc3e3fSGreg Roach * @param Individual $individual 11576d39c55SGreg Roach * @param array<bool|int|string|array<string>|null> $parameters 11660bc3e3fSGreg Roach * 117e6562982SGreg Roach * @return string 118168ff6f3Sric2016 */ 119e6562982SGreg Roach public function chartUrl(Individual $individual, array $parameters = []): string 120c1010edaSGreg Roach { 121c6266a77SGreg Roach return route('module', [ 122c6266a77SGreg Roach 'module' => $this->name(), 123c6266a77SGreg Roach 'action' => 'Chart', 124d72b284aSGreg Roach 'tree' => $individual->tree()->name(), 125e6562982SGreg Roach ] + $parameters); 126168ff6f3Sric2016 } 127c6266a77SGreg Roach 128c6266a77SGreg Roach /** 129c6266a77SGreg Roach * A form to request the chart parameters. 130c6266a77SGreg Roach * 13157ab2231SGreg Roach * @param ServerRequestInterface $request 132c6266a77SGreg Roach * 1336ccdf4f0SGreg Roach * @return ResponseInterface 134c6266a77SGreg Roach */ 13557ab2231SGreg Roach public function getChartAction(ServerRequestInterface $request): ResponseInterface 136c6266a77SGreg Roach { 137b55cbc6bSGreg Roach $tree = Validator::attributes($request)->tree(); 138b55cbc6bSGreg Roach $user = Validator::attributes($request)->user(); 13957ab2231SGreg Roach 140ef483801SGreg Roach Auth::checkComponentAccess($this, ModuleChartInterface::class, $tree, $user); 1419867b2f0SGreg Roach 142c6266a77SGreg Roach $tabs = [ 143c6266a77SGreg Roach I18N::translate('Individuals') => route('module', [ 144c6266a77SGreg Roach 'module' => $this->name(), 145c6266a77SGreg Roach 'action' => 'Individuals', 146d72b284aSGreg Roach 'tree' => $tree->name(), 147c6266a77SGreg Roach ]), 148c6266a77SGreg Roach I18N::translate('Families') => route('module', [ 149c6266a77SGreg Roach 'module' => $this->name(), 150c6266a77SGreg Roach 'action' => 'Families', 151d72b284aSGreg Roach 'tree' => $tree->name(), 152c6266a77SGreg Roach ]), 153c6266a77SGreg Roach I18N::translate('Other') => route('module', [ 154c6266a77SGreg Roach 'module' => $this->name(), 155c6266a77SGreg Roach 'action' => 'Other', 156d72b284aSGreg Roach 'tree' => $tree->name(), 157c6266a77SGreg Roach ]), 158c6266a77SGreg Roach I18N::translate('Custom') => route('module', [ 159c6266a77SGreg Roach 'module' => $this->name(), 160c6266a77SGreg Roach 'action' => 'Custom', 161d72b284aSGreg Roach 'tree' => $tree->name(), 162c6266a77SGreg Roach ]), 163c6266a77SGreg Roach ]; 164c6266a77SGreg Roach 1659b5537c3SGreg Roach return $this->viewResponse('modules/statistics-chart/page', [ 16671378461SGreg Roach 'module' => $this->name(), 167c6266a77SGreg Roach 'tabs' => $tabs, 168c6266a77SGreg Roach 'title' => $this->title(), 169ef5d23f1SGreg Roach 'tree' => $tree, 170c6266a77SGreg Roach ]); 171c6266a77SGreg Roach } 172c6266a77SGreg Roach 173c6266a77SGreg Roach /** 17457ab2231SGreg Roach * @param ServerRequestInterface $request 175c6266a77SGreg Roach * 1766ccdf4f0SGreg Roach * @return ResponseInterface 177c6266a77SGreg Roach */ 17849528f2bSGreg Roach public function getIndividualsAction(ServerRequestInterface $request): ResponseInterface 179c6266a77SGreg Roach { 180b6c326d8SGreg Roach $this->layout = 'layouts/ajax'; 181b6c326d8SGreg Roach 182b6c326d8SGreg Roach return $this->viewResponse('modules/statistics-chart/individuals', [ 183c6266a77SGreg Roach 'show_oldest_living' => Auth::check(), 184559d5fa0SGreg Roach 'statistics' => app(Statistics::class), 185c6266a77SGreg Roach ]); 186c6266a77SGreg Roach } 187c6266a77SGreg Roach 188c6266a77SGreg Roach /** 18957ab2231SGreg Roach * @param ServerRequestInterface $request 190c6266a77SGreg Roach * 1916ccdf4f0SGreg Roach * @return ResponseInterface 192c6266a77SGreg Roach */ 19349528f2bSGreg Roach public function getFamiliesAction(ServerRequestInterface $request): ResponseInterface 194c6266a77SGreg Roach { 195b6c326d8SGreg Roach $this->layout = 'layouts/ajax'; 196b6c326d8SGreg Roach 197b6c326d8SGreg Roach return $this->viewResponse('modules/statistics-chart/families', [ 198559d5fa0SGreg Roach 'statistics' => app(Statistics::class), 199c6266a77SGreg Roach ]); 200c6266a77SGreg Roach } 201c6266a77SGreg Roach 202c6266a77SGreg Roach /** 20357ab2231SGreg Roach * @param ServerRequestInterface $request 204c6266a77SGreg Roach * 2056ccdf4f0SGreg Roach * @return ResponseInterface 206c6266a77SGreg Roach */ 20749528f2bSGreg Roach public function getOtherAction(ServerRequestInterface $request): ResponseInterface 208c6266a77SGreg Roach { 209b6c326d8SGreg Roach $this->layout = 'layouts/ajax'; 210b6c326d8SGreg Roach 211b6c326d8SGreg Roach return $this->viewResponse('modules/statistics-chart/other', [ 212559d5fa0SGreg Roach 'statistics' => app(Statistics::class), 213c6266a77SGreg Roach ]); 214c6266a77SGreg Roach } 215c6266a77SGreg Roach 216c6266a77SGreg Roach /** 21757ab2231SGreg Roach * @param ServerRequestInterface $request 218c6266a77SGreg Roach * 2196ccdf4f0SGreg Roach * @return ResponseInterface 220c6266a77SGreg Roach */ 22157ab2231SGreg Roach public function getCustomAction(ServerRequestInterface $request): ResponseInterface 222c6266a77SGreg Roach { 223b6c326d8SGreg Roach $this->layout = 'layouts/ajax'; 224b6c326d8SGreg Roach 225b55cbc6bSGreg Roach $tree = Validator::attributes($request)->tree(); 22657ab2231SGreg Roach 227b6c326d8SGreg Roach return $this->viewResponse('modules/statistics-chart/custom', [ 228c6266a77SGreg Roach 'module' => $this, 229c6266a77SGreg Roach 'tree' => $tree, 230c6266a77SGreg Roach ]); 231c6266a77SGreg Roach } 232c6266a77SGreg Roach 233c6266a77SGreg Roach /** 2346ccdf4f0SGreg Roach * @param ServerRequestInterface $request 235c6266a77SGreg Roach * 2366ccdf4f0SGreg Roach * @return ResponseInterface 237c6266a77SGreg Roach */ 2383aa29b97SGreg Roach public function postCustomChartAction(ServerRequestInterface $request): ResponseInterface 239c6266a77SGreg Roach { 24057ab2231SGreg Roach $statistics = app(Statistics::class); 241dca2dd78SGreg Roach assert($statistics instanceof Statistics); 24257ab2231SGreg Roach 243748dbe15SGreg Roach $x_axis_type = Validator::parsedBody($request)->integer('x-as'); 244748dbe15SGreg Roach $y_axis_type = Validator::parsedBody($request)->integer('y-as'); 245748dbe15SGreg Roach $z_axis_type = Validator::parsedBody($request)->integer('z-as'); 246c6266a77SGreg Roach $ydata = []; 247c6266a77SGreg Roach 248c6266a77SGreg Roach switch ($x_axis_type) { 249c6266a77SGreg Roach case self::X_AXIS_INDIVIDUAL_MAP: 2506ccdf4f0SGreg Roach return response($statistics->chartDistribution( 251748dbe15SGreg Roach Validator::parsedBody($request)->string('chart_shows'), 252748dbe15SGreg Roach Validator::parsedBody($request)->string('chart_type'), 253748dbe15SGreg Roach Validator::parsedBody($request)->string('SURN') 254c6266a77SGreg Roach )); 255c6266a77SGreg Roach 256c6266a77SGreg Roach case self::X_AXIS_BIRTH_MAP: 2576ccdf4f0SGreg Roach return response($statistics->chartDistribution( 258748dbe15SGreg Roach Validator::parsedBody($request)->string('chart_shows'), 259c6266a77SGreg Roach 'birth_distribution_chart' 260c6266a77SGreg Roach )); 261c6266a77SGreg Roach 262c6266a77SGreg Roach case self::X_AXIS_DEATH_MAP: 2636ccdf4f0SGreg Roach return response($statistics->chartDistribution( 264748dbe15SGreg Roach Validator::parsedBody($request)->string('chart_shows'), 265c6266a77SGreg Roach 'death_distribution_chart' 266c6266a77SGreg Roach )); 267c6266a77SGreg Roach 268c6266a77SGreg Roach case self::X_AXIS_MARRIAGE_MAP: 2696ccdf4f0SGreg Roach return response($statistics->chartDistribution( 270748dbe15SGreg Roach Validator::parsedBody($request)->string('chart_shows'), 271c6266a77SGreg Roach 'marriage_distribution_chart' 272c6266a77SGreg Roach )); 273c6266a77SGreg Roach 274c6266a77SGreg Roach case self::X_AXIS_BIRTH_MONTH: 275c6266a77SGreg Roach $chart_title = I18N::translate('Month of birth'); 276c6266a77SGreg Roach $x_axis_title = I18N::translate('Month'); 277c6266a77SGreg Roach $x_axis = $this->axisMonths(); 278c6266a77SGreg Roach 279c6266a77SGreg Roach switch ($y_axis_type) { 280c6266a77SGreg Roach case self::Y_AXIS_NUMBERS: 281c6266a77SGreg Roach $y_axis_title = I18N::translate('Individuals'); 282c6266a77SGreg Roach break; 283c6266a77SGreg Roach case self::Y_AXIS_PERCENT: 284c6266a77SGreg Roach $y_axis_title = '%'; 285c6266a77SGreg Roach break; 286c6266a77SGreg Roach default: 287d501c45dSGreg Roach throw new HttpNotFoundException(); 288c6266a77SGreg Roach } 289c6266a77SGreg Roach 290c6266a77SGreg Roach switch ($z_axis_type) { 291c6266a77SGreg Roach case self::Z_AXIS_ALL: 292c6266a77SGreg Roach $z_axis = $this->axisAll(); 293cde1d378SGreg Roach $rows = $statistics->statsBirthQuery()->get(); 294c6266a77SGreg Roach foreach ($rows as $row) { 295c6266a77SGreg Roach $this->fillYData($row->d_month, 0, $row->total, $x_axis, $z_axis, $ydata); 296c6266a77SGreg Roach } 297c6266a77SGreg Roach break; 298c6266a77SGreg Roach case self::Z_AXIS_SEX: 299c6266a77SGreg Roach $z_axis = $this->axisSexes(); 300cde1d378SGreg Roach $rows = $statistics->statsBirthBySexQuery()->get(); 301c6266a77SGreg Roach foreach ($rows as $row) { 302c6266a77SGreg Roach $this->fillYData($row->d_month, $row->i_sex, $row->total, $x_axis, $z_axis, $ydata); 303c6266a77SGreg Roach } 304c6266a77SGreg Roach break; 305c6266a77SGreg Roach case self::Z_AXIS_TIME: 306748dbe15SGreg Roach $boundaries_csv = Validator::parsedBody($request)->string('z-axis-boundaries-periods'); 307c6266a77SGreg Roach $z_axis = $this->axisYears($boundaries_csv); 308c6266a77SGreg Roach $prev_boundary = 0; 309c6266a77SGreg Roach foreach (array_keys($z_axis) as $boundary) { 310cde1d378SGreg Roach $rows = $statistics->statsBirthQuery($prev_boundary, $boundary)->get(); 311c6266a77SGreg Roach foreach ($rows as $row) { 312c6266a77SGreg Roach $this->fillYData($row->d_month, $boundary, $row->total, $x_axis, $z_axis, $ydata); 313c6266a77SGreg Roach } 314c6266a77SGreg Roach $prev_boundary = $boundary + 1; 315c6266a77SGreg Roach } 316c6266a77SGreg Roach break; 317c6266a77SGreg Roach default: 318d501c45dSGreg Roach throw new HttpNotFoundException(); 319c6266a77SGreg Roach } 320c6266a77SGreg Roach 3216ccdf4f0SGreg Roach return response($this->myPlot($chart_title, $x_axis, $x_axis_title, $ydata, $y_axis_title, $z_axis, $y_axis_type)); 322c6266a77SGreg Roach 323c6266a77SGreg Roach case self::X_AXIS_DEATH_MONTH: 324c6266a77SGreg Roach $chart_title = I18N::translate('Month of death'); 325c6266a77SGreg Roach $x_axis_title = I18N::translate('Month'); 326c6266a77SGreg Roach $x_axis = $this->axisMonths(); 327c6266a77SGreg Roach 328c6266a77SGreg Roach switch ($y_axis_type) { 329c6266a77SGreg Roach case self::Y_AXIS_NUMBERS: 330c6266a77SGreg Roach $y_axis_title = I18N::translate('Individuals'); 331c6266a77SGreg Roach break; 332c6266a77SGreg Roach case self::Y_AXIS_PERCENT: 333c6266a77SGreg Roach $y_axis_title = '%'; 334c6266a77SGreg Roach break; 335c6266a77SGreg Roach default: 336d501c45dSGreg Roach throw new HttpNotFoundException(); 337c6266a77SGreg Roach } 338c6266a77SGreg Roach 339c6266a77SGreg Roach switch ($z_axis_type) { 340c6266a77SGreg Roach case self::Z_AXIS_ALL: 341c6266a77SGreg Roach $z_axis = $this->axisAll(); 342cde1d378SGreg Roach $rows = $statistics->statsDeathQuery()->get(); 343c6266a77SGreg Roach foreach ($rows as $row) { 344c6266a77SGreg Roach $this->fillYData($row->d_month, 0, $row->total, $x_axis, $z_axis, $ydata); 345c6266a77SGreg Roach } 346c6266a77SGreg Roach break; 347c6266a77SGreg Roach case self::Z_AXIS_SEX: 348c6266a77SGreg Roach $z_axis = $this->axisSexes(); 349cde1d378SGreg Roach $rows = $statistics->statsDeathBySexQuery()->get(); 350c6266a77SGreg Roach foreach ($rows as $row) { 351c6266a77SGreg Roach $this->fillYData($row->d_month, $row->i_sex, $row->total, $x_axis, $z_axis, $ydata); 352c6266a77SGreg Roach } 353c6266a77SGreg Roach break; 354c6266a77SGreg Roach case self::Z_AXIS_TIME: 355748dbe15SGreg Roach $boundaries_csv = Validator::parsedBody($request)->string('z-axis-boundaries-periods'); 356c6266a77SGreg Roach $z_axis = $this->axisYears($boundaries_csv); 357c6266a77SGreg Roach $prev_boundary = 0; 358c6266a77SGreg Roach foreach (array_keys($z_axis) as $boundary) { 359cde1d378SGreg Roach $rows = $statistics->statsDeathQuery($prev_boundary, $boundary)->get(); 360c6266a77SGreg Roach foreach ($rows as $row) { 361c6266a77SGreg Roach $this->fillYData($row->d_month, $boundary, $row->total, $x_axis, $z_axis, $ydata); 362c6266a77SGreg Roach } 363c6266a77SGreg Roach $prev_boundary = $boundary + 1; 364c6266a77SGreg Roach } 365c6266a77SGreg Roach break; 366c6266a77SGreg Roach default: 367d501c45dSGreg Roach throw new HttpNotFoundException(); 368c6266a77SGreg Roach } 369c6266a77SGreg Roach 3706ccdf4f0SGreg Roach return response($this->myPlot($chart_title, $x_axis, $x_axis_title, $ydata, $y_axis_title, $z_axis, $y_axis_type)); 371c6266a77SGreg Roach 372c6266a77SGreg Roach case self::X_AXIS_MARRIAGE_MONTH: 373c6266a77SGreg Roach $chart_title = I18N::translate('Month of marriage'); 374c6266a77SGreg Roach $x_axis_title = I18N::translate('Month'); 375c6266a77SGreg Roach $x_axis = $this->axisMonths(); 376c6266a77SGreg Roach 377c6266a77SGreg Roach switch ($y_axis_type) { 378c6266a77SGreg Roach case self::Y_AXIS_NUMBERS: 379c6266a77SGreg Roach $y_axis_title = I18N::translate('Families'); 380c6266a77SGreg Roach break; 381c6266a77SGreg Roach case self::Y_AXIS_PERCENT: 382c6266a77SGreg Roach $y_axis_title = '%'; 383c6266a77SGreg Roach break; 384c6266a77SGreg Roach default: 385d501c45dSGreg Roach throw new HttpNotFoundException(); 386c6266a77SGreg Roach } 387c6266a77SGreg Roach 388c6266a77SGreg Roach switch ($z_axis_type) { 389c6266a77SGreg Roach case self::Z_AXIS_ALL: 390c6266a77SGreg Roach $z_axis = $this->axisAll(); 391e6f3d5e2SGreg Roach $rows = $statistics->statsMarriageQuery()->get(); 392c6266a77SGreg Roach foreach ($rows as $row) { 393c6266a77SGreg Roach $this->fillYData($row->d_month, 0, $row->total, $x_axis, $z_axis, $ydata); 394c6266a77SGreg Roach } 395c6266a77SGreg Roach break; 396c6266a77SGreg Roach case self::Z_AXIS_TIME: 397748dbe15SGreg Roach $boundaries_csv = Validator::parsedBody($request)->string('z-axis-boundaries-periods'); 398c6266a77SGreg Roach $z_axis = $this->axisYears($boundaries_csv); 399c6266a77SGreg Roach $prev_boundary = 0; 400c6266a77SGreg Roach foreach (array_keys($z_axis) as $boundary) { 401e6f3d5e2SGreg Roach $rows = $statistics->statsMarriageQuery($prev_boundary, $boundary)->get(); 402c6266a77SGreg Roach foreach ($rows as $row) { 403c6266a77SGreg Roach $this->fillYData($row->d_month, $boundary, $row->total, $x_axis, $z_axis, $ydata); 404c6266a77SGreg Roach } 405c6266a77SGreg Roach $prev_boundary = $boundary + 1; 406c6266a77SGreg Roach } 407c6266a77SGreg Roach break; 408c6266a77SGreg Roach default: 409d501c45dSGreg Roach throw new HttpNotFoundException(); 410c6266a77SGreg Roach } 411c6266a77SGreg Roach 4126ccdf4f0SGreg Roach return response($this->myPlot($chart_title, $x_axis, $x_axis_title, $ydata, $y_axis_title, $z_axis, $y_axis_type)); 413c6266a77SGreg Roach 414c6266a77SGreg Roach case self::X_AXIS_FIRST_CHILD_MONTH: 415c6266a77SGreg Roach $chart_title = I18N::translate('Month of birth of first child in a relation'); 416c6266a77SGreg Roach $x_axis_title = I18N::translate('Month'); 417c6266a77SGreg Roach $x_axis = $this->axisMonths(); 418c6266a77SGreg Roach 419c6266a77SGreg Roach switch ($y_axis_type) { 420c6266a77SGreg Roach case self::Y_AXIS_NUMBERS: 421c6266a77SGreg Roach $y_axis_title = I18N::translate('Children'); 422c6266a77SGreg Roach break; 423c6266a77SGreg Roach case self::Y_AXIS_PERCENT: 424c6266a77SGreg Roach $y_axis_title = '%'; 425c6266a77SGreg Roach break; 426c6266a77SGreg Roach default: 427d501c45dSGreg Roach throw new HttpNotFoundException(); 428c6266a77SGreg Roach } 429c6266a77SGreg Roach 430c6266a77SGreg Roach switch ($z_axis_type) { 431c6266a77SGreg Roach case self::Z_AXIS_ALL: 432c6266a77SGreg Roach $z_axis = $this->axisAll(); 433999da590SGreg Roach $rows = $statistics->monthFirstChildQuery()->get(); 434c6266a77SGreg Roach foreach ($rows as $row) { 435c6266a77SGreg Roach $this->fillYData($row->d_month, 0, $row->total, $x_axis, $z_axis, $ydata); 436c6266a77SGreg Roach } 437c6266a77SGreg Roach break; 438c6266a77SGreg Roach case self::Z_AXIS_SEX: 439c6266a77SGreg Roach $z_axis = $this->axisSexes(); 440999da590SGreg Roach $rows = $statistics->monthFirstChildBySexQuery()->get(); 441c6266a77SGreg Roach foreach ($rows as $row) { 442c6266a77SGreg Roach $this->fillYData($row->d_month, $row->i_sex, $row->total, $x_axis, $z_axis, $ydata); 443c6266a77SGreg Roach } 444c6266a77SGreg Roach break; 445c6266a77SGreg Roach case self::Z_AXIS_TIME: 446748dbe15SGreg Roach $boundaries_csv = Validator::parsedBody($request)->string('z-axis-boundaries-periods'); 447c6266a77SGreg Roach $z_axis = $this->axisYears($boundaries_csv); 448c6266a77SGreg Roach $prev_boundary = 0; 449c6266a77SGreg Roach foreach (array_keys($z_axis) as $boundary) { 450999da590SGreg Roach $rows = $statistics->monthFirstChildQuery($prev_boundary, $boundary)->get(); 451c6266a77SGreg Roach foreach ($rows as $row) { 452c6266a77SGreg Roach $this->fillYData($row->d_month, $boundary, $row->total, $x_axis, $z_axis, $ydata); 453c6266a77SGreg Roach } 454c6266a77SGreg Roach $prev_boundary = $boundary + 1; 455c6266a77SGreg Roach } 456c6266a77SGreg Roach break; 457c6266a77SGreg Roach default: 458d501c45dSGreg Roach throw new HttpNotFoundException(); 459c6266a77SGreg Roach } 460c6266a77SGreg Roach 4616ccdf4f0SGreg Roach return response($this->myPlot($chart_title, $x_axis, $x_axis_title, $ydata, $y_axis_title, $z_axis, $y_axis_type)); 462c6266a77SGreg Roach 463c6266a77SGreg Roach case self::X_AXIS_FIRST_MARRIAGE_MONTH: 464c6266a77SGreg Roach $chart_title = I18N::translate('Month of first marriage'); 465c6266a77SGreg Roach $x_axis_title = I18N::translate('Month'); 466c6266a77SGreg Roach $x_axis = $this->axisMonths(); 467c6266a77SGreg Roach 468c6266a77SGreg Roach switch ($y_axis_type) { 469c6266a77SGreg Roach case self::Y_AXIS_NUMBERS: 470c6266a77SGreg Roach $y_axis_title = I18N::translate('Families'); 471c6266a77SGreg Roach break; 472c6266a77SGreg Roach case self::Y_AXIS_PERCENT: 473c6266a77SGreg Roach $y_axis_title = '%'; 474c6266a77SGreg Roach break; 475c6266a77SGreg Roach default: 476d501c45dSGreg Roach throw new HttpNotFoundException(); 477c6266a77SGreg Roach } 478c6266a77SGreg Roach 479c6266a77SGreg Roach switch ($z_axis_type) { 480c6266a77SGreg Roach case self::Z_AXIS_ALL: 481c6266a77SGreg Roach $z_axis = $this->axisAll(); 482e6f3d5e2SGreg Roach $rows = $statistics->statsFirstMarriageQuery()->get(); 483c6266a77SGreg Roach $indi = []; 484c6266a77SGreg Roach foreach ($rows as $row) { 485dca2dd78SGreg Roach if (!in_array($row->f_husb, $indi, true) && !in_array($row->f_wife, $indi, true)) { 486c6266a77SGreg Roach $this->fillYData($row->month, 0, 1, $x_axis, $z_axis, $ydata); 487c6266a77SGreg Roach } 488dca2dd78SGreg Roach $indi[] = $row->f_husb; 489dca2dd78SGreg Roach $indi[] = $row->f_wife; 490c6266a77SGreg Roach } 491c6266a77SGreg Roach break; 492c6266a77SGreg Roach case self::Z_AXIS_TIME: 493748dbe15SGreg Roach $boundaries_csv = Validator::parsedBody($request)->string('z-axis-boundaries-periods'); 494c6266a77SGreg Roach $z_axis = $this->axisYears($boundaries_csv); 495c6266a77SGreg Roach $prev_boundary = 0; 496c6266a77SGreg Roach $indi = []; 497c6266a77SGreg Roach foreach (array_keys($z_axis) as $boundary) { 498e6f3d5e2SGreg Roach $rows = $statistics->statsFirstMarriageQuery($prev_boundary, $boundary)->get(); 499c6266a77SGreg Roach foreach ($rows as $row) { 500dca2dd78SGreg Roach if (!in_array($row->f_husb, $indi, true) && !in_array($row->f_wife, $indi, true)) { 501c6266a77SGreg Roach $this->fillYData($row->month, $boundary, 1, $x_axis, $z_axis, $ydata); 502c6266a77SGreg Roach } 503dca2dd78SGreg Roach $indi[] = $row->f_husb; 504dca2dd78SGreg Roach $indi[] = $row->f_wife; 505c6266a77SGreg Roach } 506c6266a77SGreg Roach $prev_boundary = $boundary + 1; 507c6266a77SGreg Roach } 508c6266a77SGreg Roach break; 509c6266a77SGreg Roach default: 510d501c45dSGreg Roach throw new HttpNotFoundException(); 511c6266a77SGreg Roach } 512c6266a77SGreg Roach 5136ccdf4f0SGreg Roach return response($this->myPlot($chart_title, $x_axis, $x_axis_title, $ydata, $y_axis_title, $z_axis, $y_axis_type)); 514c6266a77SGreg Roach 515c6266a77SGreg Roach case self::X_AXIS_AGE_AT_DEATH: 516c6266a77SGreg Roach $chart_title = I18N::translate('Average age at death'); 517c6266a77SGreg Roach $x_axis_title = I18N::translate('age'); 518748dbe15SGreg Roach $boundaries_csv = Validator::parsedBody($request)->string('x-axis-boundaries-ages'); 519c6266a77SGreg Roach $x_axis = $this->axisNumbers($boundaries_csv); 520c6266a77SGreg Roach 521c6266a77SGreg Roach switch ($y_axis_type) { 522c6266a77SGreg Roach case self::Y_AXIS_NUMBERS: 523c6266a77SGreg Roach $y_axis_title = I18N::translate('Individuals'); 524c6266a77SGreg Roach break; 525c6266a77SGreg Roach case self::Y_AXIS_PERCENT: 526c6266a77SGreg Roach $y_axis_title = '%'; 527c6266a77SGreg Roach break; 528c6266a77SGreg Roach default: 529d501c45dSGreg Roach throw new HttpNotFoundException(); 530c6266a77SGreg Roach } 531c6266a77SGreg Roach 532c6266a77SGreg Roach switch ($z_axis_type) { 533c6266a77SGreg Roach case self::Z_AXIS_ALL: 534c6266a77SGreg Roach $z_axis = $this->axisAll(); 5359219296aSGreg Roach $rows = $statistics->statsAgeQuery('DEAT'); 536c6266a77SGreg Roach foreach ($rows as $row) { 537c6266a77SGreg Roach foreach ($row as $age) { 538c6266a77SGreg Roach $years = (int) ($age / self::DAYS_IN_YEAR); 539c6266a77SGreg Roach $this->fillYData($years, 0, 1, $x_axis, $z_axis, $ydata); 540c6266a77SGreg Roach } 541c6266a77SGreg Roach } 542c6266a77SGreg Roach break; 543c6266a77SGreg Roach case self::Z_AXIS_SEX: 544c6266a77SGreg Roach $z_axis = $this->axisSexes(); 545c6266a77SGreg Roach foreach (array_keys($z_axis) as $sex) { 5469219296aSGreg Roach $rows = $statistics->statsAgeQuery('DEAT', $sex); 547c6266a77SGreg Roach foreach ($rows as $row) { 548c6266a77SGreg Roach foreach ($row as $age) { 549c6266a77SGreg Roach $years = (int) ($age / self::DAYS_IN_YEAR); 550c6266a77SGreg Roach $this->fillYData($years, $sex, 1, $x_axis, $z_axis, $ydata); 551c6266a77SGreg Roach } 552c6266a77SGreg Roach } 553c6266a77SGreg Roach } 554c6266a77SGreg Roach break; 555c6266a77SGreg Roach case self::Z_AXIS_TIME: 556748dbe15SGreg Roach $boundaries_csv = Validator::parsedBody($request)->string('z-axis-boundaries-periods'); 557c6266a77SGreg Roach $z_axis = $this->axisYears($boundaries_csv); 558c6266a77SGreg Roach $prev_boundary = 0; 559c6266a77SGreg Roach foreach (array_keys($z_axis) as $boundary) { 5609219296aSGreg Roach $rows = $statistics->statsAgeQuery('DEAT', 'BOTH', $prev_boundary, $boundary); 561c6266a77SGreg Roach foreach ($rows as $row) { 562c6266a77SGreg Roach foreach ($row as $age) { 563c6266a77SGreg Roach $years = (int) ($age / self::DAYS_IN_YEAR); 564c6266a77SGreg Roach $this->fillYData($years, $boundary, 1, $x_axis, $z_axis, $ydata); 565c6266a77SGreg Roach } 566c6266a77SGreg Roach } 567c6266a77SGreg Roach $prev_boundary = $boundary + 1; 568c6266a77SGreg Roach } 569c6266a77SGreg Roach 570c6266a77SGreg Roach break; 571c6266a77SGreg Roach default: 572d501c45dSGreg Roach throw new HttpNotFoundException(); 573c6266a77SGreg Roach } 574c6266a77SGreg Roach 5756ccdf4f0SGreg Roach return response($this->myPlot($chart_title, $x_axis, $x_axis_title, $ydata, $y_axis_title, $z_axis, $y_axis_type)); 576c6266a77SGreg Roach 577c6266a77SGreg Roach case self::X_AXIS_AGE_AT_MARRIAGE: 578c6266a77SGreg Roach $chart_title = I18N::translate('Age in year of marriage'); 579c6266a77SGreg Roach $x_axis_title = I18N::translate('age'); 580748dbe15SGreg Roach $boundaries_csv = Validator::parsedBody($request)->string('x-axis-boundaries-ages_m'); 581c6266a77SGreg Roach $x_axis = $this->axisNumbers($boundaries_csv); 582c6266a77SGreg Roach 583c6266a77SGreg Roach switch ($y_axis_type) { 584c6266a77SGreg Roach case self::Y_AXIS_NUMBERS: 585c6266a77SGreg Roach $y_axis_title = I18N::translate('Individuals'); 586c6266a77SGreg Roach break; 587c6266a77SGreg Roach case self::Y_AXIS_PERCENT: 588c6266a77SGreg Roach $y_axis_title = '%'; 589c6266a77SGreg Roach break; 590c6266a77SGreg Roach default: 591d501c45dSGreg Roach throw new HttpNotFoundException(); 592c6266a77SGreg Roach } 593c6266a77SGreg Roach 594c6266a77SGreg Roach switch ($z_axis_type) { 595c6266a77SGreg Roach case self::Z_AXIS_ALL: 596c6266a77SGreg Roach $z_axis = $this->axisAll(); 597afa8d404SGreg Roach // The stats query doesn't have an "all" function, so query M/F separately 598afa8d404SGreg Roach foreach (['M', 'F'] as $sex) { 5999219296aSGreg Roach $rows = $statistics->statsMarrAgeQuery($sex); 600c6266a77SGreg Roach foreach ($rows as $row) { 601c6266a77SGreg Roach $years = (int) ($row->age / self::DAYS_IN_YEAR); 602c6266a77SGreg Roach $this->fillYData($years, 0, 1, $x_axis, $z_axis, $ydata); 603c6266a77SGreg Roach } 604c6266a77SGreg Roach } 605c6266a77SGreg Roach break; 606c6266a77SGreg Roach case self::Z_AXIS_SEX: 607c6266a77SGreg Roach $z_axis = $this->axisSexes(); 608c6266a77SGreg Roach foreach (array_keys($z_axis) as $sex) { 6099219296aSGreg Roach $rows = $statistics->statsMarrAgeQuery($sex); 610c6266a77SGreg Roach foreach ($rows as $row) { 611c6266a77SGreg Roach $years = (int) ($row->age / self::DAYS_IN_YEAR); 612c6266a77SGreg Roach $this->fillYData($years, $sex, 1, $x_axis, $z_axis, $ydata); 613c6266a77SGreg Roach } 614c6266a77SGreg Roach } 615c6266a77SGreg Roach break; 616c6266a77SGreg Roach case self::Z_AXIS_TIME: 617748dbe15SGreg Roach $boundaries_csv = Validator::parsedBody($request)->string('z-axis-boundaries-periods'); 618c6266a77SGreg Roach $z_axis = $this->axisYears($boundaries_csv); 619afa8d404SGreg Roach // The stats query doesn't have an "all" function, so query M/F separately 620afa8d404SGreg Roach foreach (['M', 'F'] as $sex) { 621c6266a77SGreg Roach $prev_boundary = 0; 622c6266a77SGreg Roach foreach (array_keys($z_axis) as $boundary) { 6239219296aSGreg Roach $rows = $statistics->statsMarrAgeQuery($sex, $prev_boundary, $boundary); 624c6266a77SGreg Roach foreach ($rows as $row) { 625c6266a77SGreg Roach $years = (int) ($row->age / self::DAYS_IN_YEAR); 626c6266a77SGreg Roach $this->fillYData($years, $boundary, 1, $x_axis, $z_axis, $ydata); 627c6266a77SGreg Roach } 628c6266a77SGreg Roach $prev_boundary = $boundary + 1; 629c6266a77SGreg Roach } 630c6266a77SGreg Roach } 631c6266a77SGreg Roach break; 632c6266a77SGreg Roach default: 633d501c45dSGreg Roach throw new HttpNotFoundException(); 634c6266a77SGreg Roach } 635c6266a77SGreg Roach 6366ccdf4f0SGreg Roach return response($this->myPlot($chart_title, $x_axis, $x_axis_title, $ydata, $y_axis_title, $z_axis, $y_axis_type)); 637c6266a77SGreg Roach 638c6266a77SGreg Roach case self::X_AXIS_AGE_AT_FIRST_MARRIAGE: 639c6266a77SGreg Roach $chart_title = I18N::translate('Age in year of first marriage'); 640c6266a77SGreg Roach $x_axis_title = I18N::translate('age'); 641748dbe15SGreg Roach $boundaries_csv = Validator::parsedBody($request)->string('x-axis-boundaries-ages_m'); 642c6266a77SGreg Roach $x_axis = $this->axisNumbers($boundaries_csv); 643c6266a77SGreg Roach 644c6266a77SGreg Roach switch ($y_axis_type) { 645c6266a77SGreg Roach case self::Y_AXIS_NUMBERS: 646c6266a77SGreg Roach $y_axis_title = I18N::translate('Individuals'); 647c6266a77SGreg Roach break; 648c6266a77SGreg Roach case self::Y_AXIS_PERCENT: 649c6266a77SGreg Roach $y_axis_title = '%'; 650c6266a77SGreg Roach break; 651c6266a77SGreg Roach default: 652d501c45dSGreg Roach throw new HttpNotFoundException(); 653c6266a77SGreg Roach } 654c6266a77SGreg Roach 655c6266a77SGreg Roach switch ($z_axis_type) { 656c6266a77SGreg Roach case self::Z_AXIS_ALL: 657c6266a77SGreg Roach $z_axis = $this->axisAll(); 658afa8d404SGreg Roach // The stats query doesn't have an "all" function, so query M/F separately 659afa8d404SGreg Roach foreach (['M', 'F'] as $sex) { 6609219296aSGreg Roach $rows = $statistics->statsMarrAgeQuery($sex); 661c6266a77SGreg Roach $indi = []; 662c6266a77SGreg Roach foreach ($rows as $row) { 6636960d4a1SGreg Roach if (!in_array($row->d_gid, $indi, true)) { 664c6266a77SGreg Roach $years = (int) ($row->age / self::DAYS_IN_YEAR); 665c6266a77SGreg Roach $this->fillYData($years, 0, 1, $x_axis, $z_axis, $ydata); 666c6266a77SGreg Roach $indi[] = $row->d_gid; 667c6266a77SGreg Roach } 668c6266a77SGreg Roach } 669c6266a77SGreg Roach } 670c6266a77SGreg Roach break; 671c6266a77SGreg Roach case self::Z_AXIS_SEX: 672c6266a77SGreg Roach $z_axis = $this->axisSexes(); 673c6266a77SGreg Roach foreach (array_keys($z_axis) as $sex) { 6749219296aSGreg Roach $rows = $statistics->statsMarrAgeQuery($sex); 675c6266a77SGreg Roach $indi = []; 676c6266a77SGreg Roach foreach ($rows as $row) { 6776960d4a1SGreg Roach if (!in_array($row->d_gid, $indi, true)) { 678c6266a77SGreg Roach $years = (int) ($row->age / self::DAYS_IN_YEAR); 679c6266a77SGreg Roach $this->fillYData($years, $sex, 1, $x_axis, $z_axis, $ydata); 680c6266a77SGreg Roach $indi[] = $row->d_gid; 681c6266a77SGreg Roach } 682c6266a77SGreg Roach } 683c6266a77SGreg Roach } 684c6266a77SGreg Roach break; 685c6266a77SGreg Roach case self::Z_AXIS_TIME: 686748dbe15SGreg Roach $boundaries_csv = Validator::parsedBody($request)->string('z-axis-boundaries-periods'); 687c6266a77SGreg Roach $z_axis = $this->axisYears($boundaries_csv); 688afa8d404SGreg Roach // The stats query doesn't have an "all" function, so query M/F separately 689afa8d404SGreg Roach foreach (['M', 'F'] as $sex) { 690c6266a77SGreg Roach $prev_boundary = 0; 691c6266a77SGreg Roach $indi = []; 692c6266a77SGreg Roach foreach (array_keys($z_axis) as $boundary) { 6939219296aSGreg Roach $rows = $statistics->statsMarrAgeQuery($sex, $prev_boundary, $boundary); 694c6266a77SGreg Roach foreach ($rows as $row) { 6956960d4a1SGreg Roach if (!in_array($row->d_gid, $indi, true)) { 696c6266a77SGreg Roach $years = (int) ($row->age / self::DAYS_IN_YEAR); 697c6266a77SGreg Roach $this->fillYData($years, $boundary, 1, $x_axis, $z_axis, $ydata); 698c6266a77SGreg Roach $indi[] = $row->d_gid; 699c6266a77SGreg Roach } 700c6266a77SGreg Roach } 701c6266a77SGreg Roach $prev_boundary = $boundary + 1; 702c6266a77SGreg Roach } 703c6266a77SGreg Roach } 704c6266a77SGreg Roach break; 705c6266a77SGreg Roach default: 706d501c45dSGreg Roach throw new HttpNotFoundException(); 707c6266a77SGreg Roach } 708c6266a77SGreg Roach 7096ccdf4f0SGreg Roach return response($this->myPlot($chart_title, $x_axis, $x_axis_title, $ydata, $y_axis_title, $z_axis, $y_axis_type)); 710c6266a77SGreg Roach 711c6266a77SGreg Roach case self::X_AXIS_NUMBER_OF_CHILDREN: 712c6266a77SGreg Roach $chart_title = I18N::translate('Number of children'); 713c6266a77SGreg Roach $x_axis_title = I18N::translate('Children'); 7146960d4a1SGreg Roach $x_axis = $this->axisNumbers('0,1,2,3,4,5,6,7,8,9,10'); 715c6266a77SGreg Roach 716c6266a77SGreg Roach switch ($y_axis_type) { 717c6266a77SGreg Roach case self::Y_AXIS_NUMBERS: 718c6266a77SGreg Roach $y_axis_title = I18N::translate('Families'); 719c6266a77SGreg Roach break; 720c6266a77SGreg Roach case self::Y_AXIS_PERCENT: 721c6266a77SGreg Roach $y_axis_title = '%'; 722c6266a77SGreg Roach break; 723c6266a77SGreg Roach default: 724d501c45dSGreg Roach throw new HttpNotFoundException(); 725c6266a77SGreg Roach } 726c6266a77SGreg Roach 727c6266a77SGreg Roach switch ($z_axis_type) { 728c6266a77SGreg Roach case self::Z_AXIS_ALL: 729c6266a77SGreg Roach $z_axis = $this->axisAll(); 7309219296aSGreg Roach $rows = $statistics->statsChildrenQuery(); 731c6266a77SGreg Roach foreach ($rows as $row) { 732c6266a77SGreg Roach $this->fillYData($row->f_numchil, 0, $row->total, $x_axis, $z_axis, $ydata); 733c6266a77SGreg Roach } 734c6266a77SGreg Roach break; 735c6266a77SGreg Roach case self::Z_AXIS_TIME: 736748dbe15SGreg Roach $boundaries_csv = Validator::parsedBody($request)->string('z-axis-boundaries-periods'); 737c6266a77SGreg Roach $z_axis = $this->axisYears($boundaries_csv); 738c6266a77SGreg Roach $prev_boundary = 0; 739c6266a77SGreg Roach foreach (array_keys($z_axis) as $boundary) { 740b1126ab4SGreg Roach $rows = $statistics->statsChildrenQuery($prev_boundary, $boundary); 741c6266a77SGreg Roach foreach ($rows as $row) { 742c6266a77SGreg Roach $this->fillYData($row->f_numchil, $boundary, $row->total, $x_axis, $z_axis, $ydata); 743c6266a77SGreg Roach } 744c6266a77SGreg Roach $prev_boundary = $boundary + 1; 745c6266a77SGreg Roach } 746c6266a77SGreg Roach break; 747c6266a77SGreg Roach default: 748d501c45dSGreg Roach throw new HttpNotFoundException(); 749c6266a77SGreg Roach } 750c6266a77SGreg Roach 7516ccdf4f0SGreg Roach return response($this->myPlot($chart_title, $x_axis, $x_axis_title, $ydata, $y_axis_title, $z_axis, $y_axis_type)); 752c6266a77SGreg Roach 753c6266a77SGreg Roach default: 754d501c45dSGreg Roach throw new HttpNotFoundException(); 755c6266a77SGreg Roach } 756c6266a77SGreg Roach } 757c6266a77SGreg Roach 758c6266a77SGreg Roach /** 75924f2a3afSGreg Roach * @return array<string> 760c6266a77SGreg Roach */ 761c6266a77SGreg Roach private function axisAll(): array 762c6266a77SGreg Roach { 763c6266a77SGreg Roach return [ 764c6266a77SGreg Roach I18N::translate('Total'), 765c6266a77SGreg Roach ]; 766c6266a77SGreg Roach } 767c6266a77SGreg Roach 768c6266a77SGreg Roach /** 76924f2a3afSGreg Roach * @return array<string> 770c6266a77SGreg Roach */ 771c6266a77SGreg Roach private function axisSexes(): array 772c6266a77SGreg Roach { 773c6266a77SGreg Roach return [ 774c6266a77SGreg Roach 'M' => I18N::translate('Male'), 775c6266a77SGreg Roach 'F' => I18N::translate('Female'), 776c6266a77SGreg Roach ]; 777c6266a77SGreg Roach } 778c6266a77SGreg Roach 779c6266a77SGreg Roach /** 780c6266a77SGreg Roach * Labels for the X axis 781c6266a77SGreg Roach * 78224f2a3afSGreg Roach * @return array<string> 783c6266a77SGreg Roach */ 784c6266a77SGreg Roach private function axisMonths(): array 785c6266a77SGreg Roach { 786c6266a77SGreg Roach return [ 787c6266a77SGreg Roach 'JAN' => I18N::translateContext('NOMINATIVE', 'January'), 788c6266a77SGreg Roach 'FEB' => I18N::translateContext('NOMINATIVE', 'February'), 789c6266a77SGreg Roach 'MAR' => I18N::translateContext('NOMINATIVE', 'March'), 790c6266a77SGreg Roach 'APR' => I18N::translateContext('NOMINATIVE', 'April'), 791c6266a77SGreg Roach 'MAY' => I18N::translateContext('NOMINATIVE', 'May'), 792c6266a77SGreg Roach 'JUN' => I18N::translateContext('NOMINATIVE', 'June'), 793c6266a77SGreg Roach 'JUL' => I18N::translateContext('NOMINATIVE', 'July'), 794c6266a77SGreg Roach 'AUG' => I18N::translateContext('NOMINATIVE', 'August'), 795c6266a77SGreg Roach 'SEP' => I18N::translateContext('NOMINATIVE', 'September'), 796c6266a77SGreg Roach 'OCT' => I18N::translateContext('NOMINATIVE', 'October'), 797c6266a77SGreg Roach 'NOV' => I18N::translateContext('NOMINATIVE', 'November'), 798c6266a77SGreg Roach 'DEC' => I18N::translateContext('NOMINATIVE', 'December'), 799c6266a77SGreg Roach ]; 800c6266a77SGreg Roach } 801c6266a77SGreg Roach 802c6266a77SGreg Roach /** 803c6266a77SGreg Roach * Convert a list of N year-boundaries into N+1 year-ranges for the z-axis. 804c6266a77SGreg Roach * 805c6266a77SGreg Roach * @param string $boundaries_csv 806c6266a77SGreg Roach * 80724f2a3afSGreg Roach * @return array<string> 808c6266a77SGreg Roach */ 809c6266a77SGreg Roach private function axisYears(string $boundaries_csv): array 810c6266a77SGreg Roach { 811c6266a77SGreg Roach $boundaries = explode(',', $boundaries_csv); 812c6266a77SGreg Roach 813c6266a77SGreg Roach $axis = []; 814c6266a77SGreg Roach foreach ($boundaries as $n => $boundary) { 815c6266a77SGreg Roach if ($n === 0) { 81667ff9eb7SGreg Roach $axis[$boundary - 1] = '–' . I18N::digits($boundary); 817c6266a77SGreg Roach } else { 81867ff9eb7SGreg Roach $axis[$boundary - 1] = I18N::digits($boundaries[$n - 1]) . '–' . I18N::digits($boundary); 819c6266a77SGreg Roach } 820c6266a77SGreg Roach } 821c6266a77SGreg Roach 82267ff9eb7SGreg Roach $axis[PHP_INT_MAX] = I18N::digits($boundaries[count($boundaries) - 1]) . '–'; 823c6266a77SGreg Roach 824c6266a77SGreg Roach return $axis; 825c6266a77SGreg Roach } 826c6266a77SGreg Roach 827c6266a77SGreg Roach /** 828c6266a77SGreg Roach * Create the X axis. 829c6266a77SGreg Roach * 830c6266a77SGreg Roach * @param string $boundaries_csv 831c6266a77SGreg Roach * 832fc26b4f6SGreg Roach * @return array<string> 833c6266a77SGreg Roach */ 834c6266a77SGreg Roach private function axisNumbers(string $boundaries_csv): array 835c6266a77SGreg Roach { 836c6266a77SGreg Roach $boundaries = explode(',', $boundaries_csv); 837c6266a77SGreg Roach 8380b5fd0a6SGreg Roach $boundaries = array_map(static function (string $x): int { 839c6266a77SGreg Roach return (int) $x; 840c6266a77SGreg Roach }, $boundaries); 841c6266a77SGreg Roach 842c6266a77SGreg Roach $axis = []; 843c6266a77SGreg Roach foreach ($boundaries as $n => $boundary) { 844c6266a77SGreg Roach if ($n === 0) { 8456960d4a1SGreg Roach $prev_boundary = 0; 8466960d4a1SGreg Roach } else { 8476960d4a1SGreg Roach $prev_boundary = $boundaries[$n - 1] + 1; 8486960d4a1SGreg Roach } 8496960d4a1SGreg Roach 8506960d4a1SGreg Roach if ($prev_boundary === $boundary) { 851c6266a77SGreg Roach /* I18N: A range of numbers */ 8526960d4a1SGreg Roach $axis[$boundary] = I18N::number($boundary); 853c6266a77SGreg Roach } else { 854c6266a77SGreg Roach /* I18N: A range of numbers */ 8556960d4a1SGreg Roach $axis[$boundary] = I18N::translate('%1$s–%2$s', I18N::number($prev_boundary), I18N::number($boundary)); 856c6266a77SGreg Roach } 857c6266a77SGreg Roach } 858c6266a77SGreg Roach 859c6266a77SGreg Roach /* I18N: Label on a graph; 40+ means 40 or more */ 860c6266a77SGreg Roach $axis[PHP_INT_MAX] = I18N::translate('%s+', I18N::number($boundaries[count($boundaries) - 1])); 861c6266a77SGreg Roach 862c6266a77SGreg Roach return $axis; 863c6266a77SGreg Roach } 864c6266a77SGreg Roach 865c6266a77SGreg Roach /** 866c6266a77SGreg Roach * Calculate the Y axis. 867c6266a77SGreg Roach * 868c6266a77SGreg Roach * @param int|string $x 869c6266a77SGreg Roach * @param int|string $z 870c6266a77SGreg Roach * @param int|string $value 87176d39c55SGreg Roach * @param array<string> $x_axis 87276d39c55SGreg Roach * @param array<string> $z_axis 87309482a55SGreg Roach * @param array<array<int>> $ydata 874c6266a77SGreg Roach * 875c6266a77SGreg Roach * @return void 876c6266a77SGreg Roach */ 877e364afe4SGreg Roach private function fillYData($x, $z, $value, array $x_axis, array $z_axis, array &$ydata): void 878c6266a77SGreg Roach { 879c6266a77SGreg Roach $x = $this->findAxisEntry($x, $x_axis); 880c6266a77SGreg Roach $z = $this->findAxisEntry($z, $z_axis); 881c6266a77SGreg Roach 8826960d4a1SGreg Roach if (!array_key_exists($z, $z_axis)) { 883c6266a77SGreg Roach foreach (array_keys($z_axis) as $key) { 884c6266a77SGreg Roach if ($value <= $key) { 885c6266a77SGreg Roach $z = $key; 886c6266a77SGreg Roach break; 887c6266a77SGreg Roach } 888c6266a77SGreg Roach } 889c6266a77SGreg Roach } 890c6266a77SGreg Roach 891c6266a77SGreg Roach // Add the value to the appropriate data point. 892c6266a77SGreg Roach $ydata[$z][$x] = ($ydata[$z][$x] ?? 0) + $value; 893c6266a77SGreg Roach } 894c6266a77SGreg Roach 895c6266a77SGreg Roach /** 896c6266a77SGreg Roach * Find the axis entry for a given value. 897c6266a77SGreg Roach * Some are direct lookup (e.g. M/F, JAN/FEB/MAR). 898d823340dSGreg Roach * Others need to find the appropriate range. 899c6266a77SGreg Roach * 90076d39c55SGreg Roach * @param int|string $value 90109482a55SGreg Roach * @param array<string> $axis 902c6266a77SGreg Roach * 903c6266a77SGreg Roach * @return int|string 904c6266a77SGreg Roach */ 90524f2a3afSGreg Roach private function findAxisEntry($value, array $axis) 906c6266a77SGreg Roach { 907c6266a77SGreg Roach if (is_numeric($value)) { 908c6266a77SGreg Roach $value = (int) $value; 909c6266a77SGreg Roach 9106960d4a1SGreg Roach if (!array_key_exists($value, $axis)) { 911c6266a77SGreg Roach foreach (array_keys($axis) as $boundary) { 912c6266a77SGreg Roach if ($value <= $boundary) { 913c6266a77SGreg Roach $value = $boundary; 914c6266a77SGreg Roach break; 915c6266a77SGreg Roach } 916c6266a77SGreg Roach } 917c6266a77SGreg Roach } 918c6266a77SGreg Roach } 919c6266a77SGreg Roach 920c6266a77SGreg Roach return $value; 921c6266a77SGreg Roach } 922c6266a77SGreg Roach 923c6266a77SGreg Roach /** 924c6266a77SGreg Roach * Plot the data. 925c6266a77SGreg Roach * 926c6266a77SGreg Roach * @param string $chart_title 92709482a55SGreg Roach * @param array<string> $x_axis 928c6266a77SGreg Roach * @param string $x_axis_title 92909482a55SGreg Roach * @param array<array<int>> $ydata 930c6266a77SGreg Roach * @param string $y_axis_title 93109482a55SGreg Roach * @param array<string> $z_axis 932c6266a77SGreg Roach * @param int $y_axis_type 933c6266a77SGreg Roach * 934c6266a77SGreg Roach * @return string 935c6266a77SGreg Roach */ 936a81e5019SRico Sonntag private function myPlot( 937a81e5019SRico Sonntag string $chart_title, 938a81e5019SRico Sonntag array $x_axis, 939a81e5019SRico Sonntag string $x_axis_title, 940a81e5019SRico Sonntag array $ydata, 941a81e5019SRico Sonntag string $y_axis_title, 942a81e5019SRico Sonntag array $z_axis, 943a81e5019SRico Sonntag int $y_axis_type 944a81e5019SRico Sonntag ): string { 9456960d4a1SGreg Roach if (!count($ydata)) { 946a81e5019SRico Sonntag return I18N::translate('This information is not available.'); 947c6266a77SGreg Roach } 948c6266a77SGreg Roach 949c6266a77SGreg Roach // Colors for z-axis 950c6266a77SGreg Roach $colors = []; 951c6266a77SGreg Roach $index = 0; 9526960d4a1SGreg Roach while (count($colors) < count($ydata)) { 953c6266a77SGreg Roach $colors[] = self::Z_AXIS_COLORS[$index]; 9546960d4a1SGreg Roach $index = ($index + 1) % count(self::Z_AXIS_COLORS); 955c6266a77SGreg Roach } 956c6266a77SGreg Roach 957c6266a77SGreg Roach // Convert our sparse dataset into a fixed-size array 958c6266a77SGreg Roach $tmp = []; 959c6266a77SGreg Roach foreach (array_keys($z_axis) as $z) { 960c6266a77SGreg Roach foreach (array_keys($x_axis) as $x) { 961c6266a77SGreg Roach $tmp[$z][$x] = $ydata[$z][$x] ?? 0; 962c6266a77SGreg Roach } 963c6266a77SGreg Roach } 964c6266a77SGreg Roach $ydata = $tmp; 965c6266a77SGreg Roach 966a81e5019SRico Sonntag // Convert the chart data to percentage 967c6266a77SGreg Roach if ($y_axis_type === self::Y_AXIS_PERCENT) { 968c6266a77SGreg Roach // Normalise each (non-zero!) set of data to total 100% 9690b5fd0a6SGreg Roach array_walk($ydata, static function (array &$x) { 970c6266a77SGreg Roach $sum = array_sum($x); 971c6266a77SGreg Roach if ($sum > 0) { 9724c78e066SGreg Roach $x = array_map(static fn (float $y): float => $y * 100.0 / $sum, $x); 973c6266a77SGreg Roach } 974c6266a77SGreg Roach }); 975c6266a77SGreg Roach } 976c6266a77SGreg Roach 977a81e5019SRico Sonntag $data = [ 978a81e5019SRico Sonntag array_merge( 979a81e5019SRico Sonntag [I18N::translate('Century')], 980a81e5019SRico Sonntag array_values($z_axis) 98171378461SGreg Roach ), 982c6266a77SGreg Roach ]; 983c6266a77SGreg Roach 984a81e5019SRico Sonntag $intermediate = []; 985d85de00fSGreg Roach foreach ($ydata as $months) { 986a81e5019SRico Sonntag foreach ($months as $month => $value) { 987a81e5019SRico Sonntag $intermediate[$month][] = [ 988a81e5019SRico Sonntag 'v' => $value, 989d85de00fSGreg Roach 'f' => $y_axis_type === self::Y_AXIS_PERCENT ? sprintf('%.1f%%', $value) : $value, 990a81e5019SRico Sonntag ]; 991a81e5019SRico Sonntag } 992c6266a77SGreg Roach } 993c6266a77SGreg Roach 994a81e5019SRico Sonntag foreach ($intermediate as $key => $values) { 995a81e5019SRico Sonntag $data[] = array_merge( 996a81e5019SRico Sonntag [$x_axis[$key]], 997a81e5019SRico Sonntag $values 998a81e5019SRico Sonntag ); 999a81e5019SRico Sonntag } 1000c6266a77SGreg Roach 1001a81e5019SRico Sonntag $chart_options = [ 1002a81e5019SRico Sonntag 'title' => '', 1003a81e5019SRico Sonntag 'subtitle' => '', 1004a81e5019SRico Sonntag 'height' => 400, 1005a81e5019SRico Sonntag 'width' => '100%', 1006a81e5019SRico Sonntag 'legend' => [ 10076960d4a1SGreg Roach 'position' => count($z_axis) > 1 ? 'right' : 'none', 1008a81e5019SRico Sonntag 'alignment' => 'center', 1009a81e5019SRico Sonntag ], 1010a81e5019SRico Sonntag 'tooltip' => [ 1011a81e5019SRico Sonntag 'format' => '\'%\'', 1012a81e5019SRico Sonntag ], 1013a81e5019SRico Sonntag 'vAxis' => [ 101476d39c55SGreg Roach 'title' => $y_axis_title, 1015a81e5019SRico Sonntag ], 1016a81e5019SRico Sonntag 'hAxis' => [ 101776d39c55SGreg Roach 'title' => $x_axis_title, 1018a81e5019SRico Sonntag ], 1019a81e5019SRico Sonntag 'colors' => $colors, 1020a81e5019SRico Sonntag ]; 1021a81e5019SRico Sonntag 102290a2f718SGreg Roach return view('statistics/other/charts/custom', [ 1023a81e5019SRico Sonntag 'data' => $data, 1024a81e5019SRico Sonntag 'chart_options' => $chart_options, 1025a81e5019SRico Sonntag 'chart_title' => $chart_title, 102665cf5706SGreg Roach 'language' => I18N::languageTag(), 102790a2f718SGreg Roach ]); 1028c6266a77SGreg Roach } 1029168ff6f3Sric2016} 1030