reactjs - d3雷达图在React中呈现不一致
问题描述
我的图表昨晚正在渲染。现在,它有时会完全渲染,有时会部分渲染,或者通常根本不渲染。我在开发工具中没有错误。我将我的 d3 代码复制到一个简单的 javascript 文件中,其中只有最新的 d3 脚本,并且它可以工作。任何见解将不胜感激。提前非常感谢。
如果有帮助,请回购:https ://github.com/EvanPoe/d3-chart-to-react
import React from "react";
import * as d3 from "d3";
function RadarChart() {
/////////////////////////////////////////////////////////
/////////////// The Radar Chart Function ////////////////
/////////////// Written by Nadieh Bremer ////////////////
////////////////// VisualCinnamon.com ///////////////////
/////////// Inspired by the code of alangrafu ///////////
/////////////////////////////////////////////////////////
/* Radar chart design created by Nadieh Bremer - VisualCinnamon.com */
//////////////////////// Set-Up //////////////////////////////
var margin = { top: 100, right: 100, bottom: 100, left: 100 },
width = Math.min(700, window.innerWidth - 10) - margin.left - margin.right,
height = Math.min(
width,
window.innerHeight - margin.top - margin.bottom - 20
);
////////////////////////// Data //////////////////////////////
var data = [
[
//Tsircon
{ axis: "BALLISTIC", value: 65 },
{ axis: "Glide", value: 39 },
{ axis: "20G TURN", value: 43 },
{ axis: "FISH HOOK DIVE", value: 50 },
{ axis: "PHUGOID MOTION", value: 60 },
{ axis: "ENERGY SCRUB", value: 52 },
],
// [
// //Kinzhal
// { axis: "BALLISTIC", value: 0.27 },
// { axis: "Glide", value: 0.16 },
// { axis: "20G TURN", value: 0.35 },
// { axis: "FISH HOOK DIVE", value: 0.13 },
// { axis: "PHUGOID MOTION", value: 0.2 },
// { axis: "ENERGY SCRUB", value: 0.13 },
// ],
// [
// //Example Missile Overlay
// { axis: "BALLISTIC", value: 0.26 },
// { axis: "Glide", value: 0.1 },
// { axis: "20G TURN", value: 0.3 },
// { axis: "FISH HOOK DIVE", value: 0.14 },
// { axis: "PHUGOID MOTION", value: 0.22 },
// { axis: "ENERGY SCRUB", value: 0.04 },
// ],
];
//////////////////// Draw the Chart //////////////////////////
var color = d3.scaleBand().range(["#739CC4", "#CC333F", "#00A0B0"]);
var radarChartOptions = {
w: width,
h: height,
margin: margin,
maxValue: 0.5,
levels: 5,
roundStrokes: false,
color: color,
};
//Call function to draw the Radar chart
RadarChart(".radarChart", data, radarChartOptions);
function RadarChart(id, data, options) {
var cfg = {
w: 600, //Width of the circle
h: 600, //Height of the circle
margin: { top: 20, right: 20, bottom: 20, left: 20 }, //The margins of the SVG
levels: 3, //How many levels or inner circles should there be drawn
maxValue: 0, //What is the value that the biggest circle will represent
labelFactor: 1.25, //How much farther than the radius of the outer circle should the labels be placed
wrapWidth: 60, //The number of pixels after which a label needs to be given a new line
opacityArea: 0.35, //The opacity of the area of the blob
dotRadius: 4, //The size of the colored circles of each blog
opacityCircles: 0.1, //The opacity of the circles of each blob
strokeWidth: 2, //The width of the stroke around each blob
roundStrokes: false, //If true the area and stroke will follow a round path (cardinal-closed)
color: d3.scaleBand(d3.schemeCategory10), //Color function
};
//Put all of the options into a variable called cfg
if ("undefined" !== typeof options) {
for (var i in options) {
if ("undefined" !== typeof options[i]) {
cfg[i] = options[i];
}
} //for i
} //if
//If the supplied maxValue is smaller than the actual one, replace by the max in the data
var maxValue = Math.max(
cfg.maxValue,
d3.max(data, function (i) {
return d3.max(
i.map(function (o) {
return o.value;
})
);
})
);
var allAxis = data[0].map(function (i, j) {
return i.axis;
}), //Names of each axis
total = allAxis.length, //The number of different axes
radius = Math.min(cfg.w / 2, cfg.h / 2), //Radius of the outermost circle
Format = d3.format("%"), //Percentage formatting
angleSlice = (Math.PI * 2) / total; //The width in radians of each "slice"
//Scale for the radius
var rScale = d3.scaleLinear().range([0, radius]).domain([0, maxValue]);
//////////// Create the container SVG and g /////////////
//Remove whatever chart with the same id/class was present before
d3.select(id).select("svg").remove();
//Initiate the radar chart SVG
var svg = d3
.select(id)
.append("svg")
.attr("width", cfg.w + cfg.margin.left + cfg.margin.right)
.attr("height", cfg.h + cfg.margin.top + cfg.margin.bottom)
.attr("class", "radar" + id);
//Append a g element
var g = svg
.append("g")
.attr(
"transform",
"translate(" +
(cfg.w / 2 + cfg.margin.left) +
"," +
(cfg.h / 2 + cfg.margin.top) +
")"
);
////////// Glow filter ///////////
//Filter for the outside glow
var filter = g.append("defs").append("filter").attr("id", "glow"),
feGaussianBlur = filter
.append("feGaussianBlur")
.attr("stdDeviation", "2.5")
.attr("result", "coloredBlur"),
feMerge = filter.append("feMerge"),
feMergeNode_1 = feMerge.append("feMergeNode").attr("in", "coloredBlur"),
feMergeNode_2 = feMerge.append("feMergeNode").attr("in", "SourceGraphic");
/////////////// Draw the Circular grid //////////////////
//Wrapper for the grid & axes
var axisGrid = g.append("g").attr("class", "axisWrapper");
//Draw the background circles
axisGrid
.selectAll(".levels")
.data(d3.range(1, cfg.levels + 1).reverse())
.enter()
.append("circle")
.attr("class", "gridCircle")
.attr("r", function (d, i) {
return (radius / cfg.levels) * d;
})
.style("fill", "#CDCDCD")
.style("stroke", "#CDCDCD")
.style("fill-opacity", cfg.opacityCircles)
.style("filter", "url(#glow)");
//Text indicating at what % each level is
axisGrid
.selectAll(".axisLabel")
.data(d3.range(1, cfg.levels + 1).reverse())
.enter()
.append("text")
.attr("class", "axisLabel")
.attr("x", 4)
.attr("y", function (d) {
return (-d * radius) / cfg.levels;
})
.attr("dy", "0.4em")
.style("font-size", "10px")
.attr("fill", "#737373")
.text(function (d, i) {
return Format((maxValue * d) / cfg.levels);
});
//////////////////// Draw the axes //////////////////////
//Create the straight lines radiating outward from the center
var axis = axisGrid
.selectAll(".axis")
.data(allAxis)
.enter()
.append("g")
.attr("class", "axis");
//Append the lines
axis
.append("line")
.attr("x1", 0)
.attr("y1", 0)
.attr("x2", function (d, i) {
return rScale(maxValue * 1.1) * Math.cos(angleSlice * i - Math.PI / 2);
})
.attr("y2", function (d, i) {
return rScale(maxValue * 1.1) * Math.sin(angleSlice * i - Math.PI / 2);
})
.attr("class", "line")
.style("stroke", "white")
.style("stroke-width", "2px");
//Append the labels at each axis
axis
.append("text")
.attr("class", "legend")
.style("font-size", "11px")
.attr("text-anchor", "middle")
.attr("dy", "0.35em")
.attr("x", function (d, i) {
return (
rScale(maxValue * cfg.labelFactor) *
Math.cos(angleSlice * i - Math.PI / 2)
);
})
.attr("y", function (d, i) {
return (
rScale(maxValue * cfg.labelFactor) *
Math.sin(angleSlice * i - Math.PI / 2)
);
})
.text(function (d) {
return d;
})
.call(wrap, cfg.wrapWidth);
svg
.append("text")
.attr("x", width / 4.8)
.attr("y", 75 - margin.top / 2)
.attr("text-anchor", "middle")
.style("font-size", "24px")
.style("font-weight", "bold")
.style("fill", "white")
.text("MANEUVERS");
svg
.append("text")
.attr("x", width / 4.2)
.attr("y", 95 - margin.top / 2)
.attr("text-anchor", "middle")
.style("font-size", "16px")
.style("fill", "white")
.text("initiate: % ground track");
svg
.append("text")
.attr("x", width / 3.6)
.attr("y", 115 - margin.top / 2)
.attr("text-anchor", "middle")
.style("font-size", "16px")
.style("fill", "#8CD9FF")
.text("% variability");
svg
.append("text")
.attr("x", width / 8)
.attr("y", 135 - margin.top / 2)
.attr("text-anchor", "middle")
.style("font-size", "16px")
.style("fill", "white")
.text("sample:");
svg
.append("text")
.attr("x", width / 3.4)
.attr("y", 135 - margin.top / 2)
.attr("text-anchor", "middle")
.style("font-size", "16px")
.style("fill", "#76ADDB")
.text("% trajectories");
///////////// Draw the radar chart blobs ////////////////
//The radial line function
var radarLine = d3
.lineRadial()
.curve(d3.curveLinearClosed)
.radius(function (d) {
return rScale(d.value);
})
.angle(function (d, i) {
return i * angleSlice;
});
if (cfg.roundStrokes) {
radarLine.curve(d3.curveCardinalClosed);
}
//Create a wrapper for the blobs
var blobWrapper = g
.selectAll(".radarWrapper")
.data(data)
.enter()
.append("g")
.attr("class", "radarWrapper");
//Append the backgrounds
blobWrapper
.append("path")
.attr("class", "radarArea")
.attr("d", function (d, i) {
return radarLine(d);
})
.style("fill", function (d, i) {
return cfg.color(i);
})
.style("fill-opacity", cfg.opacityArea)
.on("mouseover", function (d, i) {
//Dim all blobs
d3.selectAll(".radarArea")
.transition()
.duration(200)
.style("fill-opacity", 0.1);
//Bring back the hovered over blob
d3.select(this).transition().duration(200).style("fill-opacity", 0.7);
})
.on("mouseout", function () {
//Bring back all blobs
d3.selectAll(".radarArea")
.transition()
.duration(200)
.style("fill-opacity", cfg.opacityArea);
});
//Create the outlines
blobWrapper
.append("path")
.attr("class", "radarStroke")
.attr("d", function (d, i) {
return radarLine(d);
})
.style("stroke-width", cfg.strokeWidth + "px")
.style("stroke", function (d, i) {
return cfg.color(i);
})
.style("fill", "none")
.style("filter", "url(#glow)");
//Append the circles
blobWrapper
.selectAll(".radarCircle")
.data(function (d, i) {
return d;
})
.enter()
.append("circle")
.attr("class", "radarCircle")
.attr("r", cfg.dotRadius)
.attr("cx", function (d, i) {
return rScale(d.value) * Math.cos(angleSlice * i - Math.PI / 2);
})
.attr("cy", function (d, i) {
return rScale(d.value) * Math.sin(angleSlice * i - Math.PI / 2);
})
.style("fill", function (d, i, j) {
return cfg.color(j);
})
.style("fill-opacity", 0.8);
//////// Append invisible circles for tooltip ///////////
//Wrapper for the invisible circles on top
var blobCircleWrapper = g
.selectAll(".radarCircleWrapper")
.data(data)
.enter()
.append("g")
.attr("class", "radarCircleWrapper");
//Append a set of invisible circles on top for the mouseover pop-up
blobCircleWrapper
.selectAll(".radarInvisibleCircle")
.data(function (d, i) {
return d;
})
.enter()
.append("circle")
.attr("class", "radarInvisibleCircle")
.attr("r", cfg.dotRadius * 1.5)
.attr("cx", function (d, i) {
return rScale(d.value) * Math.cos(angleSlice * i - Math.PI / 2);
})
.attr("cy", function (d, i) {
return rScale(d.value) * Math.sin(angleSlice * i - Math.PI / 2);
})
.style("fill", "none")
.style("pointer-events", "all")
.on("mouseover", function (d, i) {
let newX = parseFloat(d3.select(this).attr("cx")) - 10;
let newY = parseFloat(d3.select(this).attr("cy")) - 10;
tooltip
.attr("x", newX)
.attr("y", newY)
.text(Format(d.value))
.transition()
.duration(200)
.style("opacity", 1);
})
.on("mouseout", function () {
tooltip.transition().duration(200).style("opacity", 0);
});
//Set up the small tooltip for when you hover over a circle
var tooltip = g.append("text").attr("class", "tooltip").style("opacity", 0);
/////////////////// Helper Function /////////////////////
//Taken from http://bl.ocks.org/mbostock/7555321
//Wraps SVG text
function wrap(text, width) {
text.each(function () {
var text = d3.select(this),
words = text.text().split(/\s+/).reverse(),
word,
line = [],
lineNumber = 0,
lineHeight = 1.4, // ems
y = text.attr("y"),
x = text.attr("x"),
dy = parseFloat(text.attr("dy")),
tspan = text
.text(null)
.append("tspan")
.attr("x", x)
.attr("y", y)
.attr("dy", dy + "em");
while ((word = words.pop())) {
line.push(word);
tspan.text(line.join(" "));
if (tspan.node().getComputedTextLength() > width) {
line.pop();
tspan.text(line.join(" "));
line = [word];
tspan = text
.append("tspan")
.attr("x", x)
.attr("y", y)
.attr("dy", ++lineNumber * lineHeight + dy + "em")
.text(word);
}
}
});
} //wrap
} //RadarChart
return (
<div>
<h1>Hi, I'm the radar chart</h1>
<svg className="radarChart container"></svg>
</div>
);
}
export default RadarChart;
package.json 依赖项
"d3": "^6.7.0",
"d3-scale": "^3.3.0",
"gh-pages": "^3.1.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",
解决方案
RadarChart
只有在正确安装组件后,您才能在组件内运行 D3 代码:
- 添加
useRef
钩子并将引用绑定到SVG:
const svgRef = useRef();
...
return (
<div>
<h1>Hi, I'm the radar chart</h1>
<svg ref={svgRef} className="radarChart container" ></svg>
</div>
);
- 添加
useEffect
依赖svgRef
:
useEffect(() => {
if (svgRef.current) {
const svg = d3.select(svgRef.current);
drawChart(svg);
}
}, [svgRef]);
- 将所有 D3 代码放入
drawChart
:
const drawChart = svg => {
...
svg.selectAll("*").remove();
//Initiate the radar chart SVG
svg
.attr("width", cfg.w + cfg.margin.left + cfg.margin.right)
.attr("height", cfg.h + cfg.margin.top + cfg.margin.bottom)
...
}
看看它在沙箱中工作
推荐阅读
- c++ - OOP:什么时候应该动态声明类成员?
- android - React Native Gradle ImagePicker 不会构建
- go - 如果有多个发送方在该通道上发送数据,接收方如何知道单个通道上的数据为空?
- arrays - 在 React 中为对象数组设置状态
- javascript - 推断金额问题,每个执行的值不同
- hibernate - 当休眠生成自己的表并且我们的实体模型中没有该表/实体时,在一对多关系中使用 HQL
- java - 获取一个视图类以在 main 上显示并使用视图类中的内容运行
- android - 我是否正确地在活动代码中进行依赖注入?
- reactjs - 反应获取数据并呈现错误(如果有)
- ram - 计算机如何直接访问 RAM 中的内存位置?