Метка вне дуги (круговая диаграмма) d3.js - PullRequest
44 голосов
/ 08 ноября 2011

Я новичок в d3.js и пытаюсь сделать с ним круговую диаграмму. У меня есть только одна проблема: я не могу получить свои метки за пределами моих дуг ... Эти метки расположены по дуге.centroid

arcs.append("svg:text")
    .attr("transform", function(d) {
        return "translate(" + arc.centroid(d) + ")";
    })
    .attr("text-anchor", "middle")

Кто может мне помочь с этим?

Ответы [ 8 ]

62 голосов
/ 20 ноября 2011

Я могу решить эту проблему - с помощью тригонометрии :).

См. Скрипту: http://jsfiddle.net/nrabinowitz/GQDUS/

По сути, вызов arc.centroid(d) возвращает массив [x,y].Вы можете использовать теорему Пифагора для вычисления гипотенузы, которая является длиной линии от центра пирога до центроида дуги.Затем вы можете использовать вычисления x/h * desiredLabelRadius и y/h * desiredLabelRadius для вычисления желаемого x,y для привязки вашего ярлыка:

.attr("transform", function(d) {
    var c = arc.centroid(d),
        x = c[0],
        y = c[1],
        // pythagorean theorem for hypotenuse
        h = Math.sqrt(x*x + y*y);
    return "translate(" + (x/h * labelr) +  ',' +
       (y/h * labelr) +  ")"; 
})

Единственный недостаток в том, что text-anchor: middle больше не является хорошим выбором- вам лучше установить text-anchor в зависимости от того, на какой стороне пирога мы находимся:

.attr("text-anchor", function(d) {
    // are we past the center?
    return (d.endAngle + d.startAngle)/2 > Math.PI ?
        "end" : "start";
})
16 голосов
/ 18 декабря 2012

Специально для круговых диаграмм функция d3.layout.pie() будет форматировать данные с атрибутами startAngle и endAngle. Радиус может быть любым по вашему желанию (как далеко от центра вы хотите разместить метку).

Объединение этих фрагментов информации с парой тригонометрических функций позволяет определять координаты x и y для меток.

Рассмотрим это Суть / Блок .

Относительно расположения текста по x / y, магия находится в этой строке (отформатирована для удобства чтения):

.attr("transform", function(d) {
  return "translate(" + 
    ( (radius - 12) * Math.sin( ((d.endAngle - d.startAngle) / 2) + d.startAngle ) ) +
    ", " +
    ( -1 * (radius - 12) * Math.cos( ((d.endAngle - d.startAngle) / 2) + d.startAngle ) ) +
  ")";
 })
  • ((d.endAngle - d.startAngle) / 2) + d.startAngle дает нам наш угол (тета) в радианах.
  • (radius - 12) - произвольный радиус, который я выбрал для положения текста.
  • -1 * ось Y инвертирована (см. Ниже).

Используются триггерные функции: cos = adjacent / hypotenuse и sin = opposite / hypotenuse. Но есть пара вещей, которые нам нужно учитывать, чтобы они работали с нашими лейблами.

  1. 0 угол в 12 часов.
  2. Угол по-прежнему увеличивается по часовой стрелке.
  3. Ось Y инвертирована из стандартной декартовой системы координат. Положительный у в направлении 6 часов - вниз.
  4. Положительный х по-прежнему в направлении 3 часа - вправо.

Это немного сбивает с толку и в основном приводит к обмену sin и cos. Тогда наши функции триггера становятся: sin = adjacent / hypotenuse и cos = opposite / hypotenuse.

Подставляя имена переменных, мы имеем sin(radians) = x / r и cos(radians) = y / r. После некоторой алгебраической манипуляции мы можем получить обе функции в терминах x и y соответственно r * sin(radians) = x и r * cos(radians) = y. Оттуда просто включите их в атрибут transform / translate.

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

.style("text-anchor", function(d) {
    var rads = ((d.endAngle - d.startAngle) / 2) + d.startAngle;
    if ( (rads > 7 * Math.PI / 4 && rads < Math.PI / 4) || (rads > 3 * Math.PI / 4 && rads < 5 * Math.PI / 4) ) {
      return "middle";
    } else if (rads >= Math.PI / 4 && rads <= 3 * Math.PI / 4) {
      return "start";
    } else if (rads >= 5 * Math.PI / 4 && rads <= 7 * Math.PI / 4) {
      return "end";
    } else {
      return "middle";
    }
  })

Это сделает метки с 10:30 до 1:30 и с 4:30 до 7:30, якорь в середине (они выше и ниже), метки с 1:30 до 4:30 часов привязки слева (они справа), а надписи с 7:30 до 10:30 часов привязки справа (они слева).

Те же формулы можно использовать для любого радиального графика D3, единственное отличие состоит в том, как вы определяете угол.

Надеюсь, это поможет кому-нибудь наткнуться на него!

15 голосов
/ 25 ноября 2011

Спасибо!

Я нашел другой способ решения этой проблемы, но ваш, похоже, лучше :-)

Я создал вторую дугу с большим радиусом и использовал ее для позиционирования моих меток.

///// Arc Labels ///// 
// Calculate position 
var pos = d3.svg.arc().innerRadius(r + 20).outerRadius(r + 20); 

// Place Labels 
arcs.append("svg:text") 
       .attr("transform", function(d) { return "translate(" + 
    pos.centroid(d) + ")"; }) 
       .attr("dy", 5) 
       .attr("text-anchor", "middle") 
       .attr("fill", function(d, i) { return colorL(i); }) //Colorarray Labels
       .attr("display", function(d) { return d.value >= 2 ? null : "none"; })  
       .text(function(d, i) { return d.value.toFixed(0) + "%"});
5 голосов
/ 19 апреля 2012

Не знаю, помогает ли это, но я смог создать дуги, в которых я размещаю текст как на дуге, так и за ее пределами. В одном случае, когда я размещаю величины дуги внутри дуг, я поворачиваю текст на дуге, чтобы соответствовать углу дуги. В другом, где я размещаю текст вне дуги, он просто горизонтален. Код находится по адресу: http://bl.ocks.org/2295263

My Best,

Frank

3 голосов
/ 30 мая 2015

да, детка, это SOHCAHTOA

function coordinates_on_circle(hyp, angle){
  var radian= angle * Math.PI / 180 //trig uses radians
  return {
    x: Math.cos(radian) * hyp, //adj = cos(r) * hyp
    y: Math.sin(radian) * hyp //opp = sin(r) * hyp
  }
}
var radius=100
var angle=45
coordinates_on_circle(radius, angle)
1 голос
/ 22 июля 2016

Я добился того же, нарисовав проценты в виде меток вне круговой диаграммы, вот код http://bl.ocks.org/farazshuja/e2cb52828c080ba85da5458e2304a61f

g.append("text")
        .attr("transform", function(d) {
        var _d = arc.centroid(d);
        _d[0] *= 2.2;   //multiply by a constant factor
        _d[1] *= 2.2;   //multiply by a constant factor
        return "translate(" + _d + ")";
      })
      .attr("dy", ".50em")
      .style("text-anchor", "middle")
      .text(function(d) {
        if(d.data.percentage < 8) {
          return '';
        }
        return d.data.percentage + '%';
      });
1 голос
/ 18 сентября 2013

Это был недорогой ответ, которым я был доволен. Он выталкивает все метки по горизонтали (вот где у меня было дополнительное пространство):

g.append("text")
  .attr("transform", function(d) { 
      var pos = arc.centroid(d); 
      return "translate(" + (pos[0] + (.5 - (pos[0] < 0)) * radius) + "," + (pos[1]*2) + ")"; 
  })
  .attr("dy", ".35em")
  .style("text-anchor", function(d) { 
      return arc.centroid(d)[0] > 0 ? "start" : "end";
   })
  .text(function(d) { return d.label; });
1 голос
/ 30 января 2013

Следующий CoffeeScript работал для меня, чтобы метки оставались внутри секторов, но по направлению к внешнему краю:

attr 'transform', (d) ->
  radius = width / 2 # radius of whole pie chart
  d.innerRadius = radius * 0.5
  d.outerRadius = radius * 1.5
  'translate(' + arc.centroid(d) + ')'
...