forked from excalidraw/excalidraw
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdisitrubte.ts
136 lines (111 loc) · 3.36 KB
/
disitrubte.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
import { ExcalidrawElement } from "./element/types";
import { newElementWith } from "./element/mutateElement";
import { getCommonBounds } from "./element";
interface Box {
minX: number;
minY: number;
maxX: number;
maxY: number;
midX: number;
midY: number;
width: number;
height: number;
}
export interface Distribution {
space: "between";
axis: "x" | "y";
}
export const distributeElements = (
selectedElements: ExcalidrawElement[],
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)
.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,
}),
);
});
};
export const getMaximumGroups = (
elements: ExcalidrawElement[],
): ExcalidrawElement[][] => {
const groups: Map<String, ExcalidrawElement[]> = new Map<
String,
ExcalidrawElement[]
>();
elements.forEach((element: ExcalidrawElement) => {
const groupId =
element.groupIds.length === 0
? element.id
: element.groupIds[element.groupIds.length - 1];
const currentGroupMembers = groups.get(groupId) || [];
groups.set(groupId, [...currentGroupMembers, element]);
});
return Array.from(groups.values());
};
const getCommonBoundingBox = (elements: ExcalidrawElement[]): Box => {
const [minX, minY, maxX, maxY] = getCommonBounds(elements);
return {
minX,
minY,
maxX,
maxY,
width: maxX - minX,
height: maxY - minY,
midX: (minX + maxX) / 2,
midY: (minY + maxY) / 2,
};
};