I am trying to get rid of the outer circle of the bubble chart. But actually now I am at my wit's end... It seems there is few tutorial online on how to plot bubble chart using csv Data. Please check out my working PLUNK and help me out.
PLUNK: http://plnkr.co/edit/87WLm3OmK1jRtcq8p96u?p=preview
d3.csv("count_s.csv", function(csvData) {
var years = [2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014];
pack.value(function(d) {
return +d["count" + years[i]];
});
var data = {
name: "city",
children: csvData
};
var node = svg1.selectAll("g.node")
.data(pack.nodes(data), function(d) {
return d.city;
});
The code responsible for circle creation in your example is this (file bubble.js, lines 63-70):
//Add the Circles
var circles = nodeEnter.append("circle")
.attr("r", function(d) {
return d.r;
})
.style("fill", function(d) {
return color1(d.city);
});
All you need to do is to put the line
.filter(function(d){ return d.parent; })
before call to append(), like this:
//Add the Circles
var circles = nodeEnter
.filter(function(d){ return d.parent; })
.append("circle")
.attr("r", function(d) {
return d.r;
})
.style("fill", function(d) {
return color1(d.city);
});
and you will get:
The explanation of the solution is that added line simply excludes any circle that does not have a parent (which is actually only the outermost circle) from rendering.
Modified plunk is here.
NOTE: The text in the middle of the outer circle is still displayed. If you do not want it either, you may apply similar code solutions as the one used for the circle itself.
Related
I am using this kind of scatterplot matrix and a histogram as two views, in d3. Both of them get the data from the same csv file. This is how the histogram looks like (x axis):
To brush the histogram I use the code below, which is similar to this snippet:
svg.append("g")
.attr("class", "brush")
.call(d3.brushX()
.on("end", brushed));
function brushed() {
if (!d3.event.sourceEvent) return;
if (!d3.event.selection) return;
var d0 = d3.event.selection.map(x.invert),
d1 = [Math.floor(d0[0]*10)/10, Math.ceil(d0[1]*10)/10];
if (d1[0] >= d1[1]) {
d1[0] = Math.floor(d0[0]);
d1[1] = d1[0]+0.1;
}
d3.select(this).transition().call(d3.event.target.move, d1.map(x));
}
How can I link the two views, so that when I brush the histogram, the scatterplot matrix will show the brushed points as colored in red, and the other points as, lets say, grey?
This can get you started:
3 html files:
2 for the visuals (histogram.html and scatter.html)
1 to hold them in iframes (both.html):
Dependency:
jQuery (add to all 3 files)
Create table with 2 cells in both.html:
Add iframes to each cell:
<iframe id='histo_frame' width='100%' height='600px' src='histo.html'></iframe>
<iframe id='scatter_frame' width='100%' height='600px' src='scatter.html'></iframe>
I am using this histogram, and this scatterplot.
Add the linky_dink function to call the function inside your scatter.html (see below...):
function linky_dink(linked_data) {
document.getElementById('scatter_frame').contentWindow.color_by_value(linked_data);
}
In your scatter.html change your cell.selectAll function to this:
cell.selectAll("circle")
.data(data)
.enter().append("circle")
.attr("cx", function(d) { return x(d[p.x]); })
.attr("cy", function(d) { return y(d[p.y]); })
.attr("r", 4)
.attr('data-x', function(d) { return d.frequency }) // get x value being plotted
.attr('data-y', function(d) { return d.year }) // get y value being plotted
.attr("class", "all_circles") // add custom class
.style("fill", function(d) { return color(d.species); });
}
Note the added lines in bold:
Now our histogram circle elements retain the x and y values, along with a custom class we can use for targeting.
Create a color_by_value function:
function color_by_value(passed_value) {
$('.all_circles').each(function(d, val) {
if(Number($(this).attr('data-x')) == passed_value) {
$(this).css({ fill: "#ff0000" })
}
});
}
We know from above this function will be called from the linky_dink function of the parent html file. If the passed value matches that of the circle it will be recolored to #ff0000.
Finally, look for the brushend() function inside your histogram.html file. Find where it says: d3.selectAll("rect.bar").style("opacity", function(d, i) { .... and change to:
d3.selectAll("rect.bar").style("opacity", function(d, i) {
if(d.x >= localBrushYearStart && d.x <= localBrushYearEnd || brush.empty()) {
parent.linky_dink(d.y)
return(1)
} else {
return(.4)
}
});
Now, in addition to controlling the rect opacity on brushing, we are also calling our linky_dink function in our both.html file, thus passing any brushed histogram value onto the scatterplot matrix for recoloring.
Result:
Not the greatest solution for obvious reasons. It only recolors the scatterplot when the brushing ends. It targets circles by sweeping over all classes which is horribly inefficient. The colored circles are not uncolored when the brushing leaves those values since this overwhelms the linky_dink function. And I imagine you'd rather not use iframes, let alone 3 independent files. Finally, jQuery isn't really needed as D3 provides the needed functionality. But there was also no posted solution, so perhaps this will help you or someone else come up with a better answer.
I've created a plunk to demonstrate my problem.
The issue is that my .enter(),update(),exit() method is not working for my d3.chart.layout() visualization.
Instead, I get the classic "double post" problem. My keys are the same, however.
What I want to happen is for my d3 steam graph to update its data (such that the y values all go to 0, and the chart disappears). My data binding is coded normally:
var steam = svg.selectAll(".layer")
.data(layers, function(d){console.log(d); return d.key})
steam.enter().append("path")
steam.style("fill", function(d, i) { return z(i); }).style("opacity","0").transition().duration(400)
.style("opacity","1")
.attr("class", "layer")
.attr("d", function(d) { return area(d.values); })
steam.exit().transition().duration(500).remove()
What is happening/any ideas?
So I got it to work, though I'm still confused as to why it works this way. I needed to move the svg adding out of the update function and into the namespace (that was obvious).
But the update became this, with a transition() method. Can anyone help me understand where I went wrong?
test = svg.selectAll("path").data(layers, function(d){return d.key})
test.enter().append("path")
test.style("opacity",1).style("fill", function(d, i) { return z(i); })
.attr("class", "layer").transition()
.duration(2000)
.attr("d", function(d) { return area(d.values); });
test.exit().transition().duration(1500).style("opacity",0).remove();
I am trying to draw a pack layout in d3.js without outermost variable.
I want to draw a pack layout without outer most parent circle. Is there any way to do it?
Yes, there is. I would suggest following approach: you leave all circle pack initialization intact. You only change the point of code where circles are actually appended to DOM/SVG tree. I'll show this in a couple of examples. This jsfiddle is an example of "regular" circle pack:
The key code responsible for adding circles to the DOM tree is this:
var circles = vis.append("circle")
.attr("stroke", "black")
.style("fill", function(d) { return !d.children ? "tan" : "beige"; })
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("r", function(d) { return d.r; });
If one adds just this line between "vis" and ".append("circle")": (another jsfiddle available here)
.filter(function(d){ return d.parent; })
the root node will disappear:
If one adds this line:
.filter(function(d){ return !d.children; })
all nodes except leave nodes (in other words, those without children) will disappear:
And, a little bit more complex, this line
.filter(function(d){ return (d.depth > 1); })
will make the root parent circle and all its direct children disappear:
Currently I am learning some "D3.js" and attempting to get my head around the way data is processed and selected.
I'm stuck on the following task I've created for myself.
Ideally, I want something that is functionally equivalent to:
<svg>
<circle r="20.5" cx="100" cy="200"></circle>
<circle r="20.5" cx="300" cy="10"></circle>
</svg>
What I have currently (with my logic) is:
var matrix = [ [{ "x": 100, "y": 200 }], [{ "x": 300, "y": 10 }]];
var result = d3.select("body").append("svg") // Append SVG to end of Body
.data(matrix) // select this data
.selectAll("g") //g is a svg grouping tag
.data(function (d) { return d; }) //Unwrap the first part of the array
.enter() // Grab all the data that is new to the selection in each array
.selectAll("g")
.data(function (d) { return d;}) // foreach each item inside the 1D array
.enter() // For all the data that doesn't exist already in the SVG
.append("circle") // Append Circle to the DOM with the following attributes
.attr("r", 20.5)
.attr("cx", function (d) { return d.x; })
.attr("cy", function (d) { return d.y; });
};
Weirdly enough the following :
var result = d3.select("body").append("svg")
.data(matrix)
.selectAll("g")
.enter()
.append("circle")
.attr("r", 20.5)
.attr("cx", function (d) { return d.x; })
.attr("cy", function (d) { return d.y; });
};
Seems somehow able to get the first item in the array but doesn't iterate correctly. I'm not quite sure how it's entering the array.
D3 seems to be quite a big step away from the programming paradigms I'm used to, and more difficult to debug so it would be awesome if someone could explain where I'm going wrong.
Oh, and while the example is quite useless and I could flatten it using the merge command - for the purposes of fully understanding D3 manipulation. I'd like to draw the couple of circles without the merge :)
Thanks!
Seeing you mention that you're new to d3 I'll make a few comments on the basics.
The first is that we're trying to place some svg elements on the DOM, so first we have to have a svg canvas to work on. Typically its set up early in the code and looks something like this:
var svg = d3.select("body")
.append("svg")
.attr("width", 350)
.attr("height", 250);
Note that it would be best to define variables for height and width (but I'm being lazy).
So now you have your canvas lets look at how d3 iterates. d3 iterates over an array, so you don't have to have an array within an array for your example as in:
var matrix = [ { "x": 100, "y": 200 }], [{ "x": 300, "y": 10 }];
Now you're second block of code is almost there, with just a bit of rearrangement. The first thing we need to do is t create placeholders for the circles in your svg canvas using svg.selectAll("circle"). Next we introduce the data to the empty placeholders using data(matrix) and this is bound using 'enter()`. Now all we have to do is append the circles and give them some attributes which is all the rest of the code does
svg.selectAll("circle")
.data(matrix)
.enter()
.append("circle")
.attr("r", 20.5)
.attr("cx", function (d) {
return d.x;
})
.attr("cy", function (d) {
return d.y;
});
You can see this all put together in this fiddle with some minor changes.
If you want to get to know d3 I'd really recommend get Scott Murray book on d3 it's an excellent introduction
I have a follow-up question to this question that I closed too hastily.
How/where would I put a .exit() or .transition() for the answers? In my original code, I'm saving the main chart in a variable (questions_chart), so I can say something like questions_chart.exit().remove(). However, if I put .exit().remove() after the .text() (last line in the code in Peter's answer), I get an error saying that object array has no method 'exit'
I have not tested this but will give it a shot...you would need to preserve the variable binding the data...like so:
var divs = d3.select("body").selectAll("div")
.data(data.questions);
divs
.enter().append("div") // this creates the question divs
.text(function(d) { return d.value; })
.selectAll("div")
.data(function(d) { return d.answers; })
.enter().append("div") // this creates the nested answer divs
.text(function(d) { return d.value; });
divs.exit().remove();
I am assuming you don't need to remove just the divs that are answers. Hope this helps.
UPDATE: giving it a shot at dealing with questions and answers...
var divs = d3.select("body").selectAll("div")
.data(data.questions);
var questionDivs = divs
.enter().append("div") // this creates the question divs
.text(function(d) { return d.value; });
divs.exit().remove();
var answerDivs = questionDivs
.selectAll("div")
.data(function(d) { return d.answers; });
answerDivs
.enter().append("div") // this creates the nested answer divs
.text(function(d) { return d.value; });
answerDivs.exit().remove();