Комментарии Гордона указали мне в правильном направлении. Ниже представлены две измененные версии.
Я включил таблицу стилей dc.css
для решения проблемы черной области.
Вместо group().reduceSum()
я использовал пользовательскую операцию уменьшения для Подумайте о пропущенных значениях как ноль вместо 0. Теперь мой метод доступа к значениям правильно возвращает ноль для пропущенных значений.
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>