I am playing around with a great tutorial from here http://ssun.azurewebsites.net/creating-a-draggable-object-in-d3/. What is the best way to extend the active area of the circle for dragging on click? I see three possible solutions:
create a complex object that has two circles, one visible and one invisible, but I am not sure if the invisible circle can be clicked on. Maybe 100% transparent.
Extend the active area of the mouse cursor(if that is even possible)
Extend the active area of the circle beyond visual part.
Unfortunately, I do not know how to do any of those things. Any help would be appreciated.
I like two circle approach but I'd group them in a g element. The dragging then works on the g element and the second circle is simply to expand the g:
<!DOCTYPE html>
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.js"></script>
</head>
<body>
<script>
var boxWidth = 600;
var boxHeight = 400;
var box = d3.select('body')
.append('svg')
.attr('class', 'box')
.attr('width', boxWidth)
.attr('height', boxHeight);
var drag = d3.behavior.drag()
.on('dragstart', function() {
circle.style('fill', 'red');
})
.on('drag', function() {
d3.select(this)
.attr('transform', function(d) {
return "translate(" + d3.event.x + "," + d3.event.y + ")";
});
})
.on('dragend', function() {
circle.style('fill', 'black');
});
var dragCircles = box.selectAll('.draggableCircle')
.data([{
x: (boxWidth / 2),
y: (boxHeight / 2),
r: 25
}])
.enter()
.append('g')
.attr('class', 'draggableCircle')
.attr('transform', function(d) {
return "translate(" + d.x + "," + d.y + ")";
})
.style('cursor', 'crosshair')
.call(drag);
dragCircles.append("circle")
.attr('r', function(d){
return d.r * 3;
})
.style('fill', 'transparent');
var circle = dragCircles.append("circle")
.attr('r', function(d) {
return d.r;
})
.style('fill', 'black');
</script>
</body>
</html>
Related
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.
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.
[Update]
Click jsfiddle here.
[Original Post]
How can I limit the coordinates of cross hair when moving the mouse? Notice when I move my mouse to the left of x-axis or bottom of y-axis, the cross hair and text still shows. I want the cross hair and text to stop showing when either mouse is moved to the left of x-axis or bottom of y-axis.
I have tried to add if else to limit the cross hair, but it didn't work. For instance, I tried something like .style("display", (xCoord>=minX & xCoord<=maxX & yCoord>=minY & yCoord<=maxY) ? "block" : "none") in the addCrossHair() function.
<!DOCTYPE html>
<meta charset="utf-8">
<head>
<style>
.axis path,
.axis line
{
fill:none;
rendering:crispEdges;
stroke:black;
width:2.5;
}
</style>
</head>
<body>
<script src="d3.min.js"></script>
<script>
var width = 800,
height = 600;
var randomX = [],
randomY = [];
for (var i = 0; i <= 500; i++) {
randomX[i] = Math.random() * 400;
randomY[i] = Math.random() * 400;
}
var minX = d3.min(randomX),
maxX = d3.max(randomX),
minY = d3.min(randomY),
maxY = d3.max(randomY);
var xScale = d3.scale.linear().domain([minX, maxX]).range([0, width]);
var xAxis = d3.svg.axis().scale(xScale).orient("bottom");
var yScale = d3.scale.linear().domain([minY, maxY]).range([height, 0]);
var yAxis = d3.svg.axis().scale(yScale).orient("left");
var svgContainer = d3.select("body").append("div").append("svg").attr("width", width).attr("height", height);
var svg = svgContainer.append("g").attr("transform", "translate(50, 50)");
svg.append("g").attr("class", "axis").attr("transform", "translate(0,530)").call(xAxis);
svg.append("g").attr("class", "axis").call(yAxis);
var crossHair = svg.append("g").attr("class", "crosshair");
crossHair.append("line").attr("id", "h_crosshair") // horizontal cross hair
.attr("x1", 0)
.attr("y1", 0)
.attr("x2", 0)
.attr("y2", 0)
.style("stroke", "gray")
.style("stroke-width", "1px")
.style("stroke-dasharray", "5,5")
.style("display", "none");
crossHair.append("line").attr("id", "v_crosshair") // vertical cross hair
.attr("x1", 0)
.attr("y1", 0)
.attr("x2", 0)
.attr("y2", 0)
.style("stroke", "gray")
.style("stroke-width", "1px")
.style("stroke-dasharray", "5,5")
.style("display", "none");
crossHair.append("text").attr("id", "crosshair_text") // text label for cross hair
.style("font-size", "10px")
.style("stroke", "gray")
.style("stroke-width", "0.5px");
svgContainer.on("mousemove", function () {
var xCoord = d3.mouse(this)[0] - 50,
yCoord = d3.mouse(this)[1] - 50;
addCrossHair(xCoord, yCoord);
})
.on("mouseover", function () {d3.selectAll(".crosshair").style("display", "block");})
.on("mouseout", function () {d3.selectAll(".crosshair").style("display", "none");});
function addCrossHair(xCoord, yCoord) {
// Update horizontal cross hair
d3.select("#h_crosshair")
.attr("x1", xScale(minX))
.attr("y1", yCoord)
.attr("x2", xScale(maxX))
.attr("y2", yCoord)
.style("display", "block");
// Update vertical cross hair
d3.select("#v_crosshair")
.attr("x1", xCoord)
.attr("y1", yScale(minY))
.attr("x2", xCoord)
.attr("y2", yScale(maxY))
.style("display", "block");
// Update text label
d3.select("#crosshair_text")
.attr("transform", "translate(" + (xCoord + 5) + "," + (yCoord - 5) + ")")
.text("(" + xScale.invert(xCoord) + " , " + yScale.invert(yCoord) + ")");
}
svg.selectAll("scatter-dots")
.data(randomY)
.enter().append("svg:circle")
.attr("cy", function (d) {
return yScale(d);
})
.attr("cx", function (d, i) {
return xScale(randomX[i]);
})
.style("fill", "brown")
.attr("r", 3)
</script>
</body>
The way to do this is to make the actual canvas (i.e. where you're drawing the dots) a separate g element from the ones that the axes are rendered into. Then the canvas can be translated such that it sits to the right and above the axes. The crosshair handler would be attached to this canvas g element (which is not quite straightforward, see this question) and the crosshairs or dots won't appear outside of the canvas.
Complete demo here.
I'm a rookie programmer, so this one will probably be an easy one for most of you. What lines of code do I need for labels and/or mouse-over text for this Chord diagram?
http://mbostock.github.com/d3/ex/chord.html
I need it to display the name of the category in the outer strip. When you mouse over, I want to display the exact number and both categories. Something like this: 'A: 5 thing from B'.
EDIT:
I still can't figure out how to implement it in my code. Can someone fill in my example code an explain what's going on?
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<title>Selecties EK 2010</title>
<script type="text/javascript" src="d3.v2.js"></script>
<link type="text/css" rel="stylesheet" href="ek2010.css"/>
</head>
<body>
<div id="chart"></div>
<script type="text/javascript" src="ek2010.js"></script>
</body>
</html>
and
// From http://mkweb.bcgsc.ca/circos/guide/tables/
var chord = d3.layout.chord()
.padding(.05)
.sortSubgroups(d3.descending)
.matrix([
[0, 0, 7, 5],
[0, 0, 8, 3],
[7, 8, 0, 0],
[5, 3, 0, 0]
]);
var width = 1000,
height = 1000,
innerRadius = Math.min(width, height) * .3,
outerRadius = innerRadius * 1.1;
var fill = d3.scale.ordinal()
.domain(d3.range(4))
.range(["#000000", "#FFDD89", "#957244", "#F26223"]);
var svg = d3.select("#chart")
.append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
svg.append("g")
.selectAll("path")
.data(chord.groups)
.enter().append("path")
.style("fill", function(d) { return fill(d.index); })
.style("stroke", function(d) { return fill(d.index); })
.attr("d", d3.svg.arc().innerRadius(innerRadius).outerRadius(outerRadius))
.on("mouseover", fade(.1))
.on("mouseout", fade(1));
var ticks = svg.append("g")
.selectAll("g")
.data(chord.groups)
.enter().append("g")
.selectAll("g")
.data(groupTicks)
.enter().append("g")
.attr("transform", function(d) {
return "rotate(" + (d.angle * 180 / Math.PI - 90) + ")"
+ "translate(" + outerRadius + ",0)";
});
ticks.append("line")
.attr("x1", 1)
.attr("y1", 0)
.attr("x2", 5)
.attr("y2", 0)
.style("stroke", "#000");
ticks.append("text")
.attr("x", 8)
.attr("dy", ".35em")
.attr("text-anchor", function(d) {
return d.angle > Math.PI ? "end" : null;
})
.attr("transform", function(d) {
return d.angle > Math.PI ? "rotate(180)translate(-16)" : null;
})
.text(function(d) { return d.label; });
svg.append("g")
.attr("class", "chord")
.selectAll("path")
.data(chord.chords)
.enter().append("path")
.style("fill", function(d) { return fill(d.target.index); })
.attr("d", d3.svg.chord().radius(innerRadius))
.style("opacity", 1);
/** Returns an array of tick angles and labels, given a group. */
function groupTicks(d) {
var k = (d.endAngle - d.startAngle) / d.value;
return d3.range(0, d.value, 1).map(function(v, i) {
return {
angle: v * k + d.startAngle,
label: i % 5 ? null : v / 1 + " internat."
};
});
}
/** Returns an event handler for fading a given chord group. */
function fade(opacity) {
return function(g, i) {
svg.selectAll("g.chord path")
.filter(function(d) {
return d.source.index != i && d.target.index != i;
})
.transition()
.style("opacity", opacity);
};
}
Add text elements to display labels. Alternatively, use textPath elements if you want to display text along a path. Two examples of labeled chord diagrams:
http://mbostock.github.com/d3/talk/20111018/chord.html
http://bl.ocks.org/1308257
You need to look at the (selection.on()) event handler in the d3.js wiki on Github. That shows you how to add events to elements including mouseover and mouseout. If you look at that example you linked to, you can see an instance of it already:
svg.append("g")
.selectAll("path")
.data(chord.groups)
.enter().append("path")
.style("fill", function(d) { return fill(d.index); })
.style("stroke", function(d) { return fill(d.index); })
.attr("d", d3.svg.arc().innerRadius(innerRadius).outerRadius(outerRadius))
.on("mouseover", fade(.1))
.on("mouseout", fade(1));
If you hover the mouse over the chord groups in the outer ring you will see all the other chord groups fade out.
If you want labels to pop-up that contain strings (text) you will need to define them using another JS library. One I know that works is Tipsyand there is an example using it together with d3 here. You should then be able to simply use a selector to choose which SVG element you want to illustrate this behavior.
Hope that helps.