DEV Community

Cover image for Create a MindMap
Frank Wisniewski
Frank Wisniewski

Posted on • Updated on

Create a MindMap

Make a MindMap without Lib´s.
Look at the comments in code.

HTML-Code

<!DOCTYPE html>
<html lang="de">

<head>

  <meta charset="UTF-8">
  <title>MindMap</title>
  <link type="text/css" href="mindmap.css" rel="stylesheet">

</head>

<body>
  <h1>MindMap</h1>

  <div id="container"></div>

  <script type="text/javascript" src="mindmap.js"></script>

</body>

</html>
Enter fullscreen mode Exit fullscreen mode

mindmap.css

    body {
      font-family: system-ui, sans-serif;
    }

    .f-col {
      display: flex;
      flex-direction: column;
      flex-wrap: nowrap;
      justify-content: space-around;
    }

    #container {
      display: flex;
      justify-content: space-between;
      padding: 0.5em;
      min-width: 600px;
    }

    #container p {
      padding: 0.25em 0.5em;
      background: #f3f3f3;
      border: 3px solid red;
      border-radius: 0.5em;
      text-align: center;
      max-width: 100px;
    }

    svg {
      position: absolute;
      z-index: -1
    }

    .path {
      stroke: silver;
      fill: none;
      stroke-width: 2;
    }
Enter fullscreen mode Exit fullscreen mode

mindmap.js

"use strict";
// my helpers...
const $ = function(t = null) {
  return new class {
    constructor(t) {
      t && (this.nodes = t.nodeType ? [t] : NodeList.prototype.isPrototypeOf(t) ? t : document.querySelectorAll(t), this.n = this.nodes[0])
    }
    each = t => (this.nodes.forEach((s => t(s))), this);
    addClass = t => this.each((s => s.classList.add(...t.split(",").map((t => t.trim())))), this);
    html = t => this.each(s => s.innerHTML = t)
    create = t => $(document.createElement(t));
    append = t => {
      let s = this.create(t);
      return this.n.appendChild(s.n), s
    };
    remove = () => this.each(_node => _node.remove())
    insertAfter = t => (t.n.after(this.n), this);
    setAttr = (t, s = "") => this.each((e => e.setAttribute(t, s)), this);
    sCreate = t => {
      let s = document.createElementNS("http://www.w3.org/2000/svg", "svg");
      return s.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:xlink", "http://www.w3.org/1999/xlink"), s.setAttribute("viewBox", t), $(s)
    };
    sAppend = t => (this.n.appendChild(t.n), this);
    sC = t => document.createElementNS("http://www.w3.org/2000/svg", t);
    id = t => (this.setAttr("id", t), this);
    d = t => (this.setAttr("d", t), this);
    fill = t => (this.setAttr("fill", t), this);
    stroke = t => (this.setAttr("stroke", t), this);
    strokeWidth = t => (this.setAttr("stroke-width", t), this);
    sBezier = (t, s, e, i) => $(this.sC("path")).d(`M${t},${s} C ${(t+e)/2},${s} ${(t+e)/2},${i} ${e},${i}`);
  }(t)
};
// by resize repaint SVG
const resizeObserver = new ResizeObserver(entries => {
  $('#mySVG').remove()
  drawLines(data)
})

function createMap(data) {
  //find depth of Elements
  function findDepth(id, depth = 0) {
    while (id !== null) {
      let aktEl = data.filter(x => x.id == id)
      depth++
      id = aktEl[0].parentId
      if (id == null) depth = depth * aktEl[0].div
    }
    return depth
  }
  // first div at right side (-1*-1=1)
  var aktDiv = -1
  // swap between left and right side
  data.filter(x => x.parentId == null)
    .forEach(x => {
      x.div = aktDiv;
      aktDiv *= -1
    })
  // set root Elements div = 0
  data.find(x => x.id == null).div = 0
  // set divs for Childs
  data.filter(x => (x.parentId !== null && x.id !== null))
    .forEach(x => x.div = findDepth(x.id))
  // find min and max Div-Number
  let minDiv = Math.min(...data.map(item => item.div))
  let maxDiv = Math.max(...data.map(item => item.div))
  // create Divs in container
  for (let i = minDiv; i <= maxDiv; i++) {
    $('#container').append('div').id(`div${i}`).addClass('f-col')
  }
  // put paragraphs in Div´s
  data.forEach(el => {
    $(`#div${el.div}`).append('p').html(el.text).id(`p${el.id}`)
  })
}

function drawLines(data) {
  // find Coordinates from Container
  let mC = $('#container').n.getBoundingClientRect();
  // create SVG with same ViewportSize as Container
  $().sCreate(`0,0,${mC.width},${mC.height}`).setAttr('id', 'mySVG')
    .insertAfter($('body > h1'))
  // set SVG at same Position as Container
  $('#mySVG').n.style = `top:${mC.top}px;left:${mC.left}px;
        width:${mC.width}px;height:${mC.height}px;`
  // create Bezier from Element's to Parent´s
  data.forEach(el => {
    if ('parentId' in el) {
      let aktEl = $(`#p${el.id}`).n.getBoundingClientRect()
      let parEl = $(`#p${el.parentId}`).n.getBoundingClientRect()
      let aktLeft = (aktEl.left < parEl.left) ? aktEl.left + aktEl.width : aktEl.left
      let parLeft = (aktEl.left > parEl.left) ? parEl.left + parEl.width : parEl.left
      $('#mySVG').sAppend(
        $().sBezier(aktLeft - mC.left, aktEl.top - mC.top + (aktEl.height / 2), parLeft - mC.left, parEl.top - mC.top + (parEl.height / 2)).addClass('path'))
    }
  })
}
const data = [
  { id: null , text: "<b>Test<br>Mind Map</b>" },
  { id: 1 , parentId: null, text: "Idea - 1" },
  { id: 2 , parentId: null, text: "Dr. Helmut Zimmermann" },
  { id: 3 , parentId: null, text: "Idea - 3" },
  { id: 4 , parentId: 1, text: "order Cake and <b>Coffee</b>" },
  { id: 5 , parentId: 1, text: "Idea - 1 - 2 " },
  { id: 6 , parentId: 3, text: "Idea - 3 - 1 " },
  { id: 7 , parentId: 3, text: "Idea- 3 - 2" },
  { id: 10 , parentId: 2, text: "Idea - 2 - 1" },
  { id: 12 , parentId: 2, text: "Idea - 2 - 2" },
];
createMap(data)
resizeObserver.observe($('#container').n)
Enter fullscreen mode Exit fullscreen mode

Codepen

Discussion (0)