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