59

So I'm creating a page with tabbed content using Twitter's Bootstrap, yet the content of my starting active div is always different than that of my other tabs. For example, I am putting in charts using highcharts.js in my different tabs, yet the active one always shows correctly while the others have an incorrect width.

Check out the example below:

        <div class = "row-fluid">
        <div class = "span9">
            <div class = "row-fluid">
                <h3>Test</h3>

                    <ul class="nav nav-tabs">
                        <li class = "active">
                            <a data-toggle = "tab" href = "#one">One</a>
                        </li>
                        <li><a data-toggle = "tab" href = "#two">Two</a></li>
                        <li><a data-toggle = "tab" href = "#three">Three</a></li>
                    </ul>

                    <div class="tab-content">

                        <div class="tab-pane active" id="one" >
                            <h4>One</h4>
                            <div id = "container1"></div>
                            <script type = "text/javascript">
                                $(function () {
                                    $('#container1').highcharts({
                                        chart: {
                                        },
                                        xAxis: {
                                            categories: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
                                        },
                                        series: [{
                                            data: [29.9, 71.5, 106.4, 129.2, 144.0, 176.0, 135.6, 148.5, 216.4, 194.1, 95.6, 54.4]        
                                        }]
                                    });
                                });
                            </script>
                        </div>

                        <div class="tab-pane" id="two" >
                        <h4>Two</h4>
                        <div id = "container2"></div>
                        <script type = "text/javascript">
                            $(function () {
                                $('#container2').highcharts({
                                    chart: {
                                    },
                                    xAxis: {
                                        categories: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
                                    },
                                    series: [{
                                        data: [29.9, 71.5, 106.4, 129.2, 144.0, 176.0, 135.6, 148.5, 216.4, 194.1, 95.6, 54.4]        
                                    }]
                                });
                            });
                            </script>
                        </div>

                        <div class="tab-pane" id="three" >
                        <h4>Three</h4>
                        <div id = "container3"></div>
                        <script type = "text/javascript">
                            $(function () {
                                $('#container3').highcharts({
                                    chart: {
                                    },
                                    xAxis: {
                                        categories: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
                                    },
                                    series: [{
                                        data: [29.9, 71.5, 106.4, 129.2, 144.0, 176.0, 135.6, 148.5, 216.4, 194.1, 95.6, 54.4]        
                                    }]
                                });
                            });
                            </script>
                        </div>

                    </div> 
            </div>
        </div>
        <div class = "span3">
            <p>Here is the content on my sidebar</p>
        </div>
    </div>

In this case, switching the active tab on page load makes that chart look correct for me, but the rest always extend all the way to the edge (In the case of my data, which I want to only fill up the space of the span9 div. I just used dummy charts in this case, but it's the same idea.

Here's a JSFiddle: http://jsfiddle.net/dw9tn/

My questions for you all are:

1.) Is this just me?

2.) What exactly is going on when tabs are made active? Looking at the bootstrap css, this seems to deal with display: none and display: block, but I don't have a good understanding of how that works in this case.

3.) Is this a highcharts issue or a bootstrap issue? I noticed this happened to me with non-highchart elements (like showing tweets in the sidebar), so I'm leaning toward bootstrap.

4.) Is there any solution you can find to make everything consistent?

Thanks guys!

VirtualValentin
  • 1,131
  • 1
  • 11
  • 19

6 Answers6

160

The Cause

This is not a Highcharts issue, at least not by itself, the problem is caused by the fact that Bootstrap uses display: none to hide inactive tabs:

.tab-content > .tab-pane /* , .pill-content > .pill-pane */ {
    display: none;      /* this is the problem */
}

This causes Highcharts not able to get the expected width to initialize the chart, thus defaults to 600 (px). This would also happen to any other tools using the same approach to hide content.

A Pure CSS Solution

Instead of working around the issue with an extra redraw or delayed initialization, we can achieve the hidden effect using height: 0; overflow-y: hidden, this way the hidden tab panes are still in place and have valid width:

/* bootstrap hack: fix content width inside hidden tabs */
.tab-content > .tab-pane:not(.active) /* , .pill-content > .pill-pane:not(.active) */ {
    display: block;
    height: 0;
    overflow-y: hidden;
} 
/* bootstrap hack end */

Update: now we directly target inactive tabs via :not(.active), to avoid possible side effect if any.

This solution is seamless, so you no longer have to worry whether the chart is inside a tab-pane or not. And it's efficient as it fixes the width issue in the first place, thus there's no need to workaround the width issue afterward via resize/redraw/reflow using javascript.

Note about CSS Cascade Order

This patch needs to be loaded after bootstrap css, because of CSS cascade order rules, in short, the latter rule wins.

Demo

Before vs After

Tip: switch to the Profile tab to see the difference.

ryenus
  • 15,711
  • 5
  • 56
  • 63
  • 1
    Awesome! I also had to add `!important` after each attribute to overcome the bootstrap priority, but it works perfectly. – Steve Aug 17 '14 at 23:42
  • @Steve, `!important` might be needed if certain style rules or maybe bootstrap itself come after this patch, you can try to change the order. – ryenus Sep 20 '14 at 01:28
  • 1
    Although this worked for me, when i visited the previous tabs the highcharts were hidden and when i hovered over them they got visible – VishwaKumar Dec 22 '14 at 07:16
  • @ryenus, i inspected the element and found this css "svg:not(:root) { overflow: hidden; }" from bootstrap.css which on disabling would make the chart visible. I tried to overwrite the css rule but not able to do so, but I am not sure if this is the reason – VishwaKumar Dec 23 '14 at 13:56
  • @VishwaKumar, what's the bootstrap version? would you mind to setup of fiddle? – ryenus Dec 24 '14 at 02:09
  • I have the same issue with Google charts and tried adding the CSS. It works perfectly for the chart rendering but has the side effect of scrolling the page down on initial load if the initially selected (non-chart) tab has data of the bottom of the viewport. Can this be fixed? – Alan Hay Mar 05 '15 at 20:11
  • 1
    I had to add fix for widths too: width: 0; overflow-x: hidden; and: width: auto; This make sense ? – Ricardo Vigatti Jan 29 '16 at 18:08
  • amazing ... css is a mystery if not handled properly. thanks Ryenus ! – Kings Jan 30 '16 at 19:21
  • This mostly worked for me. However, if I kept the overflow-y:hidden rule in, a little bit of the left edge of the tab content was clipped. Is that rule *really* need? The same solution without that rule seems to work (for me in current situation, not sure if I'd break it later). Secondly, if it is needed, do you have any idea what css styles I should be investigating on the tab-pane to see why it is affecting my site? (disclaimer: css skills are lacking). – Terry Mar 21 '16 at 19:08
  • Looks like the CSS was modified in the answer. If I apply the current stuff it works fine. Thanks. – Terry Mar 29 '16 at 14:21
  • @ryenus The first version of the CSS I had applied display, height, overflow-y to all tabs (it didn't include the :not(.active). I'm not sure if I added extra css selector of .tab-pane.active { height: auto; } or if that was in original answer but that's what I had and things weren't working. Applied as is above works good for Bootstrap 3. Thank. – Terry Mar 29 '16 at 21:08
  • @ryenus , I am having the same issue, I agree with you, the width issue works fine but with the initial display. Try resizing the browser window to mobile view and then toggle the tabs, resize the window back to the monitor view and see the difference by toggling the tabs, one of the graph is collapsed having an irrelevant width. – Bilal Ahmed Aug 03 '16 at 13:15
  • This also solves a messy bug with Firefox which is exposed if you use Bootstrap tabs to display iframe content: https://bugzilla.mozilla.org/show_bug.cgi?id=548397 – d13 Aug 23 '16 at 19:27
  • I had to add `visibility: hidden` in order to hide absolutely positioned child elements in "A Pure CSS Solution". – absynce Mar 06 '17 at 23:17
  • Upvoting this answer is not enough. I have been searching for this for a long time. I feel compelled to comment here and say Thank you, Thank you, Thank you!!! – dotnetengineer May 12 '17 at 15:04
  • Works beautifully, i have been searching for a solution for a while, thank you. – Sai Aug 30 '17 at 09:05
  • Upvoting this, solved a messy bug where i had used bootstrap tabs to split out different tables made with datatables.js, the ones in "hidden" tabs had a mismatch in width between headings/data rows. – Zerk Nov 02 '17 at 15:02
  • Thank you my friend, helped-me – Vinicius Aquino May 11 '18 at 14:00
  • Working as expected! – AwesomeUserName Jun 04 '21 at 20:15
30

The problem is that Highcharts can not calculate width of the container which has CSS: display:none, so applied is default width (600px). In fact, browser is not able to calculate width for such elements.

For example when you will use separator to resize Result window, chart will resize and will work. So you have three options:

  • render chart after first click on that tab
  • render chart at the beginning, but after each click and window resize use chart.setSize(w,h) with new width and height
  • render chart at the beginning, but after tab click call chart.reflow()
Paweł Fus
  • 44,795
  • 3
  • 61
  • 77
  • Thanks man. I figured I would have to override the width somehow, but it's unfortunate that there isn't an easier way around this. I noticed that this happened to other bootstrap elements (media-list), so I'll find a fix soon. – VirtualValentin Jun 20 '13 at 19:43
  • 1
    There are many posts about this on SO, yet it took forever to find this post, the only one with a good solution. So here are some keywords to help make it more visible: highcharts, bootstrap, redraw, hidden, tab, reflow, update, width, height, breaks out of parent, css, display:none, re-render, rerender, trigger redraw, resize, responsive, bootstrap 3 – metaColin Oct 10 '14 at 20:23
5

Based on the Pawel's answer I render the chart at beginning and then resize the chart.

The code used (with bootstrap 3.0.3 and HighCharts 3.0.7)

$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
    $('#container').highcharts().setSize($('#container').width(),$('#container').height());
})

Source: http://getbootstrap.com/javascript/#tabs-usage

w3spi
  • 4,380
  • 9
  • 47
  • 80
oaamados
  • 666
  • 10
  • 17
5

Based on Paweł's answer I've made a working example:

http://codepen.io/colinsteinmann/pen/BlGfE

Basically the .reflow() function is called on each chart when a tab is clicked. That way the chart gets redrawn based on the new dimensions which are applied to the container when it becomes visible.

This is the most important snippet of code:

// fix dimensions of chart that was in a hidden element
jQuery(document).on( 'shown.bs.tab', 'a[data-toggle="tab"]', function (e) { // on tab selection event
    jQuery( ".contains-chart" ).each(function() { // target each element with the .contains-chart class
        var chart = jQuery(this).highcharts(); // target the chart itself
        chart.reflow() // reflow that chart
    });
})
metaColin
  • 1,973
  • 2
  • 22
  • 37
2

I found an easier solution in another post. Basically, the problem comes because Highcharts cannot render on a div with 'display: none' set. The solution is just write a fix in your CSS stylesheet.

.tab-content > .tab-pane,
.pill-content > .pill-pane {
    display: inline;
    visibility: hidden;
    position: absolute;
    width: 930px;
}

.tab-content > .active,
.pill-content > .active {
    display: inline;
    visibility: visible;
    position: absolute;
    width: 930px;
}

This is going to make the tabs to work with visibility attribute instead of display. Charts will render well.

Hatchet
  • 5,320
  • 1
  • 30
  • 42
Joel Luevano
  • 121
  • 1
  • 2
  • 9
  • When I tried this, 'heights' of tabs weren't calculated right. For example, my containing tab-content div which had a border styled on it, was not large enough to contain all the content of the tab. – Terry Mar 28 '16 at 18:04
-1

The in-line style "width" is different for the charts. I'm not sure when and how are those calculated, but you can override those in css for example.

Balint Bako
  • 2,500
  • 1
  • 14
  • 13