Reputation: 2404
I have a grouped bar chart with an interactive legend. All seems to display and work ok but the legend is missing a value. I know that the issue is that the legend is going off the index of items on the x0 axis rather than the index of the bars (x1 axis). However i do not know how to resolve this.
My data consists of the following:
[{
"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
}]
}]
In this example, the group is the number of the month of the year i.e. 1-12. The BarValue is the name of a product. Therefore there will be a bar for each product within each month. And Value is the Y Value to determine the height of the chart.
Therefore in my legend i would expect to have:
Extension Cable
LED Light
USB
USB Socket
However, what i actually see is:
Extension Cable
LED Light
USB
This tells me that it is rendering correctly except it is using the count of the months rather than the products in order to determine the legend. However I am not sure why this is and what i am missing.
Here is an image displaying the issue. As you can see, the pink bar is not represented in the legend:
My legend code is as follows:
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);
}
});
And for reference, here is my complete code:
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();
}
I hope that is enough information and if there is any help anyone could provide, it would be greatly appreciated.
EDIT
I dont know which is easier to solve, so i will put this up.
All legend options appear if i use legendData as my data for the legend. However, the problem then is that the legend is no longer interactive. This is because legendData is simply strings containing the product name so when it attempts to get d.active to toggle the visibility of the bar, it fails as it is a string and not an object.
Upvotes: 1
Views: 783
Reputation: 2100
I would go for the following modifications:
get your legend as an array of objects (instead of strings), each one containing the name as a string and a bollean for 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};
});
map the legendData
to your legend objects
legend.selectAll("g")
.data(legendData)
.enter().append("svg:g")
and remove the former binding:
g.append("svg:rect")
//.datum(function (d) { return d.DataPoints;}) //remove this
.attr("rx", li.r)
now each legend item knows the corresponding string and its state.
name
field of the data where applicable: for any legendData[i]
within the legend part, you should have d.name
instead (notice that you're not explicitly referencing legendData
anymore, since you've done the binding earlier). There seems to be nothing to do for the active
field (you are already using d.active
).Upvotes: 1