Skip to main content
RSS feed Subscribe to feed

 

How to Create a Visualization Model

A visualization model manages the state of a visualization. It is also expected to be capable of drawing itself.

Overview

The model part inherits from CustomVisual or CustomVisualization. CustomVisualization is a subclass of CustomVisual and adds functionality for specifying an active data table and an active marking. Being able to specify an active data table and an active marking enables visualizations to participate in master detail scenarios. It also enables the framework to implement commands on marked records and show information about the number of filtered and marked rows in the status bar.

The model is typically derived from CustomVisualization if the visualization visualizes data from one or more data tables, and CustomVisual if it doesn't, which will be the case case when implementing something in line with the built-in Text Area or some sort of control panel to be embedded in a page.

The CustomVisual class was introduced in 2.0 and the CustomVisualization class in 3.0. For visualizations developed against the 2.0 API, it is perfectly safe to change the base class of from CustomVisual to CustomVisualization, and maintain backward compatibility.

A visualization is expected to be capable of rendering itself. It is also expected to be able to notify the environment when its appearance has changed and it needs to be redrawn. The model achieves this dual requirement by overriding two virtual methods defined by CustomVisual:

Creating the Model

The following sample shows a minimal visualization. It has two properties, representing a data table and a marking. When asked to render itself, it just draws a string specifying the number of marked rows in the data table.

[Serializable]
[PersistenceVersion(1, 0)]
public class MyVisualization : CustomVisualization
{
    // Property names
    
    public new abstract class PropertyNames : CustomVisualization.PropertyNames
    {
        public static readonly PropertyName DataTableReference = CreatePropertyName("DataTableReference");
        public static readonly PropertyName MarkingReference = CreatePropertyName("MarkingReference");
    }

    // Fields
    
    private readonly UndoableCrossReferenceProperty<DataTable> dataTableReference;
    private readonly UndoableCrossReferenceProperty<DataMarkingSelection> markingReference;

    // Constructor
    
    internal MyVisualization()
    {
        CreateProperty(PropertyNames.DataTableReference, out this.dataTableReference, null);
        CreateProperty(PropertyNames.MarkingReference, out this.markingReference, null);
    }

    // Properties

    public DataTable DataTableReference
    {
        get { return this.dataTableReference.Value; }
        set { this.dataTableReference.Value = value; }
    }

    public DataMarkingSelection MarkingReference
    {
        get { return this.markingReference.Value; }
        set { this.markingReference.Value = value; }
    }

    // Rendering

    protected override void RenderCore(RenderArgs renderArgs)
    {
        // Clear our drawing surface.
        renderArgs.Graphics.FillRectangle(Brushes.White, renderArgs.Bounds);

        // Draw a string showing the number of marked rows.
        if (this.DataTableReference != null && this.MarkingReference != null)
        {
            int markedRowsCount = this.MarkingReference.GetSelection(this.DataTableReference).IncludedRowCount;
            string message = string.Format(
                "{0} marked rows in table {1}",
                markedRowsCount,
                this.DataTableReference.Name);

            renderArgs.Graphics.DrawString(message, 
                SystemFonts.DefaultFont, 
                Brushes.Black, 
                renderArgs.Bounds);
        }
    }

    protected override Trigger GetRenderTriggerCore()
    {
        // Return a trigger that fires when the data table or marking properties change
        // or when the content of the marking changes.
        return Trigger.CreateCompositeTrigger(
            Trigger.CreatePropertyTrigger(
                this, PropertyNames.DataTableReference, PropertyNames.MarkingReference
            ),
            Trigger.CreateMutablePropertyTrigger<DataMarkingSelection>(
                this, PropertyNames.MarkingReference, DataSelection.PropertyNames.Selection
            )
        );
    }

    // Overrides for active data table and active marking.

    protected override DataTable GetActiveDataTableReferenceCore()
    {
        return this.dataTableReference.Value;
    }

    protected override Trigger GetActiveDataTableReferenceTriggerCore()
    {
        return Trigger.CreatePropertyTrigger(this, PropertyNames.DataTableReference);
    }

    protected override DataMarkingSelection GetActiveMarkingReferenceCore()
    {
        return this.markingReference.Value;
    }

    protected override Trigger GetActiveMarkingReferenceTriggerCore()
    {
        return Trigger.CreatePropertyTrigger(this, PropertyNames.MarkingReference);
    }

    // Serialization

    private MyVisualization(SerializationInfo info, StreamingContext context) : base(info, context)
    {
        DeserializeProperty(info, context, PropertyNames.DataTableReference, out this.dataTableReference);
        DeserializeProperty(info, context, PropertyNames.MarkingReference, out this.markingReference);
    }

    protected override void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        base.GetObjectData(info, context);

        SerializeProperty(info, context, this.dataTableReference);
        SerializeProperty(info, context, this.markingReference);
    }
}

Creating a Factory

In order for a visualization to be creatable, a custom factory must be specified for it. The factory is responsible for creation and initial configuration of the visualization. It also holds some metadata, such as an image to use for UI commands and a type identifier.

public sealed class MyCustomIdentifiers : CustomTypeIdentifiers
{
    public static readonly CustomTypeIdentifier MyVisualizationIdentifier =
        CreateTypeIdentifier(
            "Acme.MyVisualization",    // Name
            "My Visualization",        // Display name
            "This is a description");  // Description
}

internal sealed class MyVisualizationFactory : CustomVisualFactory<MyVisualization>
{
    internal MyVisualizationFactory()
        : base(
        MyCustomIdentifiers.MyVisualizationIdentifier,     
        VisualCategory.Visualization,                   
        Properties.Resources.MyVisualizationImage,      
        null)                                           
    {
    }

    protected override void AutoConfigureCore(MyVisualization visualization)
    {
        // Find good default values for properties.
        DataManager dataManager = visualization.Context.GetService<DataManager>();
        visualization.DataTableReference = dataManager.Tables.DefaultTableReference;
        visualization.MarkingReference = dataManager.Markings.DefaultMarkingReference;
    }
}

The visualization factory is derived from CustomVisualFactory. Note that the custom factory class does not actually instantiate the visualization. It is the factory base class that instantiates the visualization through the parameterless constructor of the visualization. The constructor does not have to be public; in fact, it should not be public, since there is no way for an API user to create a visualization and then insert it into the document.

When the visualization has been created and inserted into the document node hierarchy, the framework calls two virtual methods on the factory: InitializeCore, which sets properties that are not directly related o data, and AutoConfigureCore, which configures the visualization with appropriate default values for data related properties, such as the data table, marking and columns to use. In the example above, only the AutoConfigureCore method overridden.

Register the Visualization

Finally, the visualization factory must be registered with the framework. This is performed from the RegisterVisuals override in the add-in.

public sealed class MyVisualizationAddIn : AddIn
{
    protected override void RegisterVisuals(AddIn.VisualRegistrar registrar)
    {
        registrar.Register(new MyVisualizationFactory());
    }
}

The visualization can now be created in TIBCO Spotfire, and displayed both in Spotfire Professional and Spotfire Web Player. It will also be able o take part in printing and export scenarios. There is, however, no way to configure the visualization or interact with it in any way. In order to do that, a view must be created for the visualization.