Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
182 changes: 182 additions & 0 deletions components/retroui/charts/StackedBarChart.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
"use client"

import { cn } from "@/lib/utils"
import React from "react"
import {
Bar,
BarChart as RechartsBarChart,
CartesianGrid,
ResponsiveContainer,
Tooltip,
XAxis,
YAxis,
Legend,
} from "recharts"

interface StackedBarChartProps extends React.HTMLAttributes<HTMLDivElement> {
data: Record<string, any>[]
index: string
categories: string[]
strokeColors?: string[]
fillColors?: string[]
tooltipBgColor?: string
tooltipBorderColor?: string
gridColor?: string
valueFormatter?: (value: number) => string
showGrid?: boolean
showTooltip?: boolean
showLegend?: boolean
alignment?: "vertical" | "horizontal"
className?: string
}

const StackedBarChart = React.forwardRef<HTMLDivElement, StackedBarChartProps>(
(
{
data = [],
index,
categories = [],
strokeColors = ["var(--foreground)"],
fillColors = ["var(--primary)", "var(--secondary)", "var(--accent)"],
tooltipBgColor = "var(--background)",
tooltipBorderColor = "var(--border)",
gridColor = "var(--muted)",
valueFormatter = (value: number) => value.toString(),
showGrid = true,
showTooltip = true,
showLegend = true,
alignment = "vertical",
className,
...props
},
ref
) => {
return (
<div ref={ref} className={cn("h-80 w-full", className)} {...props}>
<ResponsiveContainer width="100%" height="100%">
<RechartsBarChart
data={data}
layout={alignment === "horizontal" ? "vertical" : undefined}
margin={{ top: 10, right: 30, left: 0, bottom: 0 }}
>
{showGrid && (
<CartesianGrid strokeDasharray="3 3" stroke={gridColor} />
)}

{alignment === "horizontal" ? (
<>
<XAxis
type="number"
axisLine={false}
tickLine={false}
className="text-xs fill-muted-foreground"
tickFormatter={valueFormatter}
/>

<YAxis
type="category"
dataKey={index}
axisLine={false}
tickLine={false}
className="text-xs fill-muted-foreground"
width={80}
/>
</>
) : (
<>
<XAxis
dataKey={index}
axisLine={false}
tickLine={false}
className="text-xs fill-muted-foreground"
/>

<YAxis
axisLine={false}
tickLine={false}
className="text-xs fill-muted-foreground"
tickFormatter={valueFormatter}
/>
</>
)}

{showTooltip && (
<Tooltip
content={({ active, payload, label }) => {
if (!active || !payload?.length) return null

return (
<div
className="border p-2 shadow"
style={{
backgroundColor: tooltipBgColor,
borderColor: tooltipBorderColor
}}
>
<div className="flex flex-col gap-2">
<div className="flex flex-col">
<span className="text-[0.70rem] uppercase text-muted-foreground">
{index}
</span>
<span className="font-bold text-muted-foreground">
{label}
</span>
</div>
<div className="grid gap-1">
{payload.map((entry, idx) => (
<div key={idx} className="flex items-center justify-between gap-4">
<div className="flex items-center gap-2">
<div
className="h-2 w-2 rounded-full"
style={{ backgroundColor: entry.fill }}
/>
<span className="text-xs text-muted-foreground">
{entry.dataKey}
</span>
</div>
<span className="text-xs font-bold">
{entry.value != null ? valueFormatter(entry.value as number) : '-'}
</span>
</div>
))}
</div>
</div>
</div>
)
}}
/>
)}

{showLegend && (
<Legend
wrapperStyle={{ paddingTop: '20px' }}
iconType="circle"
iconSize={8}
/>
)}

{categories.map((category, i) => {
const fillColor = fillColors[i % fillColors.length]
const strokeColor = strokeColors[i % strokeColors.length] || strokeColors[0]

return (
<Bar
key={category}
dataKey={category}
stackId="stack"
fill={fillColor}
stroke={strokeColor}
strokeWidth={1}
/>
)
})}
</RechartsBarChart>
</ResponsiveContainer>
</div>
)
}
)

StackedBarChart.displayName = "StackedBarChart"

export { StackedBarChart, type StackedBarChartProps }
24 changes: 24 additions & 0 deletions config/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ export const componentConfig: {
name: "barChart",
filePath: "components/retroui/charts/BarChart.tsx",
},
stackedBarChart: {
name: "stackedBarChart",
filePath: "components/retroui/charts/StackedBarChart.tsx",
},
carousel: {
name: "carousel",
filePath: "components/retroui/Carousel.tsx",
Expand Down Expand Up @@ -260,6 +264,26 @@ export const componentConfig: {
() => import("@/preview/charts/bar-chart-style-horizontal"),
),
},
"stacked-bar-chart-style-default": {
name: "stacked-bar-chart-style-default",
filePath: "preview/charts/stacked-bar-chart-style-default.tsx",
preview: lazy(() => import("@/preview/charts/stacked-bar-chart-style-default")),
},
"stacked-bar-chart-style-horizontal": {
name: "stacked-bar-chart-style-horizontal",
filePath: "preview/charts/stacked-bar-chart-style-horizontal.tsx",
preview: lazy(() => import("@/preview/charts/stacked-bar-chart-style-horizontal")),
},
"stacked-bar-chart-style-no-legend": {
name: "stacked-bar-chart-style-no-legend",
filePath: "preview/charts/stacked-bar-chart-style-no-legend.tsx",
preview: lazy(() => import("@/preview/charts/stacked-bar-chart-style-no-legend")),
},
"stacked-bar-chart-style-formatted": {
name: "stacked-bar-chart-style-formatted",
filePath: "preview/charts/stacked-bar-chart-style-formatted.tsx",
preview: lazy(() => import("@/preview/charts/stacked-bar-chart-style-formatted")),
},
"button-style-default": {
name: "button-style-default",
filePath: "preview/components/button-style-default.tsx",
Expand Down
1 change: 1 addition & 0 deletions config/navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ export const navConfig: INavigationConfig = {
title: "Chart",
children: [
{ title: "Bar Chart", href: `${chartsRoute}/bar-chart` },
{ title: "Stacked Bar Chart", href: `${chartsRoute}/stacked-bar-chart` },
{ title: "Line Chart", href: `${chartsRoute}/line-chart` },
{ title: "Area Chart", href: `${chartsRoute}/area-chart` },
{ title: "Pie Chart", href: `${chartsRoute}/pie-chart` },
Expand Down
160 changes: 160 additions & 0 deletions content/docs/charts/stacked-bar-chart.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
---
title: Stacked Bar Chart
description: A customizable stacked bar chart component to visualize comparative data across categories with support for stacking, custom colors, and horizontal alignment. 📊
lastUpdated: 03 Dec, 2025
links:
source: https://github.com/Logging-Studio/RetroUI/blob/main/components/retroui/charts/StackedBarChart.tsx
---

<ComponentShowcase name="stacked-bar-chart-style-default" />
<br />
<br />

## Installation

<ComponentInstall>
<ComponentInstall.Cli npmCommand="npx shadcn@latest add 'https://retroui.dev/r/stacked-bar-chart.json'" />
<ComponentInstall.Manual>

#### 1. Install dependencies:

```sh
npm install recharts
```

<br />

#### 2. Copy the code 👇 into your project:

<ComponentSource name="stackedBarChart" />

</ComponentInstall.Manual>
</ComponentInstall>

<br />
<br />

## Examples

### Default

<ComponentShowcase name="stacked-bar-chart-style-default" />
<br />
<br />

### Horizontal Alignment

<ComponentShowcase name="stacked-bar-chart-style-horizontal" />
<br />
<br />

### Without Legend

<ComponentShowcase name="stacked-bar-chart-style-no-legend" />
<br />
<br />

### With Formatted Values

<ComponentShowcase name="stacked-bar-chart-style-formatted" />
<br />
<br />

## API Reference

<Table>
<Table.Header>
<Table.Row>
<Table.Head>Prop</Table.Head>
<Table.Head>Type</Table.Head>
<Table.Head>Default</Table.Head>
<Table.Head>Description</Table.Head>
</Table.Row>
</Table.Header>
<Table.Body>
<Table.Row>
<Table.Cell className="font-medium">data</Table.Cell>
<Table.Cell className="*:text-xs">`Record<string, any>[]`</Table.Cell>
<Table.Cell className="*:text-xs">`[]`</Table.Cell>
<Table.Cell>Array of data objects to display</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell className="font-medium">index</Table.Cell>
<Table.Cell className="*:text-xs">`string`</Table.Cell>
<Table.Cell className="*:text-xs">-</Table.Cell>
<Table.Cell>Key for the x-axis (category) data</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell className="font-medium">categories</Table.Cell>
<Table.Cell className="*:text-xs">`string[]`</Table.Cell>
<Table.Cell className="*:text-xs">`[]`</Table.Cell>
<Table.Cell>Array of keys for the data values to stack in bars</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell className="font-medium">alignment</Table.Cell>
<Table.Cell className="*:text-xs">`"vertical" | "horizontal"`</Table.Cell>
<Table.Cell className="*:text-xs">`"vertical"`</Table.Cell>
<Table.Cell>Orientation of the bars</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell className="font-medium">strokeColors</Table.Cell>
<Table.Cell className="*:text-xs">`string[]`</Table.Cell>
<Table.Cell className="*:text-xs">`["var(--foreground)"]`</Table.Cell>
<Table.Cell>Array of stroke colors for the bars</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell className="font-medium">fillColors</Table.Cell>
<Table.Cell className="*:text-xs">`string[]`</Table.Cell>
<Table.Cell className="*:text-xs">`["var(--primary)", "var(--secondary)", "var(--accent)"]`</Table.Cell>
<Table.Cell>Array of fill colors for the stacked bars</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell className="font-medium">tooltipBgColor</Table.Cell>
<Table.Cell className="*:text-xs">`string`</Table.Cell>
<Table.Cell className="*:text-xs">`"var(--background)"`</Table.Cell>
<Table.Cell>Background color for tooltips</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell className="font-medium">tooltipBorderColor</Table.Cell>
<Table.Cell className="*:text-xs">`string`</Table.Cell>
<Table.Cell className="*:text-xs">`"var(--border)"`</Table.Cell>
<Table.Cell>Border color for tooltips</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell className="font-medium">gridColor</Table.Cell>
<Table.Cell className="*:text-xs">`string`</Table.Cell>
<Table.Cell className="*:text-xs">`"var(--muted)"`</Table.Cell>
<Table.Cell>Color for the grid lines</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell className="font-medium">valueFormatter</Table.Cell>
<Table.Cell className="*:text-xs">`(value: number) => string`</Table.Cell>
<Table.Cell className="*:text-xs">`(value) => value.toString()`</Table.Cell>
<Table.Cell>Function to format values</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell className="font-medium">showGrid</Table.Cell>
<Table.Cell className="*:text-xs">`boolean`</Table.Cell>
<Table.Cell className="*:text-xs">`true`</Table.Cell>
<Table.Cell>Whether to show grid lines</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell className="font-medium">showTooltip</Table.Cell>
<Table.Cell className="*:text-xs">`boolean`</Table.Cell>
<Table.Cell className="*:text-xs">`true`</Table.Cell>
<Table.Cell>Whether to show tooltips on hover</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell className="font-medium">showLegend</Table.Cell>
<Table.Cell className="*:text-xs">`boolean`</Table.Cell>
<Table.Cell className="*:text-xs">`true`</Table.Cell>
<Table.Cell>Whether to show the legend for categories</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell className="font-medium">className</Table.Cell>
<Table.Cell className="*:text-xs">`string`</Table.Cell>
<Table.Cell className="*:text-xs">-</Table.Cell>
<Table.Cell>Additional CSS classes</Table.Cell>
</Table.Row>
</Table.Body>
</Table>
Loading