Reputation: 43
I have a scatter plot based on this example http://bl.ocks.org/mbostock/3887118.
What i want to do is add interactivity by allowing the user to choose the x and y dimensions, as well as the size of the circles based on one of the columns in the data file. When they choose something, the graph should automatically scale the axis.
How would i go about doing this? Here is my code so far: http://plnkr.co/edit/ZCdEBa79Y85Koz7MepXw?p=preview
Thanks.
<!DOCTYPE html>
<meta charset="utf-8">
<style>
/*set the axis line color, dot stroke, font size, and font position*/
body {
font: 15px sans-serif;
}
.name{
position: relative;
top: 90px;
text-align: left;
font-weight: bold;
}
.title {
position: relative;
text-align: left;
font-size: 25px;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.dot {
stroke: #000;
}
#filter {
position: absolute;
}
#mark {
padding-left: 150px;
position: inherit;
}
#xAXs {
position: relative;
left: 290px;
bottom: 30px;
}
#yAXs {
position: relative;
bottom: 30px;
left: 315px;
}
#label {
position: absolute;
top: 599px;
bottom: 125px;
left: 300px;
right: 0px;
}
#label2 {
position: absolute;
top: 599px;
bottom: 125px;
left: 430px;
right: 0px;
}
</style>
<body>
<script src="d3.min.js"></script>
<script>
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scale.linear()
.range([0, width]);
var y = d3.scale.linear()
.range([height, 0]);
var color = d3.scale.category10();
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
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 + ")");
d3.csv("iris.csv", function(error, data) {
data.forEach(function(d) {
d.petalLength = +d.petalLength;
d.petalWidth = +d.petalWidth;
d.sepalLength = +d.sepalLength;
d.sepalWidth = +d.sepalWidth;
});
x.domain(d3.extent(data, function(d) { return d.petalWidth; })).nice();
y.domain(d3.extent(data, function(d) { return d.petalLength; })).nice();
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.append("text")
.attr("class", "label")
.attr("x", width)
.attr("y", -6)
.style("text-anchor", "end")
.text("Petal Width (cm)");
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("class", "label")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Petal Length (cm)")
var circles = svg.selectAll(".dot")
.data(data)
.enter().append("circle")
.attr("class", "dot")
.attr("r", 3.5)
.attr("cx", function(d) { return x(d.petalWidth); })
.attr("cy", function(d) { return y(d.petalLength); })
.style("fill", function(d) { return color(d.species); });
var legend = svg.selectAll(".legend")
.data(color.domain())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
legend.append("rect")
.attr("x", width - 18)
.attr("width", 18)
.attr("height", 18)
.style("fill", color);
legend.append("text")
.attr("x", width - 24)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) { return d; });
d3.selectAll("[name=v]").on("change", function() {
var selected = this.value;
display = this.checked ? "inline" : "none";
svg.selectAll(".dot")
.filter(function(d) {return selected == d.species;})
.attr("display", display);
});
d3.selectAll("[name=sepal]").on("change", function(d) {
radius = this.value;
svg.selectAll(".dot")
console.log(radius);
circles.attr("r", radius);
});
d3.select("[name=xAX]").on("change", function(){
xAxy = this.value;
console.log(xAxy)
})
d3.select("[name=yAX]").on("change", function(){
yAxy = this.value;
console.log(yAxy)
})
});
</script>
<br><br>
<div id="filter">
<b>Species Filter:</b>
<br>
<input name='v' value="Iris-setosa" type="checkbox" checked>Iris-setosa
</input>
<br>
<input name="v" value="Iris-versicolor" type="checkbox" checked >Iris-versicolor
</input>
<br>
<input name="v" value="Iris-virginica" type="checkbox" checked >Iris-virginica
</input>
</div>
<form id="mark">
<b>Size of Mark:</b>
<div><input type="radio" name="sepal" value='sepalWidth'>Sepal Width</div>
<div><input type="radio" name="sepal" value="sepalLength">Sepal Length</div>
</form>
<div id="label"><b>x-Axis:</b></div>
<select name="xAX" id="xAXs">
<option value ="petalWidth">petalWidth</option>
<option value ="petalLength">petalLength</option>
<option value ="sepalLength">sepalLength</option>
<option value ="sepalWidth">sepalWidth</option>
</select>
<div id="label2"><b>y-Axis:</b></div>
<select name="yAX" id="yAXs">
<option value ="petalLength">petalLength</option>
<option value ="petalWidth">petalWidth</option>
<option value ="sepalLength">sepalLength</option>
<option value ="sepalWidth">sepalWidth</option>
</select>
<br>
</body>
Upvotes: 4
Views: 5041
Reputation: 1976
Try this plunkr: http://plnkr.co/edit/MkZcXJPS7hrcWh3M0MZ1?p=preview
You were almost there, I just had to update a couple of your functions. The following describes the highlights:
d3.selectAll("[name=sepal]").on("change", function(d) {
radius = this.value;
svg.selectAll(".dot")
console.log(radius);
circles.attr("r", function(d) { return d[radius]; });
});
You are setting radius
to the column in the csv that you want to read the radius from, so now you just have to update the radius of your circles in the svg
, which is basically done the same way as when you originally set them up.
d3.select("[name=xAX]").on("change", function(){
xAxy = this.value;
console.log(xAxy)
x.domain(d3.extent(data, function(d) { return d[xAxy]; })).nice();
svg.select(".x.axis").transition().call(xAxis);
svg.selectAll(".dot").transition().attr("cx", function(d) {
return x(d[xAxy]);
});
svg.selectAll(".x.axis").selectAll("text.label").text(axisNames[xAxy] + " (cm)");
});
d3.select("[name=yAX]").on("change", function(){
yAxy = this.value;
console.log(yAxy)
y.domain(d3.extent(data, function(d) { return d[yAxy]; })).nice();
svg.select(".y.axis").transition().call(yAxis);
svg.selectAll(".dot").transition().attr("cy", function(d) {
return y(d[yAxy]);
});
svg.selectAll(".y.axis").selectAll("text.label").text(axisNames[yAxy] + " (cm)");
});
The changes for the x
and y
axes are basically the same as each other, with just the axis being referenced getting changed. Here I'm doing:
xAxy
or yAxy
)svg
by setting up a transition and calling the xAxis
or yAxis
component again.cx
or cy
position of each .dot
text
on the axis by looking it up in a new array (axisNames
) which gives a pretty print of the variable being graphed.Upvotes: 2
Reputation: 108512
Your question is asking a lot. In general dynamic d3
visualizations follow the enter, exit, and update pattern. There's some great tutorial's out there to get you started.
To fix your specific code, you divide into three parts. First init the non-update-able portions of the plot:
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 500 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scale.linear()
.range([0, width]);
var y = d3.scale.linear()
.range([height, 0]);
var color = d3.scale.category10();
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var svg = d3.select("body").insert("svg",":first-child")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// keep reference to axises
var xg = svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")");
xg
.append("text")
.attr("class", "label")
.attr("x", width)
.attr("y", -6)
.style("text-anchor", "end");
var yg = svg.append("g")
.attr("class", "y axis");
yg
.append("text")
.attr("class", "label")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end");
// legend is always static
var legend = svg.selectAll(".legend")
.data(color.domain())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
legend.append("rect")
.attr("x", width - 18)
.attr("width", 18)
.attr("height", 18)
.style("fill", color);
legend.append("text")
.attr("x", width - 24)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) { return d; });
Then wrap the update-able portion in a function:
function update(){
// get user selections
var xVar = d3.select('#xAXs').node().value,
yVar = d3.select('#yAXs').node().value;
var checks = {};
d3.selectAll('input[type=checkbox]').each(function(){
checks[this.value] = this.checked;
});
var radAttr = d3.select('input[type=radio]:checked').node().value;
// filter data based on user selections
var data = baseData.filter(function(d,i){
d.x = d[xVar]; // create/modify a x,y so that d3 will know it's an update
d.y = d[yVar];
return checks[d.species];
});
// set domains
x.domain(d3.extent(data, function(d) { return d.x; })).nice();
y.domain(d3.extent(data, function(d) { return d.y; })).nice();
xg.call(xAxis);
yg.call(yAxis);
xg.select("text").text(xVar);
yg.select("text").text(yVar);
// on enter
var circles = svg.selectAll(".dot")
.data(data);
circles.enter()
.append("circle")
.attr("class", "dot");
circles.exit().remove();
// on update
circles.attr("cx", function(d) { return x(d.x); })
.attr("cy", function(d) { return y(d.y); })
.style("fill", function(d) { return color(d.species); })
.attr("r", function(d){ return d[radAttr]; });
}
Finally trigger update on user actions:
d3.selectAll('select').on('change',function(){
update();
});
d3.selectAll('input').on('click', function(){
update();
})
Here's an example putting this together.
Upvotes: 2