← Files DataARCHIVED FILE

templates/data-app/base/src/charting/PieRenderer.jsx

3.06 KB · Oct 3, 2026 · 06:03 UTC

↓ Download file

import React from "react";
import { Cell, Label, LabelList, Pie, PieChart, Sector, useIsTooltipActive } from "recharts";
import { compact, percentage } from "./chart-theme.js";

function ActivePieSlice(props) {
  const active = useIsTooltipActive();
  return <Sector {...props} data-pie-active={active || undefined} />;
}

export function PieRenderer({ spec, rows: slices, colorFor, onSelect, children }) {
  const { x, y } = spec;
  const total = slices.reduce((sum, row) => sum + Math.max(0, Number(row[y]) || 0), 0);
  const positiveSlices = slices.filter((row) => row[y] > 0);
  const minimumSliceAngle = total ? Math.min(...positiveSlices.map((row) => row[y] / total * 360)) : 0;
  const sliceGap = positiveSlices.length > 1 ? Math.min(1.5, minimumSliceAngle / 4) : 0;
  const centerValue =
    spec.centerValue ??
    (spec.centerLabel && slices[0] && total ? percentage(Number(slices[0][y]) / total).replace("+", "") : undefined);
  return (
    <PieChart accessibilityLayer>
      {children}
      <Pie
        className="chart-pie"
        activeShape={<ActivePieSlice />}
        data={slices}
        dataKey={y}
        nameKey={x}
        cx="50%"
        cy="50%"
        innerRadius="54%"
        outerRadius="82%"
        paddingAngle={sliceGap}
        stroke="none"
        isAnimationActive={false}
        onClick={(entry, index, event) => onSelect?.(entry?.payload ?? slices[index], event)}
      >
        {slices.map((row) => (
          <Cell
            key={String(row[x])}
            fill={colorFor(row)}
          />
        ))}
        {spec.showValues && (
          <LabelList dataKey={y} position="outside" formatter={compact} fill="var(--secondary)" fontSize={12} />
        )}
        {centerValue != null && (
          <Label
            position="center"
            content={({ viewBox }) => {
              const centerX = Number(viewBox?.cx ?? Number(viewBox?.x) + Number(viewBox?.width) / 2);
              const centerY = Number(viewBox?.cy ?? Number(viewBox?.y) + Number(viewBox?.height) / 2);
              if (!Number.isFinite(centerX) || !Number.isFinite(centerY)) return null;
              return (
                <g className="chart-donut-center">
                  <text
                    x={centerX}
                    y={centerY - (spec.centerLabel ? 5 : 0)}
                    textAnchor="middle"
                    dominantBaseline="middle"
                    fill="var(--text)"
                    fontSize={26}
                    fontWeight={500}
                  >
                    {centerValue}
                  </text>
                  {spec.centerLabel && (
                    <text
                      x={centerX}
                      y={centerY + 19}
                      textAnchor="middle"
                      dominantBaseline="middle"
                      fill="var(--secondary)"
                      fontSize={12}
                    >
                      {spec.centerLabel}
                    </text>
                  )}
                </g>
              );
            }}
          />
        )}
      </Pie>
    </PieChart>
  );
}

SHA-256: fc1265bf867154c239ddb14b539c63751ab7716fb19903b535ccd9c06ca8a0cc