I have data that can have a variable numbers of series. And inside each of those series is a date and number that I want to plot as a scatter plot in D3js.
This is my (non working) code. It works when I do it straight, but not once I add the $.each loop. I'm pretty sure its some sort of problem with indexing or something like that.
var color = d3.scale.category20();
// Now actually add the data to the graph
$.each(mpgData, function(k, v) {
console.log(v);
//console.log(k);
svg.selectAll('circle')
.data(v)
.enter()
.append('circle')
.attr('cx', function(d, i) {
console.log(i);
//console.log(d);
return xScale(getDate(d[1]));
})
.attr('cy', function(dd, ii) {
//console.log(ii);
return yScale(dd[2]);
})
.attr('fill', function(d, i) {
return color(k);
})
.attr("class", "mpgColorClass"+k)
.attr("r", 5)
.on("mouseover", function() {
d3.selectAll(".mpgColorClass"+k)
.attr("r", 8);
})
.on("mouseout", function() {
d3.selectAll(".mpgColorClass"+k)
.attr("r", 5);
});
});
I only showed what I think is the relevant part.
So that code kind of works. But it only shows 6 things, which I think is because the 2nd 'series' has 6 items. So somehow its not looping over everything at the "attr('cx', function(d, i)) part. I think I'm not understanding how to get that function to loop over each part of the series.
I'm new to D3js, so still struggling through the learning curve. But it works and I get a graph out with the correct data. Its just not ALL the data, only 6 points out of the entire (variable) dataset.
Thanks!
in your $.each() block you are overwriting the same set of circles in the SVG element. So instead of using selectAll('circle') you can do this:
$.each(mpgData, function(k, v) {
svg.selectAll('circle' + k)
.data(v)
.enter()
.append('circle')
.attr('class','circle' + k)
});
truncated rest of details in your code... edit at will.
Related
Very much D3 newbie here, feeling my way around adapting an existing radar chart built in D3 v3.5.9
I'm having an issue in interpolating between points when there are zero values between them.
Example:
radar when all data points are present, looks fine
radar when there are zero values
The behaviour I want is for the interpolation to go around the circle, rather than just closing the sections bounded by the non-zero values.
green lines show desired behaviour whenever a zero is encountered
I have used the 'defined' function to find zero values in the source data, but I need to add something else to instruct D3 to draw the connecting lines between the desired points. Something with the index value for d, probably?
Or perhaps 'defined' is not the right function in this case?
var radarLine = d3.svg.line
.radial()
.defined(function (d) {
return d.value !== 0;
})
.interpolate("linear-closed")
.radius(function (d) {
return rScale(d.value);
})
.angle(function (d, i) {
return i * angleSlice;
});
if (cfg.roundStrokes) {
radarLine.interpolate("cardinal-closed");
}
//Create a wrapper for the blobs
var blobWrapper = g
.selectAll(".radarWrapper")
.data(data)
.enter()
.append("g")
.attr("class", "radarWrapper");
//Append the backgrounds
blobWrapper
.append("path")
//.attr("class", "radarArea")
.attr("class", function (d) {
return "radarArea" + " " + d[0].radar_area.replace(/\s+/g, "");
})
.attr("d", function (d, i) {
return radarLine(d);
})
.style("fill", function (d, i) {
return cfg.color(i);
})
.style("fill-opacity", 0);
//Create the outlines
blobWrapper
.append("path")
.attr("class", "radarStroke")
.attr("d", function (d, i) {
return radarLine(d);
})
.style("stroke-width", cfg.strokeWidth + "px")
.style("stroke", function (d, i) {
return cfg.color(i);
})
.style("fill", "none");
Any help would be greatly appreciated!
I am attempting to access the data index of a shape on mouseover so that I can control the behavior of the shape based on the index.
Lets say that this block of code lays out 5 rect in a vertical line based on some data.
var g_box = svg
.selectAll("g")
.data(controls)
.enter()
.append("g")
.attr("transform", function (d,i){
return "translate("+(width - 100)+","+((controlBoxSize+5)+i*(controlBoxSize+ 5))+")"
})
.attr("class", "controls");
g_box
.append("rect")
.attr("class", "control")
.attr("width", 15)
.attr("height", 15)
.style("stroke", "black")
.style("fill", "#b8b9bc");
When we mouseover rect 3, it transitions to double size.
g_box.selectAll("rect")
.on("mouseover", function(d){
d3.select(this)
.transition()
.attr("width", controlBoxSize*2)
.attr("height", controlBoxSize*2);
var additionalOffset = controlBoxSize*2;
g_box
.attr("transform", function (d,i){
if( i > this.index) { // want to do something like this, what to use for "this.index"?
return "translate("+(width - 100)+","+((controlBoxSize+5)+i*(controlBoxSize+5)+additionalOffset)+")"
} else {
return "translate("+(width - 100)+","+((controlBoxSize+5)+i*(controlBoxSize+5))+")"
}
})
})
What I want to do is move rect 4 and 5 on mouseover so they slide out of the way and do not overlap rect 3 which is expanding.
So is there a way to detect the data index "i" of "this" rect in my mouseover event so that I could implement some logic to adjust the translate() of the other rect accordingly?
You can easily get the index of any selection with the second argument of the anonymous function.
The problem here, however, is that you're trying to get the index in an anonymous function which is itself inside the event handler, and this won't work.
Thus, get the index in the event handler...
selection.on("mouseover", function(d, i) {
//index here ---------------------^
... and, inside the inner anonymous function, get the index again, using different parameter name, comparing them:
innerSelection.attr("transform", function(e, j) {
//index here, with a different name -----^
This is a simple demo (full of magic numbers), just to show you how to do it:
var svg = d3.select("svg");
var data = d3.range(5);
var groups = svg.selectAll("foo")
.data(data)
.enter()
.append("g");
var rects = groups.append("rect")
.attr("y", 10)
.attr("x", function(d) {
return 10 + d * 20
})
.attr("width", 10)
.attr("height", 100)
.attr("fill", "teal");
groups.on("mouseover", function(d, i) {
d3.select(this).select("rect").transition()
.attr("width", 50);
groups.transition()
.attr("transform", function(e, j) {
if (i < j) {
return "translate(40,0)"
}
})
}).on("mouseout", function() {
groups.transition().attr("transform", "translate(0,0)");
rects.transition().attr("width", 10);
})
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg></svg>
PS: don't do...
g_box.selectAll("rect").on("mouseover", function(d, i){
... because you won't get the correct index that way (which explain your comment). Instead of that, attach the event to the groups, and get the rectangle inside it.
I'm pretty sure d3 passes in the index as well as the data in the event listener.
So try
.on("mouseover", function(d,i)
where i is the index.
Also you can take a look at a fiddle i made a couple months ago, which is related to what you're asking.
https://jsfiddle.net/guanzo/h1hdet8d/1/
You can find the index usign indexOf(). The second argument in the event mouseover it doesn't show the index in numbers, it shows the data info you're working, well, you can pass this info inside indexOf() to find the number of the index that you need.
.on("mouseover", (event, i) => {
let index = data.indexOf(i);
console.log(index); // will show the index number
})
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 am trying to show two data sets: one with a lower opacity and the other with normal dots using D3.js. I tried this:
svg.selectAll("*").remove();
if (olddset!=dset) {
svg.selectAll("circle") .data(datasets[olddset]) .enter() .append("circle")
.attr('cx',function(a){ return xscales[whichscale][xval](a[xval]); })
.attr('cy',function(a){ return yscales[whichscale][yval](a[yval]); })
.attr('r',1)
.style("opacity", 0.2)
;
}
svg.selectAll("circle") .data(datasets[dset]) .enter() .append("circle")
.attr('cx',function(a){ return xscales[whichscale][xval](a[xval]); })
.attr('cy',function(a){ return yscales[whichscale][yval](a[yval]); })
.attr('r',3)
.style("opacity", 1)
;
However, that does not do what I was looking for. I wanted olddset to be small dots and the new dataset (dset) with r=3 and opacity=1. What am I doing wrong?
You are using the same selection for the first AND second dataset. Thus for the second dataset you are selecting <circle>s that already exist. Instead, use a different selection, for example:
svg.selectAll("circle2")
You can handle this with styles. Like:
svg.selectAll("circle.oldset").data(datasets[olddset]).enter().append("circle")
.attr('cx',function(a){ return xscales[whichscale][xval](a[xval]); })
.attr('cy',function(a){ return yscales[whichscale][yval](a[yval]); })
.attr('r',1)
.classed("oldset", true); // where the oldset class in yr styles has the opacity defined
svg.selectAll("circle.dset") .data(datasets[dset]) .enter() .append("circle")
.attr('cx',function(a){ return xscales[whichscale][xval](a[xval]); })
.attr('cy',function(a){ return yscales[whichscale][yval](a[yval]); })
.attr('r',3)
.classed("dset", true);
You will run into trouble with the "circle2" trick if you try to update because it will never select anything.
I'm trying to create something similar to this example: Wealth and Health of Nations:
My data comes from a JSON file, just like the example, but when I add the transitions, I'm getting duplicate bubbles. Instead of the bubble transitioning from point A to point B I'm getting 2 bubbles (one for point A, one for point B). Generally speaking, the transition is not able to differentiate between 2 data points for the same bubble or 2 separate bubbles.
Looking at the example, I'm missing the interpolate and bisect functions. I haven't been able to grasp how they work and what exactly i'm doing wrong. Is this what's causing the problem in my graph?
Also, can someone give me an example on how bisectors and interpolate works in d3?
Code:
g = d3.select("#animation")
.append("svg")
.attr("width", width)
.attr("height", height);
x_extent = [0, 100];
x_scale = d3.scale.linear().domain(x_extent).range([margin + 20, width - 30]);
y_extent = [0, 60];
y_scale = d3.scale.linear().domain(y_extent).range([height - margin, margin]);
r_scale = d3.scale.linear().domain([0, d3.max(jsondata, function (d) { return d.MSVMMboe; })]).range([2, 30]);
g.selectAll("circle").data(jsondata, function (d) { return d.EffectiveDate; }).enter().append("circle")
.attr("cx", function (d) { return x_scale(d.PercentageComplete * 100) })
.attr("cy", function (d) { return y_scale(d.GPoS * 100) })
.attr("r", function (d) { return r_scale(d.MSVMMboe) })
.attr("stroke", "blue")
.attr("stroke-width", 1)
.attr("opacity", 0.6)
.attr("fill", "red");
//add transition
g.selectAll("circle").data(jsondata, function (d) { return d.EffectiveDate; })
.transition()
.duration(1000);
You haven't told the transition what you want to change. You need to add some attribute changes for example. Have a look at the d3 website for examples and tutorials.