166ce3d23SRico Sonntag/** 266ce3d23SRico Sonntag * webtrees: online genealogy 3*d11be702SGreg Roach * Copyright (C) 2023 webtrees development team 466ce3d23SRico Sonntag * This program is free software: you can redistribute it and/or modify 566ce3d23SRico Sonntag * it under the terms of the GNU General Public License as published by 666ce3d23SRico Sonntag * the Free Software Foundation, either version 3 of the License, or 766ce3d23SRico Sonntag * (at your option) any later version. 866ce3d23SRico Sonntag * This program is distributed in the hope that it will be useful, 966ce3d23SRico Sonntag * but WITHOUT ANY WARRANTY; without even the implied warranty of 1066ce3d23SRico Sonntag * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 1166ce3d23SRico Sonntag * GNU General Public License for more details. 1266ce3d23SRico Sonntag * You should have received a copy of the GNU General Public License 1366ce3d23SRico Sonntag * along with this program. If not, see <http://www.gnu.org/licenses/>. 1466ce3d23SRico Sonntag */ 1566ce3d23SRico Sonntag 16efd89170SGreg Roach'use strict'; 1766ce3d23SRico Sonntag 18efd89170SGreg Roachconst GOOGLE_CHARTS_LIB = 'https://www.gstatic.com/charts/loader.js'; 1966ce3d23SRico Sonntag 2066ce3d23SRico Sonntag/** 2166ce3d23SRico Sonntag * Statistics class. 2266ce3d23SRico Sonntag */ 23efd89170SGreg Roachclass Statistics { 2466ce3d23SRico Sonntag /** 2566ce3d23SRico Sonntag * Constructor. 2666ce3d23SRico Sonntag * 2766ce3d23SRico Sonntag * @returns {Statistics} 2866ce3d23SRico Sonntag */ 29efd89170SGreg Roach constructor () { 3066ce3d23SRico Sonntag // Create singleton instance 3166ce3d23SRico Sonntag if (!Statistics.instance) { 3266ce3d23SRico Sonntag Statistics.instance = this; 3366ce3d23SRico Sonntag 3466ce3d23SRico Sonntag this.callbacks = []; 3566ce3d23SRico Sonntag this.initialized = false; 3666ce3d23SRico Sonntag this.loading = false; 3766ce3d23SRico Sonntag } 3866ce3d23SRico Sonntag 3966ce3d23SRico Sonntag return Statistics.instance; 4066ce3d23SRico Sonntag } 4166ce3d23SRico Sonntag 4266ce3d23SRico Sonntag /** 4366ce3d23SRico Sonntag * Initializes the google chart engine. Loads the chart lib only once. 4466ce3d23SRico Sonntag * 45220f62c2SGreg Roach * @param {String} locale - Locale, e.g. en, de, ... 4666ce3d23SRico Sonntag */ 47efd89170SGreg Roach init (locale) { 4866ce3d23SRico Sonntag if (this.loading || this.initialized) { 4966ce3d23SRico Sonntag return; 5066ce3d23SRico Sonntag } 5166ce3d23SRico Sonntag 5266ce3d23SRico Sonntag var that = this; 5366ce3d23SRico Sonntag 5466ce3d23SRico Sonntag Promise.all([ 5566ce3d23SRico Sonntag this.load(GOOGLE_CHARTS_LIB) 56cb8f307bSGreg Roach ]).then(() => { 5766ce3d23SRico Sonntag google.charts.load( 58efd89170SGreg Roach 'current', 5966ce3d23SRico Sonntag { 60efd89170SGreg Roach packages: [ 61efd89170SGreg Roach 'corechart', 62efd89170SGreg Roach 'geochart', 63efd89170SGreg Roach 'bar' 6466ce3d23SRico Sonntag ], 65efd89170SGreg Roach language: locale, 6666ce3d23SRico Sonntag // Note: you will need to get a mapsApiKey for your project. 6766ce3d23SRico Sonntag // See: https://developers.google.com/chart/interactive/docs/basic_load_libs#load-settings 68efd89170SGreg Roach mapsApiKey: '' 6966ce3d23SRico Sonntag } 7066ce3d23SRico Sonntag ); 7166ce3d23SRico Sonntag 7266ce3d23SRico Sonntag google.charts.setOnLoadCallback(function () { 73cb8f307bSGreg Roach that.callbacks.forEach((element) => { 7466ce3d23SRico Sonntag element(); 7566ce3d23SRico Sonntag }); 7666ce3d23SRico Sonntag }); 7766ce3d23SRico Sonntag 7866ce3d23SRico Sonntag that.initialized = true; 79cb8f307bSGreg Roach }).catch((error) => { 8066ce3d23SRico Sonntag console.log(error); 8166ce3d23SRico Sonntag }); 8266ce3d23SRico Sonntag } 8366ce3d23SRico Sonntag 8466ce3d23SRico Sonntag /** 8566ce3d23SRico Sonntag * Dynamically loads a script by the given URL. 8666ce3d23SRico Sonntag * 8766ce3d23SRico Sonntag * @param {String} url 8866ce3d23SRico Sonntag * @returns {Promise} 8966ce3d23SRico Sonntag */ 90efd89170SGreg Roach load (url) { 9166ce3d23SRico Sonntag if (this.loading) { 9266ce3d23SRico Sonntag return; 9366ce3d23SRico Sonntag } 9466ce3d23SRico Sonntag 9566ce3d23SRico Sonntag this.loading = true; 9666ce3d23SRico Sonntag 9766ce3d23SRico Sonntag return new Promise(function (resolve, reject) { 98efd89170SGreg Roach const script = document.createElement('script'); 9966ce3d23SRico Sonntag 10066ce3d23SRico Sonntag script.async = true; 10166ce3d23SRico Sonntag script.onload = function () { 10266ce3d23SRico Sonntag resolve(url); 103efd89170SGreg Roach }; 10466ce3d23SRico Sonntag script.onerror = function () { 10566ce3d23SRico Sonntag reject(url); 106efd89170SGreg Roach }; 10766ce3d23SRico Sonntag 10866ce3d23SRico Sonntag script.src = url; 10966ce3d23SRico Sonntag document.body.appendChild(script); 11066ce3d23SRico Sonntag }); 11166ce3d23SRico Sonntag } 11266ce3d23SRico Sonntag 11366ce3d23SRico Sonntag /** 11466ce3d23SRico Sonntag * Adds the given callback method to the callback stack or add it directly to 11566ce3d23SRico Sonntag * the google charts interface once the chart engine is up and running. 11666ce3d23SRico Sonntag * 11766ce3d23SRico Sonntag * @param {Function} callback 11866ce3d23SRico Sonntag */ 119efd89170SGreg Roach addCallback (callback) { 12066ce3d23SRico Sonntag if (this.initialized) { 12166ce3d23SRico Sonntag google.charts.setOnLoadCallback(callback); 12266ce3d23SRico Sonntag } else { 12366ce3d23SRico Sonntag this.callbacks.push(callback); 12466ce3d23SRico Sonntag } 12566ce3d23SRico Sonntag 12666ce3d23SRico Sonntag $(window).resize(function () { 12766ce3d23SRico Sonntag callback(); 12866ce3d23SRico Sonntag }); 12966ce3d23SRico Sonntag } 13066ce3d23SRico Sonntag 13166ce3d23SRico Sonntag /** 13266ce3d23SRico Sonntag * Draws a google chart. 13366ce3d23SRico Sonntag * 13466ce3d23SRico Sonntag * @param {String} containerId 13566ce3d23SRico Sonntag * @param {String} chartType 136220f62c2SGreg Roach * @param {Array} data 13766ce3d23SRico Sonntag * @param {Object} options 13866ce3d23SRico Sonntag */ 139efd89170SGreg Roach drawChart (containerId, chartType, data, options) { 140efd89170SGreg Roach const dataTable = google.visualization.arrayToDataTable(data); 14166ce3d23SRico Sonntag 142efd89170SGreg Roach const wrapper = new google.visualization.ChartWrapper({ 14366ce3d23SRico Sonntag chartType: chartType, 14466ce3d23SRico Sonntag dataTable: dataTable, 14566ce3d23SRico Sonntag options: options, 14666ce3d23SRico Sonntag containerId: containerId 14766ce3d23SRico Sonntag }); 14866ce3d23SRico Sonntag 14966ce3d23SRico Sonntag wrapper.draw(); 15066ce3d23SRico Sonntag } 15166ce3d23SRico Sonntag 15266ce3d23SRico Sonntag /** 15366ce3d23SRico Sonntag * Draws a pie chart. 15466ce3d23SRico Sonntag * 155220f62c2SGreg Roach * @param {String} elementId - The element id of the HTML element the chart is rendered too 156220f62c2SGreg Roach * @param {Array} data - The chart data array 157220f62c2SGreg Roach * @param {Object} options - The chart specific options to overwrite the default ones 15866ce3d23SRico Sonntag */ 159efd89170SGreg Roach drawPieChart (elementId, data, options) { 16066ce3d23SRico Sonntag // Default chart options 161efd89170SGreg Roach const defaults = { 162efd89170SGreg Roach title: '', 163efd89170SGreg Roach height: '100%', 164efd89170SGreg Roach width: '100%', 16566ce3d23SRico Sonntag pieStartAngle: 0, 166efd89170SGreg Roach pieSliceText: 'none', 16766ce3d23SRico Sonntag pieSliceTextStyle: { 168efd89170SGreg Roach color: '#777' 16966ce3d23SRico Sonntag }, 17066ce3d23SRico Sonntag pieHole: 0.4, // Donut 17166ce3d23SRico Sonntag // is3D: true, // 3D (not together with pieHole) 17266ce3d23SRico Sonntag legend: { 173efd89170SGreg Roach alignment: 'center', 17466ce3d23SRico Sonntag // Flickers on mouseover :( 175efd89170SGreg Roach labeledValueText: 'value', 176efd89170SGreg Roach position: 'labeled' 17766ce3d23SRico Sonntag }, 17866ce3d23SRico Sonntag chartArea: { 17966ce3d23SRico Sonntag left: 0, 180efd89170SGreg Roach top: '5%', 181efd89170SGreg Roach height: '90%', 182efd89170SGreg Roach width: '100%' 18366ce3d23SRico Sonntag }, 18466ce3d23SRico Sonntag tooltip: { 185efd89170SGreg Roach trigger: 'none', 186efd89170SGreg Roach text: 'both' 18766ce3d23SRico Sonntag }, 188efd89170SGreg Roach backgroundColor: 'transparent', 18966ce3d23SRico Sonntag colors: [] 19066ce3d23SRico Sonntag }; 19166ce3d23SRico Sonntag 19266ce3d23SRico Sonntag // Merge default with provided options 19366ce3d23SRico Sonntag options = Object.assign(defaults, options); 19466ce3d23SRico Sonntag 19566ce3d23SRico Sonntag // Create and draw the chart 196efd89170SGreg Roach this.drawChart(elementId, 'PieChart', data, options); 19766ce3d23SRico Sonntag } 19866ce3d23SRico Sonntag 19966ce3d23SRico Sonntag /** 20066ce3d23SRico Sonntag * Draws a column chart. 20166ce3d23SRico Sonntag * 202220f62c2SGreg Roach * @param {String} elementId - The element id of the HTML element the chart is rendered too 203220f62c2SGreg Roach * @param {Array} data - The chart data array 204220f62c2SGreg Roach * @param {Object} options - The chart specific options to overwrite the default ones 20566ce3d23SRico Sonntag */ 206efd89170SGreg Roach drawColumnChart (elementId, data, options) { 20766ce3d23SRico Sonntag // Default chart options 208efd89170SGreg Roach const defaults = { 209efd89170SGreg Roach title: '', 210efd89170SGreg Roach subtitle: '', 21166ce3d23SRico Sonntag titleTextStyle: { 212efd89170SGreg Roach color: '#757575', 213efd89170SGreg Roach fontName: 'Roboto', 214efd89170SGreg Roach fontSize: '16px', 21566ce3d23SRico Sonntag bold: false, 21666ce3d23SRico Sonntag italic: false 21766ce3d23SRico Sonntag }, 218efd89170SGreg Roach height: '100%', 219efd89170SGreg Roach width: '100%', 22066ce3d23SRico Sonntag vAxis: { 221efd89170SGreg Roach title: '' 22266ce3d23SRico Sonntag }, 22366ce3d23SRico Sonntag hAxis: { 224efd89170SGreg Roach title: '' 22566ce3d23SRico Sonntag }, 22666ce3d23SRico Sonntag legend: { 227efd89170SGreg Roach position: 'none' 22866ce3d23SRico Sonntag }, 229efd89170SGreg Roach backgroundColor: 'transparent' 23066ce3d23SRico Sonntag }; 23166ce3d23SRico Sonntag 23266ce3d23SRico Sonntag // Merge default with provided options 23366ce3d23SRico Sonntag options = Object.assign(defaults, options); 23466ce3d23SRico Sonntag 23566ce3d23SRico Sonntag // Create and draw the chart 236efd89170SGreg Roach this.drawChart(elementId, 'ColumnChart', data, options); 23766ce3d23SRico Sonntag } 23866ce3d23SRico Sonntag 23966ce3d23SRico Sonntag /** 24066ce3d23SRico Sonntag * Draws a combo chart. 24166ce3d23SRico Sonntag * 242220f62c2SGreg Roach * @param {String} elementId - The element id of the HTML element the chart is rendered too 243220f62c2SGreg Roach * @param {Array} data - The chart data array 244220f62c2SGreg Roach * @param {Object} options - The chart specific options to overwrite the default ones 24566ce3d23SRico Sonntag */ 246efd89170SGreg Roach drawComboChart (elementId, data, options) { 24766ce3d23SRico Sonntag // Default chart options 248efd89170SGreg Roach const defaults = { 249efd89170SGreg Roach title: '', 250efd89170SGreg Roach subtitle: '', 25166ce3d23SRico Sonntag titleTextStyle: { 252efd89170SGreg Roach color: '#757575', 253efd89170SGreg Roach fontName: 'Roboto', 254efd89170SGreg Roach fontSize: '16px', 25566ce3d23SRico Sonntag bold: false, 25666ce3d23SRico Sonntag italic: false 25766ce3d23SRico Sonntag }, 258efd89170SGreg Roach height: '100%', 259efd89170SGreg Roach width: '100%', 26066ce3d23SRico Sonntag vAxis: { 261efd89170SGreg Roach title: '' 26266ce3d23SRico Sonntag }, 26366ce3d23SRico Sonntag hAxis: { 264efd89170SGreg Roach title: '' 26566ce3d23SRico Sonntag }, 26666ce3d23SRico Sonntag legend: { 267efd89170SGreg Roach position: 'none' 26866ce3d23SRico Sonntag }, 269efd89170SGreg Roach seriesType: 'bars', 27066ce3d23SRico Sonntag series: { 27166ce3d23SRico Sonntag 2: { 272efd89170SGreg Roach type: 'line' 27366ce3d23SRico Sonntag } 27466ce3d23SRico Sonntag }, 27566ce3d23SRico Sonntag colors: [], 276efd89170SGreg Roach backgroundColor: 'transparent' 27766ce3d23SRico Sonntag }; 27866ce3d23SRico Sonntag 27966ce3d23SRico Sonntag // Merge default with provided options 28066ce3d23SRico Sonntag options = Object.assign(defaults, options); 28166ce3d23SRico Sonntag 28266ce3d23SRico Sonntag // Create and draw the chart 283efd89170SGreg Roach this.drawChart(elementId, 'ComboChart', data, options); 28466ce3d23SRico Sonntag } 28566ce3d23SRico Sonntag 28666ce3d23SRico Sonntag /** 28766ce3d23SRico Sonntag * Draws a geo chart. 28866ce3d23SRico Sonntag * 289220f62c2SGreg Roach * @param {String} elementId - The element id of the HTML element the chart is rendered too 290220f62c2SGreg Roach * @param {Array} data - The chart data array 291220f62c2SGreg Roach * @param {Object} options - The chart specific options to overwrite the default ones 29266ce3d23SRico Sonntag */ 293efd89170SGreg Roach drawGeoChart (elementId, data, options) { 29466ce3d23SRico Sonntag // Default chart options 295efd89170SGreg Roach const defaults = { 296efd89170SGreg Roach title: '', 297efd89170SGreg Roach subtitle: '', 298efd89170SGreg Roach height: '100%', 299efd89170SGreg Roach width: '100%' 30066ce3d23SRico Sonntag }; 30166ce3d23SRico Sonntag 30266ce3d23SRico Sonntag // Merge default with provided options 30366ce3d23SRico Sonntag options = Object.assign(defaults, options); 30466ce3d23SRico Sonntag 30566ce3d23SRico Sonntag // Create and draw the chart 306efd89170SGreg Roach this.drawChart(elementId, 'GeoChart', data, options); 30766ce3d23SRico Sonntag } 30866ce3d23SRico Sonntag} 30966ce3d23SRico Sonntag 31066ce3d23SRico Sonntag// Create singleton instance of class 31166ce3d23SRico Sonntagconst statistics = new Statistics(); 312