D3.js 分组条形图图例缺失数据
D3.js grouped bar chart legend missing data
我有一个带有交互式图例的分组条形图。一切似乎都显示和工作正常,但图例缺少一个值。我知道问题是图例偏离了 x0 轴上的项目索引,而不是条形图(x1 轴)的索引。但是我不知道如何解决这个问题。
我的数据包括以下内容:
[{
"Group": 1,
"DataPoints": [{
"BarValue": "Extension Cable",
"Value": 1
}]
},
{
"Group": 2,
"DataPoints": [{
"BarValue": "Extension Cable",
"Value": 1
},
{
"BarValue": "LED Light",
"Value": 2
},
{
"BarValue": "USB",
"Value": 4
},
{
"BarValue": "USB Socket",
"Value": 2
}]
},
{
"Group": 3,
"DataPoints": [{
"BarValue": "Extension Cable",
"Value": 2
},
{
"BarValue": "USB",
"Value": 1
}]
}]
在此示例中,组是一年中的月份数,即 1-12。 BarValue 是产品的名称。因此,每个月内每个产品都会有一个栏。而 Value 是 Y Value 来决定图表的高度。
因此在我的传说中我希望有:
延长线
LED 灯
USB
USB 插座
然而,我实际看到的是:
延长线
LED 灯
USB
这告诉我它呈现正确,除了它使用月数而不是产品来确定图例。但是我不确定这是为什么以及我缺少什么。
这是显示问题的图像。如您所见,图例中未显示粉红色条:
我的图例代码如下:
var li = {
w: 120, h: 30, s: 3, r: 3
};
var legendData = d3.set(data.reduce(function (previousValue, currentValue) {
return previousValue.concat(currentValue.DataPoints.map(function (d) {
return d.BarValue;
}))
}, [])).values();
var legend = legend.append("svg:svg")
.attr("width", li.w)
.attr("height", height)
.attr('class', 'legend');
var g = legend.selectAll("g")
.data(data.slice())
.enter().append("svg:g")
.attr("transform", function (d, i) {
return "translate(0," + ((i * (li.h + li.s)) + 20) + ")";
});
g.append("svg:rect")
.datum(function (d) { return d.DataPoints;})
.attr("rx", li.r)
.attr("ry", li.r)
.attr("width", li.w)
.attr("height", li.h)
.attr('class', function (d, i) { return 'bartag' + legendData[i].replace(/\s+/g, '') + 'rect' })
.style("fill", function (d, i) { return color(legendData[i]); })
.on('mouseover', function () {
$(this).css('cursor', 'pointer')
})
.on('click', function (d, i) {
var active = d.active ? false : true,
newOpacity = active ? 1 : 0,
id = '.bartag' + legendData[i];
d3.selectAll('.bartag' + legendData[i])
.transition().duration(100)
.style('opacity', newOpacity);
d.active = active;
if (active) {
var test = '.bartag' + legendData[i] + 'rect';
d3.selectAll('.bartag' + legendData[i] + 'rect').style("opacity", newOpacity);
}
else {
var test = d3.selectAll('.bartag' + legendData[i] + 'rect');
test.style('opacity', newOpacity);
}
});
g.append("svg:text")
.attr("x", 5)
.attr("y", li.h / 2)
.attr("dy", "0.35em")
.attr("text-anchor", "start")
.text(function (d, i) { return legendData[i]; })
.on('mouseover', function () {
$(this).css('cursor', 'pointer')
})
.on('click', function (d, i) {
var active = d.active ? false : true,
newOpacity = active ? 1 : 0,
id = '.bartag' + legendData[i].replace(/\s+/g, '');
d3.selectAll('.bartag' + legendData[i].replace(/\s+/g, ''))
.transition().duration(100)
.style('opacity', newOpacity);
d.active = active;
if (active) {
var test = '.bartag' + legendData[i].replace(/\s+/g, '') + 'rect';
d3.selectAll('.bartag' + legendData[i].replace(/\s+/g, '') + 'rect').style("opacity", newOpacity);
}
else {
var test = d3.selectAll('.bartag' + legendData[i].replace(/\s+/g, '') + 'rect');
test.style('opacity', newOpacity);
}
});
作为参考,这里是我的完整代码:
var margin = { top: 20, right: 0, bottom: 40, left: 50 },
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom,
tooltipTextColour = "white",
color = d3.scale.ordinal().range(["#FF9797", "#86BCFF", "#33FDC0", "#EFA9FE", "#7BCAE1", "#8C8CFF", "#80B584", "#C88E8E", "#DD597D", "#D8F0F8", "#DD597D", "#D6C485", "#990099", "#5B5BFF", "#1FCB4A", "#000000", "#00BFFF", "#BE81F7", "#BDBDBD", "#F79F81"]);
if (data.length > 0) {
var legendData = d3.set(data.reduce(function (previousValue, currentValue) {
return previousValue.concat(currentValue.DataPoints.map(function (d) {
return d.BarValue;
}))
}, [])).values();
var x0 = d3.scale.ordinal()
.rangeRoundBands([0, width], .1);
var x1 = d3.scale.ordinal();
var y = d3.scale.linear().range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x0)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickFormat(d3.format(".2s"));
var svg = placeholder.append("svg")
.attr('width', width + margin.left)
.attr('height', height + margin.top + margin.bottom)
.attr('class', 'chart')
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
x0.domain(data.map(function (d) { return d.Group; }));
x1.domain(d3.set(data.reduce(function (previousValue, currentValue) {
return previousValue.concat(currentValue.DataPoints.map(function (d) {
return d.BarValue;
}))
}, [])).values()).rangeRoundBands([0, x0.rangeBand()]);
y.domain([0, d3.max(data, function(d){
return d3.max(d.DataPoints, function (d) {
return d.Value;
})
})]);
svg.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(0,' + height + ')')
.call(xAxis);
svg.append('g')
.attr('class', 'y axis')
.call(yAxis)
.append('text')
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Complaints");
var type = svg.selectAll(".type")
.data(data)
.enter()
.append('g')
.attr('class', 'type')
.attr('transform', function (d) { return 'translate(' + x0(d.Group) + ',0)'; });
var rect = type.selectAll("rect")
.data(function (d) { return d.DataPoints; })
.enter()
.append('rect')
.attr('width', x1.rangeBand())
.attr('x', function (d) { return x1(d.BarValue); })
.attr('y', function (d) { return y(d.Value); })
.attr('class', function (d) { return 'bartag' + d.BarValue.replace(/\s+/g, '') })
.attr('height', function (d) { return height - y(d.Value); })
.style('fill', function (d, i) { return color(d.BarValue); });
rect.append('text')
.attr('x', function (d) { return x1(d.BarValue); })
.attr('y', function (d) { return y(d.Value); })
.attr("dy", "0.35em")
.attr("text-anchor", "middle")
.text(function (d, i) { return legendData[i]; });
if (legend != null) {
var li = {
w: 120, h: 30, s: 3, r: 3
};
var legend = legend.append("svg:svg")
.attr("width", li.w)
.attr("height", height)
.attr('class', 'legend');
var g = legend.selectAll("g")
.data(data.slice())
.enter().append("svg:g")
.attr("transform", function (d, i) {
return "translate(0," + ((i * (li.h + li.s)) + 20) + ")";
});
g.append("svg:rect")
.datum(function (d) { return d.DataPoints;})
.attr("rx", li.r)
.attr("ry", li.r)
.attr("width", li.w)
.attr("height", li.h)
.attr('class', function (d, i) { return 'bartag' + legendData[i].replace(/\s+/g, '') + 'rect' })
.style("fill", function (d, i) { return color(legendData[i]); })
.on('mouseover', function () {
$(this).css('cursor', 'pointer')
})
.on('click', function (d, i) {
var active = d.active ? false : true,
newOpacity = active ? 1 : 0,
id = '.bartag' + legendData[i];
d3.selectAll('.bartag' + legendData[i])
.transition().duration(100)
.style('opacity', newOpacity);
d.active = active;
if (active) {
var test = '.bartag' + legendData[i] + 'rect';
d3.selectAll('.bartag' + legendData[i] + 'rect').style("opacity", newOpacity);
}
else {
var test = d3.selectAll('.bartag' + legendData[i] + 'rect');
test.style('opacity', newOpacity);
}
});
g.append("svg:text")
.attr("x", 5)
.attr("y", li.h / 2)
.attr("dy", "0.35em")
.attr("text-anchor", "start")
.text(function (d, i) { return legendData[i]; })
.on('mouseover', function () {
$(this).css('cursor', 'pointer')
})
.on('click', function (d, i) {
var active = d.active ? false : true,
newOpacity = active ? 1 : 0,
id = '.bartag' + legendData[i].replace(/\s+/g, '');
d3.selectAll('.bartag' + legendData[i].replace(/\s+/g, ''))
.transition().duration(100)
.style('opacity', newOpacity);
d.active = active;
if (active) {
var test = '.bartag' + legendData[i].replace(/\s+/g, '') + 'rect';
d3.selectAll('.bartag' + legendData[i].replace(/\s+/g, '') + 'rect').style("opacity", newOpacity);
}
else {
var test = d3.selectAll('.bartag' + legendData[i].replace(/\s+/g, '') + 'rect');
test.style('opacity', newOpacity);
}
});
}
}
else {
placeholder.append('p').text('No Data to Display').style('font-weight', 'bold');
}
if (callback) {
callback();
}
我希望这是足够的信息,如果有人可以提供任何帮助,我们将不胜感激。
编辑
不知道哪一个比较容易解决,就放上来吧
如果我使用 legendData 作为我的图例数据,所有图例选项都会出现。然而,问题在于图例不再具有交互性。这是因为 legendData 只是包含产品名称的字符串,因此当它尝试获取 d.active 来切换栏的可见性时,它失败了,因为它是一个字符串而不是一个对象。
我会进行以下修改:
将您的图例作为对象数组(而不是字符串)获取,每个对象都包含作为字符串的名称和 active/inactive.
的布尔值
var legendData = d3.set(data.reduce(function (previousValue, currentValue) {
return previousValue.concat(currentValue.DataPoints.map(function (d) {
return d.BarValue;
}))
}, [])).values();
//Add this:
legendData=legendData.map(function(s){
return {name:s, active:true};
});
将 legendData
映射到您的图例对象
legend.selectAll("g")
.data(legendData)
.enter().append("svg:g")
并删除之前的绑定:
g.append("svg:rect")
//.datum(function (d) { return d.DataPoints;}) //remove this
.attr("rx", li.r)
现在每个图例项都知道相应的字符串及其状态。
- 在适用的情况下使用数据的
name
字段:对于图例部分中的任何 legendData[i]
,您应该使用 d.name
(注意您没有明确引用 legendData
了,因为你之前已经完成了绑定)。 active
字段似乎无事可做(您已经在使用 d.active
)。
我有一个带有交互式图例的分组条形图。一切似乎都显示和工作正常,但图例缺少一个值。我知道问题是图例偏离了 x0 轴上的项目索引,而不是条形图(x1 轴)的索引。但是我不知道如何解决这个问题。
我的数据包括以下内容:
[{
"Group": 1,
"DataPoints": [{
"BarValue": "Extension Cable",
"Value": 1
}]
},
{
"Group": 2,
"DataPoints": [{
"BarValue": "Extension Cable",
"Value": 1
},
{
"BarValue": "LED Light",
"Value": 2
},
{
"BarValue": "USB",
"Value": 4
},
{
"BarValue": "USB Socket",
"Value": 2
}]
},
{
"Group": 3,
"DataPoints": [{
"BarValue": "Extension Cable",
"Value": 2
},
{
"BarValue": "USB",
"Value": 1
}]
}]
在此示例中,组是一年中的月份数,即 1-12。 BarValue 是产品的名称。因此,每个月内每个产品都会有一个栏。而 Value 是 Y Value 来决定图表的高度。
因此在我的传说中我希望有:
延长线
LED 灯
USB
USB 插座
然而,我实际看到的是:
延长线
LED 灯
USB
这告诉我它呈现正确,除了它使用月数而不是产品来确定图例。但是我不确定这是为什么以及我缺少什么。
这是显示问题的图像。如您所见,图例中未显示粉红色条:
我的图例代码如下:
var li = {
w: 120, h: 30, s: 3, r: 3
};
var legendData = d3.set(data.reduce(function (previousValue, currentValue) {
return previousValue.concat(currentValue.DataPoints.map(function (d) {
return d.BarValue;
}))
}, [])).values();
var legend = legend.append("svg:svg")
.attr("width", li.w)
.attr("height", height)
.attr('class', 'legend');
var g = legend.selectAll("g")
.data(data.slice())
.enter().append("svg:g")
.attr("transform", function (d, i) {
return "translate(0," + ((i * (li.h + li.s)) + 20) + ")";
});
g.append("svg:rect")
.datum(function (d) { return d.DataPoints;})
.attr("rx", li.r)
.attr("ry", li.r)
.attr("width", li.w)
.attr("height", li.h)
.attr('class', function (d, i) { return 'bartag' + legendData[i].replace(/\s+/g, '') + 'rect' })
.style("fill", function (d, i) { return color(legendData[i]); })
.on('mouseover', function () {
$(this).css('cursor', 'pointer')
})
.on('click', function (d, i) {
var active = d.active ? false : true,
newOpacity = active ? 1 : 0,
id = '.bartag' + legendData[i];
d3.selectAll('.bartag' + legendData[i])
.transition().duration(100)
.style('opacity', newOpacity);
d.active = active;
if (active) {
var test = '.bartag' + legendData[i] + 'rect';
d3.selectAll('.bartag' + legendData[i] + 'rect').style("opacity", newOpacity);
}
else {
var test = d3.selectAll('.bartag' + legendData[i] + 'rect');
test.style('opacity', newOpacity);
}
});
g.append("svg:text")
.attr("x", 5)
.attr("y", li.h / 2)
.attr("dy", "0.35em")
.attr("text-anchor", "start")
.text(function (d, i) { return legendData[i]; })
.on('mouseover', function () {
$(this).css('cursor', 'pointer')
})
.on('click', function (d, i) {
var active = d.active ? false : true,
newOpacity = active ? 1 : 0,
id = '.bartag' + legendData[i].replace(/\s+/g, '');
d3.selectAll('.bartag' + legendData[i].replace(/\s+/g, ''))
.transition().duration(100)
.style('opacity', newOpacity);
d.active = active;
if (active) {
var test = '.bartag' + legendData[i].replace(/\s+/g, '') + 'rect';
d3.selectAll('.bartag' + legendData[i].replace(/\s+/g, '') + 'rect').style("opacity", newOpacity);
}
else {
var test = d3.selectAll('.bartag' + legendData[i].replace(/\s+/g, '') + 'rect');
test.style('opacity', newOpacity);
}
});
作为参考,这里是我的完整代码:
var margin = { top: 20, right: 0, bottom: 40, left: 50 },
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom,
tooltipTextColour = "white",
color = d3.scale.ordinal().range(["#FF9797", "#86BCFF", "#33FDC0", "#EFA9FE", "#7BCAE1", "#8C8CFF", "#80B584", "#C88E8E", "#DD597D", "#D8F0F8", "#DD597D", "#D6C485", "#990099", "#5B5BFF", "#1FCB4A", "#000000", "#00BFFF", "#BE81F7", "#BDBDBD", "#F79F81"]);
if (data.length > 0) {
var legendData = d3.set(data.reduce(function (previousValue, currentValue) {
return previousValue.concat(currentValue.DataPoints.map(function (d) {
return d.BarValue;
}))
}, [])).values();
var x0 = d3.scale.ordinal()
.rangeRoundBands([0, width], .1);
var x1 = d3.scale.ordinal();
var y = d3.scale.linear().range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x0)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickFormat(d3.format(".2s"));
var svg = placeholder.append("svg")
.attr('width', width + margin.left)
.attr('height', height + margin.top + margin.bottom)
.attr('class', 'chart')
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
x0.domain(data.map(function (d) { return d.Group; }));
x1.domain(d3.set(data.reduce(function (previousValue, currentValue) {
return previousValue.concat(currentValue.DataPoints.map(function (d) {
return d.BarValue;
}))
}, [])).values()).rangeRoundBands([0, x0.rangeBand()]);
y.domain([0, d3.max(data, function(d){
return d3.max(d.DataPoints, function (d) {
return d.Value;
})
})]);
svg.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(0,' + height + ')')
.call(xAxis);
svg.append('g')
.attr('class', 'y axis')
.call(yAxis)
.append('text')
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Complaints");
var type = svg.selectAll(".type")
.data(data)
.enter()
.append('g')
.attr('class', 'type')
.attr('transform', function (d) { return 'translate(' + x0(d.Group) + ',0)'; });
var rect = type.selectAll("rect")
.data(function (d) { return d.DataPoints; })
.enter()
.append('rect')
.attr('width', x1.rangeBand())
.attr('x', function (d) { return x1(d.BarValue); })
.attr('y', function (d) { return y(d.Value); })
.attr('class', function (d) { return 'bartag' + d.BarValue.replace(/\s+/g, '') })
.attr('height', function (d) { return height - y(d.Value); })
.style('fill', function (d, i) { return color(d.BarValue); });
rect.append('text')
.attr('x', function (d) { return x1(d.BarValue); })
.attr('y', function (d) { return y(d.Value); })
.attr("dy", "0.35em")
.attr("text-anchor", "middle")
.text(function (d, i) { return legendData[i]; });
if (legend != null) {
var li = {
w: 120, h: 30, s: 3, r: 3
};
var legend = legend.append("svg:svg")
.attr("width", li.w)
.attr("height", height)
.attr('class', 'legend');
var g = legend.selectAll("g")
.data(data.slice())
.enter().append("svg:g")
.attr("transform", function (d, i) {
return "translate(0," + ((i * (li.h + li.s)) + 20) + ")";
});
g.append("svg:rect")
.datum(function (d) { return d.DataPoints;})
.attr("rx", li.r)
.attr("ry", li.r)
.attr("width", li.w)
.attr("height", li.h)
.attr('class', function (d, i) { return 'bartag' + legendData[i].replace(/\s+/g, '') + 'rect' })
.style("fill", function (d, i) { return color(legendData[i]); })
.on('mouseover', function () {
$(this).css('cursor', 'pointer')
})
.on('click', function (d, i) {
var active = d.active ? false : true,
newOpacity = active ? 1 : 0,
id = '.bartag' + legendData[i];
d3.selectAll('.bartag' + legendData[i])
.transition().duration(100)
.style('opacity', newOpacity);
d.active = active;
if (active) {
var test = '.bartag' + legendData[i] + 'rect';
d3.selectAll('.bartag' + legendData[i] + 'rect').style("opacity", newOpacity);
}
else {
var test = d3.selectAll('.bartag' + legendData[i] + 'rect');
test.style('opacity', newOpacity);
}
});
g.append("svg:text")
.attr("x", 5)
.attr("y", li.h / 2)
.attr("dy", "0.35em")
.attr("text-anchor", "start")
.text(function (d, i) { return legendData[i]; })
.on('mouseover', function () {
$(this).css('cursor', 'pointer')
})
.on('click', function (d, i) {
var active = d.active ? false : true,
newOpacity = active ? 1 : 0,
id = '.bartag' + legendData[i].replace(/\s+/g, '');
d3.selectAll('.bartag' + legendData[i].replace(/\s+/g, ''))
.transition().duration(100)
.style('opacity', newOpacity);
d.active = active;
if (active) {
var test = '.bartag' + legendData[i].replace(/\s+/g, '') + 'rect';
d3.selectAll('.bartag' + legendData[i].replace(/\s+/g, '') + 'rect').style("opacity", newOpacity);
}
else {
var test = d3.selectAll('.bartag' + legendData[i].replace(/\s+/g, '') + 'rect');
test.style('opacity', newOpacity);
}
});
}
}
else {
placeholder.append('p').text('No Data to Display').style('font-weight', 'bold');
}
if (callback) {
callback();
}
我希望这是足够的信息,如果有人可以提供任何帮助,我们将不胜感激。
编辑
不知道哪一个比较容易解决,就放上来吧
如果我使用 legendData 作为我的图例数据,所有图例选项都会出现。然而,问题在于图例不再具有交互性。这是因为 legendData 只是包含产品名称的字符串,因此当它尝试获取 d.active 来切换栏的可见性时,它失败了,因为它是一个字符串而不是一个对象。
我会进行以下修改:
将您的图例作为对象数组(而不是字符串)获取,每个对象都包含作为字符串的名称和 active/inactive.
的布尔值var legendData = d3.set(data.reduce(function (previousValue, currentValue) { return previousValue.concat(currentValue.DataPoints.map(function (d) { return d.BarValue; })) }, [])).values(); //Add this: legendData=legendData.map(function(s){ return {name:s, active:true}; });
将
legendData
映射到您的图例对象legend.selectAll("g") .data(legendData) .enter().append("svg:g")
并删除之前的绑定:
g.append("svg:rect") //.datum(function (d) { return d.DataPoints;}) //remove this .attr("rx", li.r)
现在每个图例项都知道相应的字符串及其状态。
- 在适用的情况下使用数据的
name
字段:对于图例部分中的任何legendData[i]
,您应该使用d.name
(注意您没有明确引用legendData
了,因为你之前已经完成了绑定)。active
字段似乎无事可做(您已经在使用d.active
)。