1*66ce3d23SRico Sonntag/** 2*66ce3d23SRico Sonntag * webtrees: online genealogy 3*66ce3d23SRico Sonntag * Copyright (C) 2019 webtrees development team 4*66ce3d23SRico Sonntag * This program is free software: you can redistribute it and/or modify 5*66ce3d23SRico Sonntag * it under the terms of the GNU General Public License as published by 6*66ce3d23SRico Sonntag * the Free Software Foundation, either version 3 of the License, or 7*66ce3d23SRico Sonntag * (at your option) any later version. 8*66ce3d23SRico Sonntag * This program is distributed in the hope that it will be useful, 9*66ce3d23SRico Sonntag * but WITHOUT ANY WARRANTY; without even the implied warranty of 10*66ce3d23SRico Sonntag * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11*66ce3d23SRico Sonntag * GNU General Public License for more details. 12*66ce3d23SRico Sonntag * You should have received a copy of the GNU General Public License 13*66ce3d23SRico Sonntag * along with this program. If not, see <http://www.gnu.org/licenses/>. 14*66ce3d23SRico Sonntag */ 15*66ce3d23SRico Sonntag 16*66ce3d23SRico Sonntag"use strict"; 17*66ce3d23SRico Sonntag 18*66ce3d23SRico Sonntagconst GOOGLE_CHARTS_LIB = "https://www.gstatic.com/charts/loader.js"; 19*66ce3d23SRico Sonntag 20*66ce3d23SRico Sonntag/** 21*66ce3d23SRico Sonntag * Statistics class. 22*66ce3d23SRico Sonntag */ 23*66ce3d23SRico Sonntagclass Statistics 24*66ce3d23SRico Sonntag{ 25*66ce3d23SRico Sonntag /** 26*66ce3d23SRico Sonntag * Constructor. 27*66ce3d23SRico Sonntag * 28*66ce3d23SRico Sonntag * @returns {Statistics} 29*66ce3d23SRico Sonntag */ 30*66ce3d23SRico Sonntag constructor() 31*66ce3d23SRico Sonntag { 32*66ce3d23SRico Sonntag // Create singleton instance 33*66ce3d23SRico Sonntag if (!Statistics.instance) { 34*66ce3d23SRico Sonntag Statistics.instance = this; 35*66ce3d23SRico Sonntag 36*66ce3d23SRico Sonntag this.callbacks = []; 37*66ce3d23SRico Sonntag this.initialized = false; 38*66ce3d23SRico Sonntag this.loading = false; 39*66ce3d23SRico Sonntag } 40*66ce3d23SRico Sonntag 41*66ce3d23SRico Sonntag return Statistics.instance; 42*66ce3d23SRico Sonntag } 43*66ce3d23SRico Sonntag 44*66ce3d23SRico Sonntag /** 45*66ce3d23SRico Sonntag * Initializes the google chart engine. Loads the chart lib only once. 46*66ce3d23SRico Sonntag * 47*66ce3d23SRico Sonntag * @param {String} locale Locale, e.g. en, de, ... 48*66ce3d23SRico Sonntag */ 49*66ce3d23SRico Sonntag init(locale) 50*66ce3d23SRico Sonntag { 51*66ce3d23SRico Sonntag if (this.loading || this.initialized) { 52*66ce3d23SRico Sonntag return; 53*66ce3d23SRico Sonntag } 54*66ce3d23SRico Sonntag 55*66ce3d23SRico Sonntag var that = this; 56*66ce3d23SRico Sonntag 57*66ce3d23SRico Sonntag Promise.all([ 58*66ce3d23SRico Sonntag this.load(GOOGLE_CHARTS_LIB) 59*66ce3d23SRico Sonntag ]).then(function () { 60*66ce3d23SRico Sonntag google.charts.load( 61*66ce3d23SRico Sonntag "current", 62*66ce3d23SRico Sonntag { 63*66ce3d23SRico Sonntag "packages": [ 64*66ce3d23SRico Sonntag "corechart", 65*66ce3d23SRico Sonntag "geochart", 66*66ce3d23SRico Sonntag "bar" 67*66ce3d23SRico Sonntag ], 68*66ce3d23SRico Sonntag "language": locale, 69*66ce3d23SRico Sonntag // Note: you will need to get a mapsApiKey for your project. 70*66ce3d23SRico Sonntag // See: https://developers.google.com/chart/interactive/docs/basic_load_libs#load-settings 71*66ce3d23SRico Sonntag "mapsApiKey": "" 72*66ce3d23SRico Sonntag } 73*66ce3d23SRico Sonntag ); 74*66ce3d23SRico Sonntag 75*66ce3d23SRico Sonntag google.charts.setOnLoadCallback(function () { 76*66ce3d23SRico Sonntag that.callbacks.forEach(function(element) { 77*66ce3d23SRico Sonntag element(); 78*66ce3d23SRico Sonntag }); 79*66ce3d23SRico Sonntag }); 80*66ce3d23SRico Sonntag 81*66ce3d23SRico Sonntag that.initialized = true; 82*66ce3d23SRico Sonntag }).catch(function (error) { 83*66ce3d23SRico Sonntag console.log(error); 84*66ce3d23SRico Sonntag }); 85*66ce3d23SRico Sonntag } 86*66ce3d23SRico Sonntag 87*66ce3d23SRico Sonntag /** 88*66ce3d23SRico Sonntag * Dynamically loads a script by the given URL. 89*66ce3d23SRico Sonntag * 90*66ce3d23SRico Sonntag * @param {String} url 91*66ce3d23SRico Sonntag * 92*66ce3d23SRico Sonntag * @returns {Promise} 93*66ce3d23SRico Sonntag */ 94*66ce3d23SRico Sonntag load(url) 95*66ce3d23SRico Sonntag { 96*66ce3d23SRico Sonntag if (this.loading) { 97*66ce3d23SRico Sonntag return; 98*66ce3d23SRico Sonntag } 99*66ce3d23SRico Sonntag 100*66ce3d23SRico Sonntag this.loading = true; 101*66ce3d23SRico Sonntag 102*66ce3d23SRico Sonntag return new Promise(function (resolve, reject) { 103*66ce3d23SRico Sonntag let script = document.createElement("script"); 104*66ce3d23SRico Sonntag 105*66ce3d23SRico Sonntag script.async = true; 106*66ce3d23SRico Sonntag script.onload = function () { 107*66ce3d23SRico Sonntag resolve(url); 108*66ce3d23SRico Sonntag } 109*66ce3d23SRico Sonntag script.onerror = function () { 110*66ce3d23SRico Sonntag reject(url); 111*66ce3d23SRico Sonntag } 112*66ce3d23SRico Sonntag 113*66ce3d23SRico Sonntag script.src = url; 114*66ce3d23SRico Sonntag document.body.appendChild(script); 115*66ce3d23SRico Sonntag }); 116*66ce3d23SRico Sonntag } 117*66ce3d23SRico Sonntag 118*66ce3d23SRico Sonntag /** 119*66ce3d23SRico Sonntag * Adds the given callback method to the callback stack or add it directly to 120*66ce3d23SRico Sonntag * the google charts interface once the chart engine is up and running. 121*66ce3d23SRico Sonntag * 122*66ce3d23SRico Sonntag * @param {Function} callback 123*66ce3d23SRico Sonntag */ 124*66ce3d23SRico Sonntag addCallback(callback) 125*66ce3d23SRico Sonntag { 126*66ce3d23SRico Sonntag if (this.initialized) { 127*66ce3d23SRico Sonntag google.charts.setOnLoadCallback(callback); 128*66ce3d23SRico Sonntag } else { 129*66ce3d23SRico Sonntag this.callbacks.push(callback); 130*66ce3d23SRico Sonntag } 131*66ce3d23SRico Sonntag 132*66ce3d23SRico Sonntag $(window).resize(function () { 133*66ce3d23SRico Sonntag callback(); 134*66ce3d23SRico Sonntag }); 135*66ce3d23SRico Sonntag } 136*66ce3d23SRico Sonntag 137*66ce3d23SRico Sonntag /** 138*66ce3d23SRico Sonntag * Draws a google chart. 139*66ce3d23SRico Sonntag * 140*66ce3d23SRico Sonntag * @param {String} containerId 141*66ce3d23SRico Sonntag * @param {String} chartType 142*66ce3d23SRico Sonntag * @param {Array} dataTable 143*66ce3d23SRico Sonntag * @param {Object} options 144*66ce3d23SRico Sonntag */ 145*66ce3d23SRico Sonntag drawChart(containerId, chartType, data, options) 146*66ce3d23SRico Sonntag { 147*66ce3d23SRico Sonntag let dataTable = google.visualization.arrayToDataTable(data); 148*66ce3d23SRico Sonntag 149*66ce3d23SRico Sonntag let wrapper = new google.visualization.ChartWrapper({ 150*66ce3d23SRico Sonntag chartType: chartType, 151*66ce3d23SRico Sonntag dataTable: dataTable, 152*66ce3d23SRico Sonntag options: options, 153*66ce3d23SRico Sonntag containerId: containerId 154*66ce3d23SRico Sonntag }); 155*66ce3d23SRico Sonntag 156*66ce3d23SRico Sonntag wrapper.draw(); 157*66ce3d23SRico Sonntag } 158*66ce3d23SRico Sonntag 159*66ce3d23SRico Sonntag /** 160*66ce3d23SRico Sonntag * Draws a pie chart. 161*66ce3d23SRico Sonntag * 162*66ce3d23SRico Sonntag * @param {String} elementId The element id of the HTML element the chart is rendered too 163*66ce3d23SRico Sonntag * @param {Array} data The chart data array 164*66ce3d23SRico Sonntag * @param {Object} options The chart specific options to overwrite the default ones 165*66ce3d23SRico Sonntag */ 166*66ce3d23SRico Sonntag drawPieChart(elementId, data, options) 167*66ce3d23SRico Sonntag { 168*66ce3d23SRico Sonntag // Default chart options 169*66ce3d23SRico Sonntag let defaults = { 170*66ce3d23SRico Sonntag title: "", 171*66ce3d23SRico Sonntag height: "100%", 172*66ce3d23SRico Sonntag width: "100%", 173*66ce3d23SRico Sonntag pieStartAngle: 0, 174*66ce3d23SRico Sonntag pieSliceText: "none", 175*66ce3d23SRico Sonntag pieSliceTextStyle: { 176*66ce3d23SRico Sonntag color: "#777" 177*66ce3d23SRico Sonntag }, 178*66ce3d23SRico Sonntag pieHole: 0.4, // Donut 179*66ce3d23SRico Sonntag //is3D: true, // 3D (not together with pieHole) 180*66ce3d23SRico Sonntag legend: { 181*66ce3d23SRico Sonntag alignment: "center", 182*66ce3d23SRico Sonntag // Flickers on mouseover :( 183*66ce3d23SRico Sonntag labeledValueText: "value", 184*66ce3d23SRico Sonntag position: "labeled" 185*66ce3d23SRico Sonntag }, 186*66ce3d23SRico Sonntag chartArea: { 187*66ce3d23SRico Sonntag left: 0, 188*66ce3d23SRico Sonntag top: "5%", 189*66ce3d23SRico Sonntag height: "90%", 190*66ce3d23SRico Sonntag width: "100%" 191*66ce3d23SRico Sonntag }, 192*66ce3d23SRico Sonntag tooltip: { 193*66ce3d23SRico Sonntag trigger: "none", 194*66ce3d23SRico Sonntag text: "both" 195*66ce3d23SRico Sonntag }, 196*66ce3d23SRico Sonntag backgroundColor: "transparent", 197*66ce3d23SRico Sonntag colors: [] 198*66ce3d23SRico Sonntag }; 199*66ce3d23SRico Sonntag 200*66ce3d23SRico Sonntag // Merge default with provided options 201*66ce3d23SRico Sonntag options = Object.assign(defaults, options); 202*66ce3d23SRico Sonntag 203*66ce3d23SRico Sonntag // Create and draw the chart 204*66ce3d23SRico Sonntag this.drawChart(elementId, "PieChart", data, options); 205*66ce3d23SRico Sonntag } 206*66ce3d23SRico Sonntag 207*66ce3d23SRico Sonntag /** 208*66ce3d23SRico Sonntag * Draws a column chart. 209*66ce3d23SRico Sonntag * 210*66ce3d23SRico Sonntag * @param {String} elementId The element id of the HTML element the chart is rendered too 211*66ce3d23SRico Sonntag * @param {Array} data The chart data array 212*66ce3d23SRico Sonntag * @param {Object} options The chart specific options to overwrite the default ones 213*66ce3d23SRico Sonntag */ 214*66ce3d23SRico Sonntag drawColumnChart(elementId, data, options) 215*66ce3d23SRico Sonntag { 216*66ce3d23SRico Sonntag // Default chart options 217*66ce3d23SRico Sonntag let defaults = { 218*66ce3d23SRico Sonntag title: "", 219*66ce3d23SRico Sonntag subtitle: "", 220*66ce3d23SRico Sonntag titleTextStyle: { 221*66ce3d23SRico Sonntag color: "#757575", 222*66ce3d23SRico Sonntag fontName: "Roboto", 223*66ce3d23SRico Sonntag fontSize: "16px", 224*66ce3d23SRico Sonntag bold: false, 225*66ce3d23SRico Sonntag italic: false 226*66ce3d23SRico Sonntag }, 227*66ce3d23SRico Sonntag height: "100%", 228*66ce3d23SRico Sonntag width: "100%", 229*66ce3d23SRico Sonntag vAxis: { 230*66ce3d23SRico Sonntag title: "" 231*66ce3d23SRico Sonntag }, 232*66ce3d23SRico Sonntag hAxis: { 233*66ce3d23SRico Sonntag title: "" 234*66ce3d23SRico Sonntag }, 235*66ce3d23SRico Sonntag legend: { 236*66ce3d23SRico Sonntag position: "none" 237*66ce3d23SRico Sonntag }, 238*66ce3d23SRico Sonntag backgroundColor: "transparent" 239*66ce3d23SRico Sonntag }; 240*66ce3d23SRico Sonntag 241*66ce3d23SRico Sonntag // Merge default with provided options 242*66ce3d23SRico Sonntag options = Object.assign(defaults, options); 243*66ce3d23SRico Sonntag 244*66ce3d23SRico Sonntag // Create and draw the chart 245*66ce3d23SRico Sonntag this.drawChart(elementId, "ColumnChart", data, options); 246*66ce3d23SRico Sonntag } 247*66ce3d23SRico Sonntag 248*66ce3d23SRico Sonntag /** 249*66ce3d23SRico Sonntag * Draws a combo chart. 250*66ce3d23SRico Sonntag * 251*66ce3d23SRico Sonntag * @param {String} elementId The element id of the HTML element the chart is rendered too 252*66ce3d23SRico Sonntag * @param {Array} data The chart data array 253*66ce3d23SRico Sonntag * @param {Object} options The chart specific options to overwrite the default ones 254*66ce3d23SRico Sonntag */ 255*66ce3d23SRico Sonntag drawComboChart(elementId, data, options) 256*66ce3d23SRico Sonntag { 257*66ce3d23SRico Sonntag // Default chart options 258*66ce3d23SRico Sonntag let defaults = { 259*66ce3d23SRico Sonntag title: "", 260*66ce3d23SRico Sonntag subtitle: "", 261*66ce3d23SRico Sonntag titleTextStyle: { 262*66ce3d23SRico Sonntag color: "#757575", 263*66ce3d23SRico Sonntag fontName: "Roboto", 264*66ce3d23SRico Sonntag fontSize: "16px", 265*66ce3d23SRico Sonntag bold: false, 266*66ce3d23SRico Sonntag italic: false 267*66ce3d23SRico Sonntag }, 268*66ce3d23SRico Sonntag height: "100%", 269*66ce3d23SRico Sonntag width: "100%", 270*66ce3d23SRico Sonntag vAxis: { 271*66ce3d23SRico Sonntag title: "" 272*66ce3d23SRico Sonntag }, 273*66ce3d23SRico Sonntag hAxis: { 274*66ce3d23SRico Sonntag title: "" 275*66ce3d23SRico Sonntag }, 276*66ce3d23SRico Sonntag legend: { 277*66ce3d23SRico Sonntag position: "none" 278*66ce3d23SRico Sonntag }, 279*66ce3d23SRico Sonntag seriesType: "bars", 280*66ce3d23SRico Sonntag series: { 281*66ce3d23SRico Sonntag 2: { 282*66ce3d23SRico Sonntag type: "line" 283*66ce3d23SRico Sonntag } 284*66ce3d23SRico Sonntag }, 285*66ce3d23SRico Sonntag colors: [], 286*66ce3d23SRico Sonntag backgroundColor: "transparent" 287*66ce3d23SRico Sonntag }; 288*66ce3d23SRico Sonntag 289*66ce3d23SRico Sonntag // Merge default with provided options 290*66ce3d23SRico Sonntag options = Object.assign(defaults, options); 291*66ce3d23SRico Sonntag 292*66ce3d23SRico Sonntag // Create and draw the chart 293*66ce3d23SRico Sonntag this.drawChart(elementId, "ComboChart", data, options); 294*66ce3d23SRico Sonntag } 295*66ce3d23SRico Sonntag 296*66ce3d23SRico Sonntag /** 297*66ce3d23SRico Sonntag * Draws a geo chart. 298*66ce3d23SRico Sonntag * 299*66ce3d23SRico Sonntag * @param {String} elementId The element id of the HTML element the chart is rendered too 300*66ce3d23SRico Sonntag * @param {Array} data The chart data array 301*66ce3d23SRico Sonntag * @param {Object} options The chart specific options to overwrite the default ones 302*66ce3d23SRico Sonntag */ 303*66ce3d23SRico Sonntag drawGeoChart(elementId, data, options) 304*66ce3d23SRico Sonntag { 305*66ce3d23SRico Sonntag // Default chart options 306*66ce3d23SRico Sonntag let defaults = { 307*66ce3d23SRico Sonntag title: "", 308*66ce3d23SRico Sonntag subtitle: "", 309*66ce3d23SRico Sonntag height: "100%", 310*66ce3d23SRico Sonntag width: "100%" 311*66ce3d23SRico Sonntag }; 312*66ce3d23SRico Sonntag 313*66ce3d23SRico Sonntag // Merge default with provided options 314*66ce3d23SRico Sonntag options = Object.assign(defaults, options); 315*66ce3d23SRico Sonntag 316*66ce3d23SRico Sonntag // Create and draw the chart 317*66ce3d23SRico Sonntag this.drawChart(elementId, "GeoChart", data, options); 318*66ce3d23SRico Sonntag } 319*66ce3d23SRico Sonntag} 320*66ce3d23SRico Sonntag 321*66ce3d23SRico Sonntag// Create singleton instance of class 322*66ce3d23SRico Sonntagconst statistics = new Statistics(); 323