Pie chart component #19

Merged
j285he merged 23 commits from j285he-pie-chart into main 2022-09-07 22:20:31 -04:00
5 changed files with 229 additions and 4 deletions

View File

@ -0,0 +1,26 @@
.piePath {
fill: var(--tertiary-background);
}
.labelPath {
fill-opacity: 0;
}
j285he marked this conversation as resolved
Review

Just curious, does fill matter if fill-opacity is set to 0?

Just curious, does `fill` matter if `fill-opacity` is set to 0?
.pieText,
.labelText {
fill: var(--label);
font-weight: 800;
}
j285he marked this conversation as resolved
Review

NITs:

  • calc(40 rem / 16)
  • should this be font-weight 700 (bold)?
NITs: - `calc(40 rem / 16)` - should this be font-weight 700 (bold)?
Review

Also should/can we make the font size a prop that can be tweaked programmatically? Visx has a Text component that takes textSize as a prop, among other style attributes.

Also should/can we make the font size a prop that can be tweaked programmatically? Visx has a Text component that takes textSize as a prop, among other style attributes.
.pieText {
display: none;
}
.group:hover > .piePath {
fill: var(--primary-accent);
filter: drop-shadow(0px 0px calc(6rem / 16) var(--primary-accent));
}
j285he marked this conversation as resolved
Review

NIT: 0 0 calc(6rem / 16) var(--primary-accent)

NIT: `0 0 calc(6rem / 16) var(--primary-accent)`
.group:hover .pieText {
display: inline;
}

180
components/PieChart.tsx Normal file
View File

@ -0,0 +1,180 @@
import { Group } from "@visx/group";
import Pie, { ProvidedProps } from "@visx/shape/lib/shapes/Pie";
import { Text } from "@visx/text";
import React from "react";
import styles from "./PieChart.module.css";
interface PieChartProps {
data: PieChartData[];
/** Width of the entire graph, including labels, in pixels. */
width: number;
/** Width of the outer ring of labels, in pixels. Label text may be cut off if specified value is too small. */
labelWidth: number;
j285he marked this conversation as resolved
Review

I saw you mentioned this in the PR description, but it might be worth adding in the JSDoc comment or somewhere that the label text can get cut off if the labelWidth is too small?

Alternatively, maybe it'd make more sense to specify the radius or diameter of the pie as a prop (ie. pieRadius or pieDiameter), and then allocate labelWidth based off that, with a note that the labels might get cut off if the radius/diameter of the pie is too big relative to the width of the entire graph.

Up to you.

I saw you mentioned this in the PR description, but it might be worth adding in the JSDoc comment or somewhere that the label text can get cut off if the `labelWidth` is too small? Alternatively, maybe it'd make more sense to specify the radius or diameter of the pie as a prop (ie. `pieRadius` or `pieDiameter`), and then allocate `labelWidth` based off that, with a note that the labels might get cut off if the radius/diameter of the pie is too big relative to the width of the entire graph. Up to you.
/** Distance between pie slices, in pixels. */
padRadius?: number;
/** Distance of gap in center of pie graph, in pixels. */
innerRadius?: number;
/** Font size of text inside the pie, in pixels. */
pieTextSize?: number;
/** X-axis offset of the pie text, in pixels. */
pieTextXOffset?: number;
/** Y-axis offset of the pie text, in pixels. */
pieTextYOffset?: number;
/** Accessor function to get value to display as pie text from datum. */
getPieDisplayValueFromDatum?: (datum: PieChartData) => string;
/** Font size of labels outside the pie, in pixels. */
labelTextSize?: number;
/** X-axis offset of the label text, in pixels. */
labelTextXOffset?: number;
/** Y-axis offset of the label text, in pixels. */
labelTextYOffset?: number;
/** Accessor function to get value to display as label text from datum. */
getLabelDisplayValueFromDatum?: (datum: PieChartData) => string;
className?: string;
}
interface PieChartData {
category: string;
value: number;
}
export function PieChart({
data,
width,
labelWidth,
padRadius = width * 0.35,
innerRadius = width * 0.015,
pieTextSize = 40,
pieTextXOffset = 0,
pieTextYOffset = 10,
getPieDisplayValueFromDatum = (datum: PieChartData) => `${datum.value}%`,
labelTextSize = 40,
labelTextXOffset = 0,
labelTextYOffset = 0,
getLabelDisplayValueFromDatum = (datum: PieChartData) => `${datum.category}`,
className,
}: PieChartProps) {
const pieWidth = width * 0.5 - labelWidth;
return (
<svg className={className} width={width} height={width}>
<Group top={width * 0.5} left={width * 0.5}>
<Pie
data={data}
pieValue={(d: PieChartData) => d.value}
cornerRadius={10}
padAngle={0.075}
padRadius={padRadius}
innerRadius={innerRadius}
outerRadius={pieWidth}
>
{(pie) => (
<PieSlice
{...pie}
pieTextSize={pieTextSize}
pieTextXOffset={pieTextXOffset}
pieTextYOffset={pieTextYOffset}
getPieDisplayValueFromDatum={getPieDisplayValueFromDatum}
/>
)}
</Pie>
<Pie
data={data}
pieValue={(d: PieChartData) => d.value}
innerRadius={pieWidth}
outerRadius={width * 0.5}
>
{(pie) => (
<PieSliceLabel
{...pie}
labelTextSize={labelTextSize}
labelTextXOffset={labelTextXOffset}
labelTextYOffset={labelTextYOffset}
getLabelDisplayValueFromDatum={getLabelDisplayValueFromDatum}
/>
)}
</Pie>
</Group>
</svg>
);
}
type PieSliceProps<PieChartData> = ProvidedProps<PieChartData> & {
pieTextSize: number;
pieTextXOffset: number;
pieTextYOffset: number;
getPieDisplayValueFromDatum: (datum: PieChartData) => string;
};
export function PieSlice({
path,
arcs,
pieTextSize,
pieTextXOffset,
pieTextYOffset,
getPieDisplayValueFromDatum,
}: PieSliceProps<PieChartData>) {
return (
<>
{arcs.map((arc) => {
const [centroidX, centroidY] = path.centroid(arc);
const pathArc = path(arc) as string;
return (
<Group className={styles.group} key={`arc-${arc.data.category}`}>
<path className={styles.piePath} d={pathArc} />
<Text
className={styles.pieText}
x={centroidX + pieTextXOffset}
y={centroidY + pieTextYOffset}
textAnchor="middle"
fontSize={pieTextSize}
>
{`${getPieDisplayValueFromDatum(arc.data)}`}
</Text>
</Group>
);
})}
</>
);
}
type PieSliceLabelProps<PieChartData> = ProvidedProps<PieChartData> & {
labelTextSize: number;
labelTextXOffset: number;
labelTextYOffset: number;
getLabelDisplayValueFromDatum: (datum: PieChartData) => string;
};
export function PieSliceLabel({
path,
arcs,
labelTextSize,
labelTextXOffset,
labelTextYOffset,
getLabelDisplayValueFromDatum,
}: PieSliceLabelProps<PieChartData>) {
return (
<>
{arcs.map((arc) => {
const [centroidX, centroidY] = path.centroid(arc);
const pathArc = path(arc) as string;
return (
<Group className={styles.group} key={`arc-${arc.data.category}`}>
<path className={styles.labelPath} d={pathArc} />
<Text
className={styles.labelText}
x={centroidX + labelTextXOffset}
y={centroidY + labelTextYOffset}
textAnchor="middle"
fontSize={labelTextSize}
>
{`${getLabelDisplayValueFromDatum(arc.data)}`}
</Text>
</Group>
);
})}
</>
);
}

View File

@ -36,6 +36,21 @@ export const mockCategoricalData = [
},
];
export const mockPieData = [
{
category: "Nightingale",
value: 42,
},
{
category: "Quail",
value: 48,
},
{
category: "Cuckoo",
value: 10,
},
];
export const moreMockCategoricalData = [
{ key: "Python", value: 29.53 },
{ key: "Java", value: 17.06 },

1
package-lock.json generated
View File

@ -5,7 +5,6 @@
"requires": true,
"packages": {
"": {
"name": "cs-2022-class-profile",
"version": "0.1.0",
"dependencies": {
"@visx/axis": "^2.10.0",

View File

@ -6,9 +6,11 @@ import {
mockBoxPlotData,
mockQuoteData,
mockQuoteDataLong,
mockPieData,
} from "data/mocks";
import React from "react";
import { PieChart } from "@/components/PieChart";
import { QuotationCarousel } from "@/components/QuotationCarousel";
import { ColorPalette } from "../components/ColorPalette";
@ -21,8 +23,13 @@ export default function Home() {
<div className={styles.page}>
<h1>Playground</h1>
<p>Show off your components here!</p>
<h2>
<code>{"<PieChart />"}</code>
</h2>
<div style={{ padding: "30px" }}>
<PieChart data={mockPieData} width={800} labelWidth={215} />
</div>
<ColorPalette />
<h2>
<code>{"<BarGraphHorizontal />"}</code>
</h2>
@ -38,7 +45,6 @@ export default function Home() {
right: 20,
}}
/>
<h2>
<code>{"<BarGraphVertical />"}</code>
</h2>
@ -58,7 +64,6 @@ export default function Home() {
right: 20,
}}
/>
<h2>
<code>{"<WordCloud />"}</code>
</h2>