Positioning nodes within locations in D3 - d3.js

How can I define locations within my SVG elements that contain nodes?
I'm trying to create an abstract map where nodes are contained within locations using D3. The nodes will then be linked to other nodes (sometimes many nodes in the same location +/or other locations).
So sample data may look something like this:
{"nodes":[
{"id": "a", "location": "1"},
{"id": "b", "location": "1"},
{"id": "c", "location": "2"},
{"id": "d", "location": "2"},
{"id": "e", "location": "3"},
{"id": "f", "location": "3"},
{"id": "g", "location": "4"},
{"id": "h", "location": "4"}]
}
I want to create 4 rectangles/bubbles, with 2 nodes (circles) in each.
I'm new to D3 and guess I'm struggling to go from simple datasets to JSON objects. So sorry if I'm missing the obvious.

If you're creating a force directed chart, you can use forceX and forceY to arrange the nodes in the screen. According to the API:
The x- and y-positioning forces push nodes towards a desired position along the given dimension with a configurable strength. The strength of the force is proportional to the one-dimensional distance between the node’s position and the target position.
In this demo, I'm taking your data array and positioning in the x coordinates according to location. First, I set an scale:
var xScale = d3.scalePoint()
.domain([1, 2, 3, 4])
.range([100, width - 100]);
And use this scale in forceX:
var force = d3.forceSimulation(data)
.force('x', d3.forceX((d) => xScale(d.location)).strength(2))
Here is a demo:
var data = [{
"id": "a",
"location": "1"
}, {
"id": "b",
"location": "1"
}, {
"id": "c",
"location": "2"
}, {
"id": "d",
"location": "2"
}, {
"id": "e",
"location": "3"
}, {
"id": "f",
"location": "3"
}, {
"id": "g",
"location": "4"
}, {
"id": "h",
"location": "4"
}];
var width = 500,
height = 200;
var color = d3.scaleOrdinal(d3.schemeCategory10);
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
var xScale = d3.scalePoint()
.domain([1, 2, 3, 4])
.range([100, width - 100]);
var circles = svg.selectAll(".bigCircles")
.data(xScale.domain())
.enter()
.append("circle")
.attr("cx", d=>xScale(d))
.attr("cy", height/2)
.attr("fill", d=>color(d))
.attr("r", 40)
.attr("opacity", 0.2);
var node = svg.selectAll(".circles")
.data(data)
.enter().append("circle")
.attr("r", 10)
.attr("fill", (d) => color(d.location));
var force = d3.forceSimulation(data)
.force('x', d3.forceX((d) => xScale(d.location)).strength(2))
.force('center', d3.forceCenter(width / 2, height / 2))
.force("collide", d3.forceCollide(12));
force.nodes(data)
.on('tick', function() {
node
.attr('transform', (d) => {
return 'translate(' + (d.x) + ',' + (d.y) + ')';
});
});
<script src="https://d3js.org/d3.v4.min.js"></script>

Related

How to plot lat/long pairs over map in D3.js

I'm having a problem plotting points from a geoJSON file over a map using D3.js. The map is rendering fine, but the points are not showing up. I'm not receiving any error messages at this time.
I'm following along with this tutorial but using my own geoJSON file to plot the data.
This is what I have:
var width = 960,
height = 500;
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
var g = svg.append("g");
var projection = d3.geoAlbers()
.scale(1000)
.translate([width / 2, height / 2]);
var path = d3.geoPath()
.projection(projection);
d3.queue()
.defer(d3.json, 'states.json') // Load US States
.defer(d3.json, 'trump_geoJson.json') // Load tweet lat/long data
.await(makeMyMap); // Run 'ready' when JSONs are loaded
function makeMyMap(error,states,tweets) {
svg.append('path')
.datum(topojson.feature(states, states.objects.usStates))
.attr('d', path)
.attr('class', 'states');
svg.selectAll('.tweets')
.data(tweets.features)
.enter()
.append('path')
.attr('d',path)
.attr('class', 'tweets');
}
I'm expecting about 600 points to be plotted, but getting none.
The json file trump_geoJson looks like:
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"id": 0,
"properties": {
"primary_geo": "Utah, USA",
"tag": "#Bernie",
"text": "text",
"user_id": "id"
},
"geometry": {
"type": "Point",
"coordinates": [
39.32373809814453,
-111.67823791503906
]
}
},
{
"type": "Feature",
"id": 1,
"properties": {
"primary_geo": "New York, NY",
"tag": "#Bernie",
"text": "text",
"user_id": "id"
},
"geometry": {
"type": "Point",
"coordinates": [
40.71455001831055,
-74.00714111328125
]
}
},... ]
Your geojson uses the wrong coordinate convention. You have:
"coordinates": [ latitude, longitude ]
But, you must use:
"coordinates": [ longitude, latitude ]
From the spec:
Point coordinates are in x, y order (easting, northing for projected
coordinates, longitude, and latitude for geographic coordinates)
It is funny that the spec considers eastings and northings for projected coordinates given the spec also states geojson must use unprojected (lat/long) coordinates using the WGS84 datum
Here's a demo of the first two items in your geojson feature collection:
var data = {
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [-111.6782379150,39.32373809814]
}
},
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [-74.00714111328,40.71455001831]
}
}]};
var width = 500,
height = 300;
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
var projection = d3.geoAlbers()
.scale(600)
.translate([width / 2, height / 2]);
var path = d3.geoPath()
.projection(projection);
d3.json("https://unpkg.com/world-atlas#1/world/110m.json", function(error, world) {
if (error) throw error;
svg.append("path")
.attr("d", path(topojson.mesh(world)))
.attr("fill","none")
.attr("stroke","black")
.attr("stroke-width",1);
svg.selectAll('.tweets')
.data(data.features)
.enter()
.append('path')
.attr('d',path)
.attr('class', 'tweets');
});
.tweets {
fill: red;
opacity: 0.7;
}
<script src="http://d3js.org/d3.v4.min.js" charset="utf-8"></script>
<script src="http://d3js.org/topojson.v1.min.js"></script>
<script src="https://d3js.org/d3-queue.v2.min.js"></script>

How to reposition nodes and translate them to another foci?

im new to D3 and love it so far, I was playing around with multi foci force layout and got a part of what I want to do working fine, What I would like to know if its possible to reposition some of the node based on a time scale.
What I meant by that is currently what you see is 6(orange), 5(blue) and 3 (green) nodes in the fiddle I created, So I want them to position themselves by translating towards each other i.e on day two data could be 5(orange), 6(blue) and 4(green), that means one orange node moves to green cluster.
var data = [
{"id": 0, "name": "x", "r":5 },
{"id": 0, "name": "x", "r":5 },
{"id": 0, "name": "x", "r":5 },
{"id": 0, "name": "x", "r":5 },
{"id": 0, "name": "x", "r":5 },
{"id": 1, "name": "y", "r":5 },
{"id": 1, "name": "y", "r":5 },
{"id": 1, "name": "y", "r":5 },
{"id": 1, "name": "y", "r":5 },
{"id": 1, "name": "y", "r":5 },
{"id": 1, "name": "y", "r":5 },
{"id": 2, "name": "z", "r":5 },
{"id": 2, "name": "z", "r":5 },
{"id": 2, "name": "z", "r":5 },
];
var width = window.innerWidth,
height = 410;
var fill = d3.scale.category10();
var nodes = [], labels = [],
foci = [{x: 0, y: 10}, {x: 100, y: 210}, {x: 600, y: 210}];
var svg = d3.select("body").append("svg")
.attr("width", "100%")
.attr("height", height)
//.attr("domflag", '');
var force = d3.layout.force()
.nodes(nodes)
.links([])
.charge(-10)
//.chargeDistance(100)
.gravity(0.1)
.friction(0.8)
.size([width, height])
.on("tick", tick);
//var node = svg.selectAll("circle");
var node = svg.selectAll("g");
var counter = 0;
function tick(e) {
var k = .1 * e.alpha;
// Push nodes toward their designated focus.
nodes.forEach(function(o, i) {
o.y += (foci[o.id].y - o.y) * k;
o.x += (foci[o.id].x - o.x) * k;
});
node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
}
var timer = setInterval(function(){
if (nodes.length > data.length-1) { clearInterval(timer); return;}
var item = data[counter];
nodes.push({id: item.id, r: item.r, name: item.name});
force.start();
node = node.data(nodes);
var n = node.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
.style('cursor', 'pointer')
.on('mousedown', function() {
var sel = d3.select(this);
//``sel.moveToFront();
})
.call(force.drag);
n.append("circle")
.attr("r", function(d) { return d.r; })
.style("fill", function(d) { return fill(d.id); })
n.append("text")
.text(function(d){
return d.name;
})
.style("font-size", function(d) {
return Math.min(2 * d.r, (2 * d.r - 8) / this.getComputedTextLength() * 16) + "px";
})
.attr("dy", ".35em")
counter++;
}, 100);
node {
stroke-width: 1.5px;
}
.node:hover circle {
fill: grey !important;
}
text {
font: 18px 'Open Sans', sans-serif;;
text-anchor: middle;
pointer-events: none;
fill: white;
}
circle {
fill: #ccc;
stroke: white ;
stroke-width: 2px;
}
<script src="https://d3js.org/d3.v3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.0.0/d3.min.js"></script>
<h2>D3.js Multi-Foci Force layout </h2>
I have created a fiddle here.
What I want to know is how to translate nodes to another cluster and format data for looping through time.
I'm using D3 V3 for development.

d3js v4 hierarchical edge bundling

Im trying to make a hierarchical edge bundling like this using d3 v4. I use d3.curveBundle to make the line curved. But in my project, the line doesn't seem to be curved.
What I get is a straight line like this:
This is my code:
var data = {
"name": "Eve",
"children": [
{
"name": "Cain"
},
{
"name": "Seth",
"children": [
{
"name": "Enos"
},
{
"name": "Noam"
}
]
},
{
"name": "Abel"
},
{
"name": "Awan",
"children": [
{
"name": "Enoch"
}
]
},
{
"name": "Azura"
}
]
};
var cluster = d3.cluster()
.size([360, 120]);
var nodes = cluster(d3.hierarchy(data));
var links = nodes.links();
var svg = d3.select("div.radial-network")
.append("svg")
.attr("width", "100%")
.attr("height", "300")
.attr("transform", "translate(120,120)");
const line = d3.radialLine()
.curve(d3.curveBundle.beta(0.95))
.angle(function(d,i){ return d.x*Math.PI/180;})
.radius(function(d,i) {return d.y;});
const edges = svg.selectAll('.link').data(links);
edges.enter().append('path')
.attr('class', 'link')
.attr('stroke', 'red')
.attr('d', function(d, i) {return line(d.source.path(d.target));});

Trying to add a second parent node to a child node using d3 tree chart library

I'm trying to create a D3 Tree, and able to get the task done to some extent, but in my situation, there can be more than 1 parent node to a single node.
But as I've learned, I still need to start off with one root node to build the tree, and I did that and was able to build the tree, and now want to add a second node to a tree node.
var data = [{
"children": [{
"children": [{
"children": [{
"children": [
],
"name": "Hierarchy Child 5",
"parent": "Hierarchy Sibling",
"relation": "Parent Of",
"rid": "a059000000VBecUAAT"
}],
"name": "Hierarchy Sibling",
"parent": "Hierarchy Parent",
"relation": "Parent Of",
"rid": "a059000000VBKeYAAX"
}, {
"children": [{
"children": [
],
"name": "Hierarchy Child 1",
"parent": "ACME",
"relation": "Parent Of",
"rid": "a059000000VBKf7AAH"
}, {
"children": [
],
"name": "Hierarchy Child 2",
"parent": "ACME",
"relation": "Parent Of",
"rid": "a059000000VBKfCAAX"
}, {
"children": [
],
"name": "Hierarchy Child 3",
"parent": "ACME",
"relation": "Parent Of",
"rid": "a059000000VBKfHAAX"
}, {
"children": [
],
"name": "Hierarchy Child 4",
"parent": "ACME",
"relation": "Parent Of",
"rid": "a059000000VBKfMAAX"
}],
"name": "ACME",
"parent": "Hierarchy Parent",
"relation": "Parent Of",
"rid": "a059000000VBKeJAAX"
}],
"name": "Hierarchy Parent",
"parent": "This is Test Second Parent Node",
"relation": "Parent Of",
"rid": "a059000000VBKeOAAX"
}, {
"children": [
],
"name": "Hierarchy Uncle",
"parent": "Hierarchy Grandparent",
"relation": "Parent Of",
"rid": "a059000000VBKedAAH"
}, {
"children": [
],
"name": "Hierarchy Great Uncle",
"parent": "Hierarchy Grandparent",
"relation": "Parent Of",
"rid": "a059000000VBKenAAH"
}],
"name": "Hierarchy Grandparent",
"parent": "null",
"relation": "Parent Of",
"rid": "a059000000VBKeTAAX"
}];
// *********** Convert flat data into a nice tree ***************
// create a name: node map
var dataMap = data.reduce(function(map, node) {
map[node.name] = node;
return map;
}, {});
// create the tree array
var treeData = [];
data.forEach(function(node) {
// add to parent
var parent = dataMap[node.parent];
if (parent) {
// create child array if it doesn't exist
(parent.children || (parent.children = []))
// add node to child array
.push(node);
} else {
// parent is null or missing
treeData.push(node);
}
});
// ************** Generate the tree diagram *****************
var margin = {
top: 20,
right: 120,
bottom: 20,
left: 120
},
width = 960 - margin.right - margin.left,
height = 500 - margin.top - margin.bottom;
var i = 0;
var tree = d3.layout.tree()
.size([height, width]);
var diagonal = d3.svg.diagonal()
.projection(function(d) {
return [d.y, d.x];
});
var svg = d3.select("div#chart-container").append("svg")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
root = treeData[0];
update(root);
function update(source) {
// Compute the new tree layout.
var nodes = tree.nodes(root).reverse(),
links = tree.links(nodes);
// Normalize for fixed-depth.
nodes.forEach(function(d) {
d.y = d.depth * 180;
});
// Declare the nodes…
var node = svg.selectAll("g.node")
.data(nodes, function(d) {
return d.id || (d.id = ++i);
});
// Enter the nodes.
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) {
return "translate(" + d.y + "," + d.x + ")";
});
nodeEnter.append("circle")
.attr("r", 10)
.style("fill", "#fff");
nodeEnter.append("text")
.attr("x", function(d) {
return d.children || d._children ? -13 : 13;
})
.attr("dy", ".35em")
.attr("text-anchor", function(d) {
return d.children || d._children ? "end" : "start";
})
.text(function(d) {
return d.name;
})
.style("fill-opacity", 1);
// Declare the links…
var link = svg.selectAll("path.link")
.data(links, function(d) {
return d.target.id;
});
// Enter the links.
link.enter().insert("path", "g")
.attr("class", "link")
.attr("d", diagonal);
}
.node circle {
fill: #fff;
stroke: steelblue;
stroke-width: 3px;
}
.node text {
font: 12px sans-serif;
}
.link {
fill: none;
stroke: #ccc;
stroke-width: 2px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="chart-container">
</div>
As we can see, the code runs fine and tree is generating but my JSON data array has a second parent on node with name as Hierarchy Parent has a parent specified as This is Test Second Parent Node but this node does not appear on tree.
This Image is what I'm trying to generate as output:
Take a look at this example
http://bl.ocks.org/robschmuecker/6afc2ecb05b191359862
If that doesn't help, you might want to take a look at the force directed graph
https://bl.ocks.org/mbostock/4062045
Also take a look at cytoscape, very similar to d3 but it deals with networks (graph data structure) as well as trees http://js.cytoscape.org/demos/aedff159b0df05ccfaa5/

D3: Iterating and accessing datasets?

Context
I am making a scatterplot. For each location, in my dataset, I want to plot a circle on a map, using 'lat' and 'long' values. There will be two circles plotted, one on top of the other. The radiuses of the two circles will be defined by 'total' and 'passed' values. I've made my map; I intend my plotted data to look something like this:
I can structure my data any way I want to. I have have opted for json, below.
[
{
"year": 2006,
"inspections": [
{
"location": "Cheshire",
"total": 341,
"passed": 26,
"long": 5,
"lat": 20
},
{
"location": "County Durham",
"total": 102,
"passed": 1,
"long": 480,
"lat": 90
}
]
},
{
"year": 2007,
...
]
Eventually, I'd like to transition my circles (having them grow and shrink) through the years, but for now I'm starting simple and just trying to plot a single year of data on my map.
Here is my first attempt at the code to plot my circles:
d3.json("dataset", function(error, data) {
svg.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("cx", function(d) {
// return someting;
})
.attr("cy", function(d) {
// return someting;
})
.attr("r", 5);
});
Here's the console output of d:
Question
I don't understand too well how .data() and .enter() work? How do I access my values, 'location', 'total', 'passed', 'long' and 'lat', in turn so I can plot all my circles for the year 2006. The examples available tend to use very simple arrays. How do I get my values from my more complex structure of nested arrays and objects?
If your json data is like this
var json = [{
"year": 2006,
"inspections": [{
"location": "Cheshire",
"total": 341,
"passed": 26,
"long": 50,
"lat": 0
}, {
"location": "County Durham",
"total": 102,
"passed": 10,
"long": 52,
"lat": 0
}]
}, {
"year": 2007,
"inspections": [{
"location": "Cheshire",
"total": 341,
"passed": 26,
"long": 51,
"lat": 1
}, {
"location": "County Durham",
"total": 102,
"passed": 10,
"long": 51,
"lat": -1.8
}]
}
];
First make red circle for the first array of inspections like this:
//circle1
svg.selectAll(".red")//get all the circles with class red
.data(json).enter()//iterate over the json
.append("circle")
.attr("cx", function (d) { var circle1 = d.inspections[0]; return projection([circle1.lat,circle1.long])[0]; })//get x point based on projection set the x point which is 0 index
.attr("cy", function (d) { var circle1 = d.inspections[0]; return projection([circle1.lat,circle1.long])[1]; })//get x point based on projection set the x point which is 1 index
.attr("r", "8")
.attr("class", "red")//to get the selection for red circles
.attr("fill", "red")
Make blue circle for the second array of inspections like this:
//circle 2
svg.selectAll(".blue")
.data(json).enter()
.append("circle")
.attr("cx", function (d) { var circle1 = d.inspections[1]; return projection([circle1.lat,circle1.long])[0]; })
.attr("cy", function (d) { var circle1 = d.inspections[1]; return projection([circle1.lat,circle1.long])[1]; })
.attr("r", "8")
.attr("class", "blue")
.attr("fill", "blue")
Working code here
Hope this helps!
Easier to just have a flat array of objects if you can:
[
{
"year": 2006,
"location": "Cheshire",
"total": 341,
"passed": 26,
"long": 5,
"lat": 20
},
{
"year": 2006,
"location": "County Durham",
"total": 102,
"passed": 1,
"long": 480,
"lat": 90
}
]
Then d.lat etc will return what you expect. Otherwise your code looks fine on first glance. Just do your svg.selectAll("circle").data(data).enter().append("circle") twice--once for each set of circles.

Resources