166ce3d23SRico Sonntag/** 266ce3d23SRico Sonntag * webtrees: online genealogy 366ce3d23SRico Sonntag * Copyright (C) 2019 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 16*efd89170SGreg Roach'use strict'; 1766ce3d23SRico Sonntag 18*efd89170SGreg Roachconst GOOGLE_CHARTS_LIB = 'https://www.gstatic.com/charts/loader.js'; 1966ce3d23SRico Sonntag 2066ce3d23SRico Sonntag/** 2166ce3d23SRico Sonntag * Statistics class. 2266ce3d23SRico Sonntag */ 23*efd89170SGreg Roachclass Statistics { 2466ce3d23SRico Sonntag /** 2566ce3d23SRico Sonntag * Constructor. 2666ce3d23SRico Sonntag * 2766ce3d23SRico Sonntag * @returns {Statistics} 2866ce3d23SRico Sonntag */ 29*efd89170SGreg 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 * 4566ce3d23SRico Sonntag * @param {String} locale Locale, e.g. en, de, ... 4666ce3d23SRico Sonntag */ 47*efd89170SGreg 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) 5666ce3d23SRico Sonntag ]).then(function () { 5766ce3d23SRico Sonntag google.charts.load( 58*efd89170SGreg Roach 'current', 5966ce3d23SRico Sonntag { 60*efd89170SGreg Roach packages: [ 61*efd89170SGreg Roach 'corechart', 62*efd89170SGreg Roach 'geochart', 63*efd89170SGreg Roach 'bar' 6466ce3d23SRico Sonntag ], 65*efd89170SGreg 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 68*efd89170SGreg Roach mapsApiKey: '' 6966ce3d23SRico Sonntag } 7066ce3d23SRico Sonntag ); 7166ce3d23SRico Sonntag 7266ce3d23SRico Sonntag google.charts.setOnLoadCallback(function () { 7366ce3d23SRico Sonntag that.callbacks.forEach(function (element) { 7466ce3d23SRico Sonntag element(); 7566ce3d23SRico Sonntag }); 7666ce3d23SRico Sonntag }); 7766ce3d23SRico Sonntag 7866ce3d23SRico Sonntag that.initialized = true; 7966ce3d23SRico Sonntag }).catch(function (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 * 8966ce3d23SRico Sonntag * @returns {Promise} 9066ce3d23SRico Sonntag */ 91*efd89170SGreg Roach load (url) { 9266ce3d23SRico Sonntag if (this.loading) { 9366ce3d23SRico Sonntag return; 9466ce3d23SRico Sonntag } 9566ce3d23SRico Sonntag 9666ce3d23SRico Sonntag this.loading = true; 9766ce3d23SRico Sonntag 9866ce3d23SRico Sonntag return new Promise(function (resolve, reject) { 99*efd89170SGreg Roach const script = document.createElement('script'); 10066ce3d23SRico Sonntag 10166ce3d23SRico Sonntag script.async = true; 10266ce3d23SRico Sonntag script.onload = function () { 10366ce3d23SRico Sonntag resolve(url); 104*efd89170SGreg Roach }; 10566ce3d23SRico Sonntag script.onerror = function () { 10666ce3d23SRico Sonntag reject(url); 107*efd89170SGreg Roach }; 10866ce3d23SRico Sonntag 10966ce3d23SRico Sonntag script.src = url; 11066ce3d23SRico Sonntag document.body.appendChild(script); 11166ce3d23SRico Sonntag }); 11266ce3d23SRico Sonntag } 11366ce3d23SRico Sonntag 11466ce3d23SRico Sonntag /** 11566ce3d23SRico Sonntag * Adds the given callback method to the callback stack or add it directly to 11666ce3d23SRico Sonntag * the google charts interface once the chart engine is up and running. 11766ce3d23SRico Sonntag * 11866ce3d23SRico Sonntag * @param {Function} callback 11966ce3d23SRico Sonntag */ 120*efd89170SGreg Roach addCallback (callback) { 12166ce3d23SRico Sonntag if (this.initialized) { 12266ce3d23SRico Sonntag google.charts.setOnLoadCallback(callback); 12366ce3d23SRico Sonntag } else { 12466ce3d23SRico Sonntag this.callbacks.push(callback); 12566ce3d23SRico Sonntag } 12666ce3d23SRico Sonntag 12766ce3d23SRico Sonntag $(window).resize(function () { 12866ce3d23SRico Sonntag callback(); 12966ce3d23SRico Sonntag }); 13066ce3d23SRico Sonntag } 13166ce3d23SRico Sonntag 13266ce3d23SRico Sonntag /** 13366ce3d23SRico Sonntag * Draws a google chart. 13466ce3d23SRico Sonntag * 13566ce3d23SRico Sonntag * @param {String} containerId 13666ce3d23SRico Sonntag * @param {String} chartType 13766ce3d23SRico Sonntag * @param {Array} dataTable 13866ce3d23SRico Sonntag * @param {Object} options 13966ce3d23SRico Sonntag */ 140*efd89170SGreg Roach drawChart (containerId, chartType, data, options) { 141*efd89170SGreg Roach const dataTable = google.visualization.arrayToDataTable(data); 14266ce3d23SRico Sonntag 143*efd89170SGreg Roach const wrapper = new google.visualization.ChartWrapper({ 14466ce3d23SRico Sonntag chartType: chartType, 14566ce3d23SRico Sonntag dataTable: dataTable, 14666ce3d23SRico Sonntag options: options, 14766ce3d23SRico Sonntag containerId: containerId 14866ce3d23SRico Sonntag }); 14966ce3d23SRico Sonntag 15066ce3d23SRico Sonntag wrapper.draw(); 15166ce3d23SRico Sonntag } 15266ce3d23SRico Sonntag 15366ce3d23SRico Sonntag /** 15466ce3d23SRico Sonntag * Draws a pie chart. 15566ce3d23SRico Sonntag * 15666ce3d23SRico Sonntag * @param {String} elementId The element id of the HTML element the chart is rendered too 15766ce3d23SRico Sonntag * @param {Array} data The chart data array 15866ce3d23SRico Sonntag * @param {Object} options The chart specific options to overwrite the default ones 15966ce3d23SRico Sonntag */ 160*efd89170SGreg Roach drawPieChart (elementId, data, options) { 16166ce3d23SRico Sonntag // Default chart options 162*efd89170SGreg Roach const defaults = { 163*efd89170SGreg Roach title: '', 164*efd89170SGreg Roach height: '100%', 165*efd89170SGreg Roach width: '100%', 16666ce3d23SRico Sonntag pieStartAngle: 0, 167*efd89170SGreg Roach pieSliceText: 'none', 16866ce3d23SRico Sonntag pieSliceTextStyle: { 169*efd89170SGreg Roach color: '#777' 17066ce3d23SRico Sonntag }, 17166ce3d23SRico Sonntag pieHole: 0.4, // Donut 17266ce3d23SRico Sonntag // is3D: true, // 3D (not together with pieHole) 17366ce3d23SRico Sonntag legend: { 174*efd89170SGreg Roach alignment: 'center', 17566ce3d23SRico Sonntag // Flickers on mouseover :( 176*efd89170SGreg Roach labeledValueText: 'value', 177*efd89170SGreg Roach position: 'labeled' 17866ce3d23SRico Sonntag }, 17966ce3d23SRico Sonntag chartArea: { 18066ce3d23SRico Sonntag left: 0, 181*efd89170SGreg Roach top: '5%', 182*efd89170SGreg Roach height: '90%', 183*efd89170SGreg Roach width: '100%' 18466ce3d23SRico Sonntag }, 18566ce3d23SRico Sonntag tooltip: { 186*efd89170SGreg Roach trigger: 'none', 187*efd89170SGreg Roach text: 'both' 18866ce3d23SRico Sonntag }, 189*efd89170SGreg Roach backgroundColor: 'transparent', 19066ce3d23SRico Sonntag colors: [] 19166ce3d23SRico Sonntag }; 19266ce3d23SRico Sonntag 19366ce3d23SRico Sonntag // Merge default with provided options 19466ce3d23SRico Sonntag options = Object.assign(defaults, options); 19566ce3d23SRico Sonntag 19666ce3d23SRico Sonntag // Create and draw the chart 197*efd89170SGreg Roach this.drawChart(elementId, 'PieChart', data, options); 19866ce3d23SRico Sonntag } 19966ce3d23SRico Sonntag 20066ce3d23SRico Sonntag /** 20166ce3d23SRico Sonntag * Draws a column chart. 20266ce3d23SRico Sonntag * 20366ce3d23SRico Sonntag * @param {String} elementId The element id of the HTML element the chart is rendered too 20466ce3d23SRico Sonntag * @param {Array} data The chart data array 20566ce3d23SRico Sonntag * @param {Object} options The chart specific options to overwrite the default ones 20666ce3d23SRico Sonntag */ 207*efd89170SGreg Roach drawColumnChart (elementId, data, options) { 20866ce3d23SRico Sonntag // Default chart options 209*efd89170SGreg Roach const defaults = { 210*efd89170SGreg Roach title: '', 211*efd89170SGreg Roach subtitle: '', 21266ce3d23SRico Sonntag titleTextStyle: { 213*efd89170SGreg Roach color: '#757575', 214*efd89170SGreg Roach fontName: 'Roboto', 215*efd89170SGreg Roach fontSize: '16px', 21666ce3d23SRico Sonntag bold: false, 21766ce3d23SRico Sonntag italic: false 21866ce3d23SRico Sonntag }, 219*efd89170SGreg Roach height: '100%', 220*efd89170SGreg Roach width: '100%', 22166ce3d23SRico Sonntag vAxis: { 222*efd89170SGreg Roach title: '' 22366ce3d23SRico Sonntag }, 22466ce3d23SRico Sonntag hAxis: { 225*efd89170SGreg Roach title: '' 22666ce3d23SRico Sonntag }, 22766ce3d23SRico Sonntag legend: { 228*efd89170SGreg Roach position: 'none' 22966ce3d23SRico Sonntag }, 230*efd89170SGreg Roach backgroundColor: 'transparent' 23166ce3d23SRico Sonntag }; 23266ce3d23SRico Sonntag 23366ce3d23SRico Sonntag // Merge default with provided options 23466ce3d23SRico Sonntag options = Object.assign(defaults, options); 23566ce3d23SRico Sonntag 23666ce3d23SRico Sonntag // Create and draw the chart 237*efd89170SGreg Roach this.drawChart(elementId, 'ColumnChart', data, options); 23866ce3d23SRico Sonntag } 23966ce3d23SRico Sonntag 24066ce3d23SRico Sonntag /** 24166ce3d23SRico Sonntag * Draws a combo chart. 24266ce3d23SRico Sonntag * 24366ce3d23SRico Sonntag * @param {String} elementId The element id of the HTML element the chart is rendered too 24466ce3d23SRico Sonntag * @param {Array} data The chart data array 24566ce3d23SRico Sonntag * @param {Object} options The chart specific options to overwrite the default ones 24666ce3d23SRico Sonntag */ 247*efd89170SGreg Roach drawComboChart (elementId, data, options) { 24866ce3d23SRico Sonntag // Default chart options 249*efd89170SGreg Roach const defaults = { 250*efd89170SGreg Roach title: '', 251*efd89170SGreg Roach subtitle: '', 25266ce3d23SRico Sonntag titleTextStyle: { 253*efd89170SGreg Roach color: '#757575', 254*efd89170SGreg Roach fontName: 'Roboto', 255*efd89170SGreg Roach fontSize: '16px', 25666ce3d23SRico Sonntag bold: false, 25766ce3d23SRico Sonntag italic: false 25866ce3d23SRico Sonntag }, 259*efd89170SGreg Roach height: '100%', 260*efd89170SGreg Roach width: '100%', 26166ce3d23SRico Sonntag vAxis: { 262*efd89170SGreg Roach title: '' 26366ce3d23SRico Sonntag }, 26466ce3d23SRico Sonntag hAxis: { 265*efd89170SGreg Roach title: '' 26666ce3d23SRico Sonntag }, 26766ce3d23SRico Sonntag legend: { 268*efd89170SGreg Roach position: 'none' 26966ce3d23SRico Sonntag }, 270*efd89170SGreg Roach seriesType: 'bars', 27166ce3d23SRico Sonntag series: { 27266ce3d23SRico Sonntag 2: { 273*efd89170SGreg Roach type: 'line' 27466ce3d23SRico Sonntag } 27566ce3d23SRico Sonntag }, 27666ce3d23SRico Sonntag colors: [], 277*efd89170SGreg Roach backgroundColor: 'transparent' 27866ce3d23SRico Sonntag }; 27966ce3d23SRico Sonntag 28066ce3d23SRico Sonntag // Merge default with provided options 28166ce3d23SRico Sonntag options = Object.assign(defaults, options); 28266ce3d23SRico Sonntag 28366ce3d23SRico Sonntag // Create and draw the chart 284*efd89170SGreg Roach this.drawChart(elementId, 'ComboChart', data, options); 28566ce3d23SRico Sonntag } 28666ce3d23SRico Sonntag 28766ce3d23SRico Sonntag /** 28866ce3d23SRico Sonntag * Draws a geo chart. 28966ce3d23SRico Sonntag * 29066ce3d23SRico Sonntag * @param {String} elementId The element id of the HTML element the chart is rendered too 29166ce3d23SRico Sonntag * @param {Array} data The chart data array 29266ce3d23SRico Sonntag * @param {Object} options The chart specific options to overwrite the default ones 29366ce3d23SRico Sonntag */ 294*efd89170SGreg Roach drawGeoChart (elementId, data, options) { 29566ce3d23SRico Sonntag // Default chart options 296*efd89170SGreg Roach const defaults = { 297*efd89170SGreg Roach title: '', 298*efd89170SGreg Roach subtitle: '', 299*efd89170SGreg Roach height: '100%', 300*efd89170SGreg Roach width: '100%' 30166ce3d23SRico Sonntag }; 30266ce3d23SRico Sonntag 30366ce3d23SRico Sonntag // Merge default with provided options 30466ce3d23SRico Sonntag options = Object.assign(defaults, options); 30566ce3d23SRico Sonntag 30666ce3d23SRico Sonntag // Create and draw the chart 307*efd89170SGreg Roach this.drawChart(elementId, 'GeoChart', data, options); 30866ce3d23SRico Sonntag } 30966ce3d23SRico Sonntag} 31066ce3d23SRico Sonntag 31166ce3d23SRico Sonntag// Create singleton instance of class 31266ce3d23SRico Sonntagconst statistics = new Statistics(); 313