The per-family files under src/Plotly.NET/ChartAPI/ have grown unwieldy:
| File | Lines |
|---|---|
| Chart2D.fs | 9234 |
| Chart.fs | 3713 |
| ChartMap.fs | 2930 |
| ChartDomain.fs | 2701 |
| Chart3D.fs | 2482 |
| ChartCarpet.fs | 1765 |
| ChartPolar.fs | 1519 |
| ChartTernary.fs | 1022 |
| ChartSmith.fs | 995 |
Chart2D.fs alone holds ~25 chart-type families (Scatter, Line, Point, Bubble, Bar, Histogram, BoxPlot, Violin, Heatmap, OHLC, Candlestick, Splom, ...), each with 3–4 overloads averaging ~100 lines due primarily to long XML doc blocks and wide optional parameter lists. The tiny internal helpers at the top of the file are not the main source of bloat.
This matters for the refactor choice: simply offloading implementation bodies into helper modules would slightly reduce per-member complexity, but it would leave the bulk of the 9k-line file intact because the docs and constructor signatures would still live in Chart2D.fs.
F# has no partial classes, so we cannot simply split type Chart across files the way src/Plotly.NET.CSharp/ChartAPI/ does with public static partial class Chart.
The current layout already does a partial-class-like split across families. Each family file defines its own [<AutoOpen>] module containing an [<Extension>] type Chart whose members are all marked [<Extension>]. Example from Chart2D.fs:13-17:
[<AutoOpen>]
module Chart2D =
[<Extension>]
type Chart =
[<Extension>]
static member Scatter(...) = ...This compiles to a CLR class Plotly.NET.Chart2D+Chart carrying static methods with [ExtensionAttribute]. When module Chart2D is auto-opened, those methods are resolved by the F# compiler as extension members on the base Plotly.NET.Chart type from Chart.fs, so user code keeps calling Chart.Scatter(...) uniformly regardless of which file the overload lives in.
There are already 8 type Chart = declarations coexisting this way (Chart2D, Chart3D, ChartCarpet, ChartDomain, ChartMap, ChartPolar, ChartSmith, ChartTernary). The proposal is to apply the same mechanism at finer granularity across the large extension-based Chart API families while keeping the base Chart.fs type out of scope for this refactor.
Split each large extension-based Chart API file into one F# file per chart-type group. The public API surface stays byte-identical; only the physical location of each static member changes.
src/Plotly.NET/ChartAPI/
Chart.fs // base type Chart (unchanged)
Chart2D/
Chart2D_Scatter.fs // Scatter / Point / Line / Spline / Bubble / Range
Chart2D_Area.fs // Area / SplineArea / StackedArea
Chart2D_Bar.fs // Bar / StackedBar / Column / StackedColumn
Chart2D_Funnel.fs // Funnel / StackedFunnel / Waterfall
Chart2D_Histogram.fs // Histogram / Histogram2D / Histogram2DContour
Chart2D_Distribution.fs // BoxPlot / Violin
Chart2D_Heatmap.fs // Heatmap / AnnotatedHeatmap / Image / Contour
Chart2D_Finance.fs // OHLC / Candlestick
Chart2D_Splom.fs // Splom / PointDensity
Chart2D_Statistical.fs // Pareto / Residual
Chart3D/
Chart3D_Scatter.fs // Scatter3D / Point3D / Line3D / Bubble3D
Chart3D_Surface.fs // Surface / Mesh3D
Chart3D_VectorField.fs // Cone / StreamTube
Chart3D_Volume.fs // Volume / IsoSurface
ChartPolar/
ChartPolar_Scatter.fs // ScatterPolar / PointPolar / LinePolar / SplinePolar / BubblePolar
ChartPolar_Bar.fs // BarPolar
ChartMap/
ChartMap_Geo.fs // ChoroplethMap / ScatterGeo / PointGeo / LineGeo / BubbleGeo
ChartMap_Mapbox.fs // ScatterMapbox / PointMapbox / LineMapbox / BubbleMapbox
ChartMap_Density.fs // ChoroplethMapbox / DensityMapbox
ChartTernary/
ChartTernary_Scatter.fs // ScatterTernary / PointTernary / LineTernary / BubbleTernary
ChartCarpet/
ChartCarpet_Base.fs // Carpet
ChartCarpet_Scatter.fs // ScatterCarpet / PointCarpet / LineCarpet / SplineCarpet / BubbleCarpet
ChartCarpet_Contour.fs // ContourCarpet
ChartDomain/
ChartDomain_Pie.fs // Pie / Doughnut / FunnelArea
ChartDomain_Hierarchy.fs // Sunburst / Treemap
ChartDomain_Relations.fs // ParallelCoord / ParallelCategories / Sankey
ChartDomain_Table.fs // Table / Indicator
ChartDomain_Icicle.fs // Icicle
ChartSmith/
ChartSmith_Scatter.fs // ScatterSmith / PointSmith / LineSmith / BubbleSmith
Chart2D.fs // already removed
Chart3D.fs // trimmed remainder or deleted at the end
ChartPolar.fs // trimmed remainder or deleted at the end
ChartMap.fs // trimmed remainder or deleted at the end
ChartTernary.fs // trimmed remainder or deleted at the end
ChartCarpet.fs // trimmed remainder or deleted at the end
ChartDomain.fs // trimmed remainder or deleted at the end
ChartSmith.fs // trimmed remainder or deleted at the end
Each new file follows the established pattern:
namespace Plotly.NET
open Plotly.NET.LayoutObjects
open Plotly.NET.TraceObjects
open System
open System.Runtime.CompilerServices
[<AutoOpen>]
module Chart2D_Scatter =
[<Extension>]
type Chart =
[<Extension>]
static member Scatter(...) = ...
[<Extension>]
static member Point(...) = ...
// etc.Key rules:
- Group overloads of the same
Chart.Foomember into the same file. Never split overloads of one member name across files — F# overload resolution only works on members declared inside a singletypeblock. - Group closely related members that share private helpers into the same file (e.g.
Scatter/Point/Line/Bubbleall build the sameMarker/Lineobjects and call the samerenderScatterTracehelper). - Do not introduce a shared helper file up front unless a second split actually needs one. The current helpers are tiny; extract them only when the move would otherwise duplicate code or block file ordering.
- File-level
AutoOpenmodule names must be unique (Chart2D_Scatter,Chart2D_Bar, ...) so auto-open does not collide and each containerCharttype gets a distinct CLR name.
Keep Chart2D.fs as-is but move each overload body into a per-chart-type implementation module (module Chart2DImpl.ScatterImpl) and forward from the thin static member. Rejected:
Chart2D.fsstays ~6–7k lines as a forwarder shell because XML docs and parameter lists dominate the bulk, not bodies.- Adds an indirection hop and a parallel naming surface (
ScatterImpl.create) that the rest of the codebase does not use. - It may still be a reasonable follow-up cleanup inside an extracted file, but it does not solve the actual file-size problem.
Introduce e.g. a ScatterArgs record and reduce the 3–4 overloads per chart to a single implementation. Rejected:
- Changes API surface and migration risk is high.
- Orthogonal to the file-size problem — can be considered later as a separate refactor.
Generate the chart API from upstream schema. Rejected for this plan:
- Massive scope; affects every
Trace*Styleand docs toolchain. - Worth revisiting long-term, but not a prerequisite for fixing file size today.
Agent tools can already chunk large files, and the code works. Rejected because:
Chart2D.fsis the dominant context-cost file for any task touching 2D charts.- Human navigation (jump-to-member, diff review) is noticeably slow on 9k-line files.
- Overload resolution across files. F# cannot overload across separate
type Chartdeclarations. Mitigation: group all overloads of one member name into one file (enforced by the rule above). The PoC commit (Chart2D_Scatter) exercises this:Chart.Scatterhas 4 overloads and must compile and resolve identically. - Name collision across auto-open modules. Two files must not define the same container module name. Mitigation: one consistent naming scheme (
<Family>_<Group>). - C# consumers. The wrapper files under src/Plotly.NET.CSharp/ChartAPI/ call the F# CLR containers directly. After splitting, those calls must be retargeted to the new container names (
Plotly.NET.Chart3D_Scatter.Chart.*,Plotly.NET.ChartMap_Geo.Chart.*, etc.). Verify by running./build.cmd runTestsCore, which already covers C# interoperability. - Internal visibility. If a later split needs shared helpers, they must remain reachable from every family file. Mitigation: only extract helpers when needed, and keep them
internalin a shared file ordered before dependents. - Documentation and fsdocs. docs/ references the public API by member name, not file path — unaffected. Verify a docs build locally after the PoC.
- .fsproj compile order. F# is order-sensitive. Mitigation: explicit
<Compile Include=...>entries in Plotly.NET.fsproj, with any shared helper file placed before dependents only if such a file becomes necessary. - Refactor scope creep.
Chart2D.fswas the obvious first target, but expanding across the remaining family files increases churn. Mitigation: keepChart.fsexplicitly out of scope, preserve family-by-family grouping, and verify withrunTestsCoreafter each wave.
Each commit is independently buildable and testable. Follow the repo entry points (./build.cmd or ./build.sh), with the relevant test target running after each commit.
Scope:
- Create
src/Plotly.NET/ChartAPI/Chart2D/. - Add
Chart2D_Scatter.fsinsrc/Plotly.NET/ChartAPI/Chart2D/, containing all overloads ofScatter,Point,Line,Spline,Bubble,Range. - Remove the migrated members from
Chart2D.fs. - Keep
renderScatterTracein the moved file unless extracting it is required by file ordering or reuse. - Update
Plotly.NET.fsproj<Compile>ordering: the new file goes before the trimmedChart2D.fs. - Run
./build.cmd, then./build.cmd runTestsCoreto confirm no regression. - If all green, this PoC confirms the mechanism and unblocks the rest of the plan.
Exit criteria:
- Build green and core tests green.
Chart2D.fsshrinks substantially.- No intended public API diff.
Implementation notes:
- Created
src/Plotly.NET/ChartAPI/Chart2D/Chart2D_Scatter.fsand movedrenderScatterTraceplus theScatter/Point/Line/Spline/Bubble/Rangeoverload groups there. - Trimmed
src/Plotly.NET/ChartAPI/Chart2D.fsso it retainsrenderHeatmapTraceand starts withArea. - Added the new file to
src/Plotly.NET/Plotly.NET.fsprojahead ofChart2D.fs. - Updated
src/Plotly.NET.CSharp/ChartAPI/Chart2D.csso the wrapper calls the new CLR container for the moved members (Plotly.NET.Chart2D_Scatter.Chart). - Verified with
./build.cmd runTestsCoresuccessfully: build passed and 933/933 core tests passed.
Scope:
Chart2D_Area.fs— Area / SplineArea / StackedArea.Chart2D_Bar.fs— Bar / StackedBar / Column / StackedColumn.Chart2D_Funnel.fs— Funnel / StackedFunnel / Waterfall.- Remove migrated members from
Chart2D.fs; update fsproj.
Implementation notes:
- Added
Chart2D_Area.fs,Chart2D_Bar.fs, andChart2D_Funnel.fsundersrc/Plotly.NET/ChartAPI/Chart2D/. - Retargeted the matching C# wrapper calls in
src/Plotly.NET.CSharp/ChartAPI/Chart2D.cs. - Verified with
./build.cmd runTestsCoresuccessfully.
Scope:
Chart2D_Histogram.fs— Histogram / Histogram2D / Histogram2DContour.Chart2D_Distribution.fs— BoxPlot / Violin.- Remove from
Chart2D.fs; update fsproj.
Implementation notes:
- Added
Chart2D_Histogram.fsandChart2D_Distribution.fs. - Retargeted the matching C# wrapper calls in
src/Plotly.NET.CSharp/ChartAPI/Chart2D.cs. - Verified with
./build.cmd runTestsCoresuccessfully as part of the full Chart2D split.
Scope:
Chart2D_Heatmap.fs— Heatmap / AnnotatedHeatmap / Image / Contour.Chart2D_Finance.fs— OHLC / Candlestick.Chart2D_Splom.fs— Splom / PointDensity.Chart2D_Statistical.fs— Pareto / Residual.Chart2D.fswas deleted after the constructor families were fully migrated; the split now lives entirely underChartAPI/Chart2D/.
Implementation notes:
- Added
Chart2D_Heatmap.fs,Chart2D_Finance.fs,Chart2D_Splom.fs, andChart2D_Statistical.fs. - Moved
renderHeatmapTraceintoChart2D_Heatmap.fs. - Retargeted the remaining C# wrapper calls in
src/Plotly.NET.CSharp/ChartAPI/Chart2D.cs. - Removed
src/Plotly.NET/ChartAPI/Chart2D.fsand itsPlotly.NET.fsprojentry once the transitional placeholder was no longer needed. - Verified with
./build.cmd runTestsCoresuccessfully: build passed and 933/933 core tests passed.
Scope:
Chart3D_Scatter.fs— Scatter3D / Point3D / Line3D / Bubble3D.Chart3D_Surface.fs— Surface / Mesh3D.Chart3D_VectorField.fs— Cone / StreamTube.Chart3D_Volume.fs— Volume / IsoSurface.ChartPolar_Scatter.fs— ScatterPolar / PointPolar / LinePolar / SplinePolar / BubblePolar, carryingrenderScatterPolarTrace.ChartPolar_Bar.fs— BarPolar.ChartTernary_Scatter.fs— ScatterTernary / PointTernary / LineTernary / BubbleTernary.ChartSmith_Scatter.fs— ScatterSmith / PointSmith / LineSmith / BubbleSmith.- Remove the original family files once all constructor groups are migrated and fsproj ordering is updated.
- Retarget the matching C# wrapper files (
Chart3D.cs,ChartPolar.cs,ChartTernary.cs,ChartSmith.cs). - Run
./build.cmd runTestsCore.
Status:
- Done.
Implementation notes:
- Added
Chart3D_Scatter.fs,Chart3D_Surface.fs,Chart3D_VectorField.fs, andChart3D_Volume.fsundersrc/Plotly.NET/ChartAPI/Chart3D/. - Added
ChartPolar_Scatter.fsandChartPolar_Bar.fs, movingrenderScatterPolarTraceintoChartPolar_Scatter.fs. - Added
ChartTernary_Scatter.fsandChartSmith_Scatter.fs. - Removed the original
Chart3D.fs,ChartPolar.fs,ChartTernary.fs, andChartSmith.fsfiles and replaced theirPlotly.NET.fsprojentries with the new per-family files. - Retargeted the matching C# wrapper calls in
src/Plotly.NET.CSharp/ChartAPI/Chart3D.cs,ChartPolar.cs,ChartTernary.cs, andChartSmith.cs. - Verified with
./build.cmd runTestsCoresuccessfully as part of the full branch verification.
Scope:
ChartMap_Geo.fs— ChoroplethMap / ScatterGeo / PointGeo / LineGeo / BubbleGeo.ChartMap_Mapbox.fs— ScatterMapbox / PointMapbox / LineMapbox / BubbleMapbox.ChartMap_Density.fs— ChoroplethMapbox / DensityMapbox.ChartCarpet_Base.fs— Carpet.ChartCarpet_Scatter.fs— ScatterCarpet / PointCarpet / LineCarpet / SplineCarpet / BubbleCarpet.ChartCarpet_Contour.fs— ContourCarpet.ChartDomain_Pie.fs— Pie / Doughnut / FunnelArea.ChartDomain_Hierarchy.fs— Sunburst / Treemap.ChartDomain_Relations.fs— ParallelCoord / ParallelCategories / Sankey.ChartDomain_Table.fs— Table / Indicator.ChartDomain_Icicle.fs— Icicle.- Remove the original family files once all constructor groups are migrated and fsproj ordering is updated.
- Retarget the matching C# wrapper files (
ChartMap.cs,ChartCarpet.cs,ChartDomain.cs). - Run
./build.cmd runTestsCore.
Status:
- Done.
Implementation notes:
- Added
ChartMap_Geo.fs,ChartMap_Mapbox.fs, andChartMap_Density.fsundersrc/Plotly.NET/ChartAPI/ChartMap/. - Added
ChartCarpet_Base.fs,ChartCarpet_Scatter.fs, andChartCarpet_Contour.fsundersrc/Plotly.NET/ChartAPI/ChartCarpet/. - Added
ChartDomain_Pie.fs,ChartDomain_Hierarchy.fs,ChartDomain_Relations.fs,ChartDomain_Table.fs, andChartDomain_Icicle.fsundersrc/Plotly.NET/ChartAPI/ChartDomain/. - Removed the original
ChartMap.fs,ChartCarpet.fs, andChartDomain.fsfiles and replaced theirPlotly.NET.fsprojentries with the new per-family files. - Retargeted the matching C# wrapper calls in
src/Plotly.NET.CSharp/ChartAPI/ChartMap.cs,ChartCarpet.cs, andChartDomain.cs. - Updated internal repo references that still assumed the old monoliths:
tests/Common/FSharpTestBase/TestCharts/ChartDomainTestCharts.fsnow usesChart.Indicator, and both playground scripts now#loadthe split files. - Verified with
./build.cmd runTestsCoresuccessfully: build passed and 933/933 core tests passed.
After Commit 6 lands:
- Reassess whether Chart.fs should stay as-is. It remains explicitly out of scope for this plan unless a later follow-up chooses to redesign the base
Charttype. - Confirm every extension-based family now lives in a per-family subdirectory under
ChartAPI/. - Keep any future
Chart.fswork as a separate design/refactor effort, not an automatic continuation of this branch.
Status:
- Reached. The extension-based Chart API families now live under per-family subdirectories, and
Chart.fsremains the only intentionally unsplit top-level Chart API file.
- Do we keep flat
Chart2D_X.fsfilenames in the existingChartAPI/directory, or move to aChartAPI/Chart2D/subdirectory? Recommendation: use aChartAPI/Chart2D/subdirectory because it is cleaner for maintainers, keeps related constructor groups together, and scales better if other large families are ever split later. - Should internal helpers live on a shared
Charttype (via a futureChart2D_Shared.fs) or as free module-level functions? Recommendation: defer this until a second split actually needs shared code. Avoid inventing a helper layer before there is concrete reuse pressure. - Naming:
Chart2D_ScattervsChart2DScattervsChart2D.Scatter? The underscore form is unambiguous and avoids clashing with a nested module path. Recommendation: underscore.