From d3f574eef33e38e6a35054543d0e8823a82cd605 Mon Sep 17 00:00:00 2001 From: mirkan-samsung-wsl Date: Sat, 13 Apr 2024 22:34:16 +0300 Subject: [PATCH 01/11] Front end data type display exception --- viz-lib/src/visualizations/table/columns/number.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/viz-lib/src/visualizations/table/columns/number.tsx b/viz-lib/src/visualizations/table/columns/number.tsx index 78a8e8f415..58049eb7f0 100644 --- a/viz-lib/src/visualizations/table/columns/number.tsx +++ b/viz-lib/src/visualizations/table/columns/number.tsx @@ -36,8 +36,12 @@ export default function initNumberColumn(column: any) { const format = createNumberFormatter(column.numberFormat); function prepareData(row: any) { + let number = row[column.name]; + if (Number.isSafeInteger(number)) { + number = format(number); + } return { - text: format(row[column.name]), + text: number, }; } From 363753f9edcefe4aafb00dea131878cd72392f4b Mon Sep 17 00:00:00 2001 From: mirkan-samsung-wsl Date: Wed, 17 Apr 2024 00:19:34 +0300 Subject: [PATCH 02/11] Fix data type conversion issue in QueryResultResource for big ints --- redash/handlers/query_results.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/redash/handlers/query_results.py b/redash/handlers/query_results.py index bfc4371d08..1aedfc9c36 100644 --- a/redash/handlers/query_results.py +++ b/redash/handlers/query_results.py @@ -312,6 +312,11 @@ def get(self, query_id=None, query_result_id=None, filetype="json"): if query_result_id: query_result = get_object_or_404(models.QueryResult.get_by_id_and_org, query_result_id, self.current_org) + for i in query_result.data['rows']: + for j in i.items(): + value = j[1] + if isinstance(value, int): + i[j[0]] = str(value) if query_id is not None: query = get_object_or_404(models.Query.get_by_id_and_org, query_id, self.current_org) From 4a13709627a232aabde60be19750ff81e62d0642 Mon Sep 17 00:00:00 2001 From: mirkan-samsung-wsl Date: Wed, 17 Apr 2024 12:28:23 +0300 Subject: [PATCH 03/11] made changes for Big Integers front-end readability --- redash/handlers/query_results.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/redash/handlers/query_results.py b/redash/handlers/query_results.py index 1aedfc9c36..498bfb6897 100644 --- a/redash/handlers/query_results.py +++ b/redash/handlers/query_results.py @@ -312,11 +312,11 @@ def get(self, query_id=None, query_result_id=None, filetype="json"): if query_result_id: query_result = get_object_or_404(models.QueryResult.get_by_id_and_org, query_result_id, self.current_org) - for i in query_result.data['rows']: - for j in i.items(): - value = j[1] + + for row in query_result.data['rows']: + for key, value in row.items(): if isinstance(value, int): - i[j[0]] = str(value) + row[key] = str(value) if query_id is not None: query = get_object_or_404(models.Query.get_by_id_and_org, query_id, self.current_org) From b6cfb49eab722da0a255bb8eae455e75a9b557f3 Mon Sep 17 00:00:00 2001 From: mirkan-samsung-wsl Date: Sun, 5 May 2024 01:40:46 +0300 Subject: [PATCH 04/11] Add numeral package and neccessary changes written into axios --- client/app/services/query-result.js | 9 +- package.json | 5 +- redash/handlers/query_results.py | 5 - viz-lib/src/lib/numeral.js | 1068 +++++++++++++++++ viz-lib/src/lib/value-format.tsx | 5 +- .../visualizations/table/columns/number.tsx | 6 +- yarn.lock | 12 + 7 files changed, 1095 insertions(+), 15 deletions(-) create mode 100644 viz-lib/src/lib/numeral.js diff --git a/client/app/services/query-result.js b/client/app/services/query-result.js index 7f50228fa6..eb7178c445 100644 --- a/client/app/services/query-result.js +++ b/client/app/services/query-result.js @@ -5,6 +5,7 @@ import { QueryResultError } from "@/services/query"; import { Auth } from "@/services/auth"; import { isString, uniqBy, each, isNumber, includes, extend, forOwn, get } from "lodash"; +const JSONbigString = require('json-bigint')({ storeAsString: true }); const logger = debug("redash:services:QueryResult"); const filterTypes = ["filter", "multi-filter", "multiFilter"]; @@ -45,7 +46,9 @@ function getColumnFriendlyName(column) { const createOrSaveUrl = data => (data.id ? `api/query_results/${data.id}` : "api/query_results"); const QueryResultResource = { - get: ({ id }) => axios.get(`api/query_results/${id}`), + get: ({ id }) => axios.get(`api/query_results/${id}`, { + transformResponse: (response) => JSONbigString.parse(response) + }), post: data => axios.post(createOrSaveUrl(data), data), }; @@ -344,7 +347,9 @@ class QueryResult { queryResult.deferred.onStatusChange(ExecutionStatus.LOADING_RESULT); axios - .get(`api/queries/${queryId}/results/${id}.json`) + .get(`api/queries/${queryId}/results/${id}.json`, { + transformResponse: (response) => JSONbigString.parse(response) + }) .then(response => { // Success handler queryResult.isLoadingResult = false; diff --git a/package.json b/package.json index 551215502d..a5ec9aefa8 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,7 @@ "font-awesome": "^4.7.0", "history": "^4.10.1", "hoist-non-react-statics": "^3.3.0", + "json-bigint": "^1.0.0", "markdown": "0.5.0", "material-design-iconic-font": "^2.2.0", "mousetrap": "^1.6.1", @@ -179,8 +180,8 @@ ] }, "browser": { - "fs": false, - "path": false + "fs": false, + "path": false }, "//": "browserslist set to 'Async functions' compatibility", "browserslist": [ diff --git a/redash/handlers/query_results.py b/redash/handlers/query_results.py index 498bfb6897..bfc4371d08 100644 --- a/redash/handlers/query_results.py +++ b/redash/handlers/query_results.py @@ -313,11 +313,6 @@ def get(self, query_id=None, query_result_id=None, filetype="json"): if query_result_id: query_result = get_object_or_404(models.QueryResult.get_by_id_and_org, query_result_id, self.current_org) - for row in query_result.data['rows']: - for key, value in row.items(): - if isinstance(value, int): - row[key] = str(value) - if query_id is not None: query = get_object_or_404(models.Query.get_by_id_and_org, query_id, self.current_org) diff --git a/viz-lib/src/lib/numeral.js b/viz-lib/src/lib/numeral.js new file mode 100644 index 0000000000..be1d393692 --- /dev/null +++ b/viz-lib/src/lib/numeral.js @@ -0,0 +1,1068 @@ +/*! @preserve + * numeral.js + * version : 3.0.0 + * author : Adam Draper + * license : MIT + * http://adamwdraper.github.com/Numeral-js/ + */ + +(function (global, factory) { + if (typeof define === 'function' && define.amd) { + define(factory); + } else if (typeof module === 'object' && module.exports) { + module.exports = factory(); + } else { + global.numeral = factory(); + } +}(this, function () { + /************************************ + Variables + ************************************/ + + var numeral, + _, + VERSION = '3.0.0', + formats = {}, + locales = {}, + defaults = { + currentLocale: 'en', + zeroFormat: null, + nullFormat: null, + defaultFormat: '0,0', + scalePercentBy100: true + }, + options = { + currentLocale: defaults.currentLocale, + zeroFormat: defaults.zeroFormat, + nullFormat: defaults.nullFormat, + defaultFormat: defaults.defaultFormat, + scalePercentBy100: defaults.scalePercentBy100 + }; + + + /************************************ + Constructors + ************************************/ + + // Numeral prototype object + function Numeral(input, number) { + this._input = input; + + this._value = number; + } + + numeral = function(input) { + var value, + kind, + unformatFunction, + regexp; + + if (numeral.isNumeral(input)) { + value = input.value(); + } else if (input === 0 || typeof input === 'undefined') { + value = 0; + } else if (input === null || _.isNaN(input)) { + value = null; + } else if (typeof input === 'string') { + if (options.zeroFormat && input === options.zeroFormat) { + value = 0; + } else if (options.nullFormat && input === options.nullFormat || !input.replace(/[^0-9]+/g, '').length) { + value = null; + } else { + for (kind in formats) { + regexp = typeof formats[kind].regexps.unformat === 'function' ? formats[kind].regexps.unformat() : formats[kind].regexps.unformat; + + if (regexp && input.match(regexp)) { + unformatFunction = formats[kind].unformat; + + break; + } + } + + unformatFunction = unformatFunction || numeral._.stringToNumber; + + value = unformatFunction(input); + } + } else if (_.isBigNumber(input)) { + value = input; + } else if (typeof input === 'object') { + value = input.toString(); + } else { + value = Number(input) || null; + } + + + return new Numeral(input, value); + }; + + // version number + numeral.version = VERSION; + + // compare numeral object + numeral.isNumeral = function(obj) { + return obj instanceof Numeral; + }; + + // helper functions + numeral._ = _ = { + Big: require('big.js'), + // formats numbers separators, decimals places, signs, abbreviations + numberToFormat: function(value, format, roundingFunction) { + var locale = locales[numeral.options.currentLocale], + negP = false, + optDec = false, + leadingCount = 0, + abbr = '', + trillion = 1000000000000, + billion = 1000000000, + million = 1000000, + thousand = 1000, + decimal = '', + neg = false, + abbrForce, // force abbreviation + abs, + min, + max, + power, + int, + precision, + signed, + thousands, + output; + + // make sure we never format a null value + value = value || 0; + // Use conditional to handle regular numbers and BigInts separately + if (_.isBigNumber(value)) { + value = _.Big(value); + abs = _.Big(value).abs(); + trillion = _.Big(trillion); + billion = _.Big(billion); + million = _.Big(million); + thousand = _.Big(thousand); + } else { + abs = Math.abs(value); + } + + // see if we should use parentheses for negative number or if we should prefix with a sign + // if both are present we default to parentheses + if (numeral._.includes(format, '(')) { + negP = true; + format = format.replace(/[\(|\)]/g, ''); + } else if (numeral._.includes(format, '+') || numeral._.includes(format, '-')) { + signed = numeral._.includes(format, '+') ? format.indexOf('+') : value < 0 ? format.indexOf('-') : -1; + format = format.replace(/[\+|\-]/g, ''); + } + + // see if abbreviation is wanted + if (numeral._.includes(format, 'a')) { + abbrForce = format.match(/a(k|m|b|t)?/); + + abbrForce = abbrForce ? abbrForce[1] : false; + + // check for space before abbreviation + if (numeral._.includes(format, ' a')) { + abbr = ' '; + } + + format = format.replace(new RegExp(abbr + 'a[kmbt]?'), ''); + + if (abs >= trillion && !abbrForce || abbrForce === 't') { + // trillion + abbr += locale.abbreviations.trillion; + value = value / trillion; + } else if (abs < trillion && abs >= billion && !abbrForce || abbrForce === 'b') { + // billion + abbr += locale.abbreviations.billion; + value = value / billion; + } else if (abs < billion && abs >= million && !abbrForce || abbrForce === 'm') { + // million + abbr += locale.abbreviations.million; + value = value / million; + } else if (abs < million && abs >= thousand && !abbrForce || abbrForce === 'k') { + // thousand + abbr += locale.abbreviations.thousand; + value = value / thousand; + } + } + + // check for optional decimals + if (numeral._.includes(format, '[.]')) { + optDec = true; + format = format.replace('[.]', '.'); + } + + // break number and format + int = value.toString().split('.')[0]; + precision = format.split('.')[1]; + thousands = format.indexOf(','); + leadingCount = (format.split('.')[0].split(',')[0].match(/0/g) || []).length; + + if (precision) { + if (numeral._.includes(precision, '[')) { + precision = precision.replace(']', ''); + precision = precision.split('['); + decimal = numeral._.toFixed(value, (precision[0].length + precision[1].length), roundingFunction, precision[1].length); + } else { + decimal = numeral._.toFixed(value, precision.length, roundingFunction); + } + + int = decimal.split('.')[0]; + + if (numeral._.includes(decimal, '.')) { + decimal = locale.delimiters.decimal + decimal.split('.')[1]; + } else { + decimal = ''; + } + + if (optDec && Number(decimal.slice(1)) === 0) { + decimal = ''; + } + } else { + int = numeral._.toFixed(value, 0, roundingFunction); + } + + // check abbreviation again after rounding + if (abbr && !abbrForce && Number(int) >= 1000 && abbr !== locale.abbreviations.trillion) { + int = String(Number(int) / 1000); + + switch (abbr) { + case locale.abbreviations.thousand: + abbr = locale.abbreviations.million; + break; + case locale.abbreviations.million: + abbr = locale.abbreviations.billion; + break; + case locale.abbreviations.billion: + abbr = locale.abbreviations.trillion; + break; + } + } + + + // format number + if (numeral._.includes(int, '-')) { + int = int.slice(1); + neg = true; + } + + if (int.length < leadingCount) { + for (var i = leadingCount - int.length; i > 0; i--) { + int = '0' + int; + } + } + + if (thousands > -1) { + int = int.toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1' + locale.delimiters.thousands); + } + + if (format.indexOf('.') === 0) { + int = ''; + } + + output = int + decimal + (abbr ? abbr : ''); + + if (negP) { + output = (negP && neg ? '(' : '') + output + (negP && neg ? ')' : ''); + } else { + if (signed >= 0) { + output = signed === 0 ? (neg ? '-' : '+') + output : output + (neg ? '-' : '+'); + } else if (neg) { + output = '-' + output; + } + } + + return output; + }, + // unformats numbers separators, decimals places, signs, abbreviations + stringToNumber: function(string) { + var locale = locales[options.currentLocale], + stringOriginal = string, + abbreviations = { + thousand: 3, + million: 6, + billion: 9, + trillion: 12, + quadrillion: 15, + quintillion: 18, + sextillion: 21, + septillion: 24, + octillion: 27, + nonillion: 30, + decillion: 33, + undecillion: 36, + duodecillion: 39, + tredecillion: 42, + quattuordecillion: 45, + quindecillion: 48, + sexdecillion: 51, + septendecillion: 54, + octodecillion: 57, + novemdecillion: 60, + vigintillion: 63, + unvigintillion: 66, + }, + abbreviation, + value, + i, + regexp; + + if (options.zeroFormat && string === options.zeroFormat) { + value = _.Big(0); + } else if (options.nullFormat && string === options.nullFormat || !string.replace(/[^0-9]+/g, '').length) { + value = null; + } else { + value = _.Big(1); + + if (locale.delimiters.decimal !== '.') { + string = string.replace(/\./g, '').replace(locale.delimiters.decimal, '.'); + } + + for (abbreviation in abbreviations) { + regexp = new RegExp('[^a-zA-Z]' + locale.abbreviations[abbreviation] + '(?:\\)|(\\' + locale.currency.symbol + ')?(?:\\))?)?$'); + + if (stringOriginal.match(regexp)) { + value = value.times(new _.Big(10).pow(abbreviations[abbreviation])); + break; + } + } + + // check for negative number + value *= (string.split('-').length + Math.min(string.split('(').length - 1, string.split(')').length - 1)) % 2 ? _.Big(1) : _.Big(-1); + + // remove non numbers + string = string.replace(/[^0-9\.]+/g, ''); + value = _.Big(value); + value = _.Big(string).times(value); + if (_.isBigNumber(value)) { + value = value.toString(); + } else { + value = value.toNumber(); + } + } + + return value; + }, + isNaN: function(value) { + return typeof value === 'number' && isNaN(value); + }, + includes: function(string, search) { + return string.indexOf(search) !== -1; + }, + insert: function(string, subString, start) { + return string.slice(0, start) + subString + string.slice(start); + }, + reduce: function(array, callback /*, initialValue*/) { + if (this === null) { + throw new TypeError('Array.prototype.reduce called on null or undefined'); + } + + if (typeof callback !== 'function') { + throw new TypeError(callback + ' is not a function'); + } + + var t = Object(array), + len = t.length >>> 0, + k = 0, + value; + + if (arguments.length === 3) { + value = arguments[2]; + } else { + while (k < len && !(k in t)) { + k++; + } + + if (k >= len) { + throw new TypeError('Reduce of empty array with no initial value'); + } + + value = t[k++]; + } + for (; k < len; k++) { + if (k in t) { + value = callback(value, t[k], k, t); + } + } + return value; + }, + /** + * Computes the multiplier necessary to make x >= 1, + * effectively eliminating miscalculations caused by + * finite precision. + */ + multiplier: function (x) { + var parts = x.toString().split('.'); + + return parts.length < 2 ? 1 : Math.pow(10, parts[1].length); + }, + /** + * Given a variable number of arguments, returns the maximum + * multiplier that must be used to normalize an operation involving + * all of them. + */ + correctionFactor: function () { + var args = Array.prototype.slice.call(arguments); + + return args.reduce(function(accum, next) { + var mn = _.multiplier(next); + return accum > mn ? accum : mn; + }, 1); + }, + isBigNumber: function (number) { + return _.Big(number).abs() > Number.MAX_SAFE_INTEGER; + }, + /** + * Implementation of toFixed() that treats floats more like decimals + * + * Fixes binary rounding issues (eg. (0.615).toFixed(2) === '0.61') that present + * problems for accounting- and finance-related software. + */ + toFixed: function(value, maxDecimals, roundingFunction, optionals) { + var splitValue = value.toString().split('.'), + minDecimals = maxDecimals - (optionals || 0), + boundedPrecision, + optionalsRegExp, + power, + output; + + // Use the smallest precision value possible to avoid errors from floating point representation + if (splitValue.length === 2) { + boundedPrecision = Math.min(Math.max(splitValue[1].length, minDecimals), maxDecimals); + } else { + boundedPrecision = minDecimals; + } + if (_.isBigNumber(value)) { + power = _.Big(10).pow(boundedPrecision); + output = _.Big(value).toFixed(boundedPrecision); + } else { + power = Math.pow(10, boundedPrecision); + output = (roundingFunction(value + 'e+' + boundedPrecision) / power).toFixed(boundedPrecision); + } + + // Multiply up by precision, round accurately, then divide and use native toFixed(): + + if (optionals > maxDecimals - boundedPrecision) { + optionalsRegExp = new RegExp('\\.?0{1,' + (optionals - (maxDecimals - boundedPrecision)) + '}$'); + output = output.replace(optionalsRegExp, ''); + } + + return output; + } + }; + + // avaliable options + numeral.options = options; + + // avaliable formats + numeral.formats = formats; + + // avaliable formats + numeral.locales = locales; + + // This function sets the current locale. If + // no arguments are passed in, it will simply return the current global + // locale key. + numeral.locale = function(key) { + if (key) { + options.currentLocale = key.toLowerCase(); + } + + return options.currentLocale; + }; + + // This function provides access to the loaded locale data. If + // no arguments are passed in, it will simply return the current + // global locale object. + numeral.localeData = function(key) { + if (!key) { + return locales[options.currentLocale]; + } + + key = key.toLowerCase(); + + if (!locales[key]) { + throw new Error('Unknown locale : ' + key); + } + + return locales[key]; + }; + + numeral.reset = function() { + for (var property in defaults) { + options[property] = defaults[property]; + } + }; + + numeral.zeroFormat = function(format) { + options.zeroFormat = typeof(format) === 'string' ? format : null; + }; + + numeral.nullFormat = function (format) { + options.nullFormat = typeof(format) === 'string' ? format : null; + }; + + numeral.defaultFormat = function(format) { + options.defaultFormat = typeof(format) === 'string' ? format : '0.0'; + }; + + numeral.register = function(type, name, format) { + name = name.toLowerCase(); + + if (this[type + 's'][name]) { + throw new TypeError(name + ' ' + type + ' already registered.'); + } + + this[type + 's'][name] = format; + + return format; + }; + + + numeral.validate = function(val, culture) { + var _decimalSep, + _thousandSep, + _currSymbol, + _valArray, + _abbrObj, + _thousandRegEx, + localeData, + temp; + + //coerce val to string + if (typeof val !== 'string') { + val += ''; + + if (console.warn) { + console.warn('Numeral.js: Value is not string. It has been co-erced to: ', val); + } + } + + //trim whitespaces from either sides + val = val.trim(); + + //if val is just digits return true + if (!!val.match(/^\d+$/)) { + return true; + } + + //if val is empty return false + if (val === '') { + return false; + } + + //get the decimal and thousands separator from numeral.localeData + try { + //check if the culture is understood by numeral. if not, default it to current locale + localeData = numeral.localeData(culture); + } catch (e) { + localeData = numeral.localeData(numeral.locale()); + } + + //setup the delimiters and currency symbol based on culture/locale + _currSymbol = localeData.currency.symbol; + _abbrObj = localeData.abbreviations; + _decimalSep = localeData.delimiters.decimal; + if (localeData.delimiters.thousands === '.') { + _thousandSep = '\\.'; + } else { + _thousandSep = localeData.delimiters.thousands; + } + + // validating currency symbol + temp = val.match(/^[^\d]+/); + if (temp !== null) { + val = val.substr(1); + if (temp[0] !== _currSymbol) { + return false; + } + } + + //validating abbreviation symbol + temp = val.match(/[^\d]+$/); + if (temp !== null) { + val = val.slice(0, -1); + if (temp[0] !== _abbrObj.thousand && temp[0] !== _abbrObj.million && temp[0] !== _abbrObj.billion && temp[0] !== _abbrObj.trillion) { + return false; + } + } + + _thousandRegEx = new RegExp(_thousandSep + '{2}'); + + if (!val.match(/[^\d.,]/g)) { + _valArray = val.split(_decimalSep); + if (_valArray.length > 2) { + return false; + } else { + if (_valArray.length < 2) { + return ( !! _valArray[0].match(/^\d+.*\d$/) && !_valArray[0].match(_thousandRegEx)); + } else { + if (_valArray[0].length === 1) { + return ( !! _valArray[0].match(/^\d+$/) && !_valArray[0].match(_thousandRegEx) && !! _valArray[1].match(/^\d+$/)); + } else { + return ( !! _valArray[0].match(/^\d+.*\d$/) && !_valArray[0].match(_thousandRegEx) && !! _valArray[1].match(/^\d+$/)); + } + } + } + } + + return false; + }; + + + /************************************ + Numeral Prototype + ************************************/ + + numeral.fn = Numeral.prototype = { + clone: function() { + return numeral(this); + }, + format: function(inputString, roundingFunction) { + var value = this._value, + format = inputString || options.defaultFormat, + kind, + output, + formatFunction; + + // make sure we have a roundingFunction + roundingFunction = roundingFunction || Math.round; + + // format based on value + if (value === 0 && options.zeroFormat !== null) { + output = options.zeroFormat; + } else if (value === null && options.nullFormat !== null) { + output = options.nullFormat; + } else { + for (kind in formats) { + if (format.match(formats[kind].regexps.format)) { + formatFunction = formats[kind].format; + + break; + } + } + + formatFunction = formatFunction || numeral._.numberToFormat; + + output = formatFunction(value, format, roundingFunction); + } + + return output; + }, + value: function() { + if (!this._value) { + return this._value; + } + if (_.isBigNumber(this._value)) { + return _.Big(this._value).valueOf(); + } + return _.Big(this._value).toNumber(); + }, + input: function() { + return this._input; + }, + set: function(value) { + if (_.isBigNumber(value)) { + this._value = _.Big(value); + } else { + this._value = Number(value); + } + + return this; + }, + add: function(value) { + var corrFactor = _.correctionFactor.call(null, this._value, value); + + function cback(accum, curr, currI, O) { + return accum + Math.round(corrFactor * curr); + } + + this._value = _.reduce([this._value, value], cback, 0) / corrFactor; + + return this; + }, + subtract: function(value) { + var corrFactor = _.correctionFactor.call(null, this._value, value); + + function cback(accum, curr, currI, O) { + return accum - Math.round(corrFactor * curr); + } + + this._value = _.reduce([value], cback, Math.round(this._value * corrFactor)) / corrFactor; + + return this; + }, + multiply: function(value) { + function cback(accum, curr, currI, O) { + var corrFactor = _.correctionFactor(accum, curr); + return Math.round(accum * corrFactor) * Math.round(curr * corrFactor) / Math.round(corrFactor * corrFactor); + } + + this._value = _.reduce([this._value, value], cback, 1); + + return this; + }, + divide: function(value) { + function cback(accum, curr, currI, O) { + var corrFactor = _.correctionFactor(accum, curr); + return Math.round(accum * corrFactor) / Math.round(curr * corrFactor); + } + + this._value = _.reduce([this._value, value], cback); + + return this; + }, + difference: function(value) { + return Math.abs(numeral(this._value).subtract(value).value()); + } + }; + + /************************************ + Default Locale && Format + ************************************/ + + numeral.register('locale', 'en', { + delimiters: { + thousands: ',', + decimal: '.' + }, + abbreviations: { + thousand: 'k', + million: 'm', + billion: 'b', + trillion: 't' + }, + ordinal: function(number) { + var b = number % 10; + return (~~(number % 100 / 10) === 1) ? 'th' : + (b === 1) ? 'st' : + (b === 2) ? 'nd' : + (b === 3) ? 'rd' : 'th'; + }, + currency: { + symbol: '$' + } + }); + + + +(function() { + numeral.register('format', 'bps', { + regexps: { + format: /(BPS)/, + unformat: /(BPS)/ + }, + format: function(value, format, roundingFunction) { + var space = numeral._.includes(format, ' BPS') ? ' ' : '', + output; + + value = value * 10000; + + // check for space before BPS + format = format.replace(/\s?BPS/, ''); + + output = numeral._.numberToFormat(value, format, roundingFunction); + + if (numeral._.includes(output, ')')) { + output = output.split(''); + + output.splice(-1, 0, space + 'BPS'); + + output = output.join(''); + } else { + output = output + space + 'BPS'; + } + + return output; + }, + unformat: function(string) { + return +(numeral._.stringToNumber(string) * 0.0001).toFixed(15); + } + }); +})(); + + +(function() { + var decimal = { + base: 1000, + suffixes: ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] + }, + binary = { + base: 1024, + suffixes: ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'] + }; + + var allSuffixes = decimal.suffixes.concat(binary.suffixes.filter(function (item) { + return decimal.suffixes.indexOf(item) < 0; + })); + var unformatRegex = allSuffixes.join('|'); + // Allow support for BPS (http://www.investopedia.com/terms/b/basispoint.asp) + unformatRegex = '(' + unformatRegex.replace('B', 'B(?!PS)') + ')'; + + numeral.register('format', 'bytes', { + regexps: { + format: /([0\s]i?b)/, + unformat: new RegExp(unformatRegex) + }, + format: function(value, format, roundingFunction) { + var output, + bytes = numeral._.includes(format, 'ib') ? binary : decimal, + suffix = numeral._.includes(format, ' b') || numeral._.includes(format, ' ib') ? ' ' : '', + power, + min, + max; + + // check for space before + format = format.replace(/\s?i?b/, ''); + + for (power = 0; power <= bytes.suffixes.length; power++) { + min = Math.pow(bytes.base, power); + max = Math.pow(bytes.base, power + 1); + + if (value === null || value === 0 || value >= min && value < max) { + suffix += bytes.suffixes[power]; + + if (min > 0) { + value = value / min; + } + + break; + } + } + + output = numeral._.numberToFormat(value, format, roundingFunction); + + return output + suffix; + }, + unformat: function(string) { + var value = numeral._.stringToNumber(string), + power, + bytesMultiplier; + + if (value) { + for (power = decimal.suffixes.length - 1; power >= 0; power--) { + if (numeral._.includes(string, decimal.suffixes[power])) { + bytesMultiplier = Math.pow(decimal.base, power); + + break; + } + + if (numeral._.includes(string, binary.suffixes[power])) { + bytesMultiplier = Math.pow(binary.base, power); + + break; + } + } + + value *= (bytesMultiplier || 1); + } + + return value; + } + }); +})(); + + +(function() { + numeral.register('format', 'currency', { + regexps: { + format: /(\$)/ + }, + format: function(value, format, roundingFunction) { + var locale = numeral.locales[numeral.options.currentLocale], + symbols = { + before: format.match(/^([\+|\-|\(|\s|\$]*)/)[0], + after: format.match(/([\+|\-|\)|\s|\$]*)$/)[0] + }, + output, + symbol, + i; + + // strip format of spaces and $ + format = format.replace(/\s?\$\s?/, ''); + + // format the number + output = numeral._.numberToFormat(value, format, roundingFunction); + + // update the before and after based on value + if (value >= 0) { + symbols.before = symbols.before.replace(/[\-\(]/, ''); + symbols.after = symbols.after.replace(/[\-\)]/, ''); + } else if (value < 0 && (!numeral._.includes(symbols.before, '-') && !numeral._.includes(symbols.before, '('))) { + symbols.before = '-' + symbols.before; + } + + // loop through each before symbol + for (i = 0; i < symbols.before.length; i++) { + symbol = symbols.before[i]; + + switch (symbol) { + case '$': + output = numeral._.insert(output, locale.currency.symbol, i); + break; + case ' ': + output = numeral._.insert(output, ' ', i + locale.currency.symbol.length - 1); + break; + } + } + + // loop through each after symbol + for (i = symbols.after.length - 1; i >= 0; i--) { + symbol = symbols.after[i]; + + switch (symbol) { + case '$': + output = i === symbols.after.length - 1 ? output + locale.currency.symbol : numeral._.insert(output, locale.currency.symbol, -(symbols.after.length - (1 + i))); + break; + case ' ': + output = i === symbols.after.length - 1 ? output + ' ' : numeral._.insert(output, ' ', -(symbols.after.length - (1 + i) + locale.currency.symbol.length - 1)); + break; + } + } + + + return output; + } + }); +})(); + + +(function() { + numeral.register('format', 'exponential', { + regexps: { + format: /(e\+|e-)/, + unformat: /(e\+|e-)/ + }, + format: function(value, format, roundingFunction) { + var output, + exponential = typeof value === 'number' && !numeral._.isNaN(value) ? value.toExponential() : '0e+0', + parts = exponential.split('e'); + + format = format.replace(/e[\+|\-]{1}0/, ''); + + output = numeral._.numberToFormat(Number(parts[0]), format, roundingFunction); + + return output + 'e' + parts[1]; + }, + unformat: function(string) { + var parts = numeral._.includes(string, 'e+') ? string.split('e+') : string.split('e-'), + value = Number(parts[0]), + power = Number(parts[1]); + + power = numeral._.includes(string, 'e-') ? power *= -1 : power; + + function cback(accum, curr, currI, O) { + var corrFactor = numeral._.correctionFactor(accum, curr), + num = (accum * corrFactor) * (curr * corrFactor) / (corrFactor * corrFactor); + return num; + } + + return numeral._.reduce([value, Math.pow(10, power)], cback, 1); + } + }); +})(); + + +(function() { + numeral.register('format', 'ordinal', { + regexps: { + format: /(o)/ + }, + format: function(value, format, roundingFunction) { + var locale = numeral.locales[numeral.options.currentLocale], + output, + ordinal = numeral._.includes(format, ' o') ? ' ' : ''; + + // check for space before + format = format.replace(/\s?o/, ''); + + ordinal += locale.ordinal(value); + + output = numeral._.numberToFormat(value, format, roundingFunction); + + return output + ordinal; + } + }); +})(); + + +(function() { + numeral.register('format', 'percentage', { + regexps: { + format: /(%)/, + unformat: /(%)/ + }, + format: function(value, format, roundingFunction) { + var space = numeral._.includes(format, ' %') ? ' ' : '', + output; + + if (numeral.options.scalePercentBy100) { + value = value * 100; + } + + // check for space before % + format = format.replace(/\s?\%/, ''); + + output = numeral._.numberToFormat(value, format, roundingFunction); + + if (numeral._.includes(output, ')')) { + output = output.split(''); + + output.splice(-1, 0, space + '%'); + + output = output.join(''); + } else { + output = output + space + '%'; + } + + return output; + }, + unformat: function(string) { + var number = numeral._.stringToNumber(string); + if (numeral.options.scalePercentBy100) { + return number * 0.01; + } + return number; + } + }); +})(); + + +(function() { + numeral.register('format', 'time', { + regexps: { + format: /(:)/, + unformat: /(:)/ + }, + format: function(value, format, roundingFunction) { + var hours = Math.floor(value / 60 / 60), + minutes = Math.floor((value - (hours * 60 * 60)) / 60), + seconds = Math.round(value - (hours * 60 * 60) - (minutes * 60)); + + return hours + ':' + (minutes < 10 ? '0' + minutes : minutes) + ':' + (seconds < 10 ? '0' + seconds : seconds); + }, + unformat: function(string) { + var timeArray = string.split(':'), + seconds = 0; + + // turn hours and minutes into seconds and add them all up + if (timeArray.length === 3) { + // hours + seconds = seconds + (Number(timeArray[0]) * 60 * 60); + // minutes + seconds = seconds + (Number(timeArray[1]) * 60); + // seconds + seconds = seconds + Number(timeArray[2]); + } else if (timeArray.length === 2) { + // minutes + seconds = seconds + (Number(timeArray[0]) * 60); + // seconds + seconds = seconds + Number(timeArray[1]); + } + return Number(seconds); + } + }); +})(); + +return numeral; +})); diff --git a/viz-lib/src/lib/value-format.tsx b/viz-lib/src/lib/value-format.tsx index 96693d2d3b..529569b6f0 100644 --- a/viz-lib/src/lib/value-format.tsx +++ b/viz-lib/src/lib/value-format.tsx @@ -1,7 +1,10 @@ import React from "react"; import ReactDOMServer from "react-dom/server"; import moment from "moment/moment"; -import numeral from "numeral"; +// numeral is waiting on v3.0.0 to be available on npm +// https://github.com/adamwdraper/Numeral-js/pull/790 +// @ts-ignore +import numeral from "./numeral"; import { isString, isArray, isUndefined, isFinite, isNil, toString } from "lodash"; import { visualizationsSettings } from "@/visualizations/visualizationsSettings"; diff --git a/viz-lib/src/visualizations/table/columns/number.tsx b/viz-lib/src/visualizations/table/columns/number.tsx index 58049eb7f0..01d07ca12f 100644 --- a/viz-lib/src/visualizations/table/columns/number.tsx +++ b/viz-lib/src/visualizations/table/columns/number.tsx @@ -36,12 +36,8 @@ export default function initNumberColumn(column: any) { const format = createNumberFormatter(column.numberFormat); function prepareData(row: any) { - let number = row[column.name]; - if (Number.isSafeInteger(number)) { - number = format(number); - } return { - text: number, + text: format(row[column.name]) }; } diff --git a/yarn.lock b/yarn.lock index 8b4e90039f..3f63c093a2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3442,6 +3442,11 @@ big.js@^5.2.2: resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== +bignumber.js@^9.0.0: + version "9.1.2" + resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.1.2.tgz#b7c4242259c008903b13707983b5f4bbd31eda0c" + integrity sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug== + binary-extensions@^1.0.0: version "1.13.0" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.0.tgz#9523e001306a32444b907423f1de2164222f6ab1" @@ -9436,6 +9441,13 @@ jsesc@~0.5.0: resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0= +json-bigint@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/json-bigint/-/json-bigint-1.0.0.tgz#ae547823ac0cad8398667f8cd9ef4730f5b01ff1" + integrity sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ== + dependencies: + bignumber.js "^9.0.0" + json-parse-better-errors@^1.0.1, json-parse-better-errors@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" From ce508a3f628c901abc240cbcf043a60d4742e83d Mon Sep 17 00:00:00 2001 From: "Restyled.io" Date: Sat, 4 May 2024 22:40:53 +0000 Subject: [PATCH 05/11] Restyled by prettier --- client/app/services/query-result.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/client/app/services/query-result.js b/client/app/services/query-result.js index eb7178c445..46f5747fca 100644 --- a/client/app/services/query-result.js +++ b/client/app/services/query-result.js @@ -5,7 +5,7 @@ import { QueryResultError } from "@/services/query"; import { Auth } from "@/services/auth"; import { isString, uniqBy, each, isNumber, includes, extend, forOwn, get } from "lodash"; -const JSONbigString = require('json-bigint')({ storeAsString: true }); +const JSONbigString = require("json-bigint")({ storeAsString: true }); const logger = debug("redash:services:QueryResult"); const filterTypes = ["filter", "multi-filter", "multiFilter"]; @@ -46,9 +46,10 @@ function getColumnFriendlyName(column) { const createOrSaveUrl = data => (data.id ? `api/query_results/${data.id}` : "api/query_results"); const QueryResultResource = { - get: ({ id }) => axios.get(`api/query_results/${id}`, { - transformResponse: (response) => JSONbigString.parse(response) - }), + get: ({ id }) => + axios.get(`api/query_results/${id}`, { + transformResponse: response => JSONbigString.parse(response), + }), post: data => axios.post(createOrSaveUrl(data), data), }; @@ -347,9 +348,9 @@ class QueryResult { queryResult.deferred.onStatusChange(ExecutionStatus.LOADING_RESULT); axios - .get(`api/queries/${queryId}/results/${id}.json`, { - transformResponse: (response) => JSONbigString.parse(response) - }) + .get(`api/queries/${queryId}/results/${id}.json`, { + transformResponse: response => JSONbigString.parse(response), + }) .then(response => { // Success handler queryResult.isLoadingResult = false; From 5848c623a6a29d7a0c44befed9fd656ba7d516f1 Mon Sep 17 00:00:00 2001 From: mirkan-samsung-wsl Date: Sun, 5 May 2024 03:20:54 +0300 Subject: [PATCH 06/11] changes are reverted according to Andrew's commit --- viz-lib/src/lib/numeral.js | 1068 ------------------------------ viz-lib/src/lib/value-format.tsx | 5 +- 2 files changed, 1 insertion(+), 1072 deletions(-) delete mode 100644 viz-lib/src/lib/numeral.js diff --git a/viz-lib/src/lib/numeral.js b/viz-lib/src/lib/numeral.js deleted file mode 100644 index be1d393692..0000000000 --- a/viz-lib/src/lib/numeral.js +++ /dev/null @@ -1,1068 +0,0 @@ -/*! @preserve - * numeral.js - * version : 3.0.0 - * author : Adam Draper - * license : MIT - * http://adamwdraper.github.com/Numeral-js/ - */ - -(function (global, factory) { - if (typeof define === 'function' && define.amd) { - define(factory); - } else if (typeof module === 'object' && module.exports) { - module.exports = factory(); - } else { - global.numeral = factory(); - } -}(this, function () { - /************************************ - Variables - ************************************/ - - var numeral, - _, - VERSION = '3.0.0', - formats = {}, - locales = {}, - defaults = { - currentLocale: 'en', - zeroFormat: null, - nullFormat: null, - defaultFormat: '0,0', - scalePercentBy100: true - }, - options = { - currentLocale: defaults.currentLocale, - zeroFormat: defaults.zeroFormat, - nullFormat: defaults.nullFormat, - defaultFormat: defaults.defaultFormat, - scalePercentBy100: defaults.scalePercentBy100 - }; - - - /************************************ - Constructors - ************************************/ - - // Numeral prototype object - function Numeral(input, number) { - this._input = input; - - this._value = number; - } - - numeral = function(input) { - var value, - kind, - unformatFunction, - regexp; - - if (numeral.isNumeral(input)) { - value = input.value(); - } else if (input === 0 || typeof input === 'undefined') { - value = 0; - } else if (input === null || _.isNaN(input)) { - value = null; - } else if (typeof input === 'string') { - if (options.zeroFormat && input === options.zeroFormat) { - value = 0; - } else if (options.nullFormat && input === options.nullFormat || !input.replace(/[^0-9]+/g, '').length) { - value = null; - } else { - for (kind in formats) { - regexp = typeof formats[kind].regexps.unformat === 'function' ? formats[kind].regexps.unformat() : formats[kind].regexps.unformat; - - if (regexp && input.match(regexp)) { - unformatFunction = formats[kind].unformat; - - break; - } - } - - unformatFunction = unformatFunction || numeral._.stringToNumber; - - value = unformatFunction(input); - } - } else if (_.isBigNumber(input)) { - value = input; - } else if (typeof input === 'object') { - value = input.toString(); - } else { - value = Number(input) || null; - } - - - return new Numeral(input, value); - }; - - // version number - numeral.version = VERSION; - - // compare numeral object - numeral.isNumeral = function(obj) { - return obj instanceof Numeral; - }; - - // helper functions - numeral._ = _ = { - Big: require('big.js'), - // formats numbers separators, decimals places, signs, abbreviations - numberToFormat: function(value, format, roundingFunction) { - var locale = locales[numeral.options.currentLocale], - negP = false, - optDec = false, - leadingCount = 0, - abbr = '', - trillion = 1000000000000, - billion = 1000000000, - million = 1000000, - thousand = 1000, - decimal = '', - neg = false, - abbrForce, // force abbreviation - abs, - min, - max, - power, - int, - precision, - signed, - thousands, - output; - - // make sure we never format a null value - value = value || 0; - // Use conditional to handle regular numbers and BigInts separately - if (_.isBigNumber(value)) { - value = _.Big(value); - abs = _.Big(value).abs(); - trillion = _.Big(trillion); - billion = _.Big(billion); - million = _.Big(million); - thousand = _.Big(thousand); - } else { - abs = Math.abs(value); - } - - // see if we should use parentheses for negative number or if we should prefix with a sign - // if both are present we default to parentheses - if (numeral._.includes(format, '(')) { - negP = true; - format = format.replace(/[\(|\)]/g, ''); - } else if (numeral._.includes(format, '+') || numeral._.includes(format, '-')) { - signed = numeral._.includes(format, '+') ? format.indexOf('+') : value < 0 ? format.indexOf('-') : -1; - format = format.replace(/[\+|\-]/g, ''); - } - - // see if abbreviation is wanted - if (numeral._.includes(format, 'a')) { - abbrForce = format.match(/a(k|m|b|t)?/); - - abbrForce = abbrForce ? abbrForce[1] : false; - - // check for space before abbreviation - if (numeral._.includes(format, ' a')) { - abbr = ' '; - } - - format = format.replace(new RegExp(abbr + 'a[kmbt]?'), ''); - - if (abs >= trillion && !abbrForce || abbrForce === 't') { - // trillion - abbr += locale.abbreviations.trillion; - value = value / trillion; - } else if (abs < trillion && abs >= billion && !abbrForce || abbrForce === 'b') { - // billion - abbr += locale.abbreviations.billion; - value = value / billion; - } else if (abs < billion && abs >= million && !abbrForce || abbrForce === 'm') { - // million - abbr += locale.abbreviations.million; - value = value / million; - } else if (abs < million && abs >= thousand && !abbrForce || abbrForce === 'k') { - // thousand - abbr += locale.abbreviations.thousand; - value = value / thousand; - } - } - - // check for optional decimals - if (numeral._.includes(format, '[.]')) { - optDec = true; - format = format.replace('[.]', '.'); - } - - // break number and format - int = value.toString().split('.')[0]; - precision = format.split('.')[1]; - thousands = format.indexOf(','); - leadingCount = (format.split('.')[0].split(',')[0].match(/0/g) || []).length; - - if (precision) { - if (numeral._.includes(precision, '[')) { - precision = precision.replace(']', ''); - precision = precision.split('['); - decimal = numeral._.toFixed(value, (precision[0].length + precision[1].length), roundingFunction, precision[1].length); - } else { - decimal = numeral._.toFixed(value, precision.length, roundingFunction); - } - - int = decimal.split('.')[0]; - - if (numeral._.includes(decimal, '.')) { - decimal = locale.delimiters.decimal + decimal.split('.')[1]; - } else { - decimal = ''; - } - - if (optDec && Number(decimal.slice(1)) === 0) { - decimal = ''; - } - } else { - int = numeral._.toFixed(value, 0, roundingFunction); - } - - // check abbreviation again after rounding - if (abbr && !abbrForce && Number(int) >= 1000 && abbr !== locale.abbreviations.trillion) { - int = String(Number(int) / 1000); - - switch (abbr) { - case locale.abbreviations.thousand: - abbr = locale.abbreviations.million; - break; - case locale.abbreviations.million: - abbr = locale.abbreviations.billion; - break; - case locale.abbreviations.billion: - abbr = locale.abbreviations.trillion; - break; - } - } - - - // format number - if (numeral._.includes(int, '-')) { - int = int.slice(1); - neg = true; - } - - if (int.length < leadingCount) { - for (var i = leadingCount - int.length; i > 0; i--) { - int = '0' + int; - } - } - - if (thousands > -1) { - int = int.toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1' + locale.delimiters.thousands); - } - - if (format.indexOf('.') === 0) { - int = ''; - } - - output = int + decimal + (abbr ? abbr : ''); - - if (negP) { - output = (negP && neg ? '(' : '') + output + (negP && neg ? ')' : ''); - } else { - if (signed >= 0) { - output = signed === 0 ? (neg ? '-' : '+') + output : output + (neg ? '-' : '+'); - } else if (neg) { - output = '-' + output; - } - } - - return output; - }, - // unformats numbers separators, decimals places, signs, abbreviations - stringToNumber: function(string) { - var locale = locales[options.currentLocale], - stringOriginal = string, - abbreviations = { - thousand: 3, - million: 6, - billion: 9, - trillion: 12, - quadrillion: 15, - quintillion: 18, - sextillion: 21, - septillion: 24, - octillion: 27, - nonillion: 30, - decillion: 33, - undecillion: 36, - duodecillion: 39, - tredecillion: 42, - quattuordecillion: 45, - quindecillion: 48, - sexdecillion: 51, - septendecillion: 54, - octodecillion: 57, - novemdecillion: 60, - vigintillion: 63, - unvigintillion: 66, - }, - abbreviation, - value, - i, - regexp; - - if (options.zeroFormat && string === options.zeroFormat) { - value = _.Big(0); - } else if (options.nullFormat && string === options.nullFormat || !string.replace(/[^0-9]+/g, '').length) { - value = null; - } else { - value = _.Big(1); - - if (locale.delimiters.decimal !== '.') { - string = string.replace(/\./g, '').replace(locale.delimiters.decimal, '.'); - } - - for (abbreviation in abbreviations) { - regexp = new RegExp('[^a-zA-Z]' + locale.abbreviations[abbreviation] + '(?:\\)|(\\' + locale.currency.symbol + ')?(?:\\))?)?$'); - - if (stringOriginal.match(regexp)) { - value = value.times(new _.Big(10).pow(abbreviations[abbreviation])); - break; - } - } - - // check for negative number - value *= (string.split('-').length + Math.min(string.split('(').length - 1, string.split(')').length - 1)) % 2 ? _.Big(1) : _.Big(-1); - - // remove non numbers - string = string.replace(/[^0-9\.]+/g, ''); - value = _.Big(value); - value = _.Big(string).times(value); - if (_.isBigNumber(value)) { - value = value.toString(); - } else { - value = value.toNumber(); - } - } - - return value; - }, - isNaN: function(value) { - return typeof value === 'number' && isNaN(value); - }, - includes: function(string, search) { - return string.indexOf(search) !== -1; - }, - insert: function(string, subString, start) { - return string.slice(0, start) + subString + string.slice(start); - }, - reduce: function(array, callback /*, initialValue*/) { - if (this === null) { - throw new TypeError('Array.prototype.reduce called on null or undefined'); - } - - if (typeof callback !== 'function') { - throw new TypeError(callback + ' is not a function'); - } - - var t = Object(array), - len = t.length >>> 0, - k = 0, - value; - - if (arguments.length === 3) { - value = arguments[2]; - } else { - while (k < len && !(k in t)) { - k++; - } - - if (k >= len) { - throw new TypeError('Reduce of empty array with no initial value'); - } - - value = t[k++]; - } - for (; k < len; k++) { - if (k in t) { - value = callback(value, t[k], k, t); - } - } - return value; - }, - /** - * Computes the multiplier necessary to make x >= 1, - * effectively eliminating miscalculations caused by - * finite precision. - */ - multiplier: function (x) { - var parts = x.toString().split('.'); - - return parts.length < 2 ? 1 : Math.pow(10, parts[1].length); - }, - /** - * Given a variable number of arguments, returns the maximum - * multiplier that must be used to normalize an operation involving - * all of them. - */ - correctionFactor: function () { - var args = Array.prototype.slice.call(arguments); - - return args.reduce(function(accum, next) { - var mn = _.multiplier(next); - return accum > mn ? accum : mn; - }, 1); - }, - isBigNumber: function (number) { - return _.Big(number).abs() > Number.MAX_SAFE_INTEGER; - }, - /** - * Implementation of toFixed() that treats floats more like decimals - * - * Fixes binary rounding issues (eg. (0.615).toFixed(2) === '0.61') that present - * problems for accounting- and finance-related software. - */ - toFixed: function(value, maxDecimals, roundingFunction, optionals) { - var splitValue = value.toString().split('.'), - minDecimals = maxDecimals - (optionals || 0), - boundedPrecision, - optionalsRegExp, - power, - output; - - // Use the smallest precision value possible to avoid errors from floating point representation - if (splitValue.length === 2) { - boundedPrecision = Math.min(Math.max(splitValue[1].length, minDecimals), maxDecimals); - } else { - boundedPrecision = minDecimals; - } - if (_.isBigNumber(value)) { - power = _.Big(10).pow(boundedPrecision); - output = _.Big(value).toFixed(boundedPrecision); - } else { - power = Math.pow(10, boundedPrecision); - output = (roundingFunction(value + 'e+' + boundedPrecision) / power).toFixed(boundedPrecision); - } - - // Multiply up by precision, round accurately, then divide and use native toFixed(): - - if (optionals > maxDecimals - boundedPrecision) { - optionalsRegExp = new RegExp('\\.?0{1,' + (optionals - (maxDecimals - boundedPrecision)) + '}$'); - output = output.replace(optionalsRegExp, ''); - } - - return output; - } - }; - - // avaliable options - numeral.options = options; - - // avaliable formats - numeral.formats = formats; - - // avaliable formats - numeral.locales = locales; - - // This function sets the current locale. If - // no arguments are passed in, it will simply return the current global - // locale key. - numeral.locale = function(key) { - if (key) { - options.currentLocale = key.toLowerCase(); - } - - return options.currentLocale; - }; - - // This function provides access to the loaded locale data. If - // no arguments are passed in, it will simply return the current - // global locale object. - numeral.localeData = function(key) { - if (!key) { - return locales[options.currentLocale]; - } - - key = key.toLowerCase(); - - if (!locales[key]) { - throw new Error('Unknown locale : ' + key); - } - - return locales[key]; - }; - - numeral.reset = function() { - for (var property in defaults) { - options[property] = defaults[property]; - } - }; - - numeral.zeroFormat = function(format) { - options.zeroFormat = typeof(format) === 'string' ? format : null; - }; - - numeral.nullFormat = function (format) { - options.nullFormat = typeof(format) === 'string' ? format : null; - }; - - numeral.defaultFormat = function(format) { - options.defaultFormat = typeof(format) === 'string' ? format : '0.0'; - }; - - numeral.register = function(type, name, format) { - name = name.toLowerCase(); - - if (this[type + 's'][name]) { - throw new TypeError(name + ' ' + type + ' already registered.'); - } - - this[type + 's'][name] = format; - - return format; - }; - - - numeral.validate = function(val, culture) { - var _decimalSep, - _thousandSep, - _currSymbol, - _valArray, - _abbrObj, - _thousandRegEx, - localeData, - temp; - - //coerce val to string - if (typeof val !== 'string') { - val += ''; - - if (console.warn) { - console.warn('Numeral.js: Value is not string. It has been co-erced to: ', val); - } - } - - //trim whitespaces from either sides - val = val.trim(); - - //if val is just digits return true - if (!!val.match(/^\d+$/)) { - return true; - } - - //if val is empty return false - if (val === '') { - return false; - } - - //get the decimal and thousands separator from numeral.localeData - try { - //check if the culture is understood by numeral. if not, default it to current locale - localeData = numeral.localeData(culture); - } catch (e) { - localeData = numeral.localeData(numeral.locale()); - } - - //setup the delimiters and currency symbol based on culture/locale - _currSymbol = localeData.currency.symbol; - _abbrObj = localeData.abbreviations; - _decimalSep = localeData.delimiters.decimal; - if (localeData.delimiters.thousands === '.') { - _thousandSep = '\\.'; - } else { - _thousandSep = localeData.delimiters.thousands; - } - - // validating currency symbol - temp = val.match(/^[^\d]+/); - if (temp !== null) { - val = val.substr(1); - if (temp[0] !== _currSymbol) { - return false; - } - } - - //validating abbreviation symbol - temp = val.match(/[^\d]+$/); - if (temp !== null) { - val = val.slice(0, -1); - if (temp[0] !== _abbrObj.thousand && temp[0] !== _abbrObj.million && temp[0] !== _abbrObj.billion && temp[0] !== _abbrObj.trillion) { - return false; - } - } - - _thousandRegEx = new RegExp(_thousandSep + '{2}'); - - if (!val.match(/[^\d.,]/g)) { - _valArray = val.split(_decimalSep); - if (_valArray.length > 2) { - return false; - } else { - if (_valArray.length < 2) { - return ( !! _valArray[0].match(/^\d+.*\d$/) && !_valArray[0].match(_thousandRegEx)); - } else { - if (_valArray[0].length === 1) { - return ( !! _valArray[0].match(/^\d+$/) && !_valArray[0].match(_thousandRegEx) && !! _valArray[1].match(/^\d+$/)); - } else { - return ( !! _valArray[0].match(/^\d+.*\d$/) && !_valArray[0].match(_thousandRegEx) && !! _valArray[1].match(/^\d+$/)); - } - } - } - } - - return false; - }; - - - /************************************ - Numeral Prototype - ************************************/ - - numeral.fn = Numeral.prototype = { - clone: function() { - return numeral(this); - }, - format: function(inputString, roundingFunction) { - var value = this._value, - format = inputString || options.defaultFormat, - kind, - output, - formatFunction; - - // make sure we have a roundingFunction - roundingFunction = roundingFunction || Math.round; - - // format based on value - if (value === 0 && options.zeroFormat !== null) { - output = options.zeroFormat; - } else if (value === null && options.nullFormat !== null) { - output = options.nullFormat; - } else { - for (kind in formats) { - if (format.match(formats[kind].regexps.format)) { - formatFunction = formats[kind].format; - - break; - } - } - - formatFunction = formatFunction || numeral._.numberToFormat; - - output = formatFunction(value, format, roundingFunction); - } - - return output; - }, - value: function() { - if (!this._value) { - return this._value; - } - if (_.isBigNumber(this._value)) { - return _.Big(this._value).valueOf(); - } - return _.Big(this._value).toNumber(); - }, - input: function() { - return this._input; - }, - set: function(value) { - if (_.isBigNumber(value)) { - this._value = _.Big(value); - } else { - this._value = Number(value); - } - - return this; - }, - add: function(value) { - var corrFactor = _.correctionFactor.call(null, this._value, value); - - function cback(accum, curr, currI, O) { - return accum + Math.round(corrFactor * curr); - } - - this._value = _.reduce([this._value, value], cback, 0) / corrFactor; - - return this; - }, - subtract: function(value) { - var corrFactor = _.correctionFactor.call(null, this._value, value); - - function cback(accum, curr, currI, O) { - return accum - Math.round(corrFactor * curr); - } - - this._value = _.reduce([value], cback, Math.round(this._value * corrFactor)) / corrFactor; - - return this; - }, - multiply: function(value) { - function cback(accum, curr, currI, O) { - var corrFactor = _.correctionFactor(accum, curr); - return Math.round(accum * corrFactor) * Math.round(curr * corrFactor) / Math.round(corrFactor * corrFactor); - } - - this._value = _.reduce([this._value, value], cback, 1); - - return this; - }, - divide: function(value) { - function cback(accum, curr, currI, O) { - var corrFactor = _.correctionFactor(accum, curr); - return Math.round(accum * corrFactor) / Math.round(curr * corrFactor); - } - - this._value = _.reduce([this._value, value], cback); - - return this; - }, - difference: function(value) { - return Math.abs(numeral(this._value).subtract(value).value()); - } - }; - - /************************************ - Default Locale && Format - ************************************/ - - numeral.register('locale', 'en', { - delimiters: { - thousands: ',', - decimal: '.' - }, - abbreviations: { - thousand: 'k', - million: 'm', - billion: 'b', - trillion: 't' - }, - ordinal: function(number) { - var b = number % 10; - return (~~(number % 100 / 10) === 1) ? 'th' : - (b === 1) ? 'st' : - (b === 2) ? 'nd' : - (b === 3) ? 'rd' : 'th'; - }, - currency: { - symbol: '$' - } - }); - - - -(function() { - numeral.register('format', 'bps', { - regexps: { - format: /(BPS)/, - unformat: /(BPS)/ - }, - format: function(value, format, roundingFunction) { - var space = numeral._.includes(format, ' BPS') ? ' ' : '', - output; - - value = value * 10000; - - // check for space before BPS - format = format.replace(/\s?BPS/, ''); - - output = numeral._.numberToFormat(value, format, roundingFunction); - - if (numeral._.includes(output, ')')) { - output = output.split(''); - - output.splice(-1, 0, space + 'BPS'); - - output = output.join(''); - } else { - output = output + space + 'BPS'; - } - - return output; - }, - unformat: function(string) { - return +(numeral._.stringToNumber(string) * 0.0001).toFixed(15); - } - }); -})(); - - -(function() { - var decimal = { - base: 1000, - suffixes: ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] - }, - binary = { - base: 1024, - suffixes: ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'] - }; - - var allSuffixes = decimal.suffixes.concat(binary.suffixes.filter(function (item) { - return decimal.suffixes.indexOf(item) < 0; - })); - var unformatRegex = allSuffixes.join('|'); - // Allow support for BPS (http://www.investopedia.com/terms/b/basispoint.asp) - unformatRegex = '(' + unformatRegex.replace('B', 'B(?!PS)') + ')'; - - numeral.register('format', 'bytes', { - regexps: { - format: /([0\s]i?b)/, - unformat: new RegExp(unformatRegex) - }, - format: function(value, format, roundingFunction) { - var output, - bytes = numeral._.includes(format, 'ib') ? binary : decimal, - suffix = numeral._.includes(format, ' b') || numeral._.includes(format, ' ib') ? ' ' : '', - power, - min, - max; - - // check for space before - format = format.replace(/\s?i?b/, ''); - - for (power = 0; power <= bytes.suffixes.length; power++) { - min = Math.pow(bytes.base, power); - max = Math.pow(bytes.base, power + 1); - - if (value === null || value === 0 || value >= min && value < max) { - suffix += bytes.suffixes[power]; - - if (min > 0) { - value = value / min; - } - - break; - } - } - - output = numeral._.numberToFormat(value, format, roundingFunction); - - return output + suffix; - }, - unformat: function(string) { - var value = numeral._.stringToNumber(string), - power, - bytesMultiplier; - - if (value) { - for (power = decimal.suffixes.length - 1; power >= 0; power--) { - if (numeral._.includes(string, decimal.suffixes[power])) { - bytesMultiplier = Math.pow(decimal.base, power); - - break; - } - - if (numeral._.includes(string, binary.suffixes[power])) { - bytesMultiplier = Math.pow(binary.base, power); - - break; - } - } - - value *= (bytesMultiplier || 1); - } - - return value; - } - }); -})(); - - -(function() { - numeral.register('format', 'currency', { - regexps: { - format: /(\$)/ - }, - format: function(value, format, roundingFunction) { - var locale = numeral.locales[numeral.options.currentLocale], - symbols = { - before: format.match(/^([\+|\-|\(|\s|\$]*)/)[0], - after: format.match(/([\+|\-|\)|\s|\$]*)$/)[0] - }, - output, - symbol, - i; - - // strip format of spaces and $ - format = format.replace(/\s?\$\s?/, ''); - - // format the number - output = numeral._.numberToFormat(value, format, roundingFunction); - - // update the before and after based on value - if (value >= 0) { - symbols.before = symbols.before.replace(/[\-\(]/, ''); - symbols.after = symbols.after.replace(/[\-\)]/, ''); - } else if (value < 0 && (!numeral._.includes(symbols.before, '-') && !numeral._.includes(symbols.before, '('))) { - symbols.before = '-' + symbols.before; - } - - // loop through each before symbol - for (i = 0; i < symbols.before.length; i++) { - symbol = symbols.before[i]; - - switch (symbol) { - case '$': - output = numeral._.insert(output, locale.currency.symbol, i); - break; - case ' ': - output = numeral._.insert(output, ' ', i + locale.currency.symbol.length - 1); - break; - } - } - - // loop through each after symbol - for (i = symbols.after.length - 1; i >= 0; i--) { - symbol = symbols.after[i]; - - switch (symbol) { - case '$': - output = i === symbols.after.length - 1 ? output + locale.currency.symbol : numeral._.insert(output, locale.currency.symbol, -(symbols.after.length - (1 + i))); - break; - case ' ': - output = i === symbols.after.length - 1 ? output + ' ' : numeral._.insert(output, ' ', -(symbols.after.length - (1 + i) + locale.currency.symbol.length - 1)); - break; - } - } - - - return output; - } - }); -})(); - - -(function() { - numeral.register('format', 'exponential', { - regexps: { - format: /(e\+|e-)/, - unformat: /(e\+|e-)/ - }, - format: function(value, format, roundingFunction) { - var output, - exponential = typeof value === 'number' && !numeral._.isNaN(value) ? value.toExponential() : '0e+0', - parts = exponential.split('e'); - - format = format.replace(/e[\+|\-]{1}0/, ''); - - output = numeral._.numberToFormat(Number(parts[0]), format, roundingFunction); - - return output + 'e' + parts[1]; - }, - unformat: function(string) { - var parts = numeral._.includes(string, 'e+') ? string.split('e+') : string.split('e-'), - value = Number(parts[0]), - power = Number(parts[1]); - - power = numeral._.includes(string, 'e-') ? power *= -1 : power; - - function cback(accum, curr, currI, O) { - var corrFactor = numeral._.correctionFactor(accum, curr), - num = (accum * corrFactor) * (curr * corrFactor) / (corrFactor * corrFactor); - return num; - } - - return numeral._.reduce([value, Math.pow(10, power)], cback, 1); - } - }); -})(); - - -(function() { - numeral.register('format', 'ordinal', { - regexps: { - format: /(o)/ - }, - format: function(value, format, roundingFunction) { - var locale = numeral.locales[numeral.options.currentLocale], - output, - ordinal = numeral._.includes(format, ' o') ? ' ' : ''; - - // check for space before - format = format.replace(/\s?o/, ''); - - ordinal += locale.ordinal(value); - - output = numeral._.numberToFormat(value, format, roundingFunction); - - return output + ordinal; - } - }); -})(); - - -(function() { - numeral.register('format', 'percentage', { - regexps: { - format: /(%)/, - unformat: /(%)/ - }, - format: function(value, format, roundingFunction) { - var space = numeral._.includes(format, ' %') ? ' ' : '', - output; - - if (numeral.options.scalePercentBy100) { - value = value * 100; - } - - // check for space before % - format = format.replace(/\s?\%/, ''); - - output = numeral._.numberToFormat(value, format, roundingFunction); - - if (numeral._.includes(output, ')')) { - output = output.split(''); - - output.splice(-1, 0, space + '%'); - - output = output.join(''); - } else { - output = output + space + '%'; - } - - return output; - }, - unformat: function(string) { - var number = numeral._.stringToNumber(string); - if (numeral.options.scalePercentBy100) { - return number * 0.01; - } - return number; - } - }); -})(); - - -(function() { - numeral.register('format', 'time', { - regexps: { - format: /(:)/, - unformat: /(:)/ - }, - format: function(value, format, roundingFunction) { - var hours = Math.floor(value / 60 / 60), - minutes = Math.floor((value - (hours * 60 * 60)) / 60), - seconds = Math.round(value - (hours * 60 * 60) - (minutes * 60)); - - return hours + ':' + (minutes < 10 ? '0' + minutes : minutes) + ':' + (seconds < 10 ? '0' + seconds : seconds); - }, - unformat: function(string) { - var timeArray = string.split(':'), - seconds = 0; - - // turn hours and minutes into seconds and add them all up - if (timeArray.length === 3) { - // hours - seconds = seconds + (Number(timeArray[0]) * 60 * 60); - // minutes - seconds = seconds + (Number(timeArray[1]) * 60); - // seconds - seconds = seconds + Number(timeArray[2]); - } else if (timeArray.length === 2) { - // minutes - seconds = seconds + (Number(timeArray[0]) * 60); - // seconds - seconds = seconds + Number(timeArray[1]); - } - return Number(seconds); - } - }); -})(); - -return numeral; -})); diff --git a/viz-lib/src/lib/value-format.tsx b/viz-lib/src/lib/value-format.tsx index 529569b6f0..96693d2d3b 100644 --- a/viz-lib/src/lib/value-format.tsx +++ b/viz-lib/src/lib/value-format.tsx @@ -1,10 +1,7 @@ import React from "react"; import ReactDOMServer from "react-dom/server"; import moment from "moment/moment"; -// numeral is waiting on v3.0.0 to be available on npm -// https://github.com/adamwdraper/Numeral-js/pull/790 -// @ts-ignore -import numeral from "./numeral"; +import numeral from "numeral"; import { isString, isArray, isUndefined, isFinite, isNil, toString } from "lodash"; import { visualizationsSettings } from "@/visualizations/visualizationsSettings"; From 8b1c12d081584e2b681741e2c004aacb41b58286 Mon Sep 17 00:00:00 2001 From: mirkan-samsung-wsl Date: Mon, 6 May 2024 10:50:25 +0300 Subject: [PATCH 07/11] Fix JSONbig import and usage in query-result.js --- client/app/services/query-result.js | 7 ++++--- viz-lib/src/visualizations/table/columns/number.tsx | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/client/app/services/query-result.js b/client/app/services/query-result.js index 3aaafe7ee9..111ec4e96c 100644 --- a/client/app/services/query-result.js +++ b/client/app/services/query-result.js @@ -4,8 +4,9 @@ import { axios } from "@/services/axios"; import { QueryResultError } from "@/services/query"; import { Auth } from "@/services/auth"; import { isString, uniqBy, each, isNumber, includes, extend, forOwn, get } from "lodash"; +import JSONbig from 'json-bigint'; -const JSONbigString = require("json-bigint")({ storeAsString: true }); +const { parse: jsonParse } = JSONbig({ storeAsString: true }); const logger = debug("redash:services:QueryResult"); const filterTypes = ["filter", "multi-filter", "multiFilter"]; @@ -48,7 +49,7 @@ const createOrSaveUrl = data => (data.id ? `api/query_results/${data.id}` : "api const QueryResultResource = { get: ({ id }) => axios.get(`api/query_results/${id}`, { - transformResponse: response => JSONbigString.parse(response), + transformResponse: response => jsonParse(response), }), post: data => axios.post(createOrSaveUrl(data), data), }; @@ -353,7 +354,7 @@ class QueryResult { axios .get(`api/queries/${queryId}/results/${id}.json`, { - transformResponse: response => JSONbigString.parse(response), + transformResponse: response => jsonParse(response), }) .then(response => { // Success handler diff --git a/viz-lib/src/visualizations/table/columns/number.tsx b/viz-lib/src/visualizations/table/columns/number.tsx index 01d07ca12f..78a8e8f415 100644 --- a/viz-lib/src/visualizations/table/columns/number.tsx +++ b/viz-lib/src/visualizations/table/columns/number.tsx @@ -37,7 +37,7 @@ export default function initNumberColumn(column: any) { function prepareData(row: any) { return { - text: format(row[column.name]) + text: format(row[column.name]), }; } From 156362dd4a317a77aa4f9bbc6b619c468f35d0a3 Mon Sep 17 00:00:00 2001 From: "Restyled.io" Date: Mon, 6 May 2024 07:50:42 +0000 Subject: [PATCH 08/11] Restyled by prettier --- client/app/services/query-result.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/app/services/query-result.js b/client/app/services/query-result.js index 111ec4e96c..6b44456607 100644 --- a/client/app/services/query-result.js +++ b/client/app/services/query-result.js @@ -4,7 +4,7 @@ import { axios } from "@/services/axios"; import { QueryResultError } from "@/services/query"; import { Auth } from "@/services/auth"; import { isString, uniqBy, each, isNumber, includes, extend, forOwn, get } from "lodash"; -import JSONbig from 'json-bigint'; +import JSONbig from "json-bigint"; const { parse: jsonParse } = JSONbig({ storeAsString: true }); const logger = debug("redash:services:QueryResult"); From 2c27db5d6ffc2ed7e60d07099d5bea97cf1f24a2 Mon Sep 17 00:00:00 2001 From: mirkan-samsung-wsl Date: Mon, 6 May 2024 12:17:20 +0300 Subject: [PATCH 09/11] Fix schema handling in QuerySource.jsx --- client/app/pages/queries/QuerySource.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/app/pages/queries/QuerySource.jsx b/client/app/pages/queries/QuerySource.jsx index adb1a0bc4e..7e932587e8 100644 --- a/client/app/pages/queries/QuerySource.jsx +++ b/client/app/pages/queries/QuerySource.jsx @@ -262,7 +262,7 @@ function QuerySource(props) { data-executing={isQueryExecuting ? "true" : null} syntax={dataSource ? dataSource.syntax : null} value={query.query} - schema={schema} + schema={schema.length && schema[0].name ? schema : null} autocompleteEnabled={autocompleteAvailable && autocompleteEnabled} onChange={handleQueryEditorChange} onSelectionChange={setSelectedText} From dc7f2439ce808821857a6498689820ecf5948b21 Mon Sep 17 00:00:00 2001 From: mirkan-samsung-wsl Date: Mon, 6 May 2024 21:18:56 +0300 Subject: [PATCH 10/11] Revert "Fix schema handling in QuerySource.jsx" This reverts commit 2c27db5d6ffc2ed7e60d07099d5bea97cf1f24a2. --- client/app/pages/queries/QuerySource.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/app/pages/queries/QuerySource.jsx b/client/app/pages/queries/QuerySource.jsx index 7e932587e8..adb1a0bc4e 100644 --- a/client/app/pages/queries/QuerySource.jsx +++ b/client/app/pages/queries/QuerySource.jsx @@ -262,7 +262,7 @@ function QuerySource(props) { data-executing={isQueryExecuting ? "true" : null} syntax={dataSource ? dataSource.syntax : null} value={query.query} - schema={schema.length && schema[0].name ? schema : null} + schema={schema} autocompleteEnabled={autocompleteAvailable && autocompleteEnabled} onChange={handleQueryEditorChange} onSelectionChange={setSelectedText} From 3b6ca98df0baa50d43fd0a6705440c758b1e0e86 Mon Sep 17 00:00:00 2001 From: mirkan-samsung-wsl Date: Thu, 9 May 2024 22:35:01 +0300 Subject: [PATCH 11/11] Revert "changes are reverted according to Andrew's commit" This reverts commit 5848c623a6a29d7a0c44befed9fd656ba7d516f1. --- viz-lib/src/lib/numeral.js | 1068 ++++++++++++++++++++++++++++++ viz-lib/src/lib/value-format.tsx | 5 +- 2 files changed, 1072 insertions(+), 1 deletion(-) create mode 100644 viz-lib/src/lib/numeral.js diff --git a/viz-lib/src/lib/numeral.js b/viz-lib/src/lib/numeral.js new file mode 100644 index 0000000000..be1d393692 --- /dev/null +++ b/viz-lib/src/lib/numeral.js @@ -0,0 +1,1068 @@ +/*! @preserve + * numeral.js + * version : 3.0.0 + * author : Adam Draper + * license : MIT + * http://adamwdraper.github.com/Numeral-js/ + */ + +(function (global, factory) { + if (typeof define === 'function' && define.amd) { + define(factory); + } else if (typeof module === 'object' && module.exports) { + module.exports = factory(); + } else { + global.numeral = factory(); + } +}(this, function () { + /************************************ + Variables + ************************************/ + + var numeral, + _, + VERSION = '3.0.0', + formats = {}, + locales = {}, + defaults = { + currentLocale: 'en', + zeroFormat: null, + nullFormat: null, + defaultFormat: '0,0', + scalePercentBy100: true + }, + options = { + currentLocale: defaults.currentLocale, + zeroFormat: defaults.zeroFormat, + nullFormat: defaults.nullFormat, + defaultFormat: defaults.defaultFormat, + scalePercentBy100: defaults.scalePercentBy100 + }; + + + /************************************ + Constructors + ************************************/ + + // Numeral prototype object + function Numeral(input, number) { + this._input = input; + + this._value = number; + } + + numeral = function(input) { + var value, + kind, + unformatFunction, + regexp; + + if (numeral.isNumeral(input)) { + value = input.value(); + } else if (input === 0 || typeof input === 'undefined') { + value = 0; + } else if (input === null || _.isNaN(input)) { + value = null; + } else if (typeof input === 'string') { + if (options.zeroFormat && input === options.zeroFormat) { + value = 0; + } else if (options.nullFormat && input === options.nullFormat || !input.replace(/[^0-9]+/g, '').length) { + value = null; + } else { + for (kind in formats) { + regexp = typeof formats[kind].regexps.unformat === 'function' ? formats[kind].regexps.unformat() : formats[kind].regexps.unformat; + + if (regexp && input.match(regexp)) { + unformatFunction = formats[kind].unformat; + + break; + } + } + + unformatFunction = unformatFunction || numeral._.stringToNumber; + + value = unformatFunction(input); + } + } else if (_.isBigNumber(input)) { + value = input; + } else if (typeof input === 'object') { + value = input.toString(); + } else { + value = Number(input) || null; + } + + + return new Numeral(input, value); + }; + + // version number + numeral.version = VERSION; + + // compare numeral object + numeral.isNumeral = function(obj) { + return obj instanceof Numeral; + }; + + // helper functions + numeral._ = _ = { + Big: require('big.js'), + // formats numbers separators, decimals places, signs, abbreviations + numberToFormat: function(value, format, roundingFunction) { + var locale = locales[numeral.options.currentLocale], + negP = false, + optDec = false, + leadingCount = 0, + abbr = '', + trillion = 1000000000000, + billion = 1000000000, + million = 1000000, + thousand = 1000, + decimal = '', + neg = false, + abbrForce, // force abbreviation + abs, + min, + max, + power, + int, + precision, + signed, + thousands, + output; + + // make sure we never format a null value + value = value || 0; + // Use conditional to handle regular numbers and BigInts separately + if (_.isBigNumber(value)) { + value = _.Big(value); + abs = _.Big(value).abs(); + trillion = _.Big(trillion); + billion = _.Big(billion); + million = _.Big(million); + thousand = _.Big(thousand); + } else { + abs = Math.abs(value); + } + + // see if we should use parentheses for negative number or if we should prefix with a sign + // if both are present we default to parentheses + if (numeral._.includes(format, '(')) { + negP = true; + format = format.replace(/[\(|\)]/g, ''); + } else if (numeral._.includes(format, '+') || numeral._.includes(format, '-')) { + signed = numeral._.includes(format, '+') ? format.indexOf('+') : value < 0 ? format.indexOf('-') : -1; + format = format.replace(/[\+|\-]/g, ''); + } + + // see if abbreviation is wanted + if (numeral._.includes(format, 'a')) { + abbrForce = format.match(/a(k|m|b|t)?/); + + abbrForce = abbrForce ? abbrForce[1] : false; + + // check for space before abbreviation + if (numeral._.includes(format, ' a')) { + abbr = ' '; + } + + format = format.replace(new RegExp(abbr + 'a[kmbt]?'), ''); + + if (abs >= trillion && !abbrForce || abbrForce === 't') { + // trillion + abbr += locale.abbreviations.trillion; + value = value / trillion; + } else if (abs < trillion && abs >= billion && !abbrForce || abbrForce === 'b') { + // billion + abbr += locale.abbreviations.billion; + value = value / billion; + } else if (abs < billion && abs >= million && !abbrForce || abbrForce === 'm') { + // million + abbr += locale.abbreviations.million; + value = value / million; + } else if (abs < million && abs >= thousand && !abbrForce || abbrForce === 'k') { + // thousand + abbr += locale.abbreviations.thousand; + value = value / thousand; + } + } + + // check for optional decimals + if (numeral._.includes(format, '[.]')) { + optDec = true; + format = format.replace('[.]', '.'); + } + + // break number and format + int = value.toString().split('.')[0]; + precision = format.split('.')[1]; + thousands = format.indexOf(','); + leadingCount = (format.split('.')[0].split(',')[0].match(/0/g) || []).length; + + if (precision) { + if (numeral._.includes(precision, '[')) { + precision = precision.replace(']', ''); + precision = precision.split('['); + decimal = numeral._.toFixed(value, (precision[0].length + precision[1].length), roundingFunction, precision[1].length); + } else { + decimal = numeral._.toFixed(value, precision.length, roundingFunction); + } + + int = decimal.split('.')[0]; + + if (numeral._.includes(decimal, '.')) { + decimal = locale.delimiters.decimal + decimal.split('.')[1]; + } else { + decimal = ''; + } + + if (optDec && Number(decimal.slice(1)) === 0) { + decimal = ''; + } + } else { + int = numeral._.toFixed(value, 0, roundingFunction); + } + + // check abbreviation again after rounding + if (abbr && !abbrForce && Number(int) >= 1000 && abbr !== locale.abbreviations.trillion) { + int = String(Number(int) / 1000); + + switch (abbr) { + case locale.abbreviations.thousand: + abbr = locale.abbreviations.million; + break; + case locale.abbreviations.million: + abbr = locale.abbreviations.billion; + break; + case locale.abbreviations.billion: + abbr = locale.abbreviations.trillion; + break; + } + } + + + // format number + if (numeral._.includes(int, '-')) { + int = int.slice(1); + neg = true; + } + + if (int.length < leadingCount) { + for (var i = leadingCount - int.length; i > 0; i--) { + int = '0' + int; + } + } + + if (thousands > -1) { + int = int.toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1' + locale.delimiters.thousands); + } + + if (format.indexOf('.') === 0) { + int = ''; + } + + output = int + decimal + (abbr ? abbr : ''); + + if (negP) { + output = (negP && neg ? '(' : '') + output + (negP && neg ? ')' : ''); + } else { + if (signed >= 0) { + output = signed === 0 ? (neg ? '-' : '+') + output : output + (neg ? '-' : '+'); + } else if (neg) { + output = '-' + output; + } + } + + return output; + }, + // unformats numbers separators, decimals places, signs, abbreviations + stringToNumber: function(string) { + var locale = locales[options.currentLocale], + stringOriginal = string, + abbreviations = { + thousand: 3, + million: 6, + billion: 9, + trillion: 12, + quadrillion: 15, + quintillion: 18, + sextillion: 21, + septillion: 24, + octillion: 27, + nonillion: 30, + decillion: 33, + undecillion: 36, + duodecillion: 39, + tredecillion: 42, + quattuordecillion: 45, + quindecillion: 48, + sexdecillion: 51, + septendecillion: 54, + octodecillion: 57, + novemdecillion: 60, + vigintillion: 63, + unvigintillion: 66, + }, + abbreviation, + value, + i, + regexp; + + if (options.zeroFormat && string === options.zeroFormat) { + value = _.Big(0); + } else if (options.nullFormat && string === options.nullFormat || !string.replace(/[^0-9]+/g, '').length) { + value = null; + } else { + value = _.Big(1); + + if (locale.delimiters.decimal !== '.') { + string = string.replace(/\./g, '').replace(locale.delimiters.decimal, '.'); + } + + for (abbreviation in abbreviations) { + regexp = new RegExp('[^a-zA-Z]' + locale.abbreviations[abbreviation] + '(?:\\)|(\\' + locale.currency.symbol + ')?(?:\\))?)?$'); + + if (stringOriginal.match(regexp)) { + value = value.times(new _.Big(10).pow(abbreviations[abbreviation])); + break; + } + } + + // check for negative number + value *= (string.split('-').length + Math.min(string.split('(').length - 1, string.split(')').length - 1)) % 2 ? _.Big(1) : _.Big(-1); + + // remove non numbers + string = string.replace(/[^0-9\.]+/g, ''); + value = _.Big(value); + value = _.Big(string).times(value); + if (_.isBigNumber(value)) { + value = value.toString(); + } else { + value = value.toNumber(); + } + } + + return value; + }, + isNaN: function(value) { + return typeof value === 'number' && isNaN(value); + }, + includes: function(string, search) { + return string.indexOf(search) !== -1; + }, + insert: function(string, subString, start) { + return string.slice(0, start) + subString + string.slice(start); + }, + reduce: function(array, callback /*, initialValue*/) { + if (this === null) { + throw new TypeError('Array.prototype.reduce called on null or undefined'); + } + + if (typeof callback !== 'function') { + throw new TypeError(callback + ' is not a function'); + } + + var t = Object(array), + len = t.length >>> 0, + k = 0, + value; + + if (arguments.length === 3) { + value = arguments[2]; + } else { + while (k < len && !(k in t)) { + k++; + } + + if (k >= len) { + throw new TypeError('Reduce of empty array with no initial value'); + } + + value = t[k++]; + } + for (; k < len; k++) { + if (k in t) { + value = callback(value, t[k], k, t); + } + } + return value; + }, + /** + * Computes the multiplier necessary to make x >= 1, + * effectively eliminating miscalculations caused by + * finite precision. + */ + multiplier: function (x) { + var parts = x.toString().split('.'); + + return parts.length < 2 ? 1 : Math.pow(10, parts[1].length); + }, + /** + * Given a variable number of arguments, returns the maximum + * multiplier that must be used to normalize an operation involving + * all of them. + */ + correctionFactor: function () { + var args = Array.prototype.slice.call(arguments); + + return args.reduce(function(accum, next) { + var mn = _.multiplier(next); + return accum > mn ? accum : mn; + }, 1); + }, + isBigNumber: function (number) { + return _.Big(number).abs() > Number.MAX_SAFE_INTEGER; + }, + /** + * Implementation of toFixed() that treats floats more like decimals + * + * Fixes binary rounding issues (eg. (0.615).toFixed(2) === '0.61') that present + * problems for accounting- and finance-related software. + */ + toFixed: function(value, maxDecimals, roundingFunction, optionals) { + var splitValue = value.toString().split('.'), + minDecimals = maxDecimals - (optionals || 0), + boundedPrecision, + optionalsRegExp, + power, + output; + + // Use the smallest precision value possible to avoid errors from floating point representation + if (splitValue.length === 2) { + boundedPrecision = Math.min(Math.max(splitValue[1].length, minDecimals), maxDecimals); + } else { + boundedPrecision = minDecimals; + } + if (_.isBigNumber(value)) { + power = _.Big(10).pow(boundedPrecision); + output = _.Big(value).toFixed(boundedPrecision); + } else { + power = Math.pow(10, boundedPrecision); + output = (roundingFunction(value + 'e+' + boundedPrecision) / power).toFixed(boundedPrecision); + } + + // Multiply up by precision, round accurately, then divide and use native toFixed(): + + if (optionals > maxDecimals - boundedPrecision) { + optionalsRegExp = new RegExp('\\.?0{1,' + (optionals - (maxDecimals - boundedPrecision)) + '}$'); + output = output.replace(optionalsRegExp, ''); + } + + return output; + } + }; + + // avaliable options + numeral.options = options; + + // avaliable formats + numeral.formats = formats; + + // avaliable formats + numeral.locales = locales; + + // This function sets the current locale. If + // no arguments are passed in, it will simply return the current global + // locale key. + numeral.locale = function(key) { + if (key) { + options.currentLocale = key.toLowerCase(); + } + + return options.currentLocale; + }; + + // This function provides access to the loaded locale data. If + // no arguments are passed in, it will simply return the current + // global locale object. + numeral.localeData = function(key) { + if (!key) { + return locales[options.currentLocale]; + } + + key = key.toLowerCase(); + + if (!locales[key]) { + throw new Error('Unknown locale : ' + key); + } + + return locales[key]; + }; + + numeral.reset = function() { + for (var property in defaults) { + options[property] = defaults[property]; + } + }; + + numeral.zeroFormat = function(format) { + options.zeroFormat = typeof(format) === 'string' ? format : null; + }; + + numeral.nullFormat = function (format) { + options.nullFormat = typeof(format) === 'string' ? format : null; + }; + + numeral.defaultFormat = function(format) { + options.defaultFormat = typeof(format) === 'string' ? format : '0.0'; + }; + + numeral.register = function(type, name, format) { + name = name.toLowerCase(); + + if (this[type + 's'][name]) { + throw new TypeError(name + ' ' + type + ' already registered.'); + } + + this[type + 's'][name] = format; + + return format; + }; + + + numeral.validate = function(val, culture) { + var _decimalSep, + _thousandSep, + _currSymbol, + _valArray, + _abbrObj, + _thousandRegEx, + localeData, + temp; + + //coerce val to string + if (typeof val !== 'string') { + val += ''; + + if (console.warn) { + console.warn('Numeral.js: Value is not string. It has been co-erced to: ', val); + } + } + + //trim whitespaces from either sides + val = val.trim(); + + //if val is just digits return true + if (!!val.match(/^\d+$/)) { + return true; + } + + //if val is empty return false + if (val === '') { + return false; + } + + //get the decimal and thousands separator from numeral.localeData + try { + //check if the culture is understood by numeral. if not, default it to current locale + localeData = numeral.localeData(culture); + } catch (e) { + localeData = numeral.localeData(numeral.locale()); + } + + //setup the delimiters and currency symbol based on culture/locale + _currSymbol = localeData.currency.symbol; + _abbrObj = localeData.abbreviations; + _decimalSep = localeData.delimiters.decimal; + if (localeData.delimiters.thousands === '.') { + _thousandSep = '\\.'; + } else { + _thousandSep = localeData.delimiters.thousands; + } + + // validating currency symbol + temp = val.match(/^[^\d]+/); + if (temp !== null) { + val = val.substr(1); + if (temp[0] !== _currSymbol) { + return false; + } + } + + //validating abbreviation symbol + temp = val.match(/[^\d]+$/); + if (temp !== null) { + val = val.slice(0, -1); + if (temp[0] !== _abbrObj.thousand && temp[0] !== _abbrObj.million && temp[0] !== _abbrObj.billion && temp[0] !== _abbrObj.trillion) { + return false; + } + } + + _thousandRegEx = new RegExp(_thousandSep + '{2}'); + + if (!val.match(/[^\d.,]/g)) { + _valArray = val.split(_decimalSep); + if (_valArray.length > 2) { + return false; + } else { + if (_valArray.length < 2) { + return ( !! _valArray[0].match(/^\d+.*\d$/) && !_valArray[0].match(_thousandRegEx)); + } else { + if (_valArray[0].length === 1) { + return ( !! _valArray[0].match(/^\d+$/) && !_valArray[0].match(_thousandRegEx) && !! _valArray[1].match(/^\d+$/)); + } else { + return ( !! _valArray[0].match(/^\d+.*\d$/) && !_valArray[0].match(_thousandRegEx) && !! _valArray[1].match(/^\d+$/)); + } + } + } + } + + return false; + }; + + + /************************************ + Numeral Prototype + ************************************/ + + numeral.fn = Numeral.prototype = { + clone: function() { + return numeral(this); + }, + format: function(inputString, roundingFunction) { + var value = this._value, + format = inputString || options.defaultFormat, + kind, + output, + formatFunction; + + // make sure we have a roundingFunction + roundingFunction = roundingFunction || Math.round; + + // format based on value + if (value === 0 && options.zeroFormat !== null) { + output = options.zeroFormat; + } else if (value === null && options.nullFormat !== null) { + output = options.nullFormat; + } else { + for (kind in formats) { + if (format.match(formats[kind].regexps.format)) { + formatFunction = formats[kind].format; + + break; + } + } + + formatFunction = formatFunction || numeral._.numberToFormat; + + output = formatFunction(value, format, roundingFunction); + } + + return output; + }, + value: function() { + if (!this._value) { + return this._value; + } + if (_.isBigNumber(this._value)) { + return _.Big(this._value).valueOf(); + } + return _.Big(this._value).toNumber(); + }, + input: function() { + return this._input; + }, + set: function(value) { + if (_.isBigNumber(value)) { + this._value = _.Big(value); + } else { + this._value = Number(value); + } + + return this; + }, + add: function(value) { + var corrFactor = _.correctionFactor.call(null, this._value, value); + + function cback(accum, curr, currI, O) { + return accum + Math.round(corrFactor * curr); + } + + this._value = _.reduce([this._value, value], cback, 0) / corrFactor; + + return this; + }, + subtract: function(value) { + var corrFactor = _.correctionFactor.call(null, this._value, value); + + function cback(accum, curr, currI, O) { + return accum - Math.round(corrFactor * curr); + } + + this._value = _.reduce([value], cback, Math.round(this._value * corrFactor)) / corrFactor; + + return this; + }, + multiply: function(value) { + function cback(accum, curr, currI, O) { + var corrFactor = _.correctionFactor(accum, curr); + return Math.round(accum * corrFactor) * Math.round(curr * corrFactor) / Math.round(corrFactor * corrFactor); + } + + this._value = _.reduce([this._value, value], cback, 1); + + return this; + }, + divide: function(value) { + function cback(accum, curr, currI, O) { + var corrFactor = _.correctionFactor(accum, curr); + return Math.round(accum * corrFactor) / Math.round(curr * corrFactor); + } + + this._value = _.reduce([this._value, value], cback); + + return this; + }, + difference: function(value) { + return Math.abs(numeral(this._value).subtract(value).value()); + } + }; + + /************************************ + Default Locale && Format + ************************************/ + + numeral.register('locale', 'en', { + delimiters: { + thousands: ',', + decimal: '.' + }, + abbreviations: { + thousand: 'k', + million: 'm', + billion: 'b', + trillion: 't' + }, + ordinal: function(number) { + var b = number % 10; + return (~~(number % 100 / 10) === 1) ? 'th' : + (b === 1) ? 'st' : + (b === 2) ? 'nd' : + (b === 3) ? 'rd' : 'th'; + }, + currency: { + symbol: '$' + } + }); + + + +(function() { + numeral.register('format', 'bps', { + regexps: { + format: /(BPS)/, + unformat: /(BPS)/ + }, + format: function(value, format, roundingFunction) { + var space = numeral._.includes(format, ' BPS') ? ' ' : '', + output; + + value = value * 10000; + + // check for space before BPS + format = format.replace(/\s?BPS/, ''); + + output = numeral._.numberToFormat(value, format, roundingFunction); + + if (numeral._.includes(output, ')')) { + output = output.split(''); + + output.splice(-1, 0, space + 'BPS'); + + output = output.join(''); + } else { + output = output + space + 'BPS'; + } + + return output; + }, + unformat: function(string) { + return +(numeral._.stringToNumber(string) * 0.0001).toFixed(15); + } + }); +})(); + + +(function() { + var decimal = { + base: 1000, + suffixes: ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] + }, + binary = { + base: 1024, + suffixes: ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'] + }; + + var allSuffixes = decimal.suffixes.concat(binary.suffixes.filter(function (item) { + return decimal.suffixes.indexOf(item) < 0; + })); + var unformatRegex = allSuffixes.join('|'); + // Allow support for BPS (http://www.investopedia.com/terms/b/basispoint.asp) + unformatRegex = '(' + unformatRegex.replace('B', 'B(?!PS)') + ')'; + + numeral.register('format', 'bytes', { + regexps: { + format: /([0\s]i?b)/, + unformat: new RegExp(unformatRegex) + }, + format: function(value, format, roundingFunction) { + var output, + bytes = numeral._.includes(format, 'ib') ? binary : decimal, + suffix = numeral._.includes(format, ' b') || numeral._.includes(format, ' ib') ? ' ' : '', + power, + min, + max; + + // check for space before + format = format.replace(/\s?i?b/, ''); + + for (power = 0; power <= bytes.suffixes.length; power++) { + min = Math.pow(bytes.base, power); + max = Math.pow(bytes.base, power + 1); + + if (value === null || value === 0 || value >= min && value < max) { + suffix += bytes.suffixes[power]; + + if (min > 0) { + value = value / min; + } + + break; + } + } + + output = numeral._.numberToFormat(value, format, roundingFunction); + + return output + suffix; + }, + unformat: function(string) { + var value = numeral._.stringToNumber(string), + power, + bytesMultiplier; + + if (value) { + for (power = decimal.suffixes.length - 1; power >= 0; power--) { + if (numeral._.includes(string, decimal.suffixes[power])) { + bytesMultiplier = Math.pow(decimal.base, power); + + break; + } + + if (numeral._.includes(string, binary.suffixes[power])) { + bytesMultiplier = Math.pow(binary.base, power); + + break; + } + } + + value *= (bytesMultiplier || 1); + } + + return value; + } + }); +})(); + + +(function() { + numeral.register('format', 'currency', { + regexps: { + format: /(\$)/ + }, + format: function(value, format, roundingFunction) { + var locale = numeral.locales[numeral.options.currentLocale], + symbols = { + before: format.match(/^([\+|\-|\(|\s|\$]*)/)[0], + after: format.match(/([\+|\-|\)|\s|\$]*)$/)[0] + }, + output, + symbol, + i; + + // strip format of spaces and $ + format = format.replace(/\s?\$\s?/, ''); + + // format the number + output = numeral._.numberToFormat(value, format, roundingFunction); + + // update the before and after based on value + if (value >= 0) { + symbols.before = symbols.before.replace(/[\-\(]/, ''); + symbols.after = symbols.after.replace(/[\-\)]/, ''); + } else if (value < 0 && (!numeral._.includes(symbols.before, '-') && !numeral._.includes(symbols.before, '('))) { + symbols.before = '-' + symbols.before; + } + + // loop through each before symbol + for (i = 0; i < symbols.before.length; i++) { + symbol = symbols.before[i]; + + switch (symbol) { + case '$': + output = numeral._.insert(output, locale.currency.symbol, i); + break; + case ' ': + output = numeral._.insert(output, ' ', i + locale.currency.symbol.length - 1); + break; + } + } + + // loop through each after symbol + for (i = symbols.after.length - 1; i >= 0; i--) { + symbol = symbols.after[i]; + + switch (symbol) { + case '$': + output = i === symbols.after.length - 1 ? output + locale.currency.symbol : numeral._.insert(output, locale.currency.symbol, -(symbols.after.length - (1 + i))); + break; + case ' ': + output = i === symbols.after.length - 1 ? output + ' ' : numeral._.insert(output, ' ', -(symbols.after.length - (1 + i) + locale.currency.symbol.length - 1)); + break; + } + } + + + return output; + } + }); +})(); + + +(function() { + numeral.register('format', 'exponential', { + regexps: { + format: /(e\+|e-)/, + unformat: /(e\+|e-)/ + }, + format: function(value, format, roundingFunction) { + var output, + exponential = typeof value === 'number' && !numeral._.isNaN(value) ? value.toExponential() : '0e+0', + parts = exponential.split('e'); + + format = format.replace(/e[\+|\-]{1}0/, ''); + + output = numeral._.numberToFormat(Number(parts[0]), format, roundingFunction); + + return output + 'e' + parts[1]; + }, + unformat: function(string) { + var parts = numeral._.includes(string, 'e+') ? string.split('e+') : string.split('e-'), + value = Number(parts[0]), + power = Number(parts[1]); + + power = numeral._.includes(string, 'e-') ? power *= -1 : power; + + function cback(accum, curr, currI, O) { + var corrFactor = numeral._.correctionFactor(accum, curr), + num = (accum * corrFactor) * (curr * corrFactor) / (corrFactor * corrFactor); + return num; + } + + return numeral._.reduce([value, Math.pow(10, power)], cback, 1); + } + }); +})(); + + +(function() { + numeral.register('format', 'ordinal', { + regexps: { + format: /(o)/ + }, + format: function(value, format, roundingFunction) { + var locale = numeral.locales[numeral.options.currentLocale], + output, + ordinal = numeral._.includes(format, ' o') ? ' ' : ''; + + // check for space before + format = format.replace(/\s?o/, ''); + + ordinal += locale.ordinal(value); + + output = numeral._.numberToFormat(value, format, roundingFunction); + + return output + ordinal; + } + }); +})(); + + +(function() { + numeral.register('format', 'percentage', { + regexps: { + format: /(%)/, + unformat: /(%)/ + }, + format: function(value, format, roundingFunction) { + var space = numeral._.includes(format, ' %') ? ' ' : '', + output; + + if (numeral.options.scalePercentBy100) { + value = value * 100; + } + + // check for space before % + format = format.replace(/\s?\%/, ''); + + output = numeral._.numberToFormat(value, format, roundingFunction); + + if (numeral._.includes(output, ')')) { + output = output.split(''); + + output.splice(-1, 0, space + '%'); + + output = output.join(''); + } else { + output = output + space + '%'; + } + + return output; + }, + unformat: function(string) { + var number = numeral._.stringToNumber(string); + if (numeral.options.scalePercentBy100) { + return number * 0.01; + } + return number; + } + }); +})(); + + +(function() { + numeral.register('format', 'time', { + regexps: { + format: /(:)/, + unformat: /(:)/ + }, + format: function(value, format, roundingFunction) { + var hours = Math.floor(value / 60 / 60), + minutes = Math.floor((value - (hours * 60 * 60)) / 60), + seconds = Math.round(value - (hours * 60 * 60) - (minutes * 60)); + + return hours + ':' + (minutes < 10 ? '0' + minutes : minutes) + ':' + (seconds < 10 ? '0' + seconds : seconds); + }, + unformat: function(string) { + var timeArray = string.split(':'), + seconds = 0; + + // turn hours and minutes into seconds and add them all up + if (timeArray.length === 3) { + // hours + seconds = seconds + (Number(timeArray[0]) * 60 * 60); + // minutes + seconds = seconds + (Number(timeArray[1]) * 60); + // seconds + seconds = seconds + Number(timeArray[2]); + } else if (timeArray.length === 2) { + // minutes + seconds = seconds + (Number(timeArray[0]) * 60); + // seconds + seconds = seconds + Number(timeArray[1]); + } + return Number(seconds); + } + }); +})(); + +return numeral; +})); diff --git a/viz-lib/src/lib/value-format.tsx b/viz-lib/src/lib/value-format.tsx index 96693d2d3b..529569b6f0 100644 --- a/viz-lib/src/lib/value-format.tsx +++ b/viz-lib/src/lib/value-format.tsx @@ -1,7 +1,10 @@ import React from "react"; import ReactDOMServer from "react-dom/server"; import moment from "moment/moment"; -import numeral from "numeral"; +// numeral is waiting on v3.0.0 to be available on npm +// https://github.com/adamwdraper/Numeral-js/pull/790 +// @ts-ignore +import numeral from "./numeral"; import { isString, isArray, isUndefined, isFinite, isNil, toString } from "lodash"; import { visualizationsSettings } from "@/visualizations/visualizationsSettings";