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

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.

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)

Drawing link with d3 and d3-flextree plugin

I'm (still) trying to make an orgchart with D3 and d3-flextree plugin. I struggle drawing the links between the nodes. The "equation" I use consider the middle of the node (as I understand it) whereas I'd like to draw from the end of a node.
I think my mistake is in my drawing-link function
function diagonal(s, d) {
path = `M ${s.x} ${s.y}
L ${s.x} ${(s.y + d.y) * 0.5},
${d.x} ${(s.y + d.y) * 0.5 },
${d.x} ${d.y}`
return path
}
It's hard to explain so I made a JSFiddle: https://jsfiddle.net/ymv5sr9k/11/
In this exemple all links are the way I want thanks to right padding, but as soon as the nodeSize change (see the big node) it's all broken. I guess I need a more general drawing-link function but I can't figure it out
Thanks for reading,
Zoom
Problem solved! As i said in the comments, I needed to move the horizontal line. I added nodeSize in my two y "control points". And nodeSize is actually source.y - destination.y - padding. This is my final equation:
function diagonal(s, d) {
var nodeSize = s.y - d.y - 30
return "M" + s.x + "," + s.y
+ "L" + s.x + "," + (d.y + s.y + nodeSize) / 2
+ " " + d.x + "," + (d.y + s.y + nodeSize) / 2
+ " " + d.x + "," + d.y;
};
Thank you for your time!

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));
};
});

D3.js Arrow-like path around text

I want to reproduce this beautiful arrow-like tooltip and since the source code is not accessible i am asking myself:
how to make a "background" path for the tooltip body which fits the overlaying text block.
Do i have to create the text element and then calculate the width via getBoundingClientRect() and insert the width it into a custom path ?
Edit: just found the solution in a non minified js of bostock.
var tooltipRect = tooltipContent.node().getBoundingClientRect(),
tooltipWidth = tooltipRect.width,
tooltipHeight = tooltipRect.height;
tooltipPath
.attr("width", tooltipWidth)
.attr("height", tooltipHeight + 6)
.select("path")
.attr("d", "M0,0"
+ "H" + tooltipWidth
+ "v" + tooltipHeight
+ "H" + (tooltipWidth / 2 + 6)
+ "l-6,6"
+ "l-6,-6"
+ "H0"
+ "z");
tooltipOverlay
.style("left", (d.x + margin.left - tooltipWidth / 2) + "px")
.style("top", (d.y + margin.top - tooltipHeight - 6) + "px");

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