From 6dd5a8f59c9eebd0017522de79483114859becc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Morten=20N=2EO=2E=20N=C3=B8rgaard=20Henriksen?= <1136718+raix@users.noreply.github.com> Date: Sun, 14 Jul 2024 22:03:27 +0200 Subject: [PATCH] Initial charts --- docs/charts/basic/area-chart.mdx | 58 +++++++++++++++++++++++++++++++ docs/charts/basic/line-plot.mdx | 58 +++++++++++++++++++++++++++++++ docs/charts/basic/pie-chart.mdx | 58 +++++++++++++++++++++++++++++++ docs/charts/basic/radar-chart.mdx | 47 +++++++++++++++++++++++++ ui/charts/AreaChart.tsx | 34 ++++++++++++++++++ ui/charts/BarChart.tsx | 33 +++++++++--------- ui/charts/CartesianGrid.tsx | 6 ++++ ui/charts/LinePlot.tsx | 33 ++++++++++++++++++ ui/charts/PieChart.tsx | 57 ++++++++++++++++++++++++++++++ ui/charts/RadarChart.tsx | 46 ++++++++++++++++++++++++ ui/charts/Tooltip.tsx | 13 +++++++ ui/charts/util.ts | 2 ++ ui/theme/lightness.ts | 2 ++ 13 files changed, 430 insertions(+), 17 deletions(-) create mode 100644 docs/charts/basic/area-chart.mdx create mode 100644 docs/charts/basic/line-plot.mdx create mode 100644 docs/charts/basic/pie-chart.mdx create mode 100644 docs/charts/basic/radar-chart.mdx create mode 100644 ui/charts/AreaChart.tsx create mode 100644 ui/charts/CartesianGrid.tsx create mode 100644 ui/charts/LinePlot.tsx create mode 100644 ui/charts/PieChart.tsx create mode 100644 ui/charts/RadarChart.tsx create mode 100644 ui/charts/Tooltip.tsx diff --git a/docs/charts/basic/area-chart.mdx b/docs/charts/basic/area-chart.mdx new file mode 100644 index 0000000..c7258cc --- /dev/null +++ b/docs/charts/basic/area-chart.mdx @@ -0,0 +1,58 @@ +# AreaChart + +AreaChart is a component that displays data in a bar chart format. + +```tsx preview +import { AreaChart } from '@/ui/charts/AreaChart'; + +const data: GroupedData<"uv" | "pv" | "amt">[] = [ + { + name: "Page A", + uv: 4000, + pv: 2400, + amt: 2400 + }, + { + name: "Page B", + uv: 3000, + pv: 1398, + amt: 2210 + }, + { + name: "Page C", + uv: 2000, + pv: 9800, + amt: 2290 + }, + { + name: "Page D", + uv: 2780, + pv: 3908, + amt: 2000 + }, + { + name: "Page E", + uv: 1890, + pv: 4800, + amt: 2181 + }, + { + name: "Page F", + uv: 2390, + pv: 3800, + amt: 2500 + }, + { + name: "Page G", + uv: 3490, + pv: 4300, + amt: 2100 + } +]; + +export default function Example() { + return ( + + ); +} +``` \ No newline at end of file diff --git a/docs/charts/basic/line-plot.mdx b/docs/charts/basic/line-plot.mdx new file mode 100644 index 0000000..544e662 --- /dev/null +++ b/docs/charts/basic/line-plot.mdx @@ -0,0 +1,58 @@ +# LinePlot + +LinePlot is a component that displays data in a bar chart format. + +```tsx preview +import { LinePlot } from '@/ui/charts/LinePlot'; + +const data: GroupedData<"uv" | "pv" | "amt">[] = [ + { + name: "Page A", + uv: 4000, + pv: 2400, + amt: 2400 + }, + { + name: "Page B", + uv: 3000, + pv: 1398, + amt: 2210 + }, + { + name: "Page C", + uv: 2000, + pv: 9800, + amt: 2290 + }, + { + name: "Page D", + uv: 2780, + pv: 3908, + amt: 2000 + }, + { + name: "Page E", + uv: 1890, + pv: 4800, + amt: 2181 + }, + { + name: "Page F", + uv: 2390, + pv: 3800, + amt: 2500 + }, + { + name: "Page G", + uv: 3490, + pv: 4300, + amt: 2100 + } +]; + +export default function Example() { + return ( + + ); +} +``` \ No newline at end of file diff --git a/docs/charts/basic/pie-chart.mdx b/docs/charts/basic/pie-chart.mdx new file mode 100644 index 0000000..e32b272 --- /dev/null +++ b/docs/charts/basic/pie-chart.mdx @@ -0,0 +1,58 @@ +# PieChart + +PieChart is a component that displays data in a bar chart format. + +```tsx preview +import { PieChart } from '@/ui/charts/PieChart'; + +const data: GroupedData<"uv" | "pv" | "amt">[] = [ + { + name: "Page A", + uv: 4000, + pv: 2400, + amt: 2400 + }, + { + name: "Page B", + uv: 3000, + pv: 1398, + amt: 2210 + }, + { + name: "Page C", + uv: 2000, + pv: 9800, + amt: 2290 + }, + { + name: "Page D", + uv: 2780, + pv: 3908, + amt: 2000 + }, + { + name: "Page E", + uv: 1890, + pv: 4800, + amt: 2181 + }, + { + name: "Page F", + uv: 2390, + pv: 3800, + amt: 2500 + }, + { + name: "Page G", + uv: 3490, + pv: 4300, + amt: 2100 + } +]; + +export default function Example() { + return ( + + ); +} +``` \ No newline at end of file diff --git a/docs/charts/basic/radar-chart.mdx b/docs/charts/basic/radar-chart.mdx new file mode 100644 index 0000000..14494e7 --- /dev/null +++ b/docs/charts/basic/radar-chart.mdx @@ -0,0 +1,47 @@ +# RadarChart + +RadarChart is a component that displays data in a bar chart format. + +```tsx preview +import { RadarChart } from '@/ui/charts/RadarChart'; + +const data: GroupedData<"uv" | "pv" | "amt">[] = [ + { + name: 'Math', + Mike: 120, + Lily: 110, + }, + { + name: 'Chinese', + Mike: 98, + Lily: 130, + }, + { + name: 'English', + Mike: 86, + Lily: 130, + }, + { + name: 'Geography', + Mike: 99, + Lily: 100, + fullMark: 150, + }, + { + name: 'Physics', + Mike: 85, + Lily: 90, + }, + { + name: 'History', + Mike: 65, + Lily: 85, + }, +]; + +export default function Example() { + return ( + + ); +} +``` \ No newline at end of file diff --git a/ui/charts/AreaChart.tsx b/ui/charts/AreaChart.tsx new file mode 100644 index 0000000..308f3a3 --- /dev/null +++ b/ui/charts/AreaChart.tsx @@ -0,0 +1,34 @@ +import { Area, Legend, AreaChart as ReAreaChart, ResponsiveContainer, XAxis, YAxis } from "recharts"; +import { CartesianGrid } from "./CartesianGrid"; +import { Tooltip } from "./Tooltip"; +import { type GroupedData, getChartColor } from "./util"; + +type AreaChartProps = { + data: GroupedData[]; +}; + +export function AreaChart({ data }: AreaChartProps) { + const dataKeys = Object.keys(data[0]).filter((key) => key !== "name") as T[]; + return ( + + + + + {dataKeys.map((key, index) => ( + + ))} + + + + + + ); +} diff --git a/ui/charts/BarChart.tsx b/ui/charts/BarChart.tsx index 3043ae1..fb4e943 100644 --- a/ui/charts/BarChart.tsx +++ b/ui/charts/BarChart.tsx @@ -1,7 +1,7 @@ -import { Bar, CartesianGrid, Legend, BarChart as ReBarChart, Tooltip, XAxis, YAxis } from "recharts"; -import { getChartColor } from "./util"; - -export type GroupedData = { name: string } & Record; +import { Bar, Legend, BarChart as ReBarChart, ResponsiveContainer, XAxis, YAxis } from "recharts"; +import { CartesianGrid } from "./CartesianGrid"; +import { Tooltip } from "./Tooltip"; +import { type GroupedData, getChartColor } from "./util"; type BarChartProps = { data: GroupedData[]; @@ -10,18 +10,17 @@ type BarChartProps = { export function BarChart({ data }: BarChartProps) { const dataKeys = Object.keys(data[0]).filter((key) => key !== "name") as T[]; return ( - - - - {dataKeys.map((key, index) => ( - - ))} - - - - + + + + + {dataKeys.map((key, index) => ( + + ))} + + + + + ); } diff --git a/ui/charts/CartesianGrid.tsx b/ui/charts/CartesianGrid.tsx new file mode 100644 index 0000000..dcbc7c4 --- /dev/null +++ b/ui/charts/CartesianGrid.tsx @@ -0,0 +1,6 @@ +import { CartesianGrid as ReCartesianGrid } from "recharts"; +import type { Props } from "recharts/types/cartesian/CartesianGrid"; + +export function CartesianGrid({ className = "!stroke-border", strokeDasharray = "3 3", ...props }: Props) { + return ; +} diff --git a/ui/charts/LinePlot.tsx b/ui/charts/LinePlot.tsx new file mode 100644 index 0000000..fdb8c49 --- /dev/null +++ b/ui/charts/LinePlot.tsx @@ -0,0 +1,33 @@ +import { Legend, Line, LineChart, ResponsiveContainer, XAxis, YAxis } from "recharts"; +import { CartesianGrid } from "./CartesianGrid"; +import { Tooltip } from "./Tooltip"; +import { type GroupedData, getChartColor } from "./util"; + +type LinePlotProps = { + data: GroupedData[]; +}; + +export function LinePlot({ data }: LinePlotProps) { + const dataKeys = Object.keys(data[0]).filter((key) => key !== "name") as T[]; + return ( + + + + + {dataKeys.map((key, index) => ( + + ))} + + + + + + ); +} diff --git a/ui/charts/PieChart.tsx b/ui/charts/PieChart.tsx new file mode 100644 index 0000000..e472c6e --- /dev/null +++ b/ui/charts/PieChart.tsx @@ -0,0 +1,57 @@ +import { Legend, Pie, PieChart as RePieChart, ResponsiveContainer } from "recharts"; +import { Tooltip } from "./Tooltip"; +import { type GroupedData, getChartColor } from "./util"; + +type PieChartProps = { + data: GroupedData[]; +}; + +type Radius = { + innerRadius?: number; + outerRadius: number; +}; + +const radius: Radius[] = [ + { outerRadius: 60 }, + { innerRadius: 70, outerRadius: 90 }, + { innerRadius: 100, outerRadius: 120 }, + { innerRadius: 130, outerRadius: 150 }, + { innerRadius: 160, outerRadius: 180 }, + { innerRadius: 190, outerRadius: 210 }, + { innerRadius: 220, outerRadius: 240 }, + { innerRadius: 250, outerRadius: 270 }, + { innerRadius: 280, outerRadius: 300 } +]; + +export function PieChart({ data }: PieChartProps) { + const dataKeys = Object.keys(data[0]).filter((key) => key !== "name") as T[]; + return ( + + + + {dataKeys.map((key, index) => ( + + ))} + ({ + value, + type: "circle", + color: getChartColor(index, dataKeys.length) + }))} + /> + + + ); +} diff --git a/ui/charts/RadarChart.tsx b/ui/charts/RadarChart.tsx new file mode 100644 index 0000000..ff55936 --- /dev/null +++ b/ui/charts/RadarChart.tsx @@ -0,0 +1,46 @@ +import { + Legend, + PolarAngleAxis, + PolarGrid, + PolarRadiusAxis, + Radar, + RadarChart as ReRadarChart, + ResponsiveContainer, + XAxis, + YAxis +} from "recharts"; +import type { AxisDomain } from "recharts/types/util/types"; +import { Tooltip } from "./Tooltip"; +import { type GroupedData, getChartColor } from "./util"; + +type RadarChartProps = { + data: GroupedData[]; + domain: AxisDomain; +}; + +export function RadarChart({ data, domain }: RadarChartProps) { + const dataKeys = Object.keys(data[0]).filter((key) => key !== "name") as T[]; + const angle = 360 / dataKeys.length; + return ( + + + + + + + + {dataKeys.map((key, index) => ( + + ))} + + + + ); +} diff --git a/ui/charts/Tooltip.tsx b/ui/charts/Tooltip.tsx new file mode 100644 index 0000000..779419d --- /dev/null +++ b/ui/charts/Tooltip.tsx @@ -0,0 +1,13 @@ +import { Tooltip as ReTooltip, type TooltipProps } from "recharts"; +import type { NameType, ValueType } from "recharts/types/component/DefaultTooltipContent"; + +export function Tooltip({ + labelClassName = "!font-semibold !text-xs !text-foreground", + wrapperClassName = "!bg-background rounded-xl !border !border-border !p-2 !text-foreground !text-xs", + itemStyle = { background: "hsl(var(--background))", color: "hsl(var(--foreground))" }, + ...props +}: TooltipProps) { + return ( + + ); +} diff --git a/ui/charts/util.ts b/ui/charts/util.ts index 9ff4ea6..e56f466 100644 --- a/ui/charts/util.ts +++ b/ui/charts/util.ts @@ -1,3 +1,5 @@ +export type GroupedData = { name: string } & Record; + type ColorIndex = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9; function getColorIndex(index: number, length: number) { diff --git a/ui/theme/lightness.ts b/ui/theme/lightness.ts index c8763cf..90f19d5 100644 --- a/ui/theme/lightness.ts +++ b/ui/theme/lightness.ts @@ -1,6 +1,8 @@ // Ref: https://evilmartians.com/chronicles/better-dynamic-themes-in-tailwind-with-oklch-color-magic // Ref: https://leonardocolor.io/theme.html# // Ref: https://cran.r-project.org/web/packages/viridis/vignettes/intro-to-viridis.html +// Ref: https://sjmgarnier.github.io/viridis/ +// Ref: https://www.npmjs.com/package/viridis https://npm.nicfv.com/viridis/ export const lightness = [97.78, 93.56, 88.11, 82.67, 74.22, 64.78, 57.33, 46.89, 39.44, 32, 23.78];