diff options
author | Antenore Gatta <antenore@simbiosi.org> | 2019-01-31 01:34:22 +0300 |
---|---|---|
committer | Antenore Gatta <antenore@simbiosi.org> | 2019-01-31 01:34:22 +0300 |
commit | 240b97082f4ce0e29cd7c560ba2e5617b55d9297 (patch) | |
tree | 87ff6ee1fbfc63fc2d88a66b12d4079f2709c7c9 | |
parent | 68df8e495b99ebe8202f2cd94c49d672e175db8c (diff) |
Remmina Translations Statistics
-rw-r--r-- | data/reports/chartkick.min.js | 2214 | ||||
-rw-r--r-- | data/reports/postats.html | 49 | ||||
-rwxr-xr-x | scripts/i18nstats.sh | 248 |
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 |