d3 force graph with divs and d3 zoom - javascript

Hello Stackoverflowcommunity!
I am having trouble getting d3 zoom working in my d3force-directed graph. I could achieve that it is zooming and panning but doing so breaks the alignment between the nodes and the links and i don't know how i could fix it.... I created a fiddle showing what i mean. https://jsfiddle.net/5jgrf5h8/5/
This is the code where i perform the zoom:
svg.call(d3.zoom()
//.scaleExtent([1 / 2, 4])
.on("zoom", zoomed))
.on("dblclick.zoom", null);
//.on("wheel.zoom", null);
function zoomed() {
//link.attr("transform", d3.event.transform);
link.attr("transform", "translate(" + d3.event.transform.x + "," + d3.event.transform.y + ") scale(" + d3.event.transform.k + ")");
node.style("transform", "translate(" + d3.event.transform.x + "px," + d3.event.transform.y + "px) scale(" + d3.event.transform.k + ")");
simulation.alphaTarget(0.001).restart();
simulation.alphaTarget(0);
}

When you apply the CSS scale on your div nodes, it's with an origin of 0,0 not with an origin of the position they are current in.
Try this:
function zoomed() {
// apply CSS scale with respect to current position
node.each(function(d){
var self = d3.select(this),
x = self.style("left"),
y = self.style("top");
self.style("transform-origin", "-" + x + " -" + y);
})
link.attr("transform", "translate(" + d3.event.transform.x + "," + d3.event.transform.y + ") scale(" + d3.event.transform.k + ")");
node.style("transform", "translate(" + d3.event.transform.x + "px," + d3.event.transform.y + "px) scale(" + d3.event.transform.k + ")");
simulation.alphaTarget(0.001).restart();
simulation.alphaTarget(0);
}
Updated fiddle here

Related

Getting the id of an svg element in d3.js

I am trying to save an svg element as png as instructed here. It says to use saveSvgAsPng(document.getElementById("diagram"), "diagram.png"); where "diagram" is the id of the svg element. Now I am having trouble finding out the id.
function guid() {
function _p8(s) {
var p = (Math.random().toString(16)+"000000000").substr(2,8);
return s ? "-" + p.substr(0,4) + "-" + p.substr(4,4) : p ;
}
return _p8() + _p8(true) + _p8(true) + _p8();
}
var svg = d3.select("#hiddenblock").append("svg")
.attr("svgid",guid())
.attr("width", width + margin.left + margin.right)
.attr("height", height+300+ margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
Above I tried to give var svg a id name svgid then implemented it at the position of diagram but in the browser console in says Uncaught Error: an HTMLElement or SVGElement is required; got null. I tried to use hiddenblock but it didn't work.
A sample of my browser's output is here. It seems here the element got an id but saveSvgAsPng() function doesn't seem to recognize it.

Links in dendogram - hierarchy layout D3.JS

So, I have been trying to visualize the popular tree of life data using dendogram layout of d3.js exactly similar to that of http://bl.ocks.org/mbostock/4063570.
I have problem with the links in the diagram as you can see in screenshot. I have also posted the code don't know where exactly I am going wrong.
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height"),
g = svg.append("g").attr("transform", "translate(40,0)");
var tree = d3.cluster()
.size([height, width - 160]);
d3.text("test.txt", function(error, data) {
if (error) throw error;
var dat = parseNewick(data);
console.log(dat);
var root = d3.hierarchy(dat)
.sort(function(a,b){return b.height - a.height });
tree(root);
var link = g.selectAll(".link")
.data(root.descendants().slice(1))
.enter().append("path")
.attr("class", "link")
.attr("d", function(d) {
return "M" + d.y + "," + d.x
+ "C" + (d.parent.y + 100) + "," + d.x
+ " " + (d.parent.y + 100) + "," + d.parent.x
+ " " + d.parent.y + "," + d.parent.x;
});
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);
});

How to change the curve on an SVG element

I have this function to create a curve between two points
var amountOfCurve = (d.noOfSameConnections+1); //between 0 and 10
var dy = d.target.x - d.source.x,
dx = d.target.y - d.source.y,
dr = Math.sqrt(dx * dx + dy * dy) *(amountOfCurve);
return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + (d.target.x) + "," + (d.target.y);
Some links have both the same source and target. I solve this in the noOfSameConnections. But what I want is instead of different sized curve, as there can only be a maximum of two links between two nodes, I want the link to curve the other way. So I would do something like this :
if(d.noOfSameConnections === 1){
//curve one way
} else {
//curve the other
}
But I can't seem to work out how to switch the curve around :(
The direction of the arc can be controlled with the sweep flag.
<!DOCTYPE html>
<html>
<head>
<script data-require="d3#3.5.3" data-semver="3.5.3" src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.3/d3.js"></script>
</head>
<body>
<script>
var svg = d3.select('body')
.append('svg')
.attr('width', 500)
.attr('height', 500);
var d = {
source: {
x: 10,
y: 10
},
target: {
x: 490,
y: 490
}
};
var dy = d.target.x - d.source.x,
dx = d.target.y - d.source.y,
dr = Math.sqrt(dx * dx + dy * dy) * 0.8;
var largeSweep = 0;
var sweep = 1;
svg.append("path")
.attr("d", "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 " + largeSweep + "," + sweep + " " + d.target.x + "," + d.target.y)
.style("fill", "none")
.style("stroke", "steelblue")
.style("stoke-width", 2);
largeSweep = 0;
sweep = 0;
svg.append("path")
.attr("d", "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 " + largeSweep + "," + sweep + " " + d.target.x + "," + d.target.y)
.style("fill", "none")
.style("stroke", "orange")
.style("stoke-width", 2);
</script>
</body>
</html>

d3 transform-origin with two needles in a clock

This is my first project using D3, and I'm using WebAudioAPI to get microphone input to represent where the needles point. I got them to move, however my minute needle is going beserk and not rotating on a specific point or staying still.
var width = 960,
height = 500,
τ = 2 * Math.PI;
var arc = d3.svg.arc()
.innerRadius(180)
.outerRadius(240)
.startAngle(0);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var background = svg.append("path")
.datum({endAngle: 100})
.style("fill", "#ddd")
.attr("d", arc);
var foreground = svg.append("path")
.datum({endAngle: .1 * τ})
.style("fill", "orange")
.attr("d", arc);
var gaugeGroup = svg.append("g")
.datum({endAngle: .1 * τ})
.attr("class", "hour hands")
.attr("transform", "translate( 0 , 0 )");
var hour = gaugeGroup.append("path")
.attr("class", "tri")
.attr("d", "M" + (600/2 + 12) + " " + (240 + 10) + " L" + 600/2 + " 0 L" + (600/2 - 3) + " " + (240 + 10) + " C" + (600/2 - 3) + " " + (240 + 20) + " " + (600/2 + 3) + " " + (240 + 20) + " " + (600/2 + 12) + " " + (240 + 10) + " Z")
// .attr("transform", "rotate(-60, " + -70 + "," + (389) + ")");
.attr("transform", "translate(-300,-250) rotate(0,0,0)");
var minute = gaugeGroup.append("path")
.attr("class", "tri")
.attr("d", "M" + (300/2 + 3) + " " + (170 + 10) + " L" + 300/2 + " 0 L" + (300/2 - 3) + " " + (170 + 10) + " C" + (300/2 - 3) + " " + (170 + 20) + " " + (300/2 + 3) + " " + (170 + 20) + " " + (300/2 + 3) + " " + (170 + 10) + " Z")
.attr("transform", "translate(-150,-188) rotate(0,0,0)");
// Add the background arc, from 0 to 100% (τ).
function setValues(note, detune){
foreground.transition()
.duration(190)
.call(arcTween, note / 10);
gaugeGroup
.transition()
.duration(200)
.attr("transform", "rotate("+note *τ +",0,0)");
minute
.transition()
.duration(150)
.attr("transform","rotate("+detune * τ +",200,6)");
}
Nearly impossible to debug this without access to the working code (or a fiddle). But from just glancing at it, a couple of things stand out:
You're applying rotation to the entire gaugeGroup, which contains both the hours and minutes hands, and then you apply local rotation to the minute group. This might be appropriate but only if detune is a relative value in a range of 0 +/- n (i.e. when detune == 0 the minutes and hours hands would be expected to overlap). If detune is expressed in absolute terms, it would mean that your code is transforming the minute hand twice — once through gaugeGroup and again through minute.
It would be easier for you to understand what's happening and debug it if you set things up such that you can simply rotate minute around its origin 0,0 instead of having to specify a different rotation origin in rotate("+detune * τ +",200,6). For that, you would need to modify the minute path's "d" attribute (as in minute.attr("d", ...)) such that its pivot point is at 0,0. Before you do that, for the purpose of debugging, you can simplify the minutes path to be just a line from the origin out — somthing like M0 0 L150 0 (see how it starts at 0,0) — get the rotation working properly without offsetting the rotation origin and then bring back the more complex path.

D3 Zoom scales Map, not points

I am zooming in on a map upon click but the latitude longitude points do not scale. They are rendered as circles and I would like them to move with the map. I am following the D3 template here: http://bl.ocks.org/mbostock/2206590
var map_width = 960,
map_height = 500,
jsonRoot = '/static/d3/json/',
centered;
var projection = d3.geo.albersUsa()
.scale(1070)
.translate([map_width / 2, map_height / 2]); // default projection type for d3.geo.path
var urls = {
counties: jsonRoot + "us-counties.json",
states: jsonRoot + "us-states.json"
}
, margin = { top: 0, right: 0, bottom: 0, left: 0 }
, width = 960 - margin.right - margin.left
, height = 500
, path = d3.geo.path().projection(projection)
, map;
var q = queue()
.defer(d3.json, jsonRoot + "us-counties.json")
.defer(d3.json, jsonRoot + "us-states.json")
.await(ready);
function ready(error, countylines, statelines) {
window.error = error;
window.countylines = countylines;
window.statelines = statelines;
if (error){
throw error;
}
var stateIds = {};
statelines.features.forEach(function(d) {
stateIds[d.id] = d.properties.name;
});
countylines.features.forEach(function(d) {
d.properties.state = stateIds[d.id.slice(0,2)];
})
// remove the loading text
d3.select('.loading').remove();
map = d3.select('#map').append('svg')
.style('width', width)
.style('height', height);
counties = map.append('g')
.attr('class', 'counties')
.selectAll('path')
.data(countylines.features)
.enter().append('path')
.attr('d', path);
counties.on('mouseover', showCaption)
.on('mousemove', showCaption)
.on('mouseout', function() {
caption.html(starter);
})
.on('click', clicked);
states = map.append('g')
.attr('class', 'states')
.selectAll('path')
.data(statelines.features)
.enter().append('path')
.attr('d', path);
// Captions
var caption = d3.select('#caption')
, starter = caption.html();
function showCaption(d, i) {
var name = [d.properties.name, d.properties.state].join(', ');
caption.html(name);
}
var systemSuccess = function(result){
console.log(result);
}
var site = map.append("circle")
.attr("r",5)
.classed("system", true)
.attr("latitude",37.77521)
.attr("longitude",-122.42854)
.attr("transform", function() {
return "translate(" + projection([-122.42854,37.77521]) + ")";
});
});
})
};
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;
}
counties.selectAll("path")
.classed("active", centered && function(d) { return d === centered; });
counties.transition()
.duration(750)
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")scale(" + k + ")translate(" + -x + "," + -y + ")")
.style("stroke-width", 1.5 / k + "px");
states.transition()
.duration(750)
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")scale(" + k + ")translate(" + -x + "," + -y + ")")
.style("stroke-width", 1.5 / k + "px");
map.selectAll(".system")
.attr("transform", function(d) { return "translate(" + projection([-122.42854, 37.77521 ]) + ")" });
}
});
The map scales appropriately. But not the points.
All help is appreciated!
As Lars suggested, you could do the following.
//Same projection and transformation as applicable to the path elements.
d3.selectAll("circle")
.transition()
.duration(750)
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")scale(" + k + ")translate(" + -x + "," + -y + ")")
I am not sure if the above code would work correctly...although I have used a similar premise through the "zoom" d3 behavior.
If you want your points to retain their size, but be at the right position; you could try semantic zooming
OR
you could keep the resize the circle's radius based on the scale like this:
d3.selectAll("circle")
.attr("r", 5/k);

Categories

Resources