xref: /webtrees/resources/js/statistics.js (revision 251905c892156684514326dd10657143b6ab5ca6)
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