Fit

Interactive curve fitting for Linux, Windows and macOS

How to add a curve type

A curve type is one unit. It subclasses TNamedPointsSet, answers a few questions about itself, and registers itself from its own initialization section. Nothing else is edited — the menu, the factory and the compatibility rules are all derived from what registered.

flowchart TB
    unit["`**Your unit**
class(TNamedPointsSet)`"]
    init["`**initialization**
RegisterCurveType(TYours)`"]
    reg["`**TCurveTypesSingleton**
the curve factory`"]
    menu["`**The Curve Type menu**
grouped by GetCurveTypeGroup`"]
    engine["`**The engine**
instantiates by type id`"]
    loss["`**Objective compatibility**
derived from AmplitudeIsUnbounded`"]
    axis["`**Displayed axis**
from CreatePreferredAxis`"]
    unit --> init --> reg
    reg --> menu
    reg --> engine
    reg --> loss
    reg --> axis

The questions a curve type answers

Each has a sensible default, so a type only overrides what differs. These are capabilities, not a list of type names: adding a type needs no edit to any compatibility table.

Type Analytic Group Unbounded amplitude Placed by Preferred axis
2 br. Pseudo-Voigt
T2BranchesPseudoVoigtPointsSet
yesa single positionTDiffractionAngleAxis
Asym. Pseudo-Voigt
TAsymPseudoVoigtPointsSet
yesa single positionTDiffractionAngleAxis
Doniach-Sunjic
TDoniachSunjicPointsSet
yesa single positionTDiffractionAngleAxis
Exponentially Modified Gaussian
TEmgPointsSet
yesa single positionTDiffractionAngleAxis
Gaussian
TGaussPointsSet
yesa single positionTDiffractionAngleAxis
Lorentzian
TLorentzPointsSet
yesa single positionTDiffractionAngleAxis
Moffat
TMoffatPointsSet
yesa single positionTDiffractionAngleAxis
Pearson VII
TPearson7PointsSet
yesa single positionTDiffractionAngleAxis
Pseudo-Voigt
TPseudoVoigtPointsSet
yesa single positionTDiffractionAngleAxis
Skewed Gaussian
TSkewedGaussianPointsSet
yesa single positionTDiffractionAngleAxis
Step (erf)
TStepPointsSet
yesa single positionTDiffractionAngleAxis
User Defined
TUserPointsSet
yesa single positionTIdentityAxis
Voigt
TVoigtPointsSet
yesa single positionTDiffractionAngleAxis

13 curve types are registered in this build.

No registered type asks for a menu group, so the Group column is empty throughout. A type that returns a name from GetCurveTypeGroup gets a submenu of that name — which is what a pack contributing several related types wants, rather than crowding the flat list.

The curve classes

Read from the units themselves. A curve type is a point set first: TPointsSet holds the points, TCurvePointsSet adds the parameters a minimizer varies, and TNamedPointsSet adds the questions above. TCurveTypesSingleton is the registry every type registers into, and the three interfaces it implements are how the menu, the factory and the engine reach it — none of them names a curve class.

classDiagram
    direction LR
    class TInterfacedObject["TInterfacedObject"]
    <<external>> TInterfacedObject
    class TSelfCopiedComponent["TSelfCopiedComponent"]
    <<external>> TSelfCopiedComponent
    class ICurveFactory["ICurveFactory"]
    <<interface>> ICurveFactory
    class ICurveTypeIterator["ICurveTypeIterator"]
    <<interface>> ICurveTypeIterator
    class ICurveTypeSelector["ICurveTypeSelector"]
    <<interface>> ICurveTypeSelector
    class T2BranchesPseudoVoigtPointsSet["T2BranchesPseudoVoigtPointsSet"]
    class TAsymPseudoVoigtPointsSet["TAsymPseudoVoigtPointsSet"]
    class TCurvePointsSet["TCurvePointsSet"]
    class TCurveTypesSingleton["TCurveTypesSingleton"]
    class TDoniachSunjicPointsSet["TDoniachSunjicPointsSet"]
    class TEmgPointsSet["TEmgPointsSet"]
    class TFormulaPointsSet["TFormulaPointsSet"]
    class TGaussPointsSet["TGaussPointsSet"]
    class TLorentzPointsSet["TLorentzPointsSet"]
    class TMoffatPointsSet["TMoffatPointsSet"]
    class TNamedPointsSet["TNamedPointsSet"]
    class TNeutronPointsSet["TNeutronPointsSet"]
    class TPearson7PointsSet["TPearson7PointsSet"]
    class TPointsSet["TPointsSet"]
    class TPseudoVoigtPointsSet["TPseudoVoigtPointsSet"]
    class TSkewedGaussianPointsSet["TSkewedGaussianPointsSet"]
    class TStepPointsSet["TStepPointsSet"]
    class TTitlePointsSet["TTitlePointsSet"]
    class TVoigtPointsSet["TVoigtPointsSet"]
    TNamedPointsSet <|-- T2BranchesPseudoVoigtPointsSet
    TPseudoVoigtPointsSet <|-- TAsymPseudoVoigtPointsSet
    TTitlePointsSet <|-- TCurvePointsSet
    TInterfacedObject <|-- TCurveTypesSingleton
    ICurveFactory <|.. TCurveTypesSingleton
    ICurveTypeIterator <|.. TCurveTypesSingleton
    ICurveTypeSelector <|.. TCurveTypesSingleton
    TFormulaPointsSet <|-- TDoniachSunjicPointsSet
    TFormulaPointsSet <|-- TEmgPointsSet
    TNamedPointsSet <|-- TFormulaPointsSet
    TNamedPointsSet <|-- TGaussPointsSet
    TGaussPointsSet <|-- TLorentzPointsSet
    TFormulaPointsSet <|-- TMoffatPointsSet
    TCurvePointsSet <|-- TNamedPointsSet
    TPointsSet <|-- TNeutronPointsSet
    TFormulaPointsSet <|-- TPearson7PointsSet
    TSelfCopiedComponent <|-- TPointsSet
    TNamedPointsSet <|-- TPseudoVoigtPointsSet
    TFormulaPointsSet <|-- TSkewedGaussianPointsSet
    TFormulaPointsSet <|-- TStepPointsSet
    TNeutronPointsSet <|-- TTitlePointsSet
    TFormulaPointsSet <|-- TVoigtPointsSet

Curve types the user defines

A type whose formula is typed at run time still arrives through the same registry. The configuration lives in a separate class (GetConfigurablePointsSet) rather than on the curve itself: these are class-level questions asked before any instance exists, and Free Pascal has neither multiple inheritance nor class methods in interfaces.

classDiagram
    direction LR
    class TForm["TForm"]
    <<external>> TForm
    class TInterfacedObject["TInterfacedObject"]
    <<external>> TInterfacedObject
    class TNamedPointsSet["TNamedPointsSet"]
    <<external>> TNamedPointsSet
    class TSpecialCurveParameter["TSpecialCurveParameter"]
    <<external>> TSpecialCurveParameter
    class ICreateUserPointsSetDlg["ICreateUserPointsSetDlg"]
    <<interface>> ICreateUserPointsSetDlg
    class ICurveTypeParametersFactory["ICurveTypeParametersFactory"]
    <<interface>> ICurveTypeParametersFactory
    class ICurveTypeStorage["ICurveTypeStorage"]
    <<interface>> ICurveTypeStorage
    class IExpressionParser["IExpressionParser"]
    <<interface>> IExpressionParser
    class TConfigurablePointsSet["TConfigurablePointsSet"]
    class TConfigurableUserPointsSet["TConfigurableUserPointsSet"]
    class TCreateUserPointsSetDlg["TCreateUserPointsSetDlg"]
    class TCreateUserPointsSetDlgAdapter["TCreateUserPointsSetDlgAdapter"]
    class TCurveTypeParametersFactory["TCurveTypeParametersFactory"]
    class TCurveTypeStorageAdapter["TCurveTypeStorageAdapter"]
    class TExpressionParserAdapter["TExpressionParserAdapter"]
    class TNonConfigurablePointsSet["TNonConfigurablePointsSet"]
    class TUserCurveParameter["TUserCurveParameter"]
    class TUserPointsSet["TUserPointsSet"]
    class TUserPointsSetPropDlg["TUserPointsSetPropDlg"]
    TConfigurablePointsSet <|-- TConfigurableUserPointsSet
    TForm <|-- TCreateUserPointsSetDlg
    TInterfacedObject <|-- TCreateUserPointsSetDlgAdapter
    ICreateUserPointsSetDlg <|.. TCreateUserPointsSetDlgAdapter
    TInterfacedObject <|-- TCurveTypeParametersFactory
    ICurveTypeParametersFactory <|.. TCurveTypeParametersFactory
    TInterfacedObject <|-- TCurveTypeStorageAdapter
    ICurveTypeStorage <|.. TCurveTypeStorageAdapter
    TInterfacedObject <|-- TExpressionParserAdapter
    IExpressionParser <|.. TExpressionParserAdapter
    TConfigurablePointsSet <|-- TNonConfigurablePointsSet
    TSpecialCurveParameter <|-- TUserCurveParameter
    TNamedPointsSet <|-- TUserPointsSet
    TForm <|-- TUserPointsSetPropDlg

Creating one is two dialogs, and every failure has a defined landing place — an unparseable formula reopens the first dialog rather than raising, and a cancelled setup leaves the type unselected instead of half-registered.

sequenceDiagram
    autonumber
    participant ui as TFormMain
    participant iter as ICurveTypeIterator
    participant conf as TConfigurableUserPointsSet
    participant dlg as ICreateUserPointsSetDlg
    participant parser as IExpressionParser
    participant factory as ICurveTypeParametersFactory
    participant store as ICurveTypeStorage
    participant props as TUserPointsSetPropDlg
    ui->>iter: which registered type was clicked
    iter-->>ui: GetCurrentCurveClass
    ui->>conf: GetConfigurablePointsSet, HasConfigurableParameters
    alt the type configures itself
        ui->>conf: ShowConfigurationDialog
        conf->>dlg: ShowModal - a name and a formula in x
        conf->>parser: ParseExpression
        alt the formula does not parse
            parser-->>conf: nil, the message already shown
            conf->>dlg: re-open, never crash
        else it parses
            conf->>factory: CreateUserCurveType
            conf->>store: AddCurveType
            conf->>props: ShowModal - roles and initial values
            alt mrOk
                conf->>store: UpdateCurveType
            else mrRetry
                conf->>store: DeleteCurveType, back to the first dialog
            end
        end
    end
    alt configuration was cancelled and HasDefaults
        ui->>conf: SetDefaults
    else cancelled with no defaults
        ui->>ui: say so, and leave the type unselected
    end

Full instructions, including the traps: adding a curve model · writing a module · a complete working example.