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