How to change the curve on an SVG element - javascript

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>

Related

d3 Network with Multiple Links in the Same Direction

I am attempting to alter the Mobile Patent Suits example to allow for multiple links in one direction.
I have data (yes, I know Jim isn't actually Pam's boss):
source target relationship
Michael Scott Jan Levenson pro
Jan Levenson Michael Scott personal
Jim Halpert Pam Beasley pro
Jim Halpert Pam Beasley personal
The multi-path functionality of the Mobil Patents Suit example allows the first two rows to be presented correctly (two arcs). However, only one blended arc is presented for the last two rows.
Question: How do I allow links with the same directionality to be shown as multiple arcs rather than a single arc?
Here is my arc code (ripped straight from the Mobile Patents Example):
function tick() {
path.attr("d", linkArc);
circle.attr("transform", transform);
text.attr("transform", transform);
}
function linkArc(d) {
var dx = d.target.x - d.source.x,
dy = d.target.y - d.source.y,
dr = Math.sqrt(dx * dx + dy * dy);
return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y;
}
function transform(d) {
return "translate(" + d.x + "," + d.y + ")";
}
Any help at all would be greatly appreciated. Thank you!
There are probable a few potential approaches for this, one comes to mind rather quickly: use a different path generator for each type relationship between the nodes. You'll have to have a property indicating the nature of the relationship (which you have in your question), and use that to set the path alignment.
In the snippet below I check to see what relationship is being drawn, and reduce the radius of the arc in a personal relationship by 50% as compared to the professional relationship arc radius. The relevant part is:
function linkArc(d) {
var dx = d.target.x - d.source.x,
dy = d.target.y - d.source.y,
dr = Math.sqrt(dx * dx + dy * dy);
if(d.relationship == "pro") {
return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y;
}
else {
return "M" + d.source.x + "," + d.source.y + "A" + (dr * 0.3) + "," + (dr * 0.3) + " 0 0,1 " + d.target.x + "," + d.target.y;
}
}
Here's the whole thing in practice:
var links = [
{ source: "Michael Scott",
target:"Jan Levenson",
relationship: "pro"
},
{ source:"Jan Levenson",
target:"Michael Scott",
relationship: "Personal"
},
{ source: "Jim Halpert",
target: "Pam Beasley",
relationship: "pro"
},
{
source: "Jim Halpert",
target: "Pam Beasley",
relationship: "Personal"
}
]
var nodes = {};
// Compute the distinct nodes from the links.
links.forEach(function(link) {
link.source = nodes[link.source] || (nodes[link.source] = {name: link.source});
link.target = nodes[link.target] || (nodes[link.target] = {name: link.target});
});
var width = 960,
height = 500;
var force = d3.layout.force()
.nodes(d3.values(nodes))
.links(links)
.size([width, height])
.linkDistance(60)
.charge(-300)
.on("tick", tick)
.start();
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
// Per-type markers, as they don't inherit styles.
svg.append("defs").selectAll("marker")
.data(["suit", "licensing", "resolved"])
.enter().append("marker")
.attr("id", function(d) { return d; })
.attr("viewBox", "0 -5 10 10")
.attr("refX", 15)
.attr("refY", -1.5)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("path")
.attr("d", "M0,-5L10,0L0,5");
var path = svg.append("g").selectAll("path")
.data(force.links())
.enter().append("path")
.attr("class", function(d) { return "link " + d.type; })
.attr("marker-end", function(d) { return "url(#" + d.type + ")"; });
var circle = svg.append("g").selectAll("circle")
.data(force.nodes())
.enter().append("circle")
.attr("r", 6)
.call(force.drag);
var text = svg.append("g").selectAll("text")
.data(force.nodes())
.enter().append("text")
.attr("x", 8)
.attr("y", ".31em")
.text(function(d) { return d.name; });
// Use elliptical arc path segments to doubly-encode directionality.
function tick() {
path.attr("d", linkArc);
circle.attr("transform", transform);
text.attr("transform", transform);
}
function linkArc(d) {
var dx = d.target.x - d.source.x,
dy = d.target.y - d.source.y,
dr = Math.sqrt(dx * dx + dy * dy);
if(d.relationship == "pro") {
return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y;
}
else {
return "M" + d.source.x + "," + d.source.y + "A" + (dr * 0.3) + "," + (dr * 0.3) + " 0 0,1 " + d.target.x + "," + d.target.y;
}
}
function transform(d) {
return "translate(" + d.x + "," + d.y + ")";
}
.link {
fill: none;
stroke: #666;
stroke-width: 1.5px;
}
#licensing {
fill: green;
}
.link.licensing {
stroke: green;
}
.link.resolved {
stroke-dasharray: 0,2 1;
}
circle {
fill: #ccc;
stroke: #333;
stroke-width: 1.5px;
}
text {
font: 10px sans-serif;
pointer-events: none;
text-shadow: 0 1px 0 #fff, 1px 0 0 #fff, 0 -1px 0 #fff, -1px 0 0 #fff;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

D3 circular x-axis line graph

Code & result is here: https://jsfiddle.net/nohjose/fdarxu7j/3/#&togetherjs=HpJOmlQkpI
I have coded a d3 (or at least d3 like) radial line graph to show solar panel generation rates over the last four years. I want to have a single line per year (different colour per year) outside the base circle and with a maximum set by a control (which varies the value of the variable 'amplitude'). I have data for every 10mins providing the panels didn't generate 0 Watts but for this graph plan to average the data for each day. I have a php file that returns JSON data in this form
["2014-01-01 09:00:00","0.018"]["2014-01-01 09:00:00","0.018"]["2014-01-01 09:00:00","0.018"]["2014-01-01 09:00:00","0.018"]...
I don't see how I'd now plot the data on my graph?
I know this may not be specific enough for StackOverflow but I'm stuck at the point before where I have a specific technical issue and I'm not sure where else to turn. Kindness appreciated :o)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Solar Generation Visualisation</title>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<link href='http://fonts.googleapis.com/css?family=Lato&subset=latin,latin-ext' rel='stylesheet' type='text/css'>
<style>
.xAxis {
fill: yellow;
stroke: orange;
stroke-width: 3px;
}
.xBigTick {
fill: none;
stroke: red;
stroke-width: 1px;
}
.xSmallTick {
fill: none;
stroke: #303030;
stroke-width: 0.25px;
}
.axisText {
font-family: Lato;
font-weight: 300;
font-size: 8pt;
}
.button {
fill: silver;
stroke: gray;
stroke-width: 3px;
}
</style>
</head>
<body>
<script type="text/javascript">
// Get the data
var data;
d3.json("./test.php", function(error, json) {
if (error) return console.warn(error);
data = json;
data.forEach(function(d) {
d.Timestamp = parseDate(d.Timestamp);
d.Power = +d.Power;
});
// Scale the range of the data
x.domain(d3.extent(data, function(d) { return d.Timestamp; }));
y.domain([0, d3.max(data, function(d) { return d.Power; })]);
});
</script>
<script type="text/javascript">
var canvasWidth= 1000;
var canvasHeight= 500;
var originx = canvasWidth/2;
var originy = canvasHeight/2;
var pi = Math.PI;
var DRratio = pi/180;
var degPerDay = 360/365;
var radPerDay = degPerDay*DRratio;
var bigTick = 100;
var smallTick = 20;
var baseRad = canvasHeight/2-bigTick;
//var baseRad = 200;
var amplitude = 1; //for magnifying the y axis data
var textMargin = 25;
var textMargin180 = 11;
var angleAdjust = 1.5*DRratio;
var angleAdjust180 = 5*DRratio;
var monthAngleAdjust = -18*DRratio;
var week = 0;
// Canvas
var svg = d3.select("body")
.append("svg")
.attr("width", canvasWidth)
.attr("height", canvasHeight);
// x axis line (radial version)
var circle = d3.select("svg")
.append("circle")
.attr("cx", originx)
.attr("cy", originy)
.attr("r", baseRad)
.attr("class", "xAxis");
// weekly (big) tick marks
for (a=0; a<=2*pi; a+=(radPerDay)*7) {
var line = d3.select("svg")
.append("line")
.attr("x1", originx + (baseRad * Math.cos(a)))
.attr("y1", originy + (baseRad * Math.sin(a)))
.attr("x2", originx + ((baseRad + bigTick) * Math.cos(a)))
.attr("y2", originy + ((baseRad + bigTick) * Math.sin(a)))
.attr("class", "xBigTick");
};
// axis labels
for (a=0; a<=2*pi; a+=(radPerDay)*7) {
var group = d3.select("svg")
.append("g")
//console.log('week-' + week + ' angle:' + a/DRratio + 'degrees'+ ' cos:' + Math.cos(a)+ ' Sin:' + Math.sin(a));
var text = d3.select("g")
.append("text")
.attr("class", "axisText")
.attr("style", "fill:black")
.attr("opacity", "1")
.text(week);
// adjust the label orientation & tweak the position
if (a > 0 && (a < (pi/2))){
text.attr("transform", "translate(" + (originx + (baseRad - textMargin) * Math.cos( -a+angleAdjust )) + "," + (originy - (baseRad - textMargin) * Math.sin( -a+angleAdjust )) + ")rotate(" + (a/DRratio) +")");
}
if ((a > (pi/2)) && (a < (3*pi/2))){
text.attr("transform", "translate(" + (originx + (baseRad - textMargin180) * Math.cos( -a+angleAdjust180 )) + "," + (originy - (baseRad - textMargin180) * Math.sin( -a+angleAdjust180 )) + ")rotate(" + (a/DRratio+180) +")");
}
if ((a > (3*pi/2)) && (a < (2*pi))){
text.attr("transform", "translate(" + (originx + (baseRad - textMargin) * Math.cos( -a+angleAdjust )) + "," + (originy - (baseRad - textMargin) * Math.sin( -a+angleAdjust )) + ")rotate(" + (a/DRratio) +")");
}
week=week+1;
};
// weekly (big) tick marks
for (a=0; a<=2*pi; a+=(radPerDay)*7) {
var line = d3.select("svg")
.append("line")
.attr("x1", originx + (baseRad * Math.cos(a)))
.attr("y1", originy + (baseRad * Math.sin(a)))
.attr("x2", originx + ((baseRad + bigTick) * Math.cos(a)))
.attr("y2", originy + ((baseRad + bigTick) * Math.sin(a)))
.attr("class", "xBigTick");
};
// month labels
var inrad = 100;
var outrad = baseRad - textMargin;
var months= [31,28,31,30,31,30,31,31,30,31,30,31];
var month = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
var mr = 0;
// monthly (inner) lead lines
for (a=0; a<=11; a+=1) {
var line = d3.select("svg")
.append("line")
.attr("x1", originx + (inrad * Math.cos(mr)))
.attr("y1", originy + (inrad * Math.sin(mr)))
.attr("x2", originx + (outrad * Math.cos(mr)))
.attr("y2", originy + (outrad * Math.sin(mr)))
.attr("class", "xBigTick");
mr = mr + months[a]*radPerDay;
//console.log('cumulative day at end of month: ' + m + '(' + (m * radPerDay) + ')');
};
md = 0;
mr = 0;
//month text
for (a=0; a<=11; a+=1) {
var group = d3.select("svg")
.append("g")
//console.log('week-' + week + ' angle:' + a/DRratio + 'degrees'+ ' cos:' + Math.cos(a)+ ' Sin:' + Math.sin(a));
var text = d3.select("g")
.append("text")
.attr("class", "axisText")
.attr("style", "fill:black")
.attr("opacity", "1")
.text(month[a]);
// adjust the month label orientation & tweak the position
if (md >= 0 && (md < 90)){
text.attr("transform", "translate(" + (originx + ((inrad - textMargin) * Math.cos( -mr+monthAngleAdjust ))) + "," + (originy - ((inrad - textMargin) * Math.sin( -mr+monthAngleAdjust ))) + ")rotate(" + (md) +")");
}
if ((md > 90) && (md < 270)){
text.attr("transform", "translate(" + (originx + (inrad ) * Math.cos( -mr+monthAngleAdjust )) + "," + (originy - (inrad) * Math.sin( -mr+monthAngleAdjust )) + ")rotate(" + (md+180) +")");
}
if ((md >270) && (md < 360)){
text.attr("transform", "translate(" + (originx + (inrad - textMargin) * Math.cos( -mr+monthAngleAdjust )) + "," + (originy - (inrad - textMargin) * Math.sin( -mr+monthAngleAdjust )) + ")rotate(" + (md) +")");
}
md = md + months[a]*degPerDay;
mr = mr + months[a]*radPerDay;
//console.log(md + "deg = " + mr + "rad");
};
// daily (small) tick marks
for (a=0; a<=2*pi; a+=(radPerDay)) {
var line = d3.select("svg")
.append("line")
.attr("x1", originx + (baseRad * Math.cos(a)))
.attr("y1", originy + (baseRad * Math.sin(a)))
.attr("x2", originx + ((baseRad + smallTick) * Math.cos(a)))
.attr("y2", originy + ((baseRad + smallTick) * Math.sin(a)))
.attr("class", "xSmallTick");
};
//control handle (move to adjust amplitude)
var circle = d3.select("svg")
.append("circle")
.attr("cx", originx)
.attr("cy", originy)
.attr("r", 10)
.attr("class", "button");
</script>
</body>
</html>

Adding separate arrows on d3 network graph

I would like to add separate arrows (one for the path from node A to B, and the other for B to A) to a d3 graph that I have made (see the Plunker version here), instead of adding a single arrow with two arrows at each end.
I have tried the code here, by changing my 'tick' function, as well as defining a path variable to be as below:
var path = svg.append("svg:g").selectAll("path")
.data(force.links())
.enter().append("svg:path")
.attr("class", "link")
.attr("marker-end", "url(#end)");
function tick() {
path.attr("d", function(d) {
var dx = d.target.x - d.source.x,
dy = d.target.y - d.source.y,
dr = Math.sqrt(dx * dx + dy * dy);
return "M" +
d.source.x + "," +
d.source.y + "A" +
dr + "," + dr + " 0 0,1 " +
d.target.x + "," +
d.target.y;
});
However, this seems to stop the nodes from displaying, as well as the zoom/filtering functionality.
Does anyone know how I can stop this happening?
Best,
Ben

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.js - readings in meter gauge like circular axis in d3.js

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>

Categories

Resources