summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/Statistics.php53
-rw-r--r--app/Statistics/AbstractGoogle.php101
-rw-r--r--app/Statistics/Google/ChartAge.php117
-rw-r--r--app/Statistics/Google/ChartBirth.php64
-rw-r--r--app/Statistics/Google/ChartChildren.php81
-rw-r--r--app/Statistics/Google/ChartCommonGiven.php57
-rw-r--r--app/Statistics/Google/ChartCommonSurname.php143
-rw-r--r--app/Statistics/Google/ChartDeath.php65
-rw-r--r--app/Statistics/Google/ChartDistribution.php376
-rw-r--r--app/Statistics/Google/ChartDivorce.php64
-rw-r--r--app/Statistics/Google/ChartFamilyLargest.php81
-rw-r--r--app/Statistics/Google/ChartFamilyWithSources.php49
-rw-r--r--app/Statistics/Google/ChartIndividual.php73
-rw-r--r--app/Statistics/Google/ChartIndividualWithSources.php76
-rw-r--r--app/Statistics/Google/ChartMarriage.php65
-rw-r--r--app/Statistics/Google/ChartMarriageAge.php143
-rw-r--r--app/Statistics/Google/ChartMedia.php53
-rw-r--r--app/Statistics/Google/ChartMortality.php77
-rw-r--r--app/Statistics/Google/ChartNoChildrenFamilies.php109
-rw-r--r--app/Statistics/Google/ChartSex.php105
-rw-r--r--app/Statistics/Helper/Country.php15
-rw-r--r--app/Statistics/Repository/ContactRepository.php1
-rw-r--r--app/Statistics/Repository/FamilyRepository.php35
-rw-r--r--app/Statistics/Repository/IndividualRepository.php56
-rw-r--r--app/Statistics/Repository/Interfaces/IndividualRepositoryInterface.php2
-rw-r--r--app/Statistics/Repository/Interfaces/MediaRepositoryInterface.php3
-rw-r--r--app/Statistics/Repository/MediaRepository.php6
-rw-r--r--app/Statistics/Repository/PlaceRepository.php6
-rw-r--r--app/View.php5
-rw-r--r--public/js/webtrees.min.js2
-rw-r--r--resources/js/webtrees.js132
-rw-r--r--resources/views/modules/statistics-chart/custom.phtml14
-rw-r--r--resources/views/modules/statistics-chart/individuals.phtml2
-rw-r--r--resources/views/modules/statistics-chart/other.phtml2
-rw-r--r--resources/views/modules/statistics-chart/page.phtml12
-rw-r--r--resources/views/statistics/families/total-records.phtml6
-rw-r--r--resources/views/statistics/other/chart-distribution.phtml31
-rw-r--r--resources/views/statistics/other/chart-google.phtml1
-rw-r--r--resources/views/statistics/other/chart-setup.phtml26
-rw-r--r--resources/views/statistics/other/charts/column.phtml52
-rw-r--r--resources/views/statistics/other/charts/combo.phtml52
-rw-r--r--resources/views/statistics/other/charts/geo.phtml67
-rw-r--r--resources/views/statistics/other/charts/pie.phtml44
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&amp;chs=' . $sizes[0] . 'x' . $sizes[1]
- . '&amp;chm=D,FF0000,2,0,3,1|N*f1*,000000,0,-1,11,1|N*f1*,000000,1,-1,11,1&amp;chf=bg,s,ffffff00|c,s,ffffff00&amp;chtt='
- . rawurlencode($chtt) . '&amp;chd=' . $chd . '&amp;chco=0000FF,FFA0CB,FF0000&amp;chbh=20,3&amp;chxt=x,x,y,y&amp;chxl='
- . rawurlencode($chxl) . '&amp;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&amp;chs=' . $sizes[0] . 'x' . $sizes[1]
- . '&amp;chf=bg,s,ffffff00|c,s,ffffff00&amp;chm=D,FF0000,0,0,3,1|' . $chm
- . '&amp;chd=e:' . $chd . '&amp;chco=0000FF&amp;chbh=30,3&amp;chxt=x,x,y,y&amp;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&amp;chtm=' . $chart_shows;
- $chart_url .= '&amp;chco=' . $chart_color1 . ',' . $chart_color3 . ',' . $chart_color2; // country colours
- $chart_url .= '&amp;chf=bg,s,ECF5FF'; // sea colour
- $chart_url .= '&amp;chs=' . $map_x . 'x' . $map_y;
- $chart_url .= '&amp;chld=' . implode('', array_keys($surn_countries)) . '&amp;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&amp;chs=' . $sizes[0] . 'x' . $sizes[1]
- . '&amp;chm=D,FF0000,2,0,3,1|' . $chmm . $chmf
- . '&amp;chf=bg,s,ffffff00|c,s,ffffff00&amp;chtt=' . rawurlencode($chtt)
- . '&amp;chd=' . $chd . '&amp;chco=0000FF,FFA0CB,FF0000&amp;chbh=20,3&amp;chxt=x,x,y,y&amp;chxl='
- . rawurlencode($chxl) . '&amp;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&amp;chs=' . $sizes[0] . 'x' . $sizes[1]
- . '&amp;chf=bg,s,ffffff00|c,s,ffffff00&amp;chm=D,FF0000,0,0:'
- . ($i - 1) . ',3,1|' . $chm . '&amp;chd=e:'
- . $chd . '&amp;chco=0000FF,ffffff00&amp;chbh=30,3&amp;chxt=x,x,y,y&amp;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>