简体   繁体   中英

D3.js grouped bar chart legend missing data

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 ie 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.

I would go for the following modifications:

  1. 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}; }); 
  2. 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.

  1. use the 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 ).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM