The Problem
The getLongestLegendFormattedValueSelector in get_legend_max_formatted_value.ts makes a flawed assumption: it assumes that formatting the Y domain maximum value will produce the longest formatted string. This is not true for some Kibana formatters. Example:
Here's the key code:
const maxYValue = yDomains?.[0]?.domain?.[1];
// ...
const formatter = spec.tickFormat ?? yAxis?.tickFormat ?? defaultTickFormatter;
const formatted = formatter(maxYValue);
It only formats domain[1] (the max value) and uses that to compute the reserved minWidth. But for formatters with optional decimals or adaptive units, the domain max can actually produce a shorter string than intermediate data values.
Why It Fails
Example with optional decimals (common in average/percentile metrics):
Suppose the data is [3.5, 7.25, 12]:
- Domain:
[0, 12], so domain[1] = 12
formatter(12) = "12" (2 chars) — used for min-width calculation
formatter(7.25) = "7.25" (4 chars) — actual hover value, wider than reserved space
The min-width is computed from "VALUE: 12", but when you hover over the bar for 7.25, the legend shows "VALUE: 7.25" which is wider, causing layout shift.
Example with bytes formatter (adaptive units):
- Domain max:
1024 → "1 KB" (4 chars)
- Hover value:
500 → "500 Bytes" (9 chars) — much wider
Additional info
Additional Issues in the Same Selector
-
Only the first Y domain is checked (yDomains?.[0]): If there are multiple Y axis groups (left/right axes), only the first group's domain max is used. Series on other axes may have values that format to longer strings.
-
String length comparison instead of pixel width: The code uses formatted.length > result.length to pick the "longest" formatted string across series. But character count doesn't equal pixel width in non-monospace fonts (e.g., "111" is narrower than "000" in Inter).
The Fix
The fix should be in the elastic-charts repo, in get_legend_max_formatted_value.ts. Instead of only formatting the domain max, it should consider values that might produce longer formatted strings. Some options:
-
Format both domain[0] (min) and domain[1] (max), and also format representative values with more decimal places — but this is fragile.
-
Iterate all Y domains (not just yDomains[0]) and format both endpoints of each domain.
-
Use actual data values: scan through the data series to find the value that produces the longest formatted string (most robust but potentially expensive).
-
Use pixel width comparison instead of string length when picking the longest formatted value.
The most practical fix would be to iterate over all Y domains and format multiple representative values (including the min, max, and perhaps a value like max - 0.001 to trigger decimal formatting), then pick the widest one by pixel measurement rather than character count.
The Problem
The
getLongestLegendFormattedValueSelectoringet_legend_max_formatted_value.tsmakes a flawed assumption: it assumes that formatting the Y domain maximum value will produce the longest formatted string. This is not true for some Kibana formatters. Example:Here's the key code:
It only formats
domain[1](the max value) and uses that to compute the reservedminWidth. But for formatters with optional decimals or adaptive units, the domain max can actually produce a shorter string than intermediate data values.Why It Fails
Example with optional decimals (common in average/percentile metrics):
Suppose the data is
[3.5, 7.25, 12]:[0, 12], sodomain[1] = 12formatter(12)="12"(2 chars) — used for min-width calculationformatter(7.25)="7.25"(4 chars) — actual hover value, wider than reserved spaceThe min-width is computed from
"VALUE: 12", but when you hover over the bar for7.25, the legend shows"VALUE: 7.25"which is wider, causing layout shift.Example with bytes formatter (adaptive units):
1024→"1 KB"(4 chars)500→"500 Bytes"(9 chars) — much widerAdditional info
Additional Issues in the Same Selector
Only the first Y domain is checked (
yDomains?.[0]): If there are multiple Y axis groups (left/right axes), only the first group's domain max is used. Series on other axes may have values that format to longer strings.String length comparison instead of pixel width: The code uses
formatted.length > result.lengthto pick the "longest" formatted string across series. But character count doesn't equal pixel width in non-monospace fonts (e.g.,"111"is narrower than"000"in Inter).The Fix
The fix should be in the elastic-charts repo, in
get_legend_max_formatted_value.ts. Instead of only formatting the domain max, it should consider values that might produce longer formatted strings. Some options:Format both
domain[0](min) anddomain[1](max), and also format representative values with more decimal places — but this is fragile.Iterate all Y domains (not just
yDomains[0]) and format both endpoints of each domain.Use actual data values: scan through the data series to find the value that produces the longest formatted string (most robust but potentially expensive).
Use pixel width comparison instead of string length when picking the longest formatted value.
The most practical fix would be to iterate over all Y domains and format multiple representative values (including the min, max, and perhaps a value like
max - 0.001to trigger decimal formatting), then pick the widest one by pixel measurement rather than character count.