Files
2026-07-13 13:08:41 +08:00

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);
}