From 05eaefc2a4ac2c3a4328333117e4defc40435b9f 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 b55ec59cc348e159c902536d524f0d048194103e 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 bb6bc8b5cfb0b1f62c1a4ce706694e38e74ff0bb 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 0ad87bf7e7..09b25aa605 100644 --- a/packages/layout-engine/painters/dom/src/renderer.ts +++ b/packages/layout-engine/painters/dom/src/renderer.ts @@ -58,7 +58,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 7f8ba18110e13fdc95605a9fcf09de322a4c4003 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')); +});