这是一个 React 应用程序,可能出了什么问题?每次都会附加一个新的 svg,而不是现有的正在更新的 svg... 我不确定我应该添加哪些其他详细信息,我已经包含了您可能需要帮助我的所有信息,如果还有什么我应该添加的,请告诉我。谢谢。
这是我的组件,我通过 props 传递数据。
export const FourDirectionsTimeChart = ({data}) => {
useEffect(() => {
const width = document.getElementById("container").clientWidth;
const height = document.getElementById("container").clientHeight;
const R = (width + height) / 8;
const CX = width / 2;
const CY = height / 2;
const smallR = R * 0.1;
const circleColor = "bisque";
const itemColor = "#3F4200";
let svg = d3.select("#container")
.append("svg")
.attr("preserveAspectRatio", "xMinYMin meet")
.attr("viewBox", `0 0 ${width} ${height}`)
let mainCircle = svg.append("circle")
.attr("fill", circleColor)
.attr("r", R)
.attr("cx", CX)
.attr("cy", CY);
let centerCircle = svg.append("circle")
.attr("fill", "white")
.attr("r", smallR)
.attr("cx", CX)
.attr("cy", CY);
const timePercentage = (time) => {
const percentage = (time * 100 / 23) / 100;
return percentage;
};
function timeToRadius(time) {
return (smallR + timePercentage(time) * (R - smallR))
}
// Times concentric circles ---
for (let i = 0; i <= 23; i += 4) {
svg.append("circle")
.attr("fill", "none")
.attr("cx", CX)
.attr("cy", CY)
.attr("stroke-dasharray", "4 20")
.attr("stroke", "gray")
.attr("stroke-width", 1)
.attr("r", timeToRadius(i))
}
// Cardinal points ---
const textTime = 25;
const fontSize = R * 0.25;
svg.append("text")
.attr("dx", getPosition(0, textTime).x)
.attr("dy", getPosition(0, textTime).y)
.text("N")
.attr("fill", "black")
.attr("font-size", fontSize)
.attr("text-anchor", "middle")
.style("font-family", "serif")
svg.append("text")
.attr("dx", getPosition(180, textTime).x)
.attr("dy", getPosition(180, textTime).y)
.text("S")
.attr("fill", "black")
.attr("font-size", fontSize)
.attr("text-anchor", "middle")
.style("font-family", "serif")
.attr("alignment-baseline", "hanging")
svg.append("text")
.attr("dx", getPosition(-90, textTime).x)
.attr("dy", getPosition(-90, textTime).y)
.text("E")
.attr("fill", "black")
.attr("font-size", fontSize)
.attr("text-anchor", "start")
.attr("alignment-baseline", "middle")
.style("font-family", "serif");
svg.append("text")
.attr("dx", getPosition(90, textTime).x)
.attr("dy", getPosition(90, textTime).y)
.text("O")
.attr("fill", "black")
.attr("font-size", fontSize)
.attr("text-anchor", "end")
.attr("alignment-baseline", "middle")
.style("font-family", "serif")
// Ships positions ---
function getPosition(degrees, time) {
const getRadians = (degrees) => degrees * Math.PI / 180 + Math.PI / 2;
let x = (smallR + timePercentage(time) * (R - smallR)) * Math.cos(getRadians(degrees)) + CX;
let y = (smallR + timePercentage(time) * (R - smallR)) * Math.sin(getRadians(degrees)) * -1 + CY;
return { x, y };
}
// Data mapping ---
let parsedData = [];
(() => {
data?.forEach((x) => {
parsedData.push({
id: x.Patente,
course: x.Rumbo,
speed: x.Velocidad,
time: new Date(x.Fecha_gps).getHours()
})
});
let position;
parsedData.map(d => {
position = getPosition(d.course, d.time);
svg.append("circle")
.attr("fill", () => {
if (d.speed == 0) return "brown";
else return "brown";
})
.attr("r", R * 0.015)
.attr("cx", position.x)
.attr("cy", position.y)
.attr("opacity", 0.4);
});
})();
}, [data]);
return (
<div id="container" style={{ width: '100%', height: '100%' }}></div>
)
}
你认为
let svg = d3.select("#container").append("svg")
这条线有什么作用?它选择 ID 为 "container"
的元素并向其附加一个新的 SVG 元素。
您基本上可以做以下两件事之一:
d3.select("#container svg").remove()
,然后d3.select("#container").append("svg")
;我已经获取了您的代码并添加了一个示例来说明如何实现下面的第二种方法:
请注意,要访问
R
、smallR
等,我必须将初始化逻辑划分为两个 useEffect
钩子。
const {
useEffect,
useState
} = React;
const FourDirectionsTimeChart = () => {
const [data, setData] = useState([]);
const [constants, setConstants] = useState({});
// Initialization logic, run only once
useEffect(() => {
const width = document.getElementById("container").clientWidth;
const height = document.getElementById("container").clientHeight;
const R = (width + height) / 8;
const CX = width / 2;
const CY = height / 2;
const smallR = R * 0.1;
setConstants({
R,
CX,
CY,
smallR
});
const circleColor = "bisque";
const itemColor = "#3F4200";
const svg = d3.select("#container")
.append("svg")
.attr("preserveAspectRatio", "xMinYMin meet")
.attr("viewBox", `0 0 ${width} ${height}`);
const mainCircle = svg.append("circle")
.attr('id', 'mainCircle')
.attr("fill", circleColor);
const centerCircle = svg.append("circle")
.attr('id', 'innerCircle')
.attr("fill", "white");
// Times concentric circles ---
for (let i = 0; i <= 23; i += 4) {
svg.append("circle")
.attr("class", "concentric")
.datum(i)
.attr("fill", "none")
.attr("stroke-dasharray", "4 20")
.attr("stroke", "gray")
.attr("stroke-width", 1);
}
// Cardinal points ---
svg.append("text")
.attr("id", "N")
.text("N")
.attr("fill", "black")
.attr("text-anchor", "middle")
.style("font-family", "serif")
svg.append("text")
.attr("id", "S")
.text("S")
.attr("fill", "black")
.attr("text-anchor", "middle")
.style("font-family", "serif")
.attr("alignment-baseline", "hanging")
svg.append("text")
.attr("id", "E")
.text("E")
.attr("fill", "black")
.attr("text-anchor", "start")
.attr("alignment-baseline", "middle")
.style("font-family", "serif");
svg.append("text")
.attr("id", "O")
.text("O")
.attr("fill", "black")
.attr("text-anchor", "end")
.attr("alignment-baseline", "middle")
.style("font-family", "serif")
}, []);
// Drawing logic, run whenever `constants` changes (which is only once)
useEffect(() => {
const { R, smallR, CX, CY } = constants;
const textTime = 25;
const fontSize = R * 0.25;
const svg = d3.select("#container svg");
const mainCircle = svg.select("#mainCircle")
.attr("r", R)
.attr("cx", CX)
.attr("cy", CY);
const centerCircle = svg.select("#innerCircle")
.attr("r", smallR)
.attr("cx", CX)
.attr("cy", CY);
// Times concentric circles ---
function timeToRadius(time) {
return (smallR + timePercentage(time) * (R - smallR));
}
svg.selectAll(".concentric")
.attr("cx", CX)
.attr("cy", CY)
.attr("r", d => timeToRadius(d))
// Cardinal points ---
svg.select("text#N")
.attr("font-size", fontSize)
.attr("dx", getPosition(0, textTime).x)
.attr("dy", getPosition(0, textTime).y);
svg.select("text#S")
.attr("font-size", fontSize)
.attr("dx", getPosition(180, textTime).x)
.attr("dy", getPosition(180, textTime).y);
svg.select("text#E")
.attr("font-size", fontSize)
.attr("dx", getPosition(-90, textTime).x)
.attr("dy", getPosition(-90, textTime).y);
svg.select("text#O")
.attr("font-size", fontSize)
.attr("dx", getPosition(90, textTime).x)
.attr("dy", getPosition(90, textTime).y);
}, [constants]);
const timePercentage = (time) => {
const percentage = (time * 100 / 23) / 100;
return percentage;
};
function timeToRadius(time) {
const {
smallR,
R
} = constants;
return (smallR + timePercentage(time) * (R - smallR));
}
const getRadians = (degrees) => degrees * Math.PI / 180 + Math.PI / 2;
function getPosition(degrees, time) {
const {
smallR,
R,
CX,
CY
} = constants;
const x = (smallR + timePercentage(time) * (R - smallR)) * Math.cos(getRadians(degrees)) + CX;
const y = (smallR + timePercentage(time) * (R - smallR)) * Math.sin(getRadians(degrees)) * -1 + CY;
return {
x,
y
};
}
useEffect(() => {
const svg = d3.select('#container svg');
// Data mapping ---
let position;
const points = svg.selectAll('.point').data(data);
// Remove old points
points.exit().remove();
// Append and set constants only for new points
points
.enter()
.append('circle')
.attr('class', 'point')
.attr("r", 15)
.attr("opacity", 0.4)
// Then, take both the new and the updated points
.merge(points)
.attr("cx", d => getPosition(d.course, d.time).x)
.attr("cy", d => getPosition(d.course, d.time).y)
.attr("fill", d => d.speed === 0 ? "brown" : "red");
}, [data]);
useEffect(() => {
let i = 0;
setInterval(() => {
setData(i % 2 === 0 ? data1 : data2);
i++;
}, 2000);
}, []);
return ( <
div id = "container"
style = {
{
width: '100%',
height: '100%'
}
} > < /div>
)
}
const data1 = [{
id: 100,
course: 35,
speed: 0,
time: 5
}];
const data2 = [{
id: 101,
course: 80,
speed: 25,
time: 12
}, {
id: 102,
course: 256,
speed: 0,
time: 15
}];
ReactDOM.render( <
FourDirectionsTimeChart / > ,
document.getElementById('root')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id="root" style="width: 500px; height: 500px"></div>
有点晚了,他
另外,检查是否启用了 StrictMode,因为它会渲染包装树两次,