230 lines
7.3 KiB
TypeScript
230 lines
7.3 KiB
TypeScript
import type {
|
|
ChartElement,
|
|
ChartSeries,
|
|
DataLabelPosition,
|
|
} from "@/components/slide-editor/types";
|
|
import { rawChartType } from "@/components/slide-editor/charts/chart-data";
|
|
import {
|
|
readArray,
|
|
readNumber,
|
|
readString,
|
|
type RawElement,
|
|
type UnknownRecord,
|
|
} from "@/components/slide-editor/model/core";
|
|
|
|
const DATA_LABEL_POSITIONS = new Set(["base", "mid", "top", "outside"]);
|
|
|
|
export function rawChartToEditorChart(element: RawElement): ChartElement {
|
|
const legacyData = readArray(element.data)
|
|
.map((value) => legacyChartDatum(value))
|
|
.filter(
|
|
(value): value is { color?: string; label: string; value: number } =>
|
|
value != null,
|
|
);
|
|
const categories = readArray(element.categories).map(String);
|
|
const series = readArray(element.series)
|
|
.map((value, index): ChartSeries | null => {
|
|
const record =
|
|
value && typeof value === "object" && !Array.isArray(value)
|
|
? (value as UnknownRecord)
|
|
: null;
|
|
if (!record) return null;
|
|
const values = readArray(record.values ?? record.data).map(
|
|
(item) => readNumber(item) ?? 0,
|
|
);
|
|
return {
|
|
name: readString(record.name) ?? `Series ${index + 1}`,
|
|
values,
|
|
};
|
|
})
|
|
.filter((value): value is ChartSeries => value != null);
|
|
const normalizedInputSeries =
|
|
series.length > 0
|
|
? series
|
|
: legacyData.length > 0
|
|
? [
|
|
{
|
|
name: readString(element.title) ?? "Series 1",
|
|
values: legacyData.map((item) => item.value),
|
|
},
|
|
]
|
|
: [];
|
|
const inputCategories =
|
|
categories.length > 0
|
|
? categories
|
|
: legacyData.map((item, index) => item.label || `Item ${index + 1}`);
|
|
const chartType = rawChartType(element.chart_type ?? element.chartType);
|
|
const supportedSeries =
|
|
chartType === "pie" || chartType === "donut"
|
|
? normalizedInputSeries.slice(0, 1)
|
|
: normalizedInputSeries;
|
|
const normalizedSeries =
|
|
supportedSeries.length > 0
|
|
? supportedSeries
|
|
: [{ name: "Series 1", values: [0] }];
|
|
const categoryLength = Math.max(
|
|
inputCategories.length,
|
|
...normalizedSeries.map((item) => item.values.length),
|
|
);
|
|
const normalizedCategories =
|
|
inputCategories.length > 0
|
|
? Array.from(
|
|
{ length: categoryLength },
|
|
(_, index) => inputCategories[index] ?? `Item ${index + 1}`,
|
|
)
|
|
: Array.from({ length: categoryLength }, (_, index) => `Item ${index + 1}`);
|
|
const legacyColors = legacyData
|
|
.map((item) => item.color)
|
|
.filter((value): value is string => Boolean(value));
|
|
const colors = readArray(element.colors).map(String);
|
|
const chartColors =
|
|
colors.length > 0
|
|
? colors
|
|
: legacyColors.length > 0
|
|
? legacyColors
|
|
: [readString(element.color) ?? "7C51F8"];
|
|
const firstSeries = normalizedSeries[0];
|
|
const data = normalizedCategories.slice(0, 8).map((label, index) => ({
|
|
label,
|
|
value: firstSeries.values[index] ?? 0,
|
|
color: normalizedSeries.length === 1
|
|
? chartColors[index % chartColors.length] ?? chartColors[0]
|
|
: chartColors[0],
|
|
}));
|
|
|
|
return {
|
|
...withoutRemovedChartFields(element),
|
|
type: "chart",
|
|
chart_type: chartType,
|
|
data: data.length > 0 ? data : [{ label: "Item 1", value: 0 }],
|
|
categories: normalizedCategories,
|
|
series: normalizedSeries,
|
|
colors: chartColors,
|
|
title_color: element.title_color ?? element.titleColor,
|
|
axis_color: element.axis_color ?? element.axisColor,
|
|
grid_color: element.grid_color ?? element.gridColor,
|
|
x_axis: element.x_axis ?? element.xAxis,
|
|
y_axis: element.y_axis ?? element.yAxis,
|
|
x_axis_grid: element.x_axis_grid ?? element.xAxisGrid,
|
|
y_axis_grid: element.y_axis_grid ?? element.yAxisGrid,
|
|
x_axis_title: element.x_axis_title ?? element.xAxisTitle,
|
|
y_axis_title: element.y_axis_title ?? element.yAxisTitle,
|
|
data_labels: readDataLabelPosition(
|
|
hasOwn(element, "data_labels") ? element.data_labels : element.dataLabels,
|
|
),
|
|
legend: element.legend ?? element.showLegend,
|
|
};
|
|
}
|
|
|
|
function legacyChartDatum(value: unknown) {
|
|
const directValue = readNumber(value);
|
|
if (directValue != null) {
|
|
return {
|
|
label: "",
|
|
value: directValue,
|
|
};
|
|
}
|
|
|
|
const record =
|
|
value && typeof value === "object" && !Array.isArray(value)
|
|
? (value as UnknownRecord)
|
|
: null;
|
|
if (!record) return null;
|
|
|
|
const numericValue =
|
|
readNumber(record.value) ??
|
|
readNumber(record.data) ??
|
|
readNumber(record.y);
|
|
if (numericValue == null) return null;
|
|
|
|
return {
|
|
color: readString(record.color) ?? undefined,
|
|
label: readString(record.label) ?? readString(record.name) ?? "",
|
|
value: numericValue,
|
|
};
|
|
}
|
|
|
|
export function editorChartToRawChart(source: RawElement, chart: UnknownRecord) {
|
|
return {
|
|
...withoutRemovedChartFields(source),
|
|
...withoutRemovedChartFields(chart),
|
|
type: "chart",
|
|
position: source.position,
|
|
size: source.size,
|
|
rotation: source.rotation,
|
|
layout: source.layout,
|
|
chart_type: chart.chart_type ?? chart.chartType ?? source.chart_type,
|
|
colors: chart.colors ?? source.colors,
|
|
title_color:
|
|
chart.title_color ??
|
|
chart.titleColor ??
|
|
source.title_color ??
|
|
source.titleColor,
|
|
axis_color: chart.axis_color ?? chart.axisColor ?? source.axis_color,
|
|
grid_color: chart.grid_color ?? chart.gridColor ?? source.grid_color,
|
|
x_axis: chart.x_axis ?? chart.xAxis ?? source.x_axis,
|
|
y_axis: chart.y_axis ?? chart.yAxis ?? source.y_axis,
|
|
x_axis_grid:
|
|
chart.x_axis_grid ??
|
|
chart.xAxisGrid ??
|
|
source.x_axis_grid ??
|
|
source.xAxisGrid,
|
|
y_axis_grid:
|
|
chart.y_axis_grid ??
|
|
chart.yAxisGrid ??
|
|
source.y_axis_grid ??
|
|
source.yAxisGrid,
|
|
x_axis_title: hasOwn(chart, "x_axis_title")
|
|
? chart.x_axis_title
|
|
: chart.xAxisTitle ?? source.x_axis_title ?? source.xAxisTitle,
|
|
y_axis_title: hasOwn(chart, "y_axis_title")
|
|
? chart.y_axis_title
|
|
: chart.yAxisTitle ?? source.y_axis_title ?? source.yAxisTitle,
|
|
data_labels: hasOwn(chart, "data_labels")
|
|
? readDataLabelPosition(chart.data_labels)
|
|
: hasOwn(chart, "dataLabels")
|
|
? readDataLabelPosition(chart.dataLabels)
|
|
: readDataLabelPosition(
|
|
hasOwn(source, "data_labels") ? source.data_labels : source.dataLabels,
|
|
),
|
|
legend:
|
|
chart.legend ??
|
|
chart.showLegend ??
|
|
source.legend ??
|
|
source.showLegend,
|
|
};
|
|
}
|
|
|
|
function withoutRemovedChartFields(element: UnknownRecord) {
|
|
const sanitized = { ...element };
|
|
delete sanitized.data_labels_color;
|
|
delete sanitized.dataLabelsColor;
|
|
delete sanitized.labelColor;
|
|
delete sanitized.grid;
|
|
delete sanitized.axisColor;
|
|
delete sanitized.chartType;
|
|
delete sanitized.dataLabels;
|
|
delete sanitized.gridColor;
|
|
delete sanitized.showLegend;
|
|
delete sanitized.xAxis;
|
|
delete sanitized.xAxisGrid;
|
|
delete sanitized.xAxisTitle;
|
|
delete sanitized.yAxis;
|
|
delete sanitized.yAxisGrid;
|
|
delete sanitized.yAxisTitle;
|
|
return sanitized;
|
|
}
|
|
|
|
function readDataLabelPosition(value: unknown): DataLabelPosition | null {
|
|
if (value === true) return "top";
|
|
if (value === false || value == null) return null;
|
|
const text = readString(value);
|
|
return text && DATA_LABEL_POSITIONS.has(text)
|
|
? (text as DataLabelPosition)
|
|
: null;
|
|
}
|
|
|
|
function hasOwn(record: UnknownRecord, key: string) {
|
|
return Object.prototype.hasOwnProperty.call(record, key);
|
|
}
|