/** * WordPress dependencies */ import { useSelect } from '@safe-wordpress/data'; import { _x } from '@safe-wordpress/i18n'; /** * External dependencies */ 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 { store as NAB_DATA } from '@nab/data'; import type { ChartOptions } from 'chart.js'; import type { AlternativeTrackingData, ECommercePlugin, GoalId, Maybe, } from '@nab/types'; /** * Internal dependencies */ import { generateNLabels } from './helpers'; import { getMoneyLabel } from '../utils'; export type ArpcChartProps = { readonly alternatives?: ReadonlyArray< AlternativeTrackingData >; readonly goal: GoalId; readonly unique: boolean; }; export const ArpcChart = ( props: ArpcChartProps ): JSX.Element => { const { alternatives = [], goal, unique } = props; const ecommerce = useECommercePlugin( goal ); const data = getData( alternatives, goal, unique ); const colors = data.map( () => COLORS.wpAdminThemeColorRgb ); const chartData = { datasets: [ { data, backgroundColor: colors, maxBarThickness: 60, }, ], labels: generateNLabels( data.length ), }; const options = getOptions( ecommerce ); // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment return ; }; // ======= // HELPERS // ======= function shouldLabelHoverBar( value: unknown, data: unknown[] ) { const max = Math.max( ...data.filter( isNumber ) ); return isNumber( value ) && value / max >= 0.7; } const getOptions = ( ecommerce: Maybe< ECommercePlugin > ): ChartOptions => { return { indexAxis: 'y', layout: { padding: { left: 0, right: 0, top: 0, bottom: 0, }, }, scales: { x: { min: 0, ticks: { callback: ( value ) => isNumber( value ) ? getMoneyLabel( value, ecommerce ) : '', }, }, }, maintainAspectRatio: false, responsive: true, plugins: { title: { display: true, text: _x( 'Average Revenue Per Conversion (ARPC)', 'text', 'nelio-ab-testing' ), color: COLORS.nabText, font: { size: 14, style: 'normal', }, }, legend: { display: false, }, tooltip: { enabled: false, }, datalabels: { align: ( context ) => shouldLabelHoverBar( context.dataset.data[ context.dataIndex ], context.dataset.data ) ? 'start' : 'end', anchor: 'end', color: ( context ) => shouldLabelHoverBar( context.dataset.data[ context.dataIndex ], context.dataset.data ) ? 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 ) => getMoneyLabel( value, ecommerce ), }, }, }; }; const getData = ( alternatives: ReadonlyArray< AlternativeTrackingData >, goal: GoalId, unique: boolean ) => alternatives.map( ( alternative ) => unique ? ( alternative.uniqueValues[ goal ] || 0 ) / ( alternative.uniqueConversions[ goal ] || 0 ) || 0 : ( alternative.values[ goal ] || 0 ) / ( alternative.conversions[ goal ] || 0 ) || 0 ); // ===== // HOOKS // ===== const useECommercePlugin = ( goal: GoalId ) => useSelect( ( select ) => { const { getPageAttribute, getECommercePlugin } = select( NAB_DATA ); const activeExperiment = getPageAttribute( 'editor/activeExperiment' ) ?? 0; return getECommercePlugin( activeExperiment, goal ); }, [ goal ] );