d3.js 带有可切换系列的堆叠条
d3.js stacked bar with toggleable series
这次我尝试创建一个带有可切换系列的堆叠条 - 基于 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
}]
在嵌套数据集提供的值中,我可以将一个名为 'enabled' 的键附加到每个对象,并且可以轻松地过滤数据集,但无法弄清楚如何附加一个键来帮助过滤过程。
edit3 删除了问题中无用的信息:
这是一个有效的 fiddle:
https://jsfiddle.net/fgseaxoy/2/
有一些问题需要修复:
首先,JavaScript 通过引用分配对象。意思是在
之后
var fKeys = keys;
fKeys
和keys
都指向同一个数组。这不是你想要的。你想要一些复制的东西,例如:
var fKeys = keys.slice();
那么您的 legendItem
"click" 处理程序是错误的,因为它并没有真正切换所选项目。你想要的是
.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 尺度,请在 redraw
的代码中将 var stackedData
移到更高的位置,这样您就可以使用它来计算 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 中找到完整代码。
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
这次我尝试创建一个带有可切换系列的堆叠条 - 基于 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
}]
在嵌套数据集提供的值中,我可以将一个名为 'enabled' 的键附加到每个对象,并且可以轻松地过滤数据集,但无法弄清楚如何附加一个键来帮助过滤过程。
edit3 删除了问题中无用的信息:
这是一个有效的 fiddle: https://jsfiddle.net/fgseaxoy/2/
有一些问题需要修复:
首先,JavaScript 通过引用分配对象。意思是在
之后var fKeys = keys;
fKeys
和keys
都指向同一个数组。这不是你想要的。你想要一些复制的东西,例如:
var fKeys = keys.slice();
那么您的 legendItem
"click" 处理程序是错误的,因为它并没有真正切换所选项目。你想要的是
.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 尺度,请在 redraw
的代码中将 var stackedData
移到更高的位置,这样您就可以使用它来计算 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 中找到完整代码。
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