How do I draw semi-circular arc along the y-axis ( perpendicular axis ) of an svg using D3 ?
Here is an image of what I want to achieve target
Here is my code so far :-
var canvas = d3.select("body").append("svg")
.attr("width",width)
.attr("height",height);
var group = canvas.append("g")
.attr("transform","translate(0,200)");
var origin = d3.svg.arc()
.outerRadius(50)
.innerRadius(0)
.startAngle(-1.5755)
.endAngle(1.5755);
var arcs1 = group.append('g')
.attr('class','arc')
.attr("transform","translate(50,0)");
var yAxis = group.append("line")
.attr("transform","translate(50,0)")
.attr("x1", 0)
.attr("y1", -200)
.attr("x2", 0)
.attr("y2", height)
.attr("stroke-width", 2)
.attr("stroke", "black");
First, to draw the arc like your target you want your startAngle to start at 0 and endAngle to be 3.14 or Math.PI.
var origin = d3.svg.arc()
.outerRadius(50)
.innerRadius(0)
.startAngle(0)
.endAngle(Math.PI);
Then just append a path to arcs1 and use your origin function to draw it.
arcs1.append("path").attr("d", origin);
You can take a look at a live demo at http://jsfiddle.net/7EfS5/.
Edit
To change the color of your arc, you can either use .attr("fill", color) or .style("fill", color)
arcs1.append("path").attr("d", origin)
.attr("fill", "red");
Hope this help.
When you make a d3 arc, an angle of 0 represents the top of a circle. So to make an arc like the one shown in your image, you will want your start angle to be 0 and your end angle to be halfway around a circle, which is Pi radians:
var arc = d3.svg.arc()
.outerRadius(50)
.innerRadius(0)
.startAngle(0)
.endAngle(Math.PI);
As far as positioning your arc, you can use an svg transform to translate the origin of the arc to the position you want. For instance, if your y-axis has height h you can translate the origin to the center of the axis when you create the path element for your arc like this:
svg.append('path')
.attr('transform', 'translate(0,' + h/2 + ')')
.attr('d', arc);
HERE is a demo.
Related
Given the next information:
white point is the center of the circle.
blue, green point are points in the border of the circle.
orientation: clockwise, anticlockwise
Using any point (blue, green) with white point, then I can get the radius and I can draw a circle. However I don't want to draw a circle but just the arc of that circle between blue point and green point
Using arc with SVG (A ellipses), I get 2 options with large_arc_flag
In this example: second option is the one that I want: large_arc_flag=1, but sometimes I want large_arc_flag=0. In fact, I want just the arc that belongs to that circle. Using path with "A", I got 2 arcs to chose due to the intersection of ellipses.
How can I solve that?
Thanks!
Given a random circle centre (the white point), one random edge point (green) and a second mirrored edge point (blue), you can calculate the svg arc as follows.
Set the horizontal and vertical ellipse radii to the distance between the green and white points
Set the x-axis-rotation to zero (as rotating a circle makes no visual difference)
Set the large-arc-flag to one if the green point is above the white point, otherwise zero (I think this was the key thing you were asking)
Set the sweep flag to zero because the yellow path is arbitrarily being drawn from the left image border to the right image border, requiring counter-clockwise rotation of the arc path trajectory
Set the final point to the blue point coordinates
Run the code snippet below repeatedly for different arc configurations based on random centre and edge points.
var xmlns = "http://www.w3.org/2000/svg"; // svg namespace
var doc = document; // common abbreviation
var spc = " "; // space
var com = ","; // comma
var wd = 200; // svg width
var ht = 200; // svg height
var svg = doc.querySelector("svg"); // retrieve svg root element
setAttributes(svg, {width: wd, height: ht}); // set the svg dimensions
var cenX = wd / 2; // centre the circle horizontally
var cenY = Math.random() * (ht / 2) + (ht / 4); // pick a random circle centre height
var x1 = Math.random() * (wd / 3) + (wd / 7); // pick a random green point position
var y1 = Math.random() * (ht / 2) + (ht / 4);
var x2 = wd - x1; // mirror the blue point on the green point
var y2 = y1;
showPt(cenX, cenY, "white"); // show the white circle centre
showPt(x1, y1, "green"); // show the coloured edge points
showPt(x2, y2, "blue");
var path = doc.createElementNS(xmlns, "path"); // create the yellow path element
setAttributes(path, { // give it colour and width but no fill
stroke: "yellow",
"stroke-width": 3,
fill: "none"
});
svg.appendChild(path); // add it to the picture
var rad = Math.sqrt((x1-cenX)*(x1-cenX) + (y1-cenY)*(y1-cenY)); // calculate the circle radius
var lgArcFlag = (y1 < cenY ? 1 : 0); // the arc will be large if the edge points are above the circle centre
setAttributes(path, { // create the trajectory of the yellow path
d:
"M" +
"0," + y1 + // start it at the left border at the height of the green edge point
"L" +
x1 + com + y1 + spc + // draw it to the green edge point
"A" + // make an arc
rad + spc + rad + spc + // using the circle radius
0 + spc + // with no rotation of the ellipse/circle
lgArcFlag + spc + 0 + spc + // using the large-arc-flag
x2 + com + y2 + spc + // drawn to the blue edge point
"L" +
wd + com + y1 // and drawn straight out to the right border
});
function showPt(x, y, fill) {
var pt = doc.createElementNS(xmlns, "circle");
setAttributes(pt, {
cx: x,
cy: y,
r: 8,
fill: fill
});
svg.appendChild(pt);
return pt;
}
function setAttributes(el, attrs) {
var recursiveSet = function(at, set) {
for (var prop in at) {
var a = at[prop];
if (typeof a === 'object' && a.dataset === undefined && a[0] === undefined) {
recursiveSet(a, set [prop]);
} else {
set.setAttribute(prop, a);
}
}
}
recursiveSet(attrs, el);
}
<svg>
<rect id="bkgd" fill="black" x="0" y="0" width="300" height="300" />
</svg>
I'm trying to make a simple graph with nodes and links. I have "g" elements containing a circle and its text, and links on their own. I have, for example, this bit on code called on a mouseover event:
//check if circle is connected to current "viewed" (mouseover-ed)
//circle via a link referenced by "that" and paint it green if so
circles.filter(function() {
return d3.select(this).attr("index") == d3.select(that).attr("src");
}).attr("viewed",1).style("stroke", "green");
});
This was really a long shot as nodes is the 'g' element container and I wasn't sure what calling .style would do, but to my surprise it did change the color - but only for the text!
Is there a way to make it change the stroke style of the circle as well?
The declaration code:
var circles = svg.append("g")
.attr("class","nodes")
.selectAll("circle")
.data(graph.nodes)
.enter()
.append("g")
.attr("transform",function(d,i){d.x = getX(i);d.y=getY(i);return "translate(" + d.x + "," + d.y + ")";})
.attr("name", function(d){return d.name;})
.attr("viewed", 0)
.attr("focused", 0)
.attr("index", function(d, i) {return i;});
circles.append("circle")
.style("stroke", "gray")
.style("fill", "white")
.attr("r", node_radius_wo_pad)
.on("mouseover", function(){...};
circles.append("text")
.attr("text-anchor","middle")
.text(function(d){return d.name});
The reason this is working is that you haven't explicitly declared a stroke colour for the text and so it inherits what you set for the parent g element. To make this work for the circles, you have to select them explicitly:
var toChange = circles.filter(function() {
return d3.select(this).attr("index") == d3.select(that).attr("src");
});
toChange.attr("viewed", 1);
toChange.selectAll("circle").style("stroke", "green");
toChange.selectAll("text").style("stroke", "green");
I am working currently on a graph visualization and I use SVG and the D3 library. I was asked by our designer if I can put the arrowheads of the edges of the graph on a position corresponding to 80% of length of the lines.
I was able to achieve the first part - getting the position - by using the getPointAtLength method.
var svg = d3.select("body").append("svg")
.attr("width", 960)
.attr("height", 500)
var path = svg.append("path")
.attr("d", "M20,20C400,20,20,400,400,400")
.attr("fill", "none")
.attr("stroke", "black");
var pathEl = path.node();
var pathLength = pathEl.getTotalLength();
var pathPoint = pathEl.getPointAtLength(pathLength*0.5);
var point = svg.append("svg:circle")
.style("fill", "red")
.attr("r", 5)
.attr("cx", pathPoint.x)
.attr("cy", pathPoint.y);
Here is a jsfidle example
Now I wonder how ca I attach an arrowhead to this position with corresponding orientation. More important how can I do this so I can update the edges of the graph when moving the associated nodes.
I was not able to find any answer yet, the examples on "markers" are working with path properties like : style('marker-end', "url(#end-arrow)")
Firstly, the long answer from SO. The quick answer is SVG <markers>
The (basic) short answer: Take a point a little before the red dot, measure the slope and draw a line between the two points. Now the question is simplified to: How do add an arrow to the end of a straight line? Use the quick answer.
Add this to your code to visualize the answer:-
var pathPoint2 = pathEl.getPointAtLength(pathLength*0.78);
var point2 = svg.append("svg:circle")
.style("fill", "blue")
.attr("r", 3)
.attr("cx", pathPoint2.x)
.attr("cy", pathPoint2.y);
var slope = (pathPoint.y - pathPoint2.y)/(pathPoint.x - pathPoint2.x);
var x0 = pathPoint2.x/2;
var y0 = slope*(x0 - pathPoint.x) + pathPoint.y;
var line = svg.append("svg:path")
.style("stroke","green")
.attr("d", "M" + pathPoint.x + "," + pathPoint.y + " L" + x0 +","+ y0);
I have created an SVG map that plots tweets live containing particular keywords. I'm drawing each tweet to the screen as a circle (or dot), and after 50 tweets have been added to the map, the oldest one will disappear.
I'd like to have some sort of color decay for the circles depending on how long they've been on the map.
New tweets would pop onto the map and be red. As time passes, points already plotted on the map will slowly fade to black.
Here's where I add the circles to the map:
function mapTweet(tweetData) {
var tipText; // Ignore this. For tweet dot hovering.
var coordinates = projection([tweetData.geo.coordinates[1], tweetData.geo.coordinates[0]]);
addCircle(coordinates, tipText);
}
function addCircle(coordinates, tipText, r) {
tweetNumber++;
// too many tweets
if (tweetNumber == 50) {
tweetNumber = 0;
}
//removes expired circles
$('#' + tweetNumber).remove();
var rad;
//determine if radius size needs to be bumped
if (arguments.length == 3) {
rad = r;
} else {
rad = 3;
}
// add radar-style ping effect
map.append('svg:circle')
.style("stroke", "rgba(255,49,49,.7)")
.style("stroke-width", 1)
.style("fill", "rgba(0,0,0,0)")
.attr('cx', coordinates[0])
.attr('cy', coordinates[1])
.attr('r', 3)
.transition()
.delay(0)
.duration(2000)
.attr("r", 60)
.style("stroke-width", 2)
.style("stroke", "rgba(255,49,49,0.0001)").transition().duration(2000).remove();
// add circles representing tweets
map.append('svg:circle').attr("class", "tweetCircles")
.attr("id", tweetNumber)
.style("stroke", "rgba(255,49,49,.7)")
.style("stroke-width", 1)
.style("fill", "rgba(240,49,49,1)")
.attr('cx', coordinates[0])
.attr('cy', coordinates[1])
.attr('r', rad);
addTipsy(tipText, tweetNumber); // Ignore this. For tweet dot hovering.
}
Once a circle is drawn, does it have to be redrawn to change the color? Or can dots have their attributes changed after being added to the canvas?
How can I decay the color over, say, 20 seconds?
Append an animate element as a child of the circle
.append('svg:animate')
.attr('attributeName', 'fill')
.attr('from', 'red')
.attr('to', 'blue')
.attr('dur', '20s');
This will interpolate from red to blue or whatever colours you choose.
I'm a bit new to SVG and d3.js.
While drawing a graph with D3 force layout, I'm using a simple diagonal line generator and using marker-end to draw arrow heads.
When using arc instead of diagonal generator the arrow heads appear just fine. But using diagonal generator like in the code below doesn't produce proper markers:
var vis = this.vis = d3.select(el).append("svg:svg")
.attr("width", w)
.attr("height", h);
var force = d3.layout.force()
.gravity(0.03)
.distance(120)
.charge(-800)
.size([w, h]);
var linkDiag = d3.svg.diagonal()
.projection(function(d)
{
return [d.x, d.y];
});
vis.append("svg:defs")
.selectAll("marker")
.data(["normal", "special", "resolved"])
.enter()
.append("svg:marker")
.attr("id", String)
.attr("viewBox", "0 -5 10 10")
.attr("refX", 15)
.attr("refY", -1.5)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("svg:path")
.attr("d", "M 0,-5 L 10,0 L0,5");
...and then also:
force.on("tick", function() {
link.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; })
.attr("d", linkDiag)
.attr("marker-end", function(d) { return "url(#special)"; });
});
The markers are not oriented at all with the vertices.
Any help would be appreciated!
It only appears as if the arrows aren't pointing in the right direction because you're moving the arrowhead to a new position via refX and refY.
For example, check out this code which draws diagonals in various directions. The arrowheads appear correctly, with the exception of the one at 180 degrees, but that's probably due to a rounding error.
Now, try changing refX on line 10 to a value of, say, 5. Now, the arrowheads close to the horizontal appear incorrect. To see this more dramatically, try changing the value to 8.
What's happening is you're hiding part of the diagonal so the line appears to be ending at an earlier point, which is curved slightly differently from the actual end-point. The same thing will happen if the arrowhead is too large so that it overlays part of the curve. Note that for diagonals in d3, which are symmetrical bezier curves, the arrowheads should always appear pointing perfectly horizontally or vertically. You can see exactly what's happening by reducing the arrowhead's opacity.
Can you specify you question a little more?
However with even running you code I think you problem might be
.attr("orient", "auto")
Try to specify pos