user10914414
user10914414

Reputation: 21

Textpath in circle packing not zooming correctly

I am using d3.js version 5 and circle-pack and d3 arc generator. Circle pack and zoom code is standard and used at many places. When I try to put arc text along circle, it works fine and it loads fine but when I try to zoom in or zoom out , text/labels does not accordingly put along with circle border. Here is my code: https://codepen.io/dmd7/pen/BvMwbr

I tried 'd3-circle-text' plug-in as mentioned here in 'd3-circle-text' on zoomable circle-pack but did not work as it is using older version of d3 plus zoom in functionality is different there.

Also, wanted to try what is achieved here: http://nbremer.github.io/occupations/ but did not work as again converting from older d3 version to newer d3 version makes it harder.

Here is my code: https://codepen.io/dmd7/pen/BvMwbr

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <title>test</title>
        <script src="https://d3js.org/d3.v5.min.js"></script>
        <style>
            text {
            font-size: 11px;
            }
            text.parent {
            fill: #1f77b4;
            }
            text {
            font: 10px sans-serif;
            }
            /*.text {
              cursor: pointer;
            }*/
            text:hover {
            /*color: #FF0000; */
            font-weight: bold;
            stroke: #000;
            stroke-width: 0.5px;
            text-decoration: underline;
            }
            circle {
                fill: #ccc;
                stroke: #999;
                pointer-events: all;
                cursor: pointer;
                opacity: 0.55;
            }
            circle.parent {
                fill: #1f77b4;
                fill-opacity: .1;
                stroke: steelblue;
            }
            circle.parent:hover {
                stroke: #ff7f0e;
                stroke-width: .5px;
            }
            /*circle.child {
                pointer-events: none;
            }*/
            /*Node hover*/
             /*.node {
                cursor: pointer;
            }
            .node:hover {
                stroke: #000;
                stroke-width: 1.5px;
            }
            .node--leaf {
                fill: white;
            }
            */

            /*Circle text*/
        .label {
            font: 11px "Helvetica Neue", Helvetica, Arial, sans-serif;
            text-anchor: middle;
            text-shadow: 0 1px 0 #fff, 1px 0 0 #fff, -1px 0 0 #fff, 0 -1px 0 #fff;
        }

        .label,
        .node--root,
        .node--leaf {
            pointer-events: none;
        }
        .arc-path {
            visibility: hidden;
        }
        </style>
    </head>
    <body>
        <div id="container">
            <svg width="700" height="700" id="cir_svg"></svg>
        </div>
    </body>
</html>

and here is javaScript code:

var svg, margin, diameter, g, pack, focus, view, circle, node;
var maxNumLevels = 3;

document.addEventListener("DOMContentLoaded", function (e) {

    svg = d3.select("svg"),
        margin = 20,
        diameter = +svg.attr("width");
        g = svg.append("g").attr("transform", "translate(" + diameter / 2 + "," + diameter / 2 + ")");

    pack = d3.pack()
        .size([diameter - margin, diameter - margin])
        .padding(2);

    initialSpecialCase();
});

function generateLevels(pivotLevel) {
    pivNum = parseInt(pivotLevel.match(/VirusL(\d+)_s/)[1]);
    var r  = '';
  for (var n = 0; n <= maxNumLevels; ++n) {
    if(n!=0) {
        r += ","; 
    }
    r += "VirusL" + (pivNum + n) + "_s";
    }
    curLevelsStr = r;
  return r;
}

function extractTaxIdFromName(nameTaxId) {
    var idx = nameTaxId.lastIndexOf(", taxid:");
    if(idx != -1) {
        return nameTaxId.substring(idx + 8);
    }
    return '';
}

function initialSpecialCase() {
    var data1 = {};
    d3.json("https://api.myjson.com/bins/1h5co8").then(function(dta) {
        console.info("Data from ajax: ", dta);
        data1 = {
            'name'  : 'root',
            'field' : '',
        'value' : dta.response.numFound,
        'pivot' : dta['facet_counts']['facet_pivot'][generateLevels('VirusL0_s')]
        };
        createCircles(data1);
    });
}

function mapData(d) {
    if (!d.hasOwnProperty('pivot'))
      return;

  d.hasChildren = true;

  var maxPivName = "VirusL" + (pivNum + maxNumLevels - 1).toString() + "_s";
  if (d.field === maxPivName)
      return;

  var cnodes = d.pivot;
  for (c in cnodes) {
      if (!cnodes[c].hasOwnProperty('name')) {
          cnodes[c].name = cnodes[c].value;
          cnodes[c].value = cnodes[c].count;
      }
  }
  return cnodes;
}

function arcSVG(mx0, my0, r, larc, sweep, mx1, my1) {
  return 'M'+mx0+','+my0+' A'+r+','+r+' 0 '+larc+','+sweep+' '+mx1+','+my1;
}

function createCircles(data) {

    var root = d3.hierarchy(data, mapData)
        .sort(function(a, b) { return b.value - a.value; });
    console.info(root);

    focus = root;
  var nodes = pack(root).descendants();

  var nodeg = g.selectAll(".node")
        .data(nodes)
        .enter().append("g")
        .attr("class", function(d) {
            return d.children ? "node" : "leaf node"; 
        })
       ;

  nodeg.append("circle")
    .attr("class", function(d) { return d.children ? "parent" : "child"; })
    .attr("id", function(d) { return 'c' + extractTaxIdFromName(d.data.name); })
    .on("click", function(d) { 
      selectedVirus = d.data.name;
      if (!d.children && d.parent) {
        zoom(d); d3.event.stopPropagation();
      }
      else if (focus !== d) { 
        zoom(d); 
        d3.event.stopPropagation(); 
      } 
    });

    nodeg.append("title")
        .text(function(d) {
            return d.data.name;
        });

    nodeg.each(function(d, i) {
        var gg = d3.select(this);
        if(d.depth === 3) {
//          gg.append('text')
//                  .style('font-size', d3.min([3 * d.r / d.data.name.length, 16]))
//                  .attr('dy', '0.3em')
//                  .text(d.data.name);
        }
        else if(d.depth > 0) {
                var rr = d.r - 5;
                gg.append('path')           
            .attr('d', arcSVG(-rr, 0, rr, 1, 1, rr, 0))
            .attr('id', 'label-path-' + i)
            .style('fill', 'none')
            .style('stroke', 'none');

           gg.append('text')
            .append('textPath')
            .attr('xlink:href', '#label-path-' + i)
            .attr('startOffset', '50%')
            .style("text-anchor","middle")
            .style('font-size', '10px')
            .style('fill', 'black')
            .text(d.data.name);
            }
    });


  node = nodeg.selectAll("circle,text");
  circle = nodeg.selectAll("circle");

  svg
      .on("click", function() {
            zoom(root);
      });

  zoomTo([root.x, root.y, root.r * 2 + margin]);

}

function zoom(d) {
  var focus0 = focus; focus = d;

  var transition = d3.transition()
      .duration(d3.event.altKey ? 7500 : 750)
      .tween("zoom", function(d) {
        var i = d3.interpolateZoom(view, [focus.x, focus.y, focus.r * 2 + margin]);
        return function(t) { zoomTo(i(t)); };
      });

  transition.selectAll("text")
    .filter(function(d) { return d.parent === focus || this.style.display === "inline"; })
      .style("fill-opacity", function(d) { return d.parent === focus ? 1 : 0; })
      .on("start", function(d) { if (d.parent === focus) this.style.display = "inline"; })
      .on("end", function(d) { if (d.parent !== focus) this.style.display = "none"; });
}

function zoomTo(v) {
  var k = diameter / v[2]; view = v;
  node.attr("transform", function(d) { return "translate(" + (d.x - v[0]) * k + "," + (d.y - v[1]) * k + ")"; });
  circle.attr("r", function(d) { return d.r * k; });
}

Appreciate your help.

Thank You.

Upvotes: 2

Views: 332

Answers (1)

Gerardo Furtado
Gerardo Furtado

Reputation: 102194

You're not selecting the paths for the <textPath>s in the zoom function. It can be done as this:

svg.selectAll("path")
    .attr('d', function(d){
        return arcSVG(-(d.r - 5) * k, 0, (d.r - 5) * k, 1, 1, (d.r - 5) * k, 0)
    });

Of course, a better idea is naming your selections, and also improving the new d attribute. Also, mind creating globals (like circle and node), just for using them in the zoom function. Finally, your logic for setting the display property of the textpaths is not working correctly.

Here is the forked Codepen: https://codepen.io/anon/pen/QzYawj?editors=0010

Upvotes: 2

Related Questions