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