我有一个项目,尝试创建堆积条形图。其要求之一是图表应该能够更新。我已经使用通用更新模式来完成此任务。但我无法让代码工作。
当图表更新时,新生成的图表从底部进入。所以我猜这意味着代码永远不会执行模式的更新部分,并且始终认为更新的数据是新数据。
我在代码片段中添加了 3 个不同的数据数组,为我面临的问题提供了一个很好的例子。前两个数组包含相同的键,应该更新图表。最后一个数组包含完全不同的键,这里应该输入图表而不是更新。
我还注意到,当新数据添加到图表中时。旧数据永远不会被删除。因此更新模式的退出状态也不会被触发。
我知道更新模式的数据函数中应该定义一个键.data(stackData, d => {return d.key;})。但我似乎不知道应该输入什么键。
我已从原始代码中删除了所需的尽可能多的代码,以便使其适用于代码片段。需要代码片段中包含的所有代码才能使其正常工作。
this.width = 400;
this.height = 200;
var margin = {
top: 20,
right: 20,
bottom: 30,
left: 40
}
this.index = 0;
this.svg = d3
.select(".canvas")
.classed("svg-container", true)
.append("svg")
.attr("class", "chart")
.attr(
"viewBox",
`0 0 ${this.width} ${this.height}`
)
.attr("preserveAspectRatio", "xMinYMin meet")
.classed("svg-content-responsive", true)
.append("g");
const scale = [0, 1200];
// set the scales
this.xScale = d3
.scaleBand()
.range([0, width])
.padding(0.3);
this.yScale = d3.scaleLinear().range([this.height, 0]);
this.svg.append("g").attr("class", "bars");
const update = data => {
const scale = [0, 1200];
// Update scales.
this.xScale.domain(data.map(d => d.key));
this.yScale.domain([scale[0], scale[1]]);
const subgroups = ["home", "work", "public"];
var color = d3
.scaleOrdinal()
.domain(subgroups)
.range(["#206BF3", "#171D2C", "#8B0000"]);
var stackData = d3.stack().keys(subgroups)(data);
// Set up transition.
const dur = 1000;
const t = d3.transition().duration(dur);
this.svg
.append("g")
.selectAll("g")
.data(stackData, d => {
return d.key;
})
.join(
enter => {
enter
.append("g")
.attr("fill", d => color(d.key))
.selectAll("rect")
.data(d => {
return d;
})
.join(
enter => {
enter
.append("rect")
.attr("class", "bar")
.attr("x", d => {
return this.xScale(d.data.key);
})
.attr("y", () => {
return this.yScale(0);
})
.attr("height", () => {
return this.height - this.yScale(0);
})
.attr("width", this.xScale.bandwidth())
.transition(t)
.delay((d, i) => i * 20)
.attr("y", d => {
return this.yScale(d[1]);
})
.attr("height", d => {
return this.yScale(d[0]) - this.yScale(d[1]);
});
},
update => {
update
.transition(t)
.delay((d, i) => i * 20)
.attr("x", d => this.xScale(d.key))
.attr("y", d => {
return this.yScale(d[1]);
})
.attr("height", d => {
return this.yScale(d[0]) - this.yScale(d[1]);
});
},
exit => {
exit
.transition()
.duration(dur / 2)
.style("fill-opacity", 0)
.remove();
}
);
},
update => {
update
.transition(t)
.delay((d, i) => i * 20)
.attr("x", d => this.xScale(d.key))
.attr("y", d => {
return this.yScale(d[1]);
})
.attr("height", d => {
return this.yScale(d[0]) - this.yScale(d[1]);
});
},
exit => {
exit
.transition()
.duration(dur / 2)
.style("fill-opacity", 0)
.remove();
}
);
};
const data = [
[{
key: "Jan",
home: 371,
work: 335,
public: 300
},
{
key: "Feb",
home: 343,
work: 437,
public: 228
},
{
key: "Mrt",
home: 359,
work: 261,
public: 202
},
{
key: "Apr",
home: 274,
work: 217,
public: 482
},
{
key: "Mei",
home: 442,
work: 314,
public: 477
},
{
key: "Jun",
home: 464,
work: 261,
public: 278
},
{
key: "Jul",
home: 343,
work: 244,
public: 396
},
{
key: "Aug",
home: 231,
work: 406,
public: 338
},
{
key: "Sep",
home: 380,
work: 382,
public: 366
},
{
key: "Okt",
home: 391,
work: 408,
public: 455
},
{
key: "Nov",
home: 419,
work: 261,
public: 226
},
{
key: "Dec",
home: 217,
work: 453,
public: 335
}
],
[{
key: "Jan",
home: 282,
work: 363,
public: 379
},
{
key: "Feb",
home: 428,
work: 355,
public: 216
},
{
key: "Mrt",
home: 217,
work: 493,
public: 280
},
{
key: "Apr",
home: 304,
work: 283,
public: 454
},
{
key: "Mei",
home: 397,
work: 406,
public: 289
},
{
key: "Jun",
home: 242,
work: 239,
public: 232
},
{
key: "Jul",
home: 327,
work: 453,
public: 264
},
{
key: "Aug",
home: 242,
work: 240,
public: 414
},
{
key: "Sep",
home: 495,
work: 382,
public: 368
},
{
key: "Okt",
home: 285,
work: 471,
public: 364
},
{
key: "Nov",
home: 315,
work: 421,
public: 482
},
{
key: "Dec",
home: 214,
work: 284,
public: 297
}
],
[{
key: "1",
home: 282,
work: 363,
public: 379
},
{
key: "2",
home: 232,
work: 432,
public: 324
},
{
key: "3",
home: 324,
work: 124,
public: 432
},
{
key: "4",
home: 425,
work: 353,
public: 532
}
]
];
update(data[this.index]);
const swap = document.querySelector(".swap");
swap.addEventListener("click", () => {
if (this.index < 2) this.index += 1;
else this.index = 0;
update(data[this.index]);
});Run Code Online (Sandbox Code Playgroud)
<button class="swap">swap</button>
<div class="canvas"></div>
<script src="https://d3js.org/d3.v6.js"></script>Run Code Online (Sandbox Code Playgroud)
这里至少发生了一些事情。正如迈克尔·罗文斯基 (Michael Rovinsky) 所指出的,您将为g每次更新添加一个包含所有酒吧的新组。由于它是一个新组,因此没有绑定任何数据,这将为每个调用创建新的条形。
相反,您应该只创建一次该组,并且可能将其分配给一个变量以使其更易于使用。我将使用该this.svg.append("g").attr("class", "bars");组,因为它似乎没有用于其他任何用途:
var bars = this.svg.append("g").attr("class", "bars");
然后在您的更新函数中更改:
this.svg
.append("g")
.selectAll("g")
// ...
Run Code Online (Sandbox Code Playgroud)
到
bars
.selectAll("g")
Run Code Online (Sandbox Code Playgroud)
当您不返回、和选择时,您的调用join不太标准。当你有一个带有如下主体的箭头函数时:enterupdateexit
enter => {
enter.append('g') //...
}
Run Code Online (Sandbox Code Playgroud)
它实际上并没有返回输入选择,它应该是
enter => {
return enter.append('g') //...
}
Run Code Online (Sandbox Code Playgroud)
或者
enter => enter.append('g') //...
Run Code Online (Sandbox Code Playgroud)
虽然从技术上讲您不需要返回enter或update选择,但这是使用它们的预期方式,并且当您稍后尝试使用组合enter和选择时(我们稍后将这样做),确实会导致问题。update
您实现嵌套连接的方式表明您不太了解连接的工作原理。当您enter追加新元素时,这些元素没有任何绑定到它们的数据,因此当您使用后续的 时join,嵌套的updateandexit函数将永远不会运行。
enter您还会注意到,您在和中重复了许多相同的代码update。
我认为您打算做的事情的更标准方法是在必要时添加新条形,然后使用新条形和旧条形(由join条形段返回并更新它们)。
它看起来像这样:
bars.selectAll('g').data(data).join(
enter => enter.append('g'),
update => update, // don't do anything with only the update selection
exit => exit.remove()
) // now we're selecting all of the enter and update g's
.selectAll('rect')
.data(d => d).join(
enter => enter.append('rect'),
update => update,
exit => exit.remove()
) // now we're selecting all of the enter and update rects
.transition(t)
// ...
Run Code Online (Sandbox Code Playgroud)
如果查看数据的格式,您会发现 stackData 始终是一个长度为 3 的数组,每个条目都有一个数组。这些嵌套数组根据列数而变化,并且应该在该级别定义键:
//... group selection
.selectAll('rect')
.data(d => d, d => d.data.key)
//...
Run Code Online (Sandbox Code Playgroud)
.attr("x", d => this.xScale(d.key))最后,当您更新应该使用的酒吧位置时,有一些代码d.data.key。
我整合了大部分冗余的输入和更新代码,并删除了一些无关的代码。总而言之,它看起来像:
this.svg
.append("g")
.selectAll("g")
// ...
Run Code Online (Sandbox Code Playgroud)
bars
.selectAll("g")
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
677 次 |
| 最近记录: |