Целевой путь SVG к середине прямоугольного элемента

Я использую библиотеку D3 для рисования взаимосвязанного графика элементов. Мои узлы circles и rects соединены ориентированной линией paths.

Моя проблема в том, что строки, указывающие на элемент rects, имеют уродливую визуализацию, потому что линия заканчивается в верхнем левом углу прямоугольника, а не в его центре (как это происходит для кругов).

Как я могу сделать так, чтобы линии пути были ориентированы на центр как элементов circles, так и элементов rect?


Код для определения defs стрелок:

svg.append('defs')
  .append('marker')
  .attr('id', 'arrow')
  .attr('viewBox', '0 -5 10 10')
  .attr('refX', 17) // As far as I understood this provides the distance from the end of the path line.
  .attr('refY', -0.1)
  .attr('markerWidth', 6)
  .attr('markerHeight', 6)
  .attr('orient', 'auto')
  .attr('fill', function() {
    return 'red';
  })
  .append('path')
  .attr('d', 'M0,-5L10,0L0,5');

Определение ориентированных ссылок:

let links = svg.selectAll('.link')
  .data(data.links)
  .enter()
  .append('path')
  .attr('id', function (d) {
    return d.id;
  })
  .attr('class', 'link')
  .attr('fill', 'none')
  .attr('stroke-width', 1.2)
  .attr('marker-end', 'url(#arrow)')
  .attr('stroke', function() {
    return 'blue';
  })
  .style('cursor', 'pointer');

Определение квадратов

let squares = svg.selectAll('.square')
  .data(data.squares, function(d) {
    return d.id;
  })
  .enter().append('g')
  .call(dragger)
  .attr('class', 'square')
  .style('cursor', 'pointer');

squares.append('rect')
  .attr('width', 10)
  .attr('height', 10)
  .attr('fill', function (d) {
    return '#fff';
  })
  .style('opacity', 0.1)
  .style('stroke', function() {
    return '#555';
  })
  .style('stroke-width', '2');

На следующем скриншоте вы можете увидеть, как он себя ведет. Круги и прямоугольники имеют низкую непрозрачность, чтобы показать проблему с целью пути.

введите здесь описание изображения


ОБНОВЛЕНИЕ

Добавлено определение и использование функции tick.

simulation
  .nodes(data.nodes)
  .on('tick', _tick);

simulation
  .force('link')
  .distance(80)
  .links(data.links);

simulation.alpha(1).restart();

function _tick() {
  links.attr('d', function(d) {
    let dx = d.target.x - d.source.x;
    let dy = d.target.y - d.source.y;
    let 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);
  });
  circles.attr('transform', function (d) {
    return 'translate(' + d.x + ',' + d.y + ')';
  });
  squares.attr('transform', function (d) {
    return 'translate(' + d.x + ',' + d.y + ')';
  });
}

person fredmaggiowski    schedule 24.08.2017    source источник


Ответы (2)


  1. Вы можете перевести квадрат? Если вы можете перевести его на половину ширины квадрата.
  2. Можете ли вы найти конец этого пути на квадрате, как x2, y2? Плюс эта половина вашей квадратной ширины.

"надеюсь, это вас вдохновило"

squares.append('rect')
  .attr('width', 10)
  .attr('height', 10)
  .attr("transform", "translate(-5,0)")
  .attr('fill', function (d) {
    return '#fff';
  })
person KEKUATAN    schedule 24.08.2017
comment
Перевод квадрата не приходило мне в голову. Я пробовал с параметрами, которые вы предложили, но они не правильные, вместо этого использование .attr("transform", "translate(-5,-5)") выглядит лучше (учитывайте, что каждый элемент можно перетаскивать, поэтому мне пришлось перевести обе оси). Я жду дальнейших ответов, иначе я приму ваши. - person fredmaggiowski; 24.08.2017
comment
PS: Вам не нужно объединять строки и числа, как в "translate("+(-5)+", "+0+")", просто запишите числа в строке, как в "translate(-5,-5)". - person fredmaggiowski; 24.08.2017
comment
я обычно использую динамический параметр, как это работает? - person KEKUATAN; 24.08.2017
comment
Ага, представил ;) Да, с параметрами -5,-5 работало. В любом случае, я хотел бы дождаться других ответов, прежде чем принять это, потому что, как я уже сказал в вопросе, я хотел бы знать, есть ли способ сделать точку path центром rect, а не ее углом, и я кажется, что перевод rect - это своего рода обходной путь. :) - person fredmaggiowski; 24.08.2017

То, что у вас есть прямо сейчас, является ожидаемым поведением. В симуляции силы (я полагаю, вы запускаете симуляцию силы) функция tick изменяет свойства x и y базового объекта, и вы можете использовать их по своему усмотрению.

Поскольку вы не поделились своей функцией tick, я полагаю, что вы обновляете положение прямоугольников x и y следующим образом:

squares.attr("x", function(d) {
    return d.x
}).attr("y", function(d) {
    return d.y
});

Если это действительно так, верхний левый угол прямоугольников соответствует координатам d.x и d.y. И, поскольку вы используете одни и те же свойства для рисования пути, пути будут идти из одного верхнего левого угла в другой.

Это легко показать, взгляните на эту демонстрацию:

var width = 200;
var height = 200;

var rectSide = 40;

var svg = d3.select("body")
  .append("svg")
  .attr("width", width)
  .attr("height", height);

var nodes = [{
  name: "foo",
  color: "blue"
}, {
  name: "bar",
  color: "green"
}, {
  name: "baz",
  color: "red"
}];

var links = [{
  "source": 0,
  "target": 1
}, {
  "source": 0,
  "target": 2
}];

var simulation = d3.forceSimulation()
  .force("link", d3.forceLink().distance(100))
  .force("charge", d3.forceManyBody().strength(-50))
  .force("center", d3.forceCenter(width / 2, height / 2));

var node = svg.selectAll(null)
  .data(nodes)
  .enter()
  .append("rect")
  .attr("width", rectSide)
  .attr("height", rectSide)
  .attr("fill", function(d) {
    return d.color
  });

var link = svg.selectAll(null)
  .data(links)
  .enter()
  .append("line")
  .style("stroke", "#222")
  .style("stroke-width", 2);

simulation.nodes(nodes);
simulation.force("link")
  .links(links);

simulation.on("tick", function() {

  link.attr("x1", function(d) {
      return d.source.x;
    })
    .attr("y1", function(d) {
      return d.source.y;
    })
    .attr("x2", function(d) {
      return d.target.x;
    })
    .attr("y2", function(d) {
      return d.target.y;
    })

  node.attr("x", function(d) {
    return d.x
  }).attr("y", function(d) {
    return d.y
  });

});
<script src="https://d3js.org/d3.v4.js"></script>

Решение. Вы можете либо переместить прямоугольники, либо пути.

Поскольку ваш вопрос конкретно касается путей, решение простое: добавьте половину ширины и половины высоты к целевым и исходным координатам:

link.attr("x1", function(d) {
        return d.source.x + rectangleWidth / 2;
    })
    .attr("y1", function(d) {
        return d.source.y + rectangleHeight / 2;
    })
    .attr("x2", function(d) {
        return d.target.x + rectangleWidth / 2;
    })
    .attr("y2", function(d) {
        return d.target.y + rectangleHeight / 2;
    })

Вот демо:

var width = 200;
var height = 200;

var rectSide = 40;

var svg = d3.select("body")
  .append("svg")
  .attr("width", width)
  .attr("height", height);

var nodes = [{
  name: "foo",
  color: "blue"
}, {
  name: "bar",
  color: "green"
}, {
  name: "baz",
  color: "red"
}];

var links = [{
  "source": 0,
  "target": 1
}, {
  "source": 0,
  "target": 2
}];

var simulation = d3.forceSimulation()
  .force("link", d3.forceLink().distance(100))
  .force("charge", d3.forceManyBody().strength(-50))
  .force("center", d3.forceCenter(width / 2, height / 2));

var node = svg.selectAll(null)
  .data(nodes)
  .enter()
  .append("rect")
  .attr("width", rectSide)
  .attr("height", rectSide)
  .attr("fill", function(d) {
    return d.color
  });

var link = svg.selectAll(null)
  .data(links)
  .enter()
  .append("line")
  .style("stroke", "#222")
  .style("stroke-width", 2);

simulation.nodes(nodes);
simulation.force("link")
  .links(links);

simulation.on("tick", function() {

  link.attr("x1", function(d) {
      return d.source.x + rectSide / 2;
    })
    .attr("y1", function(d) {
      return d.source.y + rectSide / 2;
    })
    .attr("x2", function(d) {
      return d.target.x + rectSide / 2;
    })
    .attr("y2", function(d) {
      return d.target.y + rectSide / 2;
    })

  node.attr("x", function(d) {
    return d.x
  }).attr("y", function(d) {
    return d.y
  });

});
<script src="https://d3js.org/d3.v4.js"></script>

person Gerardo Furtado    schedule 25.08.2017
comment
Привет, @Gerardo, спасибо за подробный ответ, ты правильно предположил, что я использую forceSimulation. Что касается функции tick, то она немного отличается, я добавил ее в вопрос. Как видите, я использую d attr, а не x1,x2,y1,y2, как вы думаете, возможно ли это? - person fredmaggiowski; 28.08.2017
comment
Конечно, просто добавьте половину ширины и половины высоты к d.target.x и d.target.y. - person Gerardo Furtado; 28.08.2017
comment
Извините, вы имеете в виду объединение двух последних значений в итоговую строку или при вычислении dx и dy? Спасибо :) - person fredmaggiowski; 28.08.2017
comment
Я вычислил и объявил let targetX = d.target.x + (10/2); (то же самое для y) и использовал их. Теперь проблема в том, что пути правильно настроены на центр квадрата, но когда пути нацелены на круг, они теперь находятся за границей кругов. - person fredmaggiowski; 28.08.2017
comment
Оба. И не забывайте скобки, потому что вы объединяете строки и числа. - person Gerardo Furtado; 28.08.2017
comment
Что ж, для решения проблемы кругов лучше всего создать минимально воспроизводимый пример. - person Gerardo Furtado; 28.08.2017
comment
И, пожалуйста, когда у вас будет минимальный рабочий код, разместите его как новый вопрос, иначе я буду единственным, кто увидит его. - person Gerardo Furtado; 28.08.2017
comment
Хорошо, хорошее предложение. Я свяжу это здесь, когда закончу, чтобы вы могли его увидеть, если хотите помочь мне с новым вопросом :) - person fredmaggiowski; 28.08.2017