使用D3.js读取JSON矢量数据绘图的存储结构选型问题
D3时间序列数据JSON格式选型与绘图实现
选项1的JSON合法性与可用性
你构思的选项1结构属于合法JSON格式,只需要注意符合JSON的语法要求:字符串用双引号包裹、数组元素之间用逗号分隔即可,修正后的标准结构如下:
[ {"name":"X", "time":["t1", "t2", "t3"], "value":["v1", "v2", "v3"]}, {"name":"Y", "time":["t1", "t2"], "value":["V1", "V2"]}, {"name":"Z", "time":["t1", "t2", "t3", "t4"], "value":["V1", "V2", "V3", "V4"]} ]
该结构完全可以被D3正常读取并用于绘图,存储效率远高于选项2,适合数据量较大的场景。唯一需要注意的是,转换数据时要保证每个name对应的time数组和value数组长度一致、顺序一一对应,避免出现时间和数值匹配错误的问题。
选项2结构的绘图实现方法
选项2的标准合法JSON结构是对象数组,如下:
[ {"name":"X","time":"t1","value":"v1"}, {"name":"X","time":"t2","value":"v2"}, {"name":"X","time":"t3","value":"v3"}, {"name":"Y","time":"t1","value":"V1"}, {"name":"Y","time":"t2","value":"V2"}, {"name":"Z","time":"t1","value":"V1"}, {"name":"Z","time":"t2","value":"V2"}, {"name":"Z","time":"t3","value":"V3"}, {"name":"Z","time":"t4","value":"V4"} ]
这个结构的优势是D3处理逻辑更灵活,不需要提前做数据分组,D3内置的分组方法可以直接按name拆分数据。以下是D3 v7版本绘制多折线时间序列图的最简示例:
<!DOCTYPE html> <html> <head> <!-- 引入本地或CDN的D3 v7库地址即可 --> <script src="D3 v7库地址"></script> <style> .line { stroke-width: 2; fill: none; } </style> </head> <body> <svg width="800" height="400"></svg> <script> // 实际使用时用d3.json("你的数据文件路径")加载即可,此处为示例硬编码数据 const data = [ {"name":"X","time":"t1","value":1}, {"name":"X","time":"t2","value":3}, {"name":"X","time":"t3","value":2}, {"name":"Y","time":"t1","value":2}, {"name":"Y","time":"t2","value":4}, {"name":"Z","time":"t1","value":5}, {"name":"Z","time":"t2","value":3}, {"name":"Z","time":"t3","value":4}, {"name":"Z","time":"t4","value":6} ]; const svg = d3.select("svg"), margin = {top: 20, right: 30, bottom: 30, left: 40}, width = +svg.attr("width") - margin.left - margin.right, height = +svg.attr("height") - margin.top - margin.bottom, g = svg.append("g").attr("transform", `translate(${margin.left},${margin.top})`); // 定义坐标轴比例尺 const x = d3.scalePoint() .domain(data.map(d => d.time)) .range([0, width]); const y = d3.scaleLinear() .domain([0, d3.max(data, d => +d.value)]) .range([height, 0]); const color = d3.scaleOrdinal(d3.schemeCategory10) .domain(Array.from(new Set(data.map(d => d.name)))); // 定义折线生成器 const line = d3.line() .x(d => x(d.time)) .y(d => y(+d.value)); // 按名称分组数据 const groups = d3.group(data, d => d.name); // 绘制所有折线 g.selectAll(".line") .data(groups) .join("path") .attr("class", "line") .attr("d", d => line(d[1])) .attr("stroke", d => color(d[0])); // 绘制坐标轴 g.append("g") .attr("transform", `translate(0,${height})`) .call(d3.axisBottom(x)); g.append("g") .call(d3.axisLeft(y)); </script> </body> </html>
如果使用选项1的结构绘图,仅需要修改折线生成部分的逻辑即可,核心修改点如下:
// 假设加载到的选项1结构数据为groupedData const groupedData = [ {"name":"X", "time":["t1", "t2", "t3"], "value":[1,3,2]}, {"name":"Y", "time":["t1", "t2"], "value":[2,4]}, {"name":"Z", "time":["t1", "t2", "t3", "t4"], "value":[5,3,4,6]} ]; // 其余比例尺、坐标轴逻辑不变,仅修改折线绑定逻辑 g.selectAll(".line") .data(groupedData) .join("path") .attr("class", "line") .attr("d", d => { // 将同组的time和value拼接为符合折线生成器要求的对象数组 const points = d.time.map((t, i) => ({time: t, value: d.value[i]})); return line(points); }) .attr("stroke", d => color(d.name));
选型建议
- 数据量较大、对存储/传输效率要求高的场景优先选选项1
- 需要做单条数据级交互(如hover显示单条数据详情)的场景优先选选项2,无需额外做索引匹配即可直接获取单条数据的全量字段
内容的提问来源于stack exchange,提问作者Siddharth Krishnamoorthy
相关产品推荐
相关产品推荐

