# 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) ```