am pretty new to both JS and D3js. I am trying to create a single circle on the map that transitions (Size increases then decreases) through my data. However, as from this example, 4 elements are added to the svg. and the size of each of them changes at the same time. However, what i want to create a single circle than transitions using the count field. Link to my current block:
https://bl.ocks.org/shannondussoye/e8feaa2cf22f7e6a7d12582b923d999f
Thanks
Your code here:
var circle = svg.selectAll("circle")
.data(data.features)
.enter()
.append("circle");
Will append one circle for each item in the array data.features (assuming no circles are already present). As there are four items in the array, you'll have four circles. If you just want to append one circle, change the input data array to an array of one feature:
.data([data.features[0]]).enter().append()...
Then, you can update that data, after the initial append, when you want to transition to a new feature:
.data([data.features[i]])
.attr("attr to be updated",...)
The example below applies this method in a non-geographic setting:
1. append a feature,
2. update the feature with properties from the next item in an array,
3. repeat:
var svg = d3.select("body")
.append("svg")
.attr("width",400)
.attr("height",400);
var data = [{x:100,y:100,r:10},{x:200,y:100,r:30},{x:200,y:200,r:10},{y:200,x:100,r:25}];
var circle = svg.selectAll("circle")
.data([data[0]])
.enter()
.append("circle")
.attr("cx",function(d) { return d.x; })
.attr("cy",function(d) { return d.y; })
.attr("r",function(d) { return d.r; });
var i = 0;
transition();
function transition() {
circle.data([data[++i%4]]) // get the next item in the data array, assign that datum to the feature
.transition()
.attr("cx",function(d) { return d.x; }) // update the properties of the feature
.attr("cy",function(d) { return d.y; })
.attr("r", function(d) { return d.r; })
.each("end",transition) // loop
.duration(2000)
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
Here is your map with that method (with minimal code changes - though regretfully I changed the json file name)
If you do console.log(data.features.length), you'll have the result 4. That means that data.features has 4 arrays and, of course, your enter selection will have 4 circles.
As those arrays seem to have the same geographic position (all of them point to the Town Hall and have the same coordinates, which is "151.2062183,-33.8732664"), use just one of them. For instance, the first array:
var circle = svg.append("circle")
.datum(data.features[0]);
That will append just one circle, for the first element.
Here is your updated bl.ocks: https://bl.ocks.org/anonymous/7e930937a6d8c0a24c6ca3a033a7cf84/f176a662d4ccd4d33b56101a17e93e6a7e0ef724
Related
I'm trying to draw a line based on a few points that come with the help of a setinterval, I'm passing the points one at a time but I could also pass several. for now I want the points of the line to be drawn one at a time but without re-drawing those that have already been drawn. How can I solve this problem ?
http://plnkr.co/edit/JjIiqrf97Y8K7YUJRodo?p=preview
var path=g.selectAll(".line1")
.data(data)
.enter()
.append("path")
.attr("class", "line1")
.attr("d", function(d,i) { return (line(data)); })
.style("stroke", function(d) { return "brown" });
in my real problem, I will receive points in real time that is why I am practicing how to draw point to point and generate an animation
Its because of your loop. You are pushing to datos and then rerendering the entire array.
var datos = new Array();
setInterval(function(){
if(datos.length<newPoints.length){
datos.push(newPoints[count]);
count++;
// Here you are rerendering this new piece of data,
// AND all of the previous pushes of data.
display(datos);
}
}, 1000);
I want to draw a pie chart for every point on the map instead of a circle.
The map and the points are displaying well but the pie chart is not showing over the map points. There is no error also. I can see the added pie chart code inside map also.
Below is the code snippet .
var w = 600;
var h = 600;
var bounds = [[78,30], [87, 8]]; // rough extents of India
var proj = d3.geo.mercator()
.scale(800)
.translate([w/2,h/2])
.rotate([(bounds[0][0] + bounds[1][0]) / -2,
(bounds[0][1] + bounds[1][1]) / -2]); // rotate the project to bring India into view.
var path = d3.geo.path().projection(proj);
var map = d3.select("#chart").append("svg:svg")
.attr("width", w)
.attr("height", h);
var india = map.append("svg:g")
.attr("id", "india");
var gDataPoints = map.append("g"); // appended second
d3.json("data/states.json", function(json) {
india.selectAll("path")
.data(json.features)
.enter().append("path")
.attr("d", path);
});
d3.csv("data/water.csv", function(csv) {
console.log(JSON.stringify(csv))
gDataPoints.selectAll("circle")
.data(csv)
.enter()
.append("circle")
.attr("id", function (d,i) {
return "chart"+i;
})
.attr("cx", function (d) {
return proj([d.lon, d.lat])[0];
})
.attr("cy", function (d) {
return proj([d.lon, d.lat])[1];
})
.attr("r", function (d) {
return 3;
})
.each(function (d,i) {
barchart("chart"+i);
})
.style("fill", "red")
//.style("opacity", 1);
});
function barchart(id){
var data=[15,30,35,20];
var radius=30;
var color=d3.scale.category10()
var svg1=d3.select("#"+id)
.append("svg").attr('width',100).attr('height',100);
var group=svg1.append('g').attr("transform","translate(" + radius + "," + radius + ")");
var arc=d3.svg.arc()
.innerRadius('0')
.outerRadius(radius);
var pie=d3.layout.pie()
.value(function(d){
return d;
});
var arcs=group.selectAll(".arc")
.data(pie(data))
.enter()
.append('g')
.attr('class','arc')
arcs.append('path')
.attr('d',arc)
.attr("fill",function(d,i){
return color(d.data);
//return colors[i]
});
}
water.csv:
lon,lat,quality,complaints
80.06,20.07,4,17
72.822,18.968,2,62
77.216,28.613,5,49
92.79,87.208,4,3
87.208,21.813,1,12
77.589,12.987,2,54
16.320,75.724,4,7
In testing your code I was unable to see the pie charts rendering, at all. But, I believe I still have a solution for you.
You do not need a separate pie chart function to call on each point. I'm sure that there are a diversity of opinions on this, but d3 questions on Stack Overflow often invoke extra functions that lengthen code while under-utilizing d3's strengths and built in functionality.
Why do I feel this way in this case? It is hard to preserve the link between data bound to svg objects and your pie chart function, which is why you have to pass the id of the point to your function. This will be compounded if you want to have pie chart data in your csv itself.
With d3's databinding and selections, you can do everything you need with much simpler code. It took me some time to get the hang of how to do this, but it does make life easier once you get the hang of it.
Note: I apologize, I ported the code you've posted to d3v4, but I've included a link to the d3v3 code below, as well as d3v4, though in the snippets the only apparent change may be from color(i) to color[i]
In this case, rather than calling a function to append pie charts to each circle element with selection.each(), we can append a g element instead and then append elements directly to each g with selections.
Also, to make life easier, if we initially append each g element with a transform, we can use relative measurements to place items in each g, rather than finding out the absolute svg coordinates we would need otherwise.
d3.csv("water.csv", function(error, water) {
// Append one g element for each row in the csv and bind data to it:
var points = gDataPoints.selectAll("g")
.data(water)
.enter()
.append("g")
.attr("transform",function(d) { return "translate("+projection([d.lon,d.lat])+")" })
.attr("id", function (d,i) { return "chart"+i; })
.append("g").attr("class","pies");
// Add a circle to it if needed
points.append("circle")
.attr("r", 3)
.style("fill", "red");
// Select each g element we created, and fill it with pie chart:
var pies = points.selectAll(".pies")
.data(pie([0,15,30,35,20]))
.enter()
.append('g')
.attr('class','arc');
pies.append("path")
.attr('d',arc)
.attr("fill",function(d,i){
return color[i];
});
});
Now, what if we wanted to show data from the csv for each pie chart, and perhaps add a label. This is now done quite easily. In the csv, if there was a column labelled data, with values separated by a dash, and a column named label, we could easily adjust our code to show this new data:
d3.csv("water.csv", function(error, water) {
var points = gDataPoints.selectAll("g")
.data(water)
.enter()
.append("g")
.attr("transform",function(d) { return "translate("+projection([d.lon,d.lat])+")" })
.attr("class","pies")
points.append("text")
.attr("y", -radius - 5)
.text(function(d) { return d.label })
.style('text-anchor','middle');
var pies = points.selectAll(".pies")
.data(function(d) { return pie(d.data.split(['-'])); })
.enter()
.append('g')
.attr('class','arc');
pies.append("path")
.attr('d',arc)
.attr("fill",function(d,i){
return color[i];
});
});
The data we want to display is already bound to the initial g that we created for each row in the csv. Now all we have to do is append the elements we want to display and choose what properties of the bound data we want to show.
The result in this case looks like:
I've posted examples in v3 and v4 to show a potential implementation that follows the above approach for the pie charts:
With one static data array for all pie charts as in the example: v4 and v3
And by pulling data from the csv to display: v4 and v3
I'm having a mental block about using the result of:
.rollup(function(leaves) { return leaves.length;})
as the radius of a circle in a scatter plot. My complete code (and sample data) is in a plunk here https://plnkr.co/edit/Cwuce6inLV5jouCWTFfN
The scatter works with a static value of 5 but I'd like to use value based on the .rollup from the d3.nest as explained in this other SO question I had: Capturing leaves.length value from d3.nest
I think I'm missing a key concept about in this section of code:
d3.tsv("etds_small.tsv", function(error, dataset) {
dataset.forEach(function(d) {
if(deptlist.indexOf(d.dept) == -1) deptlist.push(d.dept);
if(years.indexOf(d.year) == -1) years.push(d.year);
})
var deptYearCount = d3.nest()
//.key(function(d) { return d.college;} )
.key(function(d) { return d.dept})
.key(function(d) { return d.year })
.rollup(function(leaves) { return leaves.length;})
.map(dataset);
console.log(dataset); // retains the college, dept, and year labels
console.log(deptYearCount); // replaces labels with "key"
x.domain(years.sort(d3.ascending));
y.domain(deptlist.sort(d3.ascending));
//console.log(y.domain());
//console.log(x.domain());
svg.selectAll(".dot")
.data(dataset) //should this be deptYearCount from the d3.nest above?
.enter().append("circle")
.attr("class", "dot")
.attr("r", 5) // Would like to use length (from d3.nest) as radius
//.attr("r", function(d) {return d.values.length*1.5;}) works with .data(debtYearCount)
.style("opacity", 0.3)
.style("fill", "#e31a1c" )
.attr("cx", function(d) {
return x(d.year);
})
.attr("cy", function(d) {
return y(d.dept);
});
Give this a try for your radius:
.attr("r", function(d) {return Object.keys(deptYearCount[d.dept]).length*1.5;})
Because you are using .map(dataset) instead of .entries(dataset), d3.next() is returning one-big-object instead of an array of objects. That one-big-object does not contain a property called values.
Updated explanation:
First, look at the structure of the object deptYearCount. It has property names like Earth Sciences., Education., etc.
Our d3 data is iterating over an array of objects. Each object has property dept that looks like Earth Sciences., Education., etc.
So, deptYearCount[d.dept] is getting us to the correct property within deptYearCount.
For example, at one round of our iteration we are looking at deptYearCount["Education."]. That turns out to be another object with properties like 2007,2008, etc. Therefore, the number of properties in deptYearCount["Education."] is the value we want for the radius.
How do we find the number of properties of deptYearCount["Education."]? One way is the Object.keys(someObject) function. It returns an array of strings corresonding to the property names, and we just need its .length.
I am trying to draw a circular heat or ring-chart. There are several options it seems with d3js. The most popular appears to use the pie layout to make several donut rings Another option is to use a circular heat chart like this one -
Both of these however use filling segments as their way of depicting area size. I wanted however to use lines to depict events over time. With each line occurring within a particular ring.
To get this effect, I've adapted this radial weather chart - http://bl.ocks.org/susielu/b6bdb82045c2aa8225f5
This is my attempt so far:
http://blockbuilder.org/jalapic/12a3a23651f40283d489
It does not have labeling, but each ring (12 total) represents an individual subject. Each segment represents a sample of time (says months here but could be anything). The lines are drawn within each ring that they belong to. I have kept the same variable names as the weather example to enable comparisons between my stripped down code and the author's original code.
This is what it looks like:
My question is how might it be possible to mouseover each ring to make only that ring's contents (i.e. lines) remain visible, i.e. to hide the other rings - this would make viewing the chart easier.
Here is the code for how the rings are made up:
var mycircles = [110,100, 90, 80, 70, 60,50,40,30,20,10,0]
origin.selectAll('circle.axis-green')
.data(mycircles) //original circles
.enter()
.append('circle')
.attr('r', function(d) { return rScale(d)})
.style("fill", "#fff8ee")
.style("opacity", .05)
.attr('class', 'axis record')
.on("mouseover", function(d) {d3.select(this).style("fill", "red");})
.on("mouseout", function(d) {d3.select(this).style("fill", "#fff8ee");
});
As can be seen the rings are actually overlapping circles. Is there a way to achieve what I'm trying to do using the approach I'm taking, or would I have to go back to working something out with segments like in the heatchart or pie layouts?
Looking at your data and code, one method would be to assign a class to each line representing it's ring position. You can then use mouseover and mouseout events to toggle the opacity of those lines.
First, create a couple helper functions:
// which ring is currently highlighted
var curRing = null;
// show all rings
function unShowRing(){
d3.selectAll(".record")
.style("opacity", 1);
curRing = null;
}
// only show current ring
function showRing(ringId){
// all lines that are not in my ring, hide them
d3.selectAll(".record:not(.ring" + ringId + ")")
.style("opacity", 0);
curRing = ringId;
}
Set up the lines a little different:
...
.enter().append('line')
// assign a unique class to each ring's lines
.attr('class', function(d) {
return cl + " ring" + d.recLow/10;
})
// on mouseover only show my ring
.on("mouseover", function(d){
var ringId = d.recLow/10;
showRing(ringId);
})
// on mouseout show all rings
.on("mouseout", function(d){
unShowRing();
})
// this will prevent lines transitioning in from being shown
.style('opacity', function(d){
if (!curRing){
return 1;
} else {
var ringId = d.recLow/10;
return ringId === curRing ? 1 : 0;
}
})
Finally, you'll need to handle the ring "circle" mouseovers as well in case the user mouses over lines or rings:
origin.selectAll('circle.axis-green')
.data(mycircles) //original circles
...
.on("mouseover", function(d) {
d3.select(this).style("fill", "red");
var ringId = d/10;
showRing(ringId);
})
.on("mouseout", function(d) {
d3.select(this).style("fill", "#fff8ee");
unShowRing();
});
Here's the whole thing working.
I have a function that loads an initial array of points onto a map using D3:
var fires = []; //huge array of objects
function update(selection) {
feature = g.selectAll("path")
.data(selection);
feature.attr("class", "update");
feature.enter().append("path")
.attr("class", "enter")
.style("fill", function(d) {return colorScale(d.area)});
feature.exit().remove();
}
I call it initially with a large array:
update(fires);
I also have a histogram that allows me to narrow down the selection based on years. Using a D3 brush, on 'brushend' I call a function called brushed:
function brushed() {
if (!d3.event.sourceEvent) return; // only transition after input
var extent0 = brush.extent();
startYear = Math.floor(extent0[0]);
endYear = Math.floor(extent0[1]);
var selectedFires = fires.filter(function(fire) {
if(fire.year >= startYear && fire.year <= endYear) return fire;
});
console.log(selectedFires.length); //does reflect the proper amount of elements
update(selectedFires);
}
When I narrow the selection, the points on the map disappear as expected. When I widen it, the points/elements do not return. The array is getting the correct elements in it, they're just not being appended to the SVG.
I'm missing something fundamental as the examples I've seen: http://bl.ocks.org/mbostock/3808218 appear to append elements just fine.
Without seeing the rest of the code (which really helps), and focusing on your selection piece alone, try this:
function update(selection) {
// binding the data
feature = g.selectAll(".path")
.data(selection);
// exit selection
feature
.exit()
.remove();
// enter selection
feature
.enter()
.append("path")
.attr("class","path");
// update selection
feature
.style("fill", function(d) {return colorScale(d.area)});
// update selection
feature
.style("fill", function(d) {return colorScale(d.area)})
.attr("d",path); // this was the missing piece!
}
NOTE: you also want to comment out where you hardcoded the extent of the brush:
//brush
var brush = d3.svg.brush()
.x(areaYearScale)
//.extent([1984, 2013]) // comment this out
.on("brushend", brushed);