aboutsummaryrefslogtreecommitdiffstats
path: root/packages/excalidraw/distribute.ts
diff options
context:
space:
mode:
authorkj_sh6042026-03-15 16:19:35 -0400
committerkj_sh6042026-03-15 16:19:35 -0400
commit6ec259a0e71174651bae95d4628138bf6fd68742 (patch)
tree5e33c6a5ec091ecabfcb257fdc7b6a88ed8754ac /packages/excalidraw/distribute.ts
parent16c8578b15c727f22921f8a80a56ee4d4e7f2272 (diff)
refactor: packages/
Diffstat (limited to 'packages/excalidraw/distribute.ts')
-rw-r--r--packages/excalidraw/distribute.ts91
1 files changed, 91 insertions, 0 deletions
diff --git a/packages/excalidraw/distribute.ts b/packages/excalidraw/distribute.ts
new file mode 100644
index 0000000..368b2f2
--- /dev/null
+++ b/packages/excalidraw/distribute.ts
@@ -0,0 +1,91 @@
+import { newElementWith } from "./element/mutateElement";
+import { getMaximumGroups } from "./groups";
+import { getCommonBoundingBox } from "./element/bounds";
+import type { ElementsMap, ExcalidrawElement } from "./element/types";
+
+export interface Distribution {
+ space: "between";
+ axis: "x" | "y";
+}
+
+export const distributeElements = (
+ selectedElements: ExcalidrawElement[],
+ elementsMap: ElementsMap,
+ distribution: Distribution,
+): ExcalidrawElement[] => {
+ const [start, mid, end, extent] =
+ distribution.axis === "x"
+ ? (["minX", "midX", "maxX", "width"] as const)
+ : (["minY", "midY", "maxY", "height"] as const);
+
+ const bounds = getCommonBoundingBox(selectedElements);
+ const groups = getMaximumGroups(selectedElements, elementsMap)
+ .map((group) => [group, getCommonBoundingBox(group)] as const)
+ .sort((a, b) => a[1][mid] - b[1][mid]);
+
+ let span = 0;
+ for (const group of groups) {
+ span += group[1][extent];
+ }
+
+ const step = (bounds[extent] - span) / (groups.length - 1);
+
+ if (step < 0) {
+ // If we have a negative step, we'll need to distribute from centers
+ // rather than from gaps. Buckle up, this is a weird one.
+
+ // Get indices of boxes that define start and end of our bounding box
+ const index0 = groups.findIndex((g) => g[1][start] === bounds[start]);
+ const index1 = groups.findIndex((g) => g[1][end] === bounds[end]);
+
+ // Get our step, based on the distance between the center points of our
+ // start and end boxes
+ const step =
+ (groups[index1][1][mid] - groups[index0][1][mid]) / (groups.length - 1);
+
+ let pos = groups[index0][1][mid];
+
+ return groups.flatMap(([group, box], index) => {
+ const translation = {
+ x: 0,
+ y: 0,
+ };
+
+ // Don't move our start and end boxes
+ if (index !== index0 && index !== index1) {
+ pos += step;
+ translation[distribution.axis] = pos - box[mid];
+ }
+
+ return group.map((element) =>
+ newElementWith(element, {
+ x: element.x + translation.x,
+ y: element.y + translation.y,
+ }),
+ );
+ });
+ }
+
+ // Distribute from gaps
+
+ let pos = bounds[start];
+
+ return groups.flatMap(([group, box]) => {
+ const translation = {
+ x: 0,
+ y: 0,
+ };
+
+ translation[distribution.axis] = pos - box[start];
+
+ pos += step;
+ pos += box[extent];
+
+ return group.map((element) =>
+ newElementWith(element, {
+ x: element.x + translation.x,
+ y: element.y + translation.y,
+ }),
+ );
+ });
+};