From 49d9f320d7a6f404a7d00c5139fb6fe3c84c32e6 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 9c38866f6b097071f8082f0d66e07d2076a1e012 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 5a325d828f202639f88166b94a88952d9d18f1e6 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 910e320008..0ac2a6ce40 100644 --- a/packages/layout-engine/painters/dom/src/renderer.ts +++ b/packages/layout-engine/painters/dom/src/renderer.ts @@ -49,7 +49,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 2bc2d2d3ec406a571b95744e23e1ffb98fe90f5b 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')); +});