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; 24168ff6f3Sric2016use Fisharebest\Webtrees\I18N; 25168ff6f3Sric2016use Fisharebest\Webtrees\Individual; 269219296aSGreg Roachuse Fisharebest\Webtrees\Statistics; 275229eadeSGreg Roachuse Fisharebest\Webtrees\Tree; 286ccdf4f0SGreg Roachuse Psr\Http\Message\ResponseInterface; 296ccdf4f0SGreg Roachuse Psr\Http\Message\ServerRequestInterface; 30c6266a77SGreg Roachuse Symfony\Component\HttpKernel\Exception\NotFoundHttpException; 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'); 146*75964c75SGreg 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(), 179c6266a77SGreg Roach ]); 180c6266a77SGreg Roach } 181c6266a77SGreg Roach 182c6266a77SGreg Roach /** 18357ab2231SGreg Roach * @param ServerRequestInterface $request 184c6266a77SGreg Roach * 1856ccdf4f0SGreg Roach * @return ResponseInterface 186c6266a77SGreg Roach */ 18757ab2231SGreg Roach public function getIndividualsAction(ServerRequestInterface $request): ResponseInterface 188c6266a77SGreg Roach { 189b6c326d8SGreg Roach $this->layout = 'layouts/ajax'; 190b6c326d8SGreg Roach 191b6c326d8SGreg Roach return $this->viewResponse('modules/statistics-chart/individuals', [ 192c6266a77SGreg Roach 'show_oldest_living' => Auth::check(), 19357ab2231SGreg Roach 'stats' => app(Statistics::class), 194c6266a77SGreg Roach ]); 195c6266a77SGreg Roach } 196c6266a77SGreg Roach 197c6266a77SGreg Roach /** 19857ab2231SGreg Roach * @param ServerRequestInterface $request 199c6266a77SGreg Roach * 2006ccdf4f0SGreg Roach * @return ResponseInterface 201c6266a77SGreg Roach */ 20257ab2231SGreg Roach public function getFamiliesAction(ServerRequestInterface $request): ResponseInterface 203c6266a77SGreg Roach { 204b6c326d8SGreg Roach $this->layout = 'layouts/ajax'; 205b6c326d8SGreg Roach 206b6c326d8SGreg Roach return $this->viewResponse('modules/statistics-chart/families', [ 20757ab2231SGreg Roach 'stats' => app(Statistics::class), 208c6266a77SGreg Roach ]); 209c6266a77SGreg Roach } 210c6266a77SGreg Roach 211c6266a77SGreg Roach /** 21257ab2231SGreg Roach * @param ServerRequestInterface $request 213c6266a77SGreg Roach * 2146ccdf4f0SGreg Roach * @return ResponseInterface 215c6266a77SGreg Roach */ 21657ab2231SGreg Roach public function getOtherAction(ServerRequestInterface $request): ResponseInterface 217c6266a77SGreg Roach { 218b6c326d8SGreg Roach $this->layout = 'layouts/ajax'; 219b6c326d8SGreg Roach 220b6c326d8SGreg Roach return $this->viewResponse('modules/statistics-chart/other', [ 22157ab2231SGreg Roach 'stats' => app(Statistics::class), 222c6266a77SGreg Roach ]); 223c6266a77SGreg Roach } 224c6266a77SGreg Roach 225c6266a77SGreg Roach /** 22657ab2231SGreg Roach * @param ServerRequestInterface $request 227c6266a77SGreg Roach * 2286ccdf4f0SGreg Roach * @return ResponseInterface 229c6266a77SGreg Roach */ 23057ab2231SGreg Roach public function getCustomAction(ServerRequestInterface $request): ResponseInterface 231c6266a77SGreg Roach { 232b6c326d8SGreg Roach $this->layout = 'layouts/ajax'; 233b6c326d8SGreg Roach 23457ab2231SGreg Roach $tree = $request->getAttribute('tree'); 235*75964c75SGreg Roach assert($tree instanceof Tree); 23657ab2231SGreg Roach 237b6c326d8SGreg Roach return $this->viewResponse('modules/statistics-chart/custom', [ 238c6266a77SGreg Roach 'module' => $this, 239c6266a77SGreg Roach 'tree' => $tree, 240c6266a77SGreg Roach ]); 241c6266a77SGreg Roach } 242c6266a77SGreg Roach 243c6266a77SGreg Roach /** 2446ccdf4f0SGreg Roach * @param ServerRequestInterface $request 245c6266a77SGreg Roach * 2466ccdf4f0SGreg Roach * @return ResponseInterface 247c6266a77SGreg Roach */ 24857ab2231SGreg Roach public function getCustomChartAction(ServerRequestInterface $request): ResponseInterface 249c6266a77SGreg Roach { 25057ab2231SGreg Roach $statistics = app(Statistics::class); 25157ab2231SGreg Roach 252b6b9dcc9SGreg Roach $params = $request->getQueryParams(); 253b6b9dcc9SGreg Roach 254b6b9dcc9SGreg Roach $x_axis_type = (int) $params['x-as']; 255b6b9dcc9SGreg Roach $y_axis_type = (int) $params['y-as']; 256b6b9dcc9SGreg Roach $z_axis_type = (int) $params['z-as']; 257c6266a77SGreg Roach $ydata = []; 258c6266a77SGreg Roach 259c6266a77SGreg Roach switch ($x_axis_type) { 260c6266a77SGreg Roach case self::X_AXIS_INDIVIDUAL_MAP: 2616ccdf4f0SGreg Roach return response($statistics->chartDistribution( 262b6b9dcc9SGreg Roach $params['chart_shows'], 263b6b9dcc9SGreg Roach $params['chart_type'], 264b6b9dcc9SGreg Roach $params['SURN'] 265c6266a77SGreg Roach )); 266c6266a77SGreg Roach 267c6266a77SGreg Roach case self::X_AXIS_BIRTH_MAP: 2686ccdf4f0SGreg Roach return response($statistics->chartDistribution( 269b6b9dcc9SGreg Roach $params['chart_shows'], 270c6266a77SGreg Roach 'birth_distribution_chart' 271c6266a77SGreg Roach )); 272c6266a77SGreg Roach 273c6266a77SGreg Roach case self::X_AXIS_DEATH_MAP: 2746ccdf4f0SGreg Roach return response($statistics->chartDistribution( 275b6b9dcc9SGreg Roach $params['chart_shows'], 276c6266a77SGreg Roach 'death_distribution_chart' 277c6266a77SGreg Roach )); 278c6266a77SGreg Roach 279c6266a77SGreg Roach case self::X_AXIS_MARRIAGE_MAP: 2806ccdf4f0SGreg Roach return response($statistics->chartDistribution( 281b6b9dcc9SGreg Roach $params['chart_shows'], 282c6266a77SGreg Roach 'marriage_distribution_chart' 283c6266a77SGreg Roach )); 284c6266a77SGreg Roach 285c6266a77SGreg Roach case self::X_AXIS_BIRTH_MONTH: 286c6266a77SGreg Roach $chart_title = I18N::translate('Month of birth'); 287c6266a77SGreg Roach $x_axis_title = I18N::translate('Month'); 288c6266a77SGreg Roach $x_axis = $this->axisMonths(); 289c6266a77SGreg Roach 290c6266a77SGreg Roach switch ($y_axis_type) { 291c6266a77SGreg Roach case self::Y_AXIS_NUMBERS: 292c6266a77SGreg Roach $y_axis_title = I18N::translate('Individuals'); 293c6266a77SGreg Roach break; 294c6266a77SGreg Roach case self::Y_AXIS_PERCENT: 295c6266a77SGreg Roach $y_axis_title = '%'; 296c6266a77SGreg Roach break; 297c6266a77SGreg Roach default: 298c6266a77SGreg Roach throw new NotFoundHttpException(); 299c6266a77SGreg Roach } 300c6266a77SGreg Roach 301c6266a77SGreg Roach switch ($z_axis_type) { 302c6266a77SGreg Roach case self::Z_AXIS_ALL: 303c6266a77SGreg Roach $z_axis = $this->axisAll(); 304cde1d378SGreg Roach $rows = $statistics->statsBirthQuery()->get(); 305c6266a77SGreg Roach foreach ($rows as $row) { 306c6266a77SGreg Roach $this->fillYData($row->d_month, 0, $row->total, $x_axis, $z_axis, $ydata); 307c6266a77SGreg Roach } 308c6266a77SGreg Roach break; 309c6266a77SGreg Roach case self::Z_AXIS_SEX: 310c6266a77SGreg Roach $z_axis = $this->axisSexes(); 311cde1d378SGreg Roach $rows = $statistics->statsBirthBySexQuery()->get(); 312c6266a77SGreg Roach foreach ($rows as $row) { 313c6266a77SGreg Roach $this->fillYData($row->d_month, $row->i_sex, $row->total, $x_axis, $z_axis, $ydata); 314c6266a77SGreg Roach } 315c6266a77SGreg Roach break; 316c6266a77SGreg Roach case self::Z_AXIS_TIME: 317b6b9dcc9SGreg Roach $boundaries_csv = $params['z-axis-boundaries-periods']; 318c6266a77SGreg Roach $z_axis = $this->axisYears($boundaries_csv); 319c6266a77SGreg Roach $prev_boundary = 0; 320c6266a77SGreg Roach foreach (array_keys($z_axis) as $boundary) { 321cde1d378SGreg Roach $rows = $statistics->statsBirthQuery($prev_boundary, $boundary)->get(); 322c6266a77SGreg Roach foreach ($rows as $row) { 323c6266a77SGreg Roach $this->fillYData($row->d_month, $boundary, $row->total, $x_axis, $z_axis, $ydata); 324c6266a77SGreg Roach } 325c6266a77SGreg Roach $prev_boundary = $boundary + 1; 326c6266a77SGreg Roach } 327c6266a77SGreg Roach break; 328c6266a77SGreg Roach default: 329c6266a77SGreg Roach throw new NotFoundHttpException(); 330c6266a77SGreg Roach } 331c6266a77SGreg Roach 3326ccdf4f0SGreg Roach return response($this->myPlot($chart_title, $x_axis, $x_axis_title, $ydata, $y_axis_title, $z_axis, $y_axis_type)); 333c6266a77SGreg Roach 334c6266a77SGreg Roach case self::X_AXIS_DEATH_MONTH: 335c6266a77SGreg Roach $chart_title = I18N::translate('Month of death'); 336c6266a77SGreg Roach $x_axis_title = I18N::translate('Month'); 337c6266a77SGreg Roach $x_axis = $this->axisMonths(); 338c6266a77SGreg Roach 339c6266a77SGreg Roach switch ($y_axis_type) { 340c6266a77SGreg Roach case self::Y_AXIS_NUMBERS: 341c6266a77SGreg Roach $y_axis_title = I18N::translate('Individuals'); 342c6266a77SGreg Roach break; 343c6266a77SGreg Roach case self::Y_AXIS_PERCENT: 344c6266a77SGreg Roach $y_axis_title = '%'; 345c6266a77SGreg Roach break; 346c6266a77SGreg Roach default: 347c6266a77SGreg Roach throw new NotFoundHttpException(); 348c6266a77SGreg Roach } 349c6266a77SGreg Roach 350c6266a77SGreg Roach switch ($z_axis_type) { 351c6266a77SGreg Roach case self::Z_AXIS_ALL: 352c6266a77SGreg Roach $z_axis = $this->axisAll(); 353cde1d378SGreg Roach $rows = $statistics->statsDeathQuery()->get(); 354c6266a77SGreg Roach foreach ($rows as $row) { 355c6266a77SGreg Roach $this->fillYData($row->d_month, 0, $row->total, $x_axis, $z_axis, $ydata); 356c6266a77SGreg Roach } 357c6266a77SGreg Roach break; 358c6266a77SGreg Roach case self::Z_AXIS_SEX: 359c6266a77SGreg Roach $z_axis = $this->axisSexes(); 360cde1d378SGreg Roach $rows = $statistics->statsDeathBySexQuery()->get(); 361c6266a77SGreg Roach foreach ($rows as $row) { 362c6266a77SGreg Roach $this->fillYData($row->d_month, $row->i_sex, $row->total, $x_axis, $z_axis, $ydata); 363c6266a77SGreg Roach } 364c6266a77SGreg Roach break; 365c6266a77SGreg Roach case self::Z_AXIS_TIME: 366b6b9dcc9SGreg Roach $boundaries_csv = $params['z-axis-boundaries-periods']; 367c6266a77SGreg Roach $z_axis = $this->axisYears($boundaries_csv); 368c6266a77SGreg Roach $prev_boundary = 0; 369c6266a77SGreg Roach foreach (array_keys($z_axis) as $boundary) { 370cde1d378SGreg Roach $rows = $statistics->statsDeathQuery($prev_boundary, $boundary)->get(); 371c6266a77SGreg Roach foreach ($rows as $row) { 372c6266a77SGreg Roach $this->fillYData($row->d_month, $boundary, $row->total, $x_axis, $z_axis, $ydata); 373c6266a77SGreg Roach } 374c6266a77SGreg Roach $prev_boundary = $boundary + 1; 375c6266a77SGreg Roach } 376c6266a77SGreg Roach break; 377c6266a77SGreg Roach default: 378c6266a77SGreg Roach throw new NotFoundHttpException(); 379c6266a77SGreg Roach } 380c6266a77SGreg Roach 3816ccdf4f0SGreg Roach return response($this->myPlot($chart_title, $x_axis, $x_axis_title, $ydata, $y_axis_title, $z_axis, $y_axis_type)); 382c6266a77SGreg Roach 383c6266a77SGreg Roach case self::X_AXIS_MARRIAGE_MONTH: 384c6266a77SGreg Roach $chart_title = I18N::translate('Month of marriage'); 385c6266a77SGreg Roach $x_axis_title = I18N::translate('Month'); 386c6266a77SGreg Roach $x_axis = $this->axisMonths(); 387c6266a77SGreg Roach 388c6266a77SGreg Roach switch ($y_axis_type) { 389c6266a77SGreg Roach case self::Y_AXIS_NUMBERS: 390c6266a77SGreg Roach $y_axis_title = I18N::translate('Families'); 391c6266a77SGreg Roach break; 392c6266a77SGreg Roach case self::Y_AXIS_PERCENT: 393c6266a77SGreg Roach $y_axis_title = '%'; 394c6266a77SGreg Roach break; 395c6266a77SGreg Roach default: 396c6266a77SGreg Roach throw new NotFoundHttpException(); 397c6266a77SGreg Roach } 398c6266a77SGreg Roach 399c6266a77SGreg Roach switch ($z_axis_type) { 400c6266a77SGreg Roach case self::Z_AXIS_ALL: 401c6266a77SGreg Roach $z_axis = $this->axisAll(); 402e6f3d5e2SGreg Roach $rows = $statistics->statsMarriageQuery()->get(); 403c6266a77SGreg Roach foreach ($rows as $row) { 404c6266a77SGreg Roach $this->fillYData($row->d_month, 0, $row->total, $x_axis, $z_axis, $ydata); 405c6266a77SGreg Roach } 406c6266a77SGreg Roach break; 407c6266a77SGreg Roach case self::Z_AXIS_TIME: 408b6b9dcc9SGreg Roach $boundaries_csv = $params['z-axis-boundaries-periods']; 409c6266a77SGreg Roach $z_axis = $this->axisYears($boundaries_csv); 410c6266a77SGreg Roach $prev_boundary = 0; 411c6266a77SGreg Roach foreach (array_keys($z_axis) as $boundary) { 412e6f3d5e2SGreg Roach $rows = $statistics->statsMarriageQuery($prev_boundary, $boundary)->get(); 413c6266a77SGreg Roach foreach ($rows as $row) { 414c6266a77SGreg Roach $this->fillYData($row->d_month, $boundary, $row->total, $x_axis, $z_axis, $ydata); 415c6266a77SGreg Roach } 416c6266a77SGreg Roach $prev_boundary = $boundary + 1; 417c6266a77SGreg Roach } 418c6266a77SGreg Roach break; 419c6266a77SGreg Roach default: 420c6266a77SGreg Roach throw new NotFoundHttpException(); 421c6266a77SGreg Roach } 422c6266a77SGreg Roach 4236ccdf4f0SGreg Roach return response($this->myPlot($chart_title, $x_axis, $x_axis_title, $ydata, $y_axis_title, $z_axis, $y_axis_type)); 424c6266a77SGreg Roach 425c6266a77SGreg Roach case self::X_AXIS_FIRST_CHILD_MONTH: 426c6266a77SGreg Roach $chart_title = I18N::translate('Month of birth of first child in a relation'); 427c6266a77SGreg Roach $x_axis_title = I18N::translate('Month'); 428c6266a77SGreg Roach $x_axis = $this->axisMonths(); 429c6266a77SGreg Roach 430c6266a77SGreg Roach switch ($y_axis_type) { 431c6266a77SGreg Roach case self::Y_AXIS_NUMBERS: 432c6266a77SGreg Roach $y_axis_title = I18N::translate('Children'); 433c6266a77SGreg Roach break; 434c6266a77SGreg Roach case self::Y_AXIS_PERCENT: 435c6266a77SGreg Roach $y_axis_title = '%'; 436c6266a77SGreg Roach break; 437c6266a77SGreg Roach default: 438c6266a77SGreg Roach throw new NotFoundHttpException(); 439c6266a77SGreg Roach } 440c6266a77SGreg Roach 441c6266a77SGreg Roach switch ($z_axis_type) { 442c6266a77SGreg Roach case self::Z_AXIS_ALL: 443c6266a77SGreg Roach $z_axis = $this->axisAll(); 444999da590SGreg Roach $rows = $statistics->monthFirstChildQuery()->get(); 445c6266a77SGreg Roach foreach ($rows as $row) { 446c6266a77SGreg Roach $this->fillYData($row->d_month, 0, $row->total, $x_axis, $z_axis, $ydata); 447c6266a77SGreg Roach } 448c6266a77SGreg Roach break; 449c6266a77SGreg Roach case self::Z_AXIS_SEX: 450c6266a77SGreg Roach $z_axis = $this->axisSexes(); 451999da590SGreg Roach $rows = $statistics->monthFirstChildBySexQuery()->get(); 452c6266a77SGreg Roach foreach ($rows as $row) { 453c6266a77SGreg Roach $this->fillYData($row->d_month, $row->i_sex, $row->total, $x_axis, $z_axis, $ydata); 454c6266a77SGreg Roach } 455c6266a77SGreg Roach break; 456c6266a77SGreg Roach case self::Z_AXIS_TIME: 457b6b9dcc9SGreg Roach $boundaries_csv = $params['z-axis-boundaries-periods']; 458c6266a77SGreg Roach $z_axis = $this->axisYears($boundaries_csv); 459c6266a77SGreg Roach $prev_boundary = 0; 460c6266a77SGreg Roach foreach (array_keys($z_axis) as $boundary) { 461999da590SGreg Roach $rows = $statistics->monthFirstChildQuery($prev_boundary, $boundary)->get(); 462c6266a77SGreg Roach foreach ($rows as $row) { 463c6266a77SGreg Roach $this->fillYData($row->d_month, $boundary, $row->total, $x_axis, $z_axis, $ydata); 464c6266a77SGreg Roach } 465c6266a77SGreg Roach $prev_boundary = $boundary + 1; 466c6266a77SGreg Roach } 467c6266a77SGreg Roach break; 468c6266a77SGreg Roach default: 469c6266a77SGreg Roach throw new NotFoundHttpException(); 470c6266a77SGreg Roach } 471c6266a77SGreg Roach 4726ccdf4f0SGreg Roach return response($this->myPlot($chart_title, $x_axis, $x_axis_title, $ydata, $y_axis_title, $z_axis, $y_axis_type)); 473c6266a77SGreg Roach 474c6266a77SGreg Roach case self::X_AXIS_FIRST_MARRIAGE_MONTH: 475c6266a77SGreg Roach $chart_title = I18N::translate('Month of first marriage'); 476c6266a77SGreg Roach $x_axis_title = I18N::translate('Month'); 477c6266a77SGreg Roach $x_axis = $this->axisMonths(); 478c6266a77SGreg Roach 479c6266a77SGreg Roach switch ($y_axis_type) { 480c6266a77SGreg Roach case self::Y_AXIS_NUMBERS: 481c6266a77SGreg Roach $y_axis_title = I18N::translate('Families'); 482c6266a77SGreg Roach break; 483c6266a77SGreg Roach case self::Y_AXIS_PERCENT: 484c6266a77SGreg Roach $y_axis_title = '%'; 485c6266a77SGreg Roach break; 486c6266a77SGreg Roach default: 487c6266a77SGreg Roach throw new NotFoundHttpException(); 488c6266a77SGreg Roach } 489c6266a77SGreg Roach 490c6266a77SGreg Roach switch ($z_axis_type) { 491c6266a77SGreg Roach case self::Z_AXIS_ALL: 492c6266a77SGreg Roach $z_axis = $this->axisAll(); 493e6f3d5e2SGreg Roach $rows = $statistics->statsFirstMarriageQuery()->get(); 494c6266a77SGreg Roach $indi = []; 495c6266a77SGreg Roach $fam = []; 496c6266a77SGreg Roach foreach ($rows as $row) { 4976960d4a1SGreg Roach if (!in_array($row->indi, $indi, true) && !in_array($row->fams, $fam, true)) { 498c6266a77SGreg Roach $this->fillYData($row->month, 0, 1, $x_axis, $z_axis, $ydata); 499c6266a77SGreg Roach } 500c6266a77SGreg Roach $indi[] = $row->indi; 501c6266a77SGreg Roach $fam[] = $row->fams; 502c6266a77SGreg Roach } 503c6266a77SGreg Roach break; 504c6266a77SGreg Roach case self::Z_AXIS_TIME: 505b6b9dcc9SGreg Roach $boundaries_csv = $params['z-axis-boundaries-periods']; 506c6266a77SGreg Roach $z_axis = $this->axisYears($boundaries_csv); 507c6266a77SGreg Roach $prev_boundary = 0; 508c6266a77SGreg Roach $indi = []; 509c6266a77SGreg Roach $fam = []; 510c6266a77SGreg Roach foreach (array_keys($z_axis) as $boundary) { 511e6f3d5e2SGreg Roach $rows = $statistics->statsFirstMarriageQuery($prev_boundary, $boundary)->get(); 512c6266a77SGreg Roach foreach ($rows as $row) { 5136960d4a1SGreg Roach if (!in_array($row->indi, $indi, true) && !in_array($row->fams, $fam, true)) { 514c6266a77SGreg Roach $this->fillYData($row->month, $boundary, 1, $x_axis, $z_axis, $ydata); 515c6266a77SGreg Roach } 516c6266a77SGreg Roach $indi[] = $row->indi; 517c6266a77SGreg Roach $fam[] = $row->fams; 518c6266a77SGreg Roach } 519c6266a77SGreg Roach $prev_boundary = $boundary + 1; 520c6266a77SGreg Roach } 521c6266a77SGreg Roach break; 522c6266a77SGreg Roach default: 523c6266a77SGreg Roach throw new NotFoundHttpException(); 524c6266a77SGreg Roach } 525c6266a77SGreg Roach 5266ccdf4f0SGreg Roach return response($this->myPlot($chart_title, $x_axis, $x_axis_title, $ydata, $y_axis_title, $z_axis, $y_axis_type)); 527c6266a77SGreg Roach 528c6266a77SGreg Roach case self::X_AXIS_AGE_AT_DEATH: 529c6266a77SGreg Roach $chart_title = I18N::translate('Average age at death'); 530c6266a77SGreg Roach $x_axis_title = I18N::translate('age'); 531b6b9dcc9SGreg Roach $boundaries_csv = $params['x-axis-boundaries-ages']; 532c6266a77SGreg Roach $x_axis = $this->axisNumbers($boundaries_csv); 533c6266a77SGreg Roach 534c6266a77SGreg Roach switch ($y_axis_type) { 535c6266a77SGreg Roach case self::Y_AXIS_NUMBERS: 536c6266a77SGreg Roach $y_axis_title = I18N::translate('Individuals'); 537c6266a77SGreg Roach break; 538c6266a77SGreg Roach case self::Y_AXIS_PERCENT: 539c6266a77SGreg Roach $y_axis_title = '%'; 540c6266a77SGreg Roach break; 541c6266a77SGreg Roach default: 542c6266a77SGreg Roach throw new NotFoundHttpException(); 543c6266a77SGreg Roach } 544c6266a77SGreg Roach 545c6266a77SGreg Roach switch ($z_axis_type) { 546c6266a77SGreg Roach case self::Z_AXIS_ALL: 547c6266a77SGreg Roach $z_axis = $this->axisAll(); 5489219296aSGreg Roach $rows = $statistics->statsAgeQuery('DEAT'); 549c6266a77SGreg Roach foreach ($rows as $row) { 550c6266a77SGreg Roach foreach ($row as $age) { 551c6266a77SGreg Roach $years = (int) ($age / self::DAYS_IN_YEAR); 552c6266a77SGreg Roach $this->fillYData($years, 0, 1, $x_axis, $z_axis, $ydata); 553c6266a77SGreg Roach } 554c6266a77SGreg Roach } 555c6266a77SGreg Roach break; 556c6266a77SGreg Roach case self::Z_AXIS_SEX: 557c6266a77SGreg Roach $z_axis = $this->axisSexes(); 558c6266a77SGreg Roach foreach (array_keys($z_axis) as $sex) { 5599219296aSGreg Roach $rows = $statistics->statsAgeQuery('DEAT', $sex); 560c6266a77SGreg Roach foreach ($rows as $row) { 561c6266a77SGreg Roach foreach ($row as $age) { 562c6266a77SGreg Roach $years = (int) ($age / self::DAYS_IN_YEAR); 563c6266a77SGreg Roach $this->fillYData($years, $sex, 1, $x_axis, $z_axis, $ydata); 564c6266a77SGreg Roach } 565c6266a77SGreg Roach } 566c6266a77SGreg Roach } 567c6266a77SGreg Roach break; 568c6266a77SGreg Roach case self::Z_AXIS_TIME: 569b6b9dcc9SGreg Roach $boundaries_csv = $params['z-axis-boundaries-periods']; 570c6266a77SGreg Roach $z_axis = $this->axisYears($boundaries_csv); 571c6266a77SGreg Roach $prev_boundary = 0; 572c6266a77SGreg Roach foreach (array_keys($z_axis) as $boundary) { 5739219296aSGreg Roach $rows = $statistics->statsAgeQuery('DEAT', 'BOTH', $prev_boundary, $boundary); 574c6266a77SGreg Roach foreach ($rows as $row) { 575c6266a77SGreg Roach foreach ($row as $age) { 576c6266a77SGreg Roach $years = (int) ($age / self::DAYS_IN_YEAR); 577c6266a77SGreg Roach $this->fillYData($years, $boundary, 1, $x_axis, $z_axis, $ydata); 578c6266a77SGreg Roach } 579c6266a77SGreg Roach } 580c6266a77SGreg Roach $prev_boundary = $boundary + 1; 581c6266a77SGreg Roach } 582c6266a77SGreg Roach 583c6266a77SGreg Roach break; 584c6266a77SGreg Roach default: 585c6266a77SGreg Roach throw new NotFoundHttpException(); 586c6266a77SGreg Roach } 587c6266a77SGreg Roach 5886ccdf4f0SGreg Roach return response($this->myPlot($chart_title, $x_axis, $x_axis_title, $ydata, $y_axis_title, $z_axis, $y_axis_type)); 589c6266a77SGreg Roach 590c6266a77SGreg Roach case self::X_AXIS_AGE_AT_MARRIAGE: 591c6266a77SGreg Roach $chart_title = I18N::translate('Age in year of marriage'); 592c6266a77SGreg Roach $x_axis_title = I18N::translate('age'); 593b6b9dcc9SGreg Roach $boundaries_csv = $params['x-axis-boundaries-ages_m']; 594c6266a77SGreg Roach $x_axis = $this->axisNumbers($boundaries_csv); 595c6266a77SGreg Roach 596c6266a77SGreg Roach switch ($y_axis_type) { 597c6266a77SGreg Roach case self::Y_AXIS_NUMBERS: 598c6266a77SGreg Roach $y_axis_title = I18N::translate('Individuals'); 599c6266a77SGreg Roach break; 600c6266a77SGreg Roach case self::Y_AXIS_PERCENT: 601c6266a77SGreg Roach $y_axis_title = '%'; 602c6266a77SGreg Roach break; 603c6266a77SGreg Roach default: 604c6266a77SGreg Roach throw new NotFoundHttpException(); 605c6266a77SGreg Roach } 606c6266a77SGreg Roach 607c6266a77SGreg Roach switch ($z_axis_type) { 608c6266a77SGreg Roach case self::Z_AXIS_ALL: 609c6266a77SGreg Roach $z_axis = $this->axisAll(); 610afa8d404SGreg Roach // The stats query doesn't have an "all" function, so query M/F separately 611afa8d404SGreg Roach foreach (['M', 'F'] as $sex) { 6129219296aSGreg Roach $rows = $statistics->statsMarrAgeQuery($sex); 613c6266a77SGreg Roach foreach ($rows as $row) { 614c6266a77SGreg Roach $years = (int) ($row->age / self::DAYS_IN_YEAR); 615c6266a77SGreg Roach $this->fillYData($years, 0, 1, $x_axis, $z_axis, $ydata); 616c6266a77SGreg Roach } 617c6266a77SGreg Roach } 618c6266a77SGreg Roach break; 619c6266a77SGreg Roach case self::Z_AXIS_SEX: 620c6266a77SGreg Roach $z_axis = $this->axisSexes(); 621c6266a77SGreg Roach foreach (array_keys($z_axis) as $sex) { 6229219296aSGreg Roach $rows = $statistics->statsMarrAgeQuery($sex); 623c6266a77SGreg Roach foreach ($rows as $row) { 624c6266a77SGreg Roach $years = (int) ($row->age / self::DAYS_IN_YEAR); 625c6266a77SGreg Roach $this->fillYData($years, $sex, 1, $x_axis, $z_axis, $ydata); 626c6266a77SGreg Roach } 627c6266a77SGreg Roach } 628c6266a77SGreg Roach break; 629c6266a77SGreg Roach case self::Z_AXIS_TIME: 630b6b9dcc9SGreg Roach $boundaries_csv = $params['z-axis-boundaries-periods']; 631c6266a77SGreg Roach $z_axis = $this->axisYears($boundaries_csv); 632afa8d404SGreg Roach // The stats query doesn't have an "all" function, so query M/F separately 633afa8d404SGreg Roach foreach (['M', 'F'] as $sex) { 634c6266a77SGreg Roach $prev_boundary = 0; 635c6266a77SGreg Roach foreach (array_keys($z_axis) as $boundary) { 6369219296aSGreg Roach $rows = $statistics->statsMarrAgeQuery($sex, $prev_boundary, $boundary); 637c6266a77SGreg Roach foreach ($rows as $row) { 638c6266a77SGreg Roach $years = (int) ($row->age / self::DAYS_IN_YEAR); 639c6266a77SGreg Roach $this->fillYData($years, $boundary, 1, $x_axis, $z_axis, $ydata); 640c6266a77SGreg Roach } 641c6266a77SGreg Roach $prev_boundary = $boundary + 1; 642c6266a77SGreg Roach } 643c6266a77SGreg Roach } 644c6266a77SGreg Roach break; 645c6266a77SGreg Roach default: 646c6266a77SGreg Roach throw new NotFoundHttpException(); 647c6266a77SGreg Roach } 648c6266a77SGreg Roach 6496ccdf4f0SGreg Roach return response($this->myPlot($chart_title, $x_axis, $x_axis_title, $ydata, $y_axis_title, $z_axis, $y_axis_type)); 650c6266a77SGreg Roach 651c6266a77SGreg Roach case self::X_AXIS_AGE_AT_FIRST_MARRIAGE: 652c6266a77SGreg Roach $chart_title = I18N::translate('Age in year of first marriage'); 653c6266a77SGreg Roach $x_axis_title = I18N::translate('age'); 654b6b9dcc9SGreg Roach $boundaries_csv = $params['x-axis-boundaries-ages_m']; 655c6266a77SGreg Roach $x_axis = $this->axisNumbers($boundaries_csv); 656c6266a77SGreg Roach 657c6266a77SGreg Roach switch ($y_axis_type) { 658c6266a77SGreg Roach case self::Y_AXIS_NUMBERS: 659c6266a77SGreg Roach $y_axis_title = I18N::translate('Individuals'); 660c6266a77SGreg Roach break; 661c6266a77SGreg Roach case self::Y_AXIS_PERCENT: 662c6266a77SGreg Roach $y_axis_title = '%'; 663c6266a77SGreg Roach break; 664c6266a77SGreg Roach default: 665c6266a77SGreg Roach throw new NotFoundHttpException(); 666c6266a77SGreg Roach } 667c6266a77SGreg Roach 668c6266a77SGreg Roach switch ($z_axis_type) { 669c6266a77SGreg Roach case self::Z_AXIS_ALL: 670c6266a77SGreg Roach $z_axis = $this->axisAll(); 671afa8d404SGreg Roach // The stats query doesn't have an "all" function, so query M/F separately 672afa8d404SGreg Roach foreach (['M', 'F'] as $sex) { 6739219296aSGreg Roach $rows = $statistics->statsMarrAgeQuery($sex); 674c6266a77SGreg Roach $indi = []; 675c6266a77SGreg Roach foreach ($rows as $row) { 6766960d4a1SGreg Roach if (!in_array($row->d_gid, $indi, true)) { 677c6266a77SGreg Roach $years = (int) ($row->age / self::DAYS_IN_YEAR); 678c6266a77SGreg Roach $this->fillYData($years, 0, 1, $x_axis, $z_axis, $ydata); 679c6266a77SGreg Roach $indi[] = $row->d_gid; 680c6266a77SGreg Roach } 681c6266a77SGreg Roach } 682c6266a77SGreg Roach } 683c6266a77SGreg Roach break; 684c6266a77SGreg Roach case self::Z_AXIS_SEX: 685c6266a77SGreg Roach $z_axis = $this->axisSexes(); 686c6266a77SGreg Roach foreach (array_keys($z_axis) as $sex) { 6879219296aSGreg Roach $rows = $statistics->statsMarrAgeQuery($sex); 688c6266a77SGreg Roach $indi = []; 689c6266a77SGreg Roach foreach ($rows as $row) { 6906960d4a1SGreg Roach if (!in_array($row->d_gid, $indi, true)) { 691c6266a77SGreg Roach $years = (int) ($row->age / self::DAYS_IN_YEAR); 692c6266a77SGreg Roach $this->fillYData($years, $sex, 1, $x_axis, $z_axis, $ydata); 693c6266a77SGreg Roach $indi[] = $row->d_gid; 694c6266a77SGreg Roach } 695c6266a77SGreg Roach } 696c6266a77SGreg Roach } 697c6266a77SGreg Roach break; 698c6266a77SGreg Roach case self::Z_AXIS_TIME: 699b6b9dcc9SGreg Roach $boundaries_csv = $params['z-axis-boundaries-periods']; 700c6266a77SGreg Roach $z_axis = $this->axisYears($boundaries_csv); 701afa8d404SGreg Roach // The stats query doesn't have an "all" function, so query M/F separately 702afa8d404SGreg Roach foreach (['M', 'F'] as $sex) { 703c6266a77SGreg Roach $prev_boundary = 0; 704c6266a77SGreg Roach $indi = []; 705c6266a77SGreg Roach foreach (array_keys($z_axis) as $boundary) { 7069219296aSGreg Roach $rows = $statistics->statsMarrAgeQuery($sex, $prev_boundary, $boundary); 707c6266a77SGreg Roach foreach ($rows as $row) { 7086960d4a1SGreg Roach if (!in_array($row->d_gid, $indi, true)) { 709c6266a77SGreg Roach $years = (int) ($row->age / self::DAYS_IN_YEAR); 710c6266a77SGreg Roach $this->fillYData($years, $boundary, 1, $x_axis, $z_axis, $ydata); 711c6266a77SGreg Roach $indi[] = $row->d_gid; 712c6266a77SGreg Roach } 713c6266a77SGreg Roach } 714c6266a77SGreg Roach $prev_boundary = $boundary + 1; 715c6266a77SGreg Roach } 716c6266a77SGreg Roach } 717c6266a77SGreg Roach break; 718c6266a77SGreg Roach default: 719c6266a77SGreg Roach throw new NotFoundHttpException(); 720c6266a77SGreg Roach } 721c6266a77SGreg Roach 7226ccdf4f0SGreg Roach return response($this->myPlot($chart_title, $x_axis, $x_axis_title, $ydata, $y_axis_title, $z_axis, $y_axis_type)); 723c6266a77SGreg Roach 724c6266a77SGreg Roach case self::X_AXIS_NUMBER_OF_CHILDREN: 725c6266a77SGreg Roach $chart_title = I18N::translate('Number of children'); 726c6266a77SGreg Roach $x_axis_title = I18N::translate('Children'); 7276960d4a1SGreg Roach $x_axis = $this->axisNumbers('0,1,2,3,4,5,6,7,8,9,10'); 728c6266a77SGreg Roach 729c6266a77SGreg Roach switch ($y_axis_type) { 730c6266a77SGreg Roach case self::Y_AXIS_NUMBERS: 731c6266a77SGreg Roach $y_axis_title = I18N::translate('Families'); 732c6266a77SGreg Roach break; 733c6266a77SGreg Roach case self::Y_AXIS_PERCENT: 734c6266a77SGreg Roach $y_axis_title = '%'; 735c6266a77SGreg Roach break; 736c6266a77SGreg Roach default: 737c6266a77SGreg Roach throw new NotFoundHttpException(); 738c6266a77SGreg Roach } 739c6266a77SGreg Roach 740c6266a77SGreg Roach switch ($z_axis_type) { 741c6266a77SGreg Roach case self::Z_AXIS_ALL: 742c6266a77SGreg Roach $z_axis = $this->axisAll(); 7439219296aSGreg Roach $rows = $statistics->statsChildrenQuery(); 744c6266a77SGreg Roach foreach ($rows as $row) { 745c6266a77SGreg Roach $this->fillYData($row->f_numchil, 0, $row->total, $x_axis, $z_axis, $ydata); 746c6266a77SGreg Roach } 747c6266a77SGreg Roach break; 748c6266a77SGreg Roach case self::Z_AXIS_TIME: 749b6b9dcc9SGreg Roach $boundaries_csv = $params['z-axis-boundaries-periods']; 750c6266a77SGreg Roach $z_axis = $this->axisYears($boundaries_csv); 751c6266a77SGreg Roach $prev_boundary = 0; 752c6266a77SGreg Roach foreach (array_keys($z_axis) as $boundary) { 753b1126ab4SGreg Roach $rows = $statistics->statsChildrenQuery($prev_boundary, $boundary); 754c6266a77SGreg Roach foreach ($rows as $row) { 755c6266a77SGreg Roach $this->fillYData($row->f_numchil, $boundary, $row->total, $x_axis, $z_axis, $ydata); 756c6266a77SGreg Roach } 757c6266a77SGreg Roach $prev_boundary = $boundary + 1; 758c6266a77SGreg Roach } 759c6266a77SGreg Roach break; 760c6266a77SGreg Roach default: 761c6266a77SGreg Roach throw new NotFoundHttpException(); 762c6266a77SGreg Roach } 763c6266a77SGreg Roach 7646ccdf4f0SGreg Roach return response($this->myPlot($chart_title, $x_axis, $x_axis_title, $ydata, $y_axis_title, $z_axis, $y_axis_type)); 765c6266a77SGreg Roach 766c6266a77SGreg Roach default: 767c6266a77SGreg Roach throw new NotFoundHttpException(); 768c6266a77SGreg Roach break; 769c6266a77SGreg Roach } 770c6266a77SGreg Roach } 771c6266a77SGreg Roach 772c6266a77SGreg Roach /** 773c6266a77SGreg Roach * @return string[] 774c6266a77SGreg Roach */ 775c6266a77SGreg Roach private function axisAll(): array 776c6266a77SGreg Roach { 777c6266a77SGreg Roach return [ 778c6266a77SGreg Roach I18N::translate('Total'), 779c6266a77SGreg Roach ]; 780c6266a77SGreg Roach } 781c6266a77SGreg Roach 782c6266a77SGreg Roach /** 783c6266a77SGreg Roach * @return string[] 784c6266a77SGreg Roach */ 785c6266a77SGreg Roach private function axisSexes(): array 786c6266a77SGreg Roach { 787c6266a77SGreg Roach return [ 788c6266a77SGreg Roach 'M' => I18N::translate('Male'), 789c6266a77SGreg Roach 'F' => I18N::translate('Female'), 790c6266a77SGreg Roach ]; 791c6266a77SGreg Roach } 792c6266a77SGreg Roach 793c6266a77SGreg Roach /** 794c6266a77SGreg Roach * Labels for the X axis 795c6266a77SGreg Roach * 796c6266a77SGreg Roach * @return string[] 797c6266a77SGreg Roach */ 798c6266a77SGreg Roach private function axisMonths(): array 799c6266a77SGreg Roach { 800c6266a77SGreg Roach return [ 801c6266a77SGreg Roach 'JAN' => I18N::translateContext('NOMINATIVE', 'January'), 802c6266a77SGreg Roach 'FEB' => I18N::translateContext('NOMINATIVE', 'February'), 803c6266a77SGreg Roach 'MAR' => I18N::translateContext('NOMINATIVE', 'March'), 804c6266a77SGreg Roach 'APR' => I18N::translateContext('NOMINATIVE', 'April'), 805c6266a77SGreg Roach 'MAY' => I18N::translateContext('NOMINATIVE', 'May'), 806c6266a77SGreg Roach 'JUN' => I18N::translateContext('NOMINATIVE', 'June'), 807c6266a77SGreg Roach 'JUL' => I18N::translateContext('NOMINATIVE', 'July'), 808c6266a77SGreg Roach 'AUG' => I18N::translateContext('NOMINATIVE', 'August'), 809c6266a77SGreg Roach 'SEP' => I18N::translateContext('NOMINATIVE', 'September'), 810c6266a77SGreg Roach 'OCT' => I18N::translateContext('NOMINATIVE', 'October'), 811c6266a77SGreg Roach 'NOV' => I18N::translateContext('NOMINATIVE', 'November'), 812c6266a77SGreg Roach 'DEC' => I18N::translateContext('NOMINATIVE', 'December'), 813c6266a77SGreg Roach ]; 814c6266a77SGreg Roach } 815c6266a77SGreg Roach 816c6266a77SGreg Roach /** 817c6266a77SGreg Roach * Convert a list of N year-boundaries into N+1 year-ranges for the z-axis. 818c6266a77SGreg Roach * 819c6266a77SGreg Roach * @param string $boundaries_csv 820c6266a77SGreg Roach * 821c6266a77SGreg Roach * @return string[] 822c6266a77SGreg Roach */ 823c6266a77SGreg Roach private function axisYears(string $boundaries_csv): array 824c6266a77SGreg Roach { 825c6266a77SGreg Roach $boundaries = explode(',', $boundaries_csv); 826c6266a77SGreg Roach 827c6266a77SGreg Roach $axis = []; 828c6266a77SGreg Roach foreach ($boundaries as $n => $boundary) { 829c6266a77SGreg Roach if ($n === 0) { 830c6266a77SGreg Roach $date = new Date('BEF ' . $boundary); 831c6266a77SGreg Roach } else { 832c6266a77SGreg Roach $date = new Date('BET ' . $boundaries[$n - 1] . ' AND ' . ($boundary - 1)); 833c6266a77SGreg Roach } 834c6266a77SGreg Roach $axis[$boundary - 1] = strip_tags($date->display()); 835c6266a77SGreg Roach } 836c6266a77SGreg Roach 837c6266a77SGreg Roach $date = new Date('AFT ' . $boundaries[count($boundaries) - 1]); 838c6266a77SGreg Roach $axis[PHP_INT_MAX] = strip_tags($date->display()); 839c6266a77SGreg Roach 840c6266a77SGreg Roach return $axis; 841c6266a77SGreg Roach } 842c6266a77SGreg Roach 843c6266a77SGreg Roach /** 844c6266a77SGreg Roach * Create the X axis. 845c6266a77SGreg Roach * 846c6266a77SGreg Roach * @param string $boundaries_csv 847c6266a77SGreg Roach * 848c6266a77SGreg Roach * @return array 849c6266a77SGreg Roach */ 850c6266a77SGreg Roach private function axisNumbers(string $boundaries_csv): array 851c6266a77SGreg Roach { 852c6266a77SGreg Roach $boundaries = explode(',', $boundaries_csv); 853c6266a77SGreg Roach 8540b5fd0a6SGreg Roach $boundaries = array_map(static function (string $x): int { 855c6266a77SGreg Roach return (int) $x; 856c6266a77SGreg Roach }, $boundaries); 857c6266a77SGreg Roach 858c6266a77SGreg Roach $axis = []; 859c6266a77SGreg Roach foreach ($boundaries as $n => $boundary) { 860c6266a77SGreg Roach if ($n === 0) { 8616960d4a1SGreg Roach $prev_boundary = 0; 8626960d4a1SGreg Roach } else { 8636960d4a1SGreg Roach $prev_boundary = $boundaries[$n - 1] + 1; 8646960d4a1SGreg Roach } 8656960d4a1SGreg Roach 8666960d4a1SGreg Roach if ($prev_boundary === $boundary) { 867c6266a77SGreg Roach /* I18N: A range of numbers */ 8686960d4a1SGreg Roach $axis[$boundary] = I18N::number($boundary); 869c6266a77SGreg Roach } else { 870c6266a77SGreg Roach /* I18N: A range of numbers */ 8716960d4a1SGreg Roach $axis[$boundary] = I18N::translate('%1$s–%2$s', I18N::number($prev_boundary), I18N::number($boundary)); 872c6266a77SGreg Roach } 873c6266a77SGreg Roach } 874c6266a77SGreg Roach 875c6266a77SGreg Roach /* I18N: Label on a graph; 40+ means 40 or more */ 876c6266a77SGreg Roach $axis[PHP_INT_MAX] = I18N::translate('%s+', I18N::number($boundaries[count($boundaries) - 1])); 877c6266a77SGreg Roach 878c6266a77SGreg Roach return $axis; 879c6266a77SGreg Roach } 880c6266a77SGreg Roach 881c6266a77SGreg Roach /** 882c6266a77SGreg Roach * Calculate the Y axis. 883c6266a77SGreg Roach * 884c6266a77SGreg Roach * @param int|string $x 885c6266a77SGreg Roach * @param int|string $z 886c6266a77SGreg Roach * @param int|string $value 887c6266a77SGreg Roach * @param array $x_axis 888c6266a77SGreg Roach * @param array $z_axis 889c6266a77SGreg Roach * @param int[][] $ydata 890c6266a77SGreg Roach * 891c6266a77SGreg Roach * @return void 892c6266a77SGreg Roach */ 893e364afe4SGreg Roach private function fillYData($x, $z, $value, array $x_axis, array $z_axis, array &$ydata): void 894c6266a77SGreg Roach { 895c6266a77SGreg Roach $x = $this->findAxisEntry($x, $x_axis); 896c6266a77SGreg Roach $z = $this->findAxisEntry($z, $z_axis); 897c6266a77SGreg Roach 8986960d4a1SGreg Roach if (!array_key_exists($z, $z_axis)) { 899c6266a77SGreg Roach foreach (array_keys($z_axis) as $key) { 900c6266a77SGreg Roach if ($value <= $key) { 901c6266a77SGreg Roach $z = $key; 902c6266a77SGreg Roach break; 903c6266a77SGreg Roach } 904c6266a77SGreg Roach } 905c6266a77SGreg Roach } 906c6266a77SGreg Roach 907c6266a77SGreg Roach // Add the value to the appropriate data point. 908c6266a77SGreg Roach $ydata[$z][$x] = ($ydata[$z][$x] ?? 0) + $value; 909c6266a77SGreg Roach } 910c6266a77SGreg Roach 911c6266a77SGreg Roach /** 912c6266a77SGreg Roach * Find the axis entry for a given value. 913c6266a77SGreg Roach * Some are direct lookup (e.g. M/F, JAN/FEB/MAR). 914c6266a77SGreg Roach * Others need to find the approprate range. 915c6266a77SGreg Roach * 916c6266a77SGreg Roach * @param int|float|string $value 917c6266a77SGreg Roach * @param string[] $axis 918c6266a77SGreg Roach * 919c6266a77SGreg Roach * @return int|string 920c6266a77SGreg Roach */ 921c6266a77SGreg Roach private function findAxisEntry($value, $axis) 922c6266a77SGreg Roach { 923c6266a77SGreg Roach if (is_numeric($value)) { 924c6266a77SGreg Roach $value = (int) $value; 925c6266a77SGreg Roach 9266960d4a1SGreg Roach if (!array_key_exists($value, $axis)) { 927c6266a77SGreg Roach foreach (array_keys($axis) as $boundary) { 928c6266a77SGreg Roach if ($value <= $boundary) { 929c6266a77SGreg Roach $value = $boundary; 930c6266a77SGreg Roach break; 931c6266a77SGreg Roach } 932c6266a77SGreg Roach } 933c6266a77SGreg Roach } 934c6266a77SGreg Roach } 935c6266a77SGreg Roach 936c6266a77SGreg Roach return $value; 937c6266a77SGreg Roach } 938c6266a77SGreg Roach 939c6266a77SGreg Roach /** 940c6266a77SGreg Roach * Plot the data. 941c6266a77SGreg Roach * 942c6266a77SGreg Roach * @param string $chart_title 943c6266a77SGreg Roach * @param string[] $x_axis 944c6266a77SGreg Roach * @param string $x_axis_title 945c6266a77SGreg Roach * @param int[][] $ydata 946c6266a77SGreg Roach * @param string $y_axis_title 947c6266a77SGreg Roach * @param string[] $z_axis 948c6266a77SGreg Roach * @param int $y_axis_type 949c6266a77SGreg Roach * 950c6266a77SGreg Roach * @return string 951c6266a77SGreg Roach */ 952a81e5019SRico Sonntag private function myPlot( 953a81e5019SRico Sonntag string $chart_title, 954a81e5019SRico Sonntag array $x_axis, 955a81e5019SRico Sonntag string $x_axis_title, 956a81e5019SRico Sonntag array $ydata, 957a81e5019SRico Sonntag string $y_axis_title, 958a81e5019SRico Sonntag array $z_axis, 959a81e5019SRico Sonntag int $y_axis_type 960a81e5019SRico Sonntag ): string { 9616960d4a1SGreg Roach if (!count($ydata)) { 962a81e5019SRico Sonntag return I18N::translate('This information is not available.'); 963c6266a77SGreg Roach } 964c6266a77SGreg Roach 965c6266a77SGreg Roach // Colors for z-axis 966c6266a77SGreg Roach $colors = []; 967c6266a77SGreg Roach $index = 0; 9686960d4a1SGreg Roach while (count($colors) < count($ydata)) { 969c6266a77SGreg Roach $colors[] = self::Z_AXIS_COLORS[$index]; 9706960d4a1SGreg Roach $index = ($index + 1) % count(self::Z_AXIS_COLORS); 971c6266a77SGreg Roach } 972c6266a77SGreg Roach 973c6266a77SGreg Roach // Convert our sparse dataset into a fixed-size array 974c6266a77SGreg Roach $tmp = []; 975c6266a77SGreg Roach foreach (array_keys($z_axis) as $z) { 976c6266a77SGreg Roach foreach (array_keys($x_axis) as $x) { 977c6266a77SGreg Roach $tmp[$z][$x] = $ydata[$z][$x] ?? 0; 978c6266a77SGreg Roach } 979c6266a77SGreg Roach } 980c6266a77SGreg Roach $ydata = $tmp; 981c6266a77SGreg Roach 982a81e5019SRico Sonntag // Convert the chart data to percentage 983c6266a77SGreg Roach if ($y_axis_type === self::Y_AXIS_PERCENT) { 984c6266a77SGreg Roach // Normalise each (non-zero!) set of data to total 100% 9850b5fd0a6SGreg Roach array_walk($ydata, static function (array &$x) { 986c6266a77SGreg Roach $sum = array_sum($x); 987c6266a77SGreg Roach if ($sum > 0) { 9880b5fd0a6SGreg Roach $x = array_map(static function ($y) use ($sum) { 989c6266a77SGreg Roach return $y * 100.0 / $sum; 990c6266a77SGreg Roach }, $x); 991c6266a77SGreg Roach } 992c6266a77SGreg Roach }); 993c6266a77SGreg Roach } 994c6266a77SGreg Roach 995a81e5019SRico Sonntag $data = [ 996a81e5019SRico Sonntag array_merge( 997a81e5019SRico Sonntag [I18N::translate('Century')], 998a81e5019SRico Sonntag array_values($z_axis) 99971378461SGreg Roach ), 1000c6266a77SGreg Roach ]; 1001c6266a77SGreg Roach 1002a81e5019SRico Sonntag $intermediate = []; 1003a81e5019SRico Sonntag foreach ($ydata as $century => $months) { 1004a81e5019SRico Sonntag foreach ($months as $month => $value) { 1005a81e5019SRico Sonntag $intermediate[$month][] = [ 1006a81e5019SRico Sonntag 'v' => $value, 1007a81e5019SRico Sonntag 'f' => ($y_axis_type === self::Y_AXIS_PERCENT) ? sprintf('%.1f%%', $value) : $value, 1008a81e5019SRico Sonntag ]; 1009a81e5019SRico Sonntag } 1010c6266a77SGreg Roach } 1011c6266a77SGreg Roach 1012a81e5019SRico Sonntag foreach ($intermediate as $key => $values) { 1013a81e5019SRico Sonntag $data[] = array_merge( 1014a81e5019SRico Sonntag [$x_axis[$key]], 1015a81e5019SRico Sonntag $values 1016a81e5019SRico Sonntag ); 1017a81e5019SRico Sonntag } 1018c6266a77SGreg Roach 1019a81e5019SRico Sonntag $chart_options = [ 1020a81e5019SRico Sonntag 'title' => '', 1021a81e5019SRico Sonntag 'subtitle' => '', 1022a81e5019SRico Sonntag 'height' => 400, 1023a81e5019SRico Sonntag 'width' => '100%', 1024a81e5019SRico Sonntag 'legend' => [ 10256960d4a1SGreg Roach 'position' => count($z_axis) > 1 ? 'right' : 'none', 1026a81e5019SRico Sonntag 'alignment' => 'center', 1027a81e5019SRico Sonntag ], 1028a81e5019SRico Sonntag 'tooltip' => [ 1029a81e5019SRico Sonntag 'format' => '\'%\'', 1030a81e5019SRico Sonntag ], 1031a81e5019SRico Sonntag 'vAxis' => [ 1032a81e5019SRico Sonntag 'title' => $y_axis_title ?? '', 1033a81e5019SRico Sonntag ], 1034a81e5019SRico Sonntag 'hAxis' => [ 1035a81e5019SRico Sonntag 'title' => $x_axis_title ?? '', 1036a81e5019SRico Sonntag ], 1037a81e5019SRico Sonntag 'colors' => $colors, 1038a81e5019SRico Sonntag ]; 1039a81e5019SRico Sonntag 1040a81e5019SRico Sonntag return view( 1041a81e5019SRico Sonntag 'statistics/other/charts/custom', 1042a81e5019SRico Sonntag [ 1043a81e5019SRico Sonntag 'data' => $data, 1044a81e5019SRico Sonntag 'chart_options' => $chart_options, 1045a81e5019SRico Sonntag 'chart_title' => $chart_title, 1046a81e5019SRico Sonntag ] 1047a81e5019SRico Sonntag ); 1048c6266a77SGreg Roach } 1049168ff6f3Sric2016} 1050