顯示具有 網頁技術 標籤的文章。 顯示所有文章
顯示具有 網頁技術 標籤的文章。 顯示所有文章

2026年5月18日 星期一

市圖還書 1 本 : React思維進化

此書被預約須還, 今天拿去投還書箱 :
此書改寫自 iThome 鐵人賽冠軍作品, 著重在 React 底層原理與核心觀念的介紹, 是非常新 (2024) 且能鍛鍊前端基本功的好書. 但我最近並沒時間學習 React, 前陣子借的 React 書幾乎都還回去了, 只剩 "從Hooks開始 讓你的網頁React起來" 這本 : 
等有空再回借吧!

2026年5月1日 星期五

Python 學習筆記 : 用 plotly 繪製 K 線圖 (二)

在前一篇測試中已對 Plotly 的 K 線圖類別 plotly.graph_objects.CandleStick 的用法有了基本了解, 本篇旨在利用 Figure 物件的 add_trace() 方法在畫布上添加子圖 (例如成交量或 RSI 等技術指標), 同時透過 plotly.subplots.make_subplot() 函式來規劃畫布上的子圖佈局. 

本系列測試文章索引參考 :



4. 添加成交量子圖 : 

要在畫布上繪製子圖有兩種方式, 第一種是使用較低階的絕對領域 (Domain) 劃分法, 此方法須手動切割 Y 軸空間自行布局, 好處是可以客製化布局, 缺點是維護難度較高, 調整彈性較差, 在 "最強 AI 投資分析" 這本書的第四章就是採用此方法, 參考 :


第二種方式是使用 plotly.subplots.make_subplot() 函式, 這是採用網格系統來進行排版的高階 API, 只要指定子圖的列 (row) 與欄 (col), Plotly 會在底層自動計算所有圖表的高度, 對齊與間距, 是一種較簡易的低耦合設計. 

首先匯入子圖布局函式 make_subplots() :

from plotly.subplots import make_subplots 

make_subplots() 的參數說明如下表 :


 參數名稱  說明
 rows  子圖的列數(預設為 1)
 cols  子圖的欄數(預設為 1)
 shared_xaxes  是否共用 X 軸(True / False / 'all' / 'rows' / 'columns')
 shared_yaxes  是否共用 Y 軸(True / False / 'all' / 'rows' / 'columns')
 start_cell  子圖起始位置('top-left' 或 'bottom-left')
 subplot_titles  每個子圖的標題(list)
 specs  自訂每個子圖的型態與配置(例如 type='xy', 'domain', 'scene' 等)
 row_heights  各列高度比例(list,例如 [0.7, 0.3])
 column_widths  各欄寬度比例(list,例如 [0.6, 0.4])
 horizontal_spacing  子圖之間的水平間距(0~1)
 vertical_spacing  子圖之間的垂直間距(0~1)
 insets  設定內嵌子圖(inset charts)的位置與大小
 column_titles  每一欄的標題(list)
 row_titles  每一列的標題(list)
 x_title  整體 X 軸標題
 y_title  整體 Y 軸標題
 figure  將子圖加入既有的 Figure 物件


其中最常用的參數是 rows, cols, shared_xaxes, vertical_spacing, 與 row_heights 等. 

下列程式將畫布做 2 列 1 欄布局, 第一列高度占 70% 放 K 線圖 trace; 第二列高度占 30% 放成交量長條圖 (有做漲紅跌綠顏色設定) :

# plotly_candlestick_5.py
import yfinance as yf
import plotly.graph_objects as go
import pandas as pd
from plotly.subplots import make_subplots

if __name__ == "__main__":
    df=yf.download('0050.tw', start='2026-03-01', end='2026-04-30', auto_adjust=True)
    df.columns=df.columns.map(lambda x: x[0])

    # === 建立子圖 (2 rows: 上列放 K 線圖,下列放成交量) ===
    fig=make_subplots(
        rows=2,
        cols=1,
        shared_xaxes=True,
        vertical_spacing=0.03,
        row_heights=[0.7, 0.3]   # 列高度占比
        )

    # === K 線圖 ===
    price=go.Candlestick(
        x=df.index,
        open=df['Open'],
        high=df['High'],
        low=df['Low'],
        close=df['Close'],
        increasing_line_color='red',
        decreasing_line_color='green',
        name='Price'
        )
    fig.add_trace(price, row=1, col=1)   # K 線圖放 row 1

    # === 成交量顏色(漲紅跌綠) ===
    colors=['red' if c >= o else 'green' for c, o in zip(df['Close'], df['Open'])]
    volume=go.Bar(
        x=df.index,
        y=df['Volume'],
        marker_color=colors,
        name='Volume'
        )
    fig.add_trace(volume, row=2, col=1)  # 成交量放 row 2

    # === 移除非交易日 ===
    date_range=pd.date_range(start=df.index.min(), end=df.index.max())
    breaks=date_range[~date_range.isin(df.index)]
    breaks_list=breaks.tolist()
    fig.update_xaxes(
        rangebreaks=[{'values': breaks_list}],
        showspikes=True,
        spikethickness=1,
        spikecolor='blue',
        spikedash='dot',
        spikemode='across'
        )

    # === Y 軸 spike(兩個子圖都套用)===
    fig.update_yaxes(
        showspikes=True,
        spikethickness=1,
        spikecolor='blue',
        spikedash='dot',
        spikemode='across'
        )

    # === Layout ===
    fig.update_layout(
        title='台灣五十 (0050) K線 + 成交量',
        width=800,
        height=700,
        xaxis_rangeslider_visible=False,
        hovermode='x unified'   # 很重要:跨子圖同步 hover
        )

    fig.show()

注意, 之前沒有子圖時是呼叫 fig=go.Figure() 建立 Figure 物件, 但此處是用 make_subplots() 來建立. 其次, 有子圖的情況時, hovermode 參數務必要設為 'x unified', 否則會有多個 tooltip 分散, 以及子圖之間不同步問題 (無子圖時因所有 trace 都在同一個座標系故不會有這些問題). 

結果如下 :






5. 添加技術指標子圖 : 

接下來要在成交量下面添加 MACD 指標, 使用 pandas-ta 套件計算, 參考 :


這樣畫布上總共有三個子圖 : K 線圖, 成交量, 與 MACD. 

程式碼如下 :

# plotly_candlestick_6.py
import yfinance as yf
import plotly.graph_objects as go
import pandas as pd
import pandas_ta as ta
from plotly.subplots import make_subplots

if __name__ == "__main__":
    # === 下載資料 ===
    df=yf.download('0050.tw', start='2026-03-01', end='2026-04-30', auto_adjust=True)
    df.columns=df.columns.map(lambda x: x[0])

    # === 計算 MACD(pandas-ta)===
    macd=ta.macd(df['Close'], fast=12, slow=26, signal=9)

    # pandas-ta 會回傳三欄 :contentReference[oaicite:0]{index=0}
    df['MACD']=macd['MACD_12_26_9']
    df['MACDs']=macd['MACDs_12_26_9']   # signal
    df['MACDh']=macd['MACDh_12_26_9']   # histogram

    # === 建立子圖 ===
    fig=make_subplots(
        rows=3,
        cols=1,
        shared_xaxes=True,
        vertical_spacing=0.02,
        row_heights=[0.5, 0.2, 0.3]
        )

    # =====================
    # 1. K線圖
    # =====================
    fig.add_trace(
        go.Candlestick(
            x=df.index,
            open=df['Open'],
            high=df['High'],
            low=df['Low'],
            close=df['Close'],
            increasing_line_color='red',
            decreasing_line_color='green',
            name='Price'
            ),
        row=1, col=1
        )

    # =====================
    # 2. 成交量
    # =====================
    vol_colors=['red' if c >= o else 'green'
                  for c, o in zip(df['Close'], df['Open'])]
    fig.add_trace(
        go.Bar(
            x=df.index,
            y=df['Volume'],
            marker_color=vol_colors,
            name='Volume'
            ),
        row=2, col=1
        )

    # =====================
    # 3. MACD
    # =====================
    # Histogram(紅綠柱)
    macd_colors=['red' if v >= 0 else 'green' for v in df['MACDh']]
    fig.add_trace(
        go.Bar(
            x=df.index,
            y=df['MACDh'],
            marker_color=macd_colors,
            name='MACD Hist'
            ),
        row=3, col=1
        )

    # MACD 線
    fig.add_trace(
        go.Scatter(
            x=df.index,
            y=df['MACD'],
            line=dict(color='blue'),
            name='MACD'
            ),
        row=3, col=1
        )
    # Signal 線
    fig.add_trace(
        go.Scatter(
            x=df.index,
            y=df['MACDs'],
            line=dict(color='orange'),
            name='Signal'
            ),
        row=3, col=1
        )

    # =====================
    # 移除非交易日
    # =====================
    date_range=pd.date_range(start=df.index.min(), end=df.index.max())
    breaks=date_range[~date_range.isin(df.index)]
    fig.update_xaxes(
        rangebreaks=[{'values': breaks.tolist()}],
        showspikes=True,
        spikethickness=1,
        spikecolor='blue',
        spikedash='dot',
        spikemode='across'
        )
    fig.update_yaxes(
        showspikes=True,
        spikethickness=1,
        spikecolor='blue',
        spikedash='dot',
        spikemode='across'
        )

    # =====================
    # Layout
    # =====================
    fig.update_layout(
        title='0050 台灣五十 - K線 + 成交量 + MACD',
        width=900,
        height=800,
        xaxis_rangeslider_visible=False,
        hovermode='x unified'
        )

    fig.show()

注意, MACD 的紅綠柱, MACD 線與信號線都是要畫在 row=3 子圖疊在一起, 結果如下 :




Python 學習筆記 : 用 plotly 繪製 K 線圖 (一)

雖然 mplfinance 套件可以繪製精美的 K 線圖, 但那是沒有互動效果的靜態圖表, 也無法直接佈署在網頁應用中. 繪製互動式 K 線圖可以使用 plotly 套件的低階繪圖模組 graph_objects  (注意, plotly.express 模組沒有繪製 K 線圖功能, 它主要用來快速繪製常見統計圖表).

本系列測試文章索引參考 :


關於 plotly.graph_objects 用法參考 :



1. 新版 (v4.0+) Plotly 的輸出方式 : 

Plotly 的繪圖輸出方式已與以往不同, v4.0 之前的 Plotly 主要商業模式是線上繪圖, 預設會將圖表上傳到他們的雲端伺服器 (Chart Studio), 如果要離線繪圖必須明確呼叫 plotly.offline 模組; 然而 Plotly v4 對原本分散的繪圖方式 (離線 vs 雲端) 進行重整, 採取了離線優先原則, 繪製圖表時不需要再區分線上或離線 (原本的雲端功能被拆分出去變成一個獨立的套件 chart-studio), 改為直接呼叫 Figure 物件的方法來處理 :
  • fig.show() : 用於開發過程中的快速預覽 (依據環境自動選擇方式)
  • fig.write_html() : 用於產生最終的報告檔案 (於網頁中繪製圖表)
當呼叫 fig.show() 時, Plotly 會自動偵測程式是在 VS Code, Jupyter Notebook, Google Colab 還是純 Python 腳本中執行, 並選擇最適合的方式來顯示圖表. plotly.offline 的功能已經被 fig.write_html() 取代了, 但為了向下相容, Plotly 官方目前仍保留對 plotly.offline.plot() 的支援. 

注意, fig.write_html() 只是單純輸出 HTML 檔, 不會自動開啟瀏覽器顯示繪圖結果, 如果希望在存檔後順便開啟瀏覽器展示繪製結果, 可以傳入 auto_open=True 參數 (預設 False), 例如 :

fig.write_html("plot.html", auto_open=True)  

或者先呼叫 write_html() 再呼叫 show() :

fig.write_html("plot.html")
fig.show()

除此之外, Plotly v4.0 也把以前的獨立套件 plotly_express 納入主套件中成為 plotly.express 模組, 確立了 Plotly 未來高階 API 使用 plotly.express; 低階 API 使用 plotly. graph_objects 的雙軌應用模式. 


2. 建立 CandleStick 物件繪製 K 線圖 : 

首先匯入 plotly.graph_objects 模組, 通常取簡名為 go :

import plotly.graph_objects as go  

plotly.graph_objects 模組中有一個 Candlestick 用來繪製 K 線圖 : 

>>> import plotly.graph_objects as go   
>>> dir(go)   
['AngularAxis', 'Annotation', 'Annotations', 'Bar', 'Barpolar', 'Box', 'Candlestick', 'Carpet', 'Choropleth', 'Choroplethmap', 'Choroplethmapbox', 'ColorBar', 'Cone', 'Contour', 'Contourcarpet', 'Contours', 'Data', 'Densitymap', 'Densitymapbox', 'ErrorX', 'ErrorY', 'ErrorZ', 'Figure', 'FigureWidget', 'Font', 'Frame', 'Frames', 'Funnel', 'Funnelarea', 'Heatmap', 'Histogram', 'Histogram2d', 'Histogram2dContour', 'Histogram2dcontour', 'Icicle', 'Image', 'Indicator', 'Isosurface', 'Layout', 'Legend', 'Line', 'Margin', 'Marker', 'Mesh3d', 'Ohlc', 'Parcats', 'Parcoords', 'Pie', 'RadialAxis', 'Sankey', 'Scatter', 'Scatter3d', 'Scattercarpet', 'Scattergeo', 'Scattergl', 'Scattermap', 'Scattermapbox', 'Scatterpolar', 'Scatterpolargl', 'Scattersmith', 'Scatterternary', 'Scene', 'Splom', 'Stream', 'Streamtube', 'Sunburst', 'Surface', 'Table', 'Trace', 'Treemap', 'Violin', 'Volume', 'Waterfall', 'XAxis', 'XBins', 'YAxis', 'YBins', 'ZAxis', 'bar', 'barpolar', 'box', 'candlestick', 'carpet', 'choropleth', 'choroplethmap', 'choroplethmapbox', 'cone', 'contour', 'contourcarpet', 'densitymap', 'densitymapbox', 'funnel', 'funnelarea', 'heatmap', 'histogram', 'histogram2d', 'histogram2dcontour', 'icicle', 'image', 'indicator', 'isosurface', 'layout', 'mesh3d', 'ohlc', 'parcats', 'parcoords', 'pie', 'sankey', 'scatter', 'scatter3d', 'scattercarpet', 'scattergeo', 'scattergl', 'scattermap', 'scattermapbox', 'scatterpolar', 'scatterpolargl', 'scattersmith', 'scatterternary', 'splom', 'streamtube', 'sunburst', 'surface', 'table', 'treemap', 'violin', 'volume', 'waterfall']

呼叫 Candlestick 類別的建構式 Candlestick() 會建立一個 Candlestick 物件, 這在 Plotly 文件中被稱為 Trace 物件 (畫布上的數據內容, 例如一組 K 線). 一個完整的 Plotly 圖表 (Figure) 是由 data 和 layout 兩大部分組成的, 而 Candlestick 物件就是放在 data 清單中的一個成員. 

Candlestick() 建構式的參數如下表 : 


 參數名稱  說明
 x  時間序列數據(通常為日期字串或 datetime 物件)。
 open  開盤價序列 (Open prices)。
 high  最高價序列 (High prices)。
 low  最低價序列 (Low prices)。
 close  收盤價序列 (Close prices)。
 increasing_line_color  上漲時 K 線外框與影線的顏色(例:'red')。
 decreasing_line_color  下跌時 K 線外框與影線的顏色(例:'green')。
 name  此數據序列在圖例中顯示的名稱。
 text  滑鼠懸停時顯示的額外文字內容。
 hoverinfo  設定懸停顯示的資訊內容(如 'all', 'x+y' 等)。
 opacity  設定 K 線圖層的透明度(0.0 到 1.0)。
 whiskerwidth  設定上下影線相對於 K 線實體寬度的比例。


其中前 7 個參數 x, open, high, low, close, 以及 increasing_line_color 與 decreasing_line_color (漲跌顏色, 漲紅跌綠) 最常用, 例如 : 

>>> price=go.Candlestick( 
    x=['2026-05-01', '2026-05-02'],
    open=[100, 110],
    high=[115, 120],
    low=[95, 105],
    close=[110, 105],
    increasing_line_color='red',
    decreasing_line_color='green'    
    )
>>> type(price)  
<class 'plotly.graph_objs._candlestick.Candlestick'>   

這樣就建立了一個 CandleStick 物件了. 接下來只要將此 trace 放入串列, 傳給 go.Figure() 的 data 參數即可建立 Figure 畫布物件 : 

>>> fig=go.Figure(data=[price])   
>>> type(fig)   
<class 'plotly.graph_objs._figure.Figure'>  

最後呼叫 fig.show() 即可展示所繪製之圖表 :

>>> fig.show()   

fig.show() 會開啟一個 127.0.0.1:port 的網頁來繪製圖表 :




可見當滑鼠移到 K 棒上時就會動態地彈出其 OHLC 價格資訊. 注意, Plotly 預設會在 K 線圖底下開啟一個範圍滑桿 (RangeSlider 物件), 可透過滑動或縮放來觀察不同時段的價格細節. 若想取消此預設之範圍滑桿, 可呼叫 fig.update_layout() 並傳入 xaxis_rangeslider_visible=False 參數 :

>>> fig.update_layout(xaxis_rangeslider_visible=False)   
Figure({
    'data': [{'close': [110, 105],
              'decreasing': {'line': {'color': 'green'}},
              'high': [115, 120],
              'increasing': {'line': {'color': 'red'}},
              'low': [95, 105],
              'open': [100, 110],
              'type': 'candlestick',
              'x': [2026-05-01, 2026-05-02]}],
    'layout': {'template': '...', 'xaxis': {'rangeslider': {'visible': False}}}
})
>>> fig.show()  




fig.update_layout() 是 Plotly 中最核心的佈局控制方法, 它負責處理圖表內除了數據本身以外的所有視覺元素, 例如標題, 座標軸, 圖例及畫布大小等, 常用參數如下 : 


 參數名稱  說明
 title  圖表標題。可傳入字串或 dict(包含 text, font, x, y 等屬性)。預設為 None。
 width / height  設定畫布的寬度與高度(像素)。預設通常隨容器自動縮放。
 xaxis / yaxis  設定座標軸屬性(如標題、範圍、網格)。例如 xaxis_title='時間'。
 xaxis_rangeslider_visible  設定 X 軸下方的範圍滑桿是否顯示。預設值 True。
 showlegend  是否顯示圖例。預設值:當有多組數據序列時為 True。
 legend  設定圖例的位置與外觀(如 x, y 位置、orientation 'h' 或 'v')。
 margin  設定圖表與邊界的距離。傳入 dict(l, r, t, b) 分別代表左、右、上、下邊距。
 template  設定圖表主題(如 'plotly_dark', 'ggplot2', 'seaborn' 等)。預設為 'plotly'。
 hovermode  設定懸停資訊的觸發模式('x', 'y', 'closest', False)。預設為 'closest'。
 font  設定圖表全域字體(family, size, color)。預設字體依環境而定。
 paper_bgcolor  設定整張畫布(紙張)的背景顏色。預設為 '#fff'。
 plot_bgcolor  設定繪圖區域(座標軸內)的背景顏色。預設為 '#E5ECF6'。


這些參數中以 title, width, heigh, 與 showlegend 等最常用. 

下面範例從 yfinance 取得股價資料後用 plotly 繪製 K 線圖 :

# plotly_candlestick_1.py
import yfinance as yf
import plotly.graph_objects as go

if __name__ == "__main__":
    df=yf.download('0050.tw', start='2026-03-01', end='2026-04-30', auto_adjust=True)
    df.columns=df.columns.map(lambda x: x[0])
    price=go.Candlestick(
        x=df.index,
        open=df['Open'],
        high=df['High'],
        low=df['Low'],
        close=df['Close'],
        increasing_line_color='red',
        decreasing_line_color='green'    
        )
    fig=go.Figure(data=[price])
    fig.update_layout(
        title='台灣五十股價',
        width=800,
        height=600,
        xaxis_rangeslider_visible=False
        )
    fig.show() 

此處以 width 與 height 參數設定畫布尺寸, 以 title 設定圖表標題, 結果如下 :




3. 移除 K 線圖中的非交易日空格 : 

仔細看上面範例中的 K 線圖, 會發現例假日股市休息, 從 yfinance 取得的收盤資料都會跳過這些日期, 只傳回交易日之資料, 但 Plotly 在繪製 K 線圖時會自動把 X 軸的日期序列補齊, 但因為無 OHLC 欄位值, 所以 K 棒為空格, 看起來不連續, 這可以透過呼叫 fig.update_xaxes() 方法, 並傳入rangebreaks 參數來移除, 其值為一個非交易日的日期串列的字典 (鍵為 values), 例如 :

# plotly_candlestick_2.py
import yfinance as yf
import plotly.graph_objects as go
import pandas as pd  

if __name__ == "__main__":
    df=yf.download('0050.tw', start='2026-03-01', end='2026-04-30', auto_adjust=True)
    df.columns=df.columns.map(lambda x: x[0])
    price=go.Candlestick(
        x=df.index,
        open=df['Open'],
        high=df['High'],
        low=df['Low'],
        close=df['Close'],
        increasing_line_color='red',
        decreasing_line_color='green'    
        )
    fig=go.Figure(data=[price])
    # 移除 X 軸的非交易日
    date_range=pd.date_range(start=df.index.min(), end=df.index.max())
    breaks=date_range[~date_range.isin(df.index)]  # 取出 df 空缺之日期
    breaks_list=breaks.tolist()  # 轉成串列
    fig.update_xaxes(rangebreaks=[{'values': breaks_list}])  # 移除非交易日空缺
    fig.update_layout(
        title='台灣五十股價',
        width=800,
        height=600,        
        xaxis_rangeslider_visible=False
        )
    fig.show() 

此處使用 Pandas 的 date_range() 函式取得 df 起迄日期間的所有連續日期序列, 然後用串列運算與 df 比對抓出 df 中空缺之日期即為非交易日, 轉成串列後做為字典 values 鍵之值. 結果如下 :




可見非交易日的空 K 棒已消失, 整個 K 線圖 X 軸看起來是連續無空缺了. 

fig.update_xaxes() 常用參數如下表 :


 參數名稱  說明
 title  X 軸標題。可傳入字串或 dict(text='...', font=...)。
 type  座標軸類型:'linear' (數值), 'date' (時間), 'category' (類別), 'log' (對數)。
 range  設定顯示範圍 [min, max],對於時間軸 ['2026-01-01', '2026-05-01']。
 rangebreaks  隱藏特定時間區段(如週末或收盤時段)。常用於金融圖表。
 rangeslider_visible  是否顯示底部的時間範圍滑桿(True/False)。
 showgrid  是否顯示垂直網格線(True/False)。預設為 True。
 gridcolor / gridwidth  設定網格線的顏色與寬度。
 tickformat  設定刻度文字格式(如 '%Y-%m-%d' 顯示日期,'.2f' 顯示兩位小數)。
 tickangle  刻度文字的旋轉角度(例如 -45 代表順時針旋轉 45 度)。
 side  X 軸的位置。可設定為 'bottom' (預設) 或 'top'。
 nticks  設定刻度的最大數量(Plotly 會嘗試在此範圍內自動分配)。
 fixedrange  是否鎖定縮放(True 代表禁止使用者縮放該軸)。
 showspikes  是否顯示 Y 軸輔助線。預設 False (不顯示)。
 spikemode  X 軸輔助線模式 : 'toaxis' (預設指向坐標軸)、'across' (橫跨整個繪圖區)。
 spikecolor  X 軸輔助線顏色 : 顏色名稱或色碼 (預設 None 視資料點自動指配)。
 spikethickness  X 軸輔助線厚度 : 預設 3px。
 spikedash  X 軸輔助線樣式 : 'dash' (預設), 'dot', 'solid'。


除了有 X 軸設定方法 fig.update_xaxes() 外, 還有 Y 軸設定方法 fig.update_yaxes(), 參數結構類似 :


 參數名稱  說明
 title  Y 軸標題。可設定為字串或 dict(text='...', font=...)。
 type  座標軸類型:'linear' (預設)、'log' (對數軸)、'date'。
 range  設定 Y 軸顯示數值範圍。例如 [0, 1000]。
 autorange  是否自動縮放範圍:True, False 或 'reversed'(數值由大到小反轉)。
 showgrid  是否顯示水平網格線(True/False)。
 zeroline  是否加粗顯示數值為 0 的那條基準線。預設為 True。
 tickformat  刻度數值格式。例如 ',d' (千分位整數)、'.2f' (兩位小數)、'%' (百分比)。
 tickprefix / ticksuffix  在刻度數值前後加上符號(如 tickprefix='$' 或 ticksuffix='元')。
 side  Y 軸的位置。可設定為 'left' (預設) 或 'right' (常見於雙 Y 軸設計)。
 secondary_y  多軸圖表中指定此資料是否為第二個 Y 軸(配合 make_subplots 使用)。
 fixedrange  是否固定 Y 軸。設為 True 時,使用者無法透過滑鼠縮放 Y 軸方向。
 showspikes  是否顯示 Y 軸輔助線。預設 False (不顯示)。
 spikemode  Y 軸輔助線模式 : 'toaxis' (預設指向坐標軸)、'across' (橫跨整個繪圖區)。
 spikecolor  Y 軸輔助線顏色 : 顏色名稱或色碼 (預設 None 視資料點自動指配)。
 spikethickness  Y 軸輔助線厚度 : 預設 3px。
 spikedash  Y 軸輔助線樣式 : 'dash' (預設), 'dot', 'solid'。


4. 添加輔助十字線 : 

輔助十字線是當滑鼠在畫布上移動時會出現一個平行於 X 與 Y 軸十字線的互動效果, 開啟輔助十字線顯示需要將 fig.update_xaxes() 與 fig.update_yaxes() 的 showspike 參數設為 True. 例如 :

# plotly_candlestick_3.py
import yfinance as yf
import plotly.graph_objects as go
import pandas as pd

if __name__ == "__main__":
    df=yf.download('0050.tw', start='2026-03-01', end='2026-04-30', auto_adjust=True)
    df.columns=df.columns.map(lambda x: x[0])
    price=go.Candlestick(
        x=df.index,
        open=df['Open'],
        high=df['High'],
        low=df['Low'],
        close=df['Close'],
        increasing_line_color='red',
        decreasing_line_color='green'    
        )
    fig=go.Figure(data=[price])
    # 移除 X 軸的非交易日
    date_range=pd.date_range(start=df.index.min(), end=df.index.max())
    breaks=date_range[~date_range.isin(df.index)]  # 取出 df 空缺之日期
    breaks_list=breaks.tolist()  # 轉成串列
    fig.update_xaxes(rangebreaks=[{'values': breaks_list}],
                     showspikes=True)
    fig.update_yaxes(showspikes=True)    
    fig.update_layout(
        title='台灣五十股價',
        width=800,
        height=600,        
        xaxis_rangeslider_visible=False
        )
    fig.show() 

此處將 X/Y 軸設定參數 showspikes 設為 True 後, 當滑鼠移到 K 棒上面時就會顯示指向座標軸的 輔助線, 由於 spikemode 預設 'toaxis', 故輔助線是指向坐標軸, 而非貫穿繪圖區的十字線; 由於 spikecolor 預設為 None, 所以滑鼠移到紅 K 棒時輔助線為紅色, 移到綠 K 棒時輔助線為綠色 :





下面是傳入全部輔助線參數的範例 :

# plotly_candlestick_4.py
import yfinance as yf
import plotly.graph_objects as go
import pandas as pd

if __name__ == "__main__":
    df=yf.download('0050.tw', start='2026-03-01', end='2026-04-30', auto_adjust=True)
    df.columns=df.columns.map(lambda x: x[0])
    price=go.Candlestick(
        x=df.index,
        open=df['Open'],
        high=df['High'],
        low=df['Low'],
        close=df['Close'],
        increasing_line_color='red',
        decreasing_line_color='green'    
        )
    fig=go.Figure(data=[price])
    # 移除 X 軸的非交易日
    date_range=pd.date_range(start=df.index.min(), end=df.index.max())
    breaks=date_range[~date_range.isin(df.index)]  # 取出 df 空缺之日期
    breaks_list=breaks.tolist()  # 轉成串列
    fig.update_xaxes(rangebreaks=[{'values': breaks_list}],
                     showspikes=True,
                     spikethickness=1,
                     spikecolor='blue',
                     spikedash='dot',
                     spikemode='across')
    fig.update_yaxes(showspikes=True,
                     spikethickness=1,
                     spikecolor='blue',
                     spikedash='dot',
                     spikemode='across')  
    fig.update_layout(
        title='台灣五十股價',
        width=800,
        height=600,        
        xaxis_rangeslider_visible=False,
        #hovermode='x'
        )
    fig.show() 

此例傳入輔助線參數更改預設值, 結果如下 :




可見 spikethickness=1 讓輔助線變細了, spikecolor='blue' 則使其顏色不論紅 K 或綠 K 都顯示藍色; 而 spikemode='across' 則使輔助線貫穿整個畫布成為名符其實的十字線. 注意, 預設須當滑鼠移到 K 棒上時才會顯示輔助線, 如果要讓滑鼠在畫布任何位置都顯示輔助線, 則在呼叫 fig.update_layout() 要傳入 hovermode='x' 參數, 因為 K 線圖是按時間 (X 軸) 排列的, 每一格時間通常都有資料, 所以滑鼠左右移動時, 輔助線會非常流暢地跟隨. 

2026年4月30日 星期四

Python 學習筆記 : 用 plotly 繪製互動式圖表 (三)

本篇旨在測試 Plotly 的圖表匯出功能. 


Plotly 支援多種檔案類型匯出, 可呼叫下表中 Figure 物件的方法匯出 :


Figure 物件的檔案匯出方法 說明
fig.write_image(file, **kwargs) 匯出為靜態圖片(PNG、JPEG、SVG、PDF 等),需安裝 kaleido
fig.write_html(file, **kwargs) 匯出為互動式 HTML 檔,可直接用瀏覽器開啟
fig.to_html(full_html=True, include_plotlyjs='cdn') 將圖表轉為 HTML 字串(用於網頁內嵌或 API 回傳)
fig.to_json() 將圖表轉為 JSON 格式(適合儲存、API 傳遞、版本控管)
fig.write_json(file) 將圖表 JSON 結構直接寫入檔案
fig.to_dict() 將圖表轉為 Python 字典格式,可進一步程式操作


注意, fig.write_image() 方法須依賴 kaleido 模組, 這是 Plotly 官方推出的匯圖引擎模組, 用來將 Plotly 圖表儲存為靜態圖片.


1. 匯出圖檔 : 

呼叫 Figure 物件的 write_image() 方法可將繪製的圖表匯出成圖片檔 (支援 PNG, JPG, SVG, PDF 等檔案類型), 其參數結構如下 :

fig.write_image(
    file,             # 必填,檔案路徑字串或類似檔案的物件
    format=None,      # 圖片格式,如 'png'、'jpeg'、'svg'、'pdf',若省略會自動從副檔名判斷
    width=None,       # 圖片寬度(像素),預設為圖表原始寬度
    height=None,      # 圖片高度(像素),預設為圖表原始高度
    scale=1,          # 圖片縮放倍數(例如 2 表示解析度加倍)
    validate=True,    # 是否檢查圖表是否有效(預設 True)
    engine='kaleido'  # 使用的圖像引擎,預設為 'kaleido'
    )

不過使用此方法之前須先安裝 Plotly 的 kaleido 模組, 而且 plotly 也要提升至最新版 :

pip install kaleido   
pip install plotly -U

在前一篇測試中使用了 plotly.express 來繪製長條圖, 下列沿用此範例來匯出所繪製的圖檔 : 

# plotly_chart_export_1.py
import plotly.express as px
import pandas as pd
import os

# 1. 資料來源
data={
    '月份': ['一月', '二月', '三月', '四月', '五月'],
    '營收': [120000, 135000, 99000, 150000, 170000]    
    }

# 2. 建立 Figure 圖表物件
# 注意:這裡設定了 width 和 height,匯出圖片時會以此為基準
fig=px.bar(data, x='月份', y='營收', width=800, height=600, title="月營收統計圖")

# 3. 顯示圖表 (選用)
fig.show()

# 4. 匯出圖檔 
# 建立儲存目錄(選用,避免檔案雜亂)
if not os.path.exists("output"):
    os.mkdir("output")
# 匯出為 PNG
fig.write_image("output/revenue_report.png", scale=2)
# 匯出為 JPG
fig.write_image("output/revenue_report.jpg", scale=2)
print("圖檔已匯出至 output 資料夾中。")

執行結果除了 fig.show() 會開啟瀏覽器顯示長條圖外, 也會在目前工作目錄下建立 output 子目錄存放匯出的兩個圖檔 :

>>> %Run plotly_chart_export_1.py   
圖檔已匯出至 output 資料夾中。




2. 匯出網頁 : 

呼叫 fig.write_html() 可將繪製之圖表匯出為 HTML 檔, 若傳入 include_plotlyjs='cdn' 參數會使用 CDN 的 plotly 函式庫, 這樣匯出的 HTML 檔較小但須連網才能看到互動圖表; 否則會將 plotly 函式庫一同匯出, 檔案較大些 (約 4MB) 但不須連網, 離線開啟網頁即可看到互動圖表.

程式碼如下 : 

# plotly_chart_export_2.py
import plotly.express as px
import pandas as pd
import os

# 1. 資料來源
data={
    '月份': ['一月', '二月', '三月', '四月', '五月'],
    '營收': [120000, 135000, 99000, 150000, 170000]    
    }

# 2. 建立 Figure 圖表物件
fig=px.bar(data, x='月份', y='營收', width=800, height=600, title="月營收統計圖 (互動式 HTML)")

# 3. 建立儲存目錄
output_dir="output"
if not os.path.exists(output_dir):
    os.makedirs(output_dir)

# --- 匯出 HTML  ---
# 方式 A:標準匯出 (將 Plotly.js 核心程式碼打包進去,檔案約 4 MB,可離線開啟)
fig.write_html(os.path.join(output_dir, "report_full.html"))

# 方式 B:輕量化匯出 (使用 CDN 連結,檔案僅約 50KB,開啟時需連網載入 JS)
fig.write_html(
    os.path.join(output_dir, "report_cdn.html"), 
    include_plotlyjs='cdn'
    )

print(f"✅ HTML 檔案已匯出至 {output_dir} 資料夾。")
print("- report_full.html (可離線檢視)")
print("- report_cdn.html (體積小,需連網)")

# 顯示圖表
fig.show()

執行結果如下 :

>>> %Run plotly_chart_export_2.py
✅ HTML 檔案已匯出至 output 資料夾。
- report_full.html (可離線檢視)
- report_cdn.html (體積小,需連網)

開啟 output 資料夾下的網頁檔即可看到長條圖 :




2025年7月13日 星期日

Python 學習筆記 : 用 Flask 架站 (二) 模板與靜態檔案

這篇筆記大概是 2019 年開始學 Flask 時寫的, 寫了一半就擱著改學 Django 了. 最近因為在忙  Mapleboard 架站, 打算用 Flask app 作為 Line Bot webhook 後台, 所以就把 2023 年買的 "實戰 Python Flask 開發" 這本書找出來看, 順便將這篇老筆記寫完. 

本篇旨在測試 Flask 的 Jinja2 模板與靜態檔案用法. 

本系列之前的文章參考 :


參考書籍 : 
  1. 實戰 Python Flask 開發 (碁峰, 2022, 佐藤昌基等)
  2. 超圖解 Python 程式設計入門 (旗標, 2019,  趙英傑) 第 9 章
  3. Python 網頁框架超集合 (深智, 2022, 劉長銘) 第 8 章
模板檔案是在 htm/html 檔案中嵌入模板語法使用模板可以讓應用程式與網頁 UI 分離, 這也是 MTV (Model-Template-View) 架構的精神所在, 參考 :  



一. 建立模板與靜態檔案資料夾 : 

Flask 本身自帶 Jinja2 模板引擎, 首先必須在網站專案目錄底下建立一個 templates 目錄來存放模板網頁檔案; 其次要建立 static 子目錄來存放靜態檔案, 例如圖檔, 樣式檔 (.css) 與 Javascript 程式檔 (.js), 結構如下 : 

myapp 
      |___  app.py
      |___ static 
      |            |___ js 
      |            |          |___ app.js
      |            |___ css
      |            |          |___ style.css
      |            |___ images
      |                       |___ logo.ico
      |___  templates
                   |___ index.htm
                   |___ hello.htm
                   .....
 
模板網頁檔全部放在 templates 子目錄下; 靜態檔案如果不多就不用分 js, css, 與 images 子目錄, 全部放在 static 子目錄下即可.    


二. Jinja2 模板引擎語法 : 

Flask 是在 Werkzeug 開發伺服器與 Jinja2 範本引擎基礎上建立起來的, 而 Jinja2 又是從 Django 範本引擎發展而來, 效能更好, 其語法幾乎與 Django 模板語言雷同, 以下摘要整理 Jinja2 語法 : 


1. 模板語法的三元素 :

模板網頁中可以嵌入如下三種元素 : 變數, 標籤, 以及註解 : 


 模板語法元素 說明
 {{ 變數 }} 來自 views.py 的變數用來直接替換該位置內容
 {% 標籤 %} 標籤用來控制資料顯示邏輯 (判斷 & 迴圈)
 {# 註解 #} 註解會被模板引擎忽略



2. 分支 (判斷) 語法 :


 單一分支 雙重分支 多重分支
 {% if 條件式 %}
 輸出網頁
 (% endif %}
 {% if 條件式 %}
 輸出網頁1
 {% else %}
 輸出網頁2
 (% endif %}
 {% if 條件式 %}
 輸出網頁1
 {% elif %}
 輸出網頁2
 {% elif %}
 輸出網頁3
 {% else %}
 輸出網頁4
 (% endif %}


注意, endif 的 end 與 if 之間沒有空格, elif 的 el 與 if 之間也是, 若有空格會造成語法錯誤.


3. 迴圈語法 :

模板語言迴圈透過遍歷一個可迭代物件來輸出網頁, 其關鍵字包括 for, in, endfor, empty, 與 reversed 標籤, 語法如下 : 


 順序迭代 倒序迭代
 {% for 變數 in 可迭代變數 %}
 輸出網頁 1
 {% empty %}
 迴圈為空時之輸出網頁 2
 {% endfor %}
 {% for 變數 in 可迭代變數 reversed %}
 輸出網頁 1
 {% empty %}
 迴圈為空時之輸出網頁 2
 {% endfor %}


注意, empty 標籤必須放在 for 迴圈的最後面 (即 endfor 標籤前面), 當迴圈為空 (即可迭代變數元素個數為 0) 時, 網頁 1 將不會輸出, 而是輸出夾在 empty 與 endfor 標籤中間的網頁 2.

另外還有用來控制迴圈的 loop 物件屬性值 (Django 使用 forloop) :


 迴圈變數 loop 的屬性 說明
 loop.counter 迴圈計數器 (1 起始)
 loop.counter0 迴圈計數器 (0 起始)
 loop.revcounter 倒數之迴圈計數器 (由總圈數遞減至 1)
 loop.revcounter0 倒數之迴圈計數器 (由總圈數遞減至 0)
 loop.first 是否為第一個迴圈 (True/False)
 loop.last 是否為最後一個迴圈 (True/False)
 loop.parentloop 上一層迴圈的 loop 變數


4. 過濾器 (filter) :

過濾器其實就是模板引擎內建的函數, 用來在輸出變數前依據需要對變數內容或格式做修正或運算. 用法是在變數後面以管線符號 "|" 串接過濾器名稱, 可同時串接多個過濾器, 模板引擎會依照順序處理 :

{{ 變數 | 過濾器1 | 過濾器2  | 過濾器3 | ... }} 

常用的過濾器如下表 :


 常用模板過濾器 說明 範例
 add 加上一個數值或做字串串接 {{ var | add:"2" }} 
 addslashes 跳脫特殊字元 (前面加上倒斜線 \) {{ var | addslashes }} 
 capfirst 將字串變數的首字元轉為大寫 {{ var | capfirst }}
 center  將字串變數冠上指定空格後置中 {{ var | center: "5" }}
 cut 從字串變數中刪除指定子字串 {{ var | cut: " " }} 刪除空白字元
 date 將 datetime 變數以指定格式顯示 {{ var | date: "D d M Y" }}
 default 當變數為空字串時輸出預設字串 {{ var | default: "預設值" }}
 dictsort 將串列變數中的字典依指定鍵遞增排序 {{ var | dictsort: "鍵" }}
 dictsortreversed 將串列變數中的字典依指定鍵遞減排序 {{ var | dictsortreversed: "鍵" }}
 divisibleby 測試數值變數是否可被指定數整除 {{ var | divisibleby: "7" }} 
 escape 跳脫字串變數內的 HTML 標籤 {{ var | escape }}
 filesizeformat 以 KB/MB 等單位顯示檔案大小 {{ var | fileformat }}
 first 取出串列變數中的第一個元素 {{ var | first }}
 join 將串列變數中的元素以指定字串串接 {{ var | join: "," }} 
 last 取出串列變數中的最後一個元素 {{ var | last }} 
 length 傳回串列變數的元素個數 (長度) {{ var | length }} 
 length_is 測試變數的長度是否為指定之長度 {{ var | length_is: "5" }} 
 linebreaks 將字串中的 \n 轉成 HTML 之 br 與 p {{ var | linebreaks }} 
 linebreaksbr 將字串中的 \n 轉成 HTML 之 br {{ var | linebreaksbr }} 
 linenumbers 將顯示之文字加上行號 {{ var | linenumbers }} 
 ljust 將字串變數冠上指定空格後置左 {{ var | ljust: "5" }}
 lower 將字串變數轉成小寫 {{ var | lower }}
 make_list 將字串拆成字元串列 {{ var | make_list }}
 random 隨機取出串列中的一個元素 {{ var | random }}
 rjust 將字串變數冠上指定空格後置右 {{ var | rjust: "5" }} 
 safe 以 HTML 格式讀取字串 (不須跳脫) {{ var | safe }} 
 slice 取出字串中的指定切片 {{ var | slice:":2" }} 
 slugify 將字串中的空白以 dash '-' 取代 {{ var | slugify }} 
 stringformat 以科學表示法顯示數字 {{ var | stringformat: "E" }} 
 striptags 移除字串中的 HTML 標籤 {{ var | striptags }} 
 title 將字串中每個字的第一字元大寫 {{ var | title }} 
 truncatechars 將字串中超出指定長度部分以 ... 取代 {{ var | truncatechars:"5" }} 
 upper 將字串轉成大寫 {{ var | upper }} 
 wordcount 傳回字串中的字數 {{ var | wordcount }} 
 yesno 依變數值為 True/False/None 轉成指定值 {{ var | yesno:"是, 否, 取消" }} 


過濾器用法摘要如下 :
  1. wordcount 是計算 "字" 數而非 "字元" 數, 亦即以空格為單位做區隔, 例如 "您好嗎?" 的 wordcount 是 1; 而 "您 好 嗎 ?" 的 wordcount 是 4. 
  2. 若直接輸出變數 {{ var }}, 模板引擎會自動將 HTML 標籤的角括號轉成 > 與 <, 但可以用 safe 過濾器阻止自動轉換. 
  3. linebreaksbr 會將字串變數中的跳行字元 "\n" 改為 "<br>", 例如 "Hello\nWorld" 會被轉成 "Hello<br>World". 而 linebreaks 則除了做此轉換外還會在外面套上 p 標籤, 例如 "Hello\nWorld" 會被轉成 "<p>Hello<br>World</p>". 參考 :
    # https://docs.djangoproject.com/en/2.2/ref/templates/builtins/#date
  4. date 過濾器格式與 PHP 的極類似, 如果要製作 "2019-10-03 08:43:12" 這樣的輸出, 則其格式為 date:"Y-m-d H:i:s", 
  5. slice 的切片用法與 Python 字串或串列切片的用法一樣. 

三. 模板網頁的繼承 : 

因為 HTML 網頁頭尾都有重複的部分, 因此模板引擎提供 extends 標籤, 可以讓子模板網頁繼承父模板網頁以簡化網頁內容, 其概念與物件導向中物件的繼承類似. 模板可以連續繼承, 所以只有最上層父模板才具有形式上完整的 HTML 語法架構, 子模板透過繼承當然最終也是完整網頁, 只是形式上簡化了而已. 

在父模板中定義一個 (待填入) 區塊的語法為 :

{% block block_name %}{% endblock %}   

子模板會以具體的網頁片段填入 block 與 endblock 之間. 

子模板繼承父模板時使用 extends 標籤, 指令如下 :

{% extends "father_template.htm" %}

注意, extends 指令須放在模板網頁檔的第一行, 且父模板檔名需用引號括起來 (單引號或雙引號均可). 繼承父模板後就可以在子模板中以具體的網頁片段填入指定的區塊中 :

{% block block_name %} 網頁片段 {% endblock %}

最上層的父模板具有完整的網頁結構, 通常取名為 base.htm, 例如 :

<!--base.htm-->
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>{% block title %}{% endblock %}</title>
  {% block link %}{% endblock %}
  {% block script %}{% endblock %}
  <style>
  {% block style %}{% endblock %}
  </style>
</head>
<body>
  {% block body %}{% endblock %}
</body>
</html>

此基礎父模板將 HTML 網頁中會變動的部分作成可嵌入之區塊, 主要是 head 裡面的 title, link, script, 以及 style, 以及 body 等五個元素. 繼承 base.htm 的子模版就可以寫成如下的 htm 檔 :

{% extends "base.htm" %}
{% block title %}
   網頁標題
{% endblock %}
{% block link %}
   link 元素群 (樣式檔)
{% endblock %}
{% block script %}
   script 元素群 (程式檔)
{% endblock %}
{% block body %}
   網頁內容 + script 元素群
{% endblock %}

如果要套用網頁框架專案, 例如 Bootstrap, 則其模版網頁可以寫成 : 

<!--bootstrap.htm-->
{% extends "base.htm" %}
{% block link %}
  <link rel="stylesheet" href="https://unpkg.com/bootstrap@3.3.7/dist/css/bootstrap.min.css">
  <link rel="stylesheet" href="https://unpkg.com/bootstrap@3.3.7/dist/css/bootstrap-theme.min.css">
  <link rel="stylesheet" href="https://unpkg.com/bootstrap-table@1.15.4/dist/bootstrap-table.min.css">
{% endblock %}
{% block script %}
  <script src="https://unpkg.com/jquery@3.4.1/dist/jquery.min.js"></script>
  <script src="https://unpkg.com/bootstrap@3.3.7/dist/js/bootstrap.min.js"></script>
  <script src="https://unpkg.com/bootstrap-table@1.15.4/dist/bootstrap-table.min.js"></script>
{% endblock %}

專案中的其他子模板就可以繼承此 boostrap.htm, 這樣整個專案就會有一致的 Bootstrap 風格了. 

參考 : 


2025年6月18日 星期三

Python 學習筆記 : 用 plotly 繪製互動式圖表 (二)

最近學完了一套很棒的 Python Wen app 工具 Streamlit, 它以 st.plotly_chart() 函式原生支援 Plotly 所繪製的互動式圖片, 為了做這項測試我複習了一遍 Plotly 的用法 (第一次學習 Plotly 已是 2022 年的事了), 同時也發現了它在資料視覺化應用上的價值 : 互動性 + Web-based + 豐富的圖形類型, 值得好好來學習. 

本系列之前的測試文章參考 :


更多 Python 資料視覺化學習筆記參考 : 


在繼續 plotly 的學習之前, 先摘要整理一下背景知識. 


1. plotly.graph_objs 與 plotly.express 模組 :

plotly 套件是由加拿大 Plotly 公司於 2016 年推出的 Python 開源互動式繪圖套件, 最早提供的是底層低階的 plotly.graph_objs 模組, 其功能強大提供極高的自訂能力, 可建立任何圖表元素, 軸線, 圖例, 動畫控制等, 讓使用者以類似拼積木的方式手動組合整個圖表, 但缺點是語法較冗長, 對初學者而言學習曲線較陡. 

Plotly 公司為了簡化視覺化流程於 2019 年推出快捷版繪圖模組 plotly.express 加入 plotly 主套件中, 主打類似 Seaborn 的 "一行程式即可產生漂亮圖表" 的快捷繪圖功能, 事實上 plotly.express 只是將繪圖細節做了巧妙包裝, 它在背後其實是去呼叫底層的 plotly.graph_objs 模組與 Figure 物件來完成大量的手動圖表建構工作, 兩者的功能比較如下表 :


功能/模組 plotly.graph_objs plotly.express
抽象層級 低階,需自行設定圖表細節 高階,簡化流程,快速繪圖
程式碼簡潔度 較冗長,需組裝圖形元素 非常簡潔,幾行即可完成
彈性與客製化 高度可自訂,適合複雜圖形 中等,適用常見圖形設定
資料來源格式 list、dict、DataFrame 皆可 建議使用 pandas DataFrame
支援圖形種類 完整支援所有圖表 僅支援常見圖表(如 bar, line, scatter)
使用場景 需要精細控制、Dash 整合 資料探索、快速視覺化
動畫與互動支援 完全支援,自訂自由度高 部分支援,設定較簡單


後續測試將以簡潔快速的 plotly.express 模組為主, 它所提供的 30 種繪圖函式摘要說明如下表 :


 plotly.express 模組的繪圖函式  說明
 scatter(data_frame, x, y, **kwargs) 散點圖
 line(data_frame, x, y, **kwargs) 折線圖
 area(data_frame, x, y, **kwargs) 面積圖(堆疊折線圖)
 bar(data_frame, x, y, **kwargs) 長條圖
 funnel(data_frame, x, y, **kwargs) 漏斗圖
 timeline(data_frame, x, y, **kwargs) 時間軸圖(Gantt 類型)
 pie(data_frame, names, values, **kwargs) 圓餅圖
 sunburst(data_frame, path, values, **kwargs) 旭日圖
 treemap(data_frame, path, values, **kwargs) 樹狀圖
 icicle(data_frame, path, values, **kwargs) 冰柱圖
 scatter_matrix(data_frame, dimensions, **kwargs) 散點矩陣圖
 parallel_coordinates(data_frame, dimensions, **kwargs) 平行座標圖
 parallel_categories(data_frame, dimensions=None, **kwargs) 平行分類圖
 density_contour(data_frame, x, y, **kwargs) 密度等高線圖
 density_heatmap(data_frame, x, y, **kwargs) 密度熱力圖
 histogram(data_frame, x=None, y=None, **kwargs) 直方圖
 box(data_frame, x=None, y=None, **kwargs) 箱型圖
 violin(data_frame, x=None, y=None, **kwargs) 小提琴圖
 strip(data_frame, x=None, y=None, **kwargs) 條帶圖
 scatter_3d(data_frame, x, y, z, **kwargs) 3D 散點圖
 line_3d(data_frame, x, y, z, **kwargs) 3D 折線圖
 scatter_geo(data_frame, locations, **kwargs) 地理散點圖
 choropleth(data_frame, locations, color, **kwargs) 地圖著色圖
 scatter_mapbox(data_frame, lat, lon, **kwargs) Mapbox 地圖散點圖
 choropleth_mapbox(data_frame, geojson, locations, color, **kwargs) Mapbox 地圖著色圖
 density_mapbox(data_frame, lat, lon, **kwargs) Mapbox 熱力圖
 scatter_polar(data_frame, r, theta, **kwargs) 極座標散點圖
 line_polar(data_frame, r, theta, **kwargs) 極座標折線圖
 bar_polar(data_frame, r, theta, **kwargs) 極座標長條圖
 imshow(img_array, **kwargs) 影像資料視覺化(2D array)


使用 plotly.express 模組繪圖通常在匯入時取簡名 px 以減少打字長度, 呼叫上表中的繪圖函式並傳入必要參數 (資料來源為字典或 DataFrame) 會傳回一個 Figure 物件 (圖表物件), 然後呼叫其 show() 方法就會建立一個 HTML 網頁檔並自動開啟瀏覽器來顯示這個網頁. 

Figure 物件提供了許多方法可靈活地操作圖表配置與圖層的外觀, 對於使用 plotly.express 模組繪圖而言, 最常用之方法如下表所示 : 


 Figure 物件常用方法  說明
 update_layout()  設定整體圖表外觀 (標題, 大小, 背景, 圖例, 座標軸標籤等)
 update_traces()  調整資料圖層樣式(線型、顏色、標記大小、hover 樣式等)
 update_xaxes()  調整 x 軸屬性(網格、刻度、方向等)
 update_yaxes()  調整 y 軸屬性(網格、刻度、方向等)
 add_annotation()  在圖中加上註解文字(標註最大值、最低點、特殊事件等, 可搭配箭頭)
 show()  在互動環境中(Jupyter Notebook 或瀏覽器)顯示圖表


使用 plotly.express 模組的繪圖程式基本架構例如 :

import plotly.express as px  
import pandas as pd

# 資料來源 (可以是字典或 DataFrame)
data={
    'x軸資料': [...],
    'y軸資料': [...]
    }
# 可以轉成 DataFrame
data=pd.DataFrame(data)

# 1. 建立 Figure 圖表物件 (加上初始 trace)
fig=px.xxx(data, x='欄位1', y='欄位2', 其他參數...) # 例如 px.line()

# 2. 更新整體圖表配置 (標題, 圖例, 背景, 軸設定等)
fig.update_layout(
    title='主標題',
    xaxis_title='X 軸名稱',
    yaxis_title='Y 軸名稱',
    width=800,
    height=400,
    legend=dict(orientation='h')
    )

# 3. 更新資料圖層的外觀 (顏色, 大小, 模式等)
fig.update_traces(
    line=dict(color='green', dash='dash'),
    marker=dict(size=10, color='red')
    )

# 4. 顯示圖表
fig.show()

fig.update_layout() 方法的常用參數如下表 : 


fig.update_layout() 參數 說明
title 設定圖表標題(可為字串或字典,支援位置、字體等設定)
xaxis 設定 x 軸配置,例如範圍、網格、標籤、顏色等
yaxis 設定 y 軸配置,例如範圍、網格、標籤、顏色等
width 圖表寬度(以像素為單位)
height 圖表高度(以像素為單位)
plot_bgcolor 繪圖區背景顏色(如 'white'、'#f0f0f0')
paper_bgcolor 整個圖表區域(含標題、圖例)的背景色
font 整體字體設定(字型、大小、顏色),為字典型態
legend 圖例位置與樣式設定(如位置、方向、字體)
margin 圖表四邊邊距,設定 top/right/bottom/left 整數像素值
showlegend 是否顯示圖例,布林值(True/False)
template 使用內建或自定義的主題風格(如 'plotly_white')
hovermode 設定滑鼠懸停時的行為,如 'closest' 或 'x unified'
barmode 設定長條圖類型(如 'group'、'stack')


fig.update_layout() 方法的常用參數如下表 : 


fig.update_layout() 參數 說明
marker 控制點或條狀的外觀字典(包含顏色、大小、邊框、透明度等)
marker.color 設定資料點或長條的顏色(可為單一色碼或一組顏色)
marker.line.color 設定資料點邊框或長條邊框的顏色
marker.line.width 設定資料點或長條邊框的粗細
opacity 設定圖形透明度(0.0 ~ 1.0)
text 每個點/條所顯示的文字標籤
textposition 設定文字顯示位置,如 'inside'、'outside'、'top right' 等
texttemplate 進階格式的文字模板,可用 %{value}、%{x}、%{y} 插入變數
hoverinfo 設定 hover 時顯示哪些資訊,如 'x+y+text'
hovertemplate 自訂 hover 顯示內容,支援變數與 HTML
mode (僅限 scatter)設定模式,如 'lines'、'markers'、'lines+markers'
line.color (折線圖用)設定線條顏色
line.width (折線圖用)設定線條粗細
orientation 長條方向:'v'(垂直)或 'h'(水平)
visible 設定圖形是否顯示,可為 True、False 或 'legendonly'


2. plotly.express 模組的內建資料集 :

plotly.express 內建了 10 個小型資料集, 方便使用者能快速體驗 Plotly 的繪圖功能, 這些資料集涵蓋了常見的統計, 分類, 地圖, 時間序列, 風向, 餐飲, 選舉等應用情境, 適合初學者做資料視覺化練習. 這些資料集以函式的形式存放在 plotly.express.data 模組下, 內容摘要如下表 : 


plotly.express.data 資料集 說明
gapminder() 全球國家的人口、壽命與人均 GDP(1952–2007)
iris() 經典 150 筆鳶尾花資料集 (分類與散佈圖分析)
tips() 餐廳消費與小費資料 (回歸分析和相關性分析)
titanic() 鐵達尼號乘客資料(性別、年齡、船艙、存活與否)
election() 美國 2013 年地方選舉資料(地圖應用範例)
wind() 風向與風速資料,適用於極座標圖與玫瑰圖
carshare() 共乘密度資料,含地點與時間等欄位,適合地圖或時間熱度圖
medals_long() 奧運金銀銅牌資料(長格式),適合繪製堆疊直條圖
medals_wide() 奧運獎牌資料(寬格式),可用於分組直條圖
stocks() 模擬的股票價格資料,適合時間序列折線圖


呼叫這些函式會傳回一個 Pandas DataFrame, 可直接傳給 px.*() 函式繪製圖表, 這讓初學者能快速取得資料以便學習與測試 Plotly 的資料視覺化功能. 

>>> import plotly.express as px   
>>> type(px.data)     
<class 'module'>
>>> dir(px.data)     
['__all__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__', 'absolute_import', 'carshare', 'election', 'election_geojson', 'experiment', 'gapminder', 'iris', 'medals_long', 'medals_wide', 'stocks', 'tips', 'wind']

例如 GDP 資料集 gapminder() :

>>> df=px.data.gapminder()   
>>> df.columns    
Index(['country', 'continent', 'year', 'lifeExp', 'pop', 'gdpPercap',
       'iso_alpha', 'iso_num'],
      dtype='object')
>>> len(df) 
1704

可見此資料集有 10 個欄位, 共有 1704 筆資料.

欄位說明如下表 :


gapminder() 欄位名稱 說明
country 國家名稱(類別型資料)
continent 洲別,如 Asia、Europe(類別型資料)
year 年份,從 1952 到 2007,每隔 5 年(數值型資料)
lifeExp 預期壽命(數值型資料)
pop 人口總數(數值型資料)
gdpPercap 人均 GDP(數值型資料)


3. 用 plotly.express 繪製直條圖 :

plotly.express 的直條圖函式 px.bar() 參數結構如下 : 

px.bar(data, x, y, *,
       color=None,
       text=None,
       facet_row=None,
       facet_col=None,
       barmode='relative',
       orientation='v',
       width=None,
       height=None,
       title=None,
       template='plotly',
       labels=None,
       category_orders=None,
       **kwargs)

參數說明如下表 :


plotly.express.bar() 參數 說明
data 資料來源,可為 DataFrame 或 dict 格式 ✅ 必要參數
x x 軸欄位名稱 ✅ 必要參數
y y 軸欄位名稱 ✅ 必要參數
color 根據欄位值區分長條顏色
text 在長條上顯示的文字內容(例如數值)
barmode 長條排列模式:'group'(並列)或 'relative'(堆疊),預設為 'relative'
orientation 'v' 垂直長條(預設)、'h' 水平長條
facet_row / facet_col 依欄位分成多個子圖(小 multiples)
width / height 設定圖表寬度與高度(單位為像素)
title 設定圖表標題
template 設定樣式主題,如 'plotly_white'、'simple_white'、'plotly_dark'
labels 自訂軸標籤顯示文字,格式為字典
category_orders 自訂類別軸的順序
**kwargs 其他可選參數,如 hover_name、opacity、log_y 等


其中前三個位置參數 data, x, y 為必要參數. 注意, Plotly 中的顏色可以使用顏色名稱如 'red', 16 進位色碼 '#RRGGBB', 10 進位色碼 'rgba(r, g, b, a)' 或 'rgb(r, g, b)'.

例如 :


測試 1 : 預設的長條圖 [看原始碼]

# plotly-bar-test-1.py
import plotly.express as px
import pandas as pd

# 資料來源 (可以是字典或 DataFrame)
data={
    '月份': ['一月', '二月', '三月', '四月', '五月'],
    '營收': [120000, 135000, 99000, 150000, 170000]    
    }
# 建立 Figure 圖表物件
fig=px.bar(data, x='月份', y='營收', width=800, height=600)
# 顯示圖表
fig.show()

此例使用字典做為資料來源傳給 px.bar() 的第一參數, 指定字典的鍵名稱為 x, y 參數, 並且用 width 與 height 設定圖表尺寸, 結果如下 :




可見預設的長條圖為藍色, 滑鼠移動到長條上會出現 tooltip 顯示資料點的數值, 且右上角有互動工具可縮放圖表或將圖表下載為 PNG 圖檔等. 

可以在呼叫 fig.update_traces() 設定長條的顏色與寬度, 以及其邊框的顏色與寬度, 例如 :  


測試 2 : 設定長條與其邊框的顏色與寬度 [看原始碼]

# plotly-bar-test-2.py
import plotly.express as px
import pandas as pd

# 資料來源 (可以是字典或 DataFrame)
data={
    '月份': ['一月', '二月', '三月', '四月', '五月'],
    '營收': [120000, 135000, 99000, 150000, 170000]    
    }
# 建立 Figure 圖表物件 (加上初始 trace)
fig=px.bar(data, x='月份', y='營收', width=800, height=600)
# 更新資料圖層的外觀 (直條顏色, 寬度)
fig.update_traces(
    marker_color='cyan', # 長條顏色
    width=0.4,  # 長條寬度 0~1
    marker_line_color='blue',  # 長條邊框顏色
    marker_line_width=2  # 長條邊框寬度 px
    )
# 顯示圖表
fig.show()

此例呼叫 fig.update_traces() 更新圖層, 設定長條顏色寬度與其邊框之顏色寬度, 結果如下 :




上面範例中的 Y 軸刻度值都被精簡顯示為以 K 為單位, 這可以透過呼叫 fig.update_layout() 並傳入 yaxis_tickformat="," 參數將 Y 軸刻度改為以千分位表示, 例如 : 


測試 3 : 設定 Y 軸以千分位刻度值顯示 [看原始碼]

# plotly-bar-test-3.py
import plotly.express as px
import pandas as pd

# 資料來源 (可以是字典或 DataFrame)
data=pd.DataFrame({
    '月份': ['一月', '二月', '三月', '四月', '五月'],
    '營收': [120000, 135000, 99000, 150000, 170000]
    })
# 建立 Figure 圖表物件 (加上初始 trace)
fig=px.bar(data, x='月份', y='營收', width=800, height=600)
# 更新資料圖層的外觀 (直條顏色, 寬度)
fig.update_traces(
    marker_color='orange', # 長條顏色
    width=0.5  # 長條寬度 0~1
    )
fig.update_layout(
    yaxis_tickformat=',',  # Y 軸以千分位例如 8,000 顯示
    )
# 顯示圖表
fig.show()

此例在呼叫 fig.update_layout() 時傳入 yaxis_tickformat=',' 設定 Y 軸刻度值以千分位格式顯示, 同時資料來源也改用 DataFrame, 結果如下 :




呼叫 fig.update_layout() 時傳入 title 參數可設定圖表標題, 例如 :


測試 4 : 設定圖表標題 [看原始碼]

# plotly-bar-test-4.py
import plotly.express as px
import pandas as pd

# 資料來源 (可以是字典或 DataFrame)
data=pd.DataFrame({
    '月份': ['一月', '二月', '三月', '四月', '五月'],
    '營收': [120000, 135000, 99000, 150000, 170000]
    })
# 建立 Figure 圖表物件 (加上初始 trace)
fig=px.bar(data, x='月份', y='營收', width=800, height=600)
# 更新資料圖層的外觀 (直條顏色, 寬度)
fig.update_traces(
    marker_color='navy', # 長條顏色
    width=0.5  # 長條寬度 0~1
    )
fig.update_layout(
    plot_bgcolor='rgb(255, 255, 255)',  # 繪圖區背景色
    paper_bgcolor='#f0f0f0',  # 整個畫布背景色
    title='今年前五月營收'
    )
# 顯示圖表
fig.show()

此例同時也展示了三種顏色表示法, 結果如下 :




可見預設的標題位置在左上角. 

若要設定圖表標題位置, 則 title 參數不要傳入字串, 而是要傳入含有 text, x, y, xanchor, yanchor, 與 font 六個鍵的字典, 標題文字改在 text 鍵, 字型用 font 字典設定, 其它 4 個鍵用來精確定位標題位置, 說明如下表 : 


title 參數字典的鍵 說明
text 設定標題的文字內容。
x 設定標題的水平位置,範圍為 0(左)到 1(右),預設為 0.5。
xanchor 設定標題的水平對齊方式,可為 'left' (預設)、'center' 或 'right'。
y 設定標題的垂直位置,範圍為 0(底)到 1(頂),預設約為 0.95。
yanchor 設定標題的垂直對齊方式,可為 'top'、'middle' (預設) 或 'bottom'。
font 設定字型的字典,含有 family、size 與 color 三個鍵。


標題放在圖片上方的話, y 的預設值 0.95 應該蠻恰當的不用改, 若放在下方則因為要與 X 軸刻度保持一些距離設為 0.03 較好. yanchor 使用預設值 'middle' 即可. 例如 : 


測試 5 : 設定圖表標題位置 [看原始碼]

# plotly-bar-test-5.py
import plotly.express as px
import pandas as pd

# 資料來源 (可以是字典或 DataFrame)
data=pd.DataFrame({
    '月份': ['一月', '二月', '三月', '四月', '五月'],
    '營收': [120000, 135000, 99000, 150000, 170000]
    })
# 建立 Figure 圖表物件 (加上初始 trace)
fig=px.bar(data, x='月份', y='營收', width=800, height=600)
# 更新資料圖層的外觀 (直條顏色, 寬度)
fig.update_traces(
    marker_color='navy', # 長條顏色
    width=0.5  # 長條寬度 0~1
    )
fig.update_layout(
    plot_bgcolor='rgb(255, 255, 255)',  # 繪圖區背景色
    paper_bgcolor='#f0f0f0',  # 整個畫布背景色
    title=dict(
        text='今年前五月營收',
        x=0.5,
        xanchor='center',
        y=0.95,
        yanchor='middle',
        font=dict(
            family='Arial',
            size=24,
            color='red'
            )
        )
    )
# 顯示圖表
fig.show()

此例將紅色的標題設在圖片上方中央, 結果如下 :




通常圖表標題都放在上方中央, 只需要設定 x=0.5 與 xanchor='center' 即可. 

有時 X 軸刻度標籤較長 (例如日期), 這時就需要旋轉一個角度避免刻度標籤重疊, 這可以在呼叫 fig.update_layout() 時傳入 xaxis_tickangle 參數, 其值為正時順時針旋轉; 為負時逆時針旋轉, 例如 :


測試 6 : 設定 X 軸刻度標籤旋轉角度 [看原始碼]

# plotly-bar-test-6.py
import plotly.express as px
import pandas as pd

# 資料來源 (可以是字典或 DataFrame)
data=pd.DataFrame({
    '月份': ['一月', '二月', '三月', '四月', '五月'],
    '營收': [120000, 135000, 99000, 150000, 170000]
    })
# 建立 Figure 圖表物件 (加上初始 trace)
fig=px.bar(data, x='月份', y='營收', width=800, height=600)
# 更新資料圖層的外觀 (直條顏色, 寬度)
fig.update_traces(
    marker_color='navy', # 長條顏色
    width=0.5  # 長條寬度 0~1
    )
fig.update_layout(
    plot_bgcolor='cyan',  # 繪圖區背景色
    paper_bgcolor='#f0f0f0',  # 整個畫布背景色
    title='今年前五月營收',
    xaxis_tickangle=-90  # 正為順時針,負為逆時針
    )
# 顯示圖表
fig.show()

此例旋轉角度 -90 為由預設水平逆時針 90 度, 結果如下 :




Plotly 預設會依據 X, Y 軸資料是否為數值來自動顯示網格線, 如果是類別 (categorical) 資料則預設不顯示網格線, 例如上面範例中的 X 軸月份資料為類別型故不顯示網格線; Y 軸營收是數值則顯示. 

如果要強制顯示或隱藏網格線, 可呼叫 fig.update_layout() 並傳入 xaxis 字典參數, 設定 showgrid 鍵為 True (顯示) 或 False (不顯示). 除了 showgrid 鍵外, 還可以用 gridwidth (單位 px) 與 gridcolor 來設定網格線的粗細與顏色, 例如 :


測試 7 : 設定網格線 (顯示與否及粗細顏色) [看原始碼]

# plotly-bar-test-7.py
import plotly.express as px
import pandas as pd

# 資料來源 (可以是字典或 DataFrame)
data=pd.DataFrame({
    '月份': ['一月', '二月', '三月', '四月', '五月'],
    '營收': [120000, 135000, 99000, 150000, 170000]
    })
# 建立 Figure 圖表物件 (加上初始 trace)
fig=px.bar(data, x='月份', y='營收', width=800, height=600)
# 更新資料圖層的外觀 (直條顏色, 寬度)
fig.update_traces(
    marker_color='navy', # 長條顏色
    width=0.5  # 長條寬度 0~1
    )
fig.update_layout(
    title='今年前五月營收',
    xaxis=dict(
        showgrid=True,
        gridwidth=1,
        gridcolor='red'
        ),
    yaxis=dict(
        showgrid=True,
        gridwidth=1,
        gridcolor='red'
        )
    )
# 顯示圖表
fig.show()

此例將 X, Y 軸網格線均強制顯示, 結果如下 :




類別資料的 X 軸有網格線很奇怪, 這就是為何 Plotly 預設要隱藏它的緣故. 

最後來看看兩組 Y 軸資料的長條圖, 在繪製之前要先將原本的寬格式 DataFrame :




用 melt() 方法轉成長格式的 DataFrame, 例如 :




例如 :


測試 8 : 兩組 Y 軸資料的堆疊式長條圖 [看原始碼]

# plotly-bar-test-8.py
import plotly.express as px
import pandas as pd

# 資料來源 (可以是字典或 DataFrame)
data=pd.DataFrame({
    '月份': ['一月', '二月', '三月', '四月', '五月'],
    '今年營收': [120000, 135000, 99000, 150000, 170000],
    '去年營收': [110000, 125000, 95000, 145000, 160000]
    })
# 將寬表格轉為長表格
data=data.melt(id_vars='月份', var_name='年度', value_name='營收')  
# 建立 Figure 圖表物件 (加上初始 trace)
fig=px.bar(
    data,
    x='月份',
    y='營收',
    color='年度',
    width=800,
    height=600
    )
# 顯示圖表
fig.show()

此例呼叫 DataFrame 的 melt() 方法將 id_vars 指定的月份欄位以外的所有欄位融化到 value_name 指定的新增欄位 ('營收'), 然後把被融化的欄位名稱組成串列放進 var_name 所指定的新增欄位裡 (類別), 結果如下 : 




可見預設是堆疊式 (stacked) 長條圖, 而且自動出現圖例. 

如果要繪製群組式長條圖, 則呼叫 px.bar() 時要傳入 barmode='group', 例如 : 


測試 9 : 兩組 Y 軸資料的群組式長條圖 (1) [看原始碼]

# plotly-bar-test-9.py
import plotly.express as px
import pandas as pd

# 資料來源 (可以是字典或 DataFrame)
data=pd.DataFrame({
    '月份': ['一月', '二月', '三月', '四月', '五月'],
    '今年營收': [120000, 135000, 99000, 150000, 170000],
    '去年營收': [110000, 125000, 95000, 145000, 160000]
    })
# 將寬表格轉為長表格
data=data.melt(id_vars='月份', var_name='年度', value_name='營收')
# 建立 Figure 圖表物件 (加上初始 trace)
fig=px.bar(
    data,
    x='月份',
    y='營收',
    color='年度',
    width=800,
    height=600,
    barmode='group'  # 群組式長條圖
    )
# 顯示圖表
fig.show()

結果如下 :



直條的顏色是 Plotly 自動指配的, 但可以用 color_discrete_map 參數字典來設定, 鍵就是分群欄位名稱, 例如 : 


測試 10 : 兩組 Y 軸資料的群組式長條圖 (2) [看原始碼]

# plotly-bar-test-10.py
import plotly.express as px
import pandas as pd

# 資料來源 (可以是字典或 DataFrame)
data=pd.DataFrame({
    '月份': ['一月', '二月', '三月', '四月', '五月'],
    '今年營收': [120000, 135000, 99000, 150000, 170000],
    '去年營收': [110000, 125000, 95000, 145000, 160000]
    })
# 將寬表格轉為長表格
data=data.melt(id_vars='月份', var_name='年度', value_name='營收')
# 建立 Figure 圖表物件 (加上初始 trace)
fig=px.bar(
    data,
    x='月份',
    y='營收',
    color='年度',
    width=800,
    height=600,
    barmode='group',  # 群組式長條圖
    color_discrete_map={
        '今年營收': '#00ff00',
        '去年營收': 'rgba(0, 0, 255, 0.3)'
        }
    )
# 顯示圖表
fig.show()

結果如下 : 




最後使用 Plotly 內建資料集 gapminder() 繪製 2007 年全球各洲各國 GDP 的長條圖 :


測試 11 : 2007 年全球各洲各國 GDP 的長條圖 [看原始碼]

# plotly-bar-test-11.py
import plotly.express as px
import pandas as pd

# 資料來源 (可以是字典或 DataFrame)
df=px.data.gapminder()

# 篩選 2007 年資料
df_2007=df[df['year'] == 2007]

# 建立 Figure 圖表物件繪製群組式長條圖
fig=px.bar(
    df_2007,
    x='continent',        # x 軸: 洲
    y='gdpPercap',        # y 軸: 人均 GDP
    color='country',      # 群組分類: 國家
    barmode='group',      # 群組式長條圖
    title='2007 各洲各國人均 GDP'
    )

# 顯示圖表
fig.show()

結果如下 : 




放大亞洲部分 :