/** * 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;