D3.js Автоматическое изменение размера шрифта на основе индивидуального радиуса / диаметра узлов

Как сделать так, чтобы D3.js автоматически настраивал размер шрифта для каждого узла на основе их индивидуального радиуса / диаметра?

Я использую стиль, который позволяет автоматически увеличивать размер

  node.append("text")
      .attr("dy", ".3em")
      .style("text-anchor", "middle")
      .text(function(d) { return d.className.substring(0, d.r / 3); })
      .style("font-size", "10px") // initial guess
//This is what gives it increased size...
      .style("font-size", function(d) { return (2 * d.r - 10) / this.getComputedTextLength() * 10 + "px"; })

; * 10 + "px"; })

Этот эффект удаляет текст из меньших узлов. У меня также есть функция масштабирования, которая позволяет увеличить точку, изначально покрывающую 12 пикселей, чтобы покрыть весь мой экран.

.call(d3.behavior.zoom().scaleExtent([1, 200]).on("zoom", zoom))

Есть ли способ, которым я могу автоматически форматировать шрифт узла индивидуально; писать в соответствующих размерах, чтобы при увеличении масштаба вызываемый шрифт узла отображался пропорционально размеру узла, а один размер шрифта подходит всем?

Круги правых списков: ИМЯ (РАЗМЕР)
Я хотел бы получить рабочие примеры, чтобы учиться. Таким образом, при размере изображения у маленькой зеленой точки к северу от окружности рядом с буквой P будут черные нечитаемые слова, пока мы не увеличим масштаб, чтобы увидеть, что написано на окружности. Цель состоит в том, чтобы иметь пропорциональный читаемый шрифт при увеличении..?

2 ответа

Решение

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

Код будет выглядеть примерно так.

// ...
  .append("text")
  .text("text")
  .style("font-size", "1px")
  .each(getSize)
  .style("font-size", function(d) { return d.scale + "px"; });

function getSize(d) {
  var bbox = this.getBBox(),
      cbbox = this.parentNode.getBBox(),
      scale = Math.min(cbbox.width/bbox.width, cbbox.height/bbox.height);
  d.scale = scale;
}

Благодаря ОП и принятому ответу (оба проголосовали); В итоге я сделал это немного по-другому, потому что мой текст был смещен внутри круга, а не проходил прямо по его диаметру. Текстовый узел имеет dy значение, чтобы сдвинуть его вверх или вниз по кругу, и я использую его, чтобы выяснить аккорд, который мне нужно измерить по кругу, чтобы текст по-прежнему автоматически помещался с нужным смещением по высоте. Это также помогло мне лучше сохранять вычисленный размер в атрибуте данных в текстовом элементе, а не изменять исходные данные. Подумал, что это может быть полезно для тех, кто сталкивается с этим в будущем.

jsfiddle

function appendScaledText(parentGroup, textVal, dyShift) {
  parentGroup
    .append("text")
    .attr("dy", dyShift)
    .attr("text-anchor", "middle")
    .attr("dominant-baseline", "central")
    .attr("font-family", "sans-serif")
    .attr("fill", "white")
    .text(textVal)
    .style("font-size", "1px")
    .each(getSize)
    .style("font-size", function() {
      return d3.select(this).attr("data-scale") + "px";
    });
}

function getSize() {
  var d3text = d3.select(this);
  var circ = d3.select(this.previousElementSibling); // in other cases could be parentElement or nextElementSibling
  var radius = Number(circ.attr("r"));
  var offset = Number(d3text.attr("dy"));
  var textWidth = this.getComputedTextLength(); // TODO: this could be bounding box instead
  var availWidth = chordWidth(Math.abs(offset), radius); // TODO: could adjust based on ratio of dy to radius 
  availWidth = availWidth * 0.85; // fixed 15% 'padding' for now, could be more dynamic/precise based on above TODOs
  d3text.attr("data-scale", availWidth / textWidth); // sets the data attribute, which is read in the next step
}

function chordWidth(dFromCenter, radius) {
  if (dFromCenter > radius) return Number.NaN;
  if (dFromCenter === radius) return 0;
  if (dFromCenter === 0) return radius * 2;

  // a^2 + b^2 = c^2
  var a = dFromCenter;
  var c = radius;
  var b = Math.sqrt(Math.pow(c, 2) - Math.pow(a, 2)); // 1/2 of chord length

  return b * 2;
}

В качестве альтернативы вы можете создать текстовую метку, встроенную в каждый узел, следующим образом:

this.g.append("g")
  .attr("class", "labels")
  .selectAll(".mytext")
  .data(NODE_DATA)
  .enter()
  .append("text")
  .text(function (d) {
     return d.LabelText; // Here label text is the text that you want to show in the node
  })
  .style("font-size", "1px")
  .attr("dy", ".35em") // You can adjust it
  .each(function (d) {
      var r = Number(d.Size), a = this.getComputedTextLength(),
          c=0.35, // Same as dy attribute value
          b = 2*Math.sqrt(r*r-c*c), s = Math.min(r, b/a);
      d.fs = s;
  })
  .style("font-size", function (d) {
     return d.fs + "px";
  })
Другие вопросы по тегам