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.