# **EZdrawing互動式繪圖網站製作** <div class="name">松山高中 朱禹澄 賴致全</div> <style> .name{ text-align:right; } </style> ![EZdrawing心智圖](https://hackmd.io/_uploads/BJeEiTH1R.png) 附圖為本次專案的心智圖<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:存放程式碼的位置 ![image](https://hackmd.io/_uploads/ryTvguvJR.png) ## 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 ```