javascriptd3.jscircle-pack

Why the value for r is negative from d3.hierarchy in circle packing in d3.js?


I'm using https://observablehq.com/@d3/zoomable-circle-packing as an example to try d3 and circle packing in angular. I have the data which seems to be hierarchical and I'm following along the code that's presented. However, my d3.hierarchy() is giving me strange results. For some reason, all d.x, d.y and r are all negative. I couldn't figure out why. I'm still reading some guides and books trying to understand if I can figure out how d3.hierarchy works to understand why the values are negative. But I'm bit struggling with that. What's happening in the code and how can I show the circle packing with 3 circles for each object in the array?

I have the codes in stackblitz below.

StackBlitz

The code is here in stackblitz

Code

import { Component, OnInit, ElementRef, ViewChild,  VERSION, AfterViewInit } from '@angular/core';
import * as d3 from 'd3';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ]
})
export class AppComponent implements OnInit, AfterViewInit  {
 
  private diameter: number;
  private margin = { top: 20, right: 20, bottom: 20, left: 20 };
  private width: number;
  private height: number;
  private svg; any;
  private g: any;
  private svgContainer: ElementRef;
  private color: any;
  private dac: any;
  private pack: any;

  @ViewChild('circleContainer', { static: false }) set content(content: ElementRef) {

    if (content) {
      this.svgContainer = content;
    }

  }

  ngOnInit() {

  
    this.dac = {
    "name": "DAC",
    "children":
    [
        {
            "name":
            [
                "Direction"
            ],
            "children":
            {
                "children":
                [
                    {"name":"leader","score":3.33,"_row":"leader"},
                    {"name":"same_sector","score":3.64,"_row":"same_sector"},
                    {"name":"senior_teams","score":3.81,"_row":"senior_teams"},
                    {"name":"team","score":3.81,"_row":"team"}
                ]
            }
        },
        {
            "title":
            [
                "Alignment"
            ],
            "children":
            {
                "children":
                [
                    {"name":"leader","score":3,"_row":"leader"},
                    {"name":"same_sector","score":3.51,"_row":"same_sector"},
                    {"name":"senior_teams","score":3.48,"_row":"senior_teams"},
                    {"name":"team","score":3.48,"_row":"team"}
                ]
            }
        },
        {
            "title":
            [
                "Commitment"
            ],
            "children":
            {
                "children":
                [
                    {"name":"leader","score":3.67,"_row":"leader"},
                    {"name":"same_sector","score":4.05,"_row":"same_sector"},
                    {"name":"senior_teams","score":3.57,"_row":"senior_teams"},
                    {"name":"team","score":3.57,"_row":"team"}
                ]
            }
        }
    ]
}
    this.createChart();
  
  }

  ngAfterViewInit() {

    this.width = 500 - this.margin.left - this.margin.right;
    this.height = 400 - this.margin.top - this.margin.bottom;

    this.color = d3.scaleLinear<string>()
      .domain([1, 5])
      .range(["hsl(152,80%,80%)", "hsl(228,30%,40%)"])
      .interpolate(d3.interpolateHcl);

    if (this.svgContainer && this.dac) {

      this.createChart()

    }

  }


    private createSVG(rect) {

    this.svg = d3.select(rect)
      .append('svg')
      .attr('viewBox', `0 0  ${this.width / 2} ${this.height / 2}`)
      .attr('preserveAspectRatio', 'xMinYMin meet')
      .style('display', 'block')
      .style('margin', "0 auto")
      .style('background', this.color(1))
      .style('cursor', 'pointer')
      .attr('class', 'bubble-chart')

    this.diameter = +this.svg.attr("width")
    console.log("diameter: ", this.diameter);

    this.pack = data => d3.pack()
      .size([this.diameter - this.margin.left, this.diameter - this.margin.right])
      .padding(2)
      (d3.hierarchy(data)
        .sum(d => d.score)
        .sort((a, b) => b.value - a.value))
      

  }

 private createChart() {

    let that = this;

    const rect = this.svgContainer.nativeElement;

    this.createSVG(rect);

    const root = this.pack(this.dac);
    let focus = root;
    let view;

    console.log(root);
    this.svg = this.svg
      .on('click', (event) => zoom(event, root));

    const node = this.svg.append('g')
      .selectAll('circle')
      .data(root.descendants().slice(1))
      .join('circle')
      .attr('fill', d => d.children ? this.color(d.depth) : "white")
      .attr('pointer-events', d => !d.children ? "none" : null)
      .on('mouseover', function () { d3.select(this).attr('stroke', '#999'); })
      .on('mouseout', function () { d3.select(this).attr('stroke', 'null'); })
      .on('click', (event, d) => focus !== d && (zoom(event, d), event.stopPropagation()));

    const label = this.svg.append('g')
      .style('font', '10px Roboto')
      .attr('pointer-events', 'none')
      .attr('text-anchor', 'middle')
      .selectAll('text')
      .data(root.descendants())
      .join('text')
      .style('fill-opacity', d => d.parent === root ? 1 : 0)
      .style('display', d => d.parent === root ? 'inline' : 'none')
      .text(d => d.data.name);


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

    function zoomTo(v) {
      const k = that.width / v[2];

      view = v;

      
      console.log("k: ", k);
      console.log("v:",v);

      label.attr('transform', d => {
        console.log("dx: ", d.x)
        console.log("dy: ", d.y);
        return `translate(${(d.x - v[0]) * k}, ${(d.y - v[1]) * k})`});
      node.attr('transform', d => `translate(${(d.x - v[0]) * k}, ${(d.y - v[1]) * k})`);
      node.attr('r', d => d.r * k);
    }


    function zoom(event, d) {
      const focus0 = focus;
      focus = d;

      const transition = that.svg.transition()
        .duration(event.altKey ? 7500 : 750)
        .tween('zoom', d => {
          const i = d3.interpolateZoom(view, [focus.x, focus.y, focus.r * 2]);
          return t => zoomTo(i(t));
        });

      label
        .filter(function (d) { return d.parent === focus || this.style.display === 'inline'; })
        .transition(transition)
        .style('fill-opacity', d => 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' });
    }



  }

  

}

Solution

  • You have 2 problems:

    1. Your data structure is incorrect, you have objects as the value of your children properties. They should be arrays.

    2. You are setting the SVG viewBox, not its width. Therefore, this...

      this.diameter = +this.svg.attr("width")
      

      ...is just +null, which is 0. Because of that, the array in the size() method will have negative values, which explains your main issue. Use your width and height instead.

    Here is the forked code: https://stackblitz.com/edit/angular-circle-packing-uyigxs?file=src/app/app.component.ts