Skip to content

feat: pass axis padding info to custom tick components#6163

Merged
ckifer merged 5 commits intorecharts:mainfrom
shreedharbhat98:chore-pass-padding-to-custom-tick
Aug 1, 2025
Merged

feat: pass axis padding info to custom tick components#6163
ckifer merged 5 commits intorecharts:mainfrom
shreedharbhat98:chore-pass-padding-to-custom-tick

Conversation

@shreedharbhat98
Copy link
Copy Markdown
Contributor

@shreedharbhat98 shreedharbhat98 commented Jul 31, 2025

Pass Axis Padding Info to Custom Tick Components

Description

This pull request implements a feature that allows custom tick components to receive axis padding information. Custom tick components can now access the padding prop to make informed positioning and styling decisions.

Key Changes:

  • Enhanced CartesianAxis interface with optional padding prop
  • Modified XAxisImpl and YAxisImpl to retrieve and pass padding from axis settings via Redux selectors
  • Added padding to tick props passed to custom tick components
  • Comprehensive unit test coverage integrated into existing test suites
  • Added Storybook stories demonstrating the new functionality

The feature enables developers to create more sophisticated custom ticks that respond to padding configuration, eliminating the need to duplicate padding values or make assumptions about axis layout.

Related Issue

Closes #6130 - Custom tick component should get axis padding info

Motivation and Context

Previously, when using custom tick components with XAxis or YAxis, developers faced several challenges:

  • No access to padding configuration: Custom components couldn't access the axis padding values
  • Fragile positioning logic: Had to duplicate padding configuration or make assumptions
  • Limited customization: Couldn't create responsive ticks that adapt to padding changes

This change solves these problems by providing direct access to padding information, enabling:

  • Proper alignment of custom tick content with axis layout
  • Precise positioning calculations based on actual padding values
  • Sophisticated custom ticks that respond to padding configuration

How Has This Been Tested?

Comprehensive Test Suite

  • Unit Tests: Added 8 new tests integrated into existing test suites
    • XAxis tests (4 tests in test/cartesian/XAxis.spec.tsx):
      • Object padding correctly passed to custom tick components
      • String padding correctly passed to custom tick components
      • Function-based custom ticks receive padding information
      • Default padding behavior when no padding is specified
    • YAxis tests (4 tests in test/cartesian/YAxis/YAxis.spec.tsx):
      • Object padding correctly passed to custom tick components
      • String padding correctly passed to custom tick components
      • Function-based custom ticks receive padding information
      • Default padding behavior when no padding is specified

Regression Testing

  • Existing Test Coverage: All existing tests continue to pass
  • Backward Compatibility: Verified existing custom tick components work unchanged
  • Type Safety: TypeScript compilation validates type correctness with proper interface definitions

Storybook Stories

  • Interactive Examples: Added comprehensive stories in XAxis.stories.tsx and YAxis.stories.tsx
    • CustomTickWithPadding: Demonstrates React element-based custom ticks with padding display
    • CustomTickFunction: Shows function-based custom ticks with dynamic positioning based on padding

Test Examples

// Test: XAxis padding passed to custom tick
interface TickProps {
  x?: number;
  y?: number; 
  payload?: { value: string | number };
  padding?: { left?: number; right?: number } | 'gap' | 'no-gap';
}

const CustomXAxisTick = (props: TickProps) => {
  expect(props.padding).toEqual({ left: 20, right: 30 });
  return <text {...props}>Custom Tick</text>;
};

render(
  <LineChart width={400} height={400} data={data}>
    <XAxis padding={{ left: 20, right: 30 }} tick={<CustomXAxisTick />} />
  </LineChart>
);

Usage Examples

Basic XAxis Custom Tick

const CustomXAxisTick = (props) => {
  const { x, y, payload, padding } = props;
  
  return (
    <g transform={`translate(${x},${y})`}>
      <text x={0} y={0} dy={16} textAnchor="middle" fill="#666">
        {payload.value}
      </text>
      {padding && 'left' in padding && (
        <text x={0} y={0} dy={32} textAnchor="middle" fill="#999" fontSize="8">
          L:{padding.left} R:{padding.right}
        </text>
      )}
    </g>
  );
};

<XAxis
  dataKey="name"
  padding={{ left: 20, right: 30 }}
  tick={<CustomXAxisTick />}
/>

Advanced Use Cases

Responsive Tick Positioning

const ResponsiveTick = ({ x, y, payload, padding }) => {
  const hasLeftPadding = padding?.left > 10;
  const textAnchor = hasLeftPadding ? 'start' : 'middle';
  
  return (
    <text x={x} y={y} textAnchor={textAnchor} fill="#333">
      {payload.value}
    </text>
  );
};

Function-based Custom Tick with Dynamic Positioning

const customTickFunction = (props) => {
  const { x, y, payload, padding } = props;
  
  // Calculate dynamic offset based on padding
  const xOffset = padding && typeof padding === 'object' && 'left' in padding 
    ? Math.max(0, padding.left * 0.1) 
    : 0;
    
  const isCompact = padding === 'no-gap';
  const fontSize = isCompact ? '10px' : '12px';
  
  return (
    <text x={x + xOffset} y={y + 15} textAnchor="middle" fontSize={fontSize}>
      {payload.value}
    </text>
  );
};

Implementation Details

Type Definitions

  • XAxis padding: XAxisPadding = { left?: number; right?: number } | 'gap' | 'no-gap'
  • YAxis padding: YAxisPadding = { top?: number; bottom?: number } | 'gap' | 'no-gap'

Files Modified

  • src/cartesian/CartesianAxis.tsx - Added padding prop interface and tick integration
  • src/cartesian/XAxis.tsx - Added padding retrieval from axis settings via Redux selector
  • src/cartesian/YAxis.tsx - Added padding retrieval from axis settings via Redux selector
  • test/cartesian/XAxis.spec.tsx - Comprehensive unit test coverage for XAxis custom tick padding
  • test/cartesian/YAxis/YAxis.spec.tsx - Comprehensive unit test coverage for YAxis custom tick padding
  • storybook/stories/API/cartesian/XAxis.stories.tsx - Interactive examples and demos
  • storybook/stories/API/cartesian/YAxis.stories.tsx - Interactive examples and demos

Data Flow

  1. User sets padding prop on XAxis/YAxis
  2. Axis settings store padding in Redux state via cartesianAxisSlice
  3. XAxisImpl/YAxisImpl retrieve padding from settings using selectXAxisSettings/selectYAxisSettings selectors
  4. Padding passed to CartesianAxis component as a prop
  5. CartesianAxis includes padding in tick props when rendering custom tick components
  6. Custom tick components receive padding via props interface

Types of changes

  • New feature (non-breaking change which adds functionality)
  • Bug fix (non-breaking change which fixes an issue)
  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist:

  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • I have added tests to cover my changes.
  • I have added a storybook story or extended an existing story to show my changes

Backward Compatibility

100% Backward Compatible

  • Existing custom tick components work unchanged
  • Optional padding prop doesn't break existing code
  • No changes to public APIs or default behavior
  • All existing tests continue to pass

Performance Impact

  • Zero performance impact - leverages existing Redux state management
  • Minimal memory overhead - adds one optional prop to tick interface
  • No additional re-renders - uses existing data flow and selectors

Migration Guide

No migration required! Existing code continues to work unchanged.

To use the new feature:

// Before (still works)
const MyTick = ({ x, y, payload }) => (
  <text x={x} y={y}>{payload.value}</text>
);

// After (with padding access)
const MyTick = ({ x, y, payload, padding }) => {
  // Use padding for enhanced positioning
  return <text x={x} y={y}>{payload.value}</text>;
};

The new feature is completely opt-in - custom tick components that don't use the padding prop will continue to work exactly as before.

@shreedharbhat98
Copy link
Copy Markdown
Contributor Author

shreedharbhat98 commented Jul 31, 2025

Hi @ckifer 👋

I’ve opened a PR to address this issue. Would really appreciate your feedback when you get a chance! 🙏

Also, I wasn’t sure which Node.js version to use while setting up the project. Adding an .nvmrc file with the recommended version could help contributors get started more smoothly—happy to help add it if needed.

Thanks in advance!

@ckifer
Copy link
Copy Markdown
Member

ckifer commented Jul 31, 2025

Hey, thanks for the PR. Node version shouldn't matter too much as long as it's LTS but ack we can do that

Copy link
Copy Markdown
Member

@ckifer ckifer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR looks good but your PR description says you have added test coverage when there are no unit tests. Can we add an assertion for each axis to the tests?

@codecov
Copy link
Copy Markdown

codecov bot commented Jul 31, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 96.53%. Comparing base (751bce3) to head (10491e3).
⚠️ Report is 4 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #6163      +/-   ##
==========================================
+ Coverage   96.50%   96.53%   +0.02%     
==========================================
  Files         217      217              
  Lines       19825    19826       +1     
  Branches     4081     4084       +3     
==========================================
+ Hits        19133    19139       +6     
+ Misses        686      681       -5     
  Partials        6        6              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@codecov
Copy link
Copy Markdown

codecov bot commented Jul 31, 2025

Bundle Report

Changes will increase total bundle size by 332 bytes (0.01%) ⬆️. This is within the configured threshold ✅

Detailed changes
Bundle name Size Change
recharts/bundle-cjs 1.02MB 332 bytes (0.03%) ⬆️

Affected Assets, Files, and Routes:

view changes for bundle: recharts/bundle-cjs

Assets Changed:

Asset Name Size Change Total Size Change (%)
cartesian/CartesianAxis.js 32 bytes 13.92kB 0.23%
cartesian/YAxis.js 150 bytes 8.96kB 1.7%
cartesian/XAxis.js 150 bytes 7.3kB 2.1%

@shreedharbhat98
Copy link
Copy Markdown
Contributor Author

PR looks good but your PR description says you have added test coverage when there are no unit tests. Can we add an assertion for each axis to the tests?

Thanks for the review. I've added the tests to cover the changes now.

Copy link
Copy Markdown
Member

@ckifer ckifer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test file is in a random folder. Can you remove the folder and just put the tests in XAxis.spec and YAxis.spec?

Don't let AI do it for you 😅

Comment thread test/feature/custom-tick-padding.test.tsx Outdated
@shreedharbhat98
Copy link
Copy Markdown
Contributor Author

This test file is in a random folder. Can you remove the folder and just put the tests in XAxis.spec and YAxis.spec?

I think, now it should be okay! Feel free to drop your thoughts.
Thanks

Comment thread src/cartesian/YAxis.tsx Outdated
const axisSize = useAppSelector(state => selectYAxisSize(state, yAxisId));
const position = useAppSelector(state => selectYAxisPosition(state, yAxisId));
const cartesianTickItems = useAppSelector(state => selectTicksOfAxis(state, axisType, yAxisId, isPanorama));
const axisSettings = useAppSelector(state => selectYAxisSettings(state, yAxisId));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

now that I think about it, why do you need to select this from axis settings when this is the component has access to padding in props?

you should be able to access padding with props.padding for both axes.

See line 153

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, Without it also works

Comment thread src/cartesian/YAxis.tsx Outdated
className={clsx(`recharts-${axisType} ${axisType}`, className)}
viewBox={viewBox}
ticks={cartesianTickItems}
padding={axisSettings.padding}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

padding should already be passed with {...allOtherProps}

Copy link
Copy Markdown
Member

@ckifer ckifer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thank you! The selection from state would've caused an extra re-render so this is much nicer

@shreedharbhat98
Copy link
Copy Markdown
Contributor Author

Hey @ckifer If everything looks good, Would you mind merging this PR ?

Thanks

@ckifer
Copy link
Copy Markdown
Member

ckifer commented Aug 1, 2025

Ah my bad I thought I did already

@ckifer ckifer merged commit 8119475 into recharts:main Aug 1, 2025
2 checks passed
@shreedharbhat98 shreedharbhat98 deleted the chore-pass-padding-to-custom-tick branch August 1, 2025 15:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Custom tick component should get axis padding info

2 participants