---
# System prepended metadata

title: '製作連結圖_Part 4: 可互動網頁製作'
tags: [教案]

---

# Part 4: 可互動網頁製作
時長: 2 hr

## 網頁基礎(知識系列: 30min)
- 一個完整的網頁包含兩個部分，前端與後端
    - 前端: 網頁外觀的建立
    - 後端: 存放資料與各式功能的區域
- 而前後端相連才可以形成一個可互動的網頁
- **今天的目標: 透過使用者在前端輸入資料進後端，並使用資料跑凸顯現在前端上**
## HTML （HyperText Markup Language，超文本標記語言）基礎介紹
- 前端的架構 (想要做複雜的風格可以繼續學CSS喔!)
- 以 tag 來標記物件的屬性

[基礎語言查詢](http://jinjin.mepopedia.com/~jinjin/webdesign-notes/html.html)
[Reference](https://developer.mozilla.org/zh-TW/docs/Web/HTML)
## 認識 Python Flask 套件
Python Flask 是一種輕量級的網頁框架，只要五行程式碼，就可以架設網頁伺服器

- Flask 的前端: index.html
- Flask 的後端: app.py


[Reference](https://devs.tw/post/448)
[Official Document](https://flask.palletsprojects.com/en/3.0.x/)

## 成果
▼ **網頁前端**

![image](https://hackmd.io/_uploads/S1Ivye4k0.png)

▼ **index.html**
```
<!DOCTYPE html>
<html>
<head>
    <title>填寫資料</title>
</head>
<body>
    <h1>填寫資料</h1>
    <form method="post" action="/submit">
        <label for="name">姓名:</label><br>
        <input type="text" id="name" name="name"><br>
        <label for="interest">興趣:</label><br>
        <input type="text" id="interest" name="interest"><br>
        <input type="submit" value="提交">
    </form>
    
    <div id="network-graph">
        <img id="graph-image" src="static/placeholder.png" alt="Network Graph">
    </div>
    
    <!-- 加载并实时显示 persona.csv 文件的内容 -->
    <h2>Persona 資料:</h2>
    <table id="persona_table" border="1">
        <thead>
            <tr>
                <th>姓名</th>
                <th>興趣</th>
            </tr>
        </thead>
        <tbody>
            <!-- 这里将用 JavaScript 动态添加 persona 数据 -->
        </tbody>
    </table>
    

    <script>
        // 使用 AJAX 请求获取 persona 数据
        window.onload = function() {
            fetchPersonaData();
            updateNetworkGraph(); // 在页面加载时调用生成连接图函数
            document.getElementById('submit-form').addEventListener('submit', function(event) {
                event.preventDefault(); // 阻止表单的默认提交行为
                const formData = new FormData(event.target);
                fetch('/submit', {
                    method: 'POST',
                    body: formData
                })
                .then(response => response.json())
                .then(data => {
                    // 在消息区域显示提交成功消息
                    document.getElementById('message').innerText = data.message;
                    // 清空表格数据并重新加载 persona 数据
                    fetchPersonaData();
                })
                .catch(error => console.error('Error submitting form:', error));
            });
        };
    
        function fetchPersonaData() {
            fetch('/get_persona_data')
            .then(response => response.json())
            .then(data => {
                const tableBody = document.querySelector('#persona_table tbody');
                tableBody.innerHTML = ''; // 清空表格数据
                data.forEach(row => {
                    const tr = document.createElement('tr');
                    tr.innerHTML = `<td>${row['id']}</td><td>${row['標籤']}</td>`; // 使用正确的字段名
                    tableBody.appendChild(tr);
                });
            })
            .catch(error => console.error('Error fetching persona data:', error));
        }
        
        function updateNetworkGraph() {
            fetch('/generate_network_graph', {
                method: 'POST'
            })
            .then(response => response.json())
            .then(data => {
                const graphImage = document.getElementById('graph-image');
                graphImage.src = 'data:image/png;base64,' + data.graph_data; // 使用 Base64 编码的图像数据
            })
            .catch(error => console.error('Error updating network graph:', error));
        }
                
    </script>
    
</body>
</html>
```

▼ **app.py**
```
from flask import Flask, render_template, request, jsonify, redirect, url_for
import csv
import pandas as pd
from collections import defaultdict
import networkx as nx
import matplotlib.pyplot as plt
from community import community_louvain
import io
import base64

app = Flask(__name__)

@app.route('/')
def index():
    return render_template('index.html')

@app.route('/submit', methods=['POST'])
def submit():
    if request.method == 'POST':
        data = request.form.to_dict()
        with open('persona.csv', 'a', newline='', encoding='utf-8') as csvfile:
            writer = csv.DictWriter(csvfile, fieldnames=data.keys())
            if csvfile.tell() == 0:
                writer.writeheader()
            writer.writerow(data)
        # 返回 JSON 响应以在前端显示成功消息
        return redirect(url_for('index'))


@app.route('/get_persona_data', methods=['GET'])
def get_persona_data():
    persona_data = []
    with open('persona.csv', 'r', encoding='utf-8') as csvfile:
        csvreader = csv.DictReader(csvfile)
        for row in csvreader:
            persona_data.append(row)
    print("Persona data:", persona_data)  # 在终端输出以检查数据
    return jsonify(persona_data)



@app.route('/generate_network_graph', methods=['POST'])
def generate_network_graph():
    # Load persona CSV file
    df = pd.read_csv('persona.csv')
    df['標籤'] = df['標籤'].str.replace('、', ',').str.split(',')
    
    # Create mappings
    keyword_to_ids = defaultdict(list)
    id_to_keywords = defaultdict(list)

    for index, row in df.iterrows():
        id = row['id']
        id_to_keywords[id].append
        keywords = [keyword.strip() for keyword in row['標籤']]
        for keyword in keywords:
            keyword_to_ids[keyword].append(id)

    # Load index CSV file
    dic_df = pd.read_csv("index2.csv")
    key_to_index = defaultdict(list)

    for index, row in dic_df.iterrows():
        key = row['key']
        index_val = row['index']
        key_to_index[key].append(index_val)

    # Create network graph
    T = nx.Graph()

    # Add nodes
    for id in id_to_keywords:
        T.add_node(id, type='id')
    for keyword in keyword_to_ids:
        T.add_node(keyword, type='keyword')
    for index in key_to_index:
        T.add_node(index, type="index")

    # Add edges
    for keyword, ids in keyword_to_ids.items():
        for id in ids:
            T.add_edge(id, keyword)

    for key, index in key_to_index.items():
        for index in index:
            T.add_edge(index, key)

    # Apply community detection
    partition = community_louvain.best_partition(T)

    # Draw graph
    community_colors = {node: partition[node] for node in T.nodes()}
    values = [community_colors[node] for node in T.nodes()]
    node_sizes = [100 * T.degree(node) for node in T.nodes()]
    pos = nx.spring_layout(T, k=1.5, iterations=500)

    import matplotlib
    matplotlib.use('Agg')

    plt.figure(figsize=(8, 6))
    nx.draw_networkx_edges(T, pos, alpha=0.5)
    nx.draw_networkx_nodes(T, pos, node_color=values, node_size=node_sizes, cmap=plt.cm.jet)
    nx.draw_networkx_labels(T, pos, font_size=10, font_family='SimSun')
    plt.axis('off')
    
    # Save the generated graph to a file
    img_data = io.BytesIO()
    plt.savefig(img_data, format='png')
    img_data.seek(0)
    img_base64 = base64.b64encode(img_data.getvalue()).decode('utf-8')
    
    # Return the Base64 encoded image string
    return jsonify({"graph_data": img_base64})

if __name__ == '__main__':
    app.run(debug=True)
```