Skip to content

Commit

Permalink
[IMP] chart: support import/export of dataset trendlines
Browse files Browse the repository at this point in the history
This commit introduces the ability to import and export trendlines
associated with datasets in chart configurations.

Task: 4319957
  • Loading branch information
Rachico committed Dec 5, 2024
1 parent 7b1c39b commit 83539de
Show file tree
Hide file tree
Showing 15 changed files with 314 additions and 30 deletions.
11 changes: 5 additions & 6 deletions src/helpers/figures/charts/bar_chart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import {
ChartCreationContext,
CustomizedDataSet,
DataSet,
DatasetDesign,
ExcelChartDataset,
ExcelChartDefinition,
} from "../../../types/chart/chart";
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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),
});
}
Expand Down Expand Up @@ -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),
});
}
Expand All @@ -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,
Expand Down
26 changes: 25 additions & 1 deletion src/helpers/figures/charts/chart_common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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) {
Expand All @@ -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 }),
Expand Down
2 changes: 1 addition & 1 deletion src/helpers/figures/charts/combo_chart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
11 changes: 5 additions & 6 deletions src/helpers/figures/charts/line_chart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import {
ChartJSRuntime,
CustomizedDataSet,
DataSet,
DatasetDesign,
ExcelChartDataset,
ExcelChartDefinition,
} from "../../../types/chart/chart";
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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),
});
}
Expand Down Expand Up @@ -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),
});
}
Expand Down Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion src/helpers/figures/charts/scatter_chart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
10 changes: 10 additions & 0 deletions src/types/chart/chart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
14 changes: 14 additions & 0 deletions src/xlsx/conversion/conversion_maps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,20 @@ export const CHART_TYPE_CONVERSION_MAP: Record<XLSXChartType, ExcelChartType | u
comboChart: "combo",
};

export const CHART_TRENDLINE_TYPE_CONVERSION_MAP = {
exp: "exponential",
log: "logarithmic",
poly: "polynomial",
movingAvg: "trailingMovingAverage",
} as const;

export const CHART_TRENDLINE_TYPE_CONVERSION_MAP_REVERSE = {
exponential: "exp",
logarithmic: "log",
polynomial: "poly",
trailingMovingAverage: "movingAvg",
} as const;

/** Conversion map for the SUBTOTAL(index, formula) function in xlsx, index <=> actual function*/
export const SUBTOTAL_FUNCTION_CONVERSION_MAP: Record<number, string> = {
"1": "AVERAGE",
Expand Down
35 changes: 34 additions & 1 deletion src/xlsx/conversion/figure_conversion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<any>[] {
let id = 1;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
27 changes: 26 additions & 1 deletion src/xlsx/extraction/chart_extractor.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -139,13 +144,33 @@ export class XlsxChartExtractor extends XlsxBaseExtractor {
required: true,
})!,
backgroundColor: color ? `${toHex(color.asString())}` : undefined,
trend: this.extractChartTrendline(chartDataElement),
};
}
);
})
.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" },
Expand Down
Loading

0 comments on commit 83539de

Please sign in to comment.