Skip to main content
RSS feed Subscribe to feed

 

Fitting Model: A Comprehensive Example

The following step-by-step example, including the basic extension pattern steps, shows how to implement a simple fitting model that displays a max and a min curve and a large star at the center.

Overview

The usual extension pattern is used to implement a custom fitting model: Declare a type identifier, implement the add-in and factory classes, and extend the CustomFittingModel class to add the new fitting model. In total, at least four new classes need to be implemented.

Declaring the type identifier

Type identifiers are used when instantiating custom extensions. Declare a new type identifier in a class extending from CustomTypeIdentifiers:

/// <summary>
/// Defines the identifiers used for the fitting models in this project.
/// </summary>
public sealed class FittingExampleIdentifiers : CustomTypeIdentifiers
{
    /// <summary>
    /// Type identifier for <see cref="FittingExample"/>.
    /// </summary>
    public static readonly CustomTypeIdentifier FittingExample =
        CreateTypeIdentifier(
            "SpotfireDeveloper.FittingExample",
            "Example display name.",
            "Example description.");
}

Implementing the add-in

The add-in is used to register the fitting model and its factory with the system. It is also possible to register a view to be used when editing properties of the fitting model.

/// <summary>
/// Add-In definition for the FittingExample project.
/// </summary>
public sealed class FittingExampleAddIn : AddIn
{
    protected override void RegisterFittingModels(FittingModelRegistrar registrar)
    {
        base.RegisterFittingModels(registrar);

        registrar.Register<FittingExample>(new FittingExampleFactory());
    }

    protected override void RegisterViews(ViewRegistrar registrar)
    {
        base.RegisterViews(registrar);
    }
}

Implementing the factory

The factory creates new instances of the fitting model. It can also specify a required license for the fitting model by providing a LicensedFunction as second parameter in the constructor. Specifying null means always enabled.

public sealed class FittingExampleFactory : CustomFittingModelFactory<FittingExample>
{
    /// <summary>
    /// Initializes a new instance of class <see cref="FittingExampleFactory"/>.
    /// </summary>
    public FittingExampleFactory()
        : base(FittingExampleIdentifiers.FittingExample, null)
    {
        // Empty.
    }

    /// <summary>
    /// Factory method implementation.
    /// </summary>
    protected override FittingExample CreateCore()
    {
        return new FittingExample();
    }      
}

Implementing the fitting model

Curves and points are created in the constructor with the protected methods CreateCurve and CreatePoint. The expression parameter is used to evaluate the curve. For points, two expressions are required to position it.

/// <summary>
/// Initializes a new instance of the FittingExample class.
/// </summary>
public FittingExample()
    : base()
{
    CreateCurve("max", "Line at max value", "max");
    CreateCurve("min", "Line at min value", "min");
    CreatePoint("center", "Point at center", "xcenter", "ycenter");

    this.MaxCurve.LineStyle = LineStyle.Dot;
    this.MaxCurve.Width = 1;
    this.MinCurve.LineStyle = LineStyle.Dot;
    this.MinCurve.Width = 1;
    this.CenterPoint.MarkerShape = new MarkerShape(MarkerType.StarFive);
    this.CenterPoint.Size = 100;
    this.CenterPoint.IsBackground = true;
}

The curve and point properties used above are implemented using the protected GetCurve and GetPoint methods, supplying the name of the curve or point.

public ReferenceCurve MaxCurve
{
    get { return this.GetCurve("max"); }
}

public ReferenceCurve MinCurve
{
    get { return this.GetCurve("min"); }
}

public ReferencePoint CenterPoint
{
    get { return this.GetPoint("center"); }
}

Fitting models declare result variables by implementing the ConfigureModelCore method. It is invoked when the visualization setup changes. The fitting model may supply a Trigger via GetConfigureModelTriggerCore, signalling when it needs to be reconfigured. In this way changes to properties on the fitting model can trigger a reconfiguration.

There is also a Trigger returned by GetResultTriggerCore that is used to recalculate the fitting result. Overriding these trigger methods is not needed if the fitting model does not have any properties; the default implementation returns Triggers that never fire.

 protected override void ConfigureModelCore(FittingModelConfigurator configurator)
 {
     configurator.RegisterResultVariable("max", "Max", "Max value");
     configurator.RegisterResultVariable("min", "Min", "Min value");
     configurator.RegisterResultVariable("xcenter", "X Center", "Center value");
     configurator.RegisterResultVariable("ycenter", "Y Center", "Center value");
     configurator.RegisterResultVariable("x1", "x1", "x1");
     configurator.RegisterResultVariable("x2", "x2", "x2");
 }

The configurator in this example registers the result variables, but it also contains methods to change the curve and point expressions.

Performing the Fitting

There are six result variables to compute. If not all declared result variables are assigned values in the call to SetResult an exception will be thrown. There are a few overloads to the SetResult method; it is possible to supply the results as ordered doubles instead of ResultValues as well as to attach warning messages to the result. If the calculations fails completely the SetError method should be used instead.

protected override void Fit(FittingModelData data, FittingModelOutput output)
{
    double max = double.MinValue;
    double min = double.MaxValue;
    double x1 = double.MaxValue;
    double x2 = double.MinValue;

    DataValueCursor<double> xValues = data.CreateXCursor();
    DataValueCursor<double> yValues = data.CreateYCursor();

    foreach (DataRow row in data.GetRows(xValues, yValues))
    {
        double x = xValues.CurrentValue;
        double y = yValues.CurrentValue;

        if (max < y)
        {
            max = y;
        }
        if (min > y)
        {
            min = y;
        }
        if (x1 > x)
        {
            x1 = x;
        }
        if (x2 < x)
        {
            x2 = x;
        }
    }

    output.SetResult(
        new ResultValue("max", max),
        new ResultValue("min", min),
        new ResultValue("x1", x1),
        new ResultValue("x2", x2),
        new ResultValue("xcenter", x1 + (x2 - x1) / 2),
        new ResultValue("ycenter", min + (max - min) / 2));
}