If you used solid color fills, it would be easy to turn them to gray and then back to color - just use the d3 transition of the fill property instead of the fill-opacity and stroke-opacity properties.
However, the colors in this case are not actually related to the elements of your choice. Instead, they are specified in the <stop> <radialGradient> elements created for each category. (In fact, they are currently being created for each individual circle — see My Note below.) Therefore, you need to select these elements to go to stop colors.
Since you are simultaneously moving all the elements in this category, you do not need to create additional gradient elements - you just need a way to select the gradients associated with these categories, and move them.
Here is your source code for creating gradient elements and referencing them to color the circles:
var grads = svg.append("defs").selectAll("radialGradient") .data(nodes) .enter() .append("radialGradient") .attr("gradientUnits", "objectBoundingBox") .attr("cx", 0) .attr("cy", 0) .attr("r", "100%") .attr("id", function(d, i) { return "grad" + i; }); grads.append("stop") .attr("offset", "0%") .style("stop-color", "white"); grads.append("stop") .attr("offset", "100%") .style("stop-color", function(d) { return color(d.cluster); }); var node = svg.selectAll("circle") .data(nodes) .enter() .append("circle") .style("fill", function(d, i) { return "url(#grad" + i + ")"; }) .call(force.drag) .on("mouseover", fade(.1)) .on("mouseout", fade(1));
The fade() function that you are currently using generates a separate event handler function for each element, which then selects all the circles and translates them to the specified opacity or full opacity depending on whether they are in the same cluster as and the circle that received the event:
function fade(opacity) { return function(d) { node.transition().duration(1000) .style("fill-opacity", function(o) { return isSameCluster(d, o) ? 1 : opacity; }) .style("stroke-opacity", function(o) { return isSameCluster(d, o) ? 1 : opacity; }); }; }; function isSameCluster(a, b) { return a.cluster == b.cluster; };
To move the gradients instead, you need to select the gradients instead of the circles and check which cluster they are associated with. Since the gradient elements are bound to the same data objects as the nodes, you can reuse the isSameCluster() method. You just need to change the internal function in the fade() method:
function fade(saturation) { return function(d) { grads.transition().duration(1000) .select("stop:last-child")
Some notes:
To select the correct stop element in the gradient, I use a pseudo-class from :last-child . You can also simply provide the stop elements with the regular CSS class when they are created.
To bleach the color by the specified amount, I use the d3 color functions to convert the color to HSL (shade -saturation-luminance), and then multiply the saturation property. I multiply it, instead of setting it directly, in case any of your initial colors are not 100% saturated. However, I would recommend using equally saturated colors to get a consistent effect.
In a working example, I also changed your color palette so that you don't have a gray color to start with (for the first 10 clusters, anyway). You may need to create a custom palette with the same saturation values for all colors.
If you want the final value of the fading effect to always be the same gray gradient, you could probably simplify the code a bit - delete all hsl calculations and use the boolean parameter instead of the numeric value saturation . Or even just there are two functions, one of which resets all colors, without having to check which cluster it is for, and one that checks the clusters and sets the values accordingly to gray.
Working fragment:
var width = 400, height = 400, padding = 1.5,
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
Note:
You are currently creating a separate <radialGradient> for each circle when you really only need one gradient for each cluster. You can improve overall performance by using the clusters array as data to select a gradient instead of your nodes array. However, you will then need to change the id values for the gradients, which will be based on the cluster data, not the node index.
Using filters, as suggested by Robert Longson in the comments, would be another option. However, if you need a transition effect, you still need to select the filter elements and list their attributes. At least for now. When CSS filter functions are more widely supported, you can directly translate filter: grayscale(0) to filter: grayscale(1) .