This article provides the basics on how data sources load data into Spotfire as well as a simple blueprint for for writing more advanced data sources.
Overview
A data source may be implemented using a data row reader. The data rows are loaded into Spotfire by a data source subclassing CustomDataRowReader. The row reader implemented here returns two columns, one numeric and one text. A total of five rows of data will be loaded.
Background Information
- Data Management
The Data Manager makes up a substantial part of the analysis document. It holds and handles data. It is the top node and entry point to data management.
- Creating a Data Source
Spotfire provides the option to create custom data sources. Existing data sources may also be wrapped to perform some data access related action.
Download
Creating the Project
Create a new TIBCO Spotfire Extension Project in Visual Studio:
Creating the Data Source
Create a CustomDataSource subclass, which does not prompt the user for any values:
namespace BasicDataSource
{
using System;
using System.Runtime.Serialization;
using Spotfire.Dxp.Application.Extension;
using Spotfire.Dxp.Data;
using Spotfire.Dxp.Framework.Persistence;
/// <summary>An example data source implementation.
/// </summary>
[Serializable]
[PersistenceVersion(1, 0)]
public sealed class BasicDataSource : CustomDataSource
{
/// <summary>Initializes a new instance of the BasicDataSource class.
/// </summary>
public BasicDataSource()
{
// Empty
}
/// <summary>Initializes a new instance of the BasicDataSource class.
/// </summary>
/// <param name="info">The serialization info.</param>
/// <param name="context">The streaming context.</param>
private BasicDataSource(SerializationInfo info, StreamingContext context)
: base(info, context)
{
// Empty
}
/// <summary>Gets a value indicating whether the data source is linkable.
/// </summary>
public override bool IsLinkable
{
get { return true; }
}
/// <summary>Populates the SerializationInfo with the data needed to
/// serialize the BasicDataSource instance.</summary>
/// <param name="info">The serialization info.</param>
/// <param name="context">The streaming context.</param>
public override void GetObjectData(SerializationInfo info, StreamingContext context)
{
base.GetObjectData(info, context);
}
/// <summary>Creates a DataSourceConnection, using the specified context and
/// the settings in this data source instance.
/// </summary>
/// <param name="serviceProvider">The service provider.</param>
/// <param name="promptMode">The prompt mode.</param>
/// <returns>The new DataSourceConnection instance.
/// </returns>
protected override DataSourceConnection ConnectCore(
IServiceProvider serviceProvider,
DataSourcePromptMode promptMode)
{
// Handle any prompting here. Not shown in this example.
// Return the connection if all is OK.
return DataSourceConnection.CreateConnection2(this, CreateReader, serviceProvider);
}
/// <summary>Creates the DataRowReader instance.
/// </summary>
/// <param name="serviceProvider">The service provider.</param>
/// <returns>
/// The DataRowReader instance.
/// </returns>
private static DataRowReader CreateReader(IServiceProvider serviceProvider)
{
return new BasicDataRowReader();
}
}
}
Next, subclass
CustomDataRowReader. Here it is called
BasicDataRowReader. The
MoveNextCore method tells Spotfire whether there is more data available to read, and also handles the data population for the next row:
namespace BasicDataSource
{
using System;
using System.Collections.Generic;
using Spotfire.Dxp.Application.Extension;
using Spotfire.Dxp.Data;
/// <summary>Implements CustomDataRowReader.
/// </summary>
internal class BasicDataRowReader : CustomDataRowReader
{
/// <summary>The number of rows of data available.
/// </summary>
private const int RowCount = 5;
/// <summary>Holds the list of strings in the data.
/// </summary>
private readonly List<string> stringList;
/// <summary>Holds the list of DataRowReaderColumn instances.
/// </summary>
private readonly List<DataRowReaderColumn> columns;
/// <summary>Holds the index of the current row.
/// </summary>
private int currentRowIndex = 0;
/// <summary>Initializes a new instance of the <see cref="BasicDataRowReader"/> class.
/// </summary>
public BasicDataRowReader()
{
this.columns = new List<DataRowReaderColumn>();
// First column contains numbers.
DataValueCursor column1Cursor = DataValueCursor.CreateMutableCursor(DataType.Integer);
this.columns.Add(new DataRowReaderColumn("Numbers", DataType.Integer, column1Cursor));
// Second column contains strings.
DataValueCursor column2Cursor = DataValueCursor.CreateMutableCursor(DataType.String);
this.columns.Add(new DataRowReaderColumn("Strings", DataType.String, column2Cursor));
// Populate the data store for the second column.
this.stringList = new List<string>() { "Zero", "One", "Two", "Three", "Four" };
}
/// <summary>The implementor should provide a list of <see cref="T:Spotfire.Dxp.Data.DataRowReaderColumn"/>s
/// that it returns.
/// </summary>
/// <returns>The DataRowReaderColumn instances.</returns>
/// <remarks>This method is only called once.</remarks>
protected override IEnumerable<DataRowReaderColumn> GetColumnsCore()
{
return this.columns.AsReadOnly();
}
/// <summary>The implementor should provide the result properties.
/// </summary>
/// <returns>The ResultProperties instance.</returns>
/// <remarks>This method is only called once.</remarks>
protected override ResultProperties GetResultPropertiesCore()
{
return new ResultProperties();
}
/// <summary>Advance to the next row.
/// The implementor should update all <see cref="T:Spotfire.Dxp.Data.DataValueCursor"/>s in
/// the <see cref="T:Spotfire.Dxp.Data.DataRowReaderColumn"/>s with values for the next row.
/// </summary>
/// <returns>
/// <c>true</c> if there are more rows; otherwise <c>false</c>.
/// </returns>
protected override bool MoveNextCore()
{
// Return false if trying to move past the last row.
if (this.currentRowIndex == RowCount)
{
return false;
}
// Populate the data value cursors with new values.
// First do the numbers.
MutableValueCursor<int> intCursor = (MutableValueCursor<int>) this.columns[0].Cursor;
intCursor.MutableDataValue.Value = this.currentRowIndex;
intCursor.MutableDataValue.IsValid = true;
// Now do the string.
MutableValueCursor<string> stringCursor = (MutableValueCursor<string>) this.columns[1].Cursor;
stringCursor.MutableDataValue.Value = this.stringList[this.currentRowIndex];
stringCursor.MutableDataValue.IsValid = true;
this.currentRowIndex++;
return true;
}
/// <summary>The implementor should implement this method to reset the
/// enumerator. If this method is called then the <see cref="M:Spotfire.Dxp.Data.DataRowReader.MoveNextCore"/>
/// method should return the first row again when called the next time.
/// </summary>
protected override void ResetCore()
{
this.currentRowIndex = 0;
}
}
}
Finishing the Solution
Before registering the new data source, define a new type identifier containing its name and description:
namespace BasicDataSource
{
using Spotfire.Dxp.Application.Extension;
/// <summary>Type identifiers used by the project.
/// </summary>
public class TypeIdentifiers : CustomTypeIdentifiers
{
/// <summary>The type identifier for our data source.
/// </summary>
public static readonly CustomTypeIdentifier BasicDataSource = CreateTypeIdentifier(
"BasicDataSource",
"Basic data source",
"A very basic example data source which loads a few rows of data.");
}
}
Finally, register the new data source using the AddIn class that must be part of every extension package:
namespace BasicDataSource
{
using Spotfire.Dxp.Application.Extension;
/// <summary>A very basic example data source which loads a few rows of data.
/// </summary>
public sealed class BasicDataSourceAddIn : AddIn
{
/// <summary>Register the data sources.
/// </summary>
/// <param name="registrar">The registrar.</param>
protected override void RegisterDataSources(DataSourceRegistrar registrar)
{
base.RegisterDataSources(registrar);
registrar.Register<BasicDataSource>(TypeIdentifiers.BasicDataSource);
}
}
}
When the extension has been deployed, it appears as a new File > Open From menu item.