Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[IMP] chart: support import/export of dataset trendlines #5318

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions 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 All @@ -35,6 +37,7 @@ import { rangeReference } from "../../references";
import { getZoneArea, isFullRow, toUnboundedZone, zoneToDimension, zoneToXc } from "../../zones";

export const TREND_LINE_XAXIS_ID = "x1";
export const MAX_EXCEL_POLYNOMIAL_DEGREE = 6;

/**
* This file contains helpers that are common to different charts (mainly
Expand Down Expand Up @@ -192,6 +195,7 @@ export function createDataSets(
backgroundColor: dataSet.backgroundColor,
rightYAxis: dataSet.yAxisId === "y1",
customLabel: dataSet.label,
trend: dataSet.trend,
});
}
} else {
Expand All @@ -213,6 +217,7 @@ export function createDataSets(
backgroundColor: dataSet.backgroundColor,
rightYAxis: dataSet.yAxisId === "y1",
customLabel: dataSet.label,
trend: dataSet.trend,
});
}
}
Expand Down Expand Up @@ -272,6 +277,49 @@ export function toExcelDataset(getters: CoreGetters, ds: DataSet): ExcelChartDat
};
}

let trend: ExcelChartTrendConfiguration | undefined = undefined;
if (ds?.trend?.type) {
trend = {
type: CHART_TRENDLINE_TYPE_CONVERSION_MAP_REVERSE[ds.trend.type],
};
if (ds.trend.color) {
trend = {
...trend,
color: ds.trend.color,
};
}
if (ds.trend.type === "polynomial" && ds.trend.order) {
if (ds.trend.order === 1) {
trend = {
...trend,
type: "linear",
};
} else {
trend = {
...trend,
order:
ds.trend.order > MAX_EXCEL_POLYNOMIAL_DEGREE
? MAX_EXCEL_POLYNOMIAL_DEGREE
: ds.trend.order,
};
}
}
if (ds.trend.type === "trailingMovingAverage" && ds.trend.window) {
trend = {
...trend,
window: ds.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
11 changes: 11 additions & 0 deletions src/types/chart/chart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,14 +116,25 @@ export interface DataSet {
readonly rightYAxis?: boolean; // if the dataset should be on the right Y axis
readonly backgroundColor?: Color;
readonly customLabel?: string;
readonly trend?: TrendConfiguration;
}
export interface ExcelChartDataset {
readonly label?: { text?: string } | { reference?: string };
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: convertExcelTrendline(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 convertExcelTrendline(
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
42 changes: 41 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,48 @@ 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");
let trendlineConfig: ExcelChartTrendConfiguration = {
type: trendlineType ? (trendlineType.asString() as ExcelTrendlineType) : undefined,
color: trendlineColor ? `${toHex(trendlineColor.asString())}` : undefined,
};
if (trendlineConfig.type === "poly") {
const trendlineOrder = this.extractChildAttr(trendlineElement, "c:order", "val");
if (trendlineOrder) {
trendlineConfig = {
...trendlineConfig,
order: trendlineOrder.asNum(),
};
}
}
if (trendlineConfig.type === "movingAvg") {
const trendlineWindow = this.extractChildAttr(trendlineElement, "c:period", "val");
if (trendlineWindow) {
trendlineConfig = {
...trendlineConfig,
window: trendlineWindow.asNum(),
};
}
}
return trendlineConfig;
}

private extractScatterChartDatasets(chartElement: Element): ExcelChartDataset[] {
return this.mapOnElements(
{ parent: chartElement, query: "c:ser" },
Expand Down
61 changes: 61 additions & 0 deletions src/xlsx/functions/charts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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*/ `
<c:trendline>
${extractTrendlineCommonAttributes(trend)}
<c:trendlineType val="poly" />
<c:order val="${trend.order}" />
</c:trendline>
`;
}
return escapeXml/*xml*/ `
<c:trendline>
${extractTrendlineCommonAttributes(trend)}
<c:trendlineType val="linear" />
</c:trendline>
`;
}
if (trend.type === "movingAvg") {
return escapeXml/*xml*/ `
<c:trendline>
${extractTrendlineCommonAttributes(trend)}
<c:trendlineType val="movingAvg" />
<c:period val="${trend.window}" />
</c:trendline>
`;
}
return escapeXml/*xml*/ `
<c:trendline>
${extractTrendlineCommonAttributes(trend)}
<c:trendlineType val="${trend.type}" />
</c:trendline>
`;
}

function extractTrendlineCommonAttributes(trend: ExcelChartDataset["trend"]) {
if (!trend) {
return "";
}
return escapeXml/*xml*/ `
<c:spPr>
<a:ln w="19050" cap="rnd">
<a:solidFill>
<a:srgbClr val="${trend.color ? toXlsxHexColor(trend.color) : "000000"}" />
</a:solidFill>
<a:prstDash val="sysDot" />
</a:ln>
<a:effectLst />
</c:spPr>
<c:dispRSqr val="0" />
<c:dispEq val="0" />
`;
}

function extractDataSetLabel(label: ExcelChartDataset["label"]): XMLString {
if (!label) {
return escapeXml/*xml*/ ``;
Expand Down Expand Up @@ -252,6 +309,7 @@ function addBarChart(chart: ExcelChartDefinition): XMLString {
<c:ser>
<c:idx val="${dsIndex}"/>
<c:order val="${dsIndex}"/>
${extractTrendline(dataset.trend)}
${extractDataSetLabel(dataset.label)}
${dataShapeProperty}
${
Expand Down Expand Up @@ -344,6 +402,7 @@ function addComboChart(chart: ExcelChartDefinition): XMLString {
<c:ser>
<c:idx val="0"/>
<c:order val="0"/>
${extractTrendline(dataSet.trend)}
${extractDataSetLabel(dataSet.label)}
${shapeProperty({
backgroundColor: firstColor,
Expand Down Expand Up @@ -500,6 +559,7 @@ function addLineChart(chart: ExcelChartDefinition): XMLString {
<c:size val="5"/>
${shapeProperty({ backgroundColor: color, line: { color } })}
</c:marker>
${extractTrendline(dataset.trend)}
${extractDataSetLabel(dataset.label)}
${dataShapeProperty}
${
Expand Down Expand Up @@ -594,6 +654,7 @@ function addScatterChart(chart: ExcelChartDefinition): XMLString {
<c:size val="5"/>
${shapeProperty({ backgroundColor: color, line: { color } })}
</c:marker>
${extractTrendline(dataset.trend)}
${extractDataSetLabel(dataset.label)}
${
chart.labelRange
Expand Down
Binary file modified tests/__xlsx__/xlsx_demo_data.xlsx
Binary file not shown.
Loading