資料科學 視覺化 網頁開發 JAVA SCRIPT

Chart.js 互動式圖表入門教程

Chart.js 互動式圖表入門教程

第一部分:課程介紹與環境準備

課程簡介與 Chart.js 概覽

什麼是 Chart.js? Chart.js 是一個簡單而靈活的 JavaScript 圖表庫,基於 HTML5 Canvas 元素,使網頁開發者能夠輕鬆在網頁中創建各種互動式圖表。

為什麼選擇 Chart.js?

  • 輕量級:檔案小,載入快速(壓縮後約 80KB)
  • 易於使用:簡潔的 API,上手容易
  • 響應式:圖表會自動調整大小以適應設備螢幕
  • 模組化:可以只引入需要的模組,減少載入時間
  • 內建互動性:預設具備工具提示、動畫和滑鼠懸停效果等互動功能
  • 可擴展性:豐富的插件生態系統

環境準備與 Chart.js 引入

開始使用 Chart.js 需要兩個基本步驟:

  1. 在 HTML 中引入 Chart.js 庫
  2. 創建一個 Canvas 元素作為圖表的容器

基本 HTML 結構

<!DOCTYPE html>
<html lang="zh-TW">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Chart.js 入門</title>
    <!-- 透過 CDN 引入 Chart.js -->
    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
    <style>
        .chart-container {
            width: 80%;
            max-width: 700px;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    <div class="chart-container">
        <!-- Canvas 元素,Chart.js 會在此處繪製圖表 -->
        <canvas id="myChart"></canvas>
    </div>
    
    <script>
        // 我們的 Chart.js 程式碼將在這裡編寫
    </script>
</body>
</html>

第二部分:Chart.js 核心概念與繪製第一個圖表

Chart.js 基本架構

Chart.js 的基本架構由以下幾個關鍵部分組成:

  1. Canvas Context:指定圖表繪製的 Canvas 元素
  2. Type:圖表類型(如 ‘bar’、’line’、‘pie’ 等)
  3. Data:圖表的數據,包含標籤和數據集
  4. Options:圖表的各種設定選項,包括標題、軸、圖例和互動性等

繪製第一個靜態圖表 - 長條圖 (Bar Chart)

以下是一個基本長條圖的示例,使用的是日經新聞分類統計數據:

// 取得 Canvas 元素
const ctx = document.getElementById('myChart').getContext('2d');

// 準備數據
const data = {
    labels: ['經濟', '政治', '科技', '健康', '娛樂', '體育', '國際'],
    datasets: [{
        label: '日經新聞分類統計',
        data: [100, 70, 95, 50, 25, 30, 85],
        backgroundColor: 'rgba(255, 99, 132, 0.6)',
        borderColor: 'rgba(255, 99, 132, 1)',
        borderWidth: 1
    }]
};

// 建立圖表
const myChart = new Chart(ctx, {
    type: 'bar',  // 圖表類型:長條圖
    data: data,   // 數據
    options: {}   // 暫時留空的選項
});

添加圖表基本元素 - 標題、軸標籤、圖例

現在我們來為長條圖添加一些基本元素,讓它更加信息豐富:

// 取得 Canvas 元素
const ctx = document.getElementById('myChart').getContext('2d');

// 準備數據(與前面相同)
const data = {
    labels: ['經濟', '政治', '科技', '健康', '娛樂', '體育', '國際'],
    datasets: [{
        label: '日經新聞分類統計',
        data: [100, 70, 95, 50, 25, 30, 85],
        backgroundColor: 'rgba(255, 99, 132, 0.6)',
        borderColor: 'rgba(255, 99, 132, 1)',
        borderWidth: 1
    }]
};

// 建立圖表,這次添加更多選項
const myChart = new Chart(ctx, {
    type: 'bar',
    data: data,
    options: {
        responsive: true,
        plugins: {
            title: {
                display: true,
                text: '日經新聞分類統計',
                font: {
                    size: 18
                }
            },
            legend: {
                position: 'top'
            }
        },
        scales: {
            y: {
                beginAtZero: true,
                title: {
                    display: true,
                    text: '文章數量'
                }
            },
            x: {
                title: {
                    display: true,
                    text: '新聞類別'
                }
            }
        }
    }
});

第三部分:繪製其他基本圖表類型

繪製線圖 (Line Chart)

線圖通常用於表示隨時間變化的數據。以下是使用 S&P 500 指數數據的線圖示例:

// 取得 Canvas 元素
const ctx = document.getElementById('lineChart').getContext('2d');

// 準備數據
const data = {
    labels: ['2015', '2016', '2017', '2018', '2019', '2020', '2021', '2022', '2023', '2024'],
    datasets: [{
        label: 'S&P 500',
        data: [2054, 2239, 2673, 2507, 3231, 3756, 4766, 3840, 4180, 5500],
        fill: false,
        backgroundColor: 'rgba(75, 192, 192, 0.6)',
        borderColor: 'rgba(75, 192, 192, 1)',
        borderWidth: 2,
        tension: 0.2  // 使線條略微平滑
    }]
};

// 建立圖表
const lineChart = new Chart(ctx, {
    type: 'line',  // 圖表類型:線圖
    data: data,
    options: {
        responsive: true,
        plugins: {
            title: {
                display: true,
                text: 'S&P 500 指數走勢',
                font: {
                    size: 18
                }
            }
        },
        scales: {
            y: {
                title: {
                    display: true,
                    text: '指數值'
                }
            },
            x: {
                title: {
                    display: true,
                    text: '年份'
                }
            }
        }
    }
});

繪製散點圖 (Scatter Chart)

散點圖非常適合展示兩個變量之間的關聯。以下是一個簡單的散點圖示例:

// 取得 Canvas 元素
const ctx = document.getElementById('scatterChart').getContext('2d');

// 準備數據 - 散點圖的數據格式與其他圖表不同
const data = {
    datasets: [{
        label: '國家發展指標',
        data: [
            { x: 60, y: 98, r: 5.0 },  // r 代表點的大小
            { x: 45, y: 88, r: 12.0 },
            { x: 30, y: 75, r: 20.0 },
            { x: 78, y: 99, r: 2.5 },
            { x: 35, y: 80, r: 8.0 },
            { x: 55, y: 95, r: 3.5 }
        ],
        backgroundColor: 'rgba(54, 162, 235, 0.6)',
        borderColor: 'rgba(54, 162, 235, 1)'
    }]
};

// 建立圖表
const scatterChart = new Chart(ctx, {
    type: 'bubble',  // 使用 bubble 而不是 scatter,因為我們有 r 值
    data: data,
    options: {
        responsive: true,
        plugins: {
            title: {
                display: true,
                text: '國家發展指標對比',
                font: {
                    size: 18
                }
            }
        },
        scales: {
            y: {
                title: {
                    display: true,
                    text: '識字率 (%)'
                }
            },
            x: {
                title: {
                    display: true,
                    text: '人均GDP (千美元)'
                }
            }
        }
    }
});

繪製餅圖與環圈圖 (Pie and Doughnut Charts)

餅圖和環圈圖適合用於展示部分對整體的比例關係。以下是一個簡單的環圈圖示例:

// 取得 Canvas 元素
const ctx = document.getElementById('doughnutChart').getContext('2d');

// 準備數據
const data = {
    labels: ['台灣', '日本', '韓國', '中國', '美國', '歐盟'],
    datasets: [{
        label: '半導體市場份額',
        data: [25, 18, 15, 12, 20, 10],
        backgroundColor: [
            'rgba(255, 99, 132, 0.7)',
            'rgba(54, 162, 235, 0.7)',
            'rgba(255, 206, 86, 0.7)',
            'rgba(75, 192, 192, 0.7)',
            'rgba(153, 102, 255, 0.7)',
            'rgba(255, 159, 64, 0.7)'
        ],
        borderColor: [
            'rgba(255, 99, 132, 1)',
            'rgba(54, 162, 235, 1)',
            'rgba(255, 206, 86, 1)',
            'rgba(75, 192, 192, 1)',
            'rgba(153, 102, 255, 1)',
            'rgba(255, 159, 64, 1)'
        ],
        borderWidth: 1
    }]
};

// 建立圖表
const doughnutChart = new Chart(ctx, {
    type: 'doughnut',  // 圖表類型:環圈圖 (改為 'pie' 即可變成餅圖)
    data: data,
    options: {
        responsive: true,
        plugins: {
            title: {
                display: true,
                text: '全球半導體市場份額分布',
                font: {
                    size: 18
                }
            },
            legend: {
                position: 'right'
            },
            tooltip: {
                callbacks: {
                    label: function(context) {
                        const label = context.label || '';
                        const value = context.parsed || 0;
                        const total = context.dataset.data.reduce((a, b) => a + b, 0);
                        const percentage = Math.round((value / total) * 100);
                        return `${label}: ${percentage}% (${value}%)`;
                    }
                }
            }
        }
    }
});

繪製雷達圖 (Radar Chart)

雷達圖適合比較多個群組在不同維度上的表現。以下是一個展示不同編程語言特性的雷達圖:

// 取得 Canvas 元素
const ctx = document.getElementById('radarChart').getContext('2d');

// 準備數據
const data = {
    labels: ['性能', '易用性', '社群支持', '工具生態', '跨平台', '學習曲線'],
    datasets: [{
        label: 'Python',
        data: [70, 95, 90, 85, 80, 90],
        backgroundColor: 'rgba(255, 99, 132, 0.2)',
        borderColor: 'rgba(255, 99, 132, 1)',
        pointBackgroundColor: 'rgba(255, 99, 132, 1)',
        pointBorderColor: '#fff',
        pointHoverBackgroundColor: '#fff',
        pointHoverBorderColor: 'rgba(255, 99, 132, 1)'
    }, {
        label: 'JavaScript',
        data: [65, 90, 95, 95, 100, 75],
        backgroundColor: 'rgba(54, 162, 235, 0.2)',
        borderColor: 'rgba(54, 162, 235, 1)',
        pointBackgroundColor: 'rgba(54, 162, 235, 1)',
        pointBorderColor: '#fff',
        pointHoverBackgroundColor: '#fff',
        pointHoverBorderColor: 'rgba(54, 162, 235, 1)'
    }, {
        label: 'Go',
        data: [95, 75, 70, 65, 80, 75],
        backgroundColor: 'rgba(255, 206, 86, 0.2)',
        borderColor: 'rgba(255, 206, 86, 1)',
        pointBackgroundColor: 'rgba(255, 206, 86, 1)',
        pointBorderColor: '#fff',
        pointHoverBackgroundColor: '#fff',
        pointHoverBorderColor: 'rgba(255, 206, 86, 1)'
    }]
};

// 建立圖表
const radarChart = new Chart(ctx, {
    type: 'radar',  // 圖表類型:雷達圖
    data: data,
    options: {
        responsive: true,
        plugins: {
            title: {
                display: true,
                text: '程式語言比較',
                font: {
                    size: 18
                }
            }
        },
        scales: {
            r: {
                angleLines: {
                    display: true
                },
                suggestedMin: 0,
                suggestedMax: 100
            }
        }
    }
});

第四部分:Chart.js 的互動性

理解內建互動功能

Chart.js 的一個關鍵特性是它內建的互動功能,這些功能讓您的圖表更加生動和信息豐富。

主要互動功能包括:

  1. 滑鼠懸停效果 (Hover):當用戶將滑鼠移到圖表元素上時,該元素會有視覺反饋(如高亮顯示)。這是 Chart.js 的預設行為。

  2. 工具提示 (Tooltip):當用戶懸停在數據點上時,會顯示一個包含詳細信息的小彈窗。工具提示是展示詳細數據的重要方式。

  3. 圖例交互:用戶可以點擊圖例來切換數據集的顯示與隱藏。

讓我們看看如何強化這些互動功能:

// 以長條圖為例,添加互動功能
const myChart = new Chart(ctx, {
    type: 'bar',
    data: data,
    options: {
        responsive: true,
        plugins: {
            title: {
                display: true,
                text: '日經新聞分類統計',
                font: { size: 18 }
            },
            // 自訂工具提示
            tooltip: {
                backgroundColor: 'rgba(0, 0, 0, 0.8)',
                titleFont: { size: 14 },
                bodyFont: { size: 12 },
                displayColors: false,  // 不顯示小色塊
                callbacks: {
                    label: function(context) {
                        return `文章數量: ${context.parsed.y}`;
                    }
                }
            }
        },
        // 設定懸停效果
        hover: {
            mode: 'index',  // 懸停在特定索引上
            intersect: false  // 不需要直接懸停在點上
        },
        scales: {
            y: {
                beginAtZero: true,
                title: { display: true, text: '文章數量' }
            },
            x: {
                title: { display: true, text: '新聞類別' }
            }
        }
    }
});

在範例中體驗互動

比特幣價格走勢圖表(使用蠟燭圖)

以下是一個更複雜的例子,展示了比特幣價格的蠟燭圖,這種圖表類型本身就具有很強的互動性:

// 取得 Canvas 元素
const ctx = document.getElementById('candlestickChart').getContext('2d');

// 準備數據
const data = {
    labels: [
        '2024-1-15', '2024-1-30', '2024-2-15', '2024-2-30', '2024-3-15',
        '2024-3-30', '2024-4-15', '2024-4-30', '2024-5-15', '2024-5-30'
    ],
    datasets: [{
        label: '比特幣/美元',
        data: [
            { t: '2024-01-15', o: 42000, h: 43200, l: 41500, c: 42800 },
            { t: '2024-01-30', o: 42800, h: 44500, l: 42500, c: 43900 },
            { t: '2024-02-15', o: 43900, h: 45000, l: 43200, c: 44200 },
            { t: '2024-02-29', o: 44200, h: 46700, l: 44000, c: 45800 },
            { t: '2024-03-15', o: 45800, h: 47500, l: 45300, c: 47000 },
            { t: '2024-03-30', o: 47000, h: 48800, l: 46200, c: 48500 },
            { t: '2024-04-15', o: 48500, h: 52000, l: 48200, c: 51500 },
            { t: '2024-04-30', o: 51500, h: 53500, l: 51000, c: 52800 },
            { t: '2024-05-15', o: 52800, h: 54000, l: 51500, c: 53000 },
            { t: '2024-05-30', o: 53000, h: 56000, l: 52000, c: 55000 }
        ]
    }]
};

// 由於標準 Chart.js 沒有內建蠟燭圖,這裡我們模擬一個互動效果
const candlestickChart = new Chart(ctx, {
    type: 'bar',  // 用長條圖模擬蠟燭圖
    data: {
        labels: data.labels,
        datasets: [{
            label: '比特幣/美元 價格範圍',
            backgroundColor: ctx => {
                // 依據開盤價和收盤價決定顏色
                const value = data.datasets[0].data[ctx.dataIndex];
                return value.o > value.c ? 'rgba(255, 99, 132, 0.8)' : 'rgba(75, 192, 192, 0.8)';
            },
            data: data.datasets[0].data.map(d => d.h - d.l),  // 高低差
            barPercentage: 0.4
        }]
    },
    options: {
        responsive: true,
        plugins: {
            title: {
                display: true,
                text: '比特幣/美元 價格走勢',
                font: { size: 18 }
            },
            tooltip: {
                callbacks: {
                    label: function(context) {
                        const dataIndex = context.dataIndex;
                        const value = data.datasets[0].data[dataIndex];
                        return [
                            `開盤: $${value.o}`,
                            `最高: $${value.h}`,
                            `最低: $${value.l}`,
                            `收盤: $${value.c}`
                        ];
                    }
                }
            }
        },
        scales: {
            y: {
                title: {
                    display: true,
                    text: '價格範圍 (USD)'
                }
            },
            x: {
                title: {
                    display: true,
                    text: '日期'
                }
            }
        }
    }
});

第五部分:Chart.js 動畫與效果

動畫基礎與配置

Chart.js 內建了豐富的動畫效果,讓圖表在載入時更加生動。您可以配置這些動畫的持續時間、緩動函數等:

// 以長條圖為例,添加動畫效果
const myChart = new Chart(ctx, {
    type: 'bar',
    data: data,
    options: {
        responsive: true,
        // 動畫配置
        animation: {
            duration: 2000,  // 動畫持續時間(毫秒)
            easing: 'easeOutBounce',  // 緩動函數
            delay: 500  // 延遲開始動畫的時間
        },
        // 可以為不同的動畫類型單獨配置
        animations: {
            colors: {
                type: 'color',
                duration: 2000
            },
            y: {
                type: 'number',
                from: 0  // 從 0 開始動畫
            }
        },
        plugins: {
            title: {
                display: true,
                text: '日經新聞分類統計',
                font: { size: 18 }
            }
        },
        scales: {
            y: {
                beginAtZero: true
            }
        }
    }
});

漸變色彩效果

您可以使用漸變色彩,為長條圖或其他圖表添加更吸引人的視覺效果:

// 取得 Canvas 元素
const ctx = document.getElementById('gradientChart').getContext('2d');

// 創建漸變色
const gradient = ctx.createLinearGradient(0, 0, 0, 400);
gradient.addColorStop(0, 'rgba(255, 0, 0, 0.8)');
gradient.addColorStop(0.5, 'rgba(255, 150, 0, 0.8)');
gradient.addColorStop(1, 'rgba(255, 255, 0, 0.8)');

// 準備數據
const data = {
    labels: ['一月', '二月', '三月', '四月', '五月', '六月'],
    datasets: [{
        label: '月度銷售額',
        data: [125, 190, 160, 175, 200, 230],
        backgroundColor: gradient,
        borderColor: 'rgba(255, 99, 132, 1)',
        borderWidth: 1
    }]
};

// 建立圖表
const gradientChart = new Chart(ctx, {
    type: 'bar',
    data: data,
    options: {
        responsive: true,
        plugins: {
            title: {
                display: true,
                text: '月度銷售額 (漸變色效果)',
                font: { size: 18 }
            }
        },
        // 添加漸進動畫
        animation: {
            y: {
                duration: 2000,
                from: 0
            }
        }
    }
});

第六部分:進階使用技巧

資料刷新與動態更新

在實際應用中,您可能需要定期更新圖表數據,以下是一個動態更新圖表的示例:

// 取得 Canvas 元素
const ctx = document.getElementById('dynamicChart').getContext('2d');

// 初始數據
const initialData = {
    labels: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10'],
    datasets: [{
        label: '即時數據',
        data: [65, 59, 80, 81, 56, 55, 40, 45, 60, 70],
        fill: false,
        borderColor: 'rgba(75, 192, 192, 1)',
        tension: 0.1
    }]
};

// 建立圖表
const dynamicChart = new Chart(ctx, {
    type: 'line',
    data: initialData,
    options: {
        responsive: true,
        plugins: {
            title: {
                display: true,
                text: '即時數據監控',
                font: { size: 18 }
            }
        },
        scales: {
            y: {
                min: 0,
                max: 100
            }
        }
    }
});

// 模擬數據更新
function updateChart() {
    // 移除第一個數據點
    dynamicChart.data.labels.shift();
    dynamicChart.data.datasets[0].data.shift();
    
    // 添加新數據點
    const newLabel = (parseInt(dynamicChart.data.labels[dynamicChart.data.labels.length - 1]) + 1).toString();
    dynamicChart.data.labels.push(newLabel);
    
    const newData = Math.floor(Math.random() * 100);
    dynamicChart.data.datasets[0].data.push(newData);
    
    // 更新圖表
    dynamicChart.update();
}

// 每 2 秒更新一次圖表
setInterval(updateChart, 2000);

使用事件處理程序

Chart.js 支持多種事件,可以讓您的圖表與用戶互動:

// 取得 Canvas 元素
const ctx = document.getElementById('eventChart').getContext('2d');

// 準備數據
const data = {
    labels: ['台北', '台中', '高雄', '台南', '新竹', '花蓮'],
    datasets: [{
        label: '城市人口 (萬人)',
        data: [270, 280, 280, 190, 45, 33],
        backgroundColor: 'rgba(54, 162, 235, 0.6)',
        borderColor: 'rgba(54, 162, 235, 1)',
        borderWidth: 1
    }]
};

// 建立圖表
const eventChart = new Chart(ctx, {
    type: 'bar',
    data: data,
    options: {
        responsive: true,
        plugins: {
            title: {
                display: true,
                text: '台灣主要城市人口',
                font: { size: 18 }
            }
        }
    }
});

// 添加點擊事件處理器
document.getElementById('eventChart').onclick = function(evt) {
    const points = eventChart.getElementsAtEventForMode(evt, 'nearest', { intersect: true }, true);
    
    if (points.length) {
        const firstPoint = points[0];
        const label = eventChart.data.labels[firstPoint.index];
        const value = eventChart.data.datasets[firstPoint.datasetIndex].data[firstPoint.index];
        
        alert(`您點擊了 ${label},人口數為 ${value} 萬人`);
    }
};

複合圖表與多數據集

在同一個圖表中顯示不同類型的圖表或多個數據集,可以提供更全面的數據視角:

// 取得 Canvas 元素
const ctx = document.getElementById('mixedChart').getContext('2d');

// 準備數據
const data = {
    labels: ['一月', '二月', '三月', '四月', '五月', '六月'],
    datasets: [
        {
            type: 'bar',
            label: '銷售額',
            data: [110, 148, 124, 128, 138, 141],
            backgroundColor: 'rgba(75, 192, 192, 0.6)',
            borderColor: 'rgba(75, 192, 192, 1)',
            borderWidth: 1,
            yAxisID: 'y'
        },
        {
            type: 'line',
            label: '利潤率(%)',
            data: [19.4, 19.5, 22.0, 19.0, 23.5, 28.1],
            backgroundColor: 'transparent',
            borderColor: 'rgba(255, 99, 132, 1)',
            borderWidth: 2,
            yAxisID: 'y1'
        }
    ]
};

// 建立圖表
const mixedChart = new Chart(ctx, {
    type: 'bar',  // 基本類型,但每個數據集可以有自己的類型
    data: data,
    options: {
        responsive: true,
        plugins: {
            title: {
                display: true,
                text: '銷售額與利潤率對比',
                font: { size: 18 }
            },
            tooltip: {
                mode: 'index',
                intersect: false
            }
        },
        scales: {
            y: {
                position: 'left',
                title: {
                    display: true,
                    text: '銷售額'
                }
            },
            y1: {
                position: 'right',
                title: {
                    display: true,
                    text: '利潤率(%)'
                },
                grid: {
                    drawOnChartArea: false  // 只有左軸的網格線
                },
                min: 0,
                max: 50
            }
        }
    }
});

第七部分:外部數據整合

從 JSON 檔案載入數據

在實際應用中,圖表數據通常來自外部 API 或檔案。以下是從 JSON 檔案載入數據的示例:

// 取得 Canvas 元素
const ctx = document.getElementById('jsonDataChart').getContext('2d');

// 從 JSON 檔案載入數據
fetch('data/sales-data.json')
    .then(response => response.json())
    .then(jsonData => {
        // 準備圖表數據
        const data = {
            labels: jsonData.map(item => item.month),
            datasets: [{
                label: '月度銷售額',
                data: jsonData.map(item => item.sales),
                backgroundColor: 'rgba(54, 162, 235, 0.6)',
                borderColor: 'rgba(54, 162, 235, 1)',
                borderWidth: 1
            }]
        };
        
        // 建立圖表
        const jsonDataChart = new Chart(ctx, {
            type: 'bar',
            data: data,
            options: {
                responsive: true,
                plugins: {
                    title: {
                        display: true,
                        text: '從 JSON 檔案載入的銷售數據',
                        font: { size: 18 }
                    }
                }
            }
        });
    })
    .catch(error => console.error('載入數據失敗:', error));

使用 Fetch API 從外部 API 獲取數據

以下是一個從外部 API 獲取數據並生成圖表的例子:

// 取得 Canvas 元素
const ctx = document.getElementById('apiDataChart').getContext('2d');

// 從 API 獲取數據
async function fetchStockData() {
    try {
        // 替換為實際的 API 網址
        const response = await fetch('https://api.example.com/stocks/AAPL/history');
        const data = await response.json();
        
        // 處理數據
        const labels = data.map(item => item.date);
        const prices = data.map(item => item.closePrice);
        
        // 創建圖表
        const stockChart = new Chart(ctx, {
            type: 'line',
            data: {
                labels: labels,
                datasets: [{
                    label: 'AAPL 股票價格',
                    data: prices,
                    backgroundColor: 'rgba(75, 192, 192, 0.2)',
                    borderColor: 'rgba(75, 192, 192, 1)',
                    borderWidth: 1,
                    pointRadius: 2,
                    tension: 0.1
                }]
            },
            options: {
                responsive: true,
                plugins: {
                    title: {
                        display: true,
                        text: 'AAPL 股票價格走勢',
                        font: { size: 18 }
                    }
                },
                scales: {
                    x: {
                        title: {
                            display: true,
                            text: '日期'
                        }
                    },
                    y: {
                        title: {
                            display: true,
                            text: '價格 (USD)'
                        }
                    }
                }
            }
        });
    } catch (error) {
        console.error('獲取股票數據失敗:', error);
        document.getElementById('apiDataChart').parentNode.innerHTML = '<p>無法載入股票數據</p>';
    }
}

// 執行函數
fetchStockData();

從 CSV 檔案載入數據

使用外部函式庫 PapaParse 來處理 CSV 數據:

<!-- 在 HTML 檔案中引入 PapaParse -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/PapaParse/5.3.0/papaparse.min.js"></script>
// 取得 Canvas 元素
const ctx = document.getElementById('csvDataChart').getContext('2d');

// 載入並解析 CSV 檔案
Papa.parse('data/temperature-data.csv', {
    download: true,
    header: true,
    dynamicTyping: true,
    complete: function(results) {
        // 處理數據
        const data = results.data;
        const labels = data.map(row => row.date);
        const temperatures = data.map(row => row.temperature);
        
        // 創建圖表
        const temperatureChart = new Chart(ctx, {
            type: 'line',
            data: {
                labels: labels,
                datasets: [{
                    label: '每日平均溫度',
                    data: temperatures,
                    backgroundColor: 'rgba(255, 159, 64, 0.2)',
                    borderColor: 'rgba(255, 159, 64, 1)',
                    borderWidth: 1,
                    fill: true
                }]
            },
            options: {
                responsive: true,
                plugins: {
                    title: {
                        display: true,
                        text: '每日平均溫度變化',
                        font: { size: 18 }
                    }
                },
                scales: {
                    y: {
                        title: {
                            display: true,
                            text: '溫度 (°C)'
                        }
                    }
                }
            }
        });
    }
});

第八部分:Chart.js 插件系統

常用插件介紹

Chart.js 有豐富的插件生態系統,可以擴展其功能。以下是一些常用插件:

  1. chartjs-plugin-datalabels:在圖表上直接顯示數據標籤
  2. chartjs-plugin-zoom:添加縮放和平移功能
  3. chartjs-plugin-annotation:添加註釋,如線條、框和點
  4. chartjs-plugin-colorschemes:預定義的顏色配色方案

使用 chartjs-plugin-datalabels 示例

首先在 HTML 中引入插件:

<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-datalabels@2.0.0"></script>

然後在 JavaScript 代碼中使用:

// 註冊插件(全局)
Chart.register(ChartDataLabels);

// 取得 Canvas 元素
const ctx = document.getElementById('datalabelsChart').getContext('2d');

// 準備數據
const data = {
    labels: ['一月', '二月', '三月', '四月', '五月', '六月'],
    datasets: [{
        label: '銷售額',
        data: [80, 92, 75, 110, 150, 125],
        backgroundColor: 'rgba(75, 192, 192, 0.6)',
        borderColor: 'rgba(75, 192, 192, 1)',
        borderWidth: 1
    }]
};

// 建立圖表
const datalabelsChart = new Chart(ctx, {
    type: 'bar',
    data: data,
    options: {
        responsive: true,
        plugins: {
            title: {
                display: true,
                text: '帶數據標籤的銷售圖表',
                font: { size: 18 }
            },
            // 數據標籤配置
            datalabels: {
                color: '#000',
                anchor: 'end',
                align: 'top',
                formatter: Math.round,
                font: {
                    weight: 'bold'
                }
            }
        }
    }
});

使用 chartjs-plugin-zoom 示例

首先引入插件:

<script src="https://cdn.jsdelivr.net/npm/hammerjs@2.0.8"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-zoom@1.1.1"></script>

然後在 JavaScript 代碼中使用:

// 取得 Canvas 元素
const ctx = document.getElementById('zoomChart').getContext('2d');

// 準備數據 - 生成更多的數據點
const labels = Array.from({ length: 100 }, (_, i) => i + 1);
const dataPoints = Array.from({ length: 100 }, () => Math.random() * 100);

// 建立圖表
const zoomChart = new Chart(ctx, {
    type: 'line',
    data: {
        labels: labels,
        datasets: [{
            label: '隨機數據',
            data: dataPoints,
            borderColor: 'rgba(75, 192, 192, 1)',
            borderWidth: 1,
            pointRadius: 0,  // 不顯示點,提高性能
        }]
    },
    options: {
        responsive: true,
        plugins: {
            title: {
                display: true,
                text: '可縮放和平移的圖表',
                font: { size: 18 }
            },
            zoom: {
                pan: {
                    enabled: true,
                    mode: 'xy'
                },
                zoom: {
                    wheel: {
                        enabled: true
                    },
                    pinch: {
                        enabled: true
                    },
                    mode: 'xy'
                }
            }
        }
    }
});

// 添加重置縮放按鈕
document.getElementById('resetZoom').onclick = function() {
    zoomChart.resetZoom();
};

在 HTML 中添加重置按鈕:

<button id="resetZoom">重置縮放</button>

第九部分:響應式設計與移動設備優化

響應式基礎配置

Chart.js 默認提供響應式行為,但您可以進一步優化:

const responsiveChart = new Chart(ctx, {
    type: 'bar',
    data: data,
    options: {
        responsive: true,
        maintainAspectRatio: false,  // 不維持原始寬高比
        aspectRatio: 2,  // 設定寬高比(如果 maintainAspectRatio 為 true)
        
        // 根據螢幕大小調整選項
        plugins: {
            legend: {
                position: window.innerWidth < 600 ? 'bottom' : 'top'
            }
        },
        // 針對小螢幕優化
        scales: {
            x: {
                ticks: {
                    autoSkip: true,  // 自動跳過標籤,避免過度擁擠
                    maxRotation: 45,  // 旋轉角度
                    minRotation: 45   // 最小旋轉角度
                }
            }
        }
    }
});

// 監聽視窗大小變化
window.addEventListener('resize', function() {
    // 動態調整圖表選項
    responsiveChart.options.plugins.legend.position = window.innerWidth < 600 ? 'bottom' : 'top';
    responsiveChart.update();
});

觸控事件優化

對於移動設備,可以優化觸控體驗:

const touchChart = new Chart(ctx, {
    type: 'line',
    data: data,
    options: {
        responsive: true,
        // 優化工具提示顯示
        plugins: {
            tooltip: {
                mode: 'index',  // 顯示全部同一位置的數據點
                intersect: false,  // 不需要直接點擊數據點
                // 在觸控設備上更大的工具提示
                bodyFont: {
                    size: 16  // 更大的字體,方便觸控設備閱讀
                },
                padding: 12  // 更大的內邊距
            },
            legend: {
                // 更大的點擊區域
                labels: {
                    boxWidth: 30,
                    padding: 20
                }
            }
        },
        // 更大的點,方便點擊
        elements: {
            point: {
                radius: 6,  // 默認大小
                hoverRadius: 12  // 懸停時更大
            }
        }
    }
});

第十部分:資源與總結

利用資源進行進一步學習

官方資源:

其他實用資源:

  • Stack Overflow 上的 Chart.js 標籤
  • 各種教學網站上的 Chart.js 教程
  • CodePen 和 JSFiddle 上的範例

常見問題與解決方案

1. 圖表不顯示?

  • 檢查 Canvas 元素是否存在
  • 確認數據格式是否正確
  • 檢查控制台錯誤信息

2. 響應式行為不如預期?

  • 確認父容器的樣式設置
  • 檢查 responsivemaintainAspectRatio 選項
  • 如果使用 hidden 或 display:none,需要手動更新圖表

3. 自定義工具提示不起作用?

  • 確認 callback 函數的格式是否正確
  • 注意 Chart.js 版本差異,API 可能有變化

最終項目:完整的數據儀表板

整合所學內容,創建一個小型數據儀表板:

<!DOCTYPE html>
<html lang="zh-TW">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Chart.js 數據儀表板</title>
    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
    <style>
        body {
            font-family: 'Arial', sans-serif;
            margin: 0;
            padding: 20px;
            background-color: #f8f9fa;
        }
        .dashboard {
            max-width: 1200px;
            margin: 0 auto;
        }
        .dashboard-header {
            text-align: center;
            margin-bottom: 30px;
        }
        .chart-container {
            background: white;
            border-radius: 8px;
            box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
            padding: 20px;
            margin-bottom: 20px;
        }
        .chart-row {
            display: flex;
            flex-wrap: wrap;
            gap: 20px;
            margin-bottom: 20px;
        }
        .chart-item {
            flex: 1;
            min-width: 300px;
            height: 300px;
        }
        @media (max-width: 768px) {
            .chart-item {
                min-width: 100%;
            }
        }
    </style>
</head>
<body>
    <div class="dashboard">
        <div class="dashboard-header">
            <h1>銷售數據儀表板</h1>
        </div>
        
        <div class="chart-row">
            <div class="chart-container chart-item">
                <canvas id="salesChart"></canvas>
            </div>
            <div class="chart-container chart-item">
                <canvas id="categoriesChart"></canvas>
            </div>
        </div>
        
        <div class="chart-row">
            <div class="chart-container chart-item">
                <canvas id="regionChart"></canvas>
            </div>
            <div class="chart-container chart-item">
                <canvas id="trendsChart"></canvas>
            </div>
        </div>
    </div>
    
    <script>
        // 模擬數據
        const monthlySales = {
            labels: ['一月', '二月', '三月', '四月', '五月', '六月'],
            datasets: [{
                label: '銷售額 (萬元)',
                data: [120, 190, 160, 210, 175, 225],
                backgroundColor: 'rgba(75, 192, 192, 0.6)',
                borderColor: 'rgba(75, 192, 192, 1)',
                borderWidth: 1
            }]
        };
        
        const categoryData = {
            labels: ['電子產品', '服裝', '食品', '家居', '運動'],
            datasets: [{
                label: '類別銷售佔比',
                data: [45, 25, 15, 10, 5],
                backgroundColor: [
                    'rgba(255, 99, 132, 0.7)',
                    'rgba(54, 162, 235, 0.7)',
                    'rgba(255, 206, 86, 0.7)',
                    'rgba(75, 192, 192, 0.7)',
                    'rgba(153, 102, 255, 0.7)'
                ],
                borderColor: [
                    'rgba(255, 99, 132, 1)',
                    'rgba(54, 162, 235, 1)',
                    'rgba(255, 206, 86, 1)',
                    'rgba(75, 192, 192, 1)',
                    'rgba(153, 102, 255, 1)'
                ],
                borderWidth: 1
            }]
        };
        
        const regionData = {
            labels: ['北', '中', '南', '東'],
            datasets: [{
                label: '區域銷售額',
                data: [300, 240, 190, 75],
                backgroundColor: [
                    'rgba(255, 159, 64, 0.7)',
                    'rgba(75, 192, 192, 0.7)',
                    'rgba(54, 162, 235, 0.7)',
                    'rgba(153, 102, 255, 0.7)'
                ],
                borderWidth: 1
            }]
        };
        
        const trendData = {
            labels: ['一月', '二月', '三月', '四月', '五月', '六月'],
            datasets: [{
                label: '網路銷售',
                data: [70, 90, 100, 120, 130, 140],
                borderColor: 'rgba(255, 99, 132, 1)',
                backgroundColor: 'rgba(255, 99, 132, 0.1)',
                fill: true
            }, {
                label: '實體銷售',
                data: [50, 100, 60, 90, 45, 85],
                borderColor: 'rgba(54, 162, 235, 1)',
                backgroundColor: 'rgba(54, 162, 235, 0.1)',
                fill: true
            }]
        };
        
        // 創建圖表
        const salesChart = new Chart(
            document.getElementById('salesChart').getContext('2d'),
            {
                type: 'bar',
                data: monthlySales,
                options: {
                    responsive: true,
                    maintainAspectRatio: false,
                    plugins: {
                        title: {
                            display: true,
                            text: '月度銷售額',
                            font: { size: 16 }
                        }
                    }
                }
            }
        );
        
        const categoriesChart = new Chart(
            document.getElementById('categoriesChart').getContext('2d'),
            {
                type: 'doughnut',
                data: categoryData,
                options: {
                    responsive: true,
                    maintainAspectRatio: false,
                    plugins: {
                        title: {
                            display: true,
                            text: '類別銷售佔比',
                            font: { size: 16 }
                        },
                        tooltip: {
                            callbacks: {
                                label: function(context) {
                                    const label = context.label || '';
                                    const value = context.parsed || 0;
                                    const total = context.dataset.data.reduce((a, b) => a + b, 0);
                                    const percentage = Math.round((value / total) * 100);
                                    return `${label}: ${percentage}%`;
                                }
                            }
                        }
                    }
                }
            }
        );
        
        const regionChart = new Chart(
            document.getElementById('regionChart').getContext('2d'),
            {
                type: 'polarArea',
                data: regionData,
                options: {
                    responsive: true,
                    maintainAspectRatio: false,
                    plugins: {
                        title: {
                            display: true,
                            text: '區域銷售分布',
                            font: { size: 16 }
                        }
                    }
                }
            }
        );
        
        const trendsChart = new Chart(
            document.getElementById('trendsChart').getContext('2d'),
            {
                type: 'line',
                data: trendData,
                options: {
                    responsive: true,
                    maintainAspectRatio: false,
                    plugins: {
                        title: {
                            display: true,
                            text: '銷售趨勢對比',
                            font: { size: 16 }
                        }
                    },
                    interaction: {
                        mode: 'index',
                        intersect: false
                    }
                }
            }
        );
    </script>
</body>
</html>

課程總結

在這個擴展版的 Chart.js 課程中,我們學習了:

  1. Chart.js 的基本概念和優勢

    • 輕量級、易於使用、靈活性強
    • 內建互動功能,增強用戶體驗
  2. 環境設置和基本架構

    • 如何引入 Chart.js
    • Canvas 元素的重要性
    • Chart 物件的基本結構
  3. 各種圖表類型的使用

    • 長條圖 (Bar Chart)
    • 線圖 (Line Chart)
    • 散點/氣泡圖 (Scatter/Bubble Chart)
    • 餅圖/環圈圖 (Pie/Doughnut Chart)
    • 雷達圖 (Radar Chart)
    • 極區圖 (Polar Area Chart)
  4. 動態數據與互動性

    • 數據即時更新
    • 用戶互動事件處理
    • 自定義工具提示
  5. 外部數據整合

    • 從 JSON、CSV 和 API 載入數據
  6. 使用插件擴展功能

    • 數據標籤
    • 縮放和平移
    • 其他常用插件
  7. 響應式設計與移動設備優化

    • 適應不同螢幕大小
    • 觸控體驗優化
  8. 進階應用場景

    • 儀表板設計
    • 混合圖表

這只是 Chart.js 強大功能的開始。通過持續實踐和探索,您可以創建出更加複雜、美觀、交互性強的數據可視化作品!

課後練習

  1. 嘗試將長條圖的顏色改為漸變色
  2. 為線圖添加動畫效果
  3. 創建一個餅圖或雷達圖
  4. 嘗試使用真實數據(如 CSV 檔案或 API)載入數據
  5. 使用 Chart.js 插件添加更多功能(如數據標籤或縮放功能)
  6. 搭建一個包含多個圖表的儀表板
  7. 實現一個能夠動態新增、修改數據的互動式圖表
  8. 嘗試整合第三方數據源,如氣象 API 或股票數據