Description
A d3-format specifier that starts with a sign flag — +, -, (, or a space — is silently dropped when used in hovertemplate, texttemplate, tickformat, or hoverformat, and the raw (unformatted) number is shown instead.
For example, %{y:+.2f} should render +12.35, but it renders the full-precision value 12.3456789. The sibling spec %{y:.2f} (no sign flag) works correctly and renders 12.35.
+.2f is a valid d3-format specifier (grammar: [[fill]align][sign][symbol][0][width][,][.precision][~][type]), so it is expected to work.
Root cause is in Lib.adjustFormat (src/lib/index.js). Its "add a leading tilde to trim trailing zeros" heuristic does not account for a leading sign flag:
// src/lib/index.js
lib.adjustFormat = function adjustFormat(formatStr) {
if (!formatStr || /^\d[.]\df/.test(formatStr) || /[.]\d%/.test(formatStr)) return formatStr;
if (formatStr === '0.f') return '~f';
if (/^\d%/.test(formatStr)) return '~%';
if (/^\ds/.test(formatStr)) return '~s';
// try adding tilde to the start of format in order to trim
if (!/^[~,.0$]/.test(formatStr) && /[&fps]/.test(formatStr)) return '~' + formatStr;
return formatStr;
};
For formatStr = "+.2f":
/^[~,.0$]/.test("+.2f") is false (it starts with +, which is not in the set), so !(...) is true;
/[&fps]/.test("+.2f") is true (contains f);
- therefore
adjustFormat returns "~+.2f".
"~+.2f" is an invalid d3-format spec (the ~ trim flag must appear immediately before the type, e.g. +.2~f; a leading ~ followed by a sign does not parse). d3Format('~+.2f') throws, and Lib.numberFormat catches it and falls back to Lib.noFormat, which returns String(value) — the unformatted number:
lib.numberFormat = function (formatStr) {
var fn;
try {
fn = d3Format(lib.adjustFormat(formatStr));
} catch (e) {
lib.warnBadFormat(formatStr); // logs: encountered bad format: "+.2f"
return lib.noFormat; // -> String(value), unformatted
}
return fn;
};
By contrast, ".2f" starts with ., which is in [~,.0$], so adjustFormat leaves it untouched and it formats correctly.
This affects every code path that goes through Lib.numberFormat: hovertemplate, texttemplate, tickformat, and hoverformat.
Screenshots/Video
Hovering the bar in the repro below: field A (:.2f) shows 12.35; field B (:+.2f) shows 12.3456789 instead of +12.35. The browser console also logs encountered bad format: "+.2f".
Steps to reproduce
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<script src="https://cdn.plot.ly/plotly-3.7.0.js"></script>
</head>
<body>
<div id="chart" style="width:700px;height:260px;"></div>
<script>
var delta = 12.3456789;
Plotly.newPlot('chart', [{
type: 'bar', orientation: 'h',
y: ['example'], x: [delta],
customdata: [[delta, delta]],
hovertemplate:
'A (:.2f) = %{customdata[0]:.2f}<br>' +
'B (:+.2f) = %{customdata[1]:+.2f}<extra></extra>'
}]);
// Expected hover: A = 12.35 B = +12.35
// Actual hover: A = 12.35 B = 12.3456789
</script>
</body>
</html>
- Open the page in a browser (JSFiddle).
- Hover over the bar.
- Note that field B (
%{...:+.2f}) shows 12.3456789 instead of +12.35, while field A (%{...:.2f}) correctly shows 12.35.
- Open the developer console and note the warning:
encountered bad format: "+.2f".
The same failure occurs with any spec beginning with a sign flag and containing one of f/p/s/&, e.g. -.2f, ( .2f, +,.0f, +.1%, +.3s. It also reproduces via tickformat: '+.2f' and hoverformat: '+.2f'.
Notes
- Confirmed present on
master (and released v3.7.0) — Lib.adjustFormat is unchanged as of commit 227bba91 and still contains the tilde-prepend heuristic above.
- Suggested fix: in the tilde-prepend branch, do not prepend
~ when the spec begins with a sign flag. For example, exclude a leading [+\-( ] (and any leading fill/align) from that branch, or only insert ~ immediately before the type character rather than at the start of the spec. A regression test covering +.2f, -.2f, ( .2f, +,.0f, +.3s, +.1% would guard this.
- Workaround for users: pre-format the value in the data (pass a pre-rendered string via
customdata/text) or omit the leading + sign flag.
Description
A d3-format specifier that starts with a sign flag —
+,-,(, or a space — is silently dropped when used inhovertemplate,texttemplate,tickformat, orhoverformat, and the raw (unformatted) number is shown instead.For example,
%{y:+.2f}should render+12.35, but it renders the full-precision value12.3456789. The sibling spec%{y:.2f}(no sign flag) works correctly and renders12.35.+.2fis a valid d3-format specifier (grammar:[[fill]align][sign][symbol][0][width][,][.precision][~][type]), so it is expected to work.Root cause is in
Lib.adjustFormat(src/lib/index.js). Its "add a leading tilde to trim trailing zeros" heuristic does not account for a leading sign flag:For
formatStr = "+.2f":/^[~,.0$]/.test("+.2f")isfalse(it starts with+, which is not in the set), so!(...)istrue;/[&fps]/.test("+.2f")istrue(containsf);adjustFormatreturns"~+.2f"."~+.2f"is an invalid d3-format spec (the~trim flag must appear immediately before the type, e.g.+.2~f; a leading~followed by a sign does not parse).d3Format('~+.2f')throws, andLib.numberFormatcatches it and falls back toLib.noFormat, which returnsString(value)— the unformatted number:By contrast,
".2f"starts with., which is in[~,.0$], soadjustFormatleaves it untouched and it formats correctly.This affects every code path that goes through
Lib.numberFormat:hovertemplate,texttemplate,tickformat, andhoverformat.Screenshots/Video
Hovering the bar in the repro below: field A (
:.2f) shows12.35; field B (:+.2f) shows12.3456789instead of+12.35. The browser console also logsencountered bad format: "+.2f".Steps to reproduce
%{...:+.2f}) shows12.3456789instead of+12.35, while field A (%{...:.2f}) correctly shows12.35.encountered bad format: "+.2f".The same failure occurs with any spec beginning with a sign flag and containing one of
f/p/s/&, e.g.-.2f,( .2f,+,.0f,+.1%,+.3s. It also reproduces viatickformat: '+.2f'andhoverformat: '+.2f'.Notes
master(and releasedv3.7.0) —Lib.adjustFormatis unchanged as of commit227bba91and still contains the tilde-prepend heuristic above.~when the spec begins with a sign flag. For example, exclude a leading[+\-( ](and any leading fill/align) from that branch, or only insert~immediately before the type character rather than at the start of the spec. A regression test covering+.2f,-.2f,( .2f,+,.0f,+.3s,+.1%would guard this.customdata/text) or omit the leading+sign flag.