helper.ts 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. export type NStep = {
  2. id: number;
  3. parentIds: number[];
  4. name: string;
  5. bound: { width: number; height: number; left: number; top: number };
  6. prevId: number;
  7. raw: any;
  8. level: number;
  9. };
  10. const idFactory = () => {
  11. let id = 0;
  12. return () => id++;
  13. };
  14. const getId = idFactory();
  15. const _flatSteps = (
  16. steps: any,
  17. parentIds: number[] = [],
  18. level = 0,
  19. parallel = false,
  20. nsteps: NStep[] = []
  21. ): number[] => {
  22. const lonelyStepIds: number[] = [];
  23. let tempLevel = level;
  24. let tempParentIds = parentIds;
  25. for (const step of steps) {
  26. const id = getId();
  27. const stepParallel = parallel;
  28. if (!stepParallel && lonelyStepIds.length) {
  29. tempParentIds = [...lonelyStepIds];
  30. tempLevel =
  31. Math.max(
  32. ...nsteps
  33. .filter((nstep) => lonelyStepIds.includes(nstep.id))
  34. .map((nstep) => nstep.level)
  35. ) + 1;
  36. lonelyStepIds.length = 0;
  37. }
  38. let tempPrevId = -1;
  39. for (let i = nsteps.length - 1; i >= 0; i--) {
  40. if (nsteps[i].level === tempLevel) {
  41. tempPrevId = nsteps[i].id;
  42. break;
  43. }
  44. }
  45. const nstep = {
  46. id,
  47. name: step.name,
  48. parentIds: tempParentIds,
  49. prevId: tempPrevId,
  50. raw: step,
  51. level: tempLevel,
  52. } as NStep;
  53. nsteps.push(nstep);
  54. if (step.steps && step.steps.length) {
  55. lonelyStepIds.push(
  56. ..._flatSteps(
  57. step.steps as any,
  58. [id],
  59. tempLevel + 1,
  60. step.subStepsParallel === "True" ||
  61. step.serviceTypeParallel === "True",
  62. nsteps
  63. )
  64. );
  65. } else {
  66. lonelyStepIds.push(nstep.id);
  67. }
  68. }
  69. return lonelyStepIds;
  70. };
  71. type Size = { width: number; height: number };
  72. export const attachBoundAttrib = (
  73. steps: NStep[],
  74. getStepSize: (step: any) => { width: number; height: number }
  75. ) => {
  76. steps.sort((a, b) => a.level - b.level);
  77. const stepSizeMap = new Map<NStep, Size>();
  78. for (const step of steps) {
  79. const size = getStepSize(step.raw);
  80. stepSizeMap.set(step, size);
  81. }
  82. const treeSizeMap = new Map<NStep, Size>();
  83. const levelHeights: number[] = new Array(
  84. steps[steps.length - 1].level + 1
  85. ).fill(0);
  86. for (let i = steps.length - 1; i >= 0; i--) {
  87. const root = steps[i];
  88. const child = steps.filter(
  89. (oStep) =>
  90. oStep.parentIds.length === 1 &&
  91. oStep.parentIds.some((id) => root.id === id)
  92. );
  93. const rootBound = stepSizeMap.get(steps[i])!;
  94. const topLevel = root.level;
  95. let treeWidth = rootBound.width;
  96. let treeHeight = rootBound.height;
  97. levelHeights[topLevel] = Math.max(levelHeights[topLevel], treeHeight);
  98. if (child.length) {
  99. const bottomLevel = child[child.length - 1]?.level;
  100. for (let i = topLevel; i <= bottomLevel; i++) {
  101. let width = 0;
  102. let height = 0;
  103. for (let j = 0; j < child.length; j++) {
  104. if (child[j].level === i) {
  105. const childTreeSize = treeSizeMap.get(child[j])!;
  106. width += childTreeSize.width;
  107. height = Math.max(childTreeSize.height + rootBound.height, height);
  108. }
  109. }
  110. treeWidth = Math.max(treeWidth, width);
  111. treeHeight = Math.max(treeHeight, height);
  112. }
  113. }
  114. treeSizeMap.set(root, { width: treeWidth, height: treeHeight });
  115. }
  116. const levelsSteps: NStep[][] = [];
  117. let level = 0;
  118. while (true) {
  119. const levelSteps = steps.filter((step) => step.level === level);
  120. if (levelSteps.length === 0) {
  121. break;
  122. } else {
  123. levelsSteps[level] = levelSteps;
  124. }
  125. level++;
  126. }
  127. const getStepOffset = (step: NStep) => {
  128. const stepBound = stepSizeMap.get(step)!;
  129. const treeBound = treeSizeMap.get(step)!;
  130. let offset = 0;
  131. let prevId = step.prevId;
  132. while (prevId !== -1) {
  133. const prevStep = steps.find((tstep) => tstep.id === prevId)!;
  134. const treeBound = treeSizeMap.get(prevStep)!;
  135. offset += treeBound.width;
  136. prevId = prevStep.prevId;
  137. }
  138. const prevStep = steps.find((tstep) => tstep.id === step.prevId)!;
  139. if (prevStep) {
  140. offset = Math.max(prevStep.bound.left + prevStep.bound.width, offset);
  141. }
  142. let left = offset + (treeBound.width - stepBound.width) / 2;
  143. // 如果超出预设范围则修正
  144. if (step.parentIds.length === 1 && step.parentIds[0] !== -1) {
  145. if (step.parentIds[0] !== -1) {
  146. const parent = steps.find((pstep) => pstep.id === step.parentIds[0])!;
  147. const paretnBound = treeSizeMap.get(parent)!;
  148. if (parent.bound.left - left > paretnBound.width / 2) {
  149. // const width = stepBound.width;
  150. const width = steps
  151. .filter((step) => step.parentIds.includes(parent.id))
  152. .reduce((t, c) => t + stepSizeMap.get(c)!.width, 0);
  153. left = Math.max(
  154. left,
  155. parent.bound.left + (parent.bound.width - width) / 2
  156. );
  157. }
  158. }
  159. }
  160. return left;
  161. };
  162. let top = 0;
  163. let width = 0;
  164. for (let i = 0; i < levelsSteps.length; i++) {
  165. let levelHeight = levelHeights[i];
  166. for (const step of levelsSteps[i]) {
  167. const stepSize = stepSizeMap.get(step)!;
  168. step.bound = {
  169. left: getStepOffset(step),
  170. top: top + (levelHeight - stepSize.height) / 2,
  171. ...stepSize,
  172. };
  173. width = Math.max(step.bound.left + step.bound.width, width);
  174. }
  175. top += levelHeight;
  176. }
  177. const getParentBound = (step: NStep) => {
  178. const parentSteps = step.parentIds.map(
  179. (id) => steps.find((parentStep) => parentStep.id === id)!
  180. );
  181. let left = 0;
  182. let top = 0;
  183. let width = 0,
  184. height = 0;
  185. for (const step of parentSteps) {
  186. left = Math.min(step.bound.left, left);
  187. top = Math.min(step.bound.top, top);
  188. width += step.bound.width;
  189. height += step.bound.height;
  190. }
  191. return {
  192. width,
  193. height,
  194. left,
  195. top,
  196. };
  197. };
  198. const getCompleteTree = (root: NStep) => {
  199. const completeTree = [root];
  200. const completeTreeIds = [root.id];
  201. const levelsChilds = levelsSteps.slice(root.level);
  202. for (const childs of levelsChilds) {
  203. for (const step of childs) {
  204. if (step.parentIds.some((id) => completeTreeIds.includes(id))) {
  205. completeTree.push(step);
  206. completeTreeIds.push(step.id);
  207. }
  208. }
  209. }
  210. return completeTree;
  211. };
  212. // 偏移所有多父级树
  213. for (let i = 0; i < levelsSteps.length; i++) {
  214. for (const step of levelsSteps[i]) {
  215. if (step.parentIds.length <= 1) continue;
  216. const parentBound = getParentBound(step);
  217. const offset = (parentBound.width - treeSizeMap.get(step)!.width) / 2;
  218. getCompleteTree(step).forEach((ctStep) => {
  219. ctStep.bound.left += offset;
  220. });
  221. }
  222. }
  223. let left = 0,
  224. right = 0;
  225. for (let i = 0; i < steps.length; i++) {
  226. left = Math.min(steps[i].bound.left, left);
  227. right = Math.max(steps[i].bound.left + steps[i].bound.width, right);
  228. }
  229. return { left, right, top: 0, bottom: top, levelHeights };
  230. };
  231. export const flatSteps = (data: any) => {
  232. const nsteps: NStep[] = [];
  233. _flatSteps(data, [], 0, false, nsteps);
  234. return nsteps;
  235. };
  236. const ctx = document.createElement("canvas").getContext("2d")!;
  237. export const getTextBound = (
  238. text: string,
  239. padding: number[],
  240. margin: number[],
  241. font: string
  242. ) => {
  243. ctx.font = font;
  244. const textMetrics = ctx.measureText(text);
  245. const width = textMetrics.width + (padding[1] + margin[1]) * 2;
  246. const height = textMetrics.hangingBaseline + (padding[0] + margin[0]) * 2;
  247. return { width, height };
  248. };