如何使用 D3 使用 JSON 数据创建折线图
How to create line chart with JSON data using D3
我正在使用 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
同样,我尝试制作条形图,效果很好
我对标签做了一些动态渲染,当我们调整 window 标签时,标签会自动调整。
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"
}
]
在 Muri 的原始示例中 D3 with React Hooks video series 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]);
像这样,在我们的 lineGenerator
函数中:
const lineGenerator = line()
.x((d) => xScale(new Date(d.sensorValueAddedTime))
.y((d) => yScale(d.sensorValue));
你会在上面注意到,如果我们要使用 scaleTime()
,我们必须将 sensorValueAddedTime
转换为 D3 可以理解的值——现在有一个额外的 space 在你的对象的字符串中,但是如果你去掉那个 space you can convert to a Date object and use d3.scaleTime()
.
更新:OP 要求绘制一组不同的数据并要求这些点“按该顺序出现”。
这似乎是您所要求的——“条形图的折线图版本”可能不会产生您想要的结果。只是说说而已:
如果你使用 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
and change the number of ticks that appear with 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)
产生这张图表的是:
希望对您有所帮助! ✌️
我正在使用 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
同样,我尝试制作条形图,效果很好
我对标签做了一些动态渲染,当我们调整 window 标签时,标签会自动调整。
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"
}
]
在 Muri 的原始示例中 D3 with React Hooks video series 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]);
像这样,在我们的 lineGenerator
函数中:
const lineGenerator = line()
.x((d) => xScale(new Date(d.sensorValueAddedTime))
.y((d) => yScale(d.sensorValue));
你会在上面注意到,如果我们要使用 scaleTime()
,我们必须将 sensorValueAddedTime
转换为 D3 可以理解的值——现在有一个额外的 space 在你的对象的字符串中,但是如果你去掉那个 space you can convert to a Date object and use d3.scaleTime()
.
更新:OP 要求绘制一组不同的数据并要求这些点“按该顺序出现”。
这似乎是您所要求的——“条形图的折线图版本”可能不会产生您想要的结果。只是说说而已:
如果你使用 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
and change the number of ticks that appear with 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)
产生这张图表的是:
希望对您有所帮助! ✌️