如何使用 JavaScript 创建堆叠式进度条图表
堆叠式进度条图表是数据可视化中常用的展示形式,它能够在一组进度条中同时展示多个维度的数据占比,直观呈现各部分在整体中的分布关系。相比单一进度条,堆叠式进度条可以在有限的空间内展示更多维度的信息,非常适合多分类数据的占比对比场景,比如不同产品的销售占比、项目各阶段进度占比等。
核心实现思路
要纯 JavaScript 实现堆叠式进度条,核心逻辑可以分为以下几个步骤:
- 准备数据:定义每个进度条的总长度,以及每个堆叠部分的数值和对应的颜色
- 创建容器:动态生成进度条的外层容器和内部堆叠块的元素
- 计算占比:根据每个部分的数值计算其在总进度中的宽度占比
- 渲染样式:为每个堆叠块设置宽度、背景色,完成进度条的绘制
- 添加交互:可选实现鼠标悬停显示数值提示的功能
完整实现代码
下面的代码实现了一个可复用的堆叠式进度条图表,支持自定义数据和样式,代码中包含详细注释说明每一步的作用:
// 堆叠式进度条数据示例,每个对象代表一个独立的进度条
const stackProgressData = [
{
name: "产品A销售进度",
total: 100, // 总进度值
stacks: [
{ value: 30, color: "#4CAF50", label: "线上渠道" },
{ value: 25, color: "#2196F3", label: "线下渠道" },
{ value: 20, color: "#FFC107", label: "分销渠道" },
{ value: 25, color: "#9C27B0", label: "其他渠道" }
]
},
{
name: "项目B完成进度",
total: 100,
stacks: [
{ value: 40, color: "#4CAF50", label: "需求阶段" },
{ value: 30, color: "#2196F3", label: "开发阶段" },
{ value: 20, color: "#FFC107", label: "测试阶段" },
{ value: 10, color: "#9C27B0", label: "上线阶段" }
]
}
];
// 创建堆叠式进度条图表的主函数
function createStackProgressChart(containerId, data) {
// 获取图表容器
const container = document.getElementById(containerId);
if (!container) {
console.error("未找到指定的容器元素");
return;
}
// 清空容器原有内容
container.innerHTML = "";
// 遍历每个进度条数据
data.forEach(item => {
// 创建单个进度条的容器
const progressItem = document.createElement("div");
progressItem.style.marginBottom = "20px";
// 创建进度条标题
const title = document.createElement("div");
title.textContent = item.name;
title.style.marginBottom = "8px";
title.style.fontSize = "14px";
title.style.color = "#333";
progressItem.appendChild(title);
// 创建进度条外层轨道
const progressTrack = document.createElement("div");
progressTrack.style.width = "100%";
progressTrack.style.height = "24px";
progressTrack.style.backgroundColor = "#f0f0f0";
progressTrack.style.borderRadius = "4px";
progressTrack.style.overflow = "hidden";
progressTrack.style.display = "flex";
// 遍历堆叠块数据,计算宽度并创建元素
item.stacks.forEach(stack => {
const stackBlock = document.createElement("div");
// 计算当前块占总进度的百分比
const percent = (stack.value / item.total) * 100;
stackBlock.style.width = `${percent}%`;
stackBlock.style.backgroundColor = stack.color;
stackBlock.style.height = "100%";
stackBlock.style.transition = "width 0.3s ease";
// 存储当前块的提示信息,用于鼠标悬停展示
stackBlock.dataset.label = stack.label;
stackBlock.dataset.value = stack.value;
stackBlock.dataset.percent = percent.toFixed(1);
// 添加鼠标悬停提示事件
stackBlock.addEventListener("mouseenter", function(e) {
// 创建提示框
let tooltip = document.getElementById("stack-tooltip");
if (!tooltip) {
tooltip = document.createElement("div");
tooltip.id = "stack-tooltip";
tooltip.style.position = "absolute";
tooltip.style.backgroundColor = "rgba(0,0,0,0.8)";
tooltip.style.color = "#fff";
tooltip.style.padding = "6px 10px";
tooltip.style.borderRadius = "4px";
tooltip.style.fontSize = "12px";
tooltip.style.zIndex = "1000";
tooltip.style.pointerEvents = "none";
document.body.appendChild(tooltip);
}
tooltip.textContent = `${this.dataset.label}: ${this.dataset.value} (${this.dataset.percent}%)`;
tooltip.style.left = `${e.pageX + 10}px`;
tooltip.style.top = `${e.pageY + 10}px`;
tooltip.style.display = "block";
});
stackBlock.addEventListener("mousemove", function(e) {
const tooltip = document.getElementById("stack-tooltip");
if (tooltip) {
tooltip.style.left = `${e.pageX + 10}px`;
tooltip.style.top = `${e.pageY + 10}px`;
}
});
stackBlock.addEventListener("mouseleave", function() {
const tooltip = document.getElementById("stack-tooltip");
if (tooltip) {
tooltip.style.display = "none";
}
});
progressTrack.appendChild(stackBlock);
});
progressItem.appendChild(progressTrack);
// 创建图例区域
const legend = document.createElement("div");
legend.style.display = "flex";
legend.style.gap = "16px";
legend.style.marginTop = "8px";
legend.style.flexWrap = "wrap";
item.stacks.forEach(stack => {
const legendItem = document.createElement("div");
legendItem.style.display = "flex";
legendItem.style.alignItems = "center";
legendItem.style.fontSize = "12px";
legendItem.style.color = "#666";
const colorBox = document.createElement("span");
colorBox.style.width = "12px";
colorBox.style.height = "12px";
colorBox.style.backgroundColor = stack.color;
colorBox.style.marginRight = "6px";
colorBox.style.borderRadius = "2px";
const text = document.createElement("span");
text.textContent = `${stack.label}: ${stack.value}`;
legendItem.appendChild(colorBox);
legendItem.appendChild(text);
legend.appendChild(legendItem);
});
progressItem.appendChild(legend);
container.appendChild(progressItem);
});
}
// 页面加载完成后初始化图表
document.addEventListener("DOMContentLoaded", function() {
createStackProgressChart("stack-progress-container", stackProgressData);
});HTML 结构示例
需要在页面中准备一个容器元素,用来挂载生成的堆叠式进度条,对应的 HTML 结构如下:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>堆叠式进度条示例</title>
<style>
/* 可选的基础样式,可自定义调整 */
body {
font-family: Arial, sans-serif;
padding: 20px;
}
#stack-progress-container {
max-width: 800px;
margin: 0 auto;
}
</style>
</head>
<body>
<h2>堆叠式进度条图表展示</h2>
<div id="stack-progress-container"></div>
<script src="stack-progress.js"></script>
</body>
</html>功能说明与扩展
上述实现的堆叠式进度条包含以下特性:
- 支持多组进度条同时渲染,每组进度条独立计算占比
- 每个堆叠块通过不同颜色区分,下方自动生成对应图例
- 鼠标悬停在堆叠块上时,会显示该块的标签、数值和占比百分比
- 进度条宽度变化带有过渡动画,视觉效果更平滑
如果需要扩展功能,可以基于现有代码进行调整:比如支持动态更新数据、自定义进度条高度和圆角、添加点击交互事件,或者适配移动端响应式布局。只需要修改对应的配置参数和样式逻辑即可,核心的占比计算逻辑不需要大改。