The Spotfire Community is moving to TIBCOmmunity and this forum location has closed. During the transition, you can still search the old forums but posting has been disabled. We encourage you to pick up the discussion at the new Spotfire community on TIBCOmmunity.
January 2011 - Posts - Tip of the Week

Tip of the Week

January 2011 - Posts

  • Passing Parameters to Custom Tools in the Web Player

    In this, the  final installment of our three-part series on animating visualizations, we will look at how to create controls to allow users to pass in parameters into a custom-built tool. We will go through three options for doing this: using the APIs, Preferences, and Document Properties.

    APIs
    One option for passing in parameters to a custom tool is to create properties inside your custom tool class which can be set remotely from the Script. This would allow authors to create property controls, and then via a script, pass in those values into the Custom Tool. 


    If you want to keep the complexity of your tool to a minimum, you would not create these properties as values that get persisted with the document. This means when you reopen the file, the properties are not maintained.  You could add this capability in, but this requires a lot more work to make the custom tool persist values, as it requires building a custom node which get serialized in the Spotfire Analysis Document.  So, it is possible, but not what I would recommend for this solution.

     

    Preferences
    We can also use Preferences to get configurable values into our custom tool.  According to the API information on our Spotfire Technology Network:


     

    Preferences are used by add-ins to persist one or more values, for example last used settings, user preferred values, etc. An object of this type can contain one or more such values, referred to as Preference Properties. Thus to create a set of preference properties, extend this class and populate that class with the properties that shall be persisted.

     

    When a user is logged on, the preference is populated with its persisted values, if such exist. If not, the preference is given default values while waiting for the user to set its values. When preferences are saved, their values are persisted using .Net serialization on the client and on the server depending on the settings of the preference.

     

    Preferences properties can be applied to single users or to user groups. Preferences applied to a group are inherited by all members of the group. The users can, however, choose to override the properties with custom values. In the same way, a group can override the properties inherited from its parent groups. If the group has more than one parent group, the value from the Primary parent group (as defined using the TIBCO Spotfire Administration Manager).

     

    This solution would work well if we wanted to set global defaults, or a different default for each group of users.  In our case, we want a solution where we can set the properties differently in each analysis file and for each user, so I would not recommend this approach for this situation.
     

     

    Document Properties
    The final solution would be to define Document Properties in our analysis file. Then in our custom tool, we will search for the existence of these properties and read the values. If the properties do not exist, we will use the default value, which is built into the tool.

    We can then create property controls to allow the users to configure the values of these properties directly in the analysis file.  The benefit of this approach is that we allow the users to change the values per analysis file, those values are persisted automatically, and we have very little updates that are required to our custom tool.  This is the approach I will recommend for our solution.

    First, we need to create the properties and controls. We will create two: the first will allow the user to set the speed at which we will automate through the filters. We will call this property stepRate and will expose it via a slider control:
     

     


    The second property will allow users to select which column they want to filter through. We will call this property whichColumn and expose it via a drop-down control:
     

     


    After we create the properties and the controls, we need to update our custom tool to read these values. The code snippets below go in the constructor of our RefreshDocumentWorker class inside our custom tool.  We also need to add a new private field called stepRate, as shown below:

     

    string whichColumn = Convert.ToString(this.application.Document.Properties["whichColumn"]);

     

    this.stepRate = Convert.ToDouble(this.application.Document.Properties["stepRate"]);

     

    To see the updated example work in the Web Player, visit the following URL:

    http://spottrain.tibco.com/SpotfireWeb/ViewAnalysis.aspx?file=/kevin/AnimateFiltersWithControl.dxp


    If you are interested in learning how to complete a tool like this yourself, consider taking our Developer Bootcamp. If you would like to have Spotfire build this tool or a similar tool for you, please contact Professional Services. If you have ideas or concepts you would like to see in future tip of the week articles, please contact me with your idea.

  • Making your Data Dance in TIBCO Spotfire Web Player

    In last week’s post, we learned how to animate visualizations by looping through filters automatically. This week we will learn how to get this solution to work in the Web Player.  This is a three part process. The first part is to deploy the tool to the Web Player server. The second part is to execute the tool as a button from within a text area. The third and final part is to provide a method for consumer to update parameters for the tool (the refresh rate, and which column to use). We will discuss the first two in this post, and the third section in next week’s post.


    Deploy the Tool to the Web Player
    For our tool to be available to use from within the Spotfire Web Player, we first need to make sure the tool’s dll is deployed to the Web Player server. This is documented in detail in Section 4.2 of the Spotfire Web Player Installation Manual

    This requires running an ‘Web Update Tool’ which will search the Spotfire server to find new deployed extensions, and deploy them to the Web Player.

    Execute the Tool from within a Script Control
    Next, we need to be able to call the tool from within the Web Player. By default, tools (with the exception of custom export tools) does not show up in the Web Player UI. However, using the APIs, there is a collection off the Application object called Toolbox, which allows developers to execute Custom Tools.   We can execute this code from within a script and attach it to a button from within a Text Area.


    In order for the script to see the Custom Tool dll, we first need to add a reference to it. Assume the DLL for our Custom Tool is called SpotfireTraining.Animate filters, we would add the following lines of code to reference it.

    import clr
    clr.AddReference("SpotfireTraining.AnimateFilters, Version=1.0.0.0, Culture=neutral, PublicKeyToken=481d6bdd0b956834")

    You can get the exact values to put in for the AddReference method from the tool’s module.xml file.

    After you do this, you will need to import the custom tool class from your DLL. Assuming our custom tool class is called AnimateFiltersTool, you would need to add the following:

    import Spotfire.Dxp.Application
    from SpotfireTraining.AnimateFilters import AnimateFiltersTool

    Next, we need to call the TryGetTool method off the Toolbox collection and pass in the  custom tool class name:

    found, tool = Application.Toolbox.TryGetTool[AnimateFiltersTool]()

    Finally we check to see if the tool was found, and if it is we call the Execute method , this will execute the tool. Depending on which type of tool it is , you will need to also pass in the context for the tool. In our example, we did a Page tool (which means the tool shows up in Spotfire Professional when you right click on a page tab). In this case, we are creating a Script parameter which passes in the specific page as an argument with the variable called ‘page’.

    if found:

      tool.Execute(page)

    NOTE: The Toolbox collection only works on custom tools built using the SDK, it will not work on built-in tools.

    The complete script is shown below:

    import clr
    clr.AddReference("SpotfireTraining.AnimateFilters, Version=1.0.0.0, Culture=neutral, PublicKeyToken=481d6bdd0b956834")
    import Spotfire.Dxp.Application
    from SpotfireTraining.AnimateFilters import AnimateFiltersTool

    found, tool = Application.Toolbox.TryGetTool[AnimateFiltersTool]()
    if found:
      tool.Execute(page)

    Want to see the file in action in the Spotfire Web Player?  Click here.

     


     
    Since our tool does not display a dialog, this will work fine in the Web Player.  Next week, in the third and final tip on this topic, we will learn how we can add parameters into the tool (which column to select and what the refresh rate should be) and allow the consumer to configure them in the Web Player.

    If you are interested in learning how to complete a tool like this yourself, consider taking our Developer Bootcamp. If you would like to have Spotfire build this tool or a similar tool for you, please contact Professional Services.

  • Animating Your Visualizations

    Spotfire includes very robust and dynamic visualization capabilities. In addition to offering many different visualization types,  the ability to visualize multiple dimensions, the ability to offer detail visualizations, etc... Spotfire can also animate the data available in visualizations.

    By using the SDK, Spotfire can spawn  a thread off the main application thread, allowing periodic updates to the analysis document.  To show how this is done, we will create a custom tool, which will loop through a filter's values, updating the document after each step through.

    Assume we have the following data loaded in TIBCO Spotfire Professional

     


    We may want to display a Bubble Chart with ‘Group’ on the X-Axis, ‘Sales’ on the Y-Axis, and have the markers sized by ‘Market share %’.

     


     
    In order to view how these Groups changed over time, we could trellis by ‘Quarter’, or we  could have the consumer step through  the ‘Quarter’ filter one at a time manually.


    Using a Custom Tool, we can automate the step through of the filter.  Below is the code that goes inside our tool to make this word:

          protected override void ExecuteCore(Page context)
            {
                AnalysisApplication application = context.Context.GetService<AnalysisApplication>();
                RefreshDocumentWorker refreshDocumentWorker = new RefreshDocumentWorker(application);
                refreshDocumentWorker.Start();
            }


            private class RefreshDocumentWorker
            {
                private readonly AnalysisApplication application;
                private readonly ApplicationThread applicationThread;
                private bool shallQuit;
                private string filterVal;
                private ItemFilter itemFilter;
                private IList filterValList;


                public RefreshDocumentWorker(AnalysisApplication application)
                {
                    this.shallQuit = false;
                    this.application = application;
                    this.filterVal = "";

                    FilterPanel myPanel = application.Document.ActivePageReference.FilterPanel;
                    Filter myFilter = myPanel.FilteringSchemeReference.DefaultFilterCollection["Quarter"];
                    myFilter.TypeId = FilterTypeIdentifiers.ItemFilter;
                    this.itemFilter = myFilter.As<ItemFilter>();

                    this.filterValList = this.itemFilter.Values;

                    // Fetch the application thread. It is available as an analysis service. 
                    this.applicationThread = application.GetService<ApplicationThread>();
                }

     

                public void Start()
                {
                    // Ask the application thread to spawn a new thread that executes the RefreshDataLoop. 
                    this.applicationThread.ExecuteOnWorkerThread("Document Refresh Thread", RefreshDocumentLoop);
                }

                private bool ShallQuit
                {
                    get
                    {
                        lock (this)
                        {
                            return this.shallQuit;
                        }
                    }
                    set
                    {
                        lock (this)
                        {
                            this.shallQuit = value;
                        }
                    }
                }

                // This method is executed on the worker thread. 
                private void RefreshDocumentLoop()
                {
                    foreach (string val in this.filterValList)
                    { 
                        // Sleep for 2 seconds. 
                        Thread.Sleep(TimeSpan.FromSeconds(2));

                        // Update the filterVal property
                        this.filterVal = val;

                        this.applicationThread.Invoke(RefreshDocument);
                    }
                }


                //This method is executed on the application thread. 
                private void RefreshDocument()
                {
                    Document document = this.application.Document;

                    if (document == null)
                    {
                        this.ShallQuit = true;
                        return;
                    }

                    ProgressService progressService = application.GetService<ProgressService>();
                    progressService.ExecuteWithProgress(
                        "Automatic document refresh",
                        "Refreshing the document to apply the current filter",
                        delegate
                        {
                            this.itemFilter.Value = this.filterVal;
                        });
                }
            }
     


    To see a demo of this tool in action, view the URL below: http://spotfire.tibco.com/community/downloads/AnimateFilters.htm


    The tool shown in this video is hard coded to step through the ‘Quarter’ filter at 2 second intervals, but for a more production ready tool, these values should be configured through a dialog as the tool launches.


    In next week’s tip, we will see how to make this solution work inside the Spotfire Web Player.

    If you are interested in learning how to complete a tool like this yourself, consider taking our Developer Bootcamp. If you would like to have Spotfire build this tool or a similar tool for you, please contact Professional Services.

  • Adding a Select All option for Property Controls

    In earlier posts we have discussed various strategies for manipulating Property Controls. One common request which we have not covered yet is to have a mechanism to easily select all values in a property control. 


    Let’s assume we have a Property Control that displays all the unique values in a given column. Below shows an example of a Property Control which displays all unique values from a column named 'Approver'.


    This list could get rather long if the column has lots of unique values.  You can use CTRL and SHIFT to select multiple values but cannot use SELECT + A (Select All is not supported).  It would save time for consumers to add a link underneath which allows them to select all, like shown below.

     


     
    One solution to accomplish this is to use a script to loop through all the values in a column and add them to an array. We can then later set the property (that is attached to the property control) to the array value, which will have the effect of selecting all values in the control.

    Assuming our column is called ‘Approver’, below is the script:


    from System import Array
    from Spotfire.Dxp.Data import IndexSet
    from Spotfire.Dxp.Data import DataValueCursor

    #Get access to the Column from which we want to get the values from
    myCol = Document.ActiveDataTableReference.Columns["Approver"]

    rowCount = Document.ActiveDataTableReference.RowCount
    rowsToInclude = IndexSet(rowCount,True)

    #Create a cursor to the Column we wish to get the values from
    cursor1 = DataValueCursor.CreateFormatted(Document.ActiveDataTableReference.Columns["Approver"])

    strArray = Array.CreateInstance(str,rowCount)

    #Loop through all rows, retrieve value for specific column, and add value into array
    for  row in  Document.ActiveDataTableReference.GetRows(rowsToInclude,cursor1):
       rowIndex = row.Index
       value1 = cursor1.CurrentValue
       strArray[rowIndex-1] = value1  

    #Set property to array created above
    myCol.Properties.SetProperty("whichApprovers",strArray)


    One downside of this script is that it loops through all rows in the column, and this can be very inefficient if there are a lot of values.  If you want to avoid having to loop through rows, you can use the solution shown next.

    After you create the property control, you will create another multi-select list box property control, which is attached to a different property.  Then, after exiting edit mode, select all the values manually in the property control,  as shown below ( in the second property control on the right).

     


    Once you do this, the property you just created will have a list of all unique values in that column. 
     

     

    You can then create a script control which sets the property associated with the first list box to the value of the property you just created above.  Assuming our first property is called whichApprovers and the property we create which contains all unique values is called allApprovers, the script would look like the following:

    myCol = Document.ActiveDataTableReference.Columns["Approver"]
    allVals = myCol.Properties.GetProperty("allApprovers")
    myCol.Properties.SetProperty("whichApprovers",allVals)

    Once you do this, you can then delete the second property control. The property it was attached to remains in the analysis file and will continue to hold a list of all unique values.
    While this is more work up front for the author, it works faster on large datasets as the script does not have to loop through all rows.

    Interested in learning more about what you can do with Script Controls and/or Property Controls? Consider taking our Script Controls blended training course or our Authoring Bootcamp (starting this week).

     

     

  • Saving Comments from within the Web Player

    In earlier posts, we discussed how you can share annotations with other users of an analysis file.  Most of these solutions required building a mashup using the Spotfire Web Player APIs, and quite a few people asked if there was a way to implement similar functionality without using the Web Player APIs. While there is not a solution that provides all the same functionality as the previous tips, there is a way you can accomplish the saving of comments without using the Web Player APIs. 

    First, you need to create something that will store the comments.  The best scenario is to use something that will automatically persist the information for you so you do not have to worry about implementing this. You can do this by using a Document Property. You can expose this property via a multi-line input field Property Control.

    Next, using a Script Control, you can create a button which will use the Spotfire Automation APIs to save the file back to its original location in the Spotfire Library. By doing this, it will include the latest value of the property, including any updates made by the user in the Web Player.

    The script is actually only one line of code:

    Application.Save()

    With this solution, it will also save the current state of the analysis file, including the active page, as well as active marking and filtering. This could be the desired effect, or you may want to update your script to reset the file to load the first analysis page the next time the file is opened. If you wish to save the file with the first page in the analysis loading, then add the following line of code in your script before the Application.Save() command:

    Document.ActivePageReference = Document.Pages[0]

    You can also extend this script to add some more complex functionality like setting the analysis so that no markings or filtering are used. 

    When you are done with the Script Control,  users can enter comments in the input field and then they can click the ‘Save File’ button to trigger the script. The next time the file is opened, it will contain the latest contents, including the comments in the property control.

     


     
    A variation of this solution would be to have the script take the contents of the property control and append them to a text area in the analysis file, as shown below.

     

    One issue with these approaches is that you will not be able to use it if you have anonymous access setup for the Web Player. Using the APIs to save the file will cause an authentication error.  If this is the case, you will need to alter your solution a bit.  Rather than storing the comments in a property, you will need to store the comments in another location, like a text file accessible via URL.  Then, you need to update your script to read the text from the file and then display them in a text area. This way, the analysis file is never altered or re-saved, but the script is just reading in text from an outside source which is saved somewhere. 

    Its also important to note that this solution works when saving the original file as itself, but its not possible to use any of the Save As capabilities to store different versions of this analysis file. Trying to use those Automation APIs will cause an error when executed from within the Web Player.

Syndication

Other Spotfire Blogs

©Copyright 2000-2011 TIBCO Software Inc | Privacy Policy | Terms of Use I Blog I Contact Us I Content Center