diff --git a/src/helpers/figures/charts/bar_chart.ts b/src/helpers/figures/charts/bar_chart.ts index f5abcbe96e..257d5dd981 100644 --- a/src/helpers/figures/charts/bar_chart.ts +++ b/src/helpers/figures/charts/bar_chart.ts @@ -17,7 +17,6 @@ import { ChartCreationContext, CustomizedDataSet, DataSet, - DatasetDesign, ExcelChartDataset, ExcelChartDefinition, } from "../../../types/chart/chart"; @@ -62,7 +61,7 @@ export class BarChart extends AbstractChart { readonly aggregated?: boolean; readonly type = "bar"; readonly dataSetsHaveTitle: boolean; - readonly dataSetDesign?: DatasetDesign[]; + readonly customDatasets?: CustomizedDataSet[]; readonly axesDesign?: AxesDesign; readonly horizontal?: boolean; readonly showValues?: boolean; @@ -81,7 +80,7 @@ export class BarChart extends AbstractChart { this.stacked = definition.stacked; this.aggregated = definition.aggregated; this.dataSetsHaveTitle = definition.dataSetsHaveTitle; - this.dataSetDesign = definition.dataSets; + this.customDatasets = definition.dataSets; this.axesDesign = definition.axesDesign; this.horizontal = definition.horizontal; this.showValues = definition.showValues; @@ -121,7 +120,7 @@ export class BarChart extends AbstractChart { const range: CustomizedDataSet[] = []; for (const [i, dataSet] of this.dataSets.entries()) { range.push({ - ...this.dataSetDesign?.[i], + ...this.customDatasets?.[i], dataRange: this.getters.getRangeString(dataSet.dataRange, this.sheetId), }); } @@ -162,7 +161,7 @@ export class BarChart extends AbstractChart { const ranges: CustomizedDataSet[] = []; for (const [i, dataSet] of dataSets.entries()) { ranges.push({ - ...this.dataSetDesign?.[i], + ...this.customDatasets?.[i], dataRange: this.getters.getRangeString(dataSet.dataRange, targetSheetId || this.sheetId), }); } @@ -188,7 +187,7 @@ export class BarChart extends AbstractChart { // Excel does not support aggregating labels if (this.aggregated) return undefined; const dataSets: ExcelChartDataset[] = this.dataSets - .map((ds: DataSet) => toExcelDataset(this.getters, ds)) + .map((ds: DataSet, i: number) => toExcelDataset(this.getters, ds, this.customDatasets?.[i])) .filter((ds) => ds.range !== "" && ds.range !== CellErrorType.InvalidReference); const labelRange = toExcelLabelRange( this.getters, diff --git a/src/helpers/figures/charts/chart_common.ts b/src/helpers/figures/charts/chart_common.ts index 604f6e1000..1b5b0e5a5f 100644 --- a/src/helpers/figures/charts/chart_common.ts +++ b/src/helpers/figures/charts/chart_common.ts @@ -24,9 +24,11 @@ import { DataSet, DatasetValues, ExcelChartDataset, + ExcelChartTrendConfiguration, GenericDefinition, } from "../../../types/chart/chart"; import { CellErrorType } from "../../../types/errors"; +import { CHART_TRENDLINE_TYPE_CONVERSION_MAP_REVERSE } from "../../../xlsx/conversion"; import { ColorGenerator, relativeLuminance } from "../../color"; import { formatValue } from "../../format/format"; import { isDefined, largeMax } from "../../misc"; @@ -246,7 +248,11 @@ function createDataSet( /** * Transform a dataSet to a ExcelDataSet */ -export function toExcelDataset(getters: CoreGetters, ds: DataSet): ExcelChartDataset { +export function toExcelDataset( + getters: CoreGetters, + ds: DataSet, + customDs?: CustomizedDataSet +): ExcelChartDataset { const labelZone = ds.labelCell?.zone; let dataZone = ds.dataRange.zone; if (labelZone) { @@ -272,6 +278,24 @@ export function toExcelDataset(getters: CoreGetters, ds: DataSet): ExcelChartDat }; } + let trend: ExcelChartTrendConfiguration | undefined = undefined; + if (customDs?.trend?.type) { + trend = { + type: CHART_TRENDLINE_TYPE_CONVERSION_MAP_REVERSE[customDs.trend.type], + order: customDs?.trend?.order, + color: customDs?.trend?.color, + window: customDs?.trend?.window, + }; + } + if (trend) { + return { + label, + range: getters.getRangeString(dataRange, "forceSheetReference", { useFixedReference: true }), + backgroundColor: ds.backgroundColor, + rightYAxis: ds.rightYAxis, + trend, + }; + } return { label, range: getters.getRangeString(dataRange, "forceSheetReference", { useFixedReference: true }), diff --git a/src/helpers/figures/charts/combo_chart.ts b/src/helpers/figures/charts/combo_chart.ts index 2bedd46c41..af75c159e4 100644 --- a/src/helpers/figures/charts/combo_chart.ts +++ b/src/helpers/figures/charts/combo_chart.ts @@ -156,7 +156,7 @@ export class ComboChart extends AbstractChart { return undefined; } const dataSets: ExcelChartDataset[] = this.dataSets - .map((ds: DataSet) => toExcelDataset(this.getters, ds)) + .map((ds: DataSet, i: number) => toExcelDataset(this.getters, ds, this.dataSetDesign?.[i])) .filter((ds) => ds.range !== "" && ds.range !== CellErrorType.InvalidReference); const labelRange = toExcelLabelRange( this.getters, diff --git a/src/helpers/figures/charts/line_chart.ts b/src/helpers/figures/charts/line_chart.ts index 037e711941..e13d9c4bcd 100644 --- a/src/helpers/figures/charts/line_chart.ts +++ b/src/helpers/figures/charts/line_chart.ts @@ -17,7 +17,6 @@ import { ChartJSRuntime, CustomizedDataSet, DataSet, - DatasetDesign, ExcelChartDataset, ExcelChartDefinition, } from "../../../types/chart/chart"; @@ -65,7 +64,7 @@ export class LineChart extends AbstractChart { readonly type = "line"; readonly dataSetsHaveTitle: boolean; readonly cumulative: boolean; - readonly dataSetDesign?: DatasetDesign[]; + readonly customDatasets?: CustomizedDataSet[]; readonly axesDesign?: AxesDesign; readonly fillArea?: boolean; readonly showValues?: boolean; @@ -86,7 +85,7 @@ export class LineChart extends AbstractChart { this.aggregated = definition.aggregated; this.dataSetsHaveTitle = definition.dataSetsHaveTitle; this.cumulative = definition.cumulative; - this.dataSetDesign = definition.dataSets; + this.customDatasets = definition.dataSets; this.axesDesign = definition.axesDesign; this.fillArea = definition.fillArea; this.showValues = definition.showValues; @@ -137,7 +136,7 @@ export class LineChart extends AbstractChart { const ranges: CustomizedDataSet[] = []; for (const [i, dataSet] of dataSets.entries()) { ranges.push({ - ...this.dataSetDesign?.[i], + ...this.customDatasets?.[i], dataRange: this.getters.getRangeString(dataSet.dataRange, targetSheetId || this.sheetId), }); } @@ -165,7 +164,7 @@ export class LineChart extends AbstractChart { const range: CustomizedDataSet[] = []; for (const [i, dataSet] of this.dataSets.entries()) { range.push({ - ...this.dataSetDesign?.[i], + ...this.customDatasets?.[i], dataRange: this.getters.getRangeString(dataSet.dataRange, this.sheetId), }); } @@ -196,7 +195,7 @@ export class LineChart extends AbstractChart { // Excel does not support aggregating labels if (this.aggregated) return undefined; const dataSets: ExcelChartDataset[] = this.dataSets - .map((ds: DataSet) => toExcelDataset(this.getters, ds)) + .map((ds: DataSet, i: number) => toExcelDataset(this.getters, ds, this.customDatasets?.[i])) .filter((ds) => ds.range !== "" && ds.range !== CellErrorType.InvalidReference); const labelRange = toExcelLabelRange( this.getters, diff --git a/src/helpers/figures/charts/scatter_chart.ts b/src/helpers/figures/charts/scatter_chart.ts index a4e7607687..a4d0a1ac99 100644 --- a/src/helpers/figures/charts/scatter_chart.ts +++ b/src/helpers/figures/charts/scatter_chart.ts @@ -184,7 +184,7 @@ export class ScatterChart extends AbstractChart { return undefined; } const dataSets: ExcelChartDataset[] = this.dataSets - .map((ds: DataSet) => toExcelDataset(this.getters, ds)) + .map((ds: DataSet, i: number) => toExcelDataset(this.getters, ds)) .filter((ds) => ds.range !== ""); const labelRange = toExcelLabelRange( this.getters, diff --git a/src/types/chart/chart.ts b/src/types/chart/chart.ts index d74721d28b..f77410c2d2 100644 --- a/src/types/chart/chart.ts +++ b/src/types/chart/chart.ts @@ -118,8 +118,18 @@ export interface ExcelChartDataset { readonly range: string; readonly backgroundColor?: Color; readonly rightYAxis?: boolean; + readonly trend?: ExcelChartTrendConfiguration; } +export interface ExcelChartTrendConfiguration { + readonly type?: ExcelTrendlineType; + readonly order?: number; + readonly color?: Color; + readonly window?: number; +} + +export type ExcelTrendlineType = "poly" | "exp" | "log" | "movingAvg" | "linear"; + export type ExcelChartType = "line" | "bar" | "pie" | "combo" | "scatter" | "radar"; export interface ExcelChartDefinition { diff --git a/src/xlsx/conversion/conversion_maps.ts b/src/xlsx/conversion/conversion_maps.ts index 53c6e9fad2..768ac2e1b7 100644 --- a/src/xlsx/conversion/conversion_maps.ts +++ b/src/xlsx/conversion/conversion_maps.ts @@ -208,6 +208,20 @@ export const CHART_TYPE_CONVERSION_MAP: Record actual function*/ export const SUBTOTAL_FUNCTION_CONVERSION_MAP: Record = { "1": "AVERAGE", diff --git a/src/xlsx/conversion/figure_conversion.ts b/src/xlsx/conversion/figure_conversion.ts index 2256ce2f63..0555eed542 100644 --- a/src/xlsx/conversion/figure_conversion.ts +++ b/src/xlsx/conversion/figure_conversion.ts @@ -5,12 +5,20 @@ import { toUnboundedZone, zoneToXc, } from "../../helpers"; -import { ChartDefinition, ExcelChartDefinition, FigureData } from "../../types"; +import { + ChartDefinition, + ExcelChartDefinition, + ExcelChartTrendConfiguration, + ExcelTrendlineType, + FigureData, + TrendConfiguration, +} from "../../types"; import { ExcelImage } from "../../types/image"; import { XLSXFigure, XLSXWorksheet } from "../../types/xlsx"; import { convertEMUToDotValue, getColPosition, getRowPosition } from "../helpers/content_helpers"; import { XLSXFigureAnchor } from "./../../types/xlsx"; import { convertColor } from "./color_conversion"; +import { CHART_TRENDLINE_TYPE_CONVERSION_MAP } from "./conversion_maps"; export function convertFigures(sheetData: XLSXWorksheet): FigureData[] { let id = 1; @@ -84,6 +92,7 @@ function convertChartData(chartData: ExcelChartDefinition): ChartDefinition | un dataRange: convertExcelRangeToSheetXC(data.range, dataSetsHaveTitle), label, backgroundColor: data.backgroundColor, + trend: convertExcelTrenline(data.trend), }; }); // For doughnut charts, in chartJS first dataset = outer dataset, in excel first dataset = inner dataset @@ -121,6 +130,30 @@ function convertExcelRangeToSheetXC(range: string, dataSetsHaveTitle: boolean): return getFullReference(sheetName, dataXC); } +function convertExcelTrenline( + trend: ExcelChartTrendConfiguration | undefined +): TrendConfiguration | undefined { + if (!trend) { + return undefined; + } + if (trend.type === "linear") { + return { + type: "polynomial", + order: 1, + color: trend.color, + window: trend.window, + display: true, + }; + } + return { + type: CHART_TRENDLINE_TYPE_CONVERSION_MAP[trend.type as ExcelTrendlineType], + order: trend.order, + color: trend.color, + window: trend.window, + display: true, + }; +} + function getPositionFromAnchor( anchor: XLSXFigureAnchor, sheetData: XLSXWorksheet diff --git a/src/xlsx/extraction/chart_extractor.ts b/src/xlsx/extraction/chart_extractor.ts index b4e8c902e2..d7941af45e 100644 --- a/src/xlsx/extraction/chart_extractor.ts +++ b/src/xlsx/extraction/chart_extractor.ts @@ -1,5 +1,10 @@ import { toHex } from "../../helpers"; -import { ExcelChartDataset, ExcelChartDefinition } from "../../types"; +import { + ExcelChartDataset, + ExcelChartDefinition, + ExcelChartTrendConfiguration, + ExcelTrendlineType, +} from "../../types"; import { XLSXChartType, XLSX_CHART_TYPES } from "../../types/xlsx"; import { CHART_TYPE_CONVERSION_MAP, DRAWING_LEGEND_POSITION_CONVERSION_MAP } from "../conversion"; import { removeTagEscapedNamespaces } from "../helpers/xml_helpers"; @@ -139,6 +144,7 @@ export class XlsxChartExtractor extends XlsxBaseExtractor { required: true, })!, backgroundColor: color ? `${toHex(color.asString())}` : undefined, + trend: this.extractChartTrendline(chartDataElement), }; } ); @@ -146,6 +152,25 @@ export class XlsxChartExtractor extends XlsxBaseExtractor { .flat(); } + private extractChartTrendline( + chartDataElement: Element + ): ExcelChartTrendConfiguration | undefined { + const trendlineElement = this.querySelector(chartDataElement, "c:trendline"); + if (!trendlineElement) { + return undefined; + } + const trendlineType = this.extractChildAttr(trendlineElement, "c:trendlineType", "val"); + const trendlineColor = this.extractChildAttr(trendlineElement, "a:solidFill a:srgbClr", "val"); + const trendlineOrder = this.extractChildAttr(trendlineElement, "c:order", "val"); + const trendlineWindow = this.extractChildAttr(trendlineElement, "c:period", "val"); + return { + type: trendlineType ? (trendlineType.asString() as ExcelTrendlineType) : undefined, + order: trendlineOrder ? trendlineOrder.asNum() : undefined, + window: trendlineWindow ? trendlineWindow.asNum() : undefined, + color: trendlineColor ? `${toHex(trendlineColor.asString())}` : undefined, + }; + } + private extractScatterChartDatasets(chartElement: Element): ExcelChartDataset[] { return this.mapOnElements( { parent: chartElement, query: "c:ser" }, diff --git a/src/xlsx/functions/charts.ts b/src/xlsx/functions/charts.ts index efdd7ada9f..b5050612f2 100644 --- a/src/xlsx/functions/charts.ts +++ b/src/xlsx/functions/charts.ts @@ -211,6 +211,63 @@ function insertTextProperties( `; } +function extractTrendline(trend: ExcelChartDataset["trend"]) { + if (!trend) { + return ""; + } + if (trend.type === "poly" && trend.order) { + if (trend.order > 1) { + return escapeXml/*xml*/ ` + + ${extractTrendlineCommonAttributes(trend)} + + + + `; + } + return escapeXml/*xml*/ ` + + ${extractTrendlineCommonAttributes(trend)} + + + `; + } + if (trend.type === "movingAvg") { + return escapeXml/*xml*/ ` + + ${extractTrendlineCommonAttributes(trend)} + + + + `; + } + return escapeXml/*xml*/ ` + + ${extractTrendlineCommonAttributes(trend)} + + + `; +} + +function extractTrendlineCommonAttributes(trend: ExcelChartDataset["trend"]) { + if (!trend) { + return ""; + } + return escapeXml/*xml*/ ` + + + + + + + + + + + + `; +} + function extractDataSetLabel(label: ExcelChartDataset["label"]): XMLString { if (!label) { return escapeXml/*xml*/ ``; @@ -252,6 +309,7 @@ function addBarChart(chart: ExcelChartDefinition): XMLString { + ${extractTrendline(dataset.trend)} ${extractDataSetLabel(dataset.label)} ${dataShapeProperty} ${ @@ -344,6 +402,7 @@ function addComboChart(chart: ExcelChartDefinition): XMLString { + ${extractTrendline(dataSet.trend)} ${extractDataSetLabel(dataSet.label)} ${shapeProperty({ backgroundColor: firstColor, @@ -500,6 +559,7 @@ function addLineChart(chart: ExcelChartDefinition): XMLString { ${shapeProperty({ backgroundColor: color, line: { color } })} + ${extractTrendline(dataset.trend)} ${extractDataSetLabel(dataset.label)} ${dataShapeProperty} ${ @@ -594,6 +654,7 @@ function addScatterChart(chart: ExcelChartDefinition): XMLString { ${shapeProperty({ backgroundColor: color, line: { color } })} + ${extractTrendline(dataset.trend)} ${extractDataSetLabel(dataset.label)} ${ chart.labelRange diff --git a/tests/__xlsx__/xlsx_demo_data.xlsx b/tests/__xlsx__/xlsx_demo_data.xlsx index 86ab7c1483..19c0c0d0cd 100644 Binary files a/tests/__xlsx__/xlsx_demo_data.xlsx and b/tests/__xlsx__/xlsx_demo_data.xlsx differ diff --git a/tests/xlsx/__snapshots__/xlsx_export.test.ts.snap b/tests/xlsx/__snapshots__/xlsx_export.test.ts.snap index 924364d40b..7fcca7578d 100644 --- a/tests/xlsx/__snapshots__/xlsx_export.test.ts.snap +++ b/tests/xlsx/__snapshots__/xlsx_export.test.ts.snap @@ -33553,6 +33553,21 @@ exports[`Test XLSX export references with headers should be converted to referen + + + + + + + + + + + + + + + @@ -33604,6 +33619,20 @@ exports[`Test XLSX export references with headers should be converted to referen + + + + + + + + + + + + + + @@ -33848,6 +33877,20 @@ exports[`Test XLSX export references with headers should be converted to referen + + + + + + + + + + + + + + @@ -33886,6 +33929,20 @@ exports[`Test XLSX export references with headers should be converted to referen + + + + + + + + + + + + + + diff --git a/tests/xlsx/xlsx_export.test.ts b/tests/xlsx/xlsx_export.test.ts index 9f9a5bac03..68bd94c021 100644 --- a/tests/xlsx/xlsx_export.test.ts +++ b/tests/xlsx/xlsx_export.test.ts @@ -783,7 +783,10 @@ describe("Test XLSX export", () => { createChart( model, { - dataSets: [{ dataRange: "Sheet1!B2:B" }, { dataRange: "Sheet1!C4:4" }], + dataSets: [ + { dataRange: "Sheet1!B2:B", trend: { type: "polynomial", order: 2, display: true } }, + { dataRange: "Sheet1!C4:4", trend: { type: "polynomial", order: 1, display: true } }, + ], labelRange: "Sheet1!A2:A", type: "line", }, @@ -792,7 +795,10 @@ describe("Test XLSX export", () => { createChart( model, { - dataSets: [{ dataRange: "Sheet1!B2:B" }, { dataRange: "Sheet1!C4:4" }], + dataSets: [ + { dataRange: "Sheet1!B2:B", trend: { type: "exponential", display: true } }, + { dataRange: "Sheet1!C4:4", trend: { type: "logarithmic", display: true } }, + ], labelRange: "Sheet1!A2:A", type: "bar", }, @@ -811,7 +817,13 @@ describe("Test XLSX export", () => { createChart( model, { - dataSets: [{ dataRange: "Sheet1!B2:B" }, { dataRange: "Sheet1!C4:4" }], + dataSets: [ + { + dataRange: "Sheet1!B2:B", + trend: { type: "trailingMovingAverage", window: 3, display: true }, + }, + { dataRange: "Sheet1!C4:4" }, + ], labelRange: "Sheet1!A2:A", type: "scatter", }, diff --git a/tests/xlsx/xlsx_import.test.ts b/tests/xlsx/xlsx_import.test.ts index 5541c54767..08e81021cd 100644 --- a/tests/xlsx/xlsx_import.test.ts +++ b/tests/xlsx/xlsx_import.test.ts @@ -721,18 +721,26 @@ describe("Import xlsx data", () => { [ "line", [ - { dataRange: "Sheet1!B26:B35", backgroundColor: "#7030A0" }, + { + dataRange: "Sheet1!B26:B35", + backgroundColor: "#7030A0", + trend: { type: "polynomial", order: 2, display: true }, + }, { dataRange: "Sheet1!C26:C35", backgroundColor: "#C65911" }, ], ], [ "bar", [ - { dataRange: "Sheet1!B27:B35", backgroundColor: "#7030A0" }, + { + dataRange: "Sheet1!B27:B35", + backgroundColor: "#7030A0", + trend: { type: "exponential", display: true }, + }, { dataRange: "Sheet1!C27:C35", backgroundColor: "#C65911" }, ], ], - ])("Can import charts %s with dataset colors", (chartType, chartDatasets) => { + ])("Can import charts %s with dataset colors and trendlines", (chartType, chartDatasets) => { const testSheet = getWorkbookSheet("jestCharts", convertedData)!; const figure = testSheet.figures.find((figure) => figure.data.type === chartType); const chartData = figure!.data as LineChartDefinition | BarChartDefinition; @@ -745,7 +753,11 @@ describe("Import xlsx data", () => { "bar", "#fff", [ - { dataRange: "Sheet1!B27:B35", backgroundColor: "#7030A0" }, + { + dataRange: "Sheet1!B27:B35", + backgroundColor: "#7030A0", + trend: { type: "exponential", display: true }, + }, { dataRange: "Sheet1!C27:C35", backgroundColor: "#C65911" }, ], ], @@ -754,7 +766,11 @@ describe("Import xlsx data", () => { "combo", "#fff", [ - { dataRange: "Sheet1!B27:B35", backgroundColor: "#1F77B4" }, + { + dataRange: "Sheet1!B27:B35", + backgroundColor: "#1F77B4", + trend: { type: "trailingMovingAverage", window: 3, display: true }, + }, { dataRange: "Sheet1!C27:C35", backgroundColor: "#FF7F0E" }, ], ], @@ -768,7 +784,7 @@ describe("Import xlsx data", () => { ], ], ])( - "Can import charts %s without dataset titles", + "Can import charts %s without dataset titles and trendlines", (chartTitle, chartType, chartColor, chartDatasets) => { const testSheet = getWorkbookSheet("jestCharts", convertedData)!; const figure = testSheet.figures.find((figure) => figure.data.title.text === chartTitle)!; @@ -789,7 +805,11 @@ describe("Import xlsx data", () => { "line", "#CECECE", [ - { dataRange: "Sheet1!B26:B35", backgroundColor: "#7030A0" }, + { + dataRange: "Sheet1!B26:B35", + backgroundColor: "#7030A0", + trend: { type: "polynomial", order: 2, display: true }, + }, { dataRange: "Sheet1!C26:C35", backgroundColor: "#C65911" }, ], ], diff --git a/tests/xlsx/xlsx_import_export.test.ts b/tests/xlsx/xlsx_import_export.test.ts index a5c3b280b8..841c49500f 100644 --- a/tests/xlsx/xlsx_import_export.test.ts +++ b/tests/xlsx/xlsx_import_export.test.ts @@ -274,6 +274,24 @@ describe("Export data to xlsx then import it", () => { }, "1" ); + createChart( + model, + { + dataSets: [{ dataRange: "Sheet1!B2:B4" }, { dataRange: "Sheet1!C4:4" }], + labelRange: "Sheet1!B2:B4", + type: "bar", + }, + "2" + ); + createChart( + model, + { + dataSets: [{ dataRange: "Sheet1!C2:C4" }], + labelRange: "Sheet1!C2:C4", + type: "scatter", + }, + "4" + ); const figure = model.getters.getFigures(sheetId)[0]; const importedModel = exportToXlsxThenImport(model); const importedFigure = importedModel.getters.getFigures(sheetId)[0]; @@ -287,7 +305,10 @@ describe("Export data to xlsx then import it", () => { test.each([ { title: { text: "demo chart" }, - dataSets: [{ dataRange: "Sheet1!B26:B35" }, { dataRange: "Sheet1!C26:C35" }], + dataSets: [ + { dataRange: "Sheet1!B26:B35", trend: { type: "polynomial", order: 2, display: true } }, + { dataRange: "Sheet1!C26:C35", trend: { type: "polynomial", order: 1, display: true } }, + ], labelRange: "Sheet1!A27:A35", type: "line" as const, dataSetsHaveTitle: false, @@ -298,7 +319,10 @@ describe("Export data to xlsx then import it", () => { }, { title: { text: "demo chart 2" }, - dataSets: [{ dataRange: "Sheet1!B27:B35" }, { dataRange: "Sheet1!C27:C35" }], + dataSets: [ + { dataRange: "Sheet1!B27:B35", trend: { type: "exponential", display: true } }, + { dataRange: "Sheet1!C27:C35", trend: { type: "logarithmic", display: true } }, + ], labelRange: "Sheet1!A27:A35", type: "bar" as const, dataSetsHaveTitle: false, @@ -329,7 +353,13 @@ describe("Export data to xlsx then import it", () => { }, { title: { text: "demo chart 5" }, - dataSets: [{ dataRange: "Sheet1!B27:B35" }, { dataRange: "Sheet1!C27:C35" }], + dataSets: [ + { + dataRange: "Sheet1!B27:B35", + trend: { type: "trailingMovingAverage", window: 3, display: true }, + }, + { dataRange: "Sheet1!C27:C35" }, + ], labelRange: "Sheet1!A27:A35", type: "combo" as const, dataSetsHaveTitle: false,