Skip to content
Domphy

@domphy/chart vs ECharts

Series coverage

Series@domphy/chartECharts
Line / Area✓✓
Bar (grouped, stacked, horizontal)✓✓
Scatter / Bubble✓✓
Pie / Donut / Rose✓✓
Radar / Spider✓✓
Heatmap (cartesian)✓✓
Heatmap (calendar)✓✓
Candlestick / OHLC✓✓
Boxplot / Whisker✓✓
Gauge✓✓
Treemap✓✓
Funnel✓✓
Sankey✓✓
Graph / Network✓✓
Parallel coordinates✓✓
ThemeRiver / Stream✓✓
Map / Choropleth✓✓
Custom render (custom + renderItem)✓ (cartesian2d/none — group/rect/circle/polygon/polyline/line/text; polar/geo custom series not rendered)✓
scatter3D / bar3D / line3D✓ECharts GL
surface3D✓ (SVG)ECharts GL (WebGL mesh)
Calendar coordinate✓✓
Geo coordinate✓✓
Polar coordinatetyped only (not rendered)✓
ThemeRiver (multi-series)✓✓
Lines (flow map)✓ (SVG + animateMotion)✓
EffectScatter✓ (SVG ripple animation)✓
PictorialBar✓ (SVG symbol repeat/clip)✓

Architecture

@domphy/chartECharts
RendererWebGL (luma.gl) for hot series + SVG overlay for everything elseCanvas (default) or SVG mode — no mixed mode
3DSVG perspective projection (built-in, no extra package)ECharts GL (separate npm package, WebGL)
FrameworkDomphy patch — integrates into element treeStandalone (echarts.init(dom))
ReactivityPass State<ChartOption> — auto re-renders on changeCall setOption() manually
SSRWorks with @domphy/app renderToStringPartial (SVG mode only)
Shadow DOM✓ (SVG output, no canvas clipping issues)Canvas has shadow DOM issues
Accessibilityoverlay SVG is role="img" with a derived aria-label; legend items are APG toggle buttons (focusable, aria-pressed, Enter/Space)aria component, opt-in aria.enabled
WebGL context lossrecovered automatically (fresh canvas + device)n/a (Canvas 2D)

Color system

This is the biggest architectural difference.

ECharts hardcodes palette hex values:

// ECharts default — breaks in dark mode, no contrast guarantee
color: ['#5470c6', '#91cc75', '#fac858', '#ee6666', ...]

@domphy/chart uses theme families that resolve at render time:

// Adapts to any dataTone context, WCAG 4.5:1 guaranteed
series: [
  { type: "line", color: "primary" },    // resolves via themeColor()
  { type: "bar",  color: "secondary" },  // follows dataTone cascade of parent
]

Set dataTone="shift-14" on the chart container → all series colors adapt to the dark tone automatically. Zero manual dark-mode config.

Spacing / density cascade

@domphy/chartECharts
Axis label paddingthemeSpacing(density * n)Fixed px
Tick densitycascade from dataDensityFixed
Legend gapcascadeFixed
Compact modedataDensity="decrease-1" on parentNo built-in

Components

Component@domphy/chartECharts
Title✓✓
Legend (plain/scroll)✓✓
Tooltip (axis/item)✓ (Domphy element or string)✓ (HTML or richText)
DataZoom (slider/inside)✓✓
VisualMap (continuous/piecewise)✓✓
Brush (rect/lineX/lineY, single/multiple, brushSelected, inBrush/outOfBrush)✓ (scatter/line incl. stacked/bar/candlestick; polygon not implemented)✓
Toolbox (saveAsImage incl. type:"svg" / restore / dataView / dataZoom / brush / magicType)✓✓
Mark point/line/area✓✓
Dataset + transforms✓✓
Axis pointer✓✓
Geo roam (interactive pan/zoom)✓ (drag + wheel, scaleLimit)✓
Enter/update animation (animation*)typed only (static render; effectScatter/lines have own SVG effects)✓

Bundle size

@domphy/chartECharts (tree-shaken)ECharts (full)
Core~120 KB~400 KB~1 MB
3Dbuilt-in (SVG, 0 extra)+ECharts GL ~800 KB—
Peer deps@domphy/core, @domphy/themenonenone

Migration from ECharts

The ChartOption interface is intentionally ECharts-compatible. Most options migrate without changes:

// ECharts
echarts.init(document.getElementById("chart")).setOption({
  xAxis: { type: "category", data: ["Mon","Tue","Wed"] },
  yAxis: { type: "value" },
  series: [{ type: "bar", data: [120, 200, 150] }],
})

// @domphy/chart — same option object, different mount
import { chart } from "@domphy/chart"
const App = {
  div: null,
  style: { width: "600px", height: "300px" },
  $: [chart({
    xAxis: { type: "category", data: ["Mon","Tue","Wed"] },
    yAxis: { type: "value" },
    series: [{ type: "bar", data: [120, 200, 150] }],
  })],
}

Option keys typed for interop that have no effect

The ChartOption type is deliberately ECharts-shaped so a pasted option compiles. Some of those keys are not rendered. The table below is measured, not curated: packages/chart/scripts/inert-keys.mjs walks src/ at run time and looks for a read of each key declared in types.ts (as .key, ["key"], ['key'] or a template-literal reference). Measured 2026-09: 410 of 1549 key declarations (202 of 443 distinct key names) have no reader anywhere in src/.

Every one of them carries a @deprecated Not implemented by @domphy/chart JSDoc marker on its declaration in the published types, so an editor strikes the key through at the call site instead of letting it look supported. node packages/chart/scripts/mark-inert-keys.mjs re-derives those markers, and packages/chart/tests/inert-keys-honest.test.ts fails if a marker and the source grep ever disagree.

Setting one of the bold keys additionally logs a one-time console warning at runtime.

Type#Keys with no effect
LabelOption2verticalAlign, borderRadius
LineStyleOption1dashOffset
ItemStyleOption2borderType, borderRadius
LinearGradient1global
RadialGradient1global
AreaStyleOption1origin
EmphasisOption1labelLine
LabelLineOption3showAbove, length2, minTurnAngle
MarkPointOption2emphasis, animationDuration
MarkLineOption2precision, emphasis
MarkAreaOption1emphasis
AxisLabelOption4inside, verticalAlign, ellipsis, hideOverlap
AxisLineOption2onZero, onZeroAxisIndex
AxisTickOption2alignWithLabel, inside
AxisPointerOption3snap, shadowStyle, status
AxisOption11gridIndex, polarIndex, nameTextStyle, nameGap, nameRotate, minInterval, maxInterval, triggerEvent, minorTick, minorSplitLine, splitArea
RadiusAxisOption1polarIndex
AngleAxisOption1polarIndex
GridOption1containLabel
TitleOption6link, sublink, subtarget, textVerticalAlign, triggerEvent, borderRadius
LegendOption14symbolKeepAspect, selectedMode, inactiveColor, inactiveBorderColor, inactiveBorderWidth, borderRadius, pageButtonItemGap, pageButtonGap, pageButtonPosition, pageIconColor, pageIconInactiveColor, pageIconSize, pageTextStyle, animationDurationUpdate
TooltipOption8showContent, alwaysShowContent, triggerOn, showDelay, hideDelay, enterable, renderMode, transitionDuration
TooltipParams7marker, axisDim, axisIndex, axisType, axisId, axisValue, axisValueLabel
DataZoomSliderOption29radiusAxisIndex, angleAxisIndex, filterMode, startValue, endValue, minSpan, maxSpan, minValueSpan, maxValueSpan, zoomLock, throttle, rangeMode, borderRadius, dataBackground, selectedDataBackground, fillerColor, handleColor, handleStyle, handleSize, handleIcon, moveHandleStyle, moveHandleSize, labelPrecision, labelFormatter, showDetail, showDataShadow, realtime, brushSelect, emphasis
DataZoomInsideOption12filterMode, startValue, endValue, minSpan, maxSpan, zoomLock, throttle, rangeMode, zoomOnMouseWheel, moveOnMouseMove, moveOnMouseWheel, preventDefaultMouseMove
VisualMapContinuousOption12calculable, realtime, precision, textGap, hoverLink, controller, handleIcon, handleSize, handleStyle, indicatorIcon, indicatorSize, indicatorStyle
VisualMapPiecewiseOption9minOpen, maxOpen, selectedMode, precision, textGap, showLabel, itemSymbol, hoverLink, controller
BrushOption5brushLink, transformable, throttleType, throttleDelay, removeOnClick
DatasetOption2dimensions, fromTransformResult
TransformOption1print
EncodeOption2itemId, itemGroupId
LineSeriesOption19polarIndex, symbolKeepAspect, showAllSymbol, hoverAnimation, stackStrategy, clip, endLabel, labelLayout, smoothMonotone, sampling, dimensions, seriesLayoutBy, animationThreshold, animationDuration, animationEasing, animationDelay, animationDurationUpdate, animationEasingUpdate, animationDelayUpdate
BarSeriesOption22polarIndex, coordinateSystemIndex, labelLayout, stackStrategy, barMinAngle, largeThreshold, progressiveThreshold, progressiveChunkMode, dimensions, seriesLayoutBy, clip, realtimeSort, showBackground, backgroundStyle, animationThreshold, animationDuration, animationEasing, animationDelay, animationDurationUpdate, animationEasingUpdate, animationDelayUpdate, borderRadius
PieDataItem4labelLine, emphasis, blur, select
PieSeriesOption17minAngle, minShowLabelAngle, avoidLabelOverlap, stillShowZeroSum, percentPrecision, dimensions, seriesLayoutBy, labelLine, labelLayout, animationThreshold, animationDuration, animationEasing, animationDelay, animationDurationUpdate, animationEasingUpdate, animationDelayUpdate, borderRadius
ScatterSeriesOption12polarIndex, symbolKeepAspect, largeThreshold, labelLayout, progressiveThreshold, progressiveChunkMode, dimensions, seriesLayoutBy, animationThreshold, animationDuration, animationEasing, animationDelay
RadarOption4axisName, nameGap, triggerEvent, splitArea
RadarSeriesOption5symbolKeepAspect, labelLayout, animationDuration, animationEasing, animationDelay
HeatmapSeriesOption7blurSize, pointSize, maxOpacity, minOpacity, dimensions, seriesLayoutBy, progressiveThreshold
CandlestickSeriesOption14largeThreshold, progressiveThreshold, progressiveChunkMode, dimensions, seriesLayoutBy, clip, animationDuration, animationEasing, animationDelay, animationDurationUpdate, animationEasingUpdate, animationDelayUpdate, upBorderColor, downBorderColor
BoxplotSeriesOption6boxWidth, dimensions, seriesLayoutBy, animationDuration, animationEasing, animationDelay
GaugeSeriesOption4anchor, animationDuration, animationEasing, animationDelay
TreemapLevelOption12visualDimension, visualMin, visualMax, colorAlpha, colorSaturation, colorMappingBy, visibleMin, childrenVisibleMin, upperLabel, emphasis, blur, select
TreemapDataItem7groupId, childGroupId, upperLabel, emphasis, blur, select, link
TreemapSeriesOption27squareRatio, leafDepth, drillDownIcon, nodeClick, zoomToNodeRatio, universalTransition, visualDimension, visualMin, visualMax, colorAlpha, colorSaturation, colorMappingBy, visibleMin, childrenVisibleMin, upperLabel, emphasis, blur, select, selectedMode, breadcrumb, levels, animationDuration, animationEasing, animationDelay, animationDurationUpdate, animationEasingUpdate, animationDelayUpdate
FunnelDataItem4labelLine, emphasis, blur, select
FunnelSeriesOption10minSize, maxSize, funnelAlign, labelLine, animationDuration, animationEasing, animationDelay, animationDurationUpdate, animationEasingUpdate, animationDelayUpdate
SankeyNode4emphasis, blur, select, focusNodeAdjacency
SankeyLink4emphasis, blur, select, focusNodeAdjacency
SankeySeriesOption14nodeAlign, draggable, selectedMode, levels, emphasis, blur, select, focusNodeAdjacency, animationDuration, animationEasing, animationDelay, animationDurationUpdate, animationEasingUpdate, animationDelayUpdate
GraphNode4draggable, emphasis, blur, select
GraphLink4emphasis, blur, select, ignoreForceLayout
GraphCategory3emphasis, blur, select
GraphSeriesOption18legendHoverLink, polarIndex, circular, draggable, edgeSymbolSize, edgeLabel, labelLayout, emphasis, blur, select, selectedMode, autoCurveness, animationDuration, animationEasing, animationDelay, animationDurationUpdate, animationEasingUpdate, animationDelayUpdate
CustomRenderParams2dataIndexInside, actionType
CustomElement2textConfig, during
CustomSeriesOption6polarIndex, dimensions, seriesLayoutBy, animationDuration, animationEasing, emphasis
ParallelAxisOption1parallelIndex
ParallelSeriesOption1parallelIndex
MapSeriesOption3nameProperty, selectedMode, emphasis
Grid3DOption3boxWidth, boxHeight, boxDepth
Scatter3DSeriesOption1grid3DIndex
Bar3DSeriesOption1grid3DIndex
Line3DSeriesOption1grid3DIndex
Surface3DSeriesOption1grid3DIndex
EffectScatterSeriesOption1showEffectOn
ChartOption13radiusAxis, angleAxis, animationThreshold, animationDuration, animationEasing, animationDelay, animationDurationUpdate, animationEasingUpdate, animationDelayUpdate, progressiveThreshold, blendMode, hoverLayerThreshold, useUTC

option.brush (rect/lineX/lineY drag-select, brushSelected) IS implemented — see Brush — but only brushType: "rect" | "lineX" | "lineY" render; a "polygon" value (in brush.brushType or toolbox.feature.brush.type) warns at runtime without showing up in the table as a single key. A custom series' renderItem IS called — see Custom series — but only for coordinateSystem: "cartesian2d" (the default) or "none"; "polar"/"geo" custom series are typed but not rendered, and warn instead. Top-level radiusAxis/angleAxis are inert because polar series are not rendered — the polar coordinate math exists but nothing draws through it.

The animation*, progressiveThreshold and largeThreshold keys are deliberately not warned about at runtime: they change timing or a performance hint rather than what is drawn, and warning on them would fire on nearly every pasted ECharts option. (z, zlevel and silent are no longer on this list — they are read.)

Key differences when migrating:

  • color in a series accepts a ThemeFamily string ("primary") as well as the "#hex" / "rgb()" / "var(--…)" values ECharts takes. Prefer the family name so the series follows the theme; an omitted color falls back to the palette entry for the series' index in option.series.
  • No echarts.init() — use the chart() patch directly
  • No setOption() — pass a State<ChartOption> for reactive updates
  • tooltip.formatter can return a DomphyElement (plain object) in addition to a string
  • chart.on("click", …) is chart(option, { click }) on the patch, or engine.on("click", …) on ChartEngine. selectchanged and brushSelected are wired the same way; those three are the only events.
  • emphasis / blur / select / selectedMode work on line, bar, scatter, pie, radar, heatmap, candlestick, gauge, boxplot and funnel, at series level (not per data item), and are opt-in — see Interaction & Events. Per-datum mouse hover/click hit-testing reaches all ten (heatmap cell rect, candlestick/boxplot body+wick box, radar polygon, funnel trapezoid, gauge progress arc — each the same geometry its renderer paints from). On every other series type the keys are still inert and marked @deprecated.