|
@@ -0,0 +1,276 @@
|
|
|
+export type NStep = {
|
|
|
+ id: number;
|
|
|
+ parentIds: number[];
|
|
|
+ name: string;
|
|
|
+ bound: { width: number; height: number; left: number; top: number };
|
|
|
+ prevId: number;
|
|
|
+ raw: any;
|
|
|
+ level: number;
|
|
|
+};
|
|
|
+
|
|
|
+const idFactory = () => {
|
|
|
+ let id = 0;
|
|
|
+ return () => id++;
|
|
|
+};
|
|
|
+const getId = idFactory();
|
|
|
+
|
|
|
+const _flatSteps = (
|
|
|
+ steps: any,
|
|
|
+ parentIds: number[] = [],
|
|
|
+ level = 0,
|
|
|
+ parallel = false,
|
|
|
+ nsteps: NStep[] = []
|
|
|
+): number[] => {
|
|
|
+ const lonelyStepIds: number[] = [];
|
|
|
+
|
|
|
+ let tempLevel = level;
|
|
|
+ let tempParentIds = parentIds;
|
|
|
+
|
|
|
+ for (const step of steps) {
|
|
|
+ const id = getId();
|
|
|
+ const stepParallel = parallel;
|
|
|
+
|
|
|
+ if (!stepParallel && lonelyStepIds.length) {
|
|
|
+ tempParentIds = [...lonelyStepIds];
|
|
|
+ tempLevel =
|
|
|
+ Math.max(
|
|
|
+ ...nsteps
|
|
|
+ .filter((nstep) => lonelyStepIds.includes(nstep.id))
|
|
|
+ .map((nstep) => nstep.level)
|
|
|
+ ) + 1;
|
|
|
+ lonelyStepIds.length = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ let tempPrevId = -1;
|
|
|
+ for (let i = nsteps.length - 1; i >= 0; i--) {
|
|
|
+ if (nsteps[i].level === tempLevel) {
|
|
|
+ tempPrevId = nsteps[i].id;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const nstep = {
|
|
|
+ id,
|
|
|
+ name: step.name,
|
|
|
+ parentIds: tempParentIds,
|
|
|
+ prevId: tempPrevId,
|
|
|
+ raw: step,
|
|
|
+ level: tempLevel,
|
|
|
+ } as NStep;
|
|
|
+ nsteps.push(nstep);
|
|
|
+
|
|
|
+ if (step.steps && step.steps.length) {
|
|
|
+ lonelyStepIds.push(
|
|
|
+ ..._flatSteps(
|
|
|
+ step.steps as any,
|
|
|
+ [id],
|
|
|
+ tempLevel + 1,
|
|
|
+ step.subStepsParallel === "True" ||
|
|
|
+ step.serviceTypeParallel === "True",
|
|
|
+ nsteps
|
|
|
+ )
|
|
|
+ );
|
|
|
+ } else {
|
|
|
+ lonelyStepIds.push(nstep.id);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return lonelyStepIds;
|
|
|
+};
|
|
|
+
|
|
|
+type Size = { width: number; height: number };
|
|
|
+export const attachBoundAttrib = (
|
|
|
+ steps: NStep[],
|
|
|
+ getStepSize: (step: any) => { width: number; height: number }
|
|
|
+) => {
|
|
|
+ steps.sort((a, b) => a.level - b.level);
|
|
|
+ const stepSizeMap = new Map<NStep, Size>();
|
|
|
+ for (const step of steps) {
|
|
|
+ const size = getStepSize(step.raw);
|
|
|
+ stepSizeMap.set(step, size);
|
|
|
+ }
|
|
|
+
|
|
|
+ const treeSizeMap = new Map<NStep, Size>();
|
|
|
+ const levelHeights: number[] = new Array(
|
|
|
+ steps[steps.length - 1].level + 1
|
|
|
+ ).fill(0);
|
|
|
+ for (let i = steps.length - 1; i >= 0; i--) {
|
|
|
+ const root = steps[i];
|
|
|
+ const child = steps.filter(
|
|
|
+ (oStep) =>
|
|
|
+ oStep.parentIds.length === 1 &&
|
|
|
+ oStep.parentIds.some((id) => root.id === id)
|
|
|
+ );
|
|
|
+
|
|
|
+ const rootBound = stepSizeMap.get(steps[i])!;
|
|
|
+ const topLevel = root.level;
|
|
|
+ let treeWidth = rootBound.width;
|
|
|
+ let treeHeight = rootBound.height;
|
|
|
+ levelHeights[topLevel] = Math.max(levelHeights[topLevel], treeHeight);
|
|
|
+
|
|
|
+ if (child.length) {
|
|
|
+ const bottomLevel = child[child.length - 1]?.level;
|
|
|
+ for (let i = topLevel; i <= bottomLevel; i++) {
|
|
|
+ let width = 0;
|
|
|
+ let height = 0;
|
|
|
+ for (let j = 0; j < child.length; j++) {
|
|
|
+ if (child[j].level === i) {
|
|
|
+ const childTreeSize = treeSizeMap.get(child[j])!;
|
|
|
+ width += childTreeSize.width;
|
|
|
+ height = Math.max(childTreeSize.height + rootBound.height, height);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ treeWidth = Math.max(treeWidth, width);
|
|
|
+ treeHeight = Math.max(treeHeight, height);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ treeSizeMap.set(root, { width: treeWidth, height: treeHeight });
|
|
|
+ }
|
|
|
+
|
|
|
+ const levelsSteps: NStep[][] = [];
|
|
|
+ let level = 0;
|
|
|
+ while (true) {
|
|
|
+ const levelSteps = steps.filter((step) => step.level === level);
|
|
|
+ if (levelSteps.length === 0) {
|
|
|
+ break;
|
|
|
+ } else {
|
|
|
+ levelsSteps[level] = levelSteps;
|
|
|
+ }
|
|
|
+ level++;
|
|
|
+ }
|
|
|
+
|
|
|
+ const getStepOffset = (step: NStep) => {
|
|
|
+ const stepBound = stepSizeMap.get(step)!;
|
|
|
+ const treeBound = treeSizeMap.get(step)!;
|
|
|
+
|
|
|
+ let offset = 0;
|
|
|
+ let prevId = step.prevId;
|
|
|
+ while (prevId !== -1) {
|
|
|
+ const prevStep = steps.find((tstep) => tstep.id === prevId)!;
|
|
|
+ const treeBound = treeSizeMap.get(prevStep)!;
|
|
|
+ offset += treeBound.width;
|
|
|
+ prevId = prevStep.prevId;
|
|
|
+ }
|
|
|
+ const prevStep = steps.find((tstep) => tstep.id === step.prevId)!;
|
|
|
+ if (prevStep) {
|
|
|
+ offset = Math.max(prevStep.bound.left + prevStep.bound.width, offset);
|
|
|
+ }
|
|
|
+
|
|
|
+ let left = offset + (treeBound.width - stepBound.width) / 2;
|
|
|
+
|
|
|
+ // 如果超出预设范围则修正
|
|
|
+ if (step.parentIds.length === 1 && step.parentIds[0] !== -1) {
|
|
|
+ if (step.parentIds[0] !== -1) {
|
|
|
+ const parent = steps.find((pstep) => pstep.id === step.parentIds[0])!;
|
|
|
+ const paretnBound = treeSizeMap.get(parent)!;
|
|
|
+ if (parent.bound.left - left > paretnBound.width / 2) {
|
|
|
+ // const width = stepBound.width;
|
|
|
+ const width = steps
|
|
|
+ .filter((step) => step.parentIds.includes(parent.id))
|
|
|
+ .reduce((t, c) => t + stepSizeMap.get(c)!.width, 0);
|
|
|
+
|
|
|
+ left = Math.max(
|
|
|
+ left,
|
|
|
+ parent.bound.left + (parent.bound.width - width) / 2
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return left;
|
|
|
+ };
|
|
|
+
|
|
|
+ let top = 0;
|
|
|
+ let width = 0;
|
|
|
+ for (let i = 0; i < levelsSteps.length; i++) {
|
|
|
+ let levelHeight = levelHeights[i];
|
|
|
+ for (const step of levelsSteps[i]) {
|
|
|
+ const stepSize = stepSizeMap.get(step)!;
|
|
|
+ step.bound = {
|
|
|
+ left: getStepOffset(step),
|
|
|
+ top: top + (levelHeight - stepSize.height) / 2,
|
|
|
+ ...stepSize,
|
|
|
+ };
|
|
|
+
|
|
|
+ width = Math.max(step.bound.left + step.bound.width, width);
|
|
|
+ }
|
|
|
+ top += levelHeight;
|
|
|
+ }
|
|
|
+
|
|
|
+ const getParentBound = (step: NStep) => {
|
|
|
+ const parentSteps = step.parentIds.map(
|
|
|
+ (id) => steps.find((parentStep) => parentStep.id === id)!
|
|
|
+ );
|
|
|
+ let left = 0;
|
|
|
+ let top = 0;
|
|
|
+ let width = 0,
|
|
|
+ height = 0;
|
|
|
+ for (const step of parentSteps) {
|
|
|
+ left = Math.min(step.bound.left, left);
|
|
|
+ top = Math.min(step.bound.top, top);
|
|
|
+ width += step.bound.width;
|
|
|
+ height += step.bound.height;
|
|
|
+ }
|
|
|
+ return {
|
|
|
+ width,
|
|
|
+ height,
|
|
|
+ left,
|
|
|
+ top,
|
|
|
+ };
|
|
|
+ };
|
|
|
+ const getCompleteTree = (root: NStep) => {
|
|
|
+ const completeTree = [root];
|
|
|
+ const completeTreeIds = [root.id];
|
|
|
+ const levelsChilds = levelsSteps.slice(root.level);
|
|
|
+ for (const childs of levelsChilds) {
|
|
|
+ for (const step of childs) {
|
|
|
+ if (step.parentIds.some((id) => completeTreeIds.includes(id))) {
|
|
|
+ completeTree.push(step);
|
|
|
+ completeTreeIds.push(step.id);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return completeTree;
|
|
|
+ };
|
|
|
+ // 偏移所有多父级树
|
|
|
+ for (let i = 0; i < levelsSteps.length; i++) {
|
|
|
+ for (const step of levelsSteps[i]) {
|
|
|
+ if (step.parentIds.length <= 1) continue;
|
|
|
+ const parentBound = getParentBound(step);
|
|
|
+ const offset = (parentBound.width - treeSizeMap.get(step)!.width) / 2;
|
|
|
+ getCompleteTree(step).forEach((ctStep) => {
|
|
|
+ ctStep.bound.left += offset;
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ let left = 0,
|
|
|
+ right = 0;
|
|
|
+ for (let i = 0; i < steps.length; i++) {
|
|
|
+ left = Math.min(steps[i].bound.left, left);
|
|
|
+ right = Math.max(steps[i].bound.left + steps[i].bound.width, right);
|
|
|
+ }
|
|
|
+ return { left, right, top: 0, bottom: top, levelHeights };
|
|
|
+};
|
|
|
+
|
|
|
+export const flatSteps = (data: any) => {
|
|
|
+ const nsteps: NStep[] = [];
|
|
|
+ _flatSteps(data, [], 0, false, nsteps);
|
|
|
+ return nsteps;
|
|
|
+};
|
|
|
+
|
|
|
+const ctx = document.createElement("canvas").getContext("2d")!;
|
|
|
+export const getTextBound = (
|
|
|
+ text: string,
|
|
|
+ padding: number[],
|
|
|
+ margin: number[],
|
|
|
+ font: string
|
|
|
+) => {
|
|
|
+ ctx.font = font;
|
|
|
+ const textMetrics = ctx.measureText(text);
|
|
|
+ const width = textMetrics.width + (padding[1] + margin[1]) * 2;
|
|
|
+ const height = textMetrics.hangingBaseline + (padding[0] + margin[0]) * 2;
|
|
|
+
|
|
|
+ return { width, height };
|
|
|
+};
|