8

I have a bar chart with ordinal scale for the x-axis. I want to display the y-values on the top of each bar or in the bottom of each bar. It would be also acceptable to display the y-values when one hovers over the bar. Is there a function or a way in a dc.js to do that? Here is the jsfiddle and my code is below the pic>

Edit: Here is my code: HTML

<body>
    <div id='Chart'>
    </div>
</body>

JS

var data = [{
    Category: "A",
    ID: "1"
}, {
    Category: "A",
    ID: "1"
}, {
    Category: "A",
    ID: "1"
}, {
    Category: "A",
    ID: "2"
}, {
    Category: "A",
    ID: "2"
}, {
    Category: "B",
    ID: "1"
}, {
    Category: "B",
    ID: "1"
}, {
    Category: "B",
    ID: "1"
}, {
    Category: "B",
    ID: "2"
}, {
    Category: "B",
    ID: "3"
}, {
    Category: "B",
    ID: "3"
}, {
    Category: "B",
    ID: "3"
}, {
    Category: "B",
    ID: "4"
}, {
    Category: "C",
    ID: "1"
}, {
    Category: "C",
    ID: "2"
}, {
    Category: "C",
    ID: "3"
}, {
    Category: "C",
    ID: "4"
}, {
    Category: "C",
    ID: "4"
},{
    Category: "C",
    ID: "5"
}];

var ndx = crossfilter(data);

var XDimension = ndx.dimension(function (d) {
    return d.Category;
});

var YDimension = XDimension.group().reduceCount(function (d) {
    return d.value;
});


dc.barChart("#Chart")
    .width(480).height(300)
    .dimension(XDimension)
    .group(YDimension)
    .transitionDuration(500)
    .xUnits(dc.units.ordinal)
    .label(function(d) {return d.value})
    .x(d3.scale.ordinal().domain(XDimension)) 

dc.renderAll();
Koba
  • 1,514
  • 4
  • 27
  • 48
  • Can you post your code and/or a [JSFiddle](http://jsfiddle.net) or the like? – mdml Jul 30 '14 at 14:05
  • Getting FIREBASE WARNING: on() or once() for /UniqueActivities failed: Error: permission_denied: Client doesn't have permission to access the desired data. – elzi Aug 08 '14 at 21:21
  • @elzi I created a [jsfiddle](http://jsfiddle.net/tpsc5f9f/2/) and simplified the code. – Koba Aug 08 '14 at 21:32

5 Answers5

13

Check updated jsfiddle

.renderlet(function(chart){

    var barsData = [];
    var bars = chart.selectAll('.bar').each(function(d) { barsData.push(d); });

    //Remove old values (if found)
    d3.select(bars[0][0].parentNode).select('#inline-labels').remove();
    //Create group for labels 
    var gLabels = d3.select(bars[0][0].parentNode).append('g').attr('id','inline-labels');

    for (var i = bars[0].length - 1; i >= 0; i--) {

        var b = bars[0][i];
        //Only create label if bar height is tall enough
        if (+b.getAttribute('height') < 18) continue;
        
        gLabels
            .append("text")
            .text(barsData[i].data.value)
            .attr('x', +b.getAttribute('x') + (b.getAttribute('width')/2) )
            .attr('y', +b.getAttribute('y') + 15)
            .attr('text-anchor', 'middle')
            .attr('fill', 'white');
    }

})

If you don't want the labels visible when the bars redraw (for example when bars change after user filters/clicks other chart) you can move the check of old values from de renderlet to the to a preRedraw listener.

.on("preRedraw", function(chart){

    //Remove old values (if found)
    chart.select('#inline-labels').remove();

})

Alternative

D3-ish way

Demo jsfiddle

.renderlet(function (chart) {
    
    //Check if labels exist
    var gLabels = chart.select(".labels");
    if (gLabels.empty()){
        gLabels = chart.select(".chart-body").append('g').classed('labels', true);
    }
    
    var gLabelsData = gLabels.selectAll("text").data(chart.selectAll(".bar")[0]);
    
    gLabelsData.exit().remove(); //Remove unused elements
    
    gLabelsData.enter().append("text") //Add new elements
    
    gLabelsData
    .attr('text-anchor', 'middle')
    .attr('fill', 'white')
    .text(function(d){
        return d3.select(d).data()[0].data.value
    })
    .attr('x', function(d){ 
        return +d.getAttribute('x') + (d.getAttribute('width')/2); 
    })
    .attr('y', function(d){ return +d.getAttribute('y') + 15; })
    .attr('style', function(d){
        if (+d.getAttribute('height') < 18) return "display:none";
    });
    
})
Community
  • 1
  • 1
dimirc
  • 6,347
  • 3
  • 23
  • 34
  • This is great. It isn't quite idiomatic d3 code yet, because generally you use selections to do the looping, and less manual indexing, but it's getting a lot closer. Thanks! – Gordon Aug 24 '14 at 06:22
  • @Gordon I updated my answer with an alternative for a more idiomatic code – dimirc Aug 25 '14 at 22:27
  • With dc 2.0, `.on('pretransition', function {...})` is better than `.renderlet(function {...})` – Gordon Oct 21 '15 at 20:47
  • @dimirc - thanks for this amazing answer. I've utilized this very much, but I'm trying to use this solution for stacked bar charts to avail. I've posted a question here about this: http://stackoverflow.com/questions/39581885/how-to-show-values-stacked-bar-chart-utilizing-dc-js-and-d3-js – Chris Sep 19 '16 at 20:53
8

In dc.js version 3.* . You can just use .renderLabel(true). It will print the value at top

Alauddin Ahmed
  • 1,128
  • 2
  • 14
  • 34
2

Here's a bit of a hacky solution using the renderlet method. jsFiddle: http://jsfiddle.net/tpsc5f9f/4/

JS

var barChart = dc.barChart("#Chart")
    .width(480).height(300)
    .dimension(XDimension)
    .group(YDimension)
    .transitionDuration(500)
    .xUnits(dc.units.ordinal)
    .x(d3.scale.ordinal().domain(XDimension)) 

dc.renderAll();

barChart.renderlet(function(chart){
    moveGroupNames();
});


function moveGroupNames() {
   var $chart = $('#Chart'),
       bar  = $chart.find('.bar');

    bar.each(function (i, item) {
        var bar_top    = this.height.baseVal.value;
        var bar_left   = this.width.baseVal.value;
        var bar_offset_x = 30;
        var bar_offset_y = 33;

        var bar_val = $(this).find('title').html().split(':')[1];

        $chart.append('<div class="val" style="bottom:'+(bar_top+bar_offset_y)+'px;left:'+((bar_left*i)+(bar_offset_x))+'px;width:'+bar_left+'px">'+bar_val+'</div>');

    });
}

Added CSS

body {
    margin-top:20px;
}
#Chart {
    position:relative;
}

#Chart .val {
    position:absolute;
    left:0;
    bottom:0;
    text-align: center;
}
elzi
  • 5,442
  • 7
  • 37
  • 61
  • great. However, I am looking for displaying the numbers on top of each bar, not the bar names. Sorry for confusion. I actually achieved the same result with renderlet, but dont know how to display the numbers(y values) on top of each bar. – Koba Aug 08 '14 at 22:07
  • Well in that case here's a REALLY hacky solution, haha: http://jsfiddle.net/tpsc5f9f/4/ – elzi Aug 08 '14 at 22:35
  • hella hecky. I am so annoyed it is not a default feature of dc.js. – Koba Aug 10 '14 at 03:07
  • 1
    Submit a PR! I think you should be able to get the data directly from the bar, though. d3 annotates each dom element with its data. @elzi, care to edit your answer above, at least with the title hack? – Gordon Aug 10 '14 at 15:54
  • Hmm, okay, I was hoping for a d3, svg-based solution for my bounty. It would start `chart.selectAll('.bar').each(function(d) { ...` and append SVG elements. Guess I didn't offer a high enough bounty, but thanks anyway @elzi. – Gordon Aug 12 '14 at 04:17
  • sorry @Gordon, wasn't sure what you meant me to edit from your last question. I'll spend some time on this get you a better answer. I have never used d3 but it looks like this could be done more legitimately using the renderlet callback. – elzi Aug 12 '14 at 19:17
  • 1
    Thanks. Note that the `d` parameter provided by `d3.each` provides the data, so there's no need for the title hack. Your solution is very creative but it's doing more work than it needs to. – Gordon Aug 12 '14 at 19:22
  • Alright, I gave you the bounty anyway, didn't want it to go to waste. Thanks for trying, @elzi! – Gordon Aug 15 '14 at 20:05
  • @Gordon based on your comments, I posted a new answer. I'm new to dc and d3 so let me know any comments – dimirc Aug 24 '14 at 02:36
2

If you use a specialized valueAccessor with a chart, you can make the following substitution in dimirc's "D3-ish way" solution.

Change

.text(function(d){
    return d3.select(d).data()[0].data.value
})

To

.text(function(d){
    return chart.valueAccessor()(d3.select(d).data()[0].data)
});
sw.chef
  • 61
  • 3
1

This function will reposition a row chart's labels to the end of each row. A similar technique could be used for a bar chart using the "y" attribute.

  • Labels animate along with the row rect, using chart's transitionDuration
  • Uses chart's valueAccessor function
  • Uses chart's xAxis scale to calculate label position

rowChart.on('pretransition', function(chart) {
    var padding = 2;
        
    chart.selectAll('g.row > text') //find all row labels
        .attr('x', padding) //move labels to starting position
        .transition() //start transition
        .duration(chart.transitionDuration()) //use chart's transitionDuration so labels animate with rows
        .attr('x', function(d){ //set label final position
            var dataValue = chart.valueAccessor()(d); //use chart's value accessor as basis for row label position
            var scaledValue = chart.xAxis().scale()(dataValue); //convert numeric value to row width
            return scaledValue+padding; //return scaled value to set label position
        });
});
Simon
  • 586
  • 1
  • 5
  • 6