104 lines
2.9 KiB
TypeScript
104 lines
2.9 KiB
TypeScript
import { isDevEnv } from "@excalidraw/common";
|
|
|
|
import { newElement } from "@excalidraw/element";
|
|
|
|
import { commonProps } from "./charts.constants";
|
|
import {
|
|
chartBaseElements,
|
|
chartXLabels,
|
|
createSeriesLegend,
|
|
getBackgroundColor,
|
|
getCartesianChartLayout,
|
|
getChartDimensions,
|
|
getColorOffset,
|
|
getRotatedTextElementBottom,
|
|
getSeriesColors,
|
|
} from "./charts.helpers";
|
|
|
|
import type { ChartElements, Spreadsheet } from "./charts.types";
|
|
|
|
export const renderBarChart = (
|
|
spreadsheet: Spreadsheet,
|
|
x: number,
|
|
y: number,
|
|
colorSeed?: number,
|
|
): ChartElements => {
|
|
const series = spreadsheet.series;
|
|
const layout = getCartesianChartLayout("bar", series.length);
|
|
const max = Math.max(
|
|
1,
|
|
...series.flatMap((seriesData) =>
|
|
seriesData.values.map((value) => Math.max(0, value)),
|
|
),
|
|
);
|
|
const colorOffset = getColorOffset(colorSeed);
|
|
const backgroundColor = getBackgroundColor(colorOffset);
|
|
const seriesColors = getSeriesColors(series.length, colorOffset);
|
|
const interBarGap =
|
|
series.length > 1
|
|
? Math.max(1, Math.floor(layout.gap / (series.length + 1)))
|
|
: 0;
|
|
const barWidth =
|
|
series.length > 1
|
|
? Math.max(
|
|
2,
|
|
(layout.slotWidth - interBarGap * (series.length - 1)) /
|
|
series.length,
|
|
)
|
|
: layout.slotWidth;
|
|
const clusterWidth =
|
|
series.length * barWidth + interBarGap * (series.length - 1);
|
|
const clusterOffset = (layout.slotWidth - clusterWidth) / 2;
|
|
|
|
const bars = series[0].values.flatMap((_, categoryIndex) =>
|
|
series.map((seriesData, seriesIndex) => {
|
|
const value = Math.max(0, seriesData.values[categoryIndex] ?? 0);
|
|
const barHeight = (value / max) * layout.chartHeight;
|
|
const barColor =
|
|
series.length > 1 ? seriesColors[seriesIndex] : backgroundColor;
|
|
return newElement({
|
|
backgroundColor: barColor,
|
|
...commonProps,
|
|
type: "rectangle",
|
|
fillStyle: series.length > 1 ? "solid" : commonProps.fillStyle,
|
|
strokeColor: series.length > 1 ? barColor : commonProps.strokeColor,
|
|
x:
|
|
x +
|
|
categoryIndex * (layout.slotWidth + layout.gap) +
|
|
layout.gap +
|
|
clusterOffset +
|
|
seriesIndex * (barWidth + interBarGap),
|
|
y: y - barHeight - layout.gap,
|
|
width: barWidth,
|
|
height: barHeight,
|
|
});
|
|
}),
|
|
);
|
|
|
|
const baseElements = chartBaseElements(
|
|
spreadsheet,
|
|
x,
|
|
y,
|
|
backgroundColor,
|
|
layout,
|
|
max,
|
|
isDevEnv(),
|
|
);
|
|
const xLabels = chartXLabels(spreadsheet, x, y, backgroundColor, layout);
|
|
const xLabelsBottomY = Math.max(
|
|
y + layout.gap / 2,
|
|
...xLabels.map((label) => getRotatedTextElementBottom(label)),
|
|
);
|
|
const { chartWidth } = getChartDimensions(spreadsheet, layout);
|
|
const seriesLegend = createSeriesLegend(
|
|
series,
|
|
seriesColors,
|
|
x + chartWidth / 2,
|
|
xLabelsBottomY,
|
|
y + layout.gap * 5,
|
|
backgroundColor,
|
|
);
|
|
|
|
return [...baseElements, ...bars, ...seriesLegend];
|
|
};
|