交互式图表(dc.js)没有正确地相互更新
Interactive Charts (by dc.js) not updating each other correctly
我正在尝试使用 dc.js 进行交互式数据可视化。
我的数据基本上是两个系列的值,由为每一行指定的 id
属性区分。这是我如何生成它的示例:
var data = [];
var n = 10000.;
for (var i = 0; i < n; i++) {
data.push({id: 0, "i": i, x: Math.random()});
data.push({id: 1, "i": i, x: (Math.random()+i/n)});
}
然后我将这个数据集放入一个交叉过滤器对象中,并创建两个维度和两个组。一个显示每个 id
系列中分箱值的总和,另一个显示每个 id
:
的总 selected 值的总和
var cf = crossfilter(data),
series = cf.dimension(function(d) {return [d.id, d.i];}),
series_grouped = series
.group(function(d){return [d[0], Math.floor(d[1]/100.)*100.];})
.reduceSum(function(d) { return d.x; }),
id = cf.dimension(function(d) {return d.id;}),
id_grouped = id.group().reduceSum(function(d){return d.x;});
我使用以下代码成功创建了我想要的图表。我没能做对的是交互行为:
- 当我select系列图表中的一个范围时,左边的条形图没有更新(但应该是)。
- 当我 select 系列图表中的一个范围,然后 select 一个条形图时,条形图消失了。我无法从该状态恢复(通过重新加载页面以外的方式)。 (我没有尝试在这里触发
cf.filterAll()
,因为我认为这不能解决我的根本问题。)
如何获取左侧的条形图,当我 select 右侧的系列图表中的一个范围时更新?我做错了什么吗?
我使用的是 Firefox 45.0.2,这些是我的库版本:
- dc.js: 2.0.0-beta.26
- crossfilter.js: 1.3.12
- d3.js: 3.5.16
这是完整的文档:
<!DOCTYPE html>
<meta charset="utf-8">
<head>
<script src="crossfilter.js"></script>
<script src="d3.js"></script>
<script src="dc.js"></script>
<link rel="stylesheet" type="text/css" href="dc.css" />
<style>
body { width: 960px; }
.chart.left { width: 25%; }
.chart.right { width: 75%; float: right; }
</style>
</head>
<body>
<div id="charts">
<div id="chart_a" class="chart right"></div>
<div id="chart_b" class="chart left"></div>
</div>
<script>
// generate data
var data = [];
var n = 10000.;
for (var i = 0; i < n; i++) {
data.push({id: 0, "i": i, x: Math.random()});
data.push({id: 1, "i": i, x: (Math.random()+i/n)});
}
// do some crossfilter stuff
var cf = crossfilter(data),
series = cf.dimension(function(d) {return [d.id, d.i];}),
series_grouped = series
.group(function(d){return [d[0], Math.floor(d[1]/100.)*100.];})
.reduceSum(function(d) { return d.x; }),
id = cf.dimension(function(d) {return d.id;}),
id_grouped = id.group().reduceSum(function(d){return d.x;});
// generate charts
var chart_width = 960, chart_height = 200;
dc.seriesChart("#chart_a").height(chart_height).width(.74*chart_width)
.chart(function(c) { return dc.lineChart(c).renderArea(true); })
.x(d3.scale.linear().domain([0,n]))
.dimension(series)
.group(series_grouped)
.seriesAccessor(function(d) {return d.key[0];})
.keyAccessor(function(d) {return d.key[1];})
.valueAccessor(function(d) {return d.value;})
.xAxis();
dc.barChart("#chart_b").height(chart_height).width(.24*chart_width)
.dimension(id)
.group(id_grouped)
.x(d3.scale.ordinal().domain([0,1]))
.xUnits(dc.units.ordinal)
.xAxis();
dc.renderAll();
</script>
</body>
至少您需要覆盖系列图表上的过滤器处理程序。现在,假设您在系列图表中选择 i = 20 到 400。在 Crossfilter 上显示 series.filter([20,400])
。但是你的系列维度值看起来像 [0,150]
,那么计算 20 <= [0,150] && [0,150] <= 400
是什么意思?很难说,而且几乎可以肯定不是你想做的。使用 Crossfilter 的自动类型转换,它可能正在评估 "20" < "0,150" && "0,150" < "400"
。相反,您可能希望它评估 20 <= [0,150][1] && [0,150][1] <= 400
,您可以强制它在自定义过滤器处理程序中执行此操作。
Here 是 "works" 使用自定义过滤器处理程序的版本:
dc.seriesChart("#chart_a").height(chart_height).width(.74 * chart_width)
.chart(function(c) {
return dc.lineChart(c).renderArea(true)
.filterHandler(function(dimension, filter) {
if (filter[0]) {
dimension.filterFunction(function(d) {
return d[1] > filter[0][0] && d[1] < filter[0][1];
});
} else {
dimension.filterAll();
}
setTimeout(dc.redrawAll, 0);
return filter;
});
})
.x(d3.scale.linear().domain([0, n]))
.dimension(series)
.group(series_grouped)
.seriesAccessor(function(d) {
return d.key[0];
})
.keyAccessor(function(d) {
return d.key[1];
})
.valueAccessor(function(d) {
return d.value;
});
但是,正如您可能看到的那样,在 Crossfilter 中使用数组作为维度键是一个非常糟糕的主意(除非您在 Crossfilter2 中使用数组类型,这不是您想要的)。维度必须自然排序,而数组在排序时以令人惊讶的方式起作用,正如我希望上面的解释所展示的那样。
那你是做什么的?我建议将您的数据转换为最佳选择:
var data = [];
var n = 10000.;
for (var i = 0; i < n; i++) {
data.push({"i": i, x0: Math.random(), x1:(Math.random()+i/n)});
}
使用此数据,生成对 x0 和 x1 求和的组,然后使用堆栈混合(包含在标准 dc.lineChart 中)显示每个系列的行。
另一种但更痛苦的方法是自己处理维度键与字符串之间的序列化和反序列化。只是要非常小心地考虑排序,这意味着您可能需要对您的值进行零填充,并且在序列化之前显式舍入浮点值可能是个好主意。在排序方面,您应该按 i
而不是 id
对维度值进行排序,如果这是您要过滤的内容。
我正在尝试使用 dc.js 进行交互式数据可视化。
我的数据基本上是两个系列的值,由为每一行指定的 id
属性区分。这是我如何生成它的示例:
var data = [];
var n = 10000.;
for (var i = 0; i < n; i++) {
data.push({id: 0, "i": i, x: Math.random()});
data.push({id: 1, "i": i, x: (Math.random()+i/n)});
}
然后我将这个数据集放入一个交叉过滤器对象中,并创建两个维度和两个组。一个显示每个 id
系列中分箱值的总和,另一个显示每个 id
:
var cf = crossfilter(data),
series = cf.dimension(function(d) {return [d.id, d.i];}),
series_grouped = series
.group(function(d){return [d[0], Math.floor(d[1]/100.)*100.];})
.reduceSum(function(d) { return d.x; }),
id = cf.dimension(function(d) {return d.id;}),
id_grouped = id.group().reduceSum(function(d){return d.x;});
我使用以下代码成功创建了我想要的图表。我没能做对的是交互行为:
- 当我select系列图表中的一个范围时,左边的条形图没有更新(但应该是)。
- 当我 select 系列图表中的一个范围,然后 select 一个条形图时,条形图消失了。我无法从该状态恢复(通过重新加载页面以外的方式)。 (我没有尝试在这里触发
cf.filterAll()
,因为我认为这不能解决我的根本问题。)
如何获取左侧的条形图,当我 select 右侧的系列图表中的一个范围时更新?我做错了什么吗?
我使用的是 Firefox 45.0.2,这些是我的库版本:
- dc.js: 2.0.0-beta.26
- crossfilter.js: 1.3.12
- d3.js: 3.5.16
这是完整的文档:
<!DOCTYPE html>
<meta charset="utf-8">
<head>
<script src="crossfilter.js"></script>
<script src="d3.js"></script>
<script src="dc.js"></script>
<link rel="stylesheet" type="text/css" href="dc.css" />
<style>
body { width: 960px; }
.chart.left { width: 25%; }
.chart.right { width: 75%; float: right; }
</style>
</head>
<body>
<div id="charts">
<div id="chart_a" class="chart right"></div>
<div id="chart_b" class="chart left"></div>
</div>
<script>
// generate data
var data = [];
var n = 10000.;
for (var i = 0; i < n; i++) {
data.push({id: 0, "i": i, x: Math.random()});
data.push({id: 1, "i": i, x: (Math.random()+i/n)});
}
// do some crossfilter stuff
var cf = crossfilter(data),
series = cf.dimension(function(d) {return [d.id, d.i];}),
series_grouped = series
.group(function(d){return [d[0], Math.floor(d[1]/100.)*100.];})
.reduceSum(function(d) { return d.x; }),
id = cf.dimension(function(d) {return d.id;}),
id_grouped = id.group().reduceSum(function(d){return d.x;});
// generate charts
var chart_width = 960, chart_height = 200;
dc.seriesChart("#chart_a").height(chart_height).width(.74*chart_width)
.chart(function(c) { return dc.lineChart(c).renderArea(true); })
.x(d3.scale.linear().domain([0,n]))
.dimension(series)
.group(series_grouped)
.seriesAccessor(function(d) {return d.key[0];})
.keyAccessor(function(d) {return d.key[1];})
.valueAccessor(function(d) {return d.value;})
.xAxis();
dc.barChart("#chart_b").height(chart_height).width(.24*chart_width)
.dimension(id)
.group(id_grouped)
.x(d3.scale.ordinal().domain([0,1]))
.xUnits(dc.units.ordinal)
.xAxis();
dc.renderAll();
</script>
</body>
至少您需要覆盖系列图表上的过滤器处理程序。现在,假设您在系列图表中选择 i = 20 到 400。在 Crossfilter 上显示 series.filter([20,400])
。但是你的系列维度值看起来像 [0,150]
,那么计算 20 <= [0,150] && [0,150] <= 400
是什么意思?很难说,而且几乎可以肯定不是你想做的。使用 Crossfilter 的自动类型转换,它可能正在评估 "20" < "0,150" && "0,150" < "400"
。相反,您可能希望它评估 20 <= [0,150][1] && [0,150][1] <= 400
,您可以强制它在自定义过滤器处理程序中执行此操作。
Here 是 "works" 使用自定义过滤器处理程序的版本:
dc.seriesChart("#chart_a").height(chart_height).width(.74 * chart_width)
.chart(function(c) {
return dc.lineChart(c).renderArea(true)
.filterHandler(function(dimension, filter) {
if (filter[0]) {
dimension.filterFunction(function(d) {
return d[1] > filter[0][0] && d[1] < filter[0][1];
});
} else {
dimension.filterAll();
}
setTimeout(dc.redrawAll, 0);
return filter;
});
})
.x(d3.scale.linear().domain([0, n]))
.dimension(series)
.group(series_grouped)
.seriesAccessor(function(d) {
return d.key[0];
})
.keyAccessor(function(d) {
return d.key[1];
})
.valueAccessor(function(d) {
return d.value;
});
但是,正如您可能看到的那样,在 Crossfilter 中使用数组作为维度键是一个非常糟糕的主意(除非您在 Crossfilter2 中使用数组类型,这不是您想要的)。维度必须自然排序,而数组在排序时以令人惊讶的方式起作用,正如我希望上面的解释所展示的那样。
那你是做什么的?我建议将您的数据转换为最佳选择:
var data = [];
var n = 10000.;
for (var i = 0; i < n; i++) {
data.push({"i": i, x0: Math.random(), x1:(Math.random()+i/n)});
}
使用此数据,生成对 x0 和 x1 求和的组,然后使用堆栈混合(包含在标准 dc.lineChart 中)显示每个系列的行。
另一种但更痛苦的方法是自己处理维度键与字符串之间的序列化和反序列化。只是要非常小心地考虑排序,这意味着您可能需要对您的值进行零填充,并且在序列化之前显式舍入浮点值可能是个好主意。在排序方面,您应该按 i
而不是 id
对维度值进行排序,如果这是您要过滤的内容。