-
Notifications
You must be signed in to change notification settings - Fork 63
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Format attribute for widgets, popups and legends [ch60845] #1626
Changes from all commits
9e9046b
0529e26
38b98ec
653e362
f1b85fd
9fd6442
0f7365a
469defc
4bfa8e8
467642c
f3dc0b5
22b30db
e49a7ca
74955b0
b6028fa
f779905
071945b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
cartoframes/assets/src/bundle.js |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
{ | ||
"esversion": 6 | ||
"esversion": 6, | ||
"laxbreak" : true | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -135,18 +135,330 @@ var init = (function () { | |
stacktrace$.innerHTML = list.join('\n'); | ||
} | ||
|
||
function format(value) { | ||
// Computes the decimal coefficient and exponent of the specified number x with | ||
// significant digits p, where x is positive and p is in [1, 21] or undefined. | ||
// For example, formatDecimal(1.23) returns ["123", 0]. | ||
function formatDecimal(x, p) { | ||
if ((i = (x = p ? x.toExponential(p - 1) : x.toExponential()).indexOf("e")) < 0) return null; // NaN, ±Infinity | ||
var i, coefficient = x.slice(0, i); | ||
|
||
// The string returned by toExponential either has the form \d\.\d+e[-+]\d+ | ||
// (e.g., 1.2e+3) or the form \de[-+]\d+ (e.g., 1e+3). | ||
return [ | ||
coefficient.length > 1 ? coefficient[0] + coefficient.slice(2) : coefficient, | ||
+x.slice(i + 1) | ||
]; | ||
} | ||
|
||
function exponent(x) { | ||
return x = formatDecimal(Math.abs(x)), x ? x[1] : NaN; | ||
} | ||
|
||
function formatGroup(grouping, thousands) { | ||
return function(value, width) { | ||
var i = value.length, | ||
t = [], | ||
j = 0, | ||
g = grouping[0], | ||
length = 0; | ||
|
||
while (i > 0 && g > 0) { | ||
if (length + g + 1 > width) g = Math.max(1, width - length); | ||
t.push(value.substring(i -= g, i + g)); | ||
if ((length += g + 1) > width) break; | ||
g = grouping[j = (j + 1) % grouping.length]; | ||
} | ||
|
||
return t.reverse().join(thousands); | ||
}; | ||
} | ||
|
||
function formatNumerals(numerals) { | ||
return function(value) { | ||
return value.replace(/[0-9]/g, function(i) { | ||
return numerals[+i]; | ||
}); | ||
}; | ||
} | ||
|
||
// [[fill]align][sign][symbol][0][width][,][.precision][~][type] | ||
var re = /^(?:(.)?([<>=^]))?([+\-( ])?([$#])?(0)?(\d+)?(,)?(\.\d+)?(~)?([a-z%])?$/i; | ||
|
||
function formatSpecifier(specifier) { | ||
if (!(match = re.exec(specifier))) throw new Error("invalid format: " + specifier); | ||
var match; | ||
return new FormatSpecifier({ | ||
fill: match[1], | ||
align: match[2], | ||
sign: match[3], | ||
symbol: match[4], | ||
zero: match[5], | ||
width: match[6], | ||
comma: match[7], | ||
precision: match[8] && match[8].slice(1), | ||
trim: match[9], | ||
type: match[10] | ||
}); | ||
} | ||
|
||
formatSpecifier.prototype = FormatSpecifier.prototype; // instanceof | ||
|
||
function FormatSpecifier(specifier) { | ||
this.fill = specifier.fill === undefined ? " " : specifier.fill + ""; | ||
this.align = specifier.align === undefined ? ">" : specifier.align + ""; | ||
this.sign = specifier.sign === undefined ? "-" : specifier.sign + ""; | ||
this.symbol = specifier.symbol === undefined ? "" : specifier.symbol + ""; | ||
this.zero = !!specifier.zero; | ||
this.width = specifier.width === undefined ? undefined : +specifier.width; | ||
this.comma = !!specifier.comma; | ||
this.precision = specifier.precision === undefined ? undefined : +specifier.precision; | ||
this.trim = !!specifier.trim; | ||
this.type = specifier.type === undefined ? "" : specifier.type + ""; | ||
} | ||
|
||
FormatSpecifier.prototype.toString = function() { | ||
return this.fill | ||
+ this.align | ||
+ this.sign | ||
+ this.symbol | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Misleading line break before '+'; readers may interpret this as an expression boundary. |
||
+ (this.zero ? "0" : "") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Misleading line break before '+'; readers may interpret this as an expression boundary. |
||
+ (this.width === undefined ? "" : Math.max(1, this.width | 0)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Misleading line break before '+'; readers may interpret this as an expression boundary. |
||
+ (this.comma ? "," : "") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Misleading line break before '+'; readers may interpret this as an expression boundary. |
||
+ (this.precision === undefined ? "" : "." + Math.max(0, this.precision | 0)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Misleading line break before '+'; readers may interpret this as an expression boundary. |
||
+ (this.trim ? "~" : "") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Misleading line break before '+'; readers may interpret this as an expression boundary. |
||
+ this.type; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Misleading line break before '+'; readers may interpret this as an expression boundary. |
||
}; | ||
|
||
// Trims insignificant zeros, e.g., replaces 1.2000k with 1.2k. | ||
function formatTrim(s) { | ||
out: for (var n = s.length, i = 1, i0 = -1, i1; i < n; ++i) { | ||
switch (s[i]) { | ||
case ".": i0 = i1 = i; break; | ||
case "0": if (i0 === 0) i0 = i; i1 = i; break; | ||
default: if (!+s[i]) break out; if (i0 > 0) i0 = 0; break; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Confusing use of '!'. |
||
} | ||
} | ||
return i0 > 0 ? s.slice(0, i0) + s.slice(i1 + 1) : s; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 'i0' used out of scope. |
||
} | ||
|
||
var prefixExponent; | ||
|
||
function formatPrefixAuto(x, p) { | ||
var d = formatDecimal(x, p); | ||
if (!d) return x + ""; | ||
var coefficient = d[0], | ||
exponent = d[1], | ||
i = exponent - (prefixExponent = Math.max(-8, Math.min(8, Math.floor(exponent / 3))) * 3) + 1, | ||
n = coefficient.length; | ||
return i === n ? coefficient | ||
: i > n ? coefficient + new Array(i - n + 1).join("0") | ||
: i > 0 ? coefficient.slice(0, i) + "." + coefficient.slice(i) | ||
: "0." + new Array(1 - i).join("0") + formatDecimal(x, Math.max(0, p + i - 1))[0]; // less than 1y! | ||
} | ||
|
||
function formatRounded(x, p) { | ||
var d = formatDecimal(x, p); | ||
if (!d) return x + ""; | ||
var coefficient = d[0], | ||
exponent = d[1]; | ||
return exponent < 0 ? "0." + new Array(-exponent).join("0") + coefficient | ||
: coefficient.length > exponent + 1 ? coefficient.slice(0, exponent + 1) + "." + coefficient.slice(exponent + 1) | ||
: coefficient + new Array(exponent - coefficient.length + 2).join("0"); | ||
} | ||
|
||
var formatTypes = { | ||
"%": function(x, p) { return (x * 100).toFixed(p); }, | ||
"b": function(x) { return Math.round(x).toString(2); }, | ||
"c": function(x) { return x + ""; }, | ||
"d": function(x) { return Math.round(x).toString(10); }, | ||
"e": function(x, p) { return x.toExponential(p); }, | ||
"f": function(x, p) { return x.toFixed(p); }, | ||
"g": function(x, p) { return x.toPrecision(p); }, | ||
"o": function(x) { return Math.round(x).toString(8); }, | ||
"p": function(x, p) { return formatRounded(x * 100, p); }, | ||
"r": formatRounded, | ||
"s": formatPrefixAuto, | ||
"X": function(x) { return Math.round(x).toString(16).toUpperCase(); }, | ||
"x": function(x) { return Math.round(x).toString(16); } | ||
}; | ||
|
||
function identity(x) { | ||
return x; | ||
} | ||
|
||
var map = Array.prototype.map, | ||
prefixes = ["y","z","a","f","p","n","µ","m","","k","M","G","T","P","E","Z","Y"]; | ||
|
||
function formatLocale(locale) { | ||
var group = locale.grouping === undefined || locale.thousands === undefined ? identity : formatGroup(map.call(locale.grouping, Number), locale.thousands + ""), | ||
currencyPrefix = locale.currency === undefined ? "" : locale.currency[0] + "", | ||
currencySuffix = locale.currency === undefined ? "" : locale.currency[1] + "", | ||
decimal = locale.decimal === undefined ? "." : locale.decimal + "", | ||
numerals = locale.numerals === undefined ? identity : formatNumerals(map.call(locale.numerals, String)), | ||
percent = locale.percent === undefined ? "%" : locale.percent + "", | ||
minus = locale.minus === undefined ? "-" : locale.minus + "", | ||
nan = locale.nan === undefined ? "NaN" : locale.nan + ""; | ||
|
||
function newFormat(specifier) { | ||
specifier = formatSpecifier(specifier); | ||
|
||
var fill = specifier.fill, | ||
align = specifier.align, | ||
sign = specifier.sign, | ||
symbol = specifier.symbol, | ||
zero = specifier.zero, | ||
width = specifier.width, | ||
comma = specifier.comma, | ||
precision = specifier.precision, | ||
trim = specifier.trim, | ||
type = specifier.type; | ||
|
||
// The "n" type is an alias for ",g". | ||
if (type === "n") comma = true, type = "g"; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Expected an assignment or function call and instead saw an expression. |
||
|
||
// The "" type, and any invalid type, is an alias for ".12~g". | ||
else if (!formatTypes[type]) precision === undefined && (precision = 12), trim = true, type = "g"; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Expected an assignment or function call and instead saw an expression. |
||
|
||
// If zero fill is specified, padding goes after sign and before digits. | ||
if (zero || (fill === "0" && align === "=")) zero = true, fill = "0", align = "="; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Expected an assignment or function call and instead saw an expression. |
||
|
||
// Compute the prefix and suffix. | ||
// For SI-prefix, the suffix is lazily computed. | ||
var prefix = symbol === "$" ? currencyPrefix : symbol === "#" && /[boxX]/.test(type) ? "0" + type.toLowerCase() : "", | ||
suffix = symbol === "$" ? currencySuffix : /[%p]/.test(type) ? percent : ""; | ||
|
||
// What format function should we use? | ||
// Is this an integer type? | ||
// Can this type generate exponential notation? | ||
var formatType = formatTypes[type], | ||
maybeSuffix = /[defgprs%]/.test(type); | ||
|
||
// Set the default precision if not specified, | ||
// or clamp the specified precision to the supported range. | ||
// For significant precision, it must be in [1, 21]. | ||
// For fixed precision, it must be in [0, 20]. | ||
precision = precision === undefined ? 6 | ||
: /[gprs]/.test(type) ? Math.max(1, Math.min(21, precision)) | ||
: Math.max(0, Math.min(20, precision)); | ||
|
||
function format(value) { | ||
var valuePrefix = prefix, | ||
valueSuffix = suffix, | ||
i, n, c; | ||
|
||
if (type === "c") { | ||
valueSuffix = formatType(value) + valueSuffix; | ||
value = ""; | ||
} else { | ||
value = +value; | ||
|
||
// Determine the sign. -0 is not less than 0, but 1 / -0 is! | ||
var valueNegative = value < 0 || 1 / value < 0; | ||
|
||
// Perform the initial formatting. | ||
value = isNaN(value) ? nan : formatType(Math.abs(value), precision); | ||
|
||
// Trim insignificant zeros. | ||
if (trim) value = formatTrim(value); | ||
|
||
// If a negative value rounds to zero after formatting, and no explicit positive sign is requested, hide the sign. | ||
if (valueNegative && +value === 0 && sign !== "+") valueNegative = false; | ||
|
||
// Compute the prefix and suffix. | ||
valuePrefix = (valueNegative ? (sign === "(" ? sign : minus) : sign === "-" || sign === "(" ? "" : sign) + valuePrefix; | ||
valueSuffix = (type === "s" ? prefixes[8 + prefixExponent / 3] : "") + valueSuffix + (valueNegative && sign === "(" ? ")" : ""); | ||
|
||
// Break the formatted value into the integer “value” part that can be | ||
// grouped, and fractional or exponential “suffix” part that is not. | ||
if (maybeSuffix) { | ||
i = -1, n = value.length; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Expected an assignment or function call and instead saw an expression. |
||
while (++i < n) { | ||
if (c = value.charCodeAt(i), 48 > c || c > 57) { | ||
valueSuffix = (c === 46 ? decimal + value.slice(i + 1) : value.slice(i)) + valueSuffix; | ||
value = value.slice(0, i); | ||
break; | ||
} | ||
} | ||
} | ||
} | ||
|
||
// If the fill character is not "0", grouping is applied before padding. | ||
if (comma && !zero) value = group(value, Infinity); | ||
|
||
// Compute the padding. | ||
var length = valuePrefix.length + value.length + valueSuffix.length, | ||
padding = length < width ? new Array(width - length + 1).join(fill) : ""; | ||
|
||
// If the fill character is "0", grouping is applied after padding. | ||
if (comma && zero) value = group(padding + value, padding.length ? width - valueSuffix.length : Infinity), padding = ""; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Expected an assignment or function call and instead saw an expression. |
||
|
||
// Reconstruct the final output based on the desired alignment. | ||
switch (align) { | ||
case "<": value = valuePrefix + value + valueSuffix + padding; break; | ||
case "=": value = valuePrefix + padding + value + valueSuffix; break; | ||
case "^": value = padding.slice(0, length = padding.length >> 1) + valuePrefix + value + valueSuffix + padding.slice(length); break; | ||
default: value = padding + valuePrefix + value + valueSuffix; break; | ||
} | ||
|
||
return numerals(value); | ||
} | ||
|
||
format.toString = function() { | ||
return specifier + ""; | ||
}; | ||
|
||
return format; | ||
} | ||
|
||
function formatPrefix(specifier, value) { | ||
var f = newFormat((specifier = formatSpecifier(specifier), specifier.type = "f", specifier)), | ||
e = Math.max(-8, Math.min(8, Math.floor(exponent(value) / 3))) * 3, | ||
k = Math.pow(10, -e), | ||
prefix = prefixes[8 + e / 3]; | ||
return function(value) { | ||
return f(k * value) + prefix; | ||
}; | ||
} | ||
|
||
return { | ||
format: newFormat, | ||
formatPrefix: formatPrefix | ||
}; | ||
} | ||
|
||
var locale; | ||
var format; | ||
var formatPrefix; | ||
|
||
defaultLocale({ | ||
decimal: ".", | ||
thousands: ",", | ||
grouping: [3], | ||
currency: ["$", ""], | ||
minus: "-" | ||
}); | ||
|
||
function defaultLocale(definition) { | ||
locale = formatLocale(definition); | ||
format = locale.format; | ||
formatPrefix = locale.formatPrefix; | ||
return locale; | ||
} | ||
|
||
function formatter(value, specifier) { | ||
const formatFunc = specifier ? format(specifier) : formatValue; | ||
|
||
if (Array.isArray(value)) { | ||
const [first, second] = value; | ||
if (first === -Infinity) { | ||
return `< ${formatValue(second)}`; | ||
return `< ${formatFunc(second)}`; | ||
} | ||
if (second === Infinity) { | ||
return `> ${formatValue(first)}`; | ||
return `> ${formatFunc(first)}`; | ||
} | ||
return `${formatValue(first)} - ${formatValue(second)}`; | ||
return `${formatFunc(first)} - ${formatFunc(second)}`; | ||
} | ||
return formatValue(value); | ||
return formatFunc(value); | ||
} | ||
|
||
function formatValue(value) { | ||
|
@@ -259,7 +571,7 @@ var init = (function () { | |
const variable = feature.variables[item.name]; | ||
if (variable) { | ||
let value = variable.value; | ||
value = formatValue(value); | ||
value = formatter(value, item.format); | ||
|
||
popupHTML = ` | ||
<span class="popup-name">${item.title}</span> | ||
|
@@ -329,7 +641,7 @@ var init = (function () { | |
widget.element = widget.element || document.querySelector(`#${widget.id}-value`); | ||
|
||
if (value && widget.element) { | ||
widget.element.innerText = typeof value === 'number' ? format(value) : value; | ||
widget.element.innerText = typeof value === 'number' ? formatter(value, widget.options.format) : value; | ||
} | ||
} | ||
|
||
|
@@ -341,7 +653,6 @@ var init = (function () { | |
const type = _getWidgetType(mapLayer, widget.value, widget.prop); | ||
const histogram = type === 'category' ? 'categoricalHistogram' : 'numericalHistogram'; | ||
bridge[histogram](widget.element, widget.value, widget.options); | ||
|
||
break; | ||
case 'category': | ||
bridge.category(widget.element, widget.value, widget.options); | ||
|
@@ -402,7 +713,8 @@ var init = (function () { | |
const order = legend.ascending ? 'ASC' : 'DESC'; | ||
const variable = legend.variable; | ||
const config = { othersLabel, variable, order }; | ||
const options = { format, config, dynamic }; | ||
const formatFunc = (value) => formatter(value, legend.format); | ||
const options = { format: formatFunc, config, dynamic }; | ||
|
||
if (legend.type.startsWith('size-continuous')) { | ||
config.samples = 4; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Misleading line break before '+'; readers may interpret this as an expression boundary.