D3.js 移动端柱状图实现文档
功能特性
✅ 动态数据加载
✅ 平滑缩放控制
✅ 点击高亮交互
✅ 自适应Tooltip
✅ 增加双轴线
✅ 移动端触摸优化
核心实现
1. 基础配置
// 设备检测
const isMobile = false // 手动设置移动端模式
const transitionDuration = isMobile ? 100 : 300; // 动画时长配置
// 初始数据集
const initialData = [
{ name: "A", value: 30 },
{ name: "B", value: 80 },
// ...其他数据
];
2.增加双轴线
创建双轴线
// 创建 x 轴比例尺 - 用于映射类别数据到水平位置
// scaleBand() 用于处理离散数据,如柱状图的类别
const xOriginal = d3
.scaleBand()
.domain(currentData.map((d) => d.name)) // 设置域为数据名称数组
.range([margin.left, width - margin.right | 0]) // 设置值域为图表宽度范围
.padding(0.1); // 设置柱子之间的间距
// 创建左侧 y 轴比例尺 - 用于映射柱状图数值到垂直位置
// scaleLinear() 用于处理连续数值数据
const yLeftOriginal = d3
.scaleLinear()
.domain([0, d3.max(currentData, (d) => d.barValue) * 1.1]) // 设置域为0到最大值的1.1倍
.nice() // 优化刻度值为较为规整的数字
.range([height - margin.bottom | 0, margin.top | 0]); // 设置值域为图表高度范围
// 创建右侧 y 轴比例尺 - 用于映射折线图数值到垂直位置
const yRightOriginal = d3
.scaleLinear()
.domain([0, d3.max(currentData, (d) => d.lineValue) * 1.1]) // 设置域为0到最大值的1.1倍
.nice()
.range([height - margin.bottom | 0, margin.top | 0]); // 设置值域为图表高度范围
// 创建比例尺副本用于后续缩放操作
let x = xOriginal.copy();
let yLeft = yLeftOriginal.copy();
let yRight = yRightOriginal.copy();
3. 柱状图高亮逻辑
.on("click", function(event, d) {
// 清除所有高亮
g.selectAll(".bar").style("fill", "steelblue");
// 设置当前高亮
d3.select(this).style("fill", "#ff7f0e");
// 边界检测逻辑
const rightOverflow = (rect.left + tooltipRect.width) > window.innerWidth;
const leftOverflow = (rect.left - tooltipRect.width) < 0;
// 位置自适应算法
leftPos = rightOverflow && !leftOverflow ?
rect.left - tooltipRect.width :
(leftOverflow && rightOverflow ?
(window.innerWidth - tooltipRect.width)/2 :
rect.left);
});
4. 动态数据加载
function fetchMoreData(scale) {
return new Promise((resolve) => {
setTimeout(() => {
if (scale < scaleRatio) {
// 缩小时追加数据
resolve([...currentData, ...generateRandomData(5)]);
} else if (scale > scaleRatio) {
// 放大时裁剪数据
resolve(currentData.slice(0, Math.max(3, currentData.length -4)));
}
}, 200);
});
}
5. 缩放控制优化
因为手机端缩放时候缩放
// 获取的缩比例
const k = event.transform.k;
这个 K 值是动态,一直缩小会导致放大的时候如果用固定值无法判断,所以需要每次记录最后的 lastScale,来判断这次的 k 和上次 k 的大小,从而判断用户是放大还是缩小。还有一个问题就是 手机端屏幕 k 值会因为你的缩放速度之类的呈现不规则变换,就是上次缩放大了,第二次如果是放大但是 k 值没错超过上次放大。会导致无法用 k 值的大小来判断是不是缩放。从而 增加了下面的办法,来限制每次都只增加 0.1,从而避免这个问题
const zoom = d3.zoom()
.on("zoom", (event) => {
// 缩放平滑处理
if (Math.abs(currentScale - lastScale) > 0.1) {
currentScale = currentScale > lastScale
? lastScale + 0.1
: lastScale - 0.1;
}
// 清除可视化状态
d3.selectAll(".bar").style("fill", "steelblue");
d3.select(".tooltip").style("opacity", 0);
});
性能优化
🔧 位运算加速布局计算
.attr("y", height - margin.bottom | 0) // 快速取整
🔧 防抖控制数据请求频率
const debouncedCheckFetch = debounce(checkAndFetchData, 100);
🔧 差异更新DOM元素
const bars = g.selectAll(".bar").data(data, d => d.name);
样式配置
/* 高亮样式 */
.bar-highlighted {
fill: #ff7f0e;
stroke: #ff6b0e;
transition: all 0.3s ease;
}
/* 加载提示 */
.loading {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
完整的代码