Как я могу изменить функцию Безье, используемую diagonal() D3?

#d3.js #tree #bezier #orgchart

#d3.js #дерево #безье #orgchart

Вопрос:

Я создаю организационную диаграмму в D3 на основе диаграммы Бернхарда Зуба D3.js Организационная структура. Организационная диаграмма моделирует организацию, в которой у любого данного человека (представленного белым квадратом) может быть около сотни человек непосредственно под ними (очень плоская древовидная структура с черной кривой Безье, представляющей каждое родительско-дочернее отношение).

Вот скриншот части дерева: плоское дерево

А вот увеличение нижней части родительского узла на рисунке выше: введите описание изображения здесь

Проблема в том, что связи между дочерними и родительскими узлами имеют тенденцию объединяться вместе, что приводит к очень толстой черной линии с очень постепенным наклоном, что может быть немного неприятно.

Функция, которую я использую для создания ссылок, выглядит следующим образом:

 // Diagonal function
var diagonal = d3.svg.diagonal()
.source(function (d) {
    return {
        x: d.source.x   (rectW / 2),
        y: d.source.y   rectH - 10
    };
})
.target(function (d) {
    return {
        x: d.target.x   (rectW / 2),
        y: d.target.y   10
    };
})
.projection(function (d) {
    return [d.x, d.y];
});
 

Здесь — rectW ширина каждого узла и rectH высота каждого узла.

Что я хотел бы сделать, так это внести некоторые небольшие изменения в функцию Безье, используемую для создания ссылок. В частности, я хотел бы немного сгладить контрольные точки, чтобы кривые в начале и конце кривой были более выразительными. Если кто-нибудь может показать мне, как изменить функцию, используемую diagonal() для генерации кривой Безье, я могу выяснить остальное.

Ответ №1:

Если вы посмотрите на исходный код svg.diagonal, я не вижу прямого способа настроить только контрольные точки. Можно подумать, что для этого можно использовать проекцию, но это преобразует все 4 точки, используемые для генерации пути. Теперь, я думаю, мы могли бы немного повозиться с проекцией и сделать что-то вроде этого:

 <!DOCTYPE html>
<html>

<head>
  <script data-require="d3@3.5.17" data-semver="3.5.17" src="https://d3js.org/d3.v3.min.js"></script>
</head>

<body>
  <script>
    var data = [{
        source: {
          x: 10,
          y: 10
        },
        target: {
          x: 200,
          y: 200
        }
      }, {
        source: {
          x: 50,
          y: 50
        },
        target: {
          x: 200,
          y: 200
        }
      }];

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

    var diagonal = d3.svg.diagonal()
      .projection(function(d) {
        if (!this.times) this.times = 0;
        this.times  ;
        console.log(this.times);
        if (this.times === 1) {
          return [d.x, d.y];
        } else if (this.times === 2) {
          return [d.x - 25, d.y]
        } else if (this.times === 3) {
          return [d.x   25, d.y];
        } else if (this.times === 4) {
          this.times = 0;
          return [d.x, d.y];
        }
      });

    svg.selectAll('path')
      .data(data)
      .enter()
      .append('path')
      .attr('d', diagonal)
      .style('fill', 'none')
      .style('stroke', 'black');
  </script>
</body>

</html> 

Возможно, я слишком много думаю об этом. Вероятно, вам просто лучше нарисовать дугу самостоятельно:

     <!DOCTYPE html>
    <html>

    <head>
      <script data-require="d3@3.5.17" data-semver="3.5.17" src="https://d3js.org/d3.v3.min.js"></script>
    </head>

    <body>
      <script>
        var data = [{
            source: {
              x: 10,
              y: 10
            },
            target: {
              x: 200,
              y: 200
            }
          }, {
            source: {
              x: 200,
              y: 10
            },
            target: {
              x: 10,
              y: 200
            }
          }];

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

        svg.selectAll('path')
          .data(data)
          .enter()
          .append('path')
          .attr('d', function(d){
              var s = d.source,
                  t = d.target,
                  m = (s.y   t.y) / 2,
                  p0 = [s.x, s.y],
                  p1 = [s.x, m],
                  p2 = [t.x, m],
                  p3 = [t.x, t.y];
              
              // adjust constrol points
              p1[0] -= 25;
              p2[0]  = 25;
                  
              return "M"   p0   "C"   p1   " "   p2   " "   p3;    
          })
          .style('fill', 'none')
          .style('stroke', 'black');
      </script>
    </body>

    </html> 

Комментарии:

1. Спасибо за отличный ответ. Можете ли вы объяснить, почему / как это работает? Что this относится к функции проекции?

2. @IsaacLyman, this относится к самой функции проекции. По сути, я привязываю свойство к функции, чтобы запомнить, сколько раз она вызывалась. О, радости JavaScript 🙂

3. Это имеет смысл. Итак, гарантируется ли, что функция проекции будет вызываться четыре раза для любой заданной диагональной кривой?

4. @IsaacLyman, да. Ознакомьтесь с исходным кодом, на который я ссылаюсь. Кроме того, см. Измененный ответ выше. Мой оригинальный не будет работать для нескольких путей.

5. @IsaacLyman, просто думаю об этом вопросе. Хотя мой первоначальный ответ классный и демонстрирует «удовольствие» от JavaScript, я сильно переоцениваю его. Я добавил новый ответ, который намного более понятен.