如何用D3.js结合Ajax数据实现定时更新的实时多线图
使用D3.js实现实时多线图(基于Ajax数据)
以下是完整的可运行实现代码,包含数据获取、图表初始化、实时更新逻辑:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>实时多线图</title> <script src="https://d3js.org/d3.v7.min.js"></script> <style> .chart-container { width: 90%; margin: 20px auto; } .line { fill: none; stroke-width: 2px; } .axis-label { font-size: 12px; font-family: sans-serif; } .legend { font-size: 12px; font-family: sans-serif; } </style> </head> <body> <div class="chart-container"> <svg id="line-chart"></svg> </div> <script> // 图表基础配置 const margin = { top: 20, right: 120, bottom: 50, left: 50 }; const width = document.getElementById('line-chart').clientWidth - margin.left - margin.right; const height = 500 - margin.top - margin.bottom; // 存储各渠道历史数据,结构:{ channelName: [{time: Date, value: Number}, ...], ... } let historyData = {}; // 渠道颜色映射,自动分配唯一颜色 const colorScale = d3.scaleOrdinal(d3.schemeCategory10); // 初始化SVG容器 const svg = d3.select('#line-chart') .attr('width', width + margin.left + margin.right) .attr('height', height + margin.top + margin.bottom) .append('g') .attr('transform', `translate(${margin.left}, ${margin.top})`); // 初始化比例尺与坐标轴 const xScale = d3.scaleTime().range([0, width]); const yScale = d3.scaleLinear().range([height, 0]); const xAxis = d3.axisBottom(xScale); const yAxis = d3.axisLeft(yScale); // 添加x轴及标签 svg.append('g') .attr('class', 'x-axis') .attr('transform', `translate(0, ${height})`) .call(xAxis); svg.append('text') .attr('class', 'axis-label') .attr('x', width / 2) .attr('y', height + margin.bottom - 10) .style('text-anchor', 'middle') .text('时间'); // 添加y轴及标签 svg.append('g') .attr('class', 'y-axis') .call(yAxis); svg.append('text') .attr('class', 'axis-label') .attr('transform', 'rotate(-90)') .attr('x', -height / 2) .attr('y', -margin.left + 15) .style('text-anchor', 'middle') .text('平均时长'); // 线条生成器 const lineGenerator = d3.line() .x(d => xScale(d.time)) .y(d => yScale(d.value)); // 图例容器 const legendGroup = svg.append('g') .attr('class', 'legend') .attr('transform', `translate(${width + 10}, 0)`); // 获取并处理接口数据 async function fetchAndProcessData() { try { // 替换为你的实际接口地址 const response = await fetch('your-api-url-here'); const rawData = await response.json(); const currentTime = new Date(); rawData.forEach(item => { // 统一转换avg_time为数字(兼容字符串/数字混合格式) const value = typeof item.avg_time === 'string' ? parseFloat(item.avg_time) : item.avg_time; const channel = item.channel; // 初始化新渠道的历史数据数组 if (!historyData[channel]) { historyData[channel] = []; colorScale(channel); } // 添加当前数据点 historyData[channel].push({ time: currentTime, value }); // 可选:限制历史数据点数量,避免内存溢出(示例保留24个点=2小时数据) if (historyData[channel].length > 24) { historyData[channel].shift(); } }); updateChart(); } catch (error) { console.error('数据获取失败:', error); } } // 更新图表核心逻辑 function updateChart() { const allDataPoints = Object.values(historyData).flat(); // 更新坐标轴范围 xScale.domain(d3.extent(allDataPoints, d => d.time)); const maxValue = d3.max(allDataPoints, d => d.value) || 0; yScale.domain([0, maxValue * 1.1]); // 刷新坐标轴 svg.select('.x-axis').call(xAxis); svg.select('.y-axis').call(yAxis); // 处理线条的增删改 const lineGroups = svg.selectAll('.line-group') .data(Object.entries(historyData), d => d[0]); // 移除已消失渠道的线条 lineGroups.exit().remove(); // 为新渠道创建线条组 const newLineGroups = lineGroups.enter() .append('g') .attr('class', 'line-group'); newLineGroups.append('path') .attr('class', 'line') .style('stroke', d => colorScale(d[0])); // 更新所有线条路径 newLineGroups.merge(lineGroups) .select('.line') .datum(d => d[1]) .attr('d', lineGenerator); updateLegend(); } // 更新图例 function updateLegend() { const legendItems = legendGroup.selectAll('.legend-item') .data(colorScale.domain(), d => d); legendItems.exit().remove(); const newLegendItems = legendItems.enter() .append('g') .attr('class', 'legend-item') .attr('transform', (d, i) => `translate(0, ${i * 20})`); newLegendItems.append('rect') .attr('width', 15) .attr('height', 15) .style('fill', d => colorScale(d)); newLegendItems.append('text') .attr('x', 20) .attr('y', 12) .text(d => d); // 重新排列图例位置 legendGroup.selectAll('.legend-item') .attr('transform', (d, i) => `translate(0, ${i * 20})`); } // 首次加载数据 fetchAndProcessData(); // 定时更新:每5分钟(300000毫秒)执行一次 setInterval(fetchAndProcessData, 300000); </script> </body> </html>
关键实现说明
1. 数据管理
- 用
historyData对象维护每个渠道的时间序列数据,支持动态新增/删除渠道 - 统一转换
avg_time为数字,解决原数据中字符串与数字混合的问题 - 可选限制历史数据点数量,避免长期运行导致内存占用过高
2. 图表更新机制
- 采用D3的
enter/update/exit模式,自动处理渠道的新增与移除 - 每次更新时重新计算坐标轴范围,确保图表自适应数据变化
- 自动为新渠道分配唯一颜色,并同步更新图例
3. 定时任务
通过setInterval实现每5分钟一次的数据拉取与图表更新,时间间隔可通过修改300000(毫秒)调整
注意事项
- 替换代码中的
your-api-url-here为实际接口地址 - 若接口需要请求头或参数,可在
fetch中添加配置(如headers: {'Content-Type': 'application/json'}) - 可根据需求调整图表尺寸、颜色方案、历史数据保留数量等配置
内容的提问来源于stack exchange,提问作者developer
相关产品推荐
相关产品推荐

