from github to this gitea
This commit is contained in:
370
Excalidraw/Scripts/Downloaded/Mindmap format.md
Normal file
370
Excalidraw/Scripts/Downloaded/Mindmap format.md
Normal file
@@ -0,0 +1,370 @@
|
||||
/*
|
||||
|
||||
format **the left to right** mind map
|
||||
|
||||

|
||||
|
||||
# tree
|
||||
|
||||
Mind map is actually a tree, so you must have a **root node**. The script will determine **the leftmost element** of the selected element as the root element (node is excalidraw element, e.g. rectangle, diamond, ellipse, text, image, but it can't be arrow, line, freedraw, **group**)
|
||||
|
||||
The element connecting node and node must be an **arrow** and have the correct direction, e.g. **parent node -> children node**
|
||||
|
||||
# sort
|
||||
|
||||
The order of nodes in the Y axis or vertical direction is determined by **the creation time** of the arrow connecting it
|
||||
|
||||

|
||||
|
||||
So if you want to readjust the order, you can **delete arrows and reconnect them**
|
||||
|
||||
# setting
|
||||
|
||||
Script provides options to adjust the style of mind map, The option is at the bottom of the option of the exalidraw plugin(e.g. Settings -> Community plugins -> Excalidraw -> drag to bottom)
|
||||
|
||||
# problem
|
||||
|
||||
1. since the start bingding and end bingding of the arrow are easily disconnected from the node, so if there are unformatted parts, please **check the connection** and use the script to **reformat**
|
||||
|
||||
```javascript
|
||||
*/
|
||||
|
||||
let settings = ea.getScriptSettings();
|
||||
//set default values on first run
|
||||
if (!settings["MindMap Format"]) {
|
||||
settings = {
|
||||
"MindMap Format": {
|
||||
value: "Excalidraw/MindMap Format",
|
||||
description:
|
||||
"This is prepared for the namespace of MindMap Format and does not need to be modified",
|
||||
},
|
||||
"default gap": {
|
||||
value: 10,
|
||||
description: "Interval size of element",
|
||||
},
|
||||
"curve length": {
|
||||
value: 40,
|
||||
description: "The length of the curve part in the mind map line",
|
||||
},
|
||||
"length between element and line": {
|
||||
value: 50,
|
||||
description:
|
||||
"The distance between the tail of the connection and the connecting elements of the mind map",
|
||||
},
|
||||
};
|
||||
ea.setScriptSettings(settings);
|
||||
}
|
||||
|
||||
const sceneElements = ea.getExcalidrawAPI().getSceneElements();
|
||||
|
||||
// default X coordinate of the middle point of the arc
|
||||
const defaultDotX = Number(settings["curve length"].value);
|
||||
// The default length from the middle point of the arc on the X axis
|
||||
const defaultLengthWithCenterDot = Number(
|
||||
settings["length between element and line"].value
|
||||
);
|
||||
// Initial trimming distance of the end point on the Y axis
|
||||
const initAdjLength = 4;
|
||||
// default gap
|
||||
const defaultGap = Number(settings["default gap"].value);
|
||||
|
||||
const setCenter = (parent, line) => {
|
||||
// Focus and gap need the api calculation of excalidraw
|
||||
// e.g. determineFocusDistance, but they are not available now
|
||||
// so they are uniformly set to 0/1
|
||||
line.startBinding.focus = 0;
|
||||
line.startBinding.gap = 1;
|
||||
line.endBinding.focus = 0;
|
||||
line.endBinding.gap = 1;
|
||||
line.x = parent.x + parent.width;
|
||||
line.y = parent.y + parent.height / 2;
|
||||
};
|
||||
|
||||
/**
|
||||
* set the middle point of curve
|
||||
* @param {any} lineEl the line element of excalidraw
|
||||
* @param {number} height height of dot on Y axis
|
||||
* @param {number} [ratio=1] ,coefficient of the initial trimming distance of the end point on the Y axis, default is 1
|
||||
*/
|
||||
const setTopCurveDotOnLine = (lineEl, height, ratio = 1) => {
|
||||
if (lineEl.points.length < 3) {
|
||||
lineEl.points.splice(1, 0, [defaultDotX, lineEl.points[0][1] - height]);
|
||||
} else if (lineEl.points.length === 3) {
|
||||
lineEl.points[1] = [defaultDotX, lineEl.points[0][1] - height];
|
||||
} else {
|
||||
lineEl.points.splice(2, lineEl.points.length - 3);
|
||||
lineEl.points[1] = [defaultDotX, lineEl.points[0][1] - height];
|
||||
}
|
||||
lineEl.points[2][0] = lineEl.points[1][0] + defaultLengthWithCenterDot;
|
||||
// adjust the curvature of the second line segment
|
||||
lineEl.points[2][1] = lineEl.points[1][1] - initAdjLength * ratio * 0.8;
|
||||
};
|
||||
|
||||
const setMidCurveDotOnLine = (lineEl) => {
|
||||
if (lineEl.points.length < 3) {
|
||||
lineEl.points.splice(1, 0, [defaultDotX, lineEl.points[0][1]]);
|
||||
} else if (lineEl.points.length === 3) {
|
||||
lineEl.points[1] = [defaultDotX, lineEl.points[0][1]];
|
||||
} else {
|
||||
lineEl.points.splice(2, lineEl.points.length - 3);
|
||||
lineEl.points[1] = [defaultDotX, lineEl.points[0][1]];
|
||||
}
|
||||
lineEl.points[2][0] = lineEl.points[1][0] + defaultLengthWithCenterDot;
|
||||
lineEl.points[2][1] = lineEl.points[1][1];
|
||||
};
|
||||
|
||||
/**
|
||||
* set the middle point of curve
|
||||
* @param {any} lineEl the line element of excalidraw
|
||||
* @param {number} height height of dot on Y axis
|
||||
* @param {number} [ratio=1] ,coefficient of the initial trimming distance of the end point on the Y axis, default is 1
|
||||
*/
|
||||
const setBottomCurveDotOnLine = (lineEl, height, ratio = 1) => {
|
||||
if (lineEl.points.length < 3) {
|
||||
lineEl.points.splice(1, 0, [defaultDotX, lineEl.points[0][1] + height]);
|
||||
} else if (lineEl.points.length === 3) {
|
||||
lineEl.points[1] = [defaultDotX, lineEl.points[0][1] + height];
|
||||
} else {
|
||||
lineEl.points.splice(2, lineEl.points.length - 3);
|
||||
lineEl.points[1] = [defaultDotX, lineEl.points[0][1] + height];
|
||||
}
|
||||
lineEl.points[2][0] = lineEl.points[1][0] + defaultLengthWithCenterDot;
|
||||
// adjust the curvature of the second line segment
|
||||
lineEl.points[2][1] = lineEl.points[1][1] + initAdjLength * ratio * 0.8;
|
||||
};
|
||||
|
||||
const setTextXY = (rect, text) => {
|
||||
text.x = rect.x + (rect.width - text.width) / 2;
|
||||
text.y = rect.y + (rect.height - text.height) / 2;
|
||||
};
|
||||
|
||||
const setChildrenXY = (parent, children, line, elementsMap) => {
|
||||
x = parent.x + parent.width + line.points[2][0];
|
||||
y = parent.y + parent.height / 2 + line.points[2][1] - children.height / 2;
|
||||
distX = children.x - x;
|
||||
distY = children.y - y;
|
||||
|
||||
ea.getElementsInTheSameGroupWithElement(children, sceneElements).forEach((el) => {
|
||||
el.x = el.x - distX;
|
||||
el.y = el.y - distY;
|
||||
});
|
||||
|
||||
if (
|
||||
["rectangle", "diamond", "ellipse"].includes(children.type) &&
|
||||
![null, undefined].includes(children.boundElements)
|
||||
) {
|
||||
const textDesc = children.boundElements.filter(
|
||||
(el) => el.type === "text"
|
||||
)[0];
|
||||
if (textDesc !== undefined) {
|
||||
const textEl = elementsMap.get(textDesc.id);
|
||||
setTextXY(children, textEl);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* returns the height of the upper part of all child nodes
|
||||
* and the height of the lower part of all child nodes
|
||||
* @param {Number[]} childrenTotalHeightArr
|
||||
* @returns {Number[]} [topHeight, bottomHeight]
|
||||
*/
|
||||
const getNodeCurrentHeight = (childrenTotalHeightArr) => {
|
||||
if (childrenTotalHeightArr.length <= 0) return [0, 0];
|
||||
else if (childrenTotalHeightArr.length === 1)
|
||||
return [childrenTotalHeightArr[0] / 2, childrenTotalHeightArr[0] / 2];
|
||||
const heightArr = childrenTotalHeightArr;
|
||||
let topHeight = 0,
|
||||
bottomHeight = 0;
|
||||
const isEven = heightArr.length % 2 === 0;
|
||||
const mid = Math.floor(heightArr.length / 2);
|
||||
const topI = mid - 1;
|
||||
const bottomI = isEven ? mid : mid + 1;
|
||||
topHeight = isEven ? 0 : heightArr[mid] / 2;
|
||||
for (let i = topI; i >= 0; i--) {
|
||||
topHeight += heightArr[i];
|
||||
}
|
||||
bottomHeight = isEven ? 0 : heightArr[mid] / 2;
|
||||
for (let i = bottomI; i < heightArr.length; i++) {
|
||||
bottomHeight += heightArr[i];
|
||||
}
|
||||
return [topHeight, bottomHeight];
|
||||
};
|
||||
|
||||
/**
|
||||
* handle the height of each point in the single-level tree
|
||||
* @param {Array} lines
|
||||
* @param {Map} elementsMap
|
||||
* @param {Boolean} isEven
|
||||
* @param {Number} mid 'lines' array midpoint index
|
||||
* @returns {Array} height array corresponding to 'lines'
|
||||
*/
|
||||
const handleDotYValue = (lines, elementsMap, isEven, mid) => {
|
||||
const getTotalHeight = (line, elementsMap) => {
|
||||
return elementsMap.get(line.endBinding.elementId).totalHeight;
|
||||
};
|
||||
const getTopHeight = (line, elementsMap) => {
|
||||
return elementsMap.get(line.endBinding.elementId).topHeight;
|
||||
};
|
||||
const getBottomHeight = (line, elementsMap) => {
|
||||
return elementsMap.get(line.endBinding.elementId).bottomHeight;
|
||||
};
|
||||
const heightArr = new Array(lines.length).fill(0);
|
||||
const upI = mid === 0 ? 0 : mid - 1;
|
||||
const bottomI = isEven ? mid : mid + 1;
|
||||
let initHeight = isEven ? 0 : getTopHeight(lines[mid], elementsMap);
|
||||
for (let i = upI; i >= 0; i--) {
|
||||
heightArr[i] = initHeight + getBottomHeight(lines[i], elementsMap);
|
||||
initHeight += getTotalHeight(lines[i], elementsMap);
|
||||
}
|
||||
initHeight = isEven ? 0 : getBottomHeight(lines[mid], elementsMap);
|
||||
for (let i = bottomI; i < lines.length; i++) {
|
||||
heightArr[i] = initHeight + getTopHeight(lines[i], elementsMap);
|
||||
initHeight += getTotalHeight(lines[i], elementsMap);
|
||||
}
|
||||
return heightArr;
|
||||
};
|
||||
|
||||
/**
|
||||
* format single-level tree
|
||||
* @param {any} parent
|
||||
* @param {Array} lines
|
||||
* @param {Map} childrenDescMap
|
||||
* @param {Map} elementsMap
|
||||
*/
|
||||
const formatTree = (parent, lines, childrenDescMap, elementsMap) => {
|
||||
lines.forEach((item) => setCenter(parent, item));
|
||||
|
||||
const isEven = lines.length % 2 === 0;
|
||||
const mid = Math.floor(lines.length / 2);
|
||||
const heightArr = handleDotYValue(lines, childrenDescMap, isEven, mid);
|
||||
lines.forEach((item, index) => {
|
||||
if (isEven) {
|
||||
if (index < mid) setTopCurveDotOnLine(item, heightArr[index], index + 1);
|
||||
else setBottomCurveDotOnLine(item, heightArr[index], index - mid + 1);
|
||||
} else {
|
||||
if (index < mid) setTopCurveDotOnLine(item, heightArr[index], index + 1);
|
||||
else if (index === mid) setMidCurveDotOnLine(item);
|
||||
else setBottomCurveDotOnLine(item, heightArr[index], index - mid);
|
||||
}
|
||||
});
|
||||
lines.forEach((item) => {
|
||||
if (item.endBinding !== null) {
|
||||
setChildrenXY(
|
||||
parent,
|
||||
elementsMap.get(item.endBinding.elementId),
|
||||
item,
|
||||
elementsMap
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const generateTree = (elements) => {
|
||||
const elIdMap = new Map([[elements[0].id, elements[0]]]);
|
||||
let minXEl = elements[0];
|
||||
for (let i = 1; i < elements.length; i++) {
|
||||
elIdMap.set(elements[i].id, elements[i]);
|
||||
if (
|
||||
!(elements[i].type === "arrow" || elements[i].type === "line") &&
|
||||
elements[i].x < minXEl.x
|
||||
) {
|
||||
minXEl = elements[i];
|
||||
}
|
||||
}
|
||||
const root = {
|
||||
el: minXEl,
|
||||
totalHeight: minXEl.height,
|
||||
topHeight: 0,
|
||||
bottomHeight: 0,
|
||||
linkChildrensLines: [],
|
||||
isLeafNode: false,
|
||||
children: [],
|
||||
};
|
||||
const preIdSet = new Set(); // The id_set of Elements that is already in the tree, avoid a dead cycle
|
||||
const dfsForTreeData = (root) => {
|
||||
if (preIdSet.has(root.el.id)) {
|
||||
return 0;
|
||||
}
|
||||
preIdSet.add(root.el.id);
|
||||
let lines = root.el.boundElements.filter(
|
||||
(el) =>
|
||||
el.type === "arrow" &&
|
||||
!preIdSet.has(el.id) &&
|
||||
elIdMap.get(el.id)?.startBinding?.elementId === root.el.id
|
||||
);
|
||||
if (lines.length === 0) {
|
||||
root.isLeafNode = true;
|
||||
root.totalHeight = root.el.height + 2 * defaultGap;
|
||||
[root.topHeight, root.bottomHeight] = [
|
||||
root.totalHeight / 2,
|
||||
root.totalHeight / 2,
|
||||
];
|
||||
return root.totalHeight;
|
||||
} else {
|
||||
lines = lines.map((elementDesc) => {
|
||||
preIdSet.add(elementDesc.id);
|
||||
return elIdMap.get(elementDesc.id);
|
||||
});
|
||||
}
|
||||
|
||||
const linkChildrensLines = [];
|
||||
lines.forEach((el) => {
|
||||
const line = el;
|
||||
if (
|
||||
line &&
|
||||
line.endBinding !== null &&
|
||||
line.endBinding !== undefined &&
|
||||
!preIdSet.has(elIdMap.get(line.endBinding.elementId).id)
|
||||
) {
|
||||
const children = elIdMap.get(line.endBinding.elementId);
|
||||
linkChildrensLines.push(line);
|
||||
root.children.push({
|
||||
el: children,
|
||||
totalHeight: 0,
|
||||
topHeight: 0,
|
||||
bottomHeight: 0,
|
||||
linkChildrensLines: [],
|
||||
isLeafNode: false,
|
||||
children: [],
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
let totalHeight = 0;
|
||||
root.children.forEach((el) => (totalHeight += dfsForTreeData(el)));
|
||||
|
||||
root.linkChildrensLines = linkChildrensLines;
|
||||
if (root.children.length === 0) {
|
||||
root.isLeafNode = true;
|
||||
root.totalHeight = root.el.height + 2 * defaultGap;
|
||||
[root.topHeight, root.bottomHeight] = [
|
||||
root.totalHeight / 2,
|
||||
root.totalHeight / 2,
|
||||
];
|
||||
} else if (root.children.length > 0) {
|
||||
root.totalHeight = Math.max(root.el.height + 2 * defaultGap, totalHeight);
|
||||
[root.topHeight, root.bottomHeight] = getNodeCurrentHeight(
|
||||
root.children.map((item) => item.totalHeight)
|
||||
);
|
||||
}
|
||||
|
||||
return totalHeight;
|
||||
};
|
||||
dfsForTreeData(root);
|
||||
const dfsForFormat = (root) => {
|
||||
if (root.isLeafNode) return;
|
||||
const childrenDescMap = new Map(
|
||||
root.children.map((item) => [item.el.id, item])
|
||||
);
|
||||
formatTree(root.el, root.linkChildrensLines, childrenDescMap, elIdMap);
|
||||
root.children.forEach((el) => dfsForFormat(el));
|
||||
};
|
||||
dfsForFormat(root);
|
||||
};
|
||||
|
||||
const elements = ea.getViewSelectedElements();
|
||||
generateTree(elements);
|
||||
|
||||
ea.copyViewElementsToEAforEditing(elements);
|
||||
await ea.addElementsToView(false, false);
|
Reference in New Issue
Block a user