Update d3 graph with new data - javascript

I am trying to update a d3 graph when I click on a County on a map. Instead of updating the existing chart, a new graph is created each time. Any ideas of how to rectify this? Code is here: https://github.com/careyshan/d3-graphUpgade
Thanks
This is the part of the code that I am having difficulty with:
on("click", function(d,i){
if(document.getElementById('linechart').childNodes===null){
console.log("Working")
dataNew = data.filter(function( obj ) {
return obj.County == d.properties.NAME_TAG;
console.log(data);
});
dataNew.sort(function(a,b){
// Turn your strings into dates, and then subtract them
// to get a value that is either negative, positive, or zero.
return b.date - a.date;
});
for (var i=1; i<dataNew.length;i++){
dataSel.push(dataNew[i]);
}
}else if(document.getElementById('linechart').childNodes!=null){
//console.log("check")
dataNew = data.filter(function( obj ) {
return obj.County == d.properties.NAME_TAG;
});
dataNew.sort(function(a,b){
// Turn your strings into dates, and then subtract them
// to get a value that is either negative, positive, or zero.
return b.date - a.date;
});
for (var i=1; i<dataNew.length;i++){
dataSel.push(dataNew[i]);
}
}
linechart(dataNew);
console.log(dataNew);
});

A new graph is created each time because on every call to the function linechart() you are appending a new graph (an svg element) to the body of the page.
This is the snippet from your code which appends a new graph each time.
// append the svg obgect to the body of the page
// appends a 'group' element to 'svg'
// moves the 'group' element to the top left margin
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("id","linechart")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
If you want to update the same chart, just modify your linechart() function to refer to the same svg element. You can achieve this by declaring a global variable for the svg element and use it in your linechart() method like below:
// If graph is not there, create it
if(!svg) {
// append the svg object to the body of the page
//appends a 'group' element to 'svg'
// moves the 'group' element to the top left margin
svg= d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("id","linechart")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
}
Note: Declare the svg as a global variable (may be at the starting of the script tag):
var svg;

Related

Parsing dates with d3.js v.4

I'm trying to learn how to code with the d3.js by working on my first d3 mini project based on the Free Code Camp curriculum. I am trying to make a simple bar graph with this json file. I got stuck trying to format the dates in the file. I've tried looking at the d3.js API and I am still lost. I would be very grateful for any advice that comes my way. Here is my code
// set the dimensions and margins of the graph
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
//then make a function to parse the time
var parseDate = d3.timeFormat("%Y-%m-%d");
// set the ranges
var x = d3.scaleBand().rangeRound([0, width]).padding(0.1);
var y = d3.scaleLinear().range([height, 0],0.5);
// append the svg object to the body of the page
// append a 'group' element to 'svg'
// moves the 'group' element to the top left margin
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
//setup axis
// get the data
d3.json("https://raw.githubusercontent.com/FreeCodeCamp/ProjectReferenceData/master/GDP-data.json").get(function(error,dataset){
var d =dataset.data;
d.forEach(function(datum, i) {
d[0]=parseDate(datum[0]);
//console.log(datum[1]);
});
//console.log(d[3][0]);
});
You want timeParse, not timeFormat:
var parseDate = d3.timeParse("%Y-%m-%d");
Using timeParse,
the returned function parses a specified string, returning the corresponding date or null if the string could not be parsed according to this format’s specifier.
And this is how your forEach function should be:
data.forEach(function(d) {
d[0] = parseDate(d[0]);
});
Here is a demo:
var parseDate = d3.timeParse("%Y-%m-%d");
d3.json("https://raw.githubusercontent.com/FreeCodeCamp/ProjectReferenceData/master/GDP-data.json", function(json) {
var data = json.data;
data.forEach(function(d) {
d[0] = parseDate(d[0]);
});
console.log(data);
});
<script src="https://d3js.org/d3.v4.min.js"></script>

d3.js Get Legend Outside of Chart

I am trying to get the legend of my chart outside of the charting area.
Here are the margins:
var margin = {top: 50, right: 200, bottom: 50, left: 40};
var width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
First I create the svg:
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
So, from my understanding, I now have the svg canvas element and a g inside of it which holds the chart. I am trying to add more to the right margin, so I can get some space between the svg canvas and the g appended to it, which holds the chart. Then I want to put my legend in that empty space.
Here is where I add my legend:
//add legend
var legend = svg.append("g")
.attr("class", "legend")
.attr("height", 300)
.attr("width", 200)
.attr("transform", "translate(-1000,50");
Even though I am appending to the SVG element, it is appending to the g within the svg element. So, no matter how much I translate it or try to get it to go more right on the screen, it never goes past the width of the inner g.
When troubleshooting, I see that the outer SVG element has a height of 960 and width of 500. The g appended to that has a transform/translate of 40,50. The width ends up being 839px by 433.223px (not sure I understand this). The outer svg has a bunch of space to the right now because of the margin built in.
So I'm trying to either increase the width of the g appended to the svg so I can put my legend as a child of the g and move it to the empty space created by the margin. Or, I'm trying to create another g that is a sibling to the first g and then I can use the empty space created by the margin.
I can't get either to work and don't know which way is best.
Notice that the var svg is being assigned to a <g> nested inside the <svg>
svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g") // <-- This is what svg is currently being assigned to
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
And so, when you later execute var legend = svg.append("g"), you're actually appending the legend as a child of the aforementioned <g>. And that's what you described seeing in the dev tools.
One implication is that the translate() transform you applied to the outer <g> affects the inner <g> (i.e. the translation of the innter <g> of legend is added to that of the outer <g>).
Likely, you want split things up like so:
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);
var inner = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
Then change your code to draw the existing chart into inner rather than svg.
As a result, var legend = svg.append("g") will append legend as a sibling of inner, and any translation you apply to legend would be relative to the svg's top left (as opposed to inner's top left, which is translated by margin)
And likely you want to translate legend like so:
var legend = svg.append("g")
.attr("transform", "translate(" + width - margin.right + "," + margin.top + ")");
That moves the legend to the right end of the chart, MINUS margin.right. That way, you can tweak margin.right to create enough room for legend.
Finally, note that calling
legend
.attr("height", 300)
.attr("width", 200)
doesn't do anything, because for svg <g>, there isn't a way to explicitly set the width and height. Those wouldn't mean much anyway, because svg doesn't have a the "flow" behavior of html layouts. The width and height shown in dev tools are the implicit bounds resulting from the bounds of the children of the <g>. (If needed, there's a way to get those computed bounds in javascript, using the getBBox() function).
by looking at the code you provided, you are actually attaching the legend var to your group "g", instead of "svg", var legend = svg.append("g")
in this line you are telling d3 to get your legend variable to "g" which is append to svg, if i understand correctly you should try something like this:
var legend = svg.selectAll(".legend")
.enter().append("g")
to create another group "g" for your legends.
i apologize for my bad english.

D3 updating data on box plot without removing previous SVG first

I'm following the box plot on this page Box Plot Link. In this example it reads in the box plot values from a data file. I'm trying to add a button that will read in new data from another data file and update the box plot.
Here's the code they use to first create the box plot.
var svg = d3.select("#viz").selectAll("svg")
.data(data)
.enter().append("svg")
.attr("class", "box")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.bottom + margin.top)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.call(chart.duration(1000));
and this to update the box plots every second
setInterval(function() {
svg.datum(randomize).call(chart.duration(1000));
}, 2000);
When I click on the update button it calls my snippet here:
var svg = d3.select("#viz").selectAll("svg")
.data(data)
.enter().append("svg")
.attr("class", "box")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.bottom + margin.top)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.call(chart.duration(1000));
However this won't update my boxplot. If I want the box plot to change, I have to first remove the box plot SVG first
d3.selectAll('svg').remove();
But I'd like to have an transition rather than new box plots. Why do I have to remove the previous SVG first? Any help?
So I made the following changes to the update of the data
var svg = d3.select("#viz").selectAll("svg").data(data)
svg.call(chart.duration(duration));
svg.enter().append("svg")
.attr("class", "box")
.attr("id", function (d, i) {
return "box_" + i;
})
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.bottom + margin.top)
.append("g")
.attr("id", function (d, i) {
return "g_" + i;
})
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.call(chart.duration(duration))
svg.exit().remove();
and there is actually some bugs with that box.js code that needs to be fixed which I found about here:
D3 Datum Update Boxplot

Set backgroud image from the javascript not working

In my javascript I have my div svg as following:
var svg = d3.select("#"+settings.id).append("svg")
.attr("id", "svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
And then I added
document.getElementById('svg').style.backgroundImage = 'url(myimageUrl)';
But it didnt work
you are not passing a variable to, it looks like myimageurl should contain a string with the image name like "picture.jpg" if so, you may not pass the variable in quotes, because its a string then.
document.getElementById('svg').style.backgroundImage = 'url('+myimageUrl+')';
another interpretion of the question may be :
it depends on where or better when you execute the line that changes the background color.
when you ecexute it before the svg-variable is inserted to the dom it wont find the id of the element, so be sure to run it after that.

SVG path element .transition() - where to add?

Basically, I want my graph to start at the x-axis and grow over two seconds to the actual data values. This is probably a simple thing, but I can't seem to get it to work.
I'm appending an area element, in which the d="" attribute is built by a function (area) and I'm not sure where to add a transition.
First I thought to do this in the area function, but this fails. I've also tried to do this when the area element is added without success.
Here is my code:
// Create the area element for each object - a function that will be passed to "path"
var area = d3.svg.area()
.x(function(d) { return x(d.year); })
.y0(height)
//.y1(height)
//.transition()
//.duration(2000)
.y1(function(d) { return y(d.average); });
// build the container SVG element
var svg = d3.select("#co2").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
svg.append("path")
// pull in data to the path
.datum(data)
.attr("class", "area")
// passing in the area function, for each object
.attr("d", area)
// starts the graph opacity at 0 and fades to 1 over 2.5 seconds
.style("fill-opacity", "0")
.transition()
.duration(2500)
.style("fill-opacity", "1");
Rather than try to use transition on the shape of the area graph, you could apply a scale(x,y) transform against the whole svg element that you want to animate. The advantage of this approach is that it is not limited to a particular rendering implementation (eg: not path/d3.area specific).
There are a couple of gotchas to note though:
To avoid the transition() behaviour, working on the margin adjustments, make sure you have a separate 'g' element for the transition() transforms to act on
SVG has its origin (0,0) in the top-left, so in addition to scaling the SVG area, you need to set the base of the graph
This is put together below:
'g' element:
var svg = d3.select("#co2").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left, "," + margin.top + ")")
// define a new 'g' to scope the transition, and keep separate from the margin adjusting transform above
.append("g");
transition() including base adjustment:
svg
.attr("transform", "translate(0," + height + ") scale(1, 0)")
.transition().duration(2000)
.attr("transform", "translate(0,0) scale(1, 1)");
As ever, this is best illustrated with the complete example: http://bl.ocks.org/4239516

Categories