How to display values in Stacked Multi-bar chart - nvd3 Graphs
Asked Answered
K

1

6

I got a scenario where in I need to display value for every stack in stacked multi-bar chart - nvd3 graph as we can display value in discrete value - nvd3 graph.

I understand, 'showvalue' is used in discrete bar controller, can we use showvalue in stacked graph, if not please suggest with an alternative solution.

Thanks in advance

Kreisler answered 3/6, 2015 at 13:14 Comment(1)
I am searching for that answer too.Shrum
N
8

Currently isn't possible. That option is available only in the discrete bar chart.

From the maintainer:

We don't have this ability. Stacked/Grouped charts also have complex animations making this a tricky thing to solve. We use tooltips instead.

Source https://github.com/novus/nvd3/issues/150

If you want to do this you need to make your own implementation using d3. There is a good example here http://plnkr.co/edit/BNpAlFalKz0zkkSszHh1?p=preview. It's using the angular wrapper but it's a good starting point.

var app = angular.module('app', ['nvd3ChartDirectives']);

app.controller('chartCtrl', function($scope, $timeout) {

  var ANIMATION_TIME = 1500,
    countSeriesDisplayed = 2,
    promise,
    labels = ["label1", "label2", "label3", "label4", "label5"];

  $scope.isStacked = false;

  // Example data
  $scope.chartData = [{
    "key": "Series 1",
    "values": [
      [0, 10],
      [1, 20],
      [2, 30],
      [3, 40],
      [4, 50]
    ]
  }, {
    "key": "Series 2",
    "values": [
      [0, 10],
      [1, 40],
      [2, 60],
      [3, 20],
      [4, 40]
    ]
  }];

  /* To add labels, images, or other nodes on the created SVG, we need to wait
   *  for the chart to be rendered with a callback.
   *  Once the chart is rendered, a timeout is set to wait for the animation to
   *  finish.
   *
   *  Then, we need to find the position of the labels and set it with the
   *  transform attribute in SVG.
   *  To do so, we have to get the width and height of each bar/group of bar 
   *  which changes if stacked or not
   *
   */

  // Callback called when the chartData is assigned
  $scope.initLabels = function() {
    return function(graph) {
      promise = $timeout(function() {
        var svg = d3.select("svg"),
          lastRects, rectWidth,
          heightForXvalue = []; // Used for grouped mode

        // We get one positive rect of each serie from the svg (here the last serie)
        lastRects = svg.selectAll("g.nv-group").filter(
          function(d, i) {
            return i == countSeriesDisplayed - 1;
          }).selectAll("rect.positive");

        if ($scope.isStacked) {
          // If stacked, we get the width of one rect
          rectWidth = lastRects.filter(
            function(d, i) {
              return i == countSeriesDisplayed - 1;
            }).attr("width");
        } else {
          // If grouped, we need to get the greatest height of each bar
          var nvGroups = svg.selectAll("g.nv-group").selectAll("rect.positive");
          nvGroups.each(
            function(d, i) {
              // Get the Min height space for each group (Max height for each group)
              var rectHeight = parseFloat(d3.select(this).attr("y"));
              if (angular.isUndefined(heightForXvalue[i])) {
                heightForXvalue[i] = rectHeight;
              } else {
                if (rectHeight < heightForXvalue[i]) {
                  heightForXvalue[i] = rectHeight;
                }
              }
            }
          );

          // We get the width of one rect multiplied by the number of series displayed
          rectWidth = lastRects.filter(
            function(d, i) {
              return i == countSeriesDisplayed - 1;
            }).attr("width") * countSeriesDisplayed;
        }

        // We choose a width equals to 70% of the group width
        var labelWidth = rectWidth * 70 / 100;

        var groupLabels = svg.select("g.nv-barsWrap").append("g");

        lastRects.each(
          function(d, index) {
            var transformAttr = d3.select(this).attr("transform");
            var yPos = parseFloat(d3.select(this).attr("y"));
            groupLabels.append("text")
              .attr("x", (rectWidth / 2) - (labelWidth /2)) // We center the label
              // We add a padding of 5 above the highest rect
              .attr("y", (angular.isUndefined(heightForXvalue[index]) ? yPos : heightForXvalue[index]) - 5)
              // We set the text
              .text(labels[index])
              .attr("transform", transformAttr)
              .attr("class", "bar-chart-label");
          });

      }, ANIMATION_TIME);
    }
  };

  // Tooltips
  $scope.toolTipContentFunction = function () {
    return function (key, x, y, e, graph) {
      return labels[x];
    }
};

  $scope.$on('$destroy', function () {
    // Cancel timeout if still active
    $timeout.cancel(promise);
  });

});

UPDATE:

I've created a gist that could help you to implement this by yourself.

https://gist.github.com/topicus/217444acb4204f364e46

Nocturne answered 6/8, 2015 at 21:6 Comment(1)
Thank you so much. I called the code written in gist inside the renderEnd function and now able to see values on the top.Kreisler

© 2022 - 2024 — McMap. All rights reserved.