<?xml version="1.0" encoding="UTF-8" ?>
<?xml-stylesheet type="text/xsl" href="http://spotfire.tibco.com/community/utility/FeedStylesheets/atom.xsl" media="screen"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en"><title type="html">Knowledge Base</title><subtitle type="html" /><id>http://spotfire.tibco.com/community/blogs/stn/atom.aspx</id><link rel="alternate" type="text/html" href="http://spotfire.tibco.com/community/blogs/stn/default.aspx" /><link rel="self" type="application/atom+xml" href="http://spotfire.tibco.com/community/blogs/stn/atom.aspx" /><generator uri="http://communityserver.org" version="3.1.20917.1142">Community Server</generator><updated>2010-03-06T06:43:00Z</updated><entry><title>Loading images from an URL using Data Functions</title><link rel="alternate" type="text/html" href="http://spotfire.tibco.com/community/blogs/stn/archive/2010/03/15/loading-images-from-an-url-using-data-functions.aspx" /><id>http://spotfire.tibco.com/community/blogs/stn/archive/2010/03/15/loading-images-from-an-url-using-data-functions.aspx</id><published>2010-03-15T09:42:00Z</published><updated>2010-03-15T09:42:00Z</updated><content type="html">&lt;p&gt;In this example we will show how to write a simple data function executor extension that load images from a column of URLs and returns the images as binary large objects. &lt;/p&gt; &lt;p&gt;First we need to create a function executor which takes in an input parameter called “url” which contains strings that are supposed to be URLs and returns an output parameter names “image” that is a list of images as binary large objects.&lt;/p&gt;&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;namespace&lt;/span&gt; ImageFromURL&lt;br /&gt;{&lt;br /&gt;    &lt;span class="kwrd"&gt;using&lt;/span&gt; System;&lt;br /&gt;    &lt;span class="kwrd"&gt;using&lt;/span&gt; System.Collections.Generic;&lt;br /&gt;    &lt;span class="kwrd"&gt;using&lt;/span&gt; System.IO;&lt;br /&gt;    &lt;span class="kwrd"&gt;using&lt;/span&gt; System.Net;&lt;br /&gt;    &lt;span class="kwrd"&gt;using&lt;/span&gt; System.Security;&lt;br /&gt;    &lt;span class="kwrd"&gt;using&lt;/span&gt; Spotfire.Dxp.Application.Extension;&lt;br /&gt;    &lt;span class="kwrd"&gt;using&lt;/span&gt; Spotfire.Dxp.Data;&lt;br /&gt;    &lt;span class="kwrd"&gt;using&lt;/span&gt; Spotfire.Dxp.Data.Collections;&lt;br /&gt;&lt;br /&gt;    &lt;span class="rem"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class="rem"&gt;/// This is a simple function executor that loads images from URLs.&lt;/span&gt;&lt;br /&gt;    &lt;span class="rem"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;class&lt;/span&gt; ImageFromURLFunctionExecutor : CustomDataFunctionExecutor&lt;br /&gt;    {&lt;br /&gt;        &lt;span class="rem"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;&lt;br /&gt;        &lt;span class="rem"&gt;/// Execute a function invocation. The executor is expected to add the output results to the invocation&lt;/span&gt;&lt;br /&gt;        &lt;span class="rem"&gt;/// object.&lt;/span&gt;&lt;br /&gt;        &lt;span class="rem"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;&lt;br /&gt;        &lt;span class="rem"&gt;/// &amp;lt;param name=&amp;quot;invocation&amp;quot;&amp;gt;The function invocation.&amp;lt;/param&amp;gt;&lt;/span&gt;&lt;br /&gt;        &lt;span class="rem"&gt;/// &amp;lt;returns&amp;gt;&lt;/span&gt;&lt;br /&gt;        &lt;span class="rem"&gt;/// The prompt models to return, the implementor is expected to use the yield return pattern.&lt;/span&gt;&lt;br /&gt;        &lt;span class="rem"&gt;/// &amp;lt;/returns&amp;gt;&lt;/span&gt;&lt;br /&gt;        &lt;span class="kwrd"&gt;protected&lt;/span&gt; &lt;span class="kwrd"&gt;override&lt;/span&gt; IEnumerable&amp;lt;&lt;span class="kwrd"&gt;object&lt;/span&gt;&amp;gt; ExecuteFunctionCore(Spotfire.Dxp.Data.DataFunctions.DataFunctionInvocation invocation)&lt;br /&gt;        {&lt;br /&gt;            &lt;span class="rem"&gt;// Get the input as a row reader, this one always expect an url parameter.&lt;/span&gt;&lt;br /&gt;            DataRowReader urlReader = invocation.GetInput(&lt;span class="str"&gt;&amp;quot;url&amp;quot;&lt;/span&gt;);&lt;br /&gt;&lt;br /&gt;            &lt;span class="rem"&gt;// Create a pageable list to store the result in.&lt;/span&gt;&lt;br /&gt;            PageableListSettings settings = &lt;span class="kwrd"&gt;new&lt;/span&gt; PageableListSettings();&lt;br /&gt;&lt;br /&gt;            &lt;span class="rem"&gt;// Since we only write the data sequentially we can get better performance by not&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;// allowing random writes.&lt;/span&gt;&lt;br /&gt;            settings.CanReplaceValue = &lt;span class="kwrd"&gt;false&lt;/span&gt;;&lt;br /&gt;            PageableList&amp;lt;BinaryLargeObject&amp;gt; output = &lt;span class="kwrd"&gt;new&lt;/span&gt; PageableList&amp;lt;BinaryLargeObject&amp;gt;(settings);&lt;br /&gt;&lt;br /&gt;            &lt;span class="rem"&gt;// Cursor that contains the input URL&lt;/span&gt;&lt;br /&gt;            DataValueCursor&amp;lt;&lt;span class="kwrd"&gt;string&lt;/span&gt;&amp;gt; cursor = (DataValueCursor&amp;lt;&lt;span class="kwrd"&gt;string&lt;/span&gt;&amp;gt;)urlReader.Columns[0].Cursor;            &lt;br /&gt;&lt;br /&gt;            &lt;span class="rem"&gt;// Loop over all input rows.&lt;/span&gt;&lt;br /&gt;            &lt;span class="kwrd"&gt;while&lt;/span&gt; (urlReader.MoveNext())&lt;br /&gt;            {&lt;br /&gt;                &lt;span class="kwrd"&gt;if&lt;/span&gt; (!cursor.IsCurrentValueValid)&lt;br /&gt;                {&lt;br /&gt;                    &lt;span class="rem"&gt;// Invalid value as input so return invalid value as output.&lt;/span&gt;&lt;br /&gt;                    output.Add(&lt;span class="kwrd"&gt;null&lt;/span&gt;);&lt;br /&gt;                    &lt;span class="kwrd"&gt;continue&lt;/span&gt;;&lt;br /&gt;                }&lt;br /&gt;&lt;br /&gt;                &lt;span class="kwrd"&gt;string&lt;/span&gt; url = cursor.CurrentValue;&lt;br /&gt;&lt;br /&gt;                &lt;span class="kwrd"&gt;try&lt;/span&gt;&lt;br /&gt;                {&lt;br /&gt;                    &lt;span class="rem"&gt;// Use the standard web request API to retrieve the images.&lt;/span&gt;&lt;br /&gt;                    Uri uri = &lt;span class="kwrd"&gt;new&lt;/span&gt; Uri(url);&lt;br /&gt;&lt;br /&gt;                    WebRequest webRequest = WebRequest.Create(uri);&lt;br /&gt;&lt;br /&gt;                    WebResponse response = webRequest.GetResponse();&lt;br /&gt;&lt;br /&gt;                    MemoryStream memoryStream = &lt;span class="kwrd"&gt;new&lt;/span&gt; MemoryStream();&lt;br /&gt;                    Stream responseStream = response.GetResponseStream();&lt;br /&gt;                    &lt;span class="kwrd"&gt;byte&lt;/span&gt;[] buf = &lt;span class="kwrd"&gt;new&lt;/span&gt; &lt;span class="kwrd"&gt;byte&lt;/span&gt;[8 * 1024];&lt;br /&gt;                    &lt;span class="kwrd"&gt;int&lt;/span&gt; n = 1;&lt;br /&gt;                    &lt;span class="kwrd"&gt;while&lt;/span&gt; (n &amp;gt; 0)&lt;br /&gt;                    {&lt;br /&gt;                        n = responseStream.Read(buf, 0, buf.Length);&lt;br /&gt;                        memoryStream.Write(buf, 0, n);&lt;br /&gt;                    }&lt;br /&gt;&lt;br /&gt;                    memoryStream.Seek(0, SeekOrigin.Begin);&lt;br /&gt;&lt;br /&gt;                    BinaryLargeObject image = BinaryLargeObject.Create(memoryStream);&lt;br /&gt;                    output.Add(image);&lt;br /&gt;                }&lt;br /&gt;                &lt;span class="kwrd"&gt;catch&lt;/span&gt; (UriFormatException)&lt;br /&gt;                {&lt;br /&gt;                    output.Add(&lt;span class="kwrd"&gt;null&lt;/span&gt;);&lt;br /&gt;                }&lt;br /&gt;                &lt;span class="kwrd"&gt;catch&lt;/span&gt; (NotSupportedException)&lt;br /&gt;                {&lt;br /&gt;                    output.Add(&lt;span class="kwrd"&gt;null&lt;/span&gt;);&lt;br /&gt;                }&lt;br /&gt;                &lt;span class="kwrd"&gt;catch&lt;/span&gt; (SecurityException)&lt;br /&gt;                {&lt;br /&gt;                    output.Add(&lt;span class="kwrd"&gt;null&lt;/span&gt;);&lt;br /&gt;                }&lt;br /&gt;            }&lt;br /&gt;&lt;br /&gt;            &lt;span class="rem"&gt;// Assign the result.&lt;/span&gt;&lt;br /&gt;            invocation.SetOutput(&lt;span class="str"&gt;&amp;quot;image&amp;quot;&lt;/span&gt;, &lt;span class="kwrd"&gt;new&lt;/span&gt; ImageReader(output));&lt;br /&gt;&lt;br /&gt;            &lt;span class="rem"&gt;// No need to prompt so just break.&lt;/span&gt;&lt;br /&gt;            &lt;span class="kwrd"&gt;yield&lt;/span&gt; &lt;span class="kwrd"&gt;break&lt;/span&gt;;&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        &lt;span class="rem"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;&lt;br /&gt;        &lt;span class="rem"&gt;/// Simple reader to return the images.&lt;/span&gt;&lt;br /&gt;        &lt;span class="rem"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;&lt;br /&gt;        &lt;span class="kwrd"&gt;private&lt;/span&gt; &lt;span class="kwrd"&gt;class&lt;/span&gt; ImageReader : CustomDataRowReader&lt;br /&gt;        {&lt;br /&gt;            &lt;span class="rem"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// The list of the images as binary large objects.&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class="kwrd"&gt;private&lt;/span&gt; &lt;span class="kwrd"&gt;readonly&lt;/span&gt; PageableList&amp;lt;BinaryLargeObject&amp;gt; list;&lt;br /&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// The cursor to assign the result for the current row.&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class="kwrd"&gt;private&lt;/span&gt; &lt;span class="kwrd"&gt;readonly&lt;/span&gt; MutableValueCursor&amp;lt;BinaryLargeObject&amp;gt; cursor;&lt;br /&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// The columns returned by the reader.&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class="kwrd"&gt;private&lt;/span&gt; &lt;span class="kwrd"&gt;readonly&lt;/span&gt; List&amp;lt;DataRowReaderColumn&amp;gt; columns;&lt;br /&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// The current index to return.&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class="kwrd"&gt;private&lt;/span&gt; &lt;span class="kwrd"&gt;int&lt;/span&gt; index = -1;&lt;br /&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// Initializes a new instance of the &amp;lt;see cref=&amp;quot;ImageReader&amp;quot;/&amp;gt; class.&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// &amp;lt;param name=&amp;quot;list&amp;quot;&amp;gt;The list of the images as binary large objects.&amp;lt;/param&amp;gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class="kwrd"&gt;public&lt;/span&gt; ImageReader(PageableList&amp;lt;BinaryLargeObject&amp;gt; list)&lt;br /&gt;            {&lt;br /&gt;                &lt;span class="kwrd"&gt;this&lt;/span&gt;.list = list;&lt;br /&gt;                &lt;span class="kwrd"&gt;this&lt;/span&gt;.columns = &lt;span class="kwrd"&gt;new&lt;/span&gt; List&amp;lt;DataRowReaderColumn&amp;gt;();&lt;br /&gt;                &lt;span class="kwrd"&gt;this&lt;/span&gt;.cursor = (MutableValueCursor&amp;lt;BinaryLargeObject&amp;gt;)DataValueCursor.CreateMutableCursor(DataType.Binary);&lt;br /&gt;&lt;br /&gt;                &lt;span class="rem"&gt;// Say that the result is of type image/png..&lt;/span&gt;&lt;br /&gt;                DataColumnProperties properties = &lt;span class="kwrd"&gt;new&lt;/span&gt; DataColumnProperties();&lt;br /&gt;                properties.ContentType = &lt;span class="str"&gt;&amp;quot;image/png&amp;quot;&lt;/span&gt;;&lt;br /&gt;&lt;br /&gt;                &lt;span class="rem"&gt;// Create the metadata for the put column.&lt;/span&gt;&lt;br /&gt;                DataRowReaderColumn column =&lt;br /&gt;                    &lt;span class="kwrd"&gt;new&lt;/span&gt; DataRowReaderColumn(&lt;br /&gt;                        &lt;span class="str"&gt;&amp;quot;Image&amp;quot;&lt;/span&gt;,&lt;br /&gt;                        DataType.Binary,&lt;br /&gt;                        properties,&lt;br /&gt;                        &lt;span class="kwrd"&gt;this&lt;/span&gt;.cursor);&lt;br /&gt;&lt;br /&gt;                &lt;span class="kwrd"&gt;this&lt;/span&gt;.columns.Add(column);&lt;br /&gt;            }&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// The implementor should provide the result properties.&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// &amp;lt;returns&amp;gt;The result properties.&amp;lt;/returns&amp;gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// &amp;lt;remarks&amp;gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// This method is only called once.&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// &amp;lt;/remarks&amp;gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class="kwrd"&gt;protected&lt;/span&gt; &lt;span class="kwrd"&gt;override&lt;/span&gt; ResultProperties GetResultPropertiesCore()&lt;br /&gt;            {&lt;br /&gt;                &lt;span class="rem"&gt;// Table metadata can be returned here, but we have nothing to return for now.&lt;/span&gt;&lt;br /&gt;                &lt;span class="kwrd"&gt;return&lt;/span&gt; &lt;span class="kwrd"&gt;new&lt;/span&gt; ResultProperties();&lt;br /&gt;            }&lt;br /&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// The implementor should implement this method to reset the&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// enumerator. If this method is called then the &amp;lt;see cref=&amp;quot;M:Spotfire.Dxp.Data.DataRowReader.MoveNextCore&amp;quot;/&amp;gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// method should return the first row again when called the next time.&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class="kwrd"&gt;protected&lt;/span&gt; &lt;span class="kwrd"&gt;override&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; ResetCore()&lt;br /&gt;            {&lt;br /&gt;                &lt;span class="kwrd"&gt;this&lt;/span&gt;.index = -1;&lt;br /&gt;            }&lt;br /&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// The implementor should provide a list of &amp;lt;see cref=&amp;quot;T:Spotfire.Dxp.Data.DataRowReaderColumn&amp;quot;/&amp;gt;s that it&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// returns.&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// &amp;lt;returns&amp;gt;The metadata of the columns that are returned by the reader.&amp;lt;/returns&amp;gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// &amp;lt;remarks&amp;gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// This method is only called once.&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// &amp;lt;/remarks&amp;gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class="kwrd"&gt;protected&lt;/span&gt; &lt;span class="kwrd"&gt;override&lt;/span&gt; IEnumerable&amp;lt;DataRowReaderColumn&amp;gt; GetColumnsCore()&lt;br /&gt;            {&lt;br /&gt;                &lt;span class="kwrd"&gt;return&lt;/span&gt; &lt;span class="kwrd"&gt;this&lt;/span&gt;.columns;&lt;br /&gt;            }&lt;br /&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// Advance to the next row.&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// The implementor should update all &amp;lt;see cref=&amp;quot;T:Spotfire.Dxp.Data.DataValueCursor&amp;quot;/&amp;gt;s in the &amp;lt;see cref=&amp;quot;T:Spotfire.Dxp.Data.DataRowReaderColumn&amp;quot;/&amp;gt;s&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// with values for the next row.&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// &amp;lt;returns&amp;gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;///    &amp;lt;c&amp;gt;true&amp;lt;/c&amp;gt; if there are more rows; otherwise &amp;lt;c&amp;gt;false&amp;lt;/c&amp;gt;.&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// &amp;lt;/returns&amp;gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class="kwrd"&gt;protected&lt;/span&gt; &lt;span class="kwrd"&gt;override&lt;/span&gt; &lt;span class="kwrd"&gt;bool&lt;/span&gt; MoveNextCore()&lt;br /&gt;            {&lt;br /&gt;                &lt;span class="kwrd"&gt;this&lt;/span&gt;.index++;&lt;br /&gt;                &lt;span class="kwrd"&gt;if&lt;/span&gt; (&lt;span class="kwrd"&gt;this&lt;/span&gt;.index &amp;gt;= &lt;span class="kwrd"&gt;this&lt;/span&gt;.list.Count)&lt;br /&gt;                {&lt;br /&gt;                    &lt;span class="rem"&gt;// We are done, past the final row.&lt;/span&gt;&lt;br /&gt;                    &lt;span class="kwrd"&gt;return&lt;/span&gt; &lt;span class="kwrd"&gt;false&lt;/span&gt;;&lt;br /&gt;                }&lt;br /&gt;&lt;br /&gt;                &lt;span class="rem"&gt;// Get the image for the current row and assign to the cursor.&lt;/span&gt;&lt;br /&gt;                BinaryLargeObject image = &lt;span class="kwrd"&gt;this&lt;/span&gt;.list[&lt;span class="kwrd"&gt;this&lt;/span&gt;.index];&lt;br /&gt;                &lt;span class="kwrd"&gt;if&lt;/span&gt; (image == &lt;span class="kwrd"&gt;null&lt;/span&gt;)&lt;br /&gt;                {&lt;br /&gt;                    &lt;span class="kwrd"&gt;this&lt;/span&gt;.cursor.MutableDataValue.SetNullValue();&lt;br /&gt;                }&lt;br /&gt;                &lt;span class="kwrd"&gt;else&lt;/span&gt;&lt;br /&gt;                {&lt;br /&gt;                    &lt;span class="kwrd"&gt;this&lt;/span&gt;.cursor.MutableDataValue.ValidValue = image;&lt;br /&gt;                }&lt;br /&gt;&lt;br /&gt;                &lt;span class="kwrd"&gt;return&lt;/span&gt; &lt;span class="kwrd"&gt;true&lt;/span&gt;;&lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;}&lt;/pre&gt;
&lt;p&gt;We also need to create a type identifier for the executor which is needed for registration.&lt;/p&gt;&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;namespace&lt;/span&gt; ImageFromURL&lt;br /&gt;{&lt;br /&gt;    &lt;span class="kwrd"&gt;using&lt;/span&gt; Spotfire.Dxp.Application.Extension;&lt;br /&gt;&lt;br /&gt;    &lt;span class="rem"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class="rem"&gt;/// Type identifiers for the image function exectuor.&lt;/span&gt;&lt;br /&gt;    &lt;span class="rem"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;class&lt;/span&gt; TypeIdentifiers : CustomTypeIdentifiers&lt;br /&gt;    {&lt;br /&gt;        &lt;span class="rem"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;&lt;br /&gt;        &lt;span class="rem"&gt;/// The image from URL executor.&lt;/span&gt;&lt;br /&gt;        &lt;span class="rem"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;&lt;br /&gt;        &lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;static&lt;/span&gt; CustomTypeIdentifier ImageFromURLExecutor = CreateTypeIdentifier(&lt;span class="str"&gt;&amp;quot;ImageFromURL&amp;quot;&lt;/span&gt;, &lt;span class="str"&gt;&amp;quot;Image from URL.&amp;quot;&lt;/span&gt;, &lt;span class="str"&gt;&amp;quot;Downloads an image from an URL&amp;quot;&lt;/span&gt;);&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;pre class="csharpcode"&gt;&amp;nbsp;&lt;/pre&gt;
&lt;p&gt;Now we are almost done, but we need to have a DataFunctionDefinition for this executor. To make this simple we create a tool that creates a function definition and stores it in the library so that we can use the default DataFunctions UI to execute it. This tool creates and saves the function definition in the library, it only needs to be run once then the function definition could just be import and exported to be moved to a different Spotfire server. Since we only need to run this one we make it very simple and assumes that there is a folder called “imagefromurl” in the library where we store the definition. We can always move it later once it has been stored.&lt;/p&gt;&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;namespace&lt;/span&gt; ImageFromURL&lt;br /&gt;{&lt;br /&gt;    &lt;span class="kwrd"&gt;using&lt;/span&gt; Spotfire.Dxp.Application;&lt;br /&gt;    &lt;span class="kwrd"&gt;using&lt;/span&gt; Spotfire.Dxp.Application.Extension;&lt;br /&gt;    &lt;span class="kwrd"&gt;using&lt;/span&gt; Spotfire.Dxp.Data;&lt;br /&gt;    &lt;span class="kwrd"&gt;using&lt;/span&gt; Spotfire.Dxp.Data.DataFunctions;&lt;br /&gt;    &lt;span class="kwrd"&gt;using&lt;/span&gt; Spotfire.Dxp.Framework.Library;&lt;br /&gt;&lt;br /&gt;    &lt;span class="rem"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class="rem"&gt;/// One-off tool to create and save a function definition in the library.&lt;/span&gt;&lt;br /&gt;    &lt;span class="rem"&gt;/// Assumes that there is an imagefromurl folder in the library.&lt;/span&gt;&lt;br /&gt;    &lt;span class="rem"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;class&lt;/span&gt; RegisterFunctionTool : CustomTool&amp;lt;AnalysisApplication&amp;gt;&lt;br /&gt;    {&lt;br /&gt;        &lt;span class="rem"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;&lt;br /&gt;        &lt;span class="rem"&gt;/// Initializes a new instance of the &amp;lt;see cref=&amp;quot;RegisterFunctionTool&amp;quot;/&amp;gt; class.&lt;/span&gt;&lt;br /&gt;        &lt;span class="rem"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;&lt;br /&gt;        &lt;span class="kwrd"&gt;public&lt;/span&gt; RegisterFunctionTool()&lt;br /&gt;            : &lt;span class="kwrd"&gt;base&lt;/span&gt;(&lt;span class="str"&gt;&amp;quot;Register Image from URL tool.&amp;quot;&lt;/span&gt;)&lt;br /&gt;        {               &lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        &lt;span class="rem"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;&lt;br /&gt;        &lt;span class="rem"&gt;/// Implement this method to perform the tool-specific logic.&lt;/span&gt;&lt;br /&gt;        &lt;span class="rem"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;&lt;br /&gt;        &lt;span class="rem"&gt;/// &amp;lt;param name=&amp;quot;context&amp;quot;&amp;gt;The context of the tool. Is never &amp;lt;c&amp;gt;null&amp;lt;/c&amp;gt;.&amp;lt;/param&amp;gt;&lt;/span&gt;&lt;br /&gt;        &lt;span class="rem"&gt;/// &amp;lt;remarks&amp;gt;Note that this method is only called if&lt;/span&gt;&lt;br /&gt;        &lt;span class="rem"&gt;/// &amp;lt;paramref name=&amp;quot;context&amp;quot;/&amp;gt; is not &amp;lt;c&amp;gt;null&amp;lt;/c&amp;gt; and&lt;/span&gt;&lt;br /&gt;        &lt;span class="rem"&gt;/// &amp;lt;see cref=&amp;quot;M:Spotfire.Dxp.Application.Tool`1.IsEnabled(`0)&amp;quot;/&amp;gt;&lt;/span&gt;&lt;br /&gt;        &lt;span class="rem"&gt;/// returns &amp;lt;c&amp;gt;true&amp;lt;/c&amp;gt;.&amp;lt;/remarks&amp;gt;&lt;/span&gt;&lt;br /&gt;        &lt;span class="kwrd"&gt;protected&lt;/span&gt; &lt;span class="kwrd"&gt;override&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; ExecuteCore(AnalysisApplication context)&lt;br /&gt;        {&lt;br /&gt;            &lt;span class="rem"&gt;// Create the function definition builder.&lt;/span&gt;&lt;br /&gt;            DataFunctionDefinitionBuilder builder =&lt;br /&gt;                &lt;span class="kwrd"&gt;new&lt;/span&gt; DataFunctionDefinitionBuilder(&lt;span class="str"&gt;&amp;quot;Image from URL&amp;quot;&lt;/span&gt;, TypeIdentifiers.ImageFromURLExecutor);&lt;br /&gt;&lt;br /&gt;            &lt;span class="rem"&gt;// One input argument called url which is a string column.&lt;/span&gt;&lt;br /&gt;            InputParameterBuilder inputBuilder = &lt;span class="kwrd"&gt;new&lt;/span&gt; InputParameterBuilder(&lt;span class="str"&gt;&amp;quot;url&amp;quot;&lt;/span&gt;, ParameterType.Column);&lt;br /&gt;            inputBuilder.AddAllowedDataType(DataType.String);&lt;br /&gt;            builder.InputParameters.Add(inputBuilder.Build());&lt;br /&gt;&lt;br /&gt;            &lt;span class="rem"&gt;// One output parameter called image which is a column.&lt;/span&gt;&lt;br /&gt;            OutputParameterBuilder outputBuilder = &lt;span class="kwrd"&gt;new&lt;/span&gt; OutputParameterBuilder(&lt;span class="str"&gt;&amp;quot;image&amp;quot;&lt;/span&gt;, ParameterType.Column);&lt;br /&gt;            builder.OutputParameters.Add(outputBuilder.Build());&lt;br /&gt;&lt;br /&gt;            DataFunctionDefinition functionDefinition = builder.Build();&lt;br /&gt;&lt;br /&gt;            LibraryManager lm = context.GetService&amp;lt;LibraryManager&amp;gt;();&lt;br /&gt;&lt;br /&gt;            &lt;span class="rem"&gt;// Find the folder where it should be stored.&lt;/span&gt;&lt;br /&gt;            LibraryItem li = lm.Search(&lt;span class="str"&gt;&amp;quot;imagefromurl&amp;quot;&lt;/span&gt;)[0];&lt;br /&gt;&lt;br /&gt;            &lt;span class="rem"&gt;// Save the function definition in the library.&lt;/span&gt;&lt;br /&gt;            DataFunctionDefinition saved;&lt;br /&gt;            functionDefinition.SaveAs(li, functionDefinition.FunctionName, &lt;span class="kwrd"&gt;new&lt;/span&gt; &lt;span class="kwrd"&gt;string&lt;/span&gt;[] { }, &lt;span class="kwrd"&gt;out&lt;/span&gt; saved);&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;}&lt;/pre&gt;
&lt;p&gt;To finish things, we finally add the AddIn to register the tool and function executor.&lt;/p&gt;
&lt;p&gt;namespace ImageFromURL&lt;br /&gt;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; using Spotfire.Dxp.Application.Extension; 
&lt;/p&gt;&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; /// &amp;lt;summary&amp;gt;Addin registration.&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; /// &amp;lt;/summary&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; public sealed class CustomAddIn : AddIn&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; /// &amp;lt;summary&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; /// Override this method to register data function executors with the application.&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; /// &amp;lt;/summary&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; /// &amp;lt;param name=&amp;quot;registrar&amp;quot;&amp;gt;Object responsible for registering new data function executors.&amp;lt;/param&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; protected override void RegisterDataFunctionExecutors(AddIn.DataFunctionExecutorRegistrar registrar)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; base.RegisterDataFunctionExecutors(registrar); 
&lt;/p&gt;&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; registrar.Register(TypeIdentifiers.ImageFromURLExecutor, new ImageFromURLFunctionExecutor());&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; } 
&lt;/p&gt;&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; /// &amp;lt;summary&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; /// Override this method to extend the application with new tools.&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; /// Each new tool should inherit from &amp;lt;see cref=&amp;quot;T:Spotfire.Dxp.Application.Extension.CustomTool`1&amp;quot;/&amp;gt;,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; /// and is then registered with the &amp;lt;see cref=&amp;quot;T:Spotfire.Dxp.Application.Extension.AddIn.ToolRegistrar&amp;quot;/&amp;gt;.&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; /// &amp;lt;/summary&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; /// &amp;lt;param name=&amp;quot;registrar&amp;quot;&amp;gt;Object responsible for registering new tools.&amp;lt;/param&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; protected override void RegisterTools(AddIn.ToolRegistrar registrar)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; base.RegisterTools(registrar); 
&lt;/p&gt;&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; registrar.Register(new RegisterFunctionTool());&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;}
&lt;/p&gt;&lt;p&gt;Now we can first run the “Register Image from URL Tool” from the Tools menu and then use the Tools-&amp;gt;Data Functions menu item to run the image from URL function.
&lt;/p&gt;&lt;p&gt;&lt;a href="http://spotfire.tibco.com/community/blogs/stn/image_56D51458.png"&gt;&lt;img src="http://spotfire.tibco.com/community/blogs/stn/image_thumb_35756EBC.png" title="image" style="border:0px none;display:inline;" alt="image" border="0" height="604" width="804" /&gt;&lt;/a&gt; 
&lt;/p&gt;&lt;p&gt;We then select to add columns with the result:
&lt;/p&gt;&lt;p&gt;&lt;a href="http://spotfire.tibco.com/community/blogs/stn/image_3B501255.png"&gt;&lt;img src="http://spotfire.tibco.com/community/blogs/stn/image_thumb_3A0B7976.png" title="image" style="border:0px none;display:inline;" alt="image" border="0" height="604" width="804" /&gt;&lt;/a&gt; 
&lt;/p&gt;&lt;p&gt;Finally we press OK and the images are retrieved and added to the table.
&lt;/p&gt;&lt;p&gt;&lt;a href="http://spotfire.tibco.com/community/blogs/stn/image_4DB8630A.png"&gt;&lt;img src="http://spotfire.tibco.com/community/blogs/stn/image_thumb_1E867773.png" title="image" style="border:0px none;display:inline;" alt="image" border="0" height="337" width="731" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://spotfire.tibco.com/community/aggbug.aspx?PostID=742" width="1" height="1"&gt;</content><author><name>Jonas Svensson</name><uri>http://spotfire.tibco.com/community/members/Jonas-Svensson.aspx</uri></author><category term="Developer" scheme="http://spotfire.tibco.com/community/blogs/stn/archive/tags/Developer/default.aspx" /><category term="C#" scheme="http://spotfire.tibco.com/community/blogs/stn/archive/tags/C_2300_/default.aspx" /><category term="Web Services" scheme="http://spotfire.tibco.com/community/blogs/stn/archive/tags/Web+Services/default.aspx" /><category term="Library" scheme="http://spotfire.tibco.com/community/blogs/stn/archive/tags/Library/default.aspx" /><category term="3.1" scheme="http://spotfire.tibco.com/community/blogs/stn/archive/tags/3.1/default.aspx" /><category term="Data Functions" scheme="http://spotfire.tibco.com/community/blogs/stn/archive/tags/Data+Functions/default.aspx" /><category term="Images" scheme="http://spotfire.tibco.com/community/blogs/stn/archive/tags/Images/default.aspx" /></entry><entry><title>Conditional Coloring to Highlight Top and Bottom Values</title><link rel="alternate" type="text/html" href="http://spotfire.tibco.com/community/blogs/stn/archive/2010/03/13/conditional-coloring-to-highlight-top-and-bottom-values.aspx" /><id>http://spotfire.tibco.com/community/blogs/stn/archive/2010/03/13/conditional-coloring-to-highlight-top-and-bottom-values.aspx</id><published>2010-03-13T19:15:00Z</published><updated>2010-03-13T19:15:00Z</updated><content type="html">In this week&amp;#39;s post, we will introduce you to another excited new feature of TIBCO Spotfire 3.1, Conditional Coloring. Let’s assume you have a Data Table which provides sales data for a particular store. The sales are broken up into three separate product categories: Furniture, Office Supplies, and Technology, and sales are measured across the different store locations per state. Let’s assume we want to highlight the top 5 sales result and the bottom 5 sales result for the combination of products...(&lt;a href="http://spotfire.tibco.com/community/blogs/stn/archive/2010/03/13/conditional-coloring-to-highlight-top-and-bottom-values.aspx"&gt;read more&lt;/a&gt;)&lt;img src="http://spotfire.tibco.com/community/aggbug.aspx?PostID=739" width="1" height="1"&gt;</content><author><name>Anonymous</name><uri>http://spotfire.tibco.com/community/members/Anonymous.aspx</uri></author><category term="Properties" scheme="http://spotfire.tibco.com/community/blogs/stn/archive/tags/Properties/default.aspx" /><category term="3.1" scheme="http://spotfire.tibco.com/community/blogs/stn/archive/tags/3.1/default.aspx" /><category term="Conditional Coloring" scheme="http://spotfire.tibco.com/community/blogs/stn/archive/tags/Conditional+Coloring/default.aspx" /></entry><entry><title>Introduction to Data Functions</title><link rel="alternate" type="text/html" href="http://spotfire.tibco.com/community/blogs/stn/archive/2010/03/12/introduction-to-data-functions.aspx" /><id>http://spotfire.tibco.com/community/blogs/stn/archive/2010/03/12/introduction-to-data-functions.aspx</id><published>2010-03-12T14:44:00Z</published><updated>2010-03-12T14:44:00Z</updated><content type="html">&lt;p&gt;One of the major new APIs in TIBCO Spotfire 3.1 is data functions. The data functions API is used to connect to TIBCO Spotfire Statistics Services but it can be used for much more than that. Operations that was previously performed using the data source, transformations and calculations API can now also be performed in the new API. The advantage of the data functions API is that it automatically takes care of executing operations on background threads to easily take advantage of multi-core CPUs.&lt;/p&gt;  &lt;p&gt;Conceptually&amp;nbsp; the data functions API is split into three different parts:&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;The &lt;b&gt;DataFunctionDefinition&lt;/b&gt; which describes the metadata about what a data function expects for input and the output provided by the function. These definitions can be stored and retrieved from the library and are not connected directly to any particular document. &lt;/li&gt;    &lt;li&gt;The &lt;b&gt;DataFunction&lt;/b&gt; which includes a function definition and also specifies how input and outputs should be retrieved from the document. &lt;/li&gt;    &lt;li&gt;The &lt;b&gt;DataFunctionExecutor&lt;/b&gt; which receives input from the document combined with the function definition and is responsible for calculating the output. &lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;Have a look at the DataFunctionsExample project in the SDK for code examples on how to use the API.&lt;/p&gt;  &lt;h3&gt;&lt;/h3&gt;  &lt;h3&gt;Compared to the threading framework&lt;/h3&gt;  &lt;p&gt;This component uses the threading framework but does not replace it. However for many operations such as calculations and data modification operations that one wants to execute in a background thread this is the preferred way to do it. Calculations written using data functions will automatically be executed on a background thread while still you only need to write simple sequential code.&lt;/p&gt;  &lt;h3&gt;&lt;/h3&gt;  &lt;h3&gt;Compared to calculations &lt;/h3&gt;  &lt;p&gt;In many cases it is preferable to use data functions rather than the calculations framework since you will get automatic background execution and the data functions API should be easier to use. However the calculations are more general and are needed for some scenarios.&lt;/p&gt;  &lt;h3&gt;&lt;/h3&gt;  &lt;h3&gt;Compared to transformations&lt;/h3&gt;  &lt;p&gt;Data functions are quite similar to transformations since they both takes have an API that is based on the DataRowReader enumerators, however data functions are more general since it can take multiple row readers as input and return multiple row readers as output. Simple data functions can automatically be used as transformations by using the DataFunctionTransformation. It should also be easy to expose an existing transformation by wrapping it in a data function executor.&lt;/p&gt;  &lt;h3&gt;&lt;/h3&gt;  &lt;h3&gt;Compared to data sources&lt;/h3&gt;  &lt;p&gt;Since data sources returns data row readers and an &lt;b&gt;ImportContext&lt;/b&gt; is available to the executor in the &lt;b&gt;DataFunctionInvocation&lt;/b&gt; object it is be possible to wrap a data source inside a data function executor.&lt;/p&gt;  &lt;h3&gt;DataFunctionDefinition&lt;/h3&gt;  &lt;p&gt;The data function definition object and child objects is immutable and a build class DataFunctionDefinitionBuilder is used to construct instances. To support the use-case where you want to edit a definition it is possible to create a build from a definition in which case the builder will be populated with the previous definition input.&lt;/p&gt;  &lt;p&gt;Each function definition is connected to a &lt;b&gt;TypeIdentifier &lt;/b&gt;which specifies how the function should be executed, the pre-defined type identifiers can be found in the &lt;b&gt;DataFunctionExecutorTypeIdentifiers&lt;/b&gt; static class.&lt;b&gt;&amp;nbsp;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;The design of the function definition is based on the assumption that these definitions should be applicable to large range of possible implementations such as web services, internal calculation functions or an external executable. In order to support this, a dictionary from string to string is included to allow custom information to be stored inside the definition. For S+ and R scripts the script is added to the settings with the key “script”.&lt;/p&gt;  &lt;p&gt;Here is an example that shows how to create a function definition for a simple script that performs loess smoothing:&lt;/p&gt;  &lt;pre class="csharpcode"&gt;DataFunctionDefinitionBuilder functionDefinitionBuilder =&lt;br /&gt;      &lt;span class="kwrd"&gt;new&lt;/span&gt; DataFunctionDefinitionBuilder(&lt;br /&gt;         &lt;span class="str"&gt;&amp;quot;Loess&amp;quot;&lt;/span&gt;, &lt;br /&gt;         DataFunctionExecutorTypeIdentifiers.SPlusScriptExecutor);&lt;br /&gt;&lt;br /&gt;InputParameterBuilder yBuilder =&lt;br /&gt;      &lt;span class="kwrd"&gt;new&lt;/span&gt; InputParameterBuilder(&lt;span class="str"&gt;&amp;quot;y&amp;quot;&lt;/span&gt;, ParameterType.Column);&lt;br /&gt;yBuilder.AddAllowedDataType(DataType.Integer);&lt;br /&gt;yBuilder.AddAllowedDataType(DataType.Real);&lt;br /&gt;&lt;br /&gt;InputParameter y = yBuilder.Build();&lt;br /&gt;&lt;br /&gt;functionDefinitionBuilder.InputParameters.Add(y);&lt;br /&gt;&lt;br /&gt;InputParameterBuilder xBuilder =&lt;br /&gt;      &lt;span class="kwrd"&gt;new&lt;/span&gt; InputParameterBuilder(&lt;span class="str"&gt;&amp;quot;x&amp;quot;&lt;/span&gt;, ParameterType.Column);&lt;br /&gt;&lt;br /&gt;InputParameter x = xBuilder.Build();&lt;br /&gt;&lt;br /&gt;functionDefinitionBuilder.InputParameters.Add(x);&lt;br /&gt;&lt;br /&gt;OutputParameterBuilder outputBuilder = &lt;span class="kwrd"&gt;new&lt;/span&gt; OutputParameterBuilder(&lt;span class="str"&gt;&amp;quot;smooth&amp;quot;&lt;/span&gt;, ParameterType.Table);&lt;br /&gt;&lt;br /&gt;OutputParameter output = outputBuilder.Build();&lt;br /&gt;&lt;br /&gt;functionDefinitionBuilder.OutputParameters.Add(output);&lt;br /&gt;&lt;br /&gt;DataTable inputTable = context.Data.DataTableReference;&lt;br /&gt;&lt;br /&gt;StringBuilder scriptBuilder = &lt;span class="kwrd"&gt;new&lt;/span&gt; StringBuilder();&lt;br /&gt;&lt;br /&gt;scriptBuilder.AppendLine(&lt;span class="str"&gt;&amp;quot;predicted &amp;lt;- predict(loess(y ~ x, data.frame(x=x, y=y)), data.frame(x=x))&amp;quot;&lt;/span&gt;);&lt;br /&gt;scriptBuilder.AppendLine(&lt;span class="str"&gt;&amp;quot;smooth &amp;lt;- data.frame(loess.x = x, loess.y = predicted)&amp;quot;&lt;/span&gt;);&lt;br /&gt;&lt;br /&gt;functionDefinitionBuilder.Settings.Add(&lt;span class="str"&gt;&amp;quot;script&amp;quot;&lt;/span&gt;, scriptBuilder.ToString());&lt;br /&gt;&lt;br /&gt;DataFunctionDefinition functionDefinition = functionDefinitionBuilder.Build();&lt;/pre&gt;

&lt;h3&gt;DataFunction&lt;/h3&gt;

&lt;p&gt;The purpose of the data function is to allow functions to get input from the document, do some processing on a background thread and then apply the results to the document. &lt;/p&gt;

&lt;p&gt;The inputs to a data function are expressions in the expression language, it multiple columns need to be sent then just separate the expression with commas as you would in the custom expressions dialog. It is possible to specify on which subset the expressions should be calculated on using any combination of markings and filtering. The Active Filtering property also allows you to make the data function use the filtering on the current page. If you specify multiple markings and filtering then the intersection will be used.&lt;/p&gt;

&lt;p&gt;For the output the following operations are allowed:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Adding a new table. &lt;/li&gt;

  &lt;li&gt;Adding columns to a table, both as a join and directly added to the table. &lt;/li&gt;

  &lt;li&gt;Adding rows to a table. &lt;/li&gt;

  &lt;li&gt;Replacing all the data in a table. &lt;/li&gt;

  &lt;li&gt;Setting a document property. &lt;/li&gt;

  &lt;li&gt;Setting a column property. &lt;/li&gt;

  &lt;li&gt;Setting a table property. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The following example shows how to create a data function from the expressions in a scatter plot and also update the scatter plot when the calculation has finished:&lt;/p&gt;

&lt;pre class="csharpcode"&gt;Document document = context.Context.GetService&amp;lt;Document&amp;gt;();&lt;br /&gt;&lt;br /&gt;DataSelection[] selections = &lt;span class="kwrd"&gt;new&lt;/span&gt; DataSelection[context.Data.Filterings.Count];&lt;br /&gt;&lt;span class="kwrd"&gt;for&lt;/span&gt; (&lt;span class="kwrd"&gt;int&lt;/span&gt; i = 0; i &amp;lt; context.Data.Filterings.Count; ++i)&lt;br /&gt;{&lt;br /&gt;    selections[i] = context.Data.Filterings[i];&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;span class="kwrd"&gt;string&lt;/span&gt; xExpression = &lt;span class="kwrd"&gt;null&lt;/span&gt;;&lt;br /&gt;&lt;span class="kwrd"&gt;string&lt;/span&gt; yExpression = &lt;span class="kwrd"&gt;null&lt;/span&gt;;&lt;br /&gt;&lt;span class="kwrd"&gt;try&lt;/span&gt;&lt;br /&gt;{&lt;br /&gt;    ColumnExpression xColumnExpression = ColumnExpression.Create(context.XAxis.Expression);&lt;br /&gt;    ColumnExpression yColumnExpression = ColumnExpression.Create(context.YAxis.Expression);&lt;br /&gt;&lt;br /&gt;    &lt;span class="kwrd"&gt;if&lt;/span&gt; (!xColumnExpression.IsValid &amp;amp;&amp;amp; !yColumnExpression.IsValid)&lt;br /&gt;    {&lt;br /&gt;        &lt;span class="kwrd"&gt;return&lt;/span&gt;;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    xColumnExpression.QualifyNames(inputTable.Name);&lt;br /&gt;    yColumnExpression.QualifyNames(inputTable.Name);&lt;br /&gt;&lt;br /&gt;    xExpression = xColumnExpression.Expression;&lt;br /&gt;    yExpression = yColumnExpression.Expression;&lt;br /&gt;}&lt;br /&gt;&lt;span class="kwrd"&gt;catch&lt;/span&gt; (ExpressionException)&lt;br /&gt;{&lt;br /&gt;    &lt;span class="kwrd"&gt;return&lt;/span&gt;;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;DataFunction function = document.Data.DataFunctions.AddNew(context.Title, functionDefinition);&lt;br /&gt;&lt;br /&gt;function.Inputs.SetInput(x, xExpression, &lt;span class="kwrd"&gt;true&lt;/span&gt;, selections);&lt;br /&gt;function.Inputs.SetInput(y, yExpression, &lt;span class="kwrd"&gt;true&lt;/span&gt;, selections);&lt;br /&gt;&lt;br /&gt;&lt;span class="rem"&gt;// Add columns to the input table.&lt;/span&gt;&lt;br /&gt;ColumnsOutputBuilder addColumnsBuilder =&lt;br /&gt;    &lt;span class="kwrd"&gt;new&lt;/span&gt; ColumnsOutputBuilder(inputTable);&lt;br /&gt;addColumnsBuilder.InputToAdaptSelectionsFrom = x;&lt;br /&gt;&lt;br /&gt;function.Outputs.SetColumnsOutput(output, addColumnsBuilder);&lt;br /&gt;&lt;br /&gt;ApplicationThread applicationThread = context.Context.GetService&amp;lt;ApplicationThread&amp;gt;();&lt;br /&gt;&lt;br /&gt;function.Execute(&lt;br /&gt;    &lt;span class="kwrd"&gt;delegate&lt;/span&gt;&lt;br /&gt;    {&lt;br /&gt;        applicationThread.InvokeAsynchronously(&lt;br /&gt;            &lt;span class="kwrd"&gt;delegate&lt;/span&gt;&lt;br /&gt;            {&lt;br /&gt;                context.Transactions.ExecuteTransaction(&lt;br /&gt;                    &lt;span class="kwrd"&gt;delegate&lt;/span&gt;&lt;br /&gt;                    {&lt;br /&gt;                        DataColumn xColumn = inputTable.Columns[&lt;span class="str"&gt;&amp;quot;loess.x&amp;quot;&lt;/span&gt;];&lt;br /&gt;                        DataColumn yColumn = inputTable.Columns[&lt;span class="str"&gt;&amp;quot;loess.y&amp;quot;&lt;/span&gt;];&lt;br /&gt;&lt;br /&gt;                        ColumnValuesLine fittingModel =&lt;br /&gt;                            context.FittingModels.AddColumnValuesLine(&lt;br /&gt;                                inputTable,&lt;br /&gt;                                xColumn,&lt;br /&gt;                                yColumn);&lt;br /&gt;&lt;br /&gt;                        fittingModel.SortColumnReference = xColumn;&lt;br /&gt;                        fittingModel.Enabled = &lt;span class="kwrd"&gt;true&lt;/span&gt;;&lt;br /&gt;                    });&lt;br /&gt;&lt;br /&gt;                &lt;span class="rem"&gt;// Start updating automatically&lt;/span&gt;&lt;br /&gt;                function.UpdateBehavior = DataFunctionUpdateBehavior.Automatic;&lt;br /&gt;            });&lt;br /&gt;    });&lt;/pre&gt;

&lt;h3&gt;DataFunctionExecutor&lt;/h3&gt;

&lt;p&gt;The data functions executor is the only extension point in the data functions API and one needs to inherit from the &lt;b&gt;CustomDataFunctionExecutor &lt;/b&gt;class. This class has a very simple API with only one method that receives a &lt;b&gt;DataFunctionInvocation &lt;/b&gt;object which contains a the named input parameters and the function is expected to set the output parameters with the calculated result. &lt;/p&gt;

&lt;p&gt;The following example shows a subset of simple function executor that performs a simple K-means clustering algorithm.&lt;/p&gt;

&lt;pre class="csharpcode"&gt;    &lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;sealed&lt;/span&gt; &lt;span class="kwrd"&gt;class&lt;/span&gt; KMeansDataFunctionExecutor : CustomDataFunctionExecutor&lt;br /&gt;    {&lt;br /&gt;        &lt;span class="rem"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;&lt;br /&gt;        &lt;span class="rem"&gt;/// Execute a function invocation. The executor is expected to add the output results to the invocation&lt;/span&gt;&lt;br /&gt;        &lt;span class="rem"&gt;/// object.&lt;/span&gt;&lt;br /&gt;        &lt;span class="rem"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;&lt;br /&gt;        &lt;span class="rem"&gt;/// &amp;lt;param name=&amp;quot;invocation&amp;quot;&amp;gt;The function invocation.&amp;lt;/param&amp;gt;&lt;/span&gt;&lt;br /&gt;        &lt;span class="rem"&gt;/// &amp;lt;returns&amp;gt;&lt;/span&gt;&lt;br /&gt;        &lt;span class="rem"&gt;/// The prompt models to return, the implementor is expected to use the yield return pattern.&lt;/span&gt;&lt;br /&gt;        &lt;span class="rem"&gt;/// &amp;lt;/returns&amp;gt;&lt;/span&gt;&lt;br /&gt;        &lt;span class="kwrd"&gt;protected&lt;/span&gt; &lt;span class="kwrd"&gt;override&lt;/span&gt; IEnumerable&amp;lt;&lt;span class="kwrd"&gt;object&lt;/span&gt;&amp;gt; ExecuteFunctionCore(DataFunctionInvocation invocation)&lt;br /&gt;        {&lt;br /&gt;            &lt;span class="rem"&gt;// This function only expects function definitions with two parameters:&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;//   Values -&amp;gt; Table of double values.&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;//   NumberOfClusters -&amp;gt; Integer value.&lt;/span&gt;&lt;br /&gt;            DataRowReader valuesReader;&lt;br /&gt;            &lt;span class="kwrd"&gt;if&lt;/span&gt; (!invocation.TryGetInput(&lt;span class="str"&gt;&amp;quot;Values&amp;quot;&lt;/span&gt;, &lt;span class="kwrd"&gt;out&lt;/span&gt; valuesReader))&lt;br /&gt;            {&lt;br /&gt;                &lt;span class="kwrd"&gt;throw&lt;/span&gt; &lt;span class="kwrd"&gt;new&lt;/span&gt; InvalidOperationException(&lt;span class="str"&gt;&amp;quot;Could not find values in input.&amp;quot;&lt;/span&gt;);&lt;br /&gt;            }&lt;br /&gt;&lt;br /&gt;            &lt;span class="kwrd"&gt;int&lt;/span&gt; numberOfClusters = GetNumberOfClusters(invocation);&lt;br /&gt;&lt;br /&gt;            &lt;span class="kwrd"&gt;if&lt;/span&gt; (numberOfClusters &amp;lt;= 0)&lt;br /&gt;            {&lt;br /&gt;                &lt;span class="kwrd"&gt;throw&lt;/span&gt; &lt;span class="kwrd"&gt;new&lt;/span&gt; InvalidOperationException(&lt;span class="str"&gt;&amp;quot;Too few number of clusters.&amp;quot;&lt;/span&gt;);&lt;br /&gt;            }&lt;br /&gt;&lt;br /&gt;            &lt;span class="kwrd"&gt;string&lt;/span&gt; resultName = GetResultName(invocation);&lt;br /&gt;&lt;br /&gt;            &lt;span class="rem"&gt;// Get cursors for the input as double values.&lt;/span&gt;&lt;br /&gt;            List&amp;lt;DataValueCursor&amp;lt;&lt;span class="kwrd"&gt;double&lt;/span&gt;&amp;gt;&amp;gt; numericCursors = &lt;span class="kwrd"&gt;new&lt;/span&gt; List&amp;lt;DataValueCursor&amp;lt;&lt;span class="kwrd"&gt;double&lt;/span&gt;&amp;gt;&amp;gt;();&lt;br /&gt;            &lt;span class="kwrd"&gt;foreach&lt;/span&gt; (DataRowReaderColumn column &lt;span class="kwrd"&gt;in&lt;/span&gt; valuesReader.Columns)&lt;br /&gt;            {&lt;br /&gt;                DataValueCursor&amp;lt;&lt;span class="kwrd"&gt;double&lt;/span&gt;&amp;gt; numericCursor = column.Cursor &lt;span class="kwrd"&gt;as&lt;/span&gt; DataValueCursor&amp;lt;&lt;span class="kwrd"&gt;double&lt;/span&gt;&amp;gt;;&lt;br /&gt;                &lt;span class="kwrd"&gt;if&lt;/span&gt; (numericCursor == &lt;span class="kwrd"&gt;null&lt;/span&gt;)&lt;br /&gt;                {&lt;br /&gt;                    &lt;span class="kwrd"&gt;throw&lt;/span&gt; &lt;span class="kwrd"&gt;new&lt;/span&gt; InvalidOperationException(&lt;span class="str"&gt;&amp;quot;Non double cursor sent as input.&amp;quot;&lt;/span&gt;);&lt;br /&gt;                }&lt;br /&gt;&lt;br /&gt;                numericCursors.Add(numericCursor);&lt;br /&gt;            }&lt;br /&gt;&lt;br /&gt;            &lt;span class="rem"&gt;// An array that contains the values for a row.&lt;/span&gt;&lt;br /&gt;            &lt;span class="kwrd"&gt;double&lt;/span&gt;[] row = &lt;span class="kwrd"&gt;new&lt;/span&gt; &lt;span class="kwrd"&gt;double&lt;/span&gt;[numericCursors.Count];&lt;br /&gt;&lt;br /&gt;            &lt;span class="rem"&gt;// We keep it simple and select the first numberOfClusters rows to be the initial&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;// center for the clusters.&lt;/span&gt;&lt;br /&gt;            Cluster[] clusters = &lt;span class="kwrd"&gt;new&lt;/span&gt; Cluster[numberOfClusters];&lt;br /&gt;            valuesReader.Reset();&lt;br /&gt;            &lt;span class="kwrd"&gt;for&lt;/span&gt; (&lt;span class="kwrd"&gt;int&lt;/span&gt; i = 0; i &amp;lt; numberOfClusters; ++i)&lt;br /&gt;            {&lt;br /&gt;                &lt;span class="kwrd"&gt;if&lt;/span&gt; (!valuesReader.MoveNext())&lt;br /&gt;                {&lt;br /&gt;                    &lt;span class="kwrd"&gt;throw&lt;/span&gt; &lt;span class="kwrd"&gt;new&lt;/span&gt; InvalidOperationException(&lt;span class="str"&gt;&amp;quot;Number of clusters is larger than the available rows.&amp;quot;&lt;/span&gt;);&lt;br /&gt;                }&lt;br /&gt;&lt;br /&gt;                FillRow(numericCursors, row);&lt;br /&gt;&lt;br /&gt;                &lt;span class="rem"&gt;// Initialize the row with the current center.&lt;/span&gt;&lt;br /&gt;                clusters[i].Initialize(row);&lt;br /&gt;            }&lt;br /&gt;&lt;br /&gt;            PageableListSettings settings = &lt;span class="kwrd"&gt;new&lt;/span&gt; PageableListSettings();&lt;br /&gt;            settings.CanReplaceValue = &lt;span class="kwrd"&gt;true&lt;/span&gt;;&lt;br /&gt;            PageableList&amp;lt;&lt;span class="kwrd"&gt;int&lt;/span&gt;&amp;gt; rowClusterIndex = &lt;span class="kwrd"&gt;new&lt;/span&gt; PageableList&amp;lt;&lt;span class="kwrd"&gt;int&lt;/span&gt;&amp;gt;(settings);&lt;br /&gt;&lt;br /&gt;            &lt;span class="rem"&gt;// Calculate the initial cluster for each point.&lt;/span&gt;&lt;br /&gt;            valuesReader.Reset();&lt;br /&gt;            &lt;span class="kwrd"&gt;while&lt;/span&gt; (valuesReader.MoveNext())&lt;br /&gt;            {&lt;br /&gt;                FillRow(numericCursors, row);&lt;br /&gt;&lt;br /&gt;                &lt;span class="rem"&gt;// Calculate the distance from all clusters and find&lt;/span&gt;&lt;br /&gt;                &lt;span class="rem"&gt;// the nearest cluster.&lt;/span&gt;&lt;br /&gt;                &lt;span class="kwrd"&gt;int&lt;/span&gt; nearestCluster = FindNearestCluster(row, clusters);&lt;br /&gt;&lt;br /&gt;                &lt;span class="rem"&gt;// Store the resulting index and add the row as a member of the nearest cluster.&lt;/span&gt;&lt;br /&gt;                rowClusterIndex.Add(nearestCluster);&lt;br /&gt;                clusters[nearestCluster].AddMember(row);&lt;br /&gt;            }&lt;br /&gt;&lt;br /&gt;            &lt;span class="rem"&gt;// Iterate until the maximum number of iterations is each or if no change&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;// has been performed.&lt;/span&gt;&lt;br /&gt;            &lt;span class="kwrd"&gt;int&lt;/span&gt; maxIterations = 100;&lt;br /&gt;            &lt;span class="kwrd"&gt;int&lt;/span&gt; iterationCount = 0;&lt;br /&gt;            &lt;span class="kwrd"&gt;do&lt;/span&gt; &lt;br /&gt;            {&lt;br /&gt;                &lt;span class="rem"&gt;// Calculate the centroids for all clusters.&lt;/span&gt;&lt;br /&gt;                &lt;span class="kwrd"&gt;for&lt;/span&gt; (&lt;span class="kwrd"&gt;int&lt;/span&gt; i = 0; i &amp;lt; numberOfClusters; ++i)&lt;br /&gt;                {&lt;br /&gt;                    clusters[i].CalculateCentroid();&lt;br /&gt;                }&lt;br /&gt;&lt;br /&gt;                &lt;span class="rem"&gt;// Calculate the new nearest cluster for each point.&lt;/span&gt;&lt;br /&gt;                valuesReader.Reset();&lt;br /&gt;                &lt;span class="kwrd"&gt;bool&lt;/span&gt; hasChanged = &lt;span class="kwrd"&gt;false&lt;/span&gt;;&lt;br /&gt;                &lt;span class="kwrd"&gt;int&lt;/span&gt; rowIndex = 0;&lt;br /&gt;                &lt;span class="kwrd"&gt;while&lt;/span&gt; (valuesReader.MoveNext())&lt;br /&gt;                {&lt;br /&gt;                    FillRow(numericCursors, row);&lt;br /&gt;&lt;br /&gt;                    &lt;span class="rem"&gt;// Calculate the distance from all clusters and find&lt;/span&gt;&lt;br /&gt;                    &lt;span class="rem"&gt;// the nearest cluster.&lt;/span&gt;&lt;br /&gt;                    &lt;span class="kwrd"&gt;int&lt;/span&gt; nearestCluster = FindNearestCluster(row, clusters);&lt;br /&gt;&lt;br /&gt;                    &lt;span class="kwrd"&gt;int&lt;/span&gt; oldClusterIndex = rowClusterIndex[rowIndex];&lt;br /&gt;&lt;br /&gt;                    &lt;span class="rem"&gt;// This point has changed cluster.&lt;/span&gt;&lt;br /&gt;                    &lt;span class="kwrd"&gt;if&lt;/span&gt; (nearestCluster != oldClusterIndex)&lt;br /&gt;                    {&lt;br /&gt;                        hasChanged = &lt;span class="kwrd"&gt;true&lt;/span&gt;;&lt;br /&gt;                        rowClusterIndex[rowIndex] = nearestCluster;&lt;br /&gt;                        clusters[oldClusterIndex].RemoveMember(row);&lt;br /&gt;                        clusters[nearestCluster].AddMember(row);&lt;br /&gt;                    }&lt;br /&gt;&lt;br /&gt;                    rowIndex++;&lt;br /&gt;                }&lt;br /&gt;&lt;br /&gt;                &lt;span class="kwrd"&gt;if&lt;/span&gt; (!hasChanged)&lt;br /&gt;                {&lt;br /&gt;                    &lt;span class="kwrd"&gt;break&lt;/span&gt;;&lt;br /&gt;                }&lt;br /&gt;&lt;br /&gt;                iterationCount++;&lt;br /&gt;            } &lt;br /&gt;            &lt;span class="kwrd"&gt;while&lt;/span&gt; (iterationCount &amp;lt; maxIterations);&lt;br /&gt;&lt;br /&gt;            &lt;span class="rem"&gt;// Assign the output result.&lt;/span&gt;&lt;br /&gt;            invocation.SetOutput(&lt;span class="str"&gt;&amp;quot;K-Means&amp;quot;&lt;/span&gt;, &lt;span class="kwrd"&gt;new&lt;/span&gt; PageableListDataRowReader(rowClusterIndex, resultName));&lt;br /&gt;&lt;br /&gt;            &lt;span class="rem"&gt;// Done&lt;/span&gt;&lt;br /&gt;            &lt;span class="kwrd"&gt;yield&lt;/span&gt; &lt;span class="kwrd"&gt;break&lt;/span&gt;;&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        &lt;span class="rem"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;&lt;br /&gt;        &lt;span class="rem"&gt;/// Find the cluster that is nearest the row.&lt;/span&gt;&lt;br /&gt;        &lt;span class="rem"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;&lt;br /&gt;        &lt;span class="rem"&gt;/// &amp;lt;param name=&amp;quot;row&amp;quot;&amp;gt;The row.&amp;lt;/param&amp;gt;&lt;/span&gt;&lt;br /&gt;        &lt;span class="rem"&gt;/// &amp;lt;param name=&amp;quot;clusters&amp;quot;&amp;gt;The clusters.&amp;lt;/param&amp;gt;&lt;/span&gt;&lt;br /&gt;        &lt;span class="rem"&gt;/// &amp;lt;returns&amp;gt;The index of the nearest cluster.&amp;lt;/returns&amp;gt;&lt;/span&gt;&lt;br /&gt;        &lt;span class="kwrd"&gt;private&lt;/span&gt; &lt;span class="kwrd"&gt;static&lt;/span&gt; &lt;span class="kwrd"&gt;int&lt;/span&gt; FindNearestCluster(&lt;span class="kwrd"&gt;double&lt;/span&gt;[] row, Cluster[] clusters)&lt;br /&gt;        {&lt;br /&gt;            &lt;span class="kwrd"&gt;double&lt;/span&gt; minDistance = &lt;span class="kwrd"&gt;double&lt;/span&gt;.MaxValue;&lt;br /&gt;            &lt;span class="kwrd"&gt;int&lt;/span&gt; nearestCluster = -1;&lt;br /&gt;            &lt;span class="kwrd"&gt;for&lt;/span&gt; (&lt;span class="kwrd"&gt;int&lt;/span&gt; i = 0; i &amp;lt; clusters.Length; ++i)&lt;br /&gt;            {&lt;br /&gt;                &lt;span class="kwrd"&gt;double&lt;/span&gt; distance = clusters[i].Distance(row);&lt;br /&gt;                &lt;span class="kwrd"&gt;if&lt;/span&gt; (distance &amp;lt; minDistance)&lt;br /&gt;                {&lt;br /&gt;                    minDistance = distance;&lt;br /&gt;                    nearestCluster = i;&lt;br /&gt;                }&lt;br /&gt;            }&lt;br /&gt;&lt;br /&gt;            &lt;span class="kwrd"&gt;return&lt;/span&gt; nearestCluster;&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        &lt;span class="rem"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;&lt;br /&gt;        &lt;span class="rem"&gt;/// Fill the row array with values for the current row.&lt;/span&gt;&lt;br /&gt;        &lt;span class="rem"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;&lt;br /&gt;        &lt;span class="rem"&gt;/// &amp;lt;param name=&amp;quot;numericCursors&amp;quot;&amp;gt;The numeric cursors.&amp;lt;/param&amp;gt;&lt;/span&gt;&lt;br /&gt;        &lt;span class="rem"&gt;/// &amp;lt;param name=&amp;quot;row&amp;quot;&amp;gt;The row array to full.&amp;lt;/param&amp;gt;&lt;/span&gt;&lt;br /&gt;        &lt;span class="kwrd"&gt;private&lt;/span&gt; &lt;span class="kwrd"&gt;static&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; FillRow(List&amp;lt;DataValueCursor&amp;lt;&lt;span class="kwrd"&gt;double&lt;/span&gt;&amp;gt;&amp;gt; numericCursors, &lt;span class="kwrd"&gt;double&lt;/span&gt;[] row)&lt;br /&gt;        {&lt;br /&gt;            &lt;span class="kwrd"&gt;for&lt;/span&gt; (&lt;span class="kwrd"&gt;int&lt;/span&gt; j = 0; j &amp;lt; numericCursors.Count; ++j)&lt;br /&gt;            {&lt;br /&gt;                &lt;span class="kwrd"&gt;if&lt;/span&gt; (!numericCursors[j].IsCurrentValueValid)&lt;br /&gt;                {&lt;br /&gt;                    &lt;span class="kwrd"&gt;throw&lt;/span&gt; &lt;span class="kwrd"&gt;new&lt;/span&gt; InvalidOperationException(&lt;span class="str"&gt;&amp;quot;Invalid values are not allowed.&amp;quot;&lt;/span&gt;);&lt;br /&gt;                }&lt;br /&gt;&lt;br /&gt;                row[j] = numericCursors[j].CurrentValue;&lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        &lt;span class="rem"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;&lt;br /&gt;        &lt;span class="rem"&gt;/// Get result name.&lt;/span&gt;&lt;br /&gt;        &lt;span class="rem"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;&lt;br /&gt;        &lt;span class="rem"&gt;/// &amp;lt;param name=&amp;quot;invocation&amp;quot;&amp;gt;The invocation.&amp;lt;/param&amp;gt;&lt;/span&gt;&lt;br /&gt;        &lt;span class="rem"&gt;/// &amp;lt;returns&amp;gt;Get the result name.&amp;lt;/returns&amp;gt;&lt;/span&gt;&lt;br /&gt;        &lt;span class="kwrd"&gt;private&lt;/span&gt; &lt;span class="kwrd"&gt;static&lt;/span&gt; &lt;span class="kwrd"&gt;string&lt;/span&gt; GetResultName(DataFunctionInvocation invocation)&lt;br /&gt;        {&lt;br /&gt;            DataRowReader resultNameReader;&lt;br /&gt;            &lt;span class="kwrd"&gt;if&lt;/span&gt; (!invocation.TryGetInput(&lt;span class="str"&gt;&amp;quot;ResultName&amp;quot;&lt;/span&gt;, &lt;span class="kwrd"&gt;out&lt;/span&gt; resultNameReader))&lt;br /&gt;            {&lt;br /&gt;                &lt;span class="kwrd"&gt;throw&lt;/span&gt; &lt;span class="kwrd"&gt;new&lt;/span&gt; InvalidOperationException(&lt;span class="str"&gt;&amp;quot;Could not find the result name.&amp;quot;&lt;/span&gt;);&lt;br /&gt;            }&lt;br /&gt;&lt;br /&gt;            &lt;span class="kwrd"&gt;if&lt;/span&gt; (resultNameReader.Columns.Count != 1)&lt;br /&gt;            {&lt;br /&gt;                &lt;span class="kwrd"&gt;throw&lt;/span&gt; &lt;span class="kwrd"&gt;new&lt;/span&gt; InvalidOperationException(&lt;span class="str"&gt;&amp;quot;The result name reader contains more than one argument.&amp;quot;&lt;/span&gt;);&lt;br /&gt;            }&lt;br /&gt;&lt;br /&gt;            DataRowReaderColumn resultNameColumn = resultNameReader.Columns[0];&lt;br /&gt;            DataValueCursor&amp;lt;&lt;span class="kwrd"&gt;string&lt;/span&gt;&amp;gt; resultNameCursor = resultNameColumn.Cursor &lt;span class="kwrd"&gt;as&lt;/span&gt; DataValueCursor&amp;lt;&lt;span class="kwrd"&gt;string&lt;/span&gt;&amp;gt;;&lt;br /&gt;&lt;br /&gt;            &lt;span class="kwrd"&gt;if&lt;/span&gt; (resultNameColumn == &lt;span class="kwrd"&gt;null&lt;/span&gt;)&lt;br /&gt;            {&lt;br /&gt;                &lt;span class="kwrd"&gt;throw&lt;/span&gt; &lt;span class="kwrd"&gt;new&lt;/span&gt; InvalidOperationException(&lt;span class="str"&gt;&amp;quot;The result name input was not an string.&amp;quot;&lt;/span&gt;);&lt;br /&gt;            }&lt;br /&gt;&lt;br /&gt;            &lt;span class="kwrd"&gt;if&lt;/span&gt; (!resultNameReader.MoveNext())&lt;br /&gt;            {&lt;br /&gt;                &lt;span class="kwrd"&gt;throw&lt;/span&gt; &lt;span class="kwrd"&gt;new&lt;/span&gt; InvalidOperationException(&lt;span class="str"&gt;&amp;quot;No rows in result name input.&amp;quot;&lt;/span&gt;);&lt;br /&gt;            }&lt;br /&gt;&lt;br /&gt;            &lt;span class="kwrd"&gt;if&lt;/span&gt; (!resultNameCursor.IsCurrentValueValid)&lt;br /&gt;            {&lt;br /&gt;                &lt;span class="kwrd"&gt;throw&lt;/span&gt; &lt;span class="kwrd"&gt;new&lt;/span&gt; InvalidOperationException(&lt;span class="str"&gt;&amp;quot;The result name input was not valid.&amp;quot;&lt;/span&gt;);&lt;br /&gt;            }&lt;br /&gt;&lt;br /&gt;            &lt;span class="kwrd"&gt;return&lt;/span&gt; resultNameCursor.CurrentValue;&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        &lt;span class="rem"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;&lt;br /&gt;        &lt;span class="rem"&gt;/// Get the number of clusters sent in the invocation.&lt;/span&gt;&lt;br /&gt;        &lt;span class="rem"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;&lt;br /&gt;        &lt;span class="rem"&gt;/// &amp;lt;param name=&amp;quot;invocation&amp;quot;&amp;gt;The invocation.&amp;lt;/param&amp;gt;&lt;/span&gt;&lt;br /&gt;        &lt;span class="rem"&gt;/// &amp;lt;returns&amp;gt;The number of clusters.&amp;lt;/returns&amp;gt;&lt;/span&gt;&lt;br /&gt;        &lt;span class="kwrd"&gt;private&lt;/span&gt; &lt;span class="kwrd"&gt;static&lt;/span&gt; &lt;span class="kwrd"&gt;int&lt;/span&gt; GetNumberOfClusters(DataFunctionInvocation invocation)&lt;br /&gt;        {&lt;br /&gt;            DataRowReader numberOfClustersReader;&lt;br /&gt;            &lt;span class="kwrd"&gt;if&lt;/span&gt; (!invocation.TryGetInput(&lt;span class="str"&gt;&amp;quot;NumberOfClusters&amp;quot;&lt;/span&gt;, &lt;span class="kwrd"&gt;out&lt;/span&gt; numberOfClustersReader))&lt;br /&gt;            {&lt;br /&gt;                &lt;span class="kwrd"&gt;throw&lt;/span&gt; &lt;span class="kwrd"&gt;new&lt;/span&gt; InvalidOperationException(&lt;span class="str"&gt;&amp;quot;Could not find number of clusters in input.&amp;quot;&lt;/span&gt;);&lt;br /&gt;            }&lt;br /&gt;&lt;br /&gt;            &lt;span class="kwrd"&gt;if&lt;/span&gt; (numberOfClustersReader.Columns.Count != 1)&lt;br /&gt;            {&lt;br /&gt;                &lt;span class="kwrd"&gt;throw&lt;/span&gt; &lt;span class="kwrd"&gt;new&lt;/span&gt; InvalidOperationException(&lt;span class="str"&gt;&amp;quot;The number of clusters reader contains more than one argument.&amp;quot;&lt;/span&gt;);&lt;br /&gt;            }&lt;br /&gt;&lt;br /&gt;            DataRowReaderColumn numClustersColumn = numberOfClustersReader.Columns[0];&lt;br /&gt;            DataValueCursor&amp;lt;&lt;span class="kwrd"&gt;int&lt;/span&gt;&amp;gt; numClustersCursor = numClustersColumn.Cursor &lt;span class="kwrd"&gt;as&lt;/span&gt; DataValueCursor&amp;lt;&lt;span class="kwrd"&gt;int&lt;/span&gt;&amp;gt;;&lt;br /&gt;&lt;br /&gt;            &lt;span class="kwrd"&gt;if&lt;/span&gt; (numClustersColumn == &lt;span class="kwrd"&gt;null&lt;/span&gt;)&lt;br /&gt;            {&lt;br /&gt;                &lt;span class="kwrd"&gt;throw&lt;/span&gt; &lt;span class="kwrd"&gt;new&lt;/span&gt; InvalidOperationException(&lt;span class="str"&gt;&amp;quot;The number of clusters input was not an integer.&amp;quot;&lt;/span&gt;);&lt;br /&gt;            }&lt;br /&gt;&lt;br /&gt;            &lt;span class="kwrd"&gt;if&lt;/span&gt; (!numberOfClustersReader.MoveNext())&lt;br /&gt;            {&lt;br /&gt;                &lt;span class="kwrd"&gt;throw&lt;/span&gt; &lt;span class="kwrd"&gt;new&lt;/span&gt; InvalidOperationException(&lt;span class="str"&gt;&amp;quot;No rows in number of clusters input.&amp;quot;&lt;/span&gt;);&lt;br /&gt;            }&lt;br /&gt;&lt;br /&gt;            &lt;span class="kwrd"&gt;if&lt;/span&gt; (!numClustersCursor.IsCurrentValueValid)&lt;br /&gt;            {&lt;br /&gt;                &lt;span class="kwrd"&gt;throw&lt;/span&gt; &lt;span class="kwrd"&gt;new&lt;/span&gt; InvalidOperationException(&lt;span class="str"&gt;&amp;quot;The number of clusters input was not valid.&amp;quot;&lt;/span&gt;);&lt;br /&gt;            }&lt;br /&gt;&lt;br /&gt;            &lt;span class="kwrd"&gt;int&lt;/span&gt; numberOfClusters = numClustersCursor.CurrentValue;&lt;br /&gt;            &lt;span class="kwrd"&gt;return&lt;/span&gt; numberOfClusters;&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        &lt;span class="rem"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;&lt;br /&gt;        &lt;span class="rem"&gt;/// A cluster.&lt;/span&gt;&lt;br /&gt;        &lt;span class="rem"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;&lt;br /&gt;        &lt;span class="kwrd"&gt;private&lt;/span&gt; &lt;span class="kwrd"&gt;struct&lt;/span&gt; Cluster&lt;br /&gt;        {&lt;br /&gt;            &lt;span class="rem"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// The location of the clusters.&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class="kwrd"&gt;private&lt;/span&gt; &lt;span class="kwrd"&gt;double&lt;/span&gt;[] location;&lt;br /&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// The sum of the parts of the points in the clusters.&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class="kwrd"&gt;private&lt;/span&gt; &lt;span class="kwrd"&gt;double&lt;/span&gt;[] sumOfParts;&lt;br /&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// The number of observations.&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class="kwrd"&gt;private&lt;/span&gt; &lt;span class="kwrd"&gt;int&lt;/span&gt; observations;&lt;br /&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// Initialize the cluster with the specified center.&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// &amp;lt;param name=&amp;quot;center&amp;quot;&amp;gt;The row with the initial center.&amp;lt;/param&amp;gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; Initialize(&lt;span class="kwrd"&gt;double&lt;/span&gt;[] center)&lt;br /&gt;            {&lt;br /&gt;                &lt;span class="kwrd"&gt;this&lt;/span&gt;.location = &lt;span class="kwrd"&gt;new&lt;/span&gt; &lt;span class="kwrd"&gt;double&lt;/span&gt;[center.Length];&lt;br /&gt;                &lt;span class="kwrd"&gt;this&lt;/span&gt;.sumOfParts = &lt;span class="kwrd"&gt;new&lt;/span&gt; &lt;span class="kwrd"&gt;double&lt;/span&gt;[center.Length];&lt;br /&gt;                &lt;span class="kwrd"&gt;this&lt;/span&gt;.observations = 0;&lt;br /&gt;&lt;br /&gt;                &lt;span class="kwrd"&gt;for&lt;/span&gt; (&lt;span class="kwrd"&gt;int&lt;/span&gt; i = 0; i &amp;lt; center.Length; ++i)&lt;br /&gt;                {&lt;br /&gt;                    &lt;span class="kwrd"&gt;this&lt;/span&gt;.location[i] = center[i];&lt;br /&gt;                }&lt;br /&gt;            }&lt;br /&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// &amp;lt;summary&amp;gt;Add a member to the cluster.&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// &amp;lt;param name=&amp;quot;member&amp;quot;&amp;gt;The member to add.&amp;lt;/param&amp;gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; AddMember(&lt;span class="kwrd"&gt;double&lt;/span&gt;[] member)&lt;br /&gt;            {&lt;br /&gt;                &lt;span class="kwrd"&gt;for&lt;/span&gt; (&lt;span class="kwrd"&gt;int&lt;/span&gt; i = 0; i &amp;lt; member.Length; ++i)&lt;br /&gt;                {&lt;br /&gt;                    &lt;span class="kwrd"&gt;this&lt;/span&gt;.sumOfParts[i] += member[i];&lt;br /&gt;                }&lt;br /&gt;&lt;br /&gt;                &lt;span class="kwrd"&gt;this&lt;/span&gt;.observations++;&lt;br /&gt;            }&lt;br /&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// &amp;lt;summary&amp;gt;Remove a member from the cluster.&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// &amp;lt;param name=&amp;quot;member&amp;quot;&amp;gt;The member to remove.&amp;lt;/param&amp;gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; RemoveMember(&lt;span class="kwrd"&gt;double&lt;/span&gt;[] member)&lt;br /&gt;            {&lt;br /&gt;                &lt;span class="kwrd"&gt;for&lt;/span&gt; (&lt;span class="kwrd"&gt;int&lt;/span&gt; i = 0; i &amp;lt; member.Length; ++i)&lt;br /&gt;                {&lt;br /&gt;                    &lt;span class="kwrd"&gt;this&lt;/span&gt;.sumOfParts[i] -= member[i];&lt;br /&gt;                }&lt;br /&gt;&lt;br /&gt;                &lt;span class="kwrd"&gt;this&lt;/span&gt;.observations--;&lt;br /&gt;            }&lt;br /&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// Calculate the new centroid.&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; CalculateCentroid()&lt;br /&gt;            {&lt;br /&gt;                &lt;span class="kwrd"&gt;for&lt;/span&gt; (&lt;span class="kwrd"&gt;int&lt;/span&gt; i = 0; i &amp;lt; &lt;span class="kwrd"&gt;this&lt;/span&gt;.sumOfParts.Length; ++i)&lt;br /&gt;                {&lt;br /&gt;                    &lt;span class="kwrd"&gt;this&lt;/span&gt;.location[i] = &lt;span class="kwrd"&gt;this&lt;/span&gt;.sumOfParts[i] / &lt;span class="kwrd"&gt;this&lt;/span&gt;.observations;&lt;br /&gt;                }&lt;br /&gt;            }&lt;br /&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// Calculates the euclidean distance.&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// &amp;lt;param name=&amp;quot;point&amp;quot;&amp;gt;The point to calculate the distance to this cluster.&amp;lt;/param&amp;gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// &amp;lt;returns&amp;gt;The distance from the cluster.&amp;lt;/returns&amp;gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;double&lt;/span&gt; Distance(&lt;span class="kwrd"&gt;double&lt;/span&gt;[] point)&lt;br /&gt;            {&lt;br /&gt;                &lt;span class="kwrd"&gt;double&lt;/span&gt; sum = 0;&lt;br /&gt;                &lt;span class="kwrd"&gt;for&lt;/span&gt; (&lt;span class="kwrd"&gt;int&lt;/span&gt; i = 0; i &amp;lt; point.Length; ++i)&lt;br /&gt;                {&lt;br /&gt;                    &lt;span class="kwrd"&gt;double&lt;/span&gt; difference = &lt;span class="kwrd"&gt;this&lt;/span&gt;.location[i] - point[i];&lt;br /&gt;                    sum += difference * difference;&lt;br /&gt;                }&lt;br /&gt;&lt;br /&gt;                &lt;span class="kwrd"&gt;return&lt;/span&gt; Math.Sqrt(sum);&lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        &lt;span class="rem"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;&lt;br /&gt;        &lt;span class="rem"&gt;/// A data row reader which returns a pageable list.&lt;/span&gt;&lt;br /&gt;        &lt;span class="rem"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;&lt;br /&gt;        &lt;span class="kwrd"&gt;private&lt;/span&gt; &lt;span class="kwrd"&gt;sealed&lt;/span&gt; &lt;span class="kwrd"&gt;class&lt;/span&gt; PageableListDataRowReader : CustomDataRowReader&lt;br /&gt;        {&lt;br /&gt;            &lt;span class="rem"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// The data to return.&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class="kwrd"&gt;private&lt;/span&gt; &lt;span class="kwrd"&gt;readonly&lt;/span&gt; PageableList&amp;lt;&lt;span class="kwrd"&gt;int&lt;/span&gt;&amp;gt; data;&lt;br /&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// The result cursor.&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class="kwrd"&gt;private&lt;/span&gt; &lt;span class="kwrd"&gt;readonly&lt;/span&gt; MutableValueCursor&amp;lt;&lt;span class="kwrd"&gt;int&lt;/span&gt;&amp;gt; resultCursor;&lt;br /&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// The result name.&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class="kwrd"&gt;private&lt;/span&gt; &lt;span class="kwrd"&gt;readonly&lt;/span&gt; &lt;span class="kwrd"&gt;string&lt;/span&gt; resultName;&lt;br /&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// &amp;lt;summary&amp;gt;           &lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// The current row index.&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class="kwrd"&gt;private&lt;/span&gt; &lt;span class="kwrd"&gt;int&lt;/span&gt; index = -1;&lt;br /&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// Initializes a new instance of the PagableListDataRowReader class.&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// &amp;lt;param name=&amp;quot;data&amp;quot;&amp;gt;The data to return.&amp;lt;/param&amp;gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// &amp;lt;param name=&amp;quot;resultName&amp;quot;&amp;gt;The result name.&amp;lt;/param&amp;gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class="kwrd"&gt;public&lt;/span&gt; PageableListDataRowReader(PageableList&amp;lt;&lt;span class="kwrd"&gt;int&lt;/span&gt;&amp;gt; data, &lt;span class="kwrd"&gt;string&lt;/span&gt; resultName)&lt;br /&gt;            {&lt;br /&gt;                &lt;span class="kwrd"&gt;this&lt;/span&gt;.data = data;&lt;br /&gt;                &lt;span class="kwrd"&gt;this&lt;/span&gt;.resultName = resultName;&lt;br /&gt;                &lt;span class="kwrd"&gt;this&lt;/span&gt;.resultCursor = (MutableValueCursor&amp;lt;&lt;span class="kwrd"&gt;int&lt;/span&gt;&amp;gt;)DataValueCursor.CreateMutableCursor(DataType.Integer);&lt;br /&gt;            }&lt;br /&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// The implementor should provide a list of &amp;lt;see cref=&amp;quot;T:Spotfire.Dxp.Data.DataRowReaderColumn&amp;quot;/&amp;gt;s that it&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// returns.&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// &amp;lt;remarks&amp;gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// This method is only called once.&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// &amp;lt;/remarks&amp;gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class="kwrd"&gt;protected&lt;/span&gt; &lt;span class="kwrd"&gt;override&lt;/span&gt; IEnumerable&amp;lt;DataRowReaderColumn&amp;gt; GetColumnsCore()&lt;br /&gt;            {&lt;br /&gt;                List&amp;lt;DataRowReaderColumn&amp;gt; columns = &lt;span class="kwrd"&gt;new&lt;/span&gt; List&amp;lt;DataRowReaderColumn&amp;gt;();&lt;br /&gt;&lt;br /&gt;                DataRowReaderColumn column =&lt;br /&gt;                    &lt;span class="kwrd"&gt;new&lt;/span&gt; DataRowReaderColumn(&lt;span class="kwrd"&gt;this&lt;/span&gt;.resultName, DataType.Integer, &lt;span class="kwrd"&gt;this&lt;/span&gt;.resultCursor);&lt;br /&gt;                columns.Add(column);&lt;br /&gt;&lt;br /&gt;                &lt;span class="kwrd"&gt;return&lt;/span&gt; columns;&lt;br /&gt;            }&lt;br /&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// The implementor should provide the result properties.&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// &amp;lt;remarks&amp;gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// This method is only called once.&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// &amp;lt;/remarks&amp;gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class="kwrd"&gt;protected&lt;/span&gt; &lt;span class="kwrd"&gt;override&lt;/span&gt; ResultProperties GetResultPropertiesCore()&lt;br /&gt;            {&lt;br /&gt;                &lt;span class="kwrd"&gt;return&lt;/span&gt; &lt;span class="kwrd"&gt;new&lt;/span&gt; ResultProperties();&lt;br /&gt;            }&lt;br /&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// Advance to the next row.&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// The implementor should update all &amp;lt;see cref=&amp;quot;T:Spotfire.Dxp.Data.DataValueCursor&amp;quot;/&amp;gt;s in the &amp;lt;see cref=&amp;quot;T:Spotfire.Dxp.Data.DataRowReaderColumn&amp;quot;/&amp;gt;s&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// with values for the next row.&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// &amp;lt;returns&amp;gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;///    &amp;lt;c&amp;gt;true&amp;lt;/c&amp;gt; if there are more rows; otherwise &amp;lt;c&amp;gt;false&amp;lt;/c&amp;gt;.&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// &amp;lt;/returns&amp;gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class="kwrd"&gt;protected&lt;/span&gt; &lt;span class="kwrd"&gt;override&lt;/span&gt; &lt;span class="kwrd"&gt;bool&lt;/span&gt; MoveNextCore()&lt;br /&gt;            {&lt;br /&gt;                &lt;span class="kwrd"&gt;this&lt;/span&gt;.index++;&lt;br /&gt;                &lt;span class="kwrd"&gt;if&lt;/span&gt; (&lt;span class="kwrd"&gt;this&lt;/span&gt;.index &amp;gt;= &lt;span class="kwrd"&gt;this&lt;/span&gt;.data.Count)&lt;br /&gt;                {&lt;br /&gt;                    &lt;span class="kwrd"&gt;return&lt;/span&gt; &lt;span class="kwrd"&gt;false&lt;/span&gt;;&lt;br /&gt;                }&lt;br /&gt;&lt;br /&gt;                &lt;span class="rem"&gt;// Add one to keep index one based.&lt;/span&gt;&lt;br /&gt;                &lt;span class="kwrd"&gt;this&lt;/span&gt;.resultCursor.MutableDataValue.ValidValue = &lt;span class="kwrd"&gt;this&lt;/span&gt;.data[&lt;span class="kwrd"&gt;this&lt;/span&gt;.index] + 1;&lt;br /&gt;&lt;br /&gt;                &lt;span class="kwrd"&gt;return&lt;/span&gt; &lt;span class="kwrd"&gt;true&lt;/span&gt;;&lt;br /&gt;            }&lt;br /&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// The implementor should implement this method to reset the&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// enumerator. If this method is called then the &amp;lt;see cref=&amp;quot;M:Spotfire.Dxp.Data.DataRowReader.MoveNextCore&amp;quot;/&amp;gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// method should return the first row again when called the next time.&lt;/span&gt;&lt;br /&gt;            &lt;span class="rem"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class="kwrd"&gt;protected&lt;/span&gt; &lt;span class="kwrd"&gt;override&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; ResetCore()&lt;br /&gt;            {&lt;br /&gt;                &lt;span class="kwrd"&gt;this&lt;/span&gt;.index = -1;&lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;    }&lt;/pre&gt;&lt;img src="http://spotfire.tibco.com/community/aggbug.aspx?PostID=733" width="1" height="1"&gt;</content><author><name>Jonas Svensson</name><uri>http://spotfire.tibco.com/community/members/Jonas-Svensson.aspx</uri></author><category term="Multithreading" scheme="http://spotfire.tibco.com/community/blogs/stn/archive/tags/Multithreading/default.aspx" /><category term="CustomDataSource" scheme="http://spotfire.tibco.com/community/blogs/stn/archive/tags/CustomDataSource/default.aspx" /><category term="Developer" scheme="http://spotfire.tibco.com/community/blogs/stn/archive/tags/Developer/default.aspx" /><category term="C#" scheme="http://spotfire.tibco.com/community/blogs/stn/archive/tags/C_2300_/default.aspx" /><category term="3.1" scheme="http://spotfire.tibco.com/community/blogs/stn/archive/tags/3.1/default.aspx" /><category term="Data Functions" scheme="http://spotfire.tibco.com/community/blogs/stn/archive/tags/Data+Functions/default.aspx" /></entry><entry><title>Add Action Scripts to the Text Area</title><link rel="alternate" type="text/html" href="http://spotfire.tibco.com/community/blogs/stn/archive/2010/03/11/add-action-scripts-to-the-text-area.aspx" /><id>http://spotfire.tibco.com/community/blogs/stn/archive/2010/03/11/add-action-scripts-to-the-text-area.aspx</id><published>2010-03-11T21:11:32Z</published><updated>2010-03-11T21:11:32Z</updated><content type="html">&lt;p&gt;A huge addition to the &lt;a href="http://spotfire.tibco.com/products/developer.aspx" target="_blank"&gt;TIBCO Spotfire Developer license&lt;/a&gt; in Spotfire 3.1 is the ability to use a scripting language to add document-specific scripts to Action Controls in the Text Area. These scripts have access to the entire &lt;a href="http://stn.spotfire.com/stn/API.aspx?API=dxp%2fhtml%2fN_Spotfire_Dxp_Application.htm" target="_blank"&gt;Spotfire C# API&lt;/a&gt; and can be executed in both Professional and the Web Player! This will open up an entirely new frontier in the development of sophisticated Spotfire Analytic Applications that behave exactly like the application author wants.&lt;/p&gt;  &lt;p&gt;(This new functionality will be an especially welcome addition to DecisionSite power users who built a lot of Guides with JavaScript/COM in DecisionSite!)&lt;/p&gt;  &lt;p&gt;IronPython is the scripting language that is supported with this new functionality. IronPython is Microsoft’s officially supported implementation of the Python language and interpreted through the .NET Framework. All the software needed to write and execute Python scripts is deployed with the Spotfire platform engine. Python is an easy to learn language, but in practice the majority of the code you will write behind Active Controls will require much, much less knowledge of the Python syntax and much, much more knowledge of the Spotfire C# API. If you do want to learn the details of the IronPython/Python syntax, Microsoft’s &lt;/p&gt;  &lt;p&gt;&lt;a title="http://ironpython.net/" href="http://ironpython.net/"&gt;http://ironpython.net/&lt;/a&gt; site is a great place to start.&lt;/p&gt;  &lt;p&gt;Watch the Spotfire Technology Network, this Knowledge Base and the User Community for a lot more information and examples on the power of IronPython scripting to solve many challenges, but the best place to get started is with the &lt;a href="http://stn.spotfire.com/stn/UserDoc.aspx?UserDoc=spotfire_client_help%2ftext%2ftext_using_scripts_in_the_text_area.htm" target="_blank"&gt;Using Scripts in the Text Area topic of the user manual&lt;/a&gt;. It takes you through the steps to build and deploy your first script along with a number of useful example scripts.&lt;/p&gt;&lt;img src="http://spotfire.tibco.com/community/aggbug.aspx?PostID=732" width="1" height="1"&gt;</content><author><name>Greg Goldsmith</name><uri>http://spotfire.tibco.com/community/members/Greg-Goldsmith.aspx</uri></author><category term="What's New" scheme="http://spotfire.tibco.com/community/blogs/stn/archive/tags/What_2700_s+New/default.aspx" /><category term="Developer" scheme="http://spotfire.tibco.com/community/blogs/stn/archive/tags/Developer/default.aspx" /><category term="3.1" scheme="http://spotfire.tibco.com/community/blogs/stn/archive/tags/3.1/default.aspx" /><category term="IronPython" scheme="http://spotfire.tibco.com/community/blogs/stn/archive/tags/IronPython/default.aspx" /></entry><entry><title>TIBCO Spotfire Administration Console</title><link rel="alternate" type="text/html" href="http://spotfire.tibco.com/community/blogs/stn/archive/2010/03/11/tibco-spotfire-administration-console.aspx" /><id>http://spotfire.tibco.com/community/blogs/stn/archive/2010/03/11/tibco-spotfire-administration-console.aspx</id><published>2010-03-11T20:25:57Z</published><updated>2010-03-11T20:25:57Z</updated><content type="html">&lt;p&gt;With 3.1, the Spotfire Server adds a new web browser-based Administration Console. This tool is used to manage users and groups (unless users and groups are handled by an external mechanism such as an LDAP server), and to deploy Spotfire packages.&lt;/p&gt;  &lt;p&gt;It can be reached by a standard web browser using the URL &lt;em&gt;http://spotfireserver/spotfire/administration/&lt;/em&gt;, where spotfireserver is the hostname of one of your Spotfire Servers. If you are not running Spotfire Server on the standard port 80, you must also add the port number in the URL: &lt;em&gt;http://spotfireserver:8080/spotfire/administration/.&lt;/em&gt; When prompted for a user name and password, use a Spotfire user who is assigned the Spotfire Administrator license. It does not matter which server in the cluster you use, changes made to one server will be stored in the Spotfire database and available to all servers.&lt;/p&gt;  &lt;p&gt;The Administration Manager is still available through Spotfire Professional in order to manage users, groups, licenses and preferences but Spotfire package deployments are no longer managed there, they can only be managed through the web-based Administration Console. In this release, licenses and preferences can only be managed through Professional.&lt;/p&gt;  &lt;p&gt;Below is a screenshot of the new web-based deployment mechanism:&lt;/p&gt;  &lt;p&gt;&lt;a href="http://spotfire.tibco.com/community/blogs/stn/image_5CEE0399.png"&gt;&lt;img style="border-bottom:0px;border-left:0px;display:inline;border-top:0px;border-right:0px;" title="image" border="0" alt="image" src="http://spotfire.tibco.com/community/blogs/stn/image_thumb_313D399D.png" width="585" height="476" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://spotfire.tibco.com/community/aggbug.aspx?PostID=731" width="1" height="1"&gt;</content><author><name>Greg Goldsmith</name><uri>http://spotfire.tibco.com/community/members/Greg-Goldsmith.aspx</uri></author><category term="What's New" scheme="http://spotfire.tibco.com/community/blogs/stn/archive/tags/What_2700_s+New/default.aspx" /><category term="Spotfire Server" scheme="http://spotfire.tibco.com/community/blogs/stn/archive/tags/Spotfire+Server/default.aspx" /><category term="3.1" scheme="http://spotfire.tibco.com/community/blogs/stn/archive/tags/3.1/default.aspx" /></entry><entry><title>Additional Web Player Mashup APIs</title><link rel="alternate" type="text/html" href="http://spotfire.tibco.com/community/blogs/stn/archive/2010/03/11/additional-web-player-mashup-apis.aspx" /><id>http://spotfire.tibco.com/community/blogs/stn/archive/2010/03/11/additional-web-player-mashup-apis.aspx</id><published>2010-03-11T19:47:45Z</published><updated>2010-03-11T19:47:45Z</updated><content type="html">&lt;p&gt;The pre-Spotfire 3.1 &lt;a href="http://stn.spotfire.com/stn/API.aspx?API=wpl%2fhtml%2fN_spotfire_webPlayer.htm" target="_blank"&gt;JavaScript API&lt;/a&gt; for developing mashups includes functions for modifying the objects that are used in the Web Player, marking, filtering, changing page and applying bookmarks. That version of the API also included events that trigger when one of these objects are changed.&lt;/p&gt;  &lt;p&gt;Many new APIs were added in 3.1 to make it easier for web developers to integrate with the &lt;a href="http://stn.spotfire.com/stn/Blog.aspx?blog=spotfire.tibco.com/community/blogs/stn/archive/2010/03/08/introducing-user-input-driven-analytic-applications.aspx" target="_blank"&gt;new Property-driven analytic applications&lt;/a&gt; and to build mashups that are not hardcoded to a specific Spotfire DXP file or dataset.&lt;/p&gt;  &lt;p&gt;Specifically, the new &lt;a href="http://stn.spotfire.com/stn/API.aspx?API=wpl%2fhtml%2fN_spotfire_webPlayer.htm" target="_blank"&gt;Spotfire 3.1 JavaScript APIs&lt;/a&gt; include the ability to:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;Get, set, enumerate and listen for changes of:&lt;/li&gt;    &lt;ul&gt;     &lt;li&gt;Document Properties&lt;/li&gt;      &lt;li&gt;Data Table Properties&lt;/li&gt;      &lt;li&gt;Column Properties &lt;/li&gt;   &lt;/ul&gt;    &lt;li&gt;Get the current value or state for the existing functionality:      &lt;ul&gt;       &lt;li&gt;Marking &lt;/li&gt;        &lt;li&gt;Filters &lt;/li&gt;        &lt;li&gt;Pages &lt;/li&gt;     &lt;/ul&gt;   &lt;/li&gt;    &lt;li&gt;Enumerate and get values (to go along with the existing set methods) of metadata for the document: &lt;/li&gt;    &lt;ul&gt;     &lt;li&gt;Data Tables &lt;/li&gt;      &lt;li&gt;Columns&lt;/li&gt;      &lt;li&gt;Filtering schemes &lt;/li&gt;      &lt;li&gt;Filters &lt;/li&gt;      &lt;li&gt;Pages &lt;/li&gt;      &lt;li&gt;Bookmarks &lt;/li&gt;      &lt;li&gt;Markings &lt;/li&gt;      &lt;li&gt;Analysis file &lt;/li&gt;   &lt;/ul&gt; &lt;/ul&gt;  &lt;p&gt;All of this should allow web developers to build even richer and more integrated applications that combine the unique capabilities of the Spotfire Web Player with existing web-based applications.&lt;/p&gt;&lt;img src="http://spotfire.tibco.com/community/aggbug.aspx?PostID=729" width="1" height="1"&gt;</content><author><name>Greg Goldsmith</name><uri>http://spotfire.tibco.com/community/members/Greg-Goldsmith.aspx</uri></author><category term="What's New" scheme="http://spotfire.tibco.com/community/blogs/stn/archive/tags/What_2700_s+New/default.aspx" /><category term="Web Player" scheme="http://spotfire.tibco.com/community/blogs/stn/archive/tags/Web+Player/default.aspx" /><category term="Developer" scheme="http://spotfire.tibco.com/community/blogs/stn/archive/tags/Developer/default.aspx" /><category term="ASP.NET" scheme="http://spotfire.tibco.com/community/blogs/stn/archive/tags/ASP.NET/default.aspx" /><category term="3.1" scheme="http://spotfire.tibco.com/community/blogs/stn/archive/tags/3.1/default.aspx" /></entry><entry><title>On-Demand Data Table Loading</title><link rel="alternate" type="text/html" href="http://spotfire.tibco.com/community/blogs/stn/archive/2010/03/09/on-demand-data-table-loading.aspx" /><id>http://spotfire.tibco.com/community/blogs/stn/archive/2010/03/09/on-demand-data-table-loading.aspx</id><published>2010-03-09T20:33:00Z</published><updated>2010-03-09T20:33:00Z</updated><content type="html">&lt;p&gt;On-Demand Data Tables allow virtually unlimited amounts of data to be analyzed in a single Spotfire session. The key to this mechanism is that end user interaction with a Spotfire Analytic Application triggers a database to be re-queried based on the newly generated criteria and the returned table replaces an existing in-memory data set. The result is a seamless navigation experience.&lt;/p&gt;
&lt;p&gt;Prior to Spotfire 3.1 the only user interaction that could be used as input was a single visualization marking and the resulting table had many limitations on how it could be transformed and worked with.&lt;/p&gt;
&lt;p&gt;With Spotfire 3.1 almost all of those limitation have been removed, including: &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Data Source&lt;/li&gt;
&lt;ul&gt;
&lt;li&gt;Parameterized and Stored Procedure Information Links can be used as data sources&lt;/li&gt;
&lt;li&gt;An On-Demand Data Table can be the 1st (only) data table in an analysis&lt;/li&gt;
&lt;li&gt;A new data format between the client and Spotfire Server decreases transfer time&lt;/li&gt;&lt;/ul&gt;
&lt;li&gt;Inputs&lt;/li&gt;
&lt;ul&gt;
&lt;li&gt;Multiple Markings can be used as input parameters, allowing for support of navigating independent star schema dimension tables and drilling into the fact table&lt;/li&gt;
&lt;li&gt;Filterings can be used as input parameters, so no needless data is returned from the server&lt;/li&gt;
&lt;li&gt;Custom Expressions (including Document, Data Table and Column Properties) can be used to define inputs&lt;/li&gt;
&lt;ul&gt;
&lt;li&gt;When combined with the new user input Property Controls this means that no input values have to be hardcoded or per session any more&lt;/li&gt;
&lt;li&gt;The value passed to the query no longer has to actually be in the currently loaded data set&lt;/li&gt;&lt;/ul&gt;
&lt;li&gt;Input criteria can be specified as a range rather than a fixed list of items, so for example your query could return all values in the database between the minimum and maximum date that are marked in the “time dimension” regardless of whether those dates are marked or even in the data set&lt;/li&gt;
&lt;li&gt;The number of categories per input can be restricted to limited to any given number, for example you could limit the number of “Countries” that can be selected by the end user to 3&lt;/li&gt;
&lt;li&gt;Information Link prompts are optional&lt;/li&gt;
&lt;li&gt;The definition of the input parameters and values can be edited at any time, without having to replace the entire data table&lt;/li&gt;&lt;/ul&gt;
&lt;li&gt;Transformation&lt;/li&gt;
&lt;ul&gt;
&lt;li&gt;Transformations, like pivot and normalization, can be applied to the resulting data table Rows, Columns, and calculations can be added to On-Demand Tables&lt;/li&gt;
&lt;li&gt;Table Relations are not required so there does not have to be a join compliant relationship between the Information Link and the end data table&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;
&lt;p&gt;For more information, the online user manual has a complete description and set of examples for adding on-demand data tables and configuring where the input parameters come from and when they are triggered: &lt;a title="http://stn.spotfire.com/stn/UserDoc.aspx?UserDoc=spotfire_client_help%2fadd%2fadd_loading_data_on_demand.htm" href="http://stn.spotfire.com/stn/UserDoc.aspx?UserDoc=spotfire_client_help%2fadd%2fadd_loading_data_on_demand.htm"&gt;http://stn.spotfire.com/stn/UserDoc.aspx?UserDoc=spotfire_client_help%2fadd%2fadd_loading_data_on_demand.htm&lt;/a&gt;&lt;/p&gt;&lt;img src="http://spotfire.tibco.com/community/aggbug.aspx?PostID=722" width="1" height="1"&gt;</content><author><name>Greg Goldsmith</name><uri>http://spotfire.tibco.com/community/members/Greg-Goldsmith.aspx</uri></author><category term="What's New" scheme="http://spotfire.tibco.com/community/blogs/stn/archive/tags/What_2700_s+New/default.aspx" /><category term="3.1" scheme="http://spotfire.tibco.com/community/blogs/stn/archive/tags/3.1/default.aspx" /></entry><entry><title>Heat Map with Hierarchical Clustering</title><link rel="alternate" type="text/html" href="http://spotfire.tibco.com/community/blogs/stn/archive/2010/03/08/heat-map.aspx" /><id>http://spotfire.tibco.com/community/blogs/stn/archive/2010/03/08/heat-map.aspx</id><published>2010-03-09T04:52:00Z</published><updated>2010-03-09T04:52:00Z</updated><content type="html">&lt;p&gt;Spotfire 3.1 includes a heat map visualization, which is similar to a table or cross table except it shows colors instead of numbers and is space constrained to show all the information at once.&amp;nbsp; When used with proper color management and sorted by attribute or by clustering results, it provides a powerful overview of your data.&amp;nbsp; Heat maps used with clustering algorithms became very popular in biological research for visualizing massive microarray datasets to help decipher the relationship between genomic structure and function.&amp;nbsp; But the Spotfire 3.1 heat map is flexible enough to visualize virtually any kind of data to uncover relationships and trends.&lt;/p&gt;
&lt;p&gt;At first, the heat map can be a bit intimidating since it won’t necessarily reveal any interesting features of the data.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://spotfire.tibco.com/community/blogs/stn/hm_affyunsorted_0D13C1E0.png" target="_blank"&gt;&lt;img title="hm_affyunsorted" style="BORDER-TOP-WIDTH:0px;DISPLAY:inline;BORDER-LEFT-WIDTH:0px;BORDER-BOTTOM-WIDTH:0px;BORDER-RIGHT-WIDTH:0px;" height="484" alt="hm_affyunsorted" src="http://spotfire.tibco.com/community/blogs/stn/hm_affyunsorted_thumb_7ACAFB1D.png" width="436" border="0" /&gt;&lt;/a&gt; &lt;/p&gt;
&lt;p&gt;But by simply sorting by a column you can begin to see how the other columns generally relate to the sorted column.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://spotfire.tibco.com/community/blogs/stn/hm_affysorted_017E04A1.png" target="_blank"&gt;&lt;img title="hm_affysorted" style="BORDER-TOP-WIDTH:0px;DISPLAY:inline;BORDER-LEFT-WIDTH:0px;BORDER-BOTTOM-WIDTH:0px;BORDER-RIGHT-WIDTH:0px;" height="484" alt="hm_affysorted" src="http://spotfire.tibco.com/community/blogs/stn/hm_affysorted_thumb_4147EB26.png" width="437" border="0" /&gt;&lt;/a&gt; &lt;/p&gt;
&lt;p&gt;The Spotfire 3.1 can automatically sort itself into groups of similar rows.&amp;nbsp; The heat map is tightly integrated with a hierarchical clustering algorithm, and also has a special cluster visualization/navigation feature called a dendrogram to help navigate the results of the clustering.&amp;nbsp; &lt;/p&gt;
&lt;p&gt;&lt;a href="http://spotfire.tibco.com/community/blogs/stn/hm_affyclustered_0AD9C9D5.png" target="_blank"&gt;&lt;img title="hm_affyclustered" style="BORDER-TOP-WIDTH:0px;DISPLAY:inline;BORDER-LEFT-WIDTH:0px;BORDER-BOTTOM-WIDTH:0px;BORDER-RIGHT-WIDTH:0px;" height="484" alt="hm_affyclustered" src="http://spotfire.tibco.com/community/blogs/stn/hm_affyclustered_thumb_4AA3B05A.png" width="436" border="0" /&gt;&lt;/a&gt; &lt;/p&gt;
&lt;p&gt;Further, the clustering results can be navigated in a way that integrates with other visualizations.&amp;nbsp; Here, the analyst can drag the pruning line (red dotted line) of the dendrogram to define the cutoffs for cluster groups, and that cutoff can dynamically drive (for example) the trellising of a line chart to reveal how the clusters appear in profile view. &lt;/p&gt;
&lt;p&gt;&lt;a href="http://spotfire.tibco.com/community/blogs/stn/hm_affyclusteredwprofiles_313BAD20.png" target="_blank"&gt;&lt;img title="hm_affyclusteredwprofiles" style="BORDER-TOP-WIDTH:0px;DISPLAY:inline;BORDER-LEFT-WIDTH:0px;BORDER-BOTTOM-WIDTH:0px;BORDER-RIGHT-WIDTH:0px;" height="484" alt="hm_affyclusteredwprofiles" src="http://spotfire.tibco.com/community/blogs/stn/hm_affyclusteredwprofiles_thumb_65DC095B.png" width="620" border="0" /&gt;&lt;/a&gt; &lt;/p&gt;
&lt;p&gt;The 3.1 heat map isn’t only for genomics research.&amp;nbsp; It can be configured all the same ways as a cross table and help you find patterns in any numeric data.&amp;nbsp; Here is an example of finding company stocks that behaved similarly over time:&lt;/p&gt;
&lt;p&gt;&lt;a href="http://spotfire.tibco.com/community/blogs/stn/SPClusteringMetrics_135D291F.png" target="_blank"&gt;&lt;img title="S&amp;amp;PClusteringMetrics" style="BORDER-TOP-WIDTH:0px;DISPLAY:inline;BORDER-LEFT-WIDTH:0px;BORDER-BOTTOM-WIDTH:0px;BORDER-RIGHT-WIDTH:0px;" height="484" alt="S&amp;amp;PClusteringMetrics" src="http://spotfire.tibco.com/community/blogs/stn/SPClusteringMetrics_thumb_1A1032A2.png" width="620" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Finally, the powerful coloring capabilities of Spotfire 3.1 open up many possibilities for using the heat map.&amp;nbsp; You can create independent color schemes for different heat map columns, and even create your own dynamic coloring schemes based on custom expressions.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://spotfire.tibco.com/community/blogs/stn/hm_affycolorconfig_47915265.png" target="_blank"&gt;&lt;img title="hm_affycolorconfig" style="BORDER-TOP-WIDTH:0px;DISPLAY:inline;BORDER-LEFT-WIDTH:0px;BORDER-BOTTOM-WIDTH:0px;BORDER-RIGHT-WIDTH:0px;" height="484" alt="hm_affycolorconfig" src="http://spotfire.tibco.com/community/blogs/stn/hm_affycolorconfig_thumb_6335DE5B.png" width="620" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://spotfire.tibco.com/community/aggbug.aspx?PostID=686" width="1" height="1"&gt;</content><author><name>Brendan Gibson</name><uri>http://spotfire.tibco.com/community/members/Brendan-Gibson.aspx</uri></author><category term="What's New" scheme="http://spotfire.tibco.com/community/blogs/stn/archive/tags/What_2700_s+New/default.aspx" /><category term="3.1" scheme="http://spotfire.tibco.com/community/blogs/stn/archive/tags/3.1/default.aspx" /></entry><entry><title>Combination Bar and Line Chart</title><link rel="alternate" type="text/html" href="http://spotfire.tibco.com/community/blogs/stn/archive/2010/03/08/combination-bar-and-line-chart.aspx" /><id>http://spotfire.tibco.com/community/blogs/stn/archive/2010/03/08/combination-bar-and-line-chart.aspx</id><published>2010-03-09T04:04:00Z</published><updated>2010-03-09T04:04:00Z</updated><content type="html">&lt;p&gt;In response to overwhelming customer demand, Spotfire 3.1 includes a new visualization for showing bars and lines at the same time.&amp;nbsp; Like other Spotfire visualizations it is highly configurable and interactive, it works seamlessly with the other visuals for drilling and showing relationships, and best of all it makes comparing trends easier for common cases like comparing actual figures to forecasts or quotas, or simply keeping track of a key reference while browsing other related trends.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://spotfire.tibco.com/community/blogs/stn/barline_sandp_16E5F425.png" target="_blank"&gt;&lt;img title="barline_sandp" style="BORDER-TOP-WIDTH:0px;DISPLAY:inline;BORDER-LEFT-WIDTH:0px;BORDER-BOTTOM-WIDTH:0px;BORDER-RIGHT-WIDTH:0px;" height="484" alt="barline_sandp" src="http://spotfire.tibco.com/community/blogs/stn/barline_sandp_thumb_1D2CCAB3.png" width="620" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Here are some other key features for this versatile plot …&lt;/p&gt;
&lt;p&gt;Just click on the icons in the Legend when using Spotfire Professional and you can quickly change which trends are lines and which are bars …&lt;/p&gt;
&lt;p&gt;&lt;a href="http://spotfire.tibco.com/community/blogs/stn/barline_selecttrenddisplay_5F52EE16.png" target="_blank"&gt;&lt;img title="barline_selecttrenddisplay" style="BORDER-TOP-WIDTH:0px;DISPLAY:inline;BORDER-LEFT-WIDTH:0px;BORDER-BOTTOM-WIDTH:0px;BORDER-RIGHT-WIDTH:0px;" height="260" alt="barline_selecttrenddisplay" src="http://spotfire.tibco.com/community/blogs/stn/barline_selecttrenddisplay_thumb_06210457.png" width="364" border="0" /&gt;&lt;/a&gt; &lt;/p&gt;
&lt;p&gt;You can choose to stack multiple sets of bars and put the side-by-side&lt;/p&gt;
&lt;p&gt;&lt;a href="http://spotfire.tibco.com/community/blogs/stn/barline_sidebyside_2CEF1A97.png" target="_blank"&gt;&lt;img title="barline_sidebyside" style="BORDER-TOP-WIDTH:0px;DISPLAY:inline;BORDER-LEFT-WIDTH:0px;BORDER-BOTTOM-WIDTH:0px;BORDER-RIGHT-WIDTH:0px;" height="263" alt="barline_sidebyside" src="http://spotfire.tibco.com/community/blogs/stn/barline_sidebyside_thumb_01AA8390.png" width="644" border="0" /&gt;&lt;/a&gt; &lt;/p&gt;
&lt;p&gt;&lt;a href="http://spotfire.tibco.com/community/blogs/stn/barline_stacked_48277398.png" target="_blank"&gt;&lt;img title="barline_stacked" style="BORDER-TOP-WIDTH:0px;DISPLAY:inline;BORDER-LEFT-WIDTH:0px;BORDER-BOTTOM-WIDTH:0px;BORDER-RIGHT-WIDTH:0px;" height="265" alt="barline_stacked" src="http://spotfire.tibco.com/community/blogs/stn/barline_stacked_thumb_1CE2DC91.png" width="644" border="0" /&gt;&lt;/a&gt; &lt;/p&gt;
&lt;p&gt;Sort by one of the trends to see how well the others are following … in this case the x-axis is sorted by the bars representing the Health Care sector.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://spotfire.tibco.com/community/blogs/stn/image_0EA463A1.png" target="_blank"&gt;&lt;img title="image" style="BORDER-TOP-WIDTH:0px;DISPLAY:inline;BORDER-LEFT-WIDTH:0px;BORDER-BOTTOM-WIDTH:0px;BORDER-RIGHT-WIDTH:0px;" height="265" alt="image" src="http://spotfire.tibco.com/community/blogs/stn/image_thumb_114D1F52.png" width="644" border="0" /&gt;&lt;/a&gt; &lt;/p&gt;
&lt;p&gt;And finally, multi-y scaling means that trends can easily be compared from vastly different scales.&amp;nbsp; Notice how each trellis panel has its own scale.&amp;nbsp; Individual trends can also have their own scaling within the same frame. &lt;/p&gt;
&lt;p&gt;&lt;a href="http://spotfire.tibco.com/community/blogs/stn/barline_multiy_30FBF91A.png" target="_blank"&gt;&lt;img title="barline_multiy" style="BORDER-TOP-WIDTH:0px;DISPLAY:inline;BORDER-LEFT-WIDTH:0px;BORDER-BOTTOM-WIDTH:0px;BORDER-RIGHT-WIDTH:0px;" height="484" alt="barline_multiy" src="http://spotfire.tibco.com/community/blogs/stn/barline_multiy_thumb_7E98259A.png" width="584" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://spotfire.tibco.com/community/aggbug.aspx?PostID=684" width="1" height="1"&gt;</content><author><name>Brendan Gibson</name><uri>http://spotfire.tibco.com/community/members/Brendan-Gibson.aspx</uri></author><category term="What's New" scheme="http://spotfire.tibco.com/community/blogs/stn/archive/tags/What_2700_s+New/default.aspx" /><category term="3.1" scheme="http://spotfire.tibco.com/community/blogs/stn/archive/tags/3.1/default.aspx" /></entry><entry><title>Spotfire Integration with S+ and R</title><link rel="alternate" type="text/html" href="http://spotfire.tibco.com/community/blogs/stn/archive/2010/03/08/spotfire-integration-with-s-and-r.aspx" /><id>http://spotfire.tibco.com/community/blogs/stn/archive/2010/03/08/spotfire-integration-with-s-and-r.aspx</id><published>2010-03-09T04:03:00Z</published><updated>2010-03-09T04:03:00Z</updated><content type="html">&lt;p&gt;&lt;strong&gt;Overview&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Version 3.1 makes it easy to create Spotfire applications infused with computational methods written directly in S+ or R (* see footnote). This is how Spotfire can easily become the intuitive, interactive interface to custom, sophisticated applications that find patterns, detect outliers, predict trends, cluster groups, optimize outcomes, and much much more. Spotfire 3.1 gives users a great deal of control over the values and data sent off for calculation by S+ or R, and also provides creative ways to display and refresh the results within Spotfire clients. You can even make these powerful interactive applications available to web users by simply saving the application file to the Spotfire Library. Best of all, you can do all this with point-and-click configuration, so you won’t need to write a line of code other than any custom S+ or R script you want to leverage.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Benefits&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Operationalize your analytic expertise. Here is the typical disconnect in today’s enterprise: One one hand, you have analytic experts know how to handle analytic challenges but are tied to arcane toolsets and it is difficult to rapidly scale out their work. On the other hand, business analysts have the passion to solve their domain problems but may lack sophisticated analytic methodology or access to the experts. Custom applications can be built to bridge this gap, but app building toolsets like C# and JAVA also demand time from specialized, constrained resources like IT groups. Spotfire 3.1 unleashes analytic power of your personnel by making it easy to configure reusable analytic applications that put the right amount control and flexibility in hands of business analysts in a scalable way without requiring a line of code.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A Simple Example: Loess Trend Smoothing&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Here is a simple example of integrating an S+ script with a Spotfire application. The blue trend line is raw trend data loaded in Spotfire.&amp;nbsp; The dotted red line is drawn from smoothing results calculated from an S+ script that utilizes a function called “loess”. The smoothed trend is configured to automatically recalculate every time the original trend data is filtered. (&lt;a href="http://ondemand.spotfire.com/Public/ViewAnalysis.aspx?file=Public/Loess%20Smoothing" target="_blank"&gt;Click here to try using this application&lt;/a&gt; right now!)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;a href="http://spotfire.tibco.com/community/blogs/stn/splus_loesssummary_023F2B18.png" target="_blank"&gt;&lt;img title="splus_loesssummary" style="BORDER-TOP-WIDTH:0px;DISPLAY:inline;BORDER-LEFT-WIDTH:0px;BORDER-BOTTOM-WIDTH:0px;BORDER-RIGHT-WIDTH:0px;" height="191" alt="splus_loesssummary" src="http://spotfire.tibco.com/community/blogs/stn/splus_loesssummary_thumb_3673545E.png" width="244" border="0" /&gt;&lt;/a&gt; &lt;a href="http://spotfire.tibco.com/community/blogs/stn/splus_loessresultsfilterA_337EA2F9.png" target="_blank"&gt;&lt;img title="splus_loessresultsfilterA" style="BORDER-TOP-WIDTH:0px;DISPLAY:inline;BORDER-LEFT-WIDTH:0px;BORDER-BOTTOM-WIDTH:0px;BORDER-RIGHT-WIDTH:0px;" height="191" alt="splus_loessresultsfilterA" src="http://spotfire.tibco.com/community/blogs/stn/splus_loessresultsfilterA_thumb_0EA58DD5.png" width="244" border="0" /&gt;&lt;/a&gt; &lt;a href="http://spotfire.tibco.com/community/blogs/stn/splus_loessresultsfilterB_50425AD2.png" target="_blank"&gt;&lt;img title="splus_loessresultsfilterB" style="BORDER-TOP-WIDTH:0px;DISPLAY:inline;BORDER-LEFT-WIDTH:0px;BORDER-BOTTOM-WIDTH:0px;BORDER-RIGHT-WIDTH:0px;" height="191" alt="splus_loessresultsfilterB" src="http://spotfire.tibco.com/community/blogs/stn/splus_loessresultsfilterB_thumb_112DFDEC.png" width="244" border="0" /&gt;&lt;/a&gt; &lt;/p&gt;
&lt;p&gt;Here is a little more detail on how this example is put together. The thing to remember is that these computationally sophisticated apps are configured, not coded. We start off with a simple trend line of values over time.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://spotfire.tibco.com/community/blogs/stn/splus_loessstarthere_485278DC.png" target="_blank"&gt;&lt;img title="splus_loessstarthere" style="BORDER-TOP-WIDTH:0px;DISPLAY:inline;BORDER-LEFT-WIDTH:0px;BORDER-BOTTOM-WIDTH:0px;BORDER-RIGHT-WIDTH:0px;" height="484" alt="splus_loessstarthere" src="http://spotfire.tibco.com/community/blogs/stn/splus_loessstarthere_thumb_5CD7C85A.png" width="620" border="0" /&gt;&lt;/a&gt; &lt;/p&gt;
&lt;p&gt;We register our loess algorithm a Spotfire “Data Function” by cutting and pasting S+ script right into Spotfire. The inputs and outputs required for this script are also described here. Registration is a one-time event. Once the script has been registered as a Data Function it can be referenced in as many different applications as you like.&amp;nbsp; &lt;/p&gt;
&lt;p&gt;&lt;a href="http://spotfire.tibco.com/community/blogs/stn/splus_loessregisterscript_6A3DDB60.png" target="_blank"&gt;&lt;img title="splus_loessregisterscript" style="BORDER-TOP-WIDTH:0px;DISPLAY:inline;BORDER-LEFT-WIDTH:0px;BORDER-BOTTOM-WIDTH:0px;BORDER-RIGHT-WIDTH:0px;" height="484" alt="splus_loessregisterscript" src="http://spotfire.tibco.com/community/blogs/stn/splus_loessregisterscript_thumb_57F5149E.png" width="550" border="0" /&gt;&lt;/a&gt; &lt;/p&gt;
&lt;p&gt;Now that we have registered the Data Function, we can incorporate it into our application and map the inputs and outputs appropriately to our dataset. Now is also the time to decide if the calculation is refreshed manually by the user or automatically each time the input values are changed or filtered.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://spotfire.tibco.com/community/blogs/stn/splus_loessinsertoutputs_2591411F.png" target="_blank"&gt;&lt;img title="splus_loessinsertoutputs" style="BORDER-TOP-WIDTH:0px;DISPLAY:inline;BORDER-LEFT-WIDTH:0px;BORDER-BOTTOM-WIDTH:0px;BORDER-RIGHT-WIDTH:0px;" height="482" alt="splus_loessinsertoutputs" src="http://spotfire.tibco.com/community/blogs/stn/splus_loessinsertoutputs_thumb_13487A5D.png" width="644" border="0" /&gt;&lt;/a&gt; &lt;/p&gt;
&lt;p&gt;The loess results come back as a separate table of data. A new feature called “Line from Column Values” makes it possible to juxtapose the newly calculated smoothed values with the original data. &lt;/p&gt;
&lt;p&gt;&lt;a href="http://spotfire.tibco.com/community/blogs/stn/splus_loesslinefromcolvals_60E4A6DD.png" target="_blank"&gt;&lt;img title="splus_loesslinefromcolvals" style="BORDER-TOP-WIDTH:0px;DISPLAY:inline;BORDER-LEFT-WIDTH:0px;BORDER-BOTTOM-WIDTH:0px;BORDER-RIGHT-WIDTH:0px;" height="484" alt="splus_loesslinefromcolvals" src="http://spotfire.tibco.com/community/blogs/stn/splus_loesslinefromcolvals_thumb_2E80D35E.png" width="620" border="0" /&gt;&lt;/a&gt; &lt;/p&gt;
&lt;p&gt;When the original data is filtered in Spotfire, the data is re-sent to the S+ engine, the a new smoothed trend is calculated, the new result is returned to Spotfire and refreshed visually, and all this happens automatically. To make this application available to web users, all you need to do is click File &amp;gt; Save As… &amp;gt; Library Item and you’re done.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://spotfire.tibco.com/community/blogs/stn/splus_loessresultsfinal_54E2B6A9.png" target="_blank"&gt;&lt;img title="splus_loessresultsfinal" style="BORDER-TOP-WIDTH:0px;DISPLAY:inline;BORDER-LEFT-WIDTH:0px;BORDER-BOTTOM-WIDTH:0px;BORDER-RIGHT-WIDTH:0px;" height="191" alt="splus_loessresultsfinal" src="http://spotfire.tibco.com/community/blogs/stn/splus_loessresultsfinal_thumb_175558E0.png" width="244" border="0" /&gt;&lt;/a&gt; &lt;a href="http://spotfire.tibco.com/community/blogs/stn/splus_loessresultsfilterA_32AFBE2C.png" target="_blank"&gt;&lt;img title="splus_loessresultsfilterA" style="BORDER-TOP-WIDTH:0px;DISPLAY:inline;BORDER-LEFT-WIDTH:0px;BORDER-BOTTOM-WIDTH:0px;BORDER-RIGHT-WIDTH:0px;" height="191" alt="splus_loessresultsfilterA" src="http://spotfire.tibco.com/community/blogs/stn/splus_loessresultsfilterA_thumb_1EE288FE.png" width="244" border="0" /&gt;&lt;/a&gt; &lt;a href="http://spotfire.tibco.com/community/blogs/stn/splus_loessresultsfilterB_2B72C6CB.png" target="_blank"&gt;&lt;img title="splus_loessresultsfilterB" style="BORDER-TOP-WIDTH:0px;DISPLAY:inline;BORDER-LEFT-WIDTH:0px;BORDER-BOTTOM-WIDTH:0px;BORDER-RIGHT-WIDTH:0px;" height="191" alt="splus_loessresultsfilterB" src="http://spotfire.tibco.com/community/blogs/stn/splus_loessresultsfilterB_thumb_70469D12.png" width="244" border="0" /&gt;&lt;/a&gt; &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Inputs and Outputs&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;For a given calculation Spotfire can send any number of single values, single columns or entire tables of data as inputs to S+ or R.&amp;nbsp; Spotfire can also handle any number of single values, columns or data tables as return from S+ or R.&amp;nbsp; It is even possible to return images generated by S+ and see them inside Spotfire.&lt;/p&gt;
&lt;p&gt;In Spotfire 3.1 there are new user controls available within text areas that can change visualization settings or adjust variables in calculations.&amp;nbsp; These same user-modifiable settings and variables can be used to determine input parameters to S+ or R scripts and functions.&amp;nbsp; This gives builders a great deal of control in creating powerful applications that are easy to use.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;When to Use the Local Adapter&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;When experimenting or prototyping with an S+ or R powered Spotfire application, you may not wish to constantly communicate with a Spotfire Statistics Services server. You can override Spotfire to use a locally installed S+ or R engine by installing the Local Adapter. This reduces load on server-based resources and gives you greater freedom to experiment.&amp;nbsp; The Local Adapter is bundled with Statistics Services to enable local development, though the local S+ or R engine being called from the Adapter must be installed separately.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Requirements&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Required to create the application shown in the example above: TIBCO Spotfire Server, TIBCO Spotfire Statistics Services, TIBCO Spotfire Professional &amp;amp; Metrics&lt;/p&gt;
&lt;p&gt;Optional related products: TIBCO Spotfire Web Player for web based application usage, S+ desktop or Workbench for developing S+ scripts and functions, add-on modules that enhance S+ such as FinMetrics and NuOpt, TIBCO Spotfire Local Adapter (comes with Statistics Services), and R*.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;* R is available under separate open source software license terms and is not part of the TIBCO Spotfire product line.&amp;nbsp; As such, R is not within the scope of your license for any TIBCO Spotfire product.&amp;nbsp; R is not supported, maintained, or warranted in any way by TIBCO Software Inc.&amp;nbsp; Download and use of R is solely at your own discretion and subject to the free open source license terms applicable to R.&lt;/em&gt;&lt;/p&gt;&lt;img src="http://spotfire.tibco.com/community/aggbug.aspx?PostID=682" width="1" height="1"&gt;</content><author><name>Brendan Gibson</name><uri>http://spotfire.tibco.com/community/members/Brendan-Gibson.aspx</uri></author><category term="What's New" scheme="http://spotfire.tibco.com/community/blogs/stn/archive/tags/What_2700_s+New/default.aspx" /><category term="3.1" scheme="http://spotfire.tibco.com/community/blogs/stn/archive/tags/3.1/default.aspx" /></entry><entry><title>Color</title><link rel="alternate" type="text/html" href="http://spotfire.tibco.com/community/blogs/stn/archive/2010/03/08/color.aspx" /><id>http://spotfire.tibco.com/community/blogs/stn/archive/2010/03/08/color.aspx</id><published>2010-03-08T23:54:27Z</published><updated>2010-03-08T23:54:27Z</updated><content type="html">&lt;p&gt;&lt;strong&gt;Overview&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;Spotfire 3.1 provides innovative new ways to color visualizations and tables, and provides new methods to manage and share the color schemes you use most often.&amp;#160; Color in 3.1 can be configured based on static values, based on dynamic calculations that refresh as you manipulate the data, based on values that the user controls manually, or even based on relative definitions such as ‘top 5.’&amp;#160; You can easily update and propagate color schemes in your applications, and you can share schemes with your organization saving them to the Spotfire Library.&amp;#160; Please read below for a full summary of color improvements in 3.1.&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;Rule Based Coloring&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;Often analysts want to values to jump out at them based on simple concepts like the ‘top and bottom 5’ or ‘everything over 100.’&amp;#160; Spotfire 3.1 makes it extremely easy to configure visuals to be colored in these ways.&amp;#160; For example, to see top 5th percentile of values in a cross table, simply create a rule to color all values greater than the 95th percentile.&amp;#160; See the sequential screenshots below that illustrate assigning blue to the top 5th percentile of values in a cross table.&lt;/p&gt;  &lt;p&gt;&lt;a href="http://spotfire.tibco.com/community/blogs/stn/color_rulesdlg4_66A73818.png" target="_blank"&gt;&lt;img title="color_rulesdlg4" style="border-right:0px;border-top:0px;display:inline;border-left:0px;border-bottom:0px;" height="360" alt="color_rulesdlg4" src="http://spotfire.tibco.com/community/blogs/stn/color_rulesdlg4_thumb_1EE83FE2.png" width="683" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;&lt;a href="http://spotfire.tibco.com/community/blogs/stn/color_rulesdlg_16F32E31.png" target="_blank"&gt;&lt;img title="color_rulesdlg" style="border-right:0px;border-top:0px;display:inline;border-left:0px;border-bottom:0px;" height="446" alt="color_rulesdlg" src="http://spotfire.tibco.com/community/blogs/stn/color_rulesdlg_thumb_08B745F2.png" width="572" border="0" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;&lt;a href="http://spotfire.tibco.com/community/blogs/stn/color_rulesdlg2_2F1BB9EE.png" target="_blank"&gt;&lt;img title="color_rulesdlg2" style="border-right:0px;border-top:0px;display:inline;border-left:0px;border-bottom:0px;" height="449" alt="color_rulesdlg2" src="http://spotfire.tibco.com/community/blogs/stn/color_rulesdlg2_thumb_0BEE4F3C.png" width="575" border="0" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;&lt;a href="http://spotfire.tibco.com/community/blogs/stn/color_rulesdlg3_3252C338.png" target="_blank"&gt;&lt;img title="color_rulesdlg3" style="border-right:0px;border-top:0px;display:inline;border-left:0px;border-bottom:0px;" height="340" alt="color_rulesdlg3" src="http://spotfire.tibco.com/community/blogs/stn/color_rulesdlg3_thumb_6A93CB01.png" width="644" border="0" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;&amp;#160;&lt;/p&gt;  &lt;p&gt;You can apply more than one color rule at the same time.&amp;#160; for example, You can set separate colors for the top 5 and the bottom 5 values at once.&lt;/p&gt;  &lt;p&gt;&lt;a href="http://spotfire.tibco.com/community/blogs/stn/color_topandbottom_3E9E0A16.png" target="_blank"&gt;&lt;img title="color_topandbottom" style="border-top-width:0px;display:inline;border-left-width:0px;border-bottom-width:0px;border-right-width:0px;" height="451" alt="color_topandbottom" src="http://spotfire.tibco.com/community/blogs/stn/color_topandbottom_thumb_35D5B3DD.png" width="644" border="0" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;&amp;#160;&lt;/p&gt;  &lt;p&gt;You may also assign rules even if there are other coloring schemes in place.&amp;#160; For example, you can choose a gradient color scheme to transition color from minimum to maximum values, but then create a rule to color the highest value a special standout color.&lt;/p&gt;  &lt;p&gt;&lt;a href="http://spotfire.tibco.com/community/blogs/stn/color_rulesTop1_7347E76E.png" target="_blank"&gt;&lt;img title="color_rulesTop1" style="border-right:0px;border-top:0px;display:inline;border-left:0px;border-bottom:0px;" height="360" alt="color_rulesTop1" src="http://spotfire.tibco.com/community/blogs/stn/color_rulesTop1_thumb_6B52D5BD.png" width="683" border="0" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;&amp;#160;&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;Coloring the Table&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;In Spotfire 3.1 you can set a color scheme for one or more columns in a Table view, making it easy to notice rows of interest.&amp;#160; You can independently color different columns as needed.&amp;#160; In the example below, two different columns are colored by two independent schemes.&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;&lt;a href="http://spotfire.tibco.com/community/blogs/stn/color_table1_37AF8AC1.png" target="_blank"&gt;&lt;img title="color_table1" style="border-top-width:0px;display:inline;border-left-width:0px;border-bottom-width:0px;border-right-width:0px;" height="299" alt="color_table1" src="http://spotfire.tibco.com/community/blogs/stn/color_table1_thumb_0239594D.png" width="642" border="0" /&gt;&lt;/a&gt;&amp;#160;&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;Coloring the Cross Table&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;You can set a color scheme for one or more columns in a Table view, making it easy to notice rows of interest.&amp;#160; You can independently color different columns as needed.&amp;#160; &lt;br /&gt;In the example below, each column has it’s own continuous gradient scheme to highlight the values that are close to or above quota. &lt;/p&gt;  &lt;p&gt;&lt;a href="http://spotfire.tibco.com/community/blogs/stn/colors_crosstab2_484CA711.png" target="_blank"&gt;&lt;img title="colors_crosstab2" style="border-top-width:0px;display:inline;border-left-width:0px;border-bottom-width:0px;border-right-width:0px;" height="344" alt="colors_crosstab2" src="http://spotfire.tibco.com/community/blogs/stn/colors_crosstab2_thumb_405A2611.png" width="779" border="0" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;&amp;#160;&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;Gradient Coloring and Segment Coloring&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;Gradient and Segment coloring modes are modes are used for coloring numeric ranges.&amp;#160; Gradient coloring will gradually transition colors between the “anchor” points you specify, and segment coloring lets you set one fixed color for all the values in between two specified anchor points. You can set anchor points at fixed values or dynamic values like the average or median of a value range.&amp;#160; You may add as many anchor points as you like.&amp;#160; In the example below, a quintile color scheme is created by adding an anchor point for every 20th percentile in the data.&lt;/p&gt;  &lt;p&gt;Here is a quintile gradient color scheme:&lt;/p&gt;  &lt;p&gt;&lt;a href="http://spotfire.tibco.com/community/blogs/stn/color_quintilegradient_08A9FC92.png" target="_blank"&gt;&lt;img title="color_quintilegradient" style="border-top-width:0px;display:inline;border-left-width:0px;border-bottom-width:0px;border-right-width:0px;" height="501" alt="color_quintilegradient" src="http://spotfire.tibco.com/community/blogs/stn/color_quintilegradient_thumb_657C91DF.png" width="535" border="0" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;And here is a quintile segment color scheme:&lt;/p&gt;  &lt;p&gt;&lt;a href="http://spotfire.tibco.com/community/blogs/stn/color_quintilesegment_12940F5F.png" target="_blank"&gt;&lt;img title="color_quintilesegment" style="border-top-width:0px;display:inline;border-left-width:0px;border-bottom-width:0px;border-right-width:0px;" height="501" alt="color_quintilesegment" src="http://spotfire.tibco.com/community/blogs/stn/color_quintilesegment_thumb_1D53F765.png" width="548" border="0" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;&amp;#160;&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;Unique Value Coloring and &lt;strong&gt;Custom Palettes&lt;/strong&gt;&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;You can set specific text values to a color. When you save and then re-apply this kind of color scheme, you have the choice to reassign specific colors to the same values, or simply apply the selection of colors to be reapplied in order.&amp;#160; When creating color schemes where one color is assigned per value, you can choose to simply re-apply the color selection rather than specific assignments of colors-to-values.&amp;#160; In this way you can create and use custom categorical palettes to apply to you categorical data instead of using the default Spotfire color schemes.&lt;/p&gt;  &lt;p&gt;&amp;#160;&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;Saving and Reapplying Color Schemes&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;Spotfire 3.1 includes new ways to manage and share color schemes you create.&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;Save your color schemes to the Library so others can use them&lt;/li&gt;    &lt;li&gt;When creating a new Spotfire plot, you can browse existing plots to use the color schemes you’ve already applied&lt;/li&gt;    &lt;li&gt;If a color scheme that you use frequently changes, there is an easy way to update all the plots in a file that you want to be updated to the new scheme&lt;/li&gt;    &lt;li&gt;You can still export color schemes to the file system and manage them on your own (available prior to 3.1)&lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;&amp;#160;&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;Default Schemes&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;Spotfire has a helpful set of default color schemes to select from to help you get started …&lt;/p&gt;  &lt;p&gt;&lt;a href="http://spotfire.tibco.com/community/blogs/stn/color_templates_35E62566.png" target="_blank"&gt;&lt;img title="color_templates" style="border-top-width:0px;display:inline;border-left-width:0px;border-bottom-width:0px;border-right-width:0px;" height="618" alt="color_templates" src="http://spotfire.tibco.com/community/blogs/stn/color_templates_thumb_2D1B3E7C.png" width="679" border="0" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;&amp;#160;&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;Advanced Coloring with Custom Expressions and User Controls&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;If you set coloring anchors based on custom expressions, you can tie a color scheme to one or more values that the user determines on-the-fly. In this example below, the transition point between red and blue markers is determined by the slider control at the top of the page.&amp;#160; This makes it possible for the user to adjust the colors to help certain features stand out more or less as needed. It also makes it possible for the user to adjust more than one plot at once.&amp;#160; (For more on user input controls, see the article &lt;a href="http://spotfire.tibco.com/community/blogs/stn/archive/2010/03/08/introducing-user-input-driven-analytic-applications.aspx" target="_blank"&gt;Introducing User Input Controls&lt;/a&gt; in 3.1)&lt;/p&gt;  &lt;p&gt;&lt;a href="http://spotfire.tibco.com/community/blogs/stn/color_election1_312AADB0.png" target="_blank"&gt;&lt;img title="color_election1" style="border-right:0px;border-top:0px;display:inline;border-left:0px;border-bottom:0px;" height="484" alt="color_election1" src="http://spotfire.tibco.com/community/blogs/stn/color_election1_thumb_7F12C18B.png" width="620" border="0" /&gt;&lt;/a&gt;&amp;#160;&lt;/p&gt;  &lt;p&gt;&lt;a href="http://spotfire.tibco.com/community/blogs/stn/color_election2_0E9511B4.png" target="_blank"&gt;&lt;img title="color_election2" style="border-right:0px;border-top:0px;display:inline;border-left:0px;border-bottom:0px;" height="484" alt="color_election2" src="http://spotfire.tibco.com/community/blogs/stn/color_election2_thumb_5AACCFC8.png" width="620" border="0" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;&lt;a href="http://spotfire.tibco.com/community/blogs/stn/color_election3_2C381FE3.png" target="_blank"&gt;&lt;img title="color_election3" style="border-right:0px;border-top:0px;display:inline;border-left:0px;border-bottom:0px;" height="484" alt="color_election3" src="http://spotfire.tibco.com/community/blogs/stn/color_election3_thumb_24D67D2D.png" width="620" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://spotfire.tibco.com/community/aggbug.aspx?PostID=707" width="1" height="1"&gt;</content><author><name>Brendan Gibson</name><uri>http://spotfire.tibco.com/community/members/Brendan-Gibson.aspx</uri></author><category term="What's New" scheme="http://spotfire.tibco.com/community/blogs/stn/archive/tags/What_2700_s+New/default.aspx" /><category term="3.1" scheme="http://spotfire.tibco.com/community/blogs/stn/archive/tags/3.1/default.aspx" /></entry><entry><title>Marking Improvements – Lasso Marking and Axis Marking</title><link rel="alternate" type="text/html" href="http://spotfire.tibco.com/community/blogs/stn/archive/2010/03/08/marking-improvements-lasso-marking-and-axis-marking.aspx" /><id>http://spotfire.tibco.com/community/blogs/stn/archive/2010/03/08/marking-improvements-lasso-marking-and-axis-marking.aspx</id><published>2010-03-08T23:20:27Z</published><updated>2010-03-08T23:20:27Z</updated><content type="html">&lt;p&gt;Marking in Spotfire 3.1 has two significant improvements.&amp;#160; “Marking” is what happens when you click on or drag a box around items in a Spotfire visualization.&amp;#160; Marking can be configured to show you details about the data, to show the selected data in a different visualization, to retrieve related data On-Demand, to show related values in different plots, and more.&amp;#160; So improved marking controls in 3.1 just means better ways to interact with your data.&lt;/p&gt;  &lt;p&gt;First, Lasso Marking is a way to select visualization items by drawing a freehand area where everything inside will become marked.&amp;#160; This is especially useful in the scatter plot and maps when you want to drill into collections of markers of interest, because they rarely fit neatly into the default rectangle-shaped marking areas.&amp;#160; Grab precise clusters of markers, hug fitted curves, and enjoy the control. &lt;/p&gt;  &lt;p&gt;In the sequential example below, lasso marking shows how it is possible to drill into the data points along the coastline by giving you freehand control to describe the exact marking area with your mouse.&lt;/p&gt;  &lt;p&gt;&lt;a href="http://spotfire.tibco.com/community/blogs/stn/lasso_calicoast1_79B7CB02.png" target="_blank"&gt;&lt;img title="lasso_calicoast1" style="border-top-width:0px;display:inline;border-left-width:0px;border-bottom-width:0px;border-right-width:0px;" height="359" alt="lasso_calicoast1" src="http://spotfire.tibco.com/community/blogs/stn/lasso_calicoast1_thumb_06B43BC5.png" width="546" border="0" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;&lt;a href="http://spotfire.tibco.com/community/blogs/stn/lasso_calicoast2_73959FC9.png" target="_blank"&gt;&lt;img title="lasso_calicoast2" style="border-top-width:0px;display:inline;border-left-width:0px;border-bottom-width:0px;border-right-width:0px;" height="359" alt="lasso_calicoast2" src="http://spotfire.tibco.com/community/blogs/stn/lasso_calicoast2_thumb_2B6A749E.png" width="546" border="0" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;&lt;a href="http://spotfire.tibco.com/community/blogs/stn/lasso_calicoast3_5F34FBA0.png" target="_blank"&gt;&lt;img title="lasso_calicoast3" style="border-top-width:0px;display:inline;border-left-width:0px;border-bottom-width:0px;border-right-width:0px;" height="359" alt="lasso_calicoast3" src="http://spotfire.tibco.com/community/blogs/stn/lasso_calicoast3_thumb_2784D221.png" width="546" border="0" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;&lt;a href="http://spotfire.tibco.com/community/blogs/stn/lasso_calicoast4_16A2BEE2.png" target="_blank"&gt;&lt;img title="lasso_calicoast4" style="border-top-width:0px;display:inline;border-left-width:0px;border-bottom-width:0px;border-right-width:0px;" height="359" alt="lasso_calicoast4" src="http://spotfire.tibco.com/community/blogs/stn/lasso_calicoast4_thumb_43BA3C61.png" width="546" border="0" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;&amp;#160;&lt;/p&gt;  &lt;p&gt;Axis Marking is a way to easily focus on any markers within a range of values with a quick swipe of the mouse.&amp;#160; Simply click and drag the mouse just outside the X or Y axis and you will see the marking area sweep across the plot for your selected values.&lt;/p&gt;  &lt;p&gt;In the sequential example below, axis marking makes it possible to quickly focus on all the values above $2M.&lt;/p&gt;  &lt;p&gt;&lt;a href="http://spotfire.tibco.com/community/blogs/stn/axismark_drinks1_34A5EE38.png" target="_blank"&gt;&lt;img title="axismark_drinks1" style="border-top-width:0px;display:inline;border-left-width:0px;border-bottom-width:0px;border-right-width:0px;" height="293" alt="axismark_drinks1" src="http://spotfire.tibco.com/community/blogs/stn/axismark_drinks1_thumb_545758B1.png" width="752" border="0" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;&lt;/p&gt;  &lt;p&gt;&lt;/p&gt;  &lt;p&gt;&lt;/p&gt;  &lt;p&gt;&lt;/p&gt;  &lt;p&gt;&lt;/p&gt;  &lt;p&gt;&lt;a href="http://spotfire.tibco.com/community/blogs/stn/axismark_drinks2_45AF3D7D.png" target="_blank"&gt;&lt;img title="axismark_drinks2" style="border-top-width:0px;display:inline;border-left-width:0px;border-bottom-width:0px;border-right-width:0px;" height="293" alt="axismark_drinks2" src="http://spotfire.tibco.com/community/blogs/stn/axismark_drinks2_thumb_79E5F774.png" width="752" border="0" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;&lt;a href="http://spotfire.tibco.com/community/blogs/stn/axismark_drinks3_0B58E8FE.png" target="_blank"&gt;&lt;img title="axismark_drinks3" style="border-top-width:0px;display:inline;border-left-width:0px;border-bottom-width:0px;border-right-width:0px;" height="293" alt="axismark_drinks3" src="http://spotfire.tibco.com/community/blogs/stn/axismark_drinks3_thumb_75FDC446.png" width="752" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://spotfire.tibco.com/community/aggbug.aspx?PostID=706" width="1" height="1"&gt;</content><author><name>Brendan Gibson</name><uri>http://spotfire.tibco.com/community/members/Brendan-Gibson.aspx</uri></author><category term="What's New" scheme="http://spotfire.tibco.com/community/blogs/stn/archive/tags/What_2700_s+New/default.aspx" /><category term="3.1" scheme="http://spotfire.tibco.com/community/blogs/stn/archive/tags/3.1/default.aspx" /></entry><entry><title>Improvements to K-Means Clustering and Line Similarity</title><link rel="alternate" type="text/html" href="http://spotfire.tibco.com/community/blogs/stn/archive/2010/03/08/improvements-to-k-means-clustering-and-line-similarity.aspx" /><id>http://spotfire.tibco.com/community/blogs/stn/archive/2010/03/08/improvements-to-k-means-clustering-and-line-similarity.aspx</id><published>2010-03-08T23:19:18Z</published><updated>2010-03-08T23:19:18Z</updated><content type="html">&lt;p&gt;When looking at a line chart in Spotfire you can use K-Means or Line Similarity tools to find clusters or patterns or matching trends respectively. In 3.1, Euclidian Distance has been added as an optional similarity measure to use when performing K-Means Clustering or Line Similarity calculations. Previously, K-Means and Line Similarity only supported correlation for finding patterns which meant that similarity of trends was judged based on the relative shape of the lines no matter how far apart they might have been by the Y-axis. Now that Euclidian Distance is offered, you can cluster or seek similar trends based on their nearness to one another in Y-axis space.&amp;#160; See the illustrations below based on the Line Similarity tool:&lt;/p&gt;  &lt;p&gt;We start with thousands of trends, but one particular trend is of interest and we want to find out which other trend line are most similar to the target trend.&lt;/p&gt;  &lt;p&gt;&lt;a href="http://spotfire.tibco.com/community/blogs/stn/euc_start_06E1D0BF.png" target="_blank"&gt;&lt;img title="euc_start" style="border-top-width:0px;display:inline;border-left-width:0px;border-bottom-width:0px;border-right-width:0px;" height="368" alt="euc_start" src="http://spotfire.tibco.com/community/blogs/stn/euc_start_thumb_6D102B40.png" width="594" border="0" /&gt;&lt;/a&gt;&amp;#160;&lt;/p&gt;  &lt;p&gt;&amp;#160;&lt;/p&gt;  &lt;p&gt;Go to Tools &amp;gt; Options and run the Line Similarity tool.&amp;#160; If you choose “Correlation” as a similarity metric, then similarity is judged based on the shape of the line.&amp;#160; Here is what the plot looks like after filtering down to the top 10 results using this method.&lt;/p&gt;  &lt;p&gt;&lt;a href="http://spotfire.tibco.com/community/blogs/stn/euc_corr_0C5562C5.png" target="_blank"&gt;&lt;img title="euc_corr" style="border-top-width:0px;display:inline;border-left-width:0px;border-bottom-width:0px;border-right-width:0px;" height="368" alt="euc_corr" src="http://spotfire.tibco.com/community/blogs/stn/euc_corr_thumb_4FBFF4D8.png" width="594" border="0" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;&amp;#160;&lt;/p&gt;  &lt;p&gt;But if you choose the new Euclidian Distance similarity option in 3.1 the top 10 most similar results to the target trend will be based on nearness rather than shape. You can see that the trends are not necessarily shaped exactly similar to the target trend, but they are located closely to the target trend.&lt;/p&gt;  &lt;p&gt;&lt;a href="http://spotfire.tibco.com/community/blogs/stn/euc_euc_40E47896.png" target="_blank"&gt;&lt;img title="euc_euc" style="border-right:0px;border-top:0px;display:inline;border-left:0px;border-bottom:0px;" height="367" alt="euc_euc" src="http://spotfire.tibco.com/community/blogs/stn/euc_euc_thumb_44F15719.png" width="595" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://spotfire.tibco.com/community/aggbug.aspx?PostID=705" width="1" height="1"&gt;</content><author><name>Brendan Gibson</name><uri>http://spotfire.tibco.com/community/members/Brendan-Gibson.aspx</uri></author></entry><entry><title>Introducing User Input-Driven Analytic Applications</title><link rel="alternate" type="text/html" href="http://spotfire.tibco.com/community/blogs/stn/archive/2010/03/08/introducing-user-input-driven-analytic-applications.aspx" /><id>http://spotfire.tibco.com/community/blogs/stn/archive/2010/03/08/introducing-user-input-driven-analytic-applications.aspx</id><published>2010-03-08T20:29:00Z</published><updated>2010-03-08T20:29:00Z</updated><content type="html">&lt;p&gt;New to the Spotfire 3.1 platform is the ability to add Property Controls to Text Areas.&amp;nbsp; These easy-to-configure controls allow an application builder to effectively ‘guide’ the consumer to perform more meaningful analyses, like the ability to change visualization axes through the Web Player, perform ‘what-if’ analysis and pass inputs to Spotfire Statistical Services functions &amp;amp; On-Demand Data Loading. Available Property Controls include drop-down lists, list boxes, sliders , labels and text input fields. Configuration of these controls is UI-driven, without the need for any programming skills. &lt;/p&gt;
&lt;p&gt;The process for creating a user input-drive analytic application is (in pretty much any order):&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Define a Document, Data Table or Column Property 
&lt;ul&gt;
&lt;li&gt;There are shortcuts to performing this step in either step 2 or 3 below, otherwise it can be performed in the Data Table Properties, Column Properties or Column Properties dialog boxes under the Edit menu. &lt;/li&gt;
&lt;li&gt;Scope the variable as either being a document, data table, or column property. &lt;/li&gt;
&lt;li&gt;Properties have a name, description, data type, and default value. &lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Reference the Property in a Custom Expression 
&lt;ul&gt;
&lt;li&gt;The Custom Expression dialog box has been updated in 3.1 to provide for Properties to be inserted into expressions. &lt;/li&gt;
&lt;li&gt;The context menu (usually right-click) of the properties list allows you to add new properties. &lt;/li&gt;
&lt;li&gt;Replace &lt;strong&gt;&lt;u&gt;any&lt;/u&gt;&lt;/strong&gt; valid text in an expression with the contents of a property selecting the text to be replaced and the property and then pressing Insert Properties. &lt;/li&gt;
&lt;li&gt;See what will be passed to the data engine by looking in the textbox below the “resulting expression” label. &lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Configure a Property Control in a Text Area to assign its value(s) to a Property 
&lt;ul&gt;
&lt;li&gt;Add a Text Area visualization to a page and a Property Control to the Text Area. &lt;/li&gt;
&lt;li&gt;Pick a control type. &lt;/li&gt;
&lt;li&gt;Assign its output value to an existing Document, Data Table or Column Property (or create a new one). &lt;/li&gt;
&lt;li&gt;Where appropriate, configure where the valid input values come from (list of columns, values, fixed list, numeric range). &lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ol&gt;
&lt;p&gt;The following movie gives you a quick step-by-step overview of how you would author an user input-driven analytic application that allows an end user to switch the column that is used for the axis of a visualization:&lt;/p&gt;
&lt;p&gt;&lt;iframe style="WIDTH:800px;HEIGHT:600px;" height="600" src="http://spotfire.tibco.com/community/blogs/stn/Flash/ChangeXAxisApplication.htm" frameborder="0" width="800"&gt;&lt;/iframe&gt;&lt;/p&gt;&lt;img src="http://spotfire.tibco.com/community/aggbug.aspx?PostID=704" width="1" height="1"&gt;</content><author><name>Greg Goldsmith</name><uri>http://spotfire.tibco.com/community/members/Greg-Goldsmith.aspx</uri></author><category term="What's New" scheme="http://spotfire.tibco.com/community/blogs/stn/archive/tags/What_2700_s+New/default.aspx" /><category term="3.1" scheme="http://spotfire.tibco.com/community/blogs/stn/archive/tags/3.1/default.aspx" /></entry><entry><title>Predictive Analytics with TIBCO Spotfire and S+ or R</title><link rel="alternate" type="text/html" href="http://spotfire.tibco.com/community/blogs/stn/archive/2010/03/06/predictive-analytics-with-tibco-spotfire-and-s-or-r.aspx" /><id>http://spotfire.tibco.com/community/blogs/stn/archive/2010/03/06/predictive-analytics-with-tibco-spotfire-and-s-or-r.aspx</id><published>2010-03-06T11:43:00Z</published><updated>2010-03-06T11:43:00Z</updated><content type="html">Last week we discussed an important new concept in TIBCO Spotfire version 3.1, scenario analysis. This week we will continue learning about new 3.1 features by discussing another type of analytics: Predictive Analytics. With TIBCO Spotfire version 3.1, you can communicate directly with S+ or R to execute scripts and functions. You can then return the data back into Spotfire as rows, columns, tables, and even values to properties, like Document Properties, Data Table Properties, and Column Properties...(&lt;a href="http://spotfire.tibco.com/community/blogs/stn/archive/2010/03/06/predictive-analytics-with-tibco-spotfire-and-s-or-r.aspx"&gt;read more&lt;/a&gt;)&lt;img src="http://spotfire.tibco.com/community/aggbug.aspx?PostID=699" width="1" height="1"&gt;</content><author><name>Anonymous</name><uri>http://spotfire.tibco.com/community/members/Anonymous.aspx</uri></author><category term="S+" scheme="http://spotfire.tibco.com/community/blogs/stn/archive/tags/S_2B00_/default.aspx" /><category term="Properties" scheme="http://spotfire.tibco.com/community/blogs/stn/archive/tags/Properties/default.aspx" /><category term="3.1" scheme="http://spotfire.tibco.com/community/blogs/stn/archive/tags/3.1/default.aspx" /><category term="Dat Functions" scheme="http://spotfire.tibco.com/community/blogs/stn/archive/tags/Dat+Functions/default.aspx" /><category term="Predictive Analytics" scheme="http://spotfire.tibco.com/community/blogs/stn/archive/tags/Predictive+Analytics/default.aspx" /></entry></feed>