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

使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.02 16:36:05