NVD3 chart fails to calculate legend text length in Chrome, since Window.getComputedStyle does not return font-size correctly
Asked Answered
L

1

49

Background Information

I created an integration of NVD3 charts into Eclipse-RAP using its custom widget framework. The chart is generated into a div. The CSS is loaded dynamically by creating a link entry in javascript. I check if the CSS is already loaded by creating an SVG/text element, and I check if its font-size is ok or not (see https://mcmap.net/q/356906/-how-to-check-if-a-css-rule-exists). If the CSS is loaded, I create the chart.

Problem

For some reason the chart is not rendered always correctly in Chrome. Usually first time in my session it is shown correctly, but second time it is rendered always wrong. For the wrong case I have found this in the console:

Error: Invalid value for <g> attribute transform="translate(NaN,5)"

If I make the chart redraw (for example by updating the chart data or resizing), the legend is rendered correctly.

Expected: enter image description here

Wrong layout: enter image description here

After some debugging I have found the relevant d3 code part. NVD3 asks for the font size for an SVG Text element using this function:

  d3_selectionPrototype.style = function(name, value, priority) {
    var n = arguments.length;
    if (n < 3) {
      if (typeof name !== "string") {
        if (n < 2) value = "";
        for (priority in name) this.each(d3_selection_style(priority, name[priority], value));
        return this;
      }
      if (n < 2) return d3_window.getComputedStyle(this.node(), null).getPropertyValue(name);
      priority = "";
    }
    return this.each(d3_selection_style(name, value, priority));
  };

The relevant CSS part is this:

svg text {
  font: normal 12px Arial;
}

I added the following "printpoint" (conditional breakpoint, which never stops, but prints out values) on the line with the getComputedStyle call:

name == 'font-size' &&
(
    console.log(this.node()) ||
    console.log( d3_window.getComputedStyle(this.node(), null) ) ||
    console.log( d3_window.getComputedStyle(this.node(), null).getPropertyValue(name) ) || 
    console.log( window.getMatchedCSSRules(this.node()) )
)

The result is really weird. If the chart is correct, I find this in the console for correct layout: enter image description here

And this for wrong layout: enter image description here

This is the DOM for the wrong layout:

<svg id="ujdh846lhqubvvlg2jbh16s6q9" width="1896" height="361">
    <g class="nvd3 nv-wrap nv-pieChart" transform="translate(20,90)">
        <g>
            <g class="nv-pieWrap">
                <g class="nvd3 nv-wrap nv-pie nv-chart-6450" transform="translate(0,0)">
                    <g>
                        <g class="nv-pie" transform="translate(928,125.5)">
                            <g class="nv-slice" fill="#1f77b4" stroke="#1f77b4">
                                <path d="M6.1477269317197136e-15,-100.4A100.4,100.4 0 0,1 65.39779726531111,76.17931551835622L0,0Z"/>
                            </g><g class="nv-slice" fill="#ff7f0e" stroke="#ff7f0e">
                                <path d="M65.39779726531111,76.17931551835622A100.4,100.4 0 0,1 -90.13957577290248,44.21557281638648L0,0Z"/>
                            </g><g class="nv-slice" fill="#2ca02c" stroke="#2ca02c">
                                <path d="M-90.13957577290248,44.21557281638648A100.4,100.4 0 0,1 -94.15031406756688,-34.869447385619964L0,0Z"/>
                            </g><g class="nv-slice" fill="#d62728" stroke="#d62728">
                                <path d="M-94.15031406756688,-34.869447385619964A100.4,100.4 0 0,1 -1.844318079515914e-14,-100.4L0,0Z"/>
                            </g>
                        </g><g class="nv-pieLabels" transform="translate(928,125.5)">
                            <g class="nv-label" transform="translate(112.95224431711586,-41.8329177051586)">
                                <rect rx="3" ry="3" style="stroke: rgb(255, 255, 255); fill: rgb(255, 255, 255);"/>
                                <text style="text-anchor: middle; fill: rgb(0, 0, 0);">alma</text>
                            </g><g class="nv-label" transform="translate(-24.246406744679096,117.98438142386297)">
                                <rect rx="3" ry="3" style="stroke: rgb(255, 255, 255); fill: rgb(255, 255, 255);"/>
                                <text style="text-anchor: middle; fill: rgb(0, 0, 0);">korte</text>
                            </g><g class="nv-label" transform="translate(-120.2954032887533,6.100692386622933)">
                                <rect rx="3" ry="3" style="stroke: rgb(255, 255, 255); fill: rgb(255, 255, 255);"/>
                                <text style="text-anchor: middle; fill: rgb(0, 0, 0);">szilva</text>
                            </g><g class="nv-label" transform="translate(-68.80925650816773,-98.86095649341644)">
                                <rect rx="3" ry="3" style="stroke: rgb(255, 255, 255); fill: rgb(255, 255, 255);"/>
                                <text style="text-anchor: middle; fill: rgb(0, 0, 0);">paradicsom</text>
                            </g>
                        </g>
                    </g>
                </g>
            </g><g class="nv-legendWrap" transform="translate(0,-90)">
                <g class="nvd3 nv-legend" transform="translate(0,5)">
                    <g transform="translate(NaN,5)">
                        <g class="nv-series" transform="translate(0,5)">
                            <circle class="nv-legend-symbol" r="5" style="stroke-width: 2px; fill: rgb(31, 119, 180); stroke: rgb(31, 119, 180);"/>
                            <text text-anchor="start" class="nv-legend-text" dy=".32em" dx="8">alma</text>
                        </g><g class="nv-series" transform="translate(0,25)">
                            <circle class="nv-legend-symbol" r="5" style="stroke-width: 2px; fill: rgb(255, 127, 14); stroke: rgb(255, 127, 14);"/>
                            <text text-anchor="start" class="nv-legend-text" dy=".32em" dx="8">korte</text>
                        </g><g class="nv-series" transform="translate(0,45)">
                            <circle class="nv-legend-symbol" r="5" style="stroke-width: 2px; fill: rgb(44, 160, 44); stroke: rgb(44, 160, 44);"/>
                            <text text-anchor="start" class="nv-legend-text" dy=".32em" dx="8">szilva</text>
                        </g><g class="nv-series" transform="translate(0,65)">
                            <circle class="nv-legend-symbol" r="5" style="stroke-width: 2px; fill: rgb(125, 0, 0); stroke: rgb(125, 0, 0);"/>
                            <text text-anchor="start" class="nv-legend-text" dy=".32em" dx="8">paradicsom</text>
                        </g>
                    </g>
                </g>
            </g>
        </g>
    </g>
</svg>

How can it be that once my SVG/Text has no font-size in computed style BUT it always has the font-size in one of the applied CSS rules?

Is there some known bug in Chrome for this?

Note, that in Firefox everything works fine.

Environment Details

Chrome 39.0.2171.71 (64-bit)

Kubuntu 3.13.0-29-generic

Update

I thought I am affected by this "behaviour" of the browsers : How can I change the default behavior of console.log? (*Error console in safari, no add-on*) . This means that the console does not show the state of the object at the time point of the log entry, but refers to the current state. So I made a small experiment here: http://jsfiddle.net/hdv7ty6L/ . I change the class from javascript and I check if the rule list changes in the console or not. And it seems to be a snapshot of the rule list. So still no clue, what is wrong here :)

Test code:

document.body.className='redbody';
console.log(window.getMatchedCSSRules(document.body));
document.body.className='bluebody';
console.log("Class changed");
console.log(window.getMatchedCSSRules(document.body));

Console output: enter image description here

Update 2

The problem happens also if the CSS is completely static and not loaded dynamically.

Update 3

I tried to reproduce it in a jsfiddle: dynamicly created SVG inside a div with asynchronously created chart (onclick of a button). The error does not show up unfortunately. https://jsfiddle.net/ewsb4d9k/1/

Lymphadenitis answered 7/1, 2015 at 11:53 Comment(16)
Excellent very detailed question: I want to know the answer. Since it requires some work to answer properly I would consider a bounty when it is eligible. It does seem like a possible bug.Egor
Looks like you are loading the chart into "tabs", is it possible race condition between the creation or showing of the parent tab and the child graph?Dona
@Dona I don't think so. For other reasons (waiting for CSS loading) the tab showup already creates the chart "lazy" using setTimeout and a callback. As far as I understand javascript is single threaded and the tab creation for this reason surely completes before the asynchronous job of chart creation is scheduled for execution.Perennial
i'm loading the charts in tabs and getting the same error :(Yan
Please provide information about where the javascript is being loaded from (e.g. head, body) for the JS generated CSS, libraries (d3, nvd3 etc.) and graph.Typify
@JasonAusborn: It is not trivial. I cannot post my whole project, since it is a commercial software, and its not my property. The CSS is not generated anymore. It was static. NVD3 version used is github.com/novus/nvd3/commit/0c03b27 with my patch applied on it (github.com/liptga/nvd3/commit/…), D3 version is 3.1.5.Perennial
In my experience, once you stick a NaN in an SVG attribute, all bets are off about the rest of the rendering. Could you post just a small snippet of the code that sets the translate on that inner <g>? It looks like you have a race condition there, and I've dealt with stuff like that in the past.Acculturize
link to applied patch is 404 - has this problem been reproduced in any plunker/fiddle?Consignment
Which link do you mean by "link to applied patch is 404"? Unfortunately not reproduced in any fiddle. I have a workaround in which I call update on the chart two times in case of chrome, and that works (a small flickering is still visible, bu I can live with that. I am pretty sure, that creating a fiddle for the problem would take a lot of time, which I cannot invest right now (Its a problem at my workplace, and with the workaround it does not have that much priority anymore).Perennial
I see now. The patch is at github.com/liptga/nvd3/commit/… .Perennial
I have to concur with @Milimetric. You need to isolate the source of your NaN. CSS being last in wins, your static CSS demonstrates that runtime rendering is encountering an issue and that NaN would certainly be a problem.Nystrom
I work with svg a lot, I've come across this with d3...I really wish I still remembered what the problem/solution was, it might have been something like the styles not being applied yet when the element was rendered. I just solved the problem and moved on, probably by setting a js variable with font-size that I pulled from the stylesheet at page load time.Monteverdi
Are you dynamically changing or otherwise touching legend.margin?Distich
@Distich No. I just use the out of the box functionality.Perennial
I imagine that the NaN is coming from the x assignment in the following code in legend.js (line 151/182): g.attr('transform', 'translate(' + (width - margin.right - legendWidth) + ',' + margin.top + ')');. Perhaps try setting a breakpoint there and checking those variables in nv.d3.js or console.log(chart.legend.width()) and console.log(chart.legend.margin())Distich
@Distich - yes, indeed. That is the result of getting no font-size, like it is described above. The code you have shown is the result, not the cause as far as I remember and understand.Perennial
H
6

Sorry, I'm not very fluent with D3, but a few ideas off the top of my head that might be of some help.

Have you tried using the d3.select() method and applying solely the font-size this way, to see if you can narrow down that the font/text selector combination is problem? Maybe assign an id or class on load, then define your styles using a static stylesheet.

Have you noticed anything weird prior to the legend text length breaking? Does removing the legend and font css work 100% of the time?

I noticed you're using adblock. It's worth a shot to disable it, if you haven't already. That plugin does crazy things sometimes.

Have you tried a full dom refresh, or container refresh, on load? What happens with this? Does it render 100% of the time? Still fail?

$("body").html($("body").html()); 
$("#d3div").html($("#d3div").html());

as shown from Timo in this thread jquery's append not working with svg element?

"it does seem to add them in the DOM explorer, but not on the screen" and the reason for this is different namespaces for html and svg.

The easiest workaround is to "refresh" whole svg.

It doesn't look like you were working with jQuery on this but it might be useful for testing in this case.

Sorry to hear about your crazy bug. Hope you find a solution.

Helmsman answered 23/4, 2015 at 3:22 Comment(3)
I use no JQuery. Adblock can make sense. The SVG element was not shown at all if I created that with a wrong namespace. Its already fixed, so that is not a problem. I am not sure what you mean by full dom refresh. I am using Eclipse RAP, its not really an option there as far as I understand. Manipulating the font-size with d3 is an option which I wanted to avoid. But the real problem is still there: how can it be, that a style sheet is applied according to the browser, but the style element is not. That is the question. Thanks for your answer anyway.Perennial
While this answer doesn't really solve the issue, I have awarded you the bounty for your efforts in attempting to help troubleshoot/solve the issue.Salvador
Thank you kind sir. Have a great weekend.Helmsman

© 2022 - 2024 — McMap. All rights reserved.