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;});
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")
//...
I am going through D3 Tips & Tricks and i'm on this graph: http://bl.ocks.org/d3noob/7030f35b72de721622b8.
I am playing around with the different axises to get them to re-render and re-size dynamically upon a JavaScript function has been called via a button. I want to re-render the x axis so that it takes longer to fully load than the re-generated line element.
// Select the section we want to apply our changes to
var svg = d3.select("body").transition().delay(500).style("stroke", "green");
// Make the changes
svg.select(".line") // change the line
.duration(750)
.style("stroke", "red")
.attr("d", valueline(data));
svg.select(".x.axis") // change the x axis
.duration(1750)
.call(xAxis);
svg.select(".y.axis") // change the y axis
.duration(1000000000)
.call(yAxis);
});
In theory, I suppose that .duration() command can take the highest integer value that JavaScript accepts as a .millisecond. Is that correct? I am keen to know if there is a limit here as to the longest possible duration I can make.
I just coded up a quick example with Number.MAX_VALUE and d3 doesn't complain:
<!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>
var svg = d3.select('body')
.append('svg')
.attr('width', 500)
.attr('height', 500);
var c = svg.append('circle')
.attr('transform', 'translate(20,20)')
.attr('r', 20)
.style('fill', 'steelblue');
c.transition()
.duration(Number.MAX_VALUE)
.ease(d3.easeLinear)
.tween("attr.transform", function() {
var self = d3.select(this),
i = d3.interpolateTransformSvg('translate(20,20)', 'translate(400,400)');
return function(t) {
console.log(i(t));
self.attr('transform',i(t));
};
});
</script>
</body>
</html>
Some quick calculations tell me though that it'll take 1x10306 iterations of the transition loop to move my circle 1px, so assuming the transition loop is firing every 17 milliseconds, that's around 5.34 × 10296 years before I see any movement....
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 am trying to access particular variable from a csv file as shown below:
Month,Year,leaves
Jan,2011, 20
Feb, 2011, 30
Mar,2011, 40
What I am trying to achieve is to create a bar chart with height being the leaves value. Below is the code that I used to access leaves field from the csv file imported. I am doing something wrong here, as I am new to D3.js I am pretty confused about accessing an object or referencing an object (syntax in general). I don't care about year, month, I am just trying to create a simple bar chart with leaves. Any help or pointer towards valuable resources would be much appreciated.
Thanks
<!DOCTYPE HTML>
<html>
<head>
<script type="text/javascript" src="d3.min.js"></script>
<h4> D3 Bar Chart </h4>
</head>
<style>
.bar{
display:inline-block;
width: 20px;
height: 80px;
margin-right: 2px;
background-color: teal;
}
</style>
<body>
<script>
d3.csv("sl_month_year.csv", function(error, data)
{ if(error) {
console.log(error);}
else {console.log(data);
var bar = d3.selectAll("body")
.select("div")
.data(data.length)
.enter()
.append("div")
.attr("class","bar")
.style("height", function(d) { for (i=0; i <= d.length; i++) { return d.[i].leaves + "px" ;});
});
I see a few things that seem wrong to me in your code.
First, you try to access a property using d.[i].leaves in your loop; I think what you try to do is d[i].leaves.
Second, you do not actually need to loop on the elements in your collection, as d3 will handle that for you.
I would go for a different way of using selections, using a simple select for the body and a selectAll for the div's, and bind to the data directly instead of data.length.
Code below should be fine (untested though):
d3.csv("sl_month_year.csv", function(error, data){
if(error) {
console.log(error);}
else {
console.log(data);
var bar = d3.select("body")
.selectAll("div")
.data(data)
.enter()
.append("div")
.attr("class","bar")
.style("height", function(d) {
return d.leaves + "px" ;
});
});
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]