Create a Custom Export Tool in TIBCO Spotfire®
Last updated:
12:32am May 10, 2017

Back to main C# extensions page

Introduction

Export tools operate in the context of the document. They are hooked into Spotfire in a manner that enables execution in TIBCO Spotfire Analyst, as well as in the TIBCO Spotfire Consumer and Business Author clients. This tutorial describes a simple tool that exports the active page in the analysis to an HTML page.

Prerequisites

  • TIBCO Spotfire® Developer (SDK), see download instructions here.
  • TIBCO Spotfire® Analyst, download from edelivery.tibco.com.
  • Microsoft Visual Studio® 2013 or higher. The free Community edition is available for download here.

See also

  • Create a custom tool
  • SDK examples:
    • SpotfireDeveloper.HtmlPrintToolExample - Implements the tool itself.
    • SpotfireDeveloper.HtmlPrintToolExampleForms - Implements a Windows Forms view.
    • SpotfireDeveloper.HtmlPrintToolExampleWeb -  Implements a Web Forms view.
       
      Each DLL will have its own AddIn. Note that in the Windows Forms case, both the settings dialog and the error dialog have to be registered.

Implementation

At the top a header containing the company logo and the page title should be added. At the bottom a footer with copyright information should be printed. In between the visualizations will be arranged using the layout of the page. The CSS defines the company logo location, the header text size and font, and the footer font.

Mandatory Custom Export Tool Code

Create a class that inherits from the CustomExportTool base class. There are two mandatory members:

  1. A constructor: May be empty.
  2. ExecuteCore: Contains the implementation.
public class MyCompanyExportTool : CustomExportTool
{
    public MyCompanyExportTool() : base("Create report")
    {
        // Empty
    }
 
    protected override IEnumerable<object> ExecuteCore(Document context, ExportResult result)
    {          
        // Implementation goes here ...
    }
}

 
The mandatory string passed to the constructor is the text displayed in menus and tooltips. Optional parameters to the constructor include a custom icon to be displayed in the menu, and the license required to run the tool.

The signature of the ExecuteCore method requires explanation: Note that it is supposed to return an enumerable over objects. This is achieved by the yield construct to return any object that is used as model for prompting. Use AddIn.RegisterViews to register the corresponding prompt views for any prompt models used by the tool. In this tool example no prompting is used; the active page is assumed to be exported.

The ExecuteCore method has two input parameters: The first is a reference to the document. Using the document one can access the analysis, including visualizations and data. The second argument, the result, is a container used to store any parts that should represent the output of an export.

Performing the Export Action

The implementation is added to the ExecuteCore method.

First, save a reference to the active page that is the target of the export action:

Page page = context.ActivePageReference;

 
Next, define the area withing which visualizations will be positioned:

Rectangle visualizationAreaBounds = new Rectangle(0, 0, 800, 600);

 
Then, create an HTML document:

ExportResultPart html = result.AddPart(new ContentType("text/html"));

 
The CSS file used to layout the page is included in the project as an embedded resource. When the tool is compiled it will end up in the DLL. The CSS should nevertheless be added to the result of the export:

ExportResultPart css = result.AddPart(new ContentType("text/css"));
using (StreamWriter writer = new StreamWriter(css.GetStream()))
{
    writer.WriteLine(Resources.MyCompanyExportCss);
}

 
Now write the actual HTML. Start with the HEAD section. The style sheet part is included by reference using the automatically generated URI:

using (StreamWriter writer = new StreamWriter(html.GetStream()))
{
    writer.WriteLine("<html>");
    writer.WriteLine("<head>");
    writer.WriteLine("<title>{0}</title>", page.Title);
    writer.WriteLine(
        "<link rel=\"stylesheet\" type=\"text/css\" href=\"{0}\"/>",
        css.Uri.ToString());
    writer.WriteLine("</head>");

 
The BODY section starts with the header at the top of the page. It includes the logo and the page title. Refer to the corresponding CSS class for the layout defintion.

writer.WriteLine("<body>");
writer.WriteLine("<div class=\"header\">");
writer.WriteLine("<div class=\"logo\"></div>");
writer.WriteLine("<div class=\"headerText\">{0}</span>", page.Title);
writer.WriteLine("</div>");

 
Export the visualizations in the page by iterating over the collection of visuals held by the page. The boundary of each visual is calculated and a container DIV is added and positioned according to the calculated boundary:

oreach (Visual visual in page.Visuals)
{
    Rectangle bounds = page.GetVisualBounds(visual, visualizationAreaBounds);
 
    writer.WriteLine(
        "<div class=\"visualization\" style=\"top:{0}px;left:{1}px;width:{2}px;height:{3}px;\">",
        bounds.Top, bounds.Left, bounds.Width, bounds.Height);

 
All visualizations in TIBCO Spotfire, with the exception of the text area, can be rendered as an image. In addition the API of TIBCO Spotfire 3.0 provides the ability to export text area content to HTML.

The argument to the GetHtml function is a delegate that is be called when an image is encountered during conversion (from RTF). The purpose of the delegate is to extract the image from the text area and add it to the result:

TextArea textArea = visual.As<TextArea>();
if (textArea != null)
{
    writer.WriteLine(textArea.GetHtml(
        delegate(Image image)
        {
            ExportResultPart png = result.AddPart(new ContentType("image/png"));
 
            using (Stream stream = png.GetStream())
            {
                image.Save(stream, ImageFormat.Png);
            }
 
            return png.Uri;
        }));
}

 
Otherwise, render the visualizations as PNG images of the proper sizes and add them to the result. An image tag is also appended to the HTML document, and finally, when the visual has been included, the DIV tag is closed:

    else
    {
        ExportResultPart png = result.AddPart(new ContentType("image/png"));
 
        using (Bitmap bitmap = new Bitmap(bounds.Width, bounds.Height))
        using (Graphics graphics = Graphics.FromImage(bitmap))
        {
            visual.Render(graphics, new Rectangle(Point.Empty, bitmap.Size));
 
            using (Stream stream = png.GetStream())
            {
                bitmap.Save(stream, ImageFormat.Png);
            }
        }
 
        writer.WriteLine("<img src=\"{0}\"/>", png.Uri.ToString());
    }
    writer.WriteLine("</div>");
}

 
Finally, close the visualization area, add the footer and wrap up the HTML document:

    writer.WriteLine("</div>");
 
    writer.WriteLine("<div class=\"footer\">{0}</div>",
        "Copyright 2000-2009 TIBCO Software Inc");
 
    writer.WriteLine("</body>");
    writer.WriteLine("</html>");
}
 
yield break;

 

The AddIn

Extensions are hooked into the platform using the AddIn construct. This is straightforward for an export tool:

public class MyCompanyExportToolAddIn : AddIn
{
    protected override void RegisterTools(AddIn.ToolRegistrar registrar)
    {
        base.RegisterTools(registrar);
        registrar.Register(new MyCompanyExportTool());
    }
}

 

Complete Tool Code

public class MyCompanyExportTool : CustomExportTool
{
    public MyCompanyExportTool()
        : base("Create report")
    {
        // Empty
    }
 
    protected override IEnumerable<object> ExecuteCore(Document context, ExportResult result)
    {
        // The page to export.
        Page page = context.ActivePageReference;
 
        // This is the size of the area where visualizations are shown.
        Rectangle visualizationAreaBounds = new Rectangle(0, 0, 800, 600);
 
        // Create a HTML document.
        ExportResultPart html = result.AddPart(new ContentType("text/html"));
 
        // Include the stylesheet from the embedded resource.
        ExportResultPart css = result.AddPart(new ContentType("text/css"));
        using (StreamWriter writer = new StreamWriter(css.GetStream()))
        {
            writer.WriteLine(Resources.MyCompanyExportCss);
        }
 
        // Start writing the HTML document.
        using (StreamWriter writer = new StreamWriter(html.GetStream()))
        {
            writer.WriteLine("<html>");
            writer.WriteLine("<head>");
            writer.WriteLine("<title>{0}</title>", page.Title);
 
            // Include the stylesheet in the header.
            writer.WriteLine(
                "<link rel=\"stylesheet\" type=\"text/css\" href=\"{0}\"/>",
                css.Uri.ToString());
 
            writer.WriteLine("</head>");
            writer.WriteLine("<body>");
 
            // Add the header.
            writer.WriteLine("<div class=\"header\">");
 
            // Add a div tag that will contain the logo.
            writer.WriteLine("<div class=\"logo\"></div>");
 
            // Add the current page title to the header.
            writer.WriteLine("<div class=\"headerText\">{0}</div>", page.Title);
 
            // End the header.
            writer.WriteLine("</div>");
 
            writer.WriteLine("<div class=\"visualizationArea\">");
 
            // Export all visualization from the page.
            foreach (Visual visual in page.Visuals)
            {
                // Calculate the boundaries of the plot inside the visualization area.
                Rectangle bounds = page.GetVisualBounds(visual, visualizationAreaBounds);
 
                writer.WriteLine(
                    "<div class=\"visualization\" style=\"top:{0}px;left:{1}px;width:{2}px;height:{3}px;\">",
                    bounds.Top, bounds.Left, bounds.Width, bounds.Height);
 
                TextArea textArea = visual.As<TextArea>();
                if (textArea != null)
                {
                    // The text area can't be rendered as an image. We can however
                    // directly convert it to HTML.
 
                    // The GetHtml() method takes a delegate as input that will be called
                    // when the Rtf -> Html converter detects an image. We then need
                    // add that image to the resulting export, and return its Uri
                    // so a <img> tag can be created.
 
                    writer.WriteLine(textArea.GetHtml(
                        delegate(Image image)
                        {
                            ExportResultPart png = result.AddPart(new ContentType("image/png"));
 
                            using (Stream stream = png.GetStream())
                            {
                                image.Save(stream, ImageFormat.Png);
                            }
 
                            return png.Uri;
                        }));
                }
                else
                {
                    // All other visualizations are rendered to PNG images in the
                    // below manner.
 
                    ExportResultPart png = result.AddPart(new ContentType("image/png"));
 
                    using (Bitmap bitmap = new Bitmap(bounds.Width, bounds.Height))
                    using (Graphics graphics = Graphics.FromImage(bitmap))
                    {
                        // Create the image with proper size.
                        visual.Render(graphics, new Rectangle(Point.Empty, bitmap.Size));
 
                        using (Stream stream = png.GetStream())
                        {
                            bitmap.Save(stream, ImageFormat.Png);
                        }
                    }
 
                    writer.WriteLine("<img src=\"{0}\"/>", png.Uri.ToString());
                }
 
                writer.WriteLine("</div>");
            }
 
            // End of the visualization area.
            writer.WriteLine("</div>");
 
            writer.WriteLine("<div class=\"footer\">{0}</div>",
                "Copyright 2000-2009 TIBCO Software Inc");
 
            writer.WriteLine("</body>");
            writer.WriteLine("</html>");
        }
 
        yield break;
    }
}

 

Style Sheet (CSS) for Export Result Layout

.header
{
    display: block;
    width: 800px;
    height: 74px;
}
 
.logo
{
    float: left;
    width: 164px;
    height: 74px;
    background-image: url(http://support.tibco.com/esupport/images/power/tibco_logo.gif);  
}
 
.headerText
{
    float: left;
    font-family: Verdana, Arial;
    font-size: 32px;
    line-height: 74px;
    font-weight: bold;
    vertical-align: middle;
    margin-left: 20px;
}
 
.footer
{
    display: block;
    width: 800px;
    height: 50px;
    text-align: center;
    font-family: Verdana, Arial;
}
 
.visualizationArea
{
    display: block;
    position: relative;
    width: 800px;
    height: 600px;
}
 
.visualization
{
    position: absolute;
    overflow: hidden;
}