1168ff6f3Sric2016<?php 2*3976b470SGreg 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 */ 17e7f56f2aSGreg Roachdeclare(strict_types=1); 18e7f56f2aSGreg Roach 19168ff6f3Sric2016namespace Fisharebest\Webtrees\Module; 20168ff6f3Sric2016 21c6266a77SGreg Roachuse Fisharebest\Webtrees\Auth; 22c6266a77SGreg Roachuse Fisharebest\Webtrees\Date; 23168ff6f3Sric2016use Fisharebest\Webtrees\I18N; 24168ff6f3Sric2016use Fisharebest\Webtrees\Individual; 259219296aSGreg Roachuse Fisharebest\Webtrees\Statistics; 266ccdf4f0SGreg Roachuse Psr\Http\Message\ResponseInterface; 276ccdf4f0SGreg Roachuse Psr\Http\Message\ServerRequestInterface; 28c6266a77SGreg Roachuse Symfony\Component\HttpKernel\Exception\NotFoundHttpException; 29*3976b470SGreg Roach 3057ab2231SGreg Roachuse function app; 316960d4a1SGreg Roachuse function array_key_exists; 326960d4a1SGreg Roachuse function array_keys; 336960d4a1SGreg Roachuse function array_map; 346960d4a1SGreg Roachuse function array_merge; 356960d4a1SGreg Roachuse function array_sum; 366960d4a1SGreg Roachuse function array_values; 376960d4a1SGreg Roachuse function array_walk; 386960d4a1SGreg Roachuse function count; 396960d4a1SGreg Roachuse function explode; 406960d4a1SGreg Roachuse function in_array; 416960d4a1SGreg Roachuse function is_numeric; 426960d4a1SGreg Roachuse function sprintf; 436960d4a1SGreg Roachuse function strip_tags; 44168ff6f3Sric2016 45168ff6f3Sric2016/** 46168ff6f3Sric2016 * Class StatisticsChartModule 47168ff6f3Sric2016 */ 4837eb8894SGreg Roachclass StatisticsChartModule extends AbstractModule implements ModuleChartInterface 49c1010edaSGreg Roach{ 5049a243cbSGreg Roach use ModuleChartTrait; 5149a243cbSGreg Roach 52c6266a77SGreg Roach // We generate a bitmap chart with these dimensions in image pixels. 53c6266a77SGreg Roach // These set the aspect ratio. The actual image is sized using CSS 54c6266a77SGreg Roach // The maximum size (width x height) is 300,000 55c6266a77SGreg Roach private const CHART_WIDTH = 950; 56c6266a77SGreg Roach private const CHART_HEIGHT = 315; 57c6266a77SGreg Roach 58c6266a77SGreg Roach public const X_AXIS_INDIVIDUAL_MAP = 1; 59c6266a77SGreg Roach public const X_AXIS_BIRTH_MAP = 2; 60c6266a77SGreg Roach public const X_AXIS_DEATH_MAP = 3; 61c6266a77SGreg Roach public const X_AXIS_MARRIAGE_MAP = 4; 62c6266a77SGreg Roach public const X_AXIS_BIRTH_MONTH = 11; 63c6266a77SGreg Roach public const X_AXIS_DEATH_MONTH = 12; 64c6266a77SGreg Roach public const X_AXIS_MARRIAGE_MONTH = 13; 65c6266a77SGreg Roach public const X_AXIS_FIRST_CHILD_MONTH = 14; 66c6266a77SGreg Roach public const X_AXIS_FIRST_MARRIAGE_MONTH = 15; 67c6266a77SGreg Roach public const X_AXIS_AGE_AT_DEATH = 18; 68c6266a77SGreg Roach public const X_AXIS_AGE_AT_MARRIAGE = 19; 69c6266a77SGreg Roach public const X_AXIS_AGE_AT_FIRST_MARRIAGE = 20; 70c6266a77SGreg Roach public const X_AXIS_NUMBER_OF_CHILDREN = 21; 71c6266a77SGreg Roach 72c6266a77SGreg Roach public const Y_AXIS_NUMBERS = 201; 73c6266a77SGreg Roach public const Y_AXIS_PERCENT = 202; 74c6266a77SGreg Roach 75c6266a77SGreg Roach public const Z_AXIS_ALL = 300; 76c6266a77SGreg Roach public const Z_AXIS_SEX = 301; 77c6266a77SGreg Roach public const Z_AXIS_TIME = 302; 78c6266a77SGreg Roach 79c6266a77SGreg Roach // First two colors are blue/pink, to work with Z_AXIS_SEX. 80c6266a77SGreg Roach private const Z_AXIS_COLORS = ['0000FF', 'FFA0CB', '9F00FF', 'FF7000', '905030', 'FF0000', '00FF00', 'F0F000']; 81c6266a77SGreg Roach 82c6266a77SGreg Roach private const DAYS_IN_YEAR = 365.25; 83c6266a77SGreg Roach 84168ff6f3Sric2016 /** 850cfd6963SGreg Roach * How should this module be identified in the control panel, etc.? 86168ff6f3Sric2016 * 87168ff6f3Sric2016 * @return string 88168ff6f3Sric2016 */ 8949a243cbSGreg Roach public function title(): string 90c1010edaSGreg Roach { 91bbb76c12SGreg Roach /* I18N: Name of a module/chart */ 92bbb76c12SGreg Roach return I18N::translate('Statistics'); 93168ff6f3Sric2016 } 94168ff6f3Sric2016 95168ff6f3Sric2016 /** 96168ff6f3Sric2016 * A sentence describing what this module does. 97168ff6f3Sric2016 * 98168ff6f3Sric2016 * @return string 99168ff6f3Sric2016 */ 10049a243cbSGreg Roach public function description(): string 101c1010edaSGreg Roach { 102bbb76c12SGreg Roach /* I18N: Description of the “StatisticsChart” module */ 103bbb76c12SGreg Roach return I18N::translate('Various statistics charts.'); 104168ff6f3Sric2016 } 105168ff6f3Sric2016 106168ff6f3Sric2016 /** 107377a2979SGreg Roach * CSS class for the URL. 108377a2979SGreg Roach * 109377a2979SGreg Roach * @return string 110377a2979SGreg Roach */ 111377a2979SGreg Roach public function chartMenuClass(): string 112377a2979SGreg Roach { 113377a2979SGreg Roach return 'menu-chart-statistics'; 114377a2979SGreg Roach } 115377a2979SGreg Roach 116377a2979SGreg Roach /** 117e6562982SGreg Roach * The URL for this chart. 118168ff6f3Sric2016 * 11960bc3e3fSGreg Roach * @param Individual $individual 120e6562982SGreg Roach * @param string[] $parameters 12160bc3e3fSGreg Roach * 122e6562982SGreg Roach * @return string 123168ff6f3Sric2016 */ 124e6562982SGreg Roach public function chartUrl(Individual $individual, array $parameters = []): string 125c1010edaSGreg Roach { 126c6266a77SGreg Roach return route('module', [ 127c6266a77SGreg Roach 'module' => $this->name(), 128c6266a77SGreg Roach 'action' => 'Chart', 129e6562982SGreg Roach 'ged' => $individual->tree()->name(), 130e6562982SGreg Roach ] + $parameters); 131168ff6f3Sric2016 } 132c6266a77SGreg Roach 133c6266a77SGreg Roach /** 134c6266a77SGreg Roach * A form to request the chart parameters. 135c6266a77SGreg Roach * 13657ab2231SGreg Roach * @param ServerRequestInterface $request 137c6266a77SGreg Roach * 1386ccdf4f0SGreg Roach * @return ResponseInterface 139c6266a77SGreg Roach */ 14057ab2231SGreg Roach public function getChartAction(ServerRequestInterface $request): ResponseInterface 141c6266a77SGreg Roach { 14257ab2231SGreg Roach $tree = $request->getAttribute('tree'); 14357ab2231SGreg Roach $user = $request->getAttribute('user'); 14457ab2231SGreg Roach 1459867b2f0SGreg Roach Auth::checkComponentAccess($this, 'chart', $tree, $user); 1469867b2f0SGreg Roach 147c6266a77SGreg Roach $tabs = [ 148c6266a77SGreg Roach I18N::translate('Individuals') => route('module', [ 149c6266a77SGreg Roach 'module' => $this->name(), 150c6266a77SGreg Roach 'action' => 'Individuals', 151c6266a77SGreg Roach 'ged' => $tree->name() 152c6266a77SGreg Roach ]), 153c6266a77SGreg Roach I18N::translate('Families') => route('module', [ 154c6266a77SGreg Roach 'module' => $this->name(), 155c6266a77SGreg Roach 'action' => 'Families', 156c6266a77SGreg Roach 'ged' => $tree->name() 157c6266a77SGreg Roach ]), 158c6266a77SGreg Roach I18N::translate('Other') => route('module', [ 159c6266a77SGreg Roach 'module' => $this->name(), 160c6266a77SGreg Roach 'action' => 'Other', 161c6266a77SGreg Roach 'ged' => $tree->name() 162c6266a77SGreg Roach ]), 163c6266a77SGreg Roach I18N::translate('Custom') => route('module', [ 164c6266a77SGreg Roach 'module' => $this->name(), 165c6266a77SGreg Roach 'action' => 'Custom', 166c6266a77SGreg Roach 'ged' => $tree->name() 167c6266a77SGreg Roach ]), 168c6266a77SGreg Roach ]; 169c6266a77SGreg Roach 1709b5537c3SGreg Roach return $this->viewResponse('modules/statistics-chart/page', [ 171c6266a77SGreg Roach 'tabs' => $tabs, 172c6266a77SGreg Roach 'title' => $this->title(), 173c6266a77SGreg Roach ]); 174c6266a77SGreg Roach } 175c6266a77SGreg Roach 176c6266a77SGreg Roach /** 17757ab2231SGreg Roach * @param ServerRequestInterface $request 178c6266a77SGreg Roach * 1796ccdf4f0SGreg Roach * @return ResponseInterface 180c6266a77SGreg Roach */ 18157ab2231SGreg Roach public function getIndividualsAction(ServerRequestInterface $request): ResponseInterface 182c6266a77SGreg Roach { 183b6c326d8SGreg Roach $this->layout = 'layouts/ajax'; 184b6c326d8SGreg Roach 185b6c326d8SGreg Roach return $this->viewResponse('modules/statistics-chart/individuals', [ 186c6266a77SGreg Roach 'show_oldest_living' => Auth::check(), 18757ab2231SGreg Roach 'stats' => app(Statistics::class), 188c6266a77SGreg Roach ]); 189c6266a77SGreg Roach } 190c6266a77SGreg Roach 191c6266a77SGreg Roach /** 19257ab2231SGreg Roach * @param ServerRequestInterface $request 193c6266a77SGreg Roach * 1946ccdf4f0SGreg Roach * @return ResponseInterface 195c6266a77SGreg Roach */ 19657ab2231SGreg Roach public function getFamiliesAction(ServerRequestInterface $request): ResponseInterface 197c6266a77SGreg Roach { 198b6c326d8SGreg Roach $this->layout = 'layouts/ajax'; 199b6c326d8SGreg Roach 200b6c326d8SGreg Roach return $this->viewResponse('modules/statistics-chart/families', [ 20157ab2231SGreg Roach 'stats' => app(Statistics::class), 202c6266a77SGreg Roach ]); 203c6266a77SGreg Roach } 204c6266a77SGreg Roach 205c6266a77SGreg Roach /** 20657ab2231SGreg Roach * @param ServerRequestInterface $request 207c6266a77SGreg Roach * 2086ccdf4f0SGreg Roach * @return ResponseInterface 209c6266a77SGreg Roach */ 21057ab2231SGreg Roach public function getOtherAction(ServerRequestInterface $request): ResponseInterface 211c6266a77SGreg Roach { 212b6c326d8SGreg Roach $this->layout = 'layouts/ajax'; 213b6c326d8SGreg Roach 214b6c326d8SGreg Roach return $this->viewResponse('modules/statistics-chart/other', [ 21557ab2231SGreg Roach 'stats' => app(Statistics::class), 216c6266a77SGreg Roach ]); 217c6266a77SGreg Roach } 218c6266a77SGreg Roach 219c6266a77SGreg Roach /** 22057ab2231SGreg Roach * @param ServerRequestInterface $request 221c6266a77SGreg Roach * 2226ccdf4f0SGreg Roach * @return ResponseInterface 223c6266a77SGreg Roach */ 22457ab2231SGreg Roach public function getCustomAction(ServerRequestInterface $request): ResponseInterface 225c6266a77SGreg Roach { 226b6c326d8SGreg Roach $this->layout = 'layouts/ajax'; 227b6c326d8SGreg Roach 22857ab2231SGreg Roach $tree = $request->getAttribute('tree'); 22957ab2231SGreg Roach 230b6c326d8SGreg Roach return $this->viewResponse('modules/statistics-chart/custom', [ 231c6266a77SGreg Roach 'module' => $this, 232c6266a77SGreg Roach 'tree' => $tree, 233c6266a77SGreg Roach ]); 234c6266a77SGreg Roach } 235c6266a77SGreg Roach 236c6266a77SGreg Roach /** 2376ccdf4f0SGreg Roach * @param ServerRequestInterface $request 238c6266a77SGreg Roach * 2396ccdf4f0SGreg Roach * @return ResponseInterface 240c6266a77SGreg Roach */ 24157ab2231SGreg Roach public function getCustomChartAction(ServerRequestInterface $request): ResponseInterface 242c6266a77SGreg Roach { 24357ab2231SGreg Roach $statistics = app(Statistics::class); 24457ab2231SGreg Roach 245b6b9dcc9SGreg Roach $params = $request->getQueryParams(); 246b6b9dcc9SGreg Roach 247b6b9dcc9SGreg Roach $x_axis_type = (int) $params['x-as']; 248b6b9dcc9SGreg Roach $y_axis_type = (int) $params['y-as']; 249b6b9dcc9SGreg Roach $z_axis_type = (int) $params['z-as']; 250c6266a77SGreg Roach $ydata = []; 251c6266a77SGreg Roach 252c6266a77SGreg Roach switch ($x_axis_type) { 253c6266a77SGreg Roach case self::X_AXIS_INDIVIDUAL_MAP: 2546ccdf4f0SGreg Roach return response($statistics->chartDistribution( 255b6b9dcc9SGreg Roach $params['chart_shows'], 256b6b9dcc9SGreg Roach $params['chart_type'], 257b6b9dcc9SGreg Roach $params['SURN'] 258c6266a77SGreg Roach )); 259c6266a77SGreg Roach 260c6266a77SGreg Roach case self::X_AXIS_BIRTH_MAP: 2616ccdf4f0SGreg Roach return response($statistics->chartDistribution( 262b6b9dcc9SGreg Roach $params['chart_shows'], 263c6266a77SGreg Roach 'birth_distribution_chart' 264c6266a77SGreg Roach )); 265c6266a77SGreg Roach 266c6266a77SGreg Roach case self::X_AXIS_DEATH_MAP: 2676ccdf4f0SGreg Roach return response($statistics->chartDistribution( 268b6b9dcc9SGreg Roach $params['chart_shows'], 269c6266a77SGreg Roach 'death_distribution_chart' 270c6266a77SGreg Roach )); 271c6266a77SGreg Roach 272c6266a77SGreg Roach case self::X_AXIS_MARRIAGE_MAP: 2736ccdf4f0SGreg Roach return response($statistics->chartDistribution( 274b6b9dcc9SGreg Roach $params['chart_shows'], 275c6266a77SGreg Roach 'marriage_distribution_chart' 276c6266a77SGreg Roach )); 277c6266a77SGreg Roach 278c6266a77SGreg Roach case self::X_AXIS_BIRTH_MONTH: 279c6266a77SGreg Roach $chart_title = I18N::translate('Month of birth'); 280c6266a77SGreg Roach $x_axis_title = I18N::translate('Month'); 281c6266a77SGreg Roach $x_axis = $this->axisMonths(); 282c6266a77SGreg Roach 283c6266a77SGreg Roach switch ($y_axis_type) { 284c6266a77SGreg Roach case self::Y_AXIS_NUMBERS: 285c6266a77SGreg Roach $y_axis_title = I18N::translate('Individuals'); 286c6266a77SGreg Roach break; 287c6266a77SGreg Roach case self::Y_AXIS_PERCENT: 288c6266a77SGreg Roach $y_axis_title = '%'; 289c6266a77SGreg Roach break; 290c6266a77SGreg Roach default: 291c6266a77SGreg Roach throw new NotFoundHttpException(); 292c6266a77SGreg Roach } 293c6266a77SGreg Roach 294c6266a77SGreg Roach switch ($z_axis_type) { 295c6266a77SGreg Roach case self::Z_AXIS_ALL: 296c6266a77SGreg Roach $z_axis = $this->axisAll(); 297cde1d378SGreg Roach $rows = $statistics->statsBirthQuery()->get(); 298c6266a77SGreg Roach foreach ($rows as $row) { 299c6266a77SGreg Roach $this->fillYData($row->d_month, 0, $row->total, $x_axis, $z_axis, $ydata); 300c6266a77SGreg Roach } 301c6266a77SGreg Roach break; 302c6266a77SGreg Roach case self::Z_AXIS_SEX: 303c6266a77SGreg Roach $z_axis = $this->axisSexes(); 304cde1d378SGreg Roach $rows = $statistics->statsBirthBySexQuery()->get(); 305c6266a77SGreg Roach foreach ($rows as $row) { 306c6266a77SGreg Roach $this->fillYData($row->d_month, $row->i_sex, $row->total, $x_axis, $z_axis, $ydata); 307c6266a77SGreg Roach } 308c6266a77SGreg Roach break; 309c6266a77SGreg Roach case self::Z_AXIS_TIME: 310b6b9dcc9SGreg Roach $boundaries_csv = $params['z-axis-boundaries-periods']; 311c6266a77SGreg Roach $z_axis = $this->axisYears($boundaries_csv); 312c6266a77SGreg Roach $prev_boundary = 0; 313c6266a77SGreg Roach foreach (array_keys($z_axis) as $boundary) { 314cde1d378SGreg Roach $rows = $statistics->statsBirthQuery($prev_boundary, $boundary)->get(); 315c6266a77SGreg Roach foreach ($rows as $row) { 316c6266a77SGreg Roach $this->fillYData($row->d_month, $boundary, $row->total, $x_axis, $z_axis, $ydata); 317c6266a77SGreg Roach } 318c6266a77SGreg Roach $prev_boundary = $boundary + 1; 319c6266a77SGreg Roach } 320c6266a77SGreg Roach break; 321c6266a77SGreg Roach default: 322c6266a77SGreg Roach throw new NotFoundHttpException(); 323c6266a77SGreg Roach } 324c6266a77SGreg Roach 3256ccdf4f0SGreg Roach return response($this->myPlot($chart_title, $x_axis, $x_axis_title, $ydata, $y_axis_title, $z_axis, $y_axis_type)); 326c6266a77SGreg Roach 327c6266a77SGreg Roach case self::X_AXIS_DEATH_MONTH: 328c6266a77SGreg Roach $chart_title = I18N::translate('Month of death'); 329c6266a77SGreg Roach $x_axis_title = I18N::translate('Month'); 330c6266a77SGreg Roach $x_axis = $this->axisMonths(); 331c6266a77SGreg Roach 332c6266a77SGreg Roach switch ($y_axis_type) { 333c6266a77SGreg Roach case self::Y_AXIS_NUMBERS: 334c6266a77SGreg Roach $y_axis_title = I18N::translate('Individuals'); 335c6266a77SGreg Roach break; 336c6266a77SGreg Roach case self::Y_AXIS_PERCENT: 337c6266a77SGreg Roach $y_axis_title = '%'; 338c6266a77SGreg Roach break; 339c6266a77SGreg Roach default: 340c6266a77SGreg Roach throw new NotFoundHttpException(); 341c6266a77SGreg Roach } 342c6266a77SGreg Roach 343c6266a77SGreg Roach switch ($z_axis_type) { 344c6266a77SGreg Roach case self::Z_AXIS_ALL: 345c6266a77SGreg Roach $z_axis = $this->axisAll(); 346cde1d378SGreg Roach $rows = $statistics->statsDeathQuery()->get(); 347c6266a77SGreg Roach foreach ($rows as $row) { 348c6266a77SGreg Roach $this->fillYData($row->d_month, 0, $row->total, $x_axis, $z_axis, $ydata); 349c6266a77SGreg Roach } 350c6266a77SGreg Roach break; 351c6266a77SGreg Roach case self::Z_AXIS_SEX: 352c6266a77SGreg Roach $z_axis = $this->axisSexes(); 353cde1d378SGreg Roach $rows = $statistics->statsDeathBySexQuery()->get(); 354c6266a77SGreg Roach foreach ($rows as $row) { 355c6266a77SGreg Roach $this->fillYData($row->d_month, $row->i_sex, $row->total, $x_axis, $z_axis, $ydata); 356c6266a77SGreg Roach } 357c6266a77SGreg Roach break; 358c6266a77SGreg Roach case self::Z_AXIS_TIME: 359b6b9dcc9SGreg Roach $boundaries_csv = $params['z-axis-boundaries-periods']; 360c6266a77SGreg Roach $z_axis = $this->axisYears($boundaries_csv); 361c6266a77SGreg Roach $prev_boundary = 0; 362c6266a77SGreg Roach foreach (array_keys($z_axis) as $boundary) { 363cde1d378SGreg Roach $rows = $statistics->statsDeathQuery($prev_boundary, $boundary)->get(); 364c6266a77SGreg Roach foreach ($rows as $row) { 365c6266a77SGreg Roach $this->fillYData($row->d_month, $boundary, $row->total, $x_axis, $z_axis, $ydata); 366c6266a77SGreg Roach } 367c6266a77SGreg Roach $prev_boundary = $boundary + 1; 368c6266a77SGreg Roach } 369c6266a77SGreg Roach break; 370c6266a77SGreg Roach default: 371c6266a77SGreg Roach throw new NotFoundHttpException(); 372c6266a77SGreg Roach } 373c6266a77SGreg Roach 3746ccdf4f0SGreg Roach return response($this->myPlot($chart_title, $x_axis, $x_axis_title, $ydata, $y_axis_title, $z_axis, $y_axis_type)); 375c6266a77SGreg Roach 376c6266a77SGreg Roach case self::X_AXIS_MARRIAGE_MONTH: 377c6266a77SGreg Roach $chart_title = I18N::translate('Month of marriage'); 378c6266a77SGreg Roach $x_axis_title = I18N::translate('Month'); 379c6266a77SGreg Roach $x_axis = $this->axisMonths(); 380c6266a77SGreg Roach 381c6266a77SGreg Roach switch ($y_axis_type) { 382c6266a77SGreg Roach case self::Y_AXIS_NUMBERS: 383c6266a77SGreg Roach $y_axis_title = I18N::translate('Families'); 384c6266a77SGreg Roach break; 385c6266a77SGreg Roach case self::Y_AXIS_PERCENT: 386c6266a77SGreg Roach $y_axis_title = '%'; 387c6266a77SGreg Roach break; 388c6266a77SGreg Roach default: 389c6266a77SGreg Roach throw new NotFoundHttpException(); 390c6266a77SGreg Roach } 391c6266a77SGreg Roach 392c6266a77SGreg Roach switch ($z_axis_type) { 393c6266a77SGreg Roach case self::Z_AXIS_ALL: 394c6266a77SGreg Roach $z_axis = $this->axisAll(); 395e6f3d5e2SGreg Roach $rows = $statistics->statsMarriageQuery()->get(); 396c6266a77SGreg Roach foreach ($rows as $row) { 397c6266a77SGreg Roach $this->fillYData($row->d_month, 0, $row->total, $x_axis, $z_axis, $ydata); 398c6266a77SGreg Roach } 399c6266a77SGreg Roach break; 400c6266a77SGreg Roach case self::Z_AXIS_TIME: 401b6b9dcc9SGreg Roach $boundaries_csv = $params['z-axis-boundaries-periods']; 402c6266a77SGreg Roach $z_axis = $this->axisYears($boundaries_csv); 403c6266a77SGreg Roach $prev_boundary = 0; 404c6266a77SGreg Roach foreach (array_keys($z_axis) as $boundary) { 405e6f3d5e2SGreg Roach $rows = $statistics->statsMarriageQuery($prev_boundary, $boundary)->get(); 406c6266a77SGreg Roach foreach ($rows as $row) { 407c6266a77SGreg Roach $this->fillYData($row->d_month, $boundary, $row->total, $x_axis, $z_axis, $ydata); 408c6266a77SGreg Roach } 409c6266a77SGreg Roach $prev_boundary = $boundary + 1; 410c6266a77SGreg Roach } 411c6266a77SGreg Roach break; 412c6266a77SGreg Roach default: 413c6266a77SGreg Roach throw new NotFoundHttpException(); 414c6266a77SGreg Roach } 415c6266a77SGreg Roach 4166ccdf4f0SGreg Roach return response($this->myPlot($chart_title, $x_axis, $x_axis_title, $ydata, $y_axis_title, $z_axis, $y_axis_type)); 417c6266a77SGreg Roach 418c6266a77SGreg Roach case self::X_AXIS_FIRST_CHILD_MONTH: 419c6266a77SGreg Roach $chart_title = I18N::translate('Month of birth of first child in a relation'); 420c6266a77SGreg Roach $x_axis_title = I18N::translate('Month'); 421c6266a77SGreg Roach $x_axis = $this->axisMonths(); 422c6266a77SGreg Roach 423c6266a77SGreg Roach switch ($y_axis_type) { 424c6266a77SGreg Roach case self::Y_AXIS_NUMBERS: 425c6266a77SGreg Roach $y_axis_title = I18N::translate('Children'); 426c6266a77SGreg Roach break; 427c6266a77SGreg Roach case self::Y_AXIS_PERCENT: 428c6266a77SGreg Roach $y_axis_title = '%'; 429c6266a77SGreg Roach break; 430c6266a77SGreg Roach default: 431c6266a77SGreg Roach throw new NotFoundHttpException(); 432c6266a77SGreg Roach } 433c6266a77SGreg Roach 434c6266a77SGreg Roach switch ($z_axis_type) { 435c6266a77SGreg Roach case self::Z_AXIS_ALL: 436c6266a77SGreg Roach $z_axis = $this->axisAll(); 437999da590SGreg Roach $rows = $statistics->monthFirstChildQuery()->get(); 438c6266a77SGreg Roach foreach ($rows as $row) { 439c6266a77SGreg Roach $this->fillYData($row->d_month, 0, $row->total, $x_axis, $z_axis, $ydata); 440c6266a77SGreg Roach } 441c6266a77SGreg Roach break; 442c6266a77SGreg Roach case self::Z_AXIS_SEX: 443c6266a77SGreg Roach $z_axis = $this->axisSexes(); 444999da590SGreg Roach $rows = $statistics->monthFirstChildBySexQuery()->get(); 445c6266a77SGreg Roach foreach ($rows as $row) { 446c6266a77SGreg Roach $this->fillYData($row->d_month, $row->i_sex, $row->total, $x_axis, $z_axis, $ydata); 447c6266a77SGreg Roach } 448c6266a77SGreg Roach break; 449c6266a77SGreg Roach case self::Z_AXIS_TIME: 450b6b9dcc9SGreg Roach $boundaries_csv = $params['z-axis-boundaries-periods']; 451c6266a77SGreg Roach $z_axis = $this->axisYears($boundaries_csv); 452c6266a77SGreg Roach $prev_boundary = 0; 453c6266a77SGreg Roach foreach (array_keys($z_axis) as $boundary) { 454999da590SGreg Roach $rows = $statistics->monthFirstChildQuery($prev_boundary, $boundary)->get(); 455c6266a77SGreg Roach foreach ($rows as $row) { 456c6266a77SGreg Roach $this->fillYData($row->d_month, $boundary, $row->total, $x_axis, $z_axis, $ydata); 457c6266a77SGreg Roach } 458c6266a77SGreg Roach $prev_boundary = $boundary + 1; 459c6266a77SGreg Roach } 460c6266a77SGreg Roach break; 461c6266a77SGreg Roach default: 462c6266a77SGreg Roach throw new NotFoundHttpException(); 463c6266a77SGreg Roach } 464c6266a77SGreg Roach 4656ccdf4f0SGreg Roach return response($this->myPlot($chart_title, $x_axis, $x_axis_title, $ydata, $y_axis_title, $z_axis, $y_axis_type)); 466c6266a77SGreg Roach 467c6266a77SGreg Roach case self::X_AXIS_FIRST_MARRIAGE_MONTH: 468c6266a77SGreg Roach $chart_title = I18N::translate('Month of first marriage'); 469c6266a77SGreg Roach $x_axis_title = I18N::translate('Month'); 470c6266a77SGreg Roach $x_axis = $this->axisMonths(); 471c6266a77SGreg Roach 472c6266a77SGreg Roach switch ($y_axis_type) { 473c6266a77SGreg Roach case self::Y_AXIS_NUMBERS: 474c6266a77SGreg Roach $y_axis_title = I18N::translate('Families'); 475c6266a77SGreg Roach break; 476c6266a77SGreg Roach case self::Y_AXIS_PERCENT: 477c6266a77SGreg Roach $y_axis_title = '%'; 478c6266a77SGreg Roach break; 479c6266a77SGreg Roach default: 480c6266a77SGreg Roach throw new NotFoundHttpException(); 481c6266a77SGreg Roach } 482c6266a77SGreg Roach 483c6266a77SGreg Roach switch ($z_axis_type) { 484c6266a77SGreg Roach case self::Z_AXIS_ALL: 485c6266a77SGreg Roach $z_axis = $this->axisAll(); 486e6f3d5e2SGreg Roach $rows = $statistics->statsFirstMarriageQuery()->get(); 487c6266a77SGreg Roach $indi = []; 488c6266a77SGreg Roach $fam = []; 489c6266a77SGreg Roach foreach ($rows as $row) { 4906960d4a1SGreg Roach if (!in_array($row->indi, $indi, true) && !in_array($row->fams, $fam, true)) { 491c6266a77SGreg Roach $this->fillYData($row->month, 0, 1, $x_axis, $z_axis, $ydata); 492c6266a77SGreg Roach } 493c6266a77SGreg Roach $indi[] = $row->indi; 494c6266a77SGreg Roach $fam[] = $row->fams; 495c6266a77SGreg Roach } 496c6266a77SGreg Roach break; 497c6266a77SGreg Roach case self::Z_AXIS_TIME: 498b6b9dcc9SGreg Roach $boundaries_csv = $params['z-axis-boundaries-periods']; 499c6266a77SGreg Roach $z_axis = $this->axisYears($boundaries_csv); 500c6266a77SGreg Roach $prev_boundary = 0; 501c6266a77SGreg Roach $indi = []; 502c6266a77SGreg Roach $fam = []; 503c6266a77SGreg Roach foreach (array_keys($z_axis) as $boundary) { 504e6f3d5e2SGreg Roach $rows = $statistics->statsFirstMarriageQuery($prev_boundary, $boundary)->get(); 505c6266a77SGreg Roach foreach ($rows as $row) { 5066960d4a1SGreg Roach if (!in_array($row->indi, $indi, true) && !in_array($row->fams, $fam, true)) { 507c6266a77SGreg Roach $this->fillYData($row->month, $boundary, 1, $x_axis, $z_axis, $ydata); 508c6266a77SGreg Roach } 509c6266a77SGreg Roach $indi[] = $row->indi; 510c6266a77SGreg Roach $fam[] = $row->fams; 511c6266a77SGreg Roach } 512c6266a77SGreg Roach $prev_boundary = $boundary + 1; 513c6266a77SGreg Roach } 514c6266a77SGreg Roach break; 515c6266a77SGreg Roach default: 516c6266a77SGreg Roach throw new NotFoundHttpException(); 517c6266a77SGreg Roach } 518c6266a77SGreg Roach 5196ccdf4f0SGreg Roach return response($this->myPlot($chart_title, $x_axis, $x_axis_title, $ydata, $y_axis_title, $z_axis, $y_axis_type)); 520c6266a77SGreg Roach 521c6266a77SGreg Roach case self::X_AXIS_AGE_AT_DEATH: 522c6266a77SGreg Roach $chart_title = I18N::translate('Average age at death'); 523c6266a77SGreg Roach $x_axis_title = I18N::translate('age'); 524b6b9dcc9SGreg Roach $boundaries_csv = $params['x-axis-boundaries-ages']; 525c6266a77SGreg Roach $x_axis = $this->axisNumbers($boundaries_csv); 526c6266a77SGreg Roach 527c6266a77SGreg Roach switch ($y_axis_type) { 528c6266a77SGreg Roach case self::Y_AXIS_NUMBERS: 529c6266a77SGreg Roach $y_axis_title = I18N::translate('Individuals'); 530c6266a77SGreg Roach break; 531c6266a77SGreg Roach case self::Y_AXIS_PERCENT: 532c6266a77SGreg Roach $y_axis_title = '%'; 533c6266a77SGreg Roach break; 534c6266a77SGreg Roach default: 535c6266a77SGreg Roach throw new NotFoundHttpException(); 536c6266a77SGreg Roach } 537c6266a77SGreg Roach 538c6266a77SGreg Roach switch ($z_axis_type) { 539c6266a77SGreg Roach case self::Z_AXIS_ALL: 540c6266a77SGreg Roach $z_axis = $this->axisAll(); 5419219296aSGreg Roach $rows = $statistics->statsAgeQuery('DEAT'); 542c6266a77SGreg Roach foreach ($rows as $row) { 543c6266a77SGreg Roach foreach ($row as $age) { 544c6266a77SGreg Roach $years = (int) ($age / self::DAYS_IN_YEAR); 545c6266a77SGreg Roach $this->fillYData($years, 0, 1, $x_axis, $z_axis, $ydata); 546c6266a77SGreg Roach } 547c6266a77SGreg Roach } 548c6266a77SGreg Roach break; 549c6266a77SGreg Roach case self::Z_AXIS_SEX: 550c6266a77SGreg Roach $z_axis = $this->axisSexes(); 551c6266a77SGreg Roach foreach (array_keys($z_axis) as $sex) { 5529219296aSGreg Roach $rows = $statistics->statsAgeQuery('DEAT', $sex); 553c6266a77SGreg Roach foreach ($rows as $row) { 554c6266a77SGreg Roach foreach ($row as $age) { 555c6266a77SGreg Roach $years = (int) ($age / self::DAYS_IN_YEAR); 556c6266a77SGreg Roach $this->fillYData($years, $sex, 1, $x_axis, $z_axis, $ydata); 557c6266a77SGreg Roach } 558c6266a77SGreg Roach } 559c6266a77SGreg Roach } 560c6266a77SGreg Roach break; 561c6266a77SGreg Roach case self::Z_AXIS_TIME: 562b6b9dcc9SGreg Roach $boundaries_csv = $params['z-axis-boundaries-periods']; 563c6266a77SGreg Roach $z_axis = $this->axisYears($boundaries_csv); 564c6266a77SGreg Roach $prev_boundary = 0; 565c6266a77SGreg Roach foreach (array_keys($z_axis) as $boundary) { 5669219296aSGreg Roach $rows = $statistics->statsAgeQuery('DEAT', 'BOTH', $prev_boundary, $boundary); 567c6266a77SGreg Roach foreach ($rows as $row) { 568c6266a77SGreg Roach foreach ($row as $age) { 569c6266a77SGreg Roach $years = (int) ($age / self::DAYS_IN_YEAR); 570c6266a77SGreg Roach $this->fillYData($years, $boundary, 1, $x_axis, $z_axis, $ydata); 571c6266a77SGreg Roach } 572c6266a77SGreg Roach } 573c6266a77SGreg Roach $prev_boundary = $boundary + 1; 574c6266a77SGreg Roach } 575c6266a77SGreg Roach 576c6266a77SGreg Roach break; 577c6266a77SGreg Roach default: 578c6266a77SGreg Roach throw new NotFoundHttpException(); 579c6266a77SGreg Roach } 580c6266a77SGreg Roach 5816ccdf4f0SGreg Roach return response($this->myPlot($chart_title, $x_axis, $x_axis_title, $ydata, $y_axis_title, $z_axis, $y_axis_type)); 582c6266a77SGreg Roach 583c6266a77SGreg Roach case self::X_AXIS_AGE_AT_MARRIAGE: 584c6266a77SGreg Roach $chart_title = I18N::translate('Age in year of marriage'); 585c6266a77SGreg Roach $x_axis_title = I18N::translate('age'); 586b6b9dcc9SGreg Roach $boundaries_csv = $params['x-axis-boundaries-ages_m']; 587c6266a77SGreg Roach $x_axis = $this->axisNumbers($boundaries_csv); 588c6266a77SGreg Roach 589c6266a77SGreg Roach switch ($y_axis_type) { 590c6266a77SGreg Roach case self::Y_AXIS_NUMBERS: 591c6266a77SGreg Roach $y_axis_title = I18N::translate('Individuals'); 592c6266a77SGreg Roach break; 593c6266a77SGreg Roach case self::Y_AXIS_PERCENT: 594c6266a77SGreg Roach $y_axis_title = '%'; 595c6266a77SGreg Roach break; 596c6266a77SGreg Roach default: 597c6266a77SGreg Roach throw new NotFoundHttpException(); 598c6266a77SGreg Roach } 599c6266a77SGreg Roach 600c6266a77SGreg Roach switch ($z_axis_type) { 601c6266a77SGreg Roach case self::Z_AXIS_ALL: 602c6266a77SGreg Roach $z_axis = $this->axisAll(); 603afa8d404SGreg Roach // The stats query doesn't have an "all" function, so query M/F separately 604afa8d404SGreg Roach foreach (['M', 'F'] as $sex) { 6059219296aSGreg Roach $rows = $statistics->statsMarrAgeQuery($sex); 606c6266a77SGreg Roach foreach ($rows as $row) { 607c6266a77SGreg Roach $years = (int) ($row->age / self::DAYS_IN_YEAR); 608c6266a77SGreg Roach $this->fillYData($years, 0, 1, $x_axis, $z_axis, $ydata); 609c6266a77SGreg Roach } 610c6266a77SGreg Roach } 611c6266a77SGreg Roach break; 612c6266a77SGreg Roach case self::Z_AXIS_SEX: 613c6266a77SGreg Roach $z_axis = $this->axisSexes(); 614c6266a77SGreg Roach foreach (array_keys($z_axis) as $sex) { 6159219296aSGreg Roach $rows = $statistics->statsMarrAgeQuery($sex); 616c6266a77SGreg Roach foreach ($rows as $row) { 617c6266a77SGreg Roach $years = (int) ($row->age / self::DAYS_IN_YEAR); 618c6266a77SGreg Roach $this->fillYData($years, $sex, 1, $x_axis, $z_axis, $ydata); 619c6266a77SGreg Roach } 620c6266a77SGreg Roach } 621c6266a77SGreg Roach break; 622c6266a77SGreg Roach case self::Z_AXIS_TIME: 623b6b9dcc9SGreg Roach $boundaries_csv = $params['z-axis-boundaries-periods']; 624c6266a77SGreg Roach $z_axis = $this->axisYears($boundaries_csv); 625afa8d404SGreg Roach // The stats query doesn't have an "all" function, so query M/F separately 626afa8d404SGreg Roach foreach (['M', 'F'] as $sex) { 627c6266a77SGreg Roach $prev_boundary = 0; 628c6266a77SGreg Roach foreach (array_keys($z_axis) as $boundary) { 6299219296aSGreg Roach $rows = $statistics->statsMarrAgeQuery($sex, $prev_boundary, $boundary); 630c6266a77SGreg Roach foreach ($rows as $row) { 631c6266a77SGreg Roach $years = (int) ($row->age / self::DAYS_IN_YEAR); 632c6266a77SGreg Roach $this->fillYData($years, $boundary, 1, $x_axis, $z_axis, $ydata); 633c6266a77SGreg Roach } 634c6266a77SGreg Roach $prev_boundary = $boundary + 1; 635c6266a77SGreg Roach } 636c6266a77SGreg Roach } 637c6266a77SGreg Roach break; 638c6266a77SGreg Roach default: 639c6266a77SGreg Roach throw new NotFoundHttpException(); 640c6266a77SGreg Roach } 641c6266a77SGreg Roach 6426ccdf4f0SGreg Roach return response($this->myPlot($chart_title, $x_axis, $x_axis_title, $ydata, $y_axis_title, $z_axis, $y_axis_type)); 643c6266a77SGreg Roach 644c6266a77SGreg Roach case self::X_AXIS_AGE_AT_FIRST_MARRIAGE: 645c6266a77SGreg Roach $chart_title = I18N::translate('Age in year of first marriage'); 646c6266a77SGreg Roach $x_axis_title = I18N::translate('age'); 647b6b9dcc9SGreg Roach $boundaries_csv = $params['x-axis-boundaries-ages_m']; 648c6266a77SGreg Roach $x_axis = $this->axisNumbers($boundaries_csv); 649c6266a77SGreg Roach 650c6266a77SGreg Roach switch ($y_axis_type) { 651c6266a77SGreg Roach case self::Y_AXIS_NUMBERS: 652c6266a77SGreg Roach $y_axis_title = I18N::translate('Individuals'); 653c6266a77SGreg Roach break; 654c6266a77SGreg Roach case self::Y_AXIS_PERCENT: 655c6266a77SGreg Roach $y_axis_title = '%'; 656c6266a77SGreg Roach break; 657c6266a77SGreg Roach default: 658c6266a77SGreg Roach throw new NotFoundHttpException(); 659c6266a77SGreg Roach } 660c6266a77SGreg Roach 661c6266a77SGreg Roach switch ($z_axis_type) { 662c6266a77SGreg Roach case self::Z_AXIS_ALL: 663c6266a77SGreg Roach $z_axis = $this->axisAll(); 664afa8d404SGreg Roach // The stats query doesn't have an "all" function, so query M/F separately 665afa8d404SGreg Roach foreach (['M', 'F'] as $sex) { 6669219296aSGreg Roach $rows = $statistics->statsMarrAgeQuery($sex); 667c6266a77SGreg Roach $indi = []; 668c6266a77SGreg Roach foreach ($rows as $row) { 6696960d4a1SGreg Roach if (!in_array($row->d_gid, $indi, true)) { 670c6266a77SGreg Roach $years = (int) ($row->age / self::DAYS_IN_YEAR); 671c6266a77SGreg Roach $this->fillYData($years, 0, 1, $x_axis, $z_axis, $ydata); 672c6266a77SGreg Roach $indi[] = $row->d_gid; 673c6266a77SGreg Roach } 674c6266a77SGreg Roach } 675c6266a77SGreg Roach } 676c6266a77SGreg Roach break; 677c6266a77SGreg Roach case self::Z_AXIS_SEX: 678c6266a77SGreg Roach $z_axis = $this->axisSexes(); 679c6266a77SGreg Roach foreach (array_keys($z_axis) as $sex) { 6809219296aSGreg Roach $rows = $statistics->statsMarrAgeQuery($sex); 681c6266a77SGreg Roach $indi = []; 682c6266a77SGreg Roach foreach ($rows as $row) { 6836960d4a1SGreg Roach if (!in_array($row->d_gid, $indi, true)) { 684c6266a77SGreg Roach $years = (int) ($row->age / self::DAYS_IN_YEAR); 685c6266a77SGreg Roach $this->fillYData($years, $sex, 1, $x_axis, $z_axis, $ydata); 686c6266a77SGreg Roach $indi[] = $row->d_gid; 687c6266a77SGreg Roach } 688c6266a77SGreg Roach } 689c6266a77SGreg Roach } 690c6266a77SGreg Roach break; 691c6266a77SGreg Roach case self::Z_AXIS_TIME: 692b6b9dcc9SGreg Roach $boundaries_csv = $params['z-axis-boundaries-periods']; 693c6266a77SGreg Roach $z_axis = $this->axisYears($boundaries_csv); 694afa8d404SGreg Roach // The stats query doesn't have an "all" function, so query M/F separately 695afa8d404SGreg Roach foreach (['M', 'F'] as $sex) { 696c6266a77SGreg Roach $prev_boundary = 0; 697c6266a77SGreg Roach $indi = []; 698c6266a77SGreg Roach foreach (array_keys($z_axis) as $boundary) { 6999219296aSGreg Roach $rows = $statistics->statsMarrAgeQuery($sex, $prev_boundary, $boundary); 700c6266a77SGreg Roach foreach ($rows as $row) { 7016960d4a1SGreg Roach if (!in_array($row->d_gid, $indi, true)) { 702c6266a77SGreg Roach $years = (int) ($row->age / self::DAYS_IN_YEAR); 703c6266a77SGreg Roach $this->fillYData($years, $boundary, 1, $x_axis, $z_axis, $ydata); 704c6266a77SGreg Roach $indi[] = $row->d_gid; 705c6266a77SGreg Roach } 706c6266a77SGreg Roach } 707c6266a77SGreg Roach $prev_boundary = $boundary + 1; 708c6266a77SGreg Roach } 709c6266a77SGreg Roach } 710c6266a77SGreg Roach break; 711c6266a77SGreg Roach default: 712c6266a77SGreg Roach throw new NotFoundHttpException(); 713c6266a77SGreg Roach } 714c6266a77SGreg Roach 7156ccdf4f0SGreg Roach return response($this->myPlot($chart_title, $x_axis, $x_axis_title, $ydata, $y_axis_title, $z_axis, $y_axis_type)); 716c6266a77SGreg Roach 717c6266a77SGreg Roach case self::X_AXIS_NUMBER_OF_CHILDREN: 718c6266a77SGreg Roach $chart_title = I18N::translate('Number of children'); 719c6266a77SGreg Roach $x_axis_title = I18N::translate('Children'); 7206960d4a1SGreg Roach $x_axis = $this->axisNumbers('0,1,2,3,4,5,6,7,8,9,10'); 721c6266a77SGreg Roach 722c6266a77SGreg Roach switch ($y_axis_type) { 723c6266a77SGreg Roach case self::Y_AXIS_NUMBERS: 724c6266a77SGreg Roach $y_axis_title = I18N::translate('Families'); 725c6266a77SGreg Roach break; 726c6266a77SGreg Roach case self::Y_AXIS_PERCENT: 727c6266a77SGreg Roach $y_axis_title = '%'; 728c6266a77SGreg Roach break; 729c6266a77SGreg Roach default: 730c6266a77SGreg Roach throw new NotFoundHttpException(); 731c6266a77SGreg Roach } 732c6266a77SGreg Roach 733c6266a77SGreg Roach switch ($z_axis_type) { 734c6266a77SGreg Roach case self::Z_AXIS_ALL: 735c6266a77SGreg Roach $z_axis = $this->axisAll(); 7369219296aSGreg Roach $rows = $statistics->statsChildrenQuery(); 737c6266a77SGreg Roach foreach ($rows as $row) { 738c6266a77SGreg Roach $this->fillYData($row->f_numchil, 0, $row->total, $x_axis, $z_axis, $ydata); 739c6266a77SGreg Roach } 740c6266a77SGreg Roach break; 741c6266a77SGreg Roach case self::Z_AXIS_TIME: 742b6b9dcc9SGreg Roach $boundaries_csv = $params['z-axis-boundaries-periods']; 743c6266a77SGreg Roach $z_axis = $this->axisYears($boundaries_csv); 744c6266a77SGreg Roach $prev_boundary = 0; 745c6266a77SGreg Roach foreach (array_keys($z_axis) as $boundary) { 746b1126ab4SGreg Roach $rows = $statistics->statsChildrenQuery($prev_boundary, $boundary); 747c6266a77SGreg Roach foreach ($rows as $row) { 748c6266a77SGreg Roach $this->fillYData($row->f_numchil, $boundary, $row->total, $x_axis, $z_axis, $ydata); 749c6266a77SGreg Roach } 750c6266a77SGreg Roach $prev_boundary = $boundary + 1; 751c6266a77SGreg Roach } 752c6266a77SGreg Roach break; 753c6266a77SGreg Roach default: 754c6266a77SGreg Roach throw new NotFoundHttpException(); 755c6266a77SGreg Roach } 756c6266a77SGreg Roach 7576ccdf4f0SGreg Roach return response($this->myPlot($chart_title, $x_axis, $x_axis_title, $ydata, $y_axis_title, $z_axis, $y_axis_type)); 758c6266a77SGreg Roach 759c6266a77SGreg Roach default: 760c6266a77SGreg Roach throw new NotFoundHttpException(); 761c6266a77SGreg Roach break; 762c6266a77SGreg Roach } 763c6266a77SGreg Roach } 764c6266a77SGreg Roach 765c6266a77SGreg Roach /** 766c6266a77SGreg Roach * @return string[] 767c6266a77SGreg Roach */ 768c6266a77SGreg Roach private function axisAll(): array 769c6266a77SGreg Roach { 770c6266a77SGreg Roach return [ 771c6266a77SGreg Roach I18N::translate('Total'), 772c6266a77SGreg Roach ]; 773c6266a77SGreg Roach } 774c6266a77SGreg Roach 775c6266a77SGreg Roach /** 776c6266a77SGreg Roach * @return string[] 777c6266a77SGreg Roach */ 778c6266a77SGreg Roach private function axisSexes(): array 779c6266a77SGreg Roach { 780c6266a77SGreg Roach return [ 781c6266a77SGreg Roach 'M' => I18N::translate('Male'), 782c6266a77SGreg Roach 'F' => I18N::translate('Female'), 783c6266a77SGreg Roach ]; 784c6266a77SGreg Roach } 785c6266a77SGreg Roach 786c6266a77SGreg Roach /** 787c6266a77SGreg Roach * Labels for the X axis 788c6266a77SGreg Roach * 789c6266a77SGreg Roach * @return string[] 790c6266a77SGreg Roach */ 791c6266a77SGreg Roach private function axisMonths(): array 792c6266a77SGreg Roach { 793c6266a77SGreg Roach return [ 794c6266a77SGreg Roach 'JAN' => I18N::translateContext('NOMINATIVE', 'January'), 795c6266a77SGreg Roach 'FEB' => I18N::translateContext('NOMINATIVE', 'February'), 796c6266a77SGreg Roach 'MAR' => I18N::translateContext('NOMINATIVE', 'March'), 797c6266a77SGreg Roach 'APR' => I18N::translateContext('NOMINATIVE', 'April'), 798c6266a77SGreg Roach 'MAY' => I18N::translateContext('NOMINATIVE', 'May'), 799c6266a77SGreg Roach 'JUN' => I18N::translateContext('NOMINATIVE', 'June'), 800c6266a77SGreg Roach 'JUL' => I18N::translateContext('NOMINATIVE', 'July'), 801c6266a77SGreg Roach 'AUG' => I18N::translateContext('NOMINATIVE', 'August'), 802c6266a77SGreg Roach 'SEP' => I18N::translateContext('NOMINATIVE', 'September'), 803c6266a77SGreg Roach 'OCT' => I18N::translateContext('NOMINATIVE', 'October'), 804c6266a77SGreg Roach 'NOV' => I18N::translateContext('NOMINATIVE', 'November'), 805c6266a77SGreg Roach 'DEC' => I18N::translateContext('NOMINATIVE', 'December'), 806c6266a77SGreg Roach ]; 807c6266a77SGreg Roach } 808c6266a77SGreg Roach 809c6266a77SGreg Roach /** 810c6266a77SGreg Roach * Convert a list of N year-boundaries into N+1 year-ranges for the z-axis. 811c6266a77SGreg Roach * 812c6266a77SGreg Roach * @param string $boundaries_csv 813c6266a77SGreg Roach * 814c6266a77SGreg Roach * @return string[] 815c6266a77SGreg Roach */ 816c6266a77SGreg Roach private function axisYears(string $boundaries_csv): array 817c6266a77SGreg Roach { 818c6266a77SGreg Roach $boundaries = explode(',', $boundaries_csv); 819c6266a77SGreg Roach 820c6266a77SGreg Roach $axis = []; 821c6266a77SGreg Roach foreach ($boundaries as $n => $boundary) { 822c6266a77SGreg Roach if ($n === 0) { 823c6266a77SGreg Roach $date = new Date('BEF ' . $boundary); 824c6266a77SGreg Roach } else { 825c6266a77SGreg Roach $date = new Date('BET ' . $boundaries[$n - 1] . ' AND ' . ($boundary - 1)); 826c6266a77SGreg Roach } 827c6266a77SGreg Roach $axis[$boundary - 1] = strip_tags($date->display()); 828c6266a77SGreg Roach } 829c6266a77SGreg Roach 830c6266a77SGreg Roach $date = new Date('AFT ' . $boundaries[count($boundaries) - 1]); 831c6266a77SGreg Roach $axis[PHP_INT_MAX] = strip_tags($date->display()); 832c6266a77SGreg Roach 833c6266a77SGreg Roach return $axis; 834c6266a77SGreg Roach } 835c6266a77SGreg Roach 836c6266a77SGreg Roach /** 837c6266a77SGreg Roach * Create the X axis. 838c6266a77SGreg Roach * 839c6266a77SGreg Roach * @param string $boundaries_csv 840c6266a77SGreg Roach * 841c6266a77SGreg Roach * @return array 842c6266a77SGreg Roach */ 843c6266a77SGreg Roach private function axisNumbers(string $boundaries_csv): array 844c6266a77SGreg Roach { 845c6266a77SGreg Roach $boundaries = explode(',', $boundaries_csv); 846c6266a77SGreg Roach 8470b5fd0a6SGreg Roach $boundaries = array_map(static function (string $x): int { 848c6266a77SGreg Roach return (int) $x; 849c6266a77SGreg Roach }, $boundaries); 850c6266a77SGreg Roach 851c6266a77SGreg Roach $axis = []; 852c6266a77SGreg Roach foreach ($boundaries as $n => $boundary) { 853c6266a77SGreg Roach if ($n === 0) { 8546960d4a1SGreg Roach $prev_boundary = 0; 8556960d4a1SGreg Roach } else { 8566960d4a1SGreg Roach $prev_boundary = $boundaries[$n - 1] + 1; 8576960d4a1SGreg Roach } 8586960d4a1SGreg Roach 8596960d4a1SGreg Roach if ($prev_boundary === $boundary) { 860c6266a77SGreg Roach /* I18N: A range of numbers */ 8616960d4a1SGreg Roach $axis[$boundary] = I18N::number($boundary); 862c6266a77SGreg Roach } else { 863c6266a77SGreg Roach /* I18N: A range of numbers */ 8646960d4a1SGreg Roach $axis[$boundary] = I18N::translate('%1$s–%2$s', I18N::number($prev_boundary), I18N::number($boundary)); 865c6266a77SGreg Roach } 866c6266a77SGreg Roach } 867c6266a77SGreg Roach 868c6266a77SGreg Roach /* I18N: Label on a graph; 40+ means 40 or more */ 869c6266a77SGreg Roach $axis[PHP_INT_MAX] = I18N::translate('%s+', I18N::number($boundaries[count($boundaries) - 1])); 870c6266a77SGreg Roach 871c6266a77SGreg Roach return $axis; 872c6266a77SGreg Roach } 873c6266a77SGreg Roach 874c6266a77SGreg Roach /** 875c6266a77SGreg Roach * Calculate the Y axis. 876c6266a77SGreg Roach * 877c6266a77SGreg Roach * @param int|string $x 878c6266a77SGreg Roach * @param int|string $z 879c6266a77SGreg Roach * @param int|string $value 880c6266a77SGreg Roach * @param array $x_axis 881c6266a77SGreg Roach * @param array $z_axis 882c6266a77SGreg Roach * @param int[][] $ydata 883c6266a77SGreg Roach * 884c6266a77SGreg Roach * @return void 885c6266a77SGreg Roach */ 886e364afe4SGreg Roach private function fillYData($x, $z, $value, array $x_axis, array $z_axis, array &$ydata): void 887c6266a77SGreg Roach { 888c6266a77SGreg Roach $x = $this->findAxisEntry($x, $x_axis); 889c6266a77SGreg Roach $z = $this->findAxisEntry($z, $z_axis); 890c6266a77SGreg Roach 8916960d4a1SGreg Roach if (!array_key_exists($z, $z_axis)) { 892c6266a77SGreg Roach foreach (array_keys($z_axis) as $key) { 893c6266a77SGreg Roach if ($value <= $key) { 894c6266a77SGreg Roach $z = $key; 895c6266a77SGreg Roach break; 896c6266a77SGreg Roach } 897c6266a77SGreg Roach } 898c6266a77SGreg Roach } 899c6266a77SGreg Roach 900c6266a77SGreg Roach // Add the value to the appropriate data point. 901c6266a77SGreg Roach $ydata[$z][$x] = ($ydata[$z][$x] ?? 0) + $value; 902c6266a77SGreg Roach } 903c6266a77SGreg Roach 904c6266a77SGreg Roach /** 905c6266a77SGreg Roach * Find the axis entry for a given value. 906c6266a77SGreg Roach * Some are direct lookup (e.g. M/F, JAN/FEB/MAR). 907c6266a77SGreg Roach * Others need to find the approprate range. 908c6266a77SGreg Roach * 909c6266a77SGreg Roach * @param int|float|string $value 910c6266a77SGreg Roach * @param string[] $axis 911c6266a77SGreg Roach * 912c6266a77SGreg Roach * @return int|string 913c6266a77SGreg Roach */ 914c6266a77SGreg Roach private function findAxisEntry($value, $axis) 915c6266a77SGreg Roach { 916c6266a77SGreg Roach if (is_numeric($value)) { 917c6266a77SGreg Roach $value = (int) $value; 918c6266a77SGreg Roach 9196960d4a1SGreg Roach if (!array_key_exists($value, $axis)) { 920c6266a77SGreg Roach foreach (array_keys($axis) as $boundary) { 921c6266a77SGreg Roach if ($value <= $boundary) { 922c6266a77SGreg Roach $value = $boundary; 923c6266a77SGreg Roach break; 924c6266a77SGreg Roach } 925c6266a77SGreg Roach } 926c6266a77SGreg Roach } 927c6266a77SGreg Roach } 928c6266a77SGreg Roach 929c6266a77SGreg Roach return $value; 930c6266a77SGreg Roach } 931c6266a77SGreg Roach 932c6266a77SGreg Roach /** 933c6266a77SGreg Roach * Plot the data. 934c6266a77SGreg Roach * 935c6266a77SGreg Roach * @param string $chart_title 936c6266a77SGreg Roach * @param string[] $x_axis 937c6266a77SGreg Roach * @param string $x_axis_title 938c6266a77SGreg Roach * @param int[][] $ydata 939c6266a77SGreg Roach * @param string $y_axis_title 940c6266a77SGreg Roach * @param string[] $z_axis 941c6266a77SGreg Roach * @param int $y_axis_type 942c6266a77SGreg Roach * 943c6266a77SGreg Roach * @return string 944c6266a77SGreg Roach */ 945a81e5019SRico Sonntag private function myPlot( 946a81e5019SRico Sonntag string $chart_title, 947a81e5019SRico Sonntag array $x_axis, 948a81e5019SRico Sonntag string $x_axis_title, 949a81e5019SRico Sonntag array $ydata, 950a81e5019SRico Sonntag string $y_axis_title, 951a81e5019SRico Sonntag array $z_axis, 952a81e5019SRico Sonntag int $y_axis_type 953a81e5019SRico Sonntag ): string { 9546960d4a1SGreg Roach if (!count($ydata)) { 955a81e5019SRico Sonntag return I18N::translate('This information is not available.'); 956c6266a77SGreg Roach } 957c6266a77SGreg Roach 958c6266a77SGreg Roach // Colors for z-axis 959c6266a77SGreg Roach $colors = []; 960c6266a77SGreg Roach $index = 0; 9616960d4a1SGreg Roach while (count($colors) < count($ydata)) { 962c6266a77SGreg Roach $colors[] = self::Z_AXIS_COLORS[$index]; 9636960d4a1SGreg Roach $index = ($index + 1) % count(self::Z_AXIS_COLORS); 964c6266a77SGreg Roach } 965c6266a77SGreg Roach 966c6266a77SGreg Roach // Convert our sparse dataset into a fixed-size array 967c6266a77SGreg Roach $tmp = []; 968c6266a77SGreg Roach foreach (array_keys($z_axis) as $z) { 969c6266a77SGreg Roach foreach (array_keys($x_axis) as $x) { 970c6266a77SGreg Roach $tmp[$z][$x] = $ydata[$z][$x] ?? 0; 971c6266a77SGreg Roach } 972c6266a77SGreg Roach } 973c6266a77SGreg Roach $ydata = $tmp; 974c6266a77SGreg Roach 975a81e5019SRico Sonntag // Convert the chart data to percentage 976c6266a77SGreg Roach if ($y_axis_type === self::Y_AXIS_PERCENT) { 977c6266a77SGreg Roach // Normalise each (non-zero!) set of data to total 100% 9780b5fd0a6SGreg Roach array_walk($ydata, static function (array &$x) { 979c6266a77SGreg Roach $sum = array_sum($x); 980c6266a77SGreg Roach if ($sum > 0) { 9810b5fd0a6SGreg Roach $x = array_map(static function ($y) use ($sum) { 982c6266a77SGreg Roach return $y * 100.0 / $sum; 983c6266a77SGreg Roach }, $x); 984c6266a77SGreg Roach } 985c6266a77SGreg Roach }); 986c6266a77SGreg Roach } 987c6266a77SGreg Roach 988a81e5019SRico Sonntag $data = [ 989a81e5019SRico Sonntag array_merge( 990a81e5019SRico Sonntag [ I18N::translate('Century') ], 991a81e5019SRico Sonntag array_values($z_axis) 992a81e5019SRico Sonntag ) 993c6266a77SGreg Roach ]; 994c6266a77SGreg Roach 995a81e5019SRico Sonntag $intermediate = []; 996a81e5019SRico Sonntag foreach ($ydata as $century => $months) { 997a81e5019SRico Sonntag foreach ($months as $month => $value) { 998a81e5019SRico Sonntag $intermediate[$month][] = [ 999a81e5019SRico Sonntag 'v' => $value, 1000a81e5019SRico Sonntag 'f' => ($y_axis_type === self::Y_AXIS_PERCENT) ? sprintf('%.1f%%', $value) : $value, 1001a81e5019SRico Sonntag ]; 1002a81e5019SRico Sonntag } 1003c6266a77SGreg Roach } 1004c6266a77SGreg Roach 1005a81e5019SRico Sonntag foreach ($intermediate as $key => $values) { 1006a81e5019SRico Sonntag $data[] = array_merge( 1007a81e5019SRico Sonntag [ $x_axis[$key] ], 1008a81e5019SRico Sonntag $values 1009a81e5019SRico Sonntag ); 1010a81e5019SRico Sonntag } 1011c6266a77SGreg Roach 1012a81e5019SRico Sonntag $chart_options = [ 1013a81e5019SRico Sonntag 'title' => '', 1014a81e5019SRico Sonntag 'subtitle' => '', 1015a81e5019SRico Sonntag 'height' => 400, 1016a81e5019SRico Sonntag 'width' => '100%', 1017a81e5019SRico Sonntag 'legend' => [ 10186960d4a1SGreg Roach 'position' => count($z_axis) > 1 ? 'right' : 'none', 1019a81e5019SRico Sonntag 'alignment' => 'center', 1020a81e5019SRico Sonntag ], 1021a81e5019SRico Sonntag 'tooltip' => [ 1022a81e5019SRico Sonntag 'format' => '\'%\'', 1023a81e5019SRico Sonntag ], 1024a81e5019SRico Sonntag 'vAxis' => [ 1025a81e5019SRico Sonntag 'title' => $y_axis_title ?? '', 1026a81e5019SRico Sonntag ], 1027a81e5019SRico Sonntag 'hAxis' => [ 1028a81e5019SRico Sonntag 'title' => $x_axis_title ?? '', 1029a81e5019SRico Sonntag ], 1030a81e5019SRico Sonntag 'colors' => $colors, 1031a81e5019SRico Sonntag ]; 1032a81e5019SRico Sonntag 1033a81e5019SRico Sonntag return view( 1034a81e5019SRico Sonntag 'statistics/other/charts/custom', 1035a81e5019SRico Sonntag [ 1036a81e5019SRico Sonntag 'data' => $data, 1037a81e5019SRico Sonntag 'chart_options' => $chart_options, 1038a81e5019SRico Sonntag 'chart_title' => $chart_title, 1039a81e5019SRico Sonntag ] 1040a81e5019SRico Sonntag ); 1041c6266a77SGreg Roach } 1042168ff6f3Sric2016} 1043