1168ff6f3Sric2016<?php 23976b470SGreg Roach 3168ff6f3Sric2016/** 4168ff6f3Sric2016 * webtrees: online genealogy 58fcd0d32SGreg Roach * Copyright (C) 2019 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 15168ff6f3Sric2016 * along with this program. If not, see <http://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 // We generate a bitmap chart with these dimensions in image pixels. 56c6266a77SGreg Roach // These set the aspect ratio. The actual image is sized using CSS 57c6266a77SGreg Roach // The maximum size (width x height) is 300,000 58c6266a77SGreg Roach private const CHART_WIDTH = 950; 59c6266a77SGreg Roach private const CHART_HEIGHT = 315; 60c6266a77SGreg Roach 61c6266a77SGreg Roach public const X_AXIS_INDIVIDUAL_MAP = 1; 62c6266a77SGreg Roach public const X_AXIS_BIRTH_MAP = 2; 63c6266a77SGreg Roach public const X_AXIS_DEATH_MAP = 3; 64c6266a77SGreg Roach public const X_AXIS_MARRIAGE_MAP = 4; 65c6266a77SGreg Roach public const X_AXIS_BIRTH_MONTH = 11; 66c6266a77SGreg Roach public const X_AXIS_DEATH_MONTH = 12; 67c6266a77SGreg Roach public const X_AXIS_MARRIAGE_MONTH = 13; 68c6266a77SGreg Roach public const X_AXIS_FIRST_CHILD_MONTH = 14; 69c6266a77SGreg Roach public const X_AXIS_FIRST_MARRIAGE_MONTH = 15; 70c6266a77SGreg Roach public const X_AXIS_AGE_AT_DEATH = 18; 71c6266a77SGreg Roach public const X_AXIS_AGE_AT_MARRIAGE = 19; 72c6266a77SGreg Roach public const X_AXIS_AGE_AT_FIRST_MARRIAGE = 20; 73c6266a77SGreg Roach public const X_AXIS_NUMBER_OF_CHILDREN = 21; 74c6266a77SGreg Roach 75c6266a77SGreg Roach public const Y_AXIS_NUMBERS = 201; 76c6266a77SGreg Roach public const Y_AXIS_PERCENT = 202; 77c6266a77SGreg Roach 78c6266a77SGreg Roach public const Z_AXIS_ALL = 300; 79c6266a77SGreg Roach public const Z_AXIS_SEX = 301; 80c6266a77SGreg Roach public const Z_AXIS_TIME = 302; 81c6266a77SGreg Roach 82c6266a77SGreg Roach // First two colors are blue/pink, to work with Z_AXIS_SEX. 83c6266a77SGreg Roach private const Z_AXIS_COLORS = ['0000FF', 'FFA0CB', '9F00FF', 'FF7000', '905030', 'FF0000', '00FF00', 'F0F000']; 84c6266a77SGreg Roach 85c6266a77SGreg Roach private const DAYS_IN_YEAR = 365.25; 86c6266a77SGreg Roach 87168ff6f3Sric2016 /** 880cfd6963SGreg Roach * How should this module be identified in the control panel, etc.? 89168ff6f3Sric2016 * 90168ff6f3Sric2016 * @return string 91168ff6f3Sric2016 */ 9249a243cbSGreg Roach public function title(): string 93c1010edaSGreg Roach { 94bbb76c12SGreg Roach /* I18N: Name of a module/chart */ 95bbb76c12SGreg Roach return I18N::translate('Statistics'); 96168ff6f3Sric2016 } 97168ff6f3Sric2016 98168ff6f3Sric2016 /** 99168ff6f3Sric2016 * A sentence describing what this module does. 100168ff6f3Sric2016 * 101168ff6f3Sric2016 * @return string 102168ff6f3Sric2016 */ 10349a243cbSGreg Roach public function description(): string 104c1010edaSGreg Roach { 105bbb76c12SGreg Roach /* I18N: Description of the “StatisticsChart” module */ 106bbb76c12SGreg Roach return I18N::translate('Various statistics charts.'); 107168ff6f3Sric2016 } 108168ff6f3Sric2016 109168ff6f3Sric2016 /** 110377a2979SGreg Roach * CSS class for the URL. 111377a2979SGreg Roach * 112377a2979SGreg Roach * @return string 113377a2979SGreg Roach */ 114377a2979SGreg Roach public function chartMenuClass(): string 115377a2979SGreg Roach { 116377a2979SGreg Roach return 'menu-chart-statistics'; 117377a2979SGreg Roach } 118377a2979SGreg Roach 119377a2979SGreg Roach /** 120e6562982SGreg Roach * The URL for this chart. 121168ff6f3Sric2016 * 12260bc3e3fSGreg Roach * @param Individual $individual 12359597b37SGreg Roach * @param mixed[] $parameters 12460bc3e3fSGreg Roach * 125e6562982SGreg Roach * @return string 126168ff6f3Sric2016 */ 127e6562982SGreg Roach public function chartUrl(Individual $individual, array $parameters = []): string 128c1010edaSGreg Roach { 129c6266a77SGreg Roach return route('module', [ 130c6266a77SGreg Roach 'module' => $this->name(), 131c6266a77SGreg Roach 'action' => 'Chart', 132d72b284aSGreg Roach 'tree' => $individual->tree()->name(), 133e6562982SGreg Roach ] + $parameters); 134168ff6f3Sric2016 } 135c6266a77SGreg Roach 136c6266a77SGreg Roach /** 137c6266a77SGreg Roach * A form to request the chart parameters. 138c6266a77SGreg Roach * 13957ab2231SGreg Roach * @param ServerRequestInterface $request 140c6266a77SGreg Roach * 1416ccdf4f0SGreg Roach * @return ResponseInterface 142c6266a77SGreg Roach */ 14357ab2231SGreg Roach public function getChartAction(ServerRequestInterface $request): ResponseInterface 144c6266a77SGreg Roach { 14557ab2231SGreg Roach $tree = $request->getAttribute('tree'); 14675964c75SGreg Roach assert($tree instanceof Tree); 1475229eadeSGreg Roach 14857ab2231SGreg Roach $user = $request->getAttribute('user'); 14957ab2231SGreg Roach 1509867b2f0SGreg Roach Auth::checkComponentAccess($this, 'chart', $tree, $user); 1519867b2f0SGreg Roach 152c6266a77SGreg Roach $tabs = [ 153c6266a77SGreg Roach I18N::translate('Individuals') => route('module', [ 154c6266a77SGreg Roach 'module' => $this->name(), 155c6266a77SGreg Roach 'action' => 'Individuals', 156d72b284aSGreg Roach 'tree' => $tree->name(), 157c6266a77SGreg Roach ]), 158c6266a77SGreg Roach I18N::translate('Families') => route('module', [ 159c6266a77SGreg Roach 'module' => $this->name(), 160c6266a77SGreg Roach 'action' => 'Families', 161d72b284aSGreg Roach 'tree' => $tree->name(), 162c6266a77SGreg Roach ]), 163c6266a77SGreg Roach I18N::translate('Other') => route('module', [ 164c6266a77SGreg Roach 'module' => $this->name(), 165c6266a77SGreg Roach 'action' => 'Other', 166d72b284aSGreg Roach 'tree' => $tree->name(), 167c6266a77SGreg Roach ]), 168c6266a77SGreg Roach I18N::translate('Custom') => route('module', [ 169c6266a77SGreg Roach 'module' => $this->name(), 170c6266a77SGreg Roach 'action' => 'Custom', 171d72b284aSGreg Roach 'tree' => $tree->name(), 172c6266a77SGreg Roach ]), 173c6266a77SGreg Roach ]; 174c6266a77SGreg Roach 1759b5537c3SGreg Roach return $this->viewResponse('modules/statistics-chart/page', [ 17671378461SGreg Roach 'module' => $this->name(), 177c6266a77SGreg Roach 'tabs' => $tabs, 178c6266a77SGreg Roach 'title' => $this->title(), 179ef5d23f1SGreg Roach 'tree' => $tree, 180c6266a77SGreg Roach ]); 181c6266a77SGreg Roach } 182c6266a77SGreg Roach 183c6266a77SGreg Roach /** 18457ab2231SGreg Roach * @param ServerRequestInterface $request 185c6266a77SGreg Roach * 1866ccdf4f0SGreg Roach * @return ResponseInterface 187c6266a77SGreg Roach */ 18857ab2231SGreg Roach public function getIndividualsAction(ServerRequestInterface $request): ResponseInterface 189c6266a77SGreg Roach { 190b6c326d8SGreg Roach $this->layout = 'layouts/ajax'; 191b6c326d8SGreg Roach 192b6c326d8SGreg Roach return $this->viewResponse('modules/statistics-chart/individuals', [ 193c6266a77SGreg Roach 'show_oldest_living' => Auth::check(), 19457ab2231SGreg Roach 'stats' => app(Statistics::class), 195c6266a77SGreg Roach ]); 196c6266a77SGreg Roach } 197c6266a77SGreg Roach 198c6266a77SGreg Roach /** 19957ab2231SGreg Roach * @param ServerRequestInterface $request 200c6266a77SGreg Roach * 2016ccdf4f0SGreg Roach * @return ResponseInterface 202c6266a77SGreg Roach */ 20357ab2231SGreg Roach public function getFamiliesAction(ServerRequestInterface $request): ResponseInterface 204c6266a77SGreg Roach { 205b6c326d8SGreg Roach $this->layout = 'layouts/ajax'; 206b6c326d8SGreg Roach 207b6c326d8SGreg Roach return $this->viewResponse('modules/statistics-chart/families', [ 20857ab2231SGreg Roach 'stats' => app(Statistics::class), 209c6266a77SGreg Roach ]); 210c6266a77SGreg Roach } 211c6266a77SGreg Roach 212c6266a77SGreg Roach /** 21357ab2231SGreg Roach * @param ServerRequestInterface $request 214c6266a77SGreg Roach * 2156ccdf4f0SGreg Roach * @return ResponseInterface 216c6266a77SGreg Roach */ 21757ab2231SGreg Roach public function getOtherAction(ServerRequestInterface $request): ResponseInterface 218c6266a77SGreg Roach { 219b6c326d8SGreg Roach $this->layout = 'layouts/ajax'; 220b6c326d8SGreg Roach 221b6c326d8SGreg Roach return $this->viewResponse('modules/statistics-chart/other', [ 22257ab2231SGreg Roach 'stats' => app(Statistics::class), 223c6266a77SGreg Roach ]); 224c6266a77SGreg Roach } 225c6266a77SGreg Roach 226c6266a77SGreg Roach /** 22757ab2231SGreg Roach * @param ServerRequestInterface $request 228c6266a77SGreg Roach * 2296ccdf4f0SGreg Roach * @return ResponseInterface 230c6266a77SGreg Roach */ 23157ab2231SGreg Roach public function getCustomAction(ServerRequestInterface $request): ResponseInterface 232c6266a77SGreg Roach { 233b6c326d8SGreg Roach $this->layout = 'layouts/ajax'; 234b6c326d8SGreg Roach 23557ab2231SGreg Roach $tree = $request->getAttribute('tree'); 23675964c75SGreg Roach assert($tree instanceof Tree); 23757ab2231SGreg Roach 238b6c326d8SGreg Roach return $this->viewResponse('modules/statistics-chart/custom', [ 239c6266a77SGreg Roach 'module' => $this, 240c6266a77SGreg Roach 'tree' => $tree, 241c6266a77SGreg Roach ]); 242c6266a77SGreg Roach } 243c6266a77SGreg Roach 244c6266a77SGreg Roach /** 2456ccdf4f0SGreg Roach * @param ServerRequestInterface $request 246c6266a77SGreg Roach * 2476ccdf4f0SGreg Roach * @return ResponseInterface 248c6266a77SGreg Roach */ 2493aa29b97SGreg Roach public function postCustomChartAction(ServerRequestInterface $request): ResponseInterface 250c6266a77SGreg Roach { 25157ab2231SGreg Roach $statistics = app(Statistics::class); 25257ab2231SGreg Roach 253*b46c87bdSGreg Roach $params = (array) $request->getParsedBody(); 254b6b9dcc9SGreg Roach 255b6b9dcc9SGreg Roach $x_axis_type = (int) $params['x-as']; 256b6b9dcc9SGreg Roach $y_axis_type = (int) $params['y-as']; 257b6b9dcc9SGreg Roach $z_axis_type = (int) $params['z-as']; 258c6266a77SGreg Roach $ydata = []; 259c6266a77SGreg Roach 260c6266a77SGreg Roach switch ($x_axis_type) { 261c6266a77SGreg Roach case self::X_AXIS_INDIVIDUAL_MAP: 2626ccdf4f0SGreg Roach return response($statistics->chartDistribution( 263b6b9dcc9SGreg Roach $params['chart_shows'], 264b6b9dcc9SGreg Roach $params['chart_type'], 265b6b9dcc9SGreg Roach $params['SURN'] 266c6266a77SGreg Roach )); 267c6266a77SGreg Roach 268c6266a77SGreg Roach case self::X_AXIS_BIRTH_MAP: 2696ccdf4f0SGreg Roach return response($statistics->chartDistribution( 270b6b9dcc9SGreg Roach $params['chart_shows'], 271c6266a77SGreg Roach 'birth_distribution_chart' 272c6266a77SGreg Roach )); 273c6266a77SGreg Roach 274c6266a77SGreg Roach case self::X_AXIS_DEATH_MAP: 2756ccdf4f0SGreg Roach return response($statistics->chartDistribution( 276b6b9dcc9SGreg Roach $params['chart_shows'], 277c6266a77SGreg Roach 'death_distribution_chart' 278c6266a77SGreg Roach )); 279c6266a77SGreg Roach 280c6266a77SGreg Roach case self::X_AXIS_MARRIAGE_MAP: 2816ccdf4f0SGreg Roach return response($statistics->chartDistribution( 282b6b9dcc9SGreg Roach $params['chart_shows'], 283c6266a77SGreg Roach 'marriage_distribution_chart' 284c6266a77SGreg Roach )); 285c6266a77SGreg Roach 286c6266a77SGreg Roach case self::X_AXIS_BIRTH_MONTH: 287c6266a77SGreg Roach $chart_title = I18N::translate('Month of birth'); 288c6266a77SGreg Roach $x_axis_title = I18N::translate('Month'); 289c6266a77SGreg Roach $x_axis = $this->axisMonths(); 290c6266a77SGreg Roach 291c6266a77SGreg Roach switch ($y_axis_type) { 292c6266a77SGreg Roach case self::Y_AXIS_NUMBERS: 293c6266a77SGreg Roach $y_axis_title = I18N::translate('Individuals'); 294c6266a77SGreg Roach break; 295c6266a77SGreg Roach case self::Y_AXIS_PERCENT: 296c6266a77SGreg Roach $y_axis_title = '%'; 297c6266a77SGreg Roach break; 298c6266a77SGreg Roach default: 299d501c45dSGreg Roach throw new HttpNotFoundException(); 300c6266a77SGreg Roach } 301c6266a77SGreg Roach 302c6266a77SGreg Roach switch ($z_axis_type) { 303c6266a77SGreg Roach case self::Z_AXIS_ALL: 304c6266a77SGreg Roach $z_axis = $this->axisAll(); 305cde1d378SGreg Roach $rows = $statistics->statsBirthQuery()->get(); 306c6266a77SGreg Roach foreach ($rows as $row) { 307c6266a77SGreg Roach $this->fillYData($row->d_month, 0, $row->total, $x_axis, $z_axis, $ydata); 308c6266a77SGreg Roach } 309c6266a77SGreg Roach break; 310c6266a77SGreg Roach case self::Z_AXIS_SEX: 311c6266a77SGreg Roach $z_axis = $this->axisSexes(); 312cde1d378SGreg Roach $rows = $statistics->statsBirthBySexQuery()->get(); 313c6266a77SGreg Roach foreach ($rows as $row) { 314c6266a77SGreg Roach $this->fillYData($row->d_month, $row->i_sex, $row->total, $x_axis, $z_axis, $ydata); 315c6266a77SGreg Roach } 316c6266a77SGreg Roach break; 317c6266a77SGreg Roach case self::Z_AXIS_TIME: 318b6b9dcc9SGreg Roach $boundaries_csv = $params['z-axis-boundaries-periods']; 319c6266a77SGreg Roach $z_axis = $this->axisYears($boundaries_csv); 320c6266a77SGreg Roach $prev_boundary = 0; 321c6266a77SGreg Roach foreach (array_keys($z_axis) as $boundary) { 322cde1d378SGreg Roach $rows = $statistics->statsBirthQuery($prev_boundary, $boundary)->get(); 323c6266a77SGreg Roach foreach ($rows as $row) { 324c6266a77SGreg Roach $this->fillYData($row->d_month, $boundary, $row->total, $x_axis, $z_axis, $ydata); 325c6266a77SGreg Roach } 326c6266a77SGreg Roach $prev_boundary = $boundary + 1; 327c6266a77SGreg Roach } 328c6266a77SGreg Roach break; 329c6266a77SGreg Roach default: 330d501c45dSGreg Roach throw new HttpNotFoundException(); 331c6266a77SGreg Roach } 332c6266a77SGreg Roach 3336ccdf4f0SGreg Roach return response($this->myPlot($chart_title, $x_axis, $x_axis_title, $ydata, $y_axis_title, $z_axis, $y_axis_type)); 334c6266a77SGreg Roach 335c6266a77SGreg Roach case self::X_AXIS_DEATH_MONTH: 336c6266a77SGreg Roach $chart_title = I18N::translate('Month of death'); 337c6266a77SGreg Roach $x_axis_title = I18N::translate('Month'); 338c6266a77SGreg Roach $x_axis = $this->axisMonths(); 339c6266a77SGreg Roach 340c6266a77SGreg Roach switch ($y_axis_type) { 341c6266a77SGreg Roach case self::Y_AXIS_NUMBERS: 342c6266a77SGreg Roach $y_axis_title = I18N::translate('Individuals'); 343c6266a77SGreg Roach break; 344c6266a77SGreg Roach case self::Y_AXIS_PERCENT: 345c6266a77SGreg Roach $y_axis_title = '%'; 346c6266a77SGreg Roach break; 347c6266a77SGreg Roach default: 348d501c45dSGreg Roach throw new HttpNotFoundException(); 349c6266a77SGreg Roach } 350c6266a77SGreg Roach 351c6266a77SGreg Roach switch ($z_axis_type) { 352c6266a77SGreg Roach case self::Z_AXIS_ALL: 353c6266a77SGreg Roach $z_axis = $this->axisAll(); 354cde1d378SGreg Roach $rows = $statistics->statsDeathQuery()->get(); 355c6266a77SGreg Roach foreach ($rows as $row) { 356c6266a77SGreg Roach $this->fillYData($row->d_month, 0, $row->total, $x_axis, $z_axis, $ydata); 357c6266a77SGreg Roach } 358c6266a77SGreg Roach break; 359c6266a77SGreg Roach case self::Z_AXIS_SEX: 360c6266a77SGreg Roach $z_axis = $this->axisSexes(); 361cde1d378SGreg Roach $rows = $statistics->statsDeathBySexQuery()->get(); 362c6266a77SGreg Roach foreach ($rows as $row) { 363c6266a77SGreg Roach $this->fillYData($row->d_month, $row->i_sex, $row->total, $x_axis, $z_axis, $ydata); 364c6266a77SGreg Roach } 365c6266a77SGreg Roach break; 366c6266a77SGreg Roach case self::Z_AXIS_TIME: 367b6b9dcc9SGreg Roach $boundaries_csv = $params['z-axis-boundaries-periods']; 368c6266a77SGreg Roach $z_axis = $this->axisYears($boundaries_csv); 369c6266a77SGreg Roach $prev_boundary = 0; 370c6266a77SGreg Roach foreach (array_keys($z_axis) as $boundary) { 371cde1d378SGreg Roach $rows = $statistics->statsDeathQuery($prev_boundary, $boundary)->get(); 372c6266a77SGreg Roach foreach ($rows as $row) { 373c6266a77SGreg Roach $this->fillYData($row->d_month, $boundary, $row->total, $x_axis, $z_axis, $ydata); 374c6266a77SGreg Roach } 375c6266a77SGreg Roach $prev_boundary = $boundary + 1; 376c6266a77SGreg Roach } 377c6266a77SGreg Roach break; 378c6266a77SGreg Roach default: 379d501c45dSGreg Roach throw new HttpNotFoundException(); 380c6266a77SGreg Roach } 381c6266a77SGreg Roach 3826ccdf4f0SGreg Roach return response($this->myPlot($chart_title, $x_axis, $x_axis_title, $ydata, $y_axis_title, $z_axis, $y_axis_type)); 383c6266a77SGreg Roach 384c6266a77SGreg Roach case self::X_AXIS_MARRIAGE_MONTH: 385c6266a77SGreg Roach $chart_title = I18N::translate('Month of marriage'); 386c6266a77SGreg Roach $x_axis_title = I18N::translate('Month'); 387c6266a77SGreg Roach $x_axis = $this->axisMonths(); 388c6266a77SGreg Roach 389c6266a77SGreg Roach switch ($y_axis_type) { 390c6266a77SGreg Roach case self::Y_AXIS_NUMBERS: 391c6266a77SGreg Roach $y_axis_title = I18N::translate('Families'); 392c6266a77SGreg Roach break; 393c6266a77SGreg Roach case self::Y_AXIS_PERCENT: 394c6266a77SGreg Roach $y_axis_title = '%'; 395c6266a77SGreg Roach break; 396c6266a77SGreg Roach default: 397d501c45dSGreg Roach throw new HttpNotFoundException(); 398c6266a77SGreg Roach } 399c6266a77SGreg Roach 400c6266a77SGreg Roach switch ($z_axis_type) { 401c6266a77SGreg Roach case self::Z_AXIS_ALL: 402c6266a77SGreg Roach $z_axis = $this->axisAll(); 403e6f3d5e2SGreg Roach $rows = $statistics->statsMarriageQuery()->get(); 404c6266a77SGreg Roach foreach ($rows as $row) { 405c6266a77SGreg Roach $this->fillYData($row->d_month, 0, $row->total, $x_axis, $z_axis, $ydata); 406c6266a77SGreg Roach } 407c6266a77SGreg Roach break; 408c6266a77SGreg Roach case self::Z_AXIS_TIME: 409b6b9dcc9SGreg Roach $boundaries_csv = $params['z-axis-boundaries-periods']; 410c6266a77SGreg Roach $z_axis = $this->axisYears($boundaries_csv); 411c6266a77SGreg Roach $prev_boundary = 0; 412c6266a77SGreg Roach foreach (array_keys($z_axis) as $boundary) { 413e6f3d5e2SGreg Roach $rows = $statistics->statsMarriageQuery($prev_boundary, $boundary)->get(); 414c6266a77SGreg Roach foreach ($rows as $row) { 415c6266a77SGreg Roach $this->fillYData($row->d_month, $boundary, $row->total, $x_axis, $z_axis, $ydata); 416c6266a77SGreg Roach } 417c6266a77SGreg Roach $prev_boundary = $boundary + 1; 418c6266a77SGreg Roach } 419c6266a77SGreg Roach break; 420c6266a77SGreg Roach default: 421d501c45dSGreg Roach throw new HttpNotFoundException(); 422c6266a77SGreg Roach } 423c6266a77SGreg Roach 4246ccdf4f0SGreg Roach return response($this->myPlot($chart_title, $x_axis, $x_axis_title, $ydata, $y_axis_title, $z_axis, $y_axis_type)); 425c6266a77SGreg Roach 426c6266a77SGreg Roach case self::X_AXIS_FIRST_CHILD_MONTH: 427c6266a77SGreg Roach $chart_title = I18N::translate('Month of birth of first child in a relation'); 428c6266a77SGreg Roach $x_axis_title = I18N::translate('Month'); 429c6266a77SGreg Roach $x_axis = $this->axisMonths(); 430c6266a77SGreg Roach 431c6266a77SGreg Roach switch ($y_axis_type) { 432c6266a77SGreg Roach case self::Y_AXIS_NUMBERS: 433c6266a77SGreg Roach $y_axis_title = I18N::translate('Children'); 434c6266a77SGreg Roach break; 435c6266a77SGreg Roach case self::Y_AXIS_PERCENT: 436c6266a77SGreg Roach $y_axis_title = '%'; 437c6266a77SGreg Roach break; 438c6266a77SGreg Roach default: 439d501c45dSGreg Roach throw new HttpNotFoundException(); 440c6266a77SGreg Roach } 441c6266a77SGreg Roach 442c6266a77SGreg Roach switch ($z_axis_type) { 443c6266a77SGreg Roach case self::Z_AXIS_ALL: 444c6266a77SGreg Roach $z_axis = $this->axisAll(); 445999da590SGreg Roach $rows = $statistics->monthFirstChildQuery()->get(); 446c6266a77SGreg Roach foreach ($rows as $row) { 447c6266a77SGreg Roach $this->fillYData($row->d_month, 0, $row->total, $x_axis, $z_axis, $ydata); 448c6266a77SGreg Roach } 449c6266a77SGreg Roach break; 450c6266a77SGreg Roach case self::Z_AXIS_SEX: 451c6266a77SGreg Roach $z_axis = $this->axisSexes(); 452999da590SGreg Roach $rows = $statistics->monthFirstChildBySexQuery()->get(); 453c6266a77SGreg Roach foreach ($rows as $row) { 454c6266a77SGreg Roach $this->fillYData($row->d_month, $row->i_sex, $row->total, $x_axis, $z_axis, $ydata); 455c6266a77SGreg Roach } 456c6266a77SGreg Roach break; 457c6266a77SGreg Roach case self::Z_AXIS_TIME: 458b6b9dcc9SGreg Roach $boundaries_csv = $params['z-axis-boundaries-periods']; 459c6266a77SGreg Roach $z_axis = $this->axisYears($boundaries_csv); 460c6266a77SGreg Roach $prev_boundary = 0; 461c6266a77SGreg Roach foreach (array_keys($z_axis) as $boundary) { 462999da590SGreg Roach $rows = $statistics->monthFirstChildQuery($prev_boundary, $boundary)->get(); 463c6266a77SGreg Roach foreach ($rows as $row) { 464c6266a77SGreg Roach $this->fillYData($row->d_month, $boundary, $row->total, $x_axis, $z_axis, $ydata); 465c6266a77SGreg Roach } 466c6266a77SGreg Roach $prev_boundary = $boundary + 1; 467c6266a77SGreg Roach } 468c6266a77SGreg Roach break; 469c6266a77SGreg Roach default: 470d501c45dSGreg Roach throw new HttpNotFoundException(); 471c6266a77SGreg Roach } 472c6266a77SGreg Roach 4736ccdf4f0SGreg Roach return response($this->myPlot($chart_title, $x_axis, $x_axis_title, $ydata, $y_axis_title, $z_axis, $y_axis_type)); 474c6266a77SGreg Roach 475c6266a77SGreg Roach case self::X_AXIS_FIRST_MARRIAGE_MONTH: 476c6266a77SGreg Roach $chart_title = I18N::translate('Month of first marriage'); 477c6266a77SGreg Roach $x_axis_title = I18N::translate('Month'); 478c6266a77SGreg Roach $x_axis = $this->axisMonths(); 479c6266a77SGreg Roach 480c6266a77SGreg Roach switch ($y_axis_type) { 481c6266a77SGreg Roach case self::Y_AXIS_NUMBERS: 482c6266a77SGreg Roach $y_axis_title = I18N::translate('Families'); 483c6266a77SGreg Roach break; 484c6266a77SGreg Roach case self::Y_AXIS_PERCENT: 485c6266a77SGreg Roach $y_axis_title = '%'; 486c6266a77SGreg Roach break; 487c6266a77SGreg Roach default: 488d501c45dSGreg Roach throw new HttpNotFoundException(); 489c6266a77SGreg Roach } 490c6266a77SGreg Roach 491c6266a77SGreg Roach switch ($z_axis_type) { 492c6266a77SGreg Roach case self::Z_AXIS_ALL: 493c6266a77SGreg Roach $z_axis = $this->axisAll(); 494e6f3d5e2SGreg Roach $rows = $statistics->statsFirstMarriageQuery()->get(); 495c6266a77SGreg Roach $indi = []; 496c6266a77SGreg Roach $fam = []; 497c6266a77SGreg Roach foreach ($rows as $row) { 4986960d4a1SGreg Roach if (!in_array($row->indi, $indi, true) && !in_array($row->fams, $fam, true)) { 499c6266a77SGreg Roach $this->fillYData($row->month, 0, 1, $x_axis, $z_axis, $ydata); 500c6266a77SGreg Roach } 501c6266a77SGreg Roach $indi[] = $row->indi; 502c6266a77SGreg Roach $fam[] = $row->fams; 503c6266a77SGreg Roach } 504c6266a77SGreg Roach break; 505c6266a77SGreg Roach case self::Z_AXIS_TIME: 506b6b9dcc9SGreg Roach $boundaries_csv = $params['z-axis-boundaries-periods']; 507c6266a77SGreg Roach $z_axis = $this->axisYears($boundaries_csv); 508c6266a77SGreg Roach $prev_boundary = 0; 509c6266a77SGreg Roach $indi = []; 510c6266a77SGreg Roach $fam = []; 511c6266a77SGreg Roach foreach (array_keys($z_axis) as $boundary) { 512e6f3d5e2SGreg Roach $rows = $statistics->statsFirstMarriageQuery($prev_boundary, $boundary)->get(); 513c6266a77SGreg Roach foreach ($rows as $row) { 5146960d4a1SGreg Roach if (!in_array($row->indi, $indi, true) && !in_array($row->fams, $fam, true)) { 515c6266a77SGreg Roach $this->fillYData($row->month, $boundary, 1, $x_axis, $z_axis, $ydata); 516c6266a77SGreg Roach } 517c6266a77SGreg Roach $indi[] = $row->indi; 518c6266a77SGreg Roach $fam[] = $row->fams; 519c6266a77SGreg Roach } 520c6266a77SGreg Roach $prev_boundary = $boundary + 1; 521c6266a77SGreg Roach } 522c6266a77SGreg Roach break; 523c6266a77SGreg Roach default: 524d501c45dSGreg Roach throw new HttpNotFoundException(); 525c6266a77SGreg Roach } 526c6266a77SGreg Roach 5276ccdf4f0SGreg Roach return response($this->myPlot($chart_title, $x_axis, $x_axis_title, $ydata, $y_axis_title, $z_axis, $y_axis_type)); 528c6266a77SGreg Roach 529c6266a77SGreg Roach case self::X_AXIS_AGE_AT_DEATH: 530c6266a77SGreg Roach $chart_title = I18N::translate('Average age at death'); 531c6266a77SGreg Roach $x_axis_title = I18N::translate('age'); 532b6b9dcc9SGreg Roach $boundaries_csv = $params['x-axis-boundaries-ages']; 533c6266a77SGreg Roach $x_axis = $this->axisNumbers($boundaries_csv); 534c6266a77SGreg Roach 535c6266a77SGreg Roach switch ($y_axis_type) { 536c6266a77SGreg Roach case self::Y_AXIS_NUMBERS: 537c6266a77SGreg Roach $y_axis_title = I18N::translate('Individuals'); 538c6266a77SGreg Roach break; 539c6266a77SGreg Roach case self::Y_AXIS_PERCENT: 540c6266a77SGreg Roach $y_axis_title = '%'; 541c6266a77SGreg Roach break; 542c6266a77SGreg Roach default: 543d501c45dSGreg Roach throw new HttpNotFoundException(); 544c6266a77SGreg Roach } 545c6266a77SGreg Roach 546c6266a77SGreg Roach switch ($z_axis_type) { 547c6266a77SGreg Roach case self::Z_AXIS_ALL: 548c6266a77SGreg Roach $z_axis = $this->axisAll(); 5499219296aSGreg Roach $rows = $statistics->statsAgeQuery('DEAT'); 550c6266a77SGreg Roach foreach ($rows as $row) { 551c6266a77SGreg Roach foreach ($row as $age) { 552c6266a77SGreg Roach $years = (int) ($age / self::DAYS_IN_YEAR); 553c6266a77SGreg Roach $this->fillYData($years, 0, 1, $x_axis, $z_axis, $ydata); 554c6266a77SGreg Roach } 555c6266a77SGreg Roach } 556c6266a77SGreg Roach break; 557c6266a77SGreg Roach case self::Z_AXIS_SEX: 558c6266a77SGreg Roach $z_axis = $this->axisSexes(); 559c6266a77SGreg Roach foreach (array_keys($z_axis) as $sex) { 5609219296aSGreg Roach $rows = $statistics->statsAgeQuery('DEAT', $sex); 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, $sex, 1, $x_axis, $z_axis, $ydata); 565c6266a77SGreg Roach } 566c6266a77SGreg Roach } 567c6266a77SGreg Roach } 568c6266a77SGreg Roach break; 569c6266a77SGreg Roach case self::Z_AXIS_TIME: 570b6b9dcc9SGreg Roach $boundaries_csv = $params['z-axis-boundaries-periods']; 571c6266a77SGreg Roach $z_axis = $this->axisYears($boundaries_csv); 572c6266a77SGreg Roach $prev_boundary = 0; 573c6266a77SGreg Roach foreach (array_keys($z_axis) as $boundary) { 5749219296aSGreg Roach $rows = $statistics->statsAgeQuery('DEAT', 'BOTH', $prev_boundary, $boundary); 575c6266a77SGreg Roach foreach ($rows as $row) { 576c6266a77SGreg Roach foreach ($row as $age) { 577c6266a77SGreg Roach $years = (int) ($age / self::DAYS_IN_YEAR); 578c6266a77SGreg Roach $this->fillYData($years, $boundary, 1, $x_axis, $z_axis, $ydata); 579c6266a77SGreg Roach } 580c6266a77SGreg Roach } 581c6266a77SGreg Roach $prev_boundary = $boundary + 1; 582c6266a77SGreg Roach } 583c6266a77SGreg Roach 584c6266a77SGreg Roach break; 585c6266a77SGreg Roach default: 586d501c45dSGreg Roach throw new HttpNotFoundException(); 587c6266a77SGreg Roach } 588c6266a77SGreg Roach 5896ccdf4f0SGreg Roach return response($this->myPlot($chart_title, $x_axis, $x_axis_title, $ydata, $y_axis_title, $z_axis, $y_axis_type)); 590c6266a77SGreg Roach 591c6266a77SGreg Roach case self::X_AXIS_AGE_AT_MARRIAGE: 592c6266a77SGreg Roach $chart_title = I18N::translate('Age in year of marriage'); 593c6266a77SGreg Roach $x_axis_title = I18N::translate('age'); 594b6b9dcc9SGreg Roach $boundaries_csv = $params['x-axis-boundaries-ages_m']; 595c6266a77SGreg Roach $x_axis = $this->axisNumbers($boundaries_csv); 596c6266a77SGreg Roach 597c6266a77SGreg Roach switch ($y_axis_type) { 598c6266a77SGreg Roach case self::Y_AXIS_NUMBERS: 599c6266a77SGreg Roach $y_axis_title = I18N::translate('Individuals'); 600c6266a77SGreg Roach break; 601c6266a77SGreg Roach case self::Y_AXIS_PERCENT: 602c6266a77SGreg Roach $y_axis_title = '%'; 603c6266a77SGreg Roach break; 604c6266a77SGreg Roach default: 605d501c45dSGreg Roach throw new HttpNotFoundException(); 606c6266a77SGreg Roach } 607c6266a77SGreg Roach 608c6266a77SGreg Roach switch ($z_axis_type) { 609c6266a77SGreg Roach case self::Z_AXIS_ALL: 610c6266a77SGreg Roach $z_axis = $this->axisAll(); 611afa8d404SGreg Roach // The stats query doesn't have an "all" function, so query M/F separately 612afa8d404SGreg Roach foreach (['M', 'F'] as $sex) { 6139219296aSGreg Roach $rows = $statistics->statsMarrAgeQuery($sex); 614c6266a77SGreg Roach foreach ($rows as $row) { 615c6266a77SGreg Roach $years = (int) ($row->age / self::DAYS_IN_YEAR); 616c6266a77SGreg Roach $this->fillYData($years, 0, 1, $x_axis, $z_axis, $ydata); 617c6266a77SGreg Roach } 618c6266a77SGreg Roach } 619c6266a77SGreg Roach break; 620c6266a77SGreg Roach case self::Z_AXIS_SEX: 621c6266a77SGreg Roach $z_axis = $this->axisSexes(); 622c6266a77SGreg Roach foreach (array_keys($z_axis) as $sex) { 6239219296aSGreg Roach $rows = $statistics->statsMarrAgeQuery($sex); 624c6266a77SGreg Roach foreach ($rows as $row) { 625c6266a77SGreg Roach $years = (int) ($row->age / self::DAYS_IN_YEAR); 626c6266a77SGreg Roach $this->fillYData($years, $sex, 1, $x_axis, $z_axis, $ydata); 627c6266a77SGreg Roach } 628c6266a77SGreg Roach } 629c6266a77SGreg Roach break; 630c6266a77SGreg Roach case self::Z_AXIS_TIME: 631b6b9dcc9SGreg Roach $boundaries_csv = $params['z-axis-boundaries-periods']; 632c6266a77SGreg Roach $z_axis = $this->axisYears($boundaries_csv); 633afa8d404SGreg Roach // The stats query doesn't have an "all" function, so query M/F separately 634afa8d404SGreg Roach foreach (['M', 'F'] as $sex) { 635c6266a77SGreg Roach $prev_boundary = 0; 636c6266a77SGreg Roach foreach (array_keys($z_axis) as $boundary) { 6379219296aSGreg Roach $rows = $statistics->statsMarrAgeQuery($sex, $prev_boundary, $boundary); 638c6266a77SGreg Roach foreach ($rows as $row) { 639c6266a77SGreg Roach $years = (int) ($row->age / self::DAYS_IN_YEAR); 640c6266a77SGreg Roach $this->fillYData($years, $boundary, 1, $x_axis, $z_axis, $ydata); 641c6266a77SGreg Roach } 642c6266a77SGreg Roach $prev_boundary = $boundary + 1; 643c6266a77SGreg Roach } 644c6266a77SGreg Roach } 645c6266a77SGreg Roach break; 646c6266a77SGreg Roach default: 647d501c45dSGreg Roach throw new HttpNotFoundException(); 648c6266a77SGreg Roach } 649c6266a77SGreg Roach 6506ccdf4f0SGreg Roach return response($this->myPlot($chart_title, $x_axis, $x_axis_title, $ydata, $y_axis_title, $z_axis, $y_axis_type)); 651c6266a77SGreg Roach 652c6266a77SGreg Roach case self::X_AXIS_AGE_AT_FIRST_MARRIAGE: 653c6266a77SGreg Roach $chart_title = I18N::translate('Age in year of first marriage'); 654c6266a77SGreg Roach $x_axis_title = I18N::translate('age'); 655b6b9dcc9SGreg Roach $boundaries_csv = $params['x-axis-boundaries-ages_m']; 656c6266a77SGreg Roach $x_axis = $this->axisNumbers($boundaries_csv); 657c6266a77SGreg Roach 658c6266a77SGreg Roach switch ($y_axis_type) { 659c6266a77SGreg Roach case self::Y_AXIS_NUMBERS: 660c6266a77SGreg Roach $y_axis_title = I18N::translate('Individuals'); 661c6266a77SGreg Roach break; 662c6266a77SGreg Roach case self::Y_AXIS_PERCENT: 663c6266a77SGreg Roach $y_axis_title = '%'; 664c6266a77SGreg Roach break; 665c6266a77SGreg Roach default: 666d501c45dSGreg Roach throw new HttpNotFoundException(); 667c6266a77SGreg Roach } 668c6266a77SGreg Roach 669c6266a77SGreg Roach switch ($z_axis_type) { 670c6266a77SGreg Roach case self::Z_AXIS_ALL: 671c6266a77SGreg Roach $z_axis = $this->axisAll(); 672afa8d404SGreg Roach // The stats query doesn't have an "all" function, so query M/F separately 673afa8d404SGreg Roach foreach (['M', 'F'] 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, 0, 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_SEX: 686c6266a77SGreg Roach $z_axis = $this->axisSexes(); 687c6266a77SGreg Roach foreach (array_keys($z_axis) as $sex) { 6889219296aSGreg Roach $rows = $statistics->statsMarrAgeQuery($sex); 689c6266a77SGreg Roach $indi = []; 690c6266a77SGreg Roach foreach ($rows as $row) { 6916960d4a1SGreg Roach if (!in_array($row->d_gid, $indi, true)) { 692c6266a77SGreg Roach $years = (int) ($row->age / self::DAYS_IN_YEAR); 693c6266a77SGreg Roach $this->fillYData($years, $sex, 1, $x_axis, $z_axis, $ydata); 694c6266a77SGreg Roach $indi[] = $row->d_gid; 695c6266a77SGreg Roach } 696c6266a77SGreg Roach } 697c6266a77SGreg Roach } 698c6266a77SGreg Roach break; 699c6266a77SGreg Roach case self::Z_AXIS_TIME: 700b6b9dcc9SGreg Roach $boundaries_csv = $params['z-axis-boundaries-periods']; 701c6266a77SGreg Roach $z_axis = $this->axisYears($boundaries_csv); 702afa8d404SGreg Roach // The stats query doesn't have an "all" function, so query M/F separately 703afa8d404SGreg Roach foreach (['M', 'F'] as $sex) { 704c6266a77SGreg Roach $prev_boundary = 0; 705c6266a77SGreg Roach $indi = []; 706c6266a77SGreg Roach foreach (array_keys($z_axis) as $boundary) { 7079219296aSGreg Roach $rows = $statistics->statsMarrAgeQuery($sex, $prev_boundary, $boundary); 708c6266a77SGreg Roach foreach ($rows as $row) { 7096960d4a1SGreg Roach if (!in_array($row->d_gid, $indi, true)) { 710c6266a77SGreg Roach $years = (int) ($row->age / self::DAYS_IN_YEAR); 711c6266a77SGreg Roach $this->fillYData($years, $boundary, 1, $x_axis, $z_axis, $ydata); 712c6266a77SGreg Roach $indi[] = $row->d_gid; 713c6266a77SGreg Roach } 714c6266a77SGreg Roach } 715c6266a77SGreg Roach $prev_boundary = $boundary + 1; 716c6266a77SGreg Roach } 717c6266a77SGreg Roach } 718c6266a77SGreg Roach break; 719c6266a77SGreg Roach default: 720d501c45dSGreg Roach throw new HttpNotFoundException(); 721c6266a77SGreg Roach } 722c6266a77SGreg Roach 7236ccdf4f0SGreg Roach return response($this->myPlot($chart_title, $x_axis, $x_axis_title, $ydata, $y_axis_title, $z_axis, $y_axis_type)); 724c6266a77SGreg Roach 725c6266a77SGreg Roach case self::X_AXIS_NUMBER_OF_CHILDREN: 726c6266a77SGreg Roach $chart_title = I18N::translate('Number of children'); 727c6266a77SGreg Roach $x_axis_title = I18N::translate('Children'); 7286960d4a1SGreg Roach $x_axis = $this->axisNumbers('0,1,2,3,4,5,6,7,8,9,10'); 729c6266a77SGreg Roach 730c6266a77SGreg Roach switch ($y_axis_type) { 731c6266a77SGreg Roach case self::Y_AXIS_NUMBERS: 732c6266a77SGreg Roach $y_axis_title = I18N::translate('Families'); 733c6266a77SGreg Roach break; 734c6266a77SGreg Roach case self::Y_AXIS_PERCENT: 735c6266a77SGreg Roach $y_axis_title = '%'; 736c6266a77SGreg Roach break; 737c6266a77SGreg Roach default: 738d501c45dSGreg Roach throw new HttpNotFoundException(); 739c6266a77SGreg Roach } 740c6266a77SGreg Roach 741c6266a77SGreg Roach switch ($z_axis_type) { 742c6266a77SGreg Roach case self::Z_AXIS_ALL: 743c6266a77SGreg Roach $z_axis = $this->axisAll(); 7449219296aSGreg Roach $rows = $statistics->statsChildrenQuery(); 745c6266a77SGreg Roach foreach ($rows as $row) { 746c6266a77SGreg Roach $this->fillYData($row->f_numchil, 0, $row->total, $x_axis, $z_axis, $ydata); 747c6266a77SGreg Roach } 748c6266a77SGreg Roach break; 749c6266a77SGreg Roach case self::Z_AXIS_TIME: 750b6b9dcc9SGreg Roach $boundaries_csv = $params['z-axis-boundaries-periods']; 751c6266a77SGreg Roach $z_axis = $this->axisYears($boundaries_csv); 752c6266a77SGreg Roach $prev_boundary = 0; 753c6266a77SGreg Roach foreach (array_keys($z_axis) as $boundary) { 754b1126ab4SGreg Roach $rows = $statistics->statsChildrenQuery($prev_boundary, $boundary); 755c6266a77SGreg Roach foreach ($rows as $row) { 756c6266a77SGreg Roach $this->fillYData($row->f_numchil, $boundary, $row->total, $x_axis, $z_axis, $ydata); 757c6266a77SGreg Roach } 758c6266a77SGreg Roach $prev_boundary = $boundary + 1; 759c6266a77SGreg Roach } 760c6266a77SGreg Roach break; 761c6266a77SGreg Roach default: 762d501c45dSGreg Roach throw new HttpNotFoundException(); 763c6266a77SGreg Roach } 764c6266a77SGreg Roach 7656ccdf4f0SGreg Roach return response($this->myPlot($chart_title, $x_axis, $x_axis_title, $ydata, $y_axis_title, $z_axis, $y_axis_type)); 766c6266a77SGreg Roach 767c6266a77SGreg Roach default: 768d501c45dSGreg Roach throw new HttpNotFoundException(); 769c6266a77SGreg Roach break; 770c6266a77SGreg Roach } 771c6266a77SGreg Roach } 772c6266a77SGreg Roach 773c6266a77SGreg Roach /** 774c6266a77SGreg Roach * @return string[] 775c6266a77SGreg Roach */ 776c6266a77SGreg Roach private function axisAll(): array 777c6266a77SGreg Roach { 778c6266a77SGreg Roach return [ 779c6266a77SGreg Roach I18N::translate('Total'), 780c6266a77SGreg Roach ]; 781c6266a77SGreg Roach } 782c6266a77SGreg Roach 783c6266a77SGreg Roach /** 784c6266a77SGreg Roach * @return string[] 785c6266a77SGreg Roach */ 786c6266a77SGreg Roach private function axisSexes(): array 787c6266a77SGreg Roach { 788c6266a77SGreg Roach return [ 789c6266a77SGreg Roach 'M' => I18N::translate('Male'), 790c6266a77SGreg Roach 'F' => I18N::translate('Female'), 791c6266a77SGreg Roach ]; 792c6266a77SGreg Roach } 793c6266a77SGreg Roach 794c6266a77SGreg Roach /** 795c6266a77SGreg Roach * Labels for the X axis 796c6266a77SGreg Roach * 797c6266a77SGreg Roach * @return string[] 798c6266a77SGreg Roach */ 799c6266a77SGreg Roach private function axisMonths(): array 800c6266a77SGreg Roach { 801c6266a77SGreg Roach return [ 802c6266a77SGreg Roach 'JAN' => I18N::translateContext('NOMINATIVE', 'January'), 803c6266a77SGreg Roach 'FEB' => I18N::translateContext('NOMINATIVE', 'February'), 804c6266a77SGreg Roach 'MAR' => I18N::translateContext('NOMINATIVE', 'March'), 805c6266a77SGreg Roach 'APR' => I18N::translateContext('NOMINATIVE', 'April'), 806c6266a77SGreg Roach 'MAY' => I18N::translateContext('NOMINATIVE', 'May'), 807c6266a77SGreg Roach 'JUN' => I18N::translateContext('NOMINATIVE', 'June'), 808c6266a77SGreg Roach 'JUL' => I18N::translateContext('NOMINATIVE', 'July'), 809c6266a77SGreg Roach 'AUG' => I18N::translateContext('NOMINATIVE', 'August'), 810c6266a77SGreg Roach 'SEP' => I18N::translateContext('NOMINATIVE', 'September'), 811c6266a77SGreg Roach 'OCT' => I18N::translateContext('NOMINATIVE', 'October'), 812c6266a77SGreg Roach 'NOV' => I18N::translateContext('NOMINATIVE', 'November'), 813c6266a77SGreg Roach 'DEC' => I18N::translateContext('NOMINATIVE', 'December'), 814c6266a77SGreg Roach ]; 815c6266a77SGreg Roach } 816c6266a77SGreg Roach 817c6266a77SGreg Roach /** 818c6266a77SGreg Roach * Convert a list of N year-boundaries into N+1 year-ranges for the z-axis. 819c6266a77SGreg Roach * 820c6266a77SGreg Roach * @param string $boundaries_csv 821c6266a77SGreg Roach * 822c6266a77SGreg Roach * @return string[] 823c6266a77SGreg Roach */ 824c6266a77SGreg Roach private function axisYears(string $boundaries_csv): array 825c6266a77SGreg Roach { 826c6266a77SGreg Roach $boundaries = explode(',', $boundaries_csv); 827c6266a77SGreg Roach 828c6266a77SGreg Roach $axis = []; 829c6266a77SGreg Roach foreach ($boundaries as $n => $boundary) { 830c6266a77SGreg Roach if ($n === 0) { 831c6266a77SGreg Roach $date = new Date('BEF ' . $boundary); 832c6266a77SGreg Roach } else { 833c6266a77SGreg Roach $date = new Date('BET ' . $boundaries[$n - 1] . ' AND ' . ($boundary - 1)); 834c6266a77SGreg Roach } 835c6266a77SGreg Roach $axis[$boundary - 1] = strip_tags($date->display()); 836c6266a77SGreg Roach } 837c6266a77SGreg Roach 838c6266a77SGreg Roach $date = new Date('AFT ' . $boundaries[count($boundaries) - 1]); 839c6266a77SGreg Roach $axis[PHP_INT_MAX] = strip_tags($date->display()); 840c6266a77SGreg Roach 841c6266a77SGreg Roach return $axis; 842c6266a77SGreg Roach } 843c6266a77SGreg Roach 844c6266a77SGreg Roach /** 845c6266a77SGreg Roach * Create the X axis. 846c6266a77SGreg Roach * 847c6266a77SGreg Roach * @param string $boundaries_csv 848c6266a77SGreg Roach * 849c6266a77SGreg Roach * @return array 850c6266a77SGreg Roach */ 851c6266a77SGreg Roach private function axisNumbers(string $boundaries_csv): array 852c6266a77SGreg Roach { 853c6266a77SGreg Roach $boundaries = explode(',', $boundaries_csv); 854c6266a77SGreg Roach 8550b5fd0a6SGreg Roach $boundaries = array_map(static function (string $x): int { 856c6266a77SGreg Roach return (int) $x; 857c6266a77SGreg Roach }, $boundaries); 858c6266a77SGreg Roach 859c6266a77SGreg Roach $axis = []; 860c6266a77SGreg Roach foreach ($boundaries as $n => $boundary) { 861c6266a77SGreg Roach if ($n === 0) { 8626960d4a1SGreg Roach $prev_boundary = 0; 8636960d4a1SGreg Roach } else { 8646960d4a1SGreg Roach $prev_boundary = $boundaries[$n - 1] + 1; 8656960d4a1SGreg Roach } 8666960d4a1SGreg Roach 8676960d4a1SGreg Roach if ($prev_boundary === $boundary) { 868c6266a77SGreg Roach /* I18N: A range of numbers */ 8696960d4a1SGreg Roach $axis[$boundary] = I18N::number($boundary); 870c6266a77SGreg Roach } else { 871c6266a77SGreg Roach /* I18N: A range of numbers */ 8726960d4a1SGreg Roach $axis[$boundary] = I18N::translate('%1$s–%2$s', I18N::number($prev_boundary), I18N::number($boundary)); 873c6266a77SGreg Roach } 874c6266a77SGreg Roach } 875c6266a77SGreg Roach 876c6266a77SGreg Roach /* I18N: Label on a graph; 40+ means 40 or more */ 877c6266a77SGreg Roach $axis[PHP_INT_MAX] = I18N::translate('%s+', I18N::number($boundaries[count($boundaries) - 1])); 878c6266a77SGreg Roach 879c6266a77SGreg Roach return $axis; 880c6266a77SGreg Roach } 881c6266a77SGreg Roach 882c6266a77SGreg Roach /** 883c6266a77SGreg Roach * Calculate the Y axis. 884c6266a77SGreg Roach * 885c6266a77SGreg Roach * @param int|string $x 886c6266a77SGreg Roach * @param int|string $z 887c6266a77SGreg Roach * @param int|string $value 888c6266a77SGreg Roach * @param array $x_axis 889c6266a77SGreg Roach * @param array $z_axis 890c6266a77SGreg Roach * @param int[][] $ydata 891c6266a77SGreg Roach * 892c6266a77SGreg Roach * @return void 893c6266a77SGreg Roach */ 894e364afe4SGreg Roach private function fillYData($x, $z, $value, array $x_axis, array $z_axis, array &$ydata): void 895c6266a77SGreg Roach { 896c6266a77SGreg Roach $x = $this->findAxisEntry($x, $x_axis); 897c6266a77SGreg Roach $z = $this->findAxisEntry($z, $z_axis); 898c6266a77SGreg Roach 8996960d4a1SGreg Roach if (!array_key_exists($z, $z_axis)) { 900c6266a77SGreg Roach foreach (array_keys($z_axis) as $key) { 901c6266a77SGreg Roach if ($value <= $key) { 902c6266a77SGreg Roach $z = $key; 903c6266a77SGreg Roach break; 904c6266a77SGreg Roach } 905c6266a77SGreg Roach } 906c6266a77SGreg Roach } 907c6266a77SGreg Roach 908c6266a77SGreg Roach // Add the value to the appropriate data point. 909c6266a77SGreg Roach $ydata[$z][$x] = ($ydata[$z][$x] ?? 0) + $value; 910c6266a77SGreg Roach } 911c6266a77SGreg Roach 912c6266a77SGreg Roach /** 913c6266a77SGreg Roach * Find the axis entry for a given value. 914c6266a77SGreg Roach * Some are direct lookup (e.g. M/F, JAN/FEB/MAR). 915c6266a77SGreg Roach * Others need to find the approprate range. 916c6266a77SGreg Roach * 917c6266a77SGreg Roach * @param int|float|string $value 918c6266a77SGreg Roach * @param string[] $axis 919c6266a77SGreg Roach * 920c6266a77SGreg Roach * @return int|string 921c6266a77SGreg Roach */ 922c6266a77SGreg Roach private function findAxisEntry($value, $axis) 923c6266a77SGreg Roach { 924c6266a77SGreg Roach if (is_numeric($value)) { 925c6266a77SGreg Roach $value = (int) $value; 926c6266a77SGreg Roach 9276960d4a1SGreg Roach if (!array_key_exists($value, $axis)) { 928c6266a77SGreg Roach foreach (array_keys($axis) as $boundary) { 929c6266a77SGreg Roach if ($value <= $boundary) { 930c6266a77SGreg Roach $value = $boundary; 931c6266a77SGreg Roach break; 932c6266a77SGreg Roach } 933c6266a77SGreg Roach } 934c6266a77SGreg Roach } 935c6266a77SGreg Roach } 936c6266a77SGreg Roach 937c6266a77SGreg Roach return $value; 938c6266a77SGreg Roach } 939c6266a77SGreg Roach 940c6266a77SGreg Roach /** 941c6266a77SGreg Roach * Plot the data. 942c6266a77SGreg Roach * 943c6266a77SGreg Roach * @param string $chart_title 944c6266a77SGreg Roach * @param string[] $x_axis 945c6266a77SGreg Roach * @param string $x_axis_title 946c6266a77SGreg Roach * @param int[][] $ydata 947c6266a77SGreg Roach * @param string $y_axis_title 948c6266a77SGreg Roach * @param string[] $z_axis 949c6266a77SGreg Roach * @param int $y_axis_type 950c6266a77SGreg Roach * 951c6266a77SGreg Roach * @return string 952c6266a77SGreg Roach */ 953a81e5019SRico Sonntag private function myPlot( 954a81e5019SRico Sonntag string $chart_title, 955a81e5019SRico Sonntag array $x_axis, 956a81e5019SRico Sonntag string $x_axis_title, 957a81e5019SRico Sonntag array $ydata, 958a81e5019SRico Sonntag string $y_axis_title, 959a81e5019SRico Sonntag array $z_axis, 960a81e5019SRico Sonntag int $y_axis_type 961a81e5019SRico Sonntag ): string { 9626960d4a1SGreg Roach if (!count($ydata)) { 963a81e5019SRico Sonntag return I18N::translate('This information is not available.'); 964c6266a77SGreg Roach } 965c6266a77SGreg Roach 966c6266a77SGreg Roach // Colors for z-axis 967c6266a77SGreg Roach $colors = []; 968c6266a77SGreg Roach $index = 0; 9696960d4a1SGreg Roach while (count($colors) < count($ydata)) { 970c6266a77SGreg Roach $colors[] = self::Z_AXIS_COLORS[$index]; 9716960d4a1SGreg Roach $index = ($index + 1) % count(self::Z_AXIS_COLORS); 972c6266a77SGreg Roach } 973c6266a77SGreg Roach 974c6266a77SGreg Roach // Convert our sparse dataset into a fixed-size array 975c6266a77SGreg Roach $tmp = []; 976c6266a77SGreg Roach foreach (array_keys($z_axis) as $z) { 977c6266a77SGreg Roach foreach (array_keys($x_axis) as $x) { 978c6266a77SGreg Roach $tmp[$z][$x] = $ydata[$z][$x] ?? 0; 979c6266a77SGreg Roach } 980c6266a77SGreg Roach } 981c6266a77SGreg Roach $ydata = $tmp; 982c6266a77SGreg Roach 983a81e5019SRico Sonntag // Convert the chart data to percentage 984c6266a77SGreg Roach if ($y_axis_type === self::Y_AXIS_PERCENT) { 985c6266a77SGreg Roach // Normalise each (non-zero!) set of data to total 100% 9860b5fd0a6SGreg Roach array_walk($ydata, static function (array &$x) { 987c6266a77SGreg Roach $sum = array_sum($x); 988c6266a77SGreg Roach if ($sum > 0) { 9890b5fd0a6SGreg Roach $x = array_map(static function ($y) use ($sum) { 990c6266a77SGreg Roach return $y * 100.0 / $sum; 991c6266a77SGreg Roach }, $x); 992c6266a77SGreg Roach } 993c6266a77SGreg Roach }); 994c6266a77SGreg Roach } 995c6266a77SGreg Roach 996a81e5019SRico Sonntag $data = [ 997a81e5019SRico Sonntag array_merge( 998a81e5019SRico Sonntag [I18N::translate('Century')], 999a81e5019SRico Sonntag array_values($z_axis) 100071378461SGreg Roach ), 1001c6266a77SGreg Roach ]; 1002c6266a77SGreg Roach 1003a81e5019SRico Sonntag $intermediate = []; 1004a81e5019SRico Sonntag foreach ($ydata as $century => $months) { 1005a81e5019SRico Sonntag foreach ($months as $month => $value) { 1006a81e5019SRico Sonntag $intermediate[$month][] = [ 1007a81e5019SRico Sonntag 'v' => $value, 1008a81e5019SRico Sonntag 'f' => ($y_axis_type === self::Y_AXIS_PERCENT) ? sprintf('%.1f%%', $value) : $value, 1009a81e5019SRico Sonntag ]; 1010a81e5019SRico Sonntag } 1011c6266a77SGreg Roach } 1012c6266a77SGreg Roach 1013a81e5019SRico Sonntag foreach ($intermediate as $key => $values) { 1014a81e5019SRico Sonntag $data[] = array_merge( 1015a81e5019SRico Sonntag [$x_axis[$key]], 1016a81e5019SRico Sonntag $values 1017a81e5019SRico Sonntag ); 1018a81e5019SRico Sonntag } 1019c6266a77SGreg Roach 1020a81e5019SRico Sonntag $chart_options = [ 1021a81e5019SRico Sonntag 'title' => '', 1022a81e5019SRico Sonntag 'subtitle' => '', 1023a81e5019SRico Sonntag 'height' => 400, 1024a81e5019SRico Sonntag 'width' => '100%', 1025a81e5019SRico Sonntag 'legend' => [ 10266960d4a1SGreg Roach 'position' => count($z_axis) > 1 ? 'right' : 'none', 1027a81e5019SRico Sonntag 'alignment' => 'center', 1028a81e5019SRico Sonntag ], 1029a81e5019SRico Sonntag 'tooltip' => [ 1030a81e5019SRico Sonntag 'format' => '\'%\'', 1031a81e5019SRico Sonntag ], 1032a81e5019SRico Sonntag 'vAxis' => [ 1033a81e5019SRico Sonntag 'title' => $y_axis_title ?? '', 1034a81e5019SRico Sonntag ], 1035a81e5019SRico Sonntag 'hAxis' => [ 1036a81e5019SRico Sonntag 'title' => $x_axis_title ?? '', 1037a81e5019SRico Sonntag ], 1038a81e5019SRico Sonntag 'colors' => $colors, 1039a81e5019SRico Sonntag ]; 1040a81e5019SRico Sonntag 104190a2f718SGreg Roach return view('statistics/other/charts/custom', [ 1042a81e5019SRico Sonntag 'data' => $data, 1043a81e5019SRico Sonntag 'chart_options' => $chart_options, 1044a81e5019SRico Sonntag 'chart_title' => $chart_title, 104565cf5706SGreg Roach 'language' => I18N::languageTag(), 104690a2f718SGreg Roach ]); 1047c6266a77SGreg Roach } 1048168ff6f3Sric2016} 1049