如何用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
相关产品推荐
相关产品推荐

