Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/Remmina/Remmina.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAntenore Gatta <antenore@simbiosi.org>2019-01-31 01:34:22 +0300
committerAntenore Gatta <antenore@simbiosi.org>2019-01-31 01:34:22 +0300
commit240b97082f4ce0e29cd7c560ba2e5617b55d9297 (patch)
tree87ff6ee1fbfc63fc2d88a66b12d4079f2709c7c9
parent68df8e495b99ebe8202f2cd94c49d672e175db8c (diff)
Remmina Translations Statistics
-rw-r--r--data/reports/chartkick.min.js2214
-rw-r--r--data/reports/postats.html49
-rwxr-xr-xscripts/i18nstats.sh248
3 files changed, 2511 insertions, 0 deletions
diff --git a/data/reports/chartkick.min.js b/data/reports/chartkick.min.js
new file mode 100644
index 000000000..7bea1f941
--- /dev/null
+++ b/data/reports/chartkick.min.js
@@ -0,0 +1,2214 @@
+/*
+ * Chartkick.js
+ * Create beautiful charts with one line of JavaScript
+ * https://github.com/ankane/chartkick.js
+ * v3.0.1
+ * MIT License
+ */
+
+(function (global, factory) {
+ typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
+ typeof define === 'function' && define.amd ? define(factory) :
+ (global.Chartkick = factory());
+}(this, (function () { 'use strict';
+
+ function isArray(variable) {
+ return Object.prototype.toString.call(variable) === "[object Array]";
+ }
+
+ function isFunction(variable) {
+ return variable instanceof Function;
+ }
+
+ function isPlainObject(variable) {
+ return !isFunction(variable) && variable instanceof Object;
+ }
+
+ // https://github.com/madrobby/zepto/blob/master/src/zepto.js
+ function extend(target, source) {
+ var key;
+ for (key in source) {
+ if (isPlainObject(source[key]) || isArray(source[key])) {
+ if (isPlainObject(source[key]) && !isPlainObject(target[key])) {
+ target[key] = {};
+ }
+ if (isArray(source[key]) && !isArray(target[key])) {
+ target[key] = [];
+ }
+ extend(target[key], source[key]);
+ } else if (source[key] !== undefined) {
+ target[key] = source[key];
+ }
+ }
+ }
+
+ function merge(obj1, obj2) {
+ var target = {};
+ extend(target, obj1);
+ extend(target, obj2);
+ return target;
+ }
+
+ var DATE_PATTERN = /^(\d\d\d\d)(-)?(\d\d)(-)?(\d\d)$/i;
+
+ // https://github.com/Do/iso8601.js
+ var ISO8601_PATTERN = /(\d\d\d\d)(-)?(\d\d)(-)?(\d\d)(T)?(\d\d)(:)?(\d\d)?(:)?(\d\d)?([.,]\d+)?($|Z|([+-])(\d\d)(:)?(\d\d)?)/i;
+ var DECIMAL_SEPARATOR = String(1.5).charAt(1);
+
+ function parseISO8601(input) {
+ var day, hour, matches, milliseconds, minutes, month, offset, result, seconds, type, year;
+ type = Object.prototype.toString.call(input);
+ if (type === "[object Date]") {
+ return input;
+ }
+ if (type !== "[object String]") {
+ return;
+ }
+ matches = input.match(ISO8601_PATTERN);
+ if (matches) {
+ year = parseInt(matches[1], 10);
+ month = parseInt(matches[3], 10) - 1;
+ day = parseInt(matches[5], 10);
+ hour = parseInt(matches[7], 10);
+ minutes = matches[9] ? parseInt(matches[9], 10) : 0;
+ seconds = matches[11] ? parseInt(matches[11], 10) : 0;
+ milliseconds = matches[12] ? parseFloat(DECIMAL_SEPARATOR + matches[12].slice(1)) * 1000 : 0;
+ result = Date.UTC(year, month, day, hour, minutes, seconds, milliseconds);
+ if (matches[13] && matches[14]) {
+ offset = matches[15] * 60;
+ if (matches[17]) {
+ offset += parseInt(matches[17], 10);
+ }
+ offset *= matches[14] === "-" ? -1 : 1;
+ result -= offset * 60 * 1000;
+ }
+ return new Date(result);
+ }
+ }
+ // end iso8601.js
+
+ function negativeValues(series) {
+ var i, j, data;
+ for (i = 0; i < series.length; i++) {
+ data = series[i].data;
+ for (j = 0; j < data.length; j++) {
+ if (data[j][1] < 0) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ function toStr(n) {
+ return "" + n;
+ }
+
+ function toFloat(n) {
+ return parseFloat(n);
+ }
+
+ function toDate(n) {
+ var matches, year, month, day;
+ if (typeof n !== "object") {
+ if (typeof n === "number") {
+ n = new Date(n * 1000); // ms
+ } else {
+ n = toStr(n);
+ if ((matches = n.match(DATE_PATTERN))) {
+ year = parseInt(matches[1], 10);
+ month = parseInt(matches[3], 10) - 1;
+ day = parseInt(matches[5], 10);
+ return new Date(year, month, day);
+ } else { // str
+ // try our best to get the str into iso8601
+ // TODO be smarter about this
+ var str = n.replace(/ /, "T").replace(" ", "").replace("UTC", "Z");
+ n = parseISO8601(str) || new Date(n);
+ }
+ }
+ }
+ return n;
+ }
+
+ function toArr(n) {
+ if (!isArray(n)) {
+ var arr = [], i;
+ for (i in n) {
+ if (n.hasOwnProperty(i)) {
+ arr.push([i, n[i]]);
+ }
+ }
+ n = arr;
+ }
+ return n;
+ }
+
+ function jsOptionsFunc(defaultOptions, hideLegend, setTitle, setMin, setMax, setStacked, setXtitle, setYtitle) {
+ return function (chart, opts, chartOptions) {
+ var series = chart.data;
+ var options = merge({}, defaultOptions);
+ options = merge(options, chartOptions || {});
+
+ if (chart.hideLegend || "legend" in opts) {
+ hideLegend(options, opts.legend, chart.hideLegend);
+ }
+
+ if (opts.title) {
+ setTitle(options, opts.title);
+ }
+
+ // min
+ if ("min" in opts) {
+ setMin(options, opts.min);
+ } else if (!negativeValues(series)) {
+ setMin(options, 0);
+ }
+
+ // max
+ if (opts.max) {
+ setMax(options, opts.max);
+ }
+
+ if ("stacked" in opts) {
+ setStacked(options, opts.stacked);
+ }
+
+ if (opts.colors) {
+ options.colors = opts.colors;
+ }
+
+ if (opts.xtitle) {
+ setXtitle(options, opts.xtitle);
+ }
+
+ if (opts.ytitle) {
+ setYtitle(options, opts.ytitle);
+ }
+
+ // merge library last
+ options = merge(options, opts.library || {});
+
+ return options;
+ };
+ }
+
+ function sortByTime(a, b) {
+ return a[0].getTime() - b[0].getTime();
+ }
+
+ function sortByNumberSeries(a, b) {
+ return a[0] - b[0];
+ }
+
+ function sortByNumber(a, b) {
+ return a - b;
+ }
+
+ function isMinute(d) {
+ return d.getMilliseconds() === 0 && d.getSeconds() === 0;
+ }
+
+ function isHour(d) {
+ return isMinute(d) && d.getMinutes() === 0;
+ }
+
+ function isDay(d) {
+ return isHour(d) && d.getHours() === 0;
+ }
+
+ function isWeek(d, dayOfWeek) {
+ return isDay(d) && d.getDay() === dayOfWeek;
+ }
+
+ function isMonth(d) {
+ return isDay(d) && d.getDate() === 1;
+ }
+
+ function isYear(d) {
+ return isMonth(d) && d.getMonth() === 0;
+ }
+
+ function isDate(obj) {
+ return !isNaN(toDate(obj)) && toStr(obj).length >= 6;
+ }
+
+ function isNumber(obj) {
+ return typeof obj === "number";
+ }
+
+ function formatValue(pre, value, options) {
+ pre = pre || "";
+ if (options.prefix) {
+ if (value < 0) {
+ value = value * -1;
+ pre += "-";
+ }
+ pre += options.prefix;
+ }
+
+ if (options.thousands || options.decimal) {
+ value = toStr(value);
+ var parts = value.split(".");
+ value = parts[0];
+ if (options.thousands) {
+ value = value.replace(/\B(?=(\d{3})+(?!\d))/g, options.thousands);
+ }
+ if (parts.length > 1) {
+ value += (options.decimal || ".") + parts[1];
+ }
+ }
+
+ return pre + value + (options.suffix || "");
+ }
+
+ function allZeros(data) {
+ var i, j, d;
+ for (i = 0; i < data.length; i++) {
+ d = data[i].data;
+ for (j = 0; j < d.length; j++) {
+ if (d[j][1] != 0) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ var baseOptions = {
+ maintainAspectRatio: false,
+ animation: false,
+ tooltips: {
+ displayColors: false,
+ callbacks: {}
+ },
+ legend: {},
+ title: {fontSize: 20, fontColor: "#333"}
+ };
+
+ var defaultOptions = {
+ scales: {
+ yAxes: [
+ {
+ ticks: {
+ maxTicksLimit: 4
+ },
+ scaleLabel: {
+ fontSize: 16,
+ // fontStyle: "bold",
+ fontColor: "#333"
+ }
+ }
+ ],
+ xAxes: [
+ {
+ gridLines: {
+ drawOnChartArea: false
+ },
+ scaleLabel: {
+ fontSize: 16,
+ // fontStyle: "bold",
+ fontColor: "#333"
+ },
+ time: {},
+ ticks: {}
+ }
+ ]
+ }
+ };
+
+ // http://there4.io/2012/05/02/google-chart-color-list/
+ var defaultColors = [
+ "#3366CC", "#DC3912", "#FF9900", "#109618", "#990099", "#3B3EAC", "#0099C6",
+ "#DD4477", "#66AA00", "#B82E2E", "#316395", "#994499", "#22AA99", "#AAAA11",
+ "#6633CC", "#E67300", "#8B0707", "#329262", "#5574A6", "#651067"
+ ];
+
+ var hideLegend = function (options, legend, hideLegend) {
+ if (legend !== undefined) {
+ options.legend.display = !!legend;
+ if (legend && legend !== true) {
+ options.legend.position = legend;
+ }
+ } else if (hideLegend) {
+ options.legend.display = false;
+ }
+ };
+
+ var setTitle = function (options, title) {
+ options.title.display = true;
+ options.title.text = title;
+ };
+
+ var setMin = function (options, min) {
+ if (min !== null) {
+ options.scales.yAxes[0].ticks.min = toFloat(min);
+ }
+ };
+
+ var setMax = function (options, max) {
+ options.scales.yAxes[0].ticks.max = toFloat(max);
+ };
+
+ var setBarMin = function (options, min) {
+ if (min !== null) {
+ options.scales.xAxes[0].ticks.min = toFloat(min);
+ }
+ };
+
+ var setBarMax = function (options, max) {
+ options.scales.xAxes[0].ticks.max = toFloat(max);
+ };
+
+ var setStacked = function (options, stacked) {
+ options.scales.xAxes[0].stacked = !!stacked;
+ options.scales.yAxes[0].stacked = !!stacked;
+ };
+
+ var setXtitle = function (options, title) {
+ options.scales.xAxes[0].scaleLabel.display = true;
+ options.scales.xAxes[0].scaleLabel.labelString = title;
+ };
+
+ var setYtitle = function (options, title) {
+ options.scales.yAxes[0].scaleLabel.display = true;
+ options.scales.yAxes[0].scaleLabel.labelString = title;
+ };
+
+ // https://stackoverflow.com/questions/5623838/rgb-to-hex-and-hex-to-rgb
+ var addOpacity = function(hex, opacity) {
+ var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
+ return result ? "rgba(" + parseInt(result[1], 16) + ", " + parseInt(result[2], 16) + ", " + parseInt(result[3], 16) + ", " + opacity + ")" : hex;
+ };
+
+ var setLabelSize = function (chart, data, options) {
+ var maxLabelSize = Math.ceil(chart.element.offsetWidth / 4.0 / data.labels.length);
+ if (maxLabelSize > 25) {
+ maxLabelSize = 25;
+ } else if (maxLabelSize < 10) {
+ maxLabelSize = 10;
+ }
+ if (!options.scales.xAxes[0].ticks.callback) {
+ options.scales.xAxes[0].ticks.callback = function (value) {
+ value = toStr(value);
+ if (value.length > maxLabelSize) {
+ return value.substring(0, maxLabelSize - 2) + "...";
+ } else {
+ return value;
+ }
+ };
+ }
+ };
+
+ var setFormatOptions = function(chart, options, chartType) {
+ var formatOptions = {
+ prefix: chart.options.prefix,
+ suffix: chart.options.suffix,
+ thousands: chart.options.thousands,
+ decimal: chart.options.decimal
+ };
+
+ if (chartType !== "pie") {
+ var myAxes = options.scales.yAxes;
+ if (chartType === "bar") {
+ myAxes = options.scales.xAxes;
+ }
+
+ if (!myAxes[0].ticks.callback) {
+ myAxes[0].ticks.callback = function (value) {
+ return formatValue("", value, formatOptions);
+ };
+ }
+ }
+
+ if (!options.tooltips.callbacks.label) {
+ if (chartType === "scatter") {
+ options.tooltips.callbacks.label = function (item, data) {
+ var label = data.datasets[item.datasetIndex].label || '';
+ if (label) {
+ label += ': ';
+ }
+ return label + '(' + item.xLabel + ', ' + item.yLabel + ')';
+ };
+ } else if (chartType === "bubble") {
+ options.tooltips.callbacks.label = function (item, data) {
+ var label = data.datasets[item.datasetIndex].label || '';
+ if (label) {
+ label += ': ';
+ }
+ var dataPoint = data.datasets[item.datasetIndex].data[item.index];
+ return label + '(' + item.xLabel + ', ' + item.yLabel + ', ' + dataPoint.v + ')';
+ };
+ } else if (chartType === "pie") {
+ // need to use separate label for pie charts
+ options.tooltips.callbacks.label = function (tooltipItem, data) {
+ var dataLabel = data.labels[tooltipItem.index];
+ var value = ': ';
+
+ if (isArray(dataLabel)) {
+ // show value on first line of multiline label
+ // need to clone because we are changing the value
+ dataLabel = dataLabel.slice();
+ dataLabel[0] += value;
+ } else {
+ dataLabel += value;
+ }
+
+ return formatValue(dataLabel, data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index], formatOptions);
+ };
+ } else {
+ var valueLabel = chartType === "bar" ? "xLabel" : "yLabel";
+ options.tooltips.callbacks.label = function (tooltipItem, data) {
+ var label = data.datasets[tooltipItem.datasetIndex].label || '';
+ if (label) {
+ label += ': ';
+ }
+ return formatValue(label, tooltipItem[valueLabel], formatOptions);
+ };
+ }
+ }
+ };
+
+ var jsOptions = jsOptionsFunc(merge(baseOptions, defaultOptions), hideLegend, setTitle, setMin, setMax, setStacked, setXtitle, setYtitle);
+
+ var createDataTable = function (chart, options, chartType) {
+ var datasets = [];
+ var labels = [];
+
+ var colors = chart.options.colors || defaultColors;
+
+ var day = true;
+ var week = true;
+ var dayOfWeek;
+ var month = true;
+ var year = true;
+ var hour = true;
+ var minute = true;
+
+ var series = chart.data;
+
+ var max = 0;
+ if (chartType === "bubble") {
+ for (var i$1 = 0; i$1 < series.length; i$1++) {
+ var s$1 = series[i$1];
+ for (var j$1 = 0; j$1 < s$1.data.length; j$1++) {
+ if (s$1.data[j$1][2] > max) {
+ max = s$1.data[j$1][2];
+ }
+ }
+ }
+ }
+
+ var i, j, s, d, key, rows = [], rows2 = [];
+
+ if (chartType === "bar" || chartType === "column" || (chart.xtype !== "number" && chart.xtype !== "bubble")) {
+ var sortedLabels = [];
+
+ for (i = 0; i < series.length; i++) {
+ s = series[i];
+
+ for (j = 0; j < s.data.length; j++) {
+ d = s.data[j];
+ key = chart.xtype == "datetime" ? d[0].getTime() : d[0];
+ if (!rows[key]) {
+ rows[key] = new Array(series.length);
+ }
+ rows[key][i] = toFloat(d[1]);
+ if (sortedLabels.indexOf(key) === -1) {
+ sortedLabels.push(key);
+ }
+ }
+ }
+
+ if (chart.xtype === "datetime" || chart.xtype === "number") {
+ sortedLabels.sort(sortByNumber);
+ }
+
+ for (j = 0; j < series.length; j++) {
+ rows2.push([]);
+ }
+
+ var value;
+ var k;
+ for (k = 0; k < sortedLabels.length; k++) {
+ i = sortedLabels[k];
+ if (chart.xtype === "datetime") {
+ value = new Date(toFloat(i));
+ // TODO make this efficient
+ day = day && isDay(value);
+ if (!dayOfWeek) {
+ dayOfWeek = value.getDay();
+ }
+ week = week && isWeek(value, dayOfWeek);
+ month = month && isMonth(value);
+ year = year && isYear(value);
+ hour = hour && isHour(value);
+ minute = minute && isMinute(value);
+ } else {
+ value = i;
+ }
+ labels.push(value);
+ for (j = 0; j < series.length; j++) {
+ // Chart.js doesn't like undefined
+ rows2[j].push(rows[i][j] === undefined ? null : rows[i][j]);
+ }
+ }
+ } else {
+ for (var i$2 = 0; i$2 < series.length; i$2++) {
+ var s$2 = series[i$2];
+ var d$1 = [];
+ for (var j$2 = 0; j$2 < s$2.data.length; j$2++) {
+ var point = {
+ x: toFloat(s$2.data[j$2][0]),
+ y: toFloat(s$2.data[j$2][1])
+ };
+ if (chartType === "bubble") {
+ point.r = toFloat(s$2.data[j$2][2]) * 20 / max;
+ // custom attribute, for tooltip
+ point.v = s$2.data[j$2][2];
+ }
+ d$1.push(point);
+ }
+ rows2.push(d$1);
+ }
+ }
+
+ for (i = 0; i < series.length; i++) {
+ s = series[i];
+
+ var color = s.color || colors[i];
+ var backgroundColor = chartType !== "line" ? addOpacity(color, 0.5) : color;
+
+ var dataset = {
+ label: s.name || "",
+ data: rows2[i],
+ fill: chartType === "area",
+ borderColor: color,
+ backgroundColor: backgroundColor,
+ pointBackgroundColor: color,
+ borderWidth: 2,
+ pointHoverBackgroundColor: color
+ };
+
+ if (s.stack) {
+ dataset.stack = s.stack;
+ }
+
+ if (chart.options.curve === false) {
+ dataset.lineTension = 0;
+ }
+
+ if (chart.options.points === false) {
+ dataset.pointRadius = 0;
+ dataset.pointHitRadius = 5;
+ }
+
+ dataset = merge(dataset, chart.options.dataset || {});
+ dataset = merge(dataset, s.library || {});
+ dataset = merge(dataset, s.dataset || {});
+
+ datasets.push(dataset);
+ }
+
+ if (chart.xtype === "datetime" && labels.length > 0) {
+ var minTime = labels[0].getTime();
+ var maxTime = labels[0].getTime();
+ for (i = 1; i < labels.length; i++) {
+ var value$1 = labels[i].getTime();
+ if (value$1 < minTime) {
+ minTime = value$1;
+ }
+ if (value$1 > maxTime) {
+ maxTime = value$1;
+ }
+ }
+
+ var timeDiff = (maxTime - minTime) / (86400 * 1000.0);
+
+ if (!options.scales.xAxes[0].time.unit) {
+ var step;
+ if (year || timeDiff > 365 * 10) {
+ options.scales.xAxes[0].time.unit = "year";
+ step = 365;
+ } else if (month || timeDiff > 30 * 10) {
+ options.scales.xAxes[0].time.unit = "month";
+ step = 30;
+ } else if (day || timeDiff > 10) {
+ options.scales.xAxes[0].time.unit = "day";
+ step = 1;
+ } else if (hour || timeDiff > 0.5) {
+ options.scales.xAxes[0].time.displayFormats = {hour: "MMM D, h a"};
+ options.scales.xAxes[0].time.unit = "hour";
+ step = 1 / 24.0;
+ } else if (minute) {
+ options.scales.xAxes[0].time.displayFormats = {minute: "h:mm a"};
+ options.scales.xAxes[0].time.unit = "minute";
+ step = 1 / 24.0 / 60.0;
+ }
+
+ if (step && timeDiff > 0) {
+ var unitStepSize = Math.ceil(timeDiff / step / (chart.element.offsetWidth / 100.0));
+ if (week && step === 1) {
+ unitStepSize = Math.ceil(unitStepSize / 7.0) * 7;
+ }
+ options.scales.xAxes[0].time.unitStepSize = unitStepSize;
+ }
+ }
+
+ if (!options.scales.xAxes[0].time.tooltipFormat) {
+ if (day) {
+ options.scales.xAxes[0].time.tooltipFormat = "ll";
+ } else if (hour) {
+ options.scales.xAxes[0].time.tooltipFormat = "MMM D, h a";
+ } else if (minute) {
+ options.scales.xAxes[0].time.tooltipFormat = "h:mm a";
+ }
+ }
+ }
+
+ var data = {
+ labels: labels,
+ datasets: datasets
+ };
+
+ return data;
+ };
+
+ var defaultExport = function defaultExport(library) {
+ this.name = "chartjs";
+ this.library = library;
+ };
+
+ defaultExport.prototype.renderLineChart = function renderLineChart (chart, chartType) {
+ var chartOptions = {};
+ // fix for https://github.com/chartjs/Chart.js/issues/2441
+ if (!chart.options.max && allZeros(chart.data)) {
+ chartOptions.max = 1;
+ }
+
+ var options = jsOptions(chart, merge(chartOptions, chart.options));
+ setFormatOptions(chart, options, chartType);
+
+ var data = createDataTable(chart, options, chartType || "line");
+
+ if (chart.xtype === "number") {
+ options.scales.xAxes[0].type = "linear";
+ options.scales.xAxes[0].position = "bottom";
+ } else {
+ options.scales.xAxes[0].type = chart.xtype === "string" ? "category" : "time";
+ }
+
+ this.drawChart(chart, "line", data, options);
+ };
+
+ defaultExport.prototype.renderPieChart = function renderPieChart (chart) {
+ var options = merge({}, baseOptions);
+ if (chart.options.donut) {
+ options.cutoutPercentage = 50;
+ }
+
+ if ("legend" in chart.options) {
+ hideLegend(options, chart.options.legend);
+ }
+
+ if (chart.options.title) {
+ setTitle(options, chart.options.title);
+ }
+
+ options = merge(options, chart.options.library || {});
+ setFormatOptions(chart, options, "pie");
+
+ var labels = [];
+ var values = [];
+ for (var i = 0; i < chart.data.length; i++) {
+ var point = chart.data[i];
+ labels.push(point[0]);
+ values.push(point[1]);
+ }
+
+ var dataset = {
+ data: values,
+ backgroundColor: chart.options.colors || defaultColors
+ };
+ dataset = merge(dataset, chart.options.dataset || {});
+
+ var data = {
+ labels: labels,
+ datasets: [dataset]
+ };
+
+ this.drawChart(chart, "pie", data, options);
+ };
+
+ defaultExport.prototype.renderColumnChart = function renderColumnChart (chart, chartType) {
+ var options;
+ if (chartType === "bar") {
+ options = jsOptionsFunc(merge(baseOptions, defaultOptions), hideLegend, setTitle, setBarMin, setBarMax, setStacked, setXtitle, setYtitle)(chart, chart.options);
+ } else {
+ options = jsOptions(chart, chart.options);
+ }
+ setFormatOptions(chart, options, chartType);
+ var data = createDataTable(chart, options, "column");
+ if (chartType !== "bar") {
+ setLabelSize(chart, data, options);
+ }
+ this.drawChart(chart, (chartType === "bar" ? "horizontalBar" : "bar"), data, options);
+ };
+
+ defaultExport.prototype.renderAreaChart = function renderAreaChart (chart) {
+ this.renderLineChart(chart, "area");
+ };
+
+ defaultExport.prototype.renderBarChart = function renderBarChart (chart) {
+ this.renderColumnChart(chart, "bar");
+ };
+
+ defaultExport.prototype.renderScatterChart = function renderScatterChart (chart, chartType) {
+ chartType = chartType || "scatter";
+
+ var options = jsOptions(chart, chart.options);
+ setFormatOptions(chart, options, chartType);
+
+ if (!("showLines" in options)) {
+ options.showLines = false;
+ }
+
+ var data = createDataTable(chart, options, chartType);
+
+ options.scales.xAxes[0].type = "linear";
+ options.scales.xAxes[0].position = "bottom";
+
+ this.drawChart(chart, chartType, data, options);
+ };
+
+ defaultExport.prototype.renderBubbleChart = function renderBubbleChart (chart) {
+ this.renderScatterChart(chart, "bubble");
+ };
+
+ defaultExport.prototype.destroy = function destroy (chart) {
+ if (chart.chart) {
+ chart.chart.destroy();
+ }
+ };
+
+ defaultExport.prototype.drawChart = function drawChart (chart, type, data, options) {
+ this.destroy(chart);
+
+ var chartOptions = {
+ type: type,
+ data: data,
+ options: options
+ };
+
+ if (chart.options.code) {
+ window.console.log("new Chart(ctx, " + JSON.stringify(chartOptions) + ");");
+ }
+
+ chart.element.innerHTML = "<canvas></canvas>";
+ var ctx = chart.element.getElementsByTagName("CANVAS")[0];
+ chart.chart = new this.library(ctx, chartOptions);
+ };
+
+ var defaultOptions$1 = {
+ chart: {},
+ xAxis: {
+ title: {
+ text: null
+ },
+ labels: {
+ style: {
+ fontSize: "12px"
+ }
+ }
+ },
+ yAxis: {
+ title: {
+ text: null
+ },
+ labels: {
+ style: {
+ fontSize: "12px"
+ }
+ }
+ },
+ title: {
+ text: null
+ },
+ credits: {
+ enabled: false
+ },
+ legend: {
+ borderWidth: 0
+ },
+ tooltip: {
+ style: {
+ fontSize: "12px"
+ }
+ },
+ plotOptions: {
+ areaspline: {},
+ series: {
+ marker: {}
+ }
+ }
+ };
+
+ var hideLegend$1 = function (options, legend, hideLegend) {
+ if (legend !== undefined) {
+ options.legend.enabled = !!legend;
+ if (legend && legend !== true) {
+ if (legend === "top" || legend === "bottom") {
+ options.legend.verticalAlign = legend;
+ } else {
+ options.legend.layout = "vertical";
+ options.legend.verticalAlign = "middle";
+ options.legend.align = legend;
+ }
+ }
+ } else if (hideLegend) {
+ options.legend.enabled = false;
+ }
+ };
+
+ var setTitle$1 = function (options, title) {
+ options.title.text = title;
+ };
+
+ var setMin$1 = function (options, min) {
+ options.yAxis.min = min;
+ };
+
+ var setMax$1 = function (options, max) {
+ options.yAxis.max = max;
+ };
+
+ var setStacked$1 = function (options, stacked) {
+ options.plotOptions.series.stacking = stacked ? (stacked === true ? "normal" : stacked) : null;
+ };
+
+ var setXtitle$1 = function (options, title) {
+ options.xAxis.title.text = title;
+ };
+
+ var setYtitle$1 = function (options, title) {
+ options.yAxis.title.text = title;
+ };
+
+ var jsOptions$1 = jsOptionsFunc(defaultOptions$1, hideLegend$1, setTitle$1, setMin$1, setMax$1, setStacked$1, setXtitle$1, setYtitle$1);
+
+ var setFormatOptions$1 = function(chart, options, chartType) {
+ var formatOptions = {
+ prefix: chart.options.prefix,
+ suffix: chart.options.suffix,
+ thousands: chart.options.thousands,
+ decimal: chart.options.decimal
+ };
+
+ if (chartType !== "pie" && !options.yAxis.labels.formatter) {
+ options.yAxis.labels.formatter = function () {
+ return formatValue("", this.value, formatOptions);
+ };
+ }
+
+ if (!options.tooltip.pointFormatter) {
+ options.tooltip.pointFormatter = function () {
+ return '<span style="color:' + this.color + '>\u25CF</span> ' + formatValue(this.series.name + ': <b>', this.y, formatOptions) + '</b><br/>';
+ };
+ }
+ };
+
+ var defaultExport$1 = function defaultExport(library) {
+ this.name = "highcharts";
+ this.library = library;
+ };
+
+ defaultExport$1.prototype.renderLineChart = function renderLineChart (chart, chartType) {
+ chartType = chartType || "spline";
+ var chartOptions = {};
+ if (chartType === "areaspline") {
+ chartOptions = {
+ plotOptions: {
+ areaspline: {
+ stacking: "normal"
+ },
+ area: {
+ stacking: "normal"
+ },
+ series: {
+ marker: {
+ enabled: false
+ }
+ }
+ }
+ };
+ }
+
+ if (chart.options.curve === false) {
+ if (chartType === "areaspline") {
+ chartType = "area";
+ } else if (chartType === "spline") {
+ chartType = "line";
+ }
+ }
+
+ var options = jsOptions$1(chart, chart.options, chartOptions), data, i, j;
+ options.xAxis.type = chart.xtype === "string" ? "category" : (chart.xtype === "number" ? "linear" : "datetime");
+ if (!options.chart.type) {
+ options.chart.type = chartType;
+ }
+ setFormatOptions$1(chart, options, chartType);
+
+ var series = chart.data;
+ for (i = 0; i < series.length; i++) {
+ series[i].name = series[i].name || "Value";
+ data = series[i].data;
+ if (chart.xtype === "datetime") {
+ for (j = 0; j < data.length; j++) {
+ data[j][0] = data[j][0].getTime();
+ }
+ }
+ series[i].marker = {symbol: "circle"};
+ if (chart.options.points === false) {
+ series[i].marker.enabled = false;
+ }
+ }
+
+ this.drawChart(chart, series, options);
+ };
+
+ defaultExport$1.prototype.renderScatterChart = function renderScatterChart (chart) {
+ var options = jsOptions$1(chart, chart.options, {});
+ options.chart.type = "scatter";
+ this.drawChart(chart, chart.data, options);
+ };
+
+ defaultExport$1.prototype.renderPieChart = function renderPieChart (chart) {
+ var chartOptions = merge(defaultOptions$1, {});
+
+ if (chart.options.colors) {
+ chartOptions.colors = chart.options.colors;
+ }
+ if (chart.options.donut) {
+ chartOptions.plotOptions = {pie: {innerSize: "50%"}};
+ }
+
+ if ("legend" in chart.options) {
+ hideLegend$1(chartOptions, chart.options.legend);
+ }
+
+ if (chart.options.title) {
+ setTitle$1(chartOptions, chart.options.title);
+ }
+
+ var options = merge(chartOptions, chart.options.library || {});
+ setFormatOptions$1(chart, options, "pie");
+ var series = [{
+ type: "pie",
+ name: chart.options.label || "Value",
+ data: chart.data
+ }];
+
+ this.drawChart(chart, series, options);
+ };
+
+ defaultExport$1.prototype.renderColumnChart = function renderColumnChart (chart, chartType) {
+ chartType = chartType || "column";
+ var series = chart.data;
+ var options = jsOptions$1(chart, chart.options), i, j, s, d, rows = [], categories = [];
+ options.chart.type = chartType;
+ setFormatOptions$1(chart, options, chartType);
+
+ for (i = 0; i < series.length; i++) {
+ s = series[i];
+
+ for (j = 0; j < s.data.length; j++) {
+ d = s.data[j];
+ if (!rows[d[0]]) {
+ rows[d[0]] = new Array(series.length);
+ categories.push(d[0]);
+ }
+ rows[d[0]][i] = d[1];
+ }
+ }
+
+ if (chart.xtype === "number") {
+ categories.sort(sortByNumber);
+ }
+
+ options.xAxis.categories = categories;
+
+ var newSeries = [], d2;
+ for (i = 0; i < series.length; i++) {
+ d = [];
+ for (j = 0; j < categories.length; j++) {
+ d.push(rows[categories[j]][i] || 0);
+ }
+
+ d2 = {
+ name: series[i].name || "Value",
+ data: d
+ };
+ if (series[i].stack) {
+ d2.stack = series[i].stack;
+ }
+
+ newSeries.push(d2);
+ }
+
+ this.drawChart(chart, newSeries, options);
+ };
+
+ defaultExport$1.prototype.renderBarChart = function renderBarChart (chart) {
+ this.renderColumnChart(chart, "bar");
+ };
+
+ defaultExport$1.prototype.renderAreaChart = function renderAreaChart (chart) {
+ this.renderLineChart(chart, "areaspline");
+ };
+
+ defaultExport$1.prototype.destroy = function destroy (chart) {
+ if (chart.chart) {
+ chart.chart.destroy();
+ }
+ };
+
+ defaultExport$1.prototype.drawChart = function drawChart (chart, data, options) {
+ this.destroy(chart);
+
+ options.chart.renderTo = chart.element.id;
+ options.series = data;
+
+ if (chart.options.code) {
+ window.console.log("new Highcharts.Chart(" + JSON.stringify(options) + ");");
+ }
+
+ chart.chart = new this.library.Chart(options);
+ };
+
+ var loaded = {};
+ var callbacks = [];
+
+ // Set chart options
+ var defaultOptions$2 = {
+ chartArea: {},
+ fontName: "'Lucida Grande', 'Lucida Sans Unicode', Verdana, Arial, Helvetica, sans-serif",
+ pointSize: 6,
+ legend: {
+ textStyle: {
+ fontSize: 12,
+ color: "#444"
+ },
+ alignment: "center",
+ position: "right"
+ },
+ curveType: "function",
+ hAxis: {
+ textStyle: {
+ color: "#666",
+ fontSize: 12
+ },
+ titleTextStyle: {},
+ gridlines: {
+ color: "transparent"
+ },
+ baselineColor: "#ccc",
+ viewWindow: {}
+ },
+ vAxis: {
+ textStyle: {
+ color: "#666",
+ fontSize: 12
+ },
+ titleTextStyle: {},
+ baselineColor: "#ccc",
+ viewWindow: {}
+ },
+ tooltip: {
+ textStyle: {
+ color: "#666",
+ fontSize: 12
+ }
+ }
+ };
+
+ var hideLegend$2 = function (options, legend, hideLegend) {
+ if (legend !== undefined) {
+ var position;
+ if (!legend) {
+ position = "none";
+ } else if (legend === true) {
+ position = "right";
+ } else {
+ position = legend;
+ }
+ options.legend.position = position;
+ } else if (hideLegend) {
+ options.legend.position = "none";
+ }
+ };
+
+ var setTitle$2 = function (options, title) {
+ options.title = title;
+ options.titleTextStyle = {color: "#333", fontSize: "20px"};
+ };
+
+ var setMin$2 = function (options, min) {
+ options.vAxis.viewWindow.min = min;
+ };
+
+ var setMax$2 = function (options, max) {
+ options.vAxis.viewWindow.max = max;
+ };
+
+ var setBarMin$1 = function (options, min) {
+ options.hAxis.viewWindow.min = min;
+ };
+
+ var setBarMax$1 = function (options, max) {
+ options.hAxis.viewWindow.max = max;
+ };
+
+ var setStacked$2 = function (options, stacked) {
+ options.isStacked = stacked ? stacked : false;
+ };
+
+ var setXtitle$2 = function (options, title) {
+ options.hAxis.title = title;
+ options.hAxis.titleTextStyle.italic = false;
+ };
+
+ var setYtitle$2 = function (options, title) {
+ options.vAxis.title = title;
+ options.vAxis.titleTextStyle.italic = false;
+ };
+
+ var jsOptions$2 = jsOptionsFunc(defaultOptions$2, hideLegend$2, setTitle$2, setMin$2, setMax$2, setStacked$2, setXtitle$2, setYtitle$2);
+
+ var resize = function (callback) {
+ if (window.attachEvent) {
+ window.attachEvent("onresize", callback);
+ } else if (window.addEventListener) {
+ window.addEventListener("resize", callback, true);
+ }
+ callback();
+ };
+
+ var defaultExport$2 = function defaultExport(library) {
+ this.name = "google";
+ this.library = library;
+ };
+
+ defaultExport$2.prototype.renderLineChart = function renderLineChart (chart) {
+ var this$1 = this;
+
+ this.waitForLoaded(chart, function () {
+ var chartOptions = {};
+
+ if (chart.options.curve === false) {
+ chartOptions.curveType = "none";
+ }
+
+ if (chart.options.points === false) {
+ chartOptions.pointSize = 0;
+ }
+
+ var options = jsOptions$2(chart, chart.options, chartOptions);
+ var data = this$1.createDataTable(chart.data, chart.xtype);
+
+ this$1.drawChart(chart, "LineChart", data, options);
+ });
+ };
+
+ defaultExport$2.prototype.renderPieChart = function renderPieChart (chart) {
+ var this$1 = this;
+
+ this.waitForLoaded(chart, function () {
+ var chartOptions = {
+ chartArea: {
+ top: "10%",
+ height: "80%"
+ },
+ legend: {}
+ };
+ if (chart.options.colors) {
+ chartOptions.colors = chart.options.colors;
+ }
+ if (chart.options.donut) {
+ chartOptions.pieHole = 0.5;
+ }
+ if ("legend" in chart.options) {
+ hideLegend$2(chartOptions, chart.options.legend);
+ }
+ if (chart.options.title) {
+ setTitle$2(chartOptions, chart.options.title);
+ }
+ var options = merge(merge(defaultOptions$2, chartOptions), chart.options.library || {});
+
+ var data = new this$1.library.visualization.DataTable();
+ data.addColumn("string", "");
+ data.addColumn("number", "Value");
+ data.addRows(chart.data);
+
+ this$1.drawChart(chart, "PieChart", data, options);
+ });
+ };
+
+ defaultExport$2.prototype.renderColumnChart = function renderColumnChart (chart) {
+ var this$1 = this;
+
+ this.waitForLoaded(chart, function () {
+ var options = jsOptions$2(chart, chart.options);
+ var data = this$1.createDataTable(chart.data, chart.xtype);
+
+ this$1.drawChart(chart, "ColumnChart", data, options);
+ });
+ };
+
+ defaultExport$2.prototype.renderBarChart = function renderBarChart (chart) {
+ var this$1 = this;
+
+ this.waitForLoaded(chart, function () {
+ var chartOptions = {
+ hAxis: {
+ gridlines: {
+ color: "#ccc"
+ }
+ }
+ };
+ var options = jsOptionsFunc(defaultOptions$2, hideLegend$2, setTitle$2, setBarMin$1, setBarMax$1, setStacked$2, setXtitle$2, setYtitle$2)(chart, chart.options, chartOptions);
+ var data = this$1.createDataTable(chart.data, chart.xtype);
+
+ this$1.drawChart(chart, "BarChart", data, options);
+ });
+ };
+
+ defaultExport$2.prototype.renderAreaChart = function renderAreaChart (chart) {
+ var this$1 = this;
+
+ this.waitForLoaded(chart, function () {
+ var chartOptions = {
+ isStacked: true,
+ pointSize: 0,
+ areaOpacity: 0.5
+ };
+
+ var options = jsOptions$2(chart, chart.options, chartOptions);
+ var data = this$1.createDataTable(chart.data, chart.xtype);
+
+ this$1.drawChart(chart, "AreaChart", data, options);
+ });
+ };
+
+ defaultExport$2.prototype.renderGeoChart = function renderGeoChart (chart) {
+ var this$1 = this;
+
+ this.waitForLoaded(chart, function () {
+ var chartOptions = {
+ legend: "none",
+ colorAxis: {
+ colors: chart.options.colors || ["#f6c7b6", "#ce502d"]
+ }
+ };
+ var options = merge(merge(defaultOptions$2, chartOptions), chart.options.library || {});
+
+ var data = new this$1.library.visualization.DataTable();
+ data.addColumn("string", "");
+ data.addColumn("number", chart.options.label || "Value");
+ data.addRows(chart.data);
+
+ this$1.drawChart(chart, "GeoChart", data, options);
+ });
+ };
+
+ defaultExport$2.prototype.renderScatterChart = function renderScatterChart (chart) {
+ var this$1 = this;
+
+ this.waitForLoaded(chart, function () {
+ var chartOptions = {};
+ var options = jsOptions$2(chart, chart.options, chartOptions);
+
+ var series = chart.data, rows2 = [], i, j, data, d;
+ for (i = 0; i < series.length; i++) {
+ series[i].name = series[i].name || "Value";
+ d = series[i].data;
+ for (j = 0; j < d.length; j++) {
+ var row = new Array(series.length + 1);
+ row[0] = d[j][0];
+ row[i + 1] = d[j][1];
+ rows2.push(row);
+ }
+ }
+
+ data = new this$1.library.visualization.DataTable();
+ data.addColumn("number", "");
+ for (i = 0; i < series.length; i++) {
+ data.addColumn("number", series[i].name);
+ }
+ data.addRows(rows2);
+
+ this$1.drawChart(chart, "ScatterChart", data, options);
+ });
+ };
+
+ defaultExport$2.prototype.renderTimeline = function renderTimeline (chart) {
+ var this$1 = this;
+
+ this.waitForLoaded(chart, "timeline", function () {
+ var chartOptions = {
+ legend: "none"
+ };
+
+ if (chart.options.colors) {
+ chartOptions.colors = chart.options.colors;
+ }
+ var options = merge(merge(defaultOptions$2, chartOptions), chart.options.library || {});
+
+ var data = new this$1.library.visualization.DataTable();
+ data.addColumn({type: "string", id: "Name"});
+ data.addColumn({type: "date", id: "Start"});
+ data.addColumn({type: "date", id: "End"});
+ data.addRows(chart.data);
+
+ chart.element.style.lineHeight = "normal";
+
+ this$1.drawChart(chart, "Timeline", data, options);
+ });
+ };
+
+ defaultExport$2.prototype.destroy = function destroy (chart) {
+ if (chart.chart) {
+ chart.chart.clearChart();
+ }
+ };
+
+ defaultExport$2.prototype.drawChart = function drawChart (chart, type, data, options) {
+ this.destroy(chart);
+
+ if (chart.options.code) {
+ window.console.log("var data = new google.visualization.DataTable(" + data.toJSON() + ");\nvar chart = new google.visualization." + type + "(element);\nchart.draw(data, " + JSON.stringify(options) + ");");
+ }
+
+ chart.chart = new this.library.visualization[type](chart.element);
+ resize(function () {
+ chart.chart.draw(data, options);
+ });
+ };
+
+ defaultExport$2.prototype.waitForLoaded = function waitForLoaded (chart, pack, callback) {
+ var this$1 = this;
+
+ if (!callback) {
+ callback = pack;
+ pack = "corechart";
+ }
+
+ callbacks.push({pack: pack, callback: callback});
+
+ if (loaded[pack]) {
+ this.runCallbacks();
+ } else {
+ loaded[pack] = true;
+
+ // https://groups.google.com/forum/#!topic/google-visualization-api/fMKJcyA2yyI
+ var loadOptions = {
+ packages: [pack],
+ callback: function () { this$1.runCallbacks(); }
+ };
+ var config = chart.__config();
+ if (config.language) {
+ loadOptions.language = config.language;
+ }
+ if (pack === "corechart" && config.mapsApiKey) {
+ loadOptions.mapsApiKey = config.mapsApiKey;
+ }
+
+ this.library.charts.load("current", loadOptions);
+ }
+ };
+
+ defaultExport$2.prototype.runCallbacks = function runCallbacks () {
+ var this$1 = this;
+
+ var cb, call;
+ for (var i = 0; i < callbacks.length; i++) {
+ cb = callbacks[i];
+ call = this$1.library.visualization && ((cb.pack === "corechart" && this$1.library.visualization.LineChart) || (cb.pack === "timeline" && this$1.library.visualization.Timeline));
+ if (call) {
+ cb.callback();
+ callbacks.splice(i, 1);
+ i--;
+ }
+ }
+ };
+
+ // cant use object as key
+ defaultExport$2.prototype.createDataTable = function createDataTable (series, columnType) {
+ var i, j, s, d, key, rows = [], sortedLabels = [];
+ for (i = 0; i < series.length; i++) {
+ s = series[i];
+ series[i].name = series[i].name || "Value";
+
+ for (j = 0; j < s.data.length; j++) {
+ d = s.data[j];
+ key = (columnType === "datetime") ? d[0].getTime() : d[0];
+ if (!rows[key]) {
+ rows[key] = new Array(series.length);
+ sortedLabels.push(key);
+ }
+ rows[key][i] = toFloat(d[1]);
+ }
+ }
+
+ var rows2 = [];
+ var day = true;
+ var value;
+ for (j = 0; j < sortedLabels.length; j++) {
+ i = sortedLabels[j];
+ if (columnType === "datetime") {
+ value = new Date(toFloat(i));
+ day = day && isDay(value);
+ } else if (columnType === "number") {
+ value = toFloat(i);
+ } else {
+ value = i;
+ }
+ rows2.push([value].concat(rows[i]));
+ }
+ if (columnType === "datetime") {
+ rows2.sort(sortByTime);
+ } else if (columnType === "number") {
+ rows2.sort(sortByNumberSeries);
+
+ for (i = 0; i < rows2.length; i++) {
+ rows2[i][0] = toStr(rows2[i][0]);
+ }
+
+ columnType = "string";
+ }
+
+ // create datatable
+ var data = new this.library.visualization.DataTable();
+ columnType = columnType === "datetime" && day ? "date" : columnType;
+ data.addColumn(columnType, "");
+ for (i = 0; i < series.length; i++) {
+ data.addColumn("number", series[i].name);
+ }
+ data.addRows(rows2);
+
+ return data;
+ };
+
+ var pendingRequests = [], runningRequests = 0, maxRequests = 4;
+
+ function pushRequest(url, success, error) {
+ pendingRequests.push([url, success, error]);
+ runNext();
+ }
+
+ function runNext() {
+ if (runningRequests < maxRequests) {
+ var request = pendingRequests.shift();
+ if (request) {
+ runningRequests++;
+ getJSON(request[0], request[1], request[2]);
+ runNext();
+ }
+ }
+ }
+
+ function requestComplete() {
+ runningRequests--;
+ runNext();
+ }
+
+ function getJSON(url, success, error) {
+ ajaxCall(url, success, function (jqXHR, textStatus, errorThrown) {
+ var message = (typeof errorThrown === "string") ? errorThrown : errorThrown.message;
+ error(message);
+ });
+ }
+
+ function ajaxCall(url, success, error) {
+ var $ = window.jQuery || window.Zepto || window.$;
+
+ if ($) {
+ $.ajax({
+ dataType: "json",
+ url: url,
+ success: success,
+ error: error,
+ complete: requestComplete
+ });
+ } else {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", url, true);
+ xhr.setRequestHeader("Content-Type", "application/json");
+ xhr.onload = function () {
+ requestComplete();
+ if (xhr.status === 200) {
+ success(JSON.parse(xhr.responseText), xhr.statusText, xhr);
+ } else {
+ error(xhr, "error", xhr.statusText);
+ }
+ };
+ xhr.send();
+ }
+ }
+
+ var config = {};
+ var adapters = [];
+
+ // helpers
+
+ function setText(element, text) {
+ if (document.body.innerText) {
+ element.innerText = text;
+ } else {
+ element.textContent = text;
+ }
+ }
+
+ function chartError(element, message) {
+ setText(element, "Error Loading Chart: " + message);
+ element.style.color = "#ff0000";
+ }
+
+ function errorCatcher(chart) {
+ try {
+ chart.__render();
+ } catch (err) {
+ chartError(chart.element, err.message);
+ throw err;
+ }
+ }
+
+ function fetchDataSource(chart, dataSource) {
+ if (typeof dataSource === "string") {
+ pushRequest(dataSource, function (data) {
+ chart.rawData = data;
+ errorCatcher(chart);
+ }, function (message) {
+ chartError(chart.element, message);
+ });
+ } else {
+ chart.rawData = dataSource;
+ errorCatcher(chart);
+ }
+ }
+
+ function addDownloadButton(chart) {
+ var element = chart.element;
+ var link = document.createElement("a");
+ link.download = chart.options.download === true ? "chart.png" : chart.options.download; // https://caniuse.com/download
+ link.style.position = "absolute";
+ link.style.top = "20px";
+ link.style.right = "20px";
+ link.style.zIndex = 1000;
+ link.style.lineHeight = "20px";
+ link.target = "_blank"; // for safari
+ var image = document.createElement("img");
+ image.alt = "Download";
+ image.style.border = "none";
+ // icon from font-awesome
+ // http://fa2png.io/
+ image.src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAMAAAC6V+0/AAABCFBMVEUAAADMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMywEsqxAAAAV3RSTlMAAQIDBggJCgsMDQ4PERQaHB0eISIjJCouLzE0OTo/QUJHSUpLTU5PUllhYmltcHh5foWLjI+SlaCio6atr7S1t7m6vsHHyM7R2tze5Obo7fHz9ff5+/1hlxK2AAAA30lEQVQYGUXBhVYCQQBA0TdYWAt2d3d3YWAHyur7/z9xgD16Lw0DW+XKx+1GgX+FRzM3HWQWrHl5N/oapW5RPe0PkBu+UYeICvozTWZVK23Ao04B79oJrOsJDOoxkZoQPWgX29pHpCZEk7rEvQYiNSFq1UMqvlCjJkRBS1R8hb00Vb/TajtBL7nTHE1X1vyMQF732dQhyF2o6SAwrzP06iUQzvwsArlnzcOdrgBhJyHa1QOgO9U1GsKuvjUTjavliZYQ8nNPapG6sap/3nrIdJ6bOWzmX/fy0XVpfzZP3S8OJT3g9EEiJwAAAABJRU5ErkJggg==";
+ link.appendChild(image);
+ element.style.position = "relative";
+
+ chart.__downloadAttached = true;
+
+ // mouseenter
+ chart.__enterEvent = addEvent(element, "mouseover", function(e) {
+ var related = e.relatedTarget;
+ // check download option again to ensure it wasn't changed
+ if ((!related || (related !== this && !childOf(this, related))) && chart.options.download) {
+ link.href = chart.toImage();
+ element.appendChild(link);
+ }
+ });
+
+ // mouseleave
+ chart.__leaveEvent = addEvent(element, "mouseout", function(e) {
+ var related = e.relatedTarget;
+ if (!related || (related !== this && !childOf(this, related))) {
+ if (link.parentNode) {
+ link.parentNode.removeChild(link);
+ }
+ }
+ });
+ }
+
+ // https://stackoverflow.com/questions/10149963/adding-event-listener-cross-browser
+ function addEvent(elem, event, fn) {
+ if (elem.addEventListener) {
+ elem.addEventListener(event, fn, false);
+ return fn;
+ } else {
+ var fn2 = function() {
+ // set the this pointer same as addEventListener when fn is called
+ return(fn.call(elem, window.event));
+ };
+ elem.attachEvent("on" + event, fn2);
+ return fn2;
+ }
+ }
+
+ function removeEvent(elem, event, fn) {
+ if (elem.removeEventListener) {
+ elem.removeEventListener(event, fn, false);
+ } else {
+ elem.detachEvent("on" + event, fn);
+ }
+ }
+
+ // https://gist.github.com/shawnbot/4166283
+ function childOf(p, c) {
+ if (p === c) { return false; }
+ while (c && c !== p) { c = c.parentNode; }
+ return c === p;
+ }
+
+ function getAdapterType(library) {
+ if (library) {
+ if (library.product === "Highcharts") {
+ return defaultExport$1;
+ } else if (library.charts) {
+ return defaultExport$2;
+ } else if (isFunction(library)) {
+ return defaultExport;
+ }
+ }
+ throw new Error("Unknown adapter");
+ }
+
+ function addAdapter(library) {
+ var adapterType = getAdapterType(library);
+ var adapter = new adapterType(library);
+
+ if (adapters.indexOf(adapter) === -1) {
+ adapters.push(adapter);
+ }
+ }
+
+ function loadAdapters() {
+ if ("Chart" in window) {
+ addAdapter(window.Chart);
+ }
+
+ if ("Highcharts" in window) {
+ addAdapter(window.Highcharts);
+ }
+
+ if (window.google && window.google.charts) {
+ addAdapter(window.google);
+ }
+ }
+
+ function dataEmpty(data, chartType) {
+ if (chartType === "PieChart" || chartType === "GeoChart" || chartType === "Timeline") {
+ return data.length === 0;
+ } else {
+ for (var i = 0; i < data.length; i++) {
+ if (data[i].data.length > 0) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+
+ function renderChart(chartType, chart) {
+ if (chart.options.messages && chart.options.messages.empty && dataEmpty(chart.data, chartType)) {
+ setText(chart.element, chart.options.messages.empty);
+ } else {
+ callAdapter(chartType, chart);
+ if (chart.options.download && !chart.__downloadAttached && chart.adapter === "chartjs") {
+ addDownloadButton(chart);
+ }
+ }
+ }
+
+ // TODO remove chartType if cross-browser way
+ // to get the name of the chart class
+ function callAdapter(chartType, chart) {
+ var i, adapter, fnName, adapterName;
+ fnName = "render" + chartType;
+ adapterName = chart.options.adapter;
+
+ loadAdapters();
+
+ for (i = 0; i < adapters.length; i++) {
+ adapter = adapters[i];
+ if ((!adapterName || adapterName === adapter.name) && isFunction(adapter[fnName])) {
+ chart.adapter = adapter.name;
+ chart.__adapterObject = adapter;
+ return adapter[fnName](chart);
+ }
+ }
+
+ if (adapters.length > 0) {
+ throw new Error("No charting library found for " + chartType);
+ } else {
+ throw new Error("No charting libraries found - be sure to include one before your charts");
+ }
+ }
+
+ // process data
+
+ var toFormattedKey = function (key, keyType) {
+ if (keyType === "number") {
+ key = toFloat(key);
+ } else if (keyType === "datetime") {
+ key = toDate(key);
+ } else {
+ key = toStr(key);
+ }
+ return key;
+ };
+
+ var formatSeriesData = function (data, keyType) {
+ var r = [], key, j;
+ for (j = 0; j < data.length; j++) {
+ if (keyType === "bubble") {
+ r.push([toFloat(data[j][0]), toFloat(data[j][1]), toFloat(data[j][2])]);
+ } else {
+ key = toFormattedKey(data[j][0], keyType);
+ r.push([key, toFloat(data[j][1])]);
+ }
+ }
+ if (keyType === "datetime") {
+ r.sort(sortByTime);
+ } else if (keyType === "number") {
+ r.sort(sortByNumberSeries);
+ }
+ return r;
+ };
+
+ function detectXType(series, noDatetime) {
+ if (detectXTypeWithFunction(series, isNumber)) {
+ return "number";
+ } else if (!noDatetime && detectXTypeWithFunction(series, isDate)) {
+ return "datetime";
+ } else {
+ return "string";
+ }
+ }
+
+ function detectXTypeWithFunction(series, func) {
+ var i, j, data;
+ for (i = 0; i < series.length; i++) {
+ data = toArr(series[i].data);
+ for (j = 0; j < data.length; j++) {
+ if (!func(data[j][0])) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ // creates a shallow copy of each element of the array
+ // elements are expected to be objects
+ function copySeries(series) {
+ var newSeries = [], i, j;
+ for (i = 0; i < series.length; i++) {
+ var copy = {};
+ for (j in series[i]) {
+ if (series[i].hasOwnProperty(j)) {
+ copy[j] = series[i][j];
+ }
+ }
+ newSeries.push(copy);
+ }
+ return newSeries;
+ }
+
+ function processSeries(chart, keyType, noDatetime) {
+ var i;
+
+ var opts = chart.options;
+ var series = chart.rawData;
+
+ // see if one series or multiple
+ if (!isArray(series) || typeof series[0] !== "object" || isArray(series[0])) {
+ series = [{name: opts.label, data: series}];
+ chart.hideLegend = true;
+ } else {
+ chart.hideLegend = false;
+ }
+
+ chart.xtype = keyType ? keyType : (opts.discrete ? "string" : detectXType(series, noDatetime));
+
+ // right format
+ series = copySeries(series);
+ for (i = 0; i < series.length; i++) {
+ series[i].data = formatSeriesData(toArr(series[i].data), chart.xtype);
+ }
+
+ return series;
+ }
+
+ function processSimple(chart) {
+ var perfectData = toArr(chart.rawData), i;
+ for (i = 0; i < perfectData.length; i++) {
+ perfectData[i] = [toStr(perfectData[i][0]), toFloat(perfectData[i][1])];
+ }
+ return perfectData;
+ }
+
+ // define classes
+
+ var Chart = function Chart(element, dataSource, options) {
+ var elementId;
+ if (typeof element === "string") {
+ elementId = element;
+ element = document.getElementById(element);
+ if (!element) {
+ throw new Error("No element with id " + elementId);
+ }
+ }
+ this.element = element;
+ this.options = merge(Chartkick.options, options || {});
+ this.dataSource = dataSource;
+
+ Chartkick.charts[element.id] = this;
+
+ fetchDataSource(this, dataSource);
+
+ if (this.options.refresh) {
+ this.startRefresh();
+ }
+ };
+
+ Chart.prototype.getElement = function getElement () {
+ return this.element;
+ };
+
+ Chart.prototype.getDataSource = function getDataSource () {
+ return this.dataSource;
+ };
+
+ Chart.prototype.getData = function getData () {
+ return this.data;
+ };
+
+ Chart.prototype.getOptions = function getOptions () {
+ return this.options;
+ };
+
+ Chart.prototype.getChartObject = function getChartObject () {
+ return this.chart;
+ };
+
+ Chart.prototype.getAdapter = function getAdapter () {
+ return this.adapter;
+ };
+
+ Chart.prototype.updateData = function updateData (dataSource, options) {
+ this.dataSource = dataSource;
+ if (options) {
+ this.__updateOptions(options);
+ }
+ fetchDataSource(this, dataSource);
+ };
+
+ Chart.prototype.setOptions = function setOptions (options) {
+ this.__updateOptions(options);
+ this.redraw();
+ };
+
+ Chart.prototype.redraw = function redraw () {
+ fetchDataSource(this, this.rawData);
+ };
+
+ Chart.prototype.refreshData = function refreshData () {
+ if (typeof this.dataSource === "string") {
+ // prevent browser from caching
+ var sep = this.dataSource.indexOf("?") === -1 ? "?" : "&";
+ var url = this.dataSource + sep + "_=" + (new Date()).getTime();
+ fetchDataSource(this, url);
+ }
+ };
+
+ Chart.prototype.startRefresh = function startRefresh () {
+ var this$1 = this;
+
+ var refresh = this.options.refresh;
+
+ if (!this.intervalId) {
+ if (refresh) {
+ this.intervalId = setInterval( function () {
+ this$1.refreshData();
+ }, refresh * 1000);
+ } else {
+ throw new Error("No refresh interval");
+ }
+ }
+ };
+
+ Chart.prototype.stopRefresh = function stopRefresh () {
+ if (this.intervalId) {
+ clearInterval(this.intervalId);
+ this.intervalId = null;
+ }
+ };
+
+ Chart.prototype.toImage = function toImage () {
+ if (this.adapter === "chartjs") {
+ return this.chart.toBase64Image();
+ } else {
+ return null;
+ }
+ };
+
+ Chart.prototype.destroy = function destroy () {
+ if (this.__adapterObject) {
+ this.__adapterObject.destroy(this);
+ }
+
+ if (this.__enterEvent) {
+ removeEvent(this.element, "mouseover", this.__enterEvent);
+ }
+
+ if (this.__leaveEvent) {
+ removeEvent(this.element, "mouseout", this.__leaveEvent);
+ }
+ };
+
+ Chart.prototype.__updateOptions = function __updateOptions (options) {
+ var updateRefresh = options.refresh && options.refresh !== this.options.refresh;
+ this.options = merge(Chartkick.options, options);
+ if (updateRefresh) {
+ this.stopRefresh();
+ this.startRefresh();
+ }
+ };
+
+ Chart.prototype.__render = function __render () {
+ this.data = this.__processData();
+ renderChart(this.__chartName(), this);
+ };
+
+ Chart.prototype.__config = function __config () {
+ return config;
+ };
+
+ var LineChart = (function (Chart) {
+ function LineChart () {
+ Chart.apply(this, arguments);
+ }
+
+ if ( Chart ) LineChart.__proto__ = Chart;
+ LineChart.prototype = Object.create( Chart && Chart.prototype );
+ LineChart.prototype.constructor = LineChart;
+
+ LineChart.prototype.__processData = function __processData () {
+ return processSeries(this);
+ };
+
+ LineChart.prototype.__chartName = function __chartName () {
+ return "LineChart";
+ };
+
+ return LineChart;
+ }(Chart));
+
+ var PieChart = (function (Chart) {
+ function PieChart () {
+ Chart.apply(this, arguments);
+ }
+
+ if ( Chart ) PieChart.__proto__ = Chart;
+ PieChart.prototype = Object.create( Chart && Chart.prototype );
+ PieChart.prototype.constructor = PieChart;
+
+ PieChart.prototype.__processData = function __processData () {
+ return processSimple(this);
+ };
+
+ PieChart.prototype.__chartName = function __chartName () {
+ return "PieChart";
+ };
+
+ return PieChart;
+ }(Chart));
+
+ var ColumnChart = (function (Chart) {
+ function ColumnChart () {
+ Chart.apply(this, arguments);
+ }
+
+ if ( Chart ) ColumnChart.__proto__ = Chart;
+ ColumnChart.prototype = Object.create( Chart && Chart.prototype );
+ ColumnChart.prototype.constructor = ColumnChart;
+
+ ColumnChart.prototype.__processData = function __processData () {
+ return processSeries(this, null, true);
+ };
+
+ ColumnChart.prototype.__chartName = function __chartName () {
+ return "ColumnChart";
+ };
+
+ return ColumnChart;
+ }(Chart));
+
+ var BarChart = (function (Chart) {
+ function BarChart () {
+ Chart.apply(this, arguments);
+ }
+
+ if ( Chart ) BarChart.__proto__ = Chart;
+ BarChart.prototype = Object.create( Chart && Chart.prototype );
+ BarChart.prototype.constructor = BarChart;
+
+ BarChart.prototype.__processData = function __processData () {
+ return processSeries(this, null, true);
+ };
+
+ BarChart.prototype.__chartName = function __chartName () {
+ return "BarChart";
+ };
+
+ return BarChart;
+ }(Chart));
+
+ var AreaChart = (function (Chart) {
+ function AreaChart () {
+ Chart.apply(this, arguments);
+ }
+
+ if ( Chart ) AreaChart.__proto__ = Chart;
+ AreaChart.prototype = Object.create( Chart && Chart.prototype );
+ AreaChart.prototype.constructor = AreaChart;
+
+ AreaChart.prototype.__processData = function __processData () {
+ return processSeries(this);
+ };
+
+ AreaChart.prototype.__chartName = function __chartName () {
+ return "AreaChart";
+ };
+
+ return AreaChart;
+ }(Chart));
+
+ var GeoChart = (function (Chart) {
+ function GeoChart () {
+ Chart.apply(this, arguments);
+ }
+
+ if ( Chart ) GeoChart.__proto__ = Chart;
+ GeoChart.prototype = Object.create( Chart && Chart.prototype );
+ GeoChart.prototype.constructor = GeoChart;
+
+ GeoChart.prototype.__processData = function __processData () {
+ return processSimple(this);
+ };
+
+ GeoChart.prototype.__chartName = function __chartName () {
+ return "GeoChart";
+ };
+
+ return GeoChart;
+ }(Chart));
+
+ var ScatterChart = (function (Chart) {
+ function ScatterChart () {
+ Chart.apply(this, arguments);
+ }
+
+ if ( Chart ) ScatterChart.__proto__ = Chart;
+ ScatterChart.prototype = Object.create( Chart && Chart.prototype );
+ ScatterChart.prototype.constructor = ScatterChart;
+
+ ScatterChart.prototype.__processData = function __processData () {
+ return processSeries(this, "number");
+ };
+
+ ScatterChart.prototype.__chartName = function __chartName () {
+ return "ScatterChart";
+ };
+
+ return ScatterChart;
+ }(Chart));
+
+ var BubbleChart = (function (Chart) {
+ function BubbleChart () {
+ Chart.apply(this, arguments);
+ }
+
+ if ( Chart ) BubbleChart.__proto__ = Chart;
+ BubbleChart.prototype = Object.create( Chart && Chart.prototype );
+ BubbleChart.prototype.constructor = BubbleChart;
+
+ BubbleChart.prototype.__processData = function __processData () {
+ return processSeries(this, "bubble");
+ };
+
+ BubbleChart.prototype.__chartName = function __chartName () {
+ return "BubbleChart";
+ };
+
+ return BubbleChart;
+ }(Chart));
+
+ var Timeline = (function (Chart) {
+ function Timeline () {
+ Chart.apply(this, arguments);
+ }
+
+ if ( Chart ) Timeline.__proto__ = Chart;
+ Timeline.prototype = Object.create( Chart && Chart.prototype );
+ Timeline.prototype.constructor = Timeline;
+
+ Timeline.prototype.__processData = function __processData () {
+ var i, data = this.rawData;
+ for (i = 0; i < data.length; i++) {
+ data[i][1] = toDate(data[i][1]);
+ data[i][2] = toDate(data[i][2]);
+ }
+ return data;
+ };
+
+ Timeline.prototype.__chartName = function __chartName () {
+ return "Timeline";
+ };
+
+ return Timeline;
+ }(Chart));
+
+ var Chartkick = {
+ LineChart: LineChart,
+ PieChart: PieChart,
+ ColumnChart: ColumnChart,
+ BarChart: BarChart,
+ AreaChart: AreaChart,
+ GeoChart: GeoChart,
+ ScatterChart: ScatterChart,
+ BubbleChart: BubbleChart,
+ Timeline: Timeline,
+ charts: {},
+ configure: function (options) {
+ for (var key in options) {
+ if (options.hasOwnProperty(key)) {
+ config[key] = options[key];
+ }
+ }
+ },
+ eachChart: function (callback) {
+ for (var chartId in Chartkick.charts) {
+ if (Chartkick.charts.hasOwnProperty(chartId)) {
+ callback(Chartkick.charts[chartId]);
+ }
+ }
+ },
+ config: config,
+ options: {},
+ adapters: adapters,
+ addAdapter: addAdapter
+ };
+
+ return Chartkick;
+
+})));
diff --git a/data/reports/postats.html b/data/reports/postats.html
new file mode 100644
index 000000000..6c6de499d
--- /dev/null
+++ b/data/reports/postats.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Remmina Translation Status</title>
+ <meta charset="utf-8">
+ <script src="./chartkick.min.js"></script>
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.2/Chart.bundle.js"></script>
+
+ <script>
+
+ Chartkick.CustomChart = function (element, dataSource, options) {
+ };
+
+ </script>
+
+ <style>
+ body {
+ padding: 20px;
+ margin: 0;
+ font-family: "Helvetica Neue", Arial, Helvetica, sans-serif;
+ }
+
+ h1 {
+ text-align: center;
+ }
+
+ .container-fluid {
+ max-width: 900px;
+ margin-left: auto;
+ margin-right: auto;
+ }
+ </style>
+ </head>
+ <body>
+ <div class="container-fluid">
+
+ <h1>Remmina Translation Status</h1>
+ <!--<div id="multiple-bar-stacked" style="height: 650px;"></div>-->
+ <div id="multiple-bar-stacked" style="height: 750px;"></div>
+
+ <script>
+ new Chartkick.BarChart("multiple-bar-stacked", [
+ {name: "Translated", data: [["ar.po",1],["ast.po",221],["bg.po",135],["bn.po",7],["bs.po",221],["ca.po",221],["ca@valencia.po",135],["cs.po",285],["da.po",542],["de.po",578],["el.po",221],["en_AU.po",230],["en_GB.po",230],["es.po",477],["es_VE.po",484],["et.po",112],["eu.po",221],["fi.po",173],["fr.po",578],["gl.po",221],["he.po",221],["hr.po",12],["hu.po",542],["id.po",39],["it.po",526],["ja.po",221],["kk.po",71],["km.po",62],["kn.po",6],["ko.po",221],["lt.po",221],["lv.po",221],["ms.po",221],["my.po",14],["nb.po",221],["nl.po",221],["oc.po",221],["pl.po",210],["pt_BR.po",221],["pt.po",221],["pt_PT.po",221],["ro.po",221],["ru.po",559],["shn.po",0],["si.po",40],["sk.po",120],["sl.po",221],["sq.po",93],["sr.po",120],["sv.po",195],["te.po",4],["th.po",128],["tr.po",559],["ug.po",221],["uk.po",221],["uz@cyrillic.po",308],["zh_CN.po",420],["zh_TW.po",221]]},
+ {name: "Fuzzy", data: [["ar.po",0],["ast.po",0],["bg.po",0],["bn.po",0],["bs.po",0],["ca.po",0],["ca@valencia.po",0],["cs.po",0],["da.po",0],["de.po",0],["el.po",0],["en_AU.po",0],["en_GB.po",0],["es.po",0],["es_VE.po",0],["et.po",0],["eu.po",0],["fi.po",0],["fr.po",0],["gl.po",0],["he.po",0],["hr.po",0],["hu.po",0],["id.po",0],["it.po",0],["ja.po",0],["kk.po",0],["km.po",0],["kn.po",0],["ko.po",0],["lt.po",0],["lv.po",0],["ms.po",0],["my.po",0],["nb.po",0],["nl.po",0],["oc.po",0],["pl.po",0],["pt_BR.po",0],["pt.po",0],["pt_PT.po",0],["ro.po",0],["ru.po",0],["shn.po",0],["si.po",0],["sk.po",0],["sl.po",0],["sq.po",0],["sr.po",0],["sv.po",0],["te.po",0],["th.po",0],["tr.po",0],["ug.po",0],["uk.po",0],["uz@cyrillic.po",0],["zh_CN.po",0],["zh_TW.po",0]]},
+ {name: "Untraslated", data: [["ar.po",577],["ast.po",357],["bg.po",443],["bn.po",571],["bs.po",357],["ca.po",357],["ca@valencia.po",443],["cs.po",293],["da.po",36],["de.po",0],["el.po",357],["en_AU.po",348],["en_GB.po",348],["es.po",101],["es_VE.po",94],["et.po",466],["eu.po",357],["fi.po",405],["fr.po",0],["gl.po",357],["he.po",357],["hr.po",566],["hu.po",36],["id.po",539],["it.po",52],["ja.po",357],["kk.po",507],["km.po",516],["kn.po",572],["ko.po",357],["lt.po",357],["lv.po",357],["ms.po",357],["my.po",564],["nb.po",357],["nl.po",357],["oc.po",357],["pl.po",368],["pt_BR.po",357],["pt.po",357],["pt_PT.po",357],["ro.po",357],["ru.po",19],["shn.po",578],["si.po",538],["sk.po",458],["sl.po",357],["sq.po",485],["sr.po",458],["sv.po",383],["te.po",574],["th.po",450],["tr.po",19],["ug.po",357],["uk.po",357],["uz@cyrillic.po",270],["zh_CN.po",158],["zh_TW.po",357]]}
+ ], {max: 578, stacked: true});
+ </script>
+ </body>
+</html>
diff --git a/scripts/i18nstats.sh b/scripts/i18nstats.sh
new file mode 100755
index 000000000..ed65b673d
--- /dev/null
+++ b/scripts/i18nstats.sh
@@ -0,0 +1,248 @@
+#!/bin/bash -
+#===============================================================================
+#
+# FILE: i18nstats.sh
+#
+# USAGE: ./i18nstats.sh
+#
+# DESCRIPTION:
+#
+# OPTIONS: ---
+# REQUIREMENTS: ---
+# BUGS: ---
+# NOTES: ---
+# AUTHOR: Antenore Gatta (tmow), antenore@simbiosi.org
+# ORGANIZATION: Remmina
+# CREATED: 30. 01. 19 00:05:25
+# LICENSE: GPLv2
+# REVISION: ---
+#===============================================================================
+
+set -o nounset # Treat unset variables as an error
+
+SCRIPTPATH="$( cd "$(dirname "$0")" ; pwd -P )"
+REMMINATOP="$(dirname "$SCRIPTPATH")"
+REMTMPDIR="$(mktemp -d)"
+REMTMPFILE="$(mktemp -p "$REMTMPDIR")"
+
+trap "rm -rf "$REMTMPDIR"" HUP INT QUIT TERM EXIT
+
+declare -x TRANSLATED
+declare -x UNTRANSLATED
+declare -x FUZZY
+
+#===============================================================================
+# FUNCTION DEFINITIONS
+#===============================================================================
+
+#-------------------------------------------------------------------------------
+# TODO: Move this functions in an external library file
+#-------------------------------------------------------------------------------
+
+rem_varhasvalue () {
+ if [[ -n ${!1:-} ]]; then
+ return 0
+ fi
+ return 1
+
+} # ---------- end of function rem_varhasvalue ----------
+
+rem_varisdefined () {
+ typeset -p ${1:-} >/dev/null 2>&1 # Not portable, bash specific
+} # ---------- end of function rem_varisdefined ----------
+
+rem_log () {
+ local _cmnhead="${HOSTNAME:=$(hostname)}"
+ local _header=""
+ local _message="$*"
+ #local _stdout=""
+ local _msgdate=""
+ case "$1" in
+ CRITICAL)
+ _header="CRITICAL"
+ shift
+ _message="$*"
+ ;;
+ ERROR)
+ _header="ERROR"
+ shift
+ _message="$*"
+ ;;
+ WARNING)
+ _header="WARNING"
+ shift
+ _message="$*"
+ ;;
+ DEBUG)
+ _header="DEBUG"
+ shift
+ _message="$*"
+ ;;
+ INFO)
+ # We can add color support adding colors in the beginning
+ # GREEN="\033[0;32m"
+ # RESET="\033[0m"
+ # _reset=${RESET:-'\033[0m'}
+ # _color=${_reset}
+ #_color=${GREEN}
+ _header="INFO"
+ shift
+ _message="$*"
+ ;;
+ *)
+ _header="INFO"
+ _message="$*"
+ ;;
+ esac
+ if ! rem_varisdefined DFORMAT ; then
+ local _dateformat='%d/%m/%y %H:%M:%S'
+ else
+ local _dateformat=${DFORMAT:-}
+ fi
+ _msgdate="$(date +"$_dateformat")"
+
+ # printf "%s%s - [%s] - %s - %s%s\n" "$_color" "$_header" "$_msgdate" "${_cmnhead}" "$_message" "$_reset"
+ printf "%s - [%s] - %s - %s\n" "$_header" "$_msgdate" "${_cmnhead}" "$_message"
+
+} # ---------- end of function rem_log ----------
+#-------------------------------------------------------------------------------
+# rem_which a poorman which function
+# Return 0 un success or 1 in case of failure
+rem_which () {
+ local _tool=()
+ local _ret=
+ for _tool in "$@" ; do
+ if type "$_tool" >/dev/null 2>&1 ; then
+ _ret=0
+ else
+ _ret=1
+ fi
+ case $_ret in
+ 0)
+ rem_log INFO "$_tool found"
+ ;;
+ 1)
+ rem_log ERROR "$_tool not found"
+ ;;
+ esac
+ done
+ unset _tool
+ return "$_ret"
+} # ---------- end of function rem_which ----------
+
+
+#===============================================================================
+# MAIN SCRIPT
+#===============================================================================
+
+
+if ! rem_which "xgettext" "msgmerge" "git" "gnuplot" ; then
+ rem_log ERROR "Some tools have not been found"
+ exit 1
+fi
+
+cd "$REMMINATOP"/po || { rem_log ERROR "$REMMINATOP/po not found" ; exit 1 ; }
+
+for _pofile in *po ; do
+ printf "%s: " "$_pofile" ; msgfmt --statistics "$_pofile"
+done >| "$REMTMPFILE" 2>&1
+
+TRANSLATED=""
+FUZZY=""
+UNTRANSLATED=""
+MAX=0
+while IFS= read -r _msgstat ; do
+ #rem_log INFO "dealing with data: $_msgstat"
+ if echo "$_msgstat" | grep '^[a-z@_A-Z]\+\.po:.*\.$' >/dev/null 2>&1 ; then
+ _translated="$(echo "$_msgstat" | sed -e 's/\(^[a-z]\+.*: \)\([0-9]\+\)\( \)\(translated messages\?\)\(.*\.$\)/\2/g')"
+ _fuzzy="$(echo "$_msgstat" | sed -e 's/\(^[a-z]\+.* \)\([0-9]\+\)\( \)\(fuzzy translations\?\)\(.*\.$\)/\2/g')"
+ _untranslated="$(echo "$_msgstat" | sed -e 's/\(^[a-z]\+.* \)\([0-9]\+\)\( \)\(untranslated messages\?\)\(.*\.$\)/\2/g')"
+ case $_translated in
+ ''|*[!0-9]*)
+ _translated=0
+ ;;
+ *)
+ #rem_log INFO "translated: $_translated"
+ ;;
+ esac
+ case $_untranslated in
+ ''|*[!0-9]*)
+ _untranslated=0
+ ;;
+ *)
+ #rem_log INFO "untraslated: $_untranslated"
+ ;;
+ esac
+ case $_fuzzy in
+ ''|*[!0-9]*)
+ _fuzzy=0
+ ;;
+ *)
+ #rem_log INFO "fuzzy: $_fuzzy"
+ ;;
+ esac
+ _pofile="$(echo "$_msgstat" | cut -d: -f1)"
+ #printf "%s %s %s %s\n" "$_pofile" "$_translated" "$_fuzzy" "$_untranslated"
+ _sum=$((_translated + _fuzzy + _untranslated))
+ [[ "$MAX" -lt "$_sum" ]] && MAX="$_sum"
+ # [["0",32],["1",46],["2",28],["3",21],["4",20],["5",13],["6",27]]
+ _tav="$(printf "[\"%s\",%s]," "$_pofile" "$_translated")"
+ TRANSLATED="${TRANSLATED}$_tav"
+ _tav="$(printf "[\"%s\",%s]," "$_pofile" "$_fuzzy")"
+ FUZZY="${FUZZY}$_tav"
+ _tav="$(printf "[\"%s\",%s]," "$_pofile" "$_untranslated")"
+ UNTRANSLATED="${UNTRANSLATED}$_tav"
+ unset _translated _untranslated _fuzzy _tav
+ fi
+done < "$REMTMPFILE"
+cat << EOF > "$REMMINATOP"/data/reports/postats.html
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Remmina Translation Status</title>
+ <meta charset="utf-8">
+ <script src="./chartkick.min.js"></script>
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.2/Chart.bundle.js"></script>
+
+ <script>
+
+ Chartkick.CustomChart = function (element, dataSource, options) {
+ };
+
+ </script>
+
+ <style>
+ body {
+ padding: 20px;
+ margin: 0;
+ font-family: "Helvetica Neue", Arial, Helvetica, sans-serif;
+ }
+
+ h1 {
+ text-align: center;
+ }
+
+ .container-fluid {
+ max-width: 900px;
+ margin-left: auto;
+ margin-right: auto;
+ }
+ </style>
+ </head>
+ <body>
+ <div class="container-fluid">
+
+ <h1>Remmina Translation Status</h1>
+ <!--<div id="multiple-bar-stacked" style="height: 650px;"></div>-->
+ <div id="multiple-bar-stacked" style="height: 750px;"></div>
+
+ <script>
+ new Chartkick.BarChart("multiple-bar-stacked", [
+ {name: "Translated", data: [${TRANSLATED:0:-1}]},
+ {name: "Fuzzy", data: [${FUZZY:0:-1}]},
+ {name: "Untraslated", data: [${UNTRANSLATED:0:-1}]}
+ ], {max: ${MAX}, stacked: true});
+ </script>
+ </body>
+</html>
+EOF