amCharts 互動式圖表與儀表板設計指南
amCharts 視覺化指南
1. amCharts 簡介
amCharts 是一套功能強大的 JavaScript 圖表庫,專為建立互動式、美觀的數據視覺化而設計。它提供了廣泛的圖表類型,從基本的柱狀圖和折線圖到複雜的地圖和儀表板。
主要特點
- 豐富的圖表類型:提供超過 40 種不同的圖表類型
- 高度可定制:幾乎所有元素都可以自訂
- 互動性:內建豐富的互動功能,如縮放、拖動和懸停效果
- 響應式設計:自動適應不同尺寸的螢幕
- 跨瀏覽器兼容:支援所有主流瀏覽器
- 簡單的 API:易於理解和使用的 API
amCharts 資料科學視覺化指南
課程目標
- 了解 amCharts 的基本概念與優勢。
- 掌握 amCharts 4 和 amCharts 5 的主要差異。
- 學會使用 amCharts 創建常見的資料科學圖表。
- 熟悉 amCharts 的核心功能與特性,例如資料處理、樣式客製化、互動性等。
- 能夠將 amCharts 應用於實際的資料科學視覺化場景。
- 掌握尋找和利用 amCharts 學習資源的方法。
教材大綱 第一單元:初識 amCharts
- 第一節:amCharts 簡介:一個全面的資料視覺化函式庫
- 什麼是 amCharts?互動性與多功能性
- 簡史與理念
- amCharts 4 與 amCharts 5 主要版本差異概述
- 重點整理: 了解 amCharts 的定義、主要特點、發展歷程以及 amCharts 4 和 5 的關鍵區別。
第二單元:快速入門:創建你的第一個 amCharts 圖表
- 第二節:開始使用您的第一個 amCharts 視覺化
- 先決條件:JavaScript 和 SVG 支援
- 安裝與設定
- 創建一個簡單的圓餅圖:程式碼範例 (amCharts 4)
- 創建一個簡單的圓餅圖:程式碼範例 (amCharts 5)
- 重點整理: 學習如何在 HTML 中引入 amCharts,以及使用 JavaScript 創建一個基本的圓餅圖 (分別針對 amCharts 4 和 5)。
第三單元:探索 amCharts 的核心功能與特性
- 第三節:探索 amCharts 的核心功能與特性
- 多樣的圖表類型
- 主要功能與特性 (以 amCharts 5 為主)
- 資料處理與格式化
- 重點整理: 認識 amCharts 提供的豐富圖表類型,以及其在效能、客製化、互動性、可存取性和資料處理方面的主要特性。
第四單元:進階應用:客製化與互動
- 第四節:深入探討:進階概念與客製化
- 反應式設計
- 主題與樣式
- 互動性與事件
- 即時資料更新
- 與框架的整合
- 重點整理: 學習如何讓圖表具有反應式設計,如何使用主題和樣式進行客製化,以及如何添加互動功能和處理即時資料更新。
第五單元:實戰演練:amCharts 的實際應用與使用案例
- 第五節:amCharts 的實際應用與使用案例
- 商業儀表板與分析
- 金融資料視覺化
- 專案管理與時程
- 銷售與行銷分析
- 地理資料視覺化
- 即時監控與應用
- 重點整理: 了解 amCharts 在不同領域的實際應用案例,包括商業分析、金融、專案管理、銷售行銷和地理資料視覺化。
第六單元:進階學習資源與社群
- 第六節:amCharts 的進一步學習與開發資源
- 官方文件
- 互動式範例
- 教學與指南
- 支援論壇
- Pluralsight 課程
- 最新更新與消息
- 重點整理: 掌握尋找和利用 amCharts 官方文件、範例、教學、論壇以及其他學習資源的方法。
第七單元:amCharts 5 實戰範例
- 第七節:amCharts 5 實戰範例
- 基本配置與核心概念
- 基本圖表類型
- 資料處理與格式化
- 自訂圖表樣式
- 座標軸與刻度
- 圖例與標籤
- 互動性與動畫
- 複合圖表
- 地圖視覺化
- 儀表板與多圖表整合
- 金融儀表板
- 重點整理: 通過實戰範例,深入了解 amCharts 5 的各種功能和應用。
補充說明
- 在第七單元中,我已補充了儀表板與多圖表整合,以及金融儀表板的完整實作指南,包含程式碼和詳細註解。
- 建議您在學習過程中,多參考 amCharts 的官方網站 (https://www.amcharts.com/),以獲取最新的資訊和資源。
- 透過實際操作和練習,將能更深入地掌握 amCharts 的應用技巧。
2. 基礎配置與核心概念
amCharts 5 的核心概念包括:
- Root:最頂層的元素,所有的圖表都在這之下
- Container:用於放置圖表的容器元素
- Chart:圖表本身,如 XY 圖表、餅圖等
- Series:數據系列,如線條、柱狀等
- Axis:座標軸,用於定義數據的刻度和範圍
安裝與引入
<!-- 引入 amCharts 核心庫 -->
<script src="https://cdn.amcharts.com/lib/5/index.js"></script>
<!-- 引入圖表類型 -->
<script src="https://cdn.amcharts.com/lib/5/xy.js"></script>
<!-- 引入主題 -->
<script src="https://cdn.amcharts.com/lib/5/themes/Animated.js"></script>
基本例子
// 從 JSON 字串載入資料
const json_data = `{
"x_values": [1, 2, 3, 4, 5],
"y_values": [5, 10, 15, 20, 25]
}`;
const data = JSON.parse(json_data);
// 創建 root 元素
const root = am5.Root.new("chartdiv");
// 設置主題
root.setThemes([am5themes_Animated.new(root)]);
// 創建 XY 圖表
const chart = root.container.children.push(
am5xy.XYChart.new(root, {
panX: true,
panY: true,
wheelX: "panX",
wheelY: "zoomX"
})
);
// 創建 X 軸
const xAxis = chart.xAxes.push(
am5xy.ValueAxis.new(root, {
renderer: am5xy.AxisRendererX.new(root, {}),
min: 0
})
);
// 創建 Y 軸
const yAxis = chart.yAxes.push(
am5xy.ValueAxis.new(root, {
renderer: am5xy.AxisRendererY.new(root, {})
})
);
// 創建數據系列
const series = chart.series.push(
am5xy.LineSeries.new(root, {
name: "Series",
xAxis: xAxis,
yAxis: yAxis,
valueYField: "y",
valueXField: "x",
tooltip: am5.Tooltip.new(root, {
labelText: "{valueY}"
})
})
);
// 處理數據
const seriesData = [];
for (let i = 0; i < data.x_values.length; i++) {
seriesData.push({
x: data.x_values[i],
y: data.y_values[i]
});
}
// 設置數據
series.data.setAll(seriesData);
// 添加游標
chart.set("cursor", am5xy.XYCursor.new(root, {}));
// 添加滾動條
chart.set("scrollbarX", am5.Scrollbar.new(root, {
orientation: "horizontal"
}));
3. 基本圖表類型
折線圖 (XYChart)
// 從 JSON 字串載入資料
const json_data = `{
"data": [
{"date": "2023-01-01", "value": 100},
{"date": "2023-02-01", "value": 120},
{"date": "2023-03-01", "value": 115},
{"date": "2023-04-01", "value": 130},
{"date": "2023-05-01", "value": 145}
]
}`;
const data = JSON.parse(json_data);
// 創建 root 元素
const root = am5.Root.new("chartdiv");
root.setThemes([am5themes_Animated.new(root)]);
// 創建圖表
const chart = root.container.children.push(
am5xy.XYChart.new(root, {
panX: true,
panY: true,
wheelX: "panX",
wheelY: "zoomX"
})
);
// 創建 X 軸
const xAxis = chart.xAxes.push(
am5xy.DateAxis.new(root, {
baseInterval: { timeUnit: "day", count: 1 },
renderer: am5xy.AxisRendererX.new(root, {})
})
);
// 創建 Y 軸
const yAxis = chart.yAxes.push(
am5xy.ValueAxis.new(root, {
renderer: am5xy.AxisRendererY.new(root, {})
})
);
// 創建數據系列
const series = chart.series.push(
am5xy.LineSeries.new(root, {
name: "Series",
xAxis: xAxis,
yAxis: yAxis,
valueYField: "value",
valueXField: "date",
tooltip: am5.Tooltip.new(root, {
labelText: "{valueY}"
})
})
);
// 設置數據
series.data.setAll(data.data);
// 添加游標
chart.set("cursor", am5xy.XYCursor.new(root, {}));
// 添加圖例
chart.children.push(
am5.Legend.new(root, {
centerX: am5.p50,
x: am5.p50
})
);
柱狀圖
// 從 JSON 字串載入資料
const json_data = `{
"data": [
{"category": "A", "value": 10},
{"category": "B", "value": 15},
{"category": "C", "value": 8},
{"category": "D", "value": 12}
]
}`;
const data = JSON.parse(json_data);
// 創建 root 元素
const root = am5.Root.new("chartdiv");
root.setThemes([am5themes_Animated.new(root)]);
// 創建圖表
const chart = root.container.children.push(
am5xy.XYChart.new(root, {
panX: false,
panY: false,
wheelX: "none",
wheelY: "none"
})
);
// 創建 X 軸
const xAxis = chart.xAxes.push(
am5xy.CategoryAxis.new(root, {
categoryField: "category",
renderer: am5xy.AxisRendererX.new(root, {
cellStartLocation: 0.1,
cellEndLocation: 0.9
})
})
);
xAxis.data.setAll(data.data);
// 創建 Y 軸
const yAxis = chart.yAxes.push(
am5xy.ValueAxis.new(root, {
renderer: am5xy.AxisRendererY.new(root, {})
})
);
// 創建數據系列
const series = chart.series.push(
am5xy.ColumnSeries.new(root, {
name: "Series",
xAxis: xAxis,
yAxis: yAxis,
valueYField: "value",
categoryXField: "category",
tooltip: am5.Tooltip.new(root, {
labelText: "{valueY}"
})
})
);
// 設置數據
series.data.setAll(data.data);
// 添加標籤
series.columns.template.adapters.add("fill", (fill, target) => {
return chart.get("colors").getIndex(series.columns.indexOf(target));
});
// 添加圖例
chart.children.push(
am5.Legend.new(root, {
centerX: am5.p50,
x: am5.p50
})
);
餅圖
// 從 JSON 字串載入資料
const json_data = `{
"data": [
{"category": "類別 A", "value": 30},
{"category": "類別 B", "value": 25},
{"category": "類別 C", "value": 15},
{"category": "類別 D", "value": 10},
{"category": "類別 E", "value": 20}
]
}`;
const data = JSON.parse(json_data);
// 創建 root 元素
const root = am5.Root.new("chartdiv");
root.setThemes([am5themes_Animated.new(root)]);
// 創建圖表
const chart = root.container.children.push(
am5percent.PieChart.new(root, {
layout: root.verticalLayout,
innerRadius: am5.percent(50) // 若要創建環形圖
})
);
// 創建數據系列
const series = chart.series.push(
am5percent.PieSeries.new(root, {
valueField: "value",
categoryField: "category",
endAngle: 270
})
);
// 設置數據
series.data.setAll(data.data);
// 添加圖例
const legend = chart.children.push(
am5.Legend.new(root, {
centerX: am5.percent(50),
x: am5.percent(50),
marginTop: 15,
marginBottom: 15
})
);
legend.data.setAll(series.dataItems);
// 添加標籤
series.labels.template.set("visible", false);
series.ticks.template.set("visible", false);
// 添加動畫
series.appear(1000, 100);
雷達圖
// 從 JSON 字串載入資料
const json_data = `{
"data": [
{"category": "銷售", "value": 80, "full": 100},
{"category": "行銷", "value": 65, "full": 100},
{"category": "開發", "value": 90, "full": 100},
{"category": "客服", "value": 75, "full": 100},
{"category": "人力資源", "value": 60, "full": 100}
]
}`;
const data = JSON.parse(json_data);
// 創建 root 元素
const root = am5.Root.new("chartdiv");
root.setThemes([am5themes_Animated.new(root)]);
// 創建圖表
const chart = root.container.children.push(
am5radar.RadarChart.new(root, {
panX: false,
panY: false,
wheelX: "panX",
wheelY: "zoomX"
})
);
// 創建雷達軸
const xAxis = chart.xAxes.push(
am5xy.CategoryAxis.new(root, {
renderer: am5radar.AxisRendererCircular.new(root, {
minGridDistance: 30
}),
categoryField: "category"
})
);
xAxis.data.setAll(data.data);
// 創建數值軸
const yAxis = chart.yAxes.push(
am5xy.ValueAxis.new(root, {
renderer: am5radar.AxisRendererRadial.new(root, {})
})
);
// 創建數據系列
const series = chart.series.push(
am5radar.RadarLineSeries.new(root, {
name: "Series",
xAxis: xAxis,
yAxis: yAxis,
valueYField: "value",
categoryXField: "category",
fill: am5.color(0x5555ff),
stroke: am5.color(0x5555ff)
})
);
series.strokes.template.set("strokeWidth", 2);
series.bullets.push(function() {
return am5.Bullet.new(root, {
sprite: am5.Circle.new(root, {
radius: 5,
fill: series.get("fill")
})
});
});
// 設置數據
series.data.setAll(data.data);
// 添加背景系列
const series2 = chart.series.push(
am5radar.RadarLineSeries.new(root, {
name: "Series",
xAxis: xAxis,
yAxis: yAxis,
valueYField: "full",
categoryXField: "category",
stroke: am5.color(0xdddddd),
fill: am5.color(0xdddddd)
})
);
series2.data.setAll(data.data);
4. 資料處理與格式化
amCharts 提供了多種方式來處理和格式化數據,包括直接 JSON 字串、API 請求和代理伺服器。
數據加載模式對照表
加載方式 | 適用場景 | 範例 API 端點 |
---|---|---|
直接 JSON 字串 | 靜態小數據 | 雷達圖範例中的 json_data |
遠端 API 請求 | 動態更新數據 | https://api.marketdata.com/nvda |
代理伺服器 | 跨域請求解決方案 | /json-proxy?url=外部API |
從 API 獲取數據
// 初始化圖表
let chart = am5xy.XYChart.new(root, {
panX: true,
panY: true
});
// 加載遠程數據
chart.dataSource.url = "https://api.example.com/line-data";
chart.dataSource.parser = new am5xy.JSONParser();
// 創建坐標軸
let xAxis = chart.xAxes.push(am5xy.DateAxis.new(root, {}));
let yAxis = chart.yAxes.push(am5xy.ValueAxis.new(root, {}));
// 創建折線序列
let series = chart.series.push(am5xy.LineSeries.new(root, {
name: "銷售額",
xAxis: xAxis,
yAxis: yAxis,
valueYField: "value",
valueXField: "date"
}));
處理跨域 (CORS) 問題
當遇到跨域限制時,可以使用代理伺服器來解決問題:
// 使用代理伺服器獲取數據
chart.dataSource.url = "/json-proxy?url=https://external-api.com/data";
chart.dataSource.parser = new am5xy.JSONParser();
5. 自訂圖表樣式
amCharts 提供了豐富的樣式自訂選項,讓您可以根據需求調整圖表的外觀。
主題設定
// 引入主題
root.setThemes([
am5themes_Animated.new(root),
am5themes_Dark.new(root) // 使用深色主題
]);
// 創建自訂主題
let myTheme = am5.Theme.new(root);
myTheme.rule("Grid").setAll({
strokeWidth: 0.5,
strokeOpacity: 0.5
});
// 應用自訂主題
root.setThemes([
am5themes_Animated.new(root),
myTheme
]);
顏色設定
// 設定自訂顏色
chart.get("colors").set("colors", [
am5.color(0x5555FF),
am5.color(0xFF5555),
am5.color(0x55FF55),
am5.color(0xFFFF55)
]);
// 為特定系列設定顏色
series.set("fill", am5.color(0x5555FF));
series.set("stroke", am5.color(0x5555FF));
// 根據數據值設定顏色
series.columns.template.adapters.add("fill", (fill, target) => {
const value = target.dataItem.get("valueY");
if (value < 10) {
return am5.color(0xFF0000);
} else if (value < 20) {
return am5.color(0xFFFF00);
} else {
return am5.color(0x00FF00);
}
});
6. 座標軸與刻度
座標軸是圖表中顯示數據刻度的重要元素,amCharts 提供了多種自訂選項。
座標軸類型
// 數值軸
const valueAxis = chart.yAxes.push(
am5xy.ValueAxis.new(root, {
min: 0, // 最小值
max: 100, // 最大值
strictMinMax: true, // 嚴格限制最大最小值
renderer: am5xy.AxisRendererY.new(root, {})
})
);
// 類別軸
const categoryAxis = chart.xAxes.push(
am5xy.CategoryAxis.new(root, {
categoryField: "category",
renderer: am5xy.AxisRendererX.new(root, {})
})
);
// 日期軸
const dateAxis = chart.xAxes.push(
am5xy.DateAxis.new(root, {
baseInterval: { timeUnit: "day", count: 1 }, // 基本間隔為1天
renderer: am5xy.AxisRendererX.new(root, {})
})
);
// 持續時間軸
const durationAxis = chart.yAxes.push(
am5xy.DurationAxis.new(root, {
baseUnit: "minute", // 基本單位為分鐘
renderer: am5xy.AxisRendererY.new(root, {})
})
);
刻度與網格線
// 設定 X 軸網格線
xAxis.get("renderer").grid.template.setAll({
stroke: am5.color(0x000000),
strokeOpacity: 0.1,
strokeWidth: 1
});
// 設定 Y 軸網格線
yAxis.get("renderer").grid.template.setAll({
stroke: am5.color(0x000000),
strokeOpacity: 0.1,
strokeWidth: 1
});
// 設定刻度樣式
xAxis.get("renderer").labels.template.setAll({
fill: am5.color(0x666666),
fontSize: 12
});
// 設定座標軸標題
xAxis.set("title", am5.Label.new(root, {
text: "日期",
fill: am5.color(0x000000),
fontSize: 16,
fontWeight: "bold"
}));
yAxis.set("title", am5.Label.new(root, {
text: "銷售額",
fill: am5.color(0x000000),
fontSize: 16,
fontWeight: "bold",
rotation: -90
}));
7. 圖例與標籤
圖例和標籤可以幫助用戶更好地理解圖表中的數據。
圖例配置
// 添加圖例
const legend = chart.children.push(
am5.Legend.new(root, {
centerX: am5.p50, // 水平置中
x: am5.p50, // 水平置中
marginTop: 15,
marginBottom: 15
})
);
// 設定圖例數據
legend.data.setAll(chart.series.values);
// 自訂圖例樣式
legend.labels.template.setAll({
fontSize: 14,
fill: am5.color(0x666666)
});
legend.markers.template.setAll({
width: 16,
height: 16
});
// 互動設定:點擊圖例項目顯示/隱藏相應系列
legend.itemContainers.template.events.on("click", (e) => {
const series = e.target.dataItem.dataContext;
if (!series.isHidden()) {
series.hide();
} else {
series.show();
}
});
數據標籤
// 柱狀圖標籤
series.columns.template.setAll({
tooltipText: "{categoryX}: {valueY}",
tooltipY: 0,
strokeOpacity: 0
});
// 為柱狀圖添加數據標籤
series.columns.template.adapters.add("fill", function(fill, target) {
return chart.get("colors").getIndex(series.columns.indexOf(target));
});
// 折線圖標籤
series.bullets.push(function() {
return am5.Bullet.new(root, {
locationY: 0,
sprite: am5.Label.new(root, {
text: "{valueY}",
fill: am5.color(0x000000),
centerY: am5.p50,
centerX: am5.p50,
populateText: true
})
});
});
// 餅圖標籤
pieSeries.labels.template.setAll({
fontSize: 12,
text: "{category}: {value}",
radius: 10
});
pieSeries.ticks.template.setAll({
forceHidden: false,
strokeOpacity: 0.5
});
8. 互動性與動畫
amCharts 提供了豐富的互動性和動畫選項,使圖表更加生動有趣。
游標與縮放
// 添加游標
const cursor = chart.set("cursor", am5xy.XYCursor.new(root, {
behavior: "zoomX", // 僅水平方向縮放
xAxis: xAxis,
yAxis: yAxis
}));
// 游標樣式
cursor.lineX.set("visible", true);
cursor.lineX.set("stroke", am5.color(0x000000));
cursor.lineX.set("strokeDasharray", [2, 2]);
cursor.lineY.set("visible", true);
cursor.lineY.set("stroke", am5.color(0x000000));
cursor.lineY.set("strokeDasharray", [2, 2]);
// 添加縮放按鈕
chart.set("zoomOutButton", am5.Button.new(root, {
x: am5.p100,
y: 0,
themeTags: ["zoom"],
icon: am5.Graphics.new(root, {
themeTags: ["icon"],
stroke: am5.color(0x000000),
strokeWidth: 2,
draw: function(display) {
display.moveTo(0, 0);
display.lineTo(16, 0);
display.moveTo(0, 8);
display.lineTo(16, 8);
display.moveTo(0, 16);
display.lineTo(16, 16);
}
})
}));
動畫效果
// 設置圖表動畫
chart.appear(1000, 100);
// 設置系列動畫
series.appear(1000, 100);
// 設置柱狀圖動畫
series.columns.template.setAll({
cornerRadiusTL: 5,
cornerRadiusTR: 5,
strokeOpacity: 0
});
// 自訂動畫設置
series.set("sequencedInterpolation", true); // 序列化動畫
series.set("sequencedDelay", 100); // 動畫延遲
series.set("easing", am5.ease.cubicOut); // 動畫過渡效果
// 設置滑鼠懸停效果
series.columns.template.states.create("hover", {
scale: 1.1,
fill: am5.color(0xFF0000)
});
9. 複合圖表
複合圖表結合了多種圖表類型,可以在一個視圖中展示不同維度的數據。
多軸圖表
// 創建主軸 (左側)
const yAxis = chart.yAxes.push(
am5xy.ValueAxis.new(root, {
renderer: am5xy.AxisRendererY.new(root, {})
})
);
// 創建第二軸 (右側)
const yAxis2 = chart.yAxes.push(
am5xy.ValueAxis.new(root, {
renderer: am5xy.AxisRendererY.new(root, {
opposite: true // 顯示在右側
})
})
);
// 創建折線圖系列 (使用左側軸)
const lineSeries = chart.series.push(
am5xy.LineSeries.new(root, {
name: "銷售額",
xAxis: xAxis,
yAxis: yAxis,
valueYField: "sales",
valueXField: "date",
stroke: am5.color(0x5555FF),
fill: am5.color(0x5555FF)
})
);
// 創建柱狀圖系列 (使用右側軸)
const columnSeries = chart.series.push(
am5xy.ColumnSeries.new(root, {
name: "利潤",
xAxis: xAxis,
yAxis: yAxis2,
valueYField: "profit",
valueXField: "date",
stroke: am5.color(0xFF5555),
fill: am5.color(0xFF5555)
})
);
// 設置軸標題
yAxis.set("title", am5.Label.new(root, {
text: "銷售額",
rotation: -90,
y: am5.p50,
centerX: am5.p50
}));
yAxis2.set("title", am5.Label.new(root, {
text: "利潤",
rotation: 90,
y: am5.p50,
centerX: am5.p50
}));
混合圖表
// JSON 數據
const json_data = `{
"data": [
{"date": "2025-01", "value1": 100, "value2": 120, "value3": 70},
{"date": "2025-02", "value1": 110, "value2": 130, "value3": 85},
{"date": "2025-03", "value1": 130, "value2": 100, "value3": 60},
{"date": "2025-04", "value1": 105, "value2": 110, "value3": 90}
]
}`;
const data = JSON.parse(json_data);
// 創建根元素
const root = am5.Root.new("chartdiv");
root.setThemes([am5themes_Animated.new(root)]);
// 創建圖表
const chart = root.container.children.push(
am5xy.XYChart.new(root, {
panX: true,
panY: true,
wheelX: "panX",
wheelY: "zoomX"
})
);
// 創建 X 軸
const xAxis = chart.xAxes.push(
am5xy.CategoryAxis.new(root, {
categoryField: "date",
renderer: am5xy.AxisRendererX.new(root, {}),
tooltip: am5.Tooltip.new(root, {})
})
);
xAxis.data.setAll(data.data);
// 創建 Y 軸
const yAxis = chart.yAxes.push(
am5xy.ValueAxis.new(root, {
renderer: am5xy.AxisRendererY.new(root, {})
})
);
// 創建柱狀圖系列
const columnSeries = chart.series.push(
am5xy.ColumnSeries.new(root, {
name: "收入",
xAxis: xAxis,
yAxis: yAxis,
valueYField: "value1",
categoryXField: "date",
tooltip: am5.Tooltip.new(root, {
labelText: "{name}: {valueY}"
})
})
);
columnSeries.columns.template.setAll({
width: am5.percent(80),
tooltipY: 0
});
columnSeries.data.setAll(data.data);
// 創建折線圖系列
const lineSeries1 = chart.series.push(
am5xy.LineSeries.new(root, {
name: "利潤",
xAxis: xAxis,
yAxis: yAxis,
valueYField: "value2",
categoryXField: "date",
stroke: am5.color(0xFF5555),
fill: am5.color(0xFF5555),
tooltip: am5.Tooltip.new(root, {
labelText: "{name}: {valueY}"
})
})
);
lineSeries1.strokes.template.setAll({
strokeWidth: 2
});
// 添加圓點
lineSeries1.bullets.push(function() {
return am5.Bullet.new(root, {
sprite: am5.Circle.new(root, {
radius: 4,
fill: lineSeries1.get("fill")
})
});
});
lineSeries1.data.setAll(data.data);
// 創建第二個折線圖系列
const lineSeries2 = chart.series.push(
am5xy.LineSeries.new(root, {
name: "成本",
xAxis: xAxis,
yAxis: yAxis,
valueYField: "value3",
categoryXField: "date",
stroke: am5.color(0x55FF55),
fill: am5.color(0x55FF55),
tooltip: am5.Tooltip.new(root, {
labelText: "{name}: {valueY}"
})
})
);
lineSeries2.strokes.template.setAll({
strokeWidth: 2,
strokeDasharray: [2, 2]
});
lineSeries2.bullets.push(function() {
return am5.Bullet.new(root, {
sprite: am5.Circle.new(root, {
radius: 4,
fill: lineSeries2.get("fill")
})
});
});
lineSeries2.data.setAll(data.data);
// 添加圖例
const legend = chart.children.push(
am5.Legend.new(root, {
centerX: am5.percent(50),
x: am5.percent(50)
})
);
legend.data.setAll(chart.series.values);
// 添加游標
chart.set("cursor", am5xy.XYCursor.new(root, {
behavior: "zoomX"
}));
10. 地圖視覺化
amCharts 提供了強大的地圖視覺化功能,可以創建交互式的地理數據展示。
// 創建根元素
const root = am5.Root.new("chartdiv");
root.setThemes([am5themes_Animated.new(root)]);
// 創建地圖
const chart = root.container.children.push(
am5map.MapChart.new(root, {
panX: "rotateX",
panY: "rotateY",
projection: am5map.geoMercator()
})
);
// 創建世界地圖多邊形系列
const polygonSeries = chart.series.push(
am5map.MapPolygonSeries.new(root, {
geoJSON: am5geodata_worldLow,
exclude: ["AQ"] // 排除南極洲
})
);
// 設置多邊形填充色
polygonSeries.mapPolygons.template.setAll({
tooltipText: "{name}",
toggleKey: "active",
interactive: true,
fill: am5.color(0xDDDDDD)
});
// 設置懸停狀態
polygonSeries.mapPolygons.template.states.create("hover", {
fill: am5.color(0xAAAAAA)
});
// 設置活動狀態
polygonSeries.mapPolygons.template.states.create("active", {
fill: am5.color(0x5555FF)
});
// 添加熱力數據系列
const heatSeries = chart.series.push(
am5map.MapPolygonSeries.new(root, {
geoJSON: am5geodata_worldLow,
valueField: "value",
calculateAggregates: true
})
);
// 設置熱力圖顏色
heatSeries.mapPolygons.template.setAll({
tooltipText: "{name}: {value}",
interactive: true
});
// 設置熱力圖數據
heatSeries.data.setAll([
{ id: "TW", value: 100 },
{ id: "US", value: 75 },
{ id: "JP", value: 60 },
{ id: "DE", value: 50 },
{ id: "FR", value: 45 },
{ id: "UK", value: 40 }
]);
// 設置熱力圖顏色範圍
heatSeries.set("heatRules", [{
target: heatSeries.mapPolygons.template,
dataField: "value",
min: am5.color(0xFFFFFF),
max: am5.color(0xFF0000),
key: "fill"
}]);
// 添加縮放控制
chart.set("zoomControl", am5map.ZoomControl.new(root, {}));
// 初始化地圖位置
chart.appear(1000, 100);
11. 儀表板與多圖表整合
整合多個圖表可創建功能強大的數據儀表板,以下示範垂直/水平混合布局與即時數據聯動:
// 創建根元素並啟用動畫主題
const root = am5.Root.new("dashboard");
root.setThemes([am5themes_Animated.new(root)]);
// 主容器設定 (垂直布局)
const container = root.container.children.push(
am5.Container.new(root, {
width: am5.percent(100),
height: am5.percent(100),
layout: root.verticalLayout, // 垂直排列子元素
paddingTop: 10,
paddingBottom: 10
})
);
//=== 第一行:關鍵指標區 (水平布局) ===//
const topRow = container.children.push(
am5.Container.new(root, {
width: am5.percent(100),
height: am5.percent(30), // 佔儀表板30%高度
layout: root.horizontalLayout, // 水平排列圖表
paddingLeft: 15,
paddingRight: 15
})
);
// 元件1:即時業績圓餅圖
const pieChart = topRow.children.push(
am5pie.PieChart.new(root, {
width: am5.percent(50), // 佔行寬50%
height: am5.percent(100),
radius: am5.percent(75), // 控制餅圖大小
innerRadius: am5.percent(40) // 創建環形效果
})
);
// 圓餅數據系列
const pieSeries = pieChart.series.push(
am5pie.PieSeries.new(root, {
name: "即時業績分布",
valueField: "value",
categoryField: "category",
alignLabels: true // 自動調整標籤位置
})
);
// 設定樣式與互動
pieSeries.slices.template.setAll({
stroke: am5.color(0xffffff),
strokeWidth: 2,
templateField: "sliceSettings" // 允許個別設定樣式
});
pieSeries.labels.template.setAll({
text: "{category}: [bold]{valuePercentTotal.formatNumber('0.0')}%[/]",
fontSize: 12,
fill: am5.color(0x555555)
});
// 載入範例數據
pieSeries.data.setAll([
{ category: "北美", value: 45, sliceSettings: { fill: am5.color(0x4CAF50) } },
{ category: "歐洲", value: 30, sliceSettings: { fill: am5.color(0x2196F3) } },
{ category: "亞太", value: 25, sliceSettings: { fill: am5.color(0xFF9800) } }
]);
// 元件2:即時效能儀表板
const gaugeChart = topRow.children.push(
am5radar.RadarChart.new(root, {
width: am5.percent(50),
height: am5.percent(100),
innerRadius: am5.percent(60) // 控制儀表板中心空白區域
})
);
// 儀表板軸設定
const axisRenderer = am5radar.AxisRendererCircular.new(root, {
innerRadius: -0.5, // 指針旋轉中心點
strokeOpacity: 0.8,
strokeWidth: 2
});
const valueAxis = gaugeChart.xAxes.push(
am5xy.ValueAxis.new(root, {
min: 0,
max: 100,
strictMinMax: true,
renderer: axisRenderer
})
);
// 指針系列設定
const gaugeSeries = gaugeChart.series.push(
am5radar.ClockHand.new(root, {
animationDuration: 1000, // 指針動畫時間(毫秒)
pinRadius: 15, // 指針樞紐半徑
radius: am5.percent(80), // 指針長度
value: 75 // 初始數值
})
);
// 動態更新指針 (範例)
setInterval(() => {
gaugeSeries.set("value", Math.random() * 100);
}, 3000);
//=== 第二行:趨勢分析區 ===//
const bottomRow = container.children.push(
am5.Container.new(root, {
width: am5.percent(100),
height: am5.percent(70), // 佔儀表板70%高度
layout: root.verticalLayout,
paddingTop: 10
})
);
// 元件3:互動式折線圖
const lineChart = bottomRow.children.push(
am5xy.XYChart.new(root, {
width: am5.percent(100),
height: am5.percent(100),
panX: true,
panY: true,
wheelX: "panX",
wheelY: "zoomX"
})
);
// 時間軸設定
const dateAxis = lineChart.xAxes.push(
am5xy.DateAxis.new(root, {
baseInterval: { timeUnit: "day", count: 1 },
renderer: am5xy.AxisRendererX.new(root, {
minGridDistance: 50 // 最小刻度間距(像素)
})
})
);
// 數值軸設定
const valueAxisLine = lineChart.yAxes.push(
am5xy.ValueAxis.new(root, {
extraMin: 0.1, // 防止數據貼底
renderer: am5xy.AxisRendererY.new(root, {})
})
);
// 折線系列設定
const lineSeries = lineChart.series.push(
am5xy.LineSeries.new(root, {
name: "即時趨勢",
xAxis: dateAxis,
yAxis: valueAxisLine,
valueYField: "value",
valueXField: "date",
stroke: am5.color(0x673AB7),
tooltip: am5.Tooltip.new(root, {
labelText: "[{categoryX.formatDate('yyyy-MM-dd')}] 數值: {valueY}",
pointerOrientation: "horizontal"
})
})
);
// 生成模擬時間序列數據
const generateLineData = () => {
const data = [];
let value = 50;
const today = new Date();
for (let i = -30; i <= 0; i++) {
const date = new Date(today);
date.setDate(date.getDate() + i);
value += (Math.random() - 0.5) * 10;
data.push({
date: date.getTime(),
value: Math.round(value)
});
}
return data;
};
lineSeries.data.setAll(generateLineData());
// 添加十字線
lineChart.set("cursor", am5xy.XYCursor.new(root, {
behavior: "zoomX",
xAxis: dateAxis,
yAxis: valueAxisLine
}));
// 添加滾動條
const scrollbar = lineChart.set("scrollbarX", am5xy.XYChartScrollbar.new(root, {
orientation: "horizontal",
height: 50
}));
scrollbar.series.push(lineSeries);
儀表板技術亮點
-
混合布局系統
- 使用
verticalLayout
與horizontalLayout
嵌套實現複雜布局 - 百分比尺寸設定 (
am5.percent()
) 確保響應式顯示
- 使用
-
數據聯動機制
// 點擊餅圖時更新折線圖數據
pieSeries.slices.template.events.on("click", (ev) => {
const category = ev.target.dataItem.get("category");
lineSeries.data.setAll(generateRegionData(category));
});
- 樣式模板化
// 統一設定工具提示樣式
am5.Tooltip.rootStyles.setAll({
fontSize: "0.8em",
fill: am5.color(0xFFFFFF),
stroke: am5.color(0x000000)
});
- 性能優化
// 啟用數據處理加速
root.autoResize = true;
root.fps = 60; // 限制幀率節省資源
// 關閉非焦點圖表的動畫
lineChart.series.template.set("forceHidden", true);
root.events.on("focus", () => {
lineChart.series.template.set("forceHidden", false);
});
儀表板擴充建議
- 添加控制元件
// 創建數據刷新按鈕
const refreshBtn = container.children.push(am5.Button.new(root, {
labelText: "即時更新",
x: am5.percent(95),
y: am5.percent(98),
background: am5.RoundedRectangle.new(root, {
fill: am5.color(0x2196F3)
})
}));
refreshBtn.events.on("click", () => {
pieSeries.data.setAll(generateNewPieData());
});
- 跨圖表互動
// 同步顯示工具提示
lineChart.get("cursor").lineX.set("visible", true);
pieChart.set("tooltip", lineChart.get("tooltip"));
- 狀態保存
// 保存當前縮放狀態
let initialSelection;
lineChart.events.on("selected", (ev) => {
initialSelection = ev.target.get("selection");
});
整合關鍵點說明
- 容器層級管理:使用
container.children.push()
嚴格控制元素疊加順序 - 資源回收機制:需在卸載時手動清理對象
// 卸載儀表板時
root.dispose();
完整實作教學
一、準備工作
-
建立 Replit 專案
- 登入 Replit
- 建立一個新的 HTML、CSS、JS 項目
-
安裝 amCharts 5
<script src="https://cdn.amcharts.com/lib/5/index.js"></script> <script src="https://cdn.amcharts.com/lib/5/percent.js"></script> <script src="https://cdn.amcharts.com/lib/5/radar.js"></script> <script src="https://cdn.amcharts.com/lib/5/themes/Animated.js"></script>
二、HTML 結構
在 index.html
中設置儀表板容器:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>amCharts 儀表板</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
}
#dashboard {
width: 100%;
height: 100vh;
display: flex;
flex-direction: column;
}
.row {
display: flex;
flex: 1;
}
.chart {
flex: 1;
margin: 10px;
}
</style>
</head>
<body>
<div id="dashboard">
<div class="row">
<div id="pieChart" class="chart"></div>
<div id="gaugeChart" class="chart"></div>
</div>
<div class="row">
<div id="lineChart" class="chart"></div>
</div>
</div>
<script src="script.js"></script>
</body>
</html>
三、JavaScript 實現 (儀表板邏輯)
1. 初始化 amCharts 核心
// 引入 amCharts 主題
am5.ready(function() {
const root = am5.Root.new("dashboard");
root.setThemes([am5themes_Animated.new(root)]);
});
2. 圓餅圖 (Pie Chart)
const pieChart = am5percent.PieChart.new(root, {
layout: root.horizontalLayout
});
const pieSeries = pieChart.series.push(am5percent.PieSeries.new(root, {
valueField: "value",
categoryField: "category"
}));
pieSeries.data.setAll([
{ category: "北美", value: 45 },
]);
amCharts 金融儀表板實作指南
核心需求
- 整合股價K線圖 (Candlestick)與交易量柱狀圖
- 添加技術指標面板 (RSI、MACD)
- 實現多圖表聯動互動
- 支援即時數據更新
- 使用正體中文註解
技術規格
index.html
├─ styles.css
└─ script.js
實作步驟
1. 基礎架構
<!DOCTYPE html>
<html lang="zh-Hant">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>即時金融儀表板</title>
<style>
/* 自適應容器 */
#dashboard {
width: 100vw;
height: 100vh;
font-family: "Microsoft JhengHei", sans-serif;
}
</style>
</head>
<body>
<div id="dashboard"></div>
<!-- amCharts 核心依賴 -->
<script src="https://cdn.amcharts.com/lib/5/index.js"></script>
<script src="https://cdn.amcharts.com/lib/5/stock.js"></script>
<script src="https://cdn.amcharts.com/lib/5/themes/Animated.js"></script>
<script src="script.js"></script>
</body>
</html>
2. 樣式優化
/* styles.css */
.am5-chart-div {
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
border-radius: 8px;
margin: 10px;
}
/* 手機端響應式 */
@media (max-width: 600px) {
#dashboard {
flex-direction: column;
}
}
3. 儀表板核心邏輯
// script.js
am5.ready(function() {
//▇▇ 根容器設定 ▇▇
const root = am5.Root.new("dashboard");
root.setThemes([am5themes_Animated.new(root)]);
//▇▇ 即時數據模擬器 ▇▇
function generateMockData() {
const data = [];
let value = 500;
const date = new Date();
for (let i = -100; i <= 0; i++) {
const newDate = new Date(date);
newDate.setDate(date.getDate() + i);
const open = value;
const close = open + (Math.random() - 0.5) * 20;
const high = Math.max(open, close) + Math.random() * 10;
const low = Math.min(open, close) - Math.random() * 10;
data.push({
date: newDate.getTime(),
open: open.toFixed(2),
high: high.toFixed(2),
low: low.toFixed(2),
close: close.toFixed(2),
volume: Math.round(Math.random() * 1e6)
});
value = close;
}
return data;
}
//▇▇ 主圖表組建 ▇▇
const stockChart = am5stock.StockChart.new(root, {
layout: root.verticalLayout
});
//➊ K線面板
const mainPanel = stockChart.panels.push(am5stock.StockPanel.new(root, {
height: 0.6,
panX: true,
panY: true
}));
//➋ 交易量面板
const volumePanel = stockChart.panels.push(am5stock.StockPanel.new(root, {
height: 0.2
}));
//▇▇ 即時更新機制 ▇▇
setInterval(() => {
const newData = generateMockData();
stockChart.data.setAll([{
category: "NVDA",
data: newData
}]);
}, 5000); // 每5秒更新
// 添加蜡烛序列
let candleSeries = mainPanel.series.push(am5stock.CandlestickSeries.new(root, {
name: "NVDA",
xAxis: dateAxis,
highField: "high",
lowField: "low",
openField: "open",
closeField: "close"
}));
// 添加交易量柱狀圖
let volumeSeries = volumePanel.series.push(am5stock.ColumnSeries.new(root, {
name: "交易量",
xAxis: dateAxis,
valueYField: "volume"
}));
// 添加RSI指標
const rsiPanel = stockChart.panels.push(am5stock.StockPanel.new(root, {
height: 0.1
}));
const rsiSeries = rsiPanel.series.push(am5stock.LineSeries.new(root, {
name: "RSI",
valueYField: "rsi",
valueXField: "date"
}));
// 添加MACD指標
const macdPanel = stockChart.panels.push(am5stock.StockPanel.new(root, {
height: 0.1
}));
const macdSeries = macdPanel.series.push(am5stock.LineSeries.new(root, {
name: "MACD",
valueYField: "macd",
valueXField: "date"
}));
// 初始化日期軸
const dateAxis = mainPanel.xAxes.push(am5xy.DateAxis.new(root, {
baseInterval: { timeUnit: "day", count: 1 },
renderer: am5xy.AxisRendererX.new(root, {})
}));
// 初始化數據
const initialData = generateMockData();
stockChart.data.setAll([{
category: "NVDA",
data: initialData
}]);
});
4. 技術指標計算
// 在generateMockData()中添加RSI與MACD計算
function calculateRSI(data) {
const rsiValues = [];
for (let i = 0; i < data.length; i++) {
if (i < 14) {
rsiValues.push(null); // 頭14天無法計算RSI
} else {
const gains = [];
const losses = [];
for (let j = i - 14; j <= i; j++) {
const gain = data[j].close - data[j - 1].close;
if (gain > 0) {
gains.push(gain);
} else {
losses.push(-gain);
}
}
const avgGain = gains.reduce((a, b) => a + b, 0) / gains.length;
const avgLoss = losses.reduce((a, b) => a + b, 0) / losses.length;
const rsi = 100 - (100 / (1 + avgGain / avgLoss));
rsiValues.push(rsi);
}
}
return rsiValues;
}
function calculateMACD(data) {
const macdValues = [];
const ema12 = [];
const ema26 = [];
for (let i = 0; i < data.length; i++) {
if (i < 12) {
ema12.push(null);
} else {
const sum = data.slice(i - 12, i).reduce((a, b) => a + b.close, 0);
ema12.push(sum / 12);
}
if (i < 26) {
ema26.push(null);
} else {
const sum = data.slice(i - 26, i).reduce((a, b) => a + b.close, 0);
ema26.push(sum / 26);
}
if (i >= 26) {
const macd = ema12[i] - ema26[i];
macdValues.push(macd);
} else {
macdValues.push(null);
}
}
return macdValues;
}
// 更新generateMockData()以包含RSI與MACD
function generateMockData() {
const data = [];
let value = 500;
const date = new Date();
for (let i = -100; i <= 0; i++) {
const newDate = new Date(date);
newDate.setDate(date.getDate() + i);
const open = value;
const close = open + (Math.random() - 0.5) * 20;
const high = Math.max(open, close) + Math.random() * 10;
const low = Math.min(open, close) - Math.random() * 10;
data.push({
date: newDate.getTime(),
open: open.toFixed(2),
high: high.toFixed(2),
low: low.toFixed(2),
close: close.toFixed(2),
volume: Math.round(Math.random() * 1e6)
});
value = close;
}
const rsiValues = calculateRSI(data);
const macdValues = calculateMACD(data);
for (let i = 0; i < data.length; i++) {
data[i].rsi = rsiValues[i];
data[i].macd = macdValues[i];
}
return data;
}
部署說明
部署指令
npm install http-server -g && http-server -p 3000
預期成果
- ✅ 即時更新股價走勢圖
- ✅ 雙Y軸交易量顯示
- ✅ 移動平均線(MA5/MA20)疊加
- ✅ RSI技術指標面板
- ✅ 支援觸控縮放操作
除錯提示
如遇到 CORS 錯誤,添加代理中間件:
chart.dataSource.url = "/proxy?url=REAL_API_ENDPOINT";
進階功能擴充建議
- 增加主題切換按鈕(日間/夜間模式)
- 實作數據導出功能(PNG/CSV)
- 整合新聞事件標記(用於財報公布時點)
- 添加自訂指標計算器
- 建立歷史數據回測模組