@carto/ps-react-ui 4.12.0 → 4.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/dist/{echart-BMPpj7n_.js → echart-Bdvbfx9s.js} +2 -2
  2. package/dist/echart-Bdvbfx9s.js.map +1 -0
  3. package/dist/{option-builders-F-c9ELi1.js → option-builders-DPeoyQaM.js} +41 -33
  4. package/dist/option-builders-DPeoyQaM.js.map +1 -0
  5. package/dist/types/widgets/utils/chart-config/index.d.ts +1 -1
  6. package/dist/types/widgets-v2/pie/skeleton.d.ts +9 -0
  7. package/dist/widgets/bar.js +1 -1
  8. package/dist/widgets/histogram.js +1 -1
  9. package/dist/widgets/pie.js +1 -1
  10. package/dist/widgets/scatterplot.js +5 -5
  11. package/dist/widgets/timeseries.js +1 -1
  12. package/dist/widgets/utils.js +1 -1
  13. package/dist/widgets-v2/bar.js +64 -64
  14. package/dist/widgets-v2/bar.js.map +1 -1
  15. package/dist/widgets-v2/echart.js +1 -1
  16. package/dist/widgets-v2/histogram.js +89 -89
  17. package/dist/widgets-v2/histogram.js.map +1 -1
  18. package/dist/widgets-v2/pie.js +149 -95
  19. package/dist/widgets-v2/pie.js.map +1 -1
  20. package/dist/widgets-v2/scatterplot.js +153 -157
  21. package/dist/widgets-v2/scatterplot.js.map +1 -1
  22. package/dist/widgets-v2/timeseries.js +74 -74
  23. package/dist/widgets-v2/timeseries.js.map +1 -1
  24. package/dist/widgets-v2.js +2 -2
  25. package/package.json +1 -1
  26. package/src/widgets/utils/chart-config/index.ts +1 -0
  27. package/src/widgets/utils/chart-config/option-builders.test.ts +34 -0
  28. package/src/widgets/utils/chart-config/option-builders.ts +21 -0
  29. package/src/widgets-v2/bar/options.ts +3 -2
  30. package/src/widgets-v2/echart/edge-label-clamp.ts +7 -4
  31. package/src/widgets-v2/histogram/options.ts +3 -2
  32. package/src/widgets-v2/pie/skeleton.test.tsx +6 -3
  33. package/src/widgets-v2/pie/skeleton.tsx +69 -7
  34. package/src/widgets-v2/scatterplot/options.ts +3 -6
  35. package/src/widgets-v2/timeseries/options.ts +3 -2
  36. package/dist/echart-BMPpj7n_.js.map +0 -1
  37. package/dist/option-builders-F-c9ELi1.js.map +0 -1
  38. package/dist/types/widgets/utils/chart-config/option-builders.d.ts +0 -124
@@ -1 +1 @@
1
- {"version":3,"file":"timeseries.js","sources":["../../src/widgets-v2/timeseries/options.ts","../../src/widgets-v2/timeseries/skeleton.tsx","../../src/widgets-v2/timeseries/download.ts"],"sourcesContent":["import type { EChartsOption } from 'echarts'\nimport * as echarts from 'echarts'\nimport type { CallbackDataParams } from 'echarts/types/dist/shared'\nimport {\n buildGridConfig,\n buildLegendConfig,\n createTooltipFormatter,\n createTooltipPositioner,\n niceNum,\n} from '../../widgets/utils/chart-config'\nimport { ZOOM_LAYOUT } from '../actions/zoom-toggle'\nimport type { OptionFactory } from '../echart'\nimport { mergeOptions, resolveThemeColor } from '../utils'\nimport { positionDataZoomForLegend } from '../utils/data-zoom-layout'\nimport type {\n TimeseriesEChartsOption,\n TimeseriesOptionFactoryInput,\n TimeseriesOptionsInput,\n TimeseriesWidgetData,\n} from './types'\n\n/**\n * Builds the **structural** ECharts option for a timeseries widget —\n * time x-axis, value y-axis, themed tooltip, themed legend, CARTO color\n * palette. Mirrors {@link import('../bar/options').barOptions} so all\n * four ECharts widgets share v1 look-and-feel.\n *\n * Intentional deviations from bar (timeseries-specific):\n * - **X-axis is `type: 'time'`** (not 'category'). ECharts handles\n * uneven sample spacing and zoom-level-aware label formatting.\n * `labelFormatter` is wrapped so the consumer sees a `Date`, not\n * a numeric timestamp.\n * - **Tooltip body reads `{ name, value }` rows**, same as bar, but\n * the `name` may arrive as `Date | number | string`. The\n * `labelFormatter` receives a `Date` regardless.\n *\n * Intentionally data-agnostic: no series, no dataset, no `legend.show`\n * (those depend on data and are added by the option factory's merge\n * phase via {@link createTimeseriesOptionFactory}).\n */\nexport function timeseriesOptions({\n theme,\n formatter,\n labelFormatter,\n}: TimeseriesOptionsInput): TimeseriesEChartsOption {\n // Closure shared between yAxis min/max callbacks and the label formatter,\n // so only the rounded extents are labelled (matches v1 + bar).\n let niceMin = 0\n let niceMax = 1\n\n return {\n grid: {\n left: parseInt(theme.spacing(1)),\n top: parseInt(theme.spacing(3)),\n right: parseInt(theme.spacing(1)),\n // Default: no legend. Merger bumps this when there are >1 series.\n ...buildGridConfig(false, theme),\n containLabel: true,\n },\n tooltip: {\n trigger: 'axis',\n backgroundColor: theme.palette.grey[900],\n borderWidth: 0,\n padding: [parseInt(theme.spacing(1)), parseInt(theme.spacing(1))],\n textStyle: {\n color: theme.palette.common.white,\n fontSize: 11,\n fontFamily: theme.typography.caption.fontFamily,\n },\n axisPointer: { type: 'line' },\n position: createTooltipPositioner(theme),\n formatter: buildTimeseriesTooltipFormatter(formatter, labelFormatter),\n },\n // Legend styling baked here; `show` is toggled by the merger based on\n // series count.\n legend: {\n ...buildLegendConfig({ hasLegend: false }),\n },\n axisPointer: { lineStyle: { color: theme.palette.grey[400] } },\n color: [\n theme.palette.secondary.main,\n ...Object.values(\n (theme.palette as { qualitative?: { bold?: Record<string, string> } })\n .qualitative?.bold ?? {},\n ),\n ],\n xAxis: {\n type: 'time',\n axisLine: { show: false },\n axisTick: { show: false },\n axisLabel: {\n padding: [parseInt(theme.spacing(0.5)), 0, 0, 0],\n margin: 0,\n hideOverlap: true,\n ...(labelFormatter && {\n formatter: (value: number) => labelFormatter(new Date(value)),\n }),\n },\n },\n yAxis: {\n type: 'value',\n min: (extent: { min: number }) => {\n niceMin = extent.min < 0 ? niceNum(extent.min) : 0\n return niceMin\n },\n max: (extent: { min: number; max: number }) => {\n niceMax = extent.max <= 0 ? 1 : niceNum(extent.max)\n return niceMax\n },\n axisLine: { show: false },\n axisTick: { show: false },\n splitLine: {\n show: true,\n lineStyle: { color: theme.palette.black?.[4] ?? theme.palette.divider },\n },\n axisLabel: {\n fontSize: theme.typography.overlineDelicate?.fontSize,\n fontFamily: theme.typography.overlineDelicate?.fontFamily,\n margin: parseInt(theme.spacing(1)),\n show: true,\n showMaxLabel: true,\n showMinLabel: true,\n verticalAlign: 'bottom',\n inside: true,\n formatter: (value: number) => {\n if (value !== niceMax && value !== niceMin) return ''\n if (value === 0) return ''\n return formatter ? formatter(value) : String(value)\n },\n },\n },\n } as TimeseriesEChartsOption\n}\n\n/**\n * Returns the timeseries widget's {@link OptionFactory} — one closure\n * that owns BOTH phases of option construction:\n *\n * - **Structural phase** (`option == null`) — builds the theme-aware\n * structural option via {@link timeseriesOptions}, optionally merging\n * the consumer-supplied `optionsOverride`. Called once by Provider to\n * seed `rawOptions` in the store.\n * - **Merge phase** (`option != null`) — fuses post-pipeline `state.data`\n * (`TimeseriesWidgetData`) into the option via the dataset API: one\n * dataset per series, each series referencing its dataset by index,\n * encoded by `name` (x — time) and `value` (y). Mirrors {@link import('../bar/options').createBarOptionFactory}:\n * series-template merge for `addStack`, reactive formatters from\n * `ctx`, `niceNum`-rounded y-axis bounds at fusion time, and\n * `positionDataZoomForLegend` layout for ZoomToggle sliders.\n */\nexport function createTimeseriesOptionFactory(\n options: TimeseriesOptionFactoryInput,\n): OptionFactory {\n const { theme, formatter, labelFormatter, optionsOverride } = options\n const series = options.series\n const smooth = options.smooth ?? true\n const area = options.area ?? false\n const selection = options.selection\n const selectionSet =\n selection && selection.length > 0\n ? new Set<string | number>(selection)\n : null\n // `name` may be Date | number | string. Normalize to the same type the\n // selection is keyed on (Date → ms) so Set lookups match.\n const normalizeName = (n: Date | number | string): string | number =>\n n instanceof Date ? n.getTime() : n\n return (option, data, ctx) => {\n if (option == null) {\n const structural = timeseriesOptions({ theme, formatter, labelFormatter })\n return optionsOverride\n ? (mergeOptions(\n structural as unknown as Record<string, unknown>,\n optionsOverride as Partial<Record<string, unknown>>,\n ) as EChartsOption)\n : structural\n }\n\n const seriesArr = Array.isArray(data) ? (data as TimeseriesWidgetData) : []\n if (seriesArr.length === 0) {\n return { ...option, dataset: [], series: [] }\n }\n const hasLegend = seriesArr.length > 1\n const seriesTemplates = Array.isArray(option.series) ? option.series : []\n const broadcastTemplate = seriesTemplates[0] ?? {}\n const baseYAxis =\n typeof option.yAxis === 'object' && !Array.isArray(option.yAxis)\n ? option.yAxis\n : {}\n const baseGrid =\n typeof option.grid === 'object' && !Array.isArray(option.grid)\n ? option.grid\n : {}\n const baseTooltip =\n typeof option.tooltip === 'object' && !Array.isArray(option.tooltip)\n ? option.tooltip\n : {}\n const baseLegend =\n typeof option.legend === 'object' && !Array.isArray(option.legend)\n ? option.legend\n : {}\n\n // Reactive (live store) formatter from ctx — distinct from the\n // closure-time `formatter` captured for the structural-build branch\n // above. RelativeData can install a percent formatter on the store\n // after the factory was constructed; the merge phase reads `ctx` to\n // pick that up. `labelFormatter` (Date → string) is structural-only —\n // not relativizable — so the merge branch reads the closure-time value.\n const liveFormatter = ctx?.formatter\n\n // Closure shared between the yAxis min/max callbacks and the label\n // formatter, so only the rounded extents are labelled (matches v1 +\n // bar). Delegating the extent to ECharts (rather than precomputing\n // scalars from the raw data) keeps stacked lines inside the plot: when\n // StackToggle marks the series, ECharts feeds the *post-stack* extent\n // to these callbacks, so `niceNum` rounds the stacked total.\n let niceMin = 0\n let niceMax = 1\n\n // Zoom slider layout: when ZoomToggle has installed `dataZoom`, push the\n // slider above the legend (if any) and reserve room in the grid below.\n const dataZoomLayout = positionDataZoomForLegend(option.dataZoom, hasLegend)\n const fallbackBottom =\n typeof baseGrid.bottom === 'number' ? baseGrid.bottom : 24\n const baseBottom = hasLegend ? 56 : fallbackBottom\n const gridBottom = dataZoomLayout\n ? baseBottom + ZOOM_LAYOUT.sliderHeight + ZOOM_LAYOUT.sliderGap\n : baseBottom\n\n // Dim non-selected points via `series.itemStyle.color`. Per-row\n // `itemStyle` on dataset object-rows is silently ignored when\n // `series.encode` is in play.\n //\n // We *always* emit `itemStyle.color` (a passthrough when nothing is\n // selected), not conditionally — dropping the key between renders\n // would let ECharts' default merge keep the previous callback alive\n // and points would stay dimmed forever after an external clear.\n // Always emitting lets normal merge swap the callback in place, no\n // `replaceMerge` and no entry-animation flash on selection on/off.\n const dimItemStyle = {\n color: (params: CallbackDataParams) => {\n const datum = params.value as\n | { name?: Date | number | string }\n | undefined\n const raw = datum?.name\n const base = params.color as string\n if (!selectionSet || raw == null) return base\n return selectionSet.has(normalizeName(raw))\n ? base\n : echarts.color.modifyAlpha(base, 0.15)\n },\n }\n\n return {\n ...option,\n dataset: seriesArr.map((s) => ({ source: s as readonly object[] })),\n series: seriesArr.map((_, i) => {\n const template =\n (seriesTemplates[i] as object | undefined) ??\n (broadcastTemplate as object)\n // For line series, set BOTH `series[i].color` (legend swatch +\n // markers) AND `series[i].lineStyle.color` (the line itself) so\n // the override paints everywhere a series has a colour slot.\n const overrideColor = resolveThemeColor(theme, series?.[i]?.color)\n return {\n ...(typeof template === 'object' ? template : {}),\n type: 'line' as const,\n datasetIndex: i,\n name: series?.[i]?.name ?? `Series ${i + 1}`,\n encode: { x: 'name', y: 'value' },\n smooth,\n // When a selection is active, surface markers so the per-point\n // color callback has something to dim — a continuous line would\n // hide the visual selection feedback.\n showSymbol: selectionSet != null,\n ...(area ? { areaStyle: {} } : {}),\n emphasis: { focus: 'series' },\n itemStyle: dimItemStyle,\n ...(overrideColor\n ? { color: overrideColor, lineStyle: { color: overrideColor } }\n : {}),\n }\n }),\n legend: { ...baseLegend, show: hasLegend },\n grid: { ...baseGrid, bottom: gridBottom },\n ...(dataZoomLayout ? { dataZoom: dataZoomLayout } : {}),\n yAxis: {\n ...baseYAxis,\n min: (extent: { min: number }) => {\n niceMin = extent.min < 0 ? niceNum(extent.min) : 0\n return niceMin\n },\n max: (extent: { min: number; max: number }) => {\n niceMax = extent.max <= 0 ? 1 : niceNum(extent.max)\n return niceMax\n },\n axisLabel: {\n ...((baseYAxis as { axisLabel?: object }).axisLabel ?? {}),\n formatter: (value: number) => {\n if (value !== niceMax && value !== niceMin) return ''\n if (value === 0) return ''\n return liveFormatter ? liveFormatter(value) : String(value)\n },\n },\n } as EChartsOption['yAxis'],\n tooltip: {\n ...baseTooltip,\n formatter: buildTimeseriesTooltipFormatter(\n liveFormatter,\n labelFormatter,\n ),\n },\n } as EChartsOption\n }\n}\n\n/**\n * Tooltip formatter for the timeseries `{ name, value }` row shape.\n * `name` arrives as `Date | number | string` (the time-axis stores the\n * raw value the consumer supplied). The consumer's `labelFormatter`\n * expects a `Date`, so we coerce non-Date values via `new Date(...)`\n * before invoking it.\n */\nfunction buildTimeseriesTooltipFormatter(\n formatter: ((value: number) => string) | undefined,\n labelFormatter: ((value: Date) => string) | undefined,\n) {\n return createTooltipFormatter((item) => {\n const row = item.value as\n | { name?: Date | number | string; value?: number }\n | undefined\n const raw = row?.value\n const formattedValue =\n typeof raw === 'number' && formatter ? formatter(raw) : (raw ?? '')\n const marker = typeof item.marker === 'string' ? item.marker : ''\n const seriesName = item.seriesName ? `${item.seriesName}: ` : ''\n const rawName = row?.name ?? item.name\n const name =\n labelFormatter && rawName != null\n ? labelFormatter(rawName instanceof Date ? rawName : new Date(rawName))\n : (rawName ?? '')\n return { name: String(name), seriesName, marker, value: formattedValue }\n })\n}\n","import { Box, Skeleton } from '@mui/material'\nimport type { SxProps, Theme } from '@mui/material'\n\nconst styles = {\n container: {\n display: 'flex',\n flexDirection: 'column',\n justifyContent: 'space-between',\n gap: ({ spacing }) => spacing(1),\n height: ({ spacing }) => spacing(38),\n },\n graph: {\n position: 'relative',\n flex: '1 1 auto',\n width: '100%',\n },\n segmentA: {\n position: 'absolute',\n left: 0,\n right: '70%',\n top: '60%',\n height: 4,\n },\n segmentB: {\n position: 'absolute',\n left: '25%',\n right: '40%',\n top: '35%',\n height: 4,\n },\n segmentC: {\n position: 'absolute',\n left: '55%',\n right: 0,\n top: '50%',\n height: 4,\n },\n legend: {\n display: 'flex',\n alignItems: 'center',\n gap: ({ spacing }) => spacing(2),\n height: ({ spacing }) => spacing(5),\n },\n legendItem: {\n display: 'flex',\n alignItems: 'center',\n gap: ({ spacing }) => spacing(1.5),\n },\n} satisfies Record<string, SxProps<Theme>>\n\n/**\n * Loading state for the Timeseries widget. Mirrors a line chart's\n * silhouette — three thin horizontal segments at staggered offsets to\n * suggest a moving line, plus a 2-dot legend stub. Sibling-consistent\n * with Bar / Histogram / Scatter skeletons (column-flex container with\n * legend strip below).\n */\nexport function TimeseriesSkeleton() {\n return (\n <Box sx={styles.container}>\n <Box sx={styles.graph}>\n <Skeleton variant='rectangular' sx={styles.segmentA} />\n <Skeleton variant='rectangular' sx={styles.segmentB} />\n <Skeleton variant='rectangular' sx={styles.segmentC} />\n </Box>\n <Box sx={styles.legend}>\n {[0, 1].map((i) => (\n <Box key={`legend-${i}`} sx={styles.legendItem}>\n <Skeleton variant='circular' width={8} height={8} />\n <Skeleton width={48} height={8} />\n </Box>\n ))}\n </Box>\n </Box>\n )\n}\n","import {\n buildCsvDownloadItem,\n buildPngDownloadItem,\n type DownloadItem,\n} from '../actions/download'\nimport type { TimeseriesWidgetData } from './types'\n\n/**\n * Download menu items for the Timeseries widget. Always includes a CSV\n * item with `time, series_1, series_2, …` columns (one row per unique time\n * across all series; ISO-8601 strings for `Date`/numeric times). When\n * `getCaptureEl` is supplied, prepends a PNG item that rasterises the\n * captured element via `html2canvas`.\n */\nexport function createTimeseriesDownloadConfig(args: {\n filename: string\n getData: () => TimeseriesWidgetData\n seriesNames?: readonly string[]\n getCaptureEl?: () => HTMLElement | null\n pngPixelRatio?: number\n pngBackgroundColor?: string | null\n}): DownloadItem[] {\n const items: DownloadItem[] = []\n if (args.getCaptureEl) {\n items.push(\n buildPngDownloadItem({\n filename: args.filename,\n getCaptureEl: args.getCaptureEl,\n pixelRatio: args.pngPixelRatio,\n backgroundColor: args.pngBackgroundColor,\n }),\n )\n }\n items.push(\n buildCsvDownloadItem({\n filename: args.filename,\n getRows: () => {\n const data = args.getData()\n const seriesCount = data.length\n\n // Collect every unique time, preserving insertion order.\n const timeKeys: (Date | number | string)[] = []\n const seenKeys = new Set<string>()\n for (const series of data) {\n for (const point of series) {\n const key = String(point.name)\n if (!seenKeys.has(key)) {\n seenKeys.add(key)\n timeKeys.push(point.name)\n }\n }\n }\n\n // Build a quick lookup per series for O(rows × series) emit.\n const lookups = data.map(\n (series) => new Map(series.map((p) => [String(p.name), p.value])),\n )\n\n const header: unknown[] = ['time']\n for (let i = 0; i < seriesCount; i++) {\n header.push(args.seriesNames?.[i] ?? `series_${i + 1}`)\n }\n const rows: unknown[][] = [header]\n for (const key of timeKeys) {\n const row: unknown[] = [formatTime(key)]\n const lookupKey = String(key)\n for (const lookup of lookups) row.push(lookup.get(lookupKey) ?? '')\n rows.push(row)\n }\n\n return rows\n },\n }),\n )\n return items\n}\n\nfunction formatTime(v: Date | number | string): string {\n if (v instanceof Date) return v.toISOString()\n if (typeof v === 'number') return new Date(v).toISOString()\n return v\n}\n"],"names":["timeseriesOptions","theme","formatter","labelFormatter","niceMin","niceMax","grid","left","parseInt","spacing","top","right","buildGridConfig","containLabel","tooltip","trigger","backgroundColor","palette","grey","borderWidth","padding","textStyle","color","common","white","fontSize","fontFamily","typography","caption","axisPointer","type","position","createTooltipPositioner","buildTimeseriesTooltipFormatter","legend","buildLegendConfig","hasLegend","lineStyle","secondary","main","Object","values","qualitative","bold","xAxis","axisLine","show","axisTick","axisLabel","margin","hideOverlap","value","Date","yAxis","min","extent","niceNum","max","splitLine","black","divider","overlineDelicate","showMaxLabel","showMinLabel","verticalAlign","inside","String","createTimeseriesOptionFactory","options","optionsOverride","series","smooth","area","selection","selectionSet","length","Set","normalizeName","n","getTime","option","data","ctx","structural","mergeOptions","seriesArr","Array","isArray","dataset","seriesTemplates","broadcastTemplate","baseYAxis","baseGrid","baseTooltip","baseLegend","liveFormatter","dataZoomLayout","positionDataZoomForLegend","dataZoom","fallbackBottom","bottom","baseBottom","gridBottom","ZOOM_LAYOUT","sliderHeight","sliderGap","dimItemStyle","params","raw","name","base","has","echarts","modifyAlpha","map","s","source","_","i","template","overrideColor","resolveThemeColor","datasetIndex","encode","x","y","showSymbol","areaStyle","emphasis","focus","itemStyle","createTooltipFormatter","item","row","formattedValue","marker","seriesName","rawName","styles","container","display","flexDirection","justifyContent","gap","height","graph","flex","width","segmentA","segmentB","segmentC","alignItems","legendItem","TimeseriesSkeleton","$","_c","t0","Symbol","for","jsxs","Box","jsx","Skeleton","t1","_temp","createTimeseriesDownloadConfig","args","items","getCaptureEl","push","buildPngDownloadItem","filename","pixelRatio","pngPixelRatio","pngBackgroundColor","buildCsvDownloadItem","getRows","getData","seriesCount","timeKeys","seenKeys","point","key","add","lookups","Map","p","header","seriesNames","rows","formatTime","lookupKey","lookup","get","v","toISOString"],"mappings":"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"}
1
+ {"version":3,"file":"timeseries.js","sources":["../../src/widgets-v2/timeseries/options.ts","../../src/widgets-v2/timeseries/skeleton.tsx","../../src/widgets-v2/timeseries/download.ts"],"sourcesContent":["import type { EChartsOption } from 'echarts'\nimport * as echarts from 'echarts'\nimport type { CallbackDataParams } from 'echarts/types/dist/shared'\nimport {\n buildAxisLabelStyle,\n buildGridConfig,\n buildLegendConfig,\n createTooltipFormatter,\n createTooltipPositioner,\n niceNum,\n} from '../../widgets/utils/chart-config'\nimport { ZOOM_LAYOUT } from '../actions/zoom-toggle'\nimport type { OptionFactory } from '../echart'\nimport { mergeOptions, resolveThemeColor } from '../utils'\nimport { positionDataZoomForLegend } from '../utils/data-zoom-layout'\nimport type {\n TimeseriesEChartsOption,\n TimeseriesOptionFactoryInput,\n TimeseriesOptionsInput,\n TimeseriesWidgetData,\n} from './types'\n\n/**\n * Builds the **structural** ECharts option for a timeseries widget —\n * time x-axis, value y-axis, themed tooltip, themed legend, CARTO color\n * palette. Mirrors {@link import('../bar/options').barOptions} so all\n * four ECharts widgets share v1 look-and-feel.\n *\n * Intentional deviations from bar (timeseries-specific):\n * - **X-axis is `type: 'time'`** (not 'category'). ECharts handles\n * uneven sample spacing and zoom-level-aware label formatting.\n * `labelFormatter` is wrapped so the consumer sees a `Date`, not\n * a numeric timestamp.\n * - **Tooltip body reads `{ name, value }` rows**, same as bar, but\n * the `name` may arrive as `Date | number | string`. The\n * `labelFormatter` receives a `Date` regardless.\n *\n * Intentionally data-agnostic: no series, no dataset, no `legend.show`\n * (those depend on data and are added by the option factory's merge\n * phase via {@link createTimeseriesOptionFactory}).\n */\nexport function timeseriesOptions({\n theme,\n formatter,\n labelFormatter,\n}: TimeseriesOptionsInput): TimeseriesEChartsOption {\n // Closure shared between yAxis min/max callbacks and the label formatter,\n // so only the rounded extents are labelled (matches v1 + bar).\n let niceMin = 0\n let niceMax = 1\n\n return {\n grid: {\n left: parseInt(theme.spacing(1)),\n top: parseInt(theme.spacing(3)),\n right: parseInt(theme.spacing(1)),\n // Default: no legend. Merger bumps this when there are >1 series.\n ...buildGridConfig(false, theme),\n containLabel: true,\n },\n tooltip: {\n trigger: 'axis',\n backgroundColor: theme.palette.grey[900],\n borderWidth: 0,\n padding: [parseInt(theme.spacing(1)), parseInt(theme.spacing(1))],\n textStyle: {\n color: theme.palette.common.white,\n fontSize: 11,\n fontFamily: theme.typography.caption.fontFamily,\n },\n axisPointer: { type: 'line' },\n position: createTooltipPositioner(theme),\n formatter: buildTimeseriesTooltipFormatter(formatter, labelFormatter),\n },\n // Legend styling baked here; `show` is toggled by the merger based on\n // series count.\n legend: {\n ...buildLegendConfig({ hasLegend: false }),\n },\n axisPointer: { lineStyle: { color: theme.palette.grey[400] } },\n color: [\n theme.palette.secondary.main,\n ...Object.values(\n (theme.palette as { qualitative?: { bold?: Record<string, string> } })\n .qualitative?.bold ?? {},\n ),\n ],\n xAxis: {\n type: 'time',\n axisLine: { show: false },\n axisTick: { show: false },\n axisLabel: {\n ...buildAxisLabelStyle(theme),\n padding: [parseInt(theme.spacing(0.5)), 0, 0, 0],\n margin: 0,\n hideOverlap: true,\n ...(labelFormatter && {\n formatter: (value: number) => labelFormatter(new Date(value)),\n }),\n },\n },\n yAxis: {\n type: 'value',\n min: (extent: { min: number }) => {\n niceMin = extent.min < 0 ? niceNum(extent.min) : 0\n return niceMin\n },\n max: (extent: { min: number; max: number }) => {\n niceMax = extent.max <= 0 ? 1 : niceNum(extent.max)\n return niceMax\n },\n axisLine: { show: false },\n axisTick: { show: false },\n splitLine: {\n show: true,\n lineStyle: { color: theme.palette.black?.[4] ?? theme.palette.divider },\n },\n axisLabel: {\n ...buildAxisLabelStyle(theme),\n margin: parseInt(theme.spacing(1)),\n show: true,\n showMaxLabel: true,\n showMinLabel: true,\n verticalAlign: 'bottom',\n inside: true,\n formatter: (value: number) => {\n if (value !== niceMax && value !== niceMin) return ''\n if (value === 0) return ''\n return formatter ? formatter(value) : String(value)\n },\n },\n },\n } as TimeseriesEChartsOption\n}\n\n/**\n * Returns the timeseries widget's {@link OptionFactory} — one closure\n * that owns BOTH phases of option construction:\n *\n * - **Structural phase** (`option == null`) — builds the theme-aware\n * structural option via {@link timeseriesOptions}, optionally merging\n * the consumer-supplied `optionsOverride`. Called once by Provider to\n * seed `rawOptions` in the store.\n * - **Merge phase** (`option != null`) — fuses post-pipeline `state.data`\n * (`TimeseriesWidgetData`) into the option via the dataset API: one\n * dataset per series, each series referencing its dataset by index,\n * encoded by `name` (x — time) and `value` (y). Mirrors {@link import('../bar/options').createBarOptionFactory}:\n * series-template merge for `addStack`, reactive formatters from\n * `ctx`, `niceNum`-rounded y-axis bounds at fusion time, and\n * `positionDataZoomForLegend` layout for ZoomToggle sliders.\n */\nexport function createTimeseriesOptionFactory(\n options: TimeseriesOptionFactoryInput,\n): OptionFactory {\n const { theme, formatter, labelFormatter, optionsOverride } = options\n const series = options.series\n const smooth = options.smooth ?? true\n const area = options.area ?? false\n const selection = options.selection\n const selectionSet =\n selection && selection.length > 0\n ? new Set<string | number>(selection)\n : null\n // `name` may be Date | number | string. Normalize to the same type the\n // selection is keyed on (Date → ms) so Set lookups match.\n const normalizeName = (n: Date | number | string): string | number =>\n n instanceof Date ? n.getTime() : n\n return (option, data, ctx) => {\n if (option == null) {\n const structural = timeseriesOptions({ theme, formatter, labelFormatter })\n return optionsOverride\n ? (mergeOptions(\n structural as unknown as Record<string, unknown>,\n optionsOverride as Partial<Record<string, unknown>>,\n ) as EChartsOption)\n : structural\n }\n\n const seriesArr = Array.isArray(data) ? (data as TimeseriesWidgetData) : []\n if (seriesArr.length === 0) {\n return { ...option, dataset: [], series: [] }\n }\n const hasLegend = seriesArr.length > 1\n const seriesTemplates = Array.isArray(option.series) ? option.series : []\n const broadcastTemplate = seriesTemplates[0] ?? {}\n const baseYAxis =\n typeof option.yAxis === 'object' && !Array.isArray(option.yAxis)\n ? option.yAxis\n : {}\n const baseGrid =\n typeof option.grid === 'object' && !Array.isArray(option.grid)\n ? option.grid\n : {}\n const baseTooltip =\n typeof option.tooltip === 'object' && !Array.isArray(option.tooltip)\n ? option.tooltip\n : {}\n const baseLegend =\n typeof option.legend === 'object' && !Array.isArray(option.legend)\n ? option.legend\n : {}\n\n // Reactive (live store) formatter from ctx — distinct from the\n // closure-time `formatter` captured for the structural-build branch\n // above. RelativeData can install a percent formatter on the store\n // after the factory was constructed; the merge phase reads `ctx` to\n // pick that up. `labelFormatter` (Date → string) is structural-only —\n // not relativizable — so the merge branch reads the closure-time value.\n const liveFormatter = ctx?.formatter\n\n // Closure shared between the yAxis min/max callbacks and the label\n // formatter, so only the rounded extents are labelled (matches v1 +\n // bar). Delegating the extent to ECharts (rather than precomputing\n // scalars from the raw data) keeps stacked lines inside the plot: when\n // StackToggle marks the series, ECharts feeds the *post-stack* extent\n // to these callbacks, so `niceNum` rounds the stacked total.\n let niceMin = 0\n let niceMax = 1\n\n // Zoom slider layout: when ZoomToggle has installed `dataZoom`, push the\n // slider above the legend (if any) and reserve room in the grid below.\n const dataZoomLayout = positionDataZoomForLegend(option.dataZoom, hasLegend)\n const fallbackBottom =\n typeof baseGrid.bottom === 'number' ? baseGrid.bottom : 24\n const baseBottom = hasLegend ? 56 : fallbackBottom\n const gridBottom = dataZoomLayout\n ? baseBottom + ZOOM_LAYOUT.sliderHeight + ZOOM_LAYOUT.sliderGap\n : baseBottom\n\n // Dim non-selected points via `series.itemStyle.color`. Per-row\n // `itemStyle` on dataset object-rows is silently ignored when\n // `series.encode` is in play.\n //\n // We *always* emit `itemStyle.color` (a passthrough when nothing is\n // selected), not conditionally — dropping the key between renders\n // would let ECharts' default merge keep the previous callback alive\n // and points would stay dimmed forever after an external clear.\n // Always emitting lets normal merge swap the callback in place, no\n // `replaceMerge` and no entry-animation flash on selection on/off.\n const dimItemStyle = {\n color: (params: CallbackDataParams) => {\n const datum = params.value as\n | { name?: Date | number | string }\n | undefined\n const raw = datum?.name\n const base = params.color as string\n if (!selectionSet || raw == null) return base\n return selectionSet.has(normalizeName(raw))\n ? base\n : echarts.color.modifyAlpha(base, 0.15)\n },\n }\n\n return {\n ...option,\n dataset: seriesArr.map((s) => ({ source: s as readonly object[] })),\n series: seriesArr.map((_, i) => {\n const template =\n (seriesTemplates[i] as object | undefined) ??\n (broadcastTemplate as object)\n // For line series, set BOTH `series[i].color` (legend swatch +\n // markers) AND `series[i].lineStyle.color` (the line itself) so\n // the override paints everywhere a series has a colour slot.\n const overrideColor = resolveThemeColor(theme, series?.[i]?.color)\n return {\n ...(typeof template === 'object' ? template : {}),\n type: 'line' as const,\n datasetIndex: i,\n name: series?.[i]?.name ?? `Series ${i + 1}`,\n encode: { x: 'name', y: 'value' },\n smooth,\n // When a selection is active, surface markers so the per-point\n // color callback has something to dim — a continuous line would\n // hide the visual selection feedback.\n showSymbol: selectionSet != null,\n ...(area ? { areaStyle: {} } : {}),\n emphasis: { focus: 'series' },\n itemStyle: dimItemStyle,\n ...(overrideColor\n ? { color: overrideColor, lineStyle: { color: overrideColor } }\n : {}),\n }\n }),\n legend: { ...baseLegend, show: hasLegend },\n grid: { ...baseGrid, bottom: gridBottom },\n ...(dataZoomLayout ? { dataZoom: dataZoomLayout } : {}),\n yAxis: {\n ...baseYAxis,\n min: (extent: { min: number }) => {\n niceMin = extent.min < 0 ? niceNum(extent.min) : 0\n return niceMin\n },\n max: (extent: { min: number; max: number }) => {\n niceMax = extent.max <= 0 ? 1 : niceNum(extent.max)\n return niceMax\n },\n axisLabel: {\n ...((baseYAxis as { axisLabel?: object }).axisLabel ?? {}),\n formatter: (value: number) => {\n if (value !== niceMax && value !== niceMin) return ''\n if (value === 0) return ''\n return liveFormatter ? liveFormatter(value) : String(value)\n },\n },\n } as EChartsOption['yAxis'],\n tooltip: {\n ...baseTooltip,\n formatter: buildTimeseriesTooltipFormatter(\n liveFormatter,\n labelFormatter,\n ),\n },\n } as EChartsOption\n }\n}\n\n/**\n * Tooltip formatter for the timeseries `{ name, value }` row shape.\n * `name` arrives as `Date | number | string` (the time-axis stores the\n * raw value the consumer supplied). The consumer's `labelFormatter`\n * expects a `Date`, so we coerce non-Date values via `new Date(...)`\n * before invoking it.\n */\nfunction buildTimeseriesTooltipFormatter(\n formatter: ((value: number) => string) | undefined,\n labelFormatter: ((value: Date) => string) | undefined,\n) {\n return createTooltipFormatter((item) => {\n const row = item.value as\n | { name?: Date | number | string; value?: number }\n | undefined\n const raw = row?.value\n const formattedValue =\n typeof raw === 'number' && formatter ? formatter(raw) : (raw ?? '')\n const marker = typeof item.marker === 'string' ? item.marker : ''\n const seriesName = item.seriesName ? `${item.seriesName}: ` : ''\n const rawName = row?.name ?? item.name\n const name =\n labelFormatter && rawName != null\n ? labelFormatter(rawName instanceof Date ? rawName : new Date(rawName))\n : (rawName ?? '')\n return { name: String(name), seriesName, marker, value: formattedValue }\n })\n}\n","import { Box, Skeleton } from '@mui/material'\nimport type { SxProps, Theme } from '@mui/material'\n\nconst styles = {\n container: {\n display: 'flex',\n flexDirection: 'column',\n justifyContent: 'space-between',\n gap: ({ spacing }) => spacing(1),\n height: ({ spacing }) => spacing(38),\n },\n graph: {\n position: 'relative',\n flex: '1 1 auto',\n width: '100%',\n },\n segmentA: {\n position: 'absolute',\n left: 0,\n right: '70%',\n top: '60%',\n height: 4,\n },\n segmentB: {\n position: 'absolute',\n left: '25%',\n right: '40%',\n top: '35%',\n height: 4,\n },\n segmentC: {\n position: 'absolute',\n left: '55%',\n right: 0,\n top: '50%',\n height: 4,\n },\n legend: {\n display: 'flex',\n alignItems: 'center',\n gap: ({ spacing }) => spacing(2),\n height: ({ spacing }) => spacing(5),\n },\n legendItem: {\n display: 'flex',\n alignItems: 'center',\n gap: ({ spacing }) => spacing(1.5),\n },\n} satisfies Record<string, SxProps<Theme>>\n\n/**\n * Loading state for the Timeseries widget. Mirrors a line chart's\n * silhouette — three thin horizontal segments at staggered offsets to\n * suggest a moving line, plus a 2-dot legend stub. Sibling-consistent\n * with Bar / Histogram / Scatter skeletons (column-flex container with\n * legend strip below).\n */\nexport function TimeseriesSkeleton() {\n return (\n <Box sx={styles.container}>\n <Box sx={styles.graph}>\n <Skeleton variant='rectangular' sx={styles.segmentA} />\n <Skeleton variant='rectangular' sx={styles.segmentB} />\n <Skeleton variant='rectangular' sx={styles.segmentC} />\n </Box>\n <Box sx={styles.legend}>\n {[0, 1].map((i) => (\n <Box key={`legend-${i}`} sx={styles.legendItem}>\n <Skeleton variant='circular' width={8} height={8} />\n <Skeleton width={48} height={8} />\n </Box>\n ))}\n </Box>\n </Box>\n )\n}\n","import {\n buildCsvDownloadItem,\n buildPngDownloadItem,\n type DownloadItem,\n} from '../actions/download'\nimport type { TimeseriesWidgetData } from './types'\n\n/**\n * Download menu items for the Timeseries widget. Always includes a CSV\n * item with `time, series_1, series_2, …` columns (one row per unique time\n * across all series; ISO-8601 strings for `Date`/numeric times). When\n * `getCaptureEl` is supplied, prepends a PNG item that rasterises the\n * captured element via `html2canvas`.\n */\nexport function createTimeseriesDownloadConfig(args: {\n filename: string\n getData: () => TimeseriesWidgetData\n seriesNames?: readonly string[]\n getCaptureEl?: () => HTMLElement | null\n pngPixelRatio?: number\n pngBackgroundColor?: string | null\n}): DownloadItem[] {\n const items: DownloadItem[] = []\n if (args.getCaptureEl) {\n items.push(\n buildPngDownloadItem({\n filename: args.filename,\n getCaptureEl: args.getCaptureEl,\n pixelRatio: args.pngPixelRatio,\n backgroundColor: args.pngBackgroundColor,\n }),\n )\n }\n items.push(\n buildCsvDownloadItem({\n filename: args.filename,\n getRows: () => {\n const data = args.getData()\n const seriesCount = data.length\n\n // Collect every unique time, preserving insertion order.\n const timeKeys: (Date | number | string)[] = []\n const seenKeys = new Set<string>()\n for (const series of data) {\n for (const point of series) {\n const key = String(point.name)\n if (!seenKeys.has(key)) {\n seenKeys.add(key)\n timeKeys.push(point.name)\n }\n }\n }\n\n // Build a quick lookup per series for O(rows × series) emit.\n const lookups = data.map(\n (series) => new Map(series.map((p) => [String(p.name), p.value])),\n )\n\n const header: unknown[] = ['time']\n for (let i = 0; i < seriesCount; i++) {\n header.push(args.seriesNames?.[i] ?? `series_${i + 1}`)\n }\n const rows: unknown[][] = [header]\n for (const key of timeKeys) {\n const row: unknown[] = [formatTime(key)]\n const lookupKey = String(key)\n for (const lookup of lookups) row.push(lookup.get(lookupKey) ?? '')\n rows.push(row)\n }\n\n return rows\n },\n }),\n )\n return items\n}\n\nfunction formatTime(v: Date | number | string): string {\n if (v instanceof Date) return v.toISOString()\n if (typeof v === 'number') return new Date(v).toISOString()\n return v\n}\n"],"names":["timeseriesOptions","theme","formatter","labelFormatter","niceMin","niceMax","grid","left","parseInt","spacing","top","right","buildGridConfig","containLabel","tooltip","trigger","backgroundColor","palette","grey","borderWidth","padding","textStyle","color","common","white","fontSize","fontFamily","typography","caption","axisPointer","type","position","createTooltipPositioner","buildTimeseriesTooltipFormatter","legend","buildLegendConfig","hasLegend","lineStyle","secondary","main","Object","values","qualitative","bold","xAxis","axisLine","show","axisTick","axisLabel","buildAxisLabelStyle","margin","hideOverlap","value","Date","yAxis","min","extent","niceNum","max","splitLine","black","divider","showMaxLabel","showMinLabel","verticalAlign","inside","String","createTimeseriesOptionFactory","options","optionsOverride","series","smooth","area","selection","selectionSet","length","Set","normalizeName","n","getTime","option","data","ctx","structural","mergeOptions","seriesArr","Array","isArray","dataset","seriesTemplates","broadcastTemplate","baseYAxis","baseGrid","baseTooltip","baseLegend","liveFormatter","dataZoomLayout","positionDataZoomForLegend","dataZoom","fallbackBottom","bottom","baseBottom","gridBottom","ZOOM_LAYOUT","sliderHeight","sliderGap","dimItemStyle","params","raw","name","base","has","echarts","modifyAlpha","map","s","source","_","i","template","overrideColor","resolveThemeColor","datasetIndex","encode","x","y","showSymbol","areaStyle","emphasis","focus","itemStyle","createTooltipFormatter","item","row","formattedValue","marker","seriesName","rawName","styles","container","display","flexDirection","justifyContent","gap","height","graph","flex","width","segmentA","segmentB","segmentC","alignItems","legendItem","TimeseriesSkeleton","$","_c","t0","Symbol","for","jsxs","Box","jsx","Skeleton","t1","_temp","createTimeseriesDownloadConfig","args","items","getCaptureEl","push","buildPngDownloadItem","filename","pixelRatio","pngPixelRatio","pngBackgroundColor","buildCsvDownloadItem","getRows","getData","seriesCount","timeKeys","seenKeys","point","key","add","lookups","Map","p","header","seriesNames","rows","formatTime","lookupKey","lookup","get","v","toISOString"],"mappings":"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"}
@@ -13,8 +13,8 @@ import "./lasso-tool-CDFj4zKY.js";
13
13
  import "./cjs-D4KH3azB.js";
14
14
  import { T as Xe } from "./tooltip-BDnrRKrp.js";
15
15
  import "@carto/ps-utils";
16
- import { o as Ze, a as Ke, E as Ye } from "./echart-BMPpj7n_.js";
17
- import { D as Wn } from "./echart-BMPpj7n_.js";
16
+ import { o as Ze, a as Ke, E as Ye } from "./echart-Bdvbfx9s.js";
17
+ import { D as Wn } from "./echart-Bdvbfx9s.js";
18
18
  import { a as Je, b as Qe, M as et } from "./markdown-BD1jcknS.js";
19
19
  import { C as tt, L as nt, F as ot, j as rt, R as it, B as lt, Z as st, m as at, n as ct, l as dt, k as ft } from "./change-column-DjjwoPt1.js";
20
20
  import "html2canvas";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@carto/ps-react-ui",
3
- "version": "4.12.0",
3
+ "version": "4.12.1",
4
4
  "description": "CARTO's Professional Service React Material library",
5
5
  "type": "module",
6
6
  "devDependencies": {
@@ -15,6 +15,7 @@ export { flattenObjectArrayToCSV, scatterplotDataToCSV } from './csv-modifiers'
15
15
  export {
16
16
  buildLegendConfig,
17
17
  buildGridConfig,
18
+ buildAxisLabelStyle,
18
19
  createTooltipPositioner,
19
20
  createAxisLabelFormatter,
20
21
  createTooltipFormatter,
@@ -7,6 +7,7 @@ import {
7
7
  buildHistogramSeriesLabelConfig,
8
8
  buildLegendConfig,
9
9
  buildGridConfig,
10
+ buildAxisLabelStyle,
10
11
  createTooltipPositioner,
11
12
  createAxisLabelFormatter,
12
13
  applyXAxisFormatter,
@@ -195,6 +196,39 @@ describe('buildGridConfig', () => {
195
196
  })
196
197
  })
197
198
 
199
+ describe('buildAxisLabelStyle', () => {
200
+ const themeWithTokens = {
201
+ typography: {
202
+ overlineDelicate: {
203
+ fontSize: '0.625rem',
204
+ fontFamily: 'Inter, sans-serif',
205
+ },
206
+ },
207
+ palette: {
208
+ black: { 60: 'rgba(44,48,50,0.6)' },
209
+ text: { secondary: 'rgba(0,0,0,0.6)' },
210
+ },
211
+ } as unknown as Theme
212
+
213
+ it('returns the overlineDelicate font + black[60] colour', () => {
214
+ expect(buildAxisLabelStyle(themeWithTokens)).toEqual({
215
+ fontSize: '0.625rem',
216
+ fontFamily: 'Inter, sans-serif',
217
+ color: 'rgba(44,48,50,0.6)',
218
+ })
219
+ })
220
+
221
+ it('falls back to text.secondary when black[60] is absent', () => {
222
+ const themeNoBlack = {
223
+ typography: {
224
+ overlineDelicate: { fontSize: '0.625rem', fontFamily: 'Inter' },
225
+ },
226
+ palette: { text: { secondary: 'rgba(0,0,0,0.6)' } },
227
+ } as unknown as Theme
228
+ expect(buildAxisLabelStyle(themeNoBlack).color).toBe('rgba(0,0,0,0.6)')
229
+ })
230
+ })
231
+
198
232
  describe('createTooltipPositioner', () => {
199
233
  it('returns a positioner that places tooltip on the left when there is room', () => {
200
234
  const positioner = createTooltipPositioner(fakeTheme)
@@ -66,6 +66,27 @@ export function buildGridConfig(hasLegend: boolean, theme: Theme) {
66
66
  }
67
67
  }
68
68
 
69
+ /**
70
+ * Builds the shared axis-label text style for chart widgets, matching the
71
+ * design's "Overline (Delicate)" token (Inter, 10px) in the secondary text
72
+ * colour (`rgba(44,48,50,0.6)`). Spread into an ECharts `axisLabel` for both
73
+ * x and y axes so every chart's labels stay consistent.
74
+ *
75
+ * Note: the token's `letterSpacing` and `textTransform: uppercase` are not
76
+ * representable in ECharts canvas text, so only font size/family/colour are
77
+ * applied.
78
+ *
79
+ * @param theme - MUI theme providing the typography token and palette
80
+ * @returns `{ fontSize, fontFamily, color }` for an ECharts `axisLabel`
81
+ */
82
+ export function buildAxisLabelStyle(theme: Theme) {
83
+ return {
84
+ fontSize: theme.typography.overlineDelicate?.fontSize,
85
+ fontFamily: theme.typography.overlineDelicate?.fontFamily,
86
+ color: theme.palette.black?.[60] ?? theme.palette.text.secondary,
87
+ }
88
+ }
89
+
69
90
  /**
70
91
  * Creates a tooltip position calculator that handles overflow
71
92
  * Used by bar, histogram, and scatterplot widgets
@@ -2,6 +2,7 @@ import type { EChartsOption } from 'echarts'
2
2
  import * as echarts from 'echarts'
3
3
  import type { CallbackDataParams } from 'echarts/types/dist/shared'
4
4
  import {
5
+ buildAxisLabelStyle,
5
6
  buildGridConfig,
6
7
  buildLegendConfig,
7
8
  createTooltipFormatter,
@@ -86,6 +87,7 @@ export function barOptions({
86
87
  axisLine: { show: false },
87
88
  axisTick: { show: false },
88
89
  axisLabel: {
90
+ ...buildAxisLabelStyle(theme),
89
91
  padding: [parseInt(theme.spacing(0.5)), 0, 0, 0],
90
92
  margin: 0,
91
93
  hideOverlap: true,
@@ -111,8 +113,7 @@ export function barOptions({
111
113
  lineStyle: { color: theme.palette.black?.[4] ?? theme.palette.divider },
112
114
  },
113
115
  axisLabel: {
114
- fontSize: theme.typography.overlineDelicate?.fontSize,
115
- fontFamily: theme.typography.overlineDelicate?.fontFamily,
116
+ ...buildAxisLabelStyle(theme),
116
117
  margin: parseInt(theme.spacing(1)),
117
118
  show: true,
118
119
  showMaxLabel: true,
@@ -28,10 +28,13 @@ export const CENTERED: EdgeAlignment = {
28
28
  alignMaxLabel: null,
29
29
  }
30
30
 
31
- // Bias toward anchoring when borderline: clipping is worse than a hair of
32
- // off-centering, and the reconstructed font may differ slightly from what
33
- // ECharts actually renders.
34
- const SAFETY_MARGIN_PX = 2
31
+ // Small cushion biasing toward anchoring when borderline: clipping is worse
32
+ // than a hair of off-centering, and the measured width can differ slightly
33
+ // from the final render (mainly when the web font isn't loaded yet at measure
34
+ // time). Kept to 1px — a wider margin anchored labels that only just clear the
35
+ // edge, and an anchored edge label can then be dropped by the axis
36
+ // `hideOverlap`, leaving a gap. 1px guards measurement error without that.
37
+ const SAFETY_MARGIN_PX = 1
35
38
 
36
39
  /**
37
40
  * Pure overflow decision. Inputs are anchor-independent (tick centers and text
@@ -2,6 +2,7 @@ import type { EChartsOption } from 'echarts'
2
2
  import * as echarts from 'echarts'
3
3
  import type { CallbackDataParams } from 'echarts/types/dist/shared'
4
4
  import {
5
+ buildAxisLabelStyle,
5
6
  buildGridConfig,
6
7
  buildLegendConfig,
7
8
  createTooltipFormatter,
@@ -92,6 +93,7 @@ export function histogramOptions({
92
93
  axisLine: { show: false },
93
94
  axisTick: { show: false },
94
95
  axisLabel: {
96
+ ...buildAxisLabelStyle(theme),
95
97
  padding: [parseInt(theme.spacing(0.5)), 0, 0, 0],
96
98
  margin: 0,
97
99
  hideOverlap: true,
@@ -114,8 +116,7 @@ export function histogramOptions({
114
116
  lineStyle: { color: theme.palette.black?.[4] ?? theme.palette.divider },
115
117
  },
116
118
  axisLabel: {
117
- fontSize: theme.typography.overlineDelicate?.fontSize,
118
- fontFamily: theme.typography.overlineDelicate?.fontFamily,
119
+ ...buildAxisLabelStyle(theme),
119
120
  margin: parseInt(theme.spacing(1)),
120
121
  show: true,
121
122
  showMaxLabel: true,
@@ -10,8 +10,11 @@ describe('<PieSkeleton>', () => {
10
10
  ).toBeGreaterThan(0)
11
11
  })
12
12
 
13
- it('scales with count', () => {
14
- const { container } = render(<PieSkeleton count={3} />)
15
- expect(container.firstChild).not.toBeNull()
13
+ it('accepts count but always renders a single donut', () => {
14
+ const single = render(<PieSkeleton count={1} />)
15
+ const many = render(<PieSkeleton count={3} />)
16
+ const skeletonCount = (c: HTMLElement) =>
17
+ c.querySelectorAll('.MuiSkeleton-root').length
18
+ expect(skeletonCount(many.container)).toBe(skeletonCount(single.container))
16
19
  })
17
20
  })
@@ -1,19 +1,55 @@
1
1
  import { Box, Skeleton } from '@mui/material'
2
2
  import type { SxProps, Theme } from '@mui/material'
3
3
 
4
+ const OUTER_SIZE = 160
5
+ const HOLE_SIZE = 96
6
+
4
7
  const styles = {
5
8
  root: {
6
9
  display: 'flex',
10
+ flexDirection: 'column',
7
11
  alignItems: 'center',
8
12
  justifyContent: 'center',
9
13
  minHeight: 200,
10
14
  py: 1,
11
- gap: 2,
15
+ gap: ({ spacing }) => spacing(2),
12
16
  },
13
17
  donut: {
14
- width: 160,
15
- height: 160,
18
+ position: 'relative',
19
+ display: 'flex',
20
+ alignItems: 'center',
21
+ justifyContent: 'center',
22
+ },
23
+ ring: {
24
+ width: OUTER_SIZE,
25
+ height: OUTER_SIZE,
26
+ },
27
+ hole: {
28
+ position: 'absolute',
29
+ zIndex: 1,
30
+ width: HOLE_SIZE,
31
+ height: HOLE_SIZE,
16
32
  borderRadius: '50%',
33
+ bgcolor: 'background.paper',
34
+ },
35
+ label: {
36
+ position: 'absolute',
37
+ zIndex: 2,
38
+ display: 'flex',
39
+ flexDirection: 'column',
40
+ alignItems: 'center',
41
+ justifyContent: 'center',
42
+ gap: ({ spacing }) => spacing(0.5),
43
+ },
44
+ legend: {
45
+ display: 'flex',
46
+ alignItems: 'center',
47
+ gap: ({ spacing }) => spacing(2),
48
+ },
49
+ legendItem: {
50
+ display: 'flex',
51
+ alignItems: 'center',
52
+ gap: ({ spacing }) => spacing(1.5),
17
53
  },
18
54
  } satisfies Record<string, SxProps<Theme>>
19
55
 
@@ -21,12 +57,38 @@ export interface PieSkeletonProps {
21
57
  count?: number
22
58
  }
23
59
 
24
- export function PieSkeleton({ count = 1 }: PieSkeletonProps) {
60
+ /**
61
+ * Loading state for the Pie widget. Mirrors the donut silhouette — a ring
62
+ * (gray circle with a background-coloured hole punched out), a stacked
63
+ * value/name stub in the centre, and a centered legend stub below — so the
64
+ * skeleton reads as "a donut chart" rather than a solid disc.
65
+ *
66
+ * Single donut only: multi-series pie loads as a horizontal bar chart, so
67
+ * rendering one ring stays honest regardless of `count`.
68
+ */
69
+ export function PieSkeleton({ count }: PieSkeletonProps) {
70
+ // `count` is accepted for API compatibility but intentionally unused: the
71
+ // skeleton always renders a single donut, since multi-series pie loads as a
72
+ // horizontal bar chart rather than multiple donuts.
73
+ void count
25
74
  return (
26
75
  <Box sx={styles.root}>
27
- {Array.from({ length: count }).map((_, i) => (
28
- <Skeleton key={`donut-${i}`} variant='circular' sx={styles.donut} />
29
- ))}
76
+ <Box sx={styles.donut}>
77
+ <Skeleton variant='circular' sx={styles.ring} />
78
+ <Box sx={styles.hole} />
79
+ <Box sx={styles.label}>
80
+ <Skeleton width={56} height={18} />
81
+ <Skeleton width={36} height={8} />
82
+ </Box>
83
+ </Box>
84
+ <Box sx={styles.legend}>
85
+ {[0, 1].map((i) => (
86
+ <Box key={`legend-${i}`} sx={styles.legendItem}>
87
+ <Skeleton variant='circular' width={8} height={8} />
88
+ <Skeleton width={48} height={8} />
89
+ </Box>
90
+ ))}
91
+ </Box>
30
92
  </Box>
31
93
  )
32
94
  }
@@ -2,6 +2,7 @@ import type { EChartsOption } from 'echarts'
2
2
  import * as echarts from 'echarts'
3
3
  import type { CallbackDataParams } from 'echarts/types/dist/shared'
4
4
  import {
5
+ buildAxisLabelStyle,
5
6
  buildGridConfig,
6
7
  buildLegendConfig,
7
8
  createTooltipFormatter,
@@ -80,9 +81,7 @@ export function scatterplotOptions({
80
81
  axisLine: { show: false },
81
82
  axisTick: { show: false },
82
83
  axisLabel: {
83
- fontSize: theme.typography.overlineDelicate?.fontSize,
84
- fontFamily: theme.typography.overlineDelicate?.fontFamily,
85
- color: theme.palette.black?.[60],
84
+ ...buildAxisLabelStyle(theme),
86
85
  margin: parseInt(theme.spacing(1)),
87
86
  hideOverlap: true,
88
87
  showMinLabel: true,
@@ -99,9 +98,7 @@ export function scatterplotOptions({
99
98
  axisLine: { show: false },
100
99
  axisTick: { show: false },
101
100
  axisLabel: {
102
- fontSize: theme.typography.overlineDelicate?.fontSize,
103
- fontFamily: theme.typography.overlineDelicate?.fontFamily,
104
- color: theme.palette.black?.[60],
101
+ ...buildAxisLabelStyle(theme),
105
102
  margin: parseInt(theme.spacing(1)),
106
103
  hideOverlap: true,
107
104
  showMinLabel: true,
@@ -2,6 +2,7 @@ import type { EChartsOption } from 'echarts'
2
2
  import * as echarts from 'echarts'
3
3
  import type { CallbackDataParams } from 'echarts/types/dist/shared'
4
4
  import {
5
+ buildAxisLabelStyle,
5
6
  buildGridConfig,
6
7
  buildLegendConfig,
7
8
  createTooltipFormatter,
@@ -89,6 +90,7 @@ export function timeseriesOptions({
89
90
  axisLine: { show: false },
90
91
  axisTick: { show: false },
91
92
  axisLabel: {
93
+ ...buildAxisLabelStyle(theme),
92
94
  padding: [parseInt(theme.spacing(0.5)), 0, 0, 0],
93
95
  margin: 0,
94
96
  hideOverlap: true,
@@ -114,8 +116,7 @@ export function timeseriesOptions({
114
116
  lineStyle: { color: theme.palette.black?.[4] ?? theme.palette.divider },
115
117
  },
116
118
  axisLabel: {
117
- fontSize: theme.typography.overlineDelicate?.fontSize,
118
- fontFamily: theme.typography.overlineDelicate?.fontFamily,
119
+ ...buildAxisLabelStyle(theme),
119
120
  margin: parseInt(theme.spacing(1)),
120
121
  show: true,
121
122
  showMaxLabel: true,
@@ -1 +0,0 @@
1
- {"version":3,"file":"echart-BMPpj7n_.js","sources":["../src/widgets-v2/echart/edge-label-clamp.ts","../src/widgets-v2/echart/shared-resize-observer.ts","../src/widgets-v2/echart/style.ts","../src/widgets-v2/echart/echart-ui.tsx","../src/widgets-v2/echart/echart.tsx"],"sourcesContent":["import * as echarts from 'echarts'\nimport type { ECharts, EChartsOption } from 'echarts'\n\n/**\n * Render-time clamp that stops the first/last **category x-axis** labels from\n * clipping at the chart edges — *only when they actually overflow*.\n *\n * ECharts centers each category label on its tick; the first/last ticks sit at\n * the plot boundary, so a wide edge label spills past the chart and is cut off.\n * `grid.containLabel` does not contain this horizontal overflow and the v6\n * `outerBounds` shrink is unreliable, so we measure at render time and anchor\n * the overflowing edge label inward (`alignMinLabel: 'left'` /\n * `alignMaxLabel: 'right'`). Labels that fit stay centered.\n *\n * Lives in the generic bridge because the verdict needs the laid-out chart\n * (`convertToPixel`, `getWidth`); everything else is read from the `option`.\n * Auto-targets bar + histogram (category x-axis); pie's horizontal-bar fallback\n * (category *y*-axis), scatterplot (value) and timeseries (time) are skipped.\n */\n\nexport interface EdgeAlignment {\n alignMinLabel: 'left' | null\n alignMaxLabel: 'right' | null\n}\n\nexport const CENTERED: EdgeAlignment = {\n alignMinLabel: null,\n alignMaxLabel: null,\n}\n\n// Bias toward anchoring when borderline: clipping is worse than a hair of\n// off-centering, and the reconstructed font may differ slightly from what\n// ECharts actually renders.\nconst SAFETY_MARGIN_PX = 2\n\n/**\n * Pure overflow decision. Inputs are anchor-independent (tick centers and text\n * width don't depend on the label's current `text-anchor`), so feeding the\n * result back via `setOption` doesn't change them — the next pass yields the\n * same verdict, keeping the clamp loop-stable.\n */\nexport function decideEdgeAlignment(args: {\n firstLabel: string\n lastLabel: string\n font: string\n firstTickX: number\n lastTickX: number\n width: number\n}): EdgeAlignment {\n const halfFirst =\n echarts.format.getTextRect(args.firstLabel, args.font).width / 2\n const halfLast =\n echarts.format.getTextRect(args.lastLabel, args.font).width / 2\n return {\n alignMinLabel:\n halfFirst + SAFETY_MARGIN_PX > args.firstTickX ? 'left' : null,\n alignMaxLabel:\n halfLast + SAFETY_MARGIN_PX > args.width - args.lastTickX\n ? 'right'\n : null,\n }\n}\n\ninterface AxisLabel {\n formatter?: (value: string | number) => string | number\n fontSize?: number | string\n fontFamily?: string\n fontWeight?: number | string\n}\n\ninterface CategoryXAxis {\n type?: string\n axisLabel?: AxisLabel\n}\n\ninterface SeriesLike {\n encode?: { x?: string | number }\n datasetIndex?: number\n}\n\ntype Cell = string | number | null | undefined\n\nfunction firstOf<T>(value: T | T[] | undefined): T | undefined {\n if (Array.isArray(value)) return value[0]\n return value\n}\n\nfunction firstXAxis(option: EChartsOption): CategoryXAxis | undefined {\n const axis = firstOf(\n option.xAxis as CategoryXAxis | CategoryXAxis[] | undefined,\n )\n return axis && typeof axis === 'object' ? axis : undefined\n}\n\nfunction hasDataZoom(option: EChartsOption): boolean {\n const dz = option.dataZoom\n return Array.isArray(dz) ? dz.length > 0 : dz != null\n}\n\nfunction cellToText(value: Cell, fmt: AxisLabel['formatter']): string {\n if (typeof fmt === 'function') return String(fmt(value ?? ''))\n return value == null ? '' : String(value)\n}\n\n/**\n * Extracts the displayed first/last category strings and the label font from\n * the option, reading categories generically via `series[0].encode.x` into the\n * referenced dataset (`'name'` for bar, `0` for histogram). Returns `null` when\n * the option isn't a measurable category x-axis (fewer than 2 categories, no\n * dataset, etc.).\n */\nexport function resolveEdgeLabels(option: EChartsOption): {\n firstLabel: string\n lastLabel: string\n font: string\n count: number\n} | null {\n const xAxis = firstXAxis(option)\n if (!xAxis) return null\n if (xAxis.type !== 'category') return null\n\n const series0 = firstOf(\n option.series as SeriesLike | SeriesLike[] | undefined,\n )\n const encodeX = series0?.encode?.x\n if (encodeX == null) return null\n\n const datasetIndex = series0?.datasetIndex ?? 0\n const allDatasets = option.dataset as\n | { source?: unknown }\n | { source?: unknown }[]\n | undefined\n const dataset = Array.isArray(allDatasets)\n ? allDatasets[datasetIndex]\n : allDatasets\n const source = dataset?.source\n if (!Array.isArray(source) || source.length < 2) return null\n\n const rows = source as Record<string | number, Cell>[]\n const fmt = xAxis.axisLabel?.formatter\n const axisLabel = xAxis.axisLabel ?? {}\n const size =\n typeof axisLabel.fontSize === 'number'\n ? `${axisLabel.fontSize}px`\n : (axisLabel.fontSize ?? '12px')\n const family = axisLabel.fontFamily ?? 'sans-serif'\n const weight = axisLabel.fontWeight != null ? `${axisLabel.fontWeight} ` : ''\n\n return {\n firstLabel: cellToText(rows[0]?.[encodeX], fmt),\n lastLabel: cellToText(rows[rows.length - 1]?.[encodeX], fmt),\n font: `${weight}${size} ${family}`,\n count: source.length,\n }\n}\n\n/**\n * Measures the rendered chart and, when the edge labels would clip, applies\n * `alignMinLabel`/`alignMaxLabel` (or clears them) via an imperative merge\n * `setOption`. Returns the alignment now in effect so the caller can keep a\n * `prev` ref and skip redundant `setOption`s. Should be invoked from the\n * chart's `finished` event (layout is settled, so `convertToPixel` is valid).\n */\nexport function clampEdgeLabels(\n chart: ECharts,\n option: EChartsOption,\n prev: EdgeAlignment,\n): EdgeAlignment {\n const next = computeAlignment(chart, option)\n if (\n next.alignMinLabel === prev.alignMinLabel &&\n next.alignMaxLabel === prev.alignMaxLabel\n ) {\n return prev\n }\n chart.setOption({ xAxis: { axisLabel: { ...next } } } as EChartsOption, {\n lazyUpdate: true,\n })\n return next\n}\n\nfunction computeAlignment(\n chart: ECharts,\n option: EChartsOption,\n): EdgeAlignment {\n // Under dataZoom, convertToPixel on the absolute first/last index returns\n // off-plot pixels, so the overflow math is invalid — reset to centered.\n if (hasDataZoom(option)) return CENTERED\n\n const labels = resolveEdgeLabels(option)\n if (!labels) return CENTERED\n\n const firstTickX = chart.convertToPixel({ xAxisIndex: 0 }, 0)\n const lastTickX = chart.convertToPixel({ xAxisIndex: 0 }, labels.count - 1)\n const width = chart.getWidth()\n // Layout not measurable yet (defensive — `finished` normally fires after\n // layout). Fall back to centered; a later `finished` re-measures.\n if (\n typeof firstTickX !== 'number' ||\n typeof lastTickX !== 'number' ||\n !Number.isFinite(firstTickX) ||\n !Number.isFinite(lastTickX) ||\n !width\n ) {\n return CENTERED\n }\n\n return decideEdgeAlignment({\n firstLabel: labels.firstLabel,\n lastLabel: labels.lastLabel,\n font: labels.font,\n firstTickX,\n lastTickX,\n width,\n })\n}\n","type ResizeCallback = () => void\n\nconst callbacks = new Map<Element, ResizeCallback>()\nlet observer: ResizeObserver | null = null\n\n/**\n * Returns the singleton ResizeObserver, lazily constructing it on first use.\n * Returns `null` when `ResizeObserver` is not available in the global scope\n * (SSR, very old browsers, or tests that intentionally remove it).\n */\nfunction getObserver(): ResizeObserver | null {\n if (typeof ResizeObserver === 'undefined') return null\n observer ??= new ResizeObserver((entries) => {\n for (const entry of entries) {\n const callback = callbacks.get(entry.target)\n callback?.()\n }\n })\n return observer\n}\n\nconst NOOP_CLEANUP = (): void => undefined\n\nexport function observeResize(\n element: Element,\n callback: ResizeCallback,\n): () => void {\n const ro = getObserver()\n // Gracefully degrade when no ResizeObserver is available — consumers\n // still see one initial measure() pass via their own effect; we just\n // skip the subsequent resize-driven re-measures.\n if (!ro) return NOOP_CLEANUP\n callbacks.set(element, callback)\n ro.observe(element)\n return () => {\n callbacks.delete(element)\n if (observer) {\n observer.unobserve(element)\n // Disconnect the singleton once the last subscriber leaves so the\n // observer doesn't outlive its consumers in SSR teardown / micro-\n // frontend unmount scenarios. A subsequent observeResize() will\n // lazily re-create it via getObserver().\n if (callbacks.size === 0) {\n observer.disconnect()\n observer = null\n }\n }\n }\n}\n\n/** @internal — for tests only. */\nexport function __resetSharedResizeObserver(): void {\n if (observer) observer.disconnect()\n callbacks.clear()\n observer = null\n}\n","import type { SxProps, Theme } from '@mui/material'\n\nexport const styles = {\n root: {\n width: '100%',\n minHeight: 0,\n },\n} satisfies Record<string, SxProps<Theme>>\n","import { useEffect, useEffectEvent, useRef } from 'react'\nimport { Box, type SxProps, type Theme } from '@mui/material'\nimport * as echarts from 'echarts'\nimport {\n clampEdgeLabels,\n CENTERED,\n type EdgeAlignment,\n} from './edge-label-clamp'\nimport { observeResize } from './shared-resize-observer'\nimport { styles } from './style'\n\nexport const DEFAULT_INIT_OPTS = {\n renderer: 'svg',\n height: 304,\n} satisfies echarts.EChartsInitOpts\n\nexport type EchartsEventHandler = (event: unknown) => void\n\nexport interface EchartUIProps {\n option: echarts.EChartsOption\n /**\n * Keys to merge as arrays (replace by index) instead of by id. The middleware\n * memoizes this content-stably, so identical sets don't re-trigger setOption.\n */\n replaceMerge?: readonly string[]\n /**\n * Opaque ECharts event passthrough — handlers fire untransformed.\n *\n * **Must be referentially stable** (memoize it with `useMemo` /\n * `useCallback`, or hoist to module scope). The binding effect depends on\n * the `onEvents` object identity; an inline `{ click: handler }` literal\n * recreates the object on every render and causes every listener to be\n * detached and re-attached each commit.\n */\n onEvents?: Record<string, EchartsEventHandler>\n /**\n * Init options forwarded to echarts.init. Captured at mount only — to change\n * `renderer` or `height` after mount, unmount and remount the chart.\n * Defaults: `{ renderer: 'svg', height: 304 }`.\n */\n init?: echarts.EChartsInitOpts\n /**\n * Optional callback fired once after the ECharts instance is created\n * (`onInstance(chart)`) and once when it's about to be disposed\n * (`onInstance(null)`). Used by `Widget.Echart` to publish the live\n * instance to the per-widget registry so other actions (e.g.\n * `ZoomToggle`'s disable handler) can reach it without DOM lookups.\n * No need to memoize — the bridge wraps it in `useEffectEvent` so an\n * unstable reference does not re-init the chart.\n */\n onInstance?: (chart: echarts.ECharts | null) => void\n className?: string\n sx?: SxProps<Theme>\n}\n\nexport function EchartUI({\n option,\n replaceMerge,\n onEvents,\n init,\n onInstance,\n className,\n sx,\n}: EchartUIProps) {\n const containerRef = useRef<HTMLDivElement>(null)\n const chartRef = useRef<echarts.ECharts | null>(null)\n\n // Structural fingerprint of the last applied series / dataset arrays.\n // Used to decide whether the next `setOption` should *replace* those\n // components (when their shape changed) or *merge* them (when only\n // styling-level fields differ — e.g. a new `itemStyle.color` callback\n // from a selection update). Replacing wipes ECharts' per-series runtime\n // state (hover, animation cache, brush areas), so we want to do it only\n // when the structure actually moved.\n const seriesFingerprintRef = useRef<string | null>(null)\n const datasetFingerprintRef = useRef<string | null>(null)\n\n // Latest applied option (read by the `finished` clamp listener, which is\n // bound once and must see the current option) + the edge-label alignment\n // currently applied to the chart (so the clamp skips redundant setOptions).\n const optionRef = useRef<echarts.EChartsOption>(option)\n const edgeAlignRef = useRef<EdgeAlignment>(CENTERED)\n\n // Stable notify wrapper — always reads the latest `onInstance` prop\n // without forcing the init effect below to re-run when the parent\n // passes a fresh function reference. The init effect now only fires\n // when `init` actually changes (documented as mount-only anyway).\n const notifyInstance = useEffectEvent(\n (chart: echarts.ECharts | null): void => {\n onInstance?.(chart)\n },\n )\n\n // Init / dispose. `init` is captured once at mount.\n useEffect(() => {\n if (!containerRef.current) return undefined\n const chart = echarts.init(containerRef.current, null, {\n ...DEFAULT_INIT_OPTS,\n ...init,\n })\n chartRef.current = chart\n notifyInstance(chart)\n // Edge-label clamp: after each layout settles (option change OR resize both\n // end in a render → `finished`), measure the category x-axis edge labels\n // and anchor them inward only if they'd clip. `finished` (not rAF) so\n // `convertToPixel` sees the flushed `lazyUpdate` layout. Loop-safe: the\n // verdict is anchor-independent, so our own clamp setOption recomputes the\n // same result and the ref guard short-circuits.\n const onFinished = (): void => {\n edgeAlignRef.current = clampEdgeLabels(\n chart,\n optionRef.current,\n edgeAlignRef.current,\n )\n }\n chart.on('finished', onFinished)\n return () => {\n chart.off('finished', onFinished)\n // Notify observers *before* disposal so any queued imperative\n // dispatches (e.g. ZoomToggle's `setOption` cleanup) see the\n // instance disappear before its DOM is torn down.\n notifyInstance(null)\n chart.dispose()\n chartRef.current = null\n }\n }, [init])\n\n // Apply option / replaceMerge changes via merge mode (no remount required).\n useEffect(() => {\n const seriesFp = computeSeriesFingerprint(option)\n const datasetFp = computeDatasetFingerprint(option)\n const augmented = new Set<string>(replaceMerge ?? [])\n if (seriesFp !== seriesFingerprintRef.current) augmented.add('series')\n if (datasetFp !== datasetFingerprintRef.current) augmented.add('dataset')\n seriesFingerprintRef.current = seriesFp\n datasetFingerprintRef.current = datasetFp\n // Expose the latest option to the `finished` clamp listener (bound once).\n optionRef.current = option\n chartRef.current?.setOption(option, {\n notMerge: false,\n lazyUpdate: true,\n replaceMerge: augmented.size > 0 ? Array.from(augmented) : undefined,\n })\n }, [option, replaceMerge])\n\n // Resize via shared singleton observer.\n useEffect(() => {\n const node = containerRef.current\n if (!node) return undefined\n return observeResize(node, () => {\n chartRef.current?.resize()\n })\n }, [])\n\n // Bind / unbind opaque event handlers.\n useEffect(() => {\n const chart = chartRef.current\n if (!chart || !onEvents) return undefined\n for (const [event, handler] of Object.entries(onEvents)) {\n chart.on(event, handler)\n }\n return () => {\n for (const [event, handler] of Object.entries(onEvents)) {\n chart.off(event, handler)\n }\n }\n }, [onEvents])\n\n return (\n <Box\n ref={containerRef}\n className={className}\n sx={{ ...styles.root, ...sx }}\n />\n )\n}\n\n/**\n * Cheap structural digest of the option's `series` array — covers the fields\n * that actually demand a `replaceMerge` (length, type, datasetIndex, name,\n * stack, encode). Excludes runtime-style fields like `itemStyle` (callbacks\n * have unstable identity but don't change structure).\n */\nfunction computeSeriesFingerprint(option: echarts.EChartsOption): string {\n const series = option.series\n if (!Array.isArray(series)) return series ? '1' : ''\n return series\n .map((s) => {\n if (s == null || typeof s !== 'object') return String(s)\n const o = s as Record<string, unknown>\n return [\n o.type,\n o.datasetIndex,\n o.name,\n o.stack,\n // `encode` is small; stringify is cheap and stable for plain objects.\n JSON.stringify(o.encode ?? null),\n ].join('|')\n })\n .join('||')\n}\n\n/**\n * Structural digest of the option's `dataset` array — count is the only\n * thing that needs `replaceMerge` here. Row-level changes are picked up by\n * ECharts' default merge.\n */\nfunction computeDatasetFingerprint(option: echarts.EChartsOption): string {\n const dataset = option.dataset\n if (!Array.isArray(dataset)) return dataset ? '1' : ''\n return String(dataset.length)\n}\n","import { useCallback, useMemo } from 'react'\nimport type * as echarts from 'echarts'\nimport {\n applyTransforms,\n setEchartInstance,\n useWidgetId,\n useWidgetShallow,\n type Transform,\n} from '../stores'\nimport { EchartUI, type EchartsEventHandler } from './echart-ui'\n\n/**\n * Default ECharts `replaceMerge` keys for every widget. `dataZoom` and\n * `brush` are omitted so ZoomToggle / BrushToggle's user-driven runtime\n * state (slider range, brushed areas in `multiple` mode) survives unrelated\n * re-renders. `series` and `dataset` are also omitted: EchartUI fingerprints\n * them per-render and adds them to `replaceMerge` only when their shape\n * actually changes, keeping ECharts' per-series runtime state alive when\n * only callback-style fields differ.\n *\n * Lives in this module (not the generic store) because it's an ECharts\n * concept. Module-private — callers should not need it.\n */\nconst DEFAULT_REPLACE_MERGE: readonly string[] = ['toolbox']\n\n/**\n * Reactive context passed to every {@link OptionFactory} call so the\n * factory can rebuild render-time pieces (axis label / tooltip formatters)\n * from the live store. Drives RelativeData / consumer-formatter changes\n * through to the chart without rebuilding the structural option.\n */\nexport interface OptionFactoryContext {\n formatter?: (value: number) => string\n labelFormatter?: (value: string | number) => string | number\n}\n\n/**\n * The per-widget option factory — a single callable that owns BOTH phases\n * of option construction:\n *\n * - **Structural phase** — when `option == null`, return the theme-aware\n * structural option (tooltip / legend / color palette / series\n * template, optionally merged with a consumer-supplied `optionsOverride`).\n * No data is read. `<Widget.Echart>` calls the factory with\n * `(undefined, undefined)` synchronously during render to derive the\n * structural base; `configTransforms` (Stack/Zoom/Brush) then mutate it\n * in the same render pass.\n * - **Merge phase** — when `option` is defined, fuse `data` into the\n * post-configTransforms option at fusion time. `<Widget.Echart>` calls\n * the factory with `(transformed, data, ctx)` on every render.\n *\n * The two phases share a closure (the factory creator captures `theme`,\n * `formatter`, `labelFormatter`, `seriesNames`, `selection`, `optionsOverride`,\n * …), so structural and merge agree on the same widget configuration.\n *\n * The third arg `ctx` carries the **live** store-side formatters at the\n * call site — distinct from the closure-time formatters because actions\n * like RelativeData can install a percent formatter on the store after\n * the factory was constructed. The merge phase reads from `ctx`; the\n * structural phase typically uses the closure-time values.\n */\nexport type OptionFactory = (\n option: echarts.EChartsOption | undefined,\n data: unknown,\n ctx?: OptionFactoryContext,\n) => echarts.EChartsOption\n\nexport interface EchartProps {\n /**\n * The per-widget {@link OptionFactory}. Required — `<Widget.Echart>`\n * derives the structural option from it (so configTransforms have a base\n * to mutate) and fuses `state.data` into the post-pipeline option at\n * render time. Wrap the factory creator in `useMemo` so its identity is\n * stable across renders.\n */\n optionFactory: OptionFactory\n onEvents?: Record<string, EchartsEventHandler>\n init?: echarts.EChartsInitOpts\n className?: string\n}\n\ninterface EchartSlice {\n data: unknown\n configTransforms: readonly Transform[]\n formatter?: (value: number) => string\n labelFormatter?: (value: string | number) => string | number\n}\n\nconst echartSelector = (s: {\n data: unknown\n configTransforms: readonly Transform[]\n formatter?: (value: number) => string\n labelFormatter?: (value: string | number) => string | number\n}): EchartSlice => ({\n data: s.data,\n configTransforms: s.configTransforms,\n formatter: s.formatter,\n labelFormatter: s.labelFormatter,\n})\n\n/**\n * Stateful Echart bridge — owns the entire ECharts coupling. The whole\n * option pipeline lives here, not in the store:\n *\n * 1. **Structural** — `optionFactory(undefined, undefined)` produces the\n * theme-aware base. Memoized on the factory identity, so the consumer's\n * `useMemo` ID gates the rebuild.\n * 2. **Transformed** — `applyTransforms(structural, configTransforms)`\n * applies any registered configTransforms (Stack/Zoom/Brush) over the\n * structural base. Memoized on `[structural, configTransforms]`.\n * 3. **`replaceMerge`** — derived from the enabled configTransforms'\n * `replaceMergeKeys`, deduped and sorted, seeded with\n * {@link DEFAULT_REPLACE_MERGE}. Memoized on `[configTransforms]` so\n * ECharts sees a stable array reference across non-transform changes.\n * 4. **Merge** — `optionFactory(transformed, data, ctx)` fuses post-\n * pipeline data into the option. Reactive `ctx` carries the live store\n * formatters so RelativeData's percent formatter flows through without\n * a structural rebuild.\n *\n * The `<Widget.Provider>` doesn't know about the factory at all: it stays\n * a renderer-agnostic shell. The `ProviderProps` surface has no ECharts\n * coupling, so non-Echart widgets don't transitively import the type.\n */\nexport function Echart({\n optionFactory,\n onEvents,\n init,\n className,\n}: EchartProps) {\n const id = useWidgetId()\n const slice = useWidgetShallow(id, echartSelector)\n\n // Publish the live ECharts instance to the per-id registry so actions\n // (e.g. `ZoomToggle`'s disable handler) can reach it imperatively. The\n // callback identity is stable across renders (only `id` is in deps), so\n // EchartUI's init effect doesn't see a fresh `onInstance` and re-init.\n const onInstance = useCallback(\n (chart: echarts.ECharts | null) => setEchartInstance(id, chart),\n [id],\n )\n\n // Destructure so React Compiler sees specific deps instead of inferring\n // the whole `slice` object. `useWidgetShallow` already shallow-compares\n // the slice, so per-field deps drive each memo exactly when its inputs\n // change.\n const {\n data: sliceData,\n configTransforms,\n formatter: sliceFormatter,\n labelFormatter: sliceLabelFormatter,\n } = slice\n\n const structural = useMemo(\n () => optionFactory(undefined, undefined),\n [optionFactory],\n )\n\n const transformed = useMemo(\n () =>\n applyTransforms(structural, configTransforms) as echarts.EChartsOption,\n [structural, configTransforms],\n )\n\n const replaceMerge = useMemo(() => {\n const keys = new Set<string>(DEFAULT_REPLACE_MERGE)\n for (const xf of configTransforms) {\n if (xf.enabled && xf.replaceMergeKeys?.length) {\n for (const k of xf.replaceMergeKeys) keys.add(k)\n }\n }\n return Array.from(keys).sort()\n }, [configTransforms])\n\n const option = useMemo(\n () =>\n optionFactory(transformed, sliceData, {\n formatter: sliceFormatter,\n labelFormatter: sliceLabelFormatter,\n }),\n [\n optionFactory,\n transformed,\n sliceData,\n sliceFormatter,\n sliceLabelFormatter,\n ],\n )\n\n return (\n <EchartUI\n option={option}\n replaceMerge={replaceMerge}\n onEvents={onEvents}\n init={init}\n onInstance={onInstance}\n className={className}\n />\n )\n}\n"],"names":["CENTERED","alignMinLabel","alignMaxLabel","SAFETY_MARGIN_PX","decideEdgeAlignment","args","halfFirst","echarts","format","getTextRect","firstLabel","font","width","halfLast","lastLabel","firstTickX","lastTickX","firstOf","value","Array","isArray","firstXAxis","option","axis","xAxis","undefined","hasDataZoom","dz","dataZoom","length","cellToText","fmt","String","resolveEdgeLabels","type","series0","series","encodeX","encode","x","datasetIndex","allDatasets","dataset","source","rows","axisLabel","formatter","size","fontSize","family","fontFamily","weight","fontWeight","count","clampEdgeLabels","chart","prev","next","computeAlignment","setOption","lazyUpdate","labels","convertToPixel","xAxisIndex","getWidth","Number","isFinite","callbacks","Map","observer","getObserver","ResizeObserver","entries","entry","callback","get","target","NOOP_CLEANUP","observeResize","element","ro","set","observe","delete","unobserve","disconnect","__resetSharedResizeObserver","clear","styles","root","minHeight","DEFAULT_INIT_OPTS","renderer","height","EchartUI","t0","$","_c","replaceMerge","onEvents","init","onInstance","className","sx","containerRef","useRef","chartRef","seriesFingerprintRef","datasetFingerprintRef","optionRef","edgeAlignRef","t1","notifyInstance","useEffectEvent","t2","current","chart_0","onFinished","on","off","dispose","t3","useEffect","t4","t5","seriesFp","computeSeriesFingerprint","datasetFp","computeDatasetFingerprint","augmented","Set","add","notMerge","from","t6","t7","Symbol","for","node","resize","t8","t9","chart_1","event","handler","Object","event_0","handler_0","t10","t11","Box","map","s","o","name","stack","JSON","stringify","join","DEFAULT_REPLACE_MERGE","echartSelector","data","configTransforms","labelFormatter","Echart","optionFactory","id","useWidgetId","slice","useWidgetShallow","setEchartInstance","sliceData","sliceFormatter","sliceLabelFormatter","structural","applyTransforms","transformed","keys","xf","enabled","replaceMergeKeys","k","sort"],"mappings":"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"}