Create a custom data visualization control


1. Overview

This sample will show how to create a custom sticky note data visualization control. An adapter is a class used to add a control or custom content into a Dundas BI view such as a dashboard. This is part 2 of 2, where the focus will be on an adapter that connects directly to Dundas BI data. You will learn the following:

  • How to get started
  • How to create a custom sticky note data visualization control

This article is a continuation of the Create a custom adapter article. It is strongly suggested that this article be read first before proceeding.

2. Getting started

The following prerequisites must be installed on your computer:

  • Visual Studio 2012, or higher.
  • Microsoft .NET Framework 4.5
  • Dundas BI Server

2.1. Downloading file

To download the Custom sample control solution click here.

2.2. Extracting sample to SDK folder

This sample is designed to automatically publish the extension to the instance. First you must extract the to the SDK folder within the instance. To extract the do the following:

  1. Find the SDK folder for your instance. It is located at [instance root]\sdk.
  2. Extract the 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

If the sample is not in the exact folder, it will not work correctly

2.3. Opening Solution

To open the Visual Studio solution simply do the following:

  1. Right click on the Microsoft Visual Studio shortcut, and click run as administrator.
  2. Click the File Menu, then Open Solution.
  3. Double click 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.
  • - 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 dashboard designer toolbar.

3.1. Modifying the JavaScript info class

In order to place the sticky note adapter in the data visualization category in the designer toolbar 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,
    return infos;

The sticky note data adapter added to the data visualization section of the toolbar.
The sticky note data 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. This property descriptor is now no longer necessary as this value is set in data binding rather than as a properties window. The following demonstrates the updated getControlProperties method with the text property removed.

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,

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

        // Angle.
        propertyDescriptor = new dundas.NumericPropertyDescriptor; = "Angle"; = "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;


        // Background.
        propertyDescriptor = new dundas.PropertyDescriptor(); = "Background"; = "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;

    return objectDescriptor;

Any place where the text property was initialized was also removed. This property will only be set in the loaddata method, and should be an empty string by default.

3.2.2. Defining the get supported settings method

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 set the adapter settings allowing data, for a single metric set, and make the adapter not styleable.

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;

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

3.2.3. Overriding request options

Totals by default are added to the data result. The sticky note adapter would then read this as the first row. This is not the desired behavior. The image below illustrates this difference:

Left: sticky note with totals on, Right: sticky note with totals turned off.
Left: sticky note with totals on, Right: sticky note with totals turned off.

To turn off the totals 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="">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 =;
    }, this);

    return requests;

3.2.4. Setting up bindings

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 by dragging and dropping, rather than in Properties.

For every data visualization control that shows data on your dashboard, there is an underlying metric set which encapsulates the data that is being visualized, and a set of bindings which specify how the various parts of the data visualization are connected to data elements.

In order to define bindings 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:, 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"

The result of the getAvailableBindings Method.
The result of the getAvailableBindings Method.

In order for the creation and removal of bindings to be handled by the adapter the getAvailableBindings Method is overridden. In the following example the adding and removing of the text binding is handled for the sticky note data adapter.

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:]        - 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:]        - 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 = (elementUsage) { return { elementUsage: elementUsage }; });

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

            // 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. Defining the load data method

The sticky note text property is set by overriding the loadData method. In the example below the _getData method is called. Then the data is connected based on the MetricSetBinding that is returned.

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];


        // Tell the listener we're done with data.  


    // Pass along a failure. (exceptionObj) {

    return def.promise();

_bindData: function (metricSetBinding) {
    /// <summary>
    /// Responsible for binding data from metric set bindings 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 = "";

    // 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 += ", ";


    // 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 data control, and a table data adapter.
The same metric set connected to a sticky note data control, and a table data adapter.

5. See Also


Support Articles




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