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

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions