{"id":14178,"date":"2024-07-28T13:04:27","date_gmt":"2024-07-28T11:04:27","guid":{"rendered":"https:\/\/pythonprogramming.altervista.org\/?p=14178"},"modified":"2024-07-28T13:04:27","modified_gmt":"2024-07-28T11:04:27","slug":"interactive-mind-map-with-flask","status":"publish","type":"post","link":"https:\/\/pythonprogramming.altervista.org\/interactive-mind-map-with-flask\/","title":{"rendered":"Interactive mind map with Flask"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Let&#8217;s use Python with Flask for the web server and create a more dynamic mind map with the features you&#8217;ve requested. We&#8217;ll use JSON to represent the nodes, allow creating new nodes with double-clicks, and add a save functionality.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Here&#8217;s the implementation:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This Python Flask application creates an interactive mind map with the following features:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Dynamic Node Creation: Double-click anywhere on the mind map to create a new node.<\/li>\n\n\n\n<li>Editable Nodes: Click on a node&#8217;s title or subtitle to edit its content.<\/li>\n\n\n\n<li>Draggable Nodes: Click and drag nodes to reposition them.<\/li>\n\n\n\n<li>Add Connections: Use the &#8220;Add Connection&#8221; button to create connections between nodes.<\/li>\n\n\n\n<li>Save Functionality: The &#8220;Save Mind Map&#8221; button saves the current state to the server and downloads a JSON file.<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">To run this application:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Save the Python code in a file named <code>app.py<\/code>.<\/li>\n\n\n\n<li>Create a <code>templates<\/code> folder in the same directory and save the HTML content in <code>templates\/index.html<\/code>.<\/li>\n\n\n\n<li>Install Flask if you haven&#8217;t already: <code>pip install flask<\/code><\/li>\n\n\n\n<li>Run the application: <code>python app.py<\/code><\/li>\n\n\n\n<li>Open a web browser and go to <code>http:\/\/localhost:5000<\/code><\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">The mind map data is stored in a JSON format, making it easy to save and load. The initial data is defined in the <code>mind_map_data<\/code> variable in the Python code.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">You can customize the initial mind map data by modifying the <code>mind_map_data<\/code> variable in the Python code.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This implementation should provide a more flexible and dynamic mind mapping experience, allowing you to easily create, edit, and save your mind maps.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">index.html<\/h2>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\"># templates\/index.html\n&lt;!DOCTYPE html>\n&lt;html lang=\"en\">\n&lt;head>\n    &lt;meta charset=\"UTF-8\">\n    &lt;meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    &lt;title>Interactive Mind Map&lt;\/title>\n    &lt;link href=\"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/bootstrap\/5.3.0\/css\/bootstrap.min.css\" rel=\"stylesheet\">\n    &lt;style>\n        #mind-map {\n            position: relative;\n            height: 600px;\n            border: 1px solid #ddd;\n            overflow: hidden;\n        }\n        .node {\n            position: absolute;\n            border: 2px solid #007bff;\n            border-radius: 10px;\n            padding: 10px;\n            background-color: #f8f9fa;\n            cursor: move;\n            user-select: none;\n        }\n        #connection-canvas {\n            position: absolute;\n            top: 0;\n            left: 0;\n            width: 100%;\n            height: 100%;\n            pointer-events: none;\n        }\n    &lt;\/style>\n&lt;\/head>\n&lt;body>\n    &lt;div class=\"container mt-5\">\n        &lt;h1 class=\"text-center mb-5\">Interactive Mind Map&lt;\/h1>\n        &lt;div class=\"row justify-content-center\">\n            &lt;div class=\"col-md-12\">\n                &lt;div id=\"mind-map\">\n                    &lt;canvas id=\"connection-canvas\">&lt;\/canvas>\n                &lt;\/div>\n            &lt;\/div>\n        &lt;\/div>\n        &lt;div class=\"row mt-3\">\n            &lt;div class=\"col-md-12 text-center\">\n                &lt;button id=\"add-connection\" class=\"btn btn-primary\">Add Connection&lt;\/button>\n                &lt;button id=\"save-mind-map\" class=\"btn btn-success\">Save Mind Map&lt;\/button>\n            &lt;\/div>\n        &lt;\/div>\n    &lt;\/div>\n\n    &lt;script>\n        const mindMap = document.getElementById('mind-map');\n        const canvas = document.getElementById('connection-canvas');\n        const ctx = canvas.getContext('2d');\n        let nodes = [];\n        let connections = [];\n\n        function initializeMindMap(data) {\n            nodes = data.nodes;\n            connections = data.connections;\n            renderNodes();\n            drawConnections();\n        }\n\n        function renderNodes() {\n            mindMap.querySelectorAll('.node').forEach(node => node.remove());\n            nodes.forEach(node => {\n                const nodeElement = document.createElement('div');\n                nodeElement.className = 'node';\n                nodeElement.id = node.id;\n                nodeElement.innerHTML = `\n                    &lt;h4 contenteditable=\"true\">${node.title}&lt;\/h4>\n                    &lt;p contenteditable=\"true\">${node.subtitle}&lt;\/p>\n                `;\n                nodeElement.style.left = `${node.x}%`;\n                nodeElement.style.top = `${node.y}px`;\n                mindMap.appendChild(nodeElement);\n                makeNodeDraggable(nodeElement);\n            });\n        }\n\n        function makeNodeDraggable(node) {\n            let isDragging = false;\n            let offsetX, offsetY;\n\n            node.addEventListener('mousedown', function(e) {\n                isDragging = true;\n                offsetX = e.clientX - node.offsetLeft;\n                offsetY = e.clientY - node.offsetTop;\n            });\n\n            document.addEventListener('mousemove', function(e) {\n                if (isDragging) {\n                    const x = e.clientX - offsetX;\n                    const y = e.clientY - offsetY;\n                    node.style.left = x + 'px';\n                    node.style.top = y + 'px';\n                    updateNodePosition(node.id, x, y);\n                    drawConnections();\n                }\n            });\n\n            document.addEventListener('mouseup', function() {\n                isDragging = false;\n            });\n        }\n\n        function updateNodePosition(id, x, y) {\n            const node = nodes.find(n => n.id === id);\n            if (node) {\n                node.x = (x \/ mindMap.offsetWidth) * 100;\n                node.y = y;\n            }\n        }\n\n        function drawConnections() {\n            ctx.clearRect(0, 0, canvas.width, canvas.height);\n            connections.forEach(conn => {\n                const sourceNode = document.getElementById(conn.source);\n                const targetNode = document.getElementById(conn.target);\n                if (sourceNode &amp;&amp; targetNode) {\n                    ctx.beginPath();\n                    ctx.moveTo(sourceNode.offsetLeft + sourceNode.offsetWidth \/ 2, sourceNode.offsetTop + sourceNode.offsetHeight \/ 2);\n                    ctx.lineTo(targetNode.offsetLeft + targetNode.offsetWidth \/ 2, targetNode.offsetTop + targetNode.offsetHeight \/ 2);\n                    ctx.strokeStyle = '#007bff';\n                    ctx.lineWidth = 2;\n                    ctx.stroke();\n                }\n            });\n        }\n\n        function resizeCanvas() {\n            canvas.width = mindMap.offsetWidth;\n            canvas.height = mindMap.offsetHeight;\n            drawConnections();\n        }\n\n        mindMap.addEventListener('dblclick', function(e) {\n            if (e.target === mindMap) {\n                const x = (e.offsetX \/ mindMap.offsetWidth) * 100;\n                const y = e.offsetY;\n                const id = 'node-' + Date.now();\n                nodes.push({id: id, title: 'New Node', subtitle: 'Click to edit', x: x, y: y});\n                renderNodes();\n                drawConnections();\n            }\n        });\n\n        document.getElementById('add-connection').addEventListener('click', function() {\n            const source = prompt(\"Enter the ID of the source node:\");\n            const target = prompt(\"Enter the ID of the target node:\");\n            if (source &amp;&amp; target) {\n                connections.push({source, target});\n                drawConnections();\n            }\n        });\n\n        document.getElementById('save-mind-map').addEventListener('click', function() {\n            const data = {nodes, connections};\n            fetch('\/save', {\n                method: 'POST',\n                headers: {\n                    'Content-Type': 'application\/json',\n                },\n                body: JSON.stringify(data),\n            })\n            .then(response => response.json())\n            .then(data => {\n                alert('Mind map saved successfully!');\n                const dataStr = \"data:text\/json;charset=utf-8,\" + encodeURIComponent(JSON.stringify(data));\n                const downloadAnchorNode = document.createElement('a');\n                downloadAnchorNode.setAttribute(\"href\", dataStr);\n                downloadAnchorNode.setAttribute(\"download\", \"mind_map.json\");\n                document.body.appendChild(downloadAnchorNode);\n                downloadAnchorNode.click();\n                downloadAnchorNode.remove();\n            })\n            .catch((error) => {\n                console.error('Error:', error);\n                alert('Failed to save mind map');\n            });\n        });\n\n        window.addEventListener('resize', resizeCanvas);\n        resizeCanvas();\n\n        \/\/ Initialize with data from server\n        initializeMindMap({{ mind_map_data|safe }});\n    &lt;\/script>\n&lt;\/body>\n&lt;\/html><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">app.py<\/h2>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\"># app.py\nfrom flask import Flask, render_template, request, jsonify\nimport json\n\napp = Flask(__name__)\n\n# Initial mind map data\nmind_map_data = {\n    \"nodes\": [\n        {\"id\": \"balance-sheet\", \"title\": \"Balance Sheet\", \"subtitle\": \"\", \"x\": 40, \"y\": 10},\n        {\"id\": \"purpose\", \"title\": \"What is it for?\", \"subtitle\": \"To show economic and financial data\", \"x\": 20, \"y\": 150},\n        {\"id\": \"importance\", \"title\": \"Importance\", \"subtitle\": \"Very important document\", \"x\": 60, \"y\": 150},\n        {\"id\": \"related-docs\", \"title\": \"Related Documents\", \"subtitle\": \"Stato Patrimoniale, Conto economico\", \"x\": 40, \"y\": 300}\n    ],\n    \"connections\": [\n        {\"source\": \"balance-sheet\", \"target\": \"purpose\"},\n        {\"source\": \"balance-sheet\", \"target\": \"importance\"},\n        {\"source\": \"balance-sheet\", \"target\": \"related-docs\"}\n    ]\n}\n\n@app.route('\/')\ndef index():\n    return render_template('index.html', mind_map_data=json.dumps(mind_map_data))\n\n@app.route('\/save', methods=['POST'])\ndef save():\n    global mind_map_data\n    mind_map_data = request.json\n    return jsonify({\"status\": \"success\"})\n\n@app.route('\/get_mind_map')\ndef get_mind_map():\n    return jsonify(mind_map_data)\n\nif __name__ == '__main__':\n    app.run(debug=True)\n\n<\/pre>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/pythonprogramming.altervista.org\/wp-content\/uploads\/2024\/07\/image-23.png\"><img loading=\"lazy\" decoding=\"async\" width=\"960\" height=\"744\" src=\"https:\/\/pythonprogramming.altervista.org\/wp-content\/uploads\/2024\/07\/image-23-960x744.png\" alt=\"\" class=\"wp-image-14179\" srcset=\"https:\/\/pythonprogramming.altervista.org\/wp-content\/uploads\/2024\/07\/image-23-960x744.png 960w, https:\/\/pythonprogramming.altervista.org\/wp-content\/uploads\/2024\/07\/image-23-320x248.png 320w, https:\/\/pythonprogramming.altervista.org\/wp-content\/uploads\/2024\/07\/image-23-768x595.png 768w, https:\/\/pythonprogramming.altervista.org\/wp-content\/uploads\/2024\/07\/image-23.png 1131w\" sizes=\"auto, (max-width: 960px) 100vw, 960px\" \/><\/a><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n","protected":false},"excerpt":{"rendered":"Let&#8217;s use Python with Flask for the web server and create a more dynamic mind map with the features you&#8217;ve requested. We&#8217;ll use \n<a class=\"moretag\" href=\"https:\/\/pythonprogramming.altervista.org\/interactive-mind-map-with-flask\/\"> [...]<\/a>","protected":false},"author":1,"featured_media":14179,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_crdt_document":"","footnotes":""},"categories":[1],"tags":[],"class_list":["post-14178","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-examples"],"avopt_banners_inside_post":true,"avopt_banners_on_page":true,"av_copy_from":"","av_sharing_message":"","av_sharing_allowed":true,"av_sharing_on":{"fb":[],"tw":[]},"av_allow_affiliate_banner":false,"av_allow_affiliate_multi_banner":false,"av_show_affiliation_buy_button":false,"av_post_rating":true,"av_have_post_rating_value":false,"av_is_artificial_intelligence_content":false,"_links":{"self":[{"href":"https:\/\/pythonprogramming.altervista.org\/wp-json\/wp\/v2\/posts\/14178","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/pythonprogramming.altervista.org\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/pythonprogramming.altervista.org\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/pythonprogramming.altervista.org\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/pythonprogramming.altervista.org\/wp-json\/wp\/v2\/comments?post=14178"}],"version-history":[{"count":1,"href":"https:\/\/pythonprogramming.altervista.org\/wp-json\/wp\/v2\/posts\/14178\/revisions"}],"predecessor-version":[{"id":14180,"href":"https:\/\/pythonprogramming.altervista.org\/wp-json\/wp\/v2\/posts\/14178\/revisions\/14180"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/pythonprogramming.altervista.org\/wp-json\/wp\/v2\/media\/14179"}],"wp:attachment":[{"href":"https:\/\/pythonprogramming.altervista.org\/wp-json\/wp\/v2\/media?parent=14178"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/pythonprogramming.altervista.org\/wp-json\/wp\/v2\/categories?post=14178"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/pythonprogramming.altervista.org\/wp-json\/wp\/v2\/tags?post=14178"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}