mindMap.tsx 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. import { TopicType } from "@/enum";
  2. import { MindMapProjectInfo, TopicItem } from "@/types";
  3. import { Graph, Cell, Node } from "@antv/x6";
  4. import TopicComponent from "@/components/mindMap/Topic";
  5. import { topicData } from "@/config/data";
  6. import { uuid } from "@/utils";
  7. import { hierarchyMethodMap } from "@/pages/mindmap/hierarchy";
  8. import { createEdge } from "./edge";
  9. import { getTheme } from "./theme";
  10. interface HierarchyResult {
  11. id: string;
  12. x: number;
  13. y: number;
  14. data: TopicItem;
  15. children?: HierarchyResult[];
  16. }
  17. /**
  18. * 渲染思维导图项目
  19. * @param graph
  20. */
  21. export const renderMindMap = (
  22. graph: Graph,
  23. setMindProjectInfo: () => void
  24. ) => {
  25. const projectInfo = getMindMapProjectByLocal();
  26. if(!projectInfo) return;
  27. const { topics, pageSetting } = projectInfo;
  28. const cells: Cell[] = [];
  29. topics.forEach((topic) => {
  30. // 遍历出层次结构
  31. const result: HierarchyResult = hierarchyMethodMap[projectInfo.structure]?.(topic, pageSetting);
  32. let originPosition = { x: topic?.x ?? -10, y: topic?.y ?? -10 };
  33. if(graph.hasCell(topic.id)) {
  34. const node = graph.getCellById(topic.id);
  35. if(node.isNode()) {
  36. originPosition = node.position();
  37. }
  38. }
  39. const offsetX = originPosition.x - result.x;
  40. const offsetY = originPosition.y - result.y;
  41. const traverse = (
  42. hierarchyItem: HierarchyResult,
  43. parent?: Node
  44. ) => {
  45. if (hierarchyItem) {
  46. const { data, children, x, y } = hierarchyItem;
  47. const id = data?.id || uuid();
  48. // 创建主题
  49. const node = graph.createNode({
  50. ...TopicComponent,
  51. width: data.width,
  52. height: data.height,
  53. data: {
  54. ...data,
  55. // 节点内部执行数据更新方法
  56. setMindProjectInfo,
  57. },
  58. id,
  59. x: offsetX + x,
  60. y: offsetY + y,
  61. });
  62. cells.push(node);
  63. parent && parent.addChild(node);
  64. if (children) {
  65. children.forEach((item: HierarchyResult) => {
  66. cells.push(
  67. // 创建连线
  68. createEdge(graph, id, item, projectInfo.structure, projectInfo.theme)
  69. );
  70. // 递归遍历
  71. traverse(item, node);
  72. });
  73. }
  74. }
  75. };
  76. traverse(result);
  77. });
  78. // 处理节点
  79. cells.filter(cell => cell.isNode()).forEach((cell) => {
  80. // 存在更新位置,否则添加
  81. if (graph.hasCell(cell.id)) {
  82. const oldCell = graph.getCellById(cell.id);
  83. if(oldCell.isNode()) {
  84. oldCell.position(cell.position().x, cell.position().y);
  85. oldCell.setData({
  86. ...cell.data
  87. })
  88. }
  89. } else {
  90. graph.addCell(cell);
  91. }
  92. });
  93. cells.filter(cell => cell.isEdge()).forEach((cell) => {
  94. graph.removeCell(cell.id);
  95. graph.addCell(cell);
  96. })
  97. const oldCells = graph.getCells();
  98. // 移除不存在的节点
  99. oldCells.forEach((cell) => {
  100. if (!cells.find(item => cell.id === item.id)) {
  101. graph.removeCell(cell.id + '-edge');
  102. graph.removeCell(cell);
  103. }
  104. });
  105. // graph.centerContent();
  106. };
  107. /**
  108. * 添加分支主题
  109. */
  110. export const addTopic = (
  111. type: TopicType,
  112. setMindProjectInfo: (info: MindMapProjectInfo) => void,
  113. node?: Node,
  114. otherData: Record<string, any> = {},
  115. ) => {
  116. const projectInfo = getMindMapProjectByLocal();
  117. if (!projectInfo || !setMindProjectInfo) return;
  118. const topic = buildTopic(type, {
  119. ...(otherData || {}),
  120. parentId: node?.id
  121. }, node);
  122. if( node) {
  123. const parentId = node.id;
  124. const traverse = (topics: TopicItem[]) => {
  125. topics.forEach((item) => {
  126. if (item.id === parentId) {
  127. if (item.children) {
  128. item.children?.push(topic);
  129. } else {
  130. item.children = [topic];
  131. }
  132. }
  133. if (item.children) {
  134. traverse(item.children);
  135. }
  136. });
  137. };
  138. traverse(projectInfo?.topics || []);
  139. } else {
  140. projectInfo.topics.push(topic);
  141. }
  142. setMindProjectInfo(projectInfo);
  143. return topic;
  144. };
  145. const topicMap = {
  146. [TopicType.main]: {
  147. label: "中心主题",
  148. width: 206,
  149. height: 70
  150. },
  151. [TopicType.branch]: {
  152. label: "分支主题",
  153. width: 104,
  154. height: 40
  155. },
  156. [TopicType.sub]: {
  157. label: "子主题",
  158. width: 76,
  159. height: 27
  160. }
  161. };
  162. /**
  163. * 构建一个主题数据
  164. * @param type 主题类型
  165. * @param options 配置项
  166. * @returns
  167. */
  168. export const buildTopic = (
  169. type: TopicType,
  170. options: Record<string, any> = {},
  171. parentNode?: Node
  172. ) => {
  173. const projectInfo = getMindMapProjectByLocal();
  174. const theme = getTheme(projectInfo?.theme, type === TopicType.sub ? parentNode : undefined);
  175. return {
  176. ...topicData,
  177. id: uuid(),
  178. type,
  179. label: topicMap[type].label || "自由主题",
  180. width: topicMap[type].width || 206,
  181. height: topicMap[type].height || 70,
  182. fill: {
  183. ...topicData.fill,
  184. ...theme[type]?.fill,
  185. },
  186. text: {
  187. ...topicData.text,
  188. ...theme[type]?.text,
  189. },
  190. stroke: {
  191. ...topicData.stroke,
  192. ...theme[type]?.stroke,
  193. },
  194. edge: {
  195. ...topicData.edge,
  196. color: theme[type]?.edge.color,
  197. },
  198. ...options,
  199. };
  200. };
  201. /**
  202. * 从本地获取项目信息
  203. * @returns
  204. */
  205. export const getMindMapProjectByLocal = (): MindMapProjectInfo | null => {
  206. return JSON.parse(localStorage.getItem("minMapProjectInfo") || "null");
  207. };
  208. /**
  209. * 更新主题数据
  210. * @param id 主题id
  211. * @param value 更新的数据
  212. * @param setMindProjectInfo 更新项目信息方法
  213. */
  214. export const updateTopic = (
  215. id: string,
  216. value: Partial<TopicItem>,
  217. setMindProjectInfo: (info: MindMapProjectInfo) => void
  218. ) => {
  219. const projectInfo = getMindMapProjectByLocal();
  220. if (!projectInfo || !setMindProjectInfo) return;
  221. const traverse = (topics: TopicItem[]) => {
  222. topics.forEach((item) => {
  223. if (item.id === id) {
  224. Object.assign(item, value);
  225. }
  226. if (item.children) {
  227. traverse(item.children);
  228. }
  229. });
  230. };
  231. traverse(projectInfo?.topics || []);
  232. setMindProjectInfo(projectInfo);
  233. }