如何使用 D3 使用 JSON 数据创建折线图

viv*_*ngh 6 javascript d3.js reactjs react-hooks

我正在使用 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>
Run Code Online (Sandbox Code Playgroud)

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

[
  { 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" }
]
Run Code Online (Sandbox Code Playgroud)

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

折线图的工作代码沙箱

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

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

这是我试图对折线图实施的完整工作条形图

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

编辑/更新

我一直在关注@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);
Run Code Online (Sandbox Code Playgroud)

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

而且我正在使用下面的代码来提供颜色

.attr("fill", ({ color }) => color) 
Run Code Online (Sandbox Code Playgroud)

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

我有数据显示为标签,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);
Run Code Online (Sandbox Code Playgroud)

同样,我正在尝试使用折线图,以简单的方式,我不想将其更改为任何时候。

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

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"
    }
  ]
Run Code Online (Sandbox Code Playgroud)

MGO*_*MGO 5

在 Muri 的带有 React Hooks 视频系列D3的原始示例中,data是一个扁平的一维数组:[10, 25, 30, 40, 25, 60].

在您提供的新数组 ( data1) 中,我们要求 D3 基于对象数组呈现图表。所以当我们data1作为 prop传入我们的图表函数时,我们需要做更多的工作来访问我们对象数组中的值。

我们需要将数据值缩放到适当的范围内。原始示例按数组中的索引查找值。我们将需要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]);

Run Code Online (Sandbox Code Playgroud)

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

const lineGenerator = line()
      .x((d) => xScale(new Date(d.sensorValueAddedTime))
      .y((d) => yScale(d.sensorValue));
Run Code Online (Sandbox Code Playgroud)

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

有一个更新的、有效的 Sandbox,可将这些数据呈现为带有附加注释的折线图。

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

这里有一个不同的工作沙箱,可以将这组不同的数据正确呈现为折线图。

这似乎是您所要求的 - “条形图的折线图版本”可能不会产生您想要的东西。简单说一下:

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

const xScale = scaleLinear()
      .domain([0, data.length - 1])
      .range([0, width]);
Run Code Online (Sandbox Code Playgroud)

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

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

例如:

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

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

....chart code....

.attr("cx", (value, index) => xScale(index))
Run Code Online (Sandbox Code Playgroud)

产生这个情节:

按数组中索引位置绘制点

解决方案

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

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));
Run Code Online (Sandbox Code Playgroud)

产生这个情节:

在此处输入图片说明

data1.anno明明是一年。将年份解析为日期 obects 的选项是一个不错的选择。var parse = d3.timeParse("%Y"),然后使用它来设置您的时间刻度的域:.domain([parse(minDate), parse(maxDate)])如下所示

否则,如果您想在 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);
Run Code Online (Sandbox Code Playgroud)

这产生了这个图表:

在此处输入图片说明

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

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

Run Code Online (Sandbox Code Playgroud)

这产生了这个图表:

在此处输入图片说明

这是工作沙箱。

希望这可以帮助!??