Skip to main content
RSS feed Subscribe to feed

 

How to Create an Export Tool

This tutorial describes how to create a tool for Spotfire Professional and Spotfire Web Player that exports the active page in the analysis to an HTML page.

Overview

Export tools operate in the context of the document. They are hooked into Spotfire in manner that enables execution in TIBCO Spotfire Professional and Enterprise Player, as well as in the TIBCO Spotfire Web Player.

The instructions in the Implementation section below result in a self-contained tool. Only one instance of the tool will be created. The complete code of the tool and the Cascading Style Sheet (CSS) used for the layout of the export result are provided at the end of this tutorial. Except for the use of the Spotfire Package Builder application, there are no SDK dependecies for this simple tool.

Advanced Features

The simple tool is capable of exporting the active page by iterating over all visualizations on the page, generating images for them, and producing an HTML document that links to the images. The Spotfire SDK provides the code for additional tool features:

Prerequisites

The complete source code is distributed over three projects:

  • Spotfire SDK\Examples\Extensions\SpotfireDeveloper.HtmlPrintToolExample
    Implements the tool itself.
  • Spotfire SDK\Examples\Extensions\SpotfireDeveloper.HtmlPrintToolExampleForms
    Implements a Windows Forms view.
  • Spotfire SDK\Examples\Extensions\SpotfireDeveloper.HtmlPrintToolExampleWeb
    Implements a Web Forms view.
AddIn

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 Web Player toolbar, 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. If, for instance, password protection shall be required for exporting, an object representing the password must be yielded. This requires that, for the password object, a prompt view is registered for anything to be displayed to the user. In this tool 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:

    foreach (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());
    }
}

Expected Result

This screen shot displays an analysis exported from the TIBCO Spotfire Web Player using the code discussed above:

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