Skip to content

Commit

Permalink
[IMP] xlsx: support import/export of doughnut,pyramid & horizontal ba…
Browse files Browse the repository at this point in the history
…r charts

This commit adds support for exporting & importing the charts mentioned above.
Pyramid charts are not supported in Excel, therefore, we do a best effort by
exporting them as horizontal bar charts.

Task: 3978237
  • Loading branch information
Rachico committed Jan 10, 2025
1 parent b63c922 commit 2d3d4ab
Show file tree
Hide file tree
Showing 9 changed files with 119 additions and 4 deletions.
33 changes: 32 additions & 1 deletion src/helpers/figures/charts/pyramid_chart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,27 @@ import {
CustomizedDataSet,
DataSet,
DatasetDesign,
ExcelChartDataset,
ExcelChartDefinition,
} from "../../../types/chart/chart";
import { LegendPosition } from "../../../types/chart/common_chart";
import { PyramidChartDefinition, PyramidChartRuntime } from "../../../types/chart/pyramid_chart";
import { CellErrorType } from "../../../types/errors";
import { Validator } from "../../../types/validator";
import { toXlsxHexColor } from "../../../xlsx/helpers/colors";
import { createValidRange } from "../../range";
import { AbstractChart } from "./abstract_chart";
import {
chartFontColor,
checkDataset,
checkLabelRange,
createDataSets,
duplicateDataSetsInDuplicatedSheet,
duplicateLabelRangeInDuplicatedSheet,
getDefinedAxis,
shouldRemoveFirstLabel,
toExcelDataset,
toExcelLabelRange,
transformChartDefinitionWithDataSetsWithZone,
updateChartRangesWithDataSets,
} from "./chart_common";
Expand Down Expand Up @@ -180,7 +188,30 @@ export class PyramidChart extends AbstractChart {
}

getDefinitionForExcel(): ExcelChartDefinition | undefined {
return undefined;
// Excel does not support pyramid charts, therefore we return a bar
// chart definition with the same data
if (this.aggregated) {
// Excel does not support aggregating labels
return undefined;
}
const dataSets: ExcelChartDataset[] = this.dataSets
.map((ds: DataSet) => toExcelDataset(this.getters, ds))
.filter((ds) => ds.range !== "" && ds.range !== CellErrorType.InvalidReference);
const labelRange = toExcelLabelRange(
this.getters,
this.labelRange,
shouldRemoveFirstLabel(this.labelRange, this.dataSets[0], this.dataSetsHaveTitle)
);
const definition = this.getDefinition();
return {
...definition,
horizontal: true,
backgroundColor: toXlsxHexColor(this.background || BACKGROUND_CHART_COLOR),
fontColor: toXlsxHexColor(chartFontColor(this.background)),
dataSets,
labelRange,
verticalAxis: getDefinedAxis(definition),
};
}

updateRanges(applyChange: ApplyRangeChange): PyramidChart {
Expand Down
4 changes: 3 additions & 1 deletion src/types/chart/chart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ export interface ExcelChartDataset {
readonly rightYAxis?: boolean;
}

export type ExcelChartType = "line" | "bar" | "pie" | "combo" | "scatter" | "radar";
export type ExcelChartType = "line" | "bar" | "pie" | "combo" | "scatter" | "radar" | "pyramid";

export interface ExcelChartDefinition {
readonly title?: TitleDesign;
Expand All @@ -142,6 +142,8 @@ export interface ExcelChartDefinition {
useRightAxis?: boolean;
};
readonly axesDesign?: AxesDesign;
readonly isDoughnut?: boolean;
readonly horizontal?: boolean;
}

export interface ChartCreationContext {
Expand Down
2 changes: 2 additions & 0 deletions src/xlsx/conversion/figure_conversion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ function convertChartData(chartData: ExcelChartDefinition): ChartDefinition | un
aggregated: false,
cumulative: chartData.cumulative || false,
labelsAsText: false,
horizontal: chartData.horizontal,
isDoughnut: chartData.isDoughnut,
};
}

Expand Down
10 changes: 10 additions & 0 deletions src/xlsx/extraction/chart_extractor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,14 @@ export class XlsxChartExtractor extends XlsxBaseExtractor {
default: "clustered",
}).asString();

const chartDirection = this.extractChildAttr(rootChartElement, "c:barDir", "val", {
default: "col",
}).asString();

const chartHoleSize = this.extractChildAttr(rootChartElement, "c:holeSize", "val", {
default: "0",
}).asNum();

return {
title: { text: chartTitle },
type: CHART_TYPE_CONVERSION_MAP[chartType]!,
Expand Down Expand Up @@ -56,6 +64,8 @@ export class XlsxChartExtractor extends XlsxBaseExtractor {
],
stacked: barChartGrouping === "stacked",
fontColor: "000000",
horizontal: chartDirection === "bar",
isDoughnut: chartHoleSize > 0,
};
}
)[0];
Expand Down
8 changes: 6 additions & 2 deletions src/xlsx/functions/charts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ export function createChart(
let plot = escapeXml``;
switch (chart.data.type) {
case "bar":
case "pyramid":
plot = addBarChart(chart.data);
break;
case "combo":
Expand All @@ -83,7 +84,9 @@ export function createChart(
plot = addScatterChart(chart.data);
break;
case "pie":
plot = addDoughnutChart(chart.data, chartSheetIndex, data, { holeSize: 0 });
plot = addDoughnutChart(chart.data, chartSheetIndex, data, {
holeSize: chart.data.isDoughnut ? 50 : 0,
});
break;
case "radar":
plot = addRadarChart(chart.data);
Expand Down Expand Up @@ -237,6 +240,7 @@ function addBarChart(chart: ExcelChartDefinition): XMLString {
//
// overlap and gapWitdh seems to be by default at -20 and 20 in chart.js.
// See https://www.chartjs.org/docs/latest/charts/bar.html and https://www.chartjs.org/docs/latest/charts/bar.html#barpercentage-vs-categorypercentage
const chartDirection = chart.horizontal ? "bar" : "col";
const dataSetsColors = chart.dataSets.map((ds) => ds.backgroundColor ?? "");
const colors = new ColorGenerator(chart.dataSets.length, dataSetsColors);
const leftDataSetsNodes: XMLString[] = [];
Expand Down Expand Up @@ -276,7 +280,7 @@ function addBarChart(chart: ExcelChartDefinition): XMLString {
leftDataSetsNodes.length
? escapeXml/*xml*/ `
<c:barChart>
<c:barDir val="col"/>
<c:barDir val="${chartDirection}"/>
<c:grouping val="${grouping}"/>
<c:overlap val="${overlap}"/>
<c:gapWidth val="70"/>
Expand Down
Binary file modified tests/__xlsx__/xlsx_demo_data.xlsx
Binary file not shown.
20 changes: 20 additions & 0 deletions tests/xlsx/xlsx_export.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -798,12 +798,32 @@ describe("Test XLSX export", () => {
},
"2"
);
createChart(
model,
{
dataSets: [{ dataRange: "Sheet1!B2:B" }, { dataRange: "Sheet1!C4:4" }],
labelRange: "Sheet1!A2:A",
type: "bar",
horizontal: true,
},
"2"
);
createChart(
model,
{
dataSets: [{ dataRange: "Sheet1!B2:B" }, { dataRange: "Sheet1!C4:4" }],
labelRange: "Sheet1!A2:A",
type: "pie",
},
"3"
);
createChart(
model,
{
dataSets: [{ dataRange: "Sheet1!B2:B" }, { dataRange: "Sheet1!C4:4" }],
labelRange: "Sheet1!A2:A",
type: "pie",
isDoughnut: true,
},
"3"
);
Expand Down
22 changes: 22 additions & 0 deletions tests/xlsx/xlsx_import.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -783,6 +783,28 @@ describe("Import xlsx data", () => {
}
);

test("Can import horizontal bar charts", () => {
const testSheet = getWorkbookSheet("jestCharts", convertedData)!;
const figure = testSheet.figures.find(
(figure) => figure.data.title.text === "horizontal bar chart"
)!;
const chartData = figure.data as BarChartDefinition;
expect(chartData.title.text).toEqual("horizontal bar chart");
expect(chartData.type).toEqual("bar");
expect(chartData.horizontal).toEqual(true);
});

test("Can import doughnut charts", () => {
const testSheet = getWorkbookSheet("jestCharts", convertedData)!;
const figure = testSheet.figures.find(
(figure) => figure.data.title.text === "single dataset doughnut chart"
)!;
const chartData = figure.data as PieChartDefinition;
expect(chartData.title.text).toEqual("single dataset doughnut chart");
expect(chartData.type).toEqual("pie");
expect(chartData.isDoughnut).toEqual(true);
});

test.each([
[
"line chart",
Expand Down
24 changes: 24 additions & 0 deletions tests/xlsx/xlsx_import_export.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,18 @@ describe("Export data to xlsx then import it", () => {
background: "#AAAAAA",
legendPosition: "bottom" as const,
stacked: true,
horizontal: false,
},
{
title: { text: "demo horizontal bar chart 2" },
dataSets: [{ dataRange: "Sheet1!B27:B35" }, { dataRange: "Sheet1!C27:C35" }],
labelRange: "Sheet1!A27:A35",
type: "bar" as const,
dataSetsHaveTitle: false,
background: "#AAAAAA",
legendPosition: "top" as const,
stacked: true,
horizontal: true,
},
{
title: { text: "pie demo chart" },
Expand All @@ -315,6 +327,18 @@ describe("Export data to xlsx then import it", () => {
background: "#FFFFFF",
legendPosition: "right" as const,
stacked: false,
isDoughnut: false,
},
{
title: { text: "doughnut demo chart" },
dataSets: [{ dataRange: "Sheet1!B26:B35" }, { dataRange: "Sheet1!C26:C35" }],
labelRange: "Sheet1!A27:A35",
type: "pie" as const,
dataSetsHaveTitle: false,
background: "#FFFFFF",
legendPosition: "left" as const,
stacked: false,
isDoughnut: true,
},
{
title: { text: "demo chart4" },
Expand Down

0 comments on commit 2d3d4ab

Please sign in to comment.