1168ff6f3Sric2016<?php 23976b470SGreg Roach 3168ff6f3Sric2016/** 4168ff6f3Sric2016 * webtrees: online genealogy 5*89f7189bSGreg Roach * Copyright (C) 2021 webtrees development team 6168ff6f3Sric2016 * This program is free software: you can redistribute it and/or modify 7168ff6f3Sric2016 * it under the terms of the GNU General Public License as published by 8168ff6f3Sric2016 * the Free Software Foundation, either version 3 of the License, or 9168ff6f3Sric2016 * (at your option) any later version. 10168ff6f3Sric2016 * This program is distributed in the hope that it will be useful, 11168ff6f3Sric2016 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12168ff6f3Sric2016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13168ff6f3Sric2016 * GNU General Public License for more details. 14168ff6f3Sric2016 * You should have received a copy of the GNU General Public License 15*89f7189bSGreg 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; 23c6266a77SGreg Roachuse Fisharebest\Webtrees\Date; 24d501c45dSGreg Roachuse Fisharebest\Webtrees\Exceptions\HttpNotFoundException; 25168ff6f3Sric2016use Fisharebest\Webtrees\I18N; 26168ff6f3Sric2016use Fisharebest\Webtrees\Individual; 279219296aSGreg Roachuse Fisharebest\Webtrees\Statistics; 285229eadeSGreg Roachuse Fisharebest\Webtrees\Tree; 296ccdf4f0SGreg Roachuse Psr\Http\Message\ResponseInterface; 306ccdf4f0SGreg Roachuse Psr\Http\Message\ServerRequestInterface; 313976b470SGreg Roach 3257ab2231SGreg Roachuse function app; 336960d4a1SGreg Roachuse function array_key_exists; 346960d4a1SGreg Roachuse function array_keys; 356960d4a1SGreg Roachuse function array_map; 366960d4a1SGreg Roachuse function array_merge; 376960d4a1SGreg Roachuse function array_sum; 386960d4a1SGreg Roachuse function array_values; 396960d4a1SGreg Roachuse function array_walk; 405229eadeSGreg Roachuse function assert; 416960d4a1SGreg Roachuse function count; 426960d4a1SGreg Roachuse function explode; 436960d4a1SGreg Roachuse function in_array; 446960d4a1SGreg Roachuse function is_numeric; 456960d4a1SGreg Roachuse function sprintf; 466960d4a1SGreg Roachuse function strip_tags; 47168ff6f3Sric2016 48168ff6f3Sric2016/** 49168ff6f3Sric2016 * Class StatisticsChartModule 50168ff6f3Sric2016 */ 5137eb8894SGreg Roachclass StatisticsChartModule extends AbstractModule implements ModuleChartInterface 52c1010edaSGreg Roach{ 5349a243cbSGreg Roach use ModuleChartTrait; 5449a243cbSGreg Roach 55c6266a77SGreg Roach public const X_AXIS_INDIVIDUAL_MAP = 1; 56c6266a77SGreg Roach public const X_AXIS_BIRTH_MAP = 2; 57c6266a77SGreg Roach public const X_AXIS_DEATH_MAP = 3; 58c6266a77SGreg Roach public const X_AXIS_MARRIAGE_MAP = 4; 59c6266a77SGreg Roach public const X_AXIS_BIRTH_MONTH = 11; 60c6266a77SGreg Roach public const X_AXIS_DEATH_MONTH = 12; 61c6266a77SGreg Roach public const X_AXIS_MARRIAGE_MONTH = 13; 62c6266a77SGreg Roach public const X_AXIS_FIRST_CHILD_MONTH = 14; 63c6266a77SGreg Roach public const X_AXIS_FIRST_MARRIAGE_MONTH = 15; 64c6266a77SGreg Roach public const X_AXIS_AGE_AT_DEATH = 18; 65c6266a77SGreg Roach public const X_AXIS_AGE_AT_MARRIAGE = 19; 66c6266a77SGreg Roach public const X_AXIS_AGE_AT_FIRST_MARRIAGE = 20; 67c6266a77SGreg Roach public const X_AXIS_NUMBER_OF_CHILDREN = 21; 68c6266a77SGreg Roach 69c6266a77SGreg Roach public const Y_AXIS_NUMBERS = 201; 70c6266a77SGreg Roach public const Y_AXIS_PERCENT = 202; 71c6266a77SGreg Roach 72c6266a77SGreg Roach public const Z_AXIS_ALL = 300; 73c6266a77SGreg Roach public const Z_AXIS_SEX = 301; 74c6266a77SGreg Roach public const Z_AXIS_TIME = 302; 75c6266a77SGreg Roach 76c6266a77SGreg Roach // First two colors are blue/pink, to work with Z_AXIS_SEX. 77c6266a77SGreg Roach private const Z_AXIS_COLORS = ['0000FF', 'FFA0CB', '9F00FF', 'FF7000', '905030', 'FF0000', '00FF00', 'F0F000']; 78c6266a77SGreg Roach 79c6266a77SGreg Roach private const DAYS_IN_YEAR = 365.25; 80c6266a77SGreg Roach 81168ff6f3Sric2016 /** 820cfd6963SGreg Roach * How should this module be identified in the control panel, etc.? 83168ff6f3Sric2016 * 84168ff6f3Sric2016 * @return string 85168ff6f3Sric2016 */ 8649a243cbSGreg Roach public function title(): string 87c1010edaSGreg Roach { 88bbb76c12SGreg Roach /* I18N: Name of a module/chart */ 89bbb76c12SGreg Roach return I18N::translate('Statistics'); 90168ff6f3Sric2016 } 91168ff6f3Sric2016 92168ff6f3Sric2016 /** 93168ff6f3Sric2016 * A sentence describing what this module does. 94168ff6f3Sric2016 * 95168ff6f3Sric2016 * @return string 96168ff6f3Sric2016 */ 9749a243cbSGreg Roach public function description(): string 98c1010edaSGreg Roach { 99bbb76c12SGreg Roach /* I18N: Description of the “StatisticsChart” module */ 100bbb76c12SGreg Roach return I18N::translate('Various statistics charts.'); 101168ff6f3Sric2016 } 102168ff6f3Sric2016 103168ff6f3Sric2016 /** 104377a2979SGreg Roach * CSS class for the URL. 105377a2979SGreg Roach * 106377a2979SGreg Roach * @return string 107377a2979SGreg Roach */ 108377a2979SGreg Roach public function chartMenuClass(): string 109377a2979SGreg Roach { 110377a2979SGreg Roach return 'menu-chart-statistics'; 111377a2979SGreg Roach } 112377a2979SGreg Roach 113377a2979SGreg Roach /** 114e6562982SGreg Roach * The URL for this chart. 115168ff6f3Sric2016 * 11660bc3e3fSGreg Roach * @param Individual $individual 11759597b37SGreg Roach * @param mixed[] $parameters 11860bc3e3fSGreg Roach * 119e6562982SGreg Roach * @return string 120168ff6f3Sric2016 */ 121e6562982SGreg Roach public function chartUrl(Individual $individual, array $parameters = []): string 122c1010edaSGreg Roach { 123c6266a77SGreg Roach return route('module', [ 124c6266a77SGreg Roach 'module' => $this->name(), 125c6266a77SGreg Roach 'action' => 'Chart', 126d72b284aSGreg Roach 'tree' => $individual->tree()->name(), 127e6562982SGreg Roach ] + $parameters); 128168ff6f3Sric2016 } 129c6266a77SGreg Roach 130c6266a77SGreg Roach /** 131c6266a77SGreg Roach * A form to request the chart parameters. 132c6266a77SGreg Roach * 13357ab2231SGreg Roach * @param ServerRequestInterface $request 134c6266a77SGreg Roach * 1356ccdf4f0SGreg Roach * @return ResponseInterface 136c6266a77SGreg Roach */ 13757ab2231SGreg Roach public function getChartAction(ServerRequestInterface $request): ResponseInterface 138c6266a77SGreg Roach { 13957ab2231SGreg Roach $tree = $request->getAttribute('tree'); 14075964c75SGreg Roach assert($tree instanceof Tree); 1415229eadeSGreg Roach 14257ab2231SGreg Roach $user = $request->getAttribute('user'); 14357ab2231SGreg Roach 144ef483801SGreg Roach Auth::checkComponentAccess($this, ModuleChartInterface::class, $tree, $user); 1459867b2f0SGreg Roach 146c6266a77SGreg Roach $tabs = [ 147c6266a77SGreg Roach I18N::translate('Individuals') => route('module', [ 148c6266a77SGreg Roach 'module' => $this->name(), 149c6266a77SGreg Roach 'action' => 'Individuals', 150d72b284aSGreg Roach 'tree' => $tree->name(), 151c6266a77SGreg Roach ]), 152c6266a77SGreg Roach I18N::translate('Families') => route('module', [ 153c6266a77SGreg Roach 'module' => $this->name(), 154c6266a77SGreg Roach 'action' => 'Families', 155d72b284aSGreg Roach 'tree' => $tree->name(), 156c6266a77SGreg Roach ]), 157c6266a77SGreg Roach I18N::translate('Other') => route('module', [ 158c6266a77SGreg Roach 'module' => $this->name(), 159c6266a77SGreg Roach 'action' => 'Other', 160d72b284aSGreg Roach 'tree' => $tree->name(), 161c6266a77SGreg Roach ]), 162c6266a77SGreg Roach I18N::translate('Custom') => route('module', [ 163c6266a77SGreg Roach 'module' => $this->name(), 164c6266a77SGreg Roach 'action' => 'Custom', 165d72b284aSGreg Roach 'tree' => $tree->name(), 166c6266a77SGreg Roach ]), 167c6266a77SGreg Roach ]; 168c6266a77SGreg Roach 1699b5537c3SGreg Roach return $this->viewResponse('modules/statistics-chart/page', [ 17071378461SGreg Roach 'module' => $this->name(), 171c6266a77SGreg Roach 'tabs' => $tabs, 172c6266a77SGreg Roach 'title' => $this->title(), 173ef5d23f1SGreg Roach 'tree' => $tree, 174c6266a77SGreg Roach ]); 175c6266a77SGreg Roach } 176c6266a77SGreg Roach 177c6266a77SGreg Roach /** 17857ab2231SGreg Roach * @param ServerRequestInterface $request 179c6266a77SGreg Roach * 1806ccdf4f0SGreg Roach * @return ResponseInterface 181c6266a77SGreg Roach */ 18257ab2231SGreg Roach public function getIndividualsAction(ServerRequestInterface $request): ResponseInterface 183c6266a77SGreg Roach { 184b6c326d8SGreg Roach $this->layout = 'layouts/ajax'; 185b6c326d8SGreg Roach 186b6c326d8SGreg Roach return $this->viewResponse('modules/statistics-chart/individuals', [ 187c6266a77SGreg Roach 'show_oldest_living' => Auth::check(), 18857ab2231SGreg Roach 'stats' => app(Statistics::class), 189c6266a77SGreg Roach ]); 190c6266a77SGreg Roach } 191c6266a77SGreg Roach 192c6266a77SGreg Roach /** 19357ab2231SGreg Roach * @param ServerRequestInterface $request 194c6266a77SGreg Roach * 1956ccdf4f0SGreg Roach * @return ResponseInterface 196c6266a77SGreg Roach */ 19757ab2231SGreg Roach public function getFamiliesAction(ServerRequestInterface $request): ResponseInterface 198c6266a77SGreg Roach { 199b6c326d8SGreg Roach $this->layout = 'layouts/ajax'; 200b6c326d8SGreg Roach 201b6c326d8SGreg Roach return $this->viewResponse('modules/statistics-chart/families', [ 20257ab2231SGreg Roach 'stats' => app(Statistics::class), 203c6266a77SGreg Roach ]); 204c6266a77SGreg Roach } 205c6266a77SGreg Roach 206c6266a77SGreg Roach /** 20757ab2231SGreg Roach * @param ServerRequestInterface $request 208c6266a77SGreg Roach * 2096ccdf4f0SGreg Roach * @return ResponseInterface 210c6266a77SGreg Roach */ 21157ab2231SGreg Roach public function getOtherAction(ServerRequestInterface $request): ResponseInterface 212c6266a77SGreg Roach { 213b6c326d8SGreg Roach $this->layout = 'layouts/ajax'; 214b6c326d8SGreg Roach 215b6c326d8SGreg Roach return $this->viewResponse('modules/statistics-chart/other', [ 21657ab2231SGreg Roach 'stats' => app(Statistics::class), 217c6266a77SGreg Roach ]); 218c6266a77SGreg Roach } 219c6266a77SGreg Roach 220c6266a77SGreg Roach /** 22157ab2231SGreg Roach * @param ServerRequestInterface $request 222c6266a77SGreg Roach * 2236ccdf4f0SGreg Roach * @return ResponseInterface 224c6266a77SGreg Roach */ 22557ab2231SGreg Roach public function getCustomAction(ServerRequestInterface $request): ResponseInterface 226c6266a77SGreg Roach { 227b6c326d8SGreg Roach $this->layout = 'layouts/ajax'; 228b6c326d8SGreg Roach 22957ab2231SGreg Roach $tree = $request->getAttribute('tree'); 23075964c75SGreg Roach assert($tree instanceof Tree); 23157ab2231SGreg Roach 232b6c326d8SGreg Roach return $this->viewResponse('modules/statistics-chart/custom', [ 233c6266a77SGreg Roach 'module' => $this, 234c6266a77SGreg Roach 'tree' => $tree, 235c6266a77SGreg Roach ]); 236c6266a77SGreg Roach } 237c6266a77SGreg Roach 238c6266a77SGreg Roach /** 2396ccdf4f0SGreg Roach * @param ServerRequestInterface $request 240c6266a77SGreg Roach * 2416ccdf4f0SGreg Roach * @return ResponseInterface 242c6266a77SGreg Roach */ 2433aa29b97SGreg Roach public function postCustomChartAction(ServerRequestInterface $request): ResponseInterface 244c6266a77SGreg Roach { 24557ab2231SGreg Roach $statistics = app(Statistics::class); 246dca2dd78SGreg Roach assert($statistics instanceof Statistics); 24757ab2231SGreg Roach 248b46c87bdSGreg Roach $params = (array) $request->getParsedBody(); 249b6b9dcc9SGreg Roach 250b6b9dcc9SGreg Roach $x_axis_type = (int) $params['x-as']; 251b6b9dcc9SGreg Roach $y_axis_type = (int) $params['y-as']; 252b6b9dcc9SGreg Roach $z_axis_type = (int) $params['z-as']; 253c6266a77SGreg Roach $ydata = []; 254c6266a77SGreg Roach 255c6266a77SGreg Roach switch ($x_axis_type) { 256c6266a77SGreg Roach case self::X_AXIS_INDIVIDUAL_MAP: 2576ccdf4f0SGreg Roach return response($statistics->chartDistribution( 258b6b9dcc9SGreg Roach $params['chart_shows'], 259b6b9dcc9SGreg Roach $params['chart_type'], 260b6b9dcc9SGreg Roach $params['SURN'] 261c6266a77SGreg Roach )); 262c6266a77SGreg Roach 263c6266a77SGreg Roach case self::X_AXIS_BIRTH_MAP: 2646ccdf4f0SGreg Roach return response($statistics->chartDistribution( 265b6b9dcc9SGreg Roach $params['chart_shows'], 266c6266a77SGreg Roach 'birth_distribution_chart' 267c6266a77SGreg Roach )); 268c6266a77SGreg Roach 269c6266a77SGreg Roach case self::X_AXIS_DEATH_MAP: 2706ccdf4f0SGreg Roach return response($statistics->chartDistribution( 271b6b9dcc9SGreg Roach $params['chart_shows'], 272c6266a77SGreg Roach 'death_distribution_chart' 273c6266a77SGreg Roach )); 274c6266a77SGreg Roach 275c6266a77SGreg Roach case self::X_AXIS_MARRIAGE_MAP: 2766ccdf4f0SGreg Roach return response($statistics->chartDistribution( 277b6b9dcc9SGreg Roach $params['chart_shows'], 278c6266a77SGreg Roach 'marriage_distribution_chart' 279c6266a77SGreg Roach )); 280c6266a77SGreg Roach 281c6266a77SGreg Roach case self::X_AXIS_BIRTH_MONTH: 282c6266a77SGreg Roach $chart_title = I18N::translate('Month of birth'); 283c6266a77SGreg Roach $x_axis_title = I18N::translate('Month'); 284c6266a77SGreg Roach $x_axis = $this->axisMonths(); 285c6266a77SGreg Roach 286c6266a77SGreg Roach switch ($y_axis_type) { 287c6266a77SGreg Roach case self::Y_AXIS_NUMBERS: 288c6266a77SGreg Roach $y_axis_title = I18N::translate('Individuals'); 289c6266a77SGreg Roach break; 290c6266a77SGreg Roach case self::Y_AXIS_PERCENT: 291c6266a77SGreg Roach $y_axis_title = '%'; 292c6266a77SGreg Roach break; 293c6266a77SGreg Roach default: 294d501c45dSGreg Roach throw new HttpNotFoundException(); 295c6266a77SGreg Roach } 296c6266a77SGreg Roach 297c6266a77SGreg Roach switch ($z_axis_type) { 298c6266a77SGreg Roach case self::Z_AXIS_ALL: 299c6266a77SGreg Roach $z_axis = $this->axisAll(); 300cde1d378SGreg Roach $rows = $statistics->statsBirthQuery()->get(); 301c6266a77SGreg Roach foreach ($rows as $row) { 302c6266a77SGreg Roach $this->fillYData($row->d_month, 0, $row->total, $x_axis, $z_axis, $ydata); 303c6266a77SGreg Roach } 304c6266a77SGreg Roach break; 305c6266a77SGreg Roach case self::Z_AXIS_SEX: 306c6266a77SGreg Roach $z_axis = $this->axisSexes(); 307cde1d378SGreg Roach $rows = $statistics->statsBirthBySexQuery()->get(); 308c6266a77SGreg Roach foreach ($rows as $row) { 309c6266a77SGreg Roach $this->fillYData($row->d_month, $row->i_sex, $row->total, $x_axis, $z_axis, $ydata); 310c6266a77SGreg Roach } 311c6266a77SGreg Roach break; 312c6266a77SGreg Roach case self::Z_AXIS_TIME: 313b6b9dcc9SGreg Roach $boundaries_csv = $params['z-axis-boundaries-periods']; 314c6266a77SGreg Roach $z_axis = $this->axisYears($boundaries_csv); 315c6266a77SGreg Roach $prev_boundary = 0; 316c6266a77SGreg Roach foreach (array_keys($z_axis) as $boundary) { 317cde1d378SGreg Roach $rows = $statistics->statsBirthQuery($prev_boundary, $boundary)->get(); 318c6266a77SGreg Roach foreach ($rows as $row) { 319c6266a77SGreg Roach $this->fillYData($row->d_month, $boundary, $row->total, $x_axis, $z_axis, $ydata); 320c6266a77SGreg Roach } 321c6266a77SGreg Roach $prev_boundary = $boundary + 1; 322c6266a77SGreg Roach } 323c6266a77SGreg Roach break; 324c6266a77SGreg Roach default: 325d501c45dSGreg Roach throw new HttpNotFoundException(); 326c6266a77SGreg Roach } 327c6266a77SGreg Roach 3286ccdf4f0SGreg Roach return response($this->myPlot($chart_title, $x_axis, $x_axis_title, $ydata, $y_axis_title, $z_axis, $y_axis_type)); 329c6266a77SGreg Roach 330c6266a77SGreg Roach case self::X_AXIS_DEATH_MONTH: 331c6266a77SGreg Roach $chart_title = I18N::translate('Month of death'); 332c6266a77SGreg Roach $x_axis_title = I18N::translate('Month'); 333c6266a77SGreg Roach $x_axis = $this->axisMonths(); 334c6266a77SGreg Roach 335c6266a77SGreg Roach switch ($y_axis_type) { 336c6266a77SGreg Roach case self::Y_AXIS_NUMBERS: 337c6266a77SGreg Roach $y_axis_title = I18N::translate('Individuals'); 338c6266a77SGreg Roach break; 339c6266a77SGreg Roach case self::Y_AXIS_PERCENT: 340c6266a77SGreg Roach $y_axis_title = '%'; 341c6266a77SGreg Roach break; 342c6266a77SGreg Roach default: 343d501c45dSGreg Roach throw new HttpNotFoundException(); 344c6266a77SGreg Roach } 345c6266a77SGreg Roach 346c6266a77SGreg Roach switch ($z_axis_type) { 347c6266a77SGreg Roach case self::Z_AXIS_ALL: 348c6266a77SGreg Roach $z_axis = $this->axisAll(); 349cde1d378SGreg Roach $rows = $statistics->statsDeathQuery()->get(); 350c6266a77SGreg Roach foreach ($rows as $row) { 351c6266a77SGreg Roach $this->fillYData($row->d_month, 0, $row->total, $x_axis, $z_axis, $ydata); 352c6266a77SGreg Roach } 353c6266a77SGreg Roach break; 354c6266a77SGreg Roach case self::Z_AXIS_SEX: 355c6266a77SGreg Roach $z_axis = $this->axisSexes(); 356cde1d378SGreg Roach $rows = $statistics->statsDeathBySexQuery()->get(); 357c6266a77SGreg Roach foreach ($rows as $row) { 358c6266a77SGreg Roach $this->fillYData($row->d_month, $row->i_sex, $row->total, $x_axis, $z_axis, $ydata); 359c6266a77SGreg Roach } 360c6266a77SGreg Roach break; 361c6266a77SGreg Roach case self::Z_AXIS_TIME: 362b6b9dcc9SGreg Roach $boundaries_csv = $params['z-axis-boundaries-periods']; 363c6266a77SGreg Roach $z_axis = $this->axisYears($boundaries_csv); 364c6266a77SGreg Roach $prev_boundary = 0; 365c6266a77SGreg Roach foreach (array_keys($z_axis) as $boundary) { 366cde1d378SGreg Roach $rows = $statistics->statsDeathQuery($prev_boundary, $boundary)->get(); 367c6266a77SGreg Roach foreach ($rows as $row) { 368c6266a77SGreg Roach $this->fillYData($row->d_month, $boundary, $row->total, $x_axis, $z_axis, $ydata); 369c6266a77SGreg Roach } 370c6266a77SGreg Roach $prev_boundary = $boundary + 1; 371c6266a77SGreg Roach } 372c6266a77SGreg Roach break; 373c6266a77SGreg Roach default: 374d501c45dSGreg Roach throw new HttpNotFoundException(); 375c6266a77SGreg Roach } 376c6266a77SGreg Roach 3776ccdf4f0SGreg Roach return response($this->myPlot($chart_title, $x_axis, $x_axis_title, $ydata, $y_axis_title, $z_axis, $y_axis_type)); 378c6266a77SGreg Roach 379c6266a77SGreg Roach case self::X_AXIS_MARRIAGE_MONTH: 380c6266a77SGreg Roach $chart_title = I18N::translate('Month of marriage'); 381c6266a77SGreg Roach $x_axis_title = I18N::translate('Month'); 382c6266a77SGreg Roach $x_axis = $this->axisMonths(); 383c6266a77SGreg Roach 384c6266a77SGreg Roach switch ($y_axis_type) { 385c6266a77SGreg Roach case self::Y_AXIS_NUMBERS: 386c6266a77SGreg Roach $y_axis_title = I18N::translate('Families'); 387c6266a77SGreg Roach break; 388c6266a77SGreg Roach case self::Y_AXIS_PERCENT: 389c6266a77SGreg Roach $y_axis_title = '%'; 390c6266a77SGreg Roach break; 391c6266a77SGreg Roach default: 392d501c45dSGreg Roach throw new HttpNotFoundException(); 393c6266a77SGreg Roach } 394c6266a77SGreg Roach 395c6266a77SGreg Roach switch ($z_axis_type) { 396c6266a77SGreg Roach case self::Z_AXIS_ALL: 397c6266a77SGreg Roach $z_axis = $this->axisAll(); 398e6f3d5e2SGreg Roach $rows = $statistics->statsMarriageQuery()->get(); 399c6266a77SGreg Roach foreach ($rows as $row) { 400c6266a77SGreg Roach $this->fillYData($row->d_month, 0, $row->total, $x_axis, $z_axis, $ydata); 401c6266a77SGreg Roach } 402c6266a77SGreg Roach break; 403c6266a77SGreg Roach case self::Z_AXIS_TIME: 404b6b9dcc9SGreg Roach $boundaries_csv = $params['z-axis-boundaries-periods']; 405c6266a77SGreg Roach $z_axis = $this->axisYears($boundaries_csv); 406c6266a77SGreg Roach $prev_boundary = 0; 407c6266a77SGreg Roach foreach (array_keys($z_axis) as $boundary) { 408e6f3d5e2SGreg Roach $rows = $statistics->statsMarriageQuery($prev_boundary, $boundary)->get(); 409c6266a77SGreg Roach foreach ($rows as $row) { 410c6266a77SGreg Roach $this->fillYData($row->d_month, $boundary, $row->total, $x_axis, $z_axis, $ydata); 411c6266a77SGreg Roach } 412c6266a77SGreg Roach $prev_boundary = $boundary + 1; 413c6266a77SGreg Roach } 414c6266a77SGreg Roach break; 415c6266a77SGreg Roach default: 416d501c45dSGreg Roach throw new HttpNotFoundException(); 417c6266a77SGreg Roach } 418c6266a77SGreg Roach 4196ccdf4f0SGreg Roach return response($this->myPlot($chart_title, $x_axis, $x_axis_title, $ydata, $y_axis_title, $z_axis, $y_axis_type)); 420c6266a77SGreg Roach 421c6266a77SGreg Roach case self::X_AXIS_FIRST_CHILD_MONTH: 422c6266a77SGreg Roach $chart_title = I18N::translate('Month of birth of first child in a relation'); 423c6266a77SGreg Roach $x_axis_title = I18N::translate('Month'); 424c6266a77SGreg Roach $x_axis = $this->axisMonths(); 425c6266a77SGreg Roach 426c6266a77SGreg Roach switch ($y_axis_type) { 427c6266a77SGreg Roach case self::Y_AXIS_NUMBERS: 428c6266a77SGreg Roach $y_axis_title = I18N::translate('Children'); 429c6266a77SGreg Roach break; 430c6266a77SGreg Roach case self::Y_AXIS_PERCENT: 431c6266a77SGreg Roach $y_axis_title = '%'; 432c6266a77SGreg Roach break; 433c6266a77SGreg Roach default: 434d501c45dSGreg Roach throw new HttpNotFoundException(); 435c6266a77SGreg Roach } 436c6266a77SGreg Roach 437c6266a77SGreg Roach switch ($z_axis_type) { 438c6266a77SGreg Roach case self::Z_AXIS_ALL: 439c6266a77SGreg Roach $z_axis = $this->axisAll(); 440999da590SGreg Roach $rows = $statistics->monthFirstChildQuery()->get(); 441c6266a77SGreg Roach foreach ($rows as $row) { 442c6266a77SGreg Roach $this->fillYData($row->d_month, 0, $row->total, $x_axis, $z_axis, $ydata); 443c6266a77SGreg Roach } 444c6266a77SGreg Roach break; 445c6266a77SGreg Roach case self::Z_AXIS_SEX: 446c6266a77SGreg Roach $z_axis = $this->axisSexes(); 447999da590SGreg Roach $rows = $statistics->monthFirstChildBySexQuery()->get(); 448c6266a77SGreg Roach foreach ($rows as $row) { 449c6266a77SGreg Roach $this->fillYData($row->d_month, $row->i_sex, $row->total, $x_axis, $z_axis, $ydata); 450c6266a77SGreg Roach } 451c6266a77SGreg Roach break; 452c6266a77SGreg Roach case self::Z_AXIS_TIME: 453b6b9dcc9SGreg Roach $boundaries_csv = $params['z-axis-boundaries-periods']; 454c6266a77SGreg Roach $z_axis = $this->axisYears($boundaries_csv); 455c6266a77SGreg Roach $prev_boundary = 0; 456c6266a77SGreg Roach foreach (array_keys($z_axis) as $boundary) { 457999da590SGreg Roach $rows = $statistics->monthFirstChildQuery($prev_boundary, $boundary)->get(); 458c6266a77SGreg Roach foreach ($rows as $row) { 459c6266a77SGreg Roach $this->fillYData($row->d_month, $boundary, $row->total, $x_axis, $z_axis, $ydata); 460c6266a77SGreg Roach } 461c6266a77SGreg Roach $prev_boundary = $boundary + 1; 462c6266a77SGreg Roach } 463c6266a77SGreg Roach break; 464c6266a77SGreg Roach default: 465d501c45dSGreg Roach throw new HttpNotFoundException(); 466c6266a77SGreg Roach } 467c6266a77SGreg Roach 4686ccdf4f0SGreg Roach return response($this->myPlot($chart_title, $x_axis, $x_axis_title, $ydata, $y_axis_title, $z_axis, $y_axis_type)); 469c6266a77SGreg Roach 470c6266a77SGreg Roach case self::X_AXIS_FIRST_MARRIAGE_MONTH: 471c6266a77SGreg Roach $chart_title = I18N::translate('Month of first marriage'); 472c6266a77SGreg Roach $x_axis_title = I18N::translate('Month'); 473c6266a77SGreg Roach $x_axis = $this->axisMonths(); 474c6266a77SGreg Roach 475c6266a77SGreg Roach switch ($y_axis_type) { 476c6266a77SGreg Roach case self::Y_AXIS_NUMBERS: 477c6266a77SGreg Roach $y_axis_title = I18N::translate('Families'); 478c6266a77SGreg Roach break; 479c6266a77SGreg Roach case self::Y_AXIS_PERCENT: 480c6266a77SGreg Roach $y_axis_title = '%'; 481c6266a77SGreg Roach break; 482c6266a77SGreg Roach default: 483d501c45dSGreg Roach throw new HttpNotFoundException(); 484c6266a77SGreg Roach } 485c6266a77SGreg Roach 486c6266a77SGreg Roach switch ($z_axis_type) { 487c6266a77SGreg Roach case self::Z_AXIS_ALL: 488c6266a77SGreg Roach $z_axis = $this->axisAll(); 489e6f3d5e2SGreg Roach $rows = $statistics->statsFirstMarriageQuery()->get(); 490c6266a77SGreg Roach $indi = []; 491c6266a77SGreg Roach foreach ($rows as $row) { 492dca2dd78SGreg Roach if (!in_array($row->f_husb, $indi, true) && !in_array($row->f_wife, $indi, true)) { 493c6266a77SGreg Roach $this->fillYData($row->month, 0, 1, $x_axis, $z_axis, $ydata); 494c6266a77SGreg Roach } 495dca2dd78SGreg Roach $indi[] = $row->f_husb; 496dca2dd78SGreg Roach $indi[] = $row->f_wife; 497c6266a77SGreg Roach } 498c6266a77SGreg Roach break; 499c6266a77SGreg Roach case self::Z_AXIS_TIME: 500b6b9dcc9SGreg Roach $boundaries_csv = $params['z-axis-boundaries-periods']; 501c6266a77SGreg Roach $z_axis = $this->axisYears($boundaries_csv); 502c6266a77SGreg Roach $prev_boundary = 0; 503c6266a77SGreg Roach $indi = []; 504c6266a77SGreg Roach foreach (array_keys($z_axis) as $boundary) { 505e6f3d5e2SGreg Roach $rows = $statistics->statsFirstMarriageQuery($prev_boundary, $boundary)->get(); 506c6266a77SGreg Roach foreach ($rows as $row) { 507dca2dd78SGreg Roach if (!in_array($row->f_husb, $indi, true) && !in_array($row->f_wife, $indi, true)) { 508c6266a77SGreg Roach $this->fillYData($row->month, $boundary, 1, $x_axis, $z_axis, $ydata); 509c6266a77SGreg Roach } 510dca2dd78SGreg Roach $indi[] = $row->f_husb; 511dca2dd78SGreg Roach $indi[] = $row->f_wife; 512c6266a77SGreg Roach } 513c6266a77SGreg Roach $prev_boundary = $boundary + 1; 514c6266a77SGreg Roach } 515c6266a77SGreg Roach break; 516c6266a77SGreg Roach default: 517d501c45dSGreg Roach throw new HttpNotFoundException(); 518c6266a77SGreg Roach } 519c6266a77SGreg Roach 5206ccdf4f0SGreg Roach return response($this->myPlot($chart_title, $x_axis, $x_axis_title, $ydata, $y_axis_title, $z_axis, $y_axis_type)); 521c6266a77SGreg Roach 522c6266a77SGreg Roach case self::X_AXIS_AGE_AT_DEATH: 523c6266a77SGreg Roach $chart_title = I18N::translate('Average age at death'); 524c6266a77SGreg Roach $x_axis_title = I18N::translate('age'); 525b6b9dcc9SGreg Roach $boundaries_csv = $params['x-axis-boundaries-ages']; 526c6266a77SGreg Roach $x_axis = $this->axisNumbers($boundaries_csv); 527c6266a77SGreg Roach 528c6266a77SGreg Roach switch ($y_axis_type) { 529c6266a77SGreg Roach case self::Y_AXIS_NUMBERS: 530c6266a77SGreg Roach $y_axis_title = I18N::translate('Individuals'); 531c6266a77SGreg Roach break; 532c6266a77SGreg Roach case self::Y_AXIS_PERCENT: 533c6266a77SGreg Roach $y_axis_title = '%'; 534c6266a77SGreg Roach break; 535c6266a77SGreg Roach default: 536d501c45dSGreg Roach throw new HttpNotFoundException(); 537c6266a77SGreg Roach } 538c6266a77SGreg Roach 539c6266a77SGreg Roach switch ($z_axis_type) { 540c6266a77SGreg Roach case self::Z_AXIS_ALL: 541c6266a77SGreg Roach $z_axis = $this->axisAll(); 5429219296aSGreg Roach $rows = $statistics->statsAgeQuery('DEAT'); 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, 0, 1, $x_axis, $z_axis, $ydata); 547c6266a77SGreg Roach } 548c6266a77SGreg Roach } 549c6266a77SGreg Roach break; 550c6266a77SGreg Roach case self::Z_AXIS_SEX: 551c6266a77SGreg Roach $z_axis = $this->axisSexes(); 552c6266a77SGreg Roach foreach (array_keys($z_axis) as $sex) { 5539219296aSGreg Roach $rows = $statistics->statsAgeQuery('DEAT', $sex); 554c6266a77SGreg Roach foreach ($rows as $row) { 555c6266a77SGreg Roach foreach ($row as $age) { 556c6266a77SGreg Roach $years = (int) ($age / self::DAYS_IN_YEAR); 557c6266a77SGreg Roach $this->fillYData($years, $sex, 1, $x_axis, $z_axis, $ydata); 558c6266a77SGreg Roach } 559c6266a77SGreg Roach } 560c6266a77SGreg Roach } 561c6266a77SGreg Roach break; 562c6266a77SGreg Roach case self::Z_AXIS_TIME: 563b6b9dcc9SGreg Roach $boundaries_csv = $params['z-axis-boundaries-periods']; 564c6266a77SGreg Roach $z_axis = $this->axisYears($boundaries_csv); 565c6266a77SGreg Roach $prev_boundary = 0; 566c6266a77SGreg Roach foreach (array_keys($z_axis) as $boundary) { 5679219296aSGreg Roach $rows = $statistics->statsAgeQuery('DEAT', 'BOTH', $prev_boundary, $boundary); 568c6266a77SGreg Roach foreach ($rows as $row) { 569c6266a77SGreg Roach foreach ($row as $age) { 570c6266a77SGreg Roach $years = (int) ($age / self::DAYS_IN_YEAR); 571c6266a77SGreg Roach $this->fillYData($years, $boundary, 1, $x_axis, $z_axis, $ydata); 572c6266a77SGreg Roach } 573c6266a77SGreg Roach } 574c6266a77SGreg Roach $prev_boundary = $boundary + 1; 575c6266a77SGreg Roach } 576c6266a77SGreg Roach 577c6266a77SGreg Roach break; 578c6266a77SGreg Roach default: 579d501c45dSGreg Roach throw new HttpNotFoundException(); 580c6266a77SGreg Roach } 581c6266a77SGreg Roach 5826ccdf4f0SGreg Roach return response($this->myPlot($chart_title, $x_axis, $x_axis_title, $ydata, $y_axis_title, $z_axis, $y_axis_type)); 583c6266a77SGreg Roach 584c6266a77SGreg Roach case self::X_AXIS_AGE_AT_MARRIAGE: 585c6266a77SGreg Roach $chart_title = I18N::translate('Age in year of marriage'); 586c6266a77SGreg Roach $x_axis_title = I18N::translate('age'); 587b6b9dcc9SGreg Roach $boundaries_csv = $params['x-axis-boundaries-ages_m']; 588c6266a77SGreg Roach $x_axis = $this->axisNumbers($boundaries_csv); 589c6266a77SGreg Roach 590c6266a77SGreg Roach switch ($y_axis_type) { 591c6266a77SGreg Roach case self::Y_AXIS_NUMBERS: 592c6266a77SGreg Roach $y_axis_title = I18N::translate('Individuals'); 593c6266a77SGreg Roach break; 594c6266a77SGreg Roach case self::Y_AXIS_PERCENT: 595c6266a77SGreg Roach $y_axis_title = '%'; 596c6266a77SGreg Roach break; 597c6266a77SGreg Roach default: 598d501c45dSGreg Roach throw new HttpNotFoundException(); 599c6266a77SGreg Roach } 600c6266a77SGreg Roach 601c6266a77SGreg Roach switch ($z_axis_type) { 602c6266a77SGreg Roach case self::Z_AXIS_ALL: 603c6266a77SGreg Roach $z_axis = $this->axisAll(); 604afa8d404SGreg Roach // The stats query doesn't have an "all" function, so query M/F separately 605afa8d404SGreg Roach foreach (['M', 'F'] as $sex) { 6069219296aSGreg Roach $rows = $statistics->statsMarrAgeQuery($sex); 607c6266a77SGreg Roach foreach ($rows as $row) { 608c6266a77SGreg Roach $years = (int) ($row->age / self::DAYS_IN_YEAR); 609c6266a77SGreg Roach $this->fillYData($years, 0, 1, $x_axis, $z_axis, $ydata); 610c6266a77SGreg Roach } 611c6266a77SGreg Roach } 612c6266a77SGreg Roach break; 613c6266a77SGreg Roach case self::Z_AXIS_SEX: 614c6266a77SGreg Roach $z_axis = $this->axisSexes(); 615c6266a77SGreg Roach foreach (array_keys($z_axis) as $sex) { 6169219296aSGreg Roach $rows = $statistics->statsMarrAgeQuery($sex); 617c6266a77SGreg Roach foreach ($rows as $row) { 618c6266a77SGreg Roach $years = (int) ($row->age / self::DAYS_IN_YEAR); 619c6266a77SGreg Roach $this->fillYData($years, $sex, 1, $x_axis, $z_axis, $ydata); 620c6266a77SGreg Roach } 621c6266a77SGreg Roach } 622c6266a77SGreg Roach break; 623c6266a77SGreg Roach case self::Z_AXIS_TIME: 624b6b9dcc9SGreg Roach $boundaries_csv = $params['z-axis-boundaries-periods']; 625c6266a77SGreg Roach $z_axis = $this->axisYears($boundaries_csv); 626afa8d404SGreg Roach // The stats query doesn't have an "all" function, so query M/F separately 627afa8d404SGreg Roach foreach (['M', 'F'] as $sex) { 628c6266a77SGreg Roach $prev_boundary = 0; 629c6266a77SGreg Roach foreach (array_keys($z_axis) as $boundary) { 6309219296aSGreg Roach $rows = $statistics->statsMarrAgeQuery($sex, $prev_boundary, $boundary); 631c6266a77SGreg Roach foreach ($rows as $row) { 632c6266a77SGreg Roach $years = (int) ($row->age / self::DAYS_IN_YEAR); 633c6266a77SGreg Roach $this->fillYData($years, $boundary, 1, $x_axis, $z_axis, $ydata); 634c6266a77SGreg Roach } 635c6266a77SGreg Roach $prev_boundary = $boundary + 1; 636c6266a77SGreg Roach } 637c6266a77SGreg Roach } 638c6266a77SGreg Roach break; 639c6266a77SGreg Roach default: 640d501c45dSGreg Roach throw new HttpNotFoundException(); 641c6266a77SGreg Roach } 642c6266a77SGreg Roach 6436ccdf4f0SGreg Roach return response($this->myPlot($chart_title, $x_axis, $x_axis_title, $ydata, $y_axis_title, $z_axis, $y_axis_type)); 644c6266a77SGreg Roach 645c6266a77SGreg Roach case self::X_AXIS_AGE_AT_FIRST_MARRIAGE: 646c6266a77SGreg Roach $chart_title = I18N::translate('Age in year of first marriage'); 647c6266a77SGreg Roach $x_axis_title = I18N::translate('age'); 648b6b9dcc9SGreg Roach $boundaries_csv = $params['x-axis-boundaries-ages_m']; 649c6266a77SGreg Roach $x_axis = $this->axisNumbers($boundaries_csv); 650c6266a77SGreg Roach 651c6266a77SGreg Roach switch ($y_axis_type) { 652c6266a77SGreg Roach case self::Y_AXIS_NUMBERS: 653c6266a77SGreg Roach $y_axis_title = I18N::translate('Individuals'); 654c6266a77SGreg Roach break; 655c6266a77SGreg Roach case self::Y_AXIS_PERCENT: 656c6266a77SGreg Roach $y_axis_title = '%'; 657c6266a77SGreg Roach break; 658c6266a77SGreg Roach default: 659d501c45dSGreg Roach throw new HttpNotFoundException(); 660c6266a77SGreg Roach } 661c6266a77SGreg Roach 662c6266a77SGreg Roach switch ($z_axis_type) { 663c6266a77SGreg Roach case self::Z_AXIS_ALL: 664c6266a77SGreg Roach $z_axis = $this->axisAll(); 665afa8d404SGreg Roach // The stats query doesn't have an "all" function, so query M/F separately 666afa8d404SGreg Roach foreach (['M', 'F'] as $sex) { 6679219296aSGreg Roach $rows = $statistics->statsMarrAgeQuery($sex); 668c6266a77SGreg Roach $indi = []; 669c6266a77SGreg Roach foreach ($rows as $row) { 6706960d4a1SGreg Roach if (!in_array($row->d_gid, $indi, true)) { 671c6266a77SGreg Roach $years = (int) ($row->age / self::DAYS_IN_YEAR); 672c6266a77SGreg Roach $this->fillYData($years, 0, 1, $x_axis, $z_axis, $ydata); 673c6266a77SGreg Roach $indi[] = $row->d_gid; 674c6266a77SGreg Roach } 675c6266a77SGreg Roach } 676c6266a77SGreg Roach } 677c6266a77SGreg Roach break; 678c6266a77SGreg Roach case self::Z_AXIS_SEX: 679c6266a77SGreg Roach $z_axis = $this->axisSexes(); 680c6266a77SGreg Roach foreach (array_keys($z_axis) as $sex) { 6819219296aSGreg Roach $rows = $statistics->statsMarrAgeQuery($sex); 682c6266a77SGreg Roach $indi = []; 683c6266a77SGreg Roach foreach ($rows as $row) { 6846960d4a1SGreg Roach if (!in_array($row->d_gid, $indi, true)) { 685c6266a77SGreg Roach $years = (int) ($row->age / self::DAYS_IN_YEAR); 686c6266a77SGreg Roach $this->fillYData($years, $sex, 1, $x_axis, $z_axis, $ydata); 687c6266a77SGreg Roach $indi[] = $row->d_gid; 688c6266a77SGreg Roach } 689c6266a77SGreg Roach } 690c6266a77SGreg Roach } 691c6266a77SGreg Roach break; 692c6266a77SGreg Roach case self::Z_AXIS_TIME: 693b6b9dcc9SGreg Roach $boundaries_csv = $params['z-axis-boundaries-periods']; 694c6266a77SGreg Roach $z_axis = $this->axisYears($boundaries_csv); 695afa8d404SGreg Roach // The stats query doesn't have an "all" function, so query M/F separately 696afa8d404SGreg Roach foreach (['M', 'F'] as $sex) { 697c6266a77SGreg Roach $prev_boundary = 0; 698c6266a77SGreg Roach $indi = []; 699c6266a77SGreg Roach foreach (array_keys($z_axis) as $boundary) { 7009219296aSGreg Roach $rows = $statistics->statsMarrAgeQuery($sex, $prev_boundary, $boundary); 701c6266a77SGreg Roach foreach ($rows as $row) { 7026960d4a1SGreg Roach if (!in_array($row->d_gid, $indi, true)) { 703c6266a77SGreg Roach $years = (int) ($row->age / self::DAYS_IN_YEAR); 704c6266a77SGreg Roach $this->fillYData($years, $boundary, 1, $x_axis, $z_axis, $ydata); 705c6266a77SGreg Roach $indi[] = $row->d_gid; 706c6266a77SGreg Roach } 707c6266a77SGreg Roach } 708c6266a77SGreg Roach $prev_boundary = $boundary + 1; 709c6266a77SGreg Roach } 710c6266a77SGreg Roach } 711c6266a77SGreg Roach break; 712c6266a77SGreg Roach default: 713d501c45dSGreg Roach throw new HttpNotFoundException(); 714c6266a77SGreg Roach } 715c6266a77SGreg Roach 7166ccdf4f0SGreg Roach return response($this->myPlot($chart_title, $x_axis, $x_axis_title, $ydata, $y_axis_title, $z_axis, $y_axis_type)); 717c6266a77SGreg Roach 718c6266a77SGreg Roach case self::X_AXIS_NUMBER_OF_CHILDREN: 719c6266a77SGreg Roach $chart_title = I18N::translate('Number of children'); 720c6266a77SGreg Roach $x_axis_title = I18N::translate('Children'); 7216960d4a1SGreg Roach $x_axis = $this->axisNumbers('0,1,2,3,4,5,6,7,8,9,10'); 722c6266a77SGreg Roach 723c6266a77SGreg Roach switch ($y_axis_type) { 724c6266a77SGreg Roach case self::Y_AXIS_NUMBERS: 725c6266a77SGreg Roach $y_axis_title = I18N::translate('Families'); 726c6266a77SGreg Roach break; 727c6266a77SGreg Roach case self::Y_AXIS_PERCENT: 728c6266a77SGreg Roach $y_axis_title = '%'; 729c6266a77SGreg Roach break; 730c6266a77SGreg Roach default: 731d501c45dSGreg Roach throw new HttpNotFoundException(); 732c6266a77SGreg Roach } 733c6266a77SGreg Roach 734c6266a77SGreg Roach switch ($z_axis_type) { 735c6266a77SGreg Roach case self::Z_AXIS_ALL: 736c6266a77SGreg Roach $z_axis = $this->axisAll(); 7379219296aSGreg Roach $rows = $statistics->statsChildrenQuery(); 738c6266a77SGreg Roach foreach ($rows as $row) { 739c6266a77SGreg Roach $this->fillYData($row->f_numchil, 0, $row->total, $x_axis, $z_axis, $ydata); 740c6266a77SGreg Roach } 741c6266a77SGreg Roach break; 742c6266a77SGreg Roach case self::Z_AXIS_TIME: 743b6b9dcc9SGreg Roach $boundaries_csv = $params['z-axis-boundaries-periods']; 744c6266a77SGreg Roach $z_axis = $this->axisYears($boundaries_csv); 745c6266a77SGreg Roach $prev_boundary = 0; 746c6266a77SGreg Roach foreach (array_keys($z_axis) as $boundary) { 747b1126ab4SGreg Roach $rows = $statistics->statsChildrenQuery($prev_boundary, $boundary); 748c6266a77SGreg Roach foreach ($rows as $row) { 749c6266a77SGreg Roach $this->fillYData($row->f_numchil, $boundary, $row->total, $x_axis, $z_axis, $ydata); 750c6266a77SGreg Roach } 751c6266a77SGreg Roach $prev_boundary = $boundary + 1; 752c6266a77SGreg Roach } 753c6266a77SGreg Roach break; 754c6266a77SGreg Roach default: 755d501c45dSGreg Roach throw new HttpNotFoundException(); 756c6266a77SGreg Roach } 757c6266a77SGreg Roach 7586ccdf4f0SGreg Roach return response($this->myPlot($chart_title, $x_axis, $x_axis_title, $ydata, $y_axis_title, $z_axis, $y_axis_type)); 759c6266a77SGreg Roach 760c6266a77SGreg Roach default: 761d501c45dSGreg Roach throw new HttpNotFoundException(); 762c6266a77SGreg Roach break; 763c6266a77SGreg Roach } 764c6266a77SGreg Roach } 765c6266a77SGreg Roach 766c6266a77SGreg Roach /** 767c6266a77SGreg Roach * @return string[] 768c6266a77SGreg Roach */ 769c6266a77SGreg Roach private function axisAll(): array 770c6266a77SGreg Roach { 771c6266a77SGreg Roach return [ 772c6266a77SGreg Roach I18N::translate('Total'), 773c6266a77SGreg Roach ]; 774c6266a77SGreg Roach } 775c6266a77SGreg Roach 776c6266a77SGreg Roach /** 777c6266a77SGreg Roach * @return string[] 778c6266a77SGreg Roach */ 779c6266a77SGreg Roach private function axisSexes(): array 780c6266a77SGreg Roach { 781c6266a77SGreg Roach return [ 782c6266a77SGreg Roach 'M' => I18N::translate('Male'), 783c6266a77SGreg Roach 'F' => I18N::translate('Female'), 784c6266a77SGreg Roach ]; 785c6266a77SGreg Roach } 786c6266a77SGreg Roach 787c6266a77SGreg Roach /** 788c6266a77SGreg Roach * Labels for the X axis 789c6266a77SGreg Roach * 790c6266a77SGreg Roach * @return string[] 791c6266a77SGreg Roach */ 792c6266a77SGreg Roach private function axisMonths(): array 793c6266a77SGreg Roach { 794c6266a77SGreg Roach return [ 795c6266a77SGreg Roach 'JAN' => I18N::translateContext('NOMINATIVE', 'January'), 796c6266a77SGreg Roach 'FEB' => I18N::translateContext('NOMINATIVE', 'February'), 797c6266a77SGreg Roach 'MAR' => I18N::translateContext('NOMINATIVE', 'March'), 798c6266a77SGreg Roach 'APR' => I18N::translateContext('NOMINATIVE', 'April'), 799c6266a77SGreg Roach 'MAY' => I18N::translateContext('NOMINATIVE', 'May'), 800c6266a77SGreg Roach 'JUN' => I18N::translateContext('NOMINATIVE', 'June'), 801c6266a77SGreg Roach 'JUL' => I18N::translateContext('NOMINATIVE', 'July'), 802c6266a77SGreg Roach 'AUG' => I18N::translateContext('NOMINATIVE', 'August'), 803c6266a77SGreg Roach 'SEP' => I18N::translateContext('NOMINATIVE', 'September'), 804c6266a77SGreg Roach 'OCT' => I18N::translateContext('NOMINATIVE', 'October'), 805c6266a77SGreg Roach 'NOV' => I18N::translateContext('NOMINATIVE', 'November'), 806c6266a77SGreg Roach 'DEC' => I18N::translateContext('NOMINATIVE', 'December'), 807c6266a77SGreg Roach ]; 808c6266a77SGreg Roach } 809c6266a77SGreg Roach 810c6266a77SGreg Roach /** 811c6266a77SGreg Roach * Convert a list of N year-boundaries into N+1 year-ranges for the z-axis. 812c6266a77SGreg Roach * 813c6266a77SGreg Roach * @param string $boundaries_csv 814c6266a77SGreg Roach * 815c6266a77SGreg Roach * @return string[] 816c6266a77SGreg Roach */ 817c6266a77SGreg Roach private function axisYears(string $boundaries_csv): array 818c6266a77SGreg Roach { 819c6266a77SGreg Roach $boundaries = explode(',', $boundaries_csv); 820c6266a77SGreg Roach 821c6266a77SGreg Roach $axis = []; 822c6266a77SGreg Roach foreach ($boundaries as $n => $boundary) { 823c6266a77SGreg Roach if ($n === 0) { 824c6266a77SGreg Roach $date = new Date('BEF ' . $boundary); 825c6266a77SGreg Roach } else { 826c6266a77SGreg Roach $date = new Date('BET ' . $boundaries[$n - 1] . ' AND ' . ($boundary - 1)); 827c6266a77SGreg Roach } 828c6266a77SGreg Roach $axis[$boundary - 1] = strip_tags($date->display()); 829c6266a77SGreg Roach } 830c6266a77SGreg Roach 831c6266a77SGreg Roach $date = new Date('AFT ' . $boundaries[count($boundaries) - 1]); 832c6266a77SGreg Roach $axis[PHP_INT_MAX] = strip_tags($date->display()); 833c6266a77SGreg Roach 834c6266a77SGreg Roach return $axis; 835c6266a77SGreg Roach } 836c6266a77SGreg Roach 837c6266a77SGreg Roach /** 838c6266a77SGreg Roach * Create the X axis. 839c6266a77SGreg Roach * 840c6266a77SGreg Roach * @param string $boundaries_csv 841c6266a77SGreg Roach * 842fc26b4f6SGreg Roach * @return array<string> 843c6266a77SGreg Roach */ 844c6266a77SGreg Roach private function axisNumbers(string $boundaries_csv): array 845c6266a77SGreg Roach { 846c6266a77SGreg Roach $boundaries = explode(',', $boundaries_csv); 847c6266a77SGreg Roach 8480b5fd0a6SGreg Roach $boundaries = array_map(static function (string $x): int { 849c6266a77SGreg Roach return (int) $x; 850c6266a77SGreg Roach }, $boundaries); 851c6266a77SGreg Roach 852c6266a77SGreg Roach $axis = []; 853c6266a77SGreg Roach foreach ($boundaries as $n => $boundary) { 854c6266a77SGreg Roach if ($n === 0) { 8556960d4a1SGreg Roach $prev_boundary = 0; 8566960d4a1SGreg Roach } else { 8576960d4a1SGreg Roach $prev_boundary = $boundaries[$n - 1] + 1; 8586960d4a1SGreg Roach } 8596960d4a1SGreg Roach 8606960d4a1SGreg Roach if ($prev_boundary === $boundary) { 861c6266a77SGreg Roach /* I18N: A range of numbers */ 8626960d4a1SGreg Roach $axis[$boundary] = I18N::number($boundary); 863c6266a77SGreg Roach } else { 864c6266a77SGreg Roach /* I18N: A range of numbers */ 8656960d4a1SGreg Roach $axis[$boundary] = I18N::translate('%1$s–%2$s', I18N::number($prev_boundary), I18N::number($boundary)); 866c6266a77SGreg Roach } 867c6266a77SGreg Roach } 868c6266a77SGreg Roach 869c6266a77SGreg Roach /* I18N: Label on a graph; 40+ means 40 or more */ 870c6266a77SGreg Roach $axis[PHP_INT_MAX] = I18N::translate('%s+', I18N::number($boundaries[count($boundaries) - 1])); 871c6266a77SGreg Roach 872c6266a77SGreg Roach return $axis; 873c6266a77SGreg Roach } 874c6266a77SGreg Roach 875c6266a77SGreg Roach /** 876c6266a77SGreg Roach * Calculate the Y axis. 877c6266a77SGreg Roach * 878c6266a77SGreg Roach * @param int|string $x 879c6266a77SGreg Roach * @param int|string $z 880c6266a77SGreg Roach * @param int|string $value 881c6266a77SGreg Roach * @param array $x_axis 882c6266a77SGreg Roach * @param array $z_axis 883c6266a77SGreg Roach * @param int[][] $ydata 884c6266a77SGreg Roach * 885c6266a77SGreg Roach * @return void 886c6266a77SGreg Roach */ 887e364afe4SGreg Roach private function fillYData($x, $z, $value, array $x_axis, array $z_axis, array &$ydata): void 888c6266a77SGreg Roach { 889c6266a77SGreg Roach $x = $this->findAxisEntry($x, $x_axis); 890c6266a77SGreg Roach $z = $this->findAxisEntry($z, $z_axis); 891c6266a77SGreg Roach 8926960d4a1SGreg Roach if (!array_key_exists($z, $z_axis)) { 893c6266a77SGreg Roach foreach (array_keys($z_axis) as $key) { 894c6266a77SGreg Roach if ($value <= $key) { 895c6266a77SGreg Roach $z = $key; 896c6266a77SGreg Roach break; 897c6266a77SGreg Roach } 898c6266a77SGreg Roach } 899c6266a77SGreg Roach } 900c6266a77SGreg Roach 901c6266a77SGreg Roach // Add the value to the appropriate data point. 902c6266a77SGreg Roach $ydata[$z][$x] = ($ydata[$z][$x] ?? 0) + $value; 903c6266a77SGreg Roach } 904c6266a77SGreg Roach 905c6266a77SGreg Roach /** 906c6266a77SGreg Roach * Find the axis entry for a given value. 907c6266a77SGreg Roach * Some are direct lookup (e.g. M/F, JAN/FEB/MAR). 908d823340dSGreg Roach * Others need to find the appropriate range. 909c6266a77SGreg Roach * 910c6266a77SGreg Roach * @param int|float|string $value 911c6266a77SGreg Roach * @param string[] $axis 912c6266a77SGreg Roach * 913c6266a77SGreg Roach * @return int|string 914c6266a77SGreg Roach */ 915c6266a77SGreg Roach private function findAxisEntry($value, $axis) 916c6266a77SGreg Roach { 917c6266a77SGreg Roach if (is_numeric($value)) { 918c6266a77SGreg Roach $value = (int) $value; 919c6266a77SGreg Roach 9206960d4a1SGreg Roach if (!array_key_exists($value, $axis)) { 921c6266a77SGreg Roach foreach (array_keys($axis) as $boundary) { 922c6266a77SGreg Roach if ($value <= $boundary) { 923c6266a77SGreg Roach $value = $boundary; 924c6266a77SGreg Roach break; 925c6266a77SGreg Roach } 926c6266a77SGreg Roach } 927c6266a77SGreg Roach } 928c6266a77SGreg Roach } 929c6266a77SGreg Roach 930c6266a77SGreg Roach return $value; 931c6266a77SGreg Roach } 932c6266a77SGreg Roach 933c6266a77SGreg Roach /** 934c6266a77SGreg Roach * Plot the data. 935c6266a77SGreg Roach * 936c6266a77SGreg Roach * @param string $chart_title 937c6266a77SGreg Roach * @param string[] $x_axis 938c6266a77SGreg Roach * @param string $x_axis_title 939c6266a77SGreg Roach * @param int[][] $ydata 940c6266a77SGreg Roach * @param string $y_axis_title 941c6266a77SGreg Roach * @param string[] $z_axis 942c6266a77SGreg Roach * @param int $y_axis_type 943c6266a77SGreg Roach * 944c6266a77SGreg Roach * @return string 945c6266a77SGreg Roach */ 946a81e5019SRico Sonntag private function myPlot( 947a81e5019SRico Sonntag string $chart_title, 948a81e5019SRico Sonntag array $x_axis, 949a81e5019SRico Sonntag string $x_axis_title, 950a81e5019SRico Sonntag array $ydata, 951a81e5019SRico Sonntag string $y_axis_title, 952a81e5019SRico Sonntag array $z_axis, 953a81e5019SRico Sonntag int $y_axis_type 954a81e5019SRico Sonntag ): string { 9556960d4a1SGreg Roach if (!count($ydata)) { 956a81e5019SRico Sonntag return I18N::translate('This information is not available.'); 957c6266a77SGreg Roach } 958c6266a77SGreg Roach 959c6266a77SGreg Roach // Colors for z-axis 960c6266a77SGreg Roach $colors = []; 961c6266a77SGreg Roach $index = 0; 9626960d4a1SGreg Roach while (count($colors) < count($ydata)) { 963c6266a77SGreg Roach $colors[] = self::Z_AXIS_COLORS[$index]; 9646960d4a1SGreg Roach $index = ($index + 1) % count(self::Z_AXIS_COLORS); 965c6266a77SGreg Roach } 966c6266a77SGreg Roach 967c6266a77SGreg Roach // Convert our sparse dataset into a fixed-size array 968c6266a77SGreg Roach $tmp = []; 969c6266a77SGreg Roach foreach (array_keys($z_axis) as $z) { 970c6266a77SGreg Roach foreach (array_keys($x_axis) as $x) { 971c6266a77SGreg Roach $tmp[$z][$x] = $ydata[$z][$x] ?? 0; 972c6266a77SGreg Roach } 973c6266a77SGreg Roach } 974c6266a77SGreg Roach $ydata = $tmp; 975c6266a77SGreg Roach 976a81e5019SRico Sonntag // Convert the chart data to percentage 977c6266a77SGreg Roach if ($y_axis_type === self::Y_AXIS_PERCENT) { 978c6266a77SGreg Roach // Normalise each (non-zero!) set of data to total 100% 9790b5fd0a6SGreg Roach array_walk($ydata, static function (array &$x) { 980c6266a77SGreg Roach $sum = array_sum($x); 981c6266a77SGreg Roach if ($sum > 0) { 9820b5fd0a6SGreg Roach $x = array_map(static function ($y) use ($sum) { 983c6266a77SGreg Roach return $y * 100.0 / $sum; 984c6266a77SGreg Roach }, $x); 985c6266a77SGreg Roach } 986c6266a77SGreg Roach }); 987c6266a77SGreg Roach } 988c6266a77SGreg Roach 989a81e5019SRico Sonntag $data = [ 990a81e5019SRico Sonntag array_merge( 991a81e5019SRico Sonntag [I18N::translate('Century')], 992a81e5019SRico Sonntag array_values($z_axis) 99371378461SGreg Roach ), 994c6266a77SGreg Roach ]; 995c6266a77SGreg Roach 996a81e5019SRico Sonntag $intermediate = []; 997a81e5019SRico Sonntag foreach ($ydata as $century => $months) { 998a81e5019SRico Sonntag foreach ($months as $month => $value) { 999a81e5019SRico Sonntag $intermediate[$month][] = [ 1000a81e5019SRico Sonntag 'v' => $value, 1001a81e5019SRico Sonntag 'f' => ($y_axis_type === self::Y_AXIS_PERCENT) ? sprintf('%.1f%%', $value) : $value, 1002a81e5019SRico Sonntag ]; 1003a81e5019SRico Sonntag } 1004c6266a77SGreg Roach } 1005c6266a77SGreg Roach 1006a81e5019SRico Sonntag foreach ($intermediate as $key => $values) { 1007a81e5019SRico Sonntag $data[] = array_merge( 1008a81e5019SRico Sonntag [$x_axis[$key]], 1009a81e5019SRico Sonntag $values 1010a81e5019SRico Sonntag ); 1011a81e5019SRico Sonntag } 1012c6266a77SGreg Roach 1013a81e5019SRico Sonntag $chart_options = [ 1014a81e5019SRico Sonntag 'title' => '', 1015a81e5019SRico Sonntag 'subtitle' => '', 1016a81e5019SRico Sonntag 'height' => 400, 1017a81e5019SRico Sonntag 'width' => '100%', 1018a81e5019SRico Sonntag 'legend' => [ 10196960d4a1SGreg Roach 'position' => count($z_axis) > 1 ? 'right' : 'none', 1020a81e5019SRico Sonntag 'alignment' => 'center', 1021a81e5019SRico Sonntag ], 1022a81e5019SRico Sonntag 'tooltip' => [ 1023a81e5019SRico Sonntag 'format' => '\'%\'', 1024a81e5019SRico Sonntag ], 1025a81e5019SRico Sonntag 'vAxis' => [ 1026a81e5019SRico Sonntag 'title' => $y_axis_title ?? '', 1027a81e5019SRico Sonntag ], 1028a81e5019SRico Sonntag 'hAxis' => [ 1029a81e5019SRico Sonntag 'title' => $x_axis_title ?? '', 1030a81e5019SRico Sonntag ], 1031a81e5019SRico Sonntag 'colors' => $colors, 1032a81e5019SRico Sonntag ]; 1033a81e5019SRico Sonntag 103490a2f718SGreg Roach return view('statistics/other/charts/custom', [ 1035a81e5019SRico Sonntag 'data' => $data, 1036a81e5019SRico Sonntag 'chart_options' => $chart_options, 1037a81e5019SRico Sonntag 'chart_title' => $chart_title, 103865cf5706SGreg Roach 'language' => I18N::languageTag(), 103990a2f718SGreg Roach ]); 1040c6266a77SGreg Roach } 1041168ff6f3Sric2016} 1042