d3.js 带有可切换系列的堆叠条
Posted
技术标签:
【中文标题】d3.js 带有可切换系列的堆叠条【英文标题】:d3.js stacked bar with toggleable series 【发布时间】:2017-08-24 03:21:40 【问题描述】:这一次我试图创建一个带有可切换系列的堆叠条 - 基于 Mike Bostock 的示例(再次感谢 Mike!)我已经成功地使其具有响应性和可缩放性,并且通过图例的可切换系列是最后一个剩下的东西。
我创建了图例项,并使用键应用了正确的颜色:
var legendItem = d3.select(".legend")
.selectAll("li")
.data(keys)
.enter()
.append("li")
.on('click', function(d)
keys.forEach(function(c)
if (c != d) tKeys.push(c)
);
fKeys = tKeys;
tKeys = [];
redraw();
);
legendItem
.append("span")
.attr("class", "color-square")
.style("color", function(d)
return colorScale5(d);
);
legendItem
.append("span")
.text(function(d)
return (d)
);
基于结构,为了创建可切换项目,我得出的结论是,我必须能够以某种方式从键和数据集切换它 - 还是有其他方法可以做到这一点?我已经设法从键中删除了一个特定的键,但不是从数据集中,我不知道如何正确映射它。
第二个问题是我想不出切换键的方法,而只是将其移除。这是原始数据集:
var data = [
"country": "Greece",
"Vodafone": 57,
"Wind": 12,
"Cosmote": 20
,
"country": "Italy",
"Vodafone": 40,
"Wind": 24,
"Cosmote": 35
,
"country": "France",
"Vodafone": 22,
"Wind": 9,
"Cosmote": 9
]
在从嵌套数据集提供的值中,我可以为每个对象附加一个名为“启用”的键,并且可以轻松过滤数据集,但无法弄清楚如何附加键以帮助过滤过程。
edit3从问题中删除了无用的信息:
这是一个有效的小提琴: https://jsfiddle.net/fgseaxoy/2/
【问题讨论】:
...如果有人能指出我正确的方向,那就太好了! 您还在寻找答案吗? @blackmiaool,如果有人可以提供更优雅/更清晰的解决方案(无意冒犯@SergGr),那将非常受欢迎 【参考方案1】:有几处需要修复:
首先,javascript 通过引用分配对象。这意味着之后
var fKeys = keys;
fKeys
和 keys
都指向同一个数组。这不是你想要的。你想要复制一些东西,例如:
var fKeys = keys.slice();
那么你的legendItem
“点击”处理程序是错误的,因为它并没有真正切换选定的项目。你想要的是类似的东西
.on('click', function (keyToToggle)
// Go through both keys and fKeys to find out proper
// position to insert keyToToggle if it is to be inserted
var i, j;
for (i = 0, j = 0; i < keys.length; i++)
// If we hit the end of fKeys, keyToToggle
// should be last
if (j >= fKeys.length)
fKeys.push(keyToToggle);
break;
// if we found keyToToggle in fKeys - remove it
if (fKeys[j] == keyToToggle)
// remove it
fKeys.splice(j, 1);
break;
// we found keyToToggle in the original collection
// AND it was not found at fKeys[j]. It means
// it should be inserted to fKeys at position "j"
if (keys[i] == keyToToggle)
// add it
fKeys.splice(j, 0, keyToToggle);
break;
if (keys[i] == fKeys[j])
j++;
redraw();
);
接下来,当您调用data
以获取stackedBars
时,您要提供key
功能。这很重要,因为否则数据将被索引绑定,并且总是会删除最后一条数据。
var stackedData = d3.stack().keys(fKeys)(dataset);
var stackedBars = g
.selectAll(".d3-group")
.data(stackedData , function (__data__, i, group)
return __data__.key;
);
最后,当您更新 '.d3-rect'
时,您想再次调用 data
,因为子节点缓存上次绘制的数据,并且您想用新数据覆盖它
stackedBars.selectAll('.d3-rect')
.data(function (d)
return d; // force override with updated parent's data
)
.attr("x", function (d)
return xz(d.data.country);
)
...
如果没有这样的调用,隐藏第一条数据(“Vodafone”)不会将其他堆叠的数据向下移动。
还有太多的全局变量(即太少的var
s)和一些不必要的变量。
更新(自动缩放 y)
如果您还希望更新您的 Y 比例,请将 var stackedData
移到 redraw
的代码中更高的位置,以便您可以使用它来计算您的 y
,如下所示
var stackedData = d3.stack().keys(fKeys)(dataset);
var autoScaleY = true; // scale Y according to selected data or always use original range
var stackedDataForMax;
if (autoScaleY && stackedData.length > 0)
// only selected data
stackedDataForMax = stackedData;
else
// full range
stackedDataForMax = d3.stack().keys(keys)(dataset);
var maxDataY = 1.2 * d3.max(stackedDataForMax.map(function (d)
return d3.max(d, function (innerD)
return innerD[1];
);
));
y.domain([0, maxDataY]).rangeRound([height, 0]);
你可以在the fork of your original fiddle找到完整的代码。
【讨论】:
您好,抱歉这么久才回复。谢谢你的努力!我不得不说,必须有一种更优雅的方式来做到这一点。您提供了一些很棒的想法——比如复制原始密钥数据集并比较我们是否想要添加新的数据集,但我仍然认为这太费力了。另外,y尺度应该适应变化,所以我猜数据集也需要一些改变。 @scooterlord,我不知道自动缩放 Y 是您的目标之一,但它很容易做到(请参阅答案中的更新和更新的小提琴)。至于“更优雅的方式”,您可能是对的,我只是从您的代码开始并修复了错误,而不是从头开始创建新的东西。 @scooterlord,我不是 D3 专家。我只是相当擅长 JS 和调试。因此,如果某个真正的 D3 专家提出了一个更好的解决方案,我完全可以接受。不过,出于好奇,您认为“理想解决方案”在哪些方面会更好? @scooterlord,该代码的目标是修改fKeys
,以便(未删除)键的顺序与原始keys
中的相同。这个想法是我有两个“同步”索引i
和j
应该指向keys
和fKeys
中的相同值。更具体地说,j
指向fKeys
中的一个位置,现在或应该插入等于keys[i]
的值。停止算法有两个原因:1)我们在keys
中找到了keyToToggle
- 简单的一个。 2) keyToToggle
是 keys
的最后一个元素之一,它(以及它之后的所有元素)都被删除了。
然后j
将在i
到达keyToToggle
位于keys
的位置之前到达fKeys
的末尾,因此尝试执行(fKeys[j] == keyToToggle)
检查将可能不正确。但是在这种情况下,很明显我们 a) 需要将 keyToToggle
添加到 fKeys
并且 b) 需要将其添加到最后一个位置。示例:keys
= [Vodafone, Wind, Cosmote]
、fKeys
= [Vodafone]
和 keyToToggle
= Cosmote
。在循环的第一次迭代keys[i]
=== fKeys[j]
所以j
将被递增,我们已经超出了fKeys
的末尾。 @blackmiaool 有另一种使用位掩码的有趣方法【参考方案2】:
SergGr 的代码运行良好,但有些部分可以更简洁。
点击
var fKeys = keys.slice();
//a helper object to record the state of keys
var fKeyReference = fKeys.map(function ()
return true; //used to indicate if the corresponding key is active
);
function getActiveKeys(reference)
return reference.map(function (state, index)
if (state)
return keys[index]; //just keep keys whoes state is true
return false; //return false to be filered
).filter(function (name)
return name
);
...
.on('click', function (d)
if (fKeys.length === 1 && fKeys[0] === d)
return;
var index = keys.indexOf(d);
fKeyReference[index] = !fKeyReference[index]; // toggle state of fKeyReference
fKeys = getActiveKeys(fKeyReference);
redraw();
);
.stack()
g.selectAll(".d3-group").remove();//remove all groups and draw them all again
stackedBars = g
.selectAll(".d3-group")
.data(d3.stack().keys(fKeys)(dataset));
更新坐标轴 (y.domain
)
y.domain([
0,
1.2 * d3.max(dataset, function (d)
return fKeys.reduce(function (pre, key) //calculate the sum of values of fKeys
return pre + d[key];
, 0);
)
]);
最后,jsfiddle
【讨论】:
您的代码不允许切换多个系列。例如,如果您在“Vodafone”上单击一次,在“Wind”上单击一次,而不是将它们都隐藏起来,第二次单击会切换第一次。 @scooterlord 好的,我会解决的 如果可能的话,请多分析一下你的cmets;例如为什么你必须再次复制键数组? @scooterlord 好的。由于您的第一条评论,这是一个错误。我会删除它。 @scooterlord 现在可以在 IE11 中使用。它不起作用的原因是我在其中使用了es6。关于它的元数据有很多discussions。 SO use es6有很多答案,如果你想让它们在IE11中工作,你可以使用babel以上是关于d3.js 带有可切换系列的堆叠条的主要内容,如果未能解决你的问题,请参考以下文章