import { useMemo } from 'react';
import styles from './DonutChart.module.scss';

type Item = {
  value: number;
  color: string;
};

interface ItemReduce extends Item {
  itemRatio: number;
  offset: number;
  angle: number;
  filled: number;
}

export type Props = {
  data: Item[];
};

function DonutChart({ data }: Props) {
  const cx = 100;
  const cy = 100;
  const strokeWidth = 3;
  const radius = Math.min(cx, cy) / 2;
  const dashArray = 2 * Math.PI * radius;
  const startAngle = -90;

  let filled = 0;

  const sum = data.reduce((sum, item) => {
    return sum + item.value;
  }, 0);

  const ratio = sum > 0 ? 100 / sum : 0;

  const dataReduce = useMemo(() => {
    return data.reduce<ItemReduce[]>((acc, obj) => {
      if (ratio === 0) {
        const newObj = {
          ...obj,
          itemRatio: 0,
          offset: dashArray / 100,
          angle: startAngle,
          filled,
        };
        return [newObj];
      }
      const itemRatio = ratio * obj.value;
      const angle = (filled * 360) / 100 + startAngle;
      const offset = dashArray - (dashArray * itemRatio) / 100;

      const newObj = { ...obj, itemRatio, offset, angle, filled };
      filled += itemRatio;

      return [...acc, newObj];
    }, []);
  }, [data, dashArray, filled, ratio]);

  return (
    <div className={styles.donutChart}>
      <svg width="100px" height="100px" viewBox="35 35 135 135">
        {dataReduce.map((item, index) => (
          <>
            <filter key={`filter_${index}`} id="dropshadow" height="130%">
              <feDropShadow
                dx="0"
                dy="0"
                stdDeviation="2"
                floodColor={item.color}
                floodOpacity="1"
              />
            </filter>
            <circle
              key={index}
              // filter="url(#dropshadow)"
              cx={cx}
              cy={cy}
              r={radius}
              fill="transparent"
              strokeWidth={strokeWidth}
              stroke={item.color}
              filter={`drop-shadow(0px 0px 2px ${item.color})`}
              strokeDasharray={dashArray}
              strokeDashoffset={item.offset}
              transform={`rotate(${item.angle} ${cx} ${cy})`}
            >
              <animateTransform
                attributeName="transform"
                attributeType="XML"
                type="rotate"
                from={`${startAngle} ${cx} ${cy}`}
                to={`${item.angle} ${cx} ${cy}`}
                dur="1s"
              />
            </circle>
          </>
        ))}
      </svg>
    </div>
  );
}

export default DonutChart;

Synonyms

cyb/src/index.tsx
pussy-ts/src/index.tsx
bostrom.network/src/pages/Index.tsx
pussy-landing/src/pages/index.tsx
pussy.meme/src/pages/index.tsx
pussy-ts/src/containers/txs/index.tsx
pussy-ts/src/containers/mint/index.tsx
cyb/src/containers/mint/index.tsx
pussy-ts/src/components/ButtonSwap/index.tsx
pussy-ts/src/components/denom/index.tsx
cyb/src/containers/Objects/index.tsx
cyb/src/components/btnGrd/index.tsx
pussy-ts/src/components/Select/index.tsx
cyb/src/components/actionBar/index.tsx
pussy-ts/src/components/BandwidthBar/index.tsx
cyb/src/components/Select/index.tsx
pussy-ts/src/containers/portal/index.tsx
pussy-ts/src/components/btnGrd/index.tsx
cyb/src/components/TextMarkdown/index.tsx
cyb/src/containers/energy/index.tsx
pussy-ts/src/components/MainContainer/index.tsx
pussy-ts/src/components/actionBar/index.tsx
cyb/src/containers/portal/index.tsx
cyb/src/components/ButtonSwap/index.tsx
cyb/src/containers/nebula/index.tsx
pussy-ts/src/components/DonutChart/index.tsx
pussy-ts/src/containers/nebula/index.tsx
cyb/src/components/BandwidthBar/index.tsx
pussy-ts/src/containers/energy/index.tsx
cyb/src/components/denom/index.tsx
cyb/src/containers/sigma/index.tsx
cyb/src/components/Input/index.tsx
pussy-ts/src/containers/sigma/index.tsx
pussy-ts/src/containers/taverna/index.tsx
pussy-ts/src/components/Input/index.tsx
cyb/src/containers/blok/index.tsx
cyb/src/components/PDF/index.tsx
cyb/src/containers/txs/index.tsx
cyb/src/containers/taverna/index.tsx
cyb/src/components/MainContainer/index.tsx
pussy-ts/src/components/TextMarkdown/index.tsx
pussy-ts/src/components/PDF/index.tsx
cyb/src/containers/portal/citizenship/index.tsx
pussy-landing/src/components/xp/btnGrd/index.tsx
cyb/src/containers/portal/pasport/index.tsx
pussy-landing/src/components/xp/stars/index.tsx
cyb/src/components/buttons/ButtonIcon/index.tsx
cyb/src/containers/wasm/codes/index.tsx
pussy-ts/src/features/ipfs/Drive/index.tsx
pussy-ts/src/containers/portal/gift/index.tsx
pussy-ts/src/features/ipfs/ipfsSettings/index.tsx
pussy-ts/src/components/buttons/ButtonIcon/index.tsx
pussy-ts/src/containers/portal/release/index.tsx
cyb/src/containers/portal/release/index.tsx
cyb/src/features/ipfs/Drive/index.tsx
pussy-ts/src/containers/portal/pasport/index.tsx
cyb/src/features/ipfs/ipfsSettings/index.tsx
pussy-ts/src/containers/portal/citizenship/index.tsx
cyb/src/containers/portal/gift/index.tsx
cyb/src/components/contentIpfs/component/gateway/index.tsx
pussy-ts/src/containers/portal/components/ReleaseStatus/index.tsx
pussy-ts/src/components/contentIpfs/component/gateway/index.tsx
pussy-ts/src/containers/sigma/components/CardPassport/index.tsx
cyb/src/containers/sigma/components/CardPassport/index.tsx
cyb/src/components/contentIpfs/component/img/index.tsx
pussy-ts/src/components/contentIpfs/component/img/index.tsx
cyb/src/containers/portal/components/ActionBar/index.tsx
pussy-ts/src/components/contentIpfs/component/link/index.tsx
cyb/src/containers/portal/components/ReleaseStatus/index.tsx
cyb/src/containers/portal/components/stars/index.tsx
pussy-ts/src/containers/portal/components/stars/index.tsx
cyb/src/components/contentIpfs/component/link/index.tsx
pussy-ts/src/containers/sigma/components/cardUi/TitleCard/index.tsx
cyb/src/containers/sigma/components/cardUi/TitleCard/index.tsx
cyb/src/containers/sigma/components/cardUi/RowBalancesDetails/index.tsx
pussy-ts/src/containers/sigma/components/cardUi/RowBalancesDetails/index.tsx

Neighbours