I'm working on a heatmap chart in D3 and I can't figure out how to add the text on mouseover. I am not sure how to proceed. If you could give me some clues, I would appreciate it. In the following snippet you can find the code. both the working and the non-working codeblocks. Thanks!
console.log(d3)
let screenWidth = 800
let screenHeight = 400
//load data
d3.csv('./datos/locations.csv').then(function(data){
let filtered = []
for(let item of data) {
if(item.location === "location one") {
filtered.push(item)
}
}
build(filtered)
})
//Create canvas
function createSVG() {
let container = d3.select('#container')
svg = container.append('svg')
.attr('id', 'canvas')
.attr('width', screenWidth)
.attr('height', screenHeight)
}
//Create chart
function build(data) {
let rectWidth = screenWidth / 24
let rectHeight = screenHeight / 7
let rects = svg.selectAll('rect')
.data(data)
.enter()
.append('rect')
.attr('x', function(d,i) {
return (parseInt(d.hour) - 1) * rectWidth})
.attr('y', function(d,i){
return (parseInt(d.day) - 1) * rectHeight})
.attr('height', rectHeight)
.attr('width', rectWidth)
.style('fill', 'black')
.style('stroke', 'white')
.on('mouseover', function(d,i) {
let rects = d3.select(this)
.append('text')
.attr('x')
.attr('y')
.style('font-weight', 500)
.style('font-family', 'Arial')
.style('fill', 'red')
.text(function (d,i) {return d.value})})
}
function main() {
createSVG()
build()
}
main()
```
You can append a <div> with position: absolute to body and position it on mousemove event. Change the opacity to update its display or hidden.
var div = d3.select('body').append('div')
.attr('class', 'tooltip')
.style('opacity', 0);
...
.on('mouseover', function(d) {
div.transition()
.duration(200)
.style('opacity', .9);
div.html('<h3>' + d.status + '</h3>' + '<p>' + timeFormat(new Date(d.date)) + ' at ' + monthDayFormat(new Date(d.date)) + '</p>')
.style('left', (d3.event.pageX) + 'px')
.style('top', (d3.event.pageY - 28) + 'px');
})
https://jsfiddle.net/z9ucLqu2/
<text> nodes cannot be children of <rect>s, only just as <line>s or <circle>s can't. They are figure nodes and are not meant to have children. Append the tooltip to the SVG or a <g> instead.
This means that you cannot access d.value through the function (d,i) {return d.value}) anymore, but you can get it because you have access to d from .on('mouseover', function(d,i) {, just remove everything but d.value.
If you use x and y from the <rect>, what is going to happen is that the <text> element covers the <rect>, catches the mouse event and triggers a mouseout immediately on the <rect>. Since you'll probably want to remove the tooltip on mouseout, you'll get the text node flickering on and off. Either move the text to the right by at least rectWidth or use d3.event to get the mouse coordinates of the event and position it a little down and to the right, using something like .attr('x', d3.event.clientX + 10) to move it right.
Related
I'm having an issue with a D3 pie chart where labels are cutoff when they appear. Here's a pic:
I'm new to D3, and am not sure exactly where a fix should be. The logic for making the pie chart is 400 lines, so I made a pastebin: https://pastebin.com/32CxpeDM
Maybe the issue is in this function?
function showDetails(layer, data) {
active.inner = !active.inner
active.outer = false
let lines = guideContainer.selectAll('polyline.guide-line-inner')
.data(pie(data.inner))
let text = guideContainer.selectAll('.inner-text')
.data(pie(data.inner))
if (active.inner) {
// LINES
lines.enter()
.append('polyline')
.attr('points', calcPoints)
.attr('class', 'guide-line-inner')
.style('opacity', 0)
.transition().duration(300)
.style('opacity', d => { return d.data.value <= 0 ? 0 : 1 })
lines.attr('points', calcPoints).attr('class', 'guide-line-inner')
// TEXT
text.enter()
.append('text')
.html(labelText)
.attr('class', 'inner-text label label-circle')
.attr('transform', labelTransform)
.attr('dy', '1.1em')
.attr('x', d => { return (midAngle(d) < Math.PI ? 15 : -15) })
.style('text-anchor', d => { return (midAngle(d)) < Math.PI ? 'start' : 'end' })
.style('opacity', 0)
.call(wrap, 300)
.on('click', d => { vm.$emit('details', d) })
.transition().duration(300)
.style('opacity', d => { return d.data.value <= 0 ? 0 : 1 })
} else {
lines.transition().duration(300)
.style('opacity', 0)
.remove()
text.transition().duration(300)
.style('opacity', 0)
.remove()
}
guideContainer.selectAll('polyline.guide-line-outer').transition().duration(300)
.style('opacity', 0)
.remove()
guideContainer.selectAll('.outer-text').transition().duration(300)
.style('opacity', 0)
.remove()
}
Like I said, I don't know D3 so I'm not sure what my options are for fixing this. Make the chart smaller, fix some problem with its div container, send it to the front, or edit the above code. Ideally this would be a clean fix and not a hack, which is what I've been trying.
What the easiest and cleanest fix for this problem?
Thanks!
It looks like your labels are getting transformed past the edge of your SVG boundary.
I would try to translate the label elements farther left in this function:
function labelTransform (d) {
var pos = guideArc.centroid(d)
pos[0] = (radius + 100) * (midAngle(d) < Math.PI ? 1 : -1)
return 'translate(' + pos + ')'
}
You'll have to chase down where the label line is being generated and shorten that as well.
You might also try braking the label text into more lines in this function:
function labelText (d) {
if ((radius + 100) * (midAngle(d) < Math.PI ? 1 : 0)) {
return '<tspan>' + d.data.display_name + ' </tspan><tspan x="15">' + d3.format('($,.0f')(d.data.value) + '</tspan>'
} else {
return '<tspan>' + d.data.display_name + ' </tspan><tspan x="-15" text-anchor="end">' + d3.format('($,.0f')(d.data.value) + '</tspan>'
}
}
One approach that would conserve space would be to use d3 tooltips instead of fixed-position labels as in this fiddle (created by another jsfiddle user, I just added the margin)
Javascript:
//Width/height
var w = 300;
var h = 300;
var dataset = [5, 10, 20, 45, 6, 25];
var outerRadius = w / 2;
var innerRadius = w / 3;
var arc = d3.svg.arc()
.innerRadius(innerRadius)
.outerRadius(outerRadius);
var pie = d3.layout.pie();
var color = d3.scale.category10();
//Create SVG element
var svg = d3.select("#vis")
.append("svg")
.attr("width", w)
.attr("height", h);
//Set up groups
var arcs = svg.selectAll("g.arc")
.data(pie(dataset))
.enter()
.append("g")
.attr("class", "arc")
.attr("transform", "translate(" + outerRadius + "," + outerRadius + ")");
//Draw arc paths
arcs.append("path")
.attr("fill", function(d, i) {
return color(i);
})
.attr("d", arc);
//Labels
arcs.append("text")
.attr("transform", function(d) {
return "translate(" + arc.centroid(d) + ")";
})
.attr("text-anchor", "middle")
.text(function(d) {
return d.value;
});
//
var tip = d3.tip()
.attr("class", "d3-tip")
.html(function(d, i) {
return d.value
})
svg.call(tip)
arcs
.on("mouseover", tip.show)
.on("mouseout", tip.hide)
This uses a d3 tip library by Justin Palmer, that can be found on Github.
Also, you could consider making the circle smaller?
Hope this helps
I created a d3-force layout. The nodes are allowed to be dragged within a boundary box only. When the nodes are close to the boundaries, the nodes stay fixed at their positions, according to the following functions:
function boundX(x) {
return Math.max(0, Math.min(width - (nodeWidth+padding), x));
}
function boundY(y){
return Math.max(0, Math.min(height - (nodeHeight+padding), y))
}
The nodes are connected via polylines. Each polyline is characterized by two segments. The first segment is defined by the source node's (d.source.x+nodeWidth/2, d.source.y+nodeHeight/2) coordinates, which is the middle of the node and the intersection point of the line with the target node. The second segment starts at the intersection point with the target node and ends at the target nodes middle point(d.target.x+nodeWidth/2).The intersection of a line and a target node is where a marker is placed along the polyline. This is the part of the code -in the tick function- which is responsible for calculating the intersection points, and drawing the lines:
function tick() {
link.attr("points", function(d) {
var interForw = pointOnRect(d.source.x, d.source.y,
d.target.x - nodeWidth / 2, d.target.y - nodeHeight / 2,
d.target.x + nodeWidth / 2, d.target.y + nodeHeight / 2);
if(d.direction==="forward") {
return boundXInter((d.source.x+nodeWidth/2) + " "
+ boundYInter(((d.source.y) + nodeHeight / 2) + ","
+ boundXInter(((interForw.x+nodeWidth/2))) + " "
+ boundYInter((interForw.y) + nodeHeight / 2) + ","
+ boundXInter(d.target.x+nodeWidth/2) + " "
+ boundYInter((d.target.y) + nodeHeight / 2);
}
These are the functions for defining the boundaries for the links:
function boundXInter(x) {
return Math.max(nodeWidth/2, Math.min(width - (nodeWidth/2+padding),x));
}
function boundYInter(y){
return Math.max(nodeHeight/2, Math.min(height - (nodeHeight/2+padding), y));
}
When two nodes are one below the other like in the first image. It behaves as expected. .
However, when the nodes are placed as shown in the next figure, if the user continues dragging the nodes even if they not allowed to move further the boundaries, the nodes are blocked as wanted, but the links continue to move until the width-nodeWidth/2 point, according to the boundXInter function.
What I would like to achieve is the intersection point (the marker), the first segment of the line not to move further than it's actual position in this case as shown in the third figure. I want it to be fixed and not that segment of the line stretch to the width-nodeWidth/2 position as shown in the next figure. Probably, reformating the boundXInter function would do the job. But, I have tried many combinations and it did not. I would like to mention that if the user stops dragging the links return to the desired state (as shown in the second Figure)
Any ideas? What can I do, in this case, to get the correct result?
You can find a working snippet here: https://jsfiddle.net/yx2grm4s/39/.
You have mixed modelling relative to the center and relative to the top left of the rectangle. It is nicer to do it relative to the center (is the node position). Do not change the node position other then bounds check. The rectangle, label, link and dot are just decoration relative to the node position.
Also bound node position first before updating the "in between node" stuff so that will never need to be bound again. Use a nice even padding all around the box.
Removed code duplication.
Complete running code: https://jsfiddle.net/y0eox2vn/1/
Essential code part
var link = svg.append("g")
.selectAll(".link")
.data(links)
.enter()
// .append("line")
.append("polyline")
.attr("class", "link")
.style("stroke-width","1")
.style("stroke","black")
.style("fill","none")
.attr("id", function (d, i) { return 'link'+i; });
var markerFor = svg.append("defs")
.selectAll("marker")
.data(["forward"])
.enter()
.append("marker")
.attr("id", "dirArrowFor")
.attr("viewBox", "0 -5 10 10")
.attr("markerUnits", "strokeWidth")
.attr("markerWidth", 10)
.attr("markerHeight", 10)
.attr("refX",10)
.attr("refY", 0)
.attr("overflow", "visible")
.attr("orient", "auto")
.append("path")
.attr("d", "M0,-5L10,0L0,5")
.style("fill", "#000000");
link.attr("marker-mid", checkDir);
var linkNode = svg.append("g").selectAll(".link")
.data(links)
.enter()
.append("circle")
.attr("class","link-node")
.attr("r",4)
.style("fill","#c00");
linkNode.append("title")
.text(function(d) { return d.linkingWord; });
var node = svg.append("g").selectAll(".node")
.data(nodes)
.enter()
.append("rect")
.attr("class","node")
.attr("width", conceptWidth)
.attr("height", conceptHeight)
.attr("rx",20)
.attr("ry",20)
.style('fill',function(d){ return d.color;})
.call(d3.drag()
.on("start", dragStarted)
.on("drag", dragged)
.on("end", dragEnded));
var labels = svg.append("g")
.selectAll(".labels")
.data(nodes)
.enter()
.append("text")
.attr("class", "labels")
.text(function(d){ return d.name;})
.style("text-anchor","middle")
.attr("dy", 5);
var force = d3.forceSimulation()
.force("collision", d3.forceCollide(conceptWidthHalf +1).iterations(1))
.force("link", d3.forceLink().id(function(d){ return d.name;}))
.on("tick", tick);
force.nodes(nodes);
force.force("link").links(links);
function interForwRev(d) {
var interForw = pointOnRect(d.source.x, d.source.y,
d.target.x - conceptWidthHalf, d.target.y - conceptHeightHalf,
d.target.x + conceptWidthHalf, d.target.y + conceptHeightHalf);
var interRev = pointOnRect(d.target.x, d.target.y,
d.source.x - conceptWidthHalf, d.source.y - conceptHeightHalf ,
d.source.x + conceptWidthHalf, d.source.y + conceptHeightHalf);
return [interForw, interRev];
}
function tick() {
node.attr("x", function(d) { d.x=boundX(d.x); return d.x - conceptWidthHalf; })
.attr("y", function(d) { d.y=boundY(d.y); return d.y - conceptHeightHalf; });
labels.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; });
linkNode.attr("cx", function (d) {
var interFR = interForwRev(d);
var interForw = interFR[0];
var interRev = interFR[1];
return d.cx = (interForw.x + interRev.x)*0.5;
})
.attr("cy", function (d) {
var interFR = interForwRev(d);
var interForw = interFR[0];
var interRev = interFR[1];
return d.cy = (interForw.y + interRev.y)*0.5;
});
// update the links after the nodes so we are already bounded by the box
link.attr("points", function(d) {
var interFR = interForwRev(d);
var interForw = interFR[0];
var interRev = interFR[1];
if(d.direction==="forward") {
return `${d.source.x} ${d.source.y},${interForw.x} ${interForw.y},${d.target.x} ${d.target.y}`;
}
});
}
function boundX(x) {
return Math.max(conceptWidthHalf+padding, Math.min(width - (conceptWidthHalf+padding), x));
}
function boundY(y){
return Math.max(conceptHeightHalf+padding, Math.min(height - (conceptHeightHalf+padding), y))
}
// NO other bound function needed
function dragStarted(d) {
if (!d3.event.active) force.alphaTarget(0.03).restart();
d.fx = d.x;
d.fy = d.y;
}
I have implemented a scatterplot using d3.js v3.
I would like to change the color of the datapoint if for that particular data point annotation has been added.
But when I try to do so, it is changing the color of the first data point in the array instead of the the circle where the annotation has been added.
The way I am implementing is :
eventGroup.select("circle")
.attr("class", "dot")
.attr("r", 4)
.attr("cx", 10)
.attr("cy", 10)
.attr("fill", function(d) {
return d.evtColor ? d.evtColor : "#229ae5";
})
.attr("stroke", function(d) {
return d.evtColor ? d.evtColor : "#229ae5";
})
.attr("stroke-width", 2)
.on("contextmenu", function(d) {
var position = d3.mouse(this.parentNode);
d3.event.stopPropagation(); // to avoid over-ridding of click event on the chart background
d3.select("#context-menu")
.style("position", "absolute")
.style("left", (d3.event.pageX - 220) + "px")
.style("top", (d3.event.pageY - 70) + "px")
.style("display", "inline-block")
.on("click", function() {
d3.select("#context-menu").style("display", "none");
d3.select("#annotateBox")
.style("position", "absolute")
.style("left", (d3.event.pageX - 220) + "px")
.style("top", (d3.event.pageY - 70) + "px")
.style("display", "inline-block");
d3.select("#eventLabel").text(d.label);
d3.select("#eventTime").text(d.time);
d3.select("#textArea").node().value = d.textArea || "";
d3.select("#done").on("click", function() {
d.textArea = d3.select("#textArea").node().value;
d3.select("#annotateBox").style("display", "none");
if (d.textArea) {
d3.select("circle.dot").style("fill", "#ed6a1c");
}
});
});
});
I cannot do d3.select(this.parentNode) as the parent element is not the circle.dot. What element should be selected in order to change the color of datum where annotation text has been added?
Keep a reference to the clicked DOM element (i.e., the circle)...
.on("contextmenu", function(d) {
var thisCircle = this
etc...
And use it afterwards:
if (d.textArea) {
d3.select(thisCircle).style("fill", "#ed6a1c");
}
I am trying to plot a network graph using networkD3 in R. I wanted to make some changes to the display so that the text labels (which appears when mouseover) can be easily read.
Please refer to the link here for an example. Note: Jump to the d3ForceNetwork plot.
As seen in the example, the labels are hard to read due to its colour and it often gets obstructed by the surrounding nodes. I have been messing around with the JS file and managed to change the text label color to black. However, having no knowledge of JS or CSS (I can't even tell the difference between the 2 actually), I have no idea how I can change the stack order such that the text labels will always be displayed above any other objects.
Can anyone advise me on how I can achieve the desired outcome?
Below is the full JS file:
HTMLWidgets.widget({
name: "forceNetwork",
type: "output",
initialize: function(el, width, height) {
d3.select(el).append("svg")
.attr("width", width)
.attr("height", height);
return d3.layout.force();
},
resize: function(el, width, height, force) {
d3.select(el).select("svg")
.attr("width", width)
.attr("height", height);
force.size([width, height]).resume();
},
renderValue: function(el, x, force) {
// Compute the node radius using the javascript math expression specified
function nodeSize(d) {
if(options.nodesize){
return eval(options.radiusCalculation);
}else{
return 6}
}
// alias options
var options = x.options;
// convert links and nodes data frames to d3 friendly format
var links = HTMLWidgets.dataframeToD3(x.links);
var nodes = HTMLWidgets.dataframeToD3(x.nodes);
// get the width and height
var width = el.offsetWidth;
var height = el.offsetHeight;
var color = eval(options.colourScale);
// set this up even if zoom = F
var zoom = d3.behavior.zoom();
// create d3 force layout
force
.nodes(d3.values(nodes))
.links(links)
.size([width, height])
.linkDistance(options.linkDistance)
.charge(options.charge)
.on("tick", tick)
.start();
// thanks http://plnkr.co/edit/cxLlvIlmo1Y6vJyPs6N9?p=preview
// http://stackoverflow.com/questions/22924253/adding-pan-zoom-to-d3js-force-directed
var drag = force.drag()
.on("dragstart", dragstart)
// allow force drag to work with pan/zoom drag
function dragstart(d) {
d3.event.sourceEvent.preventDefault();
d3.event.sourceEvent.stopPropagation();
}
// select the svg element and remove existing children
var svg = d3.select(el).select("svg");
svg.selectAll("*").remove();
// add two g layers; the first will be zoom target if zoom = T
// fine to have two g layers even if zoom = F
svg = svg
.append("g").attr("class","zoom-layer")
.append("g")
// add zooming if requested
if (options.zoom) {
function redraw() {
d3.select(el).select(".zoom-layer").attr("transform",
"translate(" + d3.event.translate + ")"+
" scale(" + d3.event.scale + ")");
}
zoom.on("zoom", redraw)
d3.select(el).select("svg")
.attr("pointer-events", "all")
.call(zoom);
} else {
zoom.on("zoom", null);
}
// draw links
var link = svg.selectAll(".link")
.data(force.links())
.enter().append("line")
.attr("class", "link")
.style("stroke", function(d) { return d.colour ; })
//.style("stroke", options.linkColour)
.style("opacity", options.opacity)
.style("stroke-width", eval("(" + options.linkWidth + ")"))
.on("mouseover", function(d) {
d3.select(this)
.style("opacity", 1);
})
.on("mouseout", function(d) {
d3.select(this)
.style("opacity", options.opacity);
});
// draw nodes
var node = svg.selectAll(".node")
.data(force.nodes())
.enter().append("g")
.attr("class", "node")
.style("fill", function(d) { return color(d.group); })
.style("opacity", options.opacity)
.on("mouseover", mouseover)
.on("mouseout", mouseout)
.on("click", click)
.call(force.drag);
node.append("circle")
.attr("r", function(d){return nodeSize(d);})
.style("stroke", "#fff")
.style("opacity", options.opacity)
.style("stroke-width", "1.5px");
node.append("svg:text")
.attr("class", "nodetext")
.attr("dx", 12)
.attr("dy", ".35em")
.text(function(d) { return d.name })
.style("font", options.fontSize + "px " + options.fontFamily)
.style("opacity", options.opacityNoHover)
.style("pointer-events", "none");
function tick() {
node.attr("transform", function(d) {
if(options.bounded){ // adds bounding box
d.x = Math.max(nodeSize(d), Math.min(width - nodeSize(d), d.x));
d.y = Math.max(nodeSize(d), Math.min(height - nodeSize(d), d.y));
}
return "translate(" + d.x + "," + d.y + ")"});
link
.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
}
function mouseover() {
d3.select(this).select("circle").transition()
.duration(750)
.attr("r", function(d){return nodeSize(d)+5;});
d3.select(this).select("text").transition()
.duration(750)
.attr("x", 13)
.style("stroke-width", ".5px")
.style("font", options.clickTextSize + "px ")
.style('fill', 'black')
.style('position','relative')
.style("opacity", 1);
}
function mouseout() {
d3.select(this).select("circle").transition()
.duration(750)
.attr("r", function(d){return nodeSize(d);});
d3.select(this).select("text").transition()
.duration(1250)
.attr("x", 0)
.style("font", options.fontSize + "px ")
.style("opacity", options.opacityNoHover);
}
function click(d) {
return eval(options.clickAction)
}
// add legend option
if(options.legend){
var legendRectSize = 18;
var legendSpacing = 4;
var legend = svg.selectAll('.legend')
.data(color.domain())
.enter()
.append('g')
.attr('class', 'legend')
.attr('transform', function(d, i) {
var height = legendRectSize + legendSpacing;
var offset = height * color.domain().length / 2;
var horz = legendRectSize;
var vert = i * height+4;
return 'translate(' + horz + ',' + vert + ')';
});
legend.append('rect')
.attr('width', legendRectSize)
.attr('height', legendRectSize)
.style('fill', color)
.style('stroke', color);
legend.append('text')
.attr('x', legendRectSize + legendSpacing)
.attr('y', legendRectSize - legendSpacing)
.style('fill', 'darkOrange')
.text(function(d) { return d; });
}
// make font-family consistent across all elements
d3.select(el).selectAll('text').style('font-family', options.fontFamily);
},
});
I suspect I need to make some changes to the code over here:
function mouseover() {
d3.select(this).select("circle").transition()
.duration(750)
.attr("r", function(d){return nodeSize(d)+5;});
d3.select(this).select("text").transition()
.duration(750)
.attr("x", 13)
.style("stroke-width", ".5px")
.style("font", options.clickTextSize + "px ")
.style('fill', 'black')
.style("opacity", 1);
}
You need to resort the node groups holding the circles and text so the currently mouseover'ed one is the last in that group, and thus the last one drawn so it appears on top of the others. See the first answer here -->
Updating SVG Element Z-Index With D3
In your case, if your data doesn't have an id field you may have to use 'name' instead as below (adapted to use the mouseover function you've got):
function mouseover(d) {
d3.selectAll("g.node").sort(function (a, b) {
if (a.name != d.name) return -1; // a is not the hovered element, send "a" to the back
else return 1; // a is the hovered element, bring "a" to the front (by making it last)
});
// your code continues
The pain might be that you have to do this edit for every d3 graph generated by this R script, unless you can edit the R code/package itself. (or you could suggest it to the package author as an enhancement.)
I'm aiming to add a border around the circular avatars I create in my D3 visualization. I create these circular avatars by using clip-path. When I add a border to my node it is a square border around the whole node, rather than circular like I'm aiming for (and I understand why, because this node is rectangular). Here is what that currently looks like:
I'm struggling in getting this border to instead appear around the circular, clipped, image.
Here is the code where I currently set the (rectangular) border:
var nodeEnter = node.enter().append('svg:g')
.attr('class', 'node')
.attr('cursor', 'pointer')
.attr('style', function(d) {
var color;
if (d.strength > 2) {
color = 'blue';
} else {
color = 'red';
}
return 'outline: thick solid ' + color + ';';
})
.attr('transform', function(d) {
return "translate(" + d.x + "," + d.y + ")";
})
.call(force.drag);
...and this is how I declare my clip-path:
var clipPath = defs.append('clipPath')
.attr('id', 'clip-circle')
.append('circle')
.attr('r', 25);
My full example can be found here:
http://blockbuilder.org/MattDionis/5f966a5230079d9eb9f4
How would I go about setting this as a circular border around the image rather than rectangular around the entire node?
You could just add a circle of slightly larger radius (then your clip-path) into your node:
nodeEnter.append('circle')
.attr('r',30)
.style('fill', function(d) {
return d.strength > 2 ? 'blue' : 'red'
});
var images = nodeEnter.append('svg:image')
.attr('xlink:href', function(d) {
return d.avatarUrl;
})
.attr('x', function(d) {
return -25;
})
.attr('y', function(d) {
return -25;
})
.attr('height', 50)
.attr('width', 50)
.attr('clip-path', 'url(#clip-circle)');
Updated code.