/** * WordPress dependencies */ import { _x } from '@safe-wordpress/i18n'; /** * External dependencies */ import { numberFormat } from '@nab/i18n'; import { Bar } from 'react-chartjs-2'; import ChartDataLabels from 'chartjs-plugin-datalabels'; import { Chart as ChartJS, BarElement, CategoryScale, Legend, LinearScale, Title, Tooltip, } from 'chart.js'; import { COLORS, isNumber } from '@nab/utils'; ChartJS.register( BarElement, CategoryScale, ChartDataLabels, Legend, LinearScale, Title, Tooltip ); import type { ChartOptions } from 'chart.js'; import type { AlternativeTrackingData, GoalId, GoalWinner } from '@nab/types'; /** * Internal dependencies */ import { generateNLabels } from './helpers'; export type ConversionRatesChartProps = { readonly alternatives?: ReadonlyArray< AlternativeTrackingData >; readonly goal: GoalId; readonly minConfidence: number; readonly winners: Partial< Record< GoalId, GoalWinner > >; readonly unique: boolean; }; export const ConversionRatesChart = ( props: ConversionRatesChartProps ): JSX.Element => { const { winners, alternatives = [], goal, minConfidence, unique } = props; const data = getData( alternatives, goal, unique ); const colors = data.map( () => COLORS.wpAdminThemeColorRgb ); try { const winner = winners[ goal ]; if ( winner && winner.confidence >= minConfidence ) { colors[ winner.alternative ] = COLORS.nabBackgroundGreen; } } catch ( _ ) {} const chartData = { datasets: [ { data, backgroundColor: colors, maxBarThickness: 60, }, ], labels: generateNLabels( data.length ), }; const options = getOptions(); // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment return ; }; // ======= // HELPERS // ======= function shouldLabelHoverBar( value: number, data: ReadonlyArray< number > ) { const max = Math.max( ...data ); return value / max >= 0.7; } const getOptions = (): ChartOptions => { return { indexAxis: 'y', layout: { padding: { left: 0, right: 0, top: 0, bottom: 0, }, }, scales: { x: { min: 0, ticks: { callback: ( value ) => isNumber( value ) ? `${ numberFormat( value ) }%` : '', }, }, }, maintainAspectRatio: false, responsive: true, plugins: { title: { display: true, color: COLORS.nabText, text: _x( 'Conversion Rates', 'text', 'nelio-ab-testing' ), font: { size: 14, style: 'normal', }, }, legend: { display: false, }, tooltip: { enabled: false, }, datalabels: { align: ( context ): 'start' | 'end' => shouldLabelHoverBar( context.dataset.data[ context.dataIndex ] as number, context.dataset.data as number[] ) ? 'start' : 'end', anchor: 'end', color: ( context ) => shouldLabelHoverBar( context.dataset.data[ context.dataIndex ] as number, context.dataset.data as number[] ) ? COLORS.nabTextInverted : COLORS.nabText, font: { family: '-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif', weight: 'bold', }, formatter: ( value: number ) => `${ value }%`, }, }, }; }; const getData = ( alternatives: ReadonlyArray< AlternativeTrackingData >, goal: GoalId, unique: boolean ) => { return alternatives.map( ( alternative ) => unique ? alternative.uniqueConversionRates[ goal ] ?? 0 : alternative.conversionRates[ goal ] ?? 0 ); };