• Home

Create a custom data visualization control

Contents[Hide]

1. Overview

This sample will show how to add a custom data visualization control to Dundas BI. This is part 2 of 2, where the focus will be on a control that binds to data from a metric set.

The article walks through the parts of adding a custom data visualization control that are in addition to the steps for a regular custom control described in part 1.

Important
This article is a continuation of Create a custom control, and it is strongly recommended to read part 1 first.

2. Getting started

The following prerequisites must be installed on your computer:

  • Visual Studio 2015, or higher
  • Microsoft .NET Framework 4.6.1
  • A deployed Dundas BI instance

2.1. Downloading sample solution

To download the custom sample data control solution, click here.

(A version of this solution is also available for Dundas BI versions 2 through 5.)

2.2. Extracting sample to SDK folder

This sample is designed to automatically publish the extension to the instance, but you must extract it to the SDK folder within the instance:

  1. Find the SDK folder for your instance. It is located at [instance root]\sdk.
  2. Extract the SampleDataControl.zip to the SDK folder.
  3. You should see the following result:

    Samples added to SDK folder
    Samples added to SDK folder

    With the following structure:

    [instance root]
        • sdk
            • Samples
                • SampleControl

Important
If the sample is not in this specific folder, it will not work correctly.

2.3. Opening solution

To open the Visual Studio solution:

  1. Right-click on the Microsoft Visual Studio shortcut, and click Run as administrator.
  2. Click the File menu, then Open Solution.
  3. Choose the solution located at: [instance root]\sdk\Samples\SampleControl\SampleControl.sln

3. The project

The project is a class library.

  • mycompany.controls.sample.stickynote.adapter.js - Contains the sticky note adapter class.
  • mycompany.controls.sample.stickynote.css - The custom style sheet for the sticky note adapter.
  • mycompany.controls.sample.stickynote.info.js - Gets the information about the control (such as what adapters it supports).
  • PublishExtension.targets - Used for auto publishing the extension after the build succeeds. This also copies the stylesheets, and hooks them up to the style override file.
  • SampleControlExtensionPackageInfo.cs - Contains the package information about the extension package.
  • StickyNoteAdapterInfo.cs - Contains the connection to JavaScript files, and defines the full JavaScript name.
  • StickyNoteExtension.cs - Contains the information about the adapters.
  • stickyNoteIcon.png - An icon to represent the sticky note in the toolbar.

3.1. Modifying the JavaScript info class

In order to place the sticky note adapter in the data visualization toolbar menus, the getControlInfos needs to be modified. In the example below, we change the categoryName to "Data Visualization":

getControlInfos: function () {
    /// <summary>
    /// Gets the information about the control (such as what adapters it supports).
    /// </summary>
    /// <returns type="Object">
    /// Meta-information about the control and what adapters it supports.
    /// </returns>
    var infos = [{
        // Change the category into the data visualization category.
        "categoryName": "Data Visualization",
        "subCategoryName": "Fun",
        "caption": "Stickynote extension",
        "description": "Simple stickynote sample control",
        "adapterObjectName": 'mycompany.controls.sample.stickynote.Adapter',
        "defaultWidth": 350,
        "defaultHeight": 200,
        "toolbarIconUrl": dundas.Utility.getContextualUrlPath('Content/Images/icons/stickyNoteIcon.png')
    }];
    return infos;
}

The sticky note adapter added to the data visualization section of the toolbar
The sticky note adapter added to the data visualization section of the toolbar

3.2. Modifying the JavaScript adapter class

3.2.1. Removing the text property descriptor

In the previous article, we created a Text property that displayed the main text for the sticky note. The data visualization version of this sample will leave out this property as it will determine the text using data binding:

getControlProperties: function (path) {
    /// <summary>
    /// Called to get the object with the list of properties on the root control, or any sub-element
    /// of the control.
    /// </summary>
    /// <param name="path" type="Array" elementType="dundas.view.controls.PropertyPathItem" optional="true">(optional) The path to the sub-element 
    /// of this control. Undefined if the root is requested, otherwise an array of plain objects with properties.</param>
    /// <returns type="dundas.controls.ObjectDescriptor">An object with the list of properties on the desired element.</returns>

    var objectDescriptor,
        propertyDescriptor,
        elementName;

    // Base path properties.
    if (!path) {
        // Object descriptor.
        objectDescriptor = this._super(path);

        // Angle.
        propertyDescriptor = new dundas.NumericPropertyDescriptor();
        propertyDescriptor.name = "Angle";
        propertyDescriptor.id = "angle";
        propertyDescriptor.category = "GS_PropertyGrid_Category_Common".localize();
        propertyDescriptor.description = "The angle.";
        propertyDescriptor.value = this.angle;
        propertyDescriptor.defaultValue = -3;
        propertyDescriptor.section = dundas.controls.PropertyGridSections.Look;

        objectDescriptor.addProperty(propertyDescriptor);

        // Background.
        propertyDescriptor = new dundas.PropertyDescriptor();
        propertyDescriptor.name = "Background";
        propertyDescriptor.id = "background";
        propertyDescriptor.category = "GS_PropertyGrid_Category_Common".localize();
        propertyDescriptor.description = "The background";
        propertyDescriptor.value = this.background;
        propertyDescriptor.defaultValue = new dundas.controls.SolidColorBrush(dundas.controls.Color.fromString("#cfc"));
        propertyDescriptor.isNullable = true;
        propertyDescriptor.section = dundas.controls.PropertyGridSections.Look;

        objectDescriptor.addProperty(propertyDescriptor);
    }
           
    return objectDescriptor;
}

3.2.2. Defining the supported settings

To allow the adapter to handle data, the getSupportedSettings method must be overridden. This method returns an object with the variety of settings available for the adapter. In the following example, we indicate that we support data for a single metric set, and do not allow styles to be applied:

getSupportedSettings: function () {
    /// <summary>
    /// Gets the settings for the supported operations of this adapter.
    /// Implementers of this function should call this._super() first
    /// to get the default settings.
    /// </summary>
    /// <returns type="Object">
    /// An object with the variety of settings  available for this
    /// adapter.
    /// </returns>

    var supportedSettings = this._super();

    supportedSettings.isDataSupported = true;
    supportedSettings.isSingleMetricSetOnly = true;
    supportedSettings.isStyleable = false;

    return supportedSettings;
}

Note
Implementers of this function should call this._super() first to get the default settings.

3.2.3. Overriding request options

Totals are normally added to the metric set's data result by default. The sticky note adapter would then read this total as the first row, but this is not the desired behavior. The image below illustrates this difference:

Sticky note with totals on versus off
Sticky note with totals on versus off

To override the metric set and disable totals regardless of user settings, the _getDataRequests method is overridden. The following demonstrates how to override the _getDataAdapters method on the adapter to turn off totals:

_getDataRequests: function () {
    /// <summary>
    /// A helper method which creates the default request objects from the current metric set
    /// bindings.
    /// </summary>
    /// <returns type="Array" elementType="dundas.data.Request">
    /// An array of request objects.
    /// </returns>

    var requests = this._super();

    requests.forEach(function (request) {
        var overrides = request.overrides;

        if (overrides) {
            // Turn totals off.
            overrides.shownTotals = dundas.data.CellsetAxis.NONE;
        }
    }, this);

    return requests;
}

3.2.4. Setting up bindings

Note
This is optional: state indicators have no bindings, for example. Bindings are specifically for users to be able to customize where and which data is displayed.

Data visualizations in Dundas BI display data from a metric set, and typically use a set of bindings that specify how the various parts of the data visualization are connected to data elements.

Users can use these bindings to decide for themselves whether each measure or hierarchy is visualized and where. These are set up in the Visualization tab of the Data Analysis Panel (see Setting up the visualization for details).

In order to make bindings available, you need to override the getAvailableBindings Method. In the following example, we return a list of one text binding:

getAvailableBindings: function (metricSetBinding) {
    /// <summary>
    /// Gets the available bindings for this adapter.
    /// </summary>
    /// <param name="metricSetBinding" type="dundas.view.controls.MetricSetBinding">The metric set binding to get the bindings for.</param>
    /// <returns type="Array" elementType="Object">An array of plain objects with properties:
    ///     targetId        [type: String]                                          - A unique identifier for this binding.
    ///     targetName      [type: String]                                          - The displayed text for this binding.
    ///     bindingType     [type: dundas.data.PlacementPosition, optional: true]   - (optional) The placement that should be assigned to an analysis element dropped onto this binding. If not specified, it is assigned to rows.
    /// </returns>
    return [{
        "targetId": "text",
        "targetName": "Text"
    }];
}

Bindings shown in the Data Analysis Panel's Visualization tab
Bindings shown in the Data Analysis Panel's Visualization tab

The visualization can automatically assign bindings when data is added to it, so that users don't necessarily need to also assign the data themselves, by overriding the generateBindings method. In the following example, data is automatically assigned under Text when added:

generateBindings: function (options) {
    /// <summary>
    /// Asks the adapter to create/update the bindings on the given MetricSetBinding.
    /// The _super method should be called first if automatic adding/removal is desired from metricSetBindings property, and bindings created/removed.
    /// </summary>
    /// <param name="options" type="Object">The options object for the creation of the bindings. Structure:
    ///     metricSet           [type: dundas.entities.MetricSet]               - The metric set object that is the source of the metric set binding.
    ///
    ///     metricSetBinding    [type: dundas.view.controls.MetricSetBinding]   - The metric set binding that is being modified.
    /// 
    ///     addedElements       [type: Array, elementType: Object]              - (optional) An array of objects describing what is being added.
    ///         elementUsage    [type: dundas.data.AnalysisElementUsage]        - The element usage from within the metric set that is the column.
    ///         bindingId       [type: String]                                  - (optional) The binding ID that the element was added for.
    ///         bindingName     [type: String]                                  - (optional) The binding name that the element was added for.
    ///         levelUniqueName [type: String]                                  - (optional) The level unique name that this element is for.
    /// 
    ///     removedElements     [type: Array, elementType: Object]              - (optional) An array of objects describing what is being removed.
    ///         elementUsage    [type: dundas.data.AnalysisElementUsage]        - The element usage from within the metric set that is the column.
    ///
    ///     adapterData         [type: Object]                                  - (optional) An object specified as the adapter data for the requested adapter info class.
    /// </param>
            
    var addedElements = options.addedElements;

    if (!addedElements || !addedElements.length) {
        // The entire metric set was added.
        addedElements = options.metricSet.elements.map(function (elementUsage) { 
            return { elementUsage: elementUsage };
        });
    }

    addedElements.forEach(function (addedElement) {
        // Bind to only row hierarchies and measures for now
        if (addedElement.elementUsage.placement == dundas.data.PlacementPosition.ROWS
            || addedElement.elementUsage.placement == dundas.data.PlacementPosition.MEASURE) {

            // Some elements have options to be hidden.
            if (!addedElement.elementUsage.isHidden && !addedElement.elementUsage.isNotVisualized) {

                // If the user picked no specific binding binding target, or they picked Text:
                if (!addedElement.bindingId || addedElement.bindingId == "text") {

                    // Generate a binding.
                    options.metricSetBinding.bindings.push(new dundas.view.controls.Binding({
                        elementUsageUniqueName: addedElement.elementUsage.uniqueName,
                        targetId: "text",
                        targetName: "Text"
                    }));
                }
            }
        }
    }, this);
}

3.2.5. Loading data

The sticky note text is set by overriding the loadData method. In the example below, the _getData method is called to request the data, and the DataResult that is returned and set on the metric set binding is used to display the text:

loadData: function () {
    /// <summary>
    /// Called when the adapter should load data, either for the first
    /// time or a refresh. Returns a jQuery.Deferred object.
    /// Implementers of this method should call this._super() first to
    /// get the deferred object to return as a promise.
    /// </summary>
    /// <returns type="jQuery.Deferred">
    /// A deferred object that is resolved once the data is returned,
    /// and the adapter has processed it.
    /// </returns>

    // Get the original deferred from the super.
    var def = this._super(),
        first = true;

    // Ignore if we are frozen.
    if (this._isFrozen) {
        return def.promise();
    }

    // Use the default get data call for simplicity.
    var dataDef = this._getData();

    // Keep a ref to ourselves.

    // Bind the data when it's back.
    dataDef.done(function () {
        var metricSetBinding = this.metricSetBindings[0];

        this._bindData(metricSetBinding);

        // Tell the listener we're done with data.  
        def.resolve();

    }.bind(this));

    // Pass along a failure.
    dataDef.fail(function (exceptionObj) {
        def.reject(exceptionObj);
    }.bind(this));

    return def.promise();
},

_bindData: function (metricSetBinding) {
    /// <summary>
    /// Responsible for binding data to the control.
    /// <summary>

    var dataResult = metricSetBinding && metricSetBinding.dataResult;
    var cellset = dataResult && dataResult.cellset;

    // If the control was initialized.
    if (!this.control || !cellset || !cellset.rows.length || !metricSetBinding.bindings.length) {
        this.stickynoteText = "";
        return;
    }

    // Create a text bindings lookup.
    var textBindingsLookup = {};
    metricSetBinding.bindings.forEach(function (binding) {
        // If the binding is for the text property.
        if (binding.targetId == "text") {
            textBindingsLookup[binding.elementUsageUniqueName] = binding;
        }
    });

    // text is empty until we add items that belong to the bindings.
    var text = "";

    // Look for the first populated row.
    for (var rowIndex = 0; rowIndex < cellset.rows.length; rowIndex++)
    {
        if (cellset.rows[rowIndex]) {
            // Iterate through the row members.
            for (var memberIndex = 0; memberIndex < cellset.rows[rowIndex].members.length; memberIndex++) {
                var member = cellset.rows[rowIndex].members[memberIndex];

                // If the row member is connected to the text binding.
                if (textBindingsLookup[member.hierarchyUniqueName]) {
                    // Add the member caption for the row member to the text.
                    text += member.caption += ", ";
                }
            }

            // Check measures for bindings to the text property.
            for (var columnIndex = 0; columnIndex < cellset.columns.length; columnIndex++) {
                if (cellset.columns[columnIndex]) {
                    var measureMemberIndex = cellset.columns[columnIndex].members.length - 1;
                    var member = cellset.columns[columnIndex].members[measureMemberIndex];

                    // If the measure is connected to the text binding.
                    if (textBindingsLookup[member.uniqueName]
                        && cellset.cells[columnIndex]
                        && cellset.cells[columnIndex][rowIndex]) {

                        text += member.caption += ": ";

                        // Lookup value
                        text += cellset.cells[columnIndex][rowIndex].formattedValue;

                        // Add a comma.
                        text += ", ";
                    }
                }
            }

            break;
        }
    }

    // Trim the last comma.
    this.stickynoteText = text.substring(0, text.length - 2);
}

4. Result

The result of this sample is a new sticky note data control that is added to the data visualization section of the toolbar, and displays multiple elements connected to its text properties.

The same metric set connected to a sticky note and a table
The same metric set connected to a sticky note and a table

5. See also

.NET

JavaScript

Dundas Data Visualization, Inc.
500-250 Ferrand Drive
Toronto, ON, Canada
M3C 3G8

North America: 1.800.463.1492
International: 1.416.467.5100

Dundas Support Hours: 7am-6pm, ET, Mon-Fri