Skip to main content
RSS feed Subscribe to feed

 

How to Create a Simple Data Source

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.

Loaded rows
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 a Spotfire extension project

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.

Result