To display a custom Spotfire visualization in the Web Player, implement a custom web control. It must be integrated with the model object of the visualization to communicate state change and receive updates.
Overview
This topic describes how to create a custom web visualization for Spotfire Web Player.
Background Information
-
Creating a Visualization
A visualization is a visual displaying data in Spotfire. A custom visualization defines a unique visualization, implementing model, view, and possibly using additional components.
-
The Web Document
The web document is a node tree on the server. When rendered, it translates the document model into a rendered web document that can be visualized in a web browser.
-
The Client Communication Engine
The Client Communication Engine enbables the client side web control to send control signals to its server side. If the signal changes any web control, the server responds with one or more change objects. The Client Communication Engine updates the client UI accordingly.
Prerequisites
Spotfire SDK\Examples\Extensions\SpotfireDeveloper.CustomVisualsExample
The project contains the code of several visualizations. This tutorial describes
the SpotfireDeveloper.CustomScatterPlot custom visualization, but also
outlines how to use the ChartFX for .NET third parthy visualization
control.
-
To implement an interactive control, the AJAX framework is required. It is not provided
by Spotfire.
Custom AJAX request/response designates communication between the client
and the server side custom ASP.NET control. The Spotfire framework does not know
about these requests. The control must implement them.
Some third-party controls, such as ChartFX for .NET, already have
AJAX support for user interaction. When developing custom ASP.NET controls, use
the AJAX framework of your own choosing.
Client Side API
On the client side the control is placed in an IFRAME element. An
IFRAME has its own JavaScript global scope. Any JavaScript files needed for
AJAX to work in the scope of the IFRAME can be included. Spotfire itself
will include the CustomVisualization.js file in the IFRAME
to integrate the control with Spotfire client framework. The file contains the following
methods:
update()
Called by the custom control to get an updated analysis from the server. Using the
Client Communication Engine it polls for updates from the server.
onInvalidated()
Invalided from the Client Communication Engine, the custom control must update itself.
Server Side API
The server API consists in the
VisualControlState
class. It holds the model node. Since the ASP.NET control is stateless, it also
holds a state preserving the data related rendering of a control.
The custom visualization receives a VisualControlState instance in its
constructor, as indicated by the Enabling a Third-Party Control
in the Web Player code example below.
Client-Server API Interaction Example
The problems solved by the APIs are best understood from the interaction between
web browser client and web server when the Web Player user opens an analysis:
- The client sends a request: Open the analysis.
- The server creates the web document and a CustomControlProxy.
The CustomControlProxy is a web document node acting as a placeholder for the ASP.NET
control in the web document:
- It invalidates this node when the model’s
RenderTrigger fires.
- Keep state data and model for the control in
VisualControlState.
- The server renders the web document to HTML.
- The CustomControlProxy web document node renders itself as an
IFRAME
element.
The SRC attribute of the IFRAME is set to the CustomVisualPage.aspx
page:<iframe src="CustomVisualPage.aspx?waid=guid&nid=guid">
- The client requests the
CustomVisualPage.aspx page from the server.
- The server creates the
CustomVisualPage.aspx page.
This stateless ASP.NET page is created for each request from the client. It displays
the custom ASP.NET control.
- The
CustomVisualPage.aspx page uses the
ViewRegistry
to create the right type of custom visualization ASP.NET control, and adds it to
the page.
- The server renders the custom ASP.NET control and returns it in the response to
the client.
- The custom ASP.NET control is disposed along with the
CustomVisualPage.aspx
page.
User Interaction Example: Marking from Action to Reaction
Assume that the custom plot has implemented a user interaction that affects the model
in some way, for instance marking. Then the following steps describe the typical
interaction flow.
- The user marks a part of the custom visualization on the client.
A custom AJAX request is sent to the custom ASP.NET control on the server.
- The custom ASP.NET control receives the request. The selected area is marked in
the model. The change in the model causes the web document to be invalidated.
- When the AJAX response is received on the client, update is called.
This call tells the Client Communication Engine to start polling for updates from
the server.
- The Client Communication Engine polls the server for updates.
- The server responds to Client Communication Engine polling by sending the updated
web document nodes to the client.
- The Client Communication Engine evaluates the updates from the server.
The CustomControlProxy script calls the onInvalidated
method.
- When
onInvalidated is called the client custom control sends an AJAX
request to refresh the custom ASP.NET control.
- The server responds with an updated custom control.
Examples
The following examples detail common development scenarios for custom web visualization
development: Enabling a third-party control in the Web Player and adding interaction
to a custom ASP.NET control.
The web server is multi-threaded, but since the custom ASP.NET control executes on
the main application thread, multi-threading is not at issue when developing custom
web player controls.
Enabling a Third-Party Control in the Web Player
This example shows how to add Web Player support for a custom visualization based
on a third-party component with its own AJAX based interaction, like the ChartFX
package, which contains both a .NET forms control and an ASP.NET web control. To
be able to view ChartFX in an analysis in Web Player, a web control inheriting from
the ASP.NET web control is implemented. This is how web control is built.
In the constructor, load the current data into the ChartFX data model and set up
the menus to add some custom interaction:
public ChartFxWebControl(VisualControlState<ChartFxVisual, object> state)
: base()
{
ColumnIdCollection = new Dictionary<int, MenuChoice>();
this.model = state.Node;
this.AxisX.Title.Text = this.model.XAxis.Name;
this.AxisY.Title.Text = this.model.YAxis.Name;
this.Data.Series = 1;
CustomizeMenus();
NewData();
this.RenderFormat = "Image";
}
Override OnLoad to include JavaScript defining callbacks for the interaction:
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
string javascript =
"function refresh() {\n" +
" SFX_SendUserCallback('" + this.ID + "','refresh',false);\n" +
"};\n" +
"customVisualization.onInvalidated = refresh;\n" +
The resetFilters callback is connected to a custom menu item during
menu setup:
"function resetFilters() {\n" +
" SFX_onCallbackReadyDelegate = function (context, result) {\n" +
" customVisualization.update();\n" +
" };\n" +
" SFX_SendUserCallback('" + this.ID + "','resetFilters',false);\n" +
" SFX_ClearLayers();" +
"};\n";
this.Page.ClientScript.RegisterClientScriptBlock(typeof(ChartFxWebControl),
"ChartFxWebControl", javascript, true);
Hook in the callbacks: On refresh reload data into ChartFX:
protected override void OnUserCallback(UserCallbackEventArgs e)
{
base.OnUserCallback(e);
if (e.Param == "refresh")
{
NewData();
}
On resetFilters reset the filters in the Spotfire document model:
else if (e.Param == "resetFilters")
{
this.model.ResetAllFilters();
}
}
Adding Interaction to a Custom ASP.NET Control
An ASP.NET control supporting interaction through AJAX can be used solve different
tasks. The following examples are covered in the SDK:
- The
SpotfireDeveloper.CustomScatterPlot implements marking interaction.

- The
SpotfireDeveloper.WebDetails developer example, which displays
a web page.
The following code snippets focus on the communication between the client and the
server. The example contains a method to make AJAX calls to the server, XmlHttp.post.
It takes four parameters: the URL, the parameter values to send to the server, and
a callback function for the response. When the user has marked an area on the client,
the marked rectangle is sent to the server with a call to . The callback
function is set to customVisualization.update. Refer to the CustomScatterPlotWebControl.js
file for the source code:
function serverMark(markMode, rectangle)
{
var values = { mark: 'mark'};
values.markMode = markMode;
values.x = rectangle.x;
values.y = rectangle.y;
values.width = rectangle.width;
values.height = rectangle.height;
XmlHttp.post(window.location.href, values, customVisualization.update);
}
On the server the custom control receives the request and selects the markers with
a call to the SelectMarkers method on the model:
if (Context.Request.Form["mark"] != null)
{
int x = int.Parse(Context.Request.Form["x"]);
int y = int.Parse(Context.Request.Form["y"]);
int width = int.Parse(Context.Request.Form["width"]);
int height = int.Parse(Context.Request.Form["height"]);
Layout layout = GetLayoutFromState();
if (layout != null)
{
state.Node.MarkArea(
new Rectangle(x, y, width, height),
GetMarkMode(Context.Request.Form["markMode"]),
layout.Plot);
}
}
On the client the function onInvalidated is called when the visualization
is invalid and should update. The client triggers a request for a new visualization
image by setting the src attribute of the image tag to a new string,
obtained by increaseImageId:
customVisualization.onInvalidated = updateImage;
// Request a new image from Server by setting image source.
function updateImage()
{
var image = document.getElementById('visualizationImage');
image.src = increaseImageId(image.src);
}
The server side ASP.NET control generates a new image:
if (Context.Request.QueryString["type"] == "image")
{
HttpContext context = this.Context;
context.Response.Clear();
context.Response.ContentType = "image/png";
// Get a render result and save the image to a stream
using (RenderResult renderResult = state.Node.Render(new Size((int)this.Width.Value, (int)this.Height.Value)))
{
using (MemoryStream memoryStream = new MemoryStream())
{
renderResult.Image.Save(memoryStream, ImageFormat.Png);
memoryStream.WriteTo(context.Response.OutputStream);
}
// Remember the layout. We will use it when marking
state.SetValue(renderResult.Layout);
}
context.Response.End();
}