diff --git a/projects/js-packages/charts/changelog/update-WOOA7S-534-show-ticks-for-year b/projects/js-packages/charts/changelog/update-WOOA7S-534-show-ticks-for-year new file mode 100644 index 0000000000000..da0ff7df889f3 --- /dev/null +++ b/projects/js-packages/charts/changelog/update-WOOA7S-534-show-ticks-for-year @@ -0,0 +1,4 @@ +Significance: minor +Type: added + +Show ticks in year format when the interval is more than a year. diff --git a/projects/js-packages/charts/src/components/line-chart/line-chart.tsx b/projects/js-packages/charts/src/components/line-chart/line-chart.tsx index 999fa3547d1c8..ab370ab68f71c 100644 --- a/projects/js-packages/charts/src/components/line-chart/line-chart.tsx +++ b/projects/js-packages/charts/src/components/line-chart/line-chart.tsx @@ -5,7 +5,7 @@ import { scaleTime } from '@visx/scale'; import { XYChart, AreaSeries, Grid, Axis, DataContext } from '@visx/xychart'; import { __ } from '@wordpress/i18n'; import clsx from 'clsx'; -import { differenceInHours } from 'date-fns'; +import { differenceInHours, differenceInYears } from 'date-fns'; import { useMemo, useContext, forwardRef, useImperativeHandle, useState, useRef } from 'react'; import { useXYChartTheme, @@ -101,6 +101,13 @@ const renderDefaultTooltip = ( params: RenderTooltipParams< DataPointDate > ) => ); }; +const formatYearTick = ( timestamp: number ) => { + const date = new Date( timestamp ); + return date.toLocaleDateString( undefined, { + year: 'numeric', + } ); +}; + const formatDateTick = ( timestamp: number ) => { const date = new Date( timestamp ); return date.toLocaleDateString( undefined, { @@ -117,6 +124,23 @@ const formatHourTick = ( timestamp: number ) => { } ); }; +const getFormatter = ( sortedData: ReturnType< typeof useChartDataTransform > ) => { + const minX = Math.min( ...sortedData.map( datom => datom.data.at( 0 )?.date ) ); + const maxX = Math.max( ...sortedData.map( datom => datom.data.at( -1 )?.date ) ); + + const diffInHours = Math.abs( differenceInHours( maxX, minX ) ); + if ( diffInHours <= 24 ) { + return formatHourTick; + } + + const diffInYears = Math.abs( differenceInYears( maxX, minX ) ); + if ( diffInYears <= 1 ) { + return formatDateTick; + } + + return formatYearTick; +}; + const guessOptimalNumTicks = ( data: ReturnType< typeof useChartDataTransform >, chartWidth: number, @@ -146,8 +170,13 @@ const guessOptimalNumTicks = ( return 1; } - const hasDuplicate = uniqueTicks.length < ticks.length; - if ( hasDuplicate ) { + // Example: OCT 1 JAN 1 APR 1 JUL 1 OCT 1 + // Here, the two OCTs are not duplicates as they represent October of two different years. + const hasConsecutiveDuplicate = ticks.some( + ( tick, idx ) => idx > 0 && tick === ticks[ idx - 1 ] + ); + + if ( hasConsecutiveDuplicate ) { continue; } @@ -278,12 +307,7 @@ const LineChartInternal = forwardRef< SingleChartRef, LineChartProps >( } ); const chartOptions = useMemo( () => { - const minX = Math.min( ...dataSorted.map( datom => datom.data.at( 0 )?.date ) ); - const maxX = Math.max( ...dataSorted.map( datom => datom.data.at( -1 )?.date ) ); - const diffInHours = Math.abs( differenceInHours( maxX, minX ) ); - - // Show the difference in hours if less than 24 hours; otherwise, display the date. - const formatter = diffInHours <= 24 ? formatHourTick : formatDateTick; + const formatter = getFormatter( dataSorted ); return { axis: { diff --git a/projects/js-packages/charts/src/components/line-chart/test/line-chart.test.tsx b/projects/js-packages/charts/src/components/line-chart/test/line-chart.test.tsx index c69c8e85ea624..77f3247c3f285 100644 --- a/projects/js-packages/charts/src/components/line-chart/test/line-chart.test.tsx +++ b/projects/js-packages/charts/src/components/line-chart/test/line-chart.test.tsx @@ -388,6 +388,48 @@ describe( 'LineChart', () => { expect( ticks.length ).toBeGreaterThan( 1 ); } ); + test( 'renders ticks in short date format.', () => { + renderWithTheme( { + width: 800, + data: [ + { + label: 'Series A', + data: [ + { date: new Date( '2024-01-01' ), value: 10 }, + { date: new Date( '2024-04-01' ), value: 20 }, + { date: new Date( '2024-07-01' ), value: 30 }, + { date: new Date( '2024-10-01' ), value: 40 }, + { date: new Date( '2025-03-01' ), value: 50 }, + ], + }, + ], + } ); + + const ticks = screen.getAllByText( + /^(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) \d+$/ + ); + expect( ticks.length ).toBeGreaterThan( 1 ); + } ); + + test( 'renders ticks in year format.', () => { + renderWithTheme( { + width: 800, + data: [ + { + label: 'Series A', + data: [ + { date: new Date( '2023-01-01' ), value: 10 }, + { date: new Date( '2024-01-01' ), value: 10 }, + { date: new Date( '2025-01-01' ), value: 50 }, + ], + }, + ], + } ); + + const ticks = screen.getAllByText( /^202\d$/ ); + expect( ticks.length ).toBeGreaterThan( 1 ); + } ); + test( 'renders optimal number of ticks.', () => { renderWithTheme( { width: 400,