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