【问题标题】:How to create line chart with JSON data using D3如何使用 D3 使用 JSON 数据创建折线图
【发布时间】:2021-07-12 05:45:54
【问题描述】:

我正在使用 D3.js 和 react-hooks 来创建图表,所以我尝试通过搜索创建一个 Line 图表并得到了一个。

  • 但我正在使用的是示例数据,在我的例子中,我有 JSON 数据。
  • 我也使用 resize-observer-polyfill 这个库使图表具有响应性。
  • 现在我正在努力用 JSON 数据实现它,用动态数据呈现它。

我做了什么

const svgRef = useRef();
const wrapperRef = useRef();
const dimensions = useResizeObserver(wrapperRef); // for responsive

// will be called initially and on every data change
useEffect(() => {
    const svg = select(svgRef.current);
    const { width, height } =
        dimensions || wrapperRef.current.getBoundingClientRect();

    // scales + line generator
    const xScale = scaleLinear()
        .domain([0, data.length - 1]) // here I need to pass the data
        .range([0, width]);

    const yScale = scaleLinear()
        .domain([0, max(data)])
        .range([height, 0]);

    const lineGenerator = line()
        .x((d, index) => xScale(index))
        .y((d) => yScale(d))
        .curve(curveCardinal);

    // render the line
    svg
        .selectAll('.myLine')
        .data([data])
        .join('path')
        .attr('class', 'myLine')
        .attr('stroke', 'black')
        .attr('fill', 'none')
        .attr('d', lineGenerator);

    svg
        .selectAll('.myDot')
        .data(data)
        .join('circle')
        .attr('class', 'myDot')
        .attr('stroke', 'black')
        .attr('r', (value, index) => 4)
        .attr('fill', (value, index) => 'red')
        .attr('cx', (value, index) => xScale(index))
        .attr('cy', yScale);

    // axes
    const xAxis = axisBottom(xScale);
    svg
        .select('.x-axis')
        .attr('transform', `translate(0, ${height})`)
        .call(xAxis);

    const yAxis = axisLeft(yScale);
    svg.select('.y-axis').call(yAxis);
}, [data, dimensions]);

    <React.Fragment>
        <div ref={wrapperRef} style={{ marginBottom: '2rem' }}>
            <svg ref={svgRef}>
                <g className="x-axis" />
                <g className="y-axis" />
            </svg>
        </div>
    </React.Fragment>

这里我无法传递数据,我的数据在下面

[
  { anno: 2014, consumo: 300, color: "#ff99e6" },
  { anno: 2015, consumo: 290, color: "blue" },
  { anno: 2016, consumo: 295, color: "green" },
  { anno: 2017, consumo: 287, color: "yellow" },
  { anno: 2018, consumo: 282, color: "red" },
  { anno: 2019, consumo: 195, color: "white" }
]

在我的数据中,每个数据都有颜色,我想在生成的每个点中显示。

working code sandbox of line chart

同样,我尝试制作条形图,效果很好

我对标签做了一些动态渲染,当我们调整窗口大小时,标签会自动调整。

Here is the full working bar chart what I am trying to implement to line chart

我还评论了我正在做什么。

编辑/更新

我一直在关注@MGO 的回答,它对我有很大帮助,但我仍然面临对齐标签和将颜色填充为点的问题。

实际上很明显它会因为文本大小而重叠,但只是为了克服在条形图中我使用下面的代码

 const tickWidth = 40;
const width = xScaleLabels.range()[1];
const tickN = Math.floor(width / tickWidth);
const keepEveryNth = Math.ceil(xScaleLabels.domain().length / tickN);

const xScaleLabelDomain = xScaleLabels
  .domain()
  .filter((_, i) => i % keepEveryNth === 0);
xScaleLabels.domain(xScaleLabelDomain);

它所做的是当设备尺寸较小时,它会过滤一些标签并且不会显示标签。

我也在使用下面的代码来给颜色

.attr("fill", ({ color }) => color) 

但是它没有采用任何颜色,而是默认采用黑色。

我有数据要显示为July.9.2021 11:18:28 的标签,但我只想显示时间,所以我在条形图代码中所做的如下

const xScaleLabels = scaleBand()
        .domain(
            data.map(
                ({ sensorValueAddedTime }) => sensorValueAddedTime.split(' ')[2]  // this I am using to show only time
            )
        )
        .range([0, dimensions.width])
        .padding(padding);

我想用折线图做同样的事情,以一种简单的方式,我不想将其更改为任何时间。

这是第二个数据,所以基本上我得到的答案只适用于第一个数据而不是第二个数据,如果数据以我想要显示的任何这些格式出现,我希望它是动态的。

sensorValueAddedTime 我想在 x 轴上显示 sensorValue在y轴上

我已经添加了我的条形图完整的工作代码,我想对折线图做同样的事情。

    [
    {
      sensorValue: 32,
      sensorValueAddedTime: "July.9.2021 10:56:22",
      color_code: null,
      condition: null,
      __typename: "sensorData"
    },
    {
      sensorValue: 32,
      sensorValueAddedTime: "July.9.2021 10:56:23",
      color_code: null,
      condition: null,
      __typename: "sensorData"
    },
    {
      sensorValue: 35,
      sensorValueAddedTime: "July.9.2021 11:17:51",
      color_code: null,
      condition: null,
      __typename: "sensorData"
    },
    {
      sensorValue: 35,
      sensorValueAddedTime: "July.9.2021 11:17:52",
      color_code: null,
      condition: null,
      __typename: "sensorData"
    },
    {
      sensorValue: 36,
      sensorValueAddedTime: "July.9.2021 11:18:08",
      color_code: null,
      condition: null,
      __typename: "sensorData"
    },
    {
      sensorValue: 36,
      sensorValueAddedTime: "July.9.2021 11:18:09",
      color_code: null,
      condition: null,
      __typename: "sensorData"
    },
    {
      sensorValue: 38,
      sensorValueAddedTime: "July.9.2021 11:18:27",
      condition: null,
      color_code: null,
      __typename: "sensorData"
    },
    {
      sensorValue: 38,
      sensorValueAddedTime: "July.9.2021 11:18:28",
      condition: null,
      color_code: null,
      __typename: "sensorData"
    }
  ]

【问题讨论】:

  • 澄清一下,您是要使用具有 {anno: 2014, consumo: 300, color: "#ff99e6" } 的数组还是使用 Codesandbox 中的数组生成折线图?我已经使用 Codesandbox 中提供的数据进行了回答。你问的是这个吗?

标签: javascript reactjs d3.js react-hooks


【解决方案1】:

在 Muri 的 D3 with React Hooks video series 的原始示例中,data 是一个平面一维数组:[10, 25, 30, 40, 25, 60]

在您提供的新数组 (data1) 中,我们要求 D3 根据对象数组呈现图表。因此,当我们将 data1 作为道具传递给图表函数时,我们需要做更多的工作来访问对象数组中的值。

我们需要将数据值缩放到适当的范围内。原始示例通过数组中的索引查找值。我们将需要根据这些对象的键访问 data1 数组中包含的对象中的值。像这样,创建我们的音阶:

const yScale = scaleLinear()
                  .domain([0, max(data, (d) => d.sensorValue)])
                  .range([height, 0]);

// scale the values of sensorValueAddedTime into the range for our x axis values
// since it's a date you'll need to convert the strings to Date objects:

const xScale = scaleTime()
                 .domain([
                   new Date(min(data, (d) => d.sensorValueAddedTime)),
                   new Date(max(data, (d) => d.sensorValueAddedTime))
                  ])
                 .range([0, width]);

像这样,在我们的lineGenerator 函数中:

const lineGenerator = line()
      .x((d) => xScale(new Date(d.sensorValueAddedTime))
      .y((d) => yScale(d.sensorValue));

您会在上面注意到,如果我们要使用scaleTime(),我们必须将sensorValueAddedTime 转换为D3 可以理解的值——现在字符串中有一个额外的空格你的对象,但如果你去掉那个空间you can convert to a Date object and use d3.scaleTime().

There's an updated, working Sandbox that's rendering this data to a line chart with additional comments here.

更新:OP 要求绘制一组不同的数据,并要求点“按该顺序出现”。

There's a different working Sandbox rendering this different set of data to a line chart correctly here.

这似乎是您所要求的——“条形图的折线图版本”可能不会产生您所追求的。只是说说而已:

如果您使用 xScale 上每个对象的索引位置,如下所示:

const xScale = scaleLinear()
      .domain([0, data.length - 1])
      .range([0, width]);

这将产生一个非常无聊的图表——因为你是在说“在代表数组索引的每个整数上放一个点”。换言之,1 处的点、2 处、3 处的点等等。

对于这个图表,我们应该在数据中选择有意义的值——比如data1.anno——并使用这些值进行交流。

例如:

根据数组的索引位置将值缩放到范围内,并根据它们的索引位置绘制它们,如下所示:

const xScale = scaleLinear()
      .domain([0, data.length - 1])
      .range([0, width]);
    

....chart code....

.attr("cx", (value, index) => xScale(index))

产生这个情节:

解决方案

将数据的值缩放到范围内并根据它们的值绘制点。我们可以使用d3's extent method,像这样:

const xScale = scaleLinear()
      .domain(extent(data, (d) => d.anno))
      .range([0, width]);

    const yScale = scaleLinear()
      .domain([0, max(data, (d) => d.consumo)])
      .range([height, 0]);

....chart code...

.attr("cx", (value) => xScale(value.anno))
.attr("cy", (value) => yScale(value.consumo));

产生这个情节:

data1.anno 显然是一年。将年份解析为日期对象的选项是一个不错的选择。 var parse = d3.timeParse("%Y"),然后用它来设置你的时间尺度的域:.domain([parse(minDate), parse(maxDate)])as shown here

否则,如果您想在 x 轴上保留该值而不将其视为 Date 对象,则可以使用 Javascript 的 toFixed() 方法删除小数位,使用 d3.format 删除逗号分隔符并更改出现axis.ticks() 的刻度数。

生成此图表:

要使用这些点生成一条线,请使用数据中的值,而不是索引。

const lineGenerator = line()
      // .x((d, index) => xScale(index)) // old
      // .y((d) => yScale(d)) // old
      .x((d) => xScale(d.anno)) // new
      .y((d) => yScale(d.consumo)) // new
      .curve(curveCardinal);

生成此图表:

最后,如果您的目标是将每个对象中的颜色值分配给一个点,请访问这些值并将它们的值分配给填充,如下所示:

.attr("fill", (value) => value.color)

生成此图表:

Here's the working sandbox.

希望这会有所帮助! ✌️

【讨论】:

  • 我已经用正确的数据更新了代码沙箱,请检查一次
  • 实际上我不想更改那个 ti 日期对象,我希望它显示数据是如何按那个顺序来的
  • 知道了,现在更新了答案。 tl;博士,您仍然需要访问数组中每个对象的值。让我知道这是否回答了问题,如果回答了,请标记它。这是具有不同数据集 codesandbox.io/s/eloquent-vaughan-pg99v?file=/src/… 的工作沙箱
  • 嘿,我试过你的代码,现在如果我想处理以前的数据 July.9.2021 10:56:22 日期以这种形式出现,我想以相同的格式显示,所以它实际上不起作用我不想做任何其他事情,只是我想要在该订单上显示的任何数据,所以每个案例的数据都不相同,因为我的数据是动态的,所以在一个地方我得到了像上面一样,所以tofixed导致问题,当我删除图表未清楚填充时。
  • 这是意料之中的,因为sensorValueAddedTime 是一个字符串。另一个对象 (anno) 中的值是一个数字。上面的答案正确地描述了如何根据原始问题用 D3 绘制这些值。在这两种情况下,将该值作为 Date 对象读取都可以解决问题。看起来您的问题实际上可能与 D3 无关,而是更多地与处理和规范化来自不同来源的数据有关——“给定具有不同类型值的对象,我如何处理 JSON 以便可以表示这些值在同一个维度?”
猜你喜欢
  • 1970-01-01
  • 2022-04-15
  • 2022-01-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-01-24
  • 2014-04-14
相关资源
最近更新 更多