@cj-tech-master/excelts 9.4.2 → 9.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser/index.browser.d.ts +8 -5
- package/dist/browser/index.browser.js +19 -1
- package/dist/browser/index.d.ts +4 -2
- package/dist/browser/index.js +9 -1
- package/dist/browser/modules/excel/chart/cache-populator.d.ts +49 -0
- package/dist/browser/modules/excel/chart/cache-populator.js +1171 -0
- package/dist/browser/modules/excel/chart/chart-api.d.ts +92 -0
- package/dist/browser/modules/excel/chart/chart-api.js +364 -0
- package/dist/browser/modules/excel/chart/chart-builder.d.ts +48 -0
- package/dist/browser/modules/excel/chart/chart-builder.js +2432 -0
- package/dist/browser/modules/excel/chart/chart-ex-builder.d.ts +36 -0
- package/dist/browser/modules/excel/chart/chart-ex-builder.js +903 -0
- package/dist/browser/modules/excel/chart/chart-ex-parser.d.ts +8 -0
- package/dist/browser/modules/excel/chart/chart-ex-parser.js +1205 -0
- package/dist/browser/modules/excel/chart/chart-ex-renderer.d.ts +187 -0
- package/dist/browser/modules/excel/chart/chart-ex-renderer.js +5352 -0
- package/dist/browser/modules/excel/chart/chart-ex-types.d.ts +531 -0
- package/dist/browser/modules/excel/chart/chart-ex-types.js +11 -0
- package/dist/browser/modules/excel/chart/chart-images.d.ts +78 -0
- package/dist/browser/modules/excel/chart/chart-images.js +363 -0
- package/dist/browser/modules/excel/chart/chart-presets.d.ts +392 -0
- package/dist/browser/modules/excel/chart/chart-presets.js +179 -0
- package/dist/browser/modules/excel/chart/chart-renderer.d.ts +550 -0
- package/dist/browser/modules/excel/chart/chart-renderer.js +6440 -0
- package/dist/browser/modules/excel/chart/chart-sidecar.d.ts +21 -0
- package/dist/browser/modules/excel/chart/chart-sidecar.js +427 -0
- package/dist/browser/modules/excel/chart/chart-utils.d.ts +306 -0
- package/dist/browser/modules/excel/chart/chart-utils.js +821 -0
- package/dist/browser/modules/excel/chart/chart.d.ts +504 -0
- package/dist/browser/modules/excel/chart/chart.js +1320 -0
- package/dist/browser/modules/excel/chart/glyph-rasterizer.d.ts +62 -0
- package/dist/browser/modules/excel/chart/glyph-rasterizer.js +658 -0
- package/dist/browser/modules/excel/chart/index.d.ts +54 -0
- package/dist/browser/modules/excel/chart/index.js +46 -0
- package/dist/browser/modules/excel/chart/install.d.ts +44 -0
- package/dist/browser/modules/excel/chart/install.js +91 -0
- package/dist/browser/modules/excel/chart/shape-properties.d.ts +156 -0
- package/dist/browser/modules/excel/chart/shape-properties.js +1557 -0
- package/dist/browser/modules/excel/chart/stroke-font.d.ts +36 -0
- package/dist/browser/modules/excel/chart/stroke-font.js +1556 -0
- package/dist/browser/modules/excel/chart/topojson.d.ts +98 -0
- package/dist/browser/modules/excel/chart/topojson.js +236 -0
- package/dist/browser/modules/excel/chart/types.d.ts +2559 -0
- package/dist/browser/modules/excel/chart/types.js +8 -0
- package/dist/browser/modules/excel/chart-host-registry.d.ts +157 -0
- package/dist/browser/modules/excel/chart-host-registry.js +90 -0
- package/dist/browser/modules/excel/chartsheet.d.ts +102 -0
- package/dist/browser/modules/excel/chartsheet.js +196 -0
- package/dist/browser/modules/excel/defined-names.d.ts +35 -0
- package/dist/browser/modules/excel/defined-names.js +44 -4
- package/dist/browser/modules/excel/errors.d.ts +6 -0
- package/dist/browser/modules/excel/errors.js +9 -0
- package/dist/browser/modules/excel/form-control.d.ts +6 -0
- package/dist/browser/modules/excel/form-control.js +17 -0
- package/dist/browser/modules/excel/image.js +12 -2
- package/dist/browser/modules/excel/pivot-chart.d.ts +7 -0
- package/dist/browser/modules/excel/pivot-chart.js +53 -0
- package/dist/browser/modules/excel/pivot-table.d.ts +55 -0
- package/dist/browser/modules/excel/pivot-table.js +35 -0
- package/dist/browser/modules/excel/range.js +5 -1
- package/dist/browser/modules/excel/sparkline/index.d.ts +7 -0
- package/dist/browser/modules/excel/sparkline/index.js +7 -0
- package/dist/browser/modules/excel/sparkline/sparkline.d.ts +206 -0
- package/dist/browser/modules/excel/sparkline/sparkline.js +750 -0
- package/dist/browser/modules/excel/stream/worksheet-writer.js +3 -2
- package/dist/browser/modules/excel/table.js +42 -6
- package/dist/browser/modules/excel/types.d.ts +72 -0
- package/dist/browser/modules/excel/utils/address.d.ts +18 -0
- package/dist/browser/modules/excel/utils/address.js +28 -0
- package/dist/browser/modules/excel/utils/drawing-utils.js +11 -6
- package/dist/browser/modules/excel/utils/guid.d.ts +15 -0
- package/dist/browser/modules/excel/utils/guid.js +35 -0
- package/dist/browser/modules/excel/utils/ooxml-paths.d.ts +74 -0
- package/dist/browser/modules/excel/utils/ooxml-paths.js +206 -9
- package/dist/browser/modules/excel/utils/ooxml-validator/check-chart-sidecar.d.ts +35 -0
- package/dist/browser/modules/excel/utils/ooxml-validator/check-chart-sidecar.js +101 -0
- package/dist/browser/modules/excel/utils/ooxml-validator/check-chart.d.ts +32 -0
- package/dist/browser/modules/excel/utils/ooxml-validator/check-chart.js +2125 -0
- package/dist/browser/modules/excel/utils/ooxml-validator/check-chartsheet.d.ts +9 -0
- package/dist/browser/modules/excel/utils/ooxml-validator/check-chartsheet.js +26 -0
- package/dist/browser/modules/excel/utils/ooxml-validator/check-content-types.d.ts +16 -0
- package/dist/browser/modules/excel/utils/ooxml-validator/check-content-types.js +181 -0
- package/dist/browser/modules/excel/utils/ooxml-validator/check-drawing.d.ts +34 -0
- package/dist/browser/modules/excel/utils/ooxml-validator/check-drawing.js +267 -0
- package/dist/browser/modules/excel/utils/ooxml-validator/check-pivot.d.ts +14 -0
- package/dist/browser/modules/excel/utils/ooxml-validator/check-pivot.js +104 -0
- package/dist/browser/modules/excel/utils/ooxml-validator/check-relationships.d.ts +18 -0
- package/dist/browser/modules/excel/utils/ooxml-validator/check-relationships.js +184 -0
- package/dist/browser/modules/excel/utils/ooxml-validator/check-structure.d.ts +21 -0
- package/dist/browser/modules/excel/utils/ooxml-validator/check-structure.js +56 -0
- package/dist/browser/modules/excel/utils/ooxml-validator/check-styles.d.ts +15 -0
- package/dist/browser/modules/excel/utils/ooxml-validator/check-styles.js +89 -0
- package/dist/browser/modules/excel/utils/ooxml-validator/check-table.d.ts +31 -0
- package/dist/browser/modules/excel/utils/ooxml-validator/check-table.js +177 -0
- package/dist/browser/modules/excel/utils/ooxml-validator/check-workbook.d.ts +19 -0
- package/dist/browser/modules/excel/utils/ooxml-validator/check-workbook.js +163 -0
- package/dist/browser/modules/excel/utils/ooxml-validator/check-worksheet.d.ts +25 -0
- package/dist/browser/modules/excel/utils/ooxml-validator/check-worksheet.js +569 -0
- package/dist/browser/modules/excel/utils/ooxml-validator/context.d.ts +85 -0
- package/dist/browser/modules/excel/utils/ooxml-validator/context.js +191 -0
- package/dist/browser/modules/excel/utils/ooxml-validator/index.d.ts +31 -0
- package/dist/browser/modules/excel/utils/ooxml-validator/index.js +102 -0
- package/dist/browser/modules/excel/utils/ooxml-validator/path-utils.d.ts +67 -0
- package/dist/browser/modules/excel/utils/ooxml-validator/path-utils.js +156 -0
- package/dist/browser/modules/excel/utils/ooxml-validator/reporter.d.ts +41 -0
- package/dist/browser/modules/excel/utils/ooxml-validator/reporter.js +61 -0
- package/dist/browser/modules/excel/utils/ooxml-validator/types.d.ts +109 -0
- package/dist/browser/modules/excel/utils/ooxml-validator/types.js +12 -0
- package/dist/browser/modules/excel/utils/ooxml-validator/xml-utils.d.ts +38 -0
- package/dist/browser/modules/excel/utils/ooxml-validator/xml-utils.js +100 -0
- package/dist/browser/modules/excel/workbook.browser.d.ts +248 -30
- package/dist/browser/modules/excel/workbook.browser.js +966 -31
- package/dist/browser/modules/excel/workbook.d.ts +43 -0
- package/dist/browser/modules/excel/workbook.js +48 -0
- package/dist/browser/modules/excel/worksheet.d.ts +157 -3
- package/dist/browser/modules/excel/worksheet.js +394 -35
- package/dist/browser/modules/excel/xlsx/rel-type.d.ts +40 -0
- package/dist/browser/modules/excel/xlsx/rel-type.js +41 -1
- package/dist/browser/modules/excel/xlsx/xform/book/defined-name-xform.d.ts +1 -0
- package/dist/browser/modules/excel/xlsx/xform/book/defined-name-xform.js +11 -2
- package/dist/browser/modules/excel/xlsx/xform/book/external-link-xform.js +12 -10
- package/dist/browser/modules/excel/xlsx/xform/book/workbook-xform.js +96 -22
- package/dist/browser/modules/excel/xlsx/xform/chart/chart-space-xform.d.ts +353 -0
- package/dist/browser/modules/excel/xlsx/xform/chart/chart-space-xform.js +6000 -0
- package/dist/browser/modules/excel/xlsx/xform/comment/threaded-comments-xform.d.ts +60 -0
- package/dist/browser/modules/excel/xlsx/xform/comment/threaded-comments-xform.js +213 -0
- package/dist/browser/modules/excel/xlsx/xform/core/content-types-xform.js +150 -11
- package/dist/browser/modules/excel/xlsx/xform/drawing/absolute-anchor-xform.js +20 -1
- package/dist/browser/modules/excel/xlsx/xform/drawing/base-cell-anchor-xform.js +1 -1
- package/dist/browser/modules/excel/xlsx/xform/drawing/drawing-xform.d.ts +30 -0
- package/dist/browser/modules/excel/xlsx/xform/drawing/drawing-xform.js +109 -5
- package/dist/browser/modules/excel/xlsx/xform/drawing/graphic-frame-xform.d.ts +54 -0
- package/dist/browser/modules/excel/xlsx/xform/drawing/graphic-frame-xform.js +225 -0
- package/dist/browser/modules/excel/xlsx/xform/drawing/one-cell-anchor-xform.d.ts +3 -1
- package/dist/browser/modules/excel/xlsx/xform/drawing/one-cell-anchor-xform.js +18 -3
- package/dist/browser/modules/excel/xlsx/xform/drawing/two-cell-anchor-xform.d.ts +46 -0
- package/dist/browser/modules/excel/xlsx/xform/drawing/two-cell-anchor-xform.js +294 -12
- package/dist/browser/modules/excel/xlsx/xform/pivot-table/pivot-table-xform.d.ts +13 -2
- package/dist/browser/modules/excel/xlsx/xform/pivot-table/pivot-table-xform.js +32 -6
- package/dist/browser/modules/excel/xlsx/xform/sheet/chartsheet-xform.d.ts +185 -0
- package/dist/browser/modules/excel/xlsx/xform/sheet/chartsheet-xform.js +441 -0
- package/dist/browser/modules/excel/xlsx/xform/sheet/ext-lst-xform.d.ts +1 -0
- package/dist/browser/modules/excel/xlsx/xform/sheet/ext-lst-xform.js +51 -2
- package/dist/browser/modules/excel/xlsx/xform/sheet/worksheet-xform.js +196 -20
- package/dist/browser/modules/excel/xlsx/xform/table/auto-filter-xform.js +16 -1
- package/dist/browser/modules/excel/xlsx/xform/table/table-column-xform.js +17 -2
- package/dist/browser/modules/excel/xlsx/xform/xsd-values.d.ts +63 -0
- package/dist/browser/modules/excel/xlsx/xform/xsd-values.js +101 -0
- package/dist/browser/modules/excel/xlsx/xlsx.browser.d.ts +115 -21
- package/dist/browser/modules/excel/xlsx/xlsx.browser.js +4422 -78
- package/dist/browser/modules/pdf/builder/document-builder.d.ts +74 -0
- package/dist/browser/modules/pdf/builder/document-builder.js +507 -2
- package/dist/browser/modules/pdf/builder/pdf-editor.js +48 -3
- package/dist/browser/modules/pdf/excel-bridge.d.ts +69 -0
- package/dist/browser/modules/pdf/excel-bridge.js +683 -12
- package/dist/browser/modules/pdf/font/font-manager.d.ts +25 -0
- package/dist/browser/modules/pdf/font/font-manager.js +39 -0
- package/dist/browser/modules/pdf/index.d.ts +5 -2
- package/dist/browser/modules/pdf/index.js +3 -1
- package/dist/browser/modules/pdf/render/chart-surface.d.ts +33 -0
- package/dist/browser/modules/pdf/render/chart-surface.js +200 -0
- package/dist/browser/modules/pdf/render/layout-engine.d.ts +22 -1
- package/dist/browser/modules/pdf/render/layout-engine.js +436 -56
- package/dist/browser/modules/pdf/render/page-renderer.js +169 -28
- package/dist/browser/modules/pdf/render/pdf-exporter.js +117 -7
- package/dist/browser/modules/pdf/types.d.ts +227 -23
- package/dist/browser/modules/pdf/types.js +4 -0
- package/dist/browser/modules/pdf/word-bridge.d.ts +47 -0
- package/dist/browser/modules/pdf/word-bridge.js +304 -0
- package/dist/browser/modules/word/constants.d.ts +179 -0
- package/dist/browser/modules/word/constants.js +231 -0
- package/dist/browser/modules/word/content-types.d.ts +27 -0
- package/dist/browser/modules/word/content-types.js +53 -0
- package/dist/browser/modules/word/digital-signatures.d.ts +87 -0
- package/dist/browser/modules/word/digital-signatures.js +134 -0
- package/dist/browser/modules/word/document.d.ts +728 -0
- package/dist/browser/modules/word/document.js +1795 -0
- package/dist/browser/modules/word/docx-packager.d.ts +14 -0
- package/dist/browser/modules/word/docx-packager.js +822 -0
- package/dist/browser/modules/word/docx-reader.d.ts +11 -0
- package/dist/browser/modules/word/docx-reader.js +4929 -0
- package/dist/browser/modules/word/encryption.d.ts +102 -0
- package/dist/browser/modules/word/encryption.js +274 -0
- package/dist/browser/modules/word/errors.d.ts +49 -0
- package/dist/browser/modules/word/errors.js +68 -0
- package/dist/browser/modules/word/font-obfuscation.d.ts +31 -0
- package/dist/browser/modules/word/font-obfuscation.js +83 -0
- package/dist/browser/modules/word/html-renderer.d.ts +38 -0
- package/dist/browser/modules/word/html-renderer.js +782 -0
- package/dist/browser/modules/word/index.base.d.ts +19 -0
- package/dist/browser/modules/word/index.base.js +51 -0
- package/dist/browser/modules/word/index.browser.d.ts +4 -0
- package/dist/browser/modules/word/index.browser.js +4 -0
- package/dist/browser/modules/word/index.d.ts +4 -0
- package/dist/browser/modules/word/index.js +4 -0
- package/dist/browser/modules/word/internal-utils.d.ts +23 -0
- package/dist/browser/modules/word/internal-utils.js +54 -0
- package/dist/browser/modules/word/relationships.d.ts +31 -0
- package/dist/browser/modules/word/relationships.js +56 -0
- package/dist/browser/modules/word/types.d.ts +2325 -0
- package/dist/browser/modules/word/types.js +10 -0
- package/dist/browser/modules/word/units.d.ts +49 -0
- package/dist/browser/modules/word/units.js +111 -0
- package/dist/browser/modules/word/writers/chart-writer.d.ts +10 -0
- package/dist/browser/modules/word/writers/chart-writer.js +385 -0
- package/dist/browser/modules/word/writers/checkbox-writer.d.ts +9 -0
- package/dist/browser/modules/word/writers/checkbox-writer.js +42 -0
- package/dist/browser/modules/word/writers/comment-writer.d.ts +15 -0
- package/dist/browser/modules/word/writers/comment-writer.js +70 -0
- package/dist/browser/modules/word/writers/document-writer.d.ts +16 -0
- package/dist/browser/modules/word/writers/document-writer.js +461 -0
- package/dist/browser/modules/word/writers/footnote-writer.d.ts +11 -0
- package/dist/browser/modules/word/writers/footnote-writer.js +72 -0
- package/dist/browser/modules/word/writers/header-footer-writer.d.ts +13 -0
- package/dist/browser/modules/word/writers/header-footer-writer.js +129 -0
- package/dist/browser/modules/word/writers/image-writer.d.ts +10 -0
- package/dist/browser/modules/word/writers/image-writer.js +185 -0
- package/dist/browser/modules/word/writers/math-writer.d.ts +9 -0
- package/dist/browser/modules/word/writers/math-writer.js +428 -0
- package/dist/browser/modules/word/writers/numbering-writer.d.ts +10 -0
- package/dist/browser/modules/word/writers/numbering-writer.js +125 -0
- package/dist/browser/modules/word/writers/paragraph-writer.d.ts +13 -0
- package/dist/browser/modules/word/writers/paragraph-writer.js +516 -0
- package/dist/browser/modules/word/writers/parts-writer.d.ts +26 -0
- package/dist/browser/modules/word/writers/parts-writer.js +660 -0
- package/dist/browser/modules/word/writers/run-writer.d.ts +15 -0
- package/dist/browser/modules/word/writers/run-writer.js +649 -0
- package/dist/browser/modules/word/writers/section-writer.d.ts +10 -0
- package/dist/browser/modules/word/writers/section-writer.js +238 -0
- package/dist/browser/modules/word/writers/styles-writer.d.ts +10 -0
- package/dist/browser/modules/word/writers/styles-writer.js +242 -0
- package/dist/browser/modules/word/writers/table-writer.d.ts +10 -0
- package/dist/browser/modules/word/writers/table-writer.js +503 -0
- package/dist/browser/modules/word/writers/textbox-writer.d.ts +9 -0
- package/dist/browser/modules/word/writers/textbox-writer.js +53 -0
- package/dist/browser/modules/word/writers/toc-writer.d.ts +9 -0
- package/dist/browser/modules/word/writers/toc-writer.js +79 -0
- package/dist/browser/modules/xml/encode.d.ts +56 -7
- package/dist/browser/modules/xml/encode.js +157 -11
- package/dist/cjs/index.js +13 -2
- package/dist/cjs/modules/excel/chart/cache-populator.js +1178 -0
- package/dist/cjs/modules/excel/chart/chart-api.js +371 -0
- package/dist/cjs/modules/excel/chart/chart-builder.js +2440 -0
- package/dist/cjs/modules/excel/chart/chart-ex-builder.js +907 -0
- package/dist/cjs/modules/excel/chart/chart-ex-parser.js +1208 -0
- package/dist/cjs/modules/excel/chart/chart-ex-renderer.js +5364 -0
- package/dist/cjs/modules/excel/chart/chart-ex-types.js +12 -0
- package/dist/cjs/modules/excel/chart/chart-images.js +366 -0
- package/dist/cjs/modules/excel/chart/chart-presets.js +184 -0
- package/dist/cjs/modules/excel/chart/chart-renderer.js +6450 -0
- package/dist/cjs/modules/excel/chart/chart-sidecar.js +433 -0
- package/dist/cjs/modules/excel/chart/chart-utils.js +845 -0
- package/dist/cjs/modules/excel/chart/chart.js +1324 -0
- package/dist/cjs/modules/excel/chart/glyph-rasterizer.js +664 -0
- package/dist/cjs/modules/excel/chart/index.js +101 -0
- package/dist/cjs/modules/excel/chart/install.js +95 -0
- package/dist/cjs/modules/excel/chart/shape-properties.js +1577 -0
- package/dist/cjs/modules/excel/chart/stroke-font.js +1559 -0
- package/dist/cjs/modules/excel/chart/topojson.js +239 -0
- package/dist/cjs/modules/excel/chart/types.js +9 -0
- package/dist/cjs/modules/excel/chart-host-registry.js +96 -0
- package/dist/cjs/modules/excel/chartsheet.js +199 -0
- package/dist/cjs/modules/excel/defined-names.js +44 -4
- package/dist/cjs/modules/excel/errors.js +11 -1
- package/dist/cjs/modules/excel/form-control.js +17 -0
- package/dist/cjs/modules/excel/image.js +12 -2
- package/dist/cjs/modules/excel/pivot-chart.js +56 -0
- package/dist/cjs/modules/excel/pivot-table.js +35 -0
- package/dist/cjs/modules/excel/range.js +5 -1
- package/dist/cjs/modules/excel/sparkline/index.js +23 -0
- package/dist/cjs/modules/excel/sparkline/sparkline.js +756 -0
- package/dist/cjs/modules/excel/stream/worksheet-writer.js +3 -2
- package/dist/cjs/modules/excel/table.js +42 -6
- package/dist/cjs/modules/excel/utils/address.js +29 -0
- package/dist/cjs/modules/excel/utils/drawing-utils.js +11 -6
- package/dist/cjs/modules/excel/utils/guid.js +38 -0
- package/dist/cjs/modules/excel/utils/ooxml-paths.js +246 -9
- package/dist/cjs/modules/excel/utils/ooxml-validator/check-chart-sidecar.js +103 -0
- package/dist/cjs/modules/excel/utils/ooxml-validator/check-chart.js +2128 -0
- package/dist/cjs/modules/excel/utils/ooxml-validator/check-chartsheet.js +29 -0
- package/dist/cjs/modules/excel/utils/ooxml-validator/check-content-types.js +184 -0
- package/dist/cjs/modules/excel/utils/ooxml-validator/check-drawing.js +270 -0
- package/dist/cjs/modules/excel/utils/ooxml-validator/check-pivot.js +107 -0
- package/dist/cjs/modules/excel/utils/ooxml-validator/check-relationships.js +188 -0
- package/dist/cjs/modules/excel/utils/ooxml-validator/check-structure.js +60 -0
- package/dist/cjs/modules/excel/utils/ooxml-validator/check-styles.js +92 -0
- package/dist/cjs/modules/excel/utils/ooxml-validator/check-table.js +180 -0
- package/dist/cjs/modules/excel/utils/ooxml-validator/check-workbook.js +166 -0
- package/dist/cjs/modules/excel/utils/ooxml-validator/check-worksheet.js +572 -0
- package/dist/cjs/modules/excel/utils/ooxml-validator/context.js +196 -0
- package/dist/cjs/modules/excel/utils/ooxml-validator/index.js +105 -0
- package/dist/cjs/modules/excel/utils/ooxml-validator/path-utils.js +168 -0
- package/dist/cjs/modules/excel/utils/ooxml-validator/reporter.js +66 -0
- package/dist/cjs/modules/excel/utils/ooxml-validator/types.js +13 -0
- package/dist/cjs/modules/excel/utils/ooxml-validator/xml-utils.js +110 -0
- package/dist/cjs/modules/excel/workbook.browser.js +973 -38
- package/dist/cjs/modules/excel/workbook.js +48 -0
- package/dist/cjs/modules/excel/worksheet.js +393 -34
- package/dist/cjs/modules/excel/xlsx/rel-type.js +41 -1
- package/dist/cjs/modules/excel/xlsx/xform/book/defined-name-xform.js +11 -2
- package/dist/cjs/modules/excel/xlsx/xform/book/external-link-xform.js +12 -10
- package/dist/cjs/modules/excel/xlsx/xform/book/workbook-xform.js +96 -22
- package/dist/cjs/modules/excel/xlsx/xform/chart/chart-space-xform.js +6003 -0
- package/dist/cjs/modules/excel/xlsx/xform/comment/threaded-comments-xform.js +219 -0
- package/dist/cjs/modules/excel/xlsx/xform/core/content-types-xform.js +149 -10
- package/dist/cjs/modules/excel/xlsx/xform/drawing/absolute-anchor-xform.js +20 -1
- package/dist/cjs/modules/excel/xlsx/xform/drawing/base-cell-anchor-xform.js +1 -1
- package/dist/cjs/modules/excel/xlsx/xform/drawing/drawing-xform.js +109 -5
- package/dist/cjs/modules/excel/xlsx/xform/drawing/graphic-frame-xform.js +228 -0
- package/dist/cjs/modules/excel/xlsx/xform/drawing/one-cell-anchor-xform.js +18 -3
- package/dist/cjs/modules/excel/xlsx/xform/drawing/two-cell-anchor-xform.js +294 -12
- package/dist/cjs/modules/excel/xlsx/xform/pivot-table/pivot-table-xform.js +32 -6
- package/dist/cjs/modules/excel/xlsx/xform/sheet/chartsheet-xform.js +444 -0
- package/dist/cjs/modules/excel/xlsx/xform/sheet/ext-lst-xform.js +51 -2
- package/dist/cjs/modules/excel/xlsx/xform/sheet/worksheet-xform.js +195 -19
- package/dist/cjs/modules/excel/xlsx/xform/table/auto-filter-xform.js +16 -1
- package/dist/cjs/modules/excel/xlsx/xform/table/table-column-xform.js +17 -2
- package/dist/cjs/modules/excel/xlsx/xform/xsd-values.js +106 -0
- package/dist/cjs/modules/excel/xlsx/xlsx.browser.js +4420 -76
- package/dist/cjs/modules/pdf/builder/document-builder.js +506 -1
- package/dist/cjs/modules/pdf/builder/pdf-editor.js +48 -3
- package/dist/cjs/modules/pdf/excel-bridge.js +684 -12
- package/dist/cjs/modules/pdf/font/font-manager.js +39 -0
- package/dist/cjs/modules/pdf/index.js +5 -1
- package/dist/cjs/modules/pdf/render/chart-surface.js +203 -0
- package/dist/cjs/modules/pdf/render/layout-engine.js +437 -56
- package/dist/cjs/modules/pdf/render/page-renderer.js +169 -28
- package/dist/cjs/modules/pdf/render/pdf-exporter.js +115 -5
- package/dist/cjs/modules/pdf/types.js +5 -0
- package/dist/cjs/modules/pdf/word-bridge.js +307 -0
- package/dist/cjs/modules/word/constants.js +234 -0
- package/dist/cjs/modules/word/content-types.js +57 -0
- package/dist/cjs/modules/word/digital-signatures.js +140 -0
- package/dist/cjs/modules/word/document.js +1909 -0
- package/dist/cjs/modules/word/docx-packager.js +825 -0
- package/dist/cjs/modules/word/docx-reader.js +4932 -0
- package/dist/cjs/modules/word/encryption.js +282 -0
- package/dist/cjs/modules/word/errors.js +88 -0
- package/dist/cjs/modules/word/font-obfuscation.js +88 -0
- package/dist/cjs/modules/word/html-renderer.js +785 -0
- package/dist/cjs/modules/word/index.base.js +199 -0
- package/dist/cjs/modules/word/index.browser.js +20 -0
- package/dist/cjs/modules/word/index.js +20 -0
- package/dist/cjs/modules/word/internal-utils.js +59 -0
- package/dist/cjs/modules/word/relationships.js +60 -0
- package/dist/cjs/modules/word/types.js +11 -0
- package/dist/cjs/modules/word/units.js +135 -0
- package/dist/cjs/modules/word/writers/chart-writer.js +388 -0
- package/dist/cjs/modules/word/writers/checkbox-writer.js +45 -0
- package/dist/cjs/modules/word/writers/comment-writer.js +74 -0
- package/dist/cjs/modules/word/writers/document-writer.js +465 -0
- package/dist/cjs/modules/word/writers/footnote-writer.js +76 -0
- package/dist/cjs/modules/word/writers/header-footer-writer.js +134 -0
- package/dist/cjs/modules/word/writers/image-writer.js +188 -0
- package/dist/cjs/modules/word/writers/math-writer.js +431 -0
- package/dist/cjs/modules/word/writers/numbering-writer.js +128 -0
- package/dist/cjs/modules/word/writers/paragraph-writer.js +521 -0
- package/dist/cjs/modules/word/writers/parts-writer.js +671 -0
- package/dist/cjs/modules/word/writers/run-writer.js +655 -0
- package/dist/cjs/modules/word/writers/section-writer.js +241 -0
- package/dist/cjs/modules/word/writers/styles-writer.js +245 -0
- package/dist/cjs/modules/word/writers/table-writer.js +506 -0
- package/dist/cjs/modules/word/writers/textbox-writer.js +56 -0
- package/dist/cjs/modules/word/writers/toc-writer.js +82 -0
- package/dist/cjs/modules/xml/encode.js +158 -11
- package/dist/esm/index.browser.js +20 -2
- package/dist/esm/index.js +9 -1
- package/dist/esm/modules/excel/chart/cache-populator.js +1171 -0
- package/dist/esm/modules/excel/chart/chart-api.js +364 -0
- package/dist/esm/modules/excel/chart/chart-builder.js +2432 -0
- package/dist/esm/modules/excel/chart/chart-ex-builder.js +903 -0
- package/dist/esm/modules/excel/chart/chart-ex-parser.js +1205 -0
- package/dist/esm/modules/excel/chart/chart-ex-renderer.js +5352 -0
- package/dist/esm/modules/excel/chart/chart-ex-types.js +11 -0
- package/dist/esm/modules/excel/chart/chart-images.js +363 -0
- package/dist/esm/modules/excel/chart/chart-presets.js +179 -0
- package/dist/esm/modules/excel/chart/chart-renderer.js +6440 -0
- package/dist/esm/modules/excel/chart/chart-sidecar.js +427 -0
- package/dist/esm/modules/excel/chart/chart-utils.js +821 -0
- package/dist/esm/modules/excel/chart/chart.js +1320 -0
- package/dist/esm/modules/excel/chart/glyph-rasterizer.js +658 -0
- package/dist/esm/modules/excel/chart/index.js +46 -0
- package/dist/esm/modules/excel/chart/install.js +91 -0
- package/dist/esm/modules/excel/chart/shape-properties.js +1557 -0
- package/dist/esm/modules/excel/chart/stroke-font.js +1556 -0
- package/dist/esm/modules/excel/chart/topojson.js +236 -0
- package/dist/esm/modules/excel/chart/types.js +8 -0
- package/dist/esm/modules/excel/chart-host-registry.js +90 -0
- package/dist/esm/modules/excel/chartsheet.js +196 -0
- package/dist/esm/modules/excel/defined-names.js +44 -4
- package/dist/esm/modules/excel/errors.js +9 -0
- package/dist/esm/modules/excel/form-control.js +17 -0
- package/dist/esm/modules/excel/image.js +12 -2
- package/dist/esm/modules/excel/pivot-chart.js +53 -0
- package/dist/esm/modules/excel/pivot-table.js +35 -0
- package/dist/esm/modules/excel/range.js +5 -1
- package/dist/esm/modules/excel/sparkline/index.js +7 -0
- package/dist/esm/modules/excel/sparkline/sparkline.js +750 -0
- package/dist/esm/modules/excel/stream/worksheet-writer.js +3 -2
- package/dist/esm/modules/excel/table.js +42 -6
- package/dist/esm/modules/excel/utils/address.js +28 -0
- package/dist/esm/modules/excel/utils/drawing-utils.js +11 -6
- package/dist/esm/modules/excel/utils/guid.js +35 -0
- package/dist/esm/modules/excel/utils/ooxml-paths.js +206 -9
- package/dist/esm/modules/excel/utils/ooxml-validator/check-chart-sidecar.js +101 -0
- package/dist/esm/modules/excel/utils/ooxml-validator/check-chart.js +2125 -0
- package/dist/esm/modules/excel/utils/ooxml-validator/check-chartsheet.js +26 -0
- package/dist/esm/modules/excel/utils/ooxml-validator/check-content-types.js +181 -0
- package/dist/esm/modules/excel/utils/ooxml-validator/check-drawing.js +267 -0
- package/dist/esm/modules/excel/utils/ooxml-validator/check-pivot.js +104 -0
- package/dist/esm/modules/excel/utils/ooxml-validator/check-relationships.js +184 -0
- package/dist/esm/modules/excel/utils/ooxml-validator/check-structure.js +56 -0
- package/dist/esm/modules/excel/utils/ooxml-validator/check-styles.js +89 -0
- package/dist/esm/modules/excel/utils/ooxml-validator/check-table.js +177 -0
- package/dist/esm/modules/excel/utils/ooxml-validator/check-workbook.js +163 -0
- package/dist/esm/modules/excel/utils/ooxml-validator/check-worksheet.js +569 -0
- package/dist/esm/modules/excel/utils/ooxml-validator/context.js +191 -0
- package/dist/esm/modules/excel/utils/ooxml-validator/index.js +102 -0
- package/dist/esm/modules/excel/utils/ooxml-validator/path-utils.js +156 -0
- package/dist/esm/modules/excel/utils/ooxml-validator/reporter.js +61 -0
- package/dist/esm/modules/excel/utils/ooxml-validator/types.js +12 -0
- package/dist/esm/modules/excel/utils/ooxml-validator/xml-utils.js +100 -0
- package/dist/esm/modules/excel/workbook.browser.js +969 -34
- package/dist/esm/modules/excel/workbook.js +48 -0
- package/dist/esm/modules/excel/worksheet.js +394 -35
- package/dist/esm/modules/excel/xlsx/rel-type.js +41 -1
- package/dist/esm/modules/excel/xlsx/xform/book/defined-name-xform.js +11 -2
- package/dist/esm/modules/excel/xlsx/xform/book/external-link-xform.js +12 -10
- package/dist/esm/modules/excel/xlsx/xform/book/workbook-xform.js +96 -22
- package/dist/esm/modules/excel/xlsx/xform/chart/chart-space-xform.js +6000 -0
- package/dist/esm/modules/excel/xlsx/xform/comment/threaded-comments-xform.js +213 -0
- package/dist/esm/modules/excel/xlsx/xform/core/content-types-xform.js +150 -11
- package/dist/esm/modules/excel/xlsx/xform/drawing/absolute-anchor-xform.js +20 -1
- package/dist/esm/modules/excel/xlsx/xform/drawing/base-cell-anchor-xform.js +1 -1
- package/dist/esm/modules/excel/xlsx/xform/drawing/drawing-xform.js +109 -5
- package/dist/esm/modules/excel/xlsx/xform/drawing/graphic-frame-xform.js +225 -0
- package/dist/esm/modules/excel/xlsx/xform/drawing/one-cell-anchor-xform.js +18 -3
- package/dist/esm/modules/excel/xlsx/xform/drawing/two-cell-anchor-xform.js +294 -12
- package/dist/esm/modules/excel/xlsx/xform/pivot-table/pivot-table-xform.js +32 -6
- package/dist/esm/modules/excel/xlsx/xform/sheet/chartsheet-xform.js +441 -0
- package/dist/esm/modules/excel/xlsx/xform/sheet/ext-lst-xform.js +51 -2
- package/dist/esm/modules/excel/xlsx/xform/sheet/worksheet-xform.js +196 -20
- package/dist/esm/modules/excel/xlsx/xform/table/auto-filter-xform.js +16 -1
- package/dist/esm/modules/excel/xlsx/xform/table/table-column-xform.js +17 -2
- package/dist/esm/modules/excel/xlsx/xform/xsd-values.js +101 -0
- package/dist/esm/modules/excel/xlsx/xlsx.browser.js +4422 -78
- package/dist/esm/modules/pdf/builder/document-builder.js +507 -2
- package/dist/esm/modules/pdf/builder/pdf-editor.js +48 -3
- package/dist/esm/modules/pdf/excel-bridge.js +683 -12
- package/dist/esm/modules/pdf/font/font-manager.js +39 -0
- package/dist/esm/modules/pdf/index.js +3 -1
- package/dist/esm/modules/pdf/render/chart-surface.js +200 -0
- package/dist/esm/modules/pdf/render/layout-engine.js +436 -56
- package/dist/esm/modules/pdf/render/page-renderer.js +169 -28
- package/dist/esm/modules/pdf/render/pdf-exporter.js +117 -7
- package/dist/esm/modules/pdf/types.js +4 -0
- package/dist/esm/modules/pdf/word-bridge.js +304 -0
- package/dist/esm/modules/word/constants.js +231 -0
- package/dist/esm/modules/word/content-types.js +53 -0
- package/dist/esm/modules/word/digital-signatures.js +134 -0
- package/dist/esm/modules/word/document.js +1795 -0
- package/dist/esm/modules/word/docx-packager.js +822 -0
- package/dist/esm/modules/word/docx-reader.js +4929 -0
- package/dist/esm/modules/word/encryption.js +274 -0
- package/dist/esm/modules/word/errors.js +68 -0
- package/dist/esm/modules/word/font-obfuscation.js +83 -0
- package/dist/esm/modules/word/html-renderer.js +782 -0
- package/dist/esm/modules/word/index.base.js +51 -0
- package/dist/esm/modules/word/index.browser.js +4 -0
- package/dist/esm/modules/word/index.js +4 -0
- package/dist/esm/modules/word/internal-utils.js +54 -0
- package/dist/esm/modules/word/relationships.js +56 -0
- package/dist/esm/modules/word/types.js +10 -0
- package/dist/esm/modules/word/units.js +111 -0
- package/dist/esm/modules/word/writers/chart-writer.js +385 -0
- package/dist/esm/modules/word/writers/checkbox-writer.js +42 -0
- package/dist/esm/modules/word/writers/comment-writer.js +70 -0
- package/dist/esm/modules/word/writers/document-writer.js +461 -0
- package/dist/esm/modules/word/writers/footnote-writer.js +72 -0
- package/dist/esm/modules/word/writers/header-footer-writer.js +129 -0
- package/dist/esm/modules/word/writers/image-writer.js +185 -0
- package/dist/esm/modules/word/writers/math-writer.js +428 -0
- package/dist/esm/modules/word/writers/numbering-writer.js +125 -0
- package/dist/esm/modules/word/writers/paragraph-writer.js +516 -0
- package/dist/esm/modules/word/writers/parts-writer.js +660 -0
- package/dist/esm/modules/word/writers/run-writer.js +649 -0
- package/dist/esm/modules/word/writers/section-writer.js +238 -0
- package/dist/esm/modules/word/writers/styles-writer.js +242 -0
- package/dist/esm/modules/word/writers/table-writer.js +503 -0
- package/dist/esm/modules/word/writers/textbox-writer.js +53 -0
- package/dist/esm/modules/word/writers/toc-writer.js +79 -0
- package/dist/esm/modules/xml/encode.js +157 -11
- package/dist/iife/excelts.iife.js +11789 -687
- package/dist/iife/excelts.iife.js.map +1 -1
- package/dist/iife/excelts.iife.min.js +52 -44
- package/dist/types/index.browser.d.ts +8 -5
- package/dist/types/index.d.ts +4 -2
- package/dist/types/modules/excel/chart/cache-populator.d.ts +49 -0
- package/dist/types/modules/excel/chart/chart-api.d.ts +92 -0
- package/dist/types/modules/excel/chart/chart-builder.d.ts +48 -0
- package/dist/types/modules/excel/chart/chart-ex-builder.d.ts +36 -0
- package/dist/types/modules/excel/chart/chart-ex-parser.d.ts +8 -0
- package/dist/types/modules/excel/chart/chart-ex-renderer.d.ts +187 -0
- package/dist/types/modules/excel/chart/chart-ex-types.d.ts +531 -0
- package/dist/types/modules/excel/chart/chart-images.d.ts +78 -0
- package/dist/types/modules/excel/chart/chart-presets.d.ts +392 -0
- package/dist/types/modules/excel/chart/chart-renderer.d.ts +550 -0
- package/dist/types/modules/excel/chart/chart-sidecar.d.ts +21 -0
- package/dist/types/modules/excel/chart/chart-utils.d.ts +306 -0
- package/dist/types/modules/excel/chart/chart.d.ts +504 -0
- package/dist/types/modules/excel/chart/glyph-rasterizer.d.ts +62 -0
- package/dist/types/modules/excel/chart/index.d.ts +54 -0
- package/dist/types/modules/excel/chart/install.d.ts +44 -0
- package/dist/types/modules/excel/chart/shape-properties.d.ts +156 -0
- package/dist/types/modules/excel/chart/stroke-font.d.ts +36 -0
- package/dist/types/modules/excel/chart/topojson.d.ts +98 -0
- package/dist/types/modules/excel/chart/types.d.ts +2559 -0
- package/dist/types/modules/excel/chart-host-registry.d.ts +157 -0
- package/dist/types/modules/excel/chartsheet.d.ts +102 -0
- package/dist/types/modules/excel/defined-names.d.ts +35 -0
- package/dist/types/modules/excel/errors.d.ts +6 -0
- package/dist/types/modules/excel/form-control.d.ts +6 -0
- package/dist/types/modules/excel/pivot-chart.d.ts +7 -0
- package/dist/types/modules/excel/pivot-table.d.ts +55 -0
- package/dist/types/modules/excel/sparkline/index.d.ts +7 -0
- package/dist/types/modules/excel/sparkline/sparkline.d.ts +206 -0
- package/dist/types/modules/excel/types.d.ts +72 -0
- package/dist/types/modules/excel/utils/address.d.ts +18 -0
- package/dist/types/modules/excel/utils/guid.d.ts +15 -0
- package/dist/types/modules/excel/utils/ooxml-paths.d.ts +74 -0
- package/dist/types/modules/excel/utils/ooxml-validator/check-chart-sidecar.d.ts +35 -0
- package/dist/types/modules/excel/utils/ooxml-validator/check-chart.d.ts +32 -0
- package/dist/types/modules/excel/utils/ooxml-validator/check-chartsheet.d.ts +9 -0
- package/dist/types/modules/excel/utils/ooxml-validator/check-content-types.d.ts +16 -0
- package/dist/types/modules/excel/utils/ooxml-validator/check-drawing.d.ts +34 -0
- package/dist/types/modules/excel/utils/ooxml-validator/check-pivot.d.ts +14 -0
- package/dist/types/modules/excel/utils/ooxml-validator/check-relationships.d.ts +18 -0
- package/dist/types/modules/excel/utils/ooxml-validator/check-structure.d.ts +21 -0
- package/dist/types/modules/excel/utils/ooxml-validator/check-styles.d.ts +15 -0
- package/dist/types/modules/excel/utils/ooxml-validator/check-table.d.ts +31 -0
- package/dist/types/modules/excel/utils/ooxml-validator/check-workbook.d.ts +19 -0
- package/dist/types/modules/excel/utils/ooxml-validator/check-worksheet.d.ts +25 -0
- package/dist/types/modules/excel/utils/ooxml-validator/context.d.ts +85 -0
- package/dist/types/modules/excel/utils/ooxml-validator/index.d.ts +31 -0
- package/dist/types/modules/excel/utils/ooxml-validator/path-utils.d.ts +67 -0
- package/dist/types/modules/excel/utils/ooxml-validator/reporter.d.ts +41 -0
- package/dist/types/modules/excel/utils/ooxml-validator/types.d.ts +109 -0
- package/dist/types/modules/excel/utils/ooxml-validator/xml-utils.d.ts +38 -0
- package/dist/types/modules/excel/workbook.browser.d.ts +248 -30
- package/dist/types/modules/excel/workbook.d.ts +43 -0
- package/dist/types/modules/excel/worksheet.d.ts +157 -3
- package/dist/types/modules/excel/xlsx/rel-type.d.ts +40 -0
- package/dist/types/modules/excel/xlsx/xform/book/defined-name-xform.d.ts +1 -0
- package/dist/types/modules/excel/xlsx/xform/chart/chart-space-xform.d.ts +353 -0
- package/dist/types/modules/excel/xlsx/xform/comment/threaded-comments-xform.d.ts +60 -0
- package/dist/types/modules/excel/xlsx/xform/drawing/drawing-xform.d.ts +30 -0
- package/dist/types/modules/excel/xlsx/xform/drawing/graphic-frame-xform.d.ts +54 -0
- package/dist/types/modules/excel/xlsx/xform/drawing/one-cell-anchor-xform.d.ts +3 -1
- package/dist/types/modules/excel/xlsx/xform/drawing/two-cell-anchor-xform.d.ts +46 -0
- package/dist/types/modules/excel/xlsx/xform/pivot-table/pivot-table-xform.d.ts +13 -2
- package/dist/types/modules/excel/xlsx/xform/sheet/chartsheet-xform.d.ts +185 -0
- package/dist/types/modules/excel/xlsx/xform/sheet/ext-lst-xform.d.ts +1 -0
- package/dist/types/modules/excel/xlsx/xform/xsd-values.d.ts +63 -0
- package/dist/types/modules/excel/xlsx/xlsx.browser.d.ts +115 -21
- package/dist/types/modules/pdf/builder/document-builder.d.ts +74 -0
- package/dist/types/modules/pdf/excel-bridge.d.ts +69 -0
- package/dist/types/modules/pdf/font/font-manager.d.ts +25 -0
- package/dist/types/modules/pdf/index.d.ts +5 -2
- package/dist/types/modules/pdf/render/chart-surface.d.ts +33 -0
- package/dist/types/modules/pdf/render/layout-engine.d.ts +22 -1
- package/dist/types/modules/pdf/types.d.ts +227 -23
- package/dist/types/modules/pdf/word-bridge.d.ts +47 -0
- package/dist/types/modules/word/constants.d.ts +179 -0
- package/dist/types/modules/word/content-types.d.ts +27 -0
- package/dist/types/modules/word/digital-signatures.d.ts +87 -0
- package/dist/types/modules/word/document.d.ts +728 -0
- package/dist/types/modules/word/docx-packager.d.ts +14 -0
- package/dist/types/modules/word/docx-reader.d.ts +11 -0
- package/dist/types/modules/word/encryption.d.ts +102 -0
- package/dist/types/modules/word/errors.d.ts +49 -0
- package/dist/types/modules/word/font-obfuscation.d.ts +31 -0
- package/dist/types/modules/word/html-renderer.d.ts +38 -0
- package/dist/types/modules/word/index.base.d.ts +19 -0
- package/dist/types/modules/word/index.browser.d.ts +4 -0
- package/dist/types/modules/word/index.d.ts +4 -0
- package/dist/types/modules/word/internal-utils.d.ts +23 -0
- package/dist/types/modules/word/relationships.d.ts +31 -0
- package/dist/types/modules/word/types.d.ts +2325 -0
- package/dist/types/modules/word/units.d.ts +49 -0
- package/dist/types/modules/word/writers/chart-writer.d.ts +10 -0
- package/dist/types/modules/word/writers/checkbox-writer.d.ts +9 -0
- package/dist/types/modules/word/writers/comment-writer.d.ts +15 -0
- package/dist/types/modules/word/writers/document-writer.d.ts +16 -0
- package/dist/types/modules/word/writers/footnote-writer.d.ts +11 -0
- package/dist/types/modules/word/writers/header-footer-writer.d.ts +13 -0
- package/dist/types/modules/word/writers/image-writer.d.ts +10 -0
- package/dist/types/modules/word/writers/math-writer.d.ts +9 -0
- package/dist/types/modules/word/writers/numbering-writer.d.ts +10 -0
- package/dist/types/modules/word/writers/paragraph-writer.d.ts +13 -0
- package/dist/types/modules/word/writers/parts-writer.d.ts +26 -0
- package/dist/types/modules/word/writers/run-writer.d.ts +15 -0
- package/dist/types/modules/word/writers/section-writer.d.ts +10 -0
- package/dist/types/modules/word/writers/styles-writer.d.ts +10 -0
- package/dist/types/modules/word/writers/table-writer.d.ts +10 -0
- package/dist/types/modules/word/writers/textbox-writer.d.ts +9 -0
- package/dist/types/modules/word/writers/toc-writer.d.ts +9 -0
- package/dist/types/modules/xml/encode.d.ts +56 -7
- package/package.json +29 -11
- package/dist/browser/modules/excel/utils/ooxml-validator.d.ts +0 -48
- package/dist/browser/modules/excel/utils/ooxml-validator.js +0 -493
- package/dist/browser/modules/excel/utils/passthrough-manager.d.ts +0 -77
- package/dist/browser/modules/excel/utils/passthrough-manager.js +0 -129
- package/dist/cjs/modules/excel/utils/ooxml-validator.js +0 -499
- package/dist/cjs/modules/excel/utils/passthrough-manager.js +0 -133
- package/dist/esm/modules/excel/utils/ooxml-validator.js +0 -493
- package/dist/esm/modules/excel/utils/passthrough-manager.js +0 -129
- package/dist/types/modules/excel/utils/ooxml-validator.d.ts +0 -48
- package/dist/types/modules/excel/utils/passthrough-manager.d.ts +0 -77
|
@@ -0,0 +1,2432 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chart Builder - Constructs a ChartModel from simplified AddChartOptions.
|
|
3
|
+
*
|
|
4
|
+
* This bridges the high-level API (worksheet.addChart) to the full
|
|
5
|
+
* OOXML chart model that the XForm layer serialises.
|
|
6
|
+
*/
|
|
7
|
+
import { ChartOptionsError } from "../errors.js";
|
|
8
|
+
import { escapeXml } from "./chart-utils.js";
|
|
9
|
+
const EMU_PER_POINT = 12700;
|
|
10
|
+
const DEFAULT_AXIS_START_ID = 100000000;
|
|
11
|
+
const AXIS_CHART_TYPES = new Set([
|
|
12
|
+
"bar",
|
|
13
|
+
"bar3D",
|
|
14
|
+
"line",
|
|
15
|
+
"line3D",
|
|
16
|
+
"area",
|
|
17
|
+
"area3D",
|
|
18
|
+
"scatter",
|
|
19
|
+
"bubble",
|
|
20
|
+
"radar",
|
|
21
|
+
"stock",
|
|
22
|
+
"surface",
|
|
23
|
+
"surface3D"
|
|
24
|
+
]);
|
|
25
|
+
const NO_TRENDLINE_CHART_TYPES = new Set([
|
|
26
|
+
"pie",
|
|
27
|
+
"pie3D",
|
|
28
|
+
"doughnut",
|
|
29
|
+
"ofPie",
|
|
30
|
+
"surface",
|
|
31
|
+
"surface3D"
|
|
32
|
+
]);
|
|
33
|
+
const PIE_FAMILY_CHART_TYPES = new Set([
|
|
34
|
+
"pie",
|
|
35
|
+
"pie3D",
|
|
36
|
+
"doughnut",
|
|
37
|
+
"ofPie"
|
|
38
|
+
]);
|
|
39
|
+
/**
|
|
40
|
+
* Valid `c:dLblPos` values Excel accepts per chart type.
|
|
41
|
+
*
|
|
42
|
+
* Although ECMA-376 `ST_DLblPos` technically allows any of the nine
|
|
43
|
+
* values (`b | bestFit | ctr | inBase | inEnd | l | outEnd | r | t`)
|
|
44
|
+
* on any `c:dLbl` / `c:dLbls` element, Excel's reader is stricter:
|
|
45
|
+
* emitting a value outside the per-chart-type allow-list below
|
|
46
|
+
* triggers "Repaired Records: Drawing" warnings on open, and for
|
|
47
|
+
* doughnut charts the offending `drawing*.xml` part is stripped
|
|
48
|
+
* entirely ("Removed Part"). The allow-lists match the options
|
|
49
|
+
* surfaced by Excel 365's "Format Data Labels → Label Position"
|
|
50
|
+
* panel, which is the canonical UI reference.
|
|
51
|
+
*
|
|
52
|
+
* - `doughnut`: Excel's UI exposes no position choices at all, and
|
|
53
|
+
* any `c:dLblPos` in a doughnut chart's `c:dLbls` causes the
|
|
54
|
+
* entire drawing part to be removed on open. Use an empty list so
|
|
55
|
+
* the validator rejects every value.
|
|
56
|
+
* - `bar` / `bar3D`: `inBase` is unique to bar — it anchors the
|
|
57
|
+
* label to the axis end of a column, useful for negative values.
|
|
58
|
+
* - `pie`, `pie3D`, `ofPie`: share the pie label set. `bestFit` is
|
|
59
|
+
* Excel's default and the only value that lets Excel place labels
|
|
60
|
+
* automatically with leader lines.
|
|
61
|
+
* - `line` / `line3D` / `scatter` / `bubble` / `radar` / `stock`:
|
|
62
|
+
* share the cartesian label set (above / below / left / right /
|
|
63
|
+
* center).
|
|
64
|
+
* - `area` / `area3D`: Excel only accepts `ctr` for area fills.
|
|
65
|
+
* - `surface` / `surface3D`: data labels are already rejected
|
|
66
|
+
* wholesale by `validateChartLevelOptions`.
|
|
67
|
+
*/
|
|
68
|
+
const VALID_DLBL_POSITIONS_BY_TYPE = {
|
|
69
|
+
bar: new Set(["ctr", "inBase", "inEnd", "outEnd"]),
|
|
70
|
+
bar3D: new Set(["ctr", "inBase", "inEnd", "outEnd"]),
|
|
71
|
+
line: new Set(["ctr", "l", "r", "t", "b"]),
|
|
72
|
+
line3D: new Set(["ctr", "l", "r", "t", "b"]),
|
|
73
|
+
scatter: new Set(["ctr", "l", "r", "t", "b"]),
|
|
74
|
+
bubble: new Set(["ctr", "l", "r", "t", "b"]),
|
|
75
|
+
radar: new Set(["ctr", "l", "r", "t", "b"]),
|
|
76
|
+
stock: new Set(["ctr", "l", "r", "t", "b"]),
|
|
77
|
+
pie: new Set(["bestFit", "ctr", "inEnd", "outEnd"]),
|
|
78
|
+
pie3D: new Set(["bestFit", "ctr", "inEnd", "outEnd"]),
|
|
79
|
+
ofPie: new Set(["bestFit", "ctr", "inEnd", "outEnd"]),
|
|
80
|
+
doughnut: new Set(),
|
|
81
|
+
area: new Set(["ctr"]),
|
|
82
|
+
area3D: new Set(["ctr"])
|
|
83
|
+
};
|
|
84
|
+
/**
|
|
85
|
+
* Simple axis ID allocator — scoped per buildChartModel call.
|
|
86
|
+
* Axis IDs only need to be unique within a single chart.
|
|
87
|
+
*/
|
|
88
|
+
class AxIdAllocator {
|
|
89
|
+
constructor(start = DEFAULT_AXIS_START_ID) {
|
|
90
|
+
this.next = start;
|
|
91
|
+
}
|
|
92
|
+
alloc() {
|
|
93
|
+
return this.next++;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function makeNumRef(formula) {
|
|
97
|
+
return { formula, cache: { points: [] } };
|
|
98
|
+
}
|
|
99
|
+
function makeStrRef(formula) {
|
|
100
|
+
return { formula, cache: { points: [] } };
|
|
101
|
+
}
|
|
102
|
+
function makeNumData(formula) {
|
|
103
|
+
return { numRef: makeNumRef(formula) };
|
|
104
|
+
}
|
|
105
|
+
function makeCatData(formula) {
|
|
106
|
+
return { strRef: makeStrRef(formula) };
|
|
107
|
+
}
|
|
108
|
+
function makeAxisData(input) {
|
|
109
|
+
return typeof input === "string" ? makeCatData(input) : input;
|
|
110
|
+
}
|
|
111
|
+
function makeNumericAxisData(input) {
|
|
112
|
+
return typeof input === "string" ? { numRef: makeNumRef(input) } : input;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Wrap a scatter/bubble `xValues` input as the appropriate
|
|
116
|
+
* {@link AxisDataSource}. `xValueType` disambiguates the two OOXML
|
|
117
|
+
* spellings:
|
|
118
|
+
*
|
|
119
|
+
* - `"number"` (default) → `numRef` — standard scatter usage
|
|
120
|
+
* - `"text"` → `strRef` — labelled scatter / bubble with
|
|
121
|
+
* categorical x axis (Excel renders it as evenly-spaced labels)
|
|
122
|
+
*
|
|
123
|
+
* When the caller passes a pre-built `AxisDataSource`, the hint is
|
|
124
|
+
* ignored — the structure already carries the intent.
|
|
125
|
+
*/
|
|
126
|
+
function makeXAxisData(input, xValueType) {
|
|
127
|
+
if (typeof input !== "string") {
|
|
128
|
+
return input;
|
|
129
|
+
}
|
|
130
|
+
return xValueType === "text" ? makeCatData(input) : makeNumericAxisData(input);
|
|
131
|
+
}
|
|
132
|
+
function assertChartOptions(condition, message) {
|
|
133
|
+
if (!condition) {
|
|
134
|
+
throw new ChartOptionsError(message);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
function assertFiniteNumber(value, path) {
|
|
138
|
+
assertChartOptions(typeof value === "number" && Number.isFinite(value), `${path} must be a finite number.`);
|
|
139
|
+
}
|
|
140
|
+
function assertIntegerInRange(value, path, min, max) {
|
|
141
|
+
assertFiniteNumber(value, path);
|
|
142
|
+
assertChartOptions(Number.isInteger(value) && value >= min && value <= max, `${path} must be an integer between ${min} and ${max}.`);
|
|
143
|
+
}
|
|
144
|
+
function assertNumberInRange(value, path, min, max) {
|
|
145
|
+
assertFiniteNumber(value, path);
|
|
146
|
+
assertChartOptions(value >= min && value <= max, `${path} must be between ${min} and ${max}.`);
|
|
147
|
+
}
|
|
148
|
+
function validateChartOptions(opts, path = "chart") {
|
|
149
|
+
assertChartOptions(!!opts && typeof opts === "object", `${path} options are required.`);
|
|
150
|
+
assertChartOptions(!!opts.type, `${path}.type is required.`);
|
|
151
|
+
validateChartLevelOptions(opts, path);
|
|
152
|
+
const series = opts.series ?? [];
|
|
153
|
+
// A chart with zero series is invalid — Excel will either refuse to
|
|
154
|
+
// open the file or render a broken chart area. Catch it at build
|
|
155
|
+
// time with a precise error instead of deferring to Excel.
|
|
156
|
+
assertChartOptions(series.length > 0, `${path}.series must contain at least one series.`);
|
|
157
|
+
for (let i = 0; i < series.length; i++) {
|
|
158
|
+
validateSeriesOptions(opts.type, series[i], `${path}.series[${i}]`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
function validateComboChartOptions(opts) {
|
|
162
|
+
assertChartOptions(!!opts && typeof opts === "object", "combo chart options are required.");
|
|
163
|
+
assertChartOptions(Array.isArray(opts.groups) && opts.groups.length > 0, "combo chart groups must contain at least one group.");
|
|
164
|
+
for (let i = 0; i < opts.groups.length; i++) {
|
|
165
|
+
validateChartOptions(opts.groups[i], `groups[${i}]`);
|
|
166
|
+
}
|
|
167
|
+
validateSharedChartOptions(opts, "combo chart");
|
|
168
|
+
}
|
|
169
|
+
function validateChartLevelOptions(opts, path) {
|
|
170
|
+
validateSharedChartOptions(opts, path);
|
|
171
|
+
if (opts.grouping !== undefined) {
|
|
172
|
+
assertChartOptions(opts.type === "bar" ||
|
|
173
|
+
opts.type === "bar3D" ||
|
|
174
|
+
opts.type === "line" ||
|
|
175
|
+
opts.type === "line3D" ||
|
|
176
|
+
opts.type === "area" ||
|
|
177
|
+
opts.type === "area3D", `${path}.grouping is only valid for bar, line, and area charts.`);
|
|
178
|
+
// `BarGrouping` and `LineGrouping` overlap on stacked/percentStacked but
|
|
179
|
+
// diverge on `clustered` (bar only) and `standard` (line/area only).
|
|
180
|
+
// Reject the wrong-family value up front instead of silently emitting
|
|
181
|
+
// invalid OOXML.
|
|
182
|
+
const g = opts.grouping;
|
|
183
|
+
if (opts.type === "bar" || opts.type === "bar3D") {
|
|
184
|
+
assertChartOptions(g === "clustered" || g === "stacked" || g === "percentStacked", `${path}.grouping=${JSON.stringify(g)} is not valid for bar charts (use "clustered" | "stacked" | "percentStacked").`);
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
assertChartOptions(g === "standard" || g === "stacked" || g === "percentStacked", `${path}.grouping=${JSON.stringify(g)} is not valid for ${opts.type} charts (use "standard" | "stacked" | "percentStacked").`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if (opts.barDir !== undefined) {
|
|
191
|
+
assertChartOptions(opts.type === "bar" || opts.type === "bar3D", `${path}.barDir is only valid for bar and bar3D charts.`);
|
|
192
|
+
}
|
|
193
|
+
if (opts.scatterStyle !== undefined) {
|
|
194
|
+
assertChartOptions(opts.type === "scatter", `${path}.scatterStyle is only valid for scatter charts.`);
|
|
195
|
+
}
|
|
196
|
+
if (opts.radarStyle !== undefined) {
|
|
197
|
+
assertChartOptions(opts.type === "radar", `${path}.radarStyle is only valid for radar charts.`);
|
|
198
|
+
}
|
|
199
|
+
if (opts.ofPieType !== undefined) {
|
|
200
|
+
assertChartOptions(opts.type === "ofPie", `${path}.ofPieType is only valid for ofPie charts.`);
|
|
201
|
+
}
|
|
202
|
+
if (opts.wireframe !== undefined) {
|
|
203
|
+
assertChartOptions(opts.type === "surface" || opts.type === "surface3D", `${path}.wireframe is only valid for surface and surface3D charts.`);
|
|
204
|
+
}
|
|
205
|
+
if (opts.bandFormats !== undefined) {
|
|
206
|
+
assertChartOptions(opts.type === "surface" || opts.type === "surface3D", `${path}.bandFormats is only valid for surface and surface3D charts.`);
|
|
207
|
+
for (let i = 0; i < opts.bandFormats.length; i++) {
|
|
208
|
+
assertIntegerInRange(opts.bandFormats[i].index, `${path}.bandFormats[${i}].index`, 0, Number.MAX_SAFE_INTEGER);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
if (!AXIS_CHART_TYPES.has(opts.type)) {
|
|
212
|
+
assertChartOptions(opts.categoryAxis === undefined, `${path}.categoryAxis is not valid for ${opts.type} charts because they do not have axes.`);
|
|
213
|
+
assertChartOptions(opts.valueAxis === undefined, `${path}.valueAxis is not valid for ${opts.type} charts because they do not have axes.`);
|
|
214
|
+
}
|
|
215
|
+
if ((opts.type === "surface" || opts.type === "surface3D") && opts.dataLabels !== undefined) {
|
|
216
|
+
assertChartOptions(false, `${path}.dataLabels is not supported for surface charts.`);
|
|
217
|
+
}
|
|
218
|
+
if (opts.dataLabels) {
|
|
219
|
+
// Chart-level data labels mirror the series-level ones at the
|
|
220
|
+
// `CT_*Chart/c:dLbls` slot (e.g. `CT_BarChart/c:dLbls`). Excel
|
|
221
|
+
// applies the same per-chart-type restrictions on `c:dLblPos`
|
|
222
|
+
// at this level, so run the same validator here.
|
|
223
|
+
validateDataLabelsOptions(opts.type, opts.dataLabels, `${path}.dataLabels`);
|
|
224
|
+
}
|
|
225
|
+
if (opts.holeSize !== undefined) {
|
|
226
|
+
assertChartOptions(opts.type === "doughnut", `${path}.holeSize is only valid for doughnut charts.`);
|
|
227
|
+
assertIntegerInRange(opts.holeSize, `${path}.holeSize`, 0, 90);
|
|
228
|
+
}
|
|
229
|
+
if (opts.firstSliceAng !== undefined) {
|
|
230
|
+
assertChartOptions(PIE_FAMILY_CHART_TYPES.has(opts.type), `${path}.firstSliceAng is only valid for pie, doughnut, and ofPie charts.`);
|
|
231
|
+
assertIntegerInRange(opts.firstSliceAng, `${path}.firstSliceAng`, 0, 360);
|
|
232
|
+
}
|
|
233
|
+
if (opts.gapWidth !== undefined) {
|
|
234
|
+
assertChartOptions(opts.type === "bar" || opts.type === "bar3D" || opts.type === "ofPie", `${path}.gapWidth is only valid for bar, bar3D, and ofPie charts.`);
|
|
235
|
+
assertIntegerInRange(opts.gapWidth, `${path}.gapWidth`, 0, 500);
|
|
236
|
+
}
|
|
237
|
+
if (opts.gapDepth !== undefined) {
|
|
238
|
+
// `c:gapDepth` is only declared on the 3-D chart types'
|
|
239
|
+
// `CT_*3DChart` definitions (bar3D, line3D, area3D). `pie3D`
|
|
240
|
+
// does NOT have it despite the type being 3-D.
|
|
241
|
+
assertChartOptions(opts.type === "bar3D" || opts.type === "line3D" || opts.type === "area3D", `${path}.gapDepth is only valid for bar3D, line3D, and area3D charts.`);
|
|
242
|
+
assertIntegerInRange(opts.gapDepth, `${path}.gapDepth`, 0, 500);
|
|
243
|
+
}
|
|
244
|
+
if (opts.overlap !== undefined) {
|
|
245
|
+
// `c:overlap` belongs to `CT_BarChart` only — `CT_Bar3DChart`
|
|
246
|
+
// omits it. The writer rejects it on bar3D (see
|
|
247
|
+
// `buildChartTypeGroup:case "bar3D"`); keep the two in sync so
|
|
248
|
+
// the rejection surfaces here at authoring time with a
|
|
249
|
+
// consistent error.
|
|
250
|
+
assertChartOptions(opts.type === "bar", `${path}.overlap is only valid for 2-D bar charts (bar3D rejects overlap per CT_Bar3DChart).`);
|
|
251
|
+
assertIntegerInRange(opts.overlap, `${path}.overlap`, -100, 100);
|
|
252
|
+
}
|
|
253
|
+
if (opts.bubbleScale !== undefined) {
|
|
254
|
+
assertChartOptions(opts.type === "bubble", `${path}.bubbleScale is only valid for bubble charts.`);
|
|
255
|
+
assertIntegerInRange(opts.bubbleScale, `${path}.bubbleScale`, 0, 300);
|
|
256
|
+
}
|
|
257
|
+
if (opts.showNegBubbles !== undefined) {
|
|
258
|
+
assertChartOptions(opts.type === "bubble", `${path}.showNegBubbles is only valid for bubble charts.`);
|
|
259
|
+
}
|
|
260
|
+
if (opts.sizeRepresents !== undefined) {
|
|
261
|
+
assertChartOptions(opts.type === "bubble", `${path}.sizeRepresents is only valid for bubble charts.`);
|
|
262
|
+
}
|
|
263
|
+
if (opts.splitPos !== undefined) {
|
|
264
|
+
assertChartOptions(opts.type === "ofPie", `${path}.splitPos is only valid for ofPie charts.`);
|
|
265
|
+
assertFiniteNumber(opts.splitPos, `${path}.splitPos`);
|
|
266
|
+
}
|
|
267
|
+
if (opts.secondPieSize !== undefined) {
|
|
268
|
+
assertChartOptions(opts.type === "ofPie", `${path}.secondPieSize is only valid for ofPie charts.`);
|
|
269
|
+
assertIntegerInRange(opts.secondPieSize, `${path}.secondPieSize`, 5, 200);
|
|
270
|
+
}
|
|
271
|
+
if (opts.shape !== undefined) {
|
|
272
|
+
assertChartOptions(opts.type === "bar3D", `${path}.shape is only valid for bar3D charts.`);
|
|
273
|
+
}
|
|
274
|
+
if (opts.showMarker !== undefined) {
|
|
275
|
+
assertChartOptions(opts.type === "line" || opts.type === "radar", `${path}.showMarker is only valid for line and radar charts (line3D does not support markers).`);
|
|
276
|
+
}
|
|
277
|
+
if (opts.smooth !== undefined) {
|
|
278
|
+
assertChartOptions(opts.type === "line" || opts.type === "scatter", `${path}.smooth is only valid for line and scatter charts (line3D does not support smooth).`);
|
|
279
|
+
}
|
|
280
|
+
if (opts.hiLowLines !== undefined) {
|
|
281
|
+
assertChartOptions(opts.type === "line" || opts.type === "stock", `${path}.hiLowLines is only valid for line and stock charts (line3D does not support hiLowLines).`);
|
|
282
|
+
}
|
|
283
|
+
if (opts.upDownBars !== undefined) {
|
|
284
|
+
assertChartOptions(opts.type === "line" || opts.type === "stock", `${path}.upDownBars is only valid for line and stock charts (line3D does not support upDownBars).`);
|
|
285
|
+
}
|
|
286
|
+
if (opts.dropLines !== undefined) {
|
|
287
|
+
assertChartOptions(opts.type === "line" ||
|
|
288
|
+
opts.type === "line3D" ||
|
|
289
|
+
opts.type === "area" ||
|
|
290
|
+
opts.type === "area3D" ||
|
|
291
|
+
opts.type === "stock", `${path}.dropLines is only valid for line, area, and stock charts.`);
|
|
292
|
+
}
|
|
293
|
+
if (opts.serLines !== undefined) {
|
|
294
|
+
assertChartOptions(opts.type === "bar" || opts.type === "ofPie", `${path}.serLines is only valid for bar and ofPie charts.`);
|
|
295
|
+
}
|
|
296
|
+
validateAxisOptions(opts.categoryAxis, `${path}.categoryAxis`);
|
|
297
|
+
validateAxisOptions(opts.valueAxis, `${path}.valueAxis`);
|
|
298
|
+
}
|
|
299
|
+
function validateSharedChartOptions(opts, path) {
|
|
300
|
+
if (opts.style !== undefined) {
|
|
301
|
+
assertIntegerInRange(opts.style, `${path}.style`, 1, 48);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
function validateSeriesOptions(chartType, opts, path, flags = {}) {
|
|
305
|
+
const allowMissingRefs = flags.allowMissingRefs === true;
|
|
306
|
+
assertChartOptions(!!opts && typeof opts === "object", `${path} must be an object.`);
|
|
307
|
+
if (allowMissingRefs) {
|
|
308
|
+
if (opts.values !== undefined) {
|
|
309
|
+
assertChartOptions(typeof opts.values === "string" && opts.values.length > 0, `${path}.values must be a non-empty formula string.`);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
else {
|
|
313
|
+
assertChartOptions(typeof opts.values === "string" && opts.values.length > 0, `${path}.values is required and must be a non-empty formula string.`);
|
|
314
|
+
}
|
|
315
|
+
if (opts.trendline !== undefined) {
|
|
316
|
+
assertChartOptions(!NO_TRENDLINE_CHART_TYPES.has(chartType), `${path}.trendline is not valid for ${chartType} charts.`);
|
|
317
|
+
const trendlines = Array.isArray(opts.trendline) ? opts.trendline : [opts.trendline];
|
|
318
|
+
for (let i = 0; i < trendlines.length; i++) {
|
|
319
|
+
validateTrendlineOptions(trendlines[i], `${path}.trendline${trendlines.length > 1 ? `[${i}]` : ""}`);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
if (opts.marker?.size !== undefined) {
|
|
323
|
+
assertIntegerInRange(opts.marker.size, `${path}.marker.size`, 2, 72);
|
|
324
|
+
}
|
|
325
|
+
if (opts.explosion !== undefined) {
|
|
326
|
+
assertChartOptions(PIE_FAMILY_CHART_TYPES.has(chartType), `${path}.explosion is only valid for pie, doughnut, and ofPie charts.`);
|
|
327
|
+
assertIntegerInRange(opts.explosion, `${path}.explosion`, 0, 400);
|
|
328
|
+
}
|
|
329
|
+
if (opts.bubble3D !== undefined) {
|
|
330
|
+
assertChartOptions(chartType === "bubble", `${path}.bubble3D is only valid for bubble charts.`);
|
|
331
|
+
}
|
|
332
|
+
if (opts.bubbleSize !== undefined) {
|
|
333
|
+
assertChartOptions(chartType === "bubble", `${path}.bubbleSize is only valid for bubble charts.`);
|
|
334
|
+
}
|
|
335
|
+
if (chartType === "bubble" && !allowMissingRefs) {
|
|
336
|
+
assertChartOptions(opts.xValues !== undefined, `${path}.xValues is required for bubble charts. Use a numeric range for x-values.`);
|
|
337
|
+
assertChartOptions(opts.bubbleSize !== undefined, `${path}.bubbleSize is required for bubble charts. Use a numeric range for bubble sizes.`);
|
|
338
|
+
}
|
|
339
|
+
if (chartType === "scatter" && !allowMissingRefs) {
|
|
340
|
+
assertChartOptions(opts.xValues !== undefined, `${path}.xValues is required for scatter charts. Use a numeric range for x-values.`);
|
|
341
|
+
}
|
|
342
|
+
if (opts.xValues !== undefined) {
|
|
343
|
+
assertChartOptions(chartType === "scatter" || chartType === "bubble", `${path}.xValues is only valid for scatter and bubble charts.`);
|
|
344
|
+
}
|
|
345
|
+
// Note on `categories` for scatter / bubble: the field has no direct
|
|
346
|
+
// home on `ScatterSeries` / `BubbleSeries`, which use `xVal` for the
|
|
347
|
+
// numeric X axis. `buildScatterSeries` / `buildBubbleSeries` silently
|
|
348
|
+
// ignore `opts.categories` at create time (the `xVal` slot is already
|
|
349
|
+
// populated from `opts.xValues`). On the patch path
|
|
350
|
+
// (`applyChartSeriesOptionsPatch`), `options.categories` is routed to
|
|
351
|
+
// `xVal` so callers can switch a scatter's X source to a text axis
|
|
352
|
+
// via `{ categories, xValueType: "text" }`. Accepting the field on
|
|
353
|
+
// creation preserves API symmetry (users can pass the same option
|
|
354
|
+
// bundle across chart types in test fixtures) at the cost of a silent
|
|
355
|
+
// drop for this specific combination.
|
|
356
|
+
if (opts.dataPoints) {
|
|
357
|
+
for (let i = 0; i < opts.dataPoints.length; i++) {
|
|
358
|
+
validateDataPointOptions(chartType, opts.dataPoints[i], `${path}.dataPoints[${i}]`);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
if (opts.dataLabels) {
|
|
362
|
+
validateDataLabelsOptions(chartType, opts.dataLabels, `${path}.dataLabels`);
|
|
363
|
+
}
|
|
364
|
+
if (opts.errorBars) {
|
|
365
|
+
assertChartOptions(!PIE_FAMILY_CHART_TYPES.has(chartType), `${path}.errorBars is not valid for ${chartType} charts.`);
|
|
366
|
+
const errorBars = Array.isArray(opts.errorBars) ? opts.errorBars : [opts.errorBars];
|
|
367
|
+
// Non-scatter/bubble series only support a single error-bar configuration
|
|
368
|
+
// (`ErrorBars`, not an array). Fail fast rather than silently keep only
|
|
369
|
+
// `errorBars[0]` and discard the rest.
|
|
370
|
+
if (Array.isArray(opts.errorBars) &&
|
|
371
|
+
opts.errorBars.length > 1 &&
|
|
372
|
+
chartType !== "scatter" &&
|
|
373
|
+
chartType !== "bubble") {
|
|
374
|
+
assertChartOptions(false, `${path}.errorBars must be a single configuration for ${chartType} charts; arrays are only valid for scatter and bubble.`);
|
|
375
|
+
}
|
|
376
|
+
// Scatter / bubble: CT_ScatterSer / CT_BubbleSer declare
|
|
377
|
+
// `errBars` as `maxOccurs="2"` — one X, one Y. Allowing more
|
|
378
|
+
// than two (or two with the same `c:errDir`) is an OOXML schema
|
|
379
|
+
// violation and causes Excel to repair the chart on open,
|
|
380
|
+
// silently dropping the extras. Fail the author up-front so
|
|
381
|
+
// mistakes like passing an array of "every error-bar type" on a
|
|
382
|
+
// single series are caught rather than producing a file that
|
|
383
|
+
// prompts a repair dialog.
|
|
384
|
+
if ((chartType === "scatter" || chartType === "bubble") &&
|
|
385
|
+
Array.isArray(opts.errorBars) &&
|
|
386
|
+
opts.errorBars.length > 2) {
|
|
387
|
+
assertChartOptions(false, `${path}.errorBars allows at most 2 entries on ${chartType} charts (one for direction "x", one for "y"). Split additional configurations across separate series.`);
|
|
388
|
+
}
|
|
389
|
+
if ((chartType === "scatter" || chartType === "bubble") &&
|
|
390
|
+
Array.isArray(opts.errorBars) &&
|
|
391
|
+
opts.errorBars.length === 2) {
|
|
392
|
+
const d0 = opts.errorBars[0]?.direction;
|
|
393
|
+
const d1 = opts.errorBars[1]?.direction;
|
|
394
|
+
assertChartOptions(d0 !== undefined && d1 !== undefined && d0 !== d1, `${path}.errorBars must use distinct directions ("x" and "y") when providing two entries on ${chartType} charts.`);
|
|
395
|
+
}
|
|
396
|
+
for (let i = 0; i < errorBars.length; i++) {
|
|
397
|
+
validateErrorBarsOptions(errorBars[i], `${path}.errorBars${errorBars.length > 1 ? `[${i}]` : ""}`);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
function validateSeriesPatchOptions(chartType, opts, path) {
|
|
402
|
+
// Patch path: unlike series-creation validation we accept a partial
|
|
403
|
+
// options bag with no `values` / `xValues` / `bubbleSize`. The
|
|
404
|
+
// `allowMissingRefs` flag short-circuits the "required" assertions so we
|
|
405
|
+
// don't need to inject placeholder strings (previous implementation used
|
|
406
|
+
// an `__excelts_placeholder__` sentinel that would have triggered false
|
|
407
|
+
// positives if `values` ever gained a content-level check).
|
|
408
|
+
validateSeriesOptions(chartType, opts, path, { allowMissingRefs: true });
|
|
409
|
+
}
|
|
410
|
+
function validateDataPointOptions(chartType, opts, path) {
|
|
411
|
+
assertIntegerInRange(opts.index, `${path}.index`, 0, Number.MAX_SAFE_INTEGER);
|
|
412
|
+
if (opts.explosion !== undefined) {
|
|
413
|
+
assertChartOptions(PIE_FAMILY_CHART_TYPES.has(chartType), `${path}.explosion is only valid for pie, doughnut, and ofPie charts.`);
|
|
414
|
+
assertIntegerInRange(opts.explosion, `${path}.explosion`, 0, 400);
|
|
415
|
+
}
|
|
416
|
+
if (opts.marker?.size !== undefined) {
|
|
417
|
+
assertIntegerInRange(opts.marker.size, `${path}.marker.size`, 2, 72);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
function validateTrendlineOptions(opts, path) {
|
|
421
|
+
assertChartOptions(!!opts && typeof opts === "object", `${path} must be an object.`);
|
|
422
|
+
assertChartOptions(!!opts.type, `${path}.type is required.`);
|
|
423
|
+
if (opts.type === "poly") {
|
|
424
|
+
assertIntegerInRange(opts.order, `${path}.order`, 2, 6);
|
|
425
|
+
}
|
|
426
|
+
else {
|
|
427
|
+
assertChartOptions(opts.order === undefined, `${path}.order is only valid for polynomial trendlines.`);
|
|
428
|
+
}
|
|
429
|
+
if (opts.type === "movingAvg") {
|
|
430
|
+
assertIntegerInRange(opts.period, `${path}.period`, 2, Number.MAX_SAFE_INTEGER);
|
|
431
|
+
}
|
|
432
|
+
else {
|
|
433
|
+
// `period` is only meaningful for moving-average trendlines (Excel
|
|
434
|
+
// silently ignores it on other types, which quickly devolves into
|
|
435
|
+
// phantom config bugs). Match the stricter `order` handling above.
|
|
436
|
+
assertChartOptions(opts.period === undefined, `${path}.period is only valid for movingAvg trendlines.`);
|
|
437
|
+
}
|
|
438
|
+
if (opts.forward !== undefined) {
|
|
439
|
+
assertNumberInRange(opts.forward, `${path}.forward`, 0, Number.MAX_SAFE_INTEGER);
|
|
440
|
+
}
|
|
441
|
+
if (opts.backward !== undefined) {
|
|
442
|
+
assertNumberInRange(opts.backward, `${path}.backward`, 0, Number.MAX_SAFE_INTEGER);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
function validateErrorBarsOptions(opts, path) {
|
|
446
|
+
assertChartOptions(!!opts && typeof opts === "object", `${path} must be an object.`);
|
|
447
|
+
assertChartOptions(!!opts.type, `${path}.type is required.`);
|
|
448
|
+
if (opts.type === "cust") {
|
|
449
|
+
assertChartOptions(!!opts.plus && !!opts.minus, `${path}.plus and ${path}.minus are required when type is "cust".`);
|
|
450
|
+
}
|
|
451
|
+
else {
|
|
452
|
+
assertChartOptions(opts.plus === undefined && opts.minus === undefined, `${path}.plus and ${path}.minus are only valid when type is "cust".`);
|
|
453
|
+
}
|
|
454
|
+
if (opts.value !== undefined) {
|
|
455
|
+
assertNumberInRange(opts.value, `${path}.value`, 0, Number.MAX_SAFE_INTEGER);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Ensure every `dLblPos` value (group-level and per-entry overrides)
|
|
460
|
+
* is one that Excel will accept for the chart type — see the
|
|
461
|
+
* rationale on {@link VALID_DLBL_POSITIONS_BY_TYPE}. Writing an
|
|
462
|
+
* invalid position causes Excel to flag the drawing as corrupted
|
|
463
|
+
* ("Repaired Records" / "Removed Part") even though the OOXML
|
|
464
|
+
* schema would technically accept it.
|
|
465
|
+
*
|
|
466
|
+
* Rejects at author time so the caller gets a pointer to the exact
|
|
467
|
+
* offending field rather than debugging a Removed Part dialog.
|
|
468
|
+
*/
|
|
469
|
+
function validateDataLabelsOptions(chartType, opts, path) {
|
|
470
|
+
const allowed = VALID_DLBL_POSITIONS_BY_TYPE[chartType];
|
|
471
|
+
const describeAllowed = () => {
|
|
472
|
+
if (!allowed) {
|
|
473
|
+
return "(unknown chart type)";
|
|
474
|
+
}
|
|
475
|
+
if (allowed.size === 0) {
|
|
476
|
+
return `(${chartType} does not support c:dLblPos — Excel rejects any value)`;
|
|
477
|
+
}
|
|
478
|
+
return [...allowed].sort().join(", ");
|
|
479
|
+
};
|
|
480
|
+
if (opts.position !== undefined) {
|
|
481
|
+
assertChartOptions(!!allowed && allowed.has(opts.position), `${path}.position="${opts.position}" is not valid for ${chartType} charts. Allowed: ${describeAllowed()}.`);
|
|
482
|
+
}
|
|
483
|
+
if (opts.entries) {
|
|
484
|
+
for (let i = 0; i < opts.entries.length; i++) {
|
|
485
|
+
const entry = opts.entries[i];
|
|
486
|
+
if (entry?.position !== undefined) {
|
|
487
|
+
assertChartOptions(!!allowed && allowed.has(entry.position), `${path}.entries[${i}].position="${entry.position}" is not valid for ${chartType} charts. Allowed: ${describeAllowed()}.`);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
function validateAxisOptions(opts, path) {
|
|
493
|
+
if (!opts) {
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
if (opts.min !== undefined) {
|
|
497
|
+
assertFiniteNumber(opts.min, `${path}.min`);
|
|
498
|
+
}
|
|
499
|
+
if (opts.max !== undefined) {
|
|
500
|
+
assertFiniteNumber(opts.max, `${path}.max`);
|
|
501
|
+
}
|
|
502
|
+
if (opts.min !== undefined && opts.max !== undefined) {
|
|
503
|
+
assertChartOptions(opts.min < opts.max, `${path}.min must be less than ${path}.max.`);
|
|
504
|
+
}
|
|
505
|
+
if (opts.majorUnit !== undefined) {
|
|
506
|
+
assertNumberInRange(opts.majorUnit, `${path}.majorUnit`, 0, Number.MAX_SAFE_INTEGER);
|
|
507
|
+
assertChartOptions(opts.majorUnit > 0, `${path}.majorUnit must be greater than 0.`);
|
|
508
|
+
}
|
|
509
|
+
if (opts.minorUnit !== undefined) {
|
|
510
|
+
assertNumberInRange(opts.minorUnit, `${path}.minorUnit`, 0, Number.MAX_SAFE_INTEGER);
|
|
511
|
+
assertChartOptions(opts.minorUnit > 0, `${path}.minorUnit must be greater than 0.`);
|
|
512
|
+
}
|
|
513
|
+
if (opts.majorUnit !== undefined && opts.minorUnit !== undefined) {
|
|
514
|
+
assertChartOptions(opts.minorUnit <= opts.majorUnit, `${path}.minorUnit must be less than or equal to ${path}.majorUnit.`);
|
|
515
|
+
}
|
|
516
|
+
if (opts.logBase !== undefined) {
|
|
517
|
+
assertNumberInRange(opts.logBase, `${path}.logBase`, 2, 1000);
|
|
518
|
+
}
|
|
519
|
+
if (opts.textRotation !== undefined) {
|
|
520
|
+
assertIntegerInRange(opts.textRotation, `${path}.textRotation`, -90, 90);
|
|
521
|
+
}
|
|
522
|
+
if (opts.lblOffset !== undefined) {
|
|
523
|
+
assertIntegerInRange(opts.lblOffset, `${path}.lblOffset`, 0, 1000);
|
|
524
|
+
}
|
|
525
|
+
if (opts.tickLblSkip !== undefined) {
|
|
526
|
+
assertIntegerInRange(opts.tickLblSkip, `${path}.tickLblSkip`, 1, Number.MAX_SAFE_INTEGER);
|
|
527
|
+
}
|
|
528
|
+
if (opts.tickMarkSkip !== undefined) {
|
|
529
|
+
assertIntegerInRange(opts.tickMarkSkip, `${path}.tickMarkSkip`, 1, Number.MAX_SAFE_INTEGER);
|
|
530
|
+
}
|
|
531
|
+
if (opts.crossesAt !== undefined) {
|
|
532
|
+
assertFiniteNumber(opts.crossesAt, `${path}.crossesAt`);
|
|
533
|
+
}
|
|
534
|
+
if (opts.customUnit !== undefined) {
|
|
535
|
+
assertNumberInRange(opts.customUnit, `${path}.customUnit`, 0, Number.MAX_SAFE_INTEGER);
|
|
536
|
+
assertChartOptions(opts.customUnit > 0, `${path}.customUnit must be greater than 0.`);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
/**
|
|
540
|
+
* Normalise a user-facing hex colour into a structured {@link ChartColor}.
|
|
541
|
+
* Accepts `"#RRGGBB"` / `"RRGGBB"` and the optional 8-digit
|
|
542
|
+
* `"RRGGBBAA"` form. The alpha byte, when present, is decoded into
|
|
543
|
+
* `color.alpha` on the OOXML 0–100000 scale (0 = fully transparent,
|
|
544
|
+
* 100000 = fully opaque) rather than discarded. Throws
|
|
545
|
+
* `ChartOptionsError` when the input is not a valid hex triplet so the
|
|
546
|
+
* caller sees the mistake at the assignment site rather than via a
|
|
547
|
+
* downstream XML parser rejection.
|
|
548
|
+
*/
|
|
549
|
+
export function hexToColor(hex) {
|
|
550
|
+
const cleaned = hex.replace(/^#/, "").toUpperCase();
|
|
551
|
+
if (!/^[0-9A-F]{6}([0-9A-F]{2})?$/.test(cleaned)) {
|
|
552
|
+
throw new ChartOptionsError(`Invalid hex colour: ${JSON.stringify(hex)}. Expected 6-digit (or 8-digit with alpha) hex like "#FF0000".`);
|
|
553
|
+
}
|
|
554
|
+
const color = { srgb: cleaned.slice(0, 6) };
|
|
555
|
+
if (cleaned.length === 8) {
|
|
556
|
+
// 8-digit form: trailing 2 bytes encode alpha on the 0–255 scale.
|
|
557
|
+
// `ChartColor.alpha` stores OOXML's 0–100000 integer; convert and
|
|
558
|
+
// round so the wire value is an integer, matching Excel's output.
|
|
559
|
+
const alphaByte = parseInt(cleaned.slice(6, 8), 16);
|
|
560
|
+
color.alpha = Math.round((alphaByte / 255) * 100000);
|
|
561
|
+
}
|
|
562
|
+
return color;
|
|
563
|
+
}
|
|
564
|
+
/**
|
|
565
|
+
* Escape XML text content while stripping characters forbidden by
|
|
566
|
+
* XML 1.0. Without the strip step, a chart built from user input
|
|
567
|
+
* containing `\b` / `\f` / other C0 control characters would produce
|
|
568
|
+
* an XML-invalid `<c:v>` or `<c:f>` payload that strict readers
|
|
569
|
+
* refuse to parse. Preserved: `\t`, `\n`, `\r` (the only C0 chars
|
|
570
|
+
* XML 1.0 allows in content).
|
|
571
|
+
*/
|
|
572
|
+
function buildPivotSourceXml(source) {
|
|
573
|
+
const name = typeof source === "string" ? source : source.name;
|
|
574
|
+
const fmtId = typeof source === "string" ? 0 : (source.fmtId ?? 0);
|
|
575
|
+
if (!name) {
|
|
576
|
+
throw new ChartOptionsError("Pivot chart source name is required.");
|
|
577
|
+
}
|
|
578
|
+
if (!Number.isInteger(fmtId) || fmtId < 0) {
|
|
579
|
+
throw new ChartOptionsError("Pivot chart source fmtId must be a non-negative integer.");
|
|
580
|
+
}
|
|
581
|
+
// Pivot chart options used to be embedded inside `<c:pivotSource>` under a
|
|
582
|
+
// private `xmlns:excelts` namespace; Excel never recognised that and the
|
|
583
|
+
// parser never read it back. Options are now routed into
|
|
584
|
+
// `ChartModel.pivotOptions` (see {@link ChartModel.pivotOptions}) and
|
|
585
|
+
// serialised as MS standard `c14:pivotOptions` inside chartSpace's extLst.
|
|
586
|
+
return `<c:pivotSource><c:name>${escapeXml(name)}</c:name><c:fmtId val="${fmtId}"/></c:pivotSource>`;
|
|
587
|
+
}
|
|
588
|
+
/**
|
|
589
|
+
* Convert a simplified AddShapeFillOptions (or a pre-built ShapeProperties)
|
|
590
|
+
* into a structured ShapeProperties object. Returns undefined when input is
|
|
591
|
+
* undefined.
|
|
592
|
+
*/
|
|
593
|
+
/**
|
|
594
|
+
* Normalise a user-facing shape-fill option bundle (hex-string fill,
|
|
595
|
+
* hex-string border, borderWidth in points, gradient, pattern, …) into a
|
|
596
|
+
* structured {@link ShapeProperties}. Passes through already-structured
|
|
597
|
+
* shapes unchanged so callers can mix the two forms freely.
|
|
598
|
+
*
|
|
599
|
+
* Exported so `chart-ex-builder` can reuse the exact same normalisation
|
|
600
|
+
* for ChartEx `spPr` options — previously ChartEx only accepted the
|
|
601
|
+
* fully-structured form, which was an API asymmetry.
|
|
602
|
+
*/
|
|
603
|
+
export function toShapeProperties(input) {
|
|
604
|
+
if (!input) {
|
|
605
|
+
return undefined;
|
|
606
|
+
}
|
|
607
|
+
// If it already looks like a ShapeProperties (has fill/line/effectList/_rawXml
|
|
608
|
+
// but no hex-string "fill" field), shallow-clone rather than return
|
|
609
|
+
// the caller's reference. Every downstream consumer treats the
|
|
610
|
+
// returned object as owned (e.g. `applyChartSeriesOptionsPatch`
|
|
611
|
+
// deletes `_rawXml` on it; combo-group builders overwrite inner
|
|
612
|
+
// fields), and leaking the caller's object into the model would
|
|
613
|
+
// mean subsequent patches on one chart silently mutate the
|
|
614
|
+
// caller's options blob — and if that blob was reused across
|
|
615
|
+
// multiple `addChart(...)` calls, every later chart would see the
|
|
616
|
+
// stripped / mutated state. The clone is shallow because `fill` /
|
|
617
|
+
// `line` sub-trees are themselves replaced wholesale by the
|
|
618
|
+
// downstream patchers when they change; deeper aliasing is not
|
|
619
|
+
// currently a hazard.
|
|
620
|
+
if (isShapeProperties(input)) {
|
|
621
|
+
return { ...input };
|
|
622
|
+
}
|
|
623
|
+
const opts = input;
|
|
624
|
+
const spPr = {};
|
|
625
|
+
if (opts.noFill) {
|
|
626
|
+
spPr.fill = { noFill: true };
|
|
627
|
+
}
|
|
628
|
+
else if (opts.fill) {
|
|
629
|
+
spPr.fill = { solid: hexToColor(opts.fill) };
|
|
630
|
+
}
|
|
631
|
+
else if (opts.gradient) {
|
|
632
|
+
spPr.fill = { gradient: opts.gradient };
|
|
633
|
+
}
|
|
634
|
+
else if (opts.pattern) {
|
|
635
|
+
spPr.fill = { pattern: opts.pattern };
|
|
636
|
+
}
|
|
637
|
+
if (opts.border || opts.borderWidth !== undefined) {
|
|
638
|
+
spPr.line = {};
|
|
639
|
+
if (opts.border) {
|
|
640
|
+
spPr.line.color = hexToColor(opts.border);
|
|
641
|
+
}
|
|
642
|
+
if (opts.borderWidth !== undefined) {
|
|
643
|
+
// OOXML `a:ln/@w` is `ST_LineWidth` = `xsd:int`. A fractional
|
|
644
|
+
// `borderWidth` (e.g. 0.825pt, or any point value that doesn't
|
|
645
|
+
// round-trip through `n / 12700` cleanly) would interpolate as
|
|
646
|
+
// `"10477.5"`, which strict readers reject. Rounding matches the
|
|
647
|
+
// sibling builders (`buildSeriesSpPr`, etc.).
|
|
648
|
+
spPr.line.width = Math.round(opts.borderWidth * EMU_PER_POINT);
|
|
649
|
+
}
|
|
650
|
+
else if (opts.border) {
|
|
651
|
+
// Mirror chart-ex-builder: when the caller sets a border colour
|
|
652
|
+
// without an explicit width, fall back to 9525 EMU (0.75pt).
|
|
653
|
+
// Without this DrawingML readers treat `<a:ln>` as hairline,
|
|
654
|
+
// which typically disappears on screen.
|
|
655
|
+
spPr.line.width = 9525;
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
return Object.keys(spPr).length > 0 ? spPr : undefined;
|
|
659
|
+
}
|
|
660
|
+
function isShapeProperties(v) {
|
|
661
|
+
if (!v || typeof v !== "object") {
|
|
662
|
+
return false;
|
|
663
|
+
}
|
|
664
|
+
const o = v;
|
|
665
|
+
// ShapeProperties has structured `fill` as an object or `line` or `_rawXml`,
|
|
666
|
+
// whereas AddShapeFillOptions.fill is a hex string.
|
|
667
|
+
if ("_rawXml" in o) {
|
|
668
|
+
return true;
|
|
669
|
+
}
|
|
670
|
+
if ("fill" in o && typeof o.fill === "object") {
|
|
671
|
+
return true;
|
|
672
|
+
}
|
|
673
|
+
if ("line" in o && typeof o.line === "object" && !Array.isArray(o.line)) {
|
|
674
|
+
// AddShapeFillOptions does not have `line`; ShapeProperties does.
|
|
675
|
+
return true;
|
|
676
|
+
}
|
|
677
|
+
if ("effectList" in o) {
|
|
678
|
+
return true;
|
|
679
|
+
}
|
|
680
|
+
// Check the remaining `ShapeProperties` fields that distinguish it
|
|
681
|
+
// from `AddShapeFillOptions`. `AddShapeFillOptions` has only
|
|
682
|
+
// `fill` (as hex-string) and `line` (as hex-string); anything else
|
|
683
|
+
// is a `ShapeProperties`-only field. Previously the sniffer missed
|
|
684
|
+
// the 3D/geometry fields entirely — a user passing
|
|
685
|
+
// `{ sp3d: {...} }` or `{ transform: {...} }` alone was
|
|
686
|
+
// misclassified and the fields silently dropped.
|
|
687
|
+
if ("scene3d" in o || "sp3d" in o) {
|
|
688
|
+
return true;
|
|
689
|
+
}
|
|
690
|
+
if ("transform" in o || "presetGeometry" in o || "customGeometry" in o) {
|
|
691
|
+
return true;
|
|
692
|
+
}
|
|
693
|
+
return false;
|
|
694
|
+
}
|
|
695
|
+
function makeSolidFill(hex) {
|
|
696
|
+
return { fill: { solid: hexToColor(hex) } };
|
|
697
|
+
}
|
|
698
|
+
function makeSeriesTx(name) {
|
|
699
|
+
if (name === undefined) {
|
|
700
|
+
return undefined;
|
|
701
|
+
}
|
|
702
|
+
if (typeof name === "string") {
|
|
703
|
+
return { value: name };
|
|
704
|
+
}
|
|
705
|
+
return { strRef: makeStrRef(name.formula) };
|
|
706
|
+
}
|
|
707
|
+
// ---------------------------------------------------------------------------
|
|
708
|
+
// Series-level option builders
|
|
709
|
+
// ---------------------------------------------------------------------------
|
|
710
|
+
function buildSeriesSpPr(opts) {
|
|
711
|
+
// Explicit `spPr` on the options object takes precedence over the sugared form.
|
|
712
|
+
const custom = toShapeProperties(opts.spPr);
|
|
713
|
+
if (custom) {
|
|
714
|
+
return custom;
|
|
715
|
+
}
|
|
716
|
+
let spPr;
|
|
717
|
+
if (opts.fill) {
|
|
718
|
+
spPr = makeSolidFill(opts.fill);
|
|
719
|
+
}
|
|
720
|
+
if (opts.line || opts.lineWidth !== undefined || opts.lineDash) {
|
|
721
|
+
const line = {};
|
|
722
|
+
if (opts.line) {
|
|
723
|
+
line.color = hexToColor(opts.line);
|
|
724
|
+
}
|
|
725
|
+
if (opts.lineWidth !== undefined) {
|
|
726
|
+
line.width = Math.round(opts.lineWidth * EMU_PER_POINT); // pt → EMU
|
|
727
|
+
}
|
|
728
|
+
else if (opts.line) {
|
|
729
|
+
// Mirror chart-ex-builder: when the caller sets a line colour
|
|
730
|
+
// without an explicit width, fall back to 9525 EMU (0.75pt —
|
|
731
|
+
// Excel's default for chart series borders). Without this
|
|
732
|
+
// DrawingML readers treat `<a:ln>` as hairline, which disappears
|
|
733
|
+
// at typical screen DPI and is never what the user means by
|
|
734
|
+
// `line: "#FF0000"`.
|
|
735
|
+
line.width = 9525;
|
|
736
|
+
}
|
|
737
|
+
if (opts.lineDash) {
|
|
738
|
+
line.dash = opts.lineDash;
|
|
739
|
+
}
|
|
740
|
+
spPr = { ...spPr, line };
|
|
741
|
+
}
|
|
742
|
+
return spPr;
|
|
743
|
+
}
|
|
744
|
+
function buildMarkerFromOpts(opts) {
|
|
745
|
+
const m = {};
|
|
746
|
+
if (opts.symbol) {
|
|
747
|
+
m.symbol = opts.symbol;
|
|
748
|
+
}
|
|
749
|
+
if (opts.size !== undefined) {
|
|
750
|
+
m.size = opts.size;
|
|
751
|
+
}
|
|
752
|
+
if (opts.fill || opts.border) {
|
|
753
|
+
const mSpPr = {};
|
|
754
|
+
if (opts.fill) {
|
|
755
|
+
mSpPr.fill = { solid: hexToColor(opts.fill) };
|
|
756
|
+
}
|
|
757
|
+
if (opts.border) {
|
|
758
|
+
// Default to 9525 EMU (0.75pt) — without an explicit width
|
|
759
|
+
// DrawingML treats the outline as hairline and the marker ring
|
|
760
|
+
// typically vanishes at on-screen DPI. Matches the other
|
|
761
|
+
// builder paths that default to the same width.
|
|
762
|
+
mSpPr.line = { color: hexToColor(opts.border), width: 9525 };
|
|
763
|
+
}
|
|
764
|
+
m.spPr = mSpPr;
|
|
765
|
+
}
|
|
766
|
+
return m;
|
|
767
|
+
}
|
|
768
|
+
function buildDataLabelsFromOpts(opts) {
|
|
769
|
+
const dl = {};
|
|
770
|
+
if (opts.showLegendKey !== undefined) {
|
|
771
|
+
dl.showLegendKey = opts.showLegendKey;
|
|
772
|
+
}
|
|
773
|
+
if (opts.showVal !== undefined) {
|
|
774
|
+
dl.showVal = opts.showVal;
|
|
775
|
+
}
|
|
776
|
+
if (opts.showCatName !== undefined) {
|
|
777
|
+
dl.showCatName = opts.showCatName;
|
|
778
|
+
}
|
|
779
|
+
if (opts.showSerName !== undefined) {
|
|
780
|
+
dl.showSerName = opts.showSerName;
|
|
781
|
+
}
|
|
782
|
+
if (opts.showPercent !== undefined) {
|
|
783
|
+
dl.showPercent = opts.showPercent;
|
|
784
|
+
}
|
|
785
|
+
if (opts.showBubbleSize !== undefined) {
|
|
786
|
+
dl.showBubbleSize = opts.showBubbleSize;
|
|
787
|
+
}
|
|
788
|
+
if (opts.showLeaderLines !== undefined) {
|
|
789
|
+
dl.showLeaderLines = opts.showLeaderLines;
|
|
790
|
+
}
|
|
791
|
+
if (opts.position) {
|
|
792
|
+
dl.position = opts.position;
|
|
793
|
+
}
|
|
794
|
+
if (opts.separator !== undefined) {
|
|
795
|
+
dl.separator = opts.separator;
|
|
796
|
+
}
|
|
797
|
+
if (opts.numFmt !== undefined) {
|
|
798
|
+
dl.numFmt = { formatCode: opts.numFmt, sourceLinked: opts.numFmtLinked };
|
|
799
|
+
}
|
|
800
|
+
else if (opts.numFmtLinked !== undefined) {
|
|
801
|
+
// Allow `numFmtLinked` without an explicit `numFmt` to re-link the
|
|
802
|
+
// data-label number format to the source cell. OOXML's `CT_NumFmt`
|
|
803
|
+
// requires `formatCode`, so default it to `General` (the Excel
|
|
804
|
+
// default when `sourceLinked="1"` is emitted without an author-
|
|
805
|
+
// specified format). Previously this branch silently dropped the
|
|
806
|
+
// flag: callers had no way to opt into source-linking on data
|
|
807
|
+
// labels without also supplying a redundant format code.
|
|
808
|
+
dl.numFmt = { formatCode: "General", sourceLinked: opts.numFmtLinked };
|
|
809
|
+
}
|
|
810
|
+
const spPr = toShapeProperties(opts.spPr);
|
|
811
|
+
if (spPr) {
|
|
812
|
+
dl.spPr = spPr;
|
|
813
|
+
}
|
|
814
|
+
if (opts.txPr) {
|
|
815
|
+
dl.txPr = opts.txPr;
|
|
816
|
+
}
|
|
817
|
+
if (opts.entries && opts.entries.length > 0) {
|
|
818
|
+
dl.entries = opts.entries.map(e => buildDataLabelEntryFromOpts(e));
|
|
819
|
+
}
|
|
820
|
+
if (opts.valueFromCells !== undefined) {
|
|
821
|
+
// "Value From Cells" (Excel 2013+). Accept either a bare formula or
|
|
822
|
+
// the full {formula, cache} shape. The cache is filled in later by
|
|
823
|
+
// `fillChartCaches` when the worksheet is available.
|
|
824
|
+
dl.dataLabelsRange =
|
|
825
|
+
typeof opts.valueFromCells === "string"
|
|
826
|
+
? { formula: opts.valueFromCells }
|
|
827
|
+
: opts.valueFromCells;
|
|
828
|
+
}
|
|
829
|
+
return dl;
|
|
830
|
+
}
|
|
831
|
+
function buildDataLabelEntryFromOpts(opts) {
|
|
832
|
+
const entry = { index: opts.index };
|
|
833
|
+
if (opts.delete) {
|
|
834
|
+
entry.delete = true;
|
|
835
|
+
// OOXML: a deleted label carries only its index; skip other fields.
|
|
836
|
+
return entry;
|
|
837
|
+
}
|
|
838
|
+
if (opts.text !== undefined) {
|
|
839
|
+
if (typeof opts.text === "string") {
|
|
840
|
+
entry.text = {
|
|
841
|
+
paragraphs: [{ runs: [{ text: opts.text }] }]
|
|
842
|
+
};
|
|
843
|
+
}
|
|
844
|
+
else {
|
|
845
|
+
entry.text = opts.text;
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
if (opts.position) {
|
|
849
|
+
entry.position = opts.position;
|
|
850
|
+
}
|
|
851
|
+
if (opts.numFmt) {
|
|
852
|
+
entry.numFmt = { formatCode: opts.numFmt, sourceLinked: opts.numFmtLinked };
|
|
853
|
+
}
|
|
854
|
+
else if (opts.numFmtLinked !== undefined) {
|
|
855
|
+
// Allow standalone `numFmtLinked` — see `buildDataLabelsFromOpts`
|
|
856
|
+
// for the matching semantics.
|
|
857
|
+
entry.numFmt = { formatCode: "General", sourceLinked: opts.numFmtLinked };
|
|
858
|
+
}
|
|
859
|
+
const spPr = toShapeProperties(opts.spPr);
|
|
860
|
+
if (spPr) {
|
|
861
|
+
entry.spPr = spPr;
|
|
862
|
+
}
|
|
863
|
+
if (opts.txPr) {
|
|
864
|
+
entry.txPr = opts.txPr;
|
|
865
|
+
}
|
|
866
|
+
if (opts.showVal !== undefined) {
|
|
867
|
+
entry.showVal = opts.showVal;
|
|
868
|
+
}
|
|
869
|
+
if (opts.showCatName !== undefined) {
|
|
870
|
+
entry.showCatName = opts.showCatName;
|
|
871
|
+
}
|
|
872
|
+
if (opts.showSerName !== undefined) {
|
|
873
|
+
entry.showSerName = opts.showSerName;
|
|
874
|
+
}
|
|
875
|
+
if (opts.showPercent !== undefined) {
|
|
876
|
+
entry.showPercent = opts.showPercent;
|
|
877
|
+
}
|
|
878
|
+
if (opts.showBubbleSize !== undefined) {
|
|
879
|
+
entry.showBubbleSize = opts.showBubbleSize;
|
|
880
|
+
}
|
|
881
|
+
if (opts.showLegendKey !== undefined) {
|
|
882
|
+
entry.showLegendKey = opts.showLegendKey;
|
|
883
|
+
}
|
|
884
|
+
return entry;
|
|
885
|
+
}
|
|
886
|
+
function buildTrendlineFromOpts(opts) {
|
|
887
|
+
const t = { type: opts.type };
|
|
888
|
+
if (opts.name !== undefined) {
|
|
889
|
+
t.name = opts.name;
|
|
890
|
+
}
|
|
891
|
+
if (opts.order !== undefined) {
|
|
892
|
+
t.order = opts.order;
|
|
893
|
+
}
|
|
894
|
+
if (opts.period !== undefined) {
|
|
895
|
+
t.period = opts.period;
|
|
896
|
+
}
|
|
897
|
+
if (opts.forward !== undefined) {
|
|
898
|
+
t.forward = opts.forward;
|
|
899
|
+
}
|
|
900
|
+
if (opts.backward !== undefined) {
|
|
901
|
+
t.backward = opts.backward;
|
|
902
|
+
}
|
|
903
|
+
if (opts.intercept !== undefined) {
|
|
904
|
+
t.intercept = opts.intercept;
|
|
905
|
+
}
|
|
906
|
+
if (opts.displayRSqr !== undefined) {
|
|
907
|
+
t.displayRSqr = opts.displayRSqr;
|
|
908
|
+
}
|
|
909
|
+
if (opts.displayEq !== undefined) {
|
|
910
|
+
t.displayEq = opts.displayEq;
|
|
911
|
+
}
|
|
912
|
+
if (opts.line || opts.lineWidth !== undefined || opts.lineDash) {
|
|
913
|
+
const line = {};
|
|
914
|
+
if (opts.line) {
|
|
915
|
+
line.color = hexToColor(opts.line);
|
|
916
|
+
}
|
|
917
|
+
if (opts.lineWidth !== undefined) {
|
|
918
|
+
line.width = Math.round(opts.lineWidth * EMU_PER_POINT);
|
|
919
|
+
}
|
|
920
|
+
if (opts.lineDash) {
|
|
921
|
+
line.dash = opts.lineDash;
|
|
922
|
+
}
|
|
923
|
+
t.spPr = { line };
|
|
924
|
+
}
|
|
925
|
+
if (opts.label) {
|
|
926
|
+
t.trendlineLbl = buildTrendlineLabelFromOpts(opts.label);
|
|
927
|
+
}
|
|
928
|
+
return t;
|
|
929
|
+
}
|
|
930
|
+
function buildTrendlineLabelFromOpts(opts) {
|
|
931
|
+
const lbl = {};
|
|
932
|
+
if (opts.text !== undefined) {
|
|
933
|
+
lbl.text = opts.text;
|
|
934
|
+
}
|
|
935
|
+
if (opts.numFmt !== undefined) {
|
|
936
|
+
lbl.numFmt = { formatCode: opts.numFmt, sourceLinked: opts.numFmtLinked };
|
|
937
|
+
}
|
|
938
|
+
else if (opts.numFmtLinked !== undefined) {
|
|
939
|
+
// Allow standalone `numFmtLinked` — see `buildDataLabelsFromOpts`
|
|
940
|
+
// for the matching semantics.
|
|
941
|
+
lbl.numFmt = { formatCode: "General", sourceLinked: opts.numFmtLinked };
|
|
942
|
+
}
|
|
943
|
+
if (opts.layout) {
|
|
944
|
+
lbl.layout = opts.layout;
|
|
945
|
+
}
|
|
946
|
+
const spPr = toShapeProperties(opts.spPr);
|
|
947
|
+
if (spPr) {
|
|
948
|
+
lbl.spPr = spPr;
|
|
949
|
+
}
|
|
950
|
+
if (opts.txPr) {
|
|
951
|
+
lbl.txPr = opts.txPr;
|
|
952
|
+
}
|
|
953
|
+
return lbl;
|
|
954
|
+
}
|
|
955
|
+
function buildErrorBarsFromOpts(opts) {
|
|
956
|
+
const eb = {
|
|
957
|
+
barDir: opts.barDir ?? "both",
|
|
958
|
+
errValType: opts.type
|
|
959
|
+
};
|
|
960
|
+
if (opts.direction) {
|
|
961
|
+
eb.errDir = opts.direction;
|
|
962
|
+
}
|
|
963
|
+
if (opts.value !== undefined) {
|
|
964
|
+
eb.val = opts.value;
|
|
965
|
+
}
|
|
966
|
+
if (opts.noEndCap !== undefined) {
|
|
967
|
+
eb.noEndCap = opts.noEndCap;
|
|
968
|
+
}
|
|
969
|
+
if (opts.plus) {
|
|
970
|
+
eb.plus = makeNumData(opts.plus);
|
|
971
|
+
}
|
|
972
|
+
if (opts.minus) {
|
|
973
|
+
eb.minus = makeNumData(opts.minus);
|
|
974
|
+
}
|
|
975
|
+
// Line / shape styling
|
|
976
|
+
const customSpPr = toShapeProperties(opts.spPr);
|
|
977
|
+
if (customSpPr) {
|
|
978
|
+
eb.spPr = customSpPr;
|
|
979
|
+
}
|
|
980
|
+
else if (opts.line || opts.lineWidth !== undefined || opts.lineDash) {
|
|
981
|
+
const line = {};
|
|
982
|
+
if (opts.line) {
|
|
983
|
+
line.color = hexToColor(opts.line);
|
|
984
|
+
}
|
|
985
|
+
if (opts.lineWidth !== undefined) {
|
|
986
|
+
line.width = Math.round(opts.lineWidth * EMU_PER_POINT);
|
|
987
|
+
}
|
|
988
|
+
if (opts.lineDash) {
|
|
989
|
+
line.dash = opts.lineDash;
|
|
990
|
+
}
|
|
991
|
+
eb.spPr = { line };
|
|
992
|
+
}
|
|
993
|
+
return eb;
|
|
994
|
+
}
|
|
995
|
+
function buildDataPointFromOpts(opts) {
|
|
996
|
+
const dp = { index: opts.index };
|
|
997
|
+
if (opts.fill || opts.border) {
|
|
998
|
+
const spPr = {};
|
|
999
|
+
if (opts.fill) {
|
|
1000
|
+
spPr.fill = { solid: hexToColor(opts.fill) };
|
|
1001
|
+
}
|
|
1002
|
+
if (opts.border) {
|
|
1003
|
+
spPr.line = { color: hexToColor(opts.border), width: 9525 };
|
|
1004
|
+
}
|
|
1005
|
+
dp.spPr = spPr;
|
|
1006
|
+
}
|
|
1007
|
+
if (opts.explosion !== undefined) {
|
|
1008
|
+
dp.explosion = opts.explosion;
|
|
1009
|
+
}
|
|
1010
|
+
if (opts.bubble3D !== undefined) {
|
|
1011
|
+
dp.bubble3D = opts.bubble3D;
|
|
1012
|
+
}
|
|
1013
|
+
if (opts.marker) {
|
|
1014
|
+
dp.marker = buildMarkerFromOpts(opts.marker);
|
|
1015
|
+
}
|
|
1016
|
+
if (opts.invertIfNegative !== undefined) {
|
|
1017
|
+
dp.invertIfNegative = opts.invertIfNegative;
|
|
1018
|
+
}
|
|
1019
|
+
return dp;
|
|
1020
|
+
}
|
|
1021
|
+
function applySeriesOptions(s, opts, context = {}) {
|
|
1022
|
+
s.spPr = buildSeriesSpPr(opts);
|
|
1023
|
+
if (opts.marker) {
|
|
1024
|
+
s.marker = buildMarkerFromOpts(opts.marker);
|
|
1025
|
+
}
|
|
1026
|
+
if (opts.dataLabels) {
|
|
1027
|
+
s.dataLabels = buildDataLabelsFromOpts(opts.dataLabels);
|
|
1028
|
+
}
|
|
1029
|
+
if (opts.trendline) {
|
|
1030
|
+
const trendlineOpts = Array.isArray(opts.trendline) ? opts.trendline : [opts.trendline];
|
|
1031
|
+
s.trendlines = trendlineOpts.map(buildTrendlineFromOpts);
|
|
1032
|
+
}
|
|
1033
|
+
if (opts.dataPoints) {
|
|
1034
|
+
s.dataPoints = opts.dataPoints.map(buildDataPointFromOpts);
|
|
1035
|
+
}
|
|
1036
|
+
// NOTE: classic chart series have no `txPr` slot in OOXML, so the
|
|
1037
|
+
// field was removed from `AddChartSeriesOptions`. Trendline labels,
|
|
1038
|
+
// axis labels, data labels, and titles all have their own `txPr`
|
|
1039
|
+
// entry points — route run-level text styling through those.
|
|
1040
|
+
if (opts.pictureFill) {
|
|
1041
|
+
applyPictureFillToSeries(s, opts.pictureFill, {
|
|
1042
|
+
supportsPictureOptions: context.supportsPictureOptions
|
|
1043
|
+
});
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
/**
|
|
1047
|
+
* Split an `AddShapeFillOptions`-style `pictureFill` option bundle into the
|
|
1048
|
+
* two OOXML artefacts that render the feature:
|
|
1049
|
+
* - `c:pictureOptions` (stretch/stack/apply-to-*) — stored on
|
|
1050
|
+
* `series.pictureOptions`
|
|
1051
|
+
* - `a:blipFill` (the actual image rel) — stored on
|
|
1052
|
+
* `series.spPr.fill.blip`
|
|
1053
|
+
*
|
|
1054
|
+
* Both are updated in a single place so the new-series path
|
|
1055
|
+
* ({@link applySeriesOptions}) and the patch path
|
|
1056
|
+
* ({@link applyChartSeriesOptionsPatch}) cannot diverge.
|
|
1057
|
+
*/
|
|
1058
|
+
function applyPictureFillToSeries(series, pictureFill, options = {}) {
|
|
1059
|
+
// `c:pictureOptions` (stretch/stack/applyTo*/scale) is only declared
|
|
1060
|
+
// on `CT_BarSer` per ECMA-376 §21.2.2.162. Non-bar callers get the
|
|
1061
|
+
// `<a:blipFill>` on their `spPr.fill.blip` (which is legal on every
|
|
1062
|
+
// shape), but the caller-supplied `applyToFront` / `fillMode` /
|
|
1063
|
+
// `scale` hints are silently dropped — we would otherwise emit a
|
|
1064
|
+
// `<c:pictureOptions>` that schema validators reject. Surface the
|
|
1065
|
+
// mismatch if the caller set bar-only fields on a non-bar series
|
|
1066
|
+
// rather than letting the data quietly disappear.
|
|
1067
|
+
const barOnlyFieldsUsed = pictureFill.applyToFront !== undefined ||
|
|
1068
|
+
pictureFill.applyToSides !== undefined ||
|
|
1069
|
+
pictureFill.applyToEnd !== undefined ||
|
|
1070
|
+
pictureFill.scale !== undefined;
|
|
1071
|
+
if (options.supportsPictureOptions) {
|
|
1072
|
+
series.pictureOptions = {
|
|
1073
|
+
applyToFront: pictureFill.applyToFront,
|
|
1074
|
+
applyToSides: pictureFill.applyToSides,
|
|
1075
|
+
applyToEnd: pictureFill.applyToEnd,
|
|
1076
|
+
pictureFormat: pictureFill.fillMode,
|
|
1077
|
+
pictureStackUnit: pictureFill.scale
|
|
1078
|
+
};
|
|
1079
|
+
}
|
|
1080
|
+
else if (barOnlyFieldsUsed) {
|
|
1081
|
+
throw new ChartOptionsError("pictureFill.applyToFront / applyToSides / applyToEnd / scale are only valid for bar / bar3D series (mapped to c:pictureOptions in CT_BarSer). Use the plain `fill` / `spPr.fill.blip` form for other series types, or omit the bar-only hints.");
|
|
1082
|
+
}
|
|
1083
|
+
if (pictureFill.image !== undefined || pictureFill.relationshipId) {
|
|
1084
|
+
// Parsed-from-XML series carry their `spPr` as a dual
|
|
1085
|
+
// representation: the structured `fill` / `line` slots are
|
|
1086
|
+
// populated AND `_rawXml` holds the original DrawingML bytes.
|
|
1087
|
+
// The writer short-circuits to `_rawXml` (see
|
|
1088
|
+
// `chart-space-xform._renderSpPr`) whenever it's present, which
|
|
1089
|
+
// means a downstream mutation like this one — which sets
|
|
1090
|
+
// `series.spPr.fill.blip` but can't touch the raw bytes — gets
|
|
1091
|
+
// silently overwritten at save time. The chart rel points at the
|
|
1092
|
+
// new image, the chart XML still carries the old fill. Strip
|
|
1093
|
+
// `_rawXml` here so the structured patch wins and the writer
|
|
1094
|
+
// emits the fresh `<a:blipFill>`.
|
|
1095
|
+
//
|
|
1096
|
+
// We also need to repopulate enough of the structured model that
|
|
1097
|
+
// the dropped `_rawXml` doesn't take authored line / geometry /
|
|
1098
|
+
// effect properties down with it. `parseSpPr` is already called
|
|
1099
|
+
// at load time and stores the structured fields alongside the
|
|
1100
|
+
// raw bytes, so the structured slots still carry whatever the
|
|
1101
|
+
// loaded file had. Nothing extra needed on the clone side.
|
|
1102
|
+
if (series.spPr && typeof series.spPr === "object") {
|
|
1103
|
+
delete series.spPr._rawXml;
|
|
1104
|
+
}
|
|
1105
|
+
series.spPr = series.spPr ?? {};
|
|
1106
|
+
series.spPr.fill = series.spPr.fill ?? {};
|
|
1107
|
+
// Also clear a raw representation that might live on the
|
|
1108
|
+
// existing `fill` object (rare — reserved for `<a:solidFill>`
|
|
1109
|
+
// / `<a:gradFill>` raw captures some parsers produce). The
|
|
1110
|
+
// writer emits a single `<a:*Fill>` child per `<a:spPr>`, so
|
|
1111
|
+
// the new blip must be the only fill in play.
|
|
1112
|
+
delete series.spPr.fill.solid;
|
|
1113
|
+
delete series.spPr.fill.gradient;
|
|
1114
|
+
delete series.spPr.fill.pattern;
|
|
1115
|
+
delete series.spPr.fill.noFill;
|
|
1116
|
+
series.spPr.fill.blip = {
|
|
1117
|
+
fillMode: pictureFill.fillMode === "stack" || pictureFill.fillMode === "stackScale"
|
|
1118
|
+
? "tile"
|
|
1119
|
+
: "stretch",
|
|
1120
|
+
...(pictureFill.relationshipId ? { relationshipId: pictureFill.relationshipId } : {}),
|
|
1121
|
+
...(pictureFill.image !== undefined ? { _pendingImage: pictureFill.image } : {})
|
|
1122
|
+
};
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
/**
|
|
1126
|
+
* Populates common category-value series fields (tx, cat, val) shared by
|
|
1127
|
+
* bar, line, pie, area, radar, and surface series builders.
|
|
1128
|
+
*/
|
|
1129
|
+
function populateCatValBase(s, opts) {
|
|
1130
|
+
s.tx = makeSeriesTx(opts.name);
|
|
1131
|
+
if (opts.categories) {
|
|
1132
|
+
s.cat = makeAxisData(opts.categories);
|
|
1133
|
+
}
|
|
1134
|
+
if (opts.values) {
|
|
1135
|
+
s.val = makeNumData(opts.values);
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
/**
|
|
1139
|
+
* Build a single error-bars entry from options, normalising the
|
|
1140
|
+
* array-vs-single input shape used by non-scatter/bubble series.
|
|
1141
|
+
*/
|
|
1142
|
+
function buildSingleErrorBars(opts) {
|
|
1143
|
+
if (!opts) {
|
|
1144
|
+
return undefined;
|
|
1145
|
+
}
|
|
1146
|
+
if (Array.isArray(opts)) {
|
|
1147
|
+
return opts.length > 0 ? buildErrorBarsFromOpts(opts[0]) : undefined;
|
|
1148
|
+
}
|
|
1149
|
+
return buildErrorBarsFromOpts(opts);
|
|
1150
|
+
}
|
|
1151
|
+
function buildBarSeries(opts, idx) {
|
|
1152
|
+
const s = { index: idx, order: idx };
|
|
1153
|
+
populateCatValBase(s, opts);
|
|
1154
|
+
applySeriesOptions(s, opts, { supportsPictureOptions: true });
|
|
1155
|
+
if (opts.invertIfNegative !== undefined) {
|
|
1156
|
+
s.invertIfNegative = opts.invertIfNegative;
|
|
1157
|
+
}
|
|
1158
|
+
s.errorBars = buildSingleErrorBars(opts.errorBars);
|
|
1159
|
+
return s;
|
|
1160
|
+
}
|
|
1161
|
+
function buildLineSeries(opts, idx) {
|
|
1162
|
+
const s = { index: idx, order: idx };
|
|
1163
|
+
populateCatValBase(s, opts);
|
|
1164
|
+
applySeriesOptions(s, opts);
|
|
1165
|
+
if (opts.smooth !== undefined) {
|
|
1166
|
+
s.smooth = opts.smooth;
|
|
1167
|
+
}
|
|
1168
|
+
s.errorBars = buildSingleErrorBars(opts.errorBars);
|
|
1169
|
+
return s;
|
|
1170
|
+
}
|
|
1171
|
+
function buildPieSeries(opts, idx) {
|
|
1172
|
+
const s = { index: idx, order: idx };
|
|
1173
|
+
populateCatValBase(s, opts);
|
|
1174
|
+
applySeriesOptions(s, opts);
|
|
1175
|
+
if (opts.explosion !== undefined) {
|
|
1176
|
+
s.explosion = opts.explosion;
|
|
1177
|
+
}
|
|
1178
|
+
return s;
|
|
1179
|
+
}
|
|
1180
|
+
function buildAreaSeries(opts, idx) {
|
|
1181
|
+
const s = { index: idx, order: idx };
|
|
1182
|
+
populateCatValBase(s, opts);
|
|
1183
|
+
applySeriesOptions(s, opts);
|
|
1184
|
+
s.errorBars = buildSingleErrorBars(opts.errorBars);
|
|
1185
|
+
return s;
|
|
1186
|
+
}
|
|
1187
|
+
function buildScatterSeries(opts, idx) {
|
|
1188
|
+
const s = { index: idx, order: idx };
|
|
1189
|
+
s.tx = makeSeriesTx(opts.name);
|
|
1190
|
+
if (opts.xValues) {
|
|
1191
|
+
s.xVal = makeXAxisData(opts.xValues, opts.xValueType);
|
|
1192
|
+
}
|
|
1193
|
+
if (opts.values) {
|
|
1194
|
+
s.yVal = makeNumData(opts.values);
|
|
1195
|
+
}
|
|
1196
|
+
applySeriesOptions(s, opts);
|
|
1197
|
+
if (opts.smooth !== undefined) {
|
|
1198
|
+
s.smooth = opts.smooth;
|
|
1199
|
+
}
|
|
1200
|
+
if (opts.errorBars) {
|
|
1201
|
+
const ebOpts = Array.isArray(opts.errorBars) ? opts.errorBars : [opts.errorBars];
|
|
1202
|
+
s.errorBars = ebOpts.map(buildErrorBarsFromOpts);
|
|
1203
|
+
}
|
|
1204
|
+
return s;
|
|
1205
|
+
}
|
|
1206
|
+
function buildBubbleSeries(opts, idx) {
|
|
1207
|
+
const s = { index: idx, order: idx };
|
|
1208
|
+
s.tx = makeSeriesTx(opts.name);
|
|
1209
|
+
if (opts.xValues) {
|
|
1210
|
+
s.xVal = makeXAxisData(opts.xValues, opts.xValueType);
|
|
1211
|
+
}
|
|
1212
|
+
if (opts.values) {
|
|
1213
|
+
s.yVal = makeNumData(opts.values);
|
|
1214
|
+
}
|
|
1215
|
+
if (opts.bubbleSize) {
|
|
1216
|
+
s.bubbleSize = makeNumData(opts.bubbleSize);
|
|
1217
|
+
}
|
|
1218
|
+
applySeriesOptions(s, opts);
|
|
1219
|
+
if (opts.invertIfNegative !== undefined) {
|
|
1220
|
+
s.invertIfNegative = opts.invertIfNegative;
|
|
1221
|
+
}
|
|
1222
|
+
if (opts.bubble3D !== undefined) {
|
|
1223
|
+
s.bubble3D = opts.bubble3D;
|
|
1224
|
+
}
|
|
1225
|
+
if (opts.errorBars) {
|
|
1226
|
+
const ebOpts = Array.isArray(opts.errorBars) ? opts.errorBars : [opts.errorBars];
|
|
1227
|
+
s.errorBars = ebOpts.map(buildErrorBarsFromOpts);
|
|
1228
|
+
}
|
|
1229
|
+
return s;
|
|
1230
|
+
}
|
|
1231
|
+
function buildRadarSeries(opts, idx) {
|
|
1232
|
+
const s = { index: idx, order: idx };
|
|
1233
|
+
populateCatValBase(s, opts);
|
|
1234
|
+
applySeriesOptions(s, opts);
|
|
1235
|
+
return s;
|
|
1236
|
+
}
|
|
1237
|
+
function buildSurfaceSeries(opts, idx) {
|
|
1238
|
+
const s = { index: idx, order: idx };
|
|
1239
|
+
populateCatValBase(s, opts);
|
|
1240
|
+
applySeriesOptions(s, opts);
|
|
1241
|
+
return s;
|
|
1242
|
+
}
|
|
1243
|
+
export function buildChartSeriesForType(chartType, options, index) {
|
|
1244
|
+
validateSeriesOptions(chartType, options, "series");
|
|
1245
|
+
if (chartType === "bar" || chartType === "bar3D") {
|
|
1246
|
+
return buildBarSeries(options, index);
|
|
1247
|
+
}
|
|
1248
|
+
if (chartType === "line" || chartType === "line3D" || chartType === "stock") {
|
|
1249
|
+
return buildLineSeries(options, index);
|
|
1250
|
+
}
|
|
1251
|
+
if (chartType === "pie" ||
|
|
1252
|
+
chartType === "pie3D" ||
|
|
1253
|
+
chartType === "doughnut" ||
|
|
1254
|
+
chartType === "ofPie") {
|
|
1255
|
+
return buildPieSeries(options, index);
|
|
1256
|
+
}
|
|
1257
|
+
if (chartType === "area" || chartType === "area3D") {
|
|
1258
|
+
return buildAreaSeries(options, index);
|
|
1259
|
+
}
|
|
1260
|
+
if (chartType === "scatter") {
|
|
1261
|
+
return buildScatterSeries(options, index);
|
|
1262
|
+
}
|
|
1263
|
+
if (chartType === "bubble") {
|
|
1264
|
+
return buildBubbleSeries(options, index);
|
|
1265
|
+
}
|
|
1266
|
+
if (chartType === "radar") {
|
|
1267
|
+
return buildRadarSeries(options, index);
|
|
1268
|
+
}
|
|
1269
|
+
if (chartType === "surface" || chartType === "surface3D") {
|
|
1270
|
+
return buildSurfaceSeries(options, index);
|
|
1271
|
+
}
|
|
1272
|
+
// Exhaustiveness check: every value of `AddChartOptions["type"]` must be
|
|
1273
|
+
// handled above. Falling back to `buildBarSeries` silently would mis-map
|
|
1274
|
+
// any new chart type introduced in the future.
|
|
1275
|
+
const _exhaustive = chartType;
|
|
1276
|
+
throw new ChartOptionsError(`Unsupported chart type: ${String(_exhaustive)}.`);
|
|
1277
|
+
}
|
|
1278
|
+
export function applyChartSeriesOptionsPatch(series, options, chartType) {
|
|
1279
|
+
if (chartType) {
|
|
1280
|
+
validateSeriesPatchOptions(chartType, options, "series");
|
|
1281
|
+
}
|
|
1282
|
+
if (options.name !== undefined) {
|
|
1283
|
+
series.tx = makeSeriesTx(options.name);
|
|
1284
|
+
}
|
|
1285
|
+
if (options.categories !== undefined) {
|
|
1286
|
+
const target = series;
|
|
1287
|
+
// Scatter / bubble series have no category axis — their X axis is
|
|
1288
|
+
// `xVal`. When a patch targets such a series (identified by an
|
|
1289
|
+
// already-populated `xVal` and no `cat`), route `categories`
|
|
1290
|
+
// through `makeXAxisData` so `options.xValueType: "text"` flips
|
|
1291
|
+
// the series from a numeric `xVal` to a labelled one. For every
|
|
1292
|
+
// other series type the patch goes to the structural `cat` slot.
|
|
1293
|
+
if (target.xVal && !target.cat) {
|
|
1294
|
+
target.xVal = makeXAxisData(options.categories, options.xValueType);
|
|
1295
|
+
}
|
|
1296
|
+
else {
|
|
1297
|
+
target.cat = makeAxisData(options.categories);
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
if (options.xValues !== undefined) {
|
|
1301
|
+
series.xVal = makeXAxisData(options.xValues, options.xValueType);
|
|
1302
|
+
}
|
|
1303
|
+
if (options.values !== undefined) {
|
|
1304
|
+
const target = series;
|
|
1305
|
+
if (target.yVal && !target.val) {
|
|
1306
|
+
target.yVal = makeNumData(options.values);
|
|
1307
|
+
}
|
|
1308
|
+
else {
|
|
1309
|
+
target.val = makeNumData(options.values);
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
if (options.bubbleSize !== undefined) {
|
|
1313
|
+
series.bubbleSize = makeNumData(options.bubbleSize);
|
|
1314
|
+
}
|
|
1315
|
+
if (hasSeriesShapePatch(options)) {
|
|
1316
|
+
const patchShape = buildSeriesSpPr(options);
|
|
1317
|
+
if (options.spPr !== undefined) {
|
|
1318
|
+
// Explicit `spPr` replaces the whole shape — the caller opted in
|
|
1319
|
+
// to a full structured override.
|
|
1320
|
+
series.spPr = patchShape;
|
|
1321
|
+
}
|
|
1322
|
+
else {
|
|
1323
|
+
// Sugared patches (`fill` / `line` / `lineWidth` / `lineDash`)
|
|
1324
|
+
// should update only the affected sub-object AND merge inside the
|
|
1325
|
+
// sub-object so narrow patches (e.g. `lineDash` alone) don't wipe
|
|
1326
|
+
// out the adjacent fields.
|
|
1327
|
+
//
|
|
1328
|
+
// Previously the merge stopped at the top level (`{ ...existing,
|
|
1329
|
+
// line: patchShape.line }`) — so `updateSeries(0, { lineDash: "dash" })`
|
|
1330
|
+
// on a series with `{ line: "#FF0000", lineWidth: 2 }` dropped the
|
|
1331
|
+
// colour AND width because `buildSeriesSpPr({ lineDash: "dash" })`
|
|
1332
|
+
// only sets `line.dash`, and the top-level spread replaced the
|
|
1333
|
+
// whole `line` sub-object with `{ dash: "dash" }`. Deep-merge the
|
|
1334
|
+
// sub-objects so each field survives until the caller explicitly
|
|
1335
|
+
// overwrites it.
|
|
1336
|
+
const existing = series.spPr ?? {};
|
|
1337
|
+
const next = { ...existing };
|
|
1338
|
+
if (patchShape?.fill !== undefined) {
|
|
1339
|
+
next.fill = patchShape.fill;
|
|
1340
|
+
}
|
|
1341
|
+
if (patchShape?.line !== undefined) {
|
|
1342
|
+
next.line = { ...(existing.line ?? {}), ...patchShape.line };
|
|
1343
|
+
}
|
|
1344
|
+
series.spPr = next;
|
|
1345
|
+
}
|
|
1346
|
+
// CRITICAL: clear `_rawXml` so the writer serialises from the
|
|
1347
|
+
// structured fields we just patched. The chart-space writer emits
|
|
1348
|
+
// `_rawXml` verbatim when present (`_renderSpPr` at the top of the
|
|
1349
|
+
// function body in `chart-space-xform.ts`), which meant every
|
|
1350
|
+
// structured mutation was silently overridden by the stale raw
|
|
1351
|
+
// bytes captured at parse time. After this clear the next save
|
|
1352
|
+
// re-serialises the full shape tree from the structured fields.
|
|
1353
|
+
if (series.spPr && "_rawXml" in series.spPr) {
|
|
1354
|
+
delete series.spPr._rawXml;
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
if (options.marker !== undefined) {
|
|
1358
|
+
series.marker = buildMarkerFromOpts(options.marker);
|
|
1359
|
+
}
|
|
1360
|
+
if (options.dataLabels !== undefined) {
|
|
1361
|
+
series.dataLabels = buildDataLabelsFromOpts(options.dataLabels);
|
|
1362
|
+
}
|
|
1363
|
+
if (options.trendline !== undefined) {
|
|
1364
|
+
const trendlineOpts = Array.isArray(options.trendline)
|
|
1365
|
+
? options.trendline
|
|
1366
|
+
: [options.trendline];
|
|
1367
|
+
series.trendlines =
|
|
1368
|
+
trendlineOpts.map(buildTrendlineFromOpts);
|
|
1369
|
+
}
|
|
1370
|
+
if (options.dataPoints !== undefined) {
|
|
1371
|
+
series.dataPoints =
|
|
1372
|
+
options.dataPoints.map(buildDataPointFromOpts);
|
|
1373
|
+
}
|
|
1374
|
+
if (options.errorBars !== undefined) {
|
|
1375
|
+
const errorBars = Array.isArray(options.errorBars) ? options.errorBars : [options.errorBars];
|
|
1376
|
+
// Validate each error-bar config independently. Previously the
|
|
1377
|
+
// patch path relied on the outer `validateSeriesPatchOptions` call
|
|
1378
|
+
// at the top of this function, but that skip when `chartType` is
|
|
1379
|
+
// omitted. `CT_ErrBars` requires `errValType`, and without a
|
|
1380
|
+
// validator an options object missing `type` would silently emit
|
|
1381
|
+
// `<c:errValType/>` (no attribute) → schema-invalid output.
|
|
1382
|
+
for (const eb of errorBars) {
|
|
1383
|
+
validateErrorBarsOptions(eb, "series.errorBars");
|
|
1384
|
+
}
|
|
1385
|
+
const built = errorBars.map(buildErrorBarsFromOpts);
|
|
1386
|
+
series.errorBars =
|
|
1387
|
+
chartType === "scatter" || chartType === "bubble" ? built : built[0];
|
|
1388
|
+
}
|
|
1389
|
+
// NOTE: classic chart series have no `txPr` slot in OOXML — see the
|
|
1390
|
+
// `AddChartSeriesOptions` type comment. The field was removed from
|
|
1391
|
+
// the options bag so this patch branch is gone as well.
|
|
1392
|
+
if (options.pictureFill !== undefined) {
|
|
1393
|
+
applyPictureFillToSeries(series, options.pictureFill, {
|
|
1394
|
+
supportsPictureOptions: chartType === "bar" || chartType === "bar3D"
|
|
1395
|
+
});
|
|
1396
|
+
}
|
|
1397
|
+
if (options.smooth !== undefined) {
|
|
1398
|
+
series.smooth = options.smooth;
|
|
1399
|
+
}
|
|
1400
|
+
if (options.invertIfNegative !== undefined) {
|
|
1401
|
+
series.invertIfNegative =
|
|
1402
|
+
options.invertIfNegative;
|
|
1403
|
+
}
|
|
1404
|
+
if (options.explosion !== undefined) {
|
|
1405
|
+
series.explosion = options.explosion;
|
|
1406
|
+
}
|
|
1407
|
+
if (options.bubble3D !== undefined) {
|
|
1408
|
+
series.bubble3D = options.bubble3D;
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
function hasSeriesShapePatch(options) {
|
|
1412
|
+
return (options.spPr !== undefined ||
|
|
1413
|
+
options.fill !== undefined ||
|
|
1414
|
+
options.line !== undefined ||
|
|
1415
|
+
options.lineWidth !== undefined ||
|
|
1416
|
+
options.lineDash !== undefined);
|
|
1417
|
+
}
|
|
1418
|
+
function buildTitle(input) {
|
|
1419
|
+
if (typeof input === "string") {
|
|
1420
|
+
return {
|
|
1421
|
+
text: {
|
|
1422
|
+
paragraphs: [{ runs: [{ text: input }] }]
|
|
1423
|
+
},
|
|
1424
|
+
overlay: false
|
|
1425
|
+
};
|
|
1426
|
+
}
|
|
1427
|
+
if ("formula" in input) {
|
|
1428
|
+
return {
|
|
1429
|
+
strRef: makeStrRef(input.formula),
|
|
1430
|
+
overlay: false
|
|
1431
|
+
};
|
|
1432
|
+
}
|
|
1433
|
+
// ChartRichText
|
|
1434
|
+
return {
|
|
1435
|
+
text: input,
|
|
1436
|
+
overlay: false
|
|
1437
|
+
};
|
|
1438
|
+
}
|
|
1439
|
+
function buildDataTableFromOpts(opts) {
|
|
1440
|
+
if (!opts) {
|
|
1441
|
+
return undefined;
|
|
1442
|
+
}
|
|
1443
|
+
if (opts === true) {
|
|
1444
|
+
return { showHorzBorder: true, showVertBorder: true, showOutline: true, showKeys: true };
|
|
1445
|
+
}
|
|
1446
|
+
return {
|
|
1447
|
+
showHorzBorder: opts.showHorzBorder,
|
|
1448
|
+
showVertBorder: opts.showVertBorder,
|
|
1449
|
+
showOutline: opts.showOutline,
|
|
1450
|
+
showKeys: opts.showKeys
|
|
1451
|
+
};
|
|
1452
|
+
}
|
|
1453
|
+
function buildUpDownBarsFromOpts(opts) {
|
|
1454
|
+
if (!opts) {
|
|
1455
|
+
return undefined;
|
|
1456
|
+
}
|
|
1457
|
+
if (opts === true) {
|
|
1458
|
+
return { gapWidth: 150 };
|
|
1459
|
+
}
|
|
1460
|
+
const udb = { gapWidth: opts.gapWidth ?? 150 };
|
|
1461
|
+
const upBars = toShapeProperties(opts.upBars);
|
|
1462
|
+
if (upBars) {
|
|
1463
|
+
udb.upBars = upBars;
|
|
1464
|
+
}
|
|
1465
|
+
const downBars = toShapeProperties(opts.downBars);
|
|
1466
|
+
if (downBars) {
|
|
1467
|
+
udb.downBars = downBars;
|
|
1468
|
+
}
|
|
1469
|
+
return udb;
|
|
1470
|
+
}
|
|
1471
|
+
function buildLegend(opts) {
|
|
1472
|
+
if (opts.showLegend === false) {
|
|
1473
|
+
return undefined;
|
|
1474
|
+
}
|
|
1475
|
+
return {
|
|
1476
|
+
legendPos: opts.legendPosition ?? "b",
|
|
1477
|
+
overlay: false
|
|
1478
|
+
};
|
|
1479
|
+
}
|
|
1480
|
+
/**
|
|
1481
|
+
* Apply user-specified axis options to a built axis.
|
|
1482
|
+
*/
|
|
1483
|
+
function applyAxisOptions(axis, opts) {
|
|
1484
|
+
if (!opts) {
|
|
1485
|
+
return;
|
|
1486
|
+
}
|
|
1487
|
+
if (opts.title !== undefined) {
|
|
1488
|
+
// Replace the title text but preserve any previously-applied
|
|
1489
|
+
// `titleOptions` (layout, overlay, spPr, txPr) attached to
|
|
1490
|
+
// `axis.title`. Combo charts call `applyAxisOptions` repeatedly on
|
|
1491
|
+
// a shared axis (see `buildChartTypeGroup` reuse paths), and a
|
|
1492
|
+
// wholesale replacement here discarded every field except the new
|
|
1493
|
+
// text. We graft the new title runs onto the existing frame.
|
|
1494
|
+
const fresh = buildTitle(opts.title);
|
|
1495
|
+
axis.title = axis.title
|
|
1496
|
+
? {
|
|
1497
|
+
...axis.title,
|
|
1498
|
+
strRef: fresh.strRef,
|
|
1499
|
+
text: fresh.text,
|
|
1500
|
+
rawTx: fresh.rawTx
|
|
1501
|
+
}
|
|
1502
|
+
: fresh;
|
|
1503
|
+
}
|
|
1504
|
+
if (opts.titleOptions) {
|
|
1505
|
+
if (!axis.title) {
|
|
1506
|
+
axis.title = { overlay: false };
|
|
1507
|
+
}
|
|
1508
|
+
applyTitleOptions(axis.title, opts.titleOptions);
|
|
1509
|
+
}
|
|
1510
|
+
if (opts.numFmt !== undefined) {
|
|
1511
|
+
// Merge onto any prior `numFmt` so a later call that supplies
|
|
1512
|
+
// `opts.numFmt` without `opts.numFmtLinked` doesn't reset
|
|
1513
|
+
// `sourceLinked` back to `undefined`. Same class of bug as the
|
|
1514
|
+
// title replacement above — triggered when combo-chart flows
|
|
1515
|
+
// apply options to a shared axis in multiple passes.
|
|
1516
|
+
axis.numFmt = {
|
|
1517
|
+
...axis.numFmt,
|
|
1518
|
+
formatCode: opts.numFmt,
|
|
1519
|
+
...(opts.numFmtLinked !== undefined ? { sourceLinked: opts.numFmtLinked } : {})
|
|
1520
|
+
};
|
|
1521
|
+
}
|
|
1522
|
+
if (opts.min !== undefined || opts.max !== undefined || opts.orientation || opts.logBase) {
|
|
1523
|
+
if (!axis.scaling) {
|
|
1524
|
+
axis.scaling = {};
|
|
1525
|
+
}
|
|
1526
|
+
if (opts.min !== undefined) {
|
|
1527
|
+
axis.scaling.min = opts.min;
|
|
1528
|
+
}
|
|
1529
|
+
if (opts.max !== undefined) {
|
|
1530
|
+
axis.scaling.max = opts.max;
|
|
1531
|
+
}
|
|
1532
|
+
if (opts.orientation) {
|
|
1533
|
+
axis.scaling.orientation = opts.orientation;
|
|
1534
|
+
}
|
|
1535
|
+
if (opts.logBase !== undefined) {
|
|
1536
|
+
axis.scaling.logBase = opts.logBase;
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
if (opts.majorUnit !== undefined && axis.axisType === "val") {
|
|
1540
|
+
axis.majorUnit = opts.majorUnit;
|
|
1541
|
+
}
|
|
1542
|
+
if (opts.minorUnit !== undefined && axis.axisType === "val") {
|
|
1543
|
+
axis.minorUnit = opts.minorUnit;
|
|
1544
|
+
}
|
|
1545
|
+
if (opts.majorTickMark) {
|
|
1546
|
+
axis.majorTickMark = opts.majorTickMark;
|
|
1547
|
+
}
|
|
1548
|
+
if (opts.minorTickMark) {
|
|
1549
|
+
axis.minorTickMark = opts.minorTickMark;
|
|
1550
|
+
}
|
|
1551
|
+
if (opts.tickLblPos) {
|
|
1552
|
+
axis.tickLblPos = opts.tickLblPos;
|
|
1553
|
+
}
|
|
1554
|
+
// Major gridlines: the explicit `majorGridlines` boolean is an on/off
|
|
1555
|
+
// switch that must win over any `majorGridlinesStyle`. Callers who
|
|
1556
|
+
// pass both `{ majorGridlines: false, majorGridlinesStyle: { … } }`
|
|
1557
|
+
// mean "hide them, even though I've authored a style for the ON
|
|
1558
|
+
// state" — dropping the style and emitting nothing is the right
|
|
1559
|
+
// call. The previous code checked the boolean in an `else if`, so
|
|
1560
|
+
// an explicit `false` was silently ignored whenever a style was
|
|
1561
|
+
// supplied and the gridlines stayed drawn.
|
|
1562
|
+
const majorGridlinesStyle = toShapeProperties(opts.majorGridlinesStyle);
|
|
1563
|
+
if (opts.majorGridlines === false) {
|
|
1564
|
+
axis.majorGridlines = undefined;
|
|
1565
|
+
}
|
|
1566
|
+
else if (majorGridlinesStyle) {
|
|
1567
|
+
axis.majorGridlines = majorGridlinesStyle;
|
|
1568
|
+
}
|
|
1569
|
+
else if (opts.majorGridlines === true) {
|
|
1570
|
+
axis.majorGridlines = {};
|
|
1571
|
+
}
|
|
1572
|
+
const minorGridlinesStyle = toShapeProperties(opts.minorGridlinesStyle);
|
|
1573
|
+
if (opts.minorGridlines === false) {
|
|
1574
|
+
axis.minorGridlines = undefined;
|
|
1575
|
+
}
|
|
1576
|
+
else if (minorGridlinesStyle) {
|
|
1577
|
+
axis.minorGridlines = minorGridlinesStyle;
|
|
1578
|
+
}
|
|
1579
|
+
else if (opts.minorGridlines === true) {
|
|
1580
|
+
axis.minorGridlines = {};
|
|
1581
|
+
}
|
|
1582
|
+
if (opts.hidden !== undefined) {
|
|
1583
|
+
axis.delete = opts.hidden;
|
|
1584
|
+
}
|
|
1585
|
+
if (opts.crossBetween !== undefined && axis.axisType === "val") {
|
|
1586
|
+
axis.crossBetween = opts.crossBetween;
|
|
1587
|
+
}
|
|
1588
|
+
// Text properties — structured txPr takes priority over textRotation
|
|
1589
|
+
if (opts.txPr) {
|
|
1590
|
+
axis.txPr = opts.txPr;
|
|
1591
|
+
}
|
|
1592
|
+
else if (opts.textRotation !== undefined) {
|
|
1593
|
+
// `a:bodyPr/@rot` is `ST_Angle` = `xsd:int`. Round so a caller
|
|
1594
|
+
// passing fractional degrees (or a value like 1.05° that IEEE 754
|
|
1595
|
+
// turns into 63000.000000000004) doesn't inject `"NaN"` /
|
|
1596
|
+
// `"63000.00000000001"` as an attribute value.
|
|
1597
|
+
axis.txPr = { rotation: Math.round(opts.textRotation * 60000) };
|
|
1598
|
+
}
|
|
1599
|
+
if (opts.lblAlgn !== undefined && axis.axisType === "cat") {
|
|
1600
|
+
axis.lblAlgn = opts.lblAlgn;
|
|
1601
|
+
}
|
|
1602
|
+
if (opts.lblOffset !== undefined && axis.axisType === "cat") {
|
|
1603
|
+
axis.lblOffset = opts.lblOffset;
|
|
1604
|
+
}
|
|
1605
|
+
if (opts.tickLblSkip !== undefined && axis.axisType === "cat") {
|
|
1606
|
+
axis.tickLblSkip = opts.tickLblSkip;
|
|
1607
|
+
}
|
|
1608
|
+
if (opts.tickMarkSkip !== undefined && axis.axisType === "cat") {
|
|
1609
|
+
axis.tickMarkSkip = opts.tickMarkSkip;
|
|
1610
|
+
}
|
|
1611
|
+
if (opts.crosses !== undefined) {
|
|
1612
|
+
axis.crosses = opts.crosses;
|
|
1613
|
+
}
|
|
1614
|
+
if (opts.crossesAt !== undefined) {
|
|
1615
|
+
axis.crossesAt = opts.crossesAt;
|
|
1616
|
+
}
|
|
1617
|
+
if (axis.axisType === "val") {
|
|
1618
|
+
const valAx = axis;
|
|
1619
|
+
if (opts.displayUnits !== undefined ||
|
|
1620
|
+
opts.customUnit !== undefined ||
|
|
1621
|
+
opts.displayUnitsLabel !== undefined) {
|
|
1622
|
+
valAx.dispUnits = valAx.dispUnits ?? {};
|
|
1623
|
+
if (opts.displayUnits !== undefined) {
|
|
1624
|
+
valAx.dispUnits.builtInUnit = opts.displayUnits;
|
|
1625
|
+
}
|
|
1626
|
+
if (opts.customUnit !== undefined) {
|
|
1627
|
+
valAx.dispUnits.custUnit = opts.customUnit;
|
|
1628
|
+
}
|
|
1629
|
+
if (opts.displayUnitsLabel !== undefined) {
|
|
1630
|
+
valAx.dispUnits.label = buildTitle(opts.displayUnitsLabel);
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1634
|
+
// Date-axis-specific units
|
|
1635
|
+
if (axis.axisType === "date") {
|
|
1636
|
+
const dateAx = axis;
|
|
1637
|
+
if (opts.baseTimeUnit !== undefined) {
|
|
1638
|
+
dateAx.baseTimeUnit = opts.baseTimeUnit;
|
|
1639
|
+
}
|
|
1640
|
+
if (opts.majorTimeUnit !== undefined) {
|
|
1641
|
+
dateAx.majorTimeUnit = opts.majorTimeUnit;
|
|
1642
|
+
}
|
|
1643
|
+
if (opts.minorTimeUnit !== undefined) {
|
|
1644
|
+
dateAx.minorTimeUnit = opts.minorTimeUnit;
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
// Structured spPr takes priority over line-only shortcuts
|
|
1648
|
+
const customSpPr = toShapeProperties(opts.spPr);
|
|
1649
|
+
if (customSpPr) {
|
|
1650
|
+
axis.spPr = customSpPr;
|
|
1651
|
+
}
|
|
1652
|
+
else if (opts.lineColor || opts.lineWidth !== undefined || opts.lineDash) {
|
|
1653
|
+
const line = {};
|
|
1654
|
+
if (opts.lineColor) {
|
|
1655
|
+
line.color = hexToColor(opts.lineColor);
|
|
1656
|
+
}
|
|
1657
|
+
if (opts.lineWidth !== undefined) {
|
|
1658
|
+
line.width = Math.round(opts.lineWidth * EMU_PER_POINT); // pt to EMU
|
|
1659
|
+
}
|
|
1660
|
+
if (opts.lineDash) {
|
|
1661
|
+
line.dash = opts.lineDash;
|
|
1662
|
+
}
|
|
1663
|
+
if (!axis.spPr) {
|
|
1664
|
+
axis.spPr = {};
|
|
1665
|
+
}
|
|
1666
|
+
axis.spPr.line = line;
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
function buildCatValAxes(axIds) {
|
|
1670
|
+
const catAxId = axIds.alloc();
|
|
1671
|
+
const valAxId = axIds.alloc();
|
|
1672
|
+
return {
|
|
1673
|
+
catAx: {
|
|
1674
|
+
axisType: "cat",
|
|
1675
|
+
axId: catAxId,
|
|
1676
|
+
scaling: { orientation: "minMax" },
|
|
1677
|
+
delete: false,
|
|
1678
|
+
axPos: "b",
|
|
1679
|
+
crossAx: valAxId,
|
|
1680
|
+
crosses: "autoZero",
|
|
1681
|
+
auto: true,
|
|
1682
|
+
lblAlgn: "ctr",
|
|
1683
|
+
lblOffset: 100
|
|
1684
|
+
},
|
|
1685
|
+
valAx: {
|
|
1686
|
+
axisType: "val",
|
|
1687
|
+
axId: valAxId,
|
|
1688
|
+
scaling: { orientation: "minMax" },
|
|
1689
|
+
delete: false,
|
|
1690
|
+
axPos: "l",
|
|
1691
|
+
crossAx: catAxId,
|
|
1692
|
+
crosses: "autoZero",
|
|
1693
|
+
crossBetween: "between",
|
|
1694
|
+
majorGridlines: {}
|
|
1695
|
+
}
|
|
1696
|
+
};
|
|
1697
|
+
}
|
|
1698
|
+
function buildValValAxes(axIds) {
|
|
1699
|
+
const xAxId = axIds.alloc();
|
|
1700
|
+
const yAxId = axIds.alloc();
|
|
1701
|
+
return {
|
|
1702
|
+
xAx: {
|
|
1703
|
+
axisType: "val",
|
|
1704
|
+
axId: xAxId,
|
|
1705
|
+
scaling: { orientation: "minMax" },
|
|
1706
|
+
delete: false,
|
|
1707
|
+
axPos: "b",
|
|
1708
|
+
crossAx: yAxId,
|
|
1709
|
+
crosses: "autoZero"
|
|
1710
|
+
},
|
|
1711
|
+
yAx: {
|
|
1712
|
+
axisType: "val",
|
|
1713
|
+
axId: yAxId,
|
|
1714
|
+
scaling: { orientation: "minMax" },
|
|
1715
|
+
delete: false,
|
|
1716
|
+
axPos: "l",
|
|
1717
|
+
crossAx: xAxId,
|
|
1718
|
+
crosses: "autoZero",
|
|
1719
|
+
majorGridlines: {}
|
|
1720
|
+
}
|
|
1721
|
+
};
|
|
1722
|
+
}
|
|
1723
|
+
function buildCatValSerAxes(axIds) {
|
|
1724
|
+
const { catAx, valAx } = buildCatValAxes(axIds);
|
|
1725
|
+
const serAxId = axIds.alloc();
|
|
1726
|
+
// serAx crosses the catAx
|
|
1727
|
+
const serAx = {
|
|
1728
|
+
axisType: "ser",
|
|
1729
|
+
axId: serAxId,
|
|
1730
|
+
scaling: { orientation: "minMax" },
|
|
1731
|
+
delete: false,
|
|
1732
|
+
axPos: "b",
|
|
1733
|
+
crossAx: catAx.axId,
|
|
1734
|
+
crosses: "autoZero"
|
|
1735
|
+
};
|
|
1736
|
+
return { catAx, valAx, serAx };
|
|
1737
|
+
}
|
|
1738
|
+
function buildChartTypeGroup(opts, seriesOpts, axIds) {
|
|
1739
|
+
const type = opts.type;
|
|
1740
|
+
let result;
|
|
1741
|
+
switch (type) {
|
|
1742
|
+
case "bar": {
|
|
1743
|
+
const { catAx, valAx } = buildCatValAxes(axIds);
|
|
1744
|
+
const barDir = opts.barDir ?? "col";
|
|
1745
|
+
// Horizontal bar charts (`barDir="bar"`) swap the axis
|
|
1746
|
+
// directions: the category axis is on the left (vertical),
|
|
1747
|
+
// value axis at the bottom (horizontal). Excel itself emits
|
|
1748
|
+
// `axPos="l"` / `axPos="b"` respectively in that case.
|
|
1749
|
+
// `buildCatValAxes` produces the column-chart defaults
|
|
1750
|
+
// (`catAx.axPos="b"`, `valAx.axPos="l"`), which were left
|
|
1751
|
+
// unchanged for horizontal bar charts — the resulting XML
|
|
1752
|
+
// still rendered in Excel because Excel infers orientation
|
|
1753
|
+
// from `c:barDir`, but the renderer's `pickAxis` (position-
|
|
1754
|
+
// based) picked the wrong axis for gridlines / tick labels on
|
|
1755
|
+
// horizontal bars built via `addBarChart`.
|
|
1756
|
+
//
|
|
1757
|
+
// We deliberately do NOT move `majorGridlines` between axes —
|
|
1758
|
+
// `<c:majorGridlines/>` on a value axis means "draw gridlines
|
|
1759
|
+
// perpendicular to the value axis at every tick". When the
|
|
1760
|
+
// value axis is horizontal (bottom), the renderer correctly
|
|
1761
|
+
// projects those as vertical strokes because it picks up
|
|
1762
|
+
// gridlines from the X-axis slot (by `axPos`), not by
|
|
1763
|
+
// axis-type.
|
|
1764
|
+
if (barDir === "bar") {
|
|
1765
|
+
catAx.axPos = "l";
|
|
1766
|
+
valAx.axPos = "b";
|
|
1767
|
+
}
|
|
1768
|
+
const group = {
|
|
1769
|
+
type,
|
|
1770
|
+
barDir,
|
|
1771
|
+
grouping: opts.grouping ?? "clustered",
|
|
1772
|
+
varyColors: opts.varyColors,
|
|
1773
|
+
series: seriesOpts.map(buildBarSeries),
|
|
1774
|
+
gapWidth: opts.gapWidth ?? 150,
|
|
1775
|
+
overlap: opts.overlap,
|
|
1776
|
+
serLines: opts.serLines ? {} : undefined,
|
|
1777
|
+
axisIds: [catAx.axId, valAx.axId]
|
|
1778
|
+
};
|
|
1779
|
+
result = { group, axes: [catAx, valAx] };
|
|
1780
|
+
break;
|
|
1781
|
+
}
|
|
1782
|
+
case "bar3D": {
|
|
1783
|
+
const { catAx, valAx, serAx } = buildCatValSerAxes(axIds);
|
|
1784
|
+
const barDir = opts.barDir ?? "col";
|
|
1785
|
+
if (barDir === "bar") {
|
|
1786
|
+
catAx.axPos = "l";
|
|
1787
|
+
valAx.axPos = "b";
|
|
1788
|
+
}
|
|
1789
|
+
// `CT_Bar3DChart` does NOT accept `overlap` or `serLines` — both
|
|
1790
|
+
// are 2-D-only. Reject them loud here so the options validator
|
|
1791
|
+
// catches the mistake before the writer silently drops them.
|
|
1792
|
+
if (opts.overlap !== undefined) {
|
|
1793
|
+
throw new ChartOptionsError('bar3D charts do not support `overlap` (valid only on 2-D `bar`). Remove the field or switch to `type: "bar"`.');
|
|
1794
|
+
}
|
|
1795
|
+
if (opts.serLines !== undefined) {
|
|
1796
|
+
throw new ChartOptionsError('bar3D charts do not support `serLines` (valid only on 2-D `bar`). Remove the field or switch to `type: "bar"`.');
|
|
1797
|
+
}
|
|
1798
|
+
const group = {
|
|
1799
|
+
type,
|
|
1800
|
+
barDir,
|
|
1801
|
+
grouping: opts.grouping ?? "clustered",
|
|
1802
|
+
varyColors: opts.varyColors,
|
|
1803
|
+
series: seriesOpts.map(buildBarSeries),
|
|
1804
|
+
gapWidth: opts.gapWidth ?? 150,
|
|
1805
|
+
gapDepth: opts.gapDepth,
|
|
1806
|
+
shape: opts.shape,
|
|
1807
|
+
axisIds: [catAx.axId, valAx.axId, serAx.axId]
|
|
1808
|
+
};
|
|
1809
|
+
result = { group, axes: [catAx, valAx, serAx] };
|
|
1810
|
+
break;
|
|
1811
|
+
}
|
|
1812
|
+
case "line": {
|
|
1813
|
+
const { catAx, valAx } = buildCatValAxes(axIds);
|
|
1814
|
+
const group = {
|
|
1815
|
+
type,
|
|
1816
|
+
grouping: opts.grouping ?? "standard",
|
|
1817
|
+
varyColors: opts.varyColors,
|
|
1818
|
+
series: seriesOpts.map(buildLineSeries),
|
|
1819
|
+
marker: opts.showMarker ?? true,
|
|
1820
|
+
smooth: opts.smooth,
|
|
1821
|
+
hiLowLines: opts.hiLowLines ? {} : undefined,
|
|
1822
|
+
upDownBars: buildUpDownBarsFromOpts(opts.upDownBars),
|
|
1823
|
+
dropLines: opts.dropLines ? {} : undefined,
|
|
1824
|
+
axisIds: [catAx.axId, valAx.axId]
|
|
1825
|
+
};
|
|
1826
|
+
result = { group, axes: [catAx, valAx] };
|
|
1827
|
+
break;
|
|
1828
|
+
}
|
|
1829
|
+
case "line3D": {
|
|
1830
|
+
const { catAx, valAx, serAx } = buildCatValSerAxes(axIds);
|
|
1831
|
+
// `CT_Line3DChart` does NOT accept `marker`, `smooth`,
|
|
1832
|
+
// `hiLowLines`, or `upDownBars` — all 2-D-only.
|
|
1833
|
+
// Rejected upstream in validateChartLevelOptions.
|
|
1834
|
+
const group = {
|
|
1835
|
+
type,
|
|
1836
|
+
grouping: opts.grouping ?? "standard",
|
|
1837
|
+
varyColors: opts.varyColors,
|
|
1838
|
+
series: seriesOpts.map(buildLineSeries),
|
|
1839
|
+
dropLines: opts.dropLines ? {} : undefined,
|
|
1840
|
+
gapDepth: opts.gapDepth,
|
|
1841
|
+
axisIds: [catAx.axId, valAx.axId, serAx.axId]
|
|
1842
|
+
};
|
|
1843
|
+
result = { group, axes: [catAx, valAx, serAx] };
|
|
1844
|
+
break;
|
|
1845
|
+
}
|
|
1846
|
+
case "pie":
|
|
1847
|
+
case "pie3D": {
|
|
1848
|
+
const group = {
|
|
1849
|
+
type,
|
|
1850
|
+
varyColors: opts.varyColors ?? true,
|
|
1851
|
+
series: seriesOpts.map(buildPieSeries),
|
|
1852
|
+
firstSliceAng: opts.firstSliceAng ?? 0
|
|
1853
|
+
};
|
|
1854
|
+
result = { group, axes: [] };
|
|
1855
|
+
break;
|
|
1856
|
+
}
|
|
1857
|
+
case "doughnut": {
|
|
1858
|
+
const group = {
|
|
1859
|
+
type: "doughnut",
|
|
1860
|
+
varyColors: opts.varyColors ?? true,
|
|
1861
|
+
series: seriesOpts.map(buildPieSeries),
|
|
1862
|
+
firstSliceAng: opts.firstSliceAng ?? 0,
|
|
1863
|
+
holeSize: opts.holeSize ?? 50
|
|
1864
|
+
};
|
|
1865
|
+
result = { group, axes: [] };
|
|
1866
|
+
break;
|
|
1867
|
+
}
|
|
1868
|
+
case "area": {
|
|
1869
|
+
const { catAx, valAx } = buildCatValAxes(axIds);
|
|
1870
|
+
const group = {
|
|
1871
|
+
type,
|
|
1872
|
+
grouping: opts.grouping ?? "standard",
|
|
1873
|
+
varyColors: opts.varyColors,
|
|
1874
|
+
series: seriesOpts.map(buildAreaSeries),
|
|
1875
|
+
dropLines: opts.dropLines ? {} : undefined,
|
|
1876
|
+
axisIds: [catAx.axId, valAx.axId]
|
|
1877
|
+
};
|
|
1878
|
+
result = { group, axes: [catAx, valAx] };
|
|
1879
|
+
break;
|
|
1880
|
+
}
|
|
1881
|
+
case "area3D": {
|
|
1882
|
+
const { catAx, valAx, serAx } = buildCatValSerAxes(axIds);
|
|
1883
|
+
const group = {
|
|
1884
|
+
type,
|
|
1885
|
+
grouping: opts.grouping ?? "standard",
|
|
1886
|
+
varyColors: opts.varyColors,
|
|
1887
|
+
series: seriesOpts.map(buildAreaSeries),
|
|
1888
|
+
dropLines: opts.dropLines ? {} : undefined,
|
|
1889
|
+
// `CT_Area3DChart` carries `gapDepth`; pass it through from
|
|
1890
|
+
// options. The equivalent lines for `bar3D` / `line3D` already
|
|
1891
|
+
// did this — the omission here silently dropped the option.
|
|
1892
|
+
gapDepth: opts.gapDepth,
|
|
1893
|
+
axisIds: [catAx.axId, valAx.axId, serAx.axId]
|
|
1894
|
+
};
|
|
1895
|
+
result = { group, axes: [catAx, valAx, serAx] };
|
|
1896
|
+
break;
|
|
1897
|
+
}
|
|
1898
|
+
case "scatter": {
|
|
1899
|
+
const { xAx, yAx } = buildValValAxes(axIds);
|
|
1900
|
+
const group = {
|
|
1901
|
+
type: "scatter",
|
|
1902
|
+
scatterStyle: opts.scatterStyle ?? "lineMarker",
|
|
1903
|
+
varyColors: opts.varyColors,
|
|
1904
|
+
series: seriesOpts.map(buildScatterSeries),
|
|
1905
|
+
axisIds: [xAx.axId, yAx.axId]
|
|
1906
|
+
};
|
|
1907
|
+
result = { group, axes: [xAx, yAx] };
|
|
1908
|
+
break;
|
|
1909
|
+
}
|
|
1910
|
+
case "bubble": {
|
|
1911
|
+
const { xAx, yAx } = buildValValAxes(axIds);
|
|
1912
|
+
const group = {
|
|
1913
|
+
type: "bubble",
|
|
1914
|
+
varyColors: opts.varyColors,
|
|
1915
|
+
series: seriesOpts.map(buildBubbleSeries),
|
|
1916
|
+
bubbleScale: opts.bubbleScale,
|
|
1917
|
+
showNegBubbles: opts.showNegBubbles,
|
|
1918
|
+
sizeRepresents: opts.sizeRepresents,
|
|
1919
|
+
axisIds: [xAx.axId, yAx.axId]
|
|
1920
|
+
};
|
|
1921
|
+
result = { group, axes: [xAx, yAx] };
|
|
1922
|
+
break;
|
|
1923
|
+
}
|
|
1924
|
+
case "radar": {
|
|
1925
|
+
const { catAx, valAx } = buildCatValAxes(axIds);
|
|
1926
|
+
const group = {
|
|
1927
|
+
type: "radar",
|
|
1928
|
+
radarStyle: opts.radarStyle ?? "marker",
|
|
1929
|
+
varyColors: opts.varyColors,
|
|
1930
|
+
series: seriesOpts.map(buildRadarSeries),
|
|
1931
|
+
axisIds: [catAx.axId, valAx.axId]
|
|
1932
|
+
};
|
|
1933
|
+
result = { group, axes: [catAx, valAx] };
|
|
1934
|
+
break;
|
|
1935
|
+
}
|
|
1936
|
+
case "stock": {
|
|
1937
|
+
// `CT_StockChart` has no `varyColors` attribute per schema — see
|
|
1938
|
+
// `StockChartGroup` in types.ts. Reject the option here rather
|
|
1939
|
+
// than silently dropping it at emit time so the mistake
|
|
1940
|
+
// surfaces at authoring.
|
|
1941
|
+
if (opts.varyColors !== undefined) {
|
|
1942
|
+
throw new ChartOptionsError("stock charts do not support `varyColors` (not in CT_StockChart). Remove the field or switch to a line / bar / area chart.");
|
|
1943
|
+
}
|
|
1944
|
+
const { catAx, valAx } = buildCatValAxes(axIds);
|
|
1945
|
+
const group = {
|
|
1946
|
+
type: "stock",
|
|
1947
|
+
series: seriesOpts.map(buildLineSeries),
|
|
1948
|
+
hiLowLines: opts.hiLowLines ? {} : undefined,
|
|
1949
|
+
upDownBars: buildUpDownBarsFromOpts(opts.upDownBars),
|
|
1950
|
+
dropLines: opts.dropLines ? {} : undefined,
|
|
1951
|
+
axisIds: [catAx.axId, valAx.axId]
|
|
1952
|
+
};
|
|
1953
|
+
result = { group, axes: [catAx, valAx] };
|
|
1954
|
+
break;
|
|
1955
|
+
}
|
|
1956
|
+
case "surface":
|
|
1957
|
+
case "surface3D": {
|
|
1958
|
+
const { catAx, valAx, serAx } = buildCatValSerAxes(axIds);
|
|
1959
|
+
const group = {
|
|
1960
|
+
type,
|
|
1961
|
+
wireframe: opts.wireframe,
|
|
1962
|
+
series: seriesOpts.map(buildSurfaceSeries),
|
|
1963
|
+
axisIds: [catAx.axId, valAx.axId, serAx.axId]
|
|
1964
|
+
};
|
|
1965
|
+
if (opts.bandFormats && opts.bandFormats.length > 0) {
|
|
1966
|
+
group.bandFormats = opts.bandFormats.map(bf => {
|
|
1967
|
+
const spPr = toShapeProperties(bf.spPr);
|
|
1968
|
+
return { index: bf.index, spPr: spPr ?? {} };
|
|
1969
|
+
});
|
|
1970
|
+
}
|
|
1971
|
+
result = { group, axes: [catAx, valAx, serAx] };
|
|
1972
|
+
break;
|
|
1973
|
+
}
|
|
1974
|
+
case "ofPie": {
|
|
1975
|
+
const group = {
|
|
1976
|
+
type: "ofPie",
|
|
1977
|
+
ofPieType: opts.ofPieType ?? "pie",
|
|
1978
|
+
varyColors: opts.varyColors ?? true,
|
|
1979
|
+
series: seriesOpts.map(buildPieSeries),
|
|
1980
|
+
gapWidth: opts.gapWidth,
|
|
1981
|
+
splitType: opts.splitType,
|
|
1982
|
+
splitPos: opts.splitPos,
|
|
1983
|
+
secondPieSize: opts.secondPieSize,
|
|
1984
|
+
serLines: opts.serLines ? {} : undefined
|
|
1985
|
+
};
|
|
1986
|
+
result = { group, axes: [] };
|
|
1987
|
+
break;
|
|
1988
|
+
}
|
|
1989
|
+
default: {
|
|
1990
|
+
const _exhaustive = type;
|
|
1991
|
+
throw new ChartOptionsError(`Unsupported chart type: ${String(_exhaustive)}.`);
|
|
1992
|
+
}
|
|
1993
|
+
}
|
|
1994
|
+
// Post-process: apply group-level data labels
|
|
1995
|
+
if (opts.dataLabels) {
|
|
1996
|
+
result.group.dataLabels = buildDataLabelsFromOpts(opts.dataLabels);
|
|
1997
|
+
}
|
|
1998
|
+
// Post-process: apply axis options
|
|
1999
|
+
if (result.axes.length > 0) {
|
|
2000
|
+
// For cat+val axes, apply categoryAxis options to first cat/date axis,
|
|
2001
|
+
// and valueAxis options to first val axis.
|
|
2002
|
+
// For scatter/bubble (val+val), categoryAxis → first val (x), valueAxis → second val (y).
|
|
2003
|
+
const catAx = result.axes.find(a => a.axisType === "cat" || a.axisType === "date");
|
|
2004
|
+
const valAx = result.axes.find(a => a.axisType === "val");
|
|
2005
|
+
if (catAx) {
|
|
2006
|
+
applyAxisOptions(catAx, opts.categoryAxis);
|
|
2007
|
+
const vAx = result.axes.find(a => a.axisType === "val");
|
|
2008
|
+
if (vAx) {
|
|
2009
|
+
applyAxisOptions(vAx, opts.valueAxis);
|
|
2010
|
+
}
|
|
2011
|
+
}
|
|
2012
|
+
else if (valAx) {
|
|
2013
|
+
// scatter/bubble: x axis = first val, y axis = second val
|
|
2014
|
+
applyAxisOptions(valAx, opts.categoryAxis);
|
|
2015
|
+
const secondVal = result.axes.find(a => a.axisType === "val" && a.axId !== valAx.axId);
|
|
2016
|
+
if (secondVal) {
|
|
2017
|
+
applyAxisOptions(secondVal, opts.valueAxis);
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
2020
|
+
}
|
|
2021
|
+
return result;
|
|
2022
|
+
}
|
|
2023
|
+
/**
|
|
2024
|
+
* Finalize a ChartModel from a PlotArea and shared options.
|
|
2025
|
+
*/
|
|
2026
|
+
function finalizeChartModel(plotArea, opts) {
|
|
2027
|
+
const chart = {
|
|
2028
|
+
plotArea,
|
|
2029
|
+
plotVisOnly: opts.plotVisOnly ?? true,
|
|
2030
|
+
dispBlanksAs: opts.displayBlanksAs ?? "gap"
|
|
2031
|
+
};
|
|
2032
|
+
if (opts.showDLblsOverMax !== undefined) {
|
|
2033
|
+
chart.showDLblsOverMax = opts.showDLblsOverMax;
|
|
2034
|
+
}
|
|
2035
|
+
if (opts.pivotSource) {
|
|
2036
|
+
// Don't mutate caller-owned `opts.pivotSource` — `extractPivotOptions`
|
|
2037
|
+
// already handles the merge priority between
|
|
2038
|
+
// `opts.pivotChartOptions` (explicit) and `pivotSource.options`
|
|
2039
|
+
// (embedded). Writing back here produced surprising side-effects
|
|
2040
|
+
// when callers reused the same `pivotSource` object across charts.
|
|
2041
|
+
chart.pivotFormats = [{ index: 0 }];
|
|
2042
|
+
}
|
|
2043
|
+
// Title handling has three mutually exclusive shapes:
|
|
2044
|
+
//
|
|
2045
|
+
// 1. `title === null` → explicit suppression. Emit
|
|
2046
|
+
// `autoTitleDeleted="1"` so Excel records the user removed the
|
|
2047
|
+
// auto-title. Do NOT build a title frame even if `titleOptions`
|
|
2048
|
+
// was also provided — the explicit `null` wins over layout /
|
|
2049
|
+
// style hints.
|
|
2050
|
+
// 2. `title` truthy → build the title, optionally apply
|
|
2051
|
+
// `titleOptions` on top.
|
|
2052
|
+
// 3. `title` absent, `titleOptions` set → layout / style for the
|
|
2053
|
+
// auto-generated title. Uncommon but valid.
|
|
2054
|
+
// 4. Everything else → leave `autoTitleDeleted` undefined so
|
|
2055
|
+
// the writer omits it, matching Excel's behaviour for a fresh
|
|
2056
|
+
// unmodified chart.
|
|
2057
|
+
//
|
|
2058
|
+
// Checking `title === null` before `if (opts.title)` is important —
|
|
2059
|
+
// `if (null)` is falsy, so reversing the order landed an explicit
|
|
2060
|
+
// `null` in the `titleOptions`-only branch that built an empty title
|
|
2061
|
+
// frame.
|
|
2062
|
+
if (opts.title === null) {
|
|
2063
|
+
chart.autoTitleDeleted = true;
|
|
2064
|
+
}
|
|
2065
|
+
else if (opts.title) {
|
|
2066
|
+
chart.title = buildTitle(opts.title);
|
|
2067
|
+
if (opts.titleOptions) {
|
|
2068
|
+
applyTitleOptions(chart.title, opts.titleOptions);
|
|
2069
|
+
}
|
|
2070
|
+
chart.autoTitleDeleted = false;
|
|
2071
|
+
}
|
|
2072
|
+
else if (opts.titleOptions) {
|
|
2073
|
+
// Layout/style without title text is unusual but allowed
|
|
2074
|
+
chart.title = { overlay: false };
|
|
2075
|
+
applyTitleOptions(chart.title, opts.titleOptions);
|
|
2076
|
+
chart.autoTitleDeleted = false;
|
|
2077
|
+
}
|
|
2078
|
+
// else: leave autoTitleDeleted undefined so the writer omits it,
|
|
2079
|
+
// matching Excel's behaviour for a fresh unmodified chart — the
|
|
2080
|
+
// automatic title appears unless the user explicitly removes it.
|
|
2081
|
+
const legend = buildLegend(opts);
|
|
2082
|
+
if (legend) {
|
|
2083
|
+
chart.legend = legend;
|
|
2084
|
+
if (opts.legendOptions) {
|
|
2085
|
+
applyLegendOptions(legend, opts.legendOptions);
|
|
2086
|
+
}
|
|
2087
|
+
}
|
|
2088
|
+
// Plot area options (layout, background)
|
|
2089
|
+
if (opts.plotAreaOptions) {
|
|
2090
|
+
if (opts.plotAreaOptions.layout) {
|
|
2091
|
+
plotArea.layout = opts.plotAreaOptions.layout;
|
|
2092
|
+
}
|
|
2093
|
+
const plotSpPr = toShapeProperties(opts.plotAreaOptions.spPr);
|
|
2094
|
+
if (plotSpPr) {
|
|
2095
|
+
plotArea.spPr = plotSpPr;
|
|
2096
|
+
}
|
|
2097
|
+
}
|
|
2098
|
+
if (opts.view3D) {
|
|
2099
|
+
chart.view3D = opts.view3D;
|
|
2100
|
+
}
|
|
2101
|
+
// 3D walls / floor
|
|
2102
|
+
const floor = toShapeProperties(opts.floor);
|
|
2103
|
+
if (floor) {
|
|
2104
|
+
chart.floor = floor;
|
|
2105
|
+
}
|
|
2106
|
+
const sideWall = toShapeProperties(opts.sideWall);
|
|
2107
|
+
if (sideWall) {
|
|
2108
|
+
chart.sideWall = sideWall;
|
|
2109
|
+
}
|
|
2110
|
+
const backWall = toShapeProperties(opts.backWall);
|
|
2111
|
+
if (backWall) {
|
|
2112
|
+
chart.backWall = backWall;
|
|
2113
|
+
}
|
|
2114
|
+
return {
|
|
2115
|
+
chart,
|
|
2116
|
+
style: opts.style,
|
|
2117
|
+
roundedCorners: false,
|
|
2118
|
+
lang: "en-US",
|
|
2119
|
+
pivotSource: opts.pivotSource ? buildPivotSourceXml(opts.pivotSource) : undefined,
|
|
2120
|
+
pivotOptions: extractPivotOptions(opts.pivotSource, opts.pivotChartOptions)
|
|
2121
|
+
};
|
|
2122
|
+
}
|
|
2123
|
+
/**
|
|
2124
|
+
* Resolve the effective {@link PivotChartOptions} for a model built from
|
|
2125
|
+
* {@link AddChartOptions}.
|
|
2126
|
+
*
|
|
2127
|
+
* Accepts two redundant inputs so callers can pass the structured metadata
|
|
2128
|
+
* via either the top-level `pivotChartOptions` field or the `pivotSource`
|
|
2129
|
+
* object (its `options` sub-field, retained for ergonomic clustering).
|
|
2130
|
+
* When both are set the top-level value wins — this matches the precedence
|
|
2131
|
+
* used elsewhere in `chart-builder.ts` when the same setting has two
|
|
2132
|
+
* spellings.
|
|
2133
|
+
*
|
|
2134
|
+
* Returns `undefined` when no options are provided so the writer can skip
|
|
2135
|
+
* emitting an empty `c14:pivotOptions` element.
|
|
2136
|
+
*/
|
|
2137
|
+
function extractPivotOptions(source, explicit) {
|
|
2138
|
+
if (explicit) {
|
|
2139
|
+
return explicit;
|
|
2140
|
+
}
|
|
2141
|
+
if (source && typeof source === "object" && source.options) {
|
|
2142
|
+
return source.options;
|
|
2143
|
+
}
|
|
2144
|
+
return undefined;
|
|
2145
|
+
}
|
|
2146
|
+
function applyTitleOptions(title, opts) {
|
|
2147
|
+
if (opts.layout) {
|
|
2148
|
+
title.layout = opts.layout;
|
|
2149
|
+
}
|
|
2150
|
+
if (opts.overlay !== undefined) {
|
|
2151
|
+
title.overlay = opts.overlay;
|
|
2152
|
+
}
|
|
2153
|
+
const spPr = toShapeProperties(opts.spPr);
|
|
2154
|
+
if (spPr) {
|
|
2155
|
+
title.spPr = spPr;
|
|
2156
|
+
}
|
|
2157
|
+
if (opts.txPr) {
|
|
2158
|
+
title.txPr = opts.txPr;
|
|
2159
|
+
}
|
|
2160
|
+
}
|
|
2161
|
+
function applyLegendOptions(legend, opts) {
|
|
2162
|
+
if (opts.layout) {
|
|
2163
|
+
legend.layout = opts.layout;
|
|
2164
|
+
}
|
|
2165
|
+
if (opts.overlay !== undefined) {
|
|
2166
|
+
legend.overlay = opts.overlay;
|
|
2167
|
+
}
|
|
2168
|
+
const spPr = toShapeProperties(opts.spPr);
|
|
2169
|
+
if (spPr) {
|
|
2170
|
+
legend.spPr = spPr;
|
|
2171
|
+
}
|
|
2172
|
+
if (opts.txPr) {
|
|
2173
|
+
legend.txPr = opts.txPr;
|
|
2174
|
+
}
|
|
2175
|
+
if (opts.entries && opts.entries.length > 0) {
|
|
2176
|
+
legend.legendEntries = opts.entries.map(e => {
|
|
2177
|
+
const entry = { index: e.index };
|
|
2178
|
+
if (e.hidden) {
|
|
2179
|
+
entry.delete = true;
|
|
2180
|
+
}
|
|
2181
|
+
if (e.txPr) {
|
|
2182
|
+
entry.txPr = e.txPr;
|
|
2183
|
+
}
|
|
2184
|
+
return entry;
|
|
2185
|
+
});
|
|
2186
|
+
}
|
|
2187
|
+
}
|
|
2188
|
+
/**
|
|
2189
|
+
* Build a full ChartModel from the simplified AddChartOptions.
|
|
2190
|
+
*/
|
|
2191
|
+
export function buildChartModel(opts) {
|
|
2192
|
+
validateChartOptions(opts);
|
|
2193
|
+
const seriesOpts = opts.series ?? [];
|
|
2194
|
+
const axIds = new AxIdAllocator();
|
|
2195
|
+
const { group: chartTypeGroup, axes } = buildChartTypeGroup(opts, seriesOpts, axIds);
|
|
2196
|
+
const plotArea = {
|
|
2197
|
+
chartTypes: [chartTypeGroup],
|
|
2198
|
+
axes,
|
|
2199
|
+
dataTable: buildDataTableFromOpts(opts.dataTable)
|
|
2200
|
+
};
|
|
2201
|
+
return finalizeChartModel(plotArea, opts);
|
|
2202
|
+
}
|
|
2203
|
+
/**
|
|
2204
|
+
* Build a combo chart model with multiple chart type groups.
|
|
2205
|
+
*
|
|
2206
|
+
* Each group can optionally be plotted on secondary axes.
|
|
2207
|
+
* The builder creates primary axes for the first group that needs
|
|
2208
|
+
* them, and secondary axes for any group with `useSecondaryAxis: true`.
|
|
2209
|
+
*/
|
|
2210
|
+
export function buildComboChartModel(opts) {
|
|
2211
|
+
validateComboChartOptions(opts);
|
|
2212
|
+
const axIds = new AxIdAllocator();
|
|
2213
|
+
const chartTypeGroups = [];
|
|
2214
|
+
const allAxes = [];
|
|
2215
|
+
// Create primary axes (shared by all groups without useSecondaryAxis)
|
|
2216
|
+
let primaryCatAx;
|
|
2217
|
+
let primaryValAx;
|
|
2218
|
+
let primarySerAx;
|
|
2219
|
+
let primaryXAx;
|
|
2220
|
+
let primaryYAx;
|
|
2221
|
+
// Create secondary axes (shared by all groups with useSecondaryAxis)
|
|
2222
|
+
let secondaryCatAx;
|
|
2223
|
+
let secondaryValAx;
|
|
2224
|
+
let secondarySerAx;
|
|
2225
|
+
let secondaryXAx;
|
|
2226
|
+
let secondaryYAx;
|
|
2227
|
+
for (const groupOpts of opts.groups) {
|
|
2228
|
+
const seriesOpts = groupOpts.series ?? [];
|
|
2229
|
+
const { group, axes } = buildChartTypeGroup(groupOpts, seriesOpts, axIds);
|
|
2230
|
+
// Detect serAx (3D chart types produce 3 axes: catAx + valAx + serAx)
|
|
2231
|
+
const hasSerAx = axes.length >= 3 && axes[2].axisType === "ser";
|
|
2232
|
+
if (axes.length > 0 && groupOpts.useSecondaryAxis) {
|
|
2233
|
+
// Secondary axis group: create secondary axes if not yet created
|
|
2234
|
+
const isCatVal = axes[0].axisType === "cat";
|
|
2235
|
+
if (isCatVal) {
|
|
2236
|
+
if (!secondaryCatAx) {
|
|
2237
|
+
const sCatId = axIds.alloc();
|
|
2238
|
+
const sValId = axIds.alloc();
|
|
2239
|
+
secondaryCatAx = {
|
|
2240
|
+
axisType: "cat",
|
|
2241
|
+
axId: sCatId,
|
|
2242
|
+
scaling: { orientation: "minMax" },
|
|
2243
|
+
delete: true, // secondary cat axis is typically hidden
|
|
2244
|
+
axPos: "b",
|
|
2245
|
+
crossAx: sValId,
|
|
2246
|
+
crosses: "autoZero",
|
|
2247
|
+
auto: true,
|
|
2248
|
+
lblAlgn: "ctr",
|
|
2249
|
+
lblOffset: 100
|
|
2250
|
+
};
|
|
2251
|
+
secondaryValAx = {
|
|
2252
|
+
axisType: "val",
|
|
2253
|
+
axId: sValId,
|
|
2254
|
+
scaling: { orientation: "minMax" },
|
|
2255
|
+
delete: false,
|
|
2256
|
+
axPos: "r", // right side
|
|
2257
|
+
crossAx: sCatId,
|
|
2258
|
+
crosses: "max", // cross at max of secondary cat axis
|
|
2259
|
+
crossBetween: "between"
|
|
2260
|
+
};
|
|
2261
|
+
allAxes.push(secondaryCatAx, secondaryValAx);
|
|
2262
|
+
}
|
|
2263
|
+
// Apply the group's axis options onto the shared secondary
|
|
2264
|
+
// axes. Without this, the `categoryAxis` / `valueAxis` fields
|
|
2265
|
+
// on a second (or third, …) secondary-axis group were silently
|
|
2266
|
+
// discarded along with the auto-built axes — `numFmt`,
|
|
2267
|
+
// `majorUnit`, `title`, `orientation`, etc. never reached the
|
|
2268
|
+
// shared axis object.
|
|
2269
|
+
applyAxisOptions(secondaryCatAx, groupOpts.categoryAxis);
|
|
2270
|
+
applyAxisOptions(secondaryValAx, groupOpts.valueAxis);
|
|
2271
|
+
if (hasSerAx) {
|
|
2272
|
+
// 3D secondary: create or reuse secondary serAx
|
|
2273
|
+
if (!secondarySerAx) {
|
|
2274
|
+
const sSerAxId = axIds.alloc();
|
|
2275
|
+
secondarySerAx = {
|
|
2276
|
+
axisType: "ser",
|
|
2277
|
+
axId: sSerAxId,
|
|
2278
|
+
scaling: { orientation: "minMax" },
|
|
2279
|
+
delete: false,
|
|
2280
|
+
axPos: "b",
|
|
2281
|
+
crossAx: secondaryCatAx.axId,
|
|
2282
|
+
crosses: "autoZero"
|
|
2283
|
+
};
|
|
2284
|
+
allAxes.push(secondarySerAx);
|
|
2285
|
+
}
|
|
2286
|
+
group.axisIds = [secondaryCatAx.axId, secondaryValAx.axId, secondarySerAx.axId];
|
|
2287
|
+
}
|
|
2288
|
+
else {
|
|
2289
|
+
group.axisIds = [secondaryCatAx.axId, secondaryValAx.axId];
|
|
2290
|
+
}
|
|
2291
|
+
}
|
|
2292
|
+
else {
|
|
2293
|
+
// Scatter/bubble secondary axes (valAx + valAx).
|
|
2294
|
+
//
|
|
2295
|
+
// `crossBetween` is only valid when the value axis crosses a
|
|
2296
|
+
// *category* axis — it specifies the tick-label alignment
|
|
2297
|
+
// relative to category boundaries (`between` / `midCat`). On a
|
|
2298
|
+
// val/val pair it has no defined meaning and strict OOXML
|
|
2299
|
+
// validators flag it. The primary scatter axes in
|
|
2300
|
+
// `buildValValAxes` deliberately omit it, so emit the secondary
|
|
2301
|
+
// scatter axes the same way to stay round-trip consistent.
|
|
2302
|
+
if (!secondaryXAx) {
|
|
2303
|
+
const sXId = axIds.alloc();
|
|
2304
|
+
const sYId = axIds.alloc();
|
|
2305
|
+
secondaryXAx = {
|
|
2306
|
+
axisType: "val",
|
|
2307
|
+
axId: sXId,
|
|
2308
|
+
scaling: { orientation: "minMax" },
|
|
2309
|
+
delete: true,
|
|
2310
|
+
axPos: "b",
|
|
2311
|
+
crossAx: sYId,
|
|
2312
|
+
crosses: "autoZero"
|
|
2313
|
+
};
|
|
2314
|
+
secondaryYAx = {
|
|
2315
|
+
axisType: "val",
|
|
2316
|
+
axId: sYId,
|
|
2317
|
+
scaling: { orientation: "minMax" },
|
|
2318
|
+
delete: false,
|
|
2319
|
+
axPos: "r",
|
|
2320
|
+
crossAx: sXId,
|
|
2321
|
+
crosses: "max"
|
|
2322
|
+
};
|
|
2323
|
+
allAxes.push(secondaryXAx, secondaryYAx);
|
|
2324
|
+
}
|
|
2325
|
+
// `categoryAxis` / `valueAxis` map to scatter x / y respectively
|
|
2326
|
+
// (mirroring the primary scatter path in `buildChartTypeGroup`).
|
|
2327
|
+
applyAxisOptions(secondaryXAx, groupOpts.categoryAxis);
|
|
2328
|
+
applyAxisOptions(secondaryYAx, groupOpts.valueAxis);
|
|
2329
|
+
group.axisIds = [secondaryXAx.axId, secondaryYAx.axId];
|
|
2330
|
+
}
|
|
2331
|
+
// Don't add the auto-generated axes from buildChartTypeGroup
|
|
2332
|
+
}
|
|
2333
|
+
else if (axes.length > 0) {
|
|
2334
|
+
// Primary axis group
|
|
2335
|
+
const isCatVal = axes[0].axisType === "cat";
|
|
2336
|
+
if (isCatVal) {
|
|
2337
|
+
if (!primaryCatAx) {
|
|
2338
|
+
primaryCatAx = axes[0];
|
|
2339
|
+
primaryValAx = axes[1];
|
|
2340
|
+
allAxes.push(primaryCatAx, primaryValAx);
|
|
2341
|
+
if (hasSerAx) {
|
|
2342
|
+
primarySerAx = axes[2];
|
|
2343
|
+
allAxes.push(primarySerAx);
|
|
2344
|
+
}
|
|
2345
|
+
}
|
|
2346
|
+
else {
|
|
2347
|
+
// Reuse primary axes — but apply the current group's axis
|
|
2348
|
+
// options onto the shared objects so its customisations
|
|
2349
|
+
// aren't silently discarded with the throw-away auto-built
|
|
2350
|
+
// axes. Previously only the FIRST group's `categoryAxis` /
|
|
2351
|
+
// `valueAxis` reached the output; every subsequent group's
|
|
2352
|
+
// overrides were dropped on the floor.
|
|
2353
|
+
applyAxisOptions(primaryCatAx, groupOpts.categoryAxis);
|
|
2354
|
+
applyAxisOptions(primaryValAx, groupOpts.valueAxis);
|
|
2355
|
+
if (hasSerAx) {
|
|
2356
|
+
// 3D group needs serAx — create one if the primary set didn't have it
|
|
2357
|
+
if (!primarySerAx) {
|
|
2358
|
+
const serAxId = axIds.alloc();
|
|
2359
|
+
primarySerAx = {
|
|
2360
|
+
axisType: "ser",
|
|
2361
|
+
axId: serAxId,
|
|
2362
|
+
scaling: { orientation: "minMax" },
|
|
2363
|
+
delete: false,
|
|
2364
|
+
axPos: "b",
|
|
2365
|
+
crossAx: primaryCatAx.axId,
|
|
2366
|
+
crosses: "autoZero"
|
|
2367
|
+
};
|
|
2368
|
+
allAxes.push(primarySerAx);
|
|
2369
|
+
}
|
|
2370
|
+
group.axisIds = [primaryCatAx.axId, primaryValAx.axId, primarySerAx.axId];
|
|
2371
|
+
}
|
|
2372
|
+
else {
|
|
2373
|
+
group.axisIds = [primaryCatAx.axId, primaryValAx.axId];
|
|
2374
|
+
}
|
|
2375
|
+
}
|
|
2376
|
+
}
|
|
2377
|
+
else {
|
|
2378
|
+
// Scatter/bubble
|
|
2379
|
+
if (!primaryXAx) {
|
|
2380
|
+
primaryXAx = axes[0];
|
|
2381
|
+
primaryYAx = axes[1];
|
|
2382
|
+
allAxes.push(primaryXAx, primaryYAx);
|
|
2383
|
+
}
|
|
2384
|
+
else {
|
|
2385
|
+
// Same rationale as the cat/val reuse path above — apply this
|
|
2386
|
+
// group's scatter axis options onto the shared val/val pair.
|
|
2387
|
+
applyAxisOptions(primaryXAx, groupOpts.categoryAxis);
|
|
2388
|
+
applyAxisOptions(primaryYAx, groupOpts.valueAxis);
|
|
2389
|
+
group.axisIds = [primaryXAx.axId, primaryYAx.axId];
|
|
2390
|
+
}
|
|
2391
|
+
}
|
|
2392
|
+
}
|
|
2393
|
+
chartTypeGroups.push(group);
|
|
2394
|
+
}
|
|
2395
|
+
// Renumber series index/order globally across all groups.
|
|
2396
|
+
// OOXML requires c:idx and c:order to be unique within the entire chart.
|
|
2397
|
+
// Only renumber if indices are the default per-group values (0, 1, 2...) —
|
|
2398
|
+
// i.e. they would collide across groups. If the user has already assigned
|
|
2399
|
+
// explicit globally-unique indices, leave them untouched (P4.2 fix).
|
|
2400
|
+
if (indicesWouldCollide(chartTypeGroups)) {
|
|
2401
|
+
let globalIdx = 0;
|
|
2402
|
+
for (const grp of chartTypeGroups) {
|
|
2403
|
+
for (const s of grp.series) {
|
|
2404
|
+
s.index = globalIdx;
|
|
2405
|
+
s.order = globalIdx;
|
|
2406
|
+
globalIdx++;
|
|
2407
|
+
}
|
|
2408
|
+
}
|
|
2409
|
+
}
|
|
2410
|
+
const plotArea = {
|
|
2411
|
+
chartTypes: chartTypeGroups,
|
|
2412
|
+
axes: allAxes,
|
|
2413
|
+
dataTable: buildDataTableFromOpts(opts.dataTable)
|
|
2414
|
+
};
|
|
2415
|
+
return finalizeChartModel(plotArea, opts);
|
|
2416
|
+
}
|
|
2417
|
+
/**
|
|
2418
|
+
* Check whether any two groups have colliding series indices.
|
|
2419
|
+
* When true, the combo builder renumbers all series to guarantee uniqueness.
|
|
2420
|
+
*/
|
|
2421
|
+
function indicesWouldCollide(groups) {
|
|
2422
|
+
const seen = new Set();
|
|
2423
|
+
for (const g of groups) {
|
|
2424
|
+
for (const s of g.series) {
|
|
2425
|
+
if (seen.has(s.index)) {
|
|
2426
|
+
return true;
|
|
2427
|
+
}
|
|
2428
|
+
seen.add(s.index);
|
|
2429
|
+
}
|
|
2430
|
+
}
|
|
2431
|
+
return false;
|
|
2432
|
+
}
|