You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 05:26:27