重构我大约30分钟前发布的问题,以使其变得更好。我有以下代码,该代码使用常规更新模式在屏幕上移动圆圈和文本。
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
mybutton: "A"
}
}
handleButtonChange = (event) => {
this.setState({ mybutton: event.target.value });
};
drawPoints() {
const {mybutton} = this.state;
const myData = [
{x1:30, x2: 140, y1: 50, y2: 60, letter:"A"},
{x1:50, x2: 150, y1: 60, y2: 120, letter:"B"},
{x1:70, x2: 120, y1: 70, y2: 110, letter:"C"}
];
const pointsLayer = d3.select('#my-svg').select('g.points')
const xShift = function(d) {
if(mybutton === "A") {
return d.x1
} else {
return d.x2
}
}
const yShift = function(d) {
if(mybutton === "A") {
return d.y1
} else {
return d.y2
}
}
const textChange = function(d) {
if(mybutton === "A") {
return "white"
} else {
return "black"
}
}
const circleColorChange = function(d) {
if(mybutton === "A") {
return "#FF0000"
} else {
return "#FFAAAA"
}
}
pointsLayer
.selectAll("circle")
.data(myData)
.exit()
.transition()
.duration(100)
.attr('r', 0)
.remove();
pointsLayer
.selectAll("circle")
.data(myData)
.enter()
.append("circle")
.attr("cx", d => xShift(d))
.attr("cy", d => yShift(d))
.attr("r", 30)
.attr("fill", d => circleColorChange(d))
// .on("mouseover", ...)
// .on("mouseout", ...)
pointsLayer
.selectAll("circle")
.data(myData)
.transition()
.duration(1000)
.delay((d, i) => i * 0.5)
.attr("cx", d => xShift(d))
.attr("cy", d => yShift(d))
.attr("fill", d => circleColorChange(d))
pointsLayer.selectAll("text").remove('*')
pointsLayer
.selectAll("text")
.data(myData)
.enter()
.append('text')
.attr('x', d => xShift(d))
.attr('y', d => yShift(d))
.text(d => d.letter)
}
componentDidMount() {
d3.select('#my-svg')
.attr('width', '100%')
.attr('height', '100%')
.attr('viewBox', "0 0 " + (800) + " " + 600)
.attr('preserveAspectRatio', "xMaxYMax")
this.drawPoints();
}
componentDidUpdate() {
this.drawPoints()
}
render() {
const {mybutton} = this.state;
return(
<div>
<form>
<div>
<label>
<input
type={"radio"}
value={"A"}
checked={mybutton === "A"}
onChange={this.handleButtonChange}
/>
<span>{"A"}</span>
</label>
</div>
<div>
<label>
<input
type={"radio"}
value={"B"}
checked={mybutton === "B"}
onChange={this.handleButtonChange}
/>
<span>{"B"}</span>
</label>
</div>
</form>
<svg id="my-svg">
<g className="points" />
</svg>
</div>
);
}
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.2.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.2.0/umd/react-dom.development.js"></script>
<div id='root'>
Come On Work!
</div>
目前,我对圈子使用常规的更新模式。对于文本,我只是删除显示的文本并重绘,但是,我打算使用通用更新模式来滑动文本。
但是,我的应用程序对圆具有某些.on()鼠标悬停和鼠标移出的效果,因为我的.on()函数是仅放置在我的圈子元素上。
我想同时将两者都包含在圆圈和文本中,以使其结构分别为:
<g>
<circle>
<text>
</g>
...并且理想情况下,我想在g
元素上一次应用常规更新模式,而不是对圈子和文本分别应用一次。我想,那么.on()函数也可以放在g元素上。
该示例中省略了在过渡时发生的其他事件,包括使用其他辅助函数(类似于xShfit,yShift,但对于这些样式)更改圆的颜色,半径,笔划等。此外,文本颜色也会在过渡过程中发生变化。
根据对原始问题的回答,我计划在今天晚些时候和明天详细阅读D3文档,以更好地了解与D3相关的常规更新模式,但就目前而言,我仍在寻求有关此问题的帮助,并希望将其重新发布会引起更多关注。
任何帮助重构该代码的人,将只需要一个通用的更新模式就可以从元素内一起更新圆和文本,
谢谢!
编辑:使用更改圆形颜色的功能和假设用来更改文本颜色的另一个(未使用的)功能进行编辑。
EDIT2:也不确定当前使用的常规更新模式是否良好/正确。我使用exit并输入和更新,但没有合并到任何地方(不确定是否需要)...
答案 0 :(得分:1)
对于初学者来说,这似乎是XY problem:如果唯一的问题是标签将鼠标悬停,那就做...
.attr("pointer-events", "none")
...以选择文本。
此外,尽管人们通常喜欢将给定节点的所有元素(圆圈,文本等)放在一个组中,但要记住这种方法并非没有问题:例如,使用恰好是您的坐标和圆的半径,当您将文字作为<g>
中的圆的同级文字时,其他组中的某些圆将覆盖某些文字。
说了这么多,组的模式可以是这样的:
let groups = pointsLayer.selectAll(".myGroups")
.data(myData);
const groupsExit = groups.exit().remove();
const groupsEnter = groups.enter()
.append("g")
.attr("class", "myGroups");
groupsEnter.append("circle")
.attr("r", 20)
.attr("fill", d => circleColorChange(d));
groupsEnter.append("text")
.style("text-anchor", "middle")
.style("dominant-baseline", "central")
.text(d => d.letter);
groups = groupsEnter.merge(groups)
.attr("transform", d => "translate(" + xShift(d) + "," + yShift(d) + ")");
// .on("mouseover", ...)
// .on("mouseout", ...)
请注意,我们不会像您在代码中那样(重新)绑定用于退出选择的数据。
请记住,此处的所有元素都将添加到enter选择中。如果您还需要更新/输入/退出模式,则必须将其嵌套。
最后,你说...
我使用exit并输入和更新,但没有合并到任何地方(不确定是否需要)
您根本不需要merge()
方法,没有它就可以编写任何D3代码。我们使用它(就像我在这里所做的那样)只是为了保存一些重复的行。
这是您更新的代码(圆圈的半径较小,因此我们可以看到所有圆圈的文本):
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
mybutton: "A"
}
}
handleButtonChange = (event) => {
this.setState({
mybutton: event.target.value
});
};
drawPoints() {
const {
mybutton
} = this.state;
const myData = [{
x1: 30,
x2: 140,
y1: 50,
y2: 60,
letter: "A"
},
{
x1: 50,
x2: 150,
y1: 60,
y2: 120,
letter: "B"
},
{
x1: 70,
x2: 120,
y1: 70,
y2: 110,
letter: "C"
}
];
const pointsLayer = d3.select('#my-svg').select('g.points')
const xShift = function(d) {
if (mybutton === "A") {
return d.x1
} else {
return d.x2
}
}
const yShift = function(d) {
if (mybutton === "A") {
return d.y1
} else {
return d.y2
}
}
const textChange = function(d) {
if (mybutton === "A") {
return "white"
} else {
return "black"
}
}
const circleColorChange = function(d) {
if (mybutton === "A") {
return "#FF0000"
} else {
return "#FFAAAA"
}
}
let groups = pointsLayer.selectAll(".myGroups")
.data(myData);
const groupsExit = groups.exit().remove();
const groupsEnter = groups.enter()
.append("g")
.attr("class", "myGroups");
groupsEnter.append("circle")
.attr("r", 20)
.attr("fill", d => circleColorChange(d));
groupsEnter.append("text")
.style("text-anchor", "middle")
.style("dominant-baseline", "central")
.text(d => d.letter);
groups = groupsEnter.merge(groups)
.attr("transform", d => "translate(" + xShift(d) + "," + yShift(d) + ")");
// .on("mouseover", ...)
// .on("mouseout", ...)
}
componentDidMount() {
d3.select('#my-svg')
.attr('width', '100%')
.attr('height', '100%')
.attr('viewBox', "0 0 " + (800) + " " + 600)
.attr('preserveAspectRatio', "xMaxYMax")
this.drawPoints();
}
componentDidUpdate() {
this.drawPoints()
}
render() {
const {
mybutton
} = this.state;
return ( <
div >
<
form >
<
div >
<
label >
<
input type = {
"radio"
}
value = {
"A"
}
checked = {
mybutton === "A"
}
onChange = {
this.handleButtonChange
}
/> <
span > {
"A"
} < /span> < /
label > <
/div> <
div >
<
label >
<
input type = {
"radio"
}
value = {
"B"
}
checked = {
mybutton === "B"
}
onChange = {
this.handleButtonChange
}
/> <
span > {
"B"
} < /span> < /
label > <
/div> < /
form >
<
svg id = "my-svg" >
<
g className = "points" / >
<
/svg> < /
div >
);
}
}
ReactDOM.render( <
App / > ,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.2.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.2.0/umd/react-dom.development.js"></script>
<div id='root'>
Come On Work!
</div>