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!
Related
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)
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.
I have the following code that draws a triangle in d3:
var trianglePoints = xScale(3) + ' ' + yScale(18) + ', ' + xScale(1) + ' ' + yScale(0) + ', ' + xScale(12) + ' ' + yScale(3) + ' ' + xScale(12) + ', ' + yScale(3) + ' ' + xScale(3) + ' ' + yScale(18);
console.log(trianglePoints);
svg.append('polyline')
.attr('points', trianglePoints)
.style('stroke', 'blue');
Here is a jsbin which shows it in action.
I am curious to know if this is the best way of doing in this d3 or is there a better way?
Another example using v4 of shape symbols:
var color = "green";
var triangleSize = 25;
var verticalTransform = midHeight + Math.sqrt(triangleSize);
var triangle = d3.symbol()
.type(d3.symbolTriangle)
.size(triangleSize)
;
svg.append("path")
.attr("d", triangle)
.attr("stroke", color)
.attr("fill", color)
.attr("transform", function(d) { return "translate(" + xScale(currentPrice) + "," + yScale(verticalTransform) + ")"; });
;
Some notes:
the size of the shape seems to be an area
the shape is centered at [0, 0]
The above example is trying to translate the top point of the equilateral triangle rather than the center, hence the extra amount to the "y" portion of the transfrom.
Mike Bostock, the creator of D3, thinks that what you're doing is the best method:
https://groups.google.com/forum/#!topic/d3-js/kHONjIWjAA0
Yep, I'd probably use a path element with a custom "d" attribute here.
Another option for drawing triangles, but more intended for
scatterplots, is to use d3.svg.symbol. See the documentation here:
https://github.com/mbostock/d3/wiki/SVG-Shapes#wiki-symbol
There is an inbuilt convenience function for drawing various symbols for use in Scatterplots, as stated by Kyle R. If you are only interested in drawing an equilateral triangle, then this could be used.
The function can used like so:
svg.append("path")
.attr("transform", function(d) { return "translate(" + x(d.x) + "," + y(d.y) + ")"; })
.attr("d", d3.svg.symbol().type("triangle-up"));
You can also set the size of the symbol: by appending the function call size(100).
That said, if you want to draw a different form of triangle you can either:
draw a path element and use the d attribute. Path element explained.
draw a polygon or polyline with a points attribute (as you have done in your example).
To use to d attribute. I've forked your JS Bin to demo it. The key snippet is as follows:
svg.append('path')
.attr('d', function(d) {
var x = xScale(3), y = yScale(18);
return 'M ' + x +' '+ y + ' l ' + xScale(10) + ' ' + yScale(10) + ' l ' + -xScale(10) + ' ' + yScale(20) + ' z';
})
.style('stroke', 'blue');
I've got a topology map with pan and zoom functionality.
Clicking on the country, I'm zoom/panning into the country, using this:
if (this.active === d) return
var g = this.vis.select('g')
g.selectAll(".active").classed("active", false)
d3.select(path).classed('active', active = d)
var b = this.path.bounds(d);
g.transition().duration(750).attr("transform","translate(" +
this.proj.translate() + ")" +
"scale(" + .95 / Math.max((b[1][0] - b[0][0]) / this.options.width, (b[1][1] - b[0][1]) / this.options.height) + ")" +
"translate(" + -(b[1][0] + b[0][0]) / 2 + "," + -(b[1][1] + b[0][1]) / 2 + ")");
g.selectAll('path')
.style("stroke-width", 1 / this.zoom.scale())
However, if I continue to drag pan, the map jerks back into the initial position before the click happens, before panning. Code to pan/zoom is here:
this.zoom = d3.behavior.zoom().on('zoom', redraw)
function redraw() {
console.log('redraw')
_this.vis.select('g').attr("transform","translate(" +
d3.event.translate.join(",") + ")scale(" + d3.event.scale + ")")
_this.vis.select('g').selectAll('path')
.style("stroke-width", 1 / _this.zoom.scale())
}
this.vis.call(this.zoom)
In another words, after zooming into a point by clicking, and then dragging by the redraw function, the redraw does not pick up the correct translate+scale to continue from.
To continue at the right 'zoom' after the transition, I had to set the zoom to the new translate and scale.
Example of reset which is applied similarly to my click and zoom event, the set new zoom point is the critical bit:
_this.vis.select('g').transition().duration(1000)
.attr('transform', "translate(0,0)scale(1)")
/* SET NEW ZOOM POINT */
_this.zoom.scale(1);
_this.zoom.translate([0, 0]);
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");