/**
* Comparison Bar Chart Component
*
* Use for side-by-side comparisons between two groups (A/B testing, benchmarks).
*/
import Highcharts from "highcharts";
import HighchartsReact from "highcharts-react-official";
import {
COLORS,
CHART_STYLES,
COMMON_CHART_OPTIONS,
formatLabel,
} from "@/lib/chart-constants";
import { buildComparisonData, type SortType } from "@/lib/data-utils";
// =============================================================================
// TYPES
// =============================================================================
export interface ComparisonData {
category: string;
count: number;
}
export interface ComparisonChartProps {
/** Title of the chart */
title: string;
/** Data for group A */
dataA: ComparisonData[];
/** Data for group B */
dataB: ComparisonData[];
/** Label for group A */
labelA: string;
/** Label for group B */
labelB: string;
/** How to sort the categories */
sortType?: SortType;
/** Whether to show as percentages (default) or raw counts */
showAsPercentage?: boolean;
/** Color for group A */
colorA?: string;
/** Color for group B */
colorB?: string;
/** Minimum height in pixels */
minHeight?: number;
/** Pixels per category row */
pixelsPerRow?: number;
}
// =============================================================================
// COMPONENT
// =============================================================================
export const ComparisonBarChart = ({
title,
dataA,
dataB,
labelA,
labelB,
sortType = "count",
showAsPercentage = true,
colorA = COLORS.greenPrimary,
colorB = COLORS.bluePrimary,
minHeight = 400,
pixelsPerRow = 60,
}: ComparisonChartProps) => {
// Handle empty data
if ((!dataA || dataA.length === 0) && (!dataB || dataB.length === 0)) {
return (
No data available
);
}
// Build aligned comparison data
const { labels, countsA, countsB, totalA, totalB } = buildComparisonData({
dataA,
dataB,
sortType,
});
// Convert to percentages if needed
const valuesA = showAsPercentage
? countsA.map((count) => (totalA > 0 ? (count / totalA) * 100 : 0))
: countsA;
const valuesB = showAsPercentage
? countsB.map((count) => (totalB > 0 ? (count / totalB) * 100 : 0))
: countsB;
// Calculate dynamic height
const chartHeight = Math.max(minHeight, labels.length * pixelsPerRow);
const options: Highcharts.Options = {
chart: {
type: "bar",
height: chartHeight,
backgroundColor: "transparent",
animation: false,
},
title: COMMON_CHART_OPTIONS.title(title),
xAxis: {
categories: labels.map(formatLabel),
labels: {
style: CHART_STYLES.text.tertiary,
},
},
yAxis: {
min: 0,
title: {
text: showAsPercentage ? "Percentage (%)" : "Count",
style: CHART_STYLES.text.tertiary,
},
labels: {
style: CHART_STYLES.text.tertiary,
formatter: function () {
return showAsPercentage ? `${this.value}%` : `${this.value}`;
},
},
},
plotOptions: {
bar: {
animation: false,
borderWidth: 0,
groupPadding: 0.1,
pointPadding: 0.05,
dataLabels: {
enabled: true,
style: {
...CHART_STYLES.text.secondary,
fontSize: "10px",
},
formatter: function () {
return showAsPercentage
? `${(this.y ?? 0).toFixed(1)}%`
: `${this.y}`;
},
},
},
},
series: [
{
name: labelA,
type: "bar",
color: colorA,
data: valuesA,
},
{
name: labelB,
type: "bar",
color: colorB,
data: valuesB,
},
],
legend: {
...COMMON_CHART_OPTIONS.legend,
enabled: true,
},
tooltip: {
...COMMON_CHART_OPTIONS.tooltip,
shared: true,
formatter: function () {
const points = this.points || [];
let html = `${formatLabel(String(this.x))}
`;
points.forEach((point, index) => {
const rawCount = index === 0 ? countsA[this.point.index] : countsB[this.point.index];
const value = point.y ?? 0;
html += `\u25CF ${point.series.name}: `;
if (showAsPercentage) {
html += `${value.toFixed(1)}% (${rawCount} total)
`;
} else {
html += `${value}
`;
}
});
return html;
},
},
credits: COMMON_CHART_OPTIONS.credits,
};
return ;
};
// =============================================================================
// USAGE EXAMPLE
// =============================================================================
/*
import { ComparisonBarChart } from "@/components/charts/ComparisonChart";
// A/B test comparison
// Sort by difference (highlight biggest changes)
// Show raw counts instead of percentages
// Custom colors
*/
export default ComparisonBarChart;