From 31daafa0156f30135b1a08f814cf120fea8d71af Mon Sep 17 00:00:00 2001 From: ggdaltoso Date: Fri, 29 May 2026 17:12:54 -0300 Subject: [PATCH 1/4] feat(chart): add rendering feature for DrawingML charts This commit introduces a new rendering feature for various types of charts in the feature registry. The new entry includes support for bar, line, stock, area, scatter, bubble, radar, pie, doughnut, and of pie charts, along with the corresponding module and specification reference. --- .../dom/src/features/feature-registry.ts | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/packages/layout-engine/painters/dom/src/features/feature-registry.ts b/packages/layout-engine/painters/dom/src/features/feature-registry.ts index f5fe64343f..d717b12dfd 100644 --- a/packages/layout-engine/painters/dom/src/features/feature-registry.ts +++ b/packages/layout-engine/painters/dom/src/features/feature-registry.ts @@ -76,4 +76,24 @@ export const RENDERING_FEATURES = { ], spec: '§22.1', }, + + // ─── Charts ─────────────────────────────────────────────────── + // @spec ECMA-376 §21.2 (DrawingML Charts) + 'c:chart': { + feature: 'chart', + module: './chart', + handles: [ + 'c:barChart', + 'c:lineChart', + 'c:stockChart', + 'c:areaChart', + 'c:scatterChart', + 'c:bubbleChart', + 'c:radarChart', + 'c:pieChart', + 'c:doughnutChart', + 'c:ofPieChart', + ], + spec: '§21.2', + }, } as const; From aba7e0a94b4eac1002032063041355dad6b4a4f5 Mon Sep 17 00:00:00 2001 From: ggdaltoso Date: Fri, 29 May 2026 17:13:06 -0300 Subject: [PATCH 2/4] feat(chart): add rendering feature module for DrawingML charts This module renders DrawingML chart blocks as inline SVG elements, supporting various chart types with performance guardrails in place. --- .../painters/dom/src/features/chart/index.ts | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 packages/layout-engine/painters/dom/src/features/chart/index.ts diff --git a/packages/layout-engine/painters/dom/src/features/chart/index.ts b/packages/layout-engine/painters/dom/src/features/chart/index.ts new file mode 100644 index 0000000000..540f09156e --- /dev/null +++ b/packages/layout-engine/painters/dom/src/features/chart/index.ts @@ -0,0 +1,26 @@ +/** + * Chart — rendering feature module + * + * Renders DrawingML chart blocks as inline SVG elements. + * Supports bar/column, line, area, pie, doughnut, scatter, bubble, + * radar, and stock charts, with a placeholder fallback for unsupported types. + * + * Performance guardrails: + * - Max 20 rendered series + * - Max 500 data points per series + * - Max 5,000 SVG elements per chart + * + * @ooxml c:barChart — bar and column charts (ECMA-376 §21.2.2.16) + * @ooxml c:lineChart — line charts (ECMA-376 §21.2.2.81) + * @ooxml c:stockChart — stock charts (ECMA-376 §21.2.2.157) + * @ooxml c:areaChart — area charts (ECMA-376 §21.2.2.1) + * @ooxml c:scatterChart — scatter charts (ECMA-376 §21.2.2.147) + * @ooxml c:bubbleChart — bubble charts (ECMA-376 §21.2.2.20) + * @ooxml c:radarChart — radar charts (ECMA-376 §21.2.2.132) + * @ooxml c:pieChart — pie charts (ECMA-376 §21.2.2.126) + * @ooxml c:doughnutChart — doughnut charts (ECMA-376 §21.2.2.50) + * @ooxml c:ofPieChart — bar-of-pie / pie-of-pie charts (ECMA-376 §21.2.2.111) + * @spec ECMA-376 §21.2 (DrawingML Charts) + */ + +export { createChartElement, createChartPlaceholder, formatTickValue } from '../../chart-renderer.js'; From 9f3f1e41a3543612e92b05da3305a069082055d9 Mon Sep 17 00:00:00 2001 From: ggdaltoso Date: Fri, 29 May 2026 17:13:17 -0300 Subject: [PATCH 3/4] feat(chart): update chart rendering import path Updated the import path for the chart rendering function to point to the new features directory structure. --- packages/layout-engine/painters/dom/src/renderer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/layout-engine/painters/dom/src/renderer.ts b/packages/layout-engine/painters/dom/src/renderer.ts index 12d558067f..5f66bba5c3 100644 --- a/packages/layout-engine/painters/dom/src/renderer.ts +++ b/packages/layout-engine/painters/dom/src/renderer.ts @@ -65,7 +65,7 @@ import { import { DATASET_KEYS, decodeLayoutStoryDataset, encodeLayoutStoryDataset } from '@superdoc/dom-contract'; import { getPresetShapeSvg } from '@superdoc/preset-geometry'; import { DOM_CLASS_NAMES } from './constants.js'; -import { createChartElement as renderChartToElement } from './chart-renderer.js'; +import { createChartElement as renderChartToElement } from './features/chart/index.js'; import { createRulerElement, ensureRulerStyles, generateRulerDefinitionFromPx } from './ruler/index.js'; import { CLASS_NAMES, From 491f8687320c56c476ae8689fa451c9e60e4caac Mon Sep 17 00:00:00 2001 From: ggdaltoso Date: Fri, 29 May 2026 17:13:59 -0300 Subject: [PATCH 4/4] test(chart): add smoke tests for chart feature module public API This commit introduces smoke tests to verify that all exports are correctly re-exported through the feature barrel and that the module handles every registered chart type without throwing or returning a generic placeholder. Full rendering correctness is covered by chart-renderer.test.ts. --- .../dom/src/features/chart/chart.test.ts | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 packages/layout-engine/painters/dom/src/features/chart/chart.test.ts diff --git a/packages/layout-engine/painters/dom/src/features/chart/chart.test.ts b/packages/layout-engine/painters/dom/src/features/chart/chart.test.ts new file mode 100644 index 0000000000..2018690e59 --- /dev/null +++ b/packages/layout-engine/painters/dom/src/features/chart/chart.test.ts @@ -0,0 +1,91 @@ +/** + * Smoke tests for the chart feature module public API. + * Verifies that all exports are correctly re-exported through the feature + * barrel and that the module handles every registered chart type without + * throwing or returning a generic placeholder. + * + * Full rendering correctness is covered by chart-renderer.test.ts. + */ + +import { describe, it, expect, beforeEach } from 'vitest'; +import { JSDOM } from 'jsdom'; +import { createChartElement, createChartPlaceholder, formatTickValue } from './index.js'; +import type { ChartModel, DrawingGeometry } from '@superdoc/contracts'; + +let doc: Document; + +beforeEach(() => { + doc = new JSDOM('').window.document; +}); + +const geometry: DrawingGeometry = { width: 400, height: 300, rotation: 0, flipH: false, flipV: false }; + +const REGISTERED_CHART_TYPES: ChartModel['chartType'][] = [ + 'barChart', + 'lineChart', + 'stockChart', + 'areaChart', + 'scatterChart', + 'bubbleChart', + 'radarChart', + 'pieChart', + 'doughnutChart', + 'ofPieChart', +]; + +function makeChart(chartType: ChartModel['chartType']): ChartModel { + return { + chartType, + series: [ + { name: 'S1', categories: ['A', 'B', 'C'], values: [1, 2, 3], xValues: [1, 2, 3], bubbleSizes: [1, 2, 3] }, + ], + legendPosition: 'b', + barDirection: 'col', + }; +} + +describe('chart feature module exports', () => { + it('exports createChartElement as a function', () => { + expect(typeof createChartElement).toBe('function'); + }); + + it('exports createChartPlaceholder as a function', () => { + expect(typeof createChartPlaceholder).toBe('function'); + }); + + it('exports formatTickValue as a function', () => { + expect(typeof formatTickValue).toBe('function'); + }); +}); + +describe('createChartElement via feature module', () => { + it('returns a superdoc-chart element', () => { + const el = createChartElement(doc, makeChart('barChart'), geometry); + expect(el.classList.contains('superdoc-chart')).toBe(true); + }); + + it('shows placeholder when chart data is missing', () => { + const el = createChartElement(doc, undefined, geometry); + expect(el.textContent).toContain('No chart data'); + }); + + it.each(REGISTERED_CHART_TYPES)('renders %s without throwing', (chartType) => { + const el = createChartElement(doc, makeChart(chartType), geometry); + expect(el.classList.contains('superdoc-chart')).toBe(true); + expect(el.textContent).not.toContain(`Chart: ${chartType}`); + }); +}); + +describe('createChartPlaceholder via feature module', () => { + it('renders the label text', () => { + const container = doc.createElement('div'); + const el = createChartPlaceholder(doc, container, 'Test label'); + expect(el.textContent).toContain('Test label'); + }); +}); + +describe('formatTickValue via feature module', () => { + it('formats thousands', () => expect(formatTickValue(1_500)).toBe('1.5K')); + it('formats millions', () => expect(formatTickValue(2_000_000)).toBe('2.0M')); + it('formats plain numbers', () => expect(formatTickValue(42)).toBe('42')); +});