Skip to content

Instantly share code, notes, and snippets.

@youngbloodcyb
Last active March 31, 2025 21:56
Show Gist options
  • Save youngbloodcyb/59c9a0d931d791ac10ac4bf3f5b3a833 to your computer and use it in GitHub Desktop.
Save youngbloodcyb/59c9a0d931d791ac10ac4bf3f5b3a833 to your computer and use it in GitHub Desktop.
Histogram component using shadcn/ui + recharts
"use client"
import { useState, useEffect } from "react"
import {
Bar,
BarChart,
CartesianGrid,
ResponsiveContainer,
Tooltip,
XAxis,
YAxis,
} from "recharts"
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card"
import { ChartContainer } from "@/components/ui/chart"
import { ScrollArea } from "@/components/ui/scroll-area"
// Update the interface
interface DataPoint {
key: string
value: number | null
}
// Update the tooltip component
const CustomTooltip = ({ active, payload }: any) => {
if (active && payload && payload.length) {
const data = payload[0].payload
return (
<div className="bg-background border rounded-lg shadow-md p-4 max-w-xs">
<h3 className="font-medium mb-2">Score Range: {data.range}</h3>
<p className="text-sm text-muted-foreground mb-2">
Count: {data.count}
</p>
<div className="mt-2">
<h4 className="text-sm font-medium mb-1">Items:</h4>
<ScrollArea className="h-[200px] w-full">
<ul className="text-xs space-y-1">
{data.items.map((item: string, index: number) => (
<li key={index} className="flex">
<span className="text-primary">{item}</span>
<span className="ml-auto text-muted-foreground">
{data.itemValues[item]}
</span>
</li>
))}
</ul>
</ScrollArea>
</div>
</div>
)
}
return null
}
// Update the main component to accept props
interface HistogramProps {
data: DataPoint[]
title?: string
description?: string
}
export default function Histogram({
data,
title = "Distribution",
description,
}: HistogramProps) {
const [histogramData, setHistogramData] = useState<any[]>([])
useEffect(() => {
processData(data)
}, [data])
const processData = (data: DataPoint[]) => {
// Filter out null values
const validData = data.filter((item) => item.value !== null)
// Find min and max values
const values = validData.map((item) => item.value as number)
const minValue = Math.min(...values)
const maxValue = Math.max(...values)
// Create buckets (10 point ranges)
const bucketSize = 10
const bucketCount = Math.ceil((maxValue - minValue) / bucketSize) + 1
// Initialize buckets
const buckets = Array(bucketCount)
.fill(0)
.map((_, i) => {
const bucketMin =
Math.floor(minValue / bucketSize) * bucketSize + i * bucketSize
const bucketMax = bucketMin + bucketSize - 1
return {
range: `${bucketMin}-${bucketMax}`,
bucketMin,
bucketMax,
count: 0,
items: [] as string[],
itemValues: {} as Record<string, number>,
}
})
// Fill buckets with data
validData.forEach((item) => {
const value = item.value as number
const bucketIndex = Math.floor(
(value - buckets[0].bucketMin) / bucketSize
)
if (bucketIndex >= 0 && bucketIndex < buckets.length) {
buckets[bucketIndex].count++
buckets[bucketIndex].items.push(item.key)
buckets[bucketIndex].itemValues[item.key] = value
}
})
setHistogramData(buckets)
}
return (
<Card className="w-full">
<CardHeader>
<CardTitle>{title}</CardTitle>
{description && <CardDescription>{description}</CardDescription>}
</CardHeader>
<CardContent>
<ChartContainer
config={{
count: {
label: "Count",
color: "hsl(var(--primary))",
},
}}
className="h-[400px]"
>
<ResponsiveContainer width="100%" height="100%">
<BarChart
data={histogramData}
margin={{
top: 5,
right: 30,
left: 20,
bottom: 60,
}}
>
<CartesianGrid strokeDasharray="3 3" />
<XAxis
dataKey="range"
angle={-45}
textAnchor="end"
height={60}
label={{
value: "Value Range",
position: "insideBottom",
offset: -10,
}}
/>
<YAxis
label={{
value: "Count",
angle: -90,
position: "insideLeft",
}}
/>
<Tooltip content={<CustomTooltip />} />
<Bar
dataKey="count"
fill="var(--color-count)"
radius={[4, 4, 0, 0]}
isAnimationActive={true}
animationDuration={500}
/>
</BarChart>
</ResponsiveContainer>
</ChartContainer>
</CardContent>
</Card>
)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment