D3.js分组条形图图例缺失数据

时间:2016-03-02 13:11:31

标签: javascript d3.js charts legend

我有一个带有交互式图例的分组条形图。所有似乎都显示并正常工作,但传说缺少一个值。我知道问题在于图例是在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值。

因此,在我的传说中,我希望有:

    延长电缆
    LED灯
    USB
    USB插座

然而,我实际看到的是:

    延长电缆
    LED灯
    USB

这告诉我它正在正确渲染,除了它使用月份的数量而不是产品来确定图例。但是我不确定为什么会这样,我不知道。

这是显示问题的图像。如您所见,粉红色条未显示在图例中:

enter image description here

我的传奇代码如下:

       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切换条形图的可见性时,它会失败,因为它是字符串而不是对象。

1 个答案:

答案 0 :(得分:1)

我会进行以下修改:

  1. 将您的图例作为一个对象数组(而不是字符串),每个对象包含一个字符串的名称和一个活动/非活动的bollean。

    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. 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)
    
  3. 现在每个图例项都知道相应的字符串及其状态。

    1. 使用适用的数据的name字段:对于图例部分中的任何legendData[i],您应该改为d.name(注意您没有明确引用{{1}因为你之前完成了绑定,所以已经完成了。 legendData字段似乎无关(您已使用active)。