'C3.js: Assigning a chart height independently of the legend height

(sorry for double posting, but the issue on github was not answered after one week: https://github.com/c3js/c3/issues/2254)

I'm using C3 JS and I have several stacked bar charts showing on the same page. Each one has a different legend, and some legends are quite bigger than others. Since I can only define the height for the whole image, the y-axis of charts with a bigger legend become smaller.

The figure bellow illustrates the issue:

enter image description here

My target is that the y-axis have the same height on both charts. It does not matter the overall chart height (chart+legend).

Is there any possibility of assigning a fixed height to the chart without including the legend height in this size? Maybe should I use D3 directly?

Thanks for the attention and for this awesome library.

best, alan



Solution 1:[1]

I think the only way is to use a custom legend as in the c3js examples (see http://c3js.org/samples/legend_custom.html).

So you have to disable legend:

legend: {
  show: false
}

and create your own legend (for example using d3.js):

function createLegend(chart, container) {
  var dataKeys = Object.keys(chart.internal.data.xs);
  var names = chart.data.names();
	console.log("createLegend", dataKeys, names, chart);
  
  $(container).css("max-height", "none");
  $(container).css("width", chart.internal.currentWidth);
  
  d3.select(container).insert('div', '.chart').attr('class', 'legend').selectAll('div')
    .data(dataKeys)
    .enter().append('div')
    .attr('class', 'legend-item')
    .attr('data-id', function(id) {
      return id;
    })
    .each(function(id) {
      d3.select(this)
      .append('span').style('background-color', chart.color(id)).attr('class', 'legend-box');
      var text = names[id];
      if (!text) text = id;
      d3.select(this)
      .append('span').html(text).attr('class', 'legend-text');
    })
    .on('mouseover', function(id) {
      chart.focus(id);
    })
    .on('mouseout', function(id) {
      chart.revert();
    })
    .on('click', function(id) {
      $(this).toggleClass("c3-legend-item-hidden")
      chart.toggle(id);
      $(container).css("max-height", "none");
    });
}

var chart1 = c3.generate({
  bindto: '#chart1',
  size: {
    height: 250,
    width: 400
  },
  data: {
    x: 'x',
    columns: [
      ['x', 'one', 'two'],
      ['data1', 30, 200],
      ['data2', 130, 100],
      ['data3', 30, 200],
      ['data4', 130, 100],
      ['data5', 30, 1200],
      ['data6', 130, 50],
      ['data7', 30, 20],
      ['data8', 130, 100],
      ['data9', 230, 10]
    ],
    type: 'bar',
    names: {
      data1: 'Long Name 1',
      data2: 'Long Name 2',
      data3: 'Long Name 3',
      data4: 'Long Name 4',
      data5: 'Long Name 5',
      data6: 'Long Name 6',
      data7: 'Long Name 7',
      data8: 'Long Name 8',
      data9: 'Long Name 9',
    },
    groups: [
      ['data1', 'data2', 'data3', 'data4', 'data5', 'data6', 'data7', 'data8', 'data9']
    ]
  },
  axis: {
    x: {
      type: 'category' // this needed to load string x value
    }
  },
  legend: {
    show: false
  },
  onrendered: function() {
  	$('#chart1').css("max-height", "none");
  }
});

createLegend(chart1, '#chart1');
/* only to highlight chart container */
.chart {
  background: lightgray;
  border: solid 1px red;
}

.legend-box {
  display: inline-block;
  margin-left: 7px;
  margin-right: 7px;
  padding: 5px;
}

.legend-item {
  cursor: pointer;
  display: inline-block;
}

.legend-text {
  font-family: "Helvetica";
  font-size:12px;
  white-space: nowrap;
}

.legend {
  width: 100%;
  display: inline-block;
  text-align: center;
  overflow: hidden;
}
<script src="https://code.jquery.com/jquery-3.1.0.min.js" type="text/javascript"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/c3/0.4.18/c3.css" rel="stylesheet" type="text/css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js" type="text/javascript"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/c3/0.4.18/c3.min.js" type="text/javascript"></script>

<div id="chart1" class="chart"></div>

Here is a jsfiddle showing a complete example: https://jsfiddle.net/beaver71/zp7km2L1/

Solution 2:[2]

Maybe a little late, but the current version of C3 have not a feature to do it yet.

You can do it by the next way in javascript:

  1. Set and configure your chart and legend as always (the height will be include the legend height, it is ok).
  2. Get the height of the all chart (without the legend): originalHeightChart.
  3. Hide legend. Important!: make the next steps after hide is finished, else you will get the next height wrongly, like the example.
  4. Get the height of the all chart again: totalHeightChart. Now the legend height is the current height minus the previous: totalHeightChart-originalHeightChart
  5. Calculate the new height: totalHeightChart + ( totalHeightChart - originalHeightChart).
  6. Resize the chart with the new height.
  7. Show the legend.

Done!

Now the javascript code sample:

var yourChartObject = c3.generate( yourJSonDataOrAnyWay );
var width = jQuery('#yourIdChart').find(".c3-chart").get(0).getBBox().width; //to get g group of svg chart
var partialHeight = jQuery('#yourIdChart').find(".c3-chart").get(0).getBBox().height; //to get g group of svg chart
yourChartObject.legend.hide();
var totalHeight = jQuery('#yourIdChart').find(".c3-chart").get(0).getBBox().height; //to get g group of svg chart
var newHeight = totalHeight + (totalHeight - partialHeight);
setTimeout(() => {
    yourChartObject.resize({ 'height': newHeight, 'width': width });
    yourChartObject.legend.show();
},100);

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 beaver
Solution 2