Get location along a path or line upon click in d3 - d3.js

As stated in the title, I was wondering how to catch the location along a path or line of a mouse click - is it simply using the mousedown event and then calculating the d3.event.pageX, d3.event.pageY as it matches the line? Is there a better way?

Use d3.mouse function.
d3.mouse(container)
Returns the x and y coordinates of the current d3.event, relative to
the specified container. The container may be an HTML or SVG container
element, such as an svg:g or svg:svg. The coordinates are returned as
a two-element array [x, y].
Reference: https://github.com/mbostock/d3/wiki/Selections#d3_mouse
Example: (Try clicking on the path)
var points = [
[480, 200],
[580, 400],
[680, 100],
[780, 300],
[180, 300],
[280, 100],
[380, 400]
];
var svg = d3.select("body").append("svg")
.attr("width", 960)
.attr("height", 500);
var path = svg.append("path")
.data([points])
.attr("d", d3.svg.line()
.tension(0) // Catmull–Rom
.interpolate("cardinal-closed"));
var circle = svg.append("circle")
.attr("r", 5)
.style("fill", "red")
.attr("cx", points[0][0])
.attr("cy", points[0][1]);
path.on("click", function() {
circle.attr("cx", d3.mouse(this)[0])
.attr("cy", d3.mouse(this)[1]);
});
path {
fill: none;
stroke: black;
stroke-width: 5;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

Related

How to draw a line dynamically between two circles

I have drawn two circles with the below code, i have to draw a line between these two circles but the tricky point when i start to draw a line from the first circle there should be availability of second circle then only it should draw otherwise it shouldn't draw line and vice versa. if i click outside of circle then also it shouldn't draw line
in my below code or fiddle check it i can a draw a line my condition is not working
var line;
var svg = d3.select("body").append("svg")
.attr("width", 600)
.attr("height", 400).on("mousedown", mousedown).on("mouseup", mouseup);
function mousedown() {
var m = d3.mouse(this);
line = vis.append("line")
.attr("x1", m[0])
.attr("y1", m[1])
.attr("x2", m[0])
.attr("y2", m[1]);
svg.on("mousemove", mousemove);
}
function mousemove() {
var m = d3.mouse(this);
line.attr("x2", m[0])
.attr("y2", m[1]);
}
function mouseup() {
svg.on("mousemove", null);
}
var inputs = [
{ "x" : 200, "y" : 150, r : 50},
{ "x" : 300, "y" : 250, r : 50},
]
svg.selectAll("circle").data(inputs).enter().append("circle")
.attr("r", function(d, i){ return d.r })
.attr("cx", function(d, i){ return d.x })
.attr("cy", function(d, i){ return d.y })
.attr("stroke", "red")
.attr("fill", "white")
Here is my fiddle : https://jsfiddle.net/34j6pkn9/1/
maybe this will help you but its far from a good solution, but it work
Note :
circle drawing its bit ridiculus for me, it draw rect, imagine you
draw rect first then you draw a circle inside of it that its why, it
have bug on each angle of it that look like circle but difinetly its
a reactangle,
i think it can be solve by some calculation,but sorry i didnt know any of
that
var line;
var mx =0
var my =0
var inputs = [
{ "x" : 200, "y" : 150, r : 50},
{ "x" : 300, "y" : 250, r : 50},
]
var vis = d3.select("body").append("svg")
.attr("width", 600)
.attr("height", 400)
.on("mouseup", mouseup);
function mousedown() {
var m = d3.mouse(this);
line = vis.append("line")
.attr("x1", m[0])
.attr("y1", m[1])
.attr("x2", m[0])
.attr("y2", m[1]);
mx = m[0]
my = m[1]
vis.on("mousemove", mousemove);
}
function mousemove() {
var m = d3.mouse(this);
line.attr("x2", m[0])
.attr("y2", m[1]);
}
function mouseup() {
var m = d3.mouse(this);
//console.log(mx-m[0],my-m[1])
inputs.forEach(function(d,i){
if(m[0]<(d.x+d.r)&& m[0]>(d.x-d.r)&&m[1] <(d.y+d.r)&& m[1]>(d.y-d.r)){
if(mx<(d.x+d.r)&& mx>(d.x-d.r)&&my <(d.y+d.r)&& my>(d.y-d.r)){
}else{
vis.on("mousemove", null);
}
}
})
}
vis.selectAll("circle").data(inputs).enter().append("circle")
.attr("r", function(d, i){ return d.r })
.attr("cx", function(d, i){ return d.x })
.attr("cy", function(d, i){ return d.y })
.attr("stroke", "red")
.attr("fill", "white")
.on("mousedown", mousedown).on("mouseup", mouseup);
svg {
border: 1px solid red;
}
line {
stroke: steelblue;
stroke-width: 2px;
stroke-linecap: round;
}
<html>
<head>
<meta charset="utf-8">
<script src="https://code.jquery.com/jquery-3.1.0.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.2.3/d3.min.js"></script>
<meta name="viewport" content="width=device-width">
<title>JS Bin</title>
</head><body>
</body>
There will be a line after every circle (except the first one). Each line will connect the centers of every two consecutive circles.
You've the first circle which you are appending in your svg now, but how will you find the previous circle? You can, simply, find previousElementSibling of the first circle. But, there is catch here, notice there is line after every circle. So, be sure to check the name of the previousElementSibling if it's "line", find previousElementSibling once more.
// At this point, you've just appended the circle in
// 'svg' based on where the mouse is etc.
// Now, find the previous circle.
const parentNode = document.querySelector("svg");
// 'thisCircle' is the one you're appending in svg now.
const thisCircle = parentNode.lastElementChild;
// Previous sibling of this circle.
const previousSibling = thisCircle.previousElementSibling;
// If this is the first circle drawn, then do nothing- return
if (!previousSibling) {
return;
}
// Check if previous sibling is indeed 'circle' and not 'line'
const nodeNameOfPreviousSiblig = previousSibling.nodeName;
let previousCircle = previousSibling;
// If the previous node is 'line' go one more step upwards- it must be a circle. Since pattern => 1 circle, 1 line.
if (nodeNameOfPreviousSiblig == 'line') {
previousCircle = previousSibling.previousElementSibling;
}
// center of the previous circle.
const xCoordOPrevCircleCenter = previousCircle.getAttribute("cx");
const yCoordOPrevCircleCenter = previousCircle.getAttribute("cy");
Now, finally, append the line connecting these two consecutive circles centers:
svg.append('line')
.attr('x1', xCoordOThisCircleCenter)
.attr('y1', yCoordOThisCircleCenter)
.attr('x2', xCoordOPrevCircleCenter)
.attr('y2', yCoordOPrevCircleCenter)

Why is this D3 drag not working?

I'm new to D3 and just started playing with it. I want to move a pre-created rect (or circle) through drag and drop. Here is my fiddle.
// Create the SVG
var svg = d3.select("body").append("svg")
.attr("width", 700)
.attr("height", 400);
// Add a background
svg.append("rect")
.attr("width", 700)
.attr("height", 400)
.style("stroke", "#999999")
.style("fill", "#F6F6F6")
svg.append("circle")
.attr({ cx: 50, cy: 50, r: 5, fill: "red" })
.style("cursor", "pointer")
.call(drag);
svg.append("rect")
.attr({ x: 20, y: 20, width: 10, height: 10, fill: "blue" })
.style("cursor", "pointer")
.call(drag);
// Define drag beavior
var drag = d3.behavior.drag()
.on("drag", dragmove);
function dragmove(d) {
var x = d3.event.x;
var y = d3.event.y;
d3.select(this).attr("transform", "translate(" + x + "," + y + ")");
}
Two questions:
How to make the drag move work?
Why is my rect not showing up?
http://jsfiddle.net/5hemY/1/
Quick debugging led me to find that drag wasn't defined yet when you were trying to .call() it

d3.js: map with concentric circles emanating from dots

Been away from d3.js for a few months... and I've inherited a simple US map with features that someone else started.
The features are represented by simple dots of varying sizes.
I want to add emanating concentric circles to each dot, similar to the classic Onion example by Mike Bostock: http://bl.ocks.org/mbostock/4503672 (maybe not so ominous looking though)
I've got a block set up here: http://bl.ocks.org/mbostock/4503672
(Not sure why the states aren't rendering correctly in the block, but it probably doesn't matter for this.)
In Mike's example there is only one dot, so I'm have trouble understanding how to translate what he did to what I've got (many dots).
Here's my script:
/**
* Page initialization
*/
$(function() {
renderMap('#map-container');
});
function renderMap(container) {
var width = 960,
height = 500,
active;
var projection = d3.geo.albersUsa()
.scale(960)
.translate([width / 2, height / 2]);
var path = d3.geo.path()
.projection(projection);
var radius = d3.scale.sqrt()
.domain([0, 1e7])
.range([0, 10]);
var path2 = d3.geo.path()
.projection(projection);
// Remove svg, if already exist
d3.select(container).select('svg').remove();
var svg = d3.select(container).append("svg")
.attr("width", width)
.attr("height", height);
svg.append("rect")
.attr("width", width)
.attr("height", height);
//.on("click", reset);
var g = svg.append("g");
queue()
.defer(d3.json, "/mbostock/raw/4090846/us.json")
.defer(d3.json, "dots.json")
.await(function (error, us, centroid) {
g.append("g")
.attr("id", "states")
.selectAll("path")
.data(topojson.feature(us, us.objects.states).features)
.enter().append("path")
.attr("d", path)
.attr("class", "state");
//.on('click', click);
g.append('path')
.attr("id", "state-borders")
.datum(topojson.mesh(us, us.objects.states, function(a, b) { return a !== b; }))
.attr("d", path)
.attr("class", "mesh");
var dots = g.append("g")
.attr("id", "dots")
.selectAll("path")
.data(centroid.data)
.enter().append("path")
.attr("class", "dot")
.attr("d", path2.pointRadius(function(d) { return radius(d.properties.pool); }));
}
);
}
and the key part of Mike's example for making the rings is:
setInterval(function() {
svg.append("circle")
.attr("class", "ring")
.attr("transform", "translate(" + projection([100, -8]) + ")")
.attr("r", 6)
.style("stroke-width", 3)
.style("stroke", "red")
.transition()
.ease("linear")
.duration(6000)
.style("stroke-opacity", 1e-6)
.style("stroke-width", 1)
.style("stroke", "brown")
.attr("r", 160)
.remove();
}, 750);
how do I get the rings positioned on the dots?
Review the differences between the two methods to learn a little bit more about how functional/declarative programming abstracts away the pain of iterative programming.
Approach with D3 idioms:
fiddle: http://jsfiddle.net/blakedietz/E66eT/1/
update: D3 Way
<!DOCTYPE html>
<html>
<head>
<title></title>
<style>
body {
background: #192887;
}
.graticule {
fill: none;
stroke: #fff;
stroke-width: .5px;
}
.land {
fill: #007421;
}
.dot {
fill: #c7141a;
}
.ring {
fill: none;
stroke: #c7141a;
}
</style>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
</head>
<body>
<script>
var width = 960,
height = 500;
var projection = d3.geo.mercator()
.center([113, -3])
.scale(1275)
.translate([width / 2, height / 2])
.clipExtent([[0, 0], [width, height]])
.precision(.1);
var path = d3.geo.path()
.projection(projection);
var graticule = d3.geo.graticule()
.step([5, 5]);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
svg.append("path")
.datum(graticule)
.attr("class", "graticule")
.attr("d", path);
var data = [{x:-8,y:100},{x:-10,y:110},{x: -12,y:120}];
svg.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("class","dot")
.attr("transform",translateCircle)
.attr("r",8);
function translateCircle(datum, index)
{
return "translate(" + projection([datum.y, datum.x]) + ")";
};
setInterval(function(){
svg
.selectAll("ring")
.data(data)
.enter()
.append("circle")
.attr("class", "ring")
.attr("transform", translateCircle)
.attr("r", 6)
.style("stroke-width", 3)
.style("stroke", "red")
.transition()
.ease("linear")
.duration(6000)
.style("stroke-opacity", 1e-6)
.style("stroke-width", 1)
.style("stroke", "brown")
.attr("r", 160)
.remove();
}, 750)
d3.select(self.frameElement).style("height", height + "px");
</script>
</body>
</html>
So I didn't create a fully d3 idiomatic approach for this solution, but it will work. If you can get this to work implicitly within a svg.selectAll("circle"/"unique selection"...), etc, that would be even more awesome. I'll work on that in the mean time. Until then there's this more explicitly iterative approach.
With Mike's example you're only appending a single element to the D.O.M. in the setInterval call. In order to expedite the binding process, I've created a projection method which operates on a set of coordinates: the translateCircle will operate on a datum within a collection of coordinates allowing access to the internal attributes of each collection element.
Within each setInterval call the forEach method iterates over the collection of coordinates and then calls the same internals within the setInterval method that was called by Mike originally.
Not So D3
<style>
body {
background: #192887;
}
.graticule {
fill: none;
stroke: #fff;
stroke-width: .5px;
}
.land {
fill: #007421;
}
.dot {
fill: #c7141a;
}
.ring {
fill: none;
stroke: #c7141a;
}
</style>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
</head>
<body>
<script>
var width = 960,
height = 500;
var projection = d3.geo.mercator()
.center([113, -3])
.scale(1275)
.translate([width / 2, height / 2])
.clipExtent([[0, 0], [width, height]])
.precision(.1);
var path = d3.geo.path()
.projection(projection);
var graticule = d3.geo.graticule()
.step([5, 5]);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
svg.append("path")
.datum(graticule)
.attr("class", "graticule")
.attr("d", path);
var data = [{x:-8,y:100},{x:-10,y:110},{x: -12,y:120}];
svg.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("class","dot")
.attr("transform",translateCircle)
.attr("r",8);
function translateCircle(datum, index)
{
return "translate(" + projection([datum.y, datum.x]) + ")";
};
setInterval(function(){
data.forEach(function(datum)
{
svg
.append("circle")
.attr("class", "ring")
.attr("transform", translateCircle(datum))
.attr("r", 6)
.style("stroke-width", 3)
.style("stroke", "red")
.transition()
.ease("linear")
.duration(6000)
.style("stroke-opacity", 1e-6)
.style("stroke-width", 1)
.style("stroke", "brown")
.attr("r", 160)
.remove();
})
}, 750)
d3.select(self.frameElement).style("height", height + "px");
</script>
</body>
</html>

Interpolating along consecutive paths with D3.js

I'm adapting Mike Bostock's point along path interpolation model to accept an array of n individual paths and interpolate along each consecutively. Being relatively new to D3 the code below shows as far as I've got, which is to run the point interpolation for both paths concurrently. Now I'm a bit stuck over how to restructure this to make the process consecutive (with just one moving object). Really I need to be able to stop between paths to listen for a mouseclick, but I can figure out that code once the structure is there. Most grateful for assistance.
Here's the jsfiddle.
Code for posterity:
<!DOCTYPE html>
<meta charset="utf-8">
<body>
<style>
path {
fill: none;
stroke: #000;
stroke-width: 3px;
}
circle {
stroke: #fff;
stroke-width: 3px;
}
</style>
<script type="text/javascript" src="http://d3js.org/d3.v3.js"></script><script>
var pathdata = [
[[240, 100],
[290, 200],
[340, 50]],
[[340, 50],
[90, 150],
[140, 50],
[190, 200]]
];
var svg = d3.select("body").append("svg")
.attr("width", 960)
.attr("height", 500);
var paths = svg.selectAll("path")
.data(pathdata)
.enter()
.append("path")
.attr("d", d3.svg.line())
.attr("id",function(d, i) { return "path" + i });
// plot path vertices
svg.selectAll(".point")
.data([].concat.apply([], pathdata))
.enter().append("circle")
.attr("r", 5)
.attr("fill", "red")
.attr("transform", function(d) { return "translate(" + d + ")"; });
// interpolate along path0
var circle = svg.append("circle")
.attr("r", 10)
.attr("fill", "steelblue")
.attr("transform", "translate(" + pathdata[0][1] + ")")
.transition()
.duration(4000)
.attrTween("transform", translateAlong(d3.select("#path0")[0][0]));
// interpolate along path1
var circle = svg.append("circle")
.attr("r", 10)
.attr("fill", "steelblue")
.attr("transform", "translate(" + pathdata[1][1] + ")")
.transition()
.duration(4000)
.attrTween("transform", translateAlong(d3.select("#path1")[0][0]));
function translateAlong(path) {
console.log(path);
var l = path.getTotalLength();
return function(d, i, a) {
return function(t) {
var p = path.getPointAtLength(t * l);
return "translate(" + p.x + "," + p.y + ")";
};
};
}
</script>
</body>
</html>
I'm also wondering if it might be better to format the input data along one of the following lines?
// 3rd field for path id
var points_alt1 = [
[240, 100, 0],
[290, 200, 0],
[340, 50, 0],
[340, 50, 1],
[90, 150, 1],
[140, 50, 1],
[190, 200, 1]
]
or..
// 3rd field for interpolation end-points
var points_alt2 = [
[240, 100, 0],
[290, 200, 0],
[340, 50, 1],
[340, 50, 0],
[90, 150, 0],
[140, 50, 0],
[190, 200, 1]
]
Create a function that takes as params a d3 selection of paths and an integer index of the path (within the selection) along which you want to animate. This function finds the appropriate path within that selection, starts up a transition of a circle along it, and subscribes to the 'end' event of the transition, at which point it triggers the next animation.
Here's the working fiddle
function animateSinglePath(selection, indexOfAnimated) {
indexOfAnimated = indexOfAnimated || 0;
// Create circle if doesn't already exist
// (achived by binding to single element array)
circle = svg.selectAll('.animated-circle').data([null])
circle.enter()
.append("circle")
.attr("class", "animated-circle")
.attr("r", 10)
.attr("fill", "steelblue")
selection.each(function(d, i) {
// find the path we want to animate
if(i == indexOfAnimated) {
// In this context, the "this" object is the DOM
// element of the path whose index, i equals the
// desired indexOfAnimated
path = d3.select(this)
// For style, make it dashed
path.attr('stroke-dasharray', '5, 5')
// Move circle to start pos and begin animation
console.log('Start animation', i)
circle
.attr("transform", "translate(" + d[0] + ")")
.transition()
.duration(2000)
.attrTween("transform", translateAlong(path.node()))
.each('end', function() {
console.log('End animation', i);
// For style, revert stroke to non-dashed
path.attr('stroke-dasharray', '')
// trigger the next animation by calling this
// function again
animateSinglePath(selection, indexOfAnimated + 1);
});
}
});
}
P.S
I wouldn't restructure the data as you proposed, because you need to have distinct SVG <path> elements – one for each "chapter" in the sequence. Having a distinct array for each of path, as you do now, is what enables you to create these <path>s via data() binding.
Depending on what you're trying to achieve, you may even want to further nest each path array, wrapping it with an object {}, to hold meta data about the path:
var pathData = [
{
name: "Crossing the Niemen",
points: [ [240, 100], [290, 200], [340, 50] ]
},
{
name: "March on Vilnius",
points: [ [340, 50], [90, 150], [140, 50], [190, 200] ]
},
{
name: "March on Moscow",
points: [ [190, 200], [70, 180], [30, 30], [350, 160] ]
}
];

Creating a border around your D3 graph

So I've just started my D3 journey, and wanted to ask about how one would create a small 1px border around the chart.
I created the variables "border" and "bordercolor" and then I added .attr("border",border) to the var svg = d3.select("body") portion of my code. It doesn't crash, but I get no border either.
I guess the question is how do i add this border, and if someone could explain why what i did is wrong.
<script type="text/javascript">
//Width and height
var w = 800;
var h = 400;
var padding = 20;
var border=1;
var bordercolor='black';
var dataset = [
[5, 20], [480, 90], [250, 50], [100, 33], [330, 95],[-50,-100],[50,-45],
[410, 12], [475, 44], [25, 67], [85, 21], [220, 88],[-480, 90], [3,-90]
];
// create scale functions
var xScale = d3.scale.linear()
.domain([d3.min(dataset, function(d) { return d[0]; }), d3.max(dataset, function(d) { return d[0]; })])
.range([padding, w - padding * 2]);
var yScale = d3.scale.linear()
.domain([d3.min(dataset, function(d) { return d[0]; }), d3.max(dataset, function(d) { return d[1]; })])
.range([h - padding, padding]);
var rScale = d3.scale.linear()
.domain( [-100, d3.max(dataset, function(d) { return d[1]; })] )
.range([2,5]);
//Create SVG element
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h)
.attr("border",border)
;
svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
.attr("cx", function(d) {
return xScale(d[0]);
})
.attr("cy", function(d) {
return yScale(d[1]);
})
.attr("r", 3);
svg.selectAll("text")
.data(dataset)
.enter()
.append("text")
.text(function(d) {
return d[0] + "," + d[1];
})
.attr("x", function(d) {
return xScale(d[0]);
})
.attr("y", function(d) {
return yScale(d[1]);
})
.attr("font-family", "sans-serif")
.attr("font-size", "11px")
.attr("fill", "red");
</script>
Use the style attribute to place an outline around the svg:
//Create SVG element
var svg = d3.select("body")
.append("svg")
.attr("style", "outline: thin solid red;") //This will do the job
.attr("width", w)
.attr("height", h);
The svg var is just a container. You need to add a path or element to the container and then give it the stroke color and width you want for your border. There is more than one way to do this. In this gist I did it by adding a rect with the following values:
var borderPath = svg.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("height", h)
.attr("width", w)
.style("stroke", bordercolor)
.style("fill", "none")
.style("stroke-width", border);
IMHO it's better to keep separated shape from style instructions:
.append("rect")
.attr("x", 5)
.attr("y", 5)
.attr("height", 40)
.attr("width", 50)
.attr("class","foo")
...
CSS:
svg rect.foo {
fill: white;
stroke-width: 0.5;
stroke: grey;
}
Simply use css:
svg {
border:1px solid black;
}
If X and Y Axis are used, other option is to use tickSizeOuter()
Example:
var yAxis = d3.axisLeft(y).ticks(5).tickSizeOuter(-width);
var xAxis = d3.axisBottom(x).ticks(5).tickSizeOuter(-height);

Resources