Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Offset Line stroke-weight d3.js

I'm using d3.js to plot a highway network over a map SVG. I'd like to be able to vary the stroke-weight of the line to illustrate demand based on a value.

Highway links are define as one way, so for example a two way road would have two overlapping line elements (with separate id's). I can use stroke-weight to edit the thickness of the line based on a variable (as below), but on a two way road, the larger of the two stroke weights will always cover the smaller rendering it invisible.

Is there an easy way to offset a line by half its stroke-weight to the left hand side of the direction the line is drawn? (direction denoted by x1,y1 x2,y2)

d3.csv("links.csv", function (error, data) {
d3.select("#lines").selectAll("line")
    .data(data)
    .enter()
    .append("line")
    .each(function (d) {
        d.p1 = projection([d.lng1, d.lat1]);
        d.p2 = projection([d.lng2, d.lat2]);
    })
    .attr("x1", function (d) { return d.p1[0]; })
    .attr("y1", function (d) { return d.p1[1]; })
    .attr("x2", function (d) { return d.p2[0]; })
    .attr("y2", function (d) { return d.p2[1]; })
    .on('mouseover', tip_link.show)
    .on('mouseout', tip_link.hide)
    .style("stroke", "black")
    .style("stroke-width", lineweight)

});


1 Answers

One option would be to just create new start/end points when drawing your lines and use those:

var offset = function(start,destination,distance) {
    // find angle of line
    var dx = destination[0] - start[0];  
    var dy = destination[1] - start[1];
    var angle = Math.atan2(dy,dx);
    // offset them:
    var newStart = [
      start[0] + Math.sin(angle-Math.PI)*distance,
      start[1] + Math.cos(angle)*distance
    ];
    var newDestination = [
      destination[0] + Math.sin(angle-Math.PI)*distance,  
      destination[1] + Math.cos(angle)*distance 
    ];
    // return the new start/end points
    return [newStart,newDestination]
}

This function takes two points and offsets them by a particular amount given the angle between the two points. Negative values shift to the other side, swapping the start and destination points will shift to the other side.

In action, this looks like, with the original line in black:

var offset = function(start,destination,distance) {
    // find angle of line
    var dx = destination[0] - start[0];  
	var dy = destination[1] - start[1];
    var angle = Math.atan2(dy,dx);
	// offset them:
	var newStart = [
      start[0] + Math.sin(angle-Math.PI)*distance,
	  start[1] + Math.cos(angle)*distance
	];
	var newDestination = [
      destination[0] + Math.sin(angle-Math.PI)*distance,  
	  destination[1] + Math.cos(angle)*distance	
	];
	// return the new start/end points
	return [newStart,newDestination]
}

var line = [
	[10,10],
	[200,100]
];

var svg = d3.select("svg");

// To avoid repetition:
function draw(selection) {
  selection.attr("x1",function(d) { return d[0][0]; })
  .attr("x2",function(d) { return d[1][0]; })
  .attr("y1",function(d) { return d[0][1]; })
  .attr("y2",function(d) { return d[1][1]; })
}

svg.append("line")
  .datum(line)
  .call(draw)
  .attr("stroke","black")
  .attr("stroke-width",1)

svg.append("line")
  .datum(offset(...line,6))
  .call(draw)
  .attr("stroke","orange")
  .attr("stroke-width",10)
  
svg.append("line")
  .datum(offset(...line,-4))
  .call(draw)
  .attr("stroke","steelblue")
 .attr("stroke-width",5)  
<svg width="500" height="300"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>

You will need to adapt this to your data structure, and it requires twice as many lines as before, because you aren't using stroke width, your using lines. This is advantageous if you wanted to use canvas.

like image 147
Andrew Reid Avatar answered Apr 29 '26 13:04

Andrew Reid



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!