Skip to content

ECharts: legend.left is baked to the design canvas, so chart.resize() overlaps the plot #98

Description

@zhnd

Summary

assembleECharts places the categorical legend (and the matching legend-title graphic) at an absolute left in design-canvas pixels. That is correct when the host inits ECharts at _width × _height. It breaks as soon as a host puts the chart in a fluid container and calls the official chart.resize().

This is not an ECharts resize() bug. resize() only changes the canvas / grid / axes; it does not rewrite option pixel fields. Flint is the one emitting legend.left relative to _width.

Verified against flint-chart@0.4.1. The same left: legendLeftPx path is still in main (packages/flint-js/src/echarts/instantiate-spec.ts, legend-positioning block). I did not find an existing issue for this.

Repro

Slope Chart (any multi-series cartesian chart with a right-side categorical legend works):

import { assembleECharts } from 'flint-chart/echarts';
import * as echarts from 'echarts';

const option = assembleECharts({
  data: { values: [
    { period: '2024', team: 'Alpha', nps: 32 },
    { period: '2025', team: 'Alpha', nps: 48 },
    { period: '2024', team: 'Beta', nps: 41 },
    { period: '2025', team: 'Beta', nps: 39 },
    { period: '2024', team: 'Gamma', nps: 28 },
    { period: '2025', team: 'Gamma', nps: 52 },
    { period: '2024', team: 'Delta', nps: 55 },
    { period: '2025', team: 'Delta', nps: 61 },
  ]},
  semantic_types: { period: 'Year', team: 'Name', nps: 'Score' },
  chart_spec: {
    chartType: 'Slope Chart',
    encodings: {
      x: { field: 'period' },
      y: { field: 'nps' },
      color: { field: 'team' },
    },
    baseSize: { width: 420, height: 280 },
  },
});

// Typical host: fill the reading column, then resize.
const el = document.getElementById('chart');
el.style.width = '100%';          // e.g. 800px
el.style.height = option._height + 'px';
const chart = echarts.init(el);
chart.setOption(option);
chart.resize();

Assembled bookkeeping on 0.4.1:

field value
_width / _height 534 × 377
grid.right 112 (plot ends at canvasW − 112)
legend.left 422
legend-title graphic.left 422

422 + 112 = 534. The legend’s left edge is meant to sit on the plot’s right edge only when the canvas is 534px wide.

Actual

  • Canvas 534px (Flint’s contract): legend sits in the reserved gutter. Correct.
  • Canvas 800px + resize(): plot stretches to 800 − 112 = 688, legend stays at left: 422overlaps the series.
  • Canvas 350px + resize(): legend.left: 422 is past the right edge → clipped / gone.

grid.left / grid.right as pixel insets still work at any width. Only left-origin design coordinates (legend.left, title graphic.left, and similarly series.radius: 'NNpx' on sunburst/pie) do not.

Expected

One of:

  1. Document the contract in the ECharts assemble docs: hosts must init/size the DOM to _width × _height (or CSS-scale that box). Fluid width: 100% + resize() is unsupported.
  2. Emit fluid-safe positions so official resize() keeps the legend in the gutter:
    • legend.right (and the title graphic) instead of legend.left: designW − gutter, or
    • left: 'right' / percent, with grid.right still reserving the same gutter.

Option 2 is a small change in ecApplyLayoutToSpec and would let note/editor hosts follow the ECharts handbook (width: 100% + resize()) without rewriting Flint’s option.

A host-side conversion of left → right = _width - left is wrong: ECharts right is the inset to the component’s right edge, so the legend grows left into the plot even at design size.

Why this shows up in hosts

Flint’s own MCP / gallery path paints at a fixed canvas (create_chart_view is described as fixed-size). Editors that share a reading column with prose (and iPhone/iPad widths) cannot dedicate _width forever, and resize() is the documented ECharts way to follow the container.

Related but separate: sunburst/pie radius: [\${n}px`, ...]` is also design-canvas-absolute and clips when the live box is smaller. Happy to split that if you want a narrower first fix.

Environment

  • flint-chart@0.4.1 (reproduced)
  • main as of 2026-08-15 still uses legendLeftPx = effectiveChartWidth - rightMarginPx
  • Apache ECharts 5 / 6 (behavior of legend.left vs resize() is the same)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions