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.
Related
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
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);
});
What if in th X axis I have multiple strings with different length ! how to let those strings to be drawn starting from each first char ? If I use
.attr('dy', '+50') the result wont be bad but not the desired ! because of the different length of each String ...
chart.selectAll("g.cols.axis text")
.attr('dy', '+50')
.attr("transform", function () { var coord = this.getBBox();
var x = coord.x + (coord.width / 2),
y = coord.y + (coord.height / 2);
return "rotate(90 " + x + " " + y + ")" })
.style("fill", "blue");
here is a working example : jsfiddle.net/5mt77e0o/2 Please note that names and values are randomly generated !
Please try below the codes.
Set the text-anchor:middle to text-anchor:unset and increase the svg height.
.attr("text-anchor", "middle");
http://jsfiddle.net/5mt77e0o/3/
It seems that the problem is that when I give the .style("text-anchor","unset"); the transform wont work properly and particularity the x :
chart
.selectAll("g.cols.axis text")
.attr("transform", function () {
var coord = this.getBBox();
var x = coord.x + (coord.width / 2),
y = coord.y + (coord.height / 2);
return "rotate(90 " + x + " " + y + ")"
})
.style("fill", "blue").style("text-anchor","unset");
the right x values must be :
but instead I'am getting those values :
Can it be that setting "text-anchor" to "unset" affects the calcul of x ?
Edit :
In order to fix this I got this trick.
keep the .style("text-anchor", "middle") so that x and y for the rotation can be properly calculated and then at the end change the CSS to "text-anchor : unset" with .style("text-anchor", "unset");
chart
.selectAll("g.cols.axis text")
.style("text-anchor", "middle")
.attr("transform", function () {
var coord = this.getBBox();
var x = coord.x + (coord.width / 2),
y = coord.y + (coord.height / 2);
return "rotate(90 " + x + " " + y + ")"
})
.style("fill", "blue").style("text-anchor", "unset");
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>
This time I am stuck in a circular axis for meter readings. I want to make readings and ticks along the circular path for meter gauge (just like speedometer):
However, I am not getting the exact idea or solution for it. Also, to be specific, I want to do it with D3.js only.
I have created the meter with some references, and tried to pull out some readings in it so far, but I don't feel what I have done is the most appropriate way to do this.
Please guide me through this. Here is my code : -
<!DOCTYPE html>
<meta charset="utf-8">
<title>Speedo-meter</title>
<script src="jquery-1.9.1.min.js"></script>
<style>
svg
{
margin-left: 30px;
margin-top: 30px;
border: 1px solid #ccc;
}
</style>
<body>
<script type="text/javascript" src="http://d3js.org/d3.v3.min.js"></script>
<script>
var data = {
"title": "Meter Gauge",
"value": 50,
"maxValue": 200
};
startAngle = 90, endAngle = 270, innerRad=175 , outerRad=185 ;
var svg = d3.selectAll("body").append("svg")
.attr("width", 500)
.attr("height", 500);
var arc = d3.svg.arc()
.innerRadius(innerRad)
.outerRadius(outerRad)
.startAngle(90 * (Math.PI/180))
.endAngle(270 * (Math.PI/180));
var title = svg.append("text")
.attr("x", 50)
.attr("y", 50)
.style("text-anchor", "start")
.text("Click on the meter line to turn the needle");
var plot = svg.append("g")
.attr("class", "arc")
.attr("transform", "translate(100 , 150 )");
var gauge = plot.append("path")
.attr("d", arc)
.attr("class", "gauge")
.attr("fill", "#ddd")
.attr("stroke", "#000")
.attr("transform", "translate(150,130) rotate(180)")
.on("click", turnNeedle);
var needle = svg.append("g")
.attr("class", "needle")
.attr("transform", "translate( 100 , 110 )")
.append("path")
.attr("class", "tri")
.attr("d", "M" + (300/2 + 3) + " " + (120 + 10) + " L" + 300/2 + " 0 L" + (300/2 - 3) + " " + (120 + 10) + " C" + (300/2 - 3) + " " + (120 + 20) + " " + (300/2 + 3) + " " + (120 + 20) + " " + (300/2 + 3) + " " + (120 + 10) + " Z")
.attr("transform", "rotate(-100, " + 300/2 + "," + (120 + 10) + ")");
function turnNeedle()
{
needle.transition()
.duration(2000)
.attrTween("transform", tween);
//console.log(d3.select(".needle").attr("cx"));
function tween(d, i, a) {
return d3.interpolateString("rotate(-100, 150, 130)", "rotate(100, 150, 130)");
}
}
var Speeds = data.maxValue/20;
var divisions = ((endAngle-startAngle))/Speeds ;
console.log("divisions=>"+divisions);
j=0;
endAngle1 = startAngle+ 20;
startAngle = 72;
for(i=0;i<=10;i++)
{
endAngle = startAngle+ divisions;
newArc = d3.svg.arc()
.innerRadius(innerRad - 10)
.outerRadius(outerRad)
.startAngle((startAngle+=divisions) * (Math.PI/180))
.endAngle(endAngle * (Math.PI/180));
var gauge = plot.append("path")
.attr("d",newArc)
.attr("class", "gauge")
.attr("id", "gauge"+i)
.attr("fill", "#ddd")
.attr("stroke", "#000")
.attr("transform", "translate(150,130) rotate(180)")
.on("click", turnNeedle);
var text = plot.append("text")
.style("font-size",14)
.style("fill","#000")
.style("text-anchor", "start")
.attr("x", -165 + 6)
.attr("dy",".35em")
.append("textPath")
.attr("xlink:href","#gauge"+i)
.attr("startOffset",5)
.text(Speeds*i);
}
</script>