tree-helper.ts 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  1. export type Step<T> = {
  2. raw: T;
  3. parents: Step<T>[];
  4. children: Step<T>[];
  5. box: StepCtx<T>;
  6. };
  7. export type Steps<T> = Step<T>[];
  8. export type StepCtx<T> = {
  9. level: number;
  10. step: Step<T>;
  11. // 自身大小
  12. size: { w: number; h: number };
  13. // 如果是多对一则存储多宽度
  14. parallelWidth?: number;
  15. // 树宽高
  16. treeSize?: { w: number; h: number };
  17. // 相对于兄弟
  18. offset: { x: number; y: number };
  19. treeOffset: { x: number; y: number };
  20. lines: number[][][];
  21. };
  22. export type StepsCtx<T> = {
  23. levelHeight: number[];
  24. boxs: StepCtx<T>[];
  25. roots: Step<T>[];
  26. offset: { x: number; y: number };
  27. size: { w: number; h: number };
  28. };
  29. type traversalStepsProps<T> = {
  30. steps: Steps<T>;
  31. ctx: StepsCtx<T>;
  32. oper: (data: {
  33. currents: Step<T>[];
  34. prev: Step<T> | null;
  35. next: Step<T> | null;
  36. levelSteps: Step<T>[];
  37. }) => void;
  38. cs?: Step<T>[];
  39. checkeds?: Steps<T>;
  40. level?: number;
  41. reverse?: boolean;
  42. };
  43. const setStepsLevel = <T>(
  44. steps: Steps<T>,
  45. ctx: StepsCtx<T>,
  46. level = 0,
  47. cs: Step<T>[] = ctx.roots
  48. ) => {
  49. if (cs && cs.length === 0) return;
  50. const cSteps = cs;
  51. for (let i = 0; i < cSteps.length; i++) {
  52. const current = cSteps[i];
  53. if ("level" in current.box) {
  54. current.box.level = Math.max(level, current.box.level);
  55. } else {
  56. (current.box as any).level = level;
  57. }
  58. ctx.levelHeight[level] = 0;
  59. setStepsLevel(steps, ctx, level + 1, current.children);
  60. }
  61. };
  62. export const traversalSteps = <T>({
  63. steps,
  64. ctx,
  65. oper,
  66. cs = ctx.roots,
  67. level = 0,
  68. reverse = false,
  69. checkeds = [],
  70. }: traversalStepsProps<T>) => {
  71. if (cs.length === 0) return;
  72. const cSteps = cs;
  73. for (let i = 0; i < cSteps.length; i++) {
  74. const current = cSteps[i];
  75. if (checkeds.includes(current)) continue;
  76. const children = current.children.filter(
  77. (child) => child.box.level === level + 1
  78. );
  79. // 查看是否是多对一的情况,如果是这current为所有多的step
  80. let currents = [current];
  81. if (children.length === 1 && children[0].parents.length > 1) {
  82. const steps = children[0].parents;
  83. currents = steps.filter((step) => step.box.level === level);
  84. }
  85. checkeds.push(...currents);
  86. const props = {
  87. currents: currents,
  88. prev: cSteps[i - 1] || null,
  89. next: cSteps[i + 1] || null,
  90. level: level,
  91. levelSteps: cSteps,
  92. };
  93. reverse || oper(props);
  94. traversalSteps({
  95. steps,
  96. ctx,
  97. oper,
  98. cs: current.children,
  99. level: level + 1,
  100. reverse,
  101. checkeds,
  102. });
  103. reverse && oper(props);
  104. }
  105. };
  106. const setStepsBound = <T>(
  107. steps: Steps<T>,
  108. ctx: StepsCtx<T>,
  109. getStepSize: (step: T) => { w: number; h: number }
  110. ) => {
  111. // 注入levelHeights, box size offset
  112. traversalSteps({
  113. steps,
  114. ctx,
  115. oper: ({ currents }) => {
  116. currents.forEach((current) => {
  117. const size = getStepSize(current.raw);
  118. current.box.size = size;
  119. ctx.levelHeight[current.box.level] = Math.max(
  120. size.h,
  121. ctx.levelHeight[current.box.level] || 0
  122. );
  123. });
  124. },
  125. });
  126. };
  127. const setStepsTreeSize = <T>(steps: Steps<T>, ctx: StepsCtx<T>) => {
  128. // 从低到顶分别计算树大小
  129. traversalSteps({
  130. steps,
  131. ctx,
  132. oper: ({ currents }) => {
  133. let levelHeight = 0;
  134. const level = currents[0].box.level;
  135. for (let i = 0; i <= level; i++) {
  136. levelHeight += ctx.levelHeight[i];
  137. }
  138. const current = currents[0];
  139. const treeSize = { w: 0, h: 0 };
  140. for (const child of current.children) {
  141. if (child.box.treeSize) {
  142. treeSize.w += child.box.treeSize.w;
  143. treeSize.h = Math.max(child.box.treeSize.w, treeSize.w);
  144. }
  145. }
  146. treeSize.h += levelHeight;
  147. if (currents.length === 1) {
  148. // 一对一 一对多情况
  149. treeSize.w = Math.max(current.box.size.w, treeSize.w);
  150. current.box.treeSize = treeSize;
  151. } else {
  152. // 多对一情况
  153. let parallelWidth = 0;
  154. for (const parallel of currents) {
  155. parallelWidth += parallel.box.size.w;
  156. }
  157. treeSize.w = Math.max(parallelWidth, treeSize.w);
  158. currents[0].box.parallelWidth = parallelWidth;
  159. currents[0].box.treeSize = treeSize;
  160. }
  161. },
  162. reverse: true,
  163. });
  164. };
  165. const setStepsOffset = <T>(steps: Steps<T>, ctx: StepsCtx<T>) => {
  166. // 从顶到底分别计算树偏移量以及step偏移
  167. traversalSteps({
  168. steps,
  169. ctx,
  170. oper: ({ currents, prev }) => {
  171. const box = currents[0].box;
  172. let levelHeight = 0;
  173. const level = currents[0].box.level;
  174. for (let i = 0; i < level; i++) {
  175. levelHeight += ctx.levelHeight[i];
  176. }
  177. const treeOffset = { x: 0, y: levelHeight };
  178. if (prev) {
  179. // 上一个是普通树
  180. if (prev.box.treeOffset) {
  181. treeOffset.x += prev.box.treeOffset.x + prev.box.treeSize.w;
  182. } else {
  183. // 上一个是多对一树,需要找到box属性值
  184. let prevTreeBox: StepCtx<T>;
  185. for (const prevParent of prev.parents) {
  186. for (let prevLevel of prevParent.children) {
  187. if (prevLevel.box.treeOffset) {
  188. prevTreeBox = prevLevel.box;
  189. break;
  190. }
  191. }
  192. if (prevTreeBox) break;
  193. }
  194. treeOffset.x += prevTreeBox.treeOffset.x + prevTreeBox.parallelWidth;
  195. }
  196. } else if (currents[0].parents.length) {
  197. // 如果是第一个,则需要计算子级,相对父级做偏移
  198. const parents = currents[0].parents;
  199. const levels = new Set(parents.flatMap((parent) => parent.children));
  200. let levelWidth = 0;
  201. for (const levelStep of levels) {
  202. if ("parallelWidth" in levelStep.box) {
  203. levelWidth += levelStep.box.parallelWidth;
  204. } else if (levelStep.box.treeSize) {
  205. levelWidth += levelStep.box.treeSize.w || 0;
  206. }
  207. }
  208. let parentWidth = 0;
  209. for (const parent of parents) {
  210. parentWidth +=
  211. "parallelWidth" in parent.box
  212. ? parent.box.parallelWidth
  213. : "treeSize" in parent.box
  214. ? parent.box.treeSize.w
  215. : 0;
  216. }
  217. treeOffset.x +=
  218. parents[0].box.treeOffset.x + (parentWidth - levelWidth) / 2;
  219. }
  220. box.treeOffset = { ...treeOffset };
  221. if ("parallelWidth" in box) {
  222. let lOffset = 0;
  223. for (const current of currents) {
  224. current.box.offset = {
  225. x: treeOffset.x + lOffset,
  226. y: treeOffset.y + (ctx.levelHeight[level] - current.box.size.h) / 2,
  227. };
  228. lOffset += current.box.size.w;
  229. }
  230. } else {
  231. treeOffset.x += (box.treeSize.w - box.size.w) / 2;
  232. treeOffset.y += (ctx.levelHeight[level] - box.size.h) / 2;
  233. box.offset = treeOffset;
  234. }
  235. },
  236. });
  237. };
  238. const setStepsLines = <T>(
  239. steps: Steps<T>,
  240. ctx: StepsCtx<T>,
  241. margin: number[]
  242. ) => {
  243. traversalSteps({
  244. steps,
  245. ctx,
  246. oper: ({ currents }) => {
  247. for (const current of currents) {
  248. current.box.lines = current.children.map((child) =>
  249. getStepLine(ctx, current, child, margin)
  250. );
  251. }
  252. },
  253. });
  254. };
  255. export const getStepLine = <T>(
  256. ctx: StepsCtx<T>,
  257. topStep: Step<T>,
  258. bottomStep: Step<T>,
  259. margin: number[] = [0, 0]
  260. ) => {
  261. const top = topStep.box;
  262. const bottom = bottomStep.box;
  263. const bottomHeight = ctx.levelHeight[bottom.level];
  264. const start = [
  265. top.offset.x + top.size.w / 2,
  266. top.offset.y + top.size.h - margin[0],
  267. ];
  268. const diffHeight = (bottomHeight - bottom.size.h) / 2;
  269. const c1 = [top.offset.x + top.size.w / 2, bottom.offset.y - diffHeight];
  270. const c2 = [
  271. bottom.offset.x + bottom.size.w / 2,
  272. bottom.offset.y - diffHeight,
  273. ];
  274. const end = [
  275. bottom.offset.x + bottom.size.w / 2,
  276. bottom.offset.y + margin[0],
  277. ];
  278. return [start, c1, c2, end];
  279. };
  280. export const getStepsTreeCtx = <T>(
  281. steps: Steps<T>,
  282. margin: number[],
  283. getStepSize: (step: T) => { w: number; h: number }
  284. ) => {
  285. const roots = steps.filter((step) => step.parents.length === 0);
  286. const ctx: StepsCtx<T> = {
  287. size: { w: 0, h: 0 },
  288. offset: { x: 0, y: 0 },
  289. levelHeight: [],
  290. boxs: steps.map((step) => {
  291. const box = { step } as any;
  292. step.box = box;
  293. step.box.lines = [];
  294. return box;
  295. }),
  296. roots: roots,
  297. };
  298. setStepsLevel(steps, ctx);
  299. setStepsBound(steps, ctx, getStepSize);
  300. setStepsTreeSize(steps, ctx);
  301. setStepsOffset(steps, ctx);
  302. setStepsLines(steps, ctx, margin);
  303. ctx.size.w = roots.reduce((t, root) => t + root.box.treeSize.w, 0);
  304. ctx.size.h = ctx.levelHeight.reduce((t, h) => t + h, 0);
  305. return ctx;
  306. };