/** * WordPress dependencies */ import { _x, sprintf } 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 } from '@nab/types'; /** * Internal dependencies */ import { generateNLabels, isTooltipContext } from './helpers'; export type ConversionImprovementChartProps = { readonly alternatives?: ReadonlyArray< AlternativeTrackingData >; readonly goal: GoalId; readonly unique: boolean; }; export const ConversionImprovementChart = ( props: ConversionImprovementChartProps ): JSX.Element => { const { alternatives = [], goal, unique } = props; const data = getData( alternatives, goal, unique ); const NORMAL = COLORS.wpAdminThemeColorRgb; const RED = COLORS.nabBackgroundRed; const colors = data.map( ( value ): string => 0 > value ? RED : NORMAL ); 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 // ======= const getOptions = (): ChartOptions => { return { indexAxis: 'y', layout: { padding: { left: 0, right: 0, top: 0, bottom: 0, }, }, scales: { x: { ticks: { callback: ( value ) => isNumber( value ) ? `${ numberFormat( value ) }%` : '', maxRotation: 0, }, }, }, responsive: true, maintainAspectRatio: false, plugins: { title: { display: true, color: COLORS.nabText, text: _x( 'Improvement', 'text', 'nelio-ab-testing' ), font: { size: 14, style: 'normal', }, }, tooltip: { callbacks: { title: ( [ context ] ) => sprintf( /* translators: %s: Letter, such as A, B, or C. */ _x( 'Variant %s', 'text', 'nelio-ab-testing' ), context?.label ?? '' ), label: ( context ) => { if ( ! isTooltipContext< number >( context ) ) { return ''; } const value = context.raw || 0; if ( 0 > value ) { return sprintf( /* translators: %s: Percentage number. */ _x( '%s%% worse than the control version', 'text', 'nelio-ab-testing' ), numberFormat( Math.abs( value ) ) ); } return sprintf( /* translators: %s: Percentage number. */ _x( '%s%% better than the control version', 'text', 'nelio-ab-testing' ), numberFormat( value ) ); }, }, }, legend: { display: false, }, datalabels: { align: ( context ) => { const value = context.dataset.data[ context?.dataIndex ?? '' ]; const num = isNumber( value ) ? value : 0; return num < 0 ? 'end' : 'start'; }, anchor: ( context ) => { const value = context.dataset.data[ context?.dataIndex ?? '' ]; const num = isNumber( value ) ? value : 0; return num < 0 ? 'start' : 'end'; }, color: COLORS.nabTextInverted, font: { family: '-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif', weight: 'bold', }, formatter: ( value: number ) => { if ( ! value ) { return ''; } return value < 0 ? `${ numberFormat( value ) }%` : `+${ numberFormat( value ) }%`; }, }, }, }; }; const getData = ( alternatives: ReadonlyArray< AlternativeTrackingData >, goal: GoalId, unique: boolean ) => alternatives.map( ( alternative ) => unique ? alternative.uniqueImprovementFactors[ goal ] || 0 : alternative.improvementFactors[ goal ] || 0 );