d3partition sunbursts: вращающийся текст и другие глюки - PullRequest
0 голосов
/ 03 января 2019

d3.hierarchy в целом и d3.partition в частности являются одними из моих любимых инструментов из этой великой библиотеки.Но я впервые применяю их к радиальным «солнечным лучам», а именно, кажется, отсутствуют некоторые важные биты.

Ниже приведен пример MCVE , генерирующий эти солнечные лучи, чтобы проиллюстрироватьмои основные вопросы: sample sunburst

Поворот текста

Поворот текстовых меток за пределы 180 градусов является распространенной проблемой;ср это недавнее сообщение SO

После @ собственного примера mbostock имеет этот код преобразования:

    .attr("transform", function(d) { 
                    const x = (d.x0 + d.x1) / 2 * 180 / Math.PI;
                    const y = (d.y0 + d.y1) / 2;
                    return `rotate(${x - 90}) translate(${y},0) rotate(${x < 180 ? 0 : 180})`;
                    })           

, но с использованием этого translate() в преобразованииотбрасывает текст далеко от графика?

Таким образом, приведенный ниже код выполняет вращение на основе одного и того же среднего радиуса внутренней / внешней дуги и размещает метки справа (углы <180) таким же образом, за исключениемчто он использует <code>text-anchor вариант выравнивания, чтобы выровнять метки обеих глубин по одному и тому же общему кругу.

  • 1 Мне пришлось изменить радиус взломанным фактором 1.22 чтобы подтолкнуть (правую) метки близко к линии; почему ?

  • 2 Это прекрасно работает для всех меток, кроме корневых, которые я хочу центрировать; как я могу это сделать?

  • 3 Но новые левые (> 180 градусов) метки расположены неправильно, и мне нужно было добавитьспецифичная для глубины hackRatio, чтобы смягчить перевод, чтобы даже приблизить их; почему?

  • 4 Более серьезная проблема состоит в том, чтобы выяснить, как использовать тот же прием text-anchor выравнивания, используемый для других меток?Я хочу сделать вращение «на месте» до применения выравнивания ; как я могу это сделать?

Как должна работать d3.hierarchy.sum ()?

Надписи также включают freq атрибут в скобках.Данные yearHier предоставляют этот атрибут только для данных листья .Из d3.hierarchy.sum () и d3.partition doc у меня сложилось впечатление, что при вызове корня sum() в корне будут вычисляться суммы для не-листьев ("... для этого узла и каждого потомка в обходе после заказа "); почему эти частоты равны нулю?

Итак, в качестве альтернативы я попытался использовать данные yearHierFreq, которые включают общие частоты для корня и каждого года.Но используя его, d3.partition выделяет только две трети дуг лет и только половину дуг месяцев в каждом году;см. рендеринг ниже.Это как если бы внутренние узлы 'freq были подсчитаны дважды; почему ?

using yearHierFreq data with all freq provided

<script src="https://d3js.org/d3.v5.min.js"></script>
<script>

var ColorNames = ["Blue", "Gray", "Purple", "Fuchsia", "Aqua", "Maroon", "Olive", "Yellow", "Teal", "Navy", "Green", "Silver", "Red", "Lime"];

// following after http://bl.ocks.org/kerryrodden/7090426

var width = 900;
var height = 900;
var radius = Math.min(width, height) / 2 * 0.7;

var vis = d3.select("#chart").append("svg:svg")
    .attr("width", width)
    .attr("height", height)
    .append("svg:g")
    .attr("id", "container")
    .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");

var partition = d3.partition()
    .size([2 * Math.PI, radius * radius]);

var arc = d3.arc()
    .startAngle(function(d)  { return d.x0; })
    .endAngle(function(d)    { return d.x1; })
    .innerRadius(function(d) { return Math.sqrt(d.y0); })
    .outerRadius(function(d) { return Math.sqrt(d.y1); });

function createSunburst(json) {

 vis.append("svg:circle")
      .attr("r", radius)
      .style("opacity", 0);

  // Turn the data into a d3 hierarchy and calculate the sums.
  var root = d3.hierarchy(json)
      .sum(function(d) { return d.freq; })
      .sort(function(a, b) { return b.name - a.name; });

  var partition = d3.partition()
    .size([2 * Math.PI, radius * radius]);

  var nodes = partition(root).descendants();

   var path = vis.data([json]).selectAll("path")
      .data(nodes)
      .enter().append("svg:path")
      .attr("display", function(d) { return d.depth ? null : "none"; })
      .attr("d", arc)
      .attr("fill-rule", "evenodd")
      .style("fill", function(d,i) { return ColorNames[i % 14]; })
      .style("opacity", 1);

    var texts = vis.selectAll("text")
        .data(nodes)
.enter().append("text")

/*      .attr("transform", function(d) { 
            // https://beta.observablehq.com/@mbostock/d3-sunburst
                        const x = (d.x0 + d.x1) / 2 * 180 / Math.PI;
                        const y = (d.y0 + d.y1) / 2;
                        return `rotate(${x - 90}) translate(${y},0) rotate(${x < 180 ? 0 : 180})`;
                        })           
*/
        .attr("transform", function(d) { 
            var deg;
            if (d.depth==0) {
                deg = 90;
            } else {
                deg = 180 / Math.PI * (d.x0 +d.x1) / 2;
            }
            var trans = `rotate(${deg-90})`;
            if (deg > 180) { 
                var hackRatio = (d.depth == 0) ? 160 : 130;
                var yavg = (d.y0 + d.y1) / 2 / hackRatio;
                trans += ` translate(${yavg},0) rotate(180)`; 
            }
            return trans})

        .attr("x", radius / 1.22 )
        .text(function(d) {return  `${d.data.name} (${d.data.freq})`;})

        .attr("text-anchor", function(d) { 
            var alignVec = ["center","end","start"];
            return alignVec[d.depth];})
 };

var yearHier = {"freq": 0, "name": "AllYears", "children": [{"freq": 0, "name": "2017", "children": [{"freq": 5, "name": "January", "children": []}, {"freq": 17, "name": "February", "children": []}, {"freq": 16, "name": "March", "children": []}, {"freq": 2, "name": "April", "children": []}, {"freq": 18, "name": "May", "children": []}, {"freq": 14, "name": "June", "children": []}, {"freq": 17, "name": "July", "children": []}, {"freq": 2, "name": "August", "children": []}, {"freq": 10, "name": "September", "children": []}, {"freq": 6, "name": "October", "children": []}, {"freq": 10, "name": "November", "children": []}, {"freq": 17, "name": "December", "children": []}]}, {"freq": 0, "name": "2018", "children": [{"freq": 14, "name": "January", "children": []}, {"freq": 6, "name": "February", "children": []}, {"freq": 13, "name": "March", "children": []}, {"freq": 15, "name": "April", "children": []}, {"freq": 15, "name": "May", "children": []}, {"freq": 4, "name": "June", "children": []}, {"freq": 7, "name": "July", "children": []}, {"freq": 12, "name": "August", "children": []}, {"freq": 17, "name": "September", "children": []}, {"freq": 8, "name": "October", "children": []}, {"freq": 10, "name": "November", "children": []}, {"freq": 12, "name": "December", "children": []}]}, {"freq": 0, "name": "2019", "children": [{"freq": 10, "name": "January", "children": []}, {"freq": 12, "name": "February", "children": []}, {"freq": 15, "name": "March", "children": []}, {"freq": 6, "name": "April", "children": []}, {"freq": 14, "name": "May", "children": []}, {"freq": 3, "name": "June", "children": []}, {"freq": 6, "name": "July", "children": []}, {"freq": 9, "name": "August", "children": []}, {"freq": 18, "name": "September", "children": []}, {"freq": 4, "name": "October", "children": []}, {"freq": 8, "name": "November", "children": []}, {"freq": 16, "name": "December", "children": []}]}]}

var yearHierFreq = {"freq": 355, "name": "AllMonths", "children": [{"freq": 83, "name": "2017", "children": [{"freq": 4, "name": "January", "children": []}, {"freq": 7, "name": "February", "children": []}, {"freq": 4, "name": "March", "children": []}, {"freq": 11, "name": "April", "children": []}, {"freq": 16, "name": "May", "children": []}, {"freq": 8, "name": "June", "children": []}, {"freq": 5, "name": "July", "children": []}, {"freq": 3, "name": "August", "children": []}, {"freq": 10, "name": "September", "children": []}, {"freq": 3, "name": "October", "children": []}, {"freq": 2, "name": "November", "children": []}, {"freq": 10, "name": "December", "children": []}]}, {"freq": 156, "name": "2018", "children": [{"freq": 14, "name": "January", "children": []}, {"freq": 8, "name": "February", "children": []}, {"freq": 12, "name": "March", "children": []}, {"freq": 10, "name": "April", "children": []}, {"freq": 16, "name": "May", "children": []}, {"freq": 17, "name": "June", "children": []}, {"freq": 19, "name": "July", "children": []}, {"freq": 14, "name": "August", "children": []}, {"freq": 4, "name": "September", "children": []}, {"freq": 17, "name": "October", "children": []}, {"freq": 19, "name": "November", "children": []}, {"freq": 6, "name": "December", "children": []}]}, {"freq": 116, "name": "2019", "children": [{"freq": 4, "name": "January", "children": []}, {"freq": 15, "name": "February", "children": []}, {"freq": 12, "name": "March", "children": []}, {"freq": 8, "name": "April", "children": []}, {"freq": 3, "name": "May", "children": []}, {"freq": 5, "name": "June", "children": []}, {"freq": 13, "name": "July", "children": []}, {"freq": 19, "name": "August", "children": []}, {"freq": 12, "name": "September", "children": []}, {"freq": 11, "name": "October", "children": []}, {"freq": 5, "name": "November", "children": []}, {"freq": 9, "name": "December", "children": []}]}]}

createSunburst(yearHier);
d3.select(self.frameElement).style("height", "700px");
</script>

1 Ответ

0 голосов
/ 16 января 2019

Вы можете получить следующий результат

enter image description here

с этим кодом

var radiusSeparation = 5;

var texts = vis.selectAll("text")
  .data(nodes)
  .enter().append("text")
    .attr("transform", function(d) {
        if (d.depth == 0) return null;
        d.deg = 180 / Math.PI * (d.x0 + d.x1) * 0.5;
        var translate = d.depth == 1 ? Math.sqrt(d.y1)-radiusSeparation : Math.sqrt(d.y0)+radiusSeparation;
        var trans = `rotate(${(d.deg-90).toFixed(2)}) translate(${translate.toFixed(2)},0)`;
        if (d.deg > 180) {
            trans += ` rotate(180)`;
        }
        return trans;
    })
    .text( d => `${d.data.name} (${d.value})` )
    .attr("text-anchor", function(d) {
        if (d.depth == 0) return "middle";
        if (d.depth == 1) return d.deg < 180 ? "end" : "start";
        return d.deg < 180 ? "start" : "end";
    })
    .attr("dominant-baseline", "middle")
  • использоватьрадиус ваших дуг для позиционирования текста.Используйте небольшое расстояние, чтобы текст не касался дуг

  • , чтобы сохранить значение deg в элементе данных, чтобы вы могли использовать его для привязки текста

  • переключение привязки текста на основе значения градуса

  • обработка глубины = 0 как особой во всех случаях

  • выравнивание текста по вертикалив середину с dominant-baseline

  • d3.hierarchy.sum () сохраняет результат в d.value, поэтому используйте его в тексте

...