@@ -4,6 +4,7 @@ import { memo, useEffect, useMemo, useRef, useState } from 'react'
44import { getErrorMessage } from '@sim/utils/errors'
55import type { EChartsOption } from 'echarts'
66import { useTheme } from 'next-themes'
7+ import { buildChartRenderOption } from '@/lib/charts/option'
78import { getColumnId } from '@/lib/table/column-keys'
89import { useTable , useTableRowsSample } from '@/hooks/queries/tables'
910import { PreviewLoadingFrame } from './preview-shared'
@@ -172,80 +173,8 @@ export function shapeTableRows(
172173 return out
173174}
174175
175- /**
176- * Merges the resolved rows into the ECharts option as `dataset.source`. A spec
177- * whose option already carries a dataset keeps it (fully self-contained static
178- * charts); the file-level `title` fills in only when the option has none.
179- */
180176function buildOption ( spec : ChartSpec , rows : Array < Record < string , unknown > > | null ) : EChartsOption {
181- const option = structuredClone ( spec . option )
182- if ( rows && rows . length > 0 ) {
183- // The resolved rows become the FIRST dataset (id "table", datasetIndex 0).
184- // Spec-declared datasets follow it, so filter/sort transform datasets can
185- // derive from the injected rows (transforms default to fromDatasetIndex 0,
186- // or name it explicitly with fromDatasetId: "table").
187- const injected = { id : 'table' , source : rows }
188- if ( option . dataset === undefined ) {
189- option . dataset = injected
190- } else if ( Array . isArray ( option . dataset ) ) {
191- option . dataset = [ injected , ...option . dataset ]
192- } else {
193- option . dataset = [ injected , option . dataset ]
194- }
195- }
196- if ( option . backgroundColor === undefined ) {
197- option . backgroundColor = 'transparent'
198- }
199- if ( spec . title && option . title === undefined ) {
200- option . title = { text : spec . title }
201- }
202- // Chrome layout is Sim-owned, content is spec-owned. Models reliably
203- // produce colliding title/legend placements, so the renderer pins the
204- // title top-left and the legend top-right on one chrome row (scrollable
205- // when long), overriding any spec positions — the same split the pptx
206- // renderer makes between slide chrome and slide content.
207- const hasTitle = option . title !== null && typeof option . title === 'object'
208- if ( hasTitle ) {
209- const titles = Array . isArray ( option . title ) ? option . title : [ option . title ]
210- const primary = titles [ 0 ]
211- if ( primary !== null && typeof primary === 'object' ) {
212- const t = primary as Record < string , unknown >
213- t . left = 0
214- t . top = 0
215- t . right = undefined
216- t . bottom = undefined
217- }
218- option . title = titles [ 0 ]
219- }
220- let hasLegend = false
221- if ( option . legend !== null && typeof option . legend === 'object' ) {
222- const legends = Array . isArray ( option . legend ) ? option . legend : [ option . legend ]
223- for ( const entry of legends ) {
224- if ( entry === null || typeof entry !== 'object' ) continue
225- hasLegend = true
226- const l = entry as Record < string , unknown >
227- l . top = 2
228- l . right = 0
229- l . left = undefined
230- l . bottom = undefined
231- if ( l . type === undefined ) l . type = 'scroll'
232- }
233- }
234- // Reserve a chrome row above the plot. Fill only what the spec left unset
235- // inside grid — axis-name insets remain the spec's call.
236- const chromeTop = hasTitle || hasLegend ? 48 : 16
237- if ( option . grid === undefined ) {
238- option . grid = { top : chromeTop , left : 12 , right : 12 , bottom : 12 , containLabel : true }
239- } else if (
240- option . grid !== null &&
241- typeof option . grid === 'object' &&
242- ! Array . isArray ( option . grid )
243- ) {
244- const g = option . grid as Record < string , unknown >
245- if ( g . top === undefined ) g . top = chromeTop
246- if ( g . containLabel === undefined ) g . containLabel = true
247- }
248- return option as EChartsOption
177+ return buildChartRenderOption ( { title : spec . title , option : spec . option , rows } ) as EChartsOption
249178}
250179
251180function ChartErrorPanel ( { message, content } : { message : string ; content : string } ) {
0 commit comments