d3js link arc filled useless part - d3.js

here is how my link draw currently.
here is below picture code (that comes from my real putout html)
<svg height="1210" width="1400">
<path class="link" id="linkId_2" opacity="0.9468531468531468" style="stroke-width: 5px; marker-end: url(#end-arrow);" d="M652.3404429062306,276.08144057675605A166.53959040529506,166.53959040529506 0 0,1 767.3217566577334,315.4663301140565"></path>
</svg>
But I want to draw like this. (should be like link, not part of circle)
here is my real part of code.
function updateLink() {
this.attr('d', function(d) {
var deltaX = d.target.x - d.source.x,
deltaY = d.target.y - d.source.y,
dist = Math.sqrt(deltaX * deltaX + deltaY * deltaY),
normX = deltaX / dist,
normY = deltaY / dist;
if( !normX) normX = 0;
if( !normY) normY = 0;
var sourcePadding = d.left ? circleSize(d.source.inDegree) + 10 : 20,
targetPadding = d.right ? circleSize(d.target.inDegree) + 5 : 20,
sourceX = d.source.x + (sourcePadding * normX),
sourceY = d.source.y + (sourcePadding * normY),
targetX = d.target.x - (targetPadding * normX),
targetY = d.target.y - (targetPadding * normY);
return "M" + sourceX + "," + sourceY + "A" + dist + "," + dist + " 0 0,1 " + targetX + "," + targetY;
});
}
Here is example I used (http://bl.ocks.org/mbostock/1153292)

If you want the stroke to be unfilled then simply set the fill to none and the stroke to a colour e.g.
<svg height="1210" width="1400" viewBox="600 230 600 600">
<path class="link" id="linkId_2" opacity="0.9468531468531468" style="fill:none;stroke:black;stroke-width: 5px; marker-end: url(#end-arrow);" d="M652.3404429062306,276.08144057675605A166.53959040529506,166.53959040529506 0 0,1 767.3217566577334,315.4663301140565"></path>
</svg>

Related

d3 path - split into 2 new paths

I am working with a Force-directed node chart using d3js. I would like to use marker-mid attribute on the paths but apparently there is a bug in FireFox and Safari so the markers fail to render. This bug seems to be limited to marker-mid, as marker-end and marker-start render just fine.
So unless someone shares a solution I am resorting to a work-around.
My idea is to split the arc into 2 separate paths and use the marker-end attribute.
The following is the called in the Tick function
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;
}`
As I read it, linkArc is generates the points between the 2 nodes. How can I change this function to generate a path from start to mid point ? ( then in another call, from mid-point to end )
I am thinking of calling 2 functions instead of one
linkArcA = PathA starts at the source node and ends at the mid point
linkArcB = PathB starts at the mid point and ends at the target node
Otherwise I am open to any alternative solution.
I was able to produce a working solution which might be helpful for others trying to overcome this bug.
Basics Steps
Create 2 paths for each node - pathA as first half path , path B as
the second half path.
Run linkArc for PathA. Creates the full curved path
Iterate over all PathA and push their mid-points to an array
Run linkArcStart creating paths from start to mid-points
Run LinkArcEnd creating paths from mid-points to start
Here are the snippets of the code used
pathA = svg.append("g").selectAll("path")
.data(force.links())
.enter()
.append("path")
pathB = svg.append("g").selectAll("path")
.data(force.links())
.enter()
.append("path")
var mpts = [];
function tick() {
pathA.attr("d", linkArc);
pathA.attr("mpts",function(d,i){
var p = d3.select(this).node()
var pt = p.getPointAtLength(p.getTotalLength()/2)
mpts[i] = [pt.x,pt.y];
})
pathA.attr("d", linkArcStart);
pathB.attr("d",linkArcEnd)
}
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),
pts = "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y
return pts;
}
function linkArcStart(d,i) {
dx = (d.target.x - d.source.x) ,
dy = (d.target.y - d.source.y),
dr = Math.sqrt(dx * dx + dy * dy),
pts = "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + mpts[i][0]+ "," + mpts[i][1]
return pts;
}
function linkArcEnd(d,i) {
dx = (d.target.x - d.source.x) ,
dy = (d.target.y - d.source.y),
dr = Math.sqrt(dx * dx + dy * dy),
pts = "M" + + mpts[i][0]+ "," + mpts[i][1] + "A" + dr + "," + dr + " 0 0,1 " + d.target.x+ "," + d.target.y
return pts;
}
Hope this helps others who are struggling to create marker-mid that render in all browsers (on Mac)

Is it possible to create an SVG rect element with top left and top right rounded corners for use in D3? [duplicate]

This question already has answers here:
svg / d3.js rounded corners on one side of a rectangle
(5 answers)
Closed 6 years ago.
Is there a simple way to place rounded corners just on the top of the Bar(s) in a D3 Vertical Bar Chart? I've been playing around with .attr("rx", 3) and that seems to affect all four corners of a Bar.
You cannot specify which corners you want to make round in SVG: rx will affect all 4 corners.
The only solution is using a path for simulating a rectangle. This function returns a path with top corners round:
function rectangle(x, y, width, height, radius){
return "M" + (x + radius) + "," + y + "h" + (width - 2*radius)
+ "a" + radius + "," + radius + " 0 0 1 " + radius + "," + radius + "v" +
(height - 2*radius) + "v" + radius + "h" + -radius + "h" +
(2*radius - width) + "h" + -radius + "v" + -radius + "v" +
(2*radius - height) + "a" + radius + "," + radius + " 0 0 1 "
+ radius + "," + -radius + "z";
};
Here is a demo snippet showing a "bar chart" with those paths, with a radius (the rx equivalent here) of 5px:
function rectangle(x, y, width, height, radius){
return "M" + (x + radius) + "," + y + "h" + (width - 2*radius) + "a" + radius + "," + radius + " 0 0 1 " + radius + "," + radius + "v" + (height - 2*radius) + "v" + radius + "h" + -radius + "h" + (2*radius - width) + "h" + -radius + "v" + -radius + "v" + (2*radius - height) + "a" + radius + "," + radius + " 0 0 1 " + radius + "," + -radius + "z";
};
var data = [40, 50, 30, 40, 90, 54, 20, 35, 60, 42];
var svg = d3.select("body")
.append("svg")
.attr("width", 400)
.attr("height", 120);
var rects = svg.selectAll(".paths").data(data).enter().append("path");
rects.attr("d", function(d,i){ return rectangle(10+40*i,100-d,20,d,5)});
var texts = svg.selectAll(".text").data("ABCDEFGHIJ".split("")).enter().append("text").attr("y",114).attr("x", function(d,i){return 16+40*i}).text(function(d){return d});
path {
fill:teal;
}
text {
fill:darkslategray;
font-size: 12px;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
PS: I didn't write that function, it was based on these answers by M. Bostock and R. Longson.

use d3.js to animate a gauge needle

I am trying to use d3.js to animate a gauge needle, but end up with a weird animation. I have done some search from Internet, but I couldn't figure out what solution can I use to fix the problem.
Codepen
function createNeedle(sampleAngle){
topX = centerX - Math.cos(sampleAngle) * triLength
topY = centerY - Math.sin(sampleAngle) * triLength
leftX = centerX - 10 * Math.cos(sampleAngle - Math.PI / 2)
leftY = centerY - 10 * Math.sin(sampleAngle - Math.PI / 2)
rightX = centerX - 10 * Math.cos(sampleAngle + Math.PI / 2)
rightY = centerY - 10 * Math.sin(sampleAngle + Math.PI / 2)
return " M " + leftX + " " + leftY + " L " + topX + " " + topY + " L " + rightX + " " + rightY;
}
//animate the needle
d3.select('.moveNeedle')
.attr('d', createNeedle(sampleAngle1))
.transition()
.duration(2000)
.attr('d', createNeedle(sampleAngle2));
You can make your life so much easier if you apply a transform="rotate()" instead of redrawing the path.
Nonetheless, you need to supply a custom Tween function, as the standard d3.interpolateTransformSvg acts in unexpected ways.
var topX = centerX - triLength,
topY = centerY,
leftX = centerX,
leftY = centerY + 10,
rightX = centerX,
rightY = centerY - 10;
function rotateNeedle(sampleAngle){
return "rotate(" + sampleAngle + "," + centerX + "," + centerY + ")";
}
d3.select('.moveNeedle')
// only draw once
.attr('d', "M" + leftX + " " + leftY + " " + topX + " " + topY + " " + rightX + " " + rightY)
// supply angles in degrees!
.attr('transform', rotateNeedle(sampleAngle1))
.transition()
.duration(2000)
.attrTween('transform', function () {
var i = d3.interpolate(sampleAngle1, sampleAngle2)
return function (t) {
return rotateNeedle(i(t));
};
});

Existing Triangles Cannot be Removed

I bind triangles with data in D3.js. But the triganle cannot be removed with the data. The rectangles are okay. Complete code is attached!
var svg = d3.select("body").append("svg")
.attr("width", 250)
.attr("height", 250);
function render(data){
var tris = svg.selectAll("tri").data(data);
tris.enter().append("path");
tris.attr("d", function(d) {
var x1 = (0.4 - 0.2 * (d - 1)) * 250, y1 = 0.3 * 250;
var x2 = (0.5 - 0.2 * (d - 1)) * 250, y2 = 0.1 * 250;
var x3 = (0.6 - 0.2 * (d - 1)) * 250, y3 = 0.3 * 250;
return "M" + x1 + " " + y1 + " L" + x2 + " " + y2 + " L" + x3 + " " + y3 + "Z";
});
tris.exit().remove();
var rects = svg.selectAll("rect").data(data);
rects.enter().append("rect");
rects.attr("y", 50)
.attr("width", 20)
.attr("height", 20)
.attr("x", function(d) { return d * 40; });
rects.exit().remove();
}
render([1, 2, 3]);
render([1, 2]);
render([1]);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
There is no SVG element called <tri>. Your enter selection works because you can select anything in your data binding function. As there is nothing named "tri" in the whole DOM (be it a tag, a class, an ID, whatever...), your enter selection is never empty and your exit selection is never populated.
That being said, an easy solution is selecting by class...
var tris = svg.selectAll(".tri").data(data);
And setting this class in your enter selection:
tris.enter().append("path").attr("class", "tri");
Here is your code with the changes:
var svg = d3.select("body").append("svg")
.attr("width", 250)
.attr("height", 250);
function render(data){
var tris = svg.selectAll(".tri").data(data);
tris.enter().append("path").attr("class","tri");
tris.attr("d", function(d) {
var x1 = (0.4 - 0.2 * (d - 1)) * 250, y1 = 0.3 * 250;
var x2 = (0.5 - 0.2 * (d - 1)) * 250, y2 = 0.1 * 250;
var x3 = (0.6 - 0.2 * (d - 1)) * 250, y3 = 0.3 * 250;
return "M" + x1 + " " + y1 + " L" + x2 + " " + y2 + " L" + x3 + " " + y3 + "Z";
});
tris.exit().remove();
var rects = svg.selectAll("rect").data(data);
rects.enter().append("rect");
rects.attr("y", 50)
.attr("width", 20)
.attr("height", 20)
.attr("x", function(d) { return d * 40; });
rects.exit().remove();
}
render([1, 2, 3]);
setTimeout(() => render([1, 2]), 1000);
setTimeout(() => render([1]), 2000)
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

d3 drawing arrows tips

In this example :
http://jsfiddle.net/maxl/mNmYH/2/
If I enlarge the circles, ex:
var radius = 30; // (is 6 in the jsFiddle)
var circle = svg.append("svg:g").selectAll("circle")
.data(force.nodes())
.enter().append("svg:circle")
.attr("r", radius)
What is the best way to properly adjust the drawing of the arrow
so that it points to the radius of the circle ?
Thanks
You asked for the "best way to properly adjust the drawing of the arrow ".
I cannot claim the following approach is the "best" way, and I look forward to other answers, but here is one method to tackle this issue.
http://jsfiddle.net/Y9Qq3/2/
Relevant updates are noted below.
...
var w = 960,
h = 500
markerWidth = 6,
markerHeight = 6,
cRadius = 30, // play with the cRadius value
refX = cRadius + (markerWidth * 2),
refY = -Math.sqrt(cRadius),
drSub = cRadius + refY;
...
svg.append("svg:defs").selectAll("marker")
.data(["suit", "licensing", "resolved"])
.enter().append("svg:marker")
.attr("id", String)
.attr("viewBox", "0 -5 10 10")
.attr("refX", refX)
.attr("refY", refY)
.attr("markerWidth", markerWidth)
.attr("markerHeight", markerHeight)
.attr("orient", "auto")
.append("svg:path")
.attr("d", "M0,-5L10,0L0,5");
...
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 - drSub) + "," + (dr - drSub) + " 0 0,1 " + d.target.x + "," + d.target.y;
});
...

Resources