Skip to content

Add JsFunction for tooltip/label formatter callbacks (closes #86)#105

Merged
PeiyangYu merged 1 commit into
masterfrom
feat/issue-86-js-function-formatter
May 14, 2026
Merged

Add JsFunction for tooltip/label formatter callbacks (closes #86)#105
PeiyangYu merged 1 commit into
masterfrom
feat/issue-86-js-function-formatter

Conversation

@PeiyangYu

@PeiyangYu PeiyangYu commented May 11, 2026

Copy link
Copy Markdown
Collaborator

Summary

Closes #86 — adds the ability to define a tooltip (or label / axis-label / visualMap) formatter as a real JavaScript function built in Java, matching the JS API shown in the issue:

tooltip: {
  formatter(params) {
    return '<div>' + params.name + ': ' + params.value + ' (' + params.percent + '%)</div>';
  }
}

The string-template form ("{b}: {c}") already worked through the existing setFormatter(String) overloads. The function form was unreachable because Gson serialized any Object formatter as a quoted JSON string — the rendered HTML would contain a string, not a callable function.

How it works

A new JsFunction value class wraps a JS body. A low-level Gson TypeAdapter uses JsonWriter#jsonValue(String) to emit the body straight into the output stream, unquoted. When the option JSON is dropped into the rendered <script> tag (Engine uses Handlebars triple-braces, so no HTML-escaping), the function survives as a real JS callable.

The adapter is registered automatically in EChartsSerializer's constructor — no new public surface beyond JsFunction itself.

How to use

import org.icepear.echarts.serializer.JsFunction;

JsFunction formatter = new JsFunction(
    "function(p) { return p.name + ': ' + p.value + ' (' + p.percent + '%)'; }");

new Pie()
    .setTooltip(new Tooltip().setTrigger("item").setFormatter((Object) formatter))
    .addSeries(/* ... */);

The (Object) cast picks the existing setFormatter(Object) overload over setFormatter(String). Same pattern works on SeriesLabel, AxisLabel variants, PieLabel, and any other class with an Object-typed formatter.

For multi-line bodies just concatenate strings — newlines are preserved verbatim:

new JsFunction(
    "function(p) {"
  + "  return '<b>' + p.name + '</b>: ' + p.value;"
  + "}");

⚠️ Important: JsFunction-bearing output is meant for embedding in HTML/JS. The result is valid JavaScript but not strictly valid JSON — don't feed it into a JSON parser. Body validation is the browser's job; garbage syntax surfaces as a JS error in the console.

Tests added (18 new, all passing)

serializer/JsFunctionTest covers:

  • Value class — equals/hashCode, null-body rejection, static factory parity
  • Unquoted-output invariant — top-level, nested in object, nested in array
  • String formatters remain quoted (no regression)
  • Multi-line bodies preserved exactly
  • HTML <tag> characters preserved (HTML escaping stays disabled, critical for tooltip markup)
  • End-to-end through Tooltip on Pie and Bar
  • Sanity check that the embedded result starts with f (function), not "
  • Read direction (best-effort symmetric round-trip)
  • Body-validation is explicitly out-of-scope and won't throw

Visual demo

src/test/java/org/icepear/echarts/demo/JsFunctionTooltipDemo.java builds a richer multi-element tooltip (using params.marker, params.name, params.value, params.percent) on a Pie chart and writes a self-contained dark-themed HTML to /tmp/js-function-tooltip-demo.html.

mvn test -Dtest=JsFunctionTooltipDemo
open /tmp/js-function-tooltip-demo.html
# Hover over a slice — the rich HTML tooltip is rendered by the JS function built in Java.

Test plan

  • mvn test -Dtest=JsFunctionTest — 18/18 pass
  • mvn test -Dtest=JsFunctionTooltipDemo — writes demo file
  • open /tmp/js-function-tooltip-demo.html — tooltip renders with rich HTML on hover; verified the rendered HTML contains "formatter":function (params) { (unquoted)
  • mvn test (full suite) — 95 tests, only the 3 pre-existing polar/float-precision failures remain on master
CleanShot.2026-05-10.at.21.58.37.mp4

ECharts on the JS side accepts string templates ("{b}: {c}") *or* a
function (params) => string for fields like tooltip.formatter,
label.formatter, axisLabel.formatter, visualMap.formatter, etc. The
string form already worked through the existing setFormatter(String)
overloads; the function form was unreachable because Gson quoted any
Object value as a JSON string.

This adds a JsFunction value class plus a low-level Gson TypeAdapter
that uses JsonWriter.jsonValue(String) to write the body straight
into the output stream, unquoted. Result: when the option JSON is
embedded into the rendered <script> tag (Engine uses Handlebars
triple-braces), the function survives as a real JS callable.

Usage — pass through any existing setFormatter(Object) overload:

    tooltip.setFormatter((Object) new JsFunction(
        "function (p) { return p.name + ': ' + p.value + ' (' + p.percent + '%)'; }"));

The adapter is registered automatically inside EChartsSerializer's
constructor — no new public surface beyond the JsFunction class itself.

Tests (18 new, all passing):
- JsFunctionTest covers the value class (equals/hashCode/null-rejection),
  unquoted-output invariant, multi-line bodies, HTML <tag> preservation
  (HTML escaping must stay disabled for raw JS), end-to-end through
  Tooltip on both Pie and Bar, sanity check that string formatters
  remain quoted, and the read direction (best-effort symmetric path)
- JsFunctionTooltipDemo writes /tmp/js-function-tooltip-demo.html with
  a styled Pie chart whose tooltip uses the exact JS shape from the
  issue (params.marker / name / value / percent)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@PeiyangYu PeiyangYu merged commit 629ffea into master May 14, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] Add tooltip formatter with params

2 participants