This might be trivial to some people, but I am a newbie to D3JS.
I am trying to plot two static bubbles with opacity changing with respect to an array. I am able to plot the bubbles but I can't make their opacity change continuously. I am using transition and delay and the opacity can only change once. Here is my code sample
(function() {
var dropsize = 100;
var gapsize = 20;
var osc = [[1, 1],[0.5, 0.5],[0, 0],[0.5, 0.5],[1, 1],[0.5, 0.5],[0, 0],[0.5, 0.5]];
var radius = dropsize / 2;
var h = 100;
var w = (4 * radius + 3 * gapsize);
var svg = d3.select("#chart").append("svg");
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h)
.style("background-color", "teal");
var circles = svg.selectAll("circle")
.data([radius, 3 * radius])
.enter()
.append("circle");
circles.attr("cx", function(d, i) {
return d + (i + 1) * gapsize;
})
.attr("cy", h / 2)
.attr("r", function(d, i) {
return radius;
})
.attr("fill", "orange")
.attr("class", "droplet")
.attr("id", function(d, i) {
return "c_" + (i + 1);
});
d3.select("#c_1")
.data(osc)
.transition().delay(function(d, i) {
return i * 1000;
})
.duration(1000)
.attr("opacity", function(d) {
return d[0]
});
})();
See the Pen Bubble Chart with D3.js using Realtime Data
If by "continuously" you mean that you want to run the transition infinitely, use on("end") to call the transition function again.
Here is an example:
var toggle;
var data = [0, 1];
transition();
function transition() {
toggle ^= 1;
d3.select("circle")
.transition()
.duration(1000)
.style("opacity", data[toggle])
.on("end", transition);
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg>
<circle cx="100" cy="100" r="50"></circle>
</svg>
I assume by continuously you mean smoothly transition rather than switch immediately from one opacity state to the next, as opposed to repeating the transition.
You first need to set an initial opacity on the circles when you create them:
.attr("opacity", 0)
And then use d3.selectAll rather than d3.select, or better your variable circles
...
circles
.data(osc)
.transition()
.delay(function(d,i){ return i*1000; })
.duration(1000)
.attr("opacity",function(d){ return d[0] });
Related
I'm trying to make a d3 realtime line chart with circle at the data point.
However, circles are gathered on the left side and it is not given to the data point.
This method is fine for static data to show circles with line chart.
chart.append('circle')
.data(data)
.attr('class', 'ciecle')
.attr("cy", line.x())
.attr("cy", line.y())
.attr("r", 5)
.attr("fill", 'blue');
However, it does not work with dynamically increasing data.
I want to move the circles with realtime line chat.
The follow code was forked from this URL
http://bl.ocks.org/KevinGutowski/131809cc7bcd1d37e10ca37b89da9630
Would you please let me how to change the code?
<svg id="chart"></svg>
<script src="http://d3js.org/d3.v4.min.js"></script>
<script>
var data = [];
var width = 500;
var height = 500;
var globalX = 0;
var duration = 100;
var max = 500;
var step = 10;
var chart = d3.select('#chart')
.attr('width', width + 50)
.attr('height', height + 50);
var x = d3.scaleLinear().domain([0, 500]).range([0, 500]);
var y = d3.scaleLinear().domain([0, 500]).range([500, 0]);
// -----------------------------------
var line = d3.line()
.x(function(d){ return x(d.x); })
.y(function(d){ return y(d.y); });
var smoothLine = d3.line().curve(d3.curveCardinal)
.x(function(d){ return x(d.x); })
.y(function(d){ return y(d.y); });
// -----------------------------------
// Draw the axis
var xAxis = d3.axisBottom().scale(x);
var axisX = chart.append('g').attr('class', 'x axis')
.attr('transform', 'translate(0, 500)')
.call(xAxis);
var path = chart.append('path');
var circle = chart.append('circle');
// Main loop
function tick() {
// Generate new data
var point = {
x: globalX,
y: ((Math.random() * 450 + 50) >> 0)
};
data.push(point);
globalX += step;
// Draw new line
path.datum(data)
.attr('class', 'smoothline')
.attr('d', smoothLine);
// Append circles. It should given to data point
chart.append('circle')
.data(data)
.attr('class', 'ciecle')
.attr("cy", line.x())
.attr("cy", line.y())
.attr("r", 5)
.attr("fill", 'blue');
// Shift the chart left
x.domain([globalX - (max - step), globalX]);
axisX.transition()
.duration(duration)
.ease(d3.easeLinear,.1)
.call(xAxis);
path.attr('transform', null)
.transition()
.duration(duration)
.ease(d3.easeLinear,.1)
.attr('transform', 'translate(' + x(globalX - max) + ')');
//move with line
circle.attr('transform', null)
.transition()
.duration(duration)
.ease(d3.easeLinear,.1)
.attr('transform', 'translate(' + x(globalX - max) + ')')
.on('end', tick);
// Remote old data (max 50 points)
if (data.length > 50) data.shift();
}
tick();
</script>
The coordinates of the path get repeatedly updated in the tick function (which repeatedly calls itself) using path.datum(data). You also need to update the locations of the circles on each tick using the adjusted (shifted) scale, which gets changed here:
x.domain([globalX - (max - step), globalX]);
To make the transitions smooth, you also need to update the transforms in each tick. You could update it for each circle and the path itself individually, but I just put both in a group (<g>) element and animate the whole group. Here's a working example:
http://bl.ocks.org/Sohalt/9715be30ba57e00f2275d49247fa7118/43a24a4dfa44738a58788d05230407294ab7a348
JSFiddle example
I've noticed that when updating positions of svg elements in a d3-force diagram, updating the positions of elements using (in the case of circles) the cx and cy attributes is much smoother than using the transform attribute.
In the example JSFiddle, there are two separate force simulations side-by-side. The one on the left updates positions using the transform attribute:
sim_transform.on('tick', function () {
circles_transform.attr('transform', function (d) {
return 'translate(' + d.x + ',' + d.y + ')';
});
});
The one on the right updates positions using the cx and cy attributes of a circle:
sim_position.on('tick', function () {
circles_position
.attr('cx', function (d) {
return d.x;
})
.attr('cy', function (d) {
return d.y;
})
});
The simulations appear identical until they're just about to become static, at which point the one using transforms starts to jitter quite a bit. Any ideas what is causing this? Can it be fixed so that the animation remains smooth using transforms?
It seems to me that the issue you're observing (only reproducible in FireFox, as #altocumulus noted) has something to do with the way FF uses floating numbers for the translate of the transform attribute.
We can see this if we set both simulations to use integers, doing ~~(d.x) and ~~(d.y). Have a look, both will jitter:
var svg = d3.select('svg');
var graph_transform = gen_data();
var graph_position = gen_data();
var force_left = d3.forceCenter(
parseInt(svg.style('width')) / 3,
parseInt(svg.style('height')) / 2
)
var force_right = d3.forceCenter(
2 * parseInt(svg.style('width')) / 3,
parseInt(svg.style('height')) / 2
)
var sim_transform = d3.forceSimulation()
.force('left', force_left)
.force('collide', d3.forceCollide(65))
.force('link', d3.forceLink().id(id));
var sim_position = d3.forceSimulation()
.force('right', force_right)
.force('collide', d3.forceCollide(65))
.force('link', d3.forceLink().id(id));
var g_transform = svg.append('g');
var g_position = svg.append('g');
var circles_transform = g_transform.selectAll('circle')
.data(graph_transform.nodes)
.enter()
.append('circle')
.attr('r', 40);
var circles_position = g_position.selectAll('circle')
.data(graph_position.nodes)
.enter()
.append('circle')
.attr('r', 40);
sim_transform
.nodes(graph_transform.nodes)
.force('link')
.links(graph_transform.links);
sim_position
.nodes(graph_position.nodes)
.force('link')
.links(graph_position.links);
sim_transform.on('tick', function() {
circles_transform.attr('transform', function(d) {
return 'translate(' + (~~(d.x)) + ',' + (~~(d.y)) + ')';
});
});
sim_position.on('tick', function() {
circles_position
.attr('cx', function(d) {
return ~~d.x;
})
.attr('cy', function(d) {
return ~~d.y;
})
});
function id(d) {
return d.id;
}
function gen_data() {
var nodes = [{
id: 'a'
},
{
id: 'b'
},
{
id: 'c'
},
{
id: 'd'
}
]
var links = [{
source: 'a',
target: 'b'
},
{
source: 'b',
target: 'c'
},
{
source: 'c',
target: 'd'
},
{
source: 'd',
target: 'a'
}
];
return {
nodes: nodes,
links: links
}
}
svg {
width: 100%;
height: 500px;
}
<script src="https://d3js.org/d3.v5.min.js"></script>
<svg></svg>
So, in your original code, it seems like the circles move correctly when using cx and cy, but they jump from integer to integer when using translate (or maybe half pixel, see the last demo). If the hypothesis here is correct, the reason that you just see the effect when the simulation is cooling down is because, at that moment, the movements are smaller.
Demos
Now, if we get rid of the simulation, we can see that this strange behaviour also happens with a very basic transform. To check this, I created a transition for a big black circle, using a linear ease and a very long time (to facilitate seeing the issue). The circle will move 30px to the right. I also put a gridline to make the jumps more noticeable.
(Warning: the demos below are only reproducible in FireFox, you won't see any difference in Chrome/Safari)
If we use cx, the transition is smooth:
var svg = d3.select("svg");
var gridlines = svg.selectAll(null)
.data(d3.range(10))
.enter()
.append("line")
.attr("y1", 0)
.attr("y2", 200)
.attr("x1", function(d) {
return 300 + d * 3
})
.attr("x2", function(d) {
return 300 + d * 3
})
.style("stroke", "lightgray")
.style("stroke-width", "1px");
var circle = svg.append("circle")
.attr("cx", 200)
.attr("cy", 100)
.attr("r", 98)
.transition()
.duration(10000)
.ease(d3.easeLinear)
.attr("cx", "230")
<script src="https://d3js.org/d3.v5.min.js"></script>
<svg width="500" height="200"></svg>
However, if we use translate, you can see the circle jumping 1px at every move:
var svg = d3.select("svg");
var gridlines = svg.selectAll(null)
.data(d3.range(10))
.enter()
.append("line")
.attr("y1", 0)
.attr("y2", 200)
.attr("x1", function(d) {
return 300 + d * 3
})
.attr("x2", function(d) {
return 300 + d * 3
})
.style("stroke", "lightgray")
.style("stroke-width", "1px");
var circle = svg.append("circle")
.attr("cx", 200)
.attr("cy", 100)
.attr("r", 98)
.transition()
.duration(10000)
.ease(d3.easeLinear)
.attr("transform", "translate(30,0)")
<script src="https://d3js.org/d3.v5.min.js"></script>
<svg width="500" height="200"></svg>
For you people running this in Chrome/Safari, this is how the last snippet looks like in Firefox. It's like the circle is being moved half a pixel at every change... definitely not as smooth as changing cx:
var svg = d3.select("svg");
var gridlines = svg.selectAll(null)
.data(d3.range(10))
.enter()
.append("line")
.attr("y1", 0)
.attr("y2", 200)
.attr("x1", function(d) {
return 300 + d * 3
})
.attr("x2", function(d) {
return 300 + d * 3
})
.style("stroke", "lightgray")
.style("stroke-width", "1px");
var circle = svg.append("circle")
.attr("cx", 200)
.attr("cy", 100)
.attr("r", 98);
var timer = d3.timer(function(t){
if(t>10000) timer.stop();
circle.attr("cx", 200 + (~~(60/(10000/t))/2));
})
<script src="https://d3js.org/d3.v5.min.js"></script>
<svg width="500" height="200"></svg>
As this is an implementation issue only visible in FF, it may be worth reporting a bug.
I have an array of equally spaced values which I am using to draw concentric circles. I want to use an emanating effect, in essence, remove the outermost circle once its value exceeds the maximum value and add a new one at the center to compensate. I am unsure about manipulation on data set to remove and add new circle.
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var svg = d3.select("body").append("svg");
var w = window.innerWidth;
var h = window.innerHeight;
var separation = Math.min(50, w/12);
var n=Math.floor((w/2)/separation);
var ellipse=new Array(n);
for(var i=1;i<=n;i++){
ellipse[i-1]=(i*separation);
}
svg.attr("width", w).attr("height", h);
var g = svg.append("g");
var e=g.selectAll("ellipse")
.data(ellipse)
.enter()
.append("ellipse")
.attr("cx", w/2)
.attr("cy", h/2)
.attr("rx",0)
.attr("ry",0)
.transition()
.duration(5000)
.attr("rx", function(d,i){return ellipse[i];})
.attr("ry", function(d,i){return ellipse[i]/5;});
loop();
function loop(){
e.transition()
.attr("rx", function(d,i){
return ellipse[i]+separation;
})
.attr("ry", function(d,i){
return (ellipse[i]+separation)/5;
})
.on("end",loop());
}
</script>
You could approach it with a remove().exit() and enter().append() selection for each ring - but essentially you always have the same number of rings on the screen. Why not just recycle the same elements? When the size hits a threshold, reset it to zero, or some other starting value?
Something along the lines of:
var scale = d3.scaleLinear()
.range(["orange","steelblue","purple"])
.domain([0,60]);
var data = [0,10,20,30,40,50,60];
var width = 200;
var height = 200;
var svg = d3.select("body")
.append("svg")
.attr("width",width)
.attr("height",height);
var circles = svg.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("r",function(d) { return d; })
.attr("transform","translate(80,80)")
.attr("fill","none")
.style("stroke-width","4")
.style("stroke",function(d) { return scale(d) });
function transition() {
// Update data, max d is 60:
data = data.map(function(d) { return d == 60 ? 0 : d + 10});
var i = 0;
// Grow circles
circles
.data(data)
.filter(function(d) { return d > 0 })
.transition()
.ease(d3.easeLinear)
.attr("r", function(d) { return d; })
.style("stroke", function(d) { return scale(d) })
.style("opacity",function(d) { return d == 60 ? 0 : 1 })
.duration(1000)
.on("end",function(){if(++i==circles.size()-1) { transition(); } });
// Reset circles where r == 0
circles
.filter(function(d) { return d == 0 })
.attr("r", 0)
.style("opacity",1)
.style("stroke",function(d) { return scale(d); });
}
transition();
<script src="https://d3js.org/d3.v4.min.js"></script>
Note that .on("end",... triggers on each element's transition end - this is why I count to see if all elements are done transitioning before running the transition function again.
I'm using the following script to generate a bar chart with drill down capability. (source: http://mbostock.github.io/d3/talk/20111116/bar-hierarchy.html).
What I am trying to do is - I want the bars to be different shades of a color depending on the data (pretty much what this question asks - D3.js: Changing the color of the bar depending on the value). Except, in my case... the graph is horizontal and not static so the answer may be different.
So Ideally, at the parent node and all sub nodes except the child node, it will display lets say different shades of blue based on the data, and once it reaches the end after drilling down, the remaining bars will be grey.
I've recently started using d3 and am kinda lost as to where to start. I tried
adding different colors to the color range z but that did not work.
Any help will be appreciated! Thanks.
NOTE: in my case, I am assuming.. that after a transition, either all nodes will lead to subnodes OR no node will lead to subnodes. Basically, at no point in the graph will there be bars, where some will drill down further while some won't. This assumption is based on the type of data I want to show with my graph.
<script>
var m = [80, 160, 0, 160], // top right bottom left
w = 1280 - m[1] - m[3], // width
h = 800 - m[0] - m[2], // height
x = d3.scale.linear().range([0, w]),
y = 25, // bar height
z = d3.scale.ordinal().range(["steelblue", "#aaa"]); // bar color
var hierarchy = d3.layout.partition()
.value(function(d) { return d.size; });
var xAxis = d3.svg.axis()
.scale(x)
.orient("top");
var svg = d3.select("body").append("svg:svg")
.attr("width", w + m[1] + m[3])
.attr("height", h + m[0] + m[2])
.append("svg:g")
.attr("transform", "translate(" + m[3] + "," + m[0] + ")");
svg.append("svg:rect")
.attr("class", "background")
.attr("width", w)
.attr("height", h)
.on("click", up);
svg.append("svg:g")
.attr("class", "x axis");
svg.append("svg:g")
.attr("class", "y axis")
.append("svg:line")
.attr("y1", "100%");
d3.json("flare.json", function(root) {
hierarchy.nodes(root);
x.domain([0, root.value]).nice();
down(root, 0);
});
function down(d, i) {
if (!d.children || this.__transition__) return;
var duration = d3.event && d3.event.altKey ? 7500 : 750,
delay = duration / d.children.length;
// Mark any currently-displayed bars as exiting.
var exit = svg.selectAll(".enter").attr("class", "exit");
// Entering nodes immediately obscure the clicked-on bar, so hide it.
exit.selectAll("rect").filter(function(p) { return p === d; })
.style("fill-opacity", 1e-6);
// Enter the new bars for the clicked-on data.
// Per above, entering bars are immediately visible.
var enter = bar(d)
.attr("transform", stack(i))
.style("opacity", 1);
// Have the text fade-in, even though the bars are visible.
// Color the bars as parents; they will fade to children if appropriate.
enter.select("text").style("fill-opacity", 1e-6);
enter.select("rect").style("fill", z(true));
// Update the x-scale domain.
x.domain([0, d3.max(d.children, function(d) { return d.value; })]).nice();
// Update the x-axis.
svg.selectAll(".x.axis").transition()
.duration(duration)
.call(xAxis);
// Transition entering bars to their new position.
var enterTransition = enter.transition()
.duration(duration)
.delay(function(d, i) { return i * delay; })
.attr("transform", function(d, i) { return "translate(0," + y * i * 1.2 + ")"; });
// Transition entering text.
enterTransition.select("text").style("fill-opacity", 1);
// Transition entering rects to the new x-scale.
enterTransition.select("rect")
.attr("width", function(d) { return x(d.value); })
.style("fill", function(d) { return z(!!d.children); });
// Transition exiting bars to fade out.
var exitTransition = exit.transition()
.duration(duration)
.style("opacity", 1e-6)
.remove();
// Transition exiting bars to the new x-scale.
exitTransition.selectAll("rect").attr("width", function(d) { return x(d.value); });
// Rebind the current node to the background.
svg.select(".background").data([d]).transition().duration(duration * 2); d.index = i;
}
function up(d) {
if (!d.parent || this.__transition__) return;
var duration = d3.event && d3.event.altKey ? 7500 : 750,
delay = duration / d.children.length;
// Mark any currently-displayed bars as exiting.
var exit = svg.selectAll(".enter").attr("class", "exit");
// Enter the new bars for the clicked-on data's parent.
var enter = bar(d.parent)
.attr("transform", function(d, i) { return "translate(0," + y * i * 1.2 + ")"; })
.style("opacity", 1e-6);
// Color the bars as appropriate.
// Exiting nodes will obscure the parent bar, so hide it.
enter.select("rect")
.style("fill", function(d) { return z(!!d.children); })
.filter(function(p) { return p === d; })
.style("fill-opacity", 1e-6);
// Update the x-scale domain.
x.domain([0, d3.max(d.parent.children, function(d) { return d.value; })]).nice();
// Update the x-axis.
svg.selectAll(".x.axis").transition()
.duration(duration * 2)
.call(xAxis);
// Transition entering bars to fade in over the full duration.
var enterTransition = enter.transition()
.duration(duration * 2)
.style("opacity", 1);
// Transition entering rects to the new x-scale.
// When the entering parent rect is done, make it visible!
enterTransition.select("rect")
.attr("width", function(d) { return x(d.value); })
.each("end", function(p) { if (p === d) d3.select(this).style("fill-opacity", null); });
// Transition exiting bars to the parent's position.
var exitTransition = exit.selectAll("g").transition()
.duration(duration)
.delay(function(d, i) { return i * delay; })
.attr("transform", stack(d.index));
// Transition exiting text to fade out.
exitTransition.select("text")
.style("fill-opacity", 1e-6);
// Transition exiting rects to the new scale and fade to parent color.
exitTransition.select("rect")
.attr("width", function(d) { return x(d.value); })
.style("fill", z(true));
// Remove exiting nodes when the last child has finished transitioning.
exit.transition().duration(duration * 2).remove();
// Rebind the current parent to the background.
svg.select(".background").data([d.parent]).transition().duration(duration * 2);
}
// Creates a set of bars for the given data node, at the specified index.
function bar(d) {
var bar = svg.insert("svg:g", ".y.axis")
.attr("class", "enter")
.attr("transform", "translate(0,5)")
.selectAll("g")
.data(d.children)
.enter().append("svg:g")
.style("cursor", function(d) { return !d.children ? null : "pointer"; })
.on("click", down);
bar.append("svg:text")
.attr("x", -6)
.attr("y", y / 2)
.attr("dy", ".35em")
.attr("text-anchor", "end")
.text(function(d) { return d.name; });
bar.append("svg:rect")
.attr("width", function(d) { return x(d.value); })
.attr("height", y);
return bar;
}
// A stateful closure for stacking bars horizontally.
function stack(i) {
var x0 = 0;
return function(d) {
var tx = "translate(" + x0 + "," + y * i * 1.2 + ")";
x0 += x(d.value);
return tx;
};
}
</script>
you can create a new scale to handle the "shades" of your colors,
var shades = d3.scale.sqrt()
.domain([your domain])
.clamp(true)
.range([your range]);
and create a variable to control the "depth" of your drill-down, so when you are going to color your bars, you simply set the level of the color "shade" with d3.lab (Doc), like this:
function fill(d) {
var c = d3.lab(colorScale(d.barAttr));
c.l = shades(d.depth);
return c;
}
Using the same code as the second in the link you posted you could add (the ellipsis indicates nothing is changed compared to the code in the fiddle):
//initialize the scale
var colors = ["#ffffd9", "#edf8b1", "#c7e9b4", "#7fcdbb", "#41b6c4", "#1d91c0", "#225ea8", "#253494", "#081d58"];
var colorScale = d3.scale.quantile()
Then when d3 reads the data, you need to add a domain and a range. This assumes that all the biggest value any bar would have is in the children of the root node (ie in the bars that are initially displayed).
d3.json(..., function(root) {
...
colorScale.domain([0, colors.length - 1,d3.max(root.children, function(d) {
return d.value;
})]).range(colors);
...
});
You can then use the colorScale to color the bars according the value during the transitions. Here are the lines I modified:
enter.select("rect").style("fill", colorScale(d.value));
...
enterTransition.select("rect")
.attr("width", function(d) {
return x(d.value);
})
.style("fill", function(d) {
if(!d.children) return "#aaa";
return colorScale(d.value);
});
...
enter.select("rect")
.style("fill", function(d) {
return colorScale(d.value);
})
.filter(function(p) {
return p === d;
})
.style("fill-opacity", 1e-6);
...
exitTransition.select("rect")
.attr("width", function(d) {
return x(d.value);
})
.style("fill", colorScale(d.value));
Here's a working fiddle: https://jsfiddle.net/f640v0yj/2/
I'm trying to implement drill-down capability in zoom function, i.e., I want that my initial plot shows, for example, 50 points, and when the user makes zoom the number of points increases to 500.
My attempt consists in redraw inside the zoom function all the points and remove part of them when the zoom scale is under a threshold. As you can see in this JSFIDDLE, the implementation reproduces the drill-down capability.
However, I suspect that there is a more efficient way to implement the drill-down. Therefore, the question is if I'm in the correct way or there is a standard (more efficient and elegant) way for doing this effect.
My example code:
var width = 300,
height = 300;
var randomX = d3.random.normal(width / 2, 40),
randomY = d3.random.normal(height / 2, 40);
var data = d3.range(500).map(function() {
return [randomX(), randomY()];
});
var svg = d3.select("body").append("svg");
var zoomBehav = d3.behavior.zoom();
svg.attr("height", height)
.attr("width", width)
.call(zoomBehav
.scaleExtent([1, 10])
.on("zoom", zoom));
// Initial plot
d3.select("svg").selectAll("circle")
.data(data, function(d,i) {return i;})
.enter()
.append("circle")
.attr("r", 3)
.attr("cx", function(d) {return d[0]; })
.attr("cy", function(d) {return d[1]; })
.style("fill", "red");
d3.selectAll("circle")
.filter(function(d, i) {
if (zoomBehav.scale() < 2) { return i > 50; }
})
.remove();
function zoom(){
var selection = d3.select("svg")
.selectAll("circle")
.data(data, function(d,i) { return i; });
selection
.attr("cx", function(d) { return d3.event.translate[0] + d3.event.scale * d[0]; })
.attr("cy", function(d) { return d3.event.translate[1] + d3.event.scale * d[1]; });
selection.enter()
.append("circle")
.attr("r", 3)
.attr("cx", function(d) { return d3.event.translate[0] + d3.event.scale * d[0]; })
.attr("cy", function(d) { return d3.event.translate[1] + d3.event.scale * d[1]; })
.style("fill", "red");
d3.selectAll("circle")
.filter(function(d, i) {
if (zoomBehav.scale() < 2) { return i > 50; }
})
.remove();
}
If you're interested in dealing with semantic zoom of elements on an XY canvas, then you'll want to look into d3.geom.quadtree:
https://github.com/mbostock/d3/wiki/Quadtree-Geom
You can pass your points to a quadtree and they'll be spatially nested. Then, you can tie the nesting level to the zoom level and have automatic grid clustering. It's rather more involved than would fit into this answer, since you have to come up with mechanisms for representing the clustered points, and you'll also need to get into recursive functions to deal with the hierarchical level of points.
Here's an example using quadtrees for semantic zoom and clustering for mapping:
http://bl.ocks.org/emeeks/066e20c1ce5008f884eb