Skip to content

[BUG]: hovertemplate/texttemplate/tickformat silently ignore a valid d3-format spec that begins with a sign flag (e.g. "+.2f") #7897

Description

@omtee

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugsomething broken

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions