Custom legend based on color of bars in a dc.js co

2019-02-28 09:36发布

问题:

I implemented a composite chart with two bar charts in which one bar chart consists of bars with different colored bars. Now, I want to create a custom legend that represents each color bar (similar to https://dc-js.github.io/dc.js/examples/pie-external-labels.html used for pie chart). Below is the code snippet of what I've done so far:

var buttonPress = dc.barChart(composite)
    .dimension(joyTimeDimension)
    //.renderlet(colorRenderlet)
    //.colors('red')
    .colors(colorbrewer.Set1[5])
    .colorDomain([101, 105])
    .colorAccessor(function (d) {
        return d.value;
    })
    .group(btnGroup, "Button Press")
    .keyAccessor(function(d) {return d.key[0];})
    .valueAccessor(function (d) {
        return d.value;
    })
    .title( function(d){ 
        return [
            "Time: "+d.key[0],
            "button Name: "+d.key[1],
            "button: "+ d.value
        ].join('\n')
    });


var joyStick = dc.barChart(composite)
    .dimension(joyTimeDimension)
    .colors('blue')
    .group(stepperGroup,"Joy Stick Movement")
    .keyAccessor(function(d) {return d.key[0];})
    .title( function(d){ 
        return [
            "Time: "+d.key[0],
            "Stepper Position: "+ d.value
        ].join('\n')
  });
  composite
    .width(1200)
    .transitionDuration(500)
    .margins({top: 30, right: 50, bottom: 25, left: 40})
    .x(d3.time.scale().domain([startDate,currDate]))
    .xUnits(function(){return 150;})
    //.xUnits(d3.time.second)
    .elasticY(true)
    .legend(dc.legend().x(1000).y(4).itemHeight(13).gap(5))
    .renderHorizontalGridLines(true)
    .renderTitle(true)
    .shareTitle(false)
    .compose([buttonPress, joyStick])
    .brushOn(false)

Is there a way to create a custom legend for this scenario? Thanks in advance.

回答1:

Let me provide a little bit of background about how the legend is built.

The legend in dc.js is really not all that sophisticated. It just calls .legendables() on the chart, and the chart decides what items to display in the legend.

Each chart has its own special-purpose code for this.

If we look at the source for compositeChart.legendables(), it's just recursively getting the legendables for each child chart and concatenating them:

_chart.legendables = function () {
    return _children.reduce(function (items, child) {
        if (_shareColors) {
            child.colors(_chart.colors());
        }
        items.push.apply(items, child.legendables());
        return items;
    }, []);
};

The pie chart creates a legendable for each pie slice:

_chart.legendables = function () {
    return _chart.data().map(function (d, i) {
        var legendable = {name: d.key, data: d.value, others: d.others, chart: _chart};
        legendable.color = _chart.getColor(d, i);
        return legendable;
    });
};

The legendables for the bar chart come from the stack mixin, which creates a legendable for each stack:

_chart.legendables = function () {
    return _stack.map(function (layer, i) {
        return {
            chart: _chart,
            name: layer.name,
            hidden: layer.hidden || false,
            color: _chart.getColor.call(layer, layer.values, i)
        };
    });
};

Given that there's currently no way to get a bar chart to display a pie chart's legend, I think the easiest thing to do is override legendables for your bar chart with its custom colors:

buttonPress.legendables = function() {
    return btnGroup.all().map(function(kv) {
        return {
            chart: buttonPress,
            // display the value as the text (not sure what you want here)
            name: kv.value,
            // apply the chart's color scale to get the color
            color: buttonPress.colors()(kv.value)
        };
    })
};

There are probably some more details to be worked out, such as what if the same value occurs twice? I am assuming you can just read the input data from the group and .map() it, but you might need to generate your data a different way.

But this should give the general idea. Lmk if it doesn't work and I'll be glad to follow up.