(node: ChartNodeData, ctx: RenderContext)
| 3303 | * Render a chart node into an HTML element with an ECharts instance. |
| 3304 | */ |
| 3305 | export function renderChart(node: ChartNodeData, ctx: RenderContext): HTMLElement { |
| 3306 | const wrapper = document.createElement('div') |
| 3307 | wrapper.style.position = 'absolute' |
| 3308 | wrapper.style.left = `${node.position.x}px` |
| 3309 | wrapper.style.top = `${node.position.y}px` |
| 3310 | wrapper.style.width = `${node.size.w}px` |
| 3311 | wrapper.style.height = `${node.size.h}px` |
| 3312 | wrapper.style.overflow = 'hidden' |
| 3313 | wrapper.style.display = 'flex' |
| 3314 | wrapper.style.flexDirection = 'column' |
| 3315 | |
| 3316 | const chartXml = ctx.presentation.charts?.get(node.chartPath) |
| 3317 | if (!chartXml) { |
| 3318 | wrapper.style.border = '1px dashed #ccc' |
| 3319 | wrapper.style.display = 'flex' |
| 3320 | wrapper.style.alignItems = 'center' |
| 3321 | wrapper.style.justifyContent = 'center' |
| 3322 | wrapper.style.color = '#999' |
| 3323 | wrapper.style.fontSize = '12px' |
| 3324 | wrapper.textContent = 'Chart not found' |
| 3325 | return wrapper |
| 3326 | } |
| 3327 | |
| 3328 | // Create chart container (clip content so legend/title stay inside) |
| 3329 | const chartDiv = document.createElement('div') |
| 3330 | chartDiv.style.width = '100%' |
| 3331 | chartDiv.style.flex = '1' |
| 3332 | chartDiv.style.minWidth = '0' |
| 3333 | chartDiv.style.minHeight = '0' |
| 3334 | chartDiv.style.overflow = 'hidden' |
| 3335 | wrapper.appendChild(chartDiv) |
| 3336 | |
| 3337 | // Parse chart data and create ECharts option |
| 3338 | const { option, dataTable } = parseChartXml(chartXml, ctx) |
| 3339 | const customLegend = buildCustomLegendOverlay(option, node.size) |
| 3340 | const legendOption = getLegendOptionObject(option.legend) |
| 3341 | if (customLegend && legendOption) { |
| 3342 | legendOption.show = false |
| 3343 | wrapper.appendChild(customLegend) |
| 3344 | } |
| 3345 | |
| 3346 | // Append data table below chart when c:dTable exists |
| 3347 | if (dataTable) { |
| 3348 | const seriesColors = dataTable.seriesArr.map((s) => s.colorHex).filter(Boolean) as string[] |
| 3349 | const tableEl = buildDataTableElement( |
| 3350 | dataTable, |
| 3351 | seriesColors.length > 0 ? seriesColors : undefined |
| 3352 | ) |
| 3353 | wrapper.appendChild(tableEl) |
| 3354 | } |
| 3355 | |
| 3356 | // Initialize ECharts after the element is attached to the DOM. |
| 3357 | // Use requestAnimationFrame to ensure the container has dimensions. |
| 3358 | const chartSet = ctx.chartInstances |
| 3359 | requestAnimationFrame(() => { |
| 3360 | if (!chartDiv.isConnected) return |
| 3361 | // Guard against 0-size containers (e.g. hidden tabs); defer until non-zero. |
| 3362 | if (chartDiv.offsetWidth === 0 || chartDiv.offsetHeight === 0) { |
no test coverage detected