Skip to content

fix(chart): Enhance Canvas based text measurement to account for various font features#2798

Draft
awahab07 wants to merge 15 commits intoelastic:mainfrom
awahab07:249382-Monospace-fonts-for-Lens
Draft

fix(chart): Enhance Canvas based text measurement to account for various font features#2798
awahab07 wants to merge 15 commits intoelastic:mainfrom
awahab07:249382-Monospace-fonts-for-Lens

Conversation

@awahab07
Copy link
Copy Markdown
Collaborator

@awahab07 awahab07 commented Mar 2, 2026

Problem

Text measurement should account for inherited CSS advanced font properties, not easily guessable/enumerable (e.g. font-feature-settings, font-variant-numeric and other advanced font props/settings). Text layout calculations using a detached off-screen canvas could not inherit take into account all properties reliably, causing unprecedented text clipping or overlap in charts.

For example, when a consumer application Lens uses a specialized font "Elastic UI Numeric", Metric chart with valueFontSize: 'fit' gets the primary metric label clipped.

image

Solution

An in-Dom hidden canvas should naturally inherit all font features/settings making text measurement accurate. This PR implement that and uses a zero sized hidden canvas element in every chart whose ctx passed on when measuring text for the chart.

Details

The Canvas 2D ctx.font property only accepts CSS font shorthand syntax, which does not include font-feature-settings or font-variant-numeric. The CanvasTextDrawingStyles interface also has no fontFeatureSettings attribute. The only way for measureText() to account for these properties is through CSS inheritance — which requires the canvas element to be present in the DOM tree. A detached off-screen canvas has no parent, so inherited font properties default to normal, producing measurements that don't match the rendered output.

Additionally, not all chart types use a <canvas> for rendering (e.g. Metric and Bullet graph are fully DOM-rendered), yet they still rely on canvas-based measureText() for layout calculations — so there is no rendering canvas to reuse.

This change:

  • Introduces a ChartMeasureCanvas component — a hidden, zero-size, aria-hidden <canvas> placed inside each Chart's .echChart DOM subtree so it inherits computed font properties from ancestors.
  • Updates withTextMeasure to prefer the registered in-DOM canvas, falling back to an off-screen canvas when no Chart component is mounted.

Performance

The in-DOM canvas is reused across all withTextMeasure calls for the lifetime of the Chart component, whereas the previous approach created a new off-screen canvas (document.createElement('canvas')) on every invocation. This reuse eliminates repeated canvas allocation and context creation, resulting in a net performance improvement. A DOM <span> + getBoundingClientRect() alternative was also evaluated but was significantly slower per call and triggers layout reflow, making canvas-based measurement the better choice. Also ChartMeasureCanvas component is kept to not re-render or re-mount against parent updates.

The following snippets demonstrate text measurement and performance different, with and without font features applied.

Without font featureWith font features
image image

Issues

Related to elastic/kibana#251576

Checklist

  • The proper chart type label has been added (e.g. :xy, :partition)
  • The proper feature labels have been added (e.g. :interactions, :axis)
  • All related issues have been linked (i.e. closes #123, fixes #123)
  • [ ] New public API exports have been added to packages/charts/src/index.ts
  • Unit tests have been added or updated to match the most common scenarios
  • The proper documentation and/or storybook story has been added or updated
  • The code has been checked for cross-browser compatibility (Chrome, Firefox, Safari, Edge)
  • Visual changes have been tested with light and dark themes

awahab07 added 2 commits March 2, 2026 13:08
…unt open font attributes like `font-feature-settings` and `font-variant-numeric`.
…anvas to make sure canvas based text measurement takes into account font features like `font-feature-settings` and `font-variant-numeric`.
@awahab07 awahab07 added :chart Chart element related issue :all Applies to all chart types :utils Related to utility functions like math, text, etc labels Mar 2, 2026
@awahab07 awahab07 linked an issue Mar 2, 2026 that may be closed by this pull request
@awahab07
Copy link
Copy Markdown
Collaborator Author

awahab07 commented Mar 2, 2026

This following limitation has been circumvented when Lens introduced a specialized font "Elastic UI Numeric".

Limitation

Noticed that font features can't be applied to Canvas in Firefox.
image

// measureCtx doesn't need withContext, so not using it for performance reasons
if (isMeasureCtx) {
// Avoid setting the font multiple times if it hasn't changed for performance
if (fontString !== lastFont) {
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is to save on performance cost as performance trail shows ctx.font assignment is the most expensive call when measuring text (it's even expensive than ctx.measureText()).

Image

@awahab07
Copy link
Copy Markdown
Collaborator Author

awahab07 commented Apr 3, 2026

buildkite update vrt

@elastic-datavis
Copy link
Copy Markdown
Contributor

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

:all Applies to all chart types :chart Chart element related issue :utils Related to utility functions like math, text, etc

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Lens] Activate monospaced font in charts

1 participant