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

如何用D3.js从JSON数组绘制带时间轴的多系列散点图

问题:提取JSON多组温度数据对应日期,绘制时间轴散点图

我想要制作一个带时间轴的散点图,现在卡在了数据提取环节。以下是我的JSON数据,需要提取每组里的28个温度数据,并对应单个日期绘制——每组温度对应一个日期作为一个系列,系列数量在10-50之间变化。

我的JSON数据:

{ 
  "Session_test": [
    { 
      "datas_lines": [
        { "datas_line": [], "log_time": "No data" },
        { "datas_line": [], "log_time": "No data" },
        { "datas_line": [], "log_time": "No data" },
        { 
          "datas_line": [ "157", "158", "157", "157", "157", "158", "159", "161", "160", "160", "159", "158", "159", "160", "160", "160", "160", "161", "160", "161", "159", "161", "161", "158", "161", "159", "160", "157" ],
          "log_time": "5/18/2017 4:32:14 PM" 
        },
        { 
          "datas_line": [ "154", "156", "155", "155", "155", "156", "158", "159", "158", "157", "157", "156", "157", "158", "159", "158", "158", "159", "159", "159", "158", "159", "159", "156", "159", "157", "158", "156" ],
          "log_time": "5/18/2017 4:34:14 PM" 
        },
        { 
          "datas_line": [ "154", "156", "155", "155", "155", "156", "158", "159", "158", "157", "157", "156", "157", "158", "159", "158", "158", "159", "159", "159", "158", "159", "159", "156", "159", "157", "158", "156" ],
          "log_time": "5/18/2017 4:34:14 PM" 
        } 
      ] 
    }
  ] 
}

我的现有代码:

// set the dimensions and margins of the graph
var margin = {top: 20, right: 20, bottom: 30, left: 50},
    width = 960 - margin.left - margin.right,
    height = 500 - margin.top - margin.bottom;

// parse the date / time
var parseTime = d3.timeParse("%m/%d/%Y %H:%M:%S %p");
var i = 0;

// set the ranges
var x = d3.scaleTime().range([0, width]);
var y = d3.scaleLinear().range([height, 0]);

// define the line
var valueline = d3.line()
    .x(function(d) { return x(d.log_time); })
    .y(function(d) { return y(d.datas_line); });

// append the svg obgect to the body of the page
// appends a 'group' element to 'svg'
// moves the 'group' element to the top left margin
var svg = d3.select("body").append("svg")
    .attr("width", width + margin.left + margin.right)
    .attr("height", height + margin.top + margin.bottom)
  .append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

function draw(data, session, datas_line) {
  var data = data[session][0][datas_line];

  // format the data
  data.forEach(function(d) {
    if (parseTime(d.log_time)!= "No data") {
      console.log(parseTime(d.log_time));
      d.log_time = parseTime(d.log_time);
    }
    if (!(isNaN(parseInt(d.datas_line)))) {
      d.datas_line = +parseInt(d.datas_line);
      console.log(d.datas_line);
    }
  });

  // sort years ascending
  data.sort(function(a, b){
    return a["log_time"]-b["log_time"];
  })

  // Scale the range of the data
  x.domain(d3.extent(data, function(d) { return d.log_time; }));
  y.domain([0, d3.max(data, function(d) { return Math.max(d.datas_line); })]);

  // Add the valueline path.
  svg.append("path")
      .data([data])
      .attr("class", "line")
      .attr("d", valueline);

  // Add the X Axis
  svg.append("g")
      .attr("transform", "translate(0," + height + ")")
      .call(d3.axisBottom(x))

  // Add the Y Axis
  svg.append("g")
      .call(d3.axisLeft(y));
}

// Get the data
d3.json("testSerialisation_v4.ujson", function(error, data) {
  if (error) throw error;

  // trigger render
  draw(data, "Session_test" , "datas_lines");
});

注:我原本要绘制散点图而非折线图,这点暂时忽略即可。


解决方案

我来帮你梳理下核心问题:你现在的JSON里,每个datas_lines条目包含28个温度值和1个对应日期,但你的代码试图直接把整个datas_line数组当成单个数值处理,这显然不对。我们需要把每个温度值和对应的日期绑定成独立的数据点,这样才能在时间轴上绘制散点。

步骤1:转换数据结构

首先写一个函数,把原始数据转换成散点图需要的格式——每个数据点是{ date: 解析后的日期对象, temperature: 数值型温度 }:

function transformRawData(rawData) {
  var processedPoints = [];
  // 遍历所有datas_lines条目
  rawData.Session_test[0].datas_lines.forEach(function(line) {
    // 跳过无数据的条目
    if (line.log_time === "No data" || line.datas_line.length === 0) return;
    // 解析日期
    var parsedDate = parseTime(line.log_time);
    // 遍历当前条目中的每个温度值
    line.datas_line.forEach(function(tempStr) {
      var temp = parseInt(tempStr);
      // 确保温度是有效数值
      if (!isNaN(temp)) {
        processedPoints.push({
          date: parsedDate,
          temperature: temp
        });
      }
    });
  });
  return processedPoints;
}

步骤2:修改绘制函数

接下来调整你的draw函数,适配转换后的数据,并且改成绘制散点(用<circle>元素代替折线的<path>):

function draw(processedData) {
  // 排序数据(按日期)
  processedData.sort(function(a, b) {
    return a.date - b.date;
  });

  // 设置X轴和Y轴的定义域
  x.domain(d3.extent(processedData, function(d) { return d.date; }));
  // Y轴取温度的最小值和最大值,比硬设0更合理
  y.domain([
    d3.min(processedData, function(d) { return d.temperature; }) - 2,
    d3.max(processedData, function(d) { return d.temperature; }) + 2
  ]);

  // 绘制散点
  svg.selectAll("circle")
     .data(processedData)
     .enter()
     .append("circle")
     .attr("cx", function(d) { return x(d.date); })
     .attr("cy", function(d) { return y(d.temperature); })
     .attr("r", 3) // 散点的半径
     .style("fill", "#69b3a2"); // 散点颜色

  // 添加X轴
  svg.append("g")
     .attr("transform", "translate(0," + height + ")")
     .call(d3.axisBottom(x));

  // 添加Y轴
  svg.append("g")
     .call(d3.axisLeft(y));
}

步骤3:更新数据加载逻辑

最后修改数据加载后的调用逻辑,用转换后的数据绘制:

// Get the data
d3.json("testSerialisation_v4.ujson", function(error, data) {
  if (error) throw error;

  // 转换数据格式
  var processedData = transformRawData(data);
  // 触发绘制
  draw(processedData);
});

额外说明

  • 我把Y轴的定义域改成了基于温度的实际最值(加减2是为了让散点不贴到轴上),比固定0更直观;
  • 散点的半径和颜色可以根据你的需求调整;
  • 如果需要区分不同系列(不同日期组)的散点,可以在转换数据时给每个点加上系列标识,然后用不同颜色区分。

内容的提问来源于stack exchange,提问作者Patrick Michel

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 09:45:33