【发布时间】:2021-12-28 19:47:55
【问题描述】:
出于两个原因,我将把它写得有点冗长:
- 为了证明我已经努力解决我提出的问题,因为互联网上有太多短暂的东西
- D3 很棒,因为它很复杂,很难完全理解。最近的主要版本强调使用数据连接,旨在简化一般更新模式。
因此,这可能是一篇文章,但我真的很想了解我的问题的答案,所以请多多包涵。
上下文
我想创建一个动态堆叠水平条形图,以可视化single transferable voting 流程的不同阶段(准确地说是在苏格兰的地方选举中)。
D3 堆栈
这非常类似于 Bostock 的 Stacked Bar Chart, Horizontal,显示美国各州的人口按年龄组划分。
毫不奇怪,Bostock 使用 D3's stack 生成器,我很失望地发现它将 SVG 矩形组织成跨 Y 轴的分组(svg g 元素)。以下是基于 Bostock 的Stacked Bar Chart, Horizontal 示例的实验:
const data = [
{month: "Jan", apples: 3840, bananas: 1920, cherries: 960, dates: 400},
{month: "Feb", apples: 1600, bananas: 1440, cherries: 960, dates: 400},
{month: "March", apples: 640, bananas: 960, cherries: 640, dates: 400},
{month: "Apr", apples: 3120, bananas: 1480, cherries: 640, dates: 400}
];
上述数据使用与 Bostock 示例中相同的方法进行旋转,并传递给 StackedBarChart(),我添加了一个转换以导致呈现以下内容:
在上面的示例中,数据绑定不是按月份,而是按水果。
最终状态很好,但动态(过渡)数据更改会很困难。
这是一个有些复杂的领域。我毫不掩饰地承认我不理解 Bostock 对堆栈的使用,取自他上面提到的示例:
// Compute a nested array of series where each series is [[x1, x2], [x1, x2],
// [x1, x2], …] representing the x-extent of each stacked rect. In addition,
// each tuple has an i (index) property so that we can refer back to the
// original data point (data[i]). This code assumes that there is only one
// data point for a given unique y- and z-value.
const series = d3.stack()
.keys(zDomain)
.value(([, I], z) => X[I.get(z)])
.order(order)
.offset(offset)
(d3.rollup(I, ([i]) => i, i => Y[i], i => Z[i]))
.map(s => s.map(d => Object.assign(d, {i: d.data[1].get(s.key)})));
嵌套数据连接
也许有人可以进一步阐明上述内容。也许我不了解堆栈(很可能;)。也许如果图表是垂直的而不是水平的,事情会更容易吗?不知道。
我决定放弃 D3 堆栈,转而使用数据连接嵌套数据,有点回归基础。
重复阅读 Bostock 在 general update pattern 上已有近十年历史的帖子以及阅读有关新的 join method 的文章很有帮助,旨在进一步抽象一般更新模式。-*
以下代码是对documentation nested data join example 的改编(只要确保在某处加载了 d3 库):
const svg = d3.select("body")
.append("svg");
function doD3() {
svg
.selectAll("g")
.data(generate, (d) => d[0].id.split("_")[0])
.join("g")
.attr("transform", (d, i) => "translate(0, " + i * 30 + ")")
.selectAll("text")
.data(d => d)
.join(enter => enter
.append("text").attr("fill", "green"),
update => update,
exit => exit.remove())
.attr("x", (d, i) => i * 50)
.text(d => d.value);
}
function generateRow(rowId, cols) {
let row = [];
for(let i = 0; i < cols; i++) {
row.push({"id": rowId + "_" + i, "value": highRandom()});
}
return row;
}
const lowRandom = d3.randomInt(3, 15);
const highRandom = d3.randomInt(1e2, 1e5);
function generate() {
const cols = lowRandom();
const rows = lowRandom();
let matrix = [];
for(let i = 0; i < rows; i++) {
matrix.push(generateRow(i, cols));
}
return matrix;
}
window.setInterval(doD3, 2000);
可能是原始的,但旨在演示与关键功能的成功数据绑定。 generate 和 generateRow 函数一起生成具有 id 和 value 的对象的随机矩阵。
每两秒调用一次。所有节点(文本)都在输入时呈现。我做错了什么?
感谢您的阅读和新年快乐:))
-* 不幸的是,我无法发布到 JSFiddle 的链接,因为支持的最新 D3 版本是版本 5,而我(可能不必要地)使用的是最新版本 7。Bostock 已经开始使用一个允许实验的平台,称为 Observable 但是我觉得很混乱。
【问题讨论】:
-
您能解释一下您希望过渡如何工作吗?
-
@Dan,感谢您的回复。我希望每个水平条从宽度 = 0 过渡到 x_scale(d.total_value)。此tota_value 是其堆栈值的总和。因此,要为一个水平条设置动画,您必须以类似多米诺骨牌的方式应用它(可能无法使用非线性函数)。我不认为使用堆栈是要走的路。我想成功地将关键函数应用于嵌套数据。我需要在“行”级别绑定数据: .selectAll("g") .data(generate, (d) => d[0].id.split("_")[0]) 但是还有另一个加入。这让我很困惑。
标签: d3.js