How to make such a mind map graphic

how to make such a mind map graphic

Is the graph always tree-structured? That is, no node will have more than one link coming into it, and there are no cycles.

That should be straight-forward to implement. I can try that later today or tomorrow when I have more time.

Yes, all correct.
Thank you. Highly anticipated…

Here you go:

[EDIT: removed duplication of data.color property – now only on group data, not on Trunk node data too.

<!DOCTYPE html>
<html>
<head>
  <title>Alternating Tree Layout</title>
  <!-- Copyright 1998-2024 by Northwoods Software Corporation. -->
</head>
<body>
  <div id="myDiagramDiv" style="border: solid 1px black; width:100%; height:400px"></div>

  <script src="https://cdn.jsdelivr.net/npm/gojs/release/go-debug.js"></script>
  <script id="code">

class AlternatingTreeLayout extends go.TreeLayout {
  constructor(init) {
    super();
    if (init) Object.assign(this, init);
  }

  makeNetwork(coll) {
    var net = super.makeNetwork(coll);
    net.vertexes.each(v => {
      const grp = v.node;
      if (grp instanceof go.Group) {
        const nd = grp.memberParts.first();
        if (!(nd instanceof go.Node)) return;
        const rt = nd.findTreeRoot();
        const h2 = rt.actualBounds.height/2;
        v.focusY = grp.data.up ? v.height-h2 : h2;
        v.width -= this.layerSpacing;
      }
    });
    return net;
  }
}

const myDiagram =
  new go.Diagram("myDiagramDiv", {
       layout: new AlternatingTreeLayout(),
       "draggingTool.dragsTree": true,
      "undoManager.isEnabled": true
    });

// the template for simple nodes in the subtrees
myDiagram.nodeTemplate =
  new go.Node()
    .add(
      new go.TextBlock({ margin: 2 })
        .bind("text")
    );

// the template for "Trunk" nodes, which can either be top-level or can be a member of a group
myDiagram.nodeTemplateMap.add("T",
  new go.Node("Auto", { height: 30 })
    .bindObject("width", "", n => n.isTopLevel ? 60 : 30)
    .add(
      new go.Shape("Circle", { fill: "white", strokeWidth: 2 })
        .bind("fill")
        .bindObject("stroke", "", n => (n.isTopLevel ? n.data.color : n.containingGroup.data.color) || "black")
        .bindObject("figure", "", n => n.isTopLevel ? "RoundedRectangle" : "Circle"),
      new go.TextBlock({ textAlign: "center" })
        .bind("text")
        .bind("stroke", "fill", c => go.Brush.isDark(c) ? "white" : "black")
    ));

// get the color from the containing group, or else from the toNode's containing group
function computeColor(link) {
  const grp = link.containingGroup;
  if (grp) return grp.data.color || "black";
  return link.toNode?.containingGroup?.data.color || "black";
}

myDiagram.linkTemplate =
  new go.Link({ routing: go.Link.Orthogonal })
    .add(
      new go.Shape({ strokeWidth: 2 })
        .bindObject("stroke", "", computeColor)
    );

// dynamically produce a TreeLayout for subtrees according to whether it's upward or downward
function makeLayout(up) {
  const lay = new go.TreeLayout({
      treeStyle: go.TreeStyle.RootOnly,
      layerSpacing: 0,
      nodeSpacing: 5,
      nodeIndent: 35,  // should be greater than the root node's actualBounds.height
      alignment: go.TreeAlignment.Start,
      portSpot: go.Spot.Bottom,
      alternateLayerSpacing: 30,
      alternateNodeSpacing: 5
    });
  if (up) {
    lay.alignment = go.TreeAlignment.End;
    lay.portSpot = go.Spot.Top;
  }
  return lay;
}

// groups have no visual rendering -- just a layout
myDiagram.groupTemplate =
  new go.Group({ layout: makeLayout(false) })
    .bind("layout", "up", makeLayout)
    .add(new go.Placeholder())

myDiagram.model = new go.GraphLinksModel(
[
  { key: "Start", text: "Start", fill: "indigo", category: "T" },
  { key: "A", isGroup: true, up: false, color: "red" },
  { key: 1, group: "A", text: "A", category: "T" },
  { key: 2, group: "A", text: "A 1" },
  { key: 3, group: "A", text: "A 2" },
  { key: 4, group: "A", text: "A 2 1" },
  { key: 5, group: "A", text: "A 2 2" },
  { key: "B", isGroup: true, up: true, color: "orange" },
  { key: 11, group: "B", text: "B", category: "T" },
  { key: 12, group: "B", text: "B 1" },
  { key: 13, group: "B", text: "B 2" },
  { key: 14, group: "B", text: "B 2 1" },
  { key: 15, group: "B", text: "B 2 2" },
  { key: 16, group: "B", text: "B 2 3" },
  { key: "C", isGroup: true, up: false, color: "green" },
  { key: 21, group: "C", text: "C", category: "T" },
  { key: 22, group: "C", text: "C 1" },
  { key: 23, group: "C", text: "C 2" },
  { key: 24, group: "C", text: "C 2 1" },
  { key: 25, group: "C", text: "C 2 2" },
  { key: 26, group: "C", text: "C 3" },
  { key: "End", text: "End", color: "slateblue", category: "T" },
],
[
  { from: "Start", to: 1 },
  { from: 1, to: 2 },
  { from: 1, to: 3 },
  { from: 3, to: 4 },
  { from: 3, to: 5 },
  { from: 1, to: 11 },
  { from: 11, to: 12 },
  { from: 11, to: 13 },
  { from: 13, to: 14 },
  { from: 13, to: 15 },
  { from: 13, to: 16 },
  { from: 11, to: 21 },
  { from: 21, to: 22 },
  { from: 21, to: 23 },
  { from: 23, to: 24 },
  { from: 23, to: 25 },
  { from: 21, to: 26 },
  { from: 21, to: "End" },
]);
  </script>
</body>
</html>

Thank you very much!!! you have solved my problem for the past few days.
Let me study digestion.

I have updated the code and added some comments.

Thanks. if i want add other free mind map,and connect them with dashed lines,how to do?

I have added a new category of link template, “E” (meaning “Extra”), for that backwards-looking dashed link.

Note that groups with data.up === null have a regular TreeLayout and are not positioned by the AlternatingTreeLayout as part of its tree.

<!DOCTYPE html>
<html>
<head>
  <title>Alternating Tree Layout</title>
  <!-- Copyright 1998-2024 by Northwoods Software Corporation. -->
</head>
<body>
  <div id="myDiagramDiv" style="border: solid 1px black; width:100%; height:400px"></div>
  <textarea id="mySavedModel" style="width:100%;height:450px"></textarea>

  <script src="https://cdn.jsdelivr.net/npm/gojs/release/go-debug.js"></script>
  <script id="code">

class AlternatingTreeLayout extends go.TreeLayout {
  constructor(init) {
    super();
    if (init) Object.assign(this, init);
  }

  makeNetwork(coll) {
    var net = super.makeNetwork(coll);
    net.vertexes.each(v => {
      const grp = v.node;
      if (grp instanceof go.Group) {
        const nd = grp.memberParts.first();
        if (!(nd instanceof go.Node)) return;
        const rt = nd.findTreeRoot();
        const h2 = rt.actualBounds.height/2;
        v.focusY = grp.data.up ? v.height-h2 : h2;
        v.width -= this.layerSpacing;
      }
    });
    return net;
  }
}

const myDiagram =
  new go.Diagram("myDiagramDiv", {
       layout: new AlternatingTreeLayout(),
       "draggingTool.dragsTree": true,
      "undoManager.isEnabled": true,
      "ModelChanged": e => {     // just for demonstration purposes,
        if (e.isTransactionFinished) {  // show the model data in the page's TextArea
          document.getElementById("mySavedModel").textContent = e.model.toJson();
        }
      }
    });

// the template for simple nodes in the subtrees
myDiagram.nodeTemplate =
  new go.Node()
    .bindTwoWay("location", "loc", go.Point.parse, go.Point.stringifyFixed(1))
    .add(
      new go.TextBlock({ margin: 2 })
        .bind("text")
    );

// the template for "Trunk" nodes, which can either be top-level or can be a member of a group
myDiagram.nodeTemplateMap.add("T",
  new go.Node("Auto", { height: 30 })
    .bindTwoWay("location", "loc", go.Point.parse, go.Point.stringifyFixed(1))
    .bindObject("width", "", n => n.isTopLevel ? 60 : 30)
    .add(
      new go.Shape("Circle", { fill: "white", strokeWidth: 2 })
        .bind("fill")
        .bindObject("stroke", "", n => (n.isTopLevel ? n.data.color : n.containingGroup.data.color) || "black")
        .bindObject("figure", "", n => n.isTopLevel ? "RoundedRectangle" : "Circle"),
      new go.TextBlock({ textAlign: "center" })
        .bind("text")
        .bind("stroke", "fill", c => go.Brush.isDark(c) ? "white" : "black")
    ));

// get the color from the containing group, or else from the toNode's containing group
function computeColor(link) {
  const grp = link.containingGroup;
  if (grp) return grp.data.color || "black";
  return link.toNode?.containingGroup?.data.color || "black";
}

myDiagram.linkTemplate =
  new go.Link({ routing: go.Link.Orthogonal })
    .add(
      new go.Shape({ strokeWidth: 2 })
        .bindObject("stroke", "", computeColor)
    );

myDiagram.linkTemplateMap.add("E",
  new go.Link({
      isLayoutPositioned: false,
      curve: go.Curve.Bezier,
      fromSpot: go.Spot.None, toSpot: go.Spot.None
    })
    .add(
      new go.Shape({ strokeWidth: 2, strokeDashArray: [6, 3] })
        .bindObject("stroke", "", link => link.toNode.data.fill),
      new go.Shape({ fromArrow: "Backward", strokeWidth: 0 })
        .bindObject("fill", "", link => link.toNode.data.fill)
    )
);

// dynamically produce a TreeLayout for subtrees according to whether it's upward or downward
// null means neither upwards nor downwards
function makeLayout(up) {
  if (up === null) return new go.TreeLayout({
    layerSpacing: 30,
    nodeSpacing: 5
  });
  const lay = new go.TreeLayout({
      treeStyle: go.TreeStyle.RootOnly,
      layerSpacing: 0,
      nodeSpacing: 5,
      nodeIndent: 35,  // should be greater than the root node's actualBounds.height
      alignment: go.TreeAlignment.Start,
      portSpot: go.Spot.Bottom,
      alternateLayerSpacing: 30,
      alternateNodeSpacing: 5
    });
  if (up) {
    lay.alignment = go.TreeAlignment.End;
    lay.portSpot = go.Spot.Top;
  }
  return lay;
}

// groups have no visual rendering -- just a layout
myDiagram.groupTemplate =
  new go.Group({ layout: makeLayout(null) })
    .bindTwoWay("location", "loc", go.Point.parse, go.Point.stringifyFixed(1))
    .bind("layout", "up", makeLayout)
    .bind("isLayoutPositioned", "up", up => up !== null)
    .add(new go.Placeholder())

myDiagram.model = new go.GraphLinksModel(
[
  { key: "Start", text: "Start", fill: "indigo", category: "T" },
  { key: "A", isGroup: true, up: false, color: "red" },
  { key: 1, group: "A", text: "A", category: "T" },
  { key: 2, group: "A", text: "A 1" },
  { key: 3, group: "A", text: "A 2" },
  { key: 4, group: "A", text: "A 2 1" },
  { key: 5, group: "A", text: "A 2 2" },
  { key: "B", isGroup: true, up: true, color: "orange" },
  { key: 11, group: "B", text: "B", category: "T" },
  { key: 12, group: "B", text: "B 1" },
  { key: 13, group: "B", text: "B 2" },
  { key: 14, group: "B", text: "B 2 1" },
  { key: 15, group: "B", text: "B 2 2" },
  { key: 16, group: "B", text: "B 2 3" },
  { key: "C", isGroup: true, up: false, color: "green" },
  { key: 21, group: "C", text: "C", category: "T" },
  { key: 22, group: "C", text: "C 1" },
  { key: 23, group: "C", text: "C 2" },
  { key: 24, group: "C", text: "C 2 1" },
  { key: 25, group: "C", text: "C 2 2" },
  { key: 26, group: "C", text: "C 3" },
  { key: "End", text: "End", color: "slateblue", category: "T" },
  { key: "D", isGroup: true, up: null, loc: "334 -59" },
  { key: 31, group: "D", text: "D", category: "T", fill: "green", loc: "334 -47.35" },
  { key: 32, group: "D", text: "D 1", loc: "377 -59" },
  { key: 33, group: "D", text: "D 2", loc: "377 -35.7" },
  { key: 34, group: "D", text: "D 1 1", loc: "431 -59" },
],
[
  { from: "Start", to: 1 },
  { from: 1, to: 2 },
  { from: 1, to: 3 },
  { from: 3, to: 4 },
  { from: 3, to: 5 },
  { from: 1, to: 11 },
  { from: 11, to: 12 },
  { from: 11, to: 13 },
  { from: 13, to: 14 },
  { from: 13, to: 15 },
  { from: 13, to: 16 },
  { from: 11, to: 21 },
  { from: 21, to: 22 },
  { from: 21, to: 23 },
  { from: 23, to: 24 },
  { from: 23, to: 25 },
  { from: 21, to: 26 },
  { from: 21, to: "End" },
  { from: 12, to: 31, category: "E" },
  { from: 31, to: 32 },
  { from: 31, to: 33 },
  { from: 32, to: 34 },
]);
  </script>
</body>
</html>

thank you! I’ll give it a try.