如何定义 dc.js 中缺失值的处理?
How to define handling of missing values in dc.js?
我是 dc.js 的新手,我设法创建了一些条形图和一个线系列图。
如果我点击我的条形图来过滤我的年龄值,
不存在的值似乎被视为零值而不是 NaN 或 null。
如果我 select 第一个和最后一个值,但不是中间值,我会
期望有两个点和一条连接它们的直线。
=>如何告诉 dc.js 不显示 未通过过滤器的值?
我试图从
调整我的值访问器
.valueAccessor(d => d.value)
到
.valueAccessor(d => d.value>0?d.value:null)
或
.valueAccessor(d => d.value>0?d.value:NaN)
但这并没有帮助。缺失值仍然回落到零。
我想我需要以某种方式为我的折线图应用 d3.js defined
函数,但不知道如何:How do I get d3 to treat missing values as null instead of zero?
示例代码(请在 运行 代码段后使用 "Full page" 选项。否则您可能只会看到一些警告:
<html>
<head>
<title>dc demo</title>
<meta http-equiv='content-type' content='text/html; charset=UTF8'>
<!-- this demo is based on following tuturials:
https://www.tutorialspoint.com/dcjs/dcjs_introduction_to_d3js.htm
https://www.codeproject.com/articles/693841/making-dashboards-with-dc-js-part-1-using-crossfil
-->
<script src='https://d3js.org/d3.v4.min.js'></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/crossfilter2/1.5.2/crossfilter.min.js'></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/dc/3.1.8/dc.min.js'></script>
</head>
<body>
<div style="font-family:arial;">
<div style="float:left;padding:20px;">
<div >
<label>Number of colors:</label>
<div id='color-chart-count'></div>
</div>
<div>
<label>Value sum for colors:</label>
<div id='color-chart-value-sum'></div>
</div>
</div>
<div style="float:center;padding:20px;">
<div>
<label>Number of ages:</label>
<div id='age-chart-count'></div>
</div>
<div>
<label>Value sum for ages:</label>
<div id='age-chart-value-sum'></div>
</div>
</div>
</div>
<div style="font-family:arial;">
<label for='value-chart'>Values:</label>
<div id='value-chart'></div>
</div>
<script>
let data = [
{color: 'red', age: 1, value: 10},
{color: 'red', age: 2, value: 11},
{color: 'red', age: 3, value: 12},
{color: 'green', age: 1, value: 20},
{color: 'green', age: 2, value: 21},
{color: 'green', age: 3, value: 22},
{color: 'blue', age: 1, value: 30},
{color: 'blue', age: 2, value: 31},
{color: 'blue', age: 3, value: 32},
];
//create instance of cross filter
let cf = crossfilter(data);
//define dimensions and groups
let colorDim = cf.dimension(d=> d.color);
let colorGroupCount = colorDim.group().reduceCount();
let colorGroupValueSum = colorDim.group().reduceSum(d => d.value);
let ageDim = cf.dimension(d=> d.age);
let ageGroupCount = ageDim.group().reduceCount();
let ageGroupValueSum = ageDim.group().reduceSum(d => d.value);
let colorAgeDim = cf.dimension(d => [d.color, d.age]);
let colorAgeGroup = colorAgeDim.group().reduceSum( d => d.value);
let ordinalColors = ['red','green','blue'];
let ordinalAgeColors = ['lightgray','grey','#666666'];
//color charts
let rgbColorScale = d3.scaleOrdinal().domain(ordinalColors).range(ordinalColors);
let colorChartCount = barChart('#color-chart-count')
.xAxisLabel('Color')
.x(d3.scaleBand().domain(ordinalColors))
.dimension(colorDim)
.yAxisLabel('Count')
.group(colorGroupCount)
.defineColors(rgbColorScale);
colorChartCount.yAxis().ticks(4);
barChart('#color-chart-value-sum')
.xAxisLabel('Color')
.x(d3.scaleBand().domain(ordinalColors))
.dimension(colorDim)
.yAxisLabel('Value sum')
.group(colorGroupValueSum)
.defineColors(rgbColorScale);
//age charts
let ageColorScale = d3.scaleOrdinal().domain([1,2,3]).range(ordinalAgeColors);
/*
dc.pieChart('#age-chart-count')
.width(100)
.height(100)
.ordinalColors(ordinalAgeColors)
.dimension(ageDim)
.group(ageGroupCount);
*/
let ageChartCount = barChart('#age-chart-count')
.xAxisLabel('Age')
.x(d3.scaleLinear().domain([1,2,3]))
.dimension(ageDim)
.yAxisLabel('Count')
.group(ageGroupCount)
.defineColors(ageColorScale);
ageChartCount.yAxis().ticks(4);
barChart('#age-chart-value-sum')
.xAxisLabel('Age')
.x(d3.scaleLinear().domain([1,2,3]))
.dimension(colorDim)
.yAxisLabel('Value sum')
.group(ageGroupValueSum)
.defineColors(ageColorScale);
function barChart(elementSelector){
let barChart = dc.barChart(elementSelector)
.width(200)
.height(200)
.xUnits(dc.units.ordinal)
.margins({top:10,left:30,right:15,bottom:35})
.barPadding(0.1)
.outerPadding(0.1);
barChart.defineColors = function(colorScale){
this.renderlet(chart=>{
chart.selectAll('rect.bar')
.each(function(d){
d3.select(this).attr('style', 'fill: ' + colorScale(d.x));
});
});
return this;
}
return barChart;
}
//value chart
let chart = dc.seriesChart('#value-chart');
chart
.width(500)
.height(500)
.chart( c =>
dc.lineChart(c)
.renderDataPoints(true)
)
.xAxisLabel('Age')
.x(d3.scaleLinear().domain([1,3]))
.dimension(colorAgeDim)
.yAxisLabel('Value')
.elasticY(true)
.group(colorAgeGroup)
.brushOn(false)
.clipPadding(10)
.mouseZoomable(true)
.seriesAccessor(d => d.key[0])
.keyAccessor(d => d.key[1])
.valueAccessor(d => d.value)
.ordinalColors(['blue','green','red','yellow'])
.legend(dc.legend().x(430).y(350));
dc.renderAll();
//dimensions can also be used for filtering:
//let color_red = colorDim.filter('red');
//let filterResult = JSON.stringify(color_red.top(Infinity)).replace('[','[\n\t').replace(/}\,/g,'},\n\t').replace(']','\n]');
//console.log(filterResult);
//let functionFilter = ageDim.filter(age => age === 2);
//let functionFilterResult = JSON.stringify(functionFilter.top(Infinity)).replace('[','[\n\t').replace(/}\,/g,'},\n\t').replace(']','\n]');
//console.log(functionFilterResult);
</script>
</body>
</html>
如果只有一个年龄 selected 显示水平轴上不需要的黑色区域和线条和点的线系列图表:
Gordon 的评论为我指明了正确的方向。下面是两个修改后的版本。
我包含了 dc.css
样式表来解决黑色区域问题。
而不是 group().reduceSum()
我使用自定义减少操作将缺失值视为 null 而不是 0。现在我的值访问器正确地 returns null 缺失值。
function reduceAdd(previous, current) {
if(current){
if(current.value !== null){
if(previous.sum === null){
previous.sum = current.value;
previous.count = 1;
} else {
previous.sum += current.value;
previous.count += 1;
}
}
}
return previous;
}
function reduceRemove(previous, current) {
if(current){
if(current.value !== null){
if(previous.sum !== null){
previous.sum -= current.value;
previous.count -= 1;
if(previous.count === 0){
previous.sum = null;
}
}
}
}
return previous;
}
function reduceInit() {
return {
sum: null,
count: 0
};
}
let colorAgeGroup = colorAgeDim.group().reduce(reduceAdd, reduceRemove, reduceInit);
A. 第一个示例应用defined
方法来过滤折线图中的空值。缺失值显示为间隙:
<html>
<head>
<title>dc demo</title>
<meta http-equiv='content-type' content='text/html; charset=UTF8'>
<!-- this demo is based on following tuturials:
https://www.tutorialspoint.com/dcjs/dcjs_introduction_to_d3js.htm
https://www.codeproject.com/articles/693841/making-dashboards-with-dc-js-part-1-using-crossfil
-->
<script src='https://d3js.org/d3.v4.min.js'></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/crossfilter2/1.5.2/crossfilter.min.js'></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/dc/3.1.8/dc.min.js'></script>
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/dc/3.1.8/dc.css" />
</head>
<body>
<div style="font-family:arial;">
<div style="float:left;padding:20px;">
<div >
<label>Number of colors:</label>
<div id='color-chart-count'></div>
</div>
<div>
<label>Value sum for colors:</label>
<div id='color-chart-value-sum'></div>
</div>
</div>
<div style="float:center;padding:20px;">
<div>
<label>Number of ages:</label>
<div id='age-chart-count'></div>
</div>
<div>
<label>Value sum for ages:</label>
<div id='age-chart-value-sum'></div>
</div>
</div>
</div>
<div style="font-family:arial;">
<label for='value-chart'>Values:</label>
<div id='value-chart'></div>
</div>
<script>
let data = [
{color: 'red', age: 1, value: 10},
{color: 'red', age: 2, value: 11},
{color: 'red', age: 3, value: 12},
{color: 'green', age: 1, value: 20},
{color: 'green', age: 2, value: 21},
{color: 'green', age: 3, value: 22},
{color: 'blue', age: 1, value: 30},
{color: 'blue', age: 2, value: 31},
{color: 'blue', age: 3, value: 32},
];
//create instance of cross filter
let cf = crossfilter(data);
//define dimensions and groups
let colorDim = cf.dimension(d=> d.color);
let colorGroupCount = colorDim.group().reduceCount();
let colorGroupValueSum = colorDim.group().reduceSum(d => d.value);
let ageDim = cf.dimension(d=> d.age);
let ageGroupCount = ageDim.group().reduceCount();
let ageGroupValueSum = ageDim.group().reduceSum(d => d.value);
let colorAgeDim = cf.dimension(d => [d.color, d.age]);
function reduceAdd(previous, current) {
if(current){
if(current.value !== null){
if(previous.sum === null){
previous.sum = current.value;
previous.count = 1;
} else {
previous.sum += current.value;
previous.count += 1;
}
}
}
return previous;
}
function reduceRemove(previous, current) {
if(current){
if(current.value !== null){
if(previous.sum !== null){
previous.sum -= current.value;
previous.count -= 1;
if(previous.count === 0){
previous.sum = null;
}
}
}
}
return previous;
}
function reduceInit() {
return {
sum: null,
count: 0
};
}
let colorAgeGroup = colorAgeDim.group().reduce(reduceAdd, reduceRemove, reduceInit);
let ordinalColors = ['red','green','blue'];
let ordinalAgeColors = ['lightgray','grey','#666666'];
//color charts
let rgbColorScale = d3.scaleOrdinal().domain(ordinalColors).range(ordinalColors);
let colorChartCount = barChart('#color-chart-count')
.xAxisLabel('Color')
.x(d3.scaleBand().domain(ordinalColors))
.dimension(colorDim)
.yAxisLabel('Count')
.group(colorGroupCount)
.defineColors(rgbColorScale);
colorChartCount.yAxis().ticks(4);
barChart('#color-chart-value-sum')
.xAxisLabel('Color')
.x(d3.scaleBand().domain(ordinalColors))
.dimension(colorDim)
.yAxisLabel('Value sum')
.group(colorGroupValueSum)
.defineColors(rgbColorScale);
//age charts
let ageColorScale = d3.scaleOrdinal().domain([1,2,3]).range(ordinalAgeColors);
/*
dc.pieChart('#age-chart-count')
.width(100)
.height(100)
.ordinalColors(ordinalAgeColors)
.dimension(ageDim)
.group(ageGroupCount);
*/
let ageChartCount = barChart('#age-chart-count')
.xAxisLabel('Age')
.x(d3.scaleLinear().domain([1,2,3]))
.dimension(ageDim)
.yAxisLabel('Count')
.group(ageGroupCount)
.defineColors(ageColorScale);
ageChartCount.yAxis().ticks(4);
barChart('#age-chart-value-sum')
.xAxisLabel('Age')
.x(d3.scaleLinear().domain([1,2,3]))
.dimension(colorDim)
.yAxisLabel('Value sum')
.group(ageGroupValueSum)
.defineColors(ageColorScale);
function barChart(elementSelector){
let barChart = dc.barChart(elementSelector)
.width(200)
.height(200)
.xUnits(dc.units.ordinal)
.margins({top:10,left:30,right:15,bottom:35})
.barPadding(0.1)
.outerPadding(0.1)
.transitionDuration(500);
barChart.defineColors = function(colorScale){
this.renderlet(chart=>{
chart.selectAll('rect.bar')
.each(function(d){
let isSelected = this.classList.contains('selected');
if(isSelected){
d3.select(this).attr('style', 'fill: ' + colorScale(d.x) + ';stroke-width:2;stroke:#39ff14');
} else {
d3.select(this).attr('style', 'fill: ' + colorScale(d.x));
}
});
});
return this;
}
return barChart;
}
//value chart
let chart = dc.seriesChart('#value-chart');
chart
.width(500)
.height(500)
.chart( c =>
dc.lineChart(c)
.renderDataPoints(true)
.defined(d=>{
if(d.y !== null){
return d.y;
};
})
)
.xAxisLabel('Age')
.x(d3.scaleLinear().domain([1,3]))
.dimension(colorAgeDim)
.yAxisLabel('Value')
.elasticY(true)
.group(colorAgeGroup)
.brushOn(false)
.clipPadding(10)
.mouseZoomable(true)
.seriesAccessor(d => d.key[0])
.keyAccessor(d => d.key[1])
.valueAccessor(d => {
return d.value.sum;
})
.ordinalColors(['blue','green','red','yellow'])
.legend(dc.legend().x(430).y(350));
dc.renderAll();
//dimensions can also be used for filtering:
//let color_red = colorDim.filter('red');
//let filterResult = JSON.stringify(color_red.top(Infinity)).replace('[','[\n\t').replace(/}\,/g,'},\n\t').replace(']','\n]');
//console.log(filterResult);
//let functionFilter = ageDim.filter(age => age === 2);
//let functionFilterResult = JSON.stringify(functionFilter.top(Infinity)).replace('[','[\n\t').replace(/}\,/g,'},\n\t').replace(']','\n]');
//console.log(functionFilterResult);
</script>
</body>
</html>
B. 第二个示例使用过滤器从组中删除空值。跳过缺失值并连接相邻点。
<html>
<head>
<title>dc demo</title>
<meta http-equiv='content-type' content='text/html; charset=UTF8'>
<!-- this demo is based on following tuturials:
https://www.tutorialspoint.com/dcjs/dcjs_introduction_to_d3js.htm
https://www.codeproject.com/articles/693841/making-dashboards-with-dc-js-part-1-using-crossfil
-->
<script src='https://d3js.org/d3.v4.min.js'></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/crossfilter2/1.5.2/crossfilter.min.js'></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/dc/3.1.8/dc.min.js'></script>
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/dc/3.1.8/dc.css" />
</head>
<body>
<div style="font-family:arial;">
<div style="float:left;padding:20px;">
<div >
<label>Number of colors:</label>
<div id='color-chart-count'></div>
</div>
<div>
<label>Value sum for colors:</label>
<div id='color-chart-value-sum'></div>
</div>
</div>
<div style="float:center;padding:20px;">
<div>
<label>Number of ages:</label>
<div id='age-chart-count'></div>
</div>
<div>
<label>Value sum for ages:</label>
<div id='age-chart-value-sum'></div>
</div>
</div>
</div>
<div style="font-family:arial;">
<label for='value-chart'>Values:</label>
<div id='value-chart'></div>
</div>
<script>
let data = [
{color: 'red', age: 1, value: 10},
{color: 'red', age: 2, value: 11},
{color: 'red', age: 3, value: 12},
{color: 'green', age: 1, value: 20},
{color: 'green', age: 2, value: 21},
{color: 'green', age: 3, value: 22},
{color: 'blue', age: 1, value: 30},
{color: 'blue', age: 2, value: 31},
{color: 'blue', age: 3, value: 32},
];
//create instance of cross filter
let cf = crossfilter(data);
//define dimensions and groups
let colorDim = cf.dimension(d=> d.color);
let colorGroupCount = colorDim.group().reduceCount();
let colorGroupValueSum = colorDim.group().reduceSum(d => d.value);
let ageDim = cf.dimension(d=> d.age);
let ageGroupCount = ageDim.group().reduceCount();
let ageGroupValueSum = ageDim.group().reduceSum(d => d.value);
let colorAgeDim = cf.dimension(d => [d.color, d.age]);
function reduceAdd(previous, current) {
if(current){
if(current.value !== null){
if(previous.sum === null){
previous.sum = current.value;
previous.count = 1;
} else {
previous.sum += current.value;
previous.count += 1;
}
}
}
return previous;
}
function reduceRemove(previous, current) {
if(current){
if(current.value !== null){
if(previous.sum !== null){
previous.sum -= current.value;
previous.count -= 1;
if(previous.count === 0){
previous.sum = null;
}
}
}
}
return previous;
}
function reduceInit() {
return {
sum: null,
count: 0
};
}
let colorAgeGroup = colorAgeDim.group().reduce(reduceAdd, reduceRemove, reduceInit);
let filteredColorAgeGroup = removeMissingEntries(colorAgeGroup);
function removeMissingEntries(sourceGroup) {
return {
all:function () {
return sourceGroup.all().filter(function(d) {
return d.value.sum !== null;
});
}
};
}
let ordinalColors = ['red','green','blue'];
let ordinalAgeColors = ['lightgray','grey','#666666'];
//color charts
let rgbColorScale = d3.scaleOrdinal().domain(ordinalColors).range(ordinalColors);
let colorChartCount = barChart('#color-chart-count')
.xAxisLabel('Color')
.x(d3.scaleBand().domain(ordinalColors))
.dimension(colorDim)
.yAxisLabel('Count')
.group(colorGroupCount)
.defineColors(rgbColorScale);
colorChartCount.yAxis().ticks(4);
barChart('#color-chart-value-sum')
.xAxisLabel('Color')
.x(d3.scaleBand().domain(ordinalColors))
.dimension(colorDim)
.yAxisLabel('Value sum')
.group(colorGroupValueSum)
.defineColors(rgbColorScale);
//age charts
let ageColorScale = d3.scaleOrdinal().domain([1,2,3]).range(ordinalAgeColors);
/*
dc.pieChart('#age-chart-count')
.width(100)
.height(100)
.ordinalColors(ordinalAgeColors)
.dimension(ageDim)
.group(ageGroupCount);
*/
let ageChartCount = barChart('#age-chart-count')
.xAxisLabel('Age')
.x(d3.scaleLinear().domain([1,2,3]))
.dimension(ageDim)
.yAxisLabel('Count')
.group(ageGroupCount)
.defineColors(ageColorScale);
ageChartCount.yAxis().ticks(4);
barChart('#age-chart-value-sum')
.xAxisLabel('Age')
.x(d3.scaleLinear().domain([1,2,3]))
.dimension(colorDim)
.yAxisLabel('Value sum')
.group(ageGroupValueSum)
.defineColors(ageColorScale);
function barChart(elementSelector){
let barChart = dc.barChart(elementSelector)
.width(200)
.height(200)
.xUnits(dc.units.ordinal)
.margins({top:10,left:30,right:15,bottom:35})
.barPadding(0.1)
.outerPadding(0.1)
.transitionDuration(500);
barChart.defineColors = function(colorScale){
this.renderlet(chart=>{
chart.selectAll('rect.bar')
.each(function(d){
let isSelected = this.classList.contains('selected');
if(isSelected){
d3.select(this).attr('style', 'fill: ' + colorScale(d.x) + ';stroke-width:2;stroke:#39ff14');
} else {
d3.select(this).attr('style', 'fill: ' + colorScale(d.x));
}
});
});
return this;
}
return barChart;
}
//value chart
let chart = dc.seriesChart('#value-chart');
chart
.width(500)
.height(500)
.chart( c =>
dc.lineChart(c)
.renderDataPoints(true)
)
.xAxisLabel('Age')
.x(d3.scaleLinear().domain([1,3]))
.dimension(colorAgeDim)
.yAxisLabel('Value')
.elasticY(true)
.group(filteredColorAgeGroup)
.brushOn(false)
.clipPadding(10)
.mouseZoomable(true)
.seriesAccessor(d => d.key[0])
.keyAccessor(d => d.key[1])
.valueAccessor(d => {
return d.value.sum;
})
.ordinalColors(['blue','green','red','yellow'])
.legend(dc.legend().x(430).y(350));
dc.renderAll();
//dimensions can also be used for filtering:
//let color_red = colorDim.filter('red');
//let filterResult = JSON.stringify(color_red.top(Infinity)).replace('[','[\n\t').replace(/}\,/g,'},\n\t').replace(']','\n]');
//console.log(filterResult);
//let functionFilter = ageDim.filter(age => age === 2);
//let functionFilterResult = JSON.stringify(functionFilter.top(Infinity)).replace('[','[\n\t').replace(/}\,/g,'},\n\t').replace(']','\n]');
//console.log(functionFilterResult);
</script>
</body>
</html>
我是 dc.js 的新手,我设法创建了一些条形图和一个线系列图。
如果我点击我的条形图来过滤我的年龄值,
不存在的值似乎被视为零值而不是 NaN 或 null。 如果我 select 第一个和最后一个值,但不是中间值,我会 期望有两个点和一条连接它们的直线。
=>如何告诉 dc.js 不显示 未通过过滤器的值?
我试图从
调整我的值访问器.valueAccessor(d => d.value)
到
.valueAccessor(d => d.value>0?d.value:null)
或
.valueAccessor(d => d.value>0?d.value:NaN)
但这并没有帮助。缺失值仍然回落到零。
我想我需要以某种方式为我的折线图应用 d3.js defined
函数,但不知道如何:How do I get d3 to treat missing values as null instead of zero?
示例代码(请在 运行 代码段后使用 "Full page" 选项。否则您可能只会看到一些警告:
<html>
<head>
<title>dc demo</title>
<meta http-equiv='content-type' content='text/html; charset=UTF8'>
<!-- this demo is based on following tuturials:
https://www.tutorialspoint.com/dcjs/dcjs_introduction_to_d3js.htm
https://www.codeproject.com/articles/693841/making-dashboards-with-dc-js-part-1-using-crossfil
-->
<script src='https://d3js.org/d3.v4.min.js'></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/crossfilter2/1.5.2/crossfilter.min.js'></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/dc/3.1.8/dc.min.js'></script>
</head>
<body>
<div style="font-family:arial;">
<div style="float:left;padding:20px;">
<div >
<label>Number of colors:</label>
<div id='color-chart-count'></div>
</div>
<div>
<label>Value sum for colors:</label>
<div id='color-chart-value-sum'></div>
</div>
</div>
<div style="float:center;padding:20px;">
<div>
<label>Number of ages:</label>
<div id='age-chart-count'></div>
</div>
<div>
<label>Value sum for ages:</label>
<div id='age-chart-value-sum'></div>
</div>
</div>
</div>
<div style="font-family:arial;">
<label for='value-chart'>Values:</label>
<div id='value-chart'></div>
</div>
<script>
let data = [
{color: 'red', age: 1, value: 10},
{color: 'red', age: 2, value: 11},
{color: 'red', age: 3, value: 12},
{color: 'green', age: 1, value: 20},
{color: 'green', age: 2, value: 21},
{color: 'green', age: 3, value: 22},
{color: 'blue', age: 1, value: 30},
{color: 'blue', age: 2, value: 31},
{color: 'blue', age: 3, value: 32},
];
//create instance of cross filter
let cf = crossfilter(data);
//define dimensions and groups
let colorDim = cf.dimension(d=> d.color);
let colorGroupCount = colorDim.group().reduceCount();
let colorGroupValueSum = colorDim.group().reduceSum(d => d.value);
let ageDim = cf.dimension(d=> d.age);
let ageGroupCount = ageDim.group().reduceCount();
let ageGroupValueSum = ageDim.group().reduceSum(d => d.value);
let colorAgeDim = cf.dimension(d => [d.color, d.age]);
let colorAgeGroup = colorAgeDim.group().reduceSum( d => d.value);
let ordinalColors = ['red','green','blue'];
let ordinalAgeColors = ['lightgray','grey','#666666'];
//color charts
let rgbColorScale = d3.scaleOrdinal().domain(ordinalColors).range(ordinalColors);
let colorChartCount = barChart('#color-chart-count')
.xAxisLabel('Color')
.x(d3.scaleBand().domain(ordinalColors))
.dimension(colorDim)
.yAxisLabel('Count')
.group(colorGroupCount)
.defineColors(rgbColorScale);
colorChartCount.yAxis().ticks(4);
barChart('#color-chart-value-sum')
.xAxisLabel('Color')
.x(d3.scaleBand().domain(ordinalColors))
.dimension(colorDim)
.yAxisLabel('Value sum')
.group(colorGroupValueSum)
.defineColors(rgbColorScale);
//age charts
let ageColorScale = d3.scaleOrdinal().domain([1,2,3]).range(ordinalAgeColors);
/*
dc.pieChart('#age-chart-count')
.width(100)
.height(100)
.ordinalColors(ordinalAgeColors)
.dimension(ageDim)
.group(ageGroupCount);
*/
let ageChartCount = barChart('#age-chart-count')
.xAxisLabel('Age')
.x(d3.scaleLinear().domain([1,2,3]))
.dimension(ageDim)
.yAxisLabel('Count')
.group(ageGroupCount)
.defineColors(ageColorScale);
ageChartCount.yAxis().ticks(4);
barChart('#age-chart-value-sum')
.xAxisLabel('Age')
.x(d3.scaleLinear().domain([1,2,3]))
.dimension(colorDim)
.yAxisLabel('Value sum')
.group(ageGroupValueSum)
.defineColors(ageColorScale);
function barChart(elementSelector){
let barChart = dc.barChart(elementSelector)
.width(200)
.height(200)
.xUnits(dc.units.ordinal)
.margins({top:10,left:30,right:15,bottom:35})
.barPadding(0.1)
.outerPadding(0.1);
barChart.defineColors = function(colorScale){
this.renderlet(chart=>{
chart.selectAll('rect.bar')
.each(function(d){
d3.select(this).attr('style', 'fill: ' + colorScale(d.x));
});
});
return this;
}
return barChart;
}
//value chart
let chart = dc.seriesChart('#value-chart');
chart
.width(500)
.height(500)
.chart( c =>
dc.lineChart(c)
.renderDataPoints(true)
)
.xAxisLabel('Age')
.x(d3.scaleLinear().domain([1,3]))
.dimension(colorAgeDim)
.yAxisLabel('Value')
.elasticY(true)
.group(colorAgeGroup)
.brushOn(false)
.clipPadding(10)
.mouseZoomable(true)
.seriesAccessor(d => d.key[0])
.keyAccessor(d => d.key[1])
.valueAccessor(d => d.value)
.ordinalColors(['blue','green','red','yellow'])
.legend(dc.legend().x(430).y(350));
dc.renderAll();
//dimensions can also be used for filtering:
//let color_red = colorDim.filter('red');
//let filterResult = JSON.stringify(color_red.top(Infinity)).replace('[','[\n\t').replace(/}\,/g,'},\n\t').replace(']','\n]');
//console.log(filterResult);
//let functionFilter = ageDim.filter(age => age === 2);
//let functionFilterResult = JSON.stringify(functionFilter.top(Infinity)).replace('[','[\n\t').replace(/}\,/g,'},\n\t').replace(']','\n]');
//console.log(functionFilterResult);
</script>
</body>
</html>
如果只有一个年龄 selected 显示水平轴上不需要的黑色区域和线条和点的线系列图表:
Gordon 的评论为我指明了正确的方向。下面是两个修改后的版本。
我包含了
dc.css
样式表来解决黑色区域问题。而不是
group().reduceSum()
我使用自定义减少操作将缺失值视为 null 而不是 0。现在我的值访问器正确地 returns null 缺失值。function reduceAdd(previous, current) { if(current){ if(current.value !== null){ if(previous.sum === null){ previous.sum = current.value; previous.count = 1; } else { previous.sum += current.value; previous.count += 1; } } } return previous; } function reduceRemove(previous, current) { if(current){ if(current.value !== null){ if(previous.sum !== null){ previous.sum -= current.value; previous.count -= 1; if(previous.count === 0){ previous.sum = null; } } } } return previous; } function reduceInit() { return { sum: null, count: 0 }; } let colorAgeGroup = colorAgeDim.group().reduce(reduceAdd, reduceRemove, reduceInit);
A. 第一个示例应用defined
方法来过滤折线图中的空值。缺失值显示为间隙:
<html>
<head>
<title>dc demo</title>
<meta http-equiv='content-type' content='text/html; charset=UTF8'>
<!-- this demo is based on following tuturials:
https://www.tutorialspoint.com/dcjs/dcjs_introduction_to_d3js.htm
https://www.codeproject.com/articles/693841/making-dashboards-with-dc-js-part-1-using-crossfil
-->
<script src='https://d3js.org/d3.v4.min.js'></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/crossfilter2/1.5.2/crossfilter.min.js'></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/dc/3.1.8/dc.min.js'></script>
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/dc/3.1.8/dc.css" />
</head>
<body>
<div style="font-family:arial;">
<div style="float:left;padding:20px;">
<div >
<label>Number of colors:</label>
<div id='color-chart-count'></div>
</div>
<div>
<label>Value sum for colors:</label>
<div id='color-chart-value-sum'></div>
</div>
</div>
<div style="float:center;padding:20px;">
<div>
<label>Number of ages:</label>
<div id='age-chart-count'></div>
</div>
<div>
<label>Value sum for ages:</label>
<div id='age-chart-value-sum'></div>
</div>
</div>
</div>
<div style="font-family:arial;">
<label for='value-chart'>Values:</label>
<div id='value-chart'></div>
</div>
<script>
let data = [
{color: 'red', age: 1, value: 10},
{color: 'red', age: 2, value: 11},
{color: 'red', age: 3, value: 12},
{color: 'green', age: 1, value: 20},
{color: 'green', age: 2, value: 21},
{color: 'green', age: 3, value: 22},
{color: 'blue', age: 1, value: 30},
{color: 'blue', age: 2, value: 31},
{color: 'blue', age: 3, value: 32},
];
//create instance of cross filter
let cf = crossfilter(data);
//define dimensions and groups
let colorDim = cf.dimension(d=> d.color);
let colorGroupCount = colorDim.group().reduceCount();
let colorGroupValueSum = colorDim.group().reduceSum(d => d.value);
let ageDim = cf.dimension(d=> d.age);
let ageGroupCount = ageDim.group().reduceCount();
let ageGroupValueSum = ageDim.group().reduceSum(d => d.value);
let colorAgeDim = cf.dimension(d => [d.color, d.age]);
function reduceAdd(previous, current) {
if(current){
if(current.value !== null){
if(previous.sum === null){
previous.sum = current.value;
previous.count = 1;
} else {
previous.sum += current.value;
previous.count += 1;
}
}
}
return previous;
}
function reduceRemove(previous, current) {
if(current){
if(current.value !== null){
if(previous.sum !== null){
previous.sum -= current.value;
previous.count -= 1;
if(previous.count === 0){
previous.sum = null;
}
}
}
}
return previous;
}
function reduceInit() {
return {
sum: null,
count: 0
};
}
let colorAgeGroup = colorAgeDim.group().reduce(reduceAdd, reduceRemove, reduceInit);
let ordinalColors = ['red','green','blue'];
let ordinalAgeColors = ['lightgray','grey','#666666'];
//color charts
let rgbColorScale = d3.scaleOrdinal().domain(ordinalColors).range(ordinalColors);
let colorChartCount = barChart('#color-chart-count')
.xAxisLabel('Color')
.x(d3.scaleBand().domain(ordinalColors))
.dimension(colorDim)
.yAxisLabel('Count')
.group(colorGroupCount)
.defineColors(rgbColorScale);
colorChartCount.yAxis().ticks(4);
barChart('#color-chart-value-sum')
.xAxisLabel('Color')
.x(d3.scaleBand().domain(ordinalColors))
.dimension(colorDim)
.yAxisLabel('Value sum')
.group(colorGroupValueSum)
.defineColors(rgbColorScale);
//age charts
let ageColorScale = d3.scaleOrdinal().domain([1,2,3]).range(ordinalAgeColors);
/*
dc.pieChart('#age-chart-count')
.width(100)
.height(100)
.ordinalColors(ordinalAgeColors)
.dimension(ageDim)
.group(ageGroupCount);
*/
let ageChartCount = barChart('#age-chart-count')
.xAxisLabel('Age')
.x(d3.scaleLinear().domain([1,2,3]))
.dimension(ageDim)
.yAxisLabel('Count')
.group(ageGroupCount)
.defineColors(ageColorScale);
ageChartCount.yAxis().ticks(4);
barChart('#age-chart-value-sum')
.xAxisLabel('Age')
.x(d3.scaleLinear().domain([1,2,3]))
.dimension(colorDim)
.yAxisLabel('Value sum')
.group(ageGroupValueSum)
.defineColors(ageColorScale);
function barChart(elementSelector){
let barChart = dc.barChart(elementSelector)
.width(200)
.height(200)
.xUnits(dc.units.ordinal)
.margins({top:10,left:30,right:15,bottom:35})
.barPadding(0.1)
.outerPadding(0.1)
.transitionDuration(500);
barChart.defineColors = function(colorScale){
this.renderlet(chart=>{
chart.selectAll('rect.bar')
.each(function(d){
let isSelected = this.classList.contains('selected');
if(isSelected){
d3.select(this).attr('style', 'fill: ' + colorScale(d.x) + ';stroke-width:2;stroke:#39ff14');
} else {
d3.select(this).attr('style', 'fill: ' + colorScale(d.x));
}
});
});
return this;
}
return barChart;
}
//value chart
let chart = dc.seriesChart('#value-chart');
chart
.width(500)
.height(500)
.chart( c =>
dc.lineChart(c)
.renderDataPoints(true)
.defined(d=>{
if(d.y !== null){
return d.y;
};
})
)
.xAxisLabel('Age')
.x(d3.scaleLinear().domain([1,3]))
.dimension(colorAgeDim)
.yAxisLabel('Value')
.elasticY(true)
.group(colorAgeGroup)
.brushOn(false)
.clipPadding(10)
.mouseZoomable(true)
.seriesAccessor(d => d.key[0])
.keyAccessor(d => d.key[1])
.valueAccessor(d => {
return d.value.sum;
})
.ordinalColors(['blue','green','red','yellow'])
.legend(dc.legend().x(430).y(350));
dc.renderAll();
//dimensions can also be used for filtering:
//let color_red = colorDim.filter('red');
//let filterResult = JSON.stringify(color_red.top(Infinity)).replace('[','[\n\t').replace(/}\,/g,'},\n\t').replace(']','\n]');
//console.log(filterResult);
//let functionFilter = ageDim.filter(age => age === 2);
//let functionFilterResult = JSON.stringify(functionFilter.top(Infinity)).replace('[','[\n\t').replace(/}\,/g,'},\n\t').replace(']','\n]');
//console.log(functionFilterResult);
</script>
</body>
</html>
B. 第二个示例使用过滤器从组中删除空值。跳过缺失值并连接相邻点。
<html>
<head>
<title>dc demo</title>
<meta http-equiv='content-type' content='text/html; charset=UTF8'>
<!-- this demo is based on following tuturials:
https://www.tutorialspoint.com/dcjs/dcjs_introduction_to_d3js.htm
https://www.codeproject.com/articles/693841/making-dashboards-with-dc-js-part-1-using-crossfil
-->
<script src='https://d3js.org/d3.v4.min.js'></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/crossfilter2/1.5.2/crossfilter.min.js'></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/dc/3.1.8/dc.min.js'></script>
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/dc/3.1.8/dc.css" />
</head>
<body>
<div style="font-family:arial;">
<div style="float:left;padding:20px;">
<div >
<label>Number of colors:</label>
<div id='color-chart-count'></div>
</div>
<div>
<label>Value sum for colors:</label>
<div id='color-chart-value-sum'></div>
</div>
</div>
<div style="float:center;padding:20px;">
<div>
<label>Number of ages:</label>
<div id='age-chart-count'></div>
</div>
<div>
<label>Value sum for ages:</label>
<div id='age-chart-value-sum'></div>
</div>
</div>
</div>
<div style="font-family:arial;">
<label for='value-chart'>Values:</label>
<div id='value-chart'></div>
</div>
<script>
let data = [
{color: 'red', age: 1, value: 10},
{color: 'red', age: 2, value: 11},
{color: 'red', age: 3, value: 12},
{color: 'green', age: 1, value: 20},
{color: 'green', age: 2, value: 21},
{color: 'green', age: 3, value: 22},
{color: 'blue', age: 1, value: 30},
{color: 'blue', age: 2, value: 31},
{color: 'blue', age: 3, value: 32},
];
//create instance of cross filter
let cf = crossfilter(data);
//define dimensions and groups
let colorDim = cf.dimension(d=> d.color);
let colorGroupCount = colorDim.group().reduceCount();
let colorGroupValueSum = colorDim.group().reduceSum(d => d.value);
let ageDim = cf.dimension(d=> d.age);
let ageGroupCount = ageDim.group().reduceCount();
let ageGroupValueSum = ageDim.group().reduceSum(d => d.value);
let colorAgeDim = cf.dimension(d => [d.color, d.age]);
function reduceAdd(previous, current) {
if(current){
if(current.value !== null){
if(previous.sum === null){
previous.sum = current.value;
previous.count = 1;
} else {
previous.sum += current.value;
previous.count += 1;
}
}
}
return previous;
}
function reduceRemove(previous, current) {
if(current){
if(current.value !== null){
if(previous.sum !== null){
previous.sum -= current.value;
previous.count -= 1;
if(previous.count === 0){
previous.sum = null;
}
}
}
}
return previous;
}
function reduceInit() {
return {
sum: null,
count: 0
};
}
let colorAgeGroup = colorAgeDim.group().reduce(reduceAdd, reduceRemove, reduceInit);
let filteredColorAgeGroup = removeMissingEntries(colorAgeGroup);
function removeMissingEntries(sourceGroup) {
return {
all:function () {
return sourceGroup.all().filter(function(d) {
return d.value.sum !== null;
});
}
};
}
let ordinalColors = ['red','green','blue'];
let ordinalAgeColors = ['lightgray','grey','#666666'];
//color charts
let rgbColorScale = d3.scaleOrdinal().domain(ordinalColors).range(ordinalColors);
let colorChartCount = barChart('#color-chart-count')
.xAxisLabel('Color')
.x(d3.scaleBand().domain(ordinalColors))
.dimension(colorDim)
.yAxisLabel('Count')
.group(colorGroupCount)
.defineColors(rgbColorScale);
colorChartCount.yAxis().ticks(4);
barChart('#color-chart-value-sum')
.xAxisLabel('Color')
.x(d3.scaleBand().domain(ordinalColors))
.dimension(colorDim)
.yAxisLabel('Value sum')
.group(colorGroupValueSum)
.defineColors(rgbColorScale);
//age charts
let ageColorScale = d3.scaleOrdinal().domain([1,2,3]).range(ordinalAgeColors);
/*
dc.pieChart('#age-chart-count')
.width(100)
.height(100)
.ordinalColors(ordinalAgeColors)
.dimension(ageDim)
.group(ageGroupCount);
*/
let ageChartCount = barChart('#age-chart-count')
.xAxisLabel('Age')
.x(d3.scaleLinear().domain([1,2,3]))
.dimension(ageDim)
.yAxisLabel('Count')
.group(ageGroupCount)
.defineColors(ageColorScale);
ageChartCount.yAxis().ticks(4);
barChart('#age-chart-value-sum')
.xAxisLabel('Age')
.x(d3.scaleLinear().domain([1,2,3]))
.dimension(colorDim)
.yAxisLabel('Value sum')
.group(ageGroupValueSum)
.defineColors(ageColorScale);
function barChart(elementSelector){
let barChart = dc.barChart(elementSelector)
.width(200)
.height(200)
.xUnits(dc.units.ordinal)
.margins({top:10,left:30,right:15,bottom:35})
.barPadding(0.1)
.outerPadding(0.1)
.transitionDuration(500);
barChart.defineColors = function(colorScale){
this.renderlet(chart=>{
chart.selectAll('rect.bar')
.each(function(d){
let isSelected = this.classList.contains('selected');
if(isSelected){
d3.select(this).attr('style', 'fill: ' + colorScale(d.x) + ';stroke-width:2;stroke:#39ff14');
} else {
d3.select(this).attr('style', 'fill: ' + colorScale(d.x));
}
});
});
return this;
}
return barChart;
}
//value chart
let chart = dc.seriesChart('#value-chart');
chart
.width(500)
.height(500)
.chart( c =>
dc.lineChart(c)
.renderDataPoints(true)
)
.xAxisLabel('Age')
.x(d3.scaleLinear().domain([1,3]))
.dimension(colorAgeDim)
.yAxisLabel('Value')
.elasticY(true)
.group(filteredColorAgeGroup)
.brushOn(false)
.clipPadding(10)
.mouseZoomable(true)
.seriesAccessor(d => d.key[0])
.keyAccessor(d => d.key[1])
.valueAccessor(d => {
return d.value.sum;
})
.ordinalColors(['blue','green','red','yellow'])
.legend(dc.legend().x(430).y(350));
dc.renderAll();
//dimensions can also be used for filtering:
//let color_red = colorDim.filter('red');
//let filterResult = JSON.stringify(color_red.top(Infinity)).replace('[','[\n\t').replace(/}\,/g,'},\n\t').replace(']','\n]');
//console.log(filterResult);
//let functionFilter = ageDim.filter(age => age === 2);
//let functionFilterResult = JSON.stringify(functionFilter.top(Infinity)).replace('[','[\n\t').replace(/}\,/g,'},\n\t').replace(']','\n]');
//console.log(functionFilterResult);
</script>
</body>
</html>