diff --git a/src/components/area-chart/area-chart.stories.tsx b/src/components/area-chart/area-chart.stories.tsx index 69de4b6f..3ad97839 100644 --- a/src/components/area-chart/area-chart.stories.tsx +++ b/src/components/area-chart/area-chart.stories.tsx @@ -4,7 +4,7 @@ import Label from '../label'; import Button from '../button'; import Badge from '../badge'; import Container from '../container'; -import { ArrowUpRight, ArrowUp } from 'lucide-react'; +import { ArrowUpRight, ArrowUp, AlertCircle } from 'lucide-react'; const areaChartData = [ { month: 'January', sales: 186, expenses: 80 }, @@ -15,7 +15,21 @@ const areaChartData = [ { month: 'June', sales: 214, expenses: 140 }, ]; +// Data with large values to demonstrate Y-axis formatting +const largeValuesData = [ + { month: 'January', pageviews: 1200, sessions: 800 }, + { month: 'February', pageviews: 2800, sessions: 1500 }, + { month: 'March', pageviews: 5500, sessions: 2900 }, + { month: 'April', pageviews: 8200, sessions: 4100 }, + { month: 'May', pageviews: 14000, sessions: 6200 }, + { month: 'June', pageviews: 18500, sessions: 8800 }, +]; + +// Empty data for demonstrating custom no data component +const emptyData: { month: string; sales: number; expenses: number }[] = []; + const dataKeys = [ 'sales', 'expenses' ]; +const largeDataKeys = [ 'pageviews', 'sessions' ]; const chartDataIteractive = [ { date: '2024-04-01', desktop: 222, mobile: 150 }, @@ -121,6 +135,29 @@ const colors = [ // Custom tick formatter function for months const monthFormatter = ( value: string ) => value.slice( 0, 3 ); +// Custom Y-axis formatter function to display values in K/M format +const yAxisFormatter = ( value: number ) => { + if ( value >= 1000000 ) { + return `${ ( value / 1000000 ).toFixed( 1 ) }M`; + } + if ( value >= 1000 ) { + return `${ ( value / 1000 ).toFixed( 1 ) }K`; + } + return value.toString(); +}; + +// Custom No Data Component +const CustomNoDataComponent = () => ( +
+ +
No data found
+

+ There is no data available for this chart at the moment. Try + adjusting your filters or check back later. +

+
+); + const monthFormatterInteractive = ( value: string ) => { const date = new Date( value ); return date.toLocaleDateString( 'en-US', { @@ -187,7 +224,83 @@ export const AreaChartInteractive: Story = { }, }; +export const AreaChartWithFormattedYAxis: Story = { + args: { + chartWidth: 600, + chartHeight: 300, + data: largeValuesData, + dataKeys: largeDataKeys, + colors: [ + { stroke: '#3b82f6', fill: '#BFDBFE' }, + { stroke: '#f97316', fill: '#FFEDD5' }, + ], + variant: 'solid', + showXAxis: true, + xAxisDataKey: 'month', + showYAxis: true, + tickFormatter: monthFormatter, + yAxisTickFormatter: yAxisFormatter, + showLegend: true, + areaChartWrapperProps: { + margin: { + left: 35, + right: 14, + top: 6, + bottom: 6, + }, + }, + }, +}; + +export const AreaChartGradientWithFormattedYAxis: Story = { + args: { + chartWidth: 600, + chartHeight: 300, + data: largeValuesData, + dataKeys: largeDataKeys, + colors: [ + { stroke: '#3b82f6', fill: '#BFDBFE' }, + { stroke: '#f97316', fill: '#FFEDD5' }, + ], + variant: 'gradient', + showXAxis: true, + xAxisDataKey: 'month', + showYAxis: true, + tickFormatter: monthFormatter, + yAxisTickFormatter: yAxisFormatter, + showLegend: true, + areaChartWrapperProps: { + margin: { + left: 35, + right: 14, + top: 6, + bottom: 6, + }, + }, + }, +}; + +export const AreaChartWithCustomNoDataComponent: Story = { + args: { + chartWidth: 600, + chartHeight: 300, + data: emptyData, + dataKeys, + colors, + variant: 'solid', + showXAxis: true, + xAxisDataKey: 'month', + showYAxis: true, + noDataComponent: , + }, +}; + AreaChartInteractive.storyName = 'Area Chart Gradient with Legend'; +AreaChartWithFormattedYAxis.storyName = 'Area Chart with Formatted Y-Axis'; +AreaChartGradientWithFormattedYAxis.storyName = + 'Area Chart Gradient with Formatted Y-Axis'; +AreaChartWithCustomNoDataComponent.storyName = + 'Area Chart with Custom No Data Component'; type Story1 = StoryFn; diff --git a/src/components/area-chart/area-chart.tsx b/src/components/area-chart/area-chart.tsx index a4b24b98..2255206f 100644 --- a/src/components/area-chart/area-chart.tsx +++ b/src/components/area-chart/area-chart.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react'; +import { useEffect, useState, type ReactNode } from 'react'; import { AreaChart as AreaChartWrapper, Area, @@ -14,6 +14,13 @@ import ChartTooltipContent from './chart-tooltip-content'; import Label from '../label'; import type { CategoricalChartProps } from 'recharts/types/chart/generateCategoricalChart'; +// Default color constants +const DEFAULT_FONT_COLOR = '#6B7280'; +const DEFAULT_AREA_COLORS = [ + { stroke: '#2563EB', fill: '#BFDBFE' }, + { stroke: '#38BDF8', fill: '#BAE6FD' }, +]; + interface DataItem { [key: string]: number | string; // Adjust based on your data structure } @@ -56,9 +63,18 @@ interface AreaChartProps { /** Whether to display the ``, adding horizontal and vertical grid lines. */ showCartesianGrid?: boolean; - /** A function used to format the ticks on the axes, e.g., for formatting dates or numbers. */ + /** A function used to format the ticks on the x-axis, e.g., for formatting dates or numbers. */ + xAxisTickFormatter?: ( value: string ) => string; + + /** + * A function used to format the ticks on the x-axis, e.g., for formatting dates or numbers. + * @deprecated Use `xAxisTickFormatter` instead. + */ tickFormatter?: ( value: string ) => string; + /** A function used to format the ticks on the y-axis, e.g., for converting 1000 to 1K. */ + yAxisTickFormatter?: ( value: number ) => string; + /** The key in the data objects representing values for the x-axis. This is used to access the x-axis values from each data entry. */ xAxisDataKey?: string; @@ -85,6 +101,12 @@ interface AreaChartProps { CategoricalChartProps, 'width' | 'height' | 'data' >; + + /** + * Custom component to display when no data is available. + * If not provided, a default "No data available" message will be displayed. + */ + noDataComponent?: ReactNode; } const AreaChart = ( { @@ -99,11 +121,13 @@ const AreaChart = ( { tooltipLabelKey, showLegend = true, showCartesianGrid = true, + xAxisTickFormatter, tickFormatter, + yAxisTickFormatter, xAxisDataKey, yAxisDataKey, xAxisFontSize = 'sm', // sm, md, lg - xAxisFontColor = '#6B7280', + xAxisFontColor = DEFAULT_FONT_COLOR, chartWidth = 350, chartHeight = 200, areaChartWrapperProps = { @@ -114,17 +138,12 @@ const AreaChart = ( { bottom: 6, }, }, + noDataComponent, }: AreaChartProps ) => { const [ width, setWidth ] = useState( chartWidth ); const [ height, setHeight ] = useState( chartHeight ); - // Default colors - const defaultColors: Color[] = [ - { stroke: '#2563EB', fill: '#BFDBFE' }, - { stroke: '#38BDF8', fill: '#BAE6FD' }, - ]; - - const appliedColors = colors.length > 0 ? colors : defaultColors; + const appliedColors = colors.length > 0 ? colors : DEFAULT_AREA_COLORS; useEffect( () => { setWidth( chartWidth ); @@ -167,9 +186,11 @@ const AreaChart = ( { if ( ! data || data.length === 0 ) { return ( - + noDataComponent || ( + + ) ); } @@ -182,7 +203,7 @@ const AreaChart = ( { tickLine={ false } axisLine={ false } tickMargin={ 8 } - tickFormatter={ tickFormatter } + tickFormatter={ xAxisTickFormatter || tickFormatter } tick={ { fontSize: fontSizeVariant, fill: xAxisFontColor, @@ -195,6 +216,7 @@ const AreaChart = ( { tickLine={ false } axisLine={ false } tickMargin={ 8 } + tickFormatter={ yAxisTickFormatter } tick={ { fontSize: fontSizeVariant, fill: xAxisFontColor, diff --git a/src/components/line-chart/line-chart.stories.tsx b/src/components/line-chart/line-chart.stories.tsx index c270c89e..ea1ce637 100644 --- a/src/components/line-chart/line-chart.stories.tsx +++ b/src/components/line-chart/line-chart.stories.tsx @@ -29,8 +29,22 @@ const biaxialChartData = [ { month: 'June', visits: 214, revenue: 6800 }, ]; +// Data with large values to demonstrate Y-axis formatting +const largeValuesData = [ + { month: 'January', pageviews: 1200, users: 500 }, + { month: 'February', pageviews: 2500, users: 1200 }, + { month: 'March', pageviews: 5000, users: 2300 }, + { month: 'April', pageviews: 7800, users: 3600 }, + { month: 'May', pageviews: 12000, users: 4800 }, + { month: 'June', pageviews: 15000, users: 6500 }, +]; + +// Empty data for demonstrating custom no data component +const emptyData: { month: string; desktop: number }[] = []; + const dataKeys = [ 'desktop', 'mobile' ]; const biaxialDataKeys = [ 'visits', 'revenue' ]; +const largeDataKeys = [ 'pageviews', 'users' ]; const colors = [ { stroke: '#2563EB' }, { stroke: '#38BDF8' } ]; @@ -48,6 +62,42 @@ export default meta; // Custom tick formatter function for months const monthFormatter = ( value: string ) => value.slice( 0, 3 ); +// Custom Y-axis formatter function to display values in K/M format +const yAxisFormatter = ( value: number ) => { + if ( value >= 1000000 ) { + return `${ ( value / 1000000 ).toFixed( 1 ) }M`; + } + if ( value >= 1000 ) { + return `${ ( value / 1000 ).toFixed( 1 ) }K`; + } + return value.toString(); +}; + +// Custom No Data Component +const CustomNoDataComponent = () => ( +
+ + + + +
No chart data available
+

+ Please select a different time period or check your filters +

+
+); + type Story = StoryObj; export const LineChartSimple: Story = { @@ -59,10 +109,13 @@ export const LineChartSimple: Story = { showYAxis: false, showTooltip: true, showCartesianGrid: true, - tickFormatter: monthFormatter, + xAxisTickFormatter: monthFormatter, xAxisDataKey: 'month', xAxisFontSize: 'sm', withDots: false, + lineChartWrapperProps: { + margin: { top: 5, right: 15, bottom: 5, left: 15 }, + }, }, }; @@ -75,10 +128,13 @@ export const LineChartWithDots: Story = { showYAxis: false, showTooltip: true, showCartesianGrid: true, - tickFormatter: monthFormatter, + xAxisTickFormatter: monthFormatter, xAxisDataKey: 'month', xAxisFontSize: 'sm', withDots: true, + lineChartWrapperProps: { + margin: { top: 5, right: 15, bottom: 5, left: 15 }, + }, }, }; @@ -91,10 +147,13 @@ export const LineChartMultiple: Story = { showYAxis: false, showTooltip: true, showCartesianGrid: true, - tickFormatter: monthFormatter, + xAxisTickFormatter: monthFormatter, xAxisDataKey: 'month', xAxisFontSize: 'sm', withDots: false, + lineChartWrapperProps: { + margin: { top: 5, right: 15, bottom: 5, left: 15 }, + }, }, }; @@ -107,7 +166,7 @@ export const BiaxialLineChart: Story = { showYAxis: true, showTooltip: true, showCartesianGrid: true, - tickFormatter: monthFormatter, + xAxisTickFormatter: monthFormatter, xAxisDataKey: 'month', xAxisFontSize: 'sm', withDots: true, @@ -115,7 +174,68 @@ export const BiaxialLineChart: Story = { chartWidth: 500, chartHeight: 300, lineChartWrapperProps: { - margin: { top: 5, right: 5, bottom: 5, left: 5 }, + margin: { top: 5, right: 45, bottom: 5, left: 5 }, + }, + yAxisFontColor: [ '#3b82f6', '#10B981' ], + }, +}; + +export const LineChartWithFormattedYAxis: Story = { + args: { + data: largeValuesData, + dataKeys: largeDataKeys, + colors: [ { stroke: '#3b82f6' }, { stroke: '#f97316' } ], + showXAxis: true, + showYAxis: true, + showTooltip: true, + showCartesianGrid: true, + xAxisTickFormatter: monthFormatter, + yAxisTickFormatter: yAxisFormatter, + xAxisDataKey: 'month', + xAxisFontSize: 'sm', + withDots: true, + chartWidth: 500, + chartHeight: 300, + lineChartWrapperProps: { + margin: { top: 5, right: 15, bottom: 5, left: 35 }, }, }, }; + +export const BiaxialLineChartWithFormattedYAxis: Story = { + args: { + data: largeValuesData, + dataKeys: largeDataKeys, + colors: [ { stroke: '#2563EB' }, { stroke: '#10B981' } ], + showXAxis: true, + showYAxis: true, + showTooltip: true, + showCartesianGrid: true, + xAxisTickFormatter: monthFormatter, + yAxisTickFormatter: yAxisFormatter, + xAxisDataKey: 'month', + xAxisFontSize: 'sm', + withDots: true, + biaxial: true, + chartWidth: 500, + chartHeight: 300, + lineChartWrapperProps: { + margin: { top: 5, right: 45, bottom: 5, left: 35 }, + }, + yAxisFontColor: [ '#3b82f6', '#10B981' ], + }, +}; + +export const LineChartWithCustomNoDataComponent: Story = { + args: { + data: emptyData, + dataKeys: [ 'desktop' ], + colors: [ { stroke: '#3b82f6' } ], + showXAxis: true, + showYAxis: true, + xAxisDataKey: 'month', + chartWidth: 500, + chartHeight: 300, + noDataComponent: , + }, +}; diff --git a/src/components/line-chart/line-chart.tsx b/src/components/line-chart/line-chart.tsx index ba160144..cd5f634c 100644 --- a/src/components/line-chart/line-chart.tsx +++ b/src/components/line-chart/line-chart.tsx @@ -10,6 +10,12 @@ import { import ChartTooltipContent from './chart-tooltip-content'; import Label from '../label'; import type { CategoricalChartProps } from 'recharts/types/chart/generateCategoricalChart'; +import { type ReactNode } from 'react'; + +// Default color constants +const DEFAULT_FONT_COLOR = '#6B7280'; +const DEFAULT_GRID_COLOR = '#E5E7EB'; +const DEFAULT_LINE_COLORS = [ { stroke: '#2563EB' }, { stroke: '#38BDF8' } ]; interface DataItem { [key: string]: number | string; @@ -47,8 +53,17 @@ interface LineChartProps { showCartesianGrid?: boolean; /** A function used to format the ticks on the x-axis, e.g., for formatting dates or numbers. */ + xAxisTickFormatter?: ( value: string ) => string; + + /** + * A function used to format the ticks on the x-axis, e.g., for formatting dates or numbers. + * @deprecated Use `xAxisTickFormatter` instead. + */ tickFormatter?: ( value: string ) => string; + /** A function used to format the ticks on the y-axis, e.g., for converting 1000 to 1K. */ + yAxisTickFormatter?: ( value: number ) => string; + /** The key in the data objects representing values for the x-axis. */ xAxisDataKey?: string; @@ -61,8 +76,12 @@ interface LineChartProps { /** Font color for the labels on the x-axis. */ xAxisFontColor?: string; - /** Font color for the labels on the y-axis. */ - yAxisFontColor?: string; + /** + * Font color for the labels on the y-axis. + * When biaxial is true, you can provide an array of two colors [leftAxisColor, rightAxisColor]. + * If a single color is provided, it will be used for both axes. + */ + yAxisFontColor?: string | string[]; /** Width of the chart container. */ chartWidth?: number | string; @@ -98,6 +117,12 @@ interface LineChartProps { * Biaxial chart. */ biaxial?: boolean; + + /** + * Custom component to display when no data is available. + * If not provided, a default "No data available" message will be displayed. + */ + noDataComponent?: ReactNode; } const LineChart = ( { @@ -110,23 +135,24 @@ const LineChart = ( { tooltipIndicator = 'dot', // dot, line, dashed tooltipLabelKey, showCartesianGrid = true, + xAxisTickFormatter, + yAxisTickFormatter, tickFormatter, xAxisDataKey, yAxisDataKey, xAxisFontSize = 'sm', // sm, md, lg - xAxisFontColor = '#6B7280', - yAxisFontColor = '#6B7280', + xAxisFontColor = DEFAULT_FONT_COLOR, + yAxisFontColor = DEFAULT_FONT_COLOR, chartWidth = 350, chartHeight = 200, withDots = false, lineChartWrapperProps, strokeDasharray = '3 3', - gridColor = '#E5E7EB', + gridColor = DEFAULT_GRID_COLOR, biaxial = false, + noDataComponent, }: LineChartProps ) => { - const defaultColors = [ { stroke: '#2563EB' }, { stroke: '#38BDF8' } ]; - - const appliedColors = colors.length > 0 ? colors : defaultColors; + const appliedColors = colors.length > 0 ? colors : DEFAULT_LINE_COLORS; const fontSizeMap = { sm: '12px', @@ -136,11 +162,23 @@ const LineChart = ( { const fontSizeVariant = fontSizeMap[ xAxisFontSize ] || fontSizeMap.sm; + // Handle Y-axis colors for biaxial chart + const getYAxisFontColor = ( index = 0 ) => { + if ( Array.isArray( yAxisFontColor ) ) { + return ( + yAxisFontColor[ index ] || yAxisFontColor[ 0 ] || DEFAULT_FONT_COLOR + ); + } + return yAxisFontColor; + }; + if ( ! data || data.length === 0 ) { return ( - + noDataComponent || ( + + ) ); } @@ -159,7 +197,7 @@ const LineChart = ( { tickLine={ false } axisLine={ false } tickMargin={ 8 } - tickFormatter={ tickFormatter } + tickFormatter={ xAxisTickFormatter || tickFormatter } tick={ { fontSize: fontSizeVariant, fill: xAxisFontColor, @@ -173,9 +211,10 @@ const LineChart = ( { tickLine={ false } axisLine={ false } tickMargin={ 8 } + tickFormatter={ yAxisTickFormatter } tick={ { fontSize: fontSizeVariant, - fill: yAxisFontColor, + fill: getYAxisFontColor( 0 ), } } hide={ ! showYAxis } orientation="left" @@ -188,9 +227,10 @@ const LineChart = ( { tickLine={ false } axisLine={ false } tickMargin={ 8 } + tickFormatter={ yAxisTickFormatter } tick={ { fontSize: fontSizeVariant, - fill: yAxisFontColor, + fill: getYAxisFontColor( 1 ), } } orientation="right" hide={ ! showYAxis } diff --git a/src/components/text/text.stories.tsx b/src/components/text/text.stories.tsx index 32138d79..0f4738d8 100644 --- a/src/components/text/text.stories.tsx +++ b/src/components/text/text.stories.tsx @@ -52,7 +52,8 @@ export default { 'h6', 'div', ], - description: 'The element to render the text as. Any HTML element or React component can be added here.', + description: + 'The element to render the text as. Any HTML element or React component can be added here.', }, children: { control: 'text',