Related
How to draw the line using bezierCurveTo method in d3.js such that lines appears as below image
I have just referred bezier Curve but i'm not getting any idea on it.
There are a lot of ways this could be done. One could make a custom curve that achieves this.
But, we could keep it simpler too. The datum passed to the link generator, such as d3.linkHorizontal, from a d3 layout generally contains a source and target property, each of these usually contain x and y properties. Assuming this structure, we could create a function that uses these and creates and returns the appropriate path data with a bezier curve:
var linker = function(d) {
var x0 = d.source.x;
var y0 = d.source.y;
var y1 = d.target.y;
var x1 = d.target.x;
var k = 120;
var path = d3.path()
path.moveTo(y0,x0)
path.bezierCurveTo(y1-k,x0,y0,x1,y1-k,x1);
path.lineTo(y1,x1);
return path.toString();
}
The above is pretty basic, it uses d3.path but you could easily just construct the SVG path string yourself. There are lots of interactive bezier curve explorers online so you can figure out what control points work best. As the tree layout I've used is vertical, I've turned it horizontal by inverting x and y, which is why my coordinates are [y,x]. I use k above to offset the bezier curve to a small portion of the overall link on the left:
But you could easily toy with this to place the curve in the middle of the link:
Here's it in action:
var data = { "name": "Parent", "children": [
{ "name": "Child A", "children": [ { "name": "Grandchild1"}, {"name":"Grandchild2" } ] },
{ "name": "Child B", }
] };
var width = 400;
var height = 300;
margin = {left: 50, top: 10, right:30, bottom: 10}
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var g = svg.append("g").attr('transform','translate('+ margin.left +','+ margin.right +')');
var root = d3.hierarchy(data);
var tree = d3.tree()
.size([height-margin.top-margin.bottom,width-margin.left-margin.right]);
var linker = function(d) {
var x0 = d.source.x;
var y0 = d.source.y;
var y1 = d.target.y;
var x1 = d.target.x;
var k = (y1-y0)/2;
var path = d3.path()
path.moveTo(y0,x0)
path.lineTo(y0+k/2,x0)
path.bezierCurveTo(y1-k,x0,y0+k,x1,y1-k/2,x1);
path.lineTo(y1,x1);
return path.toString();
}
var link = g.selectAll(".link")
.data(tree(root).links())
.enter().append("path")
.attr("class", "link")
.attr("d", linker);
var node = g.selectAll(".node")
.data(root.descendants())
.enter().append("g")
.attr("class", function(d) { return "node" + (d.children ? " node--internal" : " node--leaf"); })
.attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; })
node.append("circle")
.attr("r", 2.5);
node.append("text")
.text(function(d) { return d.data.name; })
.attr('y',-10)
.attr('x',-10)
.attr('text-anchor','middle');
.node circle {
fill: #fff;
stroke: steelblue;
stroke-width: 3px;
}
.link {
fill: none;
stroke: #ccc;
stroke-width: 2px;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
But, in reading the comments I notice that your question may be more about dagreD3 - which changes things considerably. Dagre D3 offers better ease of use relative to D3 at the cost of the some of D3's flexibility. If you want to provide a certain type of curve to DagreD3, then you should use a d3 curve, or some custom curve (as in the linked answer above). You can specify the curve when adding edges easily enough.
But that doesn't solve the issue of edges originating from the same point as in your image. I'll provide a d3 based solution - which probably breaks edge label placement, transitions, etc - so it should be built out a bit if you need that functionality. I'll use the bezier generator from above. The following is inspired by this:
var g = new dagreD3.graphlib.Graph()
.setGraph({rankdir: 'LR'})
.setDefaultEdgeLabel(function() { return {}; });
g.setNode(0, { label: "0"});
g.setNode(1, { label: "1"});
g.setNode(2, { label: "2"});
g.setNode(3, { label: "3"});
g.setNode(4, { label: "4"});
g.setEdge(0, 1);
g.setEdge(0, 2);
g.setEdge(1, 3);
g.setEdge(1, 4);
var render = new dagreD3.render().createEdgePaths(createEdgePaths);
var svg = d3.select("svg"),
svgGroup = svg.append("g"),
zoom = d3.zoom().on("zoom", function() {
svgGroup.attr("transform", d3.event.transform);
});
svg.call(zoom);
render(svgGroup, g);
function createEdgePaths(selection, g, arrows) {
selection.selectAll("g.edgePath")
.data(g.edges())
.enter()
.append("path")
.attr("d", function(e) {
return calcPoints(g,e);
});
}
function calcPoints(g, e) {
var source = g.node(e.v);
var target = g.node(e.w);
var x0 = source.x + source.width/2;
var x1 = target.x - target.width/2;
var y0 = source.y;
var y1 = target.y;
return linker(x0,y0,x1,y1)
}
function linker(x0,y0,x1,y1) {
var dx = x1 -x0;
var k = dx/3;
var path = d3.path()
path.moveTo(x0,y0)
path.bezierCurveTo(x1-k,y0,x0,y1,x1-k,y1);
path.lineTo(x1,y1);
return path.toString();
}
path {
stroke: #333;
stroke-width: 1.5px;
fill: none;
}
rect {
fill: none;
stroke:black;
stroke-width: 1px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/dagre-d3#0.6.1/dist/dagre-d3.min.js"></script>
<svg width="800" height="600"></svg>
Using d3.behavior.drag(), is there a way to enable a dragged image to be visible outside of its parent svg element borders.
In my app, I have a top layout based on an HTML grid (using flexBox) and several D3.js graphs located in each grid cell. Each graph is built with an SVG element and its childrens.
I need a drag and drop feature to enable copy/move of elements between these graphs. As now, the feature is working except that the drag image disappears when I cross the border of the source graph.
<!DOCTYPE html>
<meta charset="utf-8">
<style>
svg {
float: left;
border-bottom: solid 1px #ccc;
border-right: solid 1px #ccc;
margin-right: -1px;
margin-bottom: -1px;
}
</style>
<body>
<script src="//d3js.org/d3.v3.min.js"></script>
<script>
var width = 240,
height = 125,
radius = 20;
var overSVG;
var drag = d3.behavior.drag()
.origin(function (d) {
return d;
})
.on("drag", dragmove)
.on("dragend", dragend);
var svg = d3.select("body").append("div").selectAll("svg")
.data(d3.range(2).map(function (v, i) {
return {
svgElement: i,
x : width / 2,
y : height / 2
};
}))
.enter().append("svg")
.attr("width", width)
.attr("height", height)
.attr("id", function (d, i) {
return "svg_" + i
})
.on("mouseover", over)
.on("mouseout", out);
svg.append("circle")
.attr("r", radius)
.attr("cx", function (d) {
return d.x;
})
.attr("cy", function (d) {
return d.y;
})
.attr("svgElement", function (d, i) {
return i;
})
.call(drag);
function over(d, i) {
overSVG = d;
var selectedNodeId = "#svg_" + i;
d3.select(selectedNodeId)
.attr("fill", 'red');
}
function out(d, i) {
overSVG = "";
var selectedNodeId = "#svg_" + i;
d3.select(selectedNodeId)
.attr("fill", 'blue');
}
function dragmove(d) {
d3.select(this)
.attr("cx", d.x = d3.event.x)
.attr("cy", d.y = d3.event.y);
console.log("drag move", this, d3.event.x, ' ', d3.event.y);
}
function dragend(d) {
console.log("==>drag Ended :");
console.log(" dragged circle", this);
console.log(" from svg ", d);
console.log(" to", overSVG);
}
</script>
By default the svg elements have an overflow attribute set to hidden.
You can try setting the overflow attribute to visible
svg {
overflow: visible;
}
Again, without seeing a working example, it's hard to tell if this will work for you, but maybe it can help.
I'm wrestling with a problem of a brush not being removed correctly on a bar chart. You can see the Bl.ock here and see what's not working correctly.
In short, the brush highlights the bars that have been selected by the brush, as well as snaps to the edge of the rect to make selecting spans of time easier (there's a secondary bug here where the brush snapping isn't quite mapping correctly to the dates -- you'll see this if you try to draw the brush up to the edge of the barchart). Somewhere along the way (maybe with the rect snapping?) the background click-to-remove-brush feature stopped working (it now selects a single year span, although doesn't highlight the rect correctly). To make it easier for users, I wanted to add a button that a user can click to remove the brush when they're done (the resetBrush() function below).
My understanding was the brush selection can be cleared with brush.extent(), but when you clear the extent you then have to redraw the brush. I thought I was doing that correctly, but alas, I'm running into some problem somewhere that I can't seem to track down. Any pointers on where I'm tripping up would be greatly appreciated!
Code:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font-family: sans-serif;
color: #000;
text-rendering: optimizeLegibility;
}
.barchart {
z-index: 30;
display: block;
visibility: visible;
position: relative;
padding-top: 15px;
margin-top: 15px;
}
.axis {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.x.axis path {
display: none;
}
.resize path {
fill: #666;
fill-opacity: .8;
stroke: #000;
stroke-width: 1.5px;
}
.brush .extent {
stroke: #fff;
stroke-opacity: .6;
stroke-width: 2px;
fill-opacity: .1;
shape-rendering: crispEdges;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://d3js.org/d3.geo.projection.v0.min.js"></script>
<script src="http://d3js.org/topojson.v1.min.js"></script>
<script>
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 960 - margin.left - margin.right,
height = 200 - margin.top - margin.bottom;
brushYearStart = 1848;
brushYearEnd = 1905;
// Scales
var x = d3.scale.ordinal().rangeRoundBands([0, width - 60], .1);
var y = d3.scale.linear().range([height, 0]);
// Prepare the barchart canvas
var barchart = d3.select("body").append("svg")
.attr("class", "barchart")
.attr("width", "100%")
.attr("height", height + margin.top + margin.bottom)
.attr("y", height - height - 100)
.append("g");
var z = d3.scale.ordinal().range(["steelblue", "indianred"]);
var brushYears = barchart.append("g")
brushYears.append("text")
.attr("id", "brushYears")
.classed("yearText", true)
.text(brushYearStart + " - " + brushYearEnd)
.attr("x", 35)
.attr("y", 12);
d3.csv("years_count.csv", function (error, post) {
// Coercion since CSV is untyped
post.forEach(function (d) {
d["frequency"] = +d["frequency"];
d["frequency_discontinued"] = +d["frequency_discontinued"];
d["year"] = d3.time.format("%Y").parse(d["year"]).getFullYear();
});
var freqs = d3.layout.stack()(["frequency", "frequency_discontinued"].map(function (type) {
return post.map(function (d) {
return {
x: d["year"],
y: +d[type]
};
});
}));
x.domain(freqs[0].map(function (d) {
return d.x;
}));
y.domain([0, d3.max(freqs[freqs.length - 1], function (d) {
return d.y0 + d.y;
})]);
// Axis variables for the bar chart
x_axis = d3.svg.axis().scale(x).tickValues([1850, 1855, 1860, 1865, 1870, 1875, 1880, 1885, 1890, 1895, 1900]).orient("bottom");
y_axis = d3.svg.axis().scale(y).orient("right");
// x axis
barchart.append("g")
.attr("class", "x axis")
.style("fill", "#000")
.attr("transform", "translate(0," + height + ")")
.call(x_axis);
// y axis
barchart.append("g")
.attr("class", "y axis")
.style("fill", "#000")
.attr("transform", "translate(" + (width - 85) + ",0)")
.call(y_axis);
// Add a group for each cause.
var freq = barchart.selectAll("g.freq")
.data(freqs)
.enter().append("g")
.attr("class", "freq")
.style("fill", function (d, i) {
return z(i);
})
.style("stroke", "#CCE5E5");
// Add a rect for each date.
rect = freq.selectAll("rect")
.data(Object)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function (d) {
return x(d.x);
})
.attr("y", function (d) {
return y(d.y0) + y(d.y) - height;
})
.attr("height", function (d) {
return height - y(d.y);
})
.attr("width", x.rangeBand())
.attr("id", function (d) {
return d["year"];
});
// Draw the brush
brush = d3.svg.brush()
.x(x)
.on("brush", brushmove)
.on("brushend", brushend);
var arc = d3.svg.arc()
.outerRadius(height / 15)
.startAngle(0)
.endAngle(function(d, i) { return i ? -Math.PI : Math.PI; });
brushg = barchart.append("g")
.attr("class", "brush")
.call(brush);
brushg.selectAll(".resize").append("path")
.attr("transform", "translate(0," + height / 2 + ")")
.attr("d", arc);
brushg.selectAll("rect")
.attr("height", height);
});
// ****************************************
// Brush functions
// ****************************************
function brushmove() {
y.domain(x.range()).range(x.domain()).clamp(true);
b = brush.extent();
brushYearStart = Math.ceil(y(b[0]));
brushYearEnd = Math.ceil(y(b[1]));
// Snap to rect edge
d3.select("g.brush").call(brush.extent([y.invert(brushYearStart), y.invert(brushYearEnd)]));
// Fade all years in the histogram not within the brush
d3.selectAll("rect.bar").style("opacity", function (d, i) {
return d.x >= brushYearStart && d.x < brushYearEnd ? "1" : ".4"
});
}
function brushend() {
// Additional calculations happen here...
// filterPoints();
// colorPoints();
// styleOpacity();
// Update start and end years in upper right-hand corner of the map
d3.select("#brushYears").text(brushYearStart + " - " + brushYearEnd);
}
function resetBrush() {
d3.selectAll(".brush").remove();
d3.selectAll("brushg.resize").remove();
brush.clear();
brushg.call(brush);
}
</script>
<div id="resetMap">
<button
id="returnBrush"
class="btn btn-default"
onclick="resetBrush()"/>Remove Brush
</div>
</body>
</html>
If you execute d3.selectAll(".brush").remove(); you remove <g class="brush"></g> and his childs.
This d3.selectAll("brushg.resize").remove(); is a bug. Must to be brushg.selectAll(".resize").remove(); but is the same case that d3.selectAll(".brush").remove();.
You have to do this:
For reset the brush.extent() and fire the brush event.
function resetBrush() {
brush
.clear()
.event(d3.select(".brush"));
}
For reset #brushYears to the initial state
function brushend() {
var localBrushYearStart = (brush.empty()) ? brushYearStart : Math.ceil(y(b[0])),
localBrushYearEnd = (brush.empty()) ? brushYearEnd : Math.ceil(y(b[1]));
// Update start and end years in upper right-hand corner of the map
d3.select("#brushYears").text(localBrushYearStart + " - " + localBrushYearEnd);
}
For reset to initial values on brush event
function brushmove() {
y.domain(x.range()).range(x.domain()).clamp(true);
b = brush.extent();
3.1. To set the localBrushYearStart and localBrushYearEnd variables to initial state on brush.empty() or set to Math.ceil(brush.extent()))
var localBrushYearStart = (brush.empty()) ? brushYearStart : Math.ceil(y(b[0])),
localBrushYearEnd = (brush.empty()) ? brushYearEnd : Math.ceil(y(b[1]));
3.2. To execute brush.extent() on selection, or brush.clear() on brush.empty()
// Snap to rect edge
d3.select("g.brush").call((brush.empty()) ? brush.clear() : brush.extent([y.invert(localBrushYearStart), y.invert(localBrushYearEnd)]));
3.3. To set opacity=1 years on brush.empty() or selection, and opacity=.4 on not selected years
// Fade all years in the histogram not within the brush
d3.selectAll("rect.bar").style("opacity", function(d, i) {
return d.x >= localBrushYearStart && d.x < localBrushYearEnd || brush.empty() ? "1" : ".4";
});
}
Check the corrections on my BL.OCKS
Just do this
function resetBrush() {
d3.select("g.brush").call(brush.extent([0, 0]))
d3.selectAll("rect.bar").style("opacity", "0.4");
//reset year labels at top
}
I was trying to draw curves between two nodes using d3.js.
<meta charset="utf-8">
<!-- ----------------- -->
<!-- THIS PART IS CSS -->
<!-- ----------------- -->
<style>
body
{
background: lightskyblue;
}
.node {
stroke: black;
stroke-width: 4.0px;
}
.link {
stroke: slategray;
stroke-opacity: 0.6;
}
</style>
</head>
<body>
<!-- ------------------------ -->
<!-- THIS PART IS JAVASCRIPT -->
<!-- ------------------------ -->
<script src="d3.v3.js"></script>
<script>
////////////////////////////////////////////////////////////////
// Function to prepare topology background
////////////////////////////////////////////////////////////////
var width = 1280, height = 630;
var color = d3.scale.category20();
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height)
.style("border","7px solid black")
;
var nodes = [], links = [];
var graph = test_case();
nodes = graph.nodes;
links = graph.links;
for(var i = 0; i < nodes.length; i++)
{
element = nodes[i];
if(element.group == 0)
{
element.x = width/2;
element.y = height/2;
element.fixed = true;
}
}
var force = d3.layout.force()
.charge(-400)
.linkDistance(300)
.size([width, height])
.links(links)
.nodes(nodes)
.on("tick", tick);
var node = svg.selectAll(".node"),
link = svg.selectAll(".link");
drawGraph();
function test_case()
{
graph =
{
"nodes":[
{"name":0,"group":0,"pos":[216,140]},
{"name":1,"group":1,"pos":[350,200]},
{"name":2,"group":1,"pos":[478,438]},
{"name":3,"group":1,"pos":[596,498]},
{"name":4,"group":1,"pos":[422,295]},
{"name":5,"group":1,"pos":[597,357]},
{"name":6,"group":1,"pos":[530,238]},
{"name":7,"group":1,"pos":[506,100]},
{"name":8,"group":1,"pos":[793,265]},
{"name":9,"group":1,"pos":[972,228]}
],
"links":[
{"source":1,"target":2,"rssi":25,"lqi":25,"lr":120},
{"source":2,"target":0,"rssi":50,"lqi":50,"lr":80},
{"source":3,"target":0,"rssi":12,"lqi":12,"lr":200},
{"source":3,"target":2,"rssi":65,"lqi":65,"lr":40},
{"source":4,"target":0,"rssi":80,"lqi":80,"lr":170},
{"source":5,"target":0,"rssi":60,"lqi":60,"lr":110},
{"source":6,"target":7,"rssi":30,"lqi":30,"lr":64},
{"source":7,"target":0,"rssi":21,"lqi":21,"lr":97},
{"source":8,"target":3,"rssi":57,"lqi":57,"lr":190},
{"source":9,"target":0,"rssi":72,"lqi":72,"lr":12}
]
};
return graph;
}
////////////////////////////////////////////////////////////////
// Function to draw topology, including nodes and links
////////////////////////////////////////////////////////////////
function drawGraph()
{
//Add links
link = link.data(force.links());
link.enter()
.append("path")
// .insert("line", ".gnode")
.attr("class", "link")
// .style("stroke", function(d) {
// if(d.rssi<=25) return "crimson";
// else if(d.rssi<=50) return "orange";
// else if(d.rssi<=75) return "royalblue";
// else return "green"; })
//.style("stroke-dasharray" ,function(d) { if(d.target==0) return "10,10"; else return "";})
// .style("stroke-width", function(d) { return d.lqi/20 + 5; })
// .style("stroke-opacity", function(d) { return (d.lr/51)*0.1 + 0.5; });
link.exit().remove();
//Add nodes with texts in them
node = node.data(force.nodes());
node.enter().append("g").attr("class", "gnode").call(force.drag);
node.append("circle")
.attr("class", function(d) { return "node group" + d.group })
.attr("r", 25)
.style("fill", function(d) { if(d.group==0) return "mediumaquamarine"; else return "beige" });
node.append("text")
.attr("text-anchor", "middle")
.attr("dy", "+7")
.text(function(d) { return d.name; })
.attr("font-size", "22px")
.attr("font-weight", "bold")
.style("fill", "black");
node.exit().remove();
force.start();
}
function tick()
{
node.attr("transform", function(d) { return "translate(" + d.pos + ")";});
link.attr("d", function(d) {
var dx = d.target.pos[0] - d.source.pos[0],
dy = d.target.pos[1] - d.source.pos[1],
dr = Math.sqrt(dx * dx + dy * dy);
return "M" + d.source.pos[0] + "," + d.source.pos[1] + "A" + dr + "," + dr + " 0 0,1 " + d.target.pos[0] + "," + d.target.pos[1];
});
}
</script>
</body>
</html>
but the result of the code was not as I expected which was supposed to be like this http://bl.ocks.org/d3noob/5155181 with addition of colours, opacity and width. I also want to draw arrows. Could anybody figure this out please?
I'm sorry I'm very new to stackoverflow; it's my first time I have written this topic.
For adding arrow in path following steps:
Append Marker:
svg.append("svg:defs").selectAll("marker")
.data(["end"]) // Different link/path types can be defined here
.enter().append("svg:marker") // This section adds in the arrows
.attr("id", String)
.attr("viewBox", "0 -5 10 10")
.attr("refX", 45)
.attr("refY",-5.2)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("svg:path")
.attr("d", "M0,-5L10,0L0,5");
Append arrow in Path
link = link.data(force.links());
link.enter()
.append("path")
.attr("class", "link")
.attr("marker-end", "url(#end)");
Path CSS:
.link {
stroke: slategray;
stroke-opacity: 0.6;
fill: none;
stroke-width: 1.5px;
}
DEMO for your code
According to radius you want to change the arrow position refer this ,and it will help for dragging nodes
First step:
Make fill none :)
.link {
stroke: slategray;
fill: none;
stroke-opacity: 0.6;
}
Working on the arrows
JSFIDDLE
I am attempting to create a map of the 10 major NASA facilities in D3. I have successfully generated the base United States map and appended NASA logos at each one of the center locations based on a .csv with latitude and longitude. However, I cannot figure out any elegant way to draw lines / links / arcs / connections between the points on the map.
In the code below, I have drawn a line between GSFC and KSC (using the 'var = places', 'var = route', and 'svg.append("path")') but it is on an SVG layer, so it is on top of the logos (which looks awful) and does not scale (or go away would be fine, too) when clicking to zoom in on a state. I would like to be able to draw links between the centers based on the latitude and longitude data from the .csv.
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.background {
fill: none;
pointer-events: all;
}
#states {
fill: #aaaaaa;
}
#states .active {
fill: #ff0000;
fill-opacity: .5;
}
#state-borders {
fill: none;
stroke: #ffffff;
stroke-width: 1.5px;
stroke-linejoin: round;
stroke-linecap: round;
pointer-events: none;
}
path.link {
fill: none;
stroke: #666666;
stroke-width: 1.5px;
}
.stroke {
fill: none;
stroke: #000;
stroke-width: 3px;
}
.fill {
fill: #fff;
}
.graticule {
fill: none;
stroke: #777;
stroke-width: .5px;
stroke-opacity: .5;
}
.route {
fill: none;
stroke: blue;
stroke-width: 3px;
}
</style>
<body>
<h2>
<span>NASA Centers</span>
</h2>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://d3js.org/d3.geo.projection.v0.min.js"></script>
<script src="http://d3js.org/topojson.v1.min.js"></script>
<script>
var width = 1000,
height = 600,
centered;
var projection = d3.geo.albersUsa()
.scale(1070)
.translate([width / 2, height / 2]);
var path = d3.geo.path()
.projection(projection);
var graticule = d3.geo.graticule();
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var g = svg.append("g");
var places = {
GSFC: [-76.852587, 38.991621],
KSC: [-80.650813, 28.524963]
};
var route = {
type: "LineString",
coordinates: [
places.GSFC,
places.KSC
]
};
var point = svg.append("g")
.attr("class", "points")
.selectAll("g")
.data(d3.entries(places))
.enter().append("g")
.attr("transform", function(d) { return "translate(" + projection(d.value) + ")"; });
point.append("text")
.attr("y", 5)
.attr("dx", "1em")
.text(function(d) { return d.key; });
d3.json("us.json", function(error, us) {
g.append("g")
.attr("id", "states")
.selectAll("path")
.data(topojson.feature(us, us.objects.states).features)
.enter().append("path")
.attr("d", path)
.on("click", clicked);
g.append("path")
.datum(topojson.mesh(us, us.objects.states, function(a, b) { return a !== b; }))
.attr("id", "state-borders")
.attr("d", path);
d3.csv("nasacenters.csv", function(error, data) {
g.selectAll("image").data([0])
.data(data)
.enter()
.append("image")
.attr("xlink:href", "nasalogo.png")
.attr("width", "30")
.attr("height", "30")
.attr("x", function(d) {
return projection([d.lon, d.lat])[0]-15;
})
.attr("y", function(d) {
return projection([d.lon, d.lat])[1]-15;
})
svg.append("path")
.datum(route)
.attr("class", "route")
.attr("d", path)
.style("opacity", 0.5);
});
});
function clicked(d) {
var x, y, k;
if (d && centered !== d) {
var centroid = path.centroid(d);
x = centroid[0];
y = centroid[1];
k = 4;
centered = d;
} else {
x = width / 2;
y = height / 2;
k = 1;
centered = null;
}
g.selectAll("path")
.classed("active", centered && function(d) { return d === centered; });
g.transition()
.duration(750)
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")scale(" + k + ")translate(" + -x + "," + -y + ")")
.style("stroke-width", 1.5 / k + "px");
}
</script>
</body>
</html>
The .csv file is in the following format:
code,center,lat,lon
GSFC,Goddard Space Flight Center,38.991621,-76.852587
KSC,Kennedy Space Center,28.524963,-80.650813
JPL,Jet Propulsion Laboratory,34.200463,-118.176008
DFRC,Dryden Flight Research Center,34.613714,-118.076790
GRC,Glenn Research Center,41.415891,-81.861774
MSFC,Marshall Space Flight Center,34.646554,-86.674368
ARC,Ames Research Center,37.409574,-122.064292
LaRC,Langley Research Center,37.092123,-76.376230
JSC,Johnson Space Center,29.551508,-95.092256
SSC,Stennis Space Center,30.363692,-89.600036
I modified your example slightly based on the problems you described: http://bl.ocks.org/erikhazzard/6201948
It looks like there are three issues:
Paths draw on top of icon. To fix this, you can change the order of when you add items to the group, or add sub groups to your main g group, ensuring the order that you add the groups matches the order you want things to appear.
The paths between points doesn't zoom when you zoom the map. To fix this, make sure to add everything to the group that you're modifying the clicked() function. In this case, your g group is being zoomed on, so if you add the paths to the g group instead of the svg directly the paths will zoom as well. In the example provided, text does also not zoom in - that's because it's added directly to the SVG and not the g group that is being transformed.
Paths aren't created automatically from the data. To fix this, you can generate an array containing LineString objects from the data. For example,
for(var i=0, len=data.length-1; i<len; i++){
// (note: loop until length - 1 since we're getting the next
// item with i+1)
links.push({
type: "LineString",
coordinates: [
[ data[i].lon, data[i].lat ],
[ data[i+1].lon, data[i+1].lat ]
]
});
}
Then, do the standard data join pattern and pass in the links list to the data. When you pass in path as the d attribute, it will generate a great arc based on the coordinates for each item:
// Standard enter / update
var pathArcs = arcGroup.selectAll(".arc")
.data(links);
//enter
pathArcs.enter()
.append("path").attr({
'class': 'arc'
}).style({
fill: 'none',
});
//update
pathArcs.attr({
//d is the points attribute for this path, we'll draw
// an arc between the points using the arc function
d: path
})
.style({
stroke: '#0000ff',
'stroke-width': '2px'
})
In my example ( http://bl.ocks.org/enoex/6201948 ) I added a transition on the great arc paths to illustrate how the path is drawn based on the order of coordinate pairs passed into the links object.
Hope that helps!