d3 zoomable circle packaging broken zoom

39 Views Asked by At

I changed the d3 zoomable circle packaging code from here

https://codepen.io/akeyz/pen/VwaOXjN

to typescript (using d3 version "^7.8.5", angular 16) and everything works fine except for the zoom. It seems that the circle diameters and positions don't get translated correctly. Does anybody have an idea how to fix it?

import * as d3 from "d3";
import { Component, OnInit, Input, ViewEncapsulation } from "@angular/core";

@Component({
  selector: "app-chart",
  templateUrl: "./chart.component.html",
  encapsulation: ViewEncapsulation.None
})
export class ChartComponent implements OnInit {
  @Input() data: any;

  ngOnInit() {
    const width = 750;
    const height = 750;

    let svg: any = d3.select("svg").style('width', width).style('height', height);
    let margin = 20;
    let diameter = width;
    let g: any = svg.append("g").attr("transform", "translate(" + diameter / 2 + "," + diameter / 2 + ")");

    const color: any = () => {
      var color = Math.floor(Math.random() * 16777216).toString(16);
      return '#000000'.slice(0, -color.length) + color;
    }

    const root: any = d3.hierarchy(this.data)
      .sum((d: any) => d.size)
      .sort(function (a:any, b:any) {
        return b.value - a.value;
      });
    let pack = d3
      .pack()
      .size([diameter - margin, diameter - margin])
      .padding(2);

    const nodes = pack(root).descendants();

    let focus = root;
    let view: any;

    let node = g.selectAll("circle,text");

    function zoom(d: any) {
      focus = d;
      console.log(focus);
      var transition = d3
        .transition()
        .duration(750)
        .tween("zoom", function (d: any) {
          var i = d3.interpolateZoom(view, [
            focus.x,
            focus.y,
            focus.r * 2 + margin
          ]);
          return function (t: any) {
            zoomTo(i(t));
          };
        });
  
      transition
        .selectAll("text")
        .filter(function (d: any) {
          return d.parent === focus || d3.select(this).style('display') === "inline";
        })
        .style("fill-opacity", function (d: any) {
          return d.parent === focus ? 1 : 0;
        })
        .on("start", function (d: any) {
          if (d.parent === focus) d3.select(this).style('display', "inline");
        })
        .on("end", function (d: any) {
          if (d.parent !== focus) d3.select(this).style('display', 'none');
        });
    }
  
    function zoomTo(v: any) {
      var k = diameter / v[2];
      view = v;
      node.attr("transform", function (d: any) {
        return "translate(" + (d.x - v[0]) * k + "," + (d.y - v[1]) * k + ")";
      });
      circle.attr("r", function (d: any) {
        return d.r * k;
      });
    }

    const circle = g
      .selectAll("circle")
      .data(nodes)
      .enter()
      .append("circle")
      .attr("cx", function (d: any) {
        return d.x - diameter / 2;
      })
      .attr("cy", function (d: any) {
        return d.y - diameter / 2;
      })
      .attr("class", function (d: any) {
        return d.parent
          ? d.children
            ? "node"
            : "node node--leaf"
          : "node node--root";
      })
      .style("fill", function (d: any) {
        return d.children ? color() : 'transparent';
      })
      .style("stroke", function (d: any) {
        return d.children ? null : 'black';
      })
      .on("click", function (event: any, d: any) {
        if (focus !== d) {
          zoom(d);
          event.stopPropagation();
        };
      });

    const text = g
      .selectAll("text")
      .data(nodes)
      .enter()
      .append("text")
      .attr("class", "label")
      .attr("x", function (d: any) {
        return d.x - diameter / 2;
      })
      .attr("y", function (d: any) {
        return d.y - diameter / 2;
      })
      .style("fill-opacity", function (d: any) {
        return d.parent === root ? 1 : 0;
      })
      .style("display", function (d: any) {
        return d.parent === root ? "inline" : "none";
      })
      .style("text-anchor", 'middle')
      .text(function (d: any) {
        return d.data.name;
      });

    svg.style("background", color(0)).on("click", function () {
      zoom(root);
    });
    
    zoomTo([root.x, root.y, root.r * 2 + margin]);  
  }
}

HTML is

<svg #svg></svg>

and data is

public circlePackData = {
        children: [
          { 
            name: "aaa", 
            children: [
                {name: "aa",size: 20},
                {name: "ab",size: 30},
                {name: "ac",size: 40},
            ],
          },
          { 
            name: "bbb", 
            children: [
                {name: "ba",size: 100},
                {name: "bb",size: 150},
                {name: "bc",size: 200},
            ],
          },
        ],
      };

I manually added the cx and cy attributes to the circles, as well as x and y to the texts, otherwise the positioning did not work at all even though they were not present in the codepen and there everything works fine.

0

There are 0 best solutions below