Invalid attribute value <circle> cx = "NaN" using D3.js
I am trying to create a scatter chart shape. I have a special X axis and a specific scale for the a axis. I also implemented a zoom function. So far, everything is in order, but when I finally try to build my data in the form of circles, I get two errors:
.
My schedule can be viewed on this website: http://servers.binf.ku.dk/hemaexplorerbeta/ (circles are huge because I want to make sure I know where they are before I style them)
I am creating circles based on data read from the MYSQL server. I checked all my data and the numbers are correct. They either draw erroneously or scale / scale.
You can also notice that I first create my axis and scale with some values ββand immediately change them to some functions. This is due to the fact that I plan to upload a blank graph to a website, where the user can decide which data set to load it, where the functions will need to set both scales and axes for the loaded data.
I 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; }; The first and last element of each array in graphData cause errors when parsing numbers as due to an additional quote
For example, the seventh graphData array looks like this:
console.log(graphData[6]) // [""5.149230", "4.965121""] The reason for this is an unnecessary JSON.stringfiy() call when retrieving 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 already a string, so it should work as expected when you remove JSON.stringify()