Skip to content

Commit

Permalink
Merge pull request #29 from pyyding/kp-scatter-chart
Browse files Browse the repository at this point in the history
Scatter chart component
  • Loading branch information
ferrucc-io authored May 17, 2024
2 parents 71ba762 + d56bfd5 commit f4651e0
Show file tree
Hide file tree
Showing 36 changed files with 589 additions and 86 deletions.
6 changes: 5 additions & 1 deletion lib/components/Area/index.tsx
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
export { Area, type AreaProps } from "recharts";
import { Area, type AreaProps as RechartsAreaProps } from "recharts";

type AreaProps = Omit<RechartsAreaProps, "ref">;

export { Area, type AreaProps };
2 changes: 1 addition & 1 deletion lib/components/AreaChart/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const AreaChart: React.FC<AreaChartProps> = ({
...rest
}) => (
<div
className={cx("h-80 w-full", className)}
className={cx(className, "h-80 w-full")}
data-testid="area-chart-wrapper"
>
<RechartsResponsiveContainer width="100%" height="100%">
Expand Down
7 changes: 7 additions & 0 deletions lib/components/ChartCell/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { type ChartCellProps } from ".";

export const scatterChartCellProps: ChartCellProps = {
width: "20px",
height: "20px",
style: { opacity: 1 },
};
1 change: 1 addition & 0 deletions lib/components/ChartCell/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { Cell as ChartCell, type CellProps as ChartCellProps } from "recharts";
18 changes: 18 additions & 0 deletions lib/components/ChartTooltip/Content.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { cx } from "../common/utils";

type ContentProps = React.HTMLProps<HTMLDivElement>;

export const ChartTooltipContent: React.FC<ContentProps> = ({
children,
className,
...rest
}) => {
return (
<div
className={cx("bg-gray-900 px-3 py-2 rounded-md", className)}
{...rest}
>
{children}
</div>
);
};
43 changes: 27 additions & 16 deletions lib/components/ChartTooltip/DefaultTooltip.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,42 @@
import { TooltipProps } from "recharts";
import { TooltipProps as RechartsTooltipProps } from "recharts";
import { ChartTooltipValue } from "./Value";
import { ChartTooltipTitle } from "./Title";
import { ChartTooltipFooter } from "./Footer";
import { Payload as RechartsTooltipPayload } from "recharts/types/component/DefaultTooltipContent";
import { ChartTooltipContent } from "./Content.tsx";

type TypeFromArray<T> = T extends Array<infer K> ? K : never;
export type TooltipFullPayload = RechartsTooltipPayload<any, any>;
type TooltipProps = RechartsTooltipProps<any, any>;
type Payload = TooltipFullPayload["payload"];

interface DefaultTooltipProps extends TooltipProps<any, any> {
interface DefaultTooltipProps extends TooltipProps {
label: string;
valueFormatter: (payload: TypeFromArray<any[]>) => string;
footerFormatter?: (payload: TypeFromArray<any[]>) => string;
valueFormatter: (payload: Payload) => string;
footerFormatter?: (payload: Payload) => string;
active?: boolean;
}

export const DefaultTooltip: React.FC<DefaultTooltipProps> = ({ label, valueFormatter, footerFormatter, active, payload }) => {
export const DefaultTooltip: React.FC<DefaultTooltipProps> = ({
label,
valueFormatter,
footerFormatter,
active,
payload,
}) => {
const firstPayload = payload?.[0];
if (!active || !firstPayload) return null;

return (
<div className="bg-gray-900 px-3 py-2 rounded-md">
<ChartTooltipContent>
<ChartTooltipTitle>{label}</ChartTooltipTitle>
{payload
?.map((p, index) => (
<div key={index}>
<ChartTooltipValue value={valueFormatter(p.payload)}/>
{footerFormatter && <ChartTooltipFooter subtitle={footerFormatter(p.payload)} />}
</div>
))}
</div>
{payload?.map((p, index) => (
<div key={index}>
<ChartTooltipValue>{valueFormatter(p.payload)}</ChartTooltipValue>
{footerFormatter && (
<ChartTooltipFooter subtitle={footerFormatter(p.payload)} />
)}
</div>
))}
</ChartTooltipContent>
);
};
};
14 changes: 12 additions & 2 deletions lib/components/ChartTooltip/Title.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
export const ChartTooltipTitle: React.FC<{ children: React.ReactNode }> = ({ children }) => (
<p className="text-xs text-gray-400">{children}</p>
import { cx } from "../common/utils.ts";

type ChartTooltipTitleProps = React.HTMLProps<HTMLParagraphElement>;

export const ChartTooltipTitle: React.FC<ChartTooltipTitleProps> = ({
children,
className,
...rest
}) => (
<p className={cx(className, "text-xs text-gray-400")} {...rest}>
{children}
</p>
);
16 changes: 13 additions & 3 deletions lib/components/ChartTooltip/Value.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
export const ChartTooltipValue: React.FC<{ value: string }> = ({ value }) => (
<p className="text-sm text-white">{value}</p>
);
import { cx } from "../common/utils.ts";

type ChartTooltipValueProps = React.HTMLProps<HTMLParagraphElement>;

export const ChartTooltipValue: React.FC<ChartTooltipValueProps> = ({
children,
className,
...rest
}) => (
<p className={cx(className, "text-sm text-white gap-1 flex")} {...rest}>
{children}
</p>
);
16 changes: 16 additions & 0 deletions lib/components/ChartTooltip/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { TooltipProps } from "recharts";

const cursor = { fill: "#d1d5db", opacity: "0.15" };

export const defaultTooltipProps: TooltipProps<any, any> = {
cursor,
position: { y: 0 },
isAnimationActive: false,
wrapperStyle: { outline: "none" },
};

export const scatterChartTooltipProps: TooltipProps<any, any> = {
cursor,
isAnimationActive: false,
wrapperStyle: { outline: "none" },
};
36 changes: 14 additions & 22 deletions lib/components/ChartTooltip/index.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,19 @@

import { Tooltip as RechartsTooltip } from "recharts";
import {
Tooltip as ChartTooltip,
TooltipProps as ChartTooltipProps,
} from "recharts";
import { ChartTooltipValue } from "./Value";
import { ChartTooltipTitle } from "./Title";
import { ChartTooltipFooter } from "./Footer";
import { DefaultTooltip } from "./DefaultTooltip";
import { ChartTooltipContent } from "./Content";

export interface TooltipProps {
label?: string;
active?: boolean;
payload?: any[];
valueFormatter: (payload: any) => string;
footerFormatter?: (payload: any) => string;
}

class ChartTooltip extends RechartsTooltip<any, any> {
static defaultProps = {
...RechartsTooltip.defaultProps,
cursor: { fill: "#d1d5db", opacity: "0.15" } as any,
position: { y: 0 },
isAnimationActive: false,
wrapperStyle: { outline: "none" },
};
}


export { ChartTooltip, ChartTooltipTitle, ChartTooltipValue, ChartTooltipFooter, DefaultTooltip };
export {
ChartTooltip,
ChartTooltipTitle,
ChartTooltipValue,
ChartTooltipFooter,
DefaultTooltip,
ChartTooltipContent,
type ChartTooltipProps,
};
6 changes: 6 additions & 0 deletions lib/components/Grid/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,9 @@ export const defaultGridProps: GridProps = {
strokeDasharray: "3",
className: "stroke-1",
};

export const scatterGridProps = {
vertical: false,
strokeDasharray: "3 3",
className: "stroke-gray-300",
};
10 changes: 10 additions & 0 deletions lib/components/ReferenceLine/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,13 @@ export const defaultReferenceLineProps: ReferenceLineProps = {
stroke: "inherit",
className: "stroke-gray-300",
};

export const scatterReferenceLineXProps: ReferenceLineProps = {
...defaultReferenceLineProps,
x: 50,
};

export const scatterReferenceLineYProps: ReferenceLineProps = {
...defaultReferenceLineProps,
y: 50,
};
17 changes: 17 additions & 0 deletions lib/components/Scatter/ScatterShapeCircle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export const ScatterShapeCircle = (props: unknown): React.ReactElement => {
const svgProps = props as React.SVGProps<SVGCircleElement>;
return (
<circle
r="10"
cx={svgProps.cx}
cy={svgProps.cy}
fill={svgProps.fill}
height={svgProps.height}
name={svgProps.name}
style={svgProps.style}
width={svgProps.width}
x={svgProps.x}
y={svgProps.y}
/>
);
};
6 changes: 6 additions & 0 deletions lib/components/Scatter/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { type ScatterProps } from ".";
import { ScatterShapeCircle } from "./ScatterShapeCircle.tsx";

export const defaultScatterProps: ScatterProps = {
shape: ScatterShapeCircle,
};
5 changes: 5 additions & 0 deletions lib/components/Scatter/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { Scatter, ScatterProps as RechartsScatterProps } from "recharts";

type ScatterProps = Omit<RechartsScatterProps, "ref">;

export { Scatter, type ScatterProps };
26 changes: 26 additions & 0 deletions lib/components/ScatterChart/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import {
ScatterChart as RechartsScatterChart,
ResponsiveContainer as RechartsResponsiveContainer,
} from "recharts";

import { cx } from "../common/utils";
import React from "react";

type ScatterChartProps = React.ComponentProps<typeof RechartsScatterChart>;

export type { ScatterChartProps };

export const ScatterChart: React.FC<ScatterChartProps> = ({
className,
children,
...rest
}) => (
<div
data-testid="scatter-chart-wrapper"
className={cx(className, "w-full h-80")}
>
<RechartsResponsiveContainer className="w-full h-full">
<RechartsScatterChart {...rest}>{children}</RechartsScatterChart>
</RechartsResponsiveContainer>
</div>
);
35 changes: 35 additions & 0 deletions lib/components/ScatterChartTick/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { TickText } from "../TickText";
import { Payload } from "recharts/types/component/DefaultTooltipContent";

type ScatterChartTickProps = {
x: number;
y: number;
payload: Payload<number, string>;
unit?: string;
dx?: number;
dy?: number;
} & React.SVGProps<SVGTextElement>;

export const ScatterChartTick: React.FC<ScatterChartTickProps> = ({
x,
y,
dx = 0,
dy = 0,
unit,
...rest
}: ScatterChartTickProps) => (
<TickText
x={x + dx}
y={y + dy}
fill={rest.fill}
height={rest.height}
name={rest.name}
orientation={rest.orientation}
stroke={rest.stroke}
textAnchor={rest.textAnchor}
width={rest.width}
>
{rest.payload.value}
{unit}
</TickText>
);
14 changes: 14 additions & 0 deletions lib/components/TickText/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import React from "react";
import { cx } from "../common/utils.ts";

export type TickTextProps = React.SVGProps<SVGTextElement>;

export const TickText: React.FC<TickTextProps> = ({
children,
className,
...rest
}) => (
<text {...rest} className={cx("text-xs text-gray-800", className)}>
{children}
</text>
);
9 changes: 9 additions & 0 deletions lib/components/XAxis/constants.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
import { XAxisProps } from "recharts";

export const PERCENTAGE_TICKS = [0, 25, 50, 75, 100];

export const defaultXAxisProps: XAxisProps = {
interval: "preserveStartEnd",
className: "text-xs fill-gray-600",
minTickGap: 5,
fill: "",
stroke: "",
};

export const scatterXAxisProps: XAxisProps = {
axisLine: { className: "stroke-gray-300" },
tickLine: false,
type: "number",
ticks: PERCENTAGE_TICKS,
};
7 changes: 3 additions & 4 deletions lib/components/XAxis/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { XAxisProps } from "recharts";
import { XAxis, XAxisProps as RechartsXAxisProps } from "recharts";

export { XAxis } from "recharts";

export type { XAxisProps };
type XAxisProps = Omit<RechartsXAxisProps, "ref">;

export { XAxis, type XAxisProps };
8 changes: 8 additions & 0 deletions lib/components/YAxis/constants.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { type YAxisProps } from "recharts";
import { PERCENTAGE_TICKS } from "../XAxis/constants.ts";

export const defaultYAxisProps: YAxisProps = {
axisLine: false,
Expand All @@ -7,3 +8,10 @@ export const defaultYAxisProps: YAxisProps = {
fill: "",
stroke: "",
};

export const scatterYAxisProps: YAxisProps = {
axisLine: false,
tickLine: false,
type: "number",
ticks: PERCENTAGE_TICKS,
};
7 changes: 3 additions & 4 deletions lib/components/YAxis/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { YAxisProps } from "recharts";
import { YAxis, type YAxisProps as RechartsYAxisProps } from "recharts";

export { YAxis } from "recharts";

export type { YAxisProps };
type YAxisProps = Omit<RechartsYAxisProps, "ref">;

export { YAxis, type YAxisProps };
Loading

0 comments on commit f4651e0

Please sign in to comment.