旋转的矩形与文本框不匹配

Rotated rectangle not match text bbox

我尝试旋转文本和背景矩形来注释两点。但是旋转后,背景位置与文本框不匹配。

console.clear()

var svg = d3.select('body')
.append('svg')

add_defs(svg)

var g = svg.append('g')

var  data = [[0,0],[100,100]]
var nodes = g.selectAll('.circle')
.data(data)
.join('circle')
.attr('cx',d => d[0])
.attr('cy',d => d[1])
.attr('r',2)
.attr('stroke','none')
.attr('fill','red')

ann_line(svg,data[0][0],data[0][1],data[1][0],data[1][1],"hello")

adjust_view(svg)

function ann_line(svg,ax,ay,bx,by,text) {
  var w = 10
  var dx = bx - ax
  var dy = by - ay
  var l = Math.sqrt(dx*dx+dy*dy)
  dx /= l
  dy /= l
  var a1x = ax + w*dy
  var a1y = ay - w*dx
  var a2x = ax + 2*w*dy
  var a2y = ay - 2*w*dx
  var b1x = bx + w*dy
  var b1y = by - w*dx
  var b2x = bx + 2*w*dy
  var b2y = by - 2*w*dx

  var path = ['M',a1x,a1y,'L',b1x,b1y]
  var line = svg.append('path')
  .attr('d',path.join(' '))
 .attr('stroke','black')
 .attr('marker-start', 'url(#arrhead)')
  .attr('marker-end', 'url(#arrhead)');

  var cx = (a1x + b1x)/2
  var cy = (a1y + b1y)/2
  var alpha = Math.atan(dx/dy)/Math.PI*180

  const  label = svg.append('text')
  .text(text)
  .attr('text-anchor', 'middle')
  .attr('alignment-baseline', 'middle')
  .attr('transform',`translate(${cx},${cy}) rotate(${alpha})`)

  const bb = label.node().getBBox();  
  svg.append('rect').lower()
    .attr('stroke','none')
    .style('fill', 'steelblue')
    .attr('transform',`translate(${cx-bb.width/2},${cy-bb.height/2}) rotate(${alpha})`)
    .attr('width', bb.width)
    .attr('height', bb.height)
}

function renderTextInCenterOfLine(line, text) {
  const from = parseInt(line.attr('x1'));
  const to = parseInt(line.attr('x2'));
  const y = parseInt(line.attr('y1'));
  const svg = d3.select('svg');
  const textBackground = svg.append('rect')
  const textElement = svg.append('text')
  .text(text)
  .attr('x', (from + to) / 2)
  .attr('y', y)
  .attr('text-anchor', 'middle')
  .attr('alignment-baseline', 'central');

  const box = textElement.node().getBBox();  
  const width = box.width + 50; // 
  const height = box.height + 20;
  textBackground
    .attr('x', (from + to - width) / 2)
    .attr('y', y - height / 2)
    .attr('width', width)
    .attr('height', height)
    .style('fill', 'white')

}

function add_defs(svg) {
  var lw = 1
  var w = 6
  var h = 12
  var m = 2
  var lc = 'black'
  var path = ['M',2+w,2,'L',2+w,2+h,'M',2+m,2+h/2,'L',2,2+h/4,2+w-lw,2+h/2,2,2+3*h/4,'z']   
  svg
    .append('defs')
    .append('marker')
    .attr('id', 'arrhead')
    .attr('viewBox', [0, 0, w+4, h+4])
    .attr('refX', w+1)
    .attr('refY', 2+h/2)
    .attr('markerWidth', w+4)
    .attr('markerHeight', h+4)
    .attr('orient', 'auto-start-reverse')
    .append('path')
    .attr('d',path.join(' '))
    .attr('stroke', lc)
    .attr('stroke-width',lw)
    .attr('fill',lc) 
    .attr('stroke-miterlimit', 10)
}

function adjust_view(svg) {
  var bbox = svg.node().getBBox()//getBoundingClientRect()

  svg.attr('width',600)
    .attr('height',400)
    .attr('viewBox',[bbox.x,bbox.y,bbox.width,bbox.height])
    .style('border','2px solid red')
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.0.0/d3.min.js"></script>

如果您想围绕其中心旋转元素,请计算该点 (x,y) 并将这些坐标放入 rotate 方法中:

rotate(${alpha} ${bb.width/2} ${bb.height/2})`

这里是你的代码有那个变化:

console.clear()

var svg = d3.select('body')
  .append('svg')

add_defs(svg)

var g = svg.append('g')

var data = [
  [0, 0],
  [100, 100]
]
var nodes = g.selectAll('.circle')
  .data(data)
  .join('circle')
  .attr('cx', d => d[0])
  .attr('cy', d => d[1])
  .attr('r', 2)
  .attr('stroke', 'none')
  .attr('fill', 'red')

ann_line(svg, data[0][0], data[0][1], data[1][0], data[1][1], "hello")

adjust_view(svg)

function ann_line(svg, ax, ay, bx, by, text) {
  var w = 10
  var dx = bx - ax
  var dy = by - ay
  var l = Math.sqrt(dx * dx + dy * dy)
  dx /= l
  dy /= l
  var a1x = ax + w * dy
  var a1y = ay - w * dx
  var a2x = ax + 2 * w * dy
  var a2y = ay - 2 * w * dx
  var b1x = bx + w * dy
  var b1y = by - w * dx
  var b2x = bx + 2 * w * dy
  var b2y = by - 2 * w * dx

  var path = ['M', a1x, a1y, 'L', b1x, b1y]
  var line = svg.append('path')
    .attr('d', path.join(' '))
    .attr('stroke', 'black')
    .attr('marker-start', 'url(#arrhead)')
    .attr('marker-end', 'url(#arrhead)');

  var cx = (a1x + b1x) / 2
  var cy = (a1y + b1y) / 2
  var alpha = Math.atan(dx / dy) / Math.PI * 180

  const rect = svg.append('rect');

  const label = svg.append('text')
    .text(text)
    .attr('text-anchor', 'middle')
    .attr('alignment-baseline', 'middle')
    .attr('transform', `translate(${cx},${cy}) rotate(${alpha})`)

  const bb = label.node().getBBox();

  rect.attr('stroke', 'none')
    .style('fill', 'steelblue')
    .attr('transform', `translate(${cx-bb.width/2},${cy-bb.height/2}) rotate(${alpha} ${bb.width/2} ${bb.height/2})`)
    .attr('width', bb.width)
    .attr('height', bb.height)
}

function renderTextInCenterOfLine(line, text) {
  const from = parseInt(line.attr('x1'));
  const to = parseInt(line.attr('x2'));
  const y = parseInt(line.attr('y1'));
  const svg = d3.select('svg');
  const textBackground = svg.append('rect')
  const textElement = svg.append('text')
    .text(text)
    .attr('x', (from + to) / 2)
    .attr('y', y)
    .attr('text-anchor', 'middle')
    .attr('alignment-baseline', 'central');

  const box = textElement.node().getBBox();
  const width = box.width + 50; // 
  const height = box.height + 20;
  textBackground
    .attr('x', (from + to - width) / 2)
    .attr('y', y - height / 2)
    .attr('width', width)
    .attr('height', height)
    .style('fill', 'white')

}

function add_defs(svg) {
  var lw = 1
  var w = 6
  var h = 12
  var m = 2
  var lc = 'black'
  var path = ['M', 2 + w, 2, 'L', 2 + w, 2 + h, 'M', 2 + m, 2 + h / 2, 'L', 2, 2 + h / 4, 2 + w - lw, 2 + h / 2, 2, 2 + 3 * h / 4, 'z']
  svg
    .append('defs')
    .append('marker')
    .attr('id', 'arrhead')
    .attr('viewBox', [0, 0, w + 4, h + 4])
    .attr('refX', w + 1)
    .attr('refY', 2 + h / 2)
    .attr('markerWidth', w + 4)
    .attr('markerHeight', h + 4)
    .attr('orient', 'auto-start-reverse')
    .append('path')
    .attr('d', path.join(' '))
    .attr('stroke', lc)
    .attr('stroke-width', lw)
    .attr('fill', lc)
    .attr('stroke-miterlimit', 10)
}

function adjust_view(svg) {
  var bbox = svg.node().getBBox() //getBoundingClientRect()

  svg.attr('width', 600)
    .attr('height', 400)
    .attr('viewBox', [bbox.x, bbox.y, bbox.width, bbox.height])
    .style('border', '2px solid red')
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.0.0/d3.min.js"></script>