How to Limit Size of Radius With d3.js - javascript

I'm sure the solution is straight forward, but I'm trying to find out how to limit the range of radius when I plot circles onto a geomap. I have values that range in size significantly, and the larger values end up covering a significant amount of the map.
d3.csv("getdata.php", function(parsedRows) {
data = parsedRows
for (i = 0; i < data.length; i++) {
var mapCoords = this.xy([data[i].long, data[i].lat])
data[i].lat = mapCoords[0]
data[i].long = mapCoords[1]
}
vis.selectAll("circle")
.data(data)
.enter().append("svg:circle")
.attr("cx", function(d) { return d.lat })
.attr("cy", function(d) { return d.long })
.attr("stroke-width", "none")
.attr("fill", function() { return "rgb(255,148,0)" })
.attr("fill-opacity", .4)
.attr("r", function(d) { return Math.sqrt(d.count)})
})
This is what I have right now.

You'll probably want to use d3 scales by setting the domain (min/max input values) and the range (min/max output allowed values). To make this easy, don't hesitate to use d3.min and d3.max to setup the domain's values.
d3.scale will return a function that you can use when assigning the r attribute value. For example:
var scale = d3.scale.linear.domain([ inputMin, inputMax ]).range([ outputMin, outputMax ]);
vis.selectAll("circle")
// etc...
.attr( 'r', function(d) { return scale(d.count) });

Related

D3 - How to update force simulation when data values change

I'm stuck on a small problem regarding force simulation in D3.
I have data representing poverty rates for each country, from 1998 to 2008. It's a bubble chart that's split into three clusters, representing poor countries, not-poor countries, and countries with no information.
When the app is initially loaded, it's loaded with the 1998 data. However, I have some buttons at the top, that, when clicked, will change the year, and subsequently the bubbles should rearrange themselves. All I've been able to do, is when the button is clicked, I change a variable year. However, there are functions and variables that use year throughout the code. When year changes, I want to recalculate all the node properties and force parameters that are depending on year
Here's my code. I've included all of it in case you want to try it out. The data file is at the end of this post.
async function init() {
// Set up the canvas
var height = 1000, width = 2000;
var svg = d3.select("#panel1").append("svg")
.attr("height", height)
.attr("width", width)
.attr("class", "bubblePanel");
var canvas = svg.append("g")
.attr("transform", "translate(0,0)");
// Choose what year to look at, based on button clicks.
var year = "X1998"
d3.select("#b1998").on("click", function() {
year = "X1998"
console.log(year)
// NOTIFY SIMULATION OF CHANGE //
})
d3.select("#b1999").on("click", function() {
year = "X1999"
console.log(year)
// NOTIFY SIMULATION OF CHANGE //
})
d3.select("#b2000").on("click", function() {
year = "X2000"
console.log(year)
// NOTIFY SIMULATION OF CHANGE //
})
// Implement the physics of the elements. Three forces act according to the poverty level (poor, not poor, and no info)
var simulation = d3.forceSimulation()
.force("x", d3.forceX(function(d) {
if (parseFloat(d[year]) >= 10) {
return 1700
} else if (parseFloat(d[year]) === 0) {
return 1000
} else {
return 300
}
}).strength(0.05))
.force("y", d3.forceY(300).strength(0.05))
.force("collide", d3.forceCollide(function(d) {
return radiusScale(d[year])
}));
// Function to pick colour of circles according to region
function pickColor(d) {
if (d === "East Asia & Pacific") {
return "red"
} else if (d === "Europe & Central Asia") {
return "orange"
} else if (d === "Latin America & Caribbean") {
return "yellow"
} else if (d === "Middle East & North Africa") {
return "green"
} else if (d === "North America") {
return "blue"
} else if (d === "South Asia") {
return "indigo"
} else {
return "violet"
}
}
// Set the scales for bubble radius, and text size.
var radiusScale = d3.scaleSqrt().domain([0, 50]).range([20,80]);
var labelScale = d3.scaleSqrt().domain([0,50]).range([10,40]);
// Read the data
await d3.csv("wd3.csv").then(function(data) {
// Assign each data point to a circle that is colored according to region and has radius according to its poverty level
var bubbles = svg.selectAll("circle")
.data(data)
.enter().append("circle")
.attr("cx", 100)
.attr("cy", 100)
.attr("fill", function(d) {
return pickColor(d.Region)
})
.attr("r", function(d) {
return radiusScale(d[year])
});
// Assign each ddata point to a text element that shows the counry code of the data point. The text is scaled according to the poverty level
var labels = svg.selectAll("text")
.data(data)
.enter().append("text")
.attr("x", 100)
.attr("y", 100)
.attr("dominant-baseline", "central")
.text(function(d) { return d.XCountryCode })
.style("stroke", "black")
.style("text-anchor", "middle")
.style("font-size", function(d) { return labelScale(d[year]); });
// Code to handle the physics of the bubble and the text
simulation.nodes(data)
.on("tick", ticked)
function ticked() {
bubbles.attr("transform", function(d) {
var k = "translate(" + d.x + "," + d.y + ")";
return k;
})
labels.attr("transform", function(d) {
var k = "translate(" + d.x + "," + d.y + ")";
return k;
})
}
});
}
When year changes, the data values will change for each country. I want the following parts of my code to be updated.
The x forces on the nodes: Countries can go from poor in one year to not-poor in another year, so their cluster will change
The radius of the circles: The radius represents poverty level. These change from year to year, so the size of the circles will change when a button is clicked
The coordinates of the country labels: These labels are attached to the data as well. So when the x forces on the circles causes the circles to move, the labels should move as well.
I'd greatly appreciate the help.
The data file can be found here. I accidentally named it povertyCSV, but in the code, it's referenced as "wd3.csv"
If I understand the question correctly:
Re-initializing Forces
The functions provided to set parameters of d3 forces such as forceX or forceCollision are executed once per node at initialization of the simulation (when nodes are originally assigned to the layout). This saves a lot of time once the simulation starts: we aren't recalculating force parameters every tick.
However, if you have an existing force layout and want to modify forceX with a new x value or new strength, or forceCollision with a new radius, for example, we can re-initialize the force to perform the recalculation:
// assign a force to the force diagram:
simulation.force("someForce", d3.forceSomeForce().someProperty(function(d) { ... }) )
// re-initialize the force
simulation.force("someForce").initialize(nodes);
This means if we have a force such as:
simulation.force("x",d3.forceX().x(function(d) { return fn(d["year"]); }))
And we update the variable year, all we need to do is:
year = "newValue";
simulation.force("x").initialize(nodes);
Positioning
If the forces are re-initialized (or re-assigned), there is no need to touch the tick function: it'll update the nodes as needed. Labels and circles will continue to be updated correctly.
Also, non-positional things such as color need to be updated in the event handler that also re-initializes the forces. Other than radius, most things should either be updated via the force or via modifying the elements directly, not both.
Radius is a special case:
With d3.forceCollide, radius affects positioning
Radius, however, does not need to be updated every tick.
Therefore, when updating the radius, we need to update the collision force and modify the r attribute of each circle.
If looking for a smooth transition of radius that is reflected graphically and in the collision force, this should be a separate question.
Implementation
I've borrowed from your code to make a fairly generic example. The below code contains the following event listener for some buttons where each button's datum is a year:
buttons.on("click", function(d) {
// d is the year:
year = d;
// reheat the simulation:
simulation
.alpha(0.5)
.alphaTarget(0.3)
.restart();
// (re)initialize the forces
simulation.force("x").initialize(data);
simulation.force("collide").initialize(data);
// update altered visual properties:
bubbles.attr("r", function(d) {
return radiusScale(d[year]);
}).attr("fill", function(d) {
return colorScale(d[year]);
})
})
The following snippet uses arbitrary data and due to its size may not allow for nodes to re-organize perfectly every time. For simplicity, position, color, and radius are all based off the same variable. Ultimately, it should address the key part of the question: When year changes, I want to update everything that uses year to set node and force properties.
var data = [
{year1:2,year2:1,year3:3,label:"a"},
{year1:3,year2:4,year3:5,label:"b"},
{year1:5,year2:9,year3:7,label:"c"},
{year1:8,year2:16,year3:11,label:"d"},
{year1:13,year2:25,year3:13,label:"e"},
{year1:21,year2:36,year3:17,label:"f"},
{year1:34,year2:1,year3:19,label:"g"},
{year1:2,year2:4,year3:23,label:"h"},
{year1:3,year2:9,year3:29,label:"i"},
{year1:5,year2:16,year3:31,label:"j"},
{year1:8,year2:25,year3:37,label:"k"},
{year1:13,year2:36,year3:3,label:"l"},
{year1:21,year2:1,year3:5,label:"m"}
];
// Create some buttons:
var buttons = d3.select("body").selectAll("button")
.data(["year1","year2","year3"])
.enter()
.append("button")
.text(function(d) { return d; })
// Go about setting the force layout:
var svg = d3.select("body")
.append("svg")
.attr("width", 500)
.attr("height", 300);
var radiusScale = d3.scaleSqrt()
.domain([0, 40])
.range([5,30]);
var colorScale = d3.scaleLinear()
.domain([0,10,37])
.range(["#c7e9b4","#41b6c4","#253494"]);
var year = "year1";
var simulation = d3.forceSimulation()
.force("x", d3.forceX(function(d) {
if (parseFloat(d[year]) >= 15) {
return 100
} else if (parseFloat(d[year]) > 5) {
return 250
} else {
return 400
}
}).strength(0.05))
.force("y", d3.forceY(150).strength(0.05))
.force("collide", d3.forceCollide()
.radius(function(d) {
return radiusScale(d[year])
}));
var bubbles = svg.selectAll("circle")
.data(data)
.enter().append("circle")
.attr("r", function(d) {
return radiusScale(d[year])
})
.attr("fill", function(d) {
return colorScale(d[year]);
});
var labels = svg.selectAll("text")
.data(data)
.enter()
.append("text")
.text(function(d) {
return d.label;
})
.style("text-anchor","middle");
simulation.nodes(data)
.on("tick", ticked)
function ticked() {
bubbles.attr("cx", function(d) {
return d.x;
}).attr("cy", function(d) {
return d.y;
})
labels.attr("x", function(d) {
return d.x;
})
.attr("y", function(d) {
return d.y +5;
})
}
buttons.on("click", function(d) {
// d is the year:
year = d;
simulation
.alpha(0.5)
.alphaTarget(0.3)
.restart();
simulation.force("x").initialize(data);
simulation.force("collide").initialize(data);
bubbles.attr("r", function(d) {
return radiusScale(d[year]);
}).attr("fill", function(d) {
return colorScale(d[year]);
})
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

Getting a better performance on repeatedly method on d3

For example, I need to calculate a Math.sqrt of my data for each attr, how can I calculate only one time the Math.sqrt(d)?
var circle = svgContainer.data(dataJson).append("ellipse")
.attr("cx", function(d) {
return Math.sqrt(d) + 1
})
.attr("cy", function(d) {
return Math.sqrt(d) + 2
})
.attr("rx", function(d) {
return Math.sqrt(d) + 3
})
.attr("ry", function(d) {
return Math.sqrt(d) + 4
});
Has any elegant/performative mode? I'm thinking this way:
var aux;
var circle = svgContainer.data(dataJson).append("ellipse")
.attr("cx", function(d) {
aux = Math.sqrt(d);
return aux + 1
})
.attr("cy", function(d) {
return aux + 2
})
.attr("rx", function(d) {
return aux + 3
})
.attr("ry", function(d) {
return aux + 4
});
An underestimated feature of D3 is the concept of local variables which were introduced with version 4. These variables allow you to store information on a node (that is the reason why it is called local) independent of the data which might have been bound to that node. You don't have to bloat your data to store additional information.
D3 locals allow you to define local state independent of data.
Probably the major advantage of using local variables over other approaches is the fact that it smoothly fits into the classic D3 approach; there is no need to introduce another loop whereby keeping the code clean.
Using local variables to just store a pre-calculated value is probably the simplest use case one can imagine. On the other hand, it perfectly illustrates what D3's local variables are all about: Store some complex information, which might require heavy lifting to create, locally on a node, and retrieve it for later use further on in your code.
Shamelessly copying over and adapting the code from Gerardo's answer the solution can be implemented like this:
var svg = d3.select("svg");
var data = d3.range(100, 1000, 100);
var roots = d3.local(); // This is the instance where our square roots will be stored
var ellipses = svg.selectAll(null)
.data(data)
.enter()
.append("ellipse")
.attr("fill", "gainsboro")
.attr("stroke", "darkslateblue")
.attr("cx", function(d) {
return roots.set(this, Math.sqrt(d)) * 3; // Calculate and store the square root
})
.attr("cy", function(d) {
return roots.get(this) * 3; // Retrieve the previously stored root
})
.attr("rx", function(d) {
return roots.get(this) + 3; // Retrieve the previously stored root
})
.attr("ry", function(d) {
return roots.get(this) + 4; // Retrieve the previously stored root
});
<script src="//d3js.org/d3.v4.min.js"></script>
<svg></svg>
Probably, the most idiomatic way for doing this in D3 is using selection.each, which:
Invokes the specified function for each selected element, in order, being passed the current datum (d), the current index (i), and the current group (nodes), with this as the current DOM element (nodes[i]).
So, in your case:
circle.each(function(d){
//calculates the value just once for each datum:
var squareRoot = Math.sqrt(d)
//now use that value in the DOM element, which is 'this':
d3.select(this).attr("cx", squareRoot)
.attr("cy", squareRoot)
//etc...
});
Here is a demo:
var svg = d3.select("svg");
var data = d3.range(100, 1000, 100);
var ellipses = svg.selectAll(null)
.data(data)
.enter()
.append("ellipse")
.attr("fill", "gainsboro")
.attr("stroke", "darkslateblue")
.each(function(d) {
var squareRoot = Math.sqrt(d);
d3.select(this)
.attr("cx", function(d) {
return squareRoot * 3
})
.attr("cy", function(d) {
return squareRoot * 3
})
.attr("rx", function(d) {
return squareRoot + 3
})
.attr("ry", function(d) {
return squareRoot + 4
});
})
<script src="//d3js.org/d3.v4.min.js"></script>
<svg></svg>
Another common approach in D3 codes is setting a new data property in the first attr method, and retrieving it latter:
.attr("cx", function(d) {
//set a new property here
d.squareRoot = Math.sqrt(d.value);
return d.squareRoot * 3
})
.attr("cy", function(d) {
//retrieve it here
return d.squareRoot * 3
})
//etc...
That way you also perform the calculation only once per element.
Here is the demo:
var svg = d3.select("svg");
var data = d3.range(100, 1000, 100).map(function(d) {
return {
value: d
}
});
var ellipses = svg.selectAll(null)
.data(data)
.enter()
.append("ellipse")
.attr("fill", "gainsboro")
.attr("stroke", "darkslateblue")
.attr("cx", function(d) {
d.squareRoot = Math.sqrt(d.value);
return d.squareRoot * 3
})
.attr("cy", function(d) {
return d.squareRoot * 3
})
.attr("rx", function(d) {
return d.squareRoot + 3
})
.attr("ry", function(d) {
return d.squareRoot + 4
});
<script src="//d3js.org/d3.v4.min.js"></script>
<svg></svg>
PS: by the way, your solution with var aux will not work. Try it and you'll see.

D3 stops plotting points when data source is modified

I am plotting points on a UK map using D3 off a live data stream. When the data points exceed 10,000 the browser becomes sluggish and the animation is no longer smooth. So I modify the dataPoints array to keep only the last 5000 points.
However when I modify the dataPoints the first time using splice() D3 stops rendering any new points. The old points gradually disappear (due to a transition) but there are no new points. I am not sure what I am doing wrong here.
I have simulated the problem by loading data of a CSV as well storing it in memory and plotting them at a rate of 1 point every 100ms. Once the number of dots goes above 10 I splice to retain the last 5 points. I see the same behaviour. Can someone review the code and let me know what I am doing wrong?
Setup and the plotting function:
var width = 960,
height = 1160;
var dataPoints = []
var svg = d3.select("#map").append("svg")
.attr("width", width)
.attr("height", height);
var projection = d3.geo.albers()
.center([0, 55.4])
.rotate([4.4, 0])
.parallels([40, 70])
.scale(5000)
.translate([width / 2, height / 2]);
function renderPoints() {
var points = svg.selectAll("circle")
.data(dataPoints)
points.enter()
.append("circle")
.attr("cx", function (d) {
prj = projection([d.longitude, d.latitude])
return prj[0];
})
.attr("cy", function (d) {
prj = projection([d.longitude, d.latitude])
return prj[1];
})
.attr("r", "4px")
.attr("fill", "blue")
.attr("fill-opacity", ".4")
.transition()
.delay(5000)
.attr("r", "0px")
}
/* JavaScript goes here. */
d3.json("uk.json", function(error, uk) {
if (error) return console.error(error);
console.log(uk);
var subunits = topojson.feature(uk, uk.objects.subunits);
var path = d3.geo.path()
.projection(projection);
svg.selectAll(".subunit")
.data(subunits.features)
.enter().append("path")
.attr("class", function(d) { return "subunit " + d.id })
.attr("d", path);
svg.append("path")
.datum(topojson.mesh(uk, uk.objects.subunits, function(a,b) {return a!== b && a.id !== 'IRL';}))
.attr("d", path)
.attr("class", "subunit-boundary")
svg.append("path")
.datum(topojson.mesh(uk, uk.objects.subunits, function(a,b) {return a=== b && a.id === 'IRL';}))
.attr("d", path)
.attr("class", "subunit-boundary IRL")
svg.selectAll(".place-label")
.attr("x", function(d) { return d.geometry.coordinates[0] > -1 ? 6 : -6; })
.style("text-anchor", function(d) { return d.geometry.coordinates[0] > -1 ? "start": "end"; });
svg.selectAll(".subunit-label")
.data(topojson.feature(uk, uk.objects.subunits).features)
.enter().append("text")
.attr("class", function(d) { return "subunit-label " + d.id })
.attr("transform", function(d) { return "translate(" + path.centroid(d) + ")"; })
.attr("dy", ".35em")
.text(function(d) { return d.properties.name; })
// function applyProjection(d) {
// console.log(d);
// prj = projection(d)
// console.log(prj);
// return prj;
// }
lon = -4.6
lat = 55.45
dataPoints.push([lon,lat])
renderPoints()
});
Function to cleanup old points
var cleanupDataPoints = function() {
num_of_elements = dataPoints.length
console.log("Pre:" + num_of_elements)
if(num_of_elements > 10) {
dataPoints = dataPoints.splice(-5, 5)
}
console.log("Post:" + dataPoints.length)
}
Loading data from CSV and plotting at a throttled rate
var bufferedData = null
var ptr = 0
var renderNext = function() {
d = bufferedData[ptr]
console.log(d)
dataPoints.push(d)
ptr++;
renderPoints()
cleanupDataPoints()
if(ptr < bufferedData.length)
setTimeout(renderNext, 100)
}
d3.csv('test.csv', function (error, data) {
bufferedData = data
console.log(data)
setTimeout(renderNext, 100)
})
In the lines
points = svg.selectAll("circle")
.data(dataPoints)
points.enter() (...)
d3 maps each element in dataPoints (indexed from 0 to 5000) to the circle elements (of which there should be 5000 eventually). So from its point of view, there is no enter'ing data: there are enough circles to hold all your points.
To make sure that the same data point is mapped to the same html element after it changed index in its array, you need to use an id field of some sort attached to each of your data point, and tell d3 to use this id to map the data to elements, instead of their index.
points = svg.selectAll("circle")
.data(dataPoints, function(d){return d.id})
If the coordinates are a good identifier for your point, you can directly use:
points = svg.selectAll("circle")
.data(dataPoints, function(d){return d.longitude+" "+d.latitude})
See https://github.com/mbostock/d3/wiki/Selections#data for more details.

Dynamically generating multiple d3 svg graphs

I've got an array of objects called graphData (size varies). Each element contains all the information required to create a d3 graph, and I am able to successfully draw the graphs if I access graphData elements by hardcoding (i.e. graphdata[0], graphdata[1] etc).
The problem comes when I attempt to use a for loop to generate one graph for each of the elements. Looked around stackoverflow and the web, but the solutions are all about generating a fixed number of multiple graphs, not generating multiple graphs dynamically.
Below is my working code for generating one graph. What is the recommended way to generate x number of graphs automatically?
var graphData = data.graph;
var RADIUS = 15;
var edgeData = graphData[0].edges;
var nodeData = graphData[0].nodes;
var stageNum = graphData[0].stage;
var xScale = d3.scale.linear()
.domain([d3.min(edgeData, function (d) {
return d.start[0];
}),
d3.max(edgeData, function (d) {
return d.start[0];
})])
.range([50, w - 100]);
var yScale = d3.scale.linear()
.domain([d3.min(edgeData, function (d) {
return d.start[1];
}),
d3.max(edgeData, function (d) {
return d.start[1];
})])
.range([50, h - 100]);
var rScale = d3.scale.linear()
.domain([0, d3.max(edgeData, function (d) {
return d.start[1];
})])
.range([14, 17]);
// already have divs with classes stage1, stage2... created.
var svg = d3.select(".stage" + stageNum).append("svg")
.attr({"width": w, "height": h})
.style("border", "1px solid black");
var elemEdge = svg.selectAll("line")
.data(edgeData)
.enter();
var edges = elemEdge.append("line")
.attr("x1", function (d) {
return xScale(d.start[0]);
})
.attr("y1", function (d) {
return yScale(d.start[1]);
})
.attr("x2", function (d) {
return xScale(d.end[0]);
})
.attr("y2", function (d) {
return yScale(d.end[1]);
})
.attr("stroke-width", 2)
.attr("stroke", "black");
var elemNode = svg.selectAll("circle")
.data(nodeData)
.enter();
var nodes = elemNode.append("circle")
.attr("cx", function (d) {
return xScale(parseInt(d.x));
})
.attr("cy", function (d) {
return yScale(parseInt(d.y));
})
.attr({"r": rScale(RADIUS)})
.style("fill", "yellow")
.style("stroke", "black");
Mike Bostock recommends implementing charts as reusable closures with methods. This would be an ideal implementation in your case as you want to have
multiple graphs with different data
potential reloading with new data (hopefully this is what you mean by dynamic?)
In broad strokes, what you want to do is wrap your code above into a function in very much the same way Mike describes in the post above, and then have data be an attribute of your closure. So here is some badly hacked code:
// your implementation here
var chart = function(){...}
var graphData = d3.json('my/graphdata.json', function(error, data){
// now you have your data
});
// let's say you have a div called graphs
var myGraphs = d3.select('.graphs')
.data(graphData)
.enter()
.append('g')
//now you have g elements for each of your datums in the graphData array
//we use the saved selection above and call the chart function on each of the elements in the selection
myGraphs.call(chart);
//note that internally in your `chart` closure, you have to take in a selection
//object and process it(data is already bound to each of your selections from above):
function chart(selection) {
selection.each(function(data) {
//...
Here is some more good reading on the topic.
Well you can try the following approach.
var graphData = data.graph;
//forEach will return each element for the callback, you can then make use of the e1 to draw the graph.
graphData.forEach(function(e1){
//graph code goes here.
});
providing this as your source array
//it's just a single circle in 3, 4
var stuff = [3, 4];
var source = [ [stuff, stuff], [stuff] ];
a bit of Array stuff
Array.prototype.max = function() {
return Math.max.apply(null, this);
};
Array.prototype.min = function() {
return Math.min.apply(null, this);
};
setup:
var dim = [];
source.forEach(function(elem){
elem.forEach(function(circle){
dim.push(circle.min());
dim.push(circle.max());
});
});
var min = dim.min();
var max = dim.max();
var x = d3.scale.linear()
.domain([min, max])
.scale([yourscale]);
var y = d3.scale.linear()
.domain([min, max])
.scale([yourscale]);
d3.select('body').selectAll('div')
.data(source) //first step: a div with an svg foreach array in your array
.enter()
.append('div')
.append('svg')
.selectAll('circle') //second step: a circle in the svg for each item in your array
.data(function(d){
return d; //returns one of the [stuff] arrays
}).enter()
.append('circle')
.attr('r', 5)
.attr('cx', function(d){
return x(d[0]);
})
.attr('cy', function(d){
return y(d[1]);
})
.style('fill','blue');

Plotting svg circles based off csv data

I'm trying to plot circles from data in my csv file, but the circles are not appearing on the svg canvas. I believe the problem stems from how I load in the data (it gets loaded as an array of objects), but I'm not quite sure how to figure out what to do next.
Based off this tutorial: https://www.dashingd3js.com/svg-text-element
D3.js code:
var circleData = d3.csv("files/data.csv", function (error, data) {
data.forEach(function (d) {
d['KCComment'] = +d['KCComment'];
d['pscoreResult'] = +d['pscoreResult'];
d['r'] = +d['r'];
});
console.log(data);
});
var svg = d3.select("body").append("svg")
.attr("width", 480)
.attr("height", 480);
var circles = svg.selectAll("circle")
.data(circleData)
.enter()
.append("circle");
var circleAttributes = circles
.attr("cx", function (d) { return d.KCComment; })
.attr("cy", function (d) { return d.pscoreResult; })
.attr("r", function (d) { return d.r; })
.style("fill", "green");
var text = svg.selectAll("text")
.data(circleData)
.enter()
.append("text");
var textLabels = text
.attr("x", function(d) { return d.KCComment; })
.attr("y", function(d) { return d.pscoreResult; })
.text(function (d) { return "( " + d.KCComment + ", " + d.pscoreResult + " )"; })
.attr("font-family", "sans-serif")
.attr("font-size", "20px")
.attr("fill", "red");
What the CSV looks like:
fmname, fmtype, KCComment, pscoreResult, r
test1, type1, 7.1, 8, 39
test2, type2, 1.2, 3, 12
You should have the circle-drawing code within the d3.csv function's callback, so it's only processed when the data is available.
d3.csv("data.csv", function (error, circleData) {
circleData.forEach(function (d) {
d['KCComment'] = +d['KCComment'];
d['pscoreResult'] = +d['pscoreResult'];
d['r'] = +d['r'];
});
console.log(circleData);
// Do the SVG drawing stuff
...
// Finished
});
Also note that instead of setting var circleData = d3.csv(... you should just define it in the callback function.
Here's a plunker with the working code: http://embed.plnkr.co/fzBX0o/preview
You'll be able to see a number of further issues now: both circles are overlapping and only one quarter is visible. That's because your KCComment and pscoreResult values used to define the circles' cx and cy are too small. Try multiplying them up so that the circles move right and down and are a bit more visible! Same is true of the text locations, but I'll leave those problems for you to solve

Categories

Resources