xref: /webtrees/resources/js/statistics.js (revision d11be7027e34e3121be11cc025421873364403f9)
166ce3d23SRico Sonntag/**
266ce3d23SRico Sonntag * webtrees: online genealogy
3*d11be702SGreg Roach * Copyright (C) 2023 webtrees development team
466ce3d23SRico Sonntag * This program is free software: you can redistribute it and/or modify
566ce3d23SRico Sonntag * it under the terms of the GNU General Public License as published by
666ce3d23SRico Sonntag * the Free Software Foundation, either version 3 of the License, or
766ce3d23SRico Sonntag * (at your option) any later version.
866ce3d23SRico Sonntag * This program is distributed in the hope that it will be useful,
966ce3d23SRico Sonntag * but WITHOUT ANY WARRANTY; without even the implied warranty of
1066ce3d23SRico Sonntag * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1166ce3d23SRico Sonntag * GNU General Public License for more details.
1266ce3d23SRico Sonntag * You should have received a copy of the GNU General Public License
1366ce3d23SRico Sonntag * along with this program. If not, see <http://www.gnu.org/licenses/>.
1466ce3d23SRico Sonntag */
1566ce3d23SRico Sonntag
16efd89170SGreg Roach'use strict';
1766ce3d23SRico Sonntag
18efd89170SGreg Roachconst GOOGLE_CHARTS_LIB = 'https://www.gstatic.com/charts/loader.js';
1966ce3d23SRico Sonntag
2066ce3d23SRico Sonntag/**
2166ce3d23SRico Sonntag * Statistics class.
2266ce3d23SRico Sonntag */
23efd89170SGreg Roachclass Statistics {
2466ce3d23SRico Sonntag  /**
2566ce3d23SRico Sonntag   * Constructor.
2666ce3d23SRico Sonntag   *
2766ce3d23SRico Sonntag   * @returns {Statistics}
2866ce3d23SRico Sonntag     */
29efd89170SGreg Roach  constructor () {
3066ce3d23SRico Sonntag    // Create singleton instance
3166ce3d23SRico Sonntag    if (!Statistics.instance) {
3266ce3d23SRico Sonntag      Statistics.instance = this;
3366ce3d23SRico Sonntag
3466ce3d23SRico Sonntag      this.callbacks = [];
3566ce3d23SRico Sonntag      this.initialized = false;
3666ce3d23SRico Sonntag      this.loading = false;
3766ce3d23SRico Sonntag    }
3866ce3d23SRico Sonntag
3966ce3d23SRico Sonntag    return Statistics.instance;
4066ce3d23SRico Sonntag  }
4166ce3d23SRico Sonntag
4266ce3d23SRico Sonntag  /**
4366ce3d23SRico Sonntag   * Initializes the google chart engine. Loads the chart lib only once.
4466ce3d23SRico Sonntag   *
45220f62c2SGreg Roach   * @param {String} locale - Locale, e.g. en, de, ...
4666ce3d23SRico Sonntag   */
47efd89170SGreg Roach  init (locale) {
4866ce3d23SRico Sonntag    if (this.loading || this.initialized) {
4966ce3d23SRico Sonntag      return;
5066ce3d23SRico Sonntag    }
5166ce3d23SRico Sonntag
5266ce3d23SRico Sonntag    var that = this;
5366ce3d23SRico Sonntag
5466ce3d23SRico Sonntag    Promise.all([
5566ce3d23SRico Sonntag      this.load(GOOGLE_CHARTS_LIB)
56cb8f307bSGreg Roach    ]).then(() => {
5766ce3d23SRico Sonntag      google.charts.load(
58efd89170SGreg Roach        'current',
5966ce3d23SRico Sonntag        {
60efd89170SGreg Roach          packages: [
61efd89170SGreg Roach            'corechart',
62efd89170SGreg Roach            'geochart',
63efd89170SGreg Roach            'bar'
6466ce3d23SRico Sonntag          ],
65efd89170SGreg Roach          language: locale,
6666ce3d23SRico Sonntag          // Note: you will need to get a mapsApiKey for your project.
6766ce3d23SRico Sonntag          // See: https://developers.google.com/chart/interactive/docs/basic_load_libs#load-settings
68efd89170SGreg Roach          mapsApiKey: ''
6966ce3d23SRico Sonntag        }
7066ce3d23SRico Sonntag      );
7166ce3d23SRico Sonntag
7266ce3d23SRico Sonntag      google.charts.setOnLoadCallback(function () {
73cb8f307bSGreg Roach        that.callbacks.forEach((element) => {
7466ce3d23SRico Sonntag          element();
7566ce3d23SRico Sonntag        });
7666ce3d23SRico Sonntag      });
7766ce3d23SRico Sonntag
7866ce3d23SRico Sonntag      that.initialized = true;
79cb8f307bSGreg Roach    }).catch((error) => {
8066ce3d23SRico Sonntag      console.log(error);
8166ce3d23SRico Sonntag    });
8266ce3d23SRico Sonntag  }
8366ce3d23SRico Sonntag
8466ce3d23SRico Sonntag  /**
8566ce3d23SRico Sonntag   * Dynamically loads a script by the given URL.
8666ce3d23SRico Sonntag   *
8766ce3d23SRico Sonntag   * @param   {String} url
8866ce3d23SRico Sonntag   * @returns {Promise}
8966ce3d23SRico Sonntag   */
90efd89170SGreg Roach  load (url) {
9166ce3d23SRico Sonntag    if (this.loading) {
9266ce3d23SRico Sonntag      return;
9366ce3d23SRico Sonntag    }
9466ce3d23SRico Sonntag
9566ce3d23SRico Sonntag    this.loading = true;
9666ce3d23SRico Sonntag
9766ce3d23SRico Sonntag    return new Promise(function (resolve, reject) {
98efd89170SGreg Roach      const script = document.createElement('script');
9966ce3d23SRico Sonntag
10066ce3d23SRico Sonntag      script.async = true;
10166ce3d23SRico Sonntag      script.onload = function () {
10266ce3d23SRico Sonntag        resolve(url);
103efd89170SGreg Roach      };
10466ce3d23SRico Sonntag      script.onerror = function () {
10566ce3d23SRico Sonntag        reject(url);
106efd89170SGreg Roach      };
10766ce3d23SRico Sonntag
10866ce3d23SRico Sonntag      script.src = url;
10966ce3d23SRico Sonntag      document.body.appendChild(script);
11066ce3d23SRico Sonntag    });
11166ce3d23SRico Sonntag  }
11266ce3d23SRico Sonntag
11366ce3d23SRico Sonntag  /**
11466ce3d23SRico Sonntag   * Adds the given callback method to the callback stack or add it directly to
11566ce3d23SRico Sonntag   * the google charts interface once the chart engine is up and running.
11666ce3d23SRico Sonntag   *
11766ce3d23SRico Sonntag   * @param {Function} callback
11866ce3d23SRico Sonntag   */
119efd89170SGreg Roach  addCallback (callback) {
12066ce3d23SRico Sonntag    if (this.initialized) {
12166ce3d23SRico Sonntag      google.charts.setOnLoadCallback(callback);
12266ce3d23SRico Sonntag    } else {
12366ce3d23SRico Sonntag      this.callbacks.push(callback);
12466ce3d23SRico Sonntag    }
12566ce3d23SRico Sonntag
12666ce3d23SRico Sonntag    $(window).resize(function () {
12766ce3d23SRico Sonntag      callback();
12866ce3d23SRico Sonntag    });
12966ce3d23SRico Sonntag  }
13066ce3d23SRico Sonntag
13166ce3d23SRico Sonntag  /**
13266ce3d23SRico Sonntag   * Draws a google chart.
13366ce3d23SRico Sonntag   *
13466ce3d23SRico Sonntag   * @param {String} containerId
13566ce3d23SRico Sonntag   * @param {String} chartType
136220f62c2SGreg Roach   * @param {Array}  data
13766ce3d23SRico Sonntag   * @param {Object} options
13866ce3d23SRico Sonntag   */
139efd89170SGreg Roach  drawChart (containerId, chartType, data, options) {
140efd89170SGreg Roach    const dataTable = google.visualization.arrayToDataTable(data);
14166ce3d23SRico Sonntag
142efd89170SGreg Roach    const wrapper = new google.visualization.ChartWrapper({
14366ce3d23SRico Sonntag      chartType: chartType,
14466ce3d23SRico Sonntag      dataTable: dataTable,
14566ce3d23SRico Sonntag      options: options,
14666ce3d23SRico Sonntag      containerId: containerId
14766ce3d23SRico Sonntag    });
14866ce3d23SRico Sonntag
14966ce3d23SRico Sonntag    wrapper.draw();
15066ce3d23SRico Sonntag  }
15166ce3d23SRico Sonntag
15266ce3d23SRico Sonntag  /**
15366ce3d23SRico Sonntag   * Draws a pie chart.
15466ce3d23SRico Sonntag   *
155220f62c2SGreg Roach   * @param {String} elementId - The element id of the HTML element the chart is rendered too
156220f62c2SGreg Roach   * @param {Array}  data      - The chart data array
157220f62c2SGreg Roach   * @param {Object} options   - The chart specific options to overwrite the default ones
15866ce3d23SRico Sonntag   */
159efd89170SGreg Roach  drawPieChart (elementId, data, options) {
16066ce3d23SRico Sonntag    // Default chart options
161efd89170SGreg Roach    const defaults = {
162efd89170SGreg Roach      title: '',
163efd89170SGreg Roach      height: '100%',
164efd89170SGreg Roach      width: '100%',
16566ce3d23SRico Sonntag      pieStartAngle: 0,
166efd89170SGreg Roach      pieSliceText: 'none',
16766ce3d23SRico Sonntag      pieSliceTextStyle: {
168efd89170SGreg Roach        color: '#777'
16966ce3d23SRico Sonntag      },
17066ce3d23SRico Sonntag      pieHole: 0.4, // Donut
17166ce3d23SRico Sonntag      // is3D: true,  // 3D (not together with pieHole)
17266ce3d23SRico Sonntag      legend: {
173efd89170SGreg Roach        alignment: 'center',
17466ce3d23SRico Sonntag        // Flickers on mouseover :(
175efd89170SGreg Roach        labeledValueText: 'value',
176efd89170SGreg Roach        position: 'labeled'
17766ce3d23SRico Sonntag      },
17866ce3d23SRico Sonntag      chartArea: {
17966ce3d23SRico Sonntag        left: 0,
180efd89170SGreg Roach        top: '5%',
181efd89170SGreg Roach        height: '90%',
182efd89170SGreg Roach        width: '100%'
18366ce3d23SRico Sonntag      },
18466ce3d23SRico Sonntag      tooltip: {
185efd89170SGreg Roach        trigger: 'none',
186efd89170SGreg Roach        text: 'both'
18766ce3d23SRico Sonntag      },
188efd89170SGreg Roach      backgroundColor: 'transparent',
18966ce3d23SRico Sonntag      colors: []
19066ce3d23SRico Sonntag    };
19166ce3d23SRico Sonntag
19266ce3d23SRico Sonntag    // Merge default with provided options
19366ce3d23SRico Sonntag    options = Object.assign(defaults, options);
19466ce3d23SRico Sonntag
19566ce3d23SRico Sonntag    // Create and draw the chart
196efd89170SGreg Roach    this.drawChart(elementId, 'PieChart', data, options);
19766ce3d23SRico Sonntag  }
19866ce3d23SRico Sonntag
19966ce3d23SRico Sonntag  /**
20066ce3d23SRico Sonntag   * Draws a column chart.
20166ce3d23SRico Sonntag   *
202220f62c2SGreg Roach   * @param {String} elementId - The element id of the HTML element the chart is rendered too
203220f62c2SGreg Roach   * @param {Array}  data      - The chart data array
204220f62c2SGreg Roach   * @param {Object} options   - The chart specific options to overwrite the default ones
20566ce3d23SRico Sonntag   */
206efd89170SGreg Roach  drawColumnChart (elementId, data, options) {
20766ce3d23SRico Sonntag    // Default chart options
208efd89170SGreg Roach    const defaults = {
209efd89170SGreg Roach      title: '',
210efd89170SGreg Roach      subtitle: '',
21166ce3d23SRico Sonntag      titleTextStyle: {
212efd89170SGreg Roach        color: '#757575',
213efd89170SGreg Roach        fontName: 'Roboto',
214efd89170SGreg Roach        fontSize: '16px',
21566ce3d23SRico Sonntag        bold: false,
21666ce3d23SRico Sonntag        italic: false
21766ce3d23SRico Sonntag      },
218efd89170SGreg Roach      height: '100%',
219efd89170SGreg Roach      width: '100%',
22066ce3d23SRico Sonntag      vAxis: {
221efd89170SGreg Roach        title: ''
22266ce3d23SRico Sonntag      },
22366ce3d23SRico Sonntag      hAxis: {
224efd89170SGreg Roach        title: ''
22566ce3d23SRico Sonntag      },
22666ce3d23SRico Sonntag      legend: {
227efd89170SGreg Roach        position: 'none'
22866ce3d23SRico Sonntag      },
229efd89170SGreg Roach      backgroundColor: 'transparent'
23066ce3d23SRico Sonntag    };
23166ce3d23SRico Sonntag
23266ce3d23SRico Sonntag    // Merge default with provided options
23366ce3d23SRico Sonntag    options = Object.assign(defaults, options);
23466ce3d23SRico Sonntag
23566ce3d23SRico Sonntag    // Create and draw the chart
236efd89170SGreg Roach    this.drawChart(elementId, 'ColumnChart', data, options);
23766ce3d23SRico Sonntag  }
23866ce3d23SRico Sonntag
23966ce3d23SRico Sonntag  /**
24066ce3d23SRico Sonntag   * Draws a combo chart.
24166ce3d23SRico Sonntag   *
242220f62c2SGreg Roach   * @param {String} elementId - The element id of the HTML element the chart is rendered too
243220f62c2SGreg Roach   * @param {Array}  data      - The chart data array
244220f62c2SGreg Roach   * @param {Object} options   - The chart specific options to overwrite the default ones
24566ce3d23SRico Sonntag   */
246efd89170SGreg Roach  drawComboChart (elementId, data, options) {
24766ce3d23SRico Sonntag    // Default chart options
248efd89170SGreg Roach    const defaults = {
249efd89170SGreg Roach      title: '',
250efd89170SGreg Roach      subtitle: '',
25166ce3d23SRico Sonntag      titleTextStyle: {
252efd89170SGreg Roach        color: '#757575',
253efd89170SGreg Roach        fontName: 'Roboto',
254efd89170SGreg Roach        fontSize: '16px',
25566ce3d23SRico Sonntag        bold: false,
25666ce3d23SRico Sonntag        italic: false
25766ce3d23SRico Sonntag      },
258efd89170SGreg Roach      height: '100%',
259efd89170SGreg Roach      width: '100%',
26066ce3d23SRico Sonntag      vAxis: {
261efd89170SGreg Roach        title: ''
26266ce3d23SRico Sonntag      },
26366ce3d23SRico Sonntag      hAxis: {
264efd89170SGreg Roach        title: ''
26566ce3d23SRico Sonntag      },
26666ce3d23SRico Sonntag      legend: {
267efd89170SGreg Roach        position: 'none'
26866ce3d23SRico Sonntag      },
269efd89170SGreg Roach      seriesType: 'bars',
27066ce3d23SRico Sonntag      series: {
27166ce3d23SRico Sonntag        2: {
272efd89170SGreg Roach          type: 'line'
27366ce3d23SRico Sonntag        }
27466ce3d23SRico Sonntag      },
27566ce3d23SRico Sonntag      colors: [],
276efd89170SGreg Roach      backgroundColor: 'transparent'
27766ce3d23SRico Sonntag    };
27866ce3d23SRico Sonntag
27966ce3d23SRico Sonntag    // Merge default with provided options
28066ce3d23SRico Sonntag    options = Object.assign(defaults, options);
28166ce3d23SRico Sonntag
28266ce3d23SRico Sonntag    // Create and draw the chart
283efd89170SGreg Roach    this.drawChart(elementId, 'ComboChart', data, options);
28466ce3d23SRico Sonntag  }
28566ce3d23SRico Sonntag
28666ce3d23SRico Sonntag  /**
28766ce3d23SRico Sonntag     * Draws a geo chart.
28866ce3d23SRico Sonntag     *
289220f62c2SGreg Roach     * @param {String} elementId - The element id of the HTML element the chart is rendered too
290220f62c2SGreg Roach     * @param {Array}  data      - The chart data array
291220f62c2SGreg Roach     * @param {Object} options   - The chart specific options to overwrite the default ones
29266ce3d23SRico Sonntag     */
293efd89170SGreg Roach  drawGeoChart (elementId, data, options) {
29466ce3d23SRico Sonntag    // Default chart options
295efd89170SGreg Roach    const defaults = {
296efd89170SGreg Roach      title: '',
297efd89170SGreg Roach      subtitle: '',
298efd89170SGreg Roach      height: '100%',
299efd89170SGreg Roach      width: '100%'
30066ce3d23SRico Sonntag    };
30166ce3d23SRico Sonntag
30266ce3d23SRico Sonntag    // Merge default with provided options
30366ce3d23SRico Sonntag    options = Object.assign(defaults, options);
30466ce3d23SRico Sonntag
30566ce3d23SRico Sonntag    // Create and draw the chart
306efd89170SGreg Roach    this.drawChart(elementId, 'GeoChart', data, options);
30766ce3d23SRico Sonntag  }
30866ce3d23SRico Sonntag}
30966ce3d23SRico Sonntag
31066ce3d23SRico Sonntag// Create singleton instance of class
31166ce3d23SRico Sonntagconst statistics = new Statistics();
312