おまじないの綴り方

spelling of a logical spell

jsで簡易マインドマップエディタを作る

できたもの

f:id:miyatsuki_yatsuki:20171217202012p:plain

できること

  • textarea内に記載された箇条書きに従ってマインドマップ(SVG)をリアルタイムに生成する
  • 依存ライブラリがjqueryだけなので、ブラウザさえあれば特別なソフトなしで使用可能

できないこと

実装上のポイント

  • jsから動的にSVG要素を作るときは、createElementではなくcreatElementNSを使う
  • SVGで自動改行するテキストボックスを作るにはforeignObject要素の子要素としてdivを配置
    • その際foreignObjectはSVG要素の直下に置く。
    • foreignObject配下にdivを作るときもcreateElementNSを使っておいたほうがよさそう
var svgNS = "http://www.w3.org/2000/svg";
var htmlNS = "http://www.w3.org/1999/xhtml"

var foreignElement = document.createElementNS(svgNS, "foreignObject");
foreignElement.setAttribute("width", nodeWidth);
foreignElement.setAttribute("height", nodeHeight); 
foreignElement.setAttribute("x", x);
foreignElement.setAttribute("y", y);

var innerElement = document.createElementNS(htmlNS, "div");
innerElement.innerText = text;

document.getElementById("map").appendChild(foreignElement);
foreignElement.appendChild(innerElement);

コード

<html>
  <head>
    <script type="text/javascript" src="jquery-3.2.1.min.js"></script>    
  </head>

  <style>

  </style>

  <body>
<div>
    <div style="float:left">
        <textarea id="text" name="text" rows="20" cols="40"></textarea>
    </div>
    <div style="float:left;">
      <svg id="map" width="700" height="500"></svg>
    </div>
</div>

    <script type="text/javascript">

    var svgNS = "http://www.w3.org/2000/svg";
    var htmlNS = "http://www.w3.org/1999/xhtml"

    var inputText = "";
    $("#text").keyup(function(){
        if(inputText != $("#text").val())
        {
            inputText = $("#text").val()
            drawMap(inputText);
        }
    })

    var nodeWidth = 100;
    var nodeHeight = 50;
    var xMargin = 50;
    var yMargin = 20;

    function drawMap(text)
    {
        $("#map").empty();

        console.log(text)
        nodeArray = parseText(text);
        nodeArray = decideNodePosition(nodeArray);
        console.log(nodeArray);

        for(var i = 0; i < nodeArray.length; i++)
        {
            drawSingleNode(nodeArray[i].text, nodeArray[i].x, nodeArray[i].y)
            var children = nodeArray[i].children;
            for(var j = 0; j < children.length; j++)
            {
                connectNodes(nodeArray[i], children[j]);                
            }
        }
    }

    function decideNodePosition(nodeArray)
    {
        var leafCounter = 0;

        for(var i = 0; i < nodeArray.length; i++)
        {
            var level = nodeArray[i].level;
            nodeArray[i]["x"] = level*(nodeWidth + xMargin)

            if(nodeArray[i].children.length == 0)
            {
                nodeArray[i]["y"] = leafCounter*(nodeHeight + yMargin)
                leafCounter++;
            }
        }

        for(var i = nodeArray.length - 1; i >= 0; i--)
        {
            if(nodeArray[i].children.length > 0)
            {
                var y = 0;
                var middleNodeID = Math.floor(nodeArray[i].children.length/2);

                if(nodeArray[i].children.length % 2 == 1)
                {
                    y = nodeArray[i].children[middleNodeID].y;
                }
                else
                {
                    var y1 = nodeArray[i].children[middleNodeID].y;
                    var y2 = nodeArray[i].children[middleNodeID - 1].y;
                    y = (y1 + y2)/2;
                }

                nodeArray[i]["y"] = y;
            }
        }

        return nodeArray
    }

    function drawSingleNode(text, x, y)
    {
        var rectElement = document.createElementNS(svgNS, "rect");
        rectElement.setAttribute("width", nodeWidth);
        rectElement.setAttribute("height", nodeHeight);
        rectElement.setAttribute("x", x);
        rectElement.setAttribute("y", y);
        rectElement.setAttribute("fill", "white");
        rectElement.setAttribute("stroke", "black");
        var foreignElement = document.createElementNS(svgNS, "foreignObject");
        foreignElement.setAttribute("width", nodeWidth);
        foreignElement.setAttribute("height", nodeHeight); 
        foreignElement.setAttribute("x", x);
        foreignElement.setAttribute("y", y);
        var innerElement = document.createElementNS(htmlNS, "div");
        innerElement.innerText = text;
        innerElement.style["padding"] = "0 5 0 5";

        document.getElementById("map").appendChild(rectElement);
        document.getElementById("map").appendChild(foreignElement);
        foreignElement.appendChild(innerElement);
    }

    function connectNodes(fromNode, toNode)
    {
        var lineElement = document.createElementNS(svgNS, "line");
        lineElement.setAttribute("x1", fromNode.x + nodeWidth);
        lineElement.setAttribute("y1", fromNode.y + nodeHeight/2);
        lineElement.setAttribute("x2", toNode.x);
        lineElement.setAttribute("y2", toNode.y + nodeHeight/2);
        lineElement.setAttribute("stroke", "black");

        document.getElementById("map").appendChild(lineElement);
    }


    function parseText(text)
    {
        textArray = text.split("\n")
        mapObject = new Object();
        nodeArray = [];

        levelArray = [];
        levelArray[0] = null;

        for(var i = 0; i < textArray.length; i++)
        {
            var level = 0;
            var text = textArray[i];

            for(var j = 0; j < text.length; j++)
            {
                if(text.startsWith("*"))
                {
                    text = text.substring(1);
                    level++;
                }
                else
                {
                    break;
                }
            }

            text = text.trim();

            var node = {id: i.toString(), text: text, level: level, parent: levelArray[level]}
            nodeArray.push(node)
            levelArray[level + 1] = node;
        }
        
        childrenMap = {};

        for(var i = nodeArray.length - 1; i >= 0; i--)
        {
            if(nodeArray[i].id in childrenMap)
            {
                nodeArray[i]["children"] = childrenMap[nodeArray[i].id];
            }
            else
            {
                nodeArray[i]["children"] = [];                
            }

            if(nodeArray[i].parent != null)
            {
                if(!(nodeArray[i].parent.id in childrenMap))
                {
                    childrenMap[nodeArray[i].parent.id] = []
                }

                childrenMap[nodeArray[i].parent.id].push(nodeArray[i]);
            }
        }

        return nodeArray;
    }

    </script>

  </body>


</html>