I want to add distance or space between the Circular Packing Bubble chart

51 Views Asked by At
I'm utilizing D3.js for my web-based React project, and I'm facing difficulty creating space between the four outer-level circle elements. Could you please review my code and offer guidance on the correct solution?
import { useState } from 'react';
import * as d3 from 'd3';
import { Tree } from '../../../../../mocks/circularPackingData';
import { styled } from '@mui/material/styles';
import Tooltip, { TooltipProps, tooltipClasses } from '@mui/material/Tooltip';


const LightTooltip = styled(({ className, ...props }: TooltipProps) => (
    <Tooltip {...props} classes={{ popper: className }} />
  ))(({ theme }) => ({
    [`& .${tooltipClasses.tooltip}`]: {
      backgroundColor: theme.palette.common.white,
      color: 'rgba(0, 0, 0, 0.87)',
      boxShadow: theme.shadows[1],
      fontSize: 12,
      padding: 10
    },
}));



type CircularPackingProps = {
    width: any;
    height: any;
    data: Tree;
};

const colors = [
    "#FFECB3", // outer circle top
    "#C8E6C9", // outer circle right 
    "#F8BBD0", // outer circle bottom
    "#BBDEFB", // outer circle left
];


//customization
const hoverColors = [
    "#FFB600", // Hover color for category 1 // yellow
    "#7AC282", // Hover color for category 2 // green
    "#A43E50", // Hover color for category 3 // red
    "#0060D7", // Hover color for category 4 // blue
];

export const CircularPacking = ({
    width,
    height,
    data,
}: CircularPackingProps) => {
    const hierarchy = d3
        .hierarchy(data)
        .sum((d: any) => d.value)
        .sort((a: any, b: any) => b.value! - a.value!);

    const packGenerator = d3.pack<Tree>().size([width, height]).padding(6);
    const root = packGenerator(hierarchy);


    //customization
    const [hoveredLeaf, setHoveredLeaf] = useState(null);

    // List of item of level 1 (just under root)
    const firstLevelGroups = hierarchy?.children || [];

    // Generate a color scale based on the categories
    const colorScale = d3.scaleOrdinal<string>()
        .domain(firstLevelGroups.map((child) => child.data.name))
        .range(colors);

    const handleLeafHover = (leafName: any) => {
        setHoveredLeaf(leafName);
    };


    // Circles for level 1 of the hierarchy
    const allLevel1Circles = root
        .descendants()
        .filter((node) => node.depth === 1)
        .map((node: any, index: any) => {
            const parentName = node.data.name;

            return (
                <g key={index}>
                    <circle
                        cx={node.x}
                        cy={node.y}
                        r={node.r}
                        stroke={colorScale(parentName)}
                        strokeWidth={8}
                        strokeOpacity={0.3}
                        fill={colorScale(parentName)}
                        fillOpacity={0.1}
                        style={{ cursor: 'pointer' }}
                    />
                </g>
            );
        });

    // Circles for level 2 = leaves
    const allLeafCircles = root.leaves().map((leaf, index: number) => {
        const parentName = leaf.parent?.data.name;
        if (!parentName) {
            return null;
        }

        //customization
        const isHovered = hoveredLeaf === leaf.data.name;
        const hoverColorIndex = firstLevelGroups.findIndex(child => child.data.name === parentName);
        return (
            <g key={index}>
                <LightTooltip title={leaf.data.name} placement='top-start' arrow>
                <g
                // customization
                onMouseEnter={() => handleLeafHover(leaf.data.name)}
                onMouseLeave={() => handleLeafHover(null)}
                style={{ cursor: 'pointer' }}
            >
                <circle
                    cx={leaf.x}
                    cy={leaf.y}
                    r={leaf.r}

                    // customization
                    stroke={isHovered ? hoverColors[hoverColorIndex] : colorScale(parentName)}
                    strokeWidth={isHovered ? 2 : 2}
                    fill={isHovered ? hoverColors[hoverColorIndex] : colorScale(parentName)}
                    fillOpacity={isHovered ? 0.4 : 0.2}
                />
                </g>
                </LightTooltip>
            
                <text
                    key={index}
                    x={leaf.x}
                    y={leaf.y}
                    fontSize={12}
                    fontWeight={0.4}
                    textAnchor="middle"
                    alignmentBaseline="middle"
                >
                    {leaf.data.name}
                </text>
            </g>
        );
    });

    return (
        <>
            <svg width={width} height={height}>
                {allLevel1Circles}
                {allLeafCircles}
            </svg>
        </>
    );
};

Mock Data 

export type TreeNode = {
    type: 'node';
    value: number;
    name: string;
    children: Tree[];
  };
  export type TreeLeaf = {
    type: 'leaf';
    name: string;
    value: number;
  };
  
  export type Tree = TreeNode | TreeLeaf;
  
  
  export const data: Tree = {
    type: "node",
    name: "boss",
    value: 0,
    children: [
      {
        type: "node",
        name: "Renewals",
        value: 0,
        children: [
          { type: "leaf", name: "Pixar Animation", value: 90 },
          { type: "leaf", name: "National Geography", value: 42 },
          { type: "leaf", name: "Skyline Enterprise", value: 34 },
          { type: "leaf", name: "MAC", value: 50 },
          { type: "leaf", name: "Adobe", value: 50 },
          { type: "leaf", name: "Figma", value: 40 },
        ],
      },
      {
        type: "node",
        name: "Churn",
        value: 0,
        children: [
          { type: "leaf", name: "Wonka", value: 90 },
          { type: "leaf", name: "Linux", value: 40 },
          { type: "leaf", name: "Adobe", value: 40 },
          { type: "leaf", name: "Marvel Enterprise", value: 90 },
          { type: "leaf", name: "Hilton", value: 30 },
          { type: "leaf", name: "Tous", value: 25 },
          { type: "leaf", name: "AT&T", value: 30 }
        ],
      },
      {
        type: "node",
        name: "Cross sell",
        value: 0,
        children: [
          { type: "leaf", name: "Cisco", value: 80 },
          { type: "leaf", name: "Samsung", value: 90 },
          { type: "leaf", name: "MG", value: 40 },
          { type: "leaf", name: "Urban Decay", value: 70 },
          { type: "leaf", name: "Tous", value: 40 },
          { type: "leaf", name: "Skyline Enterprise", value: 70 },
          { type: "leaf", name: "Cloe", value: 40 },
          { type: "leaf", name: "Mini", value: 40 },
          { type: "leaf", name: "Zara", value: 40 }
        ]
      },
       {
        type: "node",
        name: "Upsell",
        value: 0,
        children: [
          { type: "leaf", name: "Urban Decay", value: 40 },
          { type: "leaf", name: "Hilton", value: 80 },
          { type: "leaf", name: "Tous", value: 40 },
          { type: "leaf", name: "Apple", value: 70 },
          { type: "leaf", name: "Ferrari", value: 45 },
          { type: "leaf", name: "MG", value: 40 },
          { type: "leaf", name: "Pixar Animation", value: 90 },
          { type: "leaf", name: "Nestle", value: 40 }
        ]
      }
    ]
  };
  
Calling place 

import {CircularPacking} from '../RenewalBubbleView/CircularPacking';
import {data} from '../../../../../mocks/circularPackingData';

const BubbleViewChart = () =>{
    
    return (
        <div>
           <CircularPacking data={data} width={1100} height={590}></CircularPacking>
        </div>
    )
    
}

export default BubbleViewChart;

I achieved screenshot

enter image description here

I wanted to achieve as per my design like screenshot enter image description here

In screenshot 1, you'll notice that the outer circles of level 1 are closely positioned, and I believe that introducing spacing in the form of padding or margin will enhance the visual layout as per screenshot 2(this i want to achieve in project).I did lot of search on chat GPT, but didn't find solution for it.

For your convenience, I have included both the .tsx file and the relevant mock files that showcase the existing code. Please take a look at them to provide more precise guidance on how to implement the desired gap between the outer-level circles.

0

There are 0 best solutions below