diff options
43 files changed, 1290 insertions, 1234 deletions
diff --git a/app/Statistics.php b/app/Statistics.php index 401d0fd8e9..52110acb8a 100644 --- a/app/Statistics.php +++ b/app/Statistics.php @@ -404,11 +404,10 @@ class Statistics implements * @inheritDoc */ public function chartIndisWithSources( - string $size = null, string $color_from = null, string $color_to = null ): string { - return $this->individualRepository->chartIndisWithSources($size, $color_from, $color_to); + return $this->individualRepository->chartIndisWithSources($color_from, $color_to); } /** @@ -447,11 +446,10 @@ class Statistics implements * @inheritDoc */ public function chartFamsWithSources( - string $size = null, string $color_from = null, string $color_to = null ): string { - return $this->individualRepository->chartFamsWithSources($size, $color_from, $color_to); + return $this->individualRepository->chartFamsWithSources($color_from, $color_to); } /** @@ -650,12 +648,11 @@ class Statistics implements * @inheritDoc */ public function chartSex( - string $size = null, string $color_female = null, string $color_male = null, string $color_unknown = null ): string { - return $this->individualRepository->chartSex($size, $color_female, $color_male, $color_unknown); + return $this->individualRepository->chartSex($color_female, $color_male, $color_unknown); } /** @@ -693,9 +690,9 @@ class Statistics implements /** * @inheritDoc */ - public function chartMortality(string $size = null, string $color_living = null, string $color_dead = null): string + public function chartMortality(string $color_living = null, string $color_dead = null): string { - return $this->individualRepository->chartMortality($size, $color_living, $color_dead); + return $this->individualRepository->chartMortality($color_living, $color_dead); } /** @@ -861,9 +858,9 @@ class Statistics implements /** * @inheritDoc */ - public function chartMedia(string $size = null, string $color_from = null, string $color_to = null): string + public function chartMedia(string $color_from = null, string $color_to = null): string { - return $this->mediaRepository->chartMedia($size, $color_from, $color_to); + return $this->mediaRepository->chartMedia($color_from, $color_to); } /** @@ -1000,9 +997,9 @@ class Statistics implements /** * @inheritDoc */ - public function statsBirth(string $size = null, string $color_from = null, string $color_to = null): string + public function statsBirth(string $color_from = null, string $color_to = null): string { - return $this->individualRepository->statsBirth($size, $color_from, $color_to); + return $this->individualRepository->statsBirth($color_from, $color_to); } /** @@ -1080,9 +1077,9 @@ class Statistics implements /** * @inheritDoc */ - public function statsDeath(string $size = null, string $color_from = null, string $color_to = null): string + public function statsDeath(string $color_from = null, string $color_to = null): string { - return $this->individualRepository->statsDeath($size, $color_from, $color_to); + return $this->individualRepository->statsDeath($color_from, $color_to); } /** @@ -1096,9 +1093,9 @@ class Statistics implements /** * @inheritDoc */ - public function statsAge(string $size = '230x250'): string + public function statsAge(): string { - return $this->individualRepository->statsAge($size); + return $this->individualRepository->statsAge(); } /** @@ -1448,9 +1445,9 @@ class Statistics implements /** * @inheritDoc */ - public function statsMarr(string $size = null, string $color_from = null, string $color_to = null): string + public function statsMarr(string $color_from = null, string $color_to = null): string { - return $this->familyRepository->statsMarr($size, $color_from, $color_to); + return $this->familyRepository->statsMarr($color_from, $color_to); } /** @@ -1520,9 +1517,9 @@ class Statistics implements /** * @inheritDoc */ - public function statsDiv(string $size = null, string $color_from = null, string $color_to = null): string + public function statsDiv(string $color_from = null, string $color_to = null): string { - return $this->familyRepository->statsDiv($size, $color_from, $color_to); + return $this->familyRepository->statsDiv($color_from, $color_to); } /** @@ -1632,9 +1629,9 @@ class Statistics implements /** * @inheritDoc */ - public function statsMarrAge(string $size = '200x250'): string + public function statsMarrAge(): string { - return $this->familyRepository->statsMarrAge($size); + return $this->familyRepository->statsMarrAge(); } /** @@ -1897,12 +1894,11 @@ class Statistics implements * @inheritDoc */ public function chartLargestFamilies( - string $size = null, string $color_from = null, string $color_to = null, string $total = '10' ): string { - return $this->familyRepository->chartLargestFamilies($size, $color_from, $color_to, (int) $total); + return $this->familyRepository->chartLargestFamilies($color_from, $color_to, (int) $total); } /** @@ -1989,11 +1985,10 @@ class Statistics implements * @inheritDoc */ public function chartNoChildrenFamilies( - string $size = '220x200', string $year1 = '-1', string $year2 = '-1' ): string { - return $this->familyRepository->chartNoChildrenFamilies($size, (int) $year1, (int) $year2); + return $this->familyRepository->chartNoChildrenFamilies((int) $year1, (int) $year2); } /** @@ -2069,13 +2064,12 @@ class Statistics implements * @inheritDoc */ public function chartCommonSurnames( - string $size = null, string $color_from = null, string $color_to = null, string $number_of_surnames = '10' ): string { return $this->individualRepository - ->chartCommonSurnames($size, $color_from, $color_to, (int) $number_of_surnames); + ->chartCommonSurnames($color_from, $color_to, (int) $number_of_surnames); } /** @@ -2242,12 +2236,11 @@ class Statistics implements * @inheritDoc */ public function chartCommonGiven( - string $size = null, string $color_from = null, string $color_to = null, string $maxtoshow = '7' ): string { - return $this->individualRepository->chartCommonGiven($size, $color_from, $color_to, (int) $maxtoshow); + return $this->individualRepository->chartCommonGiven($color_from, $color_to, (int) $maxtoshow); } /** diff --git a/app/Statistics/AbstractGoogle.php b/app/Statistics/AbstractGoogle.php index 8710551d71..63d4155c9b 100644 --- a/app/Statistics/AbstractGoogle.php +++ b/app/Statistics/AbstractGoogle.php @@ -17,75 +17,102 @@ declare(strict_types=1); namespace Fisharebest\Webtrees\Statistics; +use Fisharebest\Webtrees\Module\ModuleThemeInterface; use Fisharebest\Webtrees\Statistics\Helper\Sql; +use Fisharebest\Webtrees\Tree; /** * Base class for all google charts. - * - * @deprecated The pie chart API is outdated and should be replaced - * by the newer version https://developers.google.com/chart/ or - * an open source one like chart.js - * - * @see https://developers.google.com/chart/image/docs/gallery/pie_charts */ abstract class AbstractGoogle { - // Used in Google charts - public const GOOGLE_CHART_ENCODING = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-.'; + /** + * @var Tree + */ + protected $tree; /** - * Convert numbers to Google's custom encoding. + * @var ModuleThemeInterface + */ + protected $theme; + + /** + * Constructor. * - * @link http://bendodson.com/news/google-extended-encoding-made-easy + * @param Tree $tree + */ + public function __construct(Tree $tree) + { + $this->tree = $tree; + $this->theme = app()->make(ModuleThemeInterface::class); + } + + /** + * Run an SQL query and cache the result. * - * @param int[] $a + * @param string $sql * - * @return string + * @return \stdClass[] */ - protected function arrayToExtendedEncoding(array $a): string + protected function runSql(string $sql): array { - $xencoding = self::GOOGLE_CHART_ENCODING; - $encoding = ''; + return Sql::runSql($sql); + } - foreach ($a as $value) { - if ($value < 0) { - $value = 0; - } + /** + * Interpolates the number of color steps between a given start and end color. + * + * @param string $startColor The start color + * @param string $endColor The end color + * @param int $steps The number of steps to interpolate + * + * @return array + */ + public function interpolateRgb(string $startColor, string $endColor, int $steps): array + { + $s = $this->hexToRgb($startColor); + $e = $this->hexToRgb($endColor); + $colors = []; + $factorR = ($e[0] - $s[0]) / $steps; + $factorG = ($e[1] - $s[1]) / $steps; + $factorB = ($e[2] - $s[2]) / $steps; - $first = intdiv($value, 64); - $second = $value % 64; - $encoding .= $xencoding[$first] . $xencoding[$second]; + for ($x = 1; $x < $steps; ++$x) { + $colors[] = $this->rgbToHex( + (int) round($s[0] + ($factorR * $x)), + (int) round($s[1] + ($factorG * $x)), + (int) round($s[2] + ($factorB * $x)) + ); } - return $encoding; + $colors[] = $this->rgbToHex($e[0], $e[1], $e[2]); + + return $colors; } /** - * Returns the three-dimensional pie chart url. + * Converts the color values to the HTML hex representation. * - * @param string $data - * @param string $size - * @param array $colors - * @param string $labels + * @param int $r The red color value + * @param int $g The green color value + * @param int $b The blue color value * * @return string */ - protected function getPieChartUrl(string $data, string $size, array $colors, string $labels): string + private function rgbToHex(int $r, int $g, int $b): string { - return 'https://chart.googleapis.com/chart?cht=p3&chd=e:' . $data - . '&chs=' . $size . '&chco=' . implode(',', $colors) . '&chf=bg,s,ffffff00&chl=' - . $labels; + return sprintf('#%02x%02x%02x', $r, $g, $b); } /** - * Run an SQL query and cache the result. + * Converts the HTML color hex representation to an array of color values. * - * @param string $sql + * @param string $hex The HTML hex color code * - * @return \stdClass[] + * @return array */ - protected function runSql(string $sql): array + private function hexToRgb(string $hex): array { - return Sql::runSql($sql); + return array_map('hexdec', str_split(ltrim($hex, '#'), 2)); } } diff --git a/app/Statistics/Google/ChartAge.php b/app/Statistics/Google/ChartAge.php index 2ee91c38c3..c22650c54f 100644 --- a/app/Statistics/Google/ChartAge.php +++ b/app/Statistics/Google/ChartAge.php @@ -30,11 +30,6 @@ use Illuminate\Database\Query\JoinClause; class ChartAge extends AbstractGoogle { /** - * @var Tree - */ - private $tree; - - /** * @var Century */ private $centuryHelper; @@ -46,7 +41,8 @@ class ChartAge extends AbstractGoogle */ public function __construct(Tree $tree) { - $this->tree = $tree; + parent::__construct($tree); + $this->centuryHelper = new Century(); } @@ -60,16 +56,21 @@ class ChartAge extends AbstractGoogle $prefix = DB::connection()->getTablePrefix(); return DB::table('individuals') + ->select([ + DB::raw('ROUND(AVG(' . $prefix . 'death.d_julianday2 - ' . $prefix . 'birth.d_julianday1) / 365.25,1) AS age'), + DB::raw('ROUND((' . $prefix . 'death.d_year - 50) / 100) AS century'), + 'i_sex AS sex' + ]) ->join('dates AS birth', function (JoinClause $join): void { $join ->on('birth.d_file', '=', 'i_file') ->on('birth.d_gid', '=', 'i_id'); }) - ->join('dates AS death', function (JoinClause $join): void { - $join + ->join('dates AS death', function (JoinClause $join): void { + $join ->on('death.d_file', '=', 'i_file') ->on('death.d_gid', '=', 'i_id'); - }) + }) ->where('i_file', '=', $this->tree->id()) ->where('birth.d_fact', '=', 'BIRT') ->where('death.d_fact', '=', 'DEAT') @@ -77,11 +78,6 @@ class ChartAge extends AbstractGoogle ->whereIn('death.d_type', ['@#DGREGORIAN@', '@#DJULIAN@']) ->whereColumn('death.d_julianday1', '>=', 'birth.d_julianday2') ->where('birth.d_julianday2', '<>', 0) - ->select([ - DB::raw('ROUND(AVG(' . $prefix . 'death.d_julianday2 - ' . $prefix . 'birth.d_julianday1) / 365.25,1) AS age'), - DB::raw('ROUND((' . $prefix . 'death.d_year - 50) / 100) AS century'), - 'i_sex AS sex' - ]) ->groupBy(['century', 'sex']) ->orderBy('century') ->orderBy('sex') @@ -92,87 +88,46 @@ class ChartAge extends AbstractGoogle /** * General query on ages. * - * @param string $size - * * @return string */ - public function chartAge(string $size = '230x250'): string + public function chartAge(): string { - $sizes = explode('x', $size); - $rows = $this->queryRecords(); - - if (empty($rows)) { - return ''; + $out = []; + foreach ($this->queryRecords() as $record) { + $out[(int) $record->century][$record->sex] = (float) $record->age; } - $chxl = '0:|'; - $countsm = ''; - $countsf = ''; - $countsa = ''; - $out = []; - - foreach ($rows as $values) { - $out[(int) $values->century][$values->sex] = $values->age; - } + $data = [ + [ + I18N::translate('Century'), + I18N::translate('Males'), + I18N::translate('Females'), + I18N::translate('Average age'), + ] + ]; foreach ($out as $century => $values) { - if ($sizes[0] < 980) { - $sizes[0] += 50; - } - $chxl .= $this->centuryHelper->centuryName($century) . '|'; - $female_age = $values['F'] ?? 0; $male_age = $values['M'] ?? 0; - $average_age = $female_age + $male_age; - - if ($female_age > 0 && $male_age > 0) { - $average_age /= 2.0; - } - - $countsf .= $female_age . ','; - $countsm .= $male_age . ','; - $countsa .= $average_age . ','; - } - - $countsm = substr($countsm, 0, -1); - $countsf = substr($countsf, 0, -1); - $countsa = substr($countsa, 0, -1); - $chd = 't2:' . $countsm . '|' . $countsf . '|' . $countsa; - $decades = ''; - - for ($i = 0; $i <= 100; $i += 10) { - $decades .= '|' . I18N::number($i); - } + $average_age = ($female_age + $male_age) / 2.0; - $chxl .= '1:||' . I18N::translate('century') . '|2:' . $decades . '|3:||' . I18N::translate('Age') . '|'; - $title = I18N::translate('Average age related to death century'); - - if (\count($rows) > 6 || mb_strlen($title) < 30) { - $chtt = $title; - } else { - $offset = 0; - $counter = []; - - while ($offset = strpos($title, ' ', $offset + 1)) { - $counter[] = $offset; - } - - $half = intdiv(\count($counter), 2); - $chtt = substr_replace($title, '|', $counter[$half], 1); + $data[] = [ + $this->centuryHelper->centuryName($century), + $male_age, + $female_age, + $average_age, + ]; } - $chart_url = 'https://chart.googleapis.com/chart?cht=bvg&chs=' . $sizes[0] . 'x' . $sizes[1] - . '&chm=D,FF0000,2,0,3,1|N*f1*,000000,0,-1,11,1|N*f1*,000000,1,-1,11,1&chf=bg,s,ffffff00|c,s,ffffff00&chtt=' - . rawurlencode($chtt) . '&chd=' . $chd . '&chco=0000FF,FFA0CB,FF0000&chbh=20,3&chxt=x,x,y,y&chxl=' - . rawurlencode($chxl) . '&chdl=' - . rawurlencode(I18N::translate('Males') . '|' . I18N::translate('Females') . '|' . I18N::translate('Average age at death')); - return view( - 'statistics/other/chart-google', + 'statistics/other/charts/combo', [ - 'chart_title' => I18N::translate('Average age related to death century'), - 'chart_url' => $chart_url, - 'sizes' => $sizes, + 'data' => $data, + 'colors' => ['#84beff', '#ffd1dc', '#ff0000'], + 'chart_title' => I18N::translate('Average age related to death century'), + 'chart_sub_title' => I18N::translate('Average age at death'), + 'hAxis_title' => I18N::translate('Century'), + 'vAxis_title' => I18N::translate('Age'), ] ); } diff --git a/app/Statistics/Google/ChartBirth.php b/app/Statistics/Google/ChartBirth.php index 688b7a729f..e91f2c49b2 100644 --- a/app/Statistics/Google/ChartBirth.php +++ b/app/Statistics/Google/ChartBirth.php @@ -18,7 +18,6 @@ declare(strict_types=1); namespace Fisharebest\Webtrees\Statistics\Google; use Fisharebest\Webtrees\I18N; -use Fisharebest\Webtrees\Module\ModuleThemeInterface; use Fisharebest\Webtrees\Statistics\AbstractGoogle; use Fisharebest\Webtrees\Statistics\Helper\Century; use Fisharebest\Webtrees\Tree; @@ -30,11 +29,6 @@ use Illuminate\Database\Capsule\Manager as DB; class ChartBirth extends AbstractGoogle { /** - * @var Tree - */ - private $tree; - - /** * @var Century */ private $centuryHelper; @@ -46,7 +40,8 @@ class ChartBirth extends AbstractGoogle */ public function __construct(Tree $tree) { - $this->tree = $tree; + parent::__construct($tree); + $this->centuryHelper = new Century(); } @@ -73,53 +68,40 @@ class ChartBirth extends AbstractGoogle /** * Create a chart of birth places. * - * @param string|null $size * @param string|null $color_from * @param string|null $color_to * * @return string */ - public function chartBirth(string $size = null, string $color_from = null, string $color_to = null): string + public function chartBirth(string $color_from = null, string $color_to = null): string { - $chart_color1 = (string) app()->make(ModuleThemeInterface::class)->parameter('distribution-chart-no-values'); - $chart_color2 = (string) app()->make(ModuleThemeInterface::class)->parameter('distribution-chart-high-values'); - $chart_x = app()->make(ModuleThemeInterface::class)->parameter('stats-small-chart-x'); - $chart_y = app()->make(ModuleThemeInterface::class)->parameter('stats-small-chart-y'); - - $size = $size ?? ($chart_x . 'x' . $chart_y); - $color_from = $color_from ?? $chart_color1; - $color_to = $color_to ?? $chart_color2; + $chart_color1 = (string) $this->theme->parameter('distribution-chart-no-values'); + $chart_color2 = (string) $this->theme->parameter('distribution-chart-high-values'); + $color_from = $color_from ?? $chart_color1; + $color_to = $color_to ?? $chart_color2; - $sizes = explode('x', $size); - $tot = 0; - $rows = $this->queryRecords(); - - foreach ($rows as $values) { - $tot += $values->total; - } - - // Beware divide by zero - if ($tot === 0) { - return ''; - } + $data = [ + [ + I18N::translate('Century'), + I18N::translate('Total') + ], + ]; - $centuries = ''; - $counts = []; - foreach ($rows as $values) { - $counts[] = intdiv(100 * $values->total, $tot); - $centuries .= $this->centuryHelper->centuryName((int) $values->century) . ' - ' . I18N::number((int) $values->total) . '|'; + foreach ($this->queryRecords() as $record) { + $data[] = [ + $this->centuryHelper->centuryName((int) $record->century), + $record->total + ]; } - $chd = $this->arrayToExtendedEncoding($counts); - $chl = rawurlencode(substr($centuries, 0, -1)); - $colors = [$color_from, $color_to]; + $colors = $this->interpolateRgb($color_from, $color_to, \count($data) - 1); return view( - 'statistics/other/chart-google', + 'statistics/other/charts/pie', [ - 'chart_title' => I18N::translate('Births by century'), - 'chart_url' => $this->getPieChartUrl($chd, $size, $colors, $chl), - 'sizes' => $sizes, + 'title' => I18N::translate('Births by century'), + 'data' => $data, + 'colors' => $colors, ] ); } diff --git a/app/Statistics/Google/ChartChildren.php b/app/Statistics/Google/ChartChildren.php index 7c8ebcf41b..78c04be12f 100644 --- a/app/Statistics/Google/ChartChildren.php +++ b/app/Statistics/Google/ChartChildren.php @@ -30,11 +30,6 @@ use Illuminate\Database\Query\JoinClause; class ChartChildren extends AbstractGoogle { /** - * @var Tree - */ - private $tree; - - /** * @var Century */ private $centuryHelper; @@ -46,7 +41,8 @@ class ChartChildren extends AbstractGoogle */ public function __construct(Tree $tree) { - $this->tree = $tree; + parent::__construct($tree); + $this->centuryHelper = new Century(); } @@ -58,7 +54,7 @@ class ChartChildren extends AbstractGoogle private function queryRecords(): array { $query = DB::table('families') - ->selectRaw('ROUND(AVG(f_numchil),2) AS num') + ->selectRaw('ROUND(AVG(f_numchil), 2) AS num') ->selectRaw('ROUND((d_year - 50) / 100) AS century') ->join('dates', function (JoinClause $join) { $join->on('d_file', '=', 'f_file') @@ -75,69 +71,34 @@ class ChartChildren extends AbstractGoogle } /** - * General query on familes/children. - * - * @param string $size + * Creates a children per family chart. * * @return string */ - public function chartChildren(string $size = '220x200'): string + public function chartChildren(): string { - $sizes = explode('x', $size); - $max = 0; - $rows = $this->queryRecords(); - - if (empty($rows)) { - return ''; - } - - foreach ($rows as $values) { - $values->num = (int) $values->num; - if ($max < $values->num) { - $max = $values->num; - } - } - - $chm = ''; - $chxl = '0:|'; - $i = 0; - $counts = []; - - foreach ($rows as $values) { - $chxl .= $this->centuryHelper->centuryName((int) $values->century) . '|'; - if ($max <= 5) { - $counts[] = (int) ($values->num * 819.2 - 1); - } elseif ($max <= 10) { - $counts[] = (int) ($values->num * 409.6); - } else { - $counts[] = (int) ($values->num * 204.8); - } - $chm .= 't' . $values->num . ',000000,0,' . $i . ',11,1|'; - $i++; - } - - $chd = $this->arrayToExtendedEncoding($counts); - $chm = substr($chm, 0, -1); + $data = [ + [ + I18N::translate('Century'), + I18N::translate('Average number') + ] + ]; - if ($max <= 5) { - $chxl .= '1:||' . I18N::translate('century') . '|2:|0|1|2|3|4|5|3:||' . I18N::translate('Number of children') . '|'; - } elseif ($max <= 10) { - $chxl .= '1:||' . I18N::translate('century') . '|2:|0|1|2|3|4|5|6|7|8|9|10|3:||' . I18N::translate('Number of children') . '|'; - } else { - $chxl .= '1:||' . I18N::translate('century') . '|2:|0|1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|3:||' . I18N::translate('Number of children') . '|'; + foreach ($this->queryRecords() as $record) { + $data[] = [ + $this->centuryHelper->centuryName((int) $record->century), + (float) $record->num + ]; } - $chart_url = 'https://chart.googleapis.com/chart?cht=bvg&chs=' . $sizes[0] . 'x' . $sizes[1] - . '&chf=bg,s,ffffff00|c,s,ffffff00&chm=D,FF0000,0,0,3,1|' . $chm - . '&chd=e:' . $chd . '&chco=0000FF&chbh=30,3&chxt=x,x,y,y&chxl=' - . rawurlencode($chxl); - return view( - 'statistics/other/chart-google', + 'statistics/other/charts/column', [ + 'data' => $data, + 'colors' => ['#84beff'], 'chart_title' => I18N::translate('Average number of children per family'), - 'chart_url' => $chart_url, - 'sizes' => $sizes, + 'hAxis_title' => I18N::translate('Century'), + 'vAxis_title' => I18N::translate('Number of children'), ] ); } diff --git a/app/Statistics/Google/ChartCommonGiven.php b/app/Statistics/Google/ChartCommonGiven.php index 5b11c4ea9f..a82fb73759 100644 --- a/app/Statistics/Google/ChartCommonGiven.php +++ b/app/Statistics/Google/ChartCommonGiven.php @@ -18,7 +18,6 @@ declare(strict_types=1); namespace Fisharebest\Webtrees\Statistics\Google; use Fisharebest\Webtrees\I18N; -use Fisharebest\Webtrees\Module\ModuleThemeInterface; use Fisharebest\Webtrees\Statistics\AbstractGoogle; /** @@ -31,7 +30,6 @@ class ChartCommonGiven extends AbstractGoogle * * @param int $tot_indi The total number of individuals * @param array $given The list of common given names - * @param string|null $size * @param string|null $color_from * @param string|null $color_to * @@ -40,56 +38,43 @@ class ChartCommonGiven extends AbstractGoogle public function chartCommonGiven( int $tot_indi, array $given, - string $size = null, string $color_from = null, string $color_to = null ) : string { - $chart_color1 = (string) app()->make(ModuleThemeInterface::class)->parameter('distribution-chart-no-values'); - $chart_color2 = (string) app()->make(ModuleThemeInterface::class)->parameter('distribution-chart-high-values'); - $chart_x = app()->make(ModuleThemeInterface::class)->parameter('stats-small-chart-x'); - $chart_y = app()->make(ModuleThemeInterface::class)->parameter('stats-small-chart-y'); - - $size = $size ?? ($chart_x . 'x' . $chart_y); - $color_from = $color_from ?? $chart_color1; - $color_to = $color_to ?? $chart_color2; - $sizes = explode('x', $size); - - if (empty($given)) { - return ''; - } + $chart_color1 = (string) $this->theme->parameter('distribution-chart-no-values'); + $chart_color2 = (string) $this->theme->parameter('distribution-chart-high-values'); + $color_from = $color_from ?? $chart_color1; + $color_to = $color_to ?? $chart_color2; $tot = 0; foreach ($given as $count) { $tot += $count; } - $chd = ''; - $chl = []; + $data = [ + [ + I18N::translate('Name'), + I18N::translate('Total') + ], + ]; - foreach ($given as $givn => $count) { - if ($tot === 0) { - $per = 0; - } else { - $per = intdiv(100 * $count, $tot_indi); - } - $chd .= $this->arrayToExtendedEncoding([$per]); - $chl[] = $givn . ' - ' . I18N::number($count); + foreach ($given as $name => $count) { + $data[] = [ $name, $count ]; } - $per = intdiv(100 * ($tot_indi - $tot), $tot_indi); - $chd .= $this->arrayToExtendedEncoding([$per]); - $chl[] = I18N::translate('Other') . ' - ' . I18N::number($tot_indi - $tot); + $data[] = [ + I18N::translate('Other'), + $tot_indi - $tot + ]; - $chart_title = implode(I18N::$list_separator, $chl); - $chl = rawurlencode(implode('|', $chl)); - $colors = [$color_from, $color_to]; + $colors = $this->interpolateRgb($color_from, $color_to, \count($data) - 1); return view( - 'statistics/other/chart-google', + 'statistics/other/charts/pie', [ - 'chart_title' => $chart_title, - 'chart_url' => $this->getPieChartUrl($chd, $size, $colors, $chl), - 'sizes' => $sizes, + 'title' => null, + 'data' => $data, + 'colors' => $colors, ] ); } diff --git a/app/Statistics/Google/ChartCommonSurname.php b/app/Statistics/Google/ChartCommonSurname.php index 826f42b803..3533acf1a3 100644 --- a/app/Statistics/Google/ChartCommonSurname.php +++ b/app/Statistics/Google/ChartCommonSurname.php @@ -18,7 +18,6 @@ declare(strict_types=1); namespace Fisharebest\Webtrees\Statistics\Google; use Fisharebest\Webtrees\I18N; -use Fisharebest\Webtrees\Module\ModuleThemeInterface; use Fisharebest\Webtrees\Statistics\AbstractGoogle; use Fisharebest\Webtrees\Tree; @@ -28,9 +27,9 @@ use Fisharebest\Webtrees\Tree; class ChartCommonSurname extends AbstractGoogle { /** - * @var Tree + * @var string */ - private $tree; + private $surname_tradition; /** * Constructor. @@ -39,7 +38,59 @@ class ChartCommonSurname extends AbstractGoogle */ public function __construct(Tree $tree) { - $this->tree = $tree; + parent::__construct($tree); + + $this->surname_tradition = $this->tree->getPreference('SURNAME_TRADITION'); + } + + /** + * Count up the different versions of a name and returns the one with the most matches. Takes + * different surname traditions into account. + * + * @param array $surns + * + * @return array [ name, count ] + */ + private function getTopNameAndCount(array $surns): array + { + $max_name = 0; + $count_per = 0; + $top_name = ''; + + foreach ($surns as $spfxsurn => $count) { + $per = $count; + $count_per += $per; + + // select most common surname from all variants + if ($per > $max_name) { + $max_name = $per; + $top_name = $spfxsurn; + } + } + + if ($this->surname_tradition === 'polish') { + // Most common surname should be in male variant (Kowalski, not Kowalska) + $top_name = preg_replace( + [ + '/ska$/', + '/cka$/', + '/dzka$/', + '/żka$/', + ], + [ + 'ski', + 'cki', + 'dzki', + 'żki', + ], + $top_name + ); + } + + return [ + $top_name, + $count_per + ]; } /** @@ -47,7 +98,6 @@ class ChartCommonSurname extends AbstractGoogle * * @param int $tot_indi The total number of individuals * @param array $all_surnames The list of common surnames - * @param string|null $size * @param string|null $color_from * @param string|null $color_to * @@ -56,86 +106,43 @@ class ChartCommonSurname extends AbstractGoogle public function chartCommonSurnames( int $tot_indi, array $all_surnames, - string $size = null, string $color_from = null, string $color_to = null ): string { - $chart_color1 = (string) app()->make(ModuleThemeInterface::class)->parameter('distribution-chart-no-values'); - $chart_color2 = (string) app()->make(ModuleThemeInterface::class)->parameter('distribution-chart-high-values'); - $chart_x = app()->make(ModuleThemeInterface::class)->parameter('stats-small-chart-x'); - $chart_y = app()->make(ModuleThemeInterface::class)->parameter('stats-small-chart-y'); - - $size = $size ?? ($chart_x . 'x' . $chart_y); - $color_from = $color_from ?? $chart_color1; - $color_to = $color_to ?? $chart_color2; - $sizes = explode('x', $size); - - if (empty($all_surnames)) { - return ''; - } - - $surname_tradition = $this->tree->getPreference('SURNAME_TRADITION'); + $chart_color1 = (string) $this->theme->parameter('distribution-chart-no-values'); + $chart_color2 = (string) $this->theme->parameter('distribution-chart-high-values'); + $color_from = $color_from ?? $chart_color1; + $color_to = $color_to ?? $chart_color2; $tot = 0; - foreach ($all_surnames as $surn => $surnames) { $tot += array_sum($surnames); } - $chd = ''; - $chl = []; + $data = [ + [ + I18N::translate('Name'), + I18N::translate('Total') + ], + ]; - /** @var array $surns */ foreach ($all_surnames as $surns) { - $count_per = 0; - $max_name = 0; - $top_name = ''; - - foreach ($surns as $spfxsurn => $count) { - $per = $count; - $count_per += $per; - - // select most common surname from all variants - if ($per > $max_name) { - $max_name = $per; - $top_name = $spfxsurn; - } - } - - if ($surname_tradition === 'polish') { - // Most common surname should be in male variant (Kowalski, not Kowalska) - $top_name = preg_replace([ - '/ska$/', - '/cka$/', - '/dzka$/', - '/żka$/', - ], [ - 'ski', - 'cki', - 'dzki', - 'żki', - ], $top_name); - } - - $per = intdiv(100 * $count_per, $tot_indi); - $chd .= $this->arrayToExtendedEncoding([$per]); - $chl[] = $top_name . ' - ' . I18N::number($count_per); + $data[] = $this->getTopNameAndCount($surns); } - $per = intdiv(100 * ($tot_indi - $tot), $tot_indi); - $chd .= $this->arrayToExtendedEncoding([$per]); - $chl[] = I18N::translate('Other') . ' - ' . I18N::number($tot_indi - $tot); + $data[] = [ + I18N::translate('Other'), + $tot_indi - $tot + ]; - $chart_title = implode(I18N::$list_separator, $chl); - $chl = rawurlencode(implode('|', $chl)); - $colors = [$color_from, $color_to]; + $colors = $this->interpolateRgb($color_from, $color_to, \count($data) - 1); return view( - 'statistics/other/chart-google', + 'statistics/other/charts/pie', [ - 'chart_title' => $chart_title, - 'chart_url' => $this->getPieChartUrl($chd, $size, $colors, $chl), - 'sizes' => $sizes, + 'title' => null, + 'data' => $data, + 'colors' => $colors, ] ); } diff --git a/app/Statistics/Google/ChartDeath.php b/app/Statistics/Google/ChartDeath.php index e51203a392..b76b65e4dd 100644 --- a/app/Statistics/Google/ChartDeath.php +++ b/app/Statistics/Google/ChartDeath.php @@ -18,7 +18,6 @@ declare(strict_types=1); namespace Fisharebest\Webtrees\Statistics\Google; use Fisharebest\Webtrees\I18N; -use Fisharebest\Webtrees\Module\ModuleThemeInterface; use Fisharebest\Webtrees\Statistics\Helper\Century; use Fisharebest\Webtrees\Statistics\AbstractGoogle; use Fisharebest\Webtrees\Tree; @@ -30,11 +29,6 @@ use Illuminate\Database\Capsule\Manager as DB; class ChartDeath extends AbstractGoogle { /** - * @var Tree - */ - private $tree; - - /** * @var Century */ private $centuryHelper; @@ -46,7 +40,8 @@ class ChartDeath extends AbstractGoogle */ public function __construct(Tree $tree) { - $this->tree = $tree; + parent::__construct($tree); + $this->centuryHelper = new Century(); } @@ -73,54 +68,40 @@ class ChartDeath extends AbstractGoogle /** * Create a chart of death places. * - * @param string|null $size * @param string|null $color_from * @param string|null $color_to * * @return string */ - public function chartDeath(string $size = null, string $color_from = null, string $color_to = null): string + public function chartDeath(string $color_from = null, string $color_to = null): string { - $chart_color1 = (string) app()->make(ModuleThemeInterface::class)->parameter('distribution-chart-no-values'); - $chart_color2 = (string) app()->make(ModuleThemeInterface::class)->parameter('distribution-chart-high-values'); - $chart_x = app()->make(ModuleThemeInterface::class)->parameter('stats-small-chart-x'); - $chart_y = app()->make(ModuleThemeInterface::class)->parameter('stats-small-chart-y'); - - $size = $size ?? ($chart_x . 'x' . $chart_y); - $color_from = $color_from ?? $chart_color1; - $color_to = $color_to ?? $chart_color2; + $chart_color1 = (string) $this->theme->parameter('distribution-chart-no-values'); + $chart_color2 = (string) $this->theme->parameter('distribution-chart-high-values'); + $color_from = $color_from ?? $chart_color1; + $color_to = $color_to ?? $chart_color2; - $sizes = explode('x', $size); - $tot = 0; - $rows = $this->queryRecords(); - - foreach ($rows as $values) { - $values->total = (int) $values->total; - $tot += $values->total; - } - - // Beware divide by zero - if ($tot === 0) { - return ''; - } + $data = [ + [ + I18N::translate('Century'), + I18N::translate('Total') + ], + ]; - $centuries = ''; - $counts = []; - foreach ($rows as $values) { - $counts[] = intdiv(100 * $values->total, $tot); - $centuries .= $this->centuryHelper->centuryName((int) $values->century) . ' - ' . I18N::number($values->total) . '|'; + foreach ($this->queryRecords() as $record) { + $data[] = [ + $this->centuryHelper->centuryName((int) $record->century), + $record->total + ]; } - $chd = $this->arrayToExtendedEncoding($counts); - $chl = rawurlencode(substr($centuries, 0, -1)); - $colors = [$color_from, $color_to]; + $colors = $this->interpolateRgb($color_from, $color_to, \count($data) - 1); return view( - 'statistics/other/chart-google', + 'statistics/other/charts/pie', [ - 'chart_title' => I18N::translate('Deaths by century'), - 'chart_url' => $this->getPieChartUrl($chd, $size, $colors, $chl), - 'sizes' => $sizes, + 'title' => I18N::translate('Deaths by century'), + 'data' => $data, + 'colors' => $colors, ] ); } diff --git a/app/Statistics/Google/ChartDistribution.php b/app/Statistics/Google/ChartDistribution.php index 2b46fa53a0..d304b7fa1e 100644 --- a/app/Statistics/Google/ChartDistribution.php +++ b/app/Statistics/Google/ChartDistribution.php @@ -17,14 +17,14 @@ declare(strict_types=1); namespace Fisharebest\Webtrees\Statistics\Google; -use Fisharebest\Webtrees\Database; use Fisharebest\Webtrees\I18N; -use Fisharebest\Webtrees\Module\ModuleThemeInterface; -use Fisharebest\Webtrees\Statistics\Helper\Country; use Fisharebest\Webtrees\Statistics\AbstractGoogle; -use Fisharebest\Webtrees\Statistics\Repository\PlaceRepository; +use Fisharebest\Webtrees\Statistics\Helper\Country; use Fisharebest\Webtrees\Statistics\Repository\IndividualRepository; +use Fisharebest\Webtrees\Statistics\Repository\PlaceRepository; use Fisharebest\Webtrees\Tree; +use Illuminate\Database\Capsule\Manager as DB; +use Illuminate\Database\Query\JoinClause; /** * Create a chart showing where events occurred. @@ -32,11 +32,6 @@ use Fisharebest\Webtrees\Tree; class ChartDistribution extends AbstractGoogle { /** - * @var Tree - */ - private $tree; - - /** * @var Country */ private $countryHelper; @@ -52,184 +47,307 @@ class ChartDistribution extends AbstractGoogle private $placeRepository; /** + * @var string[] + */ + private $country_to_iso3166; + + /** * Constructor. * * @param Tree $tree */ public function __construct(Tree $tree) { - $this->tree = $tree; + parent::__construct($tree); + $this->countryHelper = new Country(); $this->individualRepository = new IndividualRepository($tree); $this->placeRepository = new PlaceRepository($tree); + + // Get the country names for each language + $this->country_to_iso3166 = $this->getIso3166Countries(); } /** - * Create a chart showing where events occurred. + * Returns the country names for each language. * - * @param int $tot_pl The total number of places - * @param string $chart_shows - * @param string $chart_type - * @param string $surname - * - * @return string + * @return string[] */ - public function chartDistribution( - int $tot_pl, - string $chart_shows = 'world', - string $chart_type = '', - string $surname = '' - ): string { - $chart_color1 = app()->make(ModuleThemeInterface::class)->parameter('distribution-chart-no-values'); - $chart_color2 = app()->make(ModuleThemeInterface::class)->parameter('distribution-chart-high-values'); - $chart_color3 = app()->make(ModuleThemeInterface::class)->parameter('distribution-chart-low-values'); - $map_x = app()->make(ModuleThemeInterface::class)->parameter('distribution-chart-x'); - $map_y = app()->make(ModuleThemeInterface::class)->parameter('distribution-chart-y'); - - if ($tot_pl === 0) { - return ''; - } - + private function getIso3166Countries(): array + { $countries = $this->countryHelper->getAllCountries(); // Get the country names for each language - $country_to_iso3166 = []; + $this->country_to_iso3166 = []; + foreach (I18N::activeLocales() as $locale) { I18N::init($locale->languageTag()); foreach ($this->countryHelper->iso3166() as $three => $two) { - $country_to_iso3166[$three] = $two; - $country_to_iso3166[$countries[$three]] = $two; + $this->country_to_iso3166[$three] = $two; + $this->country_to_iso3166[$countries[$three]] = $two; } } - I18N::init(WT_LOCALE); + return $this->country_to_iso3166; + } - switch ($chart_type) { - case 'surname_distribution_chart': - if ($surname === '') { - $surname = $this->individualRepository->getCommonSurname(); + /** + * Returns the data structure required by google geochart. + * + * @param array $places + * + * @return array + */ + private function createChartData(array $places): array + { + $data = [ + [ + I18N::translate('Country'), + I18N::translate('Total'), + ], + ]; + + // webtrees uses 3 letter country codes and localised country names, but google uses 2 letter codes. + foreach ($places as $country => $count) { + $data[] = [ + [ + 'v' => $country, + 'f' => $this->countryHelper->mapTwoLetterToName($country), + ], + $count + ]; + } + + return $data; + } + + /** + * Returns the google geochart data for birth fact. + * + * @return array + */ + private function getBirthChartData(): array + { + // Count how many people were born in each country + $surn_countries = []; + $b_countries = $this->placeRepository->statsPlaces('INDI', 'BIRT', 0, true); + + foreach ($b_countries as $country => $count) { + // Consolidate places (Germany, DEU => DE) + if (\array_key_exists($country, $this->country_to_iso3166)) { + $country_code = $this->country_to_iso3166[$country]; + + if (\array_key_exists($country_code, $surn_countries)) { + $surn_countries[$country_code] += $count; + } else { + $surn_countries[$country_code] = $count; } - $chart_title = I18N::translate('Surname distribution chart') . ': ' . $surname; - // Count how many people are events in each country - $surn_countries = []; + } + } + + return $this->createChartData($surn_countries); + } + + /** + * Returns the google geochart data for death fact. + * + * @return array + */ + private function getDeathChartData(): array + { + // Count how many people were death in each country + $surn_countries = []; + $d_countries = $this->placeRepository->statsPlaces('INDI', 'DEAT', 0, true); + + foreach ($d_countries as $country => $count) { + // Consolidate places (Germany, DEU => DE) + if (\array_key_exists($country, $this->country_to_iso3166)) { + $country_code = $this->country_to_iso3166[$country]; + + if (\array_key_exists($country_code, $surn_countries)) { + $surn_countries[$country_code] += $count; + } else { + $surn_countries[$country_code] = $count; + } + } + } + + return $this->createChartData($surn_countries); + } - $rows = Database::prepare( - 'SELECT i_gedcom' . ' FROM `##individuals`' . ' JOIN `##name` ON n_id = i_id AND n_file = i_file' . ' WHERE n_file = :tree_id' . ' AND n_surn COLLATE :collate = :surname' - )->execute([ - 'tree_id' => $this->tree->id(), - 'collate' => I18N::collation(), - 'surname' => $surname, - ])->fetchAll(); + /** + * Returns the google geochart data for marriages. + * + * @return array + */ + private function getMarriageChartData(): array + { + // Count how many families got marriage in each country + $surn_countries = []; + $m_countries = $this->placeRepository->statsPlaces('FAM'); - foreach ($rows as $row) { - if (preg_match_all('/^2 PLAC (?:.*, *)*(.*)/m', $row->i_gedcom, $matches)) { - // webtrees uses 3 letter country codes and localised country names, - // but google uses 2 letter codes. - foreach ($matches[1] as $country) { - if (\array_key_exists($country, $country_to_iso3166)) { - if (\array_key_exists($country_to_iso3166[$country], $surn_countries)) { - $surn_countries[$country_to_iso3166[$country]]++; - } else { - $surn_countries[$country_to_iso3166[$country]] = 1; - } - } + // webtrees uses 3 letter country codes and localised country names, but google uses 2 letter codes. + foreach ($m_countries as $place) { + // Consolidate places (Germany, DEU => DE) + if (\array_key_exists($place->country, $this->country_to_iso3166)) { + $country_code = $this->country_to_iso3166[$place->country]; + + if (\array_key_exists($country_code, $surn_countries)) { + $surn_countries[$country_code] += $place->tot; + } else { + $surn_countries[$country_code] = $place->tot; + } + } + } + + return $this->createChartData($surn_countries); + } + + /** + * Returns the related database records. + * + * @param string $surname + * + * @return \stdClass[] + */ + private function queryRecords(string $surname): array + { + $query = DB::table('individuals') + ->select(['i_gedcom']) + ->join('name', function (JoinClause $join) { + $join->on('n_id', '=', 'i_id') + ->on('n_file', '=', 'i_file'); + }) + ->where('n_file', '=', $this->tree->id()) + ->where(DB::raw('n_surn /*! COLLATE ' . I18N::collation() . ' */'), '=', $surname); + + return $query->get()->all(); + } + + /** + * Returns the google geochart data for surnames. + * + * @param string $surname The surname used to create the chart + * + * @return array + */ + private function getSurnameChartData(string $surname): array + { + if ($surname === '') { + $surname = $this->individualRepository->getCommonSurname(); + } + + // Count how many people are events in each country + $surn_countries = []; + $records = $this->queryRecords($surname); + + foreach ($records as $row) { + /** @var string[][] $matches */ + if (preg_match_all('/^2 PLAC (?:.*, *)*(.*)/m', $row->i_gedcom, $matches)) { + // webtrees uses 3 letter country codes and localised country names, + // but google uses 2 letter codes. + foreach ($matches[1] as $country) { + // Consolidate places (Germany, DEU => DE) + if (\array_key_exists($country, $this->country_to_iso3166)) { + $country_code = $this->country_to_iso3166[$country]; + + if (\array_key_exists($country_code, $surn_countries)) { + $surn_countries[$country_code]++; + } else { + $surn_countries[$country_code] = 1; } } } + } + } + + return $this->createChartData($surn_countries); + } + + /** + * Returns the google geochart data for individuals. + * + * @return array + */ + private function getIndivdualChartData(): array + { + // Count how many people have events in each country + $surn_countries = []; + $a_countries = $this->placeRepository->statsPlaces('INDI'); + + // webtrees uses 3 letter country codes and localised country names, but google uses 2 letter codes. + foreach ($a_countries as $place) { + // Consolidate places (Germany, DEU => DE) + if (\array_key_exists($place->country, $this->country_to_iso3166)) { + $country_code = $this->country_to_iso3166[$place->country]; + + if (\array_key_exists($country_code, $surn_countries)) { + $surn_countries[$country_code] += $place->tot; + } else { + $surn_countries[$country_code] = $place->tot; + } + } + } + return $this->createChartData($surn_countries); + } + + /** + * Create a chart showing where events occurred. + * + * @param string $chart_shows The type of chart map to show + * @param string $chart_type The type of chart to show + * @param string $surname The surname for surname based distribution chart + * + * @return string + */ + public function chartDistribution( + string $chart_shows = 'world', + string $chart_type = '', + string $surname = '' + ): string { + I18N::init(WT_LOCALE); + + switch ($chart_type) { + case 'surname_distribution_chart': + $chart_title = I18N::translate('Surname distribution chart') . ': ' . $surname; + $data = $this->getSurnameChartData($surname); break; case 'birth_distribution_chart': $chart_title = I18N::translate('Birth by country'); - // Count how many people were born in each country - $surn_countries = []; - $b_countries = $this->placeRepository->statsPlaces('INDI', 'BIRT', 0, true); - foreach ($b_countries as $place => $count) { - $country = $place; - if (\array_key_exists($country, $country_to_iso3166)) { - if (!isset($surn_countries[$country_to_iso3166[$country]])) { - $surn_countries[$country_to_iso3166[$country]] = $count; - } else { - $surn_countries[$country_to_iso3166[$country]] += $count; - } - } - } + $data = $this->getBirthChartData(); break; case 'death_distribution_chart': $chart_title = I18N::translate('Death by country'); - // Count how many people were death in each country - $surn_countries = []; - $d_countries = $this->placeRepository->statsPlaces('INDI', 'DEAT', 0, true); - foreach ($d_countries as $place => $count) { - $country = $place; - if (\array_key_exists($country, $country_to_iso3166)) { - if (!isset($surn_countries[$country_to_iso3166[$country]])) { - $surn_countries[$country_to_iso3166[$country]] = $count; - } else { - $surn_countries[$country_to_iso3166[$country]] += $count; - } - } - } + $data = $this->getDeathChartData(); break; case 'marriage_distribution_chart': $chart_title = I18N::translate('Marriage by country'); - // Count how many families got marriage in each country - $surn_countries = []; - $m_countries = $this->placeRepository->statsPlaces('FAM'); - // webtrees uses 3 letter country codes and localised country names, but google uses 2 letter codes. - foreach ($m_countries as $place) { - $country = $place->country; - if (\array_key_exists($country, $country_to_iso3166)) { - if (!isset($surn_countries[$country_to_iso3166[$country]])) { - $surn_countries[$country_to_iso3166[$country]] = $place->tot; - } else { - $surn_countries[$country_to_iso3166[$country]] += $place->tot; - } - } - } + $data = $this->getMarriageChartData(); break; case 'indi_distribution_chart': default: $chart_title = I18N::translate('Individual distribution chart'); - // Count how many people have events in each country - $surn_countries = []; - $a_countries = $this->placeRepository->statsPlaces('INDI'); - // webtrees uses 3 letter country codes and localised country names, but google uses 2 letter codes. - foreach ($a_countries as $place) { - $country = $place->country; - if (\array_key_exists($country, $country_to_iso3166)) { - if (!isset($surn_countries[$country_to_iso3166[$country]])) { - $surn_countries[$country_to_iso3166[$country]] = $place->tot; - } else { - $surn_countries[$country_to_iso3166[$country]] += $place->tot; - } - } - } + $data = $this->getIndivdualChartData(); break; } - $chart_url = 'https://chart.googleapis.com/chart?cht=t&chtm=' . $chart_shows; - $chart_url .= '&chco=' . $chart_color1 . ',' . $chart_color3 . ',' . $chart_color2; // country colours - $chart_url .= '&chf=bg,s,ECF5FF'; // sea colour - $chart_url .= '&chs=' . $map_x . 'x' . $map_y; - $chart_url .= '&chld=' . implode('', array_keys($surn_countries)) . '&chd=s:'; - - foreach ($surn_countries as $count) { - $chart_url .= substr(self::GOOGLE_CHART_ENCODING, (int) ($count / max($surn_countries) * 61), 1); - } + $chart_color2 = $this->theme->parameter('distribution-chart-high-values'); + $chart_color3 = $this->theme->parameter('distribution-chart-low-values'); return view( - 'statistics/other/chart-distribution', + 'statistics/other/charts/geo', [ 'chart_title' => $chart_title, - 'chart_url' => $chart_url, - 'chart_color1' => $chart_color1, 'chart_color2' => $chart_color2, 'chart_color3' => $chart_color3, + 'region' => $chart_shows, + 'data' => $data, ] ); } diff --git a/app/Statistics/Google/ChartDivorce.php b/app/Statistics/Google/ChartDivorce.php index 79b6126e4f..ea85b024a8 100644 --- a/app/Statistics/Google/ChartDivorce.php +++ b/app/Statistics/Google/ChartDivorce.php @@ -18,7 +18,6 @@ declare(strict_types=1); namespace Fisharebest\Webtrees\Statistics\Google; use Fisharebest\Webtrees\I18N; -use Fisharebest\Webtrees\Module\ModuleThemeInterface; use Fisharebest\Webtrees\Statistics\AbstractGoogle; use Fisharebest\Webtrees\Statistics\Helper\Century; use Fisharebest\Webtrees\Tree; @@ -30,11 +29,6 @@ use Illuminate\Database\Capsule\Manager as DB; class ChartDivorce extends AbstractGoogle { /** - * @var Tree - */ - private $tree; - - /** * @var Century */ private $centuryHelper; @@ -46,7 +40,8 @@ class ChartDivorce extends AbstractGoogle */ public function __construct(Tree $tree) { - $this->tree = $tree; + parent::__construct($tree); + $this->centuryHelper = new Century(); } @@ -73,53 +68,40 @@ class ChartDivorce extends AbstractGoogle /** * General query on divorces. * - * @param string|null $size * @param string|null $color_from * @param string|null $color_to * * @return string */ - public function chartDivorce(string $size = null, string $color_from = null, string $color_to = null): string + public function chartDivorce(string $color_from = null, string $color_to = null): string { - $chart_color1 = (string) app()->make(ModuleThemeInterface::class)->parameter('distribution-chart-no-values'); - $chart_color2 = (string) app()->make(ModuleThemeInterface::class)->parameter('distribution-chart-high-values'); - $chart_x = app()->make(ModuleThemeInterface::class)->parameter('stats-small-chart-x'); - $chart_y = app()->make(ModuleThemeInterface::class)->parameter('stats-small-chart-y'); - - $size = $size ?? ($chart_x . 'x' . $chart_y); - $color_from = $color_from ?? $chart_color1; - $color_to = $color_to ?? $chart_color2; + $chart_color1 = (string) $this->theme->parameter('distribution-chart-no-values'); + $chart_color2 = (string) $this->theme->parameter('distribution-chart-high-values'); + $color_from = $color_from ?? $chart_color1; + $color_to = $color_to ?? $chart_color2; - $sizes = explode('x', $size); - $tot = 0; - $rows = $this->queryRecords(); - - foreach ($rows as $values) { - $values->total = (int) $values->total; - $tot += $values->total; - } - // Beware divide by zero - if ($tot === 0) { - return ''; - } - $centuries = ''; - $counts = []; + $data = [ + [ + I18N::translate('Century'), + I18N::translate('Total') + ], + ]; - foreach ($rows as $values) { - $counts[] = intdiv(100 * $values->total, $tot); - $centuries .= $this->centuryHelper->centuryName((int) $values->century) . ' - ' . I18N::number($values->total) . '|'; + foreach ($this->queryRecords() as $record) { + $data[] = [ + $this->centuryHelper->centuryName((int) $record->century), + $record->total + ]; } - $chd = $this->arrayToExtendedEncoding($counts); - $chl = substr($centuries, 0, -1); - $colors = [$color_from, $color_to]; + $colors = $this->interpolateRgb($color_from, $color_to, \count($data) - 1); return view( - 'statistics/other/chart-google', + 'statistics/other/charts/pie', [ - 'chart_title' => I18N::translate('Divorces by century'), - 'chart_url' => $this->getPieChartUrl($chd, $size, $colors, $chl), - 'sizes' => $sizes, + 'title' => I18N::translate('Divorces by century'), + 'data' => $data, + 'colors' => $colors, ] ); } diff --git a/app/Statistics/Google/ChartFamilyLargest.php b/app/Statistics/Google/ChartFamilyLargest.php index 9abd8273e1..7f3ecc0f70 100644 --- a/app/Statistics/Google/ChartFamilyLargest.php +++ b/app/Statistics/Google/ChartFamilyLargest.php @@ -19,9 +19,7 @@ namespace Fisharebest\Webtrees\Statistics\Google; use Fisharebest\Webtrees\Family; use Fisharebest\Webtrees\I18N; -use Fisharebest\Webtrees\Module\ModuleThemeInterface; use Fisharebest\Webtrees\Statistics\AbstractGoogle; -use Fisharebest\Webtrees\Tree; use Illuminate\Database\Capsule\Manager as DB; /** @@ -30,21 +28,6 @@ use Illuminate\Database\Capsule\Manager as DB; class ChartFamilyLargest extends AbstractGoogle { /** - * @var Tree - */ - private $tree; - - /** - * Constructor. - * - * @param Tree $tree - */ - public function __construct(Tree $tree) - { - $this->tree = $tree; - } - - /** * Returns the related database records. * * @param int $total @@ -54,9 +37,9 @@ class ChartFamilyLargest extends AbstractGoogle private function queryRecords(int $total): array { $query = DB::table('families') - ->select(['f_numchil AS tot', 'f_id AS id']) + ->select(['f_numchil AS total', 'f_id AS id']) ->where('f_file', '=', $this->tree->id()) - ->orderBy('tot', 'desc') + ->orderBy('total', 'desc') ->limit($total); return $query->get()->all(); @@ -65,7 +48,6 @@ class ChartFamilyLargest extends AbstractGoogle /** * Create a chart of the largest families. * - * @param string|null $size * @param string|null $color_from * @param string|null $color_to * @param int $total @@ -73,58 +55,41 @@ class ChartFamilyLargest extends AbstractGoogle * @return string */ public function chartLargestFamilies( - string $size = null, string $color_from = null, string $color_to = null, int $total = 10 ): string { - $chart_color1 = (string) app()->make(ModuleThemeInterface::class)->parameter('distribution-chart-no-values'); - $chart_color2 = (string) app()->make(ModuleThemeInterface::class)->parameter('distribution-chart-high-values'); - $chart_x = app()->make(ModuleThemeInterface::class)->parameter('stats-large-chart-x'); - $chart_y = app()->make(ModuleThemeInterface::class)->parameter('stats-small-chart-y'); - - $size = $size ?? $chart_x . 'x' . $chart_y; - $color_from = $color_from ?? $chart_color1; - $color_to = $color_to ?? $chart_color2; - $sizes = explode('x', $size); - $rows = $this->queryRecords($total); - - if (!isset($rows[0])) { - return ''; - } + $chart_color1 = (string) $this->theme->parameter('distribution-chart-no-values'); + $chart_color2 = (string) $this->theme->parameter('distribution-chart-high-values'); + $color_from = $color_from ?? $chart_color1; + $color_to = $color_to ?? $chart_color2; - $tot = 0; - foreach ($rows as $row) { - $tot += $row->tot; - } - - $chd = ''; - $chl = []; + $data = [ + [ + I18N::translate('Type'), + I18N::translate('Total') + ], + ]; - foreach ($rows as $row) { - $family = Family::getInstance($row->id, $this->tree); + foreach ($this->queryRecords($total) as $record) { + $family = Family::getInstance($record->id, $this->tree); if ($family && $family->canShow()) { - if ($tot === 0) { - $per = 0; - } else { - $per = intdiv(100 * $row->tot, $tot); - } - - $chd .= $this->arrayToExtendedEncoding([$per]); - $chl[] = htmlspecialchars_decode(strip_tags($family->getFullName())) . ' - ' . I18N::number($row->tot); + $data[] = [ + htmlspecialchars_decode(strip_tags($family->getFullName())), + $record->total + ]; } } - $chl = rawurlencode(implode('|', $chl)); - $colors = [$color_from, $color_to]; + $colors = $this->interpolateRgb($color_from, $color_to, \count($data) - 1); return view( - 'statistics/other/chart-google', + 'statistics/other/charts/pie', [ - 'chart_title' => I18N::translate('Largest families'), - 'chart_url' => $this->getPieChartUrl($chd, $size, $colors, $chl), - 'sizes' => $sizes, + 'title' => I18N::translate('Largest families'), + 'data' => $data, + 'colors' => $colors, ] ); } diff --git a/app/Statistics/Google/ChartFamilyWithSources.php b/app/Statistics/Google/ChartFamilyWithSources.php index 21e06f4aeb..12396d6d74 100644 --- a/app/Statistics/Google/ChartFamilyWithSources.php +++ b/app/Statistics/Google/ChartFamilyWithSources.php @@ -18,7 +18,6 @@ declare(strict_types=1); namespace Fisharebest\Webtrees\Statistics\Google; use Fisharebest\Webtrees\I18N; -use Fisharebest\Webtrees\Module\ModuleThemeInterface; use Fisharebest\Webtrees\Statistics\AbstractGoogle; /** @@ -31,7 +30,6 @@ class ChartFamilyWithSources extends AbstractGoogle * * @param int $tot_fam The total number of families * @param int $tot_fam_source The total number of families with sources - * @param string|null $size * @param string|null $color_from * @param string|null $color_to * @@ -40,37 +38,38 @@ class ChartFamilyWithSources extends AbstractGoogle public function chartFamsWithSources( int $tot_fam, int $tot_fam_source, - string $size = null, string $color_from = null, string $color_to = null ): string { - $chart_color1 = (string) app()->make(ModuleThemeInterface::class)->parameter('distribution-chart-no-values'); - $chart_color2 = (string) app()->make(ModuleThemeInterface::class)->parameter('distribution-chart-high-values'); - $chart_x = app()->make(ModuleThemeInterface::class)->parameter('stats-small-chart-x'); - $chart_y = app()->make(ModuleThemeInterface::class)->parameter('stats-small-chart-y'); + $chart_color1 = (string) $this->theme->parameter('distribution-chart-no-values'); + $chart_color2 = (string) $this->theme->parameter('distribution-chart-high-values'); + $color_from = $color_from ?? $chart_color1; + $color_to = $color_to ?? $chart_color2; - $size = $size ?? ($chart_x . 'x' . $chart_y); - $color_from = $color_from ?? $chart_color1; - $color_to = $color_to ?? $chart_color2; - - $sizes = explode('x', $size); - - if ($tot_fam === 0) { - return ''; - } + $data = [ + [ + I18N::translate('Type'), + I18N::translate('Total') + ], + [ + I18N::translate('Without sources'), + $tot_fam - $tot_fam_source + ], + [ + I18N::translate('With sources'), + $tot_fam_source + ], + ]; - $tot_sfam_per = $tot_fam_source / $tot_fam; - $with = (int) (100 * $tot_sfam_per); - $chd = $this->arrayToExtendedEncoding([100 - $with, $with]); - $chl = I18N::translate('Without sources') . ' - ' . I18N::percentage(1 - $tot_sfam_per, 1) . '|' . I18N::translate('With sources') . ' - ' . I18N::percentage($tot_sfam_per, 1); - $colors = [$color_from, $color_to]; + $colors = $this->interpolateRgb($color_from, $color_to, \count($data) - 1); return view( - 'statistics/other/chart-google', + 'statistics/other/charts/pie', [ - 'chart_title' => I18N::translate('Families with sources'), - 'chart_url' => $this->getPieChartUrl($chd, $size, $colors, $chl), - 'sizes' => $sizes, + 'title' => I18N::translate('Families with sources'), + 'data' => $data, + 'colors' => $colors, + 'labeledValueText' => 'percentage', ] ); } diff --git a/app/Statistics/Google/ChartIndividual.php b/app/Statistics/Google/ChartIndividual.php deleted file mode 100644 index 274b152078..0000000000 --- a/app/Statistics/Google/ChartIndividual.php +++ /dev/null @@ -1,73 +0,0 @@ -<?php -/** - * webtrees: online genealogy - * Copyright (C) 2018 webtrees development team - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ -declare(strict_types=1); - -namespace Fisharebest\Webtrees\Statistics\Google; - -use Fisharebest\Webtrees\I18N; -use Fisharebest\Webtrees\Module\ModuleThemeInterface; -use Fisharebest\Webtrees\Statistics\AbstractGoogle; - -/** - * - */ -class ChartIndividual extends AbstractGoogle -{ - /** - * Create a chart showing individuals with/without sources. - * - * @param int $tot_indi The total number of individuals - * @param int $tot_indi_source The total number of individuals with sources - * @param string|null $size - * @param string|null $color_from - * @param string|null $color_to - * - * @return string - */ - public function chartIndisWithSources( - int $tot_indi, int $tot_indi_source, string $size = null, string $color_from = null, string $color_to = null - ): string { - $chart_color1 = (string) app()->make(ModuleThemeInterface::class)->parameter('distribution-chart-no-values'); - $chart_color2 = (string) app()->make(ModuleThemeInterface::class)->parameter('distribution-chart-high-values'); - $chart_x = app()->make(ModuleThemeInterface::class)->parameter('stats-small-chart-x'); - $chart_y = app()->make(ModuleThemeInterface::class)->parameter('stats-small-chart-y'); - - $size = $size ?? ($chart_x . 'x' . $chart_y); - $color_from = $color_from ?? $chart_color1; - $color_to = $color_to ?? $chart_color2; - - $sizes = explode('x', $size); - - if ($tot_indi === 0) { - return ''; - } - - $tot_sindi_per = $tot_indi_source / $tot_indi; - $with = (int) (100 * $tot_sindi_per); - $chd = $this->arrayToExtendedEncoding([100 - $with, $with]); - $chl = I18N::translate('Without sources') . ' - ' . I18N::percentage(1 - $tot_sindi_per, 1) . '|' . I18N::translate('With sources') . ' - ' . I18N::percentage($tot_sindi_per, 1); - $colors = [$color_from, $color_to]; - - return view( - 'statistics/other/chart-google', - [ - 'chart_title' => I18N::translate('Individuals with sources'), - 'chart_url' => $this->getPieChartUrl($chd, $size, $colors, $chl), - 'sizes' => $sizes, - ] - ); - } -} diff --git a/app/Statistics/Google/ChartIndividualWithSources.php b/app/Statistics/Google/ChartIndividualWithSources.php new file mode 100644 index 0000000000..d10f24da4b --- /dev/null +++ b/app/Statistics/Google/ChartIndividualWithSources.php @@ -0,0 +1,76 @@ +<?php +/** + * webtrees: online genealogy + * Copyright (C) 2018 webtrees development team + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +declare(strict_types=1); + +namespace Fisharebest\Webtrees\Statistics\Google; + +use Fisharebest\Webtrees\I18N; +use Fisharebest\Webtrees\Statistics\AbstractGoogle; + +/** + * + */ +class ChartIndividualWithSources extends AbstractGoogle +{ + /** + * Create a chart showing individuals with/without sources. + * + * @param int $tot_indi The total number of individuals + * @param int $tot_indi_source The total number of individuals with sources + * @param string|null $color_from + * @param string|null $color_to + * + * @return string + */ + public function chartIndisWithSources( + int $tot_indi, + int $tot_indi_source, + string $color_from = null, + string $color_to = null + ): string { + $chart_color1 = (string) $this->theme->parameter('distribution-chart-no-values'); + $chart_color2 = (string) $this->theme->parameter('distribution-chart-high-values'); + $color_from = $color_from ?? $chart_color1; + $color_to = $color_to ?? $chart_color2; + + $data = [ + [ + I18N::translate('Type'), + I18N::translate('Total') + ], + [ + I18N::translate('Without sources'), + $tot_indi - $tot_indi_source + ], + [ + I18N::translate('With sources'), + $tot_indi_source + ], + ]; + + $colors = $this->interpolateRgb($color_from, $color_to, \count($data) - 1); + + return view( + 'statistics/other/charts/pie', + [ + 'title' => I18N::translate('Individuals with sources'), + 'data' => $data, + 'colors' => $colors, + 'labeledValueText' => 'percentage', + ] + ); + } +} diff --git a/app/Statistics/Google/ChartMarriage.php b/app/Statistics/Google/ChartMarriage.php index dbc6661171..f3cb17ecbb 100644 --- a/app/Statistics/Google/ChartMarriage.php +++ b/app/Statistics/Google/ChartMarriage.php @@ -18,7 +18,6 @@ declare(strict_types=1); namespace Fisharebest\Webtrees\Statistics\Google; use Fisharebest\Webtrees\I18N; -use Fisharebest\Webtrees\Module\ModuleThemeInterface; use Fisharebest\Webtrees\Statistics\Helper\Century; use Fisharebest\Webtrees\Statistics\AbstractGoogle; use Fisharebest\Webtrees\Tree; @@ -30,11 +29,6 @@ use Illuminate\Database\Capsule\Manager as DB; class ChartMarriage extends AbstractGoogle { /** - * @var Tree - */ - private $tree; - - /** * @var Century */ private $centuryHelper; @@ -46,7 +40,8 @@ class ChartMarriage extends AbstractGoogle */ public function __construct(Tree $tree) { - $this->tree = $tree; + parent::__construct($tree); + $this->centuryHelper = new Century(); } @@ -73,54 +68,40 @@ class ChartMarriage extends AbstractGoogle /** * General query on marriages. * - * @param string|null $size * @param string|null $color_from * @param string|null $color_to * * @return string */ - public function chartMarriage(string $size = null, string $color_from = null, string $color_to = null): string + public function chartMarriage(string $color_from = null, string $color_to = null): string { - $chart_color1 = (string) app()->make(ModuleThemeInterface::class)->parameter('distribution-chart-no-values'); - $chart_color2 = (string) app()->make(ModuleThemeInterface::class)->parameter('distribution-chart-high-values'); - $chart_x = app()->make(ModuleThemeInterface::class)->parameter('stats-small-chart-x'); - $chart_y = app()->make(ModuleThemeInterface::class)->parameter('stats-small-chart-y'); - - $size = $size ?? ($chart_x . 'x' . $chart_y); - $color_from = $color_from ?? $chart_color1; - $color_to = $color_to ?? $chart_color2; + $chart_color1 = (string) $this->theme->parameter('distribution-chart-no-values'); + $chart_color2 = (string) $this->theme->parameter('distribution-chart-high-values'); + $color_from = $color_from ?? $chart_color1; + $color_to = $color_to ?? $chart_color2; - $sizes = explode('x', $size); - $tot = 0; - $rows = $this->queryRecords(); - - foreach ($rows as $values) { - $values->total = (int) $values->total; - $tot += (int) $values->total; - } - - // Beware divide by zero - if ($tot === 0) { - return ''; - } + $data = [ + [ + I18N::translate('Century'), + I18N::translate('Total') + ], + ]; - $centuries = ''; - $counts = []; - foreach ($rows as $values) { - $counts[] = intdiv(100 * $values->total, $tot); - $centuries .= $this->centuryHelper->centuryName((int) $values->century) . ' - ' . I18N::number($values->total) . '|'; + foreach ($this->queryRecords() as $record) { + $data[] = [ + $this->centuryHelper->centuryName((int) $record->century), + $record->total + ]; } - $chd = $this->arrayToExtendedEncoding($counts); - $chl = substr($centuries, 0, -1); - $colors = [$color_from, $color_to]; + $colors = $this->interpolateRgb($color_from, $color_to, \count($data) - 1); return view( - 'statistics/other/chart-google', + 'statistics/other/charts/pie', [ - 'chart_title' => I18N::translate('v by century'), - 'chart_url' => $this->getPieChartUrl($chd, $size, $colors, $chl), - 'sizes' => $sizes, + 'title' => I18N::translate('Divorces by century'), + 'data' => $data, + 'colors' => $colors, ] ); } diff --git a/app/Statistics/Google/ChartMarriageAge.php b/app/Statistics/Google/ChartMarriageAge.php index d71987b5da..b03fdbd760 100644 --- a/app/Statistics/Google/ChartMarriageAge.php +++ b/app/Statistics/Google/ChartMarriageAge.php @@ -28,11 +28,6 @@ use Fisharebest\Webtrees\Tree; class ChartMarriageAge extends AbstractGoogle { /** - * @var Tree - */ - private $tree; - - /** * @var Century */ private $centuryHelper; @@ -44,7 +39,8 @@ class ChartMarriageAge extends AbstractGoogle */ public function __construct(Tree $tree) { - $this->tree = $tree; + parent::__construct($tree); + $this->centuryHelper = new Century(); } @@ -92,125 +88,48 @@ class ChartMarriageAge extends AbstractGoogle /** * General query on ages at marriage. * - * @param string $size - * * @return string */ - public function chartMarriageAge(string $size = '200x250'): string + public function chartMarriageAge(): string { - $sex = 'BOTH'; - $sizes = explode('x', $size); - $rows = $this->queryRecords($sex); + $sex = 'BOTH'; + $out = []; - if (empty($rows)) { - return ''; + foreach ($this->queryRecords($sex) as $record) { + $out[(int) $record->century][$record->sex] = (float) $record->age; } - $max = 0; - - foreach ($rows as $values) { - $values->age = (int) $values->age; - if ($max < $values->age) { - $max = $values->age; - } - } - - $chxl = '0:|'; - $chmm = ''; - $chmf = ''; - $i = 0; - $countsm = ''; - $countsf = ''; - $countsa = ''; - $out = []; - - foreach ($rows as $values) { - $out[(int) $values->century][$values->sex] = $values->age; - } + $data = [ + [ + I18N::translate('Century'), + I18N::translate('Males'), + I18N::translate('Females'), + I18N::translate('Average age'), + ] + ]; foreach ($out as $century => $values) { - if ($sizes[0] < 1000) { - $sizes[0] += 50; - } - $chxl .= $this->centuryHelper->centuryName($century) . '|'; - $average = 0; - if (isset($values['F'])) { - if ($max <= 50) { - $value = $values['F'] * 2; - } else { - $value = $values['F']; - } - $countsf .= $value . ','; - $average = $value; - $chmf .= 't' . $values['F'] . ',000000,1,' . $i . ',11,1|'; - } else { - $countsf .= '0,'; - $chmf .= 't0,000000,1,' . $i . ',11,1|'; - } - if (isset($values['M'])) { - if ($max <= 50) { - $value = $values['M'] * 2; - } else { - $value = $values['M']; - } - $countsm .= $value . ','; - if ($average === 0) { - $countsa .= $value . ','; - } else { - $countsa .= (($value + $average) / 2) . ','; - } - $chmm .= 't' . $values['M'] . ',000000,0,' . $i . ',11,1|'; - } else { - $countsm .= '0,'; - if ($average === 0) { - $countsa .= '0,'; - } else { - $countsa .= $value . ','; - } - $chmm .= 't0,000000,0,' . $i . ',11,1|'; - } - $i++; - } + $female_age = $values['F'] ?? 0; + $male_age = $values['M'] ?? 0; + $average_age = ($female_age + $male_age) / 2.0; - $countsm = substr($countsm, 0, -1); - $countsf = substr($countsf, 0, -1); - $countsa = substr($countsa, 0, -1); - $chmf = substr($chmf, 0, -1); - $chd = 't2:' . $countsm . '|' . $countsf . '|' . $countsa; - - if ($max <= 50) { - $chxl .= '1:||' . I18N::translate('century') . '|2:|0|10|20|30|40|50|3:||' . I18N::translate('Age') . '|'; - } else { - $chxl .= '1:||' . I18N::translate('century') . '|2:|0|10|20|30|40|50|60|70|80|90|100|3:||' . I18N::translate('Age') . '|'; + $data[] = [ + $this->centuryHelper->centuryName($century), + $male_age, + $female_age, + $average_age, + ]; } - if (\count($rows) > 4 || mb_strlen(I18N::translate('Average age in century of marriage')) < 30) { - $chtt = I18N::translate('Average age in century of marriage'); - } else { - $offset = 0; - $counter = []; - - while ($offset = strpos(I18N::translate('Average age in century of marriage'), ' ', $offset + 1)) { - $counter[] = $offset; - } - - $half = intdiv(\count($counter), 2); - $chtt = substr_replace(I18N::translate('Average age in century of marriage'), '|', $counter[$half], 1); - } - - $chart_url = 'https://chart.googleapis.com/chart?cht=bvg&chs=' . $sizes[0] . 'x' . $sizes[1] - . '&chm=D,FF0000,2,0,3,1|' . $chmm . $chmf - . '&chf=bg,s,ffffff00|c,s,ffffff00&chtt=' . rawurlencode($chtt) - . '&chd=' . $chd . '&chco=0000FF,FFA0CB,FF0000&chbh=20,3&chxt=x,x,y,y&chxl=' - . rawurlencode($chxl) . '&chdl=' - . rawurlencode(I18N::translate('Males') . '|' . I18N::translate('Females') . '|' . I18N::translate('Average age')); - return view( - 'statistics/other/chart-google', + 'statistics/other/charts/combo', [ - 'chart_title' => I18N::translate('Average age in century of marriage'), - 'chart_url' => $chart_url, - 'sizes' => $sizes, + 'data' => $data, + 'colors' => ['#84beff', '#ffd1dc', '#ff0000'], + 'chart_title' => I18N::translate('Average age in century of marriage'), + 'chart_sub_title' => I18N::translate('Average age at marriage'), + 'hAxis_title' => I18N::translate('Century'), + 'vAxis_title' => I18N::translate('Age'), ] ); } diff --git a/app/Statistics/Google/ChartMedia.php b/app/Statistics/Google/ChartMedia.php index ddd97c71e3..8afac61acc 100644 --- a/app/Statistics/Google/ChartMedia.php +++ b/app/Statistics/Google/ChartMedia.php @@ -19,7 +19,6 @@ namespace Fisharebest\Webtrees\Statistics\Google; use Fisharebest\Webtrees\GedcomTag; use Fisharebest\Webtrees\I18N; -use Fisharebest\Webtrees\Module\ModuleThemeInterface; use Fisharebest\Webtrees\Statistics\AbstractGoogle; /** @@ -30,58 +29,44 @@ class ChartMedia extends AbstractGoogle /** * Create a chart of media types. * - * @param int $tot The total number of media files * @param array $media The list of media types to display - * @param string|null $size * @param string|null $color_from * @param string|null $color_to * * @return string */ public function chartMedia( - int $tot, array $media, - string $size = null, string $color_from = null, string $color_to = null ): string { - $chart_color1 = (string) app()->make(ModuleThemeInterface::class)->parameter('distribution-chart-no-values'); - $chart_color2 = (string) app()->make(ModuleThemeInterface::class)->parameter('distribution-chart-high-values'); - $chart_x = app()->make(ModuleThemeInterface::class)->parameter('stats-small-chart-x'); - $chart_y = app()->make(ModuleThemeInterface::class)->parameter('stats-small-chart-y'); + $chart_color1 = (string) $this->theme->parameter('distribution-chart-no-values'); + $chart_color2 = (string) $this->theme->parameter('distribution-chart-high-values'); + $color_from = $color_from ?? $chart_color1; + $color_to = $color_to ?? $chart_color2; - $size = $size ?? ($chart_x . 'x' . $chart_y); - $color_from = $color_from ?? $chart_color1; - $color_to = $color_to ?? $chart_color2; - $sizes = explode('x', $size); - - // Beware divide by zero - if ($tot === 0) { - return I18N::translate('None'); - } - - // Build a table listing only the media types actually present in the GEDCOM - $mediaCounts = []; - $mediaTypes = ''; - $chart_title = ''; + $data = [ + [ + I18N::translate('Type'), + I18N::translate('Total') + ], + ]; foreach ($media as $type => $count) { - $mediaCounts[] = intdiv(100 * $count, $tot); - $mediaTypes .= GedcomTag::getFileFormTypeValue($type) . ' - ' . I18N::number($count) . '|'; - $chart_title .= GedcomTag::getFileFormTypeValue($type) . ' (' . $count . '), '; + $data[] = [ + GedcomTag::getFileFormTypeValue($type), + $count + ]; } - $chart_title = substr($chart_title, 0, -2); - $chd = $this->arrayToExtendedEncoding($mediaCounts); - $chl = substr($mediaTypes, 0, -1); - $colors = [$color_from, $color_to]; + $colors = $this->interpolateRgb($color_from, $color_to, \count($data) - 1); return view( - 'statistics/other/chart-google', + 'statistics/other/charts/pie', [ - 'chart_title' => $chart_title, - 'chart_url' => $this->getPieChartUrl($chd, $size, $colors, $chl), - 'sizes' => $sizes, + 'title' => null, + 'data' => $data, + 'colors' => $colors, ] ); } diff --git a/app/Statistics/Google/ChartMortality.php b/app/Statistics/Google/ChartMortality.php index 839ed6b747..6458e77c88 100644 --- a/app/Statistics/Google/ChartMortality.php +++ b/app/Statistics/Google/ChartMortality.php @@ -18,10 +18,7 @@ declare(strict_types=1); namespace Fisharebest\Webtrees\Statistics\Google; use Fisharebest\Webtrees\I18N; -use Fisharebest\Webtrees\Module\ModuleThemeInterface; use Fisharebest\Webtrees\Statistics\AbstractGoogle; -use Fisharebest\Webtrees\Statistics\Repository\IndividualRepository; -use Fisharebest\Webtrees\Tree; /** * @@ -29,26 +26,10 @@ use Fisharebest\Webtrees\Tree; class ChartMortality extends AbstractGoogle { /** - * @var IndividualRepository - */ - private $individualRepository; - - /** - * Constructor. - * - * @param Tree $tree - */ - public function __construct(Tree $tree) - { - $this->individualRepository = new IndividualRepository($tree); - } - - /** * Create a chart showing mortality. * * @param int $tot_l * @param int $tot_d - * @param string|null $size * @param string|null $color_living * @param string|null $color_dead * @@ -57,50 +38,36 @@ class ChartMortality extends AbstractGoogle public function chartMortality( int $tot_l, int $tot_d, - string $size = null, string $color_living = null, string $color_dead = null ): string { - // Raw data - for calculation - $tot = $tot_l + $tot_d; - - if ($tot === 0) { - return ''; - } + $color_living = $color_living ?? '#ffffff'; + $color_dead = $color_dead ?? '#cccccc'; - $chart_x = app()->make(ModuleThemeInterface::class)->parameter('stats-small-chart-x'); - $chart_y = app()->make(ModuleThemeInterface::class)->parameter('stats-small-chart-y'); - - $size = $size ?? ($chart_x . 'x' . $chart_y); - $color_living = $color_living ?? 'ffffff'; - $color_dead = $color_dead ?? 'cccccc'; - - $sizes = explode('x', $size); - - $chd = $this->arrayToExtendedEncoding([ - intdiv(4095 * $tot_l, $tot), - intdiv(4095 * $tot_d, $tot), - ]); - - $per_l = $this->individualRepository->totalLivingPercentage(); - $per_d = $this->individualRepository->totalDeceasedPercentage(); - - $chl = - I18N::translate('Living') . ' - ' . $per_l . '|' . - I18N::translate('Dead') . ' - ' . $per_d . '|'; - - $chart_title = - I18N::translate('Living') . ' - ' . $per_l . I18N::$list_separator . - I18N::translate('Dead') . ' - ' . $per_d; + $data = [ + [ + I18N::translate('Century'), + I18N::translate('Total') + ], + [ + I18N::translate('Living'), + $tot_l + ], + [ + I18N::translate('Dead'), + $tot_d + ], + ]; - $colors = [$color_living, $color_dead]; + $colors = $this->interpolateRgb($color_living, $color_dead, \count($data) - 1); return view( - 'statistics/other/chart-google', + 'statistics/other/charts/pie', [ - 'chart_title' => $chart_title, - 'chart_url' => $this->getPieChartUrl($chd, $size, $colors, $chl), - 'sizes' => $sizes, + 'title' => null, + 'data' => $data, + 'colors' => $colors, + 'labeledValueText' => 'percentage', ] ); } diff --git a/app/Statistics/Google/ChartNoChildrenFamilies.php b/app/Statistics/Google/ChartNoChildrenFamilies.php index 8c211bfb0e..170a33a310 100644 --- a/app/Statistics/Google/ChartNoChildrenFamilies.php +++ b/app/Statistics/Google/ChartNoChildrenFamilies.php @@ -30,11 +30,6 @@ use Illuminate\Database\Query\JoinClause; class ChartNoChildrenFamilies extends AbstractGoogle { /** - * @var Tree - */ - private $tree; - - /** * @var Century */ private $centuryHelper; @@ -46,7 +41,8 @@ class ChartNoChildrenFamilies extends AbstractGoogle */ public function __construct(Tree $tree) { - $this->tree = $tree; + parent::__construct($tree); + $this->centuryHelper = new Century(); } @@ -62,7 +58,7 @@ class ChartNoChildrenFamilies extends AbstractGoogle { $query = DB::table('families') ->selectRaw('ROUND((d_year - 50) / 100) AS century') - ->selectRaw('COUNT(*) AS count') + ->selectRaw('COUNT(*) AS total') ->join('dates', function (JoinClause $join) { $join->on('d_file', '=', 'f_file') ->on('d_gid', '=', 'f_id'); @@ -84,93 +80,48 @@ class ChartNoChildrenFamilies extends AbstractGoogle /** * Create a chart of children with no families. * - * @param int $no_child_fam The number of families with no children - * @param string $size - * @param int $year1 - * @param int $year2 + * @param int $no_child_fam The number of families with no children + * @param int $year1 + * @param int $year2 * * @return string */ public function chartNoChildrenFamilies( int $no_child_fam, - string $size = '220x200', - int $year1 = -1, - int $year2 = -1 + int $year1 = -1, + int $year2 = -1 ): string { - $sizes = explode('x', $size); - $max = 0; - $tot = 0; - $rows = $this->queryRecords($year1, $year2); - - if (empty($rows)) { - return ''; - } - - foreach ($rows as $values) { - $values->count = (int) $values->count; - - if ($max < $values->count) { - $max = $values->count; - } - $tot += $values->count; - } - - $unknown = $no_child_fam - $tot; - - if ($unknown > $max) { - $max = $unknown; - } - - $chm = ''; - $chxl = '0:|'; - $i = 0; - $counts = []; - - foreach ($rows as $values) { - $chxl .= $this->centuryHelper->centuryName((int) $values->century) . '|'; - $counts[] = intdiv(4095 * $values->count, $max + 1); - $chm .= 't' . $values->count . ',000000,0,' . $i . ',11,1|'; - $i++; - } - - $counts[] = intdiv(4095 * $unknown, $max + 1); - $chd = $this->arrayToExtendedEncoding($counts); - $chm .= 't' . $unknown . ',000000,0,' . $i . ',11,1'; - $chxl .= I18N::translateContext('unknown century', 'Unknown') . '|1:||' . I18N::translate('century') . '|2:|0|'; - $step = $max + 1; + $data = [ + [ + I18N::translate('Century'), + I18N::translate('Total') + ] + ]; - for ($d = ($max + 1); $d > 0; $d--) { - if (($max + 1) < ($d * 10 + 1) && fmod($max + 1, $d) === 0) { - $step = $d; - } - } + $total = 0; - if ($step === ($max + 1)) { - for ($d = $max; $d > 0; $d--) { - if ($max < ($d * 10 + 1) && fmod($max, $d) === 0) { - $step = $d; - } - } - } + foreach ($this->queryRecords($year1, $year2) as $record) { + $total += $record->total; - for ($n = $step; $n <= ($max + 1); $n += $step) { - $chxl .= $n . '|'; + $data[] = [ + $this->centuryHelper->centuryName((int) $record->century), + $record->total + ]; } - $chxl .= '3:||' . I18N::translate('Total families') . '|'; - - $chart_url = 'https://chart.googleapis.com/chart?cht=bvg&chs=' . $sizes[0] . 'x' . $sizes[1] - . '&chf=bg,s,ffffff00|c,s,ffffff00&chm=D,FF0000,0,0:' - . ($i - 1) . ',3,1|' . $chm . '&chd=e:' - . $chd . '&chco=0000FF,ffffff00&chbh=30,3&chxt=x,x,y,y&chxl=' - . rawurlencode($chxl); + $data[] = [ + I18N::translateContext('unknown century', 'Unknown'), + $no_child_fam - $total + ]; return view( - 'statistics/other/chart-google', + 'statistics/other/charts/column', [ + 'data' => $data, + 'colors' => ['#84beff'], 'chart_title' => I18N::translate('Number of families without children'), - 'chart_url' => $chart_url, - 'sizes' => $sizes, + 'hAxis_title' => I18N::translate('Century'), + 'vAxis_title' => I18N::translate('Total families'), ] ); } diff --git a/app/Statistics/Google/ChartSex.php b/app/Statistics/Google/ChartSex.php index 60eb1d5f88..c66df56020 100644 --- a/app/Statistics/Google/ChartSex.php +++ b/app/Statistics/Google/ChartSex.php @@ -18,10 +18,7 @@ declare(strict_types=1); namespace Fisharebest\Webtrees\Statistics\Google; use Fisharebest\Webtrees\I18N; -use Fisharebest\Webtrees\Module\ModuleThemeInterface; use Fisharebest\Webtrees\Statistics\AbstractGoogle; -use Fisharebest\Webtrees\Statistics\Repository\IndividualRepository; -use Fisharebest\Webtrees\Tree; /** * @@ -29,27 +26,11 @@ use Fisharebest\Webtrees\Tree; class ChartSex extends AbstractGoogle { /** - * @var IndividualRepository - */ - private $individualRepository; - - /** - * Constructor. - * - * @param Tree $tree - */ - public function __construct(Tree $tree) - { - $this->individualRepository = new IndividualRepository($tree); - } - - /** * Generate a chart showing sex distribution. * * @param int $tot_m The total number of male individuals * @param int $tot_f The total number of female individuals * @param int $tot_u The total number of unknown individuals - * @param string|null $size * @param string|null $color_female * @param string|null $color_male * @param string|null $color_unknown @@ -60,74 +41,40 @@ class ChartSex extends AbstractGoogle int $tot_m, int $tot_f, int $tot_u, - string $size = null, string $color_female = null, string $color_male = null, string $color_unknown = null ): string { - $chart_x = app()->make(ModuleThemeInterface::class)->parameter('stats-small-chart-x'); - $chart_y = app()->make(ModuleThemeInterface::class)->parameter('stats-small-chart-y'); - - $size = $size ?? ($chart_x . 'x' . $chart_y); - $color_female = $color_female ?? 'ffd1dc'; - $color_male = $color_male ?? '84beff'; - $color_unknown = $color_unknown ?? '777777'; - - $sizes = explode('x', $size); - - // Raw data - for calculation - $tot = $tot_f + $tot_m + $tot_u; - - // I18N data - for display - $per_f = $this->individualRepository->totalSexFemalesPercentage(); - $per_m = $this->individualRepository->totalSexMalesPercentage(); - $per_u = $this->individualRepository->totalSexUnknownPercentage(); + $color_female = $color_female ?? '#ffd1dc'; + $color_male = $color_male ?? '#84beff'; + $color_unknown = $color_unknown ?? '#777777'; - if ($tot === 0) { - return ''; - } - - if ($tot_u > 0) { - $chd = $this->arrayToExtendedEncoding([ - intdiv(4095 * $tot_u, $tot), - intdiv(4095 * $tot_f, $tot), - intdiv(4095 * $tot_m, $tot), - ]); - - $chl = - I18N::translateContext('unknown people', 'Unknown') . ' - ' . $per_u . '|' . - I18N::translate('Females') . ' - ' . $per_f . '|' . - I18N::translate('Males') . ' - ' . $per_m; - - $chart_title = - I18N::translate('Males') . ' - ' . $per_m . I18N::$list_separator . - I18N::translate('Females') . ' - ' . $per_f . I18N::$list_separator . - I18N::translateContext('unknown people', 'Unknown') . ' - ' . $per_u; - - $colors = [$color_unknown, $color_female, $color_male]; - } else { - $chd = $this->arrayToExtendedEncoding([ - intdiv(4095 * $tot_f, $tot), - intdiv(4095 * $tot_m, $tot), - ]); - - $chl = - I18N::translate('Females') . ' - ' . $per_f . '|' . - I18N::translate('Males') . ' - ' . $per_m; - - $chart_title = - I18N::translate('Males') . ' - ' . $per_m . I18N::$list_separator . - I18N::translate('Females') . ' - ' . $per_f; - - $colors = [$color_female, $color_male]; - } + $data = [ + [ + I18N::translate('Type'), + I18N::translate('Total') + ], + [ + I18N::translate('Males'), + $tot_m + ], + [ + I18N::translate('Females'), + $tot_f + ], + [ + I18N::translate('Unknown'), + $tot_u + ], + ]; return view( - 'statistics/other/chart-google', + 'statistics/other/charts/pie', [ - 'chart_title' => $chart_title, - 'chart_url' => $this->getPieChartUrl($chd, $size, $colors, $chl), - 'sizes' => $sizes, + 'title' => null, + 'data' => $data, + 'colors' => [$color_male, $color_female, $color_unknown], + 'labeledValueText' => 'percentage', ] ); } diff --git a/app/Statistics/Helper/Country.php b/app/Statistics/Helper/Country.php index 83d63984fc..763f1bf7c2 100644 --- a/app/Statistics/Helper/Country.php +++ b/app/Statistics/Helper/Country.php @@ -799,4 +799,19 @@ class Country 'ZWE' => 'ZW', ]; } + + /** + * Returns the translated country name based on the given two letter country code. + * + * @param string $twoLetterCode The two letter country code + * + * @return string + */ + public function mapTwoLetterToName(string $twoLetterCode): string + { + $threeLetterCode = array_search($twoLetterCode, $this->iso3166(), true); + $threeLetterCode = $threeLetterCode ?: '???'; + + return $this->getAllCountries()[$threeLetterCode]; + } } diff --git a/app/Statistics/Repository/ContactRepository.php b/app/Statistics/Repository/ContactRepository.php index 16a387febd..65e692e97b 100644 --- a/app/Statistics/Repository/ContactRepository.php +++ b/app/Statistics/Repository/ContactRepository.php @@ -21,7 +21,6 @@ use Fisharebest\Webtrees\Services\UserService; use Fisharebest\Webtrees\Statistics\Repository\Interfaces\ContactRepositoryInterface; use Fisharebest\Webtrees\Tree; use Fisharebest\Webtrees\User; -use Symfony\Component\HttpFoundation\Request; /** * A repository providing methods for contact related statistics. diff --git a/app/Statistics/Repository/FamilyRepository.php b/app/Statistics/Repository/FamilyRepository.php index fdbab4e647..896c65b229 100644 --- a/app/Statistics/Repository/FamilyRepository.php +++ b/app/Statistics/Repository/FamilyRepository.php @@ -326,18 +326,17 @@ class FamilyRepository /** * Create a chart of children with no families. * - * @param string $size - * @param int $year1 - * @param int $year2 + * @param int $year1 + * @param int $year2 * * @return string */ - public function chartNoChildrenFamilies(string $size = '220x200', int $year1 = -1, int $year2 = -1): string + public function chartNoChildrenFamilies(int $year1 = -1, int $year2 = -1): string { $no_child_fam = $this->noChildrenFamiliesQuery(); return (new ChartNoChildrenFamilies($this->tree)) - ->chartNoChildrenFamilies($no_child_fam, $size, $year1, $year2); + ->chartNoChildrenFamilies($no_child_fam, $year1, $year2); } /** @@ -662,14 +661,12 @@ class FamilyRepository /** * Genearl query on families/children. * - * @param string $size - * * @return string */ - public function statsChildren(string $size = '220x200'): string + public function statsChildren(): string { return (new ChartChildren($this->tree)) - ->chartChildren($size); + ->chartChildren(); } /** @@ -799,7 +796,6 @@ class FamilyRepository /** * Create a chart of the largest families. * - * @param string|null $size * @param string|null $color_from * @param string|null $color_to * @param int $total @@ -807,13 +803,12 @@ class FamilyRepository * @return string */ public function chartLargestFamilies( - string $size = null, string $color_from = null, string $color_to = null, int $total = 10 ): string { return (new ChartFamilyLargest($this->tree)) - ->chartLargestFamilies($size, $color_from, $color_to, $total); + ->chartLargestFamilies($color_from, $color_to, $total); } /** @@ -1545,14 +1540,12 @@ class FamilyRepository /** * General query on marriage ages. * - * @param string $size - * * @return string */ - public function statsMarrAge(string $size = '200x250'): string + public function statsMarrAge(): string { return (new ChartMarriageAge($this->tree)) - ->chartMarriageAge($size); + ->chartMarriageAge(); } /** @@ -1817,30 +1810,28 @@ class FamilyRepository /** * General query on marriages. * - * @param string|null $size * @param string|null $color_from * @param string|null $color_to * * @return string */ - public function statsMarr(string $size = null, string $color_from = null, string $color_to = null): string + public function statsMarr(string $color_from = null, string $color_to = null): string { return (new ChartMarriage($this->tree)) - ->chartMarriage($size, $color_from, $color_to); + ->chartMarriage($color_from, $color_to); } /** * General divorce query. * - * @param string|null $size * @param string|null $color_from * @param string|null $color_to * * @return string */ - public function statsDiv(string $size = null, string $color_from = null, string $color_to = null): string + public function statsDiv(string $color_from = null, string $color_to = null): string { return (new ChartDivorce($this->tree)) - ->chartDivorce($size, $color_from, $color_to); + ->chartDivorce($color_from, $color_to); } } diff --git a/app/Statistics/Repository/IndividualRepository.php b/app/Statistics/Repository/IndividualRepository.php index 55190d5357..d8e037d335 100644 --- a/app/Statistics/Repository/IndividualRepository.php +++ b/app/Statistics/Repository/IndividualRepository.php @@ -30,7 +30,7 @@ use Fisharebest\Webtrees\Statistics\Google\ChartCommonGiven; use Fisharebest\Webtrees\Statistics\Google\ChartCommonSurname; use Fisharebest\Webtrees\Statistics\Google\ChartDeath; use Fisharebest\Webtrees\Statistics\Google\ChartFamilyWithSources; -use Fisharebest\Webtrees\Statistics\Google\ChartIndividual; +use Fisharebest\Webtrees\Statistics\Google\ChartIndividualWithSources; use Fisharebest\Webtrees\Statistics\Google\ChartMortality; use Fisharebest\Webtrees\Statistics\Google\ChartSex; use Fisharebest\Webtrees\Statistics\Helper\Sql; @@ -676,16 +676,15 @@ class IndividualRepository implements IndividualRepositoryInterface /** * General query on births. * - * @param string|null $size * @param string|null $color_from * @param string|null $color_to * * @return string */ - public function statsBirth(string $size = null, string $color_from = null, string $color_to = null): string + public function statsBirth(string $color_from = null, string $color_to = null): string { return (new ChartBirth($this->tree)) - ->chartBirth($size, $color_from, $color_to); + ->chartBirth($color_from, $color_to); } /** @@ -732,16 +731,15 @@ class IndividualRepository implements IndividualRepositoryInterface /** * General query on deaths. * - * @param string|null $size * @param string|null $color_from * @param string|null $color_to * * @return string */ - public function statsDeath(string $size = null, string $color_from = null, string $color_to = null): string + public function statsDeath(string $color_from = null, string $color_to = null): string { return (new ChartDeath($this->tree)) - ->chartDeath($size, $color_from, $color_to); + ->chartDeath($color_from, $color_to); } /** @@ -782,13 +780,11 @@ class IndividualRepository implements IndividualRepositoryInterface /** * General query on ages. * - * @param string $size - * * @return string */ - public function statsAge(string $size = '230x250'): string + public function statsAge(): string { - return (new ChartAge($this->tree))->chartAge($size); + return (new ChartAge($this->tree))->chartAge(); } /** @@ -1776,7 +1772,6 @@ class IndividualRepository implements IndividualRepositoryInterface /** * Create a chart of common given names. * - * @param string|null $size * @param string|null $color_from * @param string|null $color_to * @param int $maxtoshow @@ -1784,7 +1779,6 @@ class IndividualRepository implements IndividualRepositoryInterface * @return string */ public function chartCommonGiven( - string $size = null, string $color_from = null, string $color_to = null, int $maxtoshow = 7 @@ -1792,14 +1786,17 @@ class IndividualRepository implements IndividualRepositoryInterface $tot_indi = $this->totalIndividualsQuery(); $given = $this->commonGivenQuery('B', 'chart', false, 1, $maxtoshow); - return (new ChartCommonGiven()) - ->chartCommonGiven($tot_indi, $given, $size, $color_from, $color_to); + if (empty($given)) { + return ''; + } + + return (new ChartCommonGiven($this->tree)) + ->chartCommonGiven($tot_indi, $given, $color_from, $color_to); } /** * Create a chart of common surnames. * - * @param string|null $size * @param string|null $color_from * @param string|null $color_to * @param int $number_of_surnames @@ -1807,7 +1804,6 @@ class IndividualRepository implements IndividualRepositoryInterface * @return string */ public function chartCommonSurnames( - string $size = null, string $color_from = null, string $color_to = null, int $number_of_surnames = 10 @@ -1815,75 +1811,73 @@ class IndividualRepository implements IndividualRepositoryInterface $tot_indi = $this->totalIndividualsQuery(); $all_surnames = $this->topSurnames($number_of_surnames, 0); + if (empty($all_surnames)) { + return ''; + } + return (new ChartCommonSurname($this->tree)) - ->chartCommonSurnames($tot_indi, $all_surnames, $size, $color_from, $color_to); + ->chartCommonSurnames($tot_indi, $all_surnames, $color_from, $color_to); } /** * Create a chart showing mortality. * - * @param string|null $size * @param string|null $color_living * @param string|null $color_dead * * @return string */ - public function chartMortality(string $size = null, string $color_living = null, string $color_dead = null): string + public function chartMortality(string $color_living = null, string $color_dead = null): string { $tot_l = $this->totalLivingQuery(); $tot_d = $this->totalDeceasedQuery(); return (new ChartMortality($this->tree)) - ->chartMortality($tot_l, $tot_d, $size, $color_living, $color_dead); + ->chartMortality($tot_l, $tot_d, $color_living, $color_dead); } /** * Create a chart showing individuals with/without sources. * - * @param string|null $size * @param string|null $color_from * @param string|null $color_to * * @return string */ public function chartIndisWithSources( - string $size = null, string $color_from = null, string $color_to = null ): string { $tot_indi = $this->totalIndividualsQuery(); $tot_indi_source = $this->totalIndisWithSourcesQuery(); - return (new ChartIndividual()) - ->chartIndisWithSources($tot_indi, $tot_indi_source, $size, $color_from, $color_to); + return (new ChartIndividualWithSources($this->tree)) + ->chartIndisWithSources($tot_indi, $tot_indi_source, $color_from, $color_to); } /** * Create a chart of individuals with/without sources. * - * @param string|null $size * @param string|null $color_from * @param string|null $color_to * * @return string */ public function chartFamsWithSources( - string $size = null, string $color_from = null, string $color_to = null ): string { $tot_fam = $this->totalFamiliesQuery(); $tot_fam_source = $this->totalFamsWithSourcesQuery(); - return (new ChartFamilyWithSources()) - ->chartFamsWithSources($tot_fam, $tot_fam_source, $size, $color_from, $color_to); + return (new ChartFamilyWithSources($this->tree)) + ->chartFamsWithSources($tot_fam, $tot_fam_source, $color_from, $color_to); } /** * @inheritDoc */ public function chartSex( - string $size = null, string $color_female = null, string $color_male = null, string $color_unknown = null @@ -1893,7 +1887,7 @@ class IndividualRepository implements IndividualRepositoryInterface $tot_u = $this->totalSexUnknownQuery(); return (new ChartSex($this->tree)) - ->chartSex($tot_m, $tot_f, $tot_u, $size, $color_female, $color_male, $color_unknown); + ->chartSex($tot_m, $tot_f, $tot_u, $color_female, $color_male, $color_unknown); } /** diff --git a/app/Statistics/Repository/Interfaces/IndividualRepositoryInterface.php b/app/Statistics/Repository/Interfaces/IndividualRepositoryInterface.php index 1bad0df3e1..2244054697 100644 --- a/app/Statistics/Repository/Interfaces/IndividualRepositoryInterface.php +++ b/app/Statistics/Repository/Interfaces/IndividualRepositoryInterface.php @@ -158,7 +158,6 @@ interface IndividualRepositoryInterface /** * Generate a chart showing sex distribution. * - * @param string|null $size * @param string|null $color_female * @param string|null $color_male * @param string|null $color_unknown @@ -166,7 +165,6 @@ interface IndividualRepositoryInterface * @return string */ public function chartSex( - string $size = null, string $color_female = null, string $color_male = null, string $color_unknown = null diff --git a/app/Statistics/Repository/Interfaces/MediaRepositoryInterface.php b/app/Statistics/Repository/Interfaces/MediaRepositoryInterface.php index ac7cd69f52..b16cb77ad1 100644 --- a/app/Statistics/Repository/Interfaces/MediaRepositoryInterface.php +++ b/app/Statistics/Repository/Interfaces/MediaRepositoryInterface.php @@ -165,11 +165,10 @@ interface MediaRepositoryInterface /** * Create a chart of media types. * - * @param string|null $size * @param string|null $color_from * @param string|null $color_to * * @return string */ - public function chartMedia(string $size = null, string $color_from = null, string $color_to = null): string; + public function chartMedia(string $color_from = null, string $color_to = null): string; } diff --git a/app/Statistics/Repository/MediaRepository.php b/app/Statistics/Repository/MediaRepository.php index 95798fb83b..4618c00953 100644 --- a/app/Statistics/Repository/MediaRepository.php +++ b/app/Statistics/Repository/MediaRepository.php @@ -354,12 +354,12 @@ class MediaRepository implements MediaRepositoryInterface /** * @inheritDoc */ - public function chartMedia(string $size = null, string $color_from = null, string $color_to = null): string + public function chartMedia(string $color_from = null, string $color_to = null): string { $tot = $this->totalMediaTypeQuery(self::MEDIA_TYPE_ALL); $media = $this->getSortedMediaTypeList($tot); - return (new ChartMedia()) - ->chartMedia($tot, $media, $size, $color_from, $color_to); + return (new ChartMedia($this->tree)) + ->chartMedia($media, $color_from, $color_to); } } diff --git a/app/Statistics/Repository/PlaceRepository.php b/app/Statistics/Repository/PlaceRepository.php index a8acc75d65..a44cc10d06 100644 --- a/app/Statistics/Repository/PlaceRepository.php +++ b/app/Statistics/Repository/PlaceRepository.php @@ -349,9 +349,11 @@ class PlaceRepository implements PlaceRepositoryInterface string $chart_type = '', string $surname = '' ): string { - $tot_pl = $this->totalPlacesQuery(); + if ($this->totalPlacesQuery() === 0) { + return ''; + } return (new ChartDistribution($this->tree)) - ->chartDistribution($tot_pl, $chart_shows, $chart_type, $surname); + ->chartDistribution($chart_shows, $chart_type, $surname); } } diff --git a/app/View.php b/app/View.php index aba43583d6..04c2a2eb9e 100644 --- a/app/View.php +++ b/app/View.php @@ -105,7 +105,10 @@ class View */ public static function endpush() { - self::$stacks[self::$stack][] = ob_get_clean(); + $content = ob_get_clean(); + $hash = sha1($content); + + self::$stacks[self::$stack][$hash] = $content; } /** diff --git a/public/js/webtrees.min.js b/public/js/webtrees.min.js index 4f3eed50a7..6659619536 100644 --- a/public/js/webtrees.min.js +++ b/public/js/webtrees.min.js @@ -1 +1 @@ -"use strict";function expand_layer(e){return $("#"+e+"_img").toggleClass("icon-plus icon-minus"),$("#"+e).slideToggle("fast"),$("#"+e+"-alt").toggle(),!1}function accept_changes(e,t){return $.post("index.php",{route:"accept-changes",xref:e,ged:t},function(){document.location.reload()}),!1}function reject_changes(e,t){return $.post("index.php",{route:"reject-changes",xref:e,ged:t},function(){document.location.reload()}),!1}function delete_record(e,t){return $.post("index.php",{route:"delete-record",xref:e,ged:t},function(){document.location.reload()}),!1}function delete_fact(e,t,n,o){return confirm(e)&&$.post("index.php",{route:"delete-fact",xref:n,fact_id:o,ged:t},function(){document.location.reload()}),!1}function copy_fact(e,t,n){return $.post("index.php",{route:"copy-fact",xref:t,fact_id:n,ged:e},function(){document.location.reload()}),!1}function paste_fact(e,t,n){return $.post("index.php",{route:"paste-fact",xref:t,fact_id:$(n).val(),ged:e},function(){document.location.reload()}),!1}function delete_user(e,t){return confirm(e)&&$.post("index.php",{route:"delete-user",user_id:t},function(){document.location.reload()}),!1}function masquerade(e){return $.post("index.php",{route:"masquerade",user_id:e},function(){document.location.reload()}),!1}var pastefield;function addmedia_links(e,t,n){return pastefield=e,insertRowToTable(t,n),!1}function valid_date(e,t){var n=["JAN","FEB","MAR","APR","MAY","JUN","JUL","AUG","SEP","OCT","NOV","DEC"],o=e.value,a=o.split("("),l="";a.length>1&&(o=a[0],l=a[1]),(o=(o=(o=(o=(o=o.toUpperCase()).replace(/\s+/," ")).replace(/(^\s)|(\s$)/,"")).replace(/(\d)([A-Z])/,"$1 $2")).replace(/([A-Z])(\d)/,"$1 $2")).match(/^Q ([1-4]) (\d\d\d\d)$/)&&(o="BET "+n[3*RegExp.$1-3]+" "+RegExp.$2+" AND "+n[3*RegExp.$1-1]+" "+RegExp.$2),o.match(/^(@#DHIJRI@|HIJRI)( \d?\d )(\d?\d)( \d?\d?\d?\d)$/)&&(o="@#DHIJRI@"+RegExp.$2+["MUHAR","SAFAR","RABIA","RABIT","JUMAA","JUMAT","RAJAB","SHAAB","RAMAD","SHAWW","DHUAQ","DHUAH"][parseInt(RegExp.$3,10)-1]+RegExp.$4),o.match(/^(@#DJALALI@|JALALI)( \d?\d )(\d?\d)( \d?\d?\d?\d)$/)&&(o="@#DJALALI@"+RegExp.$2+["FARVA","ORDIB","KHORD","TIR","MORDA","SHAHR","MEHR","ABAN","AZAR","DEY","BAHMA","ESFAN"][parseInt(RegExp.$3,10)-1]+RegExp.$4),o.match(/^(@#DHEBREW@|HEBREW)( \d?\d )(\d?\d)( \d?\d?\d?\d)$/)&&(o="@#DHEBREW@"+RegExp.$2+["TSH","CSH","KSL","TVT","SHV","ADR","ADS","NSN","IYR","SVN","TMZ","AAV","ELL"][parseInt(RegExp.$3,10)-1]+RegExp.$4),o.match(/^(@#DFRENCH R@|FRENCH)( \d?\d )(\d?\d)( \d?\d?\d?\d)$/)&&(o="@#DFRENCH R@"+RegExp.$2+["VEND","BRUM","FRIM","NIVO","PLUV","VENT","GERM","FLOR","PRAI","MESS","THER","FRUC","COMP"][parseInt(RegExp.$3,10)-1]+RegExp.$4);if(/^([^\d]*)(\d+)[^\d](\d+)[^\d](\d+)$/i.exec(o)){var r=RegExp.$1,s=parseInt(RegExp.$2,10),i=parseInt(RegExp.$3,10),c=parseInt(RegExp.$4,10),d=(new Date).getFullYear(),u=d%100,p=d-u;"DMY"===t&&s<=31&&i<=12||s>13&&s<=31&&i<=12&&c>31?o=r+s+" "+n[i-1]+" "+(c>=100?c:c<=u?c+p:c+p-100):"MDY"===t&&s<=12&&i<=31||i>13&&i<=31&&s<=12&&c>31?o=r+i+" "+n[s-1]+" "+(c>=100?c:c<=u?c+p:c+p-100):("YMD"===t&&i<=12&&c<=31||c>13&&c<=31&&i<=12&&s>31)&&(o=r+c+" "+n[i-1]+" "+(s>=100?s:s<=u?s+p:s+p-100))}o=(o=(o=(o=(o=(o=(o=(o=(o=(o=(o=(o=(o=(o=(o=(o=(o=(o=(o=(o=(o=(o=(o=o.replace(/^[>]([\w ]+)$/,"AFT $1")).replace(/^[<]([\w ]+)$/,"BEF $1")).replace(/^([\w ]+)[-]$/,"FROM $1")).replace(/^[-]([\w ]+)$/,"TO $1")).replace(/^[~]([\w ]+)$/,"ABT $1")).replace(/^[*]([\w ]+)$/,"EST $1")).replace(/^[#]([\w ]+)$/,"CAL $1")).replace(/^([\w ]+) ?- ?([\w ]+)$/,"BET $1 AND $2")).replace(/^([\w ]+) ?~ ?([\w ]+)$/,"FROM $1 TO $2")).replace(/(JANUARY)/,"JAN")).replace(/(FEBRUARY)/,"FEB")).replace(/(MARCH)/,"MAR")).replace(/(APRIL)/,"APR")).replace(/(MAY)/,"MAY")).replace(/(JUNE)/,"JUN")).replace(/(JULY)/,"JUL")).replace(/(AUGUST)/,"AUG")).replace(/(SEPTEMBER)/,"SEP")).replace(/(OCTOBER)/,"OCT")).replace(/(NOVEMBER)/,"NOV")).replace(/(DECEMBER)/,"DEC")).replace(/(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)\.? (\d\d?)[, ]+(\d\d\d\d)/,"$2 $1 $3")).replace(/(^| )(\d [A-Z]{3,5} \d{4})/,"$10$2"),l&&(o=o+" ("+l),e.value!==o&&(e.value=o)}var menutimeouts=[];function show_submenu(e,t){var n=document.body.scrollWidth+document.documentElement.scrollLeft,o=document.getElementById(e);if(o&&o.style){document.all?n=document.body.offsetWidth:(n=document.body.scrollWidth+document.documentElement.scrollLeft-55,"rtl"===document.documentElement.dir&&o.offsetLeft+o.offsetWidth+10);for(var a,l=0,r=o.childNodes.length,s=0;s<r;s++){var i=o.childNodes[s];i.offsetWidth>l+5&&(l=i.offsetWidth)}if(o.offsetWidth<l&&(o.style.width=l+"px"),(a=document.getElementById(t))&&(o.style.left=a.style.left,o.offsetLeft+o.offsetWidth+10>n)){var c=n-o.offsetWidth;o.style.left=c+"px"}o.offsetLeft<0&&(o.style.left="0px"),o.offsetHeight>500&&(o.style.height="400px",o.style.overflow="auto"),o.style.visibility="visible"}clearTimeout(menutimeouts[e]),menutimeouts[e]=null}function hide_submenu(e){if("number"==typeof menutimeouts[e]){var t=document.getElementById(e);t&&t.style&&(t.style.visibility="hidden"),clearTimeout(menutimeouts[e]),menutimeouts[e]=null}}function timeout_submenu(e){"number"!=typeof menutimeouts[e]&&(menutimeouts[e]=setTimeout("hide_submenu('"+e+"')",100))}var monthLabels=[];monthLabels[1]="January",monthLabels[2]="February",monthLabels[3]="March",monthLabels[4]="April",monthLabels[5]="May",monthLabels[6]="June",monthLabels[7]="July",monthLabels[8]="August",monthLabels[9]="September",monthLabels[10]="October",monthLabels[11]="November",monthLabels[12]="December";var monthShort=[];monthShort[1]="JAN",monthShort[2]="FEB",monthShort[3]="MAR",monthShort[4]="APR",monthShort[5]="MAY",monthShort[6]="JUN",monthShort[7]="JUL",monthShort[8]="AUG",monthShort[9]="SEP",monthShort[10]="OCT",monthShort[11]="NOV",monthShort[12]="DEC";var daysOfWeek=[];daysOfWeek[0]="S",daysOfWeek[1]="M",daysOfWeek[2]="T",daysOfWeek[3]="W",daysOfWeek[4]="T",daysOfWeek[5]="F",daysOfWeek[6]="S";var weekStart=0;function cal_setMonthNames(e,t,n,o,a,l,r,s,i,c,d,u){monthLabels[1]=e,monthLabels[2]=t,monthLabels[3]=n,monthLabels[4]=o,monthLabels[5]=a,monthLabels[6]=l,monthLabels[7]=r,monthLabels[8]=s,monthLabels[9]=i,monthLabels[10]=c,monthLabels[11]=d,monthLabels[12]=u}function cal_setDayHeaders(e,t,n,o,a,l,r){daysOfWeek[0]=e,daysOfWeek[1]=t,daysOfWeek[2]=n,daysOfWeek[3]=o,daysOfWeek[4]=a,daysOfWeek[5]=l,daysOfWeek[6]=r}function cal_setWeekStart(e){e>=0&&e<7&&(weekStart=e)}function calendarWidget(e,t){var n=document.getElementById(e),o=document.getElementById(t);if("visible"===n.style.visibility)return n.style.visibility="hidden",!1;if("show"===n.style.visibility)return n.style.visibility="hide",!1;var a;return a=/((\d+ (JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC) )?\d+)/i.exec(o.value)?new Date(RegExp.$1):new Date,n.innerHTML=cal_generateSelectorContent(t,e,a),"hidden"===n.style.visibility?(n.style.visibility="visible",!1):"hide"===n.style.visibility&&(n.style.visibility="show",!1)}function cal_generateSelectorContent(e,t,n){var o,a,l='<table border="1"><tr>';for(l+='<td><select class="form-control" id="'+e+'_daySelect" onchange="return cal_updateCalendar(\''+e+"', '"+t+"');\">",o=1;o<32;o++)l+='<option value="'+o+'"',n.getDate()===o&&(l+=' selected="selected"'),l+=">"+o+"</option>";for(l+="</select></td>",l+='<td><select class="form-control" id="'+e+'_monSelect" onchange="return cal_updateCalendar(\''+e+"', '"+t+"');\">",o=1;o<13;o++)l+='<option value="'+o+'"',n.getMonth()+1===o&&(l+=' selected="selected"'),l+=">"+monthLabels[o]+"</option>";for(l+="</select></td>",l+='<td><input class="form-control" type="text" id="'+e+'_yearInput" size="5" value="'+n.getFullYear()+'" onchange="return cal_updateCalendar(\''+e+"', '"+t+"');\" /></td></tr>",l+='<tr><td colspan="3">',l+='<table width="100%">',l+="<tr>",a=weekStart,o=0;o<7;o++)l+="<td ",l+='class="descriptionbox"',l+=">",l+=daysOfWeek[a],l+="</td>",++a>6&&(a=0);l+="</tr>";var r=new Date(n.getFullYear(),n.getMonth(),1),s=r.getDay();s-=weekStart;for(r=r.getTime()-864e5*s+432e5,r=new Date(r),a=0;a<6;a++){for(l+="<tr>",o=0;o<7;o++){l+="<td ",r.getMonth()===n.getMonth()?r.getDate()===n.getDate()?l+='class="descriptionbox"':l+='class="optionbox"':l+='style="background-color:#EAEAEA; border: solid #AAAAAA 1px;"',l+='><a href="#" onclick="return cal_dateClicked(\''+e+"', '"+t+"', "+r.getFullYear()+", "+r.getMonth()+", "+r.getDate()+');">',l+=r.getDate(),l+="</a></td>";var i=r.getTime()+864e5;r=new Date(i)}l+="</tr>"}return l+="</table>",l+="</td></tr>",l+="</table>"}function cal_setDateField(e,t,n,o){var a=document.getElementById(e);return!!a&&(o<10&&(o="0"+o),a.value=o+" "+monthShort[n+1]+" "+t,!1)}function cal_updateCalendar(e,t){var n=document.getElementById(e+"_daySelect");if(!n)return!1;var o=document.getElementById(e+"_monSelect");if(!o)return!1;var a=document.getElementById(e+"_yearInput");if(!a)return!1;var l=parseInt(o.options[o.selectedIndex].value,10);l-=1;var r=new Date(a.value,l,n.options[n.selectedIndex].value);cal_setDateField(e,r.getFullYear(),r.getMonth(),r.getDate());var s=document.getElementById(t);return s?(s.innerHTML=cal_generateSelectorContent(e,t,r),!1):(alert("no dateDiv "+t),!1)}function cal_dateClicked(e,t,n,o,a){return cal_setDateField(e,n,o,a),calendarWidget(t,e),!1}function openerpasteid(e){window.opener.paste_id&&window.opener.paste_id(e),window.close()}function paste_id(e){pastefield.value=e}function pastename(e){nameElement&&(nameElement.innerHTML=e),remElement&&(remElement.style.display="block")}function paste_char(e){document.selection?(pastefield.focus(),document.selection.createRange().text=e):pastefield.selectionStart||0===pastefield.selectionStart?(pastefield.value=pastefield.value.substring(0,pastefield.selectionStart)+e+pastefield.value.substring(pastefield.selectionEnd,pastefield.value.length),pastefield.selectionStart=pastefield.selectionEnd=pastefield.selectionStart+e.length):pastefield.value+=e,"NPFX"!==pastefield.id&&"GIVN"!==pastefield.id&&"SPFX"!==pastefield.id&&"SURN"!==pastefield.id&&"NSFX"!==pastefield.id||updatewholename()}function persistent_toggle(e){var t=document.getElementById(e),n="state-of-"+e;"true"===localStorage.getItem(n)&&$(t).click(),$(t).on("change",function(){localStorage.setItem(n,t.checked)})}function valid_lati_long(e,t,n){var o=e.value.toUpperCase();(o=(o=(o=(o=(o=(o=(o=(o=(o=(o=o.replace(/(^\s*)|(\s*$)/g,"")).replace(/ /g,":")).replace(/\+/g,"")).replace(/-/g,n)).replace(/,/g,".")).replace(/\u00b0/g,":")).replace(/\u0027/g,":")).replace(/^([0-9]+):([0-9]+):([0-9.]+)(.*)/g,function(e,t,n,o,a){var l=parseFloat(t);return l+=n/60,l+=o/3600,a+(l=Math.round(1e4*l)/1e4)})).replace(/^([0-9]+):([0-9]+)(.*)/g,function(e,t,n,o){var a=parseFloat(t);return a+=n/60,o+(a=Math.round(1e4*a)/1e4)})).replace(/(.*)([N|S|E|W]+)$/g,"$2$1"))&&o.charAt(0)!==n&&o.charAt(0)!==t&&(o=t+o),e.value=o}function activate_colorbox(e){$.extend($.colorbox.settings,{fixed:!0,current:"",previous:"",next:"",slideshowStart:"",slideshowStop:"",close:""}),e&&$.extend($.colorbox.settings,e),$("body").on("click","a.gallery",function(){$("a[type^=image].gallery").colorbox({photo:!0,maxWidth:"95%",maxHeight:"95%",rel:"gallery",slideshow:!0,slideshowAuto:!1,onComplete:function(){$(".cboxPhoto").unbind("click"),wheelzoom(document.querySelectorAll(".cboxPhoto"))}})})}function autocomplete(e){$(e).each(function(){var e=this;$(this).typeahead(null,{display:"value",source:new Bloodhound({datumTokenizer:Bloodhound.tokenizers.obj.whitespace("value"),queryTokenizer:Bloodhound.tokenizers.whitespace,remote:{url:this.dataset.autocompleteUrl,replace:function(t,n){if(e.dataset.autocompleteExtra){var o=$(document.querySelector(e.dataset.autocompleteExtra)).val();return t.replace("QUERY",n)+"&extra="+encodeURIComponent(o)}return t.replace("QUERY",n)},wildcard:"QUERY"}})})})}function insertTextAtCursor(e,t){var n=e.scrollTop,o=e.selectionStart,a=e.value.substring(0,o),l=e.value.substring(e.selectionEnd,e.value.length);e.value=a+t+l,e.selectionStart=o+t.length,e.selectionEnd=e.selectionStart,e.focus(),e.scrollTop=n}$("body").on("click",".iconz",function(e){e.stopPropagation();var t=$(this).closest(".person_box_template"),n=t.find(".inout"),o=t.find(".inout2"),a=t.find(".namedef"),l=t.attr("class").match(/(box-style[0-2])/)[1];function r(){t.toggleClass(function(){return l+" "+l+"-expanded"})}n.text().length||(t.css("cursor","progress"),n.load("index.php",{route:"expand-chart-box",xref:t.data("xref"),ged:t.data("tree")},function(){t.css("cursor","")})),t.hasClass(l)?(t.parent().css("z-index",100),r(),a.addClass("nameZoom"),o.hide(0,function(){n.slideDown()})):n.slideUp(function(){o.show(0),a.removeClass("nameZoom"),r(),t.parent().css("z-index","")}),$(".iconz-zoom-icon",t).toggleClass("d-none")}),$.ajaxSetup({headers:{"X-CSRF-TOKEN":$("meta[name=csrf]").attr("content")}}),$(function(){var e;$("[data-ajax-url]").each(function(){$(this).load($(this).data("ajaxUrl"))}),autocomplete("input[data-autocomplete-url]"),$("select.select2").select2({escapeMarkup:function(e){return e}}).on("select2:unselect",function(e){$(e.delegateTarget).append('<option value="" selected="selected"></option>')}),$.fn.dataTableExt.oSort["text-asc"]=function(e,t){return e.localeCompare(t,document.documentElement.lang,{sensitivity:"base"})},$.fn.dataTableExt.oSort["text-desc"]=function(e,t){return t.localeCompare(e,document.documentElement.lang,{sensitivity:"base"})},$("table.datatables").each(function(){$(this).DataTable(),$(this).removeClass("d-none")}),$(".wt-modal-create-record").on("show.bs.modal",function(e){$("form",$(this)).data("element-id",$(e.relatedTarget).data("element-id")),$("form .form-group input:first",$(this)).focus()}),$(".wt-modal-create-record form").on("submit",function(e){e.preventDefault();var t=$(this).data("element-id");$.ajax({url:"index.php",type:"POST",data:new FormData(this),async:!1,cache:!1,contentType:!1,processData:!1,success:function(e){$("#"+t).select2().empty().append(new Option(e.text,e.id)).val(e.id).trigger("change")},failure:function(e){alert(e.error_message)}}),this.reset(),$(this).closest(".wt-modal-create-record").modal("hide")}),$(".menu-language").on("click","[data-language]",function(){return $.post("index.php",{route:"language",language:$(this).data("language")},function(){document.location.reload()}),!1}),$(".menu-theme").on("click","[data-theme]",function(){return $.post("index.php",{route:"theme",theme:$(this).data("theme")},function(){document.location.reload()}),!1}),$(".wt-osk-trigger").click(function(){(e=document.getElementById($(this).data("id"))).focus(),$(".wt-osk").show()}),$(".wt-osk-script-button").change(function(){$(".wt-osk-script").prop("hidden",!0),$(".wt-osk-script-"+$(this).data("script")).prop("hidden",!1)}),$(".wt-osk-shift-button").click(function(){document.querySelector(".wt-osk-keys").classList.toggle("shifted")}),$(".wt-osk-keys").on("click",".wt-osk-key",function(){var t=$(this).contents().get(0).nodeValue,n=$(".wt-osk-shift-button").hasClass("active"),o=$("sup",this)[0];if(n&&void 0!==o&&(t=o.innerText),null!==e){var a=e.selectionStart,l=e.value,r=l.substring(0,a),s=l.substring(a,l.length);e.value=r+t+s,!1===$(".wt-osk-pin-button").hasClass("active")&&$(".wt-osk").hide()}}),$(".wt-osk-close").on("click",function(){$(".wt-osk").hide()})}); +"use strict";function expand_layer(e){return $("#"+e+"_img").toggleClass("icon-plus icon-minus"),$("#"+e).slideToggle("fast"),$("#"+e+"-alt").toggle(),!1}function accept_changes(e,t){return $.post("index.php",{route:"accept-changes",xref:e,ged:t},function(){document.location.reload()}),!1}function reject_changes(e,t){return $.post("index.php",{route:"reject-changes",xref:e,ged:t},function(){document.location.reload()}),!1}function delete_record(e,t){return $.post("index.php",{route:"delete-record",xref:e,ged:t},function(){document.location.reload()}),!1}function delete_fact(e,t,o,n){return confirm(e)&&$.post("index.php",{route:"delete-fact",xref:o,fact_id:n,ged:t},function(){document.location.reload()}),!1}function copy_fact(e,t,o){return $.post("index.php",{route:"copy-fact",xref:t,fact_id:o,ged:e},function(){document.location.reload()}),!1}function paste_fact(e,t,o){return $.post("index.php",{route:"paste-fact",xref:t,fact_id:$(o).val(),ged:e},function(){document.location.reload()}),!1}function delete_user(e,t){return confirm(e)&&$.post("index.php",{route:"delete-user",user_id:t},function(){document.location.reload()}),!1}function masquerade(e){return $.post("index.php",{route:"masquerade",user_id:e},function(){document.location.reload()}),!1}var pastefield;function addmedia_links(e,t,o){return pastefield=e,insertRowToTable(t,o),!1}function valid_date(e,t){var o=["JAN","FEB","MAR","APR","MAY","JUN","JUL","AUG","SEP","OCT","NOV","DEC"],n=e.value,a=n.split("("),l="";a.length>1&&(n=a[0],l=a[1]),(n=(n=(n=(n=(n=n.toUpperCase()).replace(/\s+/," ")).replace(/(^\s)|(\s$)/,"")).replace(/(\d)([A-Z])/,"$1 $2")).replace(/([A-Z])(\d)/,"$1 $2")).match(/^Q ([1-4]) (\d\d\d\d)$/)&&(n="BET "+o[3*RegExp.$1-3]+" "+RegExp.$2+" AND "+o[3*RegExp.$1-1]+" "+RegExp.$2),n.match(/^(@#DHIJRI@|HIJRI)( \d?\d )(\d?\d)( \d?\d?\d?\d)$/)&&(n="@#DHIJRI@"+RegExp.$2+["MUHAR","SAFAR","RABIA","RABIT","JUMAA","JUMAT","RAJAB","SHAAB","RAMAD","SHAWW","DHUAQ","DHUAH"][parseInt(RegExp.$3,10)-1]+RegExp.$4),n.match(/^(@#DJALALI@|JALALI)( \d?\d )(\d?\d)( \d?\d?\d?\d)$/)&&(n="@#DJALALI@"+RegExp.$2+["FARVA","ORDIB","KHORD","TIR","MORDA","SHAHR","MEHR","ABAN","AZAR","DEY","BAHMA","ESFAN"][parseInt(RegExp.$3,10)-1]+RegExp.$4),n.match(/^(@#DHEBREW@|HEBREW)( \d?\d )(\d?\d)( \d?\d?\d?\d)$/)&&(n="@#DHEBREW@"+RegExp.$2+["TSH","CSH","KSL","TVT","SHV","ADR","ADS","NSN","IYR","SVN","TMZ","AAV","ELL"][parseInt(RegExp.$3,10)-1]+RegExp.$4),n.match(/^(@#DFRENCH R@|FRENCH)( \d?\d )(\d?\d)( \d?\d?\d?\d)$/)&&(n="@#DFRENCH R@"+RegExp.$2+["VEND","BRUM","FRIM","NIVO","PLUV","VENT","GERM","FLOR","PRAI","MESS","THER","FRUC","COMP"][parseInt(RegExp.$3,10)-1]+RegExp.$4);if(/^([^\d]*)(\d+)[^\d](\d+)[^\d](\d+)$/i.exec(n)){var r=RegExp.$1,i=parseInt(RegExp.$2,10),s=parseInt(RegExp.$3,10),d=parseInt(RegExp.$4,10),c=(new Date).getFullYear(),u=c%100,p=c-u;"DMY"===t&&i<=31&&s<=12||i>13&&i<=31&&s<=12&&d>31?n=r+i+" "+o[s-1]+" "+(d>=100?d:d<=u?d+p:d+p-100):"MDY"===t&&i<=12&&s<=31||s>13&&s<=31&&i<=12&&d>31?n=r+s+" "+o[i-1]+" "+(d>=100?d:d<=u?d+p:d+p-100):("YMD"===t&&s<=12&&d<=31||d>13&&d<=31&&s<=12&&i>31)&&(n=r+d+" "+o[s-1]+" "+(i>=100?i:i<=u?i+p:i+p-100))}n=(n=(n=(n=(n=(n=(n=(n=(n=(n=(n=(n=(n=(n=(n=(n=(n=(n=(n=(n=(n=(n=(n=n.replace(/^[>]([\w ]+)$/,"AFT $1")).replace(/^[<]([\w ]+)$/,"BEF $1")).replace(/^([\w ]+)[-]$/,"FROM $1")).replace(/^[-]([\w ]+)$/,"TO $1")).replace(/^[~]([\w ]+)$/,"ABT $1")).replace(/^[*]([\w ]+)$/,"EST $1")).replace(/^[#]([\w ]+)$/,"CAL $1")).replace(/^([\w ]+) ?- ?([\w ]+)$/,"BET $1 AND $2")).replace(/^([\w ]+) ?~ ?([\w ]+)$/,"FROM $1 TO $2")).replace(/(JANUARY)/,"JAN")).replace(/(FEBRUARY)/,"FEB")).replace(/(MARCH)/,"MAR")).replace(/(APRIL)/,"APR")).replace(/(MAY)/,"MAY")).replace(/(JUNE)/,"JUN")).replace(/(JULY)/,"JUL")).replace(/(AUGUST)/,"AUG")).replace(/(SEPTEMBER)/,"SEP")).replace(/(OCTOBER)/,"OCT")).replace(/(NOVEMBER)/,"NOV")).replace(/(DECEMBER)/,"DEC")).replace(/(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)\.? (\d\d?)[, ]+(\d\d\d\d)/,"$2 $1 $3")).replace(/(^| )(\d [A-Z]{3,5} \d{4})/,"$10$2"),l&&(n=n+" ("+l),e.value!==n&&(e.value=n)}var menutimeouts=[];function show_submenu(e,t){var o=document.body.scrollWidth+document.documentElement.scrollLeft,n=document.getElementById(e);if(n&&n.style){document.all?o=document.body.offsetWidth:(o=document.body.scrollWidth+document.documentElement.scrollLeft-55,"rtl"===document.documentElement.dir&&n.offsetLeft+n.offsetWidth+10);for(var a,l=0,r=n.childNodes.length,i=0;i<r;i++){var s=n.childNodes[i];s.offsetWidth>l+5&&(l=s.offsetWidth)}if(n.offsetWidth<l&&(n.style.width=l+"px"),(a=document.getElementById(t))&&(n.style.left=a.style.left,n.offsetLeft+n.offsetWidth+10>o)){var d=o-n.offsetWidth;n.style.left=d+"px"}n.offsetLeft<0&&(n.style.left="0px"),n.offsetHeight>500&&(n.style.height="400px",n.style.overflow="auto"),n.style.visibility="visible"}clearTimeout(menutimeouts[e]),menutimeouts[e]=null}function hide_submenu(e){if("number"==typeof menutimeouts[e]){var t=document.getElementById(e);t&&t.style&&(t.style.visibility="hidden"),clearTimeout(menutimeouts[e]),menutimeouts[e]=null}}function timeout_submenu(e){"number"!=typeof menutimeouts[e]&&(menutimeouts[e]=setTimeout("hide_submenu('"+e+"')",100))}var monthLabels=[];monthLabels[1]="January",monthLabels[2]="February",monthLabels[3]="March",monthLabels[4]="April",monthLabels[5]="May",monthLabels[6]="June",monthLabels[7]="July",monthLabels[8]="August",monthLabels[9]="September",monthLabels[10]="October",monthLabels[11]="November",monthLabels[12]="December";var monthShort=[];monthShort[1]="JAN",monthShort[2]="FEB",monthShort[3]="MAR",monthShort[4]="APR",monthShort[5]="MAY",monthShort[6]="JUN",monthShort[7]="JUL",monthShort[8]="AUG",monthShort[9]="SEP",monthShort[10]="OCT",monthShort[11]="NOV",monthShort[12]="DEC";var daysOfWeek=[];daysOfWeek[0]="S",daysOfWeek[1]="M",daysOfWeek[2]="T",daysOfWeek[3]="W",daysOfWeek[4]="T",daysOfWeek[5]="F",daysOfWeek[6]="S";var weekStart=0;function cal_setMonthNames(e,t,o,n,a,l,r,i,s,d,c,u){monthLabels[1]=e,monthLabels[2]=t,monthLabels[3]=o,monthLabels[4]=n,monthLabels[5]=a,monthLabels[6]=l,monthLabels[7]=r,monthLabels[8]=i,monthLabels[9]=s,monthLabels[10]=d,monthLabels[11]=c,monthLabels[12]=u}function cal_setDayHeaders(e,t,o,n,a,l,r){daysOfWeek[0]=e,daysOfWeek[1]=t,daysOfWeek[2]=o,daysOfWeek[3]=n,daysOfWeek[4]=a,daysOfWeek[5]=l,daysOfWeek[6]=r}function cal_setWeekStart(e){e>=0&&e<7&&(weekStart=e)}function calendarWidget(e,t){var o=document.getElementById(e),n=document.getElementById(t);if("visible"===o.style.visibility)return o.style.visibility="hidden",!1;if("show"===o.style.visibility)return o.style.visibility="hide",!1;var a;return a=/((\d+ (JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC) )?\d+)/i.exec(n.value)?new Date(RegExp.$1):new Date,o.innerHTML=cal_generateSelectorContent(t,e,a),"hidden"===o.style.visibility?(o.style.visibility="visible",!1):"hide"===o.style.visibility&&(o.style.visibility="show",!1)}function cal_generateSelectorContent(e,t,o){var n,a,l='<table border="1"><tr>';for(l+='<td><select class="form-control" id="'+e+'_daySelect" onchange="return cal_updateCalendar(\''+e+"', '"+t+"');\">",n=1;n<32;n++)l+='<option value="'+n+'"',o.getDate()===n&&(l+=' selected="selected"'),l+=">"+n+"</option>";for(l+="</select></td>",l+='<td><select class="form-control" id="'+e+'_monSelect" onchange="return cal_updateCalendar(\''+e+"', '"+t+"');\">",n=1;n<13;n++)l+='<option value="'+n+'"',o.getMonth()+1===n&&(l+=' selected="selected"'),l+=">"+monthLabels[n]+"</option>";for(l+="</select></td>",l+='<td><input class="form-control" type="text" id="'+e+'_yearInput" size="5" value="'+o.getFullYear()+'" onchange="return cal_updateCalendar(\''+e+"', '"+t+"');\" /></td></tr>",l+='<tr><td colspan="3">',l+='<table width="100%">',l+="<tr>",a=weekStart,n=0;n<7;n++)l+="<td ",l+='class="descriptionbox"',l+=">",l+=daysOfWeek[a],l+="</td>",++a>6&&(a=0);l+="</tr>";var r=new Date(o.getFullYear(),o.getMonth(),1),i=r.getDay();i-=weekStart;for(r=r.getTime()-864e5*i+432e5,r=new Date(r),a=0;a<6;a++){for(l+="<tr>",n=0;n<7;n++){l+="<td ",r.getMonth()===o.getMonth()?r.getDate()===o.getDate()?l+='class="descriptionbox"':l+='class="optionbox"':l+='style="background-color:#EAEAEA; border: solid #AAAAAA 1px;"',l+='><a href="#" onclick="return cal_dateClicked(\''+e+"', '"+t+"', "+r.getFullYear()+", "+r.getMonth()+", "+r.getDate()+');">',l+=r.getDate(),l+="</a></td>";var s=r.getTime()+864e5;r=new Date(s)}l+="</tr>"}return l+="</table>",l+="</td></tr>",l+="</table>"}function cal_setDateField(e,t,o,n){var a=document.getElementById(e);return!!a&&(n<10&&(n="0"+n),a.value=n+" "+monthShort[o+1]+" "+t,!1)}function cal_updateCalendar(e,t){var o=document.getElementById(e+"_daySelect");if(!o)return!1;var n=document.getElementById(e+"_monSelect");if(!n)return!1;var a=document.getElementById(e+"_yearInput");if(!a)return!1;var l=parseInt(n.options[n.selectedIndex].value,10);l-=1;var r=new Date(a.value,l,o.options[o.selectedIndex].value);cal_setDateField(e,r.getFullYear(),r.getMonth(),r.getDate());var i=document.getElementById(t);return i?(i.innerHTML=cal_generateSelectorContent(e,t,r),!1):(alert("no dateDiv "+t),!1)}function cal_dateClicked(e,t,o,n,a){return cal_setDateField(e,o,n,a),calendarWidget(t,e),!1}function openerpasteid(e){window.opener.paste_id&&window.opener.paste_id(e),window.close()}function paste_id(e){pastefield.value=e}function pastename(e){nameElement&&(nameElement.innerHTML=e),remElement&&(remElement.style.display="block")}function paste_char(e){document.selection?(pastefield.focus(),document.selection.createRange().text=e):pastefield.selectionStart||0===pastefield.selectionStart?(pastefield.value=pastefield.value.substring(0,pastefield.selectionStart)+e+pastefield.value.substring(pastefield.selectionEnd,pastefield.value.length),pastefield.selectionStart=pastefield.selectionEnd=pastefield.selectionStart+e.length):pastefield.value+=e,"NPFX"!==pastefield.id&&"GIVN"!==pastefield.id&&"SPFX"!==pastefield.id&&"SURN"!==pastefield.id&&"NSFX"!==pastefield.id||updatewholename()}function persistent_toggle(e){var t=document.getElementById(e),o="state-of-"+e;"true"===localStorage.getItem(o)&&$(t).click(),$(t).on("change",function(){localStorage.setItem(o,t.checked)})}function valid_lati_long(e,t,o){var n=e.value.toUpperCase();(n=(n=(n=(n=(n=(n=(n=(n=(n=(n=n.replace(/(^\s*)|(\s*$)/g,"")).replace(/ /g,":")).replace(/\+/g,"")).replace(/-/g,o)).replace(/,/g,".")).replace(/\u00b0/g,":")).replace(/\u0027/g,":")).replace(/^([0-9]+):([0-9]+):([0-9.]+)(.*)/g,function(e,t,o,n,a){var l=parseFloat(t);return l+=o/60,l+=n/3600,a+(l=Math.round(1e4*l)/1e4)})).replace(/^([0-9]+):([0-9]+)(.*)/g,function(e,t,o,n){var a=parseFloat(t);return a+=o/60,n+(a=Math.round(1e4*a)/1e4)})).replace(/(.*)([N|S|E|W]+)$/g,"$2$1"))&&n.charAt(0)!==o&&n.charAt(0)!==t&&(n=t+n),e.value=n}function activate_colorbox(e){$.extend($.colorbox.settings,{fixed:!0,current:"",previous:"",next:"",slideshowStart:"",slideshowStop:"",close:""}),e&&$.extend($.colorbox.settings,e),$("body").on("click","a.gallery",function(){$("a[type^=image].gallery").colorbox({photo:!0,maxWidth:"95%",maxHeight:"95%",rel:"gallery",slideshow:!0,slideshowAuto:!1,onComplete:function(){$(".cboxPhoto").unbind("click"),wheelzoom(document.querySelectorAll(".cboxPhoto"))}})})}function autocomplete(e){$(e).each(function(){var e=this;$(this).typeahead(null,{display:"value",source:new Bloodhound({datumTokenizer:Bloodhound.tokenizers.obj.whitespace("value"),queryTokenizer:Bloodhound.tokenizers.whitespace,remote:{url:this.dataset.autocompleteUrl,replace:function(t,o){if(e.dataset.autocompleteExtra){var n=$(document.querySelector(e.dataset.autocompleteExtra)).val();return t.replace("QUERY",o)+"&extra="+encodeURIComponent(n)}return t.replace("QUERY",o)},wildcard:"QUERY"}})})})}function insertTextAtCursor(e,t){var o=e.scrollTop,n=e.selectionStart,a=e.value.substring(0,n),l=e.value.substring(e.selectionEnd,e.value.length);e.value=a+t+l,e.selectionStart=n+t.length,e.selectionEnd=e.selectionStart,e.focus(),e.scrollTop=o}function drawPieChart(e,t,o,n,a){t=google.visualization.arrayToDataTable(t);var l={title:n,height:"100%",width:"100%",pieStartAngle:0,pieSliceText:"none",pieSliceTextStyle:{color:"#777"},pieHole:.4,legend:{alignment:"center",labeledValueText:a||"value",position:"labeled"},chartArea:{left:0,top:"5%",height:"90%",width:"100%"},tooltip:{trigger:"none",text:"both"},backgroundColor:"transparent",colors:o};new google.visualization.PieChart(document.getElementById(e)).draw(t,l)}function drawColumnChart(e,t,o){o=Object.assign({title:"",subtitle:"",height:"100%",width:"100%",vAxis:{title:""},hAxis:{title:""},legend:{position:"none"},backgroundColor:"transparent"},o);var n=new google.visualization.ColumnChart(document.getElementById(e));t=google.visualization.arrayToDataTable(t);n.draw(t,o)}function drawComboChart(e,t,o){o=Object.assign({title:"",subtitle:"",titleTextStyle:{color:"#757575",fontName:"Roboto",fontSize:"16px",bold:!1,italic:!1},height:"100%",width:"100%",vAxis:{title:""},hAxis:{title:""},legend:{position:"none"},seriesType:"bars",series:{2:{type:"line"}},colors:[],backgroundColor:"transparent"},o);var n=new google.visualization.ComboChart(document.getElementById(e));t=google.visualization.arrayToDataTable(t);n.draw(t,o)}$("body").on("click",".iconz",function(e){e.stopPropagation();var t=$(this).closest(".person_box_template"),o=t.find(".inout"),n=t.find(".inout2"),a=t.find(".namedef"),l=t.attr("class").match(/(box-style[0-2])/)[1];function r(){t.toggleClass(function(){return l+" "+l+"-expanded"})}o.text().length||(t.css("cursor","progress"),o.load("index.php",{route:"expand-chart-box",xref:t.data("xref"),ged:t.data("tree")},function(){t.css("cursor","")})),t.hasClass(l)?(t.parent().css("z-index",100),r(),a.addClass("nameZoom"),n.hide(0,function(){o.slideDown()})):o.slideUp(function(){n.show(0),a.removeClass("nameZoom"),r(),t.parent().css("z-index","")}),$(".iconz-zoom-icon",t).toggleClass("d-none")}),$.ajaxSetup({headers:{"X-CSRF-TOKEN":$("meta[name=csrf]").attr("content")}}),$(function(){var e;$("[data-ajax-url]").each(function(){$(this).load($(this).data("ajaxUrl"))}),autocomplete("input[data-autocomplete-url]"),$("select.select2").select2({escapeMarkup:function(e){return e}}).on("select2:unselect",function(e){$(e.delegateTarget).append('<option value="" selected="selected"></option>')}),$.fn.dataTableExt.oSort["text-asc"]=function(e,t){return e.localeCompare(t,document.documentElement.lang,{sensitivity:"base"})},$.fn.dataTableExt.oSort["text-desc"]=function(e,t){return t.localeCompare(e,document.documentElement.lang,{sensitivity:"base"})},$("table.datatables").each(function(){$(this).DataTable(),$(this).removeClass("d-none")}),$(".wt-modal-create-record").on("show.bs.modal",function(e){$("form",$(this)).data("element-id",$(e.relatedTarget).data("element-id")),$("form .form-group input:first",$(this)).focus()}),$(".wt-modal-create-record form").on("submit",function(e){e.preventDefault();var t=$(this).data("element-id");$.ajax({url:"index.php",type:"POST",data:new FormData(this),async:!1,cache:!1,contentType:!1,processData:!1,success:function(e){$("#"+t).select2().empty().append(new Option(e.text,e.id)).val(e.id).trigger("change")},failure:function(e){alert(e.error_message)}}),this.reset(),$(this).closest(".wt-modal-create-record").modal("hide")}),$(".menu-language").on("click","[data-language]",function(){return $.post("index.php",{route:"language",language:$(this).data("language")},function(){document.location.reload()}),!1}),$(".menu-theme").on("click","[data-theme]",function(){return $.post("index.php",{route:"theme",theme:$(this).data("theme")},function(){document.location.reload()}),!1}),$(".wt-osk-trigger").click(function(){(e=document.getElementById($(this).data("id"))).focus(),$(".wt-osk").show()}),$(".wt-osk-script-button").change(function(){$(".wt-osk-script").prop("hidden",!0),$(".wt-osk-script-"+$(this).data("script")).prop("hidden",!1)}),$(".wt-osk-shift-button").click(function(){document.querySelector(".wt-osk-keys").classList.toggle("shifted")}),$(".wt-osk-keys").on("click",".wt-osk-key",function(){var t=$(this).contents().get(0).nodeValue,o=$(".wt-osk-shift-button").hasClass("active"),n=$("sup",this)[0];if(o&&void 0!==n&&(t=n.innerText),null!==e){var a=e.selectionStart,l=e.value,r=l.substring(0,a),i=l.substring(a,l.length);e.value=r+t+i,!1===$(".wt-osk-pin-button").hasClass("active")&&$(".wt-osk").hide()}}),$(".wt-osk-close").on("click",function(){$(".wt-osk").hide()})}); diff --git a/resources/js/webtrees.js b/resources/js/webtrees.js index dc552b73ba..53b2a61c79 100644 --- a/resources/js/webtrees.js +++ b/resources/js/webtrees.js @@ -849,6 +849,138 @@ function insertTextAtCursor(e, t) e.scrollTop = scrollTop; } + +/** + * Draws a google pie chart. + * + * @param {String} elementId The element id of the HTML element the chart is rendered too + * @param {Array} data The chart data array + * @param {Array} colors The chart color array + * @param {String} title The chart title + * @param {String} labeledValueText The type of how to display the slice text + */ +function drawPieChart(elementId, data, colors, title, labeledValueText) +{ + var data = google.visualization.arrayToDataTable(data); + var options = { + title: title, + height: '100%', + width: '100%', + pieStartAngle: 0, + pieSliceText: 'none', + pieSliceTextStyle: { + color: '#777' + }, + pieHole: 0.4, // Donut + //is3D: true, // 3D (not together with pieHole) + legend: { + alignment: 'center', + // Flickers on mouseover :( + labeledValueText: labeledValueText || 'value', + position: 'labeled' + }, + chartArea: { + left: 0, + top: '5%', + height: '90%', + width: '100%' + }, + tooltip: { + trigger: 'none', + text: 'both' + }, + backgroundColor: 'transparent', + colors: colors + }; + + var chart = new google.visualization.PieChart(document.getElementById(elementId)); + + chart.draw(data, options); +} + +/** + * Draws a google column chart. + * + * @param {String} elementId The element id of the HTML element the chart is rendered too + * @param {Array} data The chart data array + * @param {Object} options The chart specific options to overwrite the default ones + */ +function drawColumnChart(elementId, data, options) +{ + var defaults = { + title: '', + subtitle: '', + height: '100%', + width: '100%', + vAxis: { + title: '' + }, + hAxis: { + title: '' + }, + legend: { + position: 'none' + }, + backgroundColor: 'transparent' + }; + + options = Object.assign(defaults, options); + + var chart = new google.visualization.ColumnChart(document.getElementById(elementId)); + var data = google.visualization.arrayToDataTable(data); + + chart.draw(data, options); +} + +/** + * Draws a google combo chart. + * + * @param {String} elementId The element id of the HTML element the chart is rendered too + * @param {Array} data The chart data array + * @param {Object} options The chart specific options to overwrite the default ones + */ +function drawComboChart(elementId, data, options) +{ + var defaults = { + title: '', + subtitle: '', + titleTextStyle: { + color: '#757575', + fontName: 'Roboto', + fontSize: '16px', + bold: false, + italic: false + }, + height: '100%', + width: '100%', + vAxis: { + title: '' + }, + hAxis: { + title: '' + }, + legend: { + position: 'none' + }, + seriesType: 'bars', + series: { + 2: { + type: 'line' + } + }, + colors: [], + backgroundColor: 'transparent' + }; + + options = Object.assign(defaults, options); + + var chart = new google.visualization.ComboChart(document.getElementById(elementId)); + var data = google.visualization.arrayToDataTable(data); + + chart.draw(data, options); +} + + // Send the CSRF token on all AJAX requests $.ajaxSetup({ headers: { diff --git a/resources/views/modules/statistics-chart/custom.phtml b/resources/views/modules/statistics-chart/custom.phtml index 7fb3fe9369..df8bc61762 100644 --- a/resources/views/modules/statistics-chart/custom.phtml +++ b/resources/views/modules/statistics-chart/custom.phtml @@ -246,22 +246,22 @@ <option value="world" selected> <?= I18N::translate('World') ?> </option> - <option value="europe"> + <option value="150"> <?= I18N::translate('Europe') ?> </option> - <option value="usa"> - <?= I18N::translate('United States') ?> + <option value="021"> + <?= I18N::translate('Northern America') ?> </option> - <option value="south_america"> + <option value="005"> <?= I18N::translate('South America') ?> </option> - <option value="asia"> + <option value="142"> <?= I18N::translate('Asia') ?> </option> - <option value="middle_east"> + <option value="145"> <?= I18N::translate('Middle East') ?> </option> - <option value="africa"> + <option value="002"> <?= I18N::translate('Africa') ?> </option> </select> diff --git a/resources/views/modules/statistics-chart/individuals.phtml b/resources/views/modules/statistics-chart/individuals.phtml index 71b550881c..0585809dfb 100644 --- a/resources/views/modules/statistics-chart/individuals.phtml +++ b/resources/views/modules/statistics-chart/individuals.phtml @@ -1,5 +1,3 @@ -<?php use Fisharebest\Webtrees\I18N; ?> - <?php /** @var \Fisharebest\Webtrees\Statistics $stats */ ?> diff --git a/resources/views/modules/statistics-chart/other.phtml b/resources/views/modules/statistics-chart/other.phtml index d20f54ed63..c681580a1b 100644 --- a/resources/views/modules/statistics-chart/other.phtml +++ b/resources/views/modules/statistics-chart/other.phtml @@ -1,4 +1,6 @@ <?php +declare(strict_types=1); + /** @var \Fisharebest\Webtrees\Statistics $stats */ ?> diff --git a/resources/views/modules/statistics-chart/page.phtml b/resources/views/modules/statistics-chart/page.phtml index 7f8775b966..b27d3f31bb 100644 --- a/resources/views/modules/statistics-chart/page.phtml +++ b/resources/views/modules/statistics-chart/page.phtml @@ -1,4 +1,10 @@ -<?php use Fisharebest\Webtrees\View; ?> +<?php +declare(strict_types=1); + +use Fisharebest\Webtrees\View; +?> + +<?= view('statistics/other/chart-setup') ?> <h2 class="wt-page-title"> <?= $title ?> @@ -33,7 +39,7 @@ // If the URL contains a fragment, then activate the corresponding tab. // Use a prefix on the fragment, to prevent scrolling to the element. - var target = document.location.hash.replace("tab-", ""); + var target = window.location.hash.replace("tab-", ""); var tab = $("#statistics-tabs .nav-link[href='" + target + "']"); // If not, then activate the first tab. if (tab.length === 0) { @@ -43,7 +49,7 @@ // If the user selects a tab, update the URL to reflect this $('#statistics-tabs a[data-toggle="tab"]').on('shown.bs.tab', function (e) { - document.location.hash = "tab-" + e.target.href.substring(e.target.href.indexOf('#') + 1); + window.location.hash = "tab-" + e.target.href.substring(e.target.href.indexOf('#') + 1); }); </script> <?php View::endpush() ?> diff --git a/resources/views/statistics/families/total-records.phtml b/resources/views/statistics/families/total-records.phtml index 522d6ae88f..69d23009d7 100644 --- a/resources/views/statistics/families/total-records.phtml +++ b/resources/views/statistics/families/total-records.phtml @@ -26,7 +26,7 @@ use Fisharebest\Webtrees\I18N; <div class="mb-3 col-12"> <div class="card m-0"> - <h5 class="card-header border-bottom-0"> + <h5 class="card-header"> <?= I18N::translate('Marriages by century') ?> </h5> <div class="card-body"> @@ -39,7 +39,7 @@ use Fisharebest\Webtrees\I18N; <div class="card-deck"> <div class="mb-3 col-lg-12 col-md-6"> <div class="card m-0"> - <h5 class="card-header border-bottom-0"> + <h5 class="card-header"> <?= I18N::translate('Earliest marriage') ?> </h5> <div class="card-body"> @@ -50,7 +50,7 @@ use Fisharebest\Webtrees\I18N; <div class="mb-3 col-lg-12 col-md-6"> <div class="card m-0"> - <h5 class="card-header border-bottom-0"> + <h5 class="card-header"> <?= I18N::translate('Latest marriage') ?> </h5> <div class="card-body"> diff --git a/resources/views/statistics/other/chart-distribution.phtml b/resources/views/statistics/other/chart-distribution.phtml deleted file mode 100644 index fe75a0e30f..0000000000 --- a/resources/views/statistics/other/chart-distribution.phtml +++ /dev/null @@ -1,31 +0,0 @@ -<?php -declare(strict_types=1); - -use Fisharebest\Webtrees\I18N; -?> - -<h4 class="border-bottom p-2 mb-4"> - <?= $chart_title ?> -</h4> - -<div class="mb-3"> - <div class="card-deck"> - <div class="col-12 mb-3"> - <div class="card m-0"> - <div class="card-body"> - <div id="google_charts" class="text-center"> - <img src="<?= $chart_url ?>" alt="<?= $chart_title ?>" title="<?= $chart_title ?>" class="gchart" /> - <br> - <table class="center"> - <tr> - <td bgcolor="#<?= $chart_color2 ?>" width="12"></td><td><?= I18N::translate('Highest population') ?></td> - <td bgcolor="#<?= $chart_color3 ?>" width="12"></td><td><?= I18N::translate('Lowest population') ?></td> - <td bgcolor="#<?= $chart_color1 ?>" width="12"></td><td><?= I18N::translate('Nobody at all') ?></td> - </tr> - </table> - </div> - </div> - </div> - </div> - </div> -</div> diff --git a/resources/views/statistics/other/chart-google.phtml b/resources/views/statistics/other/chart-google.phtml deleted file mode 100644 index 5172f56d28..0000000000 --- a/resources/views/statistics/other/chart-google.phtml +++ /dev/null @@ -1 +0,0 @@ -<img src="<?= $chart_url ?>" width="<?= $sizes[0] ?>" height="<?= $sizes[1] ?>" alt="<?= $chart_title ?>" title="<?= $chart_title ?>" /> diff --git a/resources/views/statistics/other/chart-setup.phtml b/resources/views/statistics/other/chart-setup.phtml new file mode 100644 index 0000000000..694fc2da9e --- /dev/null +++ b/resources/views/statistics/other/chart-setup.phtml @@ -0,0 +1,26 @@ +<?php +declare(strict_types=1); + +use Fisharebest\Webtrees\View; +?> + +<?php View::push('javascript') ?> +<script src="https://www.gstatic.com/charts/loader.js"></script> +<script> + +google.charts.load( + 'current', + { + 'packages': [ + 'corechart', + 'geochart', + 'bar' + ], + // Note: you will need to get a mapsApiKey for your project. + // See: https://developers.google.com/chart/interactive/docs/basic_load_libs#load-settings + 'mapsApiKey': '' + } +); + +</script> +<?php View::endpush() ?> diff --git a/resources/views/statistics/other/charts/column.phtml b/resources/views/statistics/other/charts/column.phtml new file mode 100644 index 0000000000..b0ffd0d454 --- /dev/null +++ b/resources/views/statistics/other/charts/column.phtml @@ -0,0 +1,52 @@ +<?php +declare(strict_types=1); + +$id = 'google-chart-' . bin2hex(random_bytes(8)); +?> + +<?= view('statistics/other/chart-setup') ?> + +<div id="<?= $id ?>" title="<?= $chart_title ?>"></div> + +<script> + +var callbackColumnChart = function () { + var options = { + title: '<?= $chart_title ?? '' ?>', + subtitle: '<?= $chart_sub_title ?? '' ?>', + vAxis: { + title: '<?= $vAxis_title ?? '' ?>' + }, + hAxis: { + title: '<?= $hAxis_title ?? '' ?>' + }, + colors: <?= json_encode($colors) ?> + }; + + google.charts.setOnLoadCallback(function () { + drawColumnChart( + '<?= $id ?>', + <?= json_encode($data) ?>, + options + ); + }); + + $(window).resize(function () { + drawColumnChart( + '<?= $id ?>', + <?= json_encode($data) ?>, + options + ); + }); +}; + +if ( + document.readyState === "complete" || + (document.readyState !== "loading" && !document.documentElement.doScroll) +) { + callbackColumnChart(); +} else { + document.addEventListener("DOMContentLoaded", callbackColumnChart); +} + +</script> diff --git a/resources/views/statistics/other/charts/combo.phtml b/resources/views/statistics/other/charts/combo.phtml new file mode 100644 index 0000000000..dc0e8cbab2 --- /dev/null +++ b/resources/views/statistics/other/charts/combo.phtml @@ -0,0 +1,52 @@ +<?php +declare(strict_types=1); + +$id = 'google-chart-' . bin2hex(random_bytes(8)); +?> + +<?= view('statistics/other/chart-setup') ?> + +<div id="<?= $id ?>" title="<?= $chart_title ?>"></div> + +<script> + +var callbackComboChart = function () { + var options = { + title: '<?= $chart_title ?? '' ?>', + subtitle: '<?= $chart_sub_title ?? '' ?>', + vAxis: { + title: '<?= $vAxis_title ?? '' ?>' + }, + hAxis: { + title: '<?= $hAxis_title ?? '' ?>' + }, + colors: <?= json_encode($colors) ?> + }; + + google.charts.setOnLoadCallback(function () { + drawComboChart( + '<?= $id ?>', + <?= json_encode($data) ?>, + options + ); + }); + + $(window).resize(function () { + drawComboChart( + '<?= $id ?>', + <?= json_encode($data) ?>, + options + ); + }); +}; + +if ( + document.readyState === "complete" || + (document.readyState !== "loading" && !document.documentElement.doScroll) +) { + callbackComboChart(); +} else { + document.addEventListener("DOMContentLoaded", callbackComboChart); +} + +</script> diff --git a/resources/views/statistics/other/charts/geo.phtml b/resources/views/statistics/other/charts/geo.phtml new file mode 100644 index 0000000000..c12a979f67 --- /dev/null +++ b/resources/views/statistics/other/charts/geo.phtml @@ -0,0 +1,67 @@ +<?php +declare(strict_types=1); + +$id = 'google-chart-' . bin2hex(random_bytes(8)); +?> + +<?= view('statistics/other/chart-setup') ?> + +<h4 class="border-bottom p-2 mb-4"> + <?= $chart_title ?> +</h4> + +<div class="mb-3"> + <div class="card-deck"> + <div class="col-12 mb-3"> + <div class="card m-0"> + <div class="card-body"> + <div id="google_charts" class="text-center"> + <div id="<?= $id ?>"></div> + </div> + </div> + </div> + </div> + </div> +</div> + +<script> + +var callbackGeoChart = function () { + google.charts.setOnLoadCallback(drawRegionsMap); + + function drawRegionsMap () { + var data = google.visualization.arrayToDataTable( + <?= json_encode($data) ?> + ); + + var options = { + title: '<?= $chart_title ?>', + region: '<?= $region ?>', + height: '100%', + width: '100%', + colorAxis: { + colors: [ + '#<?= $chart_color3 ?>', + '#<?= $chart_color2 ?>' + ] + } + }; + + var chart = new google.visualization.GeoChart(document.getElementById('<?= $id ?>')); + + chart.draw(data, options); + } + + $(window).resize(drawRegionsMap); +}; + +if ( + document.readyState === "complete" || + (document.readyState !== "loading" && !document.documentElement.doScroll) +) { + callbackGeoChart(); +} else { + document.addEventListener("DOMContentLoaded", callbackGeoChart); +} + +</script> diff --git a/resources/views/statistics/other/charts/pie.phtml b/resources/views/statistics/other/charts/pie.phtml new file mode 100644 index 0000000000..d463654899 --- /dev/null +++ b/resources/views/statistics/other/charts/pie.phtml @@ -0,0 +1,44 @@ +<?php +declare(strict_types=1); + +$id = 'google-chart-' . bin2hex(random_bytes(8)); +?> + +<?= view('statistics/other/chart-setup') ?> + +<div id="<?= $id ?>"></div> + +<script> + +var callbackPieChart = function () { + google.charts.setOnLoadCallback(function () { + drawPieChart( + '<?= $id ?>', + <?= json_encode($data) ?>, + <?= json_encode($colors) ?>, + '<?= $title ?>', + '<?= $labeledValueText ?? 'value' ?>' + ); + }); + + $(window).resize(function () { + drawPieChart( + '<?= $id ?>', + <?= json_encode($data) ?>, + <?= json_encode($colors) ?>, + '<?= $title ?>', + '<?= $labeledValueText ?? 'value' ?>' + ); + }); +}; + +if ( + document.readyState === "complete" || + (document.readyState !== "loading" && !document.documentElement.doScroll) +) { + callbackPieChart(); +} else { + document.addEventListener("DOMContentLoaded", callbackPieChart); +} + +</script> |
