I have the following code that works great, except when I iterate through my data set, the first row (the 0 index) is getting skipped.
svg.selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("x",function(d){
console.log(data);
console.log(d);
return xScale(d.year-1980);
})
Note the console.log(data) returns my full data set, including the first row so the data is there!
But console.log(d) shows all rows after and including my second row of data - it drops the first row.
Any suggestions are welcome.
I had the same issue with similar code, and fixed it based on Lars Kothoff's comment.
In my case it made sense to change the selectAll to work on a g element, more like so:
svg.selectAll("g")
.data(data);
.enter()
.append("g")
.append("rect")
.attr("x",function(d) { return xScale(d.year-1980); });
You could also differentiate the rects with a class:
svg.selectAll("rect.year")
.data(data)
.enter()
.append("rect")
.attr("x",function(d){ return xScale(d.year-1980); })
.classed("year");
Yeah, it seems if there is already an element, it gets "skipped" by the .enter()
<html>
<head>
<title>D3 Test</title>
</head>
<body>
</body>
<script type='text/javascript' src='https://d3js.org/d3.v4.min.js'></script>
<script type='text/javascript'>
var theData = ["James", "Sue", "Ben"]
var p = d3.select("body").selectAll("p")
.data(theData)
.enter()
.append('p')
.text(function (d,i) {
return " i =" + i + "data =" + d;
});
</script>
</html>
Produces
i =0data =James
i =1data =Sue
i =2data =Ben
But if you add a p element in there, it'll skip it.
...[previous code]
<body>
<p>Here is the first p tag that "James gets matched too"</p>
</body>
...[previous code]
Related
Today I am learning about D3.js
I started by studying this content:
https://bost.ocks.org/mike/circles/
Most of it seems easy to understand and follow.
But I have a problem getting exit() to work.
I do understand the idea that I can use exit() to force elements in my DOM to synch with values in a JS array.
So I wrote some code to demonstrate this idea and my code fails.
I want to know how I can rewrite my JS so that exit() will force elements in my DOM to synch with values in a JS array.
<html>
<body>
Ref:
<a href='https://bost.ocks.org/mike/circles/' target='x'>
https://bost.ocks.org/mike/circles/
</a>
<script src="https://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script>
// I should create 3 circles
var svg1 = d3.select('body')
.append('svg')
.attr('id','svg1')
.attr('width',800).selectAll("circle")
.data([0, 1, 2])
.enter()
.append("circle")
.attr("cy", 60)
.attr("cx", function(d, i) { return i * 100 + 30 })
.attr("r", function(d) { return 5+5*d })
// So far so good. I see 3 circles
// Now I should remove some.
var mycircles_a = svg1.selectAll("circle")
.data([99, 88])
// I should ask D3 to make the data-array sync with the circle-elements:
mycircles_a.exit().remove()
// Above call fails for some reason; I still see 3 circles!
// I should see 2 circles because mycircles_a is bound to an array with only 2 values.
'bye'
</script>
</body>
</html>
In your example svg1 is, itself, an "enter" selection.
Your code works just fine if you break the chain, making svg1 just a selection that creates the SVG:
var svg1 = d3.select('body')
.append('svg')
.attr('id','svg1')
.attr('width',800);
svg1.selectAll("circle")
.data([0, 1, 2])
.enter()
.append("circle")
.attr("cy", 60)
.attr("cx", function(d, i) { return i * 100 + 30 })
.attr("r", function(d) { return 5+5*d })
var mycircles_a = svg1.selectAll("circle")
.data([99, 88])
mycircles_a.exit().remove()
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
You're not saving a reference to svg1 correctly.
https://jsfiddle.net/y008c61L/
var svg1 = d3.select('body')
.append('svg')
.attr('id','svg1')
.attr('width',800);
svg1.selectAll("circle")
//...
In d3 creating a path involves a sequence of method calls like this:
var path = svg.append("path")
.data([points])
.attr("d", d3.svg.line()
.tension(0) // Catmull–Rom
.interpolate("cardinal-closed"));
This above is excerpted from the following:
https://bl.ocks.org/mbostock/1705868
Given that points is itself an array, why is it necessary to enclose points inside square brackets in the method invocation .data([points])?
I believe this inner square brackets requirement obtains in d3 v3 and v4.
#davenewton's comment is close. The simply answer is that d3.line expects an array. By passing an array of arrays to .data d3's data-binding will call .attr with the first array in that array of arrays.
Examine this code:
<!DOCTYPE html>
<html>
<head>
<script data-require="d3#4.0.0" data-semver="4.0.0" src="https://d3js.org/d3.v4.min.js"></script>
</head>
<body>
<script>
d3.select('body')
.append('div')
.data([1,2,3])
.attr('d', function(d, i){
console.log(d, i);
});
</script>
</body>
</html>
This is a strange use of the data-binding since it doesn't fulfill the enter, update, exit we usually use with data-binding (and that's why only the first iteration of binding get's called).
Finally, it should be noted that this code could be written as:
var path = svg.append("path")
.datum(points)
.attr("d", d3.svg.line()
.tension(0) // Catmull–Rom
.interpolate("cardinal-closed"));
Or even:
var path = svg.append("path")
.attr("d", d3.svg.line()
.tension(0) // Catmull–Rom
.interpolate("cardinal-closed")(points));
Since:
.append("path")
.data([array])
.attr("d", line)
and
.append("path")
.datum(array)
.attr("d", line)
and
.append("path")
.attr("d", line(points))
are all equivalent.
I've got a haproxy status endpoint which outputs the following data:
I want to parse it's output and build a nice interactive dashboard (in this case monitoring which Riak nodes are up or down at a given time.
I've written the following:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>dadsa</title>
</head>
<body>
<div id="viz"></div>
<script src="//d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script>
var hosts = [];
var url = "http://localhost:8666/haproxy-csv";
function update() {
d3.csv(url, function(data) {
hosts = data.filter(
function(d){
console.log(JSON.stringify( d) );
console.log(Object.keys(d));
console.log(d["# pxname"]);
return d["# pxname"] == "riak_backend_http"
&& d["svname"] != "BACKEND";
}
).map(function(d){
return {"label": d["svname"],"value": d["status"]};
});
d3.select("#viz")
.append("table")
.style("border-collapse", "collapse")
.style("border", "2px black solid")
.selectAll("tr")
.data(hosts)
.enter().append("tr")
.selectAll("td")
.data(function(d){
return [
d["label"], d["value"]
];
})
.enter().append("td")
.style("border", "1px black solid")
.style("padding", "5px")
.on("mouseover", function(){d3.select(this).style("background-color", "aliceblue")})
.on("mouseout", function(){d3.select(this).style("background-color", "white")})
.text(function(d){return d;})
.style("font-size", "12px");
});
}
update();
setInterval(update, 1000);
</script>
<button onclick="alert(JSON.stringify(hosts[0]))"> see value </button>
<div id="svg"/>
</body>
</html>
That code works well, with one small flaw.. It continuously appends a new table element on each update. I've rewritten this code many times but whatever I write seems to just update once.
Here's what the output currently looks like:
I'm really ignorant about d3 so not even sure what exactly I should be binding or hooking into.
Any pointers?
You are getting a new table with each update because you are actually appending a new table each time when you do this inside the update function.
...
d3.select("#viz")
.append("table")
...
Instead create your table beforehand either through HTML tags or using javascript. Create it outside the update function. Say it's something like:
var myTable = d3.select("#viz")
.append("table")
.style("border-collapse", "collapse")
.style("border", "2px black solid");
Now that you have your table set up we can update it using the 'D3.js way' of enter(), exit() and update() as follows:
//bind the data to rows
var rows = myTable.selectAll("tr").data(hosts);
//append as needed using enter()
rows.enter().append("tr");
//remove placeholders in exit
rows.exit().remove();
//bind the data to columns in each row
var columns = myTable.selectAll("tr")
.selectAll("td")
.data(function(d){
return [
d["label"], d["value"]
];
});
//enter and exit as above
columns.enter().append("td");
columns.exit().remove();
//finally update the columns
myTable.selectAll("td")
.style("border", "1px black solid")
.style("padding", "5px")
.on("mouseover", function(){d3.select(this).style("background-color", "aliceblue")})
.on("mouseout", function(){d3.select(this).style("background-color", "white")})
.text(function(d){return d;})
.style("font-size", "12px");
Hope this helps.
Why not remove the table element on each update.
Add this line
d3.csv(url, function(data) {
d3.select("#viz").select("table").remove();//removes the table
//other code as usual
I'm trying to write a simple d3js app which will display a graph from an array of object literals. This literal will be updated on regular basis (from the backend). But for simplicity sake, I have rewritten it using setInterval() which should update the content.
<!doctype html>
<html>
<head>
<script src="d3.js" type="text/javascript"></script>
<script src="underscore.js" type="text/javascript"></script>
</head>
<body>
<div id="graph">
</div>
<script type="text/javascript">
var data=[
{ name: 'NONE', distribution: 5}
];
var graphElement = d3.select("#graph");
// set initial data
graphElement.selectAll("p")
.data(data, function(d){return d.name} )
.enter()
.append("p")
.text(function(d){return d.name + d.distribution;});
var redraw= function() {
console.dir(data);
graphElement.selectAll("p")
.data(data, function(d){return d.name})
.enter()
.append("p")
.text(function(d){return d.name + d.distribution;});
};
setInterval(function() {
var advice = _.find(data, function(i){return i.name == 'ADVICE'});
if (!advice){
advice = {name: 'ADVICE', distribution: 0};
data.push(advice);
}
advice.distribution = advice.distribution + 1;
redraw();
}, 3000);
</script>
</body>
</html>
The page will initially displays one paragraph (as expected). Then the setInterval() kicks off, and insert an 'ADVICE' method with value of 1. Again, as expected.
But when the subsequent setInterval() kicks off, the 'ADVICE' node is not updated - i.e. it stays off as 'ADVICE1'.
Have I missed something obvious here?
Thanks for the help!
Regards,
Alex
There are a few things to note here:
1) the enter/update selections were not being properly handled;
2) your advice.distribution was not changing from 1.
UPDATE FIDDLE with refresh of ADVICE string.
var ge = graphElement.selectAll("p")
.data(data, function(d){return d.name});
ge.exit().remove();
ge.enter().append("p");
ge.text(function(d){return d.name + d.distribution;});
I am a beginner with D3 and JS in general.
I am trying to do a simple rectangle visualisation with a small csv file as a source.
price, units
80.67, 100
80.87, 99
79.34, 47
File, csv are in the same folder.
I am using Python's SimpleHTTPServer to serve locally in this folder.
This is my code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Test Data</title>
<script type="text/javascript" src="../d3/d3.v3.js"></script>
</head>
<body>
<script type="text/javascript">
// load csv from the same directory
d3.csv("test.csv", function (data){
return {
price: +data.price, // convert to number with +
units: +data.units, // convert to number with +
};
var canvas = d3.select("body").append("svg")
.attr("width", 500)
.attr("height", 500)
canvas.selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("width", function (d) { return d.price; })
.attr("height", 48)
.attr("y", function (d) { return d.units; })
.attr("fill", "blue");
canvas.selectAll("text")
.data(data)
.enter()
.append("text")
.attr("fill", "white")
.attr("y", function (d) { return d.units + 24; })
.text( function (d) { return d.units;})
});
</script>
</body>
I am getting no errors, just a blank page.
What is wrong with this code?
The first thing you do in your callback is to return. None of the code after that is being executed. I'm referring to
return {
price: +data.price, // convert to number with +
units: +data.units, // convert to number with +
};
which should probably be
data.forEach(function(d) {
d.price = +d.price;
d.units = +d.units;
});
The signature of the callback should also be function(error, data) instead of function(data).