distribute.ts 2.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990
  1. import { ExcalidrawElement } from "./element/types";
  2. import { newElementWith } from "./element/mutateElement";
  3. import { getMaximumGroups } from "./groups";
  4. import { getCommonBoundingBox } from "./element/bounds";
  5. export interface Distribution {
  6. space: "between";
  7. axis: "x" | "y";
  8. }
  9. export const distributeElements = (
  10. selectedElements: ExcalidrawElement[],
  11. distribution: Distribution,
  12. ): ExcalidrawElement[] => {
  13. const [start, mid, end, extent] =
  14. distribution.axis === "x"
  15. ? (["minX", "midX", "maxX", "width"] as const)
  16. : (["minY", "midY", "maxY", "height"] as const);
  17. const bounds = getCommonBoundingBox(selectedElements);
  18. const groups = getMaximumGroups(selectedElements)
  19. .map((group) => [group, getCommonBoundingBox(group)] as const)
  20. .sort((a, b) => a[1][mid] - b[1][mid]);
  21. let span = 0;
  22. for (const group of groups) {
  23. span += group[1][extent];
  24. }
  25. const step = (bounds[extent] - span) / (groups.length - 1);
  26. if (step < 0) {
  27. // If we have a negative step, we'll need to distribute from centers
  28. // rather than from gaps. Buckle up, this is a weird one.
  29. // Get indices of boxes that define start and end of our bounding box
  30. const index0 = groups.findIndex((g) => g[1][start] === bounds[start]);
  31. const index1 = groups.findIndex((g) => g[1][end] === bounds[end]);
  32. // Get our step, based on the distance between the center points of our
  33. // start and end boxes
  34. const step =
  35. (groups[index1][1][mid] - groups[index0][1][mid]) / (groups.length - 1);
  36. let pos = groups[index0][1][mid];
  37. return groups.flatMap(([group, box], index) => {
  38. const translation = {
  39. x: 0,
  40. y: 0,
  41. };
  42. // Don't move our start and end boxes
  43. if (index !== index0 && index !== index1) {
  44. pos += step;
  45. translation[distribution.axis] = pos - box[mid];
  46. }
  47. return group.map((element) =>
  48. newElementWith(element, {
  49. x: element.x + translation.x,
  50. y: element.y + translation.y,
  51. }),
  52. );
  53. });
  54. }
  55. // Distribute from gaps
  56. let pos = bounds[start];
  57. return groups.flatMap(([group, box]) => {
  58. const translation = {
  59. x: 0,
  60. y: 0,
  61. };
  62. translation[distribution.axis] = pos - box[start];
  63. pos += step;
  64. pos += box[extent];
  65. return group.map((element) =>
  66. newElementWith(element, {
  67. x: element.x + translation.x,
  68. y: element.y + translation.y,
  69. }),
  70. );
  71. });
  72. };