提问人:Mendes 提问时间:11/6/2023 更新时间:11/6/2023 访问量:16
无法在 d3 和 NextJs 上显示折线图
Cannot show line graph on d3 and NextJs
问:
我正在使用 d3 和 NextJs 13 编写一个 SpeedGraph 组件。目标是接收速度和时间(epoch)对的数组,绘制一个简单的折线图,显示每个时间戳的速度。
代码如下:
SpeedChart.tsx
"use client"
import { useRef, useEffect } from "react";
import moment from "moment";
import styles from "./SpeedChart.module.css";
import {
select,
scaleLinear,
axisBottom,
max,
axisLeft,
line,
scaleOrdinal,
schemeCategory10,
scaleTime,
} from "d3";
const HEIGHT = 500;
const WIDTH = 500;
const renderChart = (svgRef, data) => {
let duration = 10;
let dateFormat = "L LT";
let height = HEIGHT;
let width = WIDTH;
const title = "SPEED CHART GRAPH";
let margin = {
top: 10,
bottom: 10,
right: 10,
left: 10
}
let innerHeight = height - margin.top - margin.bottom; //480
let innerWidth = width - margin.left - margin.right; // 480
// Get values from data
const xValue = (d) => d.date_time;
const yValue = (d) => d.speed;
let minDT = 0;
let maxDT = 0;
data.map(d => {
if (d.date_time > maxDT) maxDT = d.date_time;
if (d.date_time < minDT) minDT = d.date_time;
})
let yScale = scaleLinear()
.domain([0, max(data, yValue)])
.range([0, innerHeight])
.nice();
let xScale = scaleTime()
.domain([minDT, maxDT])
.range([0, innerWidth]);
const yAxis = scaleLinear()
.domain([0, max(data, yValue)])
.range([innerHeight, 0])
.nice();
const colorScale = scaleOrdinal(schemeCategory10);
const graph = select(svgRef.current);
graph
.append("svg")
.attr("width", innerWidth)
.attr("height", innerHeight)
.classed("SpeedChartGraphContainerSvg", true)
.attr("transform", `translate(${margin.left},${margin.top})`);
graph.select("svg").append("g");
graph
.select("g")
.append("g")
.call(axisLeft(yAxis).tickSize(-innerWidth))
.classed("horizontalLines", true);
graph
.select("g")
.append("g")
.call(
axisBottom(xScale).tickFormat((d, i) => {
return moment(d).format(dateFormat);
})
)
.attr("transform", `translate(0,${innerHeight})`)
.selectAll("text")
.attr("transform", "rotate(330)")
.style("text-anchor", "end");
let nested = [];
//nest().key(colorValue).entries(data);
colorScale.domain(nested.map((d) => d.key));
const lineGenerator = line()
.x((d) => xScale(xValue(d)))
.y((d) => innerHeight - yScale(yValue(d)));
graph
.select("g")
.selectAll(".LinePath")
.data(nested)
.enter()
.append("path")
.classed("LinePath", true)
.attr("d", (d) => lineGenerator(d.values))
.transition()
.duration(4000)
.attr("stroke", (d) => colorScale(d.key))
.style("fill", "none");
const handleDurationConverter = (value) => {
let duration = moment.duration(value);
let x = null;
if (duration.years())
x =
duration.years() +
"y " +
Math.floor(duration.asDays()) +
"d " +
duration.hours() +
"h " +
duration.minutes() +
"m ";
else if (Math.floor(duration.asDays()))
x =
Math.floor(duration.asDays()) +
"d " +
duration.hours() +
"h " +
duration.minutes() +
"m ";
else if (duration.hours())
x = duration.hours() + "h " + duration.minutes() + "m ";
else x = duration.minutes() + "m ";
return x;
};
graph
.select("g")
.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("cx", (d) => xScale(xValue(d)))
.attr("cy", (d) => innerHeight - yScale(yValue(d)))
.attr("r", 4)
.attr("fill", (d) => colorScale(d.key))
.on("mouseenter", (d) => {
graph
.select("g")
.selectAll(".tooltip")
.data([d])
.join("text")
.attr("class", "SpeedChartTooltip")
.text(
`${moment.unix(d.data[0].dateTime / 1000).format("L LT")} - ${duration
? handleDurationConverter(d.data[0].value)
: d.data[0].value
}`
)
.attr("x", (d) => xScale(xValue(d)))
.attr("y", (d) => innerHeight - yScale(yValue(d)) - 10)
.style("visibility", "visible");
})
.on("mouseleave", (d) => {
graph.select("g").selectAll(".SpeedChartTooltip").remove();
});
graph
.select("svg")
.append("text")
.text(title)
.classed("title", true)
.attr("transform", `translate(${innerWidth / 2},-20)`)
.style("text-anchor", "middle");
};
const SpeedChart = (props) => {
const svgRef = useRef();
if (!props.data) return <h3>"No Data"</h3>
useEffect(() => {
renderChart(svgRef, props.data);
}, [svgRef]);
return (
<div className={styles.container} ref={svgRef} />
);
};
export default SpeedChart;
SpeedChart.module.css
.SpeedChartContainer{
display: flex;
flex-direction: column;
margin: 40px 0px 0px 40px;
}
.SpeedChartGraphContainer{
flex:3;
}
.SpeedChartGraphContainer svg{
overflow: visible;
}
.SubtitleContainer {
padding-top: 50px;
}
.SpeedChartSubtitle{
align-self: flex-end;
flex: 1;
}
.SpeedChartGraphContainer .horizontalLines .tick line{
opacity: 0.2;
}
.LinePath {
stroke-width: 1;
stroke-linejoin: round;
stroke-linecap: round;
}
.title{
font-size: 16px;
}
.tick text{
font-size: 12px;
}
.legendBox{
font-size: 12px;
}
.SpeedChartTooltip {
position: relative;
display: inline-block;
border-bottom: 1px dotted black;
}
我怎么称呼它:
import SpeedChart from "../SpeedChart";
import styles from "./App.module.css";
const App = () => {
const data = [
{
date_time: 1699276312738211946,
speed: 1.0915E4
},
{
date_time: 1699276313739358704,
speed: 1.0917E4
},
{
date_time: 1699276314740502972,
speed: 1.0919E4
},
{
date_time: 1699276315741013020,
speed: 1.0921E4
},
{
date_time: 1699276316741536807,
speed: 1.0923E4
},
{
date_time: 1699276317741867170,
speed: 1.0925E4
},
{
date_time: 1699276318742266805,
speed: 1.0927E4
},
{
date_time: 1699276319742930549,
speed: 1.0929E4
},
{
date_time: 1699276320767546107,
speed: 1.0931E4
},
{
date_time: 1699276321774277296,
speed: 1.0933E4
}];
return (
<div className={styles.container}>
<h1><SpeedChart data={data} /></h1>
</div>)
}
export default App;
这是我在屏幕上看到的:
一个附带问题,useEffect 被调用了两次,所以看到 da d3 将图形加倍。
如何修复图形并使useEffect仅调用一次?
答: 暂无答案
评论