# 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/)
## 成果
▼ **網頁前端**

▼ **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)
```