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

如何仅使用单个d3.json().then Promise为多个柱状图加载数据

问题根因

获取不到抽离函数返回值的核心原因是d3.json()是异步Promise操作:如果在函数内用同步写法返回值,return执行时异步请求还未完成,拿到的结果永远是undefined。
要实现单文件多D3图表仅发起一次数据请求、同时符合DRY原则,按照下面的结构写即可。

实现方案

核心思路是:通用配置全局抽离复用、单个图表逻辑封装为独立作用域函数、全局仅发起1次数据请求,拿到格式化后的数据后统一传入所有图表函数完成渲染。

错误写法参考

这是绝大多数人第一次抽离逻辑会踩的坑:

// 错误:return只在then回调内部生效,外层函数拿不到异步结果
function loadData() {
  d3.json("data.json").then(res => {
    return res.map(d => ({...d, value: +d.value}))
  })
}
const data = loadData() // 永远返回undefined

正确实现步骤

1. 抽离全局通用配置

把所有图表共用的边距、尺寸、通用比例尺生成逻辑抽离到全局,避免每个图表重复定义:

// 全局通用配置,所有图表可直接引用
const CHART_COMMON_CONFIG = {
  margin: {top: 20, right: 20, bottom: 30, left: 40},
  baseWidth: 600,
  baseHeight: 400,
  // 通用比例尺生成方法,接收数据和尺寸动态返回实例
  genXScale: (data, innerWidth) => d3.scaleBand()
    .domain(data.map(d => d.category))
    .range([0, innerWidth])
    .padding(0.2),
  genYScale: (data, innerHeight) => d3.scaleLinear()
    .domain([0, d3.max(data, d => d.value)])
    .nice()
    .range([innerHeight, 0])
}

2. 封装独立图表渲染函数

每个图表单独封装为函数,所有函数统一接收挂载容器、格式化后的数据作为入参,函数内部变量为函数作用域,不会产生命名冲突:

/**
 * 渲染第一个柱状图
 * @param {string} container 挂载选择器
 * @param {Array} data 格式化后的通用数据
 */
function renderSalesBar(container, data) {
  const {margin, baseWidth, baseHeight, genXScale, genYScale} = CHART_COMMON_CONFIG
  const innerWidth = baseWidth - margin.left - margin.right
  const innerHeight = baseHeight - margin.top - margin.bottom
  // 复用通用方法生成比例尺
  const xScale = genXScale(data, innerWidth)
  const yScale = genYScale(data, innerHeight)

  // 正常编写绘图逻辑即可
  const svg = d3.select(container)
    .append("svg")
    .attr("width", baseWidth)
    .attr("height", baseHeight)
    .append("g")
    .attr("transform", `translate(${margin.left}, ${margin.top})`)
  
  // 画坐标轴、柱子、交互逻辑省略...
}

/**
 * 渲染第二个柱状图
 * @param {string} container 挂载选择器
 * @param {Array} data 格式化后的通用数据
 */
function renderProfitBar(container, data) {
  // 复用通用配置,仅编写该图表的差异化逻辑即可
  const {margin, baseWidth, baseHeight, genXScale, genYScale} = CHART_COMMON_CONFIG
  const innerWidth = baseWidth - margin.left - margin.right
  const innerHeight = baseHeight - margin.top - margin.bottom
  const xScale = genXScale(data, innerWidth)
  const yScale = genYScale(data, innerHeight)
  // 后续绘图逻辑...
}

3. 单次请求完成全量渲染

全局仅调用一次d3.json,在请求回调内统一完成数据格式化,再依次调用所有图表的渲染函数,传入同一份数据:

// 仅发起1次数据请求
d3.json("data.json")
  .then(rawData => {
    // 统一做一次数据格式化,所有图表共用结果
    const formattedData = rawData.map(d => ({
      category: d.type,
      value: +d.count,
      // 其他统一数据处理逻辑
    }))
    // 依次渲染所有图表,传入同一份格式化后的数据
    renderSalesBar("#chart-sales", formattedData)
    renderProfitBar("#chart-profit", formattedData)
    // 后续新增柱状图,只需要在这里加一行调用即可,不需要重复写请求
  })
  .catch(err => console.error("数据加载异常:", err))
抽离数据加载逻辑的写法

如果需要把数据加载、格式化逻辑也抽离为独立函数,直接返回整个Promise链即可,不要在Promise内部同步return:

// 抽离数据加载+格式化函数
function fetchChartData() {
  // 必须return整个Promise实例
  return d3.json("data.json").then(rawData => {
    return rawData.map(d => ({
      category: d.type,
      value: +d.count
    }))
  })
}

// 调用时在then回调里拿处理好的数据,统一渲染
fetchChartData().then(formattedData => {
  renderSalesBar("#chart-sales", formattedData)
  renderProfitBar("#chart-profit", formattedData)
})
注意事项
  • 所有依赖数据的渲染逻辑必须写在Promise的then回调内部,不要在回调外部读取数据变量,否则会拿到undefined
  • 不要在单个图表函数内部调用数据请求方法,否则会产生重复请求
  • 每个图表函数内部的变量都是独立函数作用域,不需要担心命名冲突问题

内容的提问来源于stack exchange,提问作者Aloysia de Argenteuil

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.03 05:21:46