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) => (
+
+ ))}
+
+
+ );
+}
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];