Highcharts: Flex container doesn't resize charts properly, if you have several charts inside one flex container.

Created on 2 Mar 2017  ·  18Comments  ·  Source: highcharts/highcharts

Expected behaviour

If you have a flex container with css
display: flex;
flex-direction: row;

and 4 children with
flex-grow: 1;
flex-basis:25%;
You expect to have one row with 4 charts, each size of 25% of the parent.

Actual behaviour

Actual behaviour depends on the size of the parent it might be
4 items in one row with 25% (expected behaviour)
3 items of small size in one row and 100% item on the next row.
4 rows with 100% items inside it.

Live demo with steps to reproduce

http://jsfiddle.net/xLz8tcrc/1/

Affected browser(s)

Chrome
Firefox

Undecided

Most helpful comment

@KacperMadej
well, svg is a vector graphics. That means putting SVG width and height to 100% should not have any bad effect on rendering, correct me if i'm wrong.

I managed to override the inner svg element width CSS:

highcharts-chart::ng-deep {
  .highcharts-container,
  .highcharts-container svg {
     width: 100% !important;
     height: 100% !important;
  }
}

It works reasonably well now.

All 18 comments

In your demo, I added a snippet to run chart.reflow() after resizing the flex box. This is necessary, as the charts only reflow automatically when the _window_ resizes.

Also, adding overflow: hidden to the chart containers makes sure that the container can be smaller than the chart, which prevents them from being too wide when scaling down.

Does this resolve your problem? http://jsfiddle.net/xLz8tcrc/2/

Thanks for your reply.
Using chart.reflow() looks more like a workaround than a fix to the original problem. It will be much simpler if HighCharts will resize properly according to CSS rules of the parent element.

I've posted just an example, in real use case we want charts to resize on window resize or parent element resize, so it's not so easy to implement logic where you can call chart.reflow(). For Window resize it's more a less straightforward, just using window resize event.
For other components, we need to implement some kind of a resizable interface for a parent component, so we can call chart.reflow on resize, that might work in some cases, but not sure about all of them.

Can you please not close the ticket for now, I'll try to implement your approach or maybe someone has other ideas.

What you're describing is basically the same problem we have internally in Highcharts. We currently do automatic reflows on window resize. Ideally we would like to do automatic reflows on containing element resize too, but there is currently no effective way to listen to DOM element size changes.

See http://stackoverflow.com/questions/6492683/how-to-detect-divs-dimension-changed for a discussion, and the same topic has been discussed previously in this forum too. As mentioned in the thread, the only possible way currently seems to be using setTimeout or requestAnimationFrame, both of which are wasting CPU.

Did you have a look at the ResizeSensor mentioned in the StackOverflow post you linked to (https://github.com/marcj/css-element-queries)? It uses a couple of extra DOM elements and a scroll event listener to detect resizes. It only uses requestAnimationFrame to limit how many resize events are sent (max 1 per frame).

There is also http://www.backalleycoder.com/2013/03/18/cross-browser-event-based-element-resize-detection/ which describes how to accomplish this using an object tag, which coincidentally fires a resize event when its size changes.

In the future, there will be ResizeObserver implemented in the browsers but that is not feasible right now. There are several polyfills for that listed at https://github.com/WICG/ResizeObserver/issues/3 but they seem to rely on MutationObeserver and polling or other wasteful approaches.

In my opinion Highcharts shouldn't implement tricks like this in the core. The window resize listener covers most cases, then we have the chart.reflow hook to allow implementers to cover other cases, when an element resizes without a window resize. So if you tested one of the approaches above and it works for your case, I suggest you use that together with chart.reflow.

+1 We just hit this issue, took forever to realize the problem was flexbox.

one of the reasons why I stop using flex ... resizing never works as expected , same shit for display grid

display inline block is far better

one of the reasons why I stop using flex ... resizing never works as expected , same shit for display grid

this is just another workaround.

Is there any updates on this? Is there any plans on fixing this @TorsteinHonsi ?

I'm starting to use Highcharts in Angular projects and I've run into this as well.
What I don't get is the chart is actually SVG. So there should not be any problem to put it to 100% size. Instead it looks like the width & height is explicitly set directly to svg.

capture

Hi @GeorgeKnap

Could you provide a live demo with Highcharts and a non-Highcharts SVG to show what do you have in mind? How will this influence scaling and pixel precision of sharp rendering?

@KacperMadej
well, svg is a vector graphics. That means putting SVG width and height to 100% should not have any bad effect on rendering, correct me if i'm wrong.

I managed to override the inner svg element width CSS:

highcharts-chart::ng-deep {
  .highcharts-container,
  .highcharts-container svg {
     width: 100% !important;
     height: 100% !important;
  }
}

It works reasonably well now.

Thank you for sharing the solution.

The problem with scaling SVG is that this could cause blurry rendering as pixel precision could be lost. Another issue for a general Highcharts solution is support for legacy browsers (it's not a blocker, we'll just need to be sure that we have a proper polyfill).

I've done a deep dive in this issue and found three issues:

  1. When determining the size of the container, Highcharts uses scrollWidth, which is rounded up in case the real width is sub pixel. In the original fiddle, this results in the first chart being drawn a fraction of a pixel too wide, and the flex layout jumping from 4 columns to 3 columns. This is fixed in the commit above by using Math.floor and element.getBoundingClientRect.

  2. When scaling down the width of the page or the container, the charts would be wider than the 25% assigned to them, thereby breaking the layout. This is fixed in the commit above by adding overflow:hidden to the top-level chart container.

  3. When scaling up or down the flex container _without_ resizing the window, the charts won't scale. They still won't, but at least now they don't break the layout. @GeorgeKnap's solution may work for some, but as Kacper says it may result in blurry charts, plus it will also resize the height, it would disrespect font sizing etc. See http://jsfiddle.net/highcharts/xLz8tcrc/219/ and the same setup with an explicit chart.reflow call: http://jsfiddle.net/highcharts/xLz8tcrc/221/. The desired solution would be to listen to an event when the container div is resized, but for now the only native such event is Chrome's ResizeObserver.

My CSS override solution above caused major problems with mouse over (tooltip) position. To fix it, I removed the CSS override completely and implemented ResizeObserver. It observes the size on parent element and change width and height properties directly in chart options:

This is the Angular solution so there is used ElementRef to acces native DOM element and ChangeDetectorRef to trigger change check:

    const resizeObserver = new ResizeObserver(debounce((data: Array<ResizeObserverEntry>) => {
      if (this.chartOptions) {
        const options = { ...this.chartOptions } as Highcharts.Options;
        options.chart!.height = data[0].contentRect.height;
        options.chart!.width = data[0].contentRect.width;
        this.chartOptions = options;
        this.cd.markForCheck();
      }
    }, 200));
    resizeObserver.observe(this.elementRef.nativeElement.children[0]);

Thanks for sharing! Here's a general plugin that responds to the chart.reflow option and unbinds the built-in window.onresize handler if ResizeObserver is supported:

// Generic plugin that adds support for listening to container resize rather than
// window resize. As of 2018 only Chrome supports ResizeObserver.
Highcharts.wrap(Highcharts.Chart.prototype, 'setReflow', function(proceed, reflow) {

    var chart = this;

    proceed.call(this, reflow);

    if (reflow !== false && typeof ResizeObserver === 'function') {

        // Unbind window.onresize handler so we don't do double redraws
        if (this.unbindReflow) {
            this.unbindReflow();
        }

        var ro = new ResizeObserver(function () {
            chart.reflow();
        });

        ro.observe(this.renderTo);
    }
});

View it live on jsFiddle.

Chart1
chart2
Hi,

I am new to using highcharts, in those attached screen shots, you could clearly see that the chart is exceeding the width of the container. i have many charts in the same page which open when i click on the accordion. On every initial load, the chart width is exceeding it's container width.
is there any work around for this problem?

below is the code regarding the accordion.

function initsectionclicks(container) {
$('.fe-section-accordion-header', container).click(function () {
var parentcontainer = $(this).parent();
var accordioncontainer = $(parentcontainer).find('.fe-section-accordion-container');

        if (accordioncontainer != null)
        {
            var accordioncontainerOpen = accordioncontainer.is(':visible');

            accordioncontainer[accordioncontainerOpen ? 'slideUp' : 'slideDown'](400, "swing", function() {
                ToolkitUI.Common.ResizeIFrame();
                var highChartsWrapper = $('.fe-section-chart-wrap', accordioncontainer);
                if (highChartsWrapper.length) {
                    $.each(highChartsWrapper,
                        function(index, obj) {
                            var hcObj = $(obj).highcharts();
                            if (hcObj)
                                hcObj.reflow();
                        });
                }
            });

            if (!accordioncontainerOpen)
            {
                $(accordioncontainer).ShowLoadingInsideContainer();
            }

            $(parentcontainer).find('.fe-expandcollapse').toggleClass("fe-icon-expand", accordioncontainerOpen).toggleClass("fe-icon-collapse", !accordioncontainerOpen);
            $('.fe-section-accordion-header', parentcontainer).toggleClass('highlight', !accordioncontainerOpen);
        }

        $(window).resize();
    });

    //we have to unbind any click event handlers before attaching a new one, as without unbind we will be attaching the same handler multiple times.
    //this is because we are binding the data from the start for each tab when it is clicked, which rebinds the same method again and again.
    $('.fe-accordion-expandcollapse-all', container).unbind('click').click(function () {
        var isallopen = $(this).hasClass('fe-accordion-collapse');
        var counter = 1;
        $(this).toggleClass('fe-accordion-expand', isallopen).toggleClass('fe-accordion-collapse', !isallopen);
        $('.fe-expandcollapse', container).toggleClass('fe-icon-expand', isallopen).toggleClass('fe-icon-collapse', !isallopen);
        $('.fe-section-accordion-header', container).toggleClass('highlight', !isallopen);
        $('.fe-section-accordion-container')[isallopen ? 'slideUp' : 'slideDown'](400, "swing", function () {
            if (counter == $('.fe-section-accordion-container').length) {
                ToolkitUI.Common.ResizeIFrame();
            }
            counter++;
        });

        if (!isallopen)
        {
            $('.fe-section-accordion-container').ShowLoadingInsideContainer();

        }
        $(window).resize();
    });
};

@Nagaraj2106

Please add live demo for the problem, so we could recreate the problem, debug it and test possible solutions and workarounds. Also, please check the suggested workaround in this comment: https://github.com/highcharts/highcharts/issues/6427#issuecomment-438212322

Neither calling reflow() on window resize nor using the plugin solved the problem for me; however this answer from SO did.

I'm using highcharts in a bootstrap 4 flex container. The chart resizes up (wider) but doesn't resize down unless I make the container absolute:

<div id="chart-container">
    <div id="chart"></div>
</div>

with the CSS:

#chart-container {
    position: relative;
    width: 100%;
    height: 280px;
}

#chart {
    position: absolute;
    width: 100%;
}

I've attached the chart to #chart with renderTo: 'chart'.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

deanshub picture deanshub  ·  3Comments

zhy1stgg picture zhy1stgg  ·  3Comments

sebastianbochan picture sebastianbochan  ·  3Comments

oysteinmoseng picture oysteinmoseng  ·  3Comments

balupton picture balupton  ·  3Comments