# **EZdrawing互動式繪圖網站製作**
<div class="name">松山高中 朱禹澄 賴致全</div>
<style>
.name{
text-align:right;
}
</style>

附圖為本次專案的心智圖<br>右側說明開發網站的過程,從HTML撰寫前端使用者介面、CSS美編與排版設計,到後端繪圖與動畫生成函式庫編寫。最後使用Flask API網頁框架設計相應路由,串通使用者端和數據端的溝通。<br>左側則是部署網站的過程,從使用Replit作為編撰與修改程式的整合開發環境(IDE),並將資料作為檔案存放在Github上。最後,透過render.com提供的網路服務,獲得一個公開的連結與IP位置,讓所有人都能夠拜訪並體驗網站內的功能。
[toc]
<p>
注:以下提供之程式碼皆為本人親自撰寫
</p>
# *Flask API主程序app.py檔*
## 1.載入模組
```python=
#載入模組
from flask import Flask
from flask import render_template
from flask import request
import numpy as np
from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
from matplotlib.figure import Figure
import io
import base64
#載入繪圖函式庫
from ellipsegif import main as ellipsegif
from hypergif import main as hypergif
from paragif import main as paragif
```
## 2.建立application物件
```python=
app = Flask(
__name__,
static_folder="static",
static_url_path="/static"
) #所有在static資料夾下的檔案,都對應到網址路徑/static/
```
## 3-1 建立初始路徑("/")對應的處理函式
```pyhton=
@app.route('/')
def hello(): #用來回應路徑 / 的處理函式
return render_template('index1.html')
```
## 3-2 運行測試(顯示前端使用者資訊)
```python=
@app.route('/info')
def info():
print("請求方法", request.method)
print("通訊協定", request.scheme)
print("主機名稱", request.host)
print("路徑", request.path)
print("網址", request.url)
print("瀏覽器和作業系統", request.headers.get("user-agent"))
print("語言偏好", request.headers.get("accept-language"))
print("引薦網址", request.headers.get("referrer"))
return None
```
## 4.啟動網站伺服器
```python=
if __name__ == '__main__':
app.run(debug=True, port=3000, host='0.0.0.0')
```
## 5.建立網站功能(靜態圖片繪圖)對應的路由
#### 1.接收前端html form的字串並轉換成可計算的整數型式<br>2.在路由內呼叫繪圖函式繪圖 <br>3.使用try塊避免ValueError導制崩潰<br> 4.將圖片轉為二進制後利用base64編碼,使得前端HTML可以成功找到圖片並顯示
### 5-1橢圓
```python=
@app.route("/calculate_elipse")
def calculate_elipse():
try:
a2 = int(request.args.get("a2", 0))
b2 = int(request.args.get("b2", 0))
h = int(request.args.get("h", 0))
k = int(request.args.get("k", 0))
print("elipse running")
image_data = drawelipse(a2,b2,h,k)
encoded_image = base64.b64encode(image_data).decode('utf-8')
return render_template('result.html',
file_name=f"data:image/png;base64,{encoded_image}",
title_name='ellipse plot' )
# return Response(image_data, mimetype='image/png')
except ValueError:
return "Error: Please enter valid integer values for a2, b2, h, and k."
```
### 5-2拋物線<br>
#### 注: 拋物線需考慮上下或左右開口的型式,故有兩個分的路由
```python=
@app.route("/calculate_parabola1")
def calculate_parabola1():
try:
p = int(request.args.get("p"))
h = int(request.args.get("h", 0))
k = int(request.args.get("k", 0))
print("parabola1 running")
image_data = drawparabola1(p, h, k)
encoded_image = base64.b64encode(image_data).decode('utf-8')
return render_template('result.html',file_name=f"data:image/png;base64,{encoded_image}",title_name='parabola plot' )
except ValueError:
return "Error: Please enter valid integer values for 4c, h, and k."
@app.route("/calculate_parabola2")
def calculate_parabola2():
try:
p = int(request.args.get("p"))
h = int(request.args.get("h", 0))
k = int(request.args.get("k", 0))
print("parabola2 running")
image_data = drawparabola2(p, h, k)
encoded_image = base64.b64encode(image_data).decode('utf-8')
return render_template('result.html',file_name=f"data:image/png;base64,{encoded_image}",title_name='parabola plot' )
except ValueError:
return "Error: Please enter valid integer values for 4c, h, and k."
```
### 5-3雙曲線
#### 注: 雙曲線需考慮上下或左右開口的型式,故有兩個分別的路由
```python=
def calculate_hyperbola1():
try:
a2 = int(request.args.get("a2"))
b2 = int(request.args.get("b2"))
h = int(request.args.get("h", 0))
k = int(request.args.get("k", 0))
print("hyperbola1 running")
image_data = drawhyperbola1(a2,b2,h,k)
encoded_image = base64.b64encode(image_data).decode('utf-8')
return render_template('result.html',file_name=f"data:image/png;base64,{encoded_image}",title_name='hyperbola pair plot' )
except ValueError:
return "Error: Please enter valid integer values for a2, b2, h, and k."
@app.route("/calculate_hyperbola2")
def calculate_hyperbola2():
try:
b2 = int(request.args.get("b2"))
a2 = int(request.args.get("a2"))
h = int(request.args.get("h", 0))
k = int(request.args.get("k", 0))
print("hyperbola2 running")
image_data = drawhyperbola2(a2,b2,h,k)
encoded_image = base64.b64encode(image_data).decode('utf-8')
return render_template('result.html',file_name=f"data:image/png;base64,{encoded_image}",title_name='hyperbola pair plot' )
except ValueError:
return "Error: Please enter valid integer values for a2, b2, h, and k."
```
## 6.建立網站功能(動態圖片繪圖)對應的路由
### 6-1拋物線:與座標平面上一圓與一直線(鉛直或水平線)的第二圓圓心軌跡
```python=
@app.route("/paragif")
def calculate_paragif():
try:
x_or_y = request.args.get("x_or_y")
m = int(request.args.get("m", 0))
c_0_x = int(request.args.get("c_0_x", 0))
c_0_y = int(request.args.get("c_0_y", 0))
c_0_r = int(request.args.get("c_0_r", 0))
print("para gif running")
paragif(x_or_y,m,c_0_x,c_0_y,c_0_r)
return render_template('result.html', file_name="/static/gif/parabola.gif", title_name="parabola Gif")
except ValueError:
return "Error: Please enter valid integer values for a2, b2, h, and k."
```
### 6-2橢圓:與座標平面上兩圓(大圓包小圓)均相切的第三圓圓心軌跡
```python=
@app.route("/ellipsegif")
def calculate_elipsegif():
try:
x_b = int(request.args.get("x_b", 0))
y_b = int(request.args.get("y_b", 0))
r_b = int(request.args.get("r_b", 0))
x_s = int(request.args.get("x_s", 0))
y_s = int(request.args.get("y_s", 0))
r_s = int(request.args.get("r_s", 0))
print("elipsegif running")
ellipsegif(x_b,y_b,r_b,x_s,y_s,r_s)
return render_template('result.html',file_name="/static/gif/oval.gif",title_name="ellipse Gif")
# return Response(image_data, mimetype='image/png')
except ValueError:
return "Error: Please enter valid integer values for a2, b2, h, and k."
```
### 6-3雙曲線:與座標平面上兩不相交圓均內切或均外切的第三圓圓心軌跡
```python=
@app.route("/hypergif")
def calculate_hypergif():
try:
x_b = int(request.args.get("x_b", 0))
y_b = int(request.args.get("y_b", 0))
r_b = int(request.args.get("r_b", 0))
x_s = int(request.args.get("x_s", 0))
y_s = int(request.args.get("y_s", 0))
r_s = int(request.args.get("r_s", 0))
print("hyper gif running")
hypergif(x_b,y_b,r_b,x_s,y_s,r_s)
return render_template('result2.html',file_name1="/static/gif/hyperbolic_inner.gif",file_name2="/static/gif/hyperbolic_outer.gif", title_name1="hyperbola pair Gif 與兩圓均內切", title_name2="hyperbola pair Gif 與兩圓均外切")
except (ValueError, ZeroDivisionError):
return "Error: Please enter valid integer values for a2, b2, h, and k."
```
# *繪圖函式庫*
## 1.靜態圖片
### 1-1左右開口拋物線
#### $$(y-k)^2=4c(x-h)$$
```python=
def drawparabola2(p, h, k):
# 計算焦點的位置
c = p / 4
f = (h + c, k)
# 生成拋物線的 y 坐標
if c > 0:
y = np.linspace(-(5 * c) - 5, 5 *c + 5)
else:
y = np.linspace((5 * c) - 5, -(5 *c) + 5)
# 計算拋物線的 x 坐標
x = (y - k)**2 / (4 * c) + h
# 準線方程式
directrix = h - c
# 繪製拋物線
fig = Figure()
axis = fig.add_subplot(1, 1, 1)
axis.plot(x, y)
# 準線
axis.axvline(x=directrix, color='green', linestyle='--', label=f'Directrix: $x = {h-c}$')
# 標出焦點、中心
axis.scatter(h+c, k, color='red', label=f'Foci: ({h+c:.1f}, {k})')
axis.scatter(h, k, color='orange', label=f'Center: ({h}, {k})')
# 設定坐標軸的範圍
if c > 0:
axis.set_xlim(h - 4 * c - 10, h + 8 * c + 10) # 之後可能用 if statement 判定是要往左右取多一點
axis.set_ylim(-(5 * c) -5, 5 *c + 5)
else:
axis.set_xlim((h + 8 * c -10, h - 4 * c + 10))
axis.set_ylim((5 * c) -5, -(5 *c) + 5)
axis.set_title(f'Parabola: $x = ((y - {k})^2) / {p} + {h}$') # 標題
axis.set_xlabel('X-axis') # X 軸標籤
axis.set_ylabel('Y-axis') # Y 軸標籤
axis.grid(True) # 顯示網格線
if c < 0:
axis.legend(loc='upper center', bbox_to_anchor=(0.2, 0.55), prop={'size': 7}) # 將標籤放在中間上方
else:
axis.legend(loc='upper center', bbox_to_anchor=(0.8, 0.55), prop={'size': 7})
axis.set_aspect('equal', adjustable='box') # 設置坐標軸比例為相等
# 將圖像保存到 BytesIO 對象中
output = io.BytesIO()
FigureCanvas(fig).print_png(output)
return output.getvalue()
```
### 1-2上下開口拋物線
#### $$(x-h)^2=4c(y-k)$$
```python=
def drawparabola1(p, h, k):
# 計算焦點的位置
c = p / 4
f = (h, k + c)
# 生成拋物線的 x 坐標
if c > 0:
x = np.linspace(-(5 * c) -5, 5 *c + 5, 400)
else:
x = np.linspace(5 * c - 5, -(5 *c) + 5, 400)
# 計算拋物線的 y 坐標
y = ((x - h)**2) / (4 * c) + k
#準線方程式
directrix = k - c
# 繪製拋物線
fig = Figure()
axis = fig.add_subplot(1, 1, 1)
axis.plot(x,y)
# 準線
axis.axhline(y=directrix, color='green', linestyle='--', label=f'Directrix: $y = {k-c}$')
# 標出焦點、中心
axis.scatter(h, k+c, color='red', label=f'Foci: ({h}, {k+c:.1f})')
axis.scatter(h, k, color='orange', label=f'Center: ({h}, {k})')
# 設定坐標軸的範圍
if c > 0:
axis.set_ylim(k - 4 * c - 10, k + 8 * c + 10) #之後可能用if statment來判定是要往左上右下哪邊取多一點
axis.set_xlim(-(5 * c) -5, 5 *c + 5)
else:
axis.set_ylim((k + 8 * c- 10, k - 4 * c + 10))
axis.set_xlim(5 * c - 5, -(5 *c) + 5)
axis.set_title(f'Parabola: $y = (x-{h})^2/{p} + {k}$') # 標題
axis.set_xlabel('X-axis') # X 軸標籤
axis.set_ylabel('Y-axis') # Y 軸標籤
axis.grid(True) # 顯示網格線
if c < 0:
axis.legend(loc='upper center', bbox_to_anchor=(0.5, 0.95), prop={'size': 6}) # 將標籤放在中間上方
else:
axis.legend(loc='upper center', bbox_to_anchor=(0.5, 0.25), prop={'size': 6})
axis.set_aspect('equal', adjustable='box') # 設置坐標軸比例為相等# plt.gca().set_aspect('equal', adjustable='box') # 設置坐標軸比例為相等
# 將圖像保存到 BytesIO 對象中
output = io.BytesIO()
FigureCanvas(fig).print_png(output)
return output.getvalue()
```
### 2-1.橢圓
#### $$\frac{(x-h)^2}{a^2}+\frac{(y-k)^2}{b^2}=1$$
```python=
def drawelipse(a2, b2, h, k):
a = np.sqrt(a2)
b = np.sqrt(b2)
# 計算焦點的位置、C
if a > b:
c = np.sqrt(a**2 - b**2)
else:
c = np.sqrt(b**2 - a**2)
# 生成橢圓的角度值
theta = np.linspace(0, 2*np.pi, 100)
# 計算橢圓上的點的坐標
x = h + a * np.cos(theta) # 將 x 坐標移動到中心點
y = k + b * np.sin(theta) # 將 y 坐標移動到中心點
# 繪製橢圓
fig = Figure()
axis = fig.add_subplot(1, 1, 1)
axis.plot(x,y)
# 標出焦點、中心
if a > b:
axis.scatter([h+c, h-c], [k, k], color='red', label=f'Foci: ({h+c:.1f},{k}) and ({h-c:.1f},{k})')
else:
axis.scatter([h, h], [k+c, k-c], color='red', label=f'Foci: ({h},{k+c:.1f}) and ({h},{k-c:.1f})')
axis.scatter(h,k, color='orange', label=f'center: ({h}, {k})')
# 設置坐標軸的範圍
axis.set_xlim(h - a - 1,h + a + 1)
axis.set_ylim(k - b - 1,k + b + 1)
axis.set_aspect('equal', adjustable='box') # 設置坐標軸比例為相等
axis.set_title(f'Ellipse: $(x-{h})^2/{a**2:.0f} + (y-{k})^2/{b**2:.0f} = 1$') # 修改標題為橢圓的標準式
axis.set_xlabel('X-axis')
axis.set_ylabel('Y-axis')
axis.grid(True)
axis.legend(loc='upper right') # 將標籤放在右上角
# 將圖像保存到 BytesIO 對象中
output = io.BytesIO()
FigureCanvas(fig).print_png(output)
return output.getvalue()
```
### 3-1左右開口雙曲線
#### $$\frac{(x-h)^2}{a^2}-\frac{(y-k)^2}{b^2}=1$$
```python=
def drawhyperbola1(a2, b2, h, k):
a = np.sqrt(a2)
b = np.sqrt(b2)
print(a)
# 生成雙曲線的角度值
theta = np.linspace(-1*np.pi, 1*np.pi, 1000)
# 計算雙曲線上的點的坐標
x_right = h + a * np.cosh(theta)
y_right = k + b * np.sinh(theta)
x_left = h - a * np.cosh(theta)
y_left = k - b * np.sinh(theta)
# 計算焦點的位置
c = np.sqrt(a**2 + b**2)
f1 = (h + c, k)
f2 = (h - c, k)
# 繪製拋物線
fig = Figure(figsize=(8,7))
axis = fig.add_subplot(1, 1, 1)
axis.plot(x_right, y_right, color='blue', label="hyperbola pair")
axis.plot(x_left, y_left, color='blue')
# 繪製漸進線
x_asymptote = np.linspace(h - 100, h + 100, 100) # 漸進線的 x 坐標
y_asymptote1 = k + (b / a) * (x_asymptote - h) # 漸進線1的 y 坐標
y_asymptote2 = k - (b / a) * (x_asymptote - h) # 漸進線2的 y 坐標
axis.plot(x_asymptote, y_asymptote1, linestyle='--', color='green', label='Asymptote 1')
axis.plot(x_asymptote, y_asymptote2, linestyle='--', color='green', label='Asymptote 2')
# 繪製焦點
axis.scatter(f1[0], f1[1], color='red', label=f'Foci 1: ({f1[0]:.1f}, {f1[1]:.1f})')
axis.scatter(f2[0], f2[1], color='red', label=f'Foci 2: ({f2[0]:.1f}, {f2[1]:.1f})')
# 標出中心
axis.scatter(h, k, color='orange', label=f'Center: ({h}, {k})')
axis.set_xlabel('X-axis')
axis.set_ylabel('Y-axis')
#左右開口漸近線
def display_asymptotes(a2, b2, num1, num2):
if isinstance(a, int) or a.is_integer():
asymptote1_a = str(int(a))
else:
asymptote1_a = rf'$\sqrt{{{a2}}}$'
if isinstance(b, int) or b.is_integer():
asymptote1_b = str(int(b))
else:
asymptote1_b = rf'$\sqrt{{{b2}}}$'
asymptote2_a = asymptote1_a
asymptote2_b = asymptote1_b
# if not isinstance(a2, int):
# asymptote1_a = rf'√{a2}'
# asymptote2_a = rf'√{a2}'
# if not isinstance(b2, int):
# asymptote1_b = rf'√{b2}'
# asymptote2_b = rf'√{b2}'
asymptote1 = f'asymptote1: {asymptote1_a}x + {asymptote1_b}y = {num1:.1f}'
asymptote2 = f'asymptote2: {asymptote2_a}x - {asymptote2_b}y = {num2:.1f}'
return asymptote1, asymptote2
num1 = b * h + a * k
num2 = b * h - a * k
asymptote1, asymptote2 = display_asymptotes(a2, b2, num1, num2)
axis.set_title(rf'Hyperbola Pair: $(x-{h})^2/{a2}-(y-{k})^2/{b2})=1$'+'\n'
+asymptote1 + '\n' + asymptote2, fontsize=10)
axis.grid(True)
axis.legend(loc='upper center', bbox_to_anchor=(0.5, 0.35), prop={'size': 7})
axis.set_aspect('equal', adjustable='box') # 設置坐標軸比例為相等
# 設定坐標軸的範圍
axis.set_xlim(h - 4 * a - 10 , h + 4 * a + 10)
axis.set_ylim(k - 4 * b - 10 , k + 4 * b + 10)
# 將圖像保存到 BytesIO 對象中
output = io.BytesIO()
FigureCanvas(fig).print_png(output)
return output.getvalue()
```
### 3-2上下開口雙曲線
#### $$\frac{(x-h)^2}{a^2}-\frac{(y-k)^2}{b^2}=-1$$
```python=
def drawhyperbola2(a2, b2, h, k):
a = np.sqrt(a2)
b = np.sqrt(b2)
# 生成雙曲線的角度值
theta = np.linspace(-1*np.pi, 1*np.pi, 1000)
# 計算雙曲線上的點的坐標
x_right = h + b * np.sinh(theta)
y_right = k + a * np.cosh(theta)
x_left = h - b * np.sinh(theta)
y_left = k - a * np.cosh(theta)
# 計算焦點的位置
c = np.sqrt(a**2 + b**2)
f1 = (h, k + c)
f2 = (h, k - c)
# 繪製雙曲線
fig = Figure(figsize=(8,7))
axis = fig.add_subplot(1, 1, 1)
axis.plot(x_right, y_right, color='blue', label="hyperbola pair")
axis.plot(x_left, y_left, color='blue')
# 繪製漸進線
x_asymptote = np.linspace(h - 100, h + 100, 100) # 漸進線的 x 坐標
y_asymptote1 = k + (a / b) * (x_asymptote - h) # 漸進線1的 y 坐標
y_asymptote2 = k - (a / b) * (x_asymptote - h) # 漸進線2的 y 坐標
axis.plot(x_asymptote, y_asymptote1, linestyle='--', color='green', label='Asymptote 1')
axis.plot(x_asymptote, y_asymptote2, linestyle='--', color='green', label='Asymptote 2')
# 繪製焦點
axis.scatter(f1[0], f1[1], color='red', label=f'Foci 1: ({f1[0]:.1f}, {f1[1]:.1f})')
axis.scatter(f2[0], f2[1], color='red', label=f'Foci 2: ({f2[0]:.1f}, {f2[1]:.1f})')
# 標出中心
axis.scatter(h, k, color='orange', label=f'Center: ({h}, {k})')
axis.set_xlabel('X-axis')
axis.set_ylabel('Y-axis')
# 設置標題(上下開口漸近線)
def display_asymptotes(a2, b2, num1, num2):
if isinstance(a, int) or a.is_integer():
asymptote1_a = str(int(a))
else:
asymptote1_a = rf'$\sqrt{{{a2}}}$'
if isinstance(b, int) or b.is_integer():
asymptote1_b = str(int(b))
else:
asymptote1_b = rf'$\sqrt{{{b2}}}$'
asymptote2_a = asymptote1_a
asymptote2_b = asymptote1_b
asymptote1 = f'asymptote1: {asymptote1_a}x + {asymptote1_b}y = {num1:.1f}'
asymptote2 = f'asymptote2: {asymptote2_a}x - {asymptote2_b}y = {num2:.1f}'
return asymptote1, asymptote2
num1 = a * h + b * k
num2 = a * h - b * k
asymptote1, asymptote2 = display_asymptotes(a2, b2, num1, num2)
axis.set_title(rf'Hyperbola Pair: $(y-{k})^2/{a2}-(x-{h})^2/{b2})=1$'+'\n'
+asymptote1 + '\n' + asymptote2, fontsize=10)
axis.grid(True)
axis.legend(loc='upper center', bbox_to_anchor=(0.2, 0.55), prop={'size': 7})
axis.set_aspect('equal', adjustable='box') # 設置坐標軸比例為相等
# 設定坐標軸的範圍
axis.set_xlim(h - 3 * a - 20 , h + 3 * a + 20)
axis.set_ylim(k - 3 * b - 20 , k + 3 * b + 20)
# 將圖像保存到 BytesIO 對象中
output = io.BytesIO()
FigureCanvas(fig).print_png(output)
return output.getvalue()
```
## 2.動態圖片
### 1.與一直線和一圓相切之圓心的軌跡為拋物線
#### $$(x-h)^2=4c(y-k)或(y-k)^2=4c(x-h)$$
<br>
#### 主程序引入繪圖檔案(paragif.py檔)中的繪圖函式(main())
```python=
#app.py
from paragif import main as paragif
```
#### 繪圖檔案:paragif.py檔
```python=
from PIL import Image
import imageio
import matplotlib.pyplot as plt
import numpy as np
import os
from sympy import *
import datetime
root='static/gif/'
#1.x_or_y=m
#2.3.圓
#4.h,k,c 拋物線參數
def draw(x_or_y,m,c_0_x,c_0_y,c_0_r,c_a_x,c_a_y,c_a_r,h,k,c):
range1=max(abs(int(h)),abs(int(k)))+10
line = np.arange(-range1-1, range1+1, 1)
#準線
if x_or_y=="x":
x_1=[m]*2*range1+[m]*2
y_1=line
#拋物線
y_2= np.arange(-range1,range1, 0.01)
x_2=(y_2-h)**2/4/c+k
if x_or_y=="y":
y_1=[m]*2*range1+[m]*2
x_1=line
#拋物線
x_2= np.arange(-range1,range1, 0.01)
y_2=(x_2-h)**2/4/c+k
#題目圓
theta = np.arange(0, 2*np.pi, 0.01)
x_3= c_0_x + c_0_r * np.cos(theta)
y_3= c_0_y + c_0_r * np.sin(theta)
#解答圓
x_4= c_a_x + c_a_r * np.cos(theta)
y_4= c_a_y + c_a_r * np.sin(theta)
fig = plt.figure(figsize=(7,7))
axes = fig.add_subplot(111)
axes.plot(x_1, y_1,c='g')
axes.plot(x_2, y_2,c='b',label='Answer')
axes.scatter(c_a_x,c_a_y,c='r')
axes.plot(x_3, y_3,c='g',label='given')
axes.plot(x_4, y_4,c='r',label='Circles')
plt.legend(loc='upper right',fontsize=20,edgecolor='#000',title_fontsize=20)
if x_or_y=="x":#準線為x=m (y-h)^2=...
axes.set_title(r'Parabola=$(y-%.0f)^2=4(%.0f) (x-%.0f)$' %(h,c,k), fontsize=30)
if x_or_y=="y":#準線為x=m (y-h)^2=...
axes.set_title(r'Parabola=$(x-%.0f)^2=4(%.0f) (y-%.0f)$' %(h,c,k), fontsize=30)
#控制大小
plt.xlim(-range1,range1)
plt.ylim(-range1,range1)
plt.xticks(np.linspace(-range1,range1, 11))
plt.yticks(np.linspace(-range1,range1, 11))
#座標軸繪製
axes.spines['right'].set_visible(False)
axes.spines['top'].set_visible(False)
axes.spines['bottom'].set_position(('axes',0.5))
axes.spines['left'].set_position(('axes',0.5))
#plt.show()
#線'x_or_y'=m,小圓參數
def main(x_or_y,m,c_0_x,c_0_y,c_0_r):
if x_or_y=='x':
filenames = []
d=abs(c_0_x-m)#已知圓心到線距離
h=c_0_y
k=c_0_x-(d-c_0_r)/2*abs(c_0_x-m)/(c_0_x-m)
c=(d-c_0_r)/2*abs(c_0_x-m)/(c_0_x-m)
#拋物險上點
range1=max(abs(int(h)),abs(int(k)))+10
y_2= np.arange(-range1,range1, 0.4)
x_2=(y_2-h)**2/4/c+k
for i in range(0,len(x_2)):
print(len(x_2)-i)#確認剩餘迴圈
R_2=abs(x_2[i]-m)
draw(x_or_y,m,c_0_x,c_0_y,c_0_r,x_2[i],y_2[i],R_2,h,k,c)#平行移動的圓
plt.savefig(f'{root}test1_'+str(i)+".png")
filenames.append(f'{root}test1_'+str(i)+".png")#儲存檔名方便利用
if x_or_y=='y':
filenames = []
d=abs(c_0_y-m)#已知圓心到線距離
h=c_0_x
k=c_0_y-(d-c_0_r)/2*abs(c_0_y-m)/(c_0_y-m)
c=(d-c_0_r)/2*abs(c_0_y-m)/(c_0_y-m)
#拋物險上點
range1=max(abs(int(h)),abs(int(k)))+10
x_2= np.arange(-range1,range1, 0.4)
y_2=(x_2-h)**2/4/c+k
for i in range(0,len(x_2)):
print(len(x_2)-i)#確認剩餘迴圈
R_2=abs(y_2[i]-m)
draw(x_or_y,m,c_0_x,c_0_y,c_0_r,x_2[i],y_2[i],R_2,h,k,c)#平行移動的圓
plt.savefig(f'{root}parabola_'+str(i)+".png")
filenames.append(f'{root}parabola_'+str(i)+".png")#儲存檔名方便利用
with imageio.get_writer(f'{root}parabola.gif', mode='I',loop=0) as writer:
for filename in filenames:
print(filename)
image = imageio.imread(filename)
writer.append_data(image)
#刪除png檔
for filename in filenames:
os.remove(filename)
print("Finished, GIF saved:parabola.gif")
if __name__=="__main__":
dstart = datetime.datetime.now()
main('y',5,2,0,1)
dend = datetime.datetime.now()
print(dend-dstart)
```
### 2.與兩圓(大圓包小圓)相切之圓心的軌跡為一橢圓
#### $$\frac{(x-h)^2}{a^2}+\frac{(y-k)^2}{b^2}=1$$
#### 主程序引入繪圖檔案(ellipsegif.py檔)中的繪圖函式(main())
```python=
#app.py
from ellipsegif import main as ellipsegif
```
```python=
from PIL import Image
import imageio
import matplotlib.pyplot as plt
import numpy as np
import os
from sympy import *
import datetime
root='static/gif/'
#line 32 主程式:輸入[大圓x,大圓y,大圓r,小圓x,小圓y,小圓r]
def draw(a_1,b_1,r_1,a_2,b_2,r_2,Ans_x,Ans_y,Ans_r,x_a,y_a,x_v,y_v):
#畫一個圖上三個圓
theta = np.arange(0, 2*np.pi, 0.01)
x_1= a_1 + r_1 * np.cos(theta)
y_1= b_1 + r_1 * np.sin(theta)
x_2= a_2 + r_2 * np.cos(theta)
y_2= b_2 + r_2 * np.sin(theta)
x_3= Ans_x + Ans_r * np.cos(theta)
y_3= Ans_y + Ans_r * np.sin(theta)
#繪圖
fig = plt.figure(figsize=(7,7))
axes = fig.add_subplot(111)
axes.plot(x_1, y_1,c='g')
axes.plot(x_2, y_2,c='g',label='given')
axes.plot(x_3, y_3,c='r',label='Circles')
axes.plot(x_a, y_a,c='b',label='Answer')
axes.scatter(Ans_x,Ans_y,c='r')
axes.set_title(r'Oval=$\frac{x^2}{%.2f^2}+\frac{y^2}{%.2f^2}$=1' %(x_v,y_v), fontsize=30)
plt.legend(loc='upper right',fontsize=20,edgecolor='#000',title_fontsize=20)
#控制大小
range1=max(abs(a_1+r_1),abs(a_1-r_1),abs(a_2+r_1),abs(a_2-r_1))+1
plt.xlim(-range1,range1)
plt.ylim(-range1,range1)
plt.xticks(np.linspace(-range1,range1, 11))
plt.yticks(np.linspace(-range1,range1, 11))
#座標軸繪製
axes.spines['right'].set_visible(False)
axes.spines['top'].set_visible(False)
axes.spines['bottom'].set_position(('axes',0.5))
axes.spines['left'].set_position(('axes',0.5))
#不同解的xy座標
def main(x_b,y_b,r_b,x_s,y_s,r_s):
Input=[x_b,y_b,r_b,x_s,y_s,r_s] #x_big,y_big...x_small,y_small...
x_oval=(x_b+x_s)/2 #橢圓中心
y_oval=(y_b+y_s)/2 #橢圓中心
a_oval=(r_b+r_s)/2
c_oval=((Input[0]-Input[3])**2+(Input[1]-Input[4])**2)**(1/2)/2
b_oval=(a_oval**2-c_oval**2)**(1/2)
if (Input[0]-Input[3])!=0 and (Input[1]-Input[4])==0:
theta2 = np.arange(0, 2*np.pi+0.1, 0.2)
x_a=x_oval+a_oval*np.cos(theta2)
y_a=y_oval+b_oval*np.sin(theta2)
x_v=a_oval**2
y_v=b_oval**2
elif(Input[0]-Input[3])==0 and (Input[1]-Input[4])!=0:
theta2 = np.arange(0, 2*np.pi+0.1, 0.2)
x_a=x_oval+b_oval*np.cos(theta2)
y_a=y_oval+a_oval*np.sin(theta2)
y_v=a_oval**2
x_v=b_oval**2
else:
print("Error")
exit(0)
#繪製解答 圓族
filenames = []
for i in range(0,len(x_a)):
print(len(x_a)-i)#確認剩餘迴圈
d_s_a=((x_a[i]-x_s)**2+(y_a[i]-y_s)**2)**(1/2)#與小圓圓心距離
r_a=d_s_a-Input[5]#與小圓圓心距離-r(小圓半徑)
draw(Input[0],Input[1],Input[2],Input[3],Input[4],Input[5],x_a[i],y_a[i],r_a,x_a,y_a,x_v,y_v)#平行移動的圓
plt.savefig(f'{root}oval_'+str(i)+".png")
filenames.append(f'{root}oval_'+str(i)+".png")#儲存檔名方便利用
# 生成gif
with imageio.get_writer(f'{root}oval.gif', mode='I',loop=0) as writer:
for filename in filenames:
print(filename)
image = imageio.imread(filename)
writer.append_data(image)
#刪除png檔
for filename in filenames:
os.remove(filename)
print("Finished")
if __name__=="__main__":
dstart = datetime.datetime.now()
main(-1,0,4,1,0,1)
dend = datetime.datetime.now()
print(dend-dstart)
```
### 3.與兩圓均內切或外切之圓心的軌跡為一雙曲線對(均外切與均內切各為一
#### $$\frac{(x-h)^2}{a^2}-\frac{(y-k)^2}{b^2}=\pm1$$
#### 主程序引入繪圖檔案(hypergif.py檔)中的繪圖函式(main())
```python=
from hypergif import main as hypergif
```
```python=
from PIL import Image
import imageio
import matplotlib.pyplot as plt
import numpy as np
import os
from sympy import *
import datetime
# 雙曲線,三圓(Big,small,ans)
root='static/gif/'
def draw(b_x,b_y,b_r,s_x,s_y,s_r,ans_x,ans_y,ans_r,x_left_down,y_left_down,x_right_up,y_right_up,rlud,a,b,c):
#畫一個圖上三個圓
theta = np.arange(0, 2*np.pi, 0.01)
#大圓
x_1= b_x + b_r * np.cos(theta)
y_1= b_y + b_r * np.sin(theta)
#小圓
x_2= s_x + s_r * np.cos(theta)
y_2= s_y + s_r * np.sin(theta)
#解答圓
x_3= ans_x + ans_r * np.cos(theta)
y_3= ans_y + ans_r * np.sin(theta)
#繪圖
fig = plt.figure(figsize=(7,7))
axes = fig.add_subplot(111)
axes.plot(x_1, y_1,c='g')
axes.plot(x_2, y_2,c='g',label='given')
axes.plot(x_3, y_3,c='r',label='Circles')
axes.plot(x_left_down, y_left_down,c='b',label='Answer')
axes.plot(x_right_up, y_right_up,c='b')
axes.scatter(ans_x,ans_y,c='r')
if rlud=="rightleft":
axes.set_title(r'Hyperbolic=$\frac{x^2}{%.2f^2}-\frac{y^2}{%.2f^2}=1$' %(a,b), fontsize=20)
elif rlud=="updown":
axes.set_title(r'Hyperbolic=$\frac{y^2}{%.2f^2}-\frac{x^2}{%.2f^2}=1$' %(a,b), fontsize=20)
plt.legend(loc='upper right', bbox_to_anchor=(0.95, 0.5), fontsize=15,edgecolor='#000',title_fontsize=15)
#控制大小
range1=max(abs(b_x+b_r),abs(b_x-b_r),abs(s_x+s_r),abs(s_x-s_r),abs(b_y+b_r),abs(b_y-b_r),abs(s_y+s_r),abs(s_y-s_r))+10
plt.xlim(-range1,range1)
plt.ylim(-range1,range1)
plt.xticks(np.linspace(-range1,range1, 11))
plt.yticks(np.linspace(-range1,range1, 11))
#座標軸繪製
axes.spines['right'].set_visible(False)
axes.spines['top'].set_visible(False)
axes.spines['bottom'].set_position(('axes',0.5))
axes.spines['left'].set_position(('axes',0.5))
def Save_delete(filename1,filenames):
with imageio.get_writer(filename1, mode='I',loop=0) as writer:
for filename in filenames:
print(filename)
image = imageio.imread(filename)
writer.append_data(image)
#刪除png檔
for filename in filenames:
os.remove(filename)
print("Finished")
#不同解的xy座標
def main(x_b,y_b,r_b,x_s,y_s,r_s):#x_big,y_big...x_small,y_small...
#a,b,c
a=(r_b-r_s)/2
c=((x_b-x_s)**2+(y_b-y_s)**2)**(1/2)/2
b=(c**2-a**2)**(1/2)
# 生成雙曲線的角度值
theta = np.linspace(-1*np.pi, 1*np.pi, 50)
#找出中心
if (x_b-x_s)!=0 and (y_b-y_s)==0:
rlud="rightleft"
if x_b>x_s:#大圓在右
center_x=(x_b+x_s)/2
outer="left"
elif x_b<x_s:#大圓在左
center_x=(x_b+x_s)/2
outer="right"
else:
print("error")
exit(0)
center_y=y_b
# 計算雙曲線上的點的坐標
x_right = center_x + a * np.cosh(theta)
y_right = center_y + b* np.sinh(theta)
x_left = center_x - a * np.cosh(theta)
y_left = center_y - b * np.sinh(theta)
if outer=="left":#外切圓在左
filenames = []
for i in range(0,len(x_left)):#外切
print(len(x_left)-i)#確認剩餘迴圈
r_change=((x_left[i]-x_s)**2+(y_left[i]-y_s)**2)**(1/2)-r_s
draw(x_b,y_b,r_b,x_s,y_s,r_s,x_left[i],y_left[i],r_change,x_left,y_left,x_right,y_right,rlud,a,b,c)#平行移動的圓
plt.savefig(f'{root}hyperbolic_outer_'+str(i)+".png")
filenames.append(f'{root}hyperbolic_outer_'+str(i)+".png")#儲存檔名方便利用
Save_delete(f'{root}hyperbolic_outer.gif',filenames)
filenames = []
for i in range(0,len(x_right)):#內切
print(len(x_right)-i)#確認剩餘迴圈
r_change=((x_right[i]-x_s)**2+(y_right[i]-y_s)**2)**(1/2)+r_s
draw(x_b,y_b,r_b,x_s,y_s,r_s,x_right[i],y_right[i],r_change,x_left,y_left,x_right,y_right,rlud,a,b,c)#平行移動的圓
plt.savefig(f'{root}hyperbolic_inner_'+str(i)+".png")
filenames.append(f'{root}hyperbolic_inner_'+str(i)+".png")#儲存檔名方便利用
Save_delete(f'{root}hyperbolic_inner.gif',filenames)
if outer=="right":#外切圓在右
filenames = []
for i in range(0,len(x_right)):#外切
print(len(x_right)-i)#確認剩餘迴圈
r_change=((x_right[i]-x_s)**2+(y_right[i]-y_s)**2)**(1/2)-r_s
draw(x_b,y_b,r_b,x_s,y_s,r_s,x_right[i],y_right[i],r_change,x_left,y_left,x_right,y_right,rlud,a,b,c)#平行移動的圓
plt.savefig(f'{root}hyperbolic_outer_'+str(i)+".png")
filenames.append(f'{root}hyperbolic_outer_'+str(i)+".png")#儲存檔名方便利用
Save_delete(f'{root}hyperbolic_outer.gif',filenames)
filenames = []
for i in range(0,len(x_left)):#內切
print(len(x_left)-i)#確認剩餘迴圈
r_change=((x_left[i]-x_s)**2+(y_left[i]-y_s)**2)**(1/2)+r_s
draw(x_b,y_b,r_b,x_s,y_s,r_s,x_left[i],y_left[i],r_change,x_left,y_left,x_right,y_right,rlud,a,b,c)#平行移動的圓
plt.savefig(f'{root}hyperbolic_inner_'+str(i)+".png")
filenames.append(f'{root}hyperbolic_inner_'+str(i)+".png")#儲存檔名方便利用
Save_delete(f'{root}hyperbolic_inner.gif',filenames)
elif(y_b-y_s)!=0 and (x_b-x_s)==0:
rlud="updown"
if y_b>y_s:#大圓在上
center_y=(y_b+y_s)/2
outer="down"
elif y_b<y_s:#大圓在下
center_y=(y_b+y_s)/2
outer="up"
else:
print("error")
exit(0)
center_x=x_b
# 計算雙曲線上的點的坐標
x_up = center_x + b* np.sinh(theta)
y_up = center_y + a * np.cosh (theta)
x_down = center_x - b * np.sinh(theta)
y_down = center_y - a * np.cosh(theta)
if outer=="down":#外切圓在下
filenames = []
for i in range(0,len(y_down)):#外切
print(len(y_down)-i)#確認剩餘迴圈
r_change=((x_down[i]-x_s)**2+(y_down[i]-y_s)**2)**(1/2)-r_s
draw(x_b,y_b,r_b,x_s,y_s,r_s,x_down[i],y_down[i],r_change,x_up,y_up,x_down,y_down,rlud,a,b,c)#平行移動的圓
plt.savefig(f'{root}hyperbolic_outer_'+str(i)+".png")
filenames.append(f'{root}hyperbolic_outer_'+str(i)+".png")#儲存檔名方便利用
Save_delete(f'{root}/hyperbolic_outer.gif',filenames)
filenames = []
for i in range(0,len(y_up)):#內切
print(len(x_up)-i)#確認剩餘迴圈
r_change=((x_up[i]-x_s)**2+(y_up[i]-y_s)**2)**(1/2)+r_s
draw(x_b,y_b,r_b,x_s,y_s,r_s,x_up[i],y_up[i],r_change,x_up,y_up,x_down,y_down,rlud,a,b,c)#平行移動的圓
plt.savefig(f'{root}hyperbolic_inner_'+str(i)+".png")
filenames.append(f'{root}hyperbolic_inner_'+str(i)+".png")#儲存檔名方便利用
Save_delete(f'{root}/hyperbolic_inner.gif',filenames)
if outer=="up":#外切圓在上
filenames = []
for i in range(0,len(y_up)):#外切
print(len(y_up)-i)#確認剩餘迴圈
r_change=((x_up[i]-x_s)**2+(y_up[i]-y_s)**2)**(1/2)-r_s
draw(x_b,y_b,r_b,x_s,y_s,r_s,x_up[i],y_up[i],r_change,x_up,y_up,x_down,y_down,rlud,a,b,c)#平行移動的圓
plt.savefig(f'{root}hyperbolic_outer_'+str(i)+".png")
filenames.append(f'{root}hyperbolic_outer_'+str(i)+".png")#儲存檔名方便利用
Save_delete(f'{root}/hyperbolic_outer.gif',filenames)
filenames = []
for i in range(0,len(y_down)):#內切
print(len(x_down)-i)#確認剩餘迴圈
r_change=((x_down[i]-x_s)**2+(y_down[i]-y_s)**2)**(1/2)+r_s
draw(x_b,y_b,r_b,x_s,y_s,r_s,x_down[i],y_down[i],r_change,x_up,y_up,x_down,y_down,rlud,a,b,c)#平行移動的圓
plt.savefig('hyperbolic_inner_'+str(i)+".png")
filenames.append('hyperbolic_inner_'+str(i)+".png")#儲存檔名方便利用
Save_delete(f'{root}/hyperbolic_inner.gif',filenames)
else:
print("Error")
exit(0)
if __name__=="__main__":
start=datetime.datetime.now()
main(0,-1,7,0,9,1)
end=datetime.datetime.now()
print('total run time:',end-start)
```
# *HTML網站骨架*
## 1.index.html主頁面
#### 包含<br>1.頂部網站圖案<br>2.目錄超連結<br>3.輸入參數所需的表單<br>4.基本介紹與使用說明
```html=
<!-- index1.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>二次曲線繪圖神器???</title>
<link rel="icon" type="image/png" href="/static/image/icon.png">
<meta name="keywords" content="高中數學,拋物線,橢圓,雙曲線,畫圖">
<meta name="description" content="這是一個完全免費的客製化繪圖工具">
<link href="/static/css/style.css" rel="stylesheet" media="All" type="text/css">
<script src="/static/JS/index1.js"></script>
</head>
<body>
<header>
<h1>EZdrawing</h1>
</header>
<nav>
<h2>目錄</h2>
<ul>
<li><a href="#presentation">動圖成品</a></li>
<li><a href="#elipse">橢圓</a></li>
<li><a href="#parabola">拋物線</a></li>
<li><a href="#hyperbola pair">雙曲線</a></li>
<li><a href="#gif">動圖生成</a></li>
<li><a href="#guideline">使用說明</a></li>
<li><a href="#calculator">計算機</a></li>
<li><a href="#introandmotivation">開發者與動機</a></li>
<hr/>
</ul>
</nav>
<section id="#presentation">
<h2>動圖成品</h2>
<div class="image-container">
<img src="/static/gif/hyperbolic_innershow.gif">
<img src="/static/gif/hyperbolic_outershow.gif">
</div>
<div class="image-container">
<img src="/static/gif/ovalshow.gif">
<img src="/static/gif/parabolashow.gif">
</div>
<section id="elipse">
<h2>橢圓</h2>
<div class="form-container">
<form action="/calculate_elipse">
<h4>橢圓方程式<br>((x-h)<sup>2</sup>/a<sup>2</sup> + (y-k)<sup>2</sup>/b<sup>2</sup>=1)</h4>
a<sup>2</sup><input type="text" name="a2">
b<sup>2</sup><input type="text" name="b2"><br>
h<input type="text" name="h" value="0">
k<input type="text" name="k" value="0"><br>
<button title="Let's go!">開始計算結果</button>
</form>
</div>
</section>
<section id="parabola">
<h2>拋物線</h2>
<div class="form-container">
<form action="/calculate_parabola1">
<h4>上下開口拋物線<br>((x-h)<sup>2</sup>=4c(y-k))</h4>
4c<input type="text" name="p">
h<input type="text" name="h" value="0"><br>
k<input type="text" name="k" value="0"><br>
<button title="Let's go!">開始計算結果</button>
</form>
<form action="/calculate_parabola2">
<h4>左右開口拋物線<br>((y-k)<sup>2</sup>=4c(x-h))</h4>
4c<input type="text" name="p">
h<input type="text" name="h" value="0"><br>
k<input type="text" name="k" value="0"><br>
<button title="Let's go!">開始計算結果</button>
</form>
</div>
</section>
<hr/>
</section>
<hr/>
</section>
<section id="hyperbola pair">
<h2>雙曲線</h2>
<div class="form-container">
<form action="/calculate_hyperbola1">
<h4>左右開口雙曲線方程式<br>((x-h)<sup>2</sup>/a<sup>2</sup> - (y-k)<sup>2</sup>/b<sup>2</sup>=1)</h4>
a<sup>2</sup>(x下係數)<input type="text" name="a2"><br>
b<sup>2</sup>(y下係數)<input type="text" name="b2"><br>
h<input type="text" name="h" value="0"><br>
k<input type="text" name="k" value="0"><br>
<button title="Let's go!">開始計算結果</button>
</form>
<form action="/calculate_hyperbola2">
<h4>上下開口雙曲線方程式<br>((y-k)<sup>2</sup>/a<sup>2</sup>-(x-h)<sup>2</sup>/b<sup>2</sup>=1)</h4>
a<sup>2</sup>(y下係數)<input type="text" name="a2"><br>
b<sup>2</sup>(x下係數)<input type="text" name="b2"><br>
h<input type="text" name="h" value="0"><br>
k<input type="text" name="k" value="0"><br>
<button title="Let's go!">開始計算結果</button>
</form>
</div>
</section>
<section id="gif">
<h2>動圖生成</h2>
<h3>拋物線</h3><hr>
<div class="form-container">
<form action="/paragif" onsubmit="startProgress()">
<h4>與一直線(x or y=m)相切以及<br>和一圓內切之圓心軌跡方程式</h4>
x or y<input type=""text" name="x_or_y" value="y"><br>
m<input type="text" name="m" value="5"><br>
C2:X座標<input type="text" name="c_0_x" value="2"><br>
C2:y座標<input type="text" name="c_0_y" value="0"><br>
C2:半徑<input type="text" name="c_0_r" value="1"><br>
<button title="Let's go!">開始計算結果</button>
</form>
<div id="progressBar">
<div id="progress" style="width: 0%;">0%</div>
</div>
</div>
<hr>
<h3>橢圓</h3><hr>
<div class="form-container">
<form action="/ellipsegif">
<h4>與兩圓相切之圓心軌跡方程式<br>(大圓C1包小圓C2)<br></h4>
C1:X座標<input type="text" name="x_b" value="-1"><br>
C1:y座標<input type="text" name="y_b" value="0"><br>
C1:半徑<input type="text" name="r_b" value="4"><br>
C2:X座標<input type="text" name="x_s" value="1"><br>
C2:y座標<input type="text" name="y_s" value="0"><br>
C2:半徑<input type="text" name="r_s" value="1"><br>
<button title="Let's go!">開始計算結果</button>
</form>
</div>
<hr>
<h3>雙曲線</h3><hr>
<div class="form-container">
<form action="/hypergif" onsubmit="startProgress()">
<h4>與兩圓均內切或外切<br>之圓新軌跡方程式<br></h4>
C1:X座標<input type="text" name="x_b" value="0"><br>
C1:y座標<input type="text" name="y_b" value="-1"><br>
C1:半徑<input type="text" name="r_b" value="7"><br>
C2:X座標<input type="text" name="x_s" value="0"><br>
C2:y座標<input type="text" name="y_s" value="9"><br>
C2:半徑<input type="text" name="r_s" value="1"><br>
<button title="Let's go!">開始計算結果</button>
</form>
</div>
<hr>
<div id="progressBar">
<div id="progress" style="width: 0%;">0%</div>
</div>
</section>
<section id="calculator">
<h2>計算機</h2>
<a href="/calculator"><h4>簡易計算機</h4></a>
</section>
<section id="guideline">
<h2>使用說明</6></h2>
1.為避免表單內請輸入整數<br>
2.動圖生成圓心必須在軸上<br>
3.動圖表單預設數字為一示例<br>
4.進度條僅供參考<br>
5.動圖題目中圓的參數請正確輸入,以免錯誤<br>
<hr>
</section>
<section id="introandmotivation">
<h2>開發者與動機</h2>
<p>本網站由松山高中318班賴志全、朱禹澄所研究開發</p><hr>
<p>目前數學進度為需要大量熟悉方程式圖形的二次曲線單元,為提升同儕們的學習品質,特別規劃此專案,
期待能幫助每位同學們對於拋物線、橢圓、雙曲線的圖形理解。</p>
</section>
</body>
<strong>Copyright All Rights Reserved.</strong>
</html>
```
## 2.result.html成品頁面
#### 此為送出表單後成果展示的頁面,使用了將照片連結參數化,使得HTML成為一個模板,而不需重複書寫以獲取不同來源的照片
```html=
<!--result.html-->
<html>
<head>
<title>繪圖成果</title>
<link rel="icon" type="image/png" href="/static/image/icon.png">
<link href="/static/css/style.css" rel="stylesheet" media="All" type="text/css">
</head>
<body>
<h1>Result Plot</h1><br>
<h3>{{title_name}}</h3>
<img src={{file_name}} alt="{{title_name}}"><br>
<a href="/">回首頁</a><br>
<a href="/calculator">計算機</a>
</body>
</html>
```
# *CSS網站美編設計*
#### 此為進度中最後的步驟,處理考慮手機使用者的使用體驗,主要優化了<br>1.表單的排列<br>2.字體的大小<br>3.照片的寬度調整
```css=
@charset "utf-8";
body{
background-color:rgb(216, 214, 209);
background-image: url(/static/image/star.png);
background-repeat: repeat-x;
background-size: 300px;
font-size:16px;
}
.bold-center {
font-weight: bold;
text-align: center;
}
.center {
text-align: center;
}
h1 {
font-size: 85px;
text-align:center;
background-image:url(/static/gif/math.gif);
background-repeat:no-repeat;
background-position:center ;
color:transparent;
background-clip:text ;
filter:drop-shadow(10px 5px 30px rgb(197, 182, 182))
}
h2 {
font-size: 50px;
padding: 10px;
margin-bottom: 30px;
border: 2px dotted #09af33;
border-left: 10px solid #12de75;
color: #236244;
background-color: rgba(191, 175, 175, 0.5);
}
h4{
text-align:center;
font-size:25px
}
button{
background-color: #10c065;
border-radius:40px ;
width:130px
}
button:active{
background-color: #0a7b46;
}
button:hover{
background-color: #0f0;
}
ul {
columns: 4; /* 將列表分為五列 */
column-gap: 20px; /* 列間距 */
}
li {
display: inline-block; /* 將列表項目設置為內聯塊 */
width: 100%; /* 使列表項目填滿列寬 */
text-align: center;
font-size: 30px;
}
a {
color: #0a7b46; /* 將超連結的字體顏色設置為藍色 */
text-decoration: none; /* 移除超連結的底線 */
}
a:hover {
color: rgb(101, 226, 166); /* 將超連結的字體顏色設置為紅色,當滑鼠懸停在超連結上時 */
}
.form-container {
display:flex;
justify-content: center; /* 將內容水平置中 */
align-items: center; /* 將內容垂直置中 */
flex-wrap: wrap; /* 如果表單太寬,則換行顯示 */
}
.form-container form {
margin: 10px auto; /* 给表单之间一些间距,并水平居中 */
text-align: center; /* 文本居中 */
width: 450px; /* 设置表单宽度 */
padding: 20px; /* 增加内边距 */
border-radius: 10px; /* 添加边框圆角 */
background-color: #f0f0f0; /* 添加背景色 */
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); /* 添加阴影效果 */
margin-right: 80px;
margin-left: 80px;
}
.form-container form {
margin: 10px; /* 給表單之間一些間距*/
text-align: center;
margin-right: 80px;
margin-left: 80px;
}
.form-container h4 {
margin-bottom: 10px; /*調整標題下方間距*/
}
.form-container input,
.form-container button {
margin-bottom: 5px; /* 調整輸入框和按鈕下方間距 */
width: 130px; /* 調整輸入框和按鈕的寬度 */
}
#progressBar {
width: 100%;
background-color: #f1f1f1;
}
#progressBar>div {
height: 30px;
text-align: center;
line-height: 30px;
color: white;
background: linear-gradient(90deg, #0f0, #0ff);
animation:ease-in-out
}
.image-container {
display: flex;
justify-content: space-between; /* 图片之间的间距相等 */
margin-bottom: 20px; /* 设置图片之间的垂直间距 */
}
.image-container img {
width: 430px; /* 设置宽度为 190px */
height: auto; /* 高度自动等比例缩放 */
border-radius: 10px;
}
@keyframes progress-animation {
from {
width: 0%;
}
to {
width: 100%;
}
}
@media (max-width:500px){
/* 手機版 */
h1{
font-size: 60px;
}
body{
background-color:rgb(216, 214, 209);
background-image: url(/static/image/star.png);
background-repeat: repeat-x;
background-size: 200px;
}
img[src=""] {
display: none;
/* display: none; 如果没有 src 属性,则隐藏图片 */
}
img[src] {
width: 360px;
height: auto;
display: block;
margin: 0 auto;
}
.form-container {
display: flex;
justify-content: flex-start; /* 將內容靠左對齊 */
align-items: center; /* 將內容垂直置中 */
flex-wrap: wrap; /* 如果表單太寬,則換行顯示 */
}
.form-container form {
margin: 10px; /* 給表單之間一些間距*/
text-align: center;
width:360px;
}
.form-container h4 {
margin-bottom: 5px; /* 調整標題下方間距*/
}
.form-container input[type="text"],
.form-container button {
margin-bottom: 5px; /* 調整輸入框和按鈕下方間距 */
width: 80px; /* 調整輸入框的寬度 */
}
.form-container button {
width: auto; /* 保持按鈕寬度自動調整 */
}
}
```
# *Side Project:進度條*
## 1.進度條HTML
```html=
<form action="/paragif" onsubmit="startProgress()">
<button>submit!</button>
</form>
<div id="progressBar">
<div id="progress" style="width: 0%;">0%</div>
</div>
```
## 2.進度條CSS
```css=
#progressBar {
width: 100%;
background-color: #f1f1f1;
}
#progressBar>div {
height: 30px;
text-align: center;
line-height: 30px;
color: white;
background: linear-gradient(90deg, #0f0, #0ff);
animation:ease-in-out
}
```
## 3.進度條JavaScript
```javascript=
function updateProgress(progress) {
var progressBar = document.getElementById('progress');
progressBar.style.width = progress + '%';
progressBar.innerHTML = progress + '%';
}
function simulateProgress() {
var progress = 0;
var interval = setInterval(function() {
progress += 0.083;
updateProgress( Math.round(progress * 100) / 100);
if (progress >= 99.99) {
clearInterval(interval);
}
}, 50); // 每0.05秒更新一次進度
}
function startProgress() {
simulateProgress(); // 開始模擬進度
}
```
# *Side Project:計算機製作*
## 1.計算機路由
```python=
@app.route('/calculator')
def calculator():
return render_template('index2.html')
```
## 計算機骨架HTML
```html=
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="/static/css/calculator.css">
</head>
<body>
<div id="calculator">
<input id="display" readonly>
<div id="keys">
<button onclick="clearDisplay()" class="operator-btn">c</button>
<button onclick="appendToDisplay('7')">7</button>
<button onclick="appendToDisplay('8')">8</button>
<button onclick="appendToDisplay('9')">9</button>
<button onclick="appendToDisplay('+')" class="operator-btn">+</button>
<button onclick="appendToDisplay('4')">4</button>
<button onclick="appendToDisplay('5')">5</button>
<button onclick="appendToDisplay('6')">6</button>
<button onclick="appendToDisplay('-')" class="operator-btn">-</button>
<button onclick="appendToDisplay('1')">1</button>
<button onclick="appendToDisplay('2')">2</button>
<button onclick="appendToDisplay('3')">3</button>
<button onclick="appendToDisplay('*')" class="operator-btn">*</button>
<button onclick="appendToDisplay('0')">0</button>
<button onclick="appendToDisplay('.')">.</button>
<button onclick="calculate()">=</button>
<button onclick="appendToDisplay('/')" class="operator-btn">/</button>
<button onclick="appendToDisplay('(')" class="operator-btn">(</button>
<button onclick="appendToDisplay(')')" class="operator-btn">)</button>
<button onclick="appendToDisplay('^')" class="operator-btn">^</button>
</div>
</div>
<script src="/static/JS/index2.js"></script>
<section class="homelink">
<a href="/">回首頁</a><br>
</section>
</body>
</html>
```
## 2.計算機美編CSS
```css=
/*calculator.css*/
body{
margin: 0;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background-color: hsl(0, 0%, 95%);
}
#calculator{
font-family: Arial, sans-serif;
background-color: hsl(0, 0%, 15%);
border-radius: 15px;
max-width: 500px;
overflow: hidden;
}
#display{
width: 100%;
padding: 20px;
font-size: 5rem;
text-align: left;
border: none;
background-color: hsl(0, 0%, 20%);
color: white;
}
#keys{
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 10px;
padding: 25px;
}
button{
width: 100px;
height: 100px;
border-radius: 50px;
border: none;
background-color: hsl(0, 0%, 30%);
color: white;
font-size: 3rem;
font-weight: bold;
cursor: pointer;
}
button:hover{
background-color: hsl(0, 0%, 40%);
}
button:active{
background-color: hsl(0, 0%, 50%);
}
.operator-btn{
background-color: #236244;
}
.operator-btn:hover{
background-color: #0f0;
}
.operator-btn:active{
background-color: #09af33;
}
.homelink {
position: fixed;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
text-align: center;
width: 100%;
}
a {
color: #0a7b46;
text-decoration: none;
}
a:hover {
color: rgb(101, 226, 166); /
}
@media (max-width:500px){
button{
width: 72px;
height: 72px;
border-radius: 50px;
font-size: 2rem
}
#calculator{
font-family: Arial, sans-serif;
background-color: hsl(0, 0%, 15%);
border-radius: 15px;
max-width: 360px;
overflow: hidden;
}
}
```
## 3.計算機功能JavaScript
```javascript=
const display = document.getElementById("display");
function appendToDisplay(input) {
// 添加 ^ 符号表示指数的功能
if (input === '^') {
display.value += '^';
} else if (input === '(' || input === ')') {
display.value += input;
} else {
display.value += input;
}
}
function clearDisplay() {
display.value = "";
}
function calculate() {
try {
let expression = display.value;
// 處理括號內的表達式
while (expression.includes('(')) {
const startIndex = expression.lastIndexOf('(');
const endIndex = expression.indexOf(')', startIndex);
const subExpression = expression.slice(startIndex + 1, endIndex);
const subResult = evaluateExpression(subExpression);
expression = expression.slice(0, startIndex) + subResult + expression.slice(endIndex + 1);
}
// 處理指數運算
const powerRegex = /(\d+(\.\d+)?)\^(\d+(\.\d+)?)/;
while (expression.match(powerRegex)) {
expression = expression.replace(powerRegex, (match, base, _, exponent) => {
return Math.pow(parseFloat(base), parseFloat(exponent));
});
}
// 處理乘除運算
expression = expression.replace(/(\d+(\.\d+)?)\*(\d+(\.\d+)?)/g, (match, num1, _, num2) => {
return parseFloat(num1) * parseFloat(num2);
});
expression = expression.replace(/(\d+(\.\d+)?)\/(\d+(\.\d+)?)/g, (match, num1, _, num2) => {
return parseFloat(num1) / parseFloat(num2);
});
// 處理加減運算
expression = expression.replace(/(\d+(\.\d+)?)\+(\d+(\.\d+)?)/g, (match, num1, _, num2) => {
return parseFloat(num1) + parseFloat(num2);
});
expression = expression.replace(/(\d+(\.\d+)?)\-(\d+(\.\d+)?)/g, (match, num1, _, num2) => {
return parseFloat(num1) - parseFloat(num2);
});
display.value = expression;
} catch (error) {
display.value = "Error";
}
}
function evaluateExpression(expression) {
// 遞歸地計算括號內的表達式
let result = expression;
let newExpression = expression;
while (newExpression.includes('^')) {
const match = newExpression.match(/(\d+(\.\d+)?)\^(\d+(\.\d+)?)/);
if (match) {
const base = parseFloat(match[1]);
const exponent = parseFloat(match[3]);
const subResult = Math.pow(base, exponent);
newExpression = newExpression.replace(match[0], subResult);
}
}
// 遞歸地計算括號內的表達式
while (result.includes('(')) {
const startIndex = result.lastIndexOf('(');
const endIndex = result.indexOf(')', startIndex);
const subExpression = result.slice(startIndex + 1, endIndex);
const subResult = evaluateExpression(subExpression);
result = result.slice(0, startIndex) + subResult + result.slice(endIndex + 1);
}
// 計算乘除運算
result = result.replace(/(\d+(\.\d+)?)\*(\d+(\.\d+)?)/g, (match, num1, _, num2) => {
return parseFloat(num1) * parseFloat(num2);
});
result = result.replace(/(\d+(\.\d+)?)\/(\d+(\.\d+)?)/g, (match, num1, _, num2) => {
return parseFloat(num1) / parseFloat(num2);
});
// 計算加減運算
result = result.replace(/(\d+(\.\d+)?)\+(\d+(\.\d+)?)/g, (match, num1, _, num2) => {
return parseFloat(num1) + parseFloat(num2);
});
result = result.replace(/(\d+(\.\d+)?)\-(\d+(\.\d+)?)/g, (match, num1, _, num2) => {
return parseFloat(num1) - parseFloat(num2);
});
return result;
}
```
# *部署*
## Github:存放程式碼的位置

## Replit:編輯程式碼並與Github連結
## Render.com:提供網頁服務與伺服器
### requirements.txt:讓Render.com知道該下載甚麼版本的甚麼模組
```python=
Flask==3.0.2
matplotlib==3.4.3
imageio==2.34.0
sympy==1.12
numpy==1.26.4
gunicorn==21.2.0
```