Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Invalid value for <circle> attribute cx=“NaN” using D3.js

I'm trying to create a form of scatterplot. I have a custom x-axis and a specific scale for both a-axis. I have implemented a zoom functionality for it as well. So far everything is fine, but when I finally try to plot my data as circles I get two errors:

Errors when trying to draw circles .

My graph can be viewed on this website: http://servers.binf.ku.dk/hemaexplorerbeta/ (the circles are huge, because I want to make sure I know approximately where they are before I style them)

I create my circles based on data read from a MYSQL server. I have checked all my data and the numbers are correct. They are either being plotting wrongly or my scales/zoom are faulty.

Also you might notice I create my axis and scale with some values initially and change them in some functions right after. This is due to the fact that I'm planning on loading an empty graph on the website where the user can decide what dataset to load it, where the functions will have to customize both scales and axes to the data being loaded.

I've pasted my source code below:

    //Setting generic width and height values for our SVG.
    var margin = {top: 60, right: 0, bottom: 70, left: 40},

        genWidth = 1024;
        genHeight = 768;

        width = genWidth - 70 - margin.left - margin.right,
        height = genHeight - 100 - margin.top - margin.bottom;

     //Other variable declarations.
     var valueY = 0;
     var graphData = Array();


    //Creating scales used to scale everything to the size of the SVG.
    var xScale = d3.scale.linear()
        .domain([0, genWidth])
        .range([0, width-margin.right]);

    var yScale = d3.scale.linear()
        .domain([0, genHeight])
        .range([height, margin.bottom]);

    var xAxis = d3.svg.axis()
        .scale(xScale)
        .orient("bottom");

    var yAxis = d3.svg.axis()
        .scale(yScale)
        .orient("left");

    //Zoom command ...
    var zoom = d3.behavior.zoom()
        .x(xScale)
        .y(yScale)
        .scaleExtent([1,10])
        .on("zoom", zoomTargets);

    // The mark '#' indicates an ID. IF '#' isn't included argument expected is a tag such as "svg" or "p" etc..
    var SVG = d3.select("#mainSVG")
                .attr("class", "SVG")
                .attr("width", width + margin.left + margin.right)
                .attr("height", height + margin.top + margin.bottom)
                .attr("pointer-events", "all")
                .append("g")
                .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

    //This creates a body with a clippath inside the svg where all element in the graph will be. This prevents elemnts on the graph to go past the axis.
    var SVGbody = SVG.append("g")
        .attr("clip-path", "url(#clip)")
        .call(zoom);


    //Create background. The mouse must be over an object on the graph for the zoom to work. The rectangle will cover the entire graph.
    var rect = SVGbody.append("rect")
        .attr("width", width)
        .attr("height", height);

    //Showing the axis that we created earlier in the script for both X and Y.
    SVG.append("g")
        .attr("class", "x axis")
        .attr("transform", "translate(0," + height + ")")
        .call(xAxis)
            .selectAll("text")
                .style("text-anchor", "end")
                .attr("transform", function(d) {
                return "rotate(-30)" 
                });;

    SVG.append("g")
        .attr("class", "y axis")
        .call(yAxis);


    d3.json("getdata.php?type=load&gene=CCL5&data=human", function(error, data) {
        var arrayValues = [];
        if(error){ return console.log(error); }

        data.forEach( function(d) { 
            arrayValues.push(d.gene_name);
            valueY = getValueY(d.gene_data);
            var string = JSON.stringify(d.gene_data);
            graphData.push(string.split(" "));
        });

        //console.log(graphData);

        arrayValues = removeDuplicatesInPlace(arrayValues);
        updateScaleX(arrayValues.length);
        updateAxisX(arrayValues);
        //console.log(arrayValues);

        updateScaleY(valueY);

        //This selects 4 circles (non-existent, there requires data-binding) and appends them all below enter.
        //The amount of numbers in data is the amount of circles to be appended in the enter() section. 
        for(var i = 0;i <= graphData.length;i++){
            var circle = SVGbody
                .selectAll("circle")
                .data(graphData[i])
                .enter()
                    .append("circle")
                    .attr("cx",function(d){return xScale((i*100)+100);})
                    .attr("cy",function(d){return yScale(d)})
                    .attr("r",20);
        }



    });

    //Clipping is defined here used to prevent elements from the graph from going past the axis.
    var clip = SVG.append("defs").append("svg:clipPath")
        .attr("id", "clip")
        .append("svg:rect")
        .attr("id", "clip-rect")
        .attr("x", "0")
        .attr("y", "0")
        .attr("width", width)
        .attr("height", height);

    //Resets zoom when click on circle object. Zoom work now, should be changed to a button instead of click on circle though.
    SVG.selectAll("circle").on("click", function() {
        zoom.scale(1);
        zoom.translate([0,0]);
        zoomTargets();
    });

    //The function handleling the zoom. Nothing is zoomed automatically, every elemnt must me defined here.
    function zoomTargets() {

        var translate = zoom.translate(),
        scale = zoom.scale();

        tx = Math.min(0, Math.max(width * (1 - scale), translate[0]));
        ty = Math.min(0, Math.max(height * (1 - scale), translate[1]));

        //This line applies the tx and ty which prevents the graphs from moving out of the limits. This means it can't be moved until zoomed in first.
        zoom.translate([tx, ty]);

        SVG.select(".x.axis").call(xAxis)
            .selectAll("text")
                .style("text-anchor", "end")
                .attr("transform", function(d) {
                return "rotate(-30)" 
                });
        SVG.select(".y.axis").call(yAxis);
        SVG.selectAll("circle").attr("cx",function(d){return xScale(d)}).attr("cy",function(d){return yScale(d)});
    }

    function resetZoom() {
        zoom.scale(1); 
        zoom.translate([0,0]);
        zoomTargets();
    }

    function updateAxisX(arr) {
        var formatAxis = function(d, i) { return arr[i]; }

        xAxis = d3.svg.axis()
            .scale(xScale)
            .orient("bottom")
            .tickValues(createTickValuesArray(arr.length))
            .tickFormat(formatAxis);

        SVG.select(".x.axis")
            .call(xAxis)
            .selectAll("text")
                .style("text-anchor", "end")
                .attr("transform", function(d) {
                return "rotate(-30)" 
                });
    }

    function updateScaleX(newWidth){

        genWidth = newWidth;
        xScale = d3.scale.linear()
            .domain([0, (newWidth*100)+50])
            .range([0, width-margin.right]);

        SVG.selectAll("circle").attr("cx",function(d){return xScale(d)}).attr("cy",function(d){return yScale(d)});
        zoom.x(xScale);
    }

    function updateScaleY(newHeight){

        console.log(newHeight);
        var yScale = d3.scale.linear()
            .domain([0, newHeight])
            .range([height, margin.bottom]);

        yAxis = d3.svg.axis()
            .scale(yScale)
            .orient("left");

        SVG.select(".y.axis").call(yAxis);
        SVG.selectAll("circle").attr("cx",function(d){return xScale(d)}).attr("cy",function(d){return yScale(d)});
        zoom.y(yScale);
    }

    function createTickValuesArray(amountOfTicks){
        var tickValuesArr = [];
        for(var i = 1;i<=amountOfTicks;i++){
            tickValuesArr[i-1] = 100*i;
        }
        return tickValuesArr;
    }

    function getValueY(coordinates){
        return d3.max(coordinates, Number);
    }

     //Custom functions used for specific uses.
    var removeDuplicatesInPlace = function (arr) {
        var i, j, cur, found;
        for (i = arr.length - 1; i >= 0; i--) {
            cur = arr[i];
            found = false;
            for (j = i - 1; !found && j >= 0; j--) {
                if (cur === arr[j]) {
                    if (i !== j) {
                        arr.splice(i, 1);
                    }
                    found = true;
                }
            }
        }
        return arr;
    };
like image 520
Vanquiza Avatar asked Apr 03 '14 19:04

Vanquiza


1 Answers

The first and last element of each array in graphData cause errors when parsed as numbers because of an additional quote
For example the seventh array of graphData looks like this:

console.log(graphData[6])    // [""5.149230", "4.965121""]

The cause of this seems to be the unnecessary JSON.stringfiy() call when fetching the data

d3.json("getdata.php?type=load&gene=CCL5&data=human", function(error, data) {
    var arrayValues = [];
    if(error){ return console.log(error); }

    data.forEach( function(d) { 
        arrayValues.push(d.gene_name);
        valueY = getValueY(d.gene_data);
        var string = JSON.stringify(d.gene_data);    // <-- this one
        graphData.push(string.split(" "));
    });

d.gene_data is already a string so it should work as expected, when you remove the JSON.stringify()

like image 55
Andreas Avatar answered Oct 22 '22 01:10

Andreas