I am using amCharts (which uses Raphaël behind the scenes) to render some charts as SVG; and have noticed that if the SVG is rendered in an initially-invisible div, the browser does not immediately render the image when the div becomes visible. If I modify the display however, e.g. by resizing the browser or Ctrl-mousewheel zooming, the SVG image is then rendered as expected when the page is redrawn.
The exact method of div visibility switching is via Bootstrap's tabbed navbar.
I admit to not being very experienced with SVG - is this an issue with the browsers' rendering, or amCharts' SVG markup, or am I required to explicitly call some sort of repaint method when I can tell the visibility of an SVG has changed?
Here's a jsFiddle which illustrates the problem; if you switch to Section 2 (in Chrome, Firefox) the chart isn't visible initially. Resizing the display causes it to appear.
I've found the reason for both the initial behaviour and the workaround - and it's all amCharts specific (nothing to do with SVG per se) so I'm rephrasing the title accordingly.
What happens is that when amCharts creates the SVG, it needs to (or at least, decides to) define the width and height in absolute terms. These are based on the size of the target div, obtained via the offsetWidth
and offsetHeight
properties.
The inactive tab has the display: none
property set, and as a result this part of the DOM is not even rendered, so returns zero for both size properties. This ultimately leads to amCharts creating a 0x0 SVG chart when chart.write
is called for the hidden div.
Resizing fixes things because each chart registers a listener to the onresize
window event, which calls the chart's handleResize
method. This forces a recalculation of the width and height based on the div's new (current) dimensions.
So in conclusion I think there are two alternative ways to handle this:
chart.write
for a chart when and only when its tab becomes visible.handleResize
method when the tabs change.(The first option avoids the initial hit of rendering an invisible chart, but then does a full redraw every time the tabs are changed. The latter takes a hit up-front but is likely quicker thereafter. For bonus marks, the best solution would be to render each chart exactly once between each resize, the first time it becomes visible, but that's a lot more complex as it would involve interfering with the default event listeners, amongst other things.)
Update: There's further complications with rendering an invisible chart; in particular, I found issues with the height calculations not taking into account the space required by the domain axis and so stretching the chart out of its div. This wasn't fixed by calling handleResize
- calling measureMargins
beforehand looked like it should work but didn't. (There's probably another method one could call after this to make it work such as resetMargins
but at this point it started to feel very flaky...)
As such I don't think it's practical to render a chart for the first time on a non-visible div, so I went with some combination of the bullets above. I listen for when a chart's tab becomes visible for the first time and then call chart.write
for the appropriate chart object - and whenever the tabs change, all previously-rendered charts are told to handle the resize.
* Edited *
Here is a updated fiddle. The Canvas will only be rendered once the tab is shown. I store the chartdiv ids in an array and check whether there are in it or not.
* Edited *
The only solution I found was to show the Graph after the specific tab is shown.
As you see in this jsFiddle.
var tabs = $('.tabbable').tab() tabs.on('shown', function(e){ id = $(e.target).attr('href'); chartdiv_id = $(id).find('.chartdiv').attr('id'); doChart(chartdiv_id, true); });
I guess it isn't exactly what you are looking for, but i hope it helps for the moment.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With