From 618014d084708467274abd202be1e1689ff69062 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 bdc4df6d9837a12296282239123713db4bc52c48 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 5bdffcc1c8230bd6f9f4f851bb26bc5a19e5c4b0 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 e5ef3ea5ca..d3cbc492d9 100644 --- a/packages/layout-engine/painters/dom/src/renderer.ts +++ b/packages/layout-engine/painters/dom/src/renderer.ts @@ -57,7 +57,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 ffc85c53d120edf833afa033208fa9b31f8b68e5 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')); +});