Skip to content

[Refactor] Split combineConfiguredScale into its own function#6930

Merged
ckifer merged 1 commit into
mainfrom
combineConfiguredScale
Jan 28, 2026
Merged

[Refactor] Split combineConfiguredScale into its own function#6930
ckifer merged 1 commit into
mainfrom
combineConfiguredScale

Conversation

@PavelVanecek

@PavelVanecek PavelVanecek commented Jan 26, 2026

Copy link
Copy Markdown
Collaborator

Description

I am going to reuse this combiner in another selector later and this turned out to be a fairly big change that I can isolate so let's merge this refactor first.

Related Issue

#1678

Summary by CodeRabbit

  • Refactor

    • Restructured scale selection logic to use a two-step configuration and factory pattern.
    • Simplified scale factory API for improved code maintainability.
  • Chores

    • Added stricter type assertion rules for enhanced code quality.
    • Reorganized scale combiner utilities for better modularity.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitai Bot commented Jan 26, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

This PR refactors the scale handling system by introducing a two-step approach: creating a CustomScaleDefinition configuration via combineConfiguredScale, then materializing it into a RechartsScale via a simplified rechartsScaleFactory. New scale combiners are added, selectors are updated to use this pattern, and test files are adapted accordingly.

Changes

Cohort / File(s) Summary
Configuration & Linting
eslint.config.mjs
Added ESLint configuration block targeting src/util/scale/** with rules for type assertions and comments.
Snapshot Files
scripts/snapshots/es6Files.txt, scripts/snapshots/libFiles.txt, scripts/snapshots/typesFiles.txt
Added entries for two new combiner modules: combineConfiguredScale and combineRealScaleType across ES6, lib, and type declaration snapshots.
Scale Infrastructure
src/util/scale/RechartsScale.ts
Simplified rechartsScaleFactory to accept optional CustomScaleDefinition and return RechartsScale | undefined. Removed multi-parameter overloads and getD3ScaleFromType helper; eliminated dependencies on d3-scale and AxisRange.
Scale Combiners (New)
src/state/selectors/combiners/combineConfiguredScale.ts, src/state/selectors/combiners/combineRealScaleType.ts
Introduced two new modules: combineConfiguredScale (90 lines) with overloaded signatures for normalizing scales and applying domains/ranges, and combineRealScaleType (41 lines) for determining appropriate D3 scale types based on axis configuration.
Axis & Scale Selectors
src/state/selectors/axisSelectors.ts, src/state/selectors/polarScaleSelectors.ts, src/state/selectors/tooltipSelectors.ts
Refactored to use two-step scale construction: internal selectors compute CustomScaleDefinition via combineConfiguredScale, then public selectors apply rechartsScaleFactory. Replaced combineScaleFunction with new combiner pattern and updated imports.
Test Files
test/cartesian/ReferenceLine/getEndPoints.spec.tsx, test/helper/mockAxes.ts, test/util/CartesianUtils/CartesianUtils.spec.ts, test/util/ChartUtils.spec.tsx
Updated factory function name from d3ScaleToRechartsScale to rechartsScaleFactory across all scale initialization calls; imports and test logic remain functionally equivalent.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

refactor

Suggested reviewers

  • ckifer
🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The PR description is incomplete. While it explains the motivation (reusing combiner in another selector), it lacks sections for Related Issue link format, Motivation and Context details, testing information, and type of change checkboxes as specified in the template. Fill in the description template more completely: formally link the issue, expand motivation/context, document testing approach, and mark the appropriate change type checkboxes.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: extracting combineConfiguredScale into its own function. It is specific, concise, and reflects the primary refactoring objective.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/state/selectors/combiners/combineRealScaleType.ts`:
- Around line 6-38: The function combineRealScaleType currently builds a
prefixed key (`scale${upperFirst(scale)}`) and returns it, which doesn't match
the D3ScaleType union; instead, validate existence using the prefixed key but
return the original unprefixed scale string (or fall back to 'point').
Concretely: inside combineRealScaleType where you compute `const name =
\`scale\${upperFirst(scale)}\``, call the validator against `name` (keep using
d3Scales and upperFirst) but if it passes return `scale as D3ScaleType`; if it
fails return `'point'`. Also update or replace isSupportedScaleName so it
returns a boolean (not a `name is D3ScaleType` type guard) since it checks
prefixed keys in d3Scales, or add a new boolean helper used only for the
prefixed-key check. Ensure all branches return values assignable to D3ScaleType
| undefined.

Comment on lines +6 to +38
function isSupportedScaleName(name: string): name is D3ScaleType {
return name in d3Scales;
}

export const combineRealScaleType = (
axisConfig: BaseCartesianAxis | undefined,
hasBar: boolean,
chartType: string,
): D3ScaleType | undefined => {
if (axisConfig == null) {
return undefined;
}
const { scale, type } = axisConfig;
if (scale === 'auto') {
if (
type === 'category' &&
chartType &&
(chartType.indexOf('LineChart') >= 0 ||
chartType.indexOf('AreaChart') >= 0 ||
(chartType.indexOf('ComposedChart') >= 0 && !hasBar))
) {
return 'point';
}
if (type === 'category') {
return 'band';
}

return 'linear';
}
if (typeof scale === 'string') {
const name = `scale${upperFirst(scale)}`;

return isSupportedScaleName(name) ? name : 'point';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "combineRealScaleType.ts" | head -20

Repository: recharts/recharts

Length of output: 116


🏁 Script executed:

find . -type f \( -name "*.ts" -o -name "*.tsx" \) | grep -i scale | head -20

Repository: recharts/recharts

Length of output: 1011


🏁 Script executed:

fd -t f "combineRealScaleType" -o fd -t f "d3Scale" | head -20

Repository: recharts/recharts

Length of output: 184


🏁 Script executed:

cat -n ./src/state/selectors/combiners/combineRealScaleType.ts

Repository: recharts/recharts

Length of output: 1435


🏁 Script executed:

# Find D3ScaleType definition
rg "type D3ScaleType|interface D3ScaleType" -A 5

Repository: recharts/recharts

Length of output: 251


🏁 Script executed:

# Find d3Scales definition
rg "const d3Scales|const.*d3Scales" -A 10

Repository: recharts/recharts

Length of output: 43


🏁 Script executed:

# Search for where d3Scales is used and what it contains
rg "victory-vendor/d3-scale" -A 2

Repository: recharts/recharts

Length of output: 3351


🏁 Script executed:

# Get complete D3ScaleType definition
rg "type D3ScaleType" -A 20

Repository: recharts/recharts

Length of output: 827


🏁 Script executed:

# Check how combineRealScaleType return value is used downstream
rg "combineRealScaleType" -B 2 -A 5

Repository: recharts/recharts

Length of output: 6208


🏁 Script executed:

# Let me check the actual keys in d3Scales to confirm the review is correct
python3 << 'EOF'
# Check what the actual keys would be in d3Scales
d3_scales = {
    'scaleLinear': None,
    'scaleBand': None,
    'scalePoint': None,
    'scaleLog': None,
    'scalePow': None,
    'scaleSqrt': None,
    'scaleSymlog': None,
    'scaleTime': None,
    'scaleUtc': None,
    'scaleIdentity': None,
    'scaleOrdinal': None,
    'scaleQuantile': None,
    'scaleQuantize': None,
    'scaleSequential': None,
    'scaleThreshold': None,
}

d3_scale_type = {
    'linear', 'pow', 'sqrt', 'log', 'symlog', 'identity', 'time', 
    'band', 'point', 'ordinal', 'quantile', 'quantize', 'utc', 'sequential', 'threshold'
}

# Check if scaleLinear is in d3_scale_type
test_value = 'scaleLinear'
print(f"Is '{test_value}' in D3ScaleType? {test_value in d3_scale_type}")

# Verify the mapping
print("\nVerifying scale name mappings:")
for scale_name in d3_scale_type:
    prefixed = f'scale{scale_name[0].upper()}{scale_name[1:]}'
    is_in_d3 = prefixed in d3_scales
    print(f"  '{scale_name}' -> '{prefixed}' (in d3Scales: {is_in_d3})")
EOF

Repository: recharts/recharts

Length of output: 147


🏁 Script executed:

# Check test file to see how combineRealScaleType is tested
cat ./test/state/selectors/selectRealScaleType.spec.tsx

Repository: recharts/recharts

Length of output: 1061


🏁 Script executed:

# Check combineConfiguredScale to see how it uses the scaleType
cat -n ./src/state/selectors/combiners/combineConfiguredScale.ts | head -100

Repository: recharts/recharts

Length of output: 4208


🏁 Script executed:

# Search for where the return value of combineRealScaleType is actually used
rg "selectRealScaleType|combineRealScaleType" -B 3 -A 10 | head -80

Repository: recharts/recharts

Length of output: 6915


🏁 Script executed:

# Check if there are any direct comparisons or type checks on the return value
rg "realScaleType.*==|realScaleType.*===|realScaleType.*in " -A 2 -B 2

Repository: recharts/recharts

Length of output: 2235


Return unprefixed scale types; current logic emits scale* keys that don't match D3ScaleType

combineRealScaleType returns scale${upperFirst(scale)} (e.g., scaleBand) which is not part of the D3ScaleType union ('band' | 'linear' | ...). This causes type inconsistency: the function signature claims it returns D3ScaleType | undefined, but the actual value is a d3-scale key that doesn't match the type. Downstream code already has workarounds checking for both prefixed and unprefixed values, with @ts-expect-error comments acknowledging the mismatch. The type guard is also unsound—it narrows to D3ScaleType but actually validates against d3Scales keys. Validate via the prefixed key but return the original unprefixed scale string.

🔧 Proposed fix
-function isSupportedScaleName(name: string): name is D3ScaleType {
-  return name in d3Scales;
-}
+function isSupportedScaleType(scale: string): scale is D3ScaleType {
+  const name = `scale${upperFirst(scale)}`;
+  return name in d3Scales;
+}

 ...
   if (typeof scale === 'string') {
-    const name = `scale${upperFirst(scale)}`;
-
-    return isSupportedScaleName(name) ? name : 'point';
+    return isSupportedScaleType(scale) ? scale : 'point';
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function isSupportedScaleName(name: string): name is D3ScaleType {
return name in d3Scales;
}
export const combineRealScaleType = (
axisConfig: BaseCartesianAxis | undefined,
hasBar: boolean,
chartType: string,
): D3ScaleType | undefined => {
if (axisConfig == null) {
return undefined;
}
const { scale, type } = axisConfig;
if (scale === 'auto') {
if (
type === 'category' &&
chartType &&
(chartType.indexOf('LineChart') >= 0 ||
chartType.indexOf('AreaChart') >= 0 ||
(chartType.indexOf('ComposedChart') >= 0 && !hasBar))
) {
return 'point';
}
if (type === 'category') {
return 'band';
}
return 'linear';
}
if (typeof scale === 'string') {
const name = `scale${upperFirst(scale)}`;
return isSupportedScaleName(name) ? name : 'point';
function isSupportedScaleType(scale: string): scale is D3ScaleType {
const name = `scale${upperFirst(scale)}`;
return name in d3Scales;
}
export const combineRealScaleType = (
axisConfig: BaseCartesianAxis | undefined,
hasBar: boolean,
chartType: string,
): D3ScaleType | undefined => {
if (axisConfig == null) {
return undefined;
}
const { scale, type } = axisConfig;
if (scale === 'auto') {
if (
type === 'category' &&
chartType &&
(chartType.indexOf('LineChart') >= 0 ||
chartType.indexOf('AreaChart') >= 0 ||
(chartType.indexOf('ComposedChart') >= 0 && !hasBar))
) {
return 'point';
}
if (type === 'category') {
return 'band';
}
return 'linear';
}
if (typeof scale === 'string') {
return isSupportedScaleType(scale) ? scale : 'point';
}
🤖 Prompt for AI Agents
In `@src/state/selectors/combiners/combineRealScaleType.ts` around lines 6 - 38,
The function combineRealScaleType currently builds a prefixed key
(`scale${upperFirst(scale)}`) and returns it, which doesn't match the
D3ScaleType union; instead, validate existence using the prefixed key but return
the original unprefixed scale string (or fall back to 'point'). Concretely:
inside combineRealScaleType where you compute `const name =
\`scale\${upperFirst(scale)}\``, call the validator against `name` (keep using
d3Scales and upperFirst) but if it passes return `scale as D3ScaleType`; if it
fails return `'point'`. Also update or replace isSupportedScaleName so it
returns a boolean (not a `name is D3ScaleType` type guard) since it checks
prefixed keys in d3Scales, or add a new boolean helper used only for the
prefixed-key check. Ensure all branches return values assignable to D3ScaleType
| undefined.

@codecov

codecov Bot commented Jan 26, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.82759% with 6 lines in your changes missing coverage. Please review.
✅ Project coverage is 94.26%. Comparing base (8047386) to head (44f83ee).
⚠️ Report is 10 commits behind head on main.

Files with missing lines Patch % Lines
...tate/selectors/combiners/combineConfiguredScale.ts 91.30% 4 Missing ⚠️
.../state/selectors/combiners/combineRealScaleType.ts 94.28% 2 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             main    #6930   +/-   ##
=======================================
  Coverage   94.26%   94.26%           
=======================================
  Files         569      571    +2     
  Lines       55746    55767   +21     
  Branches     5185     5187    +2     
=======================================
+ Hits        52550    52571   +21     
  Misses       3187     3187           
  Partials        9        9           

☔ 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.

@github-actions

Copy link
Copy Markdown
Contributor

Staging Deployment Details

These deployments will remain available for 30 days.

To update snapshots: Comment /update-snapshots on this PR to automatically update the baseline screenshots.

@codecov

codecov Bot commented Jan 27, 2026

Copy link
Copy Markdown

Bundle Report

Changes will increase total bundle size by 2.51kB (0.09%) ⬆️. This is within the configured threshold ✅

Detailed changes
Bundle name Size Change
recharts/bundle-cjs 1.21MB 1.44kB (0.12%) ⬆️
recharts/bundle-umd 529.54kB 57 bytes (0.01%) ⬆️
recharts/bundle-es6 1.05MB 1.02kB (0.1%) ⬆️

Affected Assets, Files, and Routes:

view changes for bundle: recharts/bundle-cjs

Assets Changed:

Asset Name Size Change Total Size Change (%)
state/selectors/axisSelectors.js -1.49kB 62.56kB -2.33%
state/selectors/tooltipSelectors.js 367 bytes 17.91kB 2.09%
state/selectors/polarScaleSelectors.js 283 bytes 3.76kB 8.13% ⚠️
state/selectors/combiners/combineConfiguredScale.js (New) 2.46kB 2.46kB 100.0% 🚀
util/scale/RechartsScale.js -1.86kB 2.25kB -45.28%
state/selectors/combiners/combineRealScaleType.js (New) 1.67kB 1.67kB 100.0% 🚀
view changes for bundle: recharts/bundle-umd

Assets Changed:

Asset Name Size Change Total Size Change (%)
Recharts.js 57 bytes 529.54kB 0.01%
view changes for bundle: recharts/bundle-es6

Assets Changed:

Asset Name Size Change Total Size Change (%)
state/selectors/axisSelectors.js -783 bytes 53.26kB -1.45%
state/selectors/tooltipSelectors.js 284 bytes 14.43kB 2.01%
state/selectors/polarScaleSelectors.js 229 bytes 3.08kB 8.04% ⚠️
util/scale/RechartsScale.js -1.13kB 2.12kB -34.74%
state/selectors/combiners/combineConfiguredScale.js (New) 1.59kB 1.59kB 100.0% 🚀
state/selectors/combiners/combineRealScaleType.js (New) 827 bytes 827 bytes 100.0% 🚀

@ckifer
ckifer merged commit 742149a into main Jan 28, 2026
48 checks passed
@ckifer
ckifer deleted the combineConfiguredScale branch January 28, 2026 18:11
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.

2 participants