Reading time: 17 minutes and 45 seconds.

Snippets

Snippets showing what is possible to do with the API.

How to use a snippet

Each snippet listed below contains three code blocks. The first block shows the command to run to add the script with an ID and a name. For example npx @spotfire/mods-sdk add-script my-snippet-id --name my-snippet-name. The second block shows the expected entry in the scripts array of the mod manifest. The third block shows the source code of the script. Test the snippet by adding the entry to your mod manifest, then copy and paste the source code into the script file. Some snippets use APIs which require your mod to declare additional capabilities.

Snippets

Add a hierarchy column

Adds a hierarchy column created from the column names.

npx @spotfire/mods-sdk add-script add-hierarchy-column --name "Add a hierarchy column"

mod-manifest.json:

{
    "id": "add-hierarchy-column",
    "name": "Add a hierarchy column",
    "file": "build/add-hierarchy-column.js",
    "entryPoint": "addHierarchyColumn",
    "description": "Adds a hierarchy column created from the column names.",
    "parameters": [
        {
            "name": "table",
            "type": "DataTable",
            "description": "Table which contains the columns and to which the hierarchy column should be added."
        },
        { "name": "hierarchyColumnName", "type": "String", "description": "The name of the hierarchy column." },
        {
            "name": "columnNames",
            "type": "String",
            "description": "A comma separated list of column names from which to create a hierarchy column, e.g: 'column1,column2,column3'."
        }
    ]
}

src/scripts/add-hierarchy-column.ts:

const { HierarchyDefinition, HierarchyNestingMode } = Spotfire.Dxp.Data;

export function addHierarchyColumn({ table, columnNames, hierarchyColumnName }: AddHierarchyColumnParameters) {
    // Split the input string to get the names of the columns.
    const columns = columnNames.split(",");

    // Create column expressions from the names, e.g. [column1].
    const columnExpressions = columns.map(columnName => `[${columnName}]`);

    // Create a managed array required by the HierarchyDefinition constructor.
    const expressions = TypedArray.create(System.String, columnExpressions);

    // Create the definition from the hierarchy column.
    const definition = new HierarchyDefinition(HierarchyNestingMode.Nested, expressions);

    try {
        // Add the column to the table.
        console.log(
            `Creating a hierarchy column called '${hierarchyColumnName}' with the expressions '${columnExpressions}'.`
        );
        table.Columns.AddHierarchyColumn(hierarchyColumnName, definition);
    } catch (e) {
        console.log(`Creating column failed: ${e}`);
    }
}

RegisterEntryPoint(addHierarchyColumn);

Add a horizontal bar chart

Adds a bar chart visualization and configures its orientation to be horizontal.

npx @spotfire/mods-sdk add-script add-horizontal-bar-chart --name "Add a horizontal bar chart"

mod-manifest.json:

{
    "id": "add-horizontal-bar-chart",
    "name": "Add a horizontal bar chart",
    "entryPoint": "addHorizontalBarChart",
    "file": "build/add-horizontal-bar-chart.js",
    "description": "Adds a bar chart visualization and configures its orientation to be horizontal."
}

src/scripts/add-horizontal-bar-chart.ts:

const { BarChart, BarChartOrientation } = Spotfire.Dxp.Application.Visuals;

export function addHorizontalBarChart({ document }: AddHorizontalBarChartParameters) {
    const page = document.ActivePageReference ?? document.Pages.AddNew();
    const barChart = page.Visuals.AddNew(BarChart);

    // Auto configure to select axis.
    barChart.AutoConfigure();

    // Proceed with custom configuration.
    barChart.Title = "Horizontal Bar Chart";
    barChart.Orientation = BarChartOrientation.Horizontal;
}

RegisterEntryPoint(addHorizontalBarChart);

Add a shapefile to the active map chart

Adds a feature layer to the active map chart visualization using a shapefile from the library. Useful when configured as an action trigger on a map chart visualization.

Requires the following capabilities: LibraryRead

npx @spotfire/mods-sdk add-script add-shapefile-to-active-map-chart --name "Add a shapefile to the active map chart"

mod-manifest.json:

{
    "id": "add-shapefile-to-active-map-chart",
    "name": "Add a shapefile to the active map chart",
    "entryPoint": "addShapefileToActiveMapChart",
    "file": "build/add-shapefile-to-active-map-chart.js",
    "description": "Adds a feature layer to the active map chart visualization using a shapefile from the library. Useful when configured as an action trigger on a map chart visualization.",
    "parameters": [
        { "name": "shapeFileLibraryName", "type": "String", "description": "The name of the shapefile in the library." }
    ]
}

src/scripts/add-shapefile-to-active-map-chart.ts:

const { LibraryManager } = Spotfire.Dxp.Framework.Library;
const { SbdfLibraryDataSource } = Spotfire.Dxp.Data.Import;
const { VisualTypeIdentifiers } = Spotfire.Dxp.Application.Visuals;
const { MapChart, FeatureLayerVisualization } = Spotfire.Dxp.Application.Visuals.Maps;

export function addShapefileToActiveMapChart({
    document,
    application,
    shapeFileLibraryName
}: AddShapefileToActiveMapChartParameters) {
    const activeVisual = document.ActiveVisualReference;
    if (activeVisual?.TypeId !== VisualTypeIdentifiers.MapChart2) {
        throw new Error("The active visualization is not a map chart.");
    }

    const mapChart = activeVisual.As(MapChart)!;
    const libraryManager = application.GetService(LibraryManager)!;

    const shapeFiles = Array.from(libraryManager.Search(`type::sbdf ${shapeFileLibraryName}`));
    if (shapeFiles.length === 0) {
        throw new Error(`Cannot find shapefile with name '${shapeFileLibraryName}' in the library.`);
    }
    const shapeFileItem = shapeFiles[0];
    const table = document.Data.Tables.Add(shapeFileLibraryName, new SbdfLibraryDataSource(shapeFileItem));

    const featureLayer = OutParam.create(FeatureLayerVisualization);
    mapChart.Layers.AddNewFeatureLayer(table, featureLayer.out);

    // Configure the layer
    featureLayer.Title = `Feature Layer from '${shapeFileLibraryName}'`;
    featureLayer.AutoConfigure();
}

RegisterEntryPoint(addShapefileToActiveMapChart);

Embed all data tables in the analysis

Updates the save settings of all tables in the analysis to be embedded in the analysis.

npx @spotfire/mods-sdk add-script embed-data-tables --name "Embed all data tables in the analysis"

mod-manifest.json:

{
    "id": "embed-data-tables",
    "name": "Embed all data tables in the analysis",
    "file": "build/embed-data-tables.js",
    "entryPoint": "embedDataTables",
    "description": "Updates the save settings of all tables in the analysis to be embedded in the analysis."
}

src/scripts/embed-data-tables.ts:

const { DataTableSaveSettings } = Spotfire.Dxp.Data;

export function embedDataTables({ document }: EmbedDataTablesParameters) {
    // Loop through all data tables current in the analysis.
    for (const table of document.Data.Tables) {
        // Create save settins with 'useLinkedData' disabled.
        const settings = new DataTableSaveSettings(table, false, true);

        // Add the save settings to the active settings of the document.
        document.Data.SaveSettings.DataTableSettings.Add(settings);
    }
}

RegisterEntryPoint(embedDataTables);

Find all columns that match a predicate

This snippet shows how to use a predicate callback to find all time columns of a table and prints them to the debug output.

npx @spotfire/mods-sdk add-script find-columns-with-predicate --name "Find all columns that match a predicate"

mod-manifest.json:

{
    "id": "find-columns-with-predicate",
    "name": "Find all columns that match a predicate",
    "entryPoint": "findColumnsWithPredicate",
    "file": "build/find-columns-with-predicate.js",
    "description": "This snippet shows how to use a predicate callback to find all time columns of a table and prints them to the debug output.",
    "parameters": [
        { "name": "table", "type": "DataTable", "description": "The data table in which to search for columns." }
    ]
}

src/scripts/find-columns-with-predicate.ts:

const { DataColumn } = Spotfire.Dxp.Data;

export function findColumnsWithPredicate({ table }: FindColumnsWithPredicateParameters) {
    console.log(`Searching for all time columns in table '${table.Name}'.`);
    const predicate = new System.Predicate(DataColumn, dc => dc.DataType.IsTime);
    const columns = table.Columns.FindAll(predicate);

    for (const column of columns) {
        console.log(`Found time column '${column.Name}'.`);
    }
}

RegisterEntryPoint(findColumnsWithPredicate);

Insert columns to an existing data table with custom column match

Inserts columns from one table to another using a custom column match.

npx @spotfire/mods-sdk add-script insert-columns-with-column-match --name "Insert columns to an existing data table with custom column match"

mod-manifest.json:

{
    "id": "insert-columns-with-column-match",
    "name": "Insert columns to an existing data table with custom column match",
    "entryPoint": "insertColumnsWithColumnMatch",
    "file": "build/insert-columns-with-column-match.js",
    "description": "Inserts columns from one table to another using a custom column match.",
    "parameters": [
        { "name": "srcTable", "type": "DataTable", "description": "The table which holds the rows to be added." },
        { "name": "destTable", "type": "DataTable", "description": "The table to which the rows should be added." },
        {
            "name": "srcColumn",
            "type": "String",
            "description": "The name of the column in 'srcTable' which matches 'destColumn' in 'destTable'."
        },
        {
            "name": "destColumn",
            "type": "String",
            "description": "The name of the column 'destTable' which matches 'srcColumn' in 'srcTable'."
        },
        {
            "name": "ignoreColumns",
            "type": "String",
            "description": "Comma separated list of columns in 'srcTable' to ignore, e.g. col1,col2,col3."
        }
    ]
}

src/scripts/insert-columns-with-column-match.ts:

const { Dictionary, List } = System.Collections.Generic;
const { DataColumnSignature, AddColumnsSettings, JoinType, DataColumn } = Spotfire.Dxp.Data;
const { DataTableDataSource } = Spotfire.Dxp.Data.Import;

export function insertColumnsWithColumnMatch({
    srcTable,
    srcColumn,
    destTable,
    destColumn,
    ignoreColumns
}: InsertColumnsWithColumnMatchParameters) {
    const destCol = getColumnOrThrow(destTable, destColumn);
    const srcCol = getColumnOrThrow(srcTable, srcColumn);

    // The matching column.
    const map = new Dictionary(DataColumnSignature, DataColumnSignature);
    map.Add(new DataColumnSignature(destCol), new DataColumnSignature(srcCol));

    // Columns to ignore.
    const ignoredColumns = new List(DataColumnSignature);
    for (const ignoreColumn of ignoreColumns.split(",")) {
        ignoredColumns.Add(new DataColumnSignature(getColumnOrThrow(srcTable, ignoreColumn)));
    }

    const settings = new AddColumnsSettings(map, JoinType.InnerJoin, ignoredColumns);
    const dataSource = new DataTableDataSource(srcTable);
    destTable.AddColumns(dataSource, settings);
}

function getColumnOrThrow(table: Spotfire.Dxp.Data.DataTable, columnName: string) {
    const col = OutParam.create(DataColumn);
    if (!table.Columns.TryGetValue(columnName, col.out)) {
        throw new Error(`Cannot find column '${columnName}' in table '${table.Name}'.`);
    }
    return col.value;
}

RegisterEntryPoint(insertColumnsWithColumnMatch);

Read the values of a column

Shows how to read the values of data column.

npx @spotfire/mods-sdk add-script read-column-values --name "Read the values of a column"

mod-manifest.json:

{
    "id": "read-column-values",
    "name": "Read the values of a column",
    "entryPoint": "readColumnValues",
    "file": "build/read-column-values.js",
    "description": "Shows how to read the values of data column.",
    "parameters": [
        { "name": "table", "type": "DataTable", "description": "The data table to read from." },
        { "name": "column", "type": "String", "description": "The name of the column to read from." }
    ]
}

src/scripts/read-column-values.ts:

const { DataValueCursor, IndexSet, DataColumn } = Spotfire.Dxp.Data;

export function readColumnValues({ document, table, column }: ReadColumnValuesParameters) {
    const dataColumn = OutParam.create(DataColumn);
    if (!table.Columns.TryGetValue(column, dataColumn.out)) {
        throw new Error(`Data table '${table.Name}' contains no column with name '${column}'.`);
    }

    const rowsToInclude = new IndexSet(table.RowCount, true);
    const cursor = DataValueCursor.CreateFormatted(dataColumn);
    for (const row of table.GetRows(rowsToInclude, cursor)) {
        const rowIndex = row.Index;
        const value = cursor.CurrentValue;

        if (rowIndex < 10) {
            console.log(`Row ${rowIndex} = ${value}`);
        } else if (rowIndex === 10) {
            // Logging all data would be very performance intensive for large data tables.
            console.log("Skipping logging of further rows.");
        }
    }
}

RegisterEntryPoint(readColumnValues);

Remove all bookmarks from the document

Removes all bookmarks from the document.

npx @spotfire/mods-sdk add-script remove-all-bookmarks --name "Remove all bookmarks from the document"

mod-manifest.json:

{
    "id": "remove-all-bookmarks",
    "name": "Remove all bookmarks from the document",
    "entryPoint": "removeAllBookmarks",
    "file": "build/remove-all-bookmarks.js",
    "description": "Removes all bookmarks from the document."
}

src/scripts/remove-all-bookmarks.ts:

const { BookmarkManager } = Spotfire.Dxp.Application.AnalyticItems;

export function removeAllBookmarks({ application }: RemoveAllBookmarksParameters) {
    const bookmarkManager = application.GetService(BookmarkManager);
    if (bookmarkManager == null) {
        throw new Error("BookmarkManager is not available.");
    }

    for (const bookmark of bookmarkManager.GetBookmarks()) {
        console.log(`Removing bookmark with name '${bookmark.Name}'.`);
        bookmarkManager.Remove(bookmark);
    }
}

RegisterEntryPoint(removeAllBookmarks);

Remove unreferenced filtering schemes

Removes all filtering schemes which are not referenced by a visualization.

npx @spotfire/mods-sdk add-script remove-unreferenced-filtering-schemes --name "Remove unreferenced filtering schemes"

mod-manifest.json:

{
    "id": "remove-unreferenced-filtering-schemes",
    "name": "Remove unreferenced filtering schemes",
    "entryPoint": "removeUnreferencedFilteringSchemes",
    "file": "build/remove-unreferenced-filtering-schemes.js",
    "description": "Removes all filtering schemes which are not referenced by a visualization."
}

src/scripts/remove-unreferenced-filtering-schemes.ts:

const { DataFilteringSelection } = Spotfire.Dxp.Data;
const { Visualization } = Spotfire.Dxp.Application.Visuals;

export function removeUnreferencedFilteringSchemes({ document }: RemoveUnreferencedFilteringSchemesParameters) {
    const referencedFilterings: Spotfire.Dxp.Data.DataFilteringSelection[] = [];
    for (const page of document.Pages) {
        for (const visual of page.Visuals) {
            const visualization = visual.As(Visualization);
            if (visualization?.Data == null) {
                continue;
            }

            if (visualization.Data.UseActiveFiltering) {
                referencedFilterings.push(page.ActiveFilteringSelectionReference);
            }

            for (const selection of visualization.Data.Filterings) {
                const filtering = selection.TryCast(DataFilteringSelection);
                if (filtering != null) {
                    referencedFilterings.push(filtering);
                }
            }
        }
    }

    const unreferencedFilterings = Array.from(document.FilteringSchemes).filter(
        filtering => !referencedFilterings.includes(filtering.FilteringSelectionReference)
    );
    for (const filtering of unreferencedFilterings) {
        document.Data.Filterings.Remove(filtering.FilteringSelectionReference);
    }
}

RegisterEntryPoint(removeUnreferencedFilteringSchemes);

Remove unreferenced markings

Removes all markings which are not referenced by a visualization.

npx @spotfire/mods-sdk add-script remove-unreferenced-markings --name "Remove unreferenced markings"

mod-manifest.json:

{
    "id": "remove-unreferenced-markings",
    "name": "Remove unreferenced markings",
    "entryPoint": "removeUnreferencedMarkings",
    "file": "build/remove-unreferenced-markings.js",
    "description": "Removes all markings which are not referenced by a visualization."
}

src/scripts/remove-unreferenced-markings.ts:

const { DataMarkingSelection } = Spotfire.Dxp.Data;
const { Visualization } = Spotfire.Dxp.Application.Visuals;

export function removeUnreferencedMarkings({ document }: RemoveUnreferencedMarkingsParameters) {
    const referencedMarkings: Spotfire.Dxp.Data.DataMarkingSelection[] = [];
    for (const page of document.Pages) {
        for (const visual of page.Visuals) {
            const visualization = visual.As(Visualization);
            if (visualization?.Data == null) {
                continue;
            }

            if (visualization.Data.MarkingReference != null) {
                referencedMarkings.push(visualization.Data.MarkingReference);
            }

            for (const selection of visualization.Data.Filterings) {
                const marking = selection.TryCast(DataMarkingSelection);
                if (marking != null) {
                    referencedMarkings.push(marking);
                }
            }
        }
    }

    const unreferencedMarkings = Array.from(document.Data.Markings).filter(
        marking => !referencedMarkings.includes(marking)
    );
    for (const marking of unreferencedMarkings) {
        document.Data.Markings.Remove(marking);
    }
}

RegisterEntryPoint(removeUnreferencedMarkings);

Replace a data table with an SBDF file from the library

Replaces a table in the analysis with data from an SBDF file saved in the library.

Requires the following capabilities: LibraryRead

npx @spotfire/mods-sdk add-script replace-table-with-library-data --name "Replace a data table with an SBDF file from the library"

mod-manifest.json:

{
    "id": "replace-table-with-library-data",
    "name": "Replace a data table with an SBDF file from the library",
    "entryPoint": "replaceTableWithLibraryData",
    "file": "build/replace-table-with-library-data.js",
    "description": "Replaces a table in the analysis with data from an SBDF file saved in the library.",
    "parameters": [
        { "name": "libraryGuid", "type": "String", "description": "The library ID of the SBDF." },
        { "name": "table", "type": "DataTable", "description": "The data table which should be replaced." }
    ]
}

src/scripts/replace-table-with-library-data.ts:

const { Guid } = System;

const { SbdfLibraryDataSource } = Spotfire.Dxp.Data.Import;
const { LibraryManager, LibraryItem, LibraryItemType } = Spotfire.Dxp.Framework.Library;

export function replaceTableWithLibraryData({
    application,
    libraryGuid,
    table
}: ReplaceTableWithLibraryDataParameters) {
    const manager = application.GetService(LibraryManager);
    if (manager == null) {
        throw new Error("LibraryManager is unavailable.");
    }

    const guid = OutParam.create(Guid);
    if (!Guid.TryParse(libraryGuid, guid.out)) {
        throw new Error(`The provided library ID '${libraryGuid}' is not a valid GUID.`);
    }

    const item = OutParam.create(LibraryItem);
    if (!manager.TryGetItem(guid, item.out)) {
        throw new Error(`A library item with the id '${libraryGuid}' could not be found.`);
    }

    if (item.ItemType !== LibraryItemType.SbdfDataFile) {
        throw new Error("The library item is not an SBDF file.");
    }
    const ds = new SbdfLibraryDataSource(item);

    try {
        table.ReplaceData(ds);
    } catch (e) {
        throw new Error(`Failed to replace table with library data, exception: ${e}`);
    }
}

RegisterEntryPoint(replaceTableWithLibraryData);

Replace empty values of a column

Replaces all the empty values of a column with another value.

npx @spotfire/mods-sdk add-script replace-empty-values --name "Replace empty values of a column"

mod-manifest.json:

{
    "id": "replace-empty-values",
    "name": "Replace empty values of a column",
    "entryPoint": "replaceEmptyValues",
    "file": "build/replace-empty-values.js",
    "description": "Replaces all the empty values of a column with another value.",
    "parameters": [
        { "name": "table", "type": "DataTable", "description": "The table in which the column can be found." },
        { "name": "columnName", "type": "String", "description": "The name of the column." },
        { "name": "value", "type": "String", "description": "The value which should replace the (Empty) values." }
    ]
}

src/scripts/replace-empty-values.ts:

const { DataColumn, DataColumnSignature, DataType } = Spotfire.Dxp.Data;
const { ReplaceValuesTransformation } = Spotfire.Dxp.Data.Transformations;

export function replaceEmptyValues({ table, columnName, value }: ReplaceEmptyValuesParameters) {
    const column = OutParam.create(DataColumn);
    if (!table.Columns.TryGetValue(columnName, column.out)) {
        throw new Error(`Cannot find column with name '${columnName}' in table '${table.Name}'.`);
    }

    const dataOperations = Array.from(table.GenerateSourceView().OperationsSupportingTransformations);
    if (dataOperations.length === 0) {
        throw new Error(`There are no data operations for table '${table.Name}' which support transformations.`);
    }

    const dataOperation = dataOperations[0];
    const transformations = dataOperation.GetTransformations();

    // Clear existing transformations if necessary.
    transformations.Clear();

    const typedValue = OutParam.create(System.Object);
    if (!column.DataType.Formatter.TryParse(value, typedValue.out)) {
        throw new Error(`Failed to convert '${value}' to '${column.DataType.Name}'.`);
    }

    transformations.Add(
        new ReplaceValuesTransformation(new DataColumnSignature(column.Name, column.DataType), null, typedValue)
    );
    dataOperation.ReplaceTransformations(transformations);
}

RegisterEntryPoint(replaceEmptyValues);

Save the analysis to the library

Saves the current analysis to the library.

Requires the following capabilities: LibraryRead, LibraryWrite

Requires wrapInTransaction to be false.

npx @spotfire/mods-sdk add-script save-analysis-to-library --name "Save the analysis to the library"

mod-manifest.json:

{
    "id": "save-analysis-to-library",
    "name": "Save the analysis to the library",
    "entryPoint": "saveAnalysisToLibrary",
    "file": "build/save-analysis-to-library.js",
    "description": "Saves the current analysis to the library.",
    "wrapInTransaction": false,
    "parameters": [
        {
            "name": "libraryFolder",
            "type": "String",
            "description": "The path to the folder in the library where the analysis should be saved."
        },
        { "name": "title", "type": "String", "description": "The title of the analysis." }
    ]
}

src/scripts/save-analysis-to-library.ts:

const { LibraryManager, LibraryItem, LibraryItemType, LibraryItemRetrievalOption, LibraryItemMetadataSettings } =
    Spotfire.Dxp.Framework.Library;
const { DocumentSaveSettings } = Spotfire.Dxp.Application;

export function saveAnalysisToLibrary({ application, libraryFolder, title }: SaveAnalysisToLibraryParameters) {
    const libraryManager = application.GetService(LibraryManager)!;

    const folderItem = getOrCreateLibraryFolder(libraryManager, libraryFolder);
    console.log(`Saving analysis to '${joinPath(folderItem.Path, title)}'`);
    application.SaveAs(folderItem, title, new LibraryItemMetadataSettings(), new DocumentSaveSettings());
}

/**
 * Gets or creates a library folder with the given path. If it does not exist then the folder structure
 * is recursively created by splitting the path on "/".
 */
function getOrCreateLibraryFolder(libraryManager: Spotfire.Dxp.Framework.Library.LibraryManager, folderPath: string) {
    const folderItem = OutParam.create(LibraryItem);
    if (
        !libraryManager.TryGetItem(
            folderPath,
            LibraryItemType.Folder,
            folderItem.out,
            LibraryItemRetrievalOption.IncludePath
        )
    ) {
        const parentFolder = OutParam.create(LibraryItem);
        if (!libraryManager.TryGetItem("/", LibraryItemType.Folder, parentFolder.out)) {
            throw new Error("Cannot retrieve library root.");
        }

        let parent = parentFolder.value;
        const pathParts = folderPath.split("/");
        for (const part of pathParts) {
            // Skip initial "/" root if it exists.
            if (part === "") {
                continue;
            }

            const path = joinPath(parent.Path, part);
            if (
                !libraryManager.TryGetItem(
                    path,
                    LibraryItemType.Folder,
                    parentFolder.out,
                    LibraryItemRetrievalOption.IncludePath
                )
            ) {
                console.log(`Creating library folder with path '${path}'`);
                parent = libraryManager.CreateFolder(parent, part, new LibraryItemMetadataSettings());
            }
        }

        folderItem.value = parent;
    }

    return folderItem.value;
}

function joinPath(left: string, right: string) {
    return left.split("/").concat(right.split("/")).join("/").replace("//", "/");
}

RegisterEntryPoint(saveAnalysisToLibrary);

Set a custom date format for a column

Sets a custom date format for a specific column.

npx @spotfire/mods-sdk add-script set-custom-date-format --name "Set a custom date format for a column"

mod-manifest.json:

{
    "id": "set-custom-date-format",
    "name": "Set a custom date format for a column",
    "entryPoint": "setCustomDateFormat",
    "file": "build/set-custom-date-format.js",
    "description": "Sets a custom date format for a specific column.",
    "parameters": [
        { "name": "format", "type": "String", "description": "The date format, e.g. 'dd-MM-yyyy' for day/month-year." },
        {
            "name": "columnName",
            "type": "String",
            "description": "The name of the column whose date format should be set."
        },
        { "name": "table", "type": "DataTable", "description": "The data table in which the column can be found." }
    ]
}

src/scripts/set-custom-date-format.ts:

const { DataType, DataColumn } = Spotfire.Dxp.Data;
const { DateTimeFormatter } = Spotfire.Dxp.Data.Formatters;

export function setCustomDateFormat({ format, columnName, table }: SetCustomDateFormatParameters) {
    const column = OutParam.create(DataColumn);
    if (!table.Columns.TryGetValue(columnName, column.out)) {
        throw new Error(`Table '${table.Name}' does not contain a column with the name '${columnName}'.`);
    }

    if (column.Properties.DataType !== DataType.DateTime) {
        throw new Error(`The column '${columnName}' in table '${table.Name}' is not a DateTime column.`);
    }

    // We have to cast here since CreateLocalizedFormatter returns a type which is not specific enough.
    const formatter = DataType.DateTime.CreateLocalizedFormatter().Cast(DateTimeFormatter);
    formatter.FormatString = format;
    column.Properties.Formatter = formatter;
}

RegisterEntryPoint(setCustomDateFormat);

Set a document property

Sets a document property to a specific value, creating the property if it does not already exist.

npx @spotfire/mods-sdk add-script set-document-property --name "Set a document property"

mod-manifest.json:

{
    "id": "set-document-property",
    "name": "Set a document property",
    "entryPoint": "setDocumentProperty",
    "file": "build/set-document-property.js",
    "description": "Sets a document property to a specific value, creating the property if it does not already exist.",
    "parameters": [
        { "name": "propertyName", "type": "String", "description": "The name of the property." },
        { "name": "type", "type": "String", "description": "The type of the property." },
        { "name": "value", "type": "String", "description": "The value to set the document property to." }
    ]
}

src/scripts/set-document-property.ts:

const { DataType, DataProperty, DataPropertyClass } = Spotfire.Dxp.Data;

export function setDocumentProperty({ document, propertyName, type, value }: SetDocumentPropertyParameters) {
    const dataType = DataType.FromName(type);
    const typedValue = OutParam.create(System.Object);

    if (dataType.Formatter.TryParse(value, typedValue.out)) {
        const properties = document.Data.Properties;
        const property = OutParam.create(DataProperty);
        if (!properties.TryGetProperty(DataPropertyClass.Document, propertyName, property.out)) {
            property.value = DataProperty.CreateCustomPrototype(propertyName, dataType, DataProperty.DefaultAttributes);
            properties.AddProperty(DataPropertyClass.Document, property);
            console.log(
                `Document property with name '${propertyName}' does not exist. Creating it with type '${dataType.Name}'.`
            );
        }

        console.log(`Setting value of '${propertyName}' to '${dataType.Formatter.Format(typedValue)}'.`);
        property.Value = typedValue;
    } else {
        throw new Error(`Failed to parse '${value}' as type '${dataType.Name}'.`);
    }
}

RegisterEntryPoint(setDocumentProperty);

Set a projection for all layers in a mapchart

Sets a projection for all layers in a mapchart.

npx @spotfire/mods-sdk add-script set-projection-for-mapchart-layer --name "Set a projection for all layers in a mapchart"

mod-manifest.json:

{
    "id": "set-projection-for-mapchart-layer",
    "name": "Set a projection for all layers in a mapchart",
    "entryPoint": "setProjectionForMapchartLayer",
    "file": "build/set-projection-for-mapchart-layer.js",
    "description": "Sets a projection for all layers in a mapchart.",
    "parameters": [
        { "name": "visual", "type": "Visualization", "description": "The map chart visualization." },
        {
            "name": "useCustomProjection",
            "type": "Boolean",
            "description": "If a custom projection should be used instead of an EPSG one."
        }
    ]
}

src/scripts/set-projection-for-mapchart-layer.ts:

const { MapChart, Projection } = Spotfire.Dxp.Application.Visuals.Maps;

export function setProjectionForMapchartLayer({
    visual,
    useCustomProjection
}: SetProjectionForMapchartLayerParameters) {
    const mapchart = visual.As(MapChart);
    if (mapchart == null) {
        throw new Error(`Visualization '${visual.Title}' is not a map chart.`);
    }

    // Creating a projection using an EPSG code.
    const projection = new Projection("EPSG:3857");

    // Creating a projection using an proj4 definition.
    const customProjection = new Projection(
        "CustomIdentifierPrefix:1",
        "+proj=aeqd +lat_0=0 +lon_0=0 +x_0=0 +y_0=0 +units=m +datum=WGS84 +no_defs",
        "Generated CRS"
    );

    for (const layer of mapchart.Layers) {
        if (useCustomProjection) {
            layer.Projection = customProjection;
        } else {
            layer.Projection = projection;
        }
    }
}

RegisterEntryPoint(setProjectionForMapchartLayer);

Set the colors of a bar chart

Updates the categorical coloring of a bar chart visualization.

npx @spotfire/mods-sdk add-script bar-chart-coloring --name "Set the colors of a bar chart"

mod-manifest.json:

{
    "id": "bar-chart-coloring",
    "name": "Set the colors of a bar chart",
    "entryPoint": "barChartColoring",
    "file": "build/bar-chart-coloring.js",
    "description": "Updates the categorical coloring of a bar chart visualization.",
    "parameters": [
        { "name": "visual", "type": "Visualization", "description": "The bar chart visualization." },
        {
            "name": "colorMap",
            "type": "String",
            "description": "A comma separated list of key=color combinators, e.g. \"Apple=red,Banana=yellow\"."
        }
    ]
}

src/scripts/bar-chart-coloring.ts:

const { BarChart, CategoryKey, AxisMode } = Spotfire.Dxp.Application.Visuals;
const { Color } = System.Drawing;

const red = Color.FromArgb(255, 0, 0);
const blue = Color.FromArgb(0, 0, 255);
const green = Color.FromArgb(0, 255, 0);
const yellow = Color.FromArgb(255, 255, 0);

function toColor(name: string) {
    switch (name.toLowerCase()) {
        case "red":
            return red;
        case "blue":
            return blue;
        case "green":
            return green;
        case "yellow":
            return yellow;
        default:
            throw new Error(`Unknown color name '${name}'`);
    }
}

export function barChartColoring({ visual, colorMap }: BarChartColoringParameters) {
    const barChart = visual.As(BarChart);
    if (!barChart) {
        throw new Error(`The visual provided is not a bar chart visualization.`);
    }

    barChart.ColorAxis.Coloring.Clear();
    for (const keyColor of colorMap.split(",")) {
        if (!keyColor.includes("=")) {
            throw new Error(`Broken key=color pair: '${keyColor}'.`);
        }

        const [key, color] = keyColor.split("=");
        barChart.ColorAxis.Coloring.SetColorForCategory(new CategoryKey(key), toColor(color));
    }
}

RegisterEntryPoint(barChartColoring);

Set up a page layout and rotate the visualizations on consecutive runs

Creates a layout where one visualization is enlarged and all others are stacked to its the right. On consecutive runs the enlarged visualization is rotated with one of the smaller ones.

npx @spotfire/mods-sdk add-script layout-and-rotate --name "Set up a page layout and rotate the visualizations on consecutive runs"

mod-manifest.json:

{
    "id": "layout-and-rotate",
    "name": "Set up a page layout and rotate the visualizations on consecutive runs",
    "entryPoint": "layoutAndRotate",
    "file": "build/layout-and-rotate.js",
    "description": "Creates a layout where one visualization is enlarged and all others are stacked to its the right. On consecutive runs the enlarged visualization is rotated with one of the smaller ones."
}

src/scripts/layout-and-rotate.ts:

const { DataProperty, DataType, DataPropertyClass, DataPropertyAttributes } = Spotfire.Dxp.Data;
const { LayoutDefinition } = Spotfire.Dxp.Application.Layout;

export function layoutAndRotate({ document }: LayoutAndRotateParameters) {
    if (document.ActivePageReference == null) {
        throw new Error("No active page open.");
    }

    const page = document.ActivePageReference;
    const visuals = Array.from(page.Visuals);

    if (visuals.length < 2) {
        throw new Error("No or too few visualizations on the current page to properly layout.");
    }

    // Find out what index was used last rotation and increment it.
    const indexProperty = getIndexProperty();
    const nextIndex = (indexProperty.Value + 1) % visuals.length;
    const largeVisual = visuals[nextIndex];

    // Store the index for next run.
    indexProperty.Value = nextIndex;

    const layout = new LayoutDefinition();
    layout.BeginSideBySideSection();

    // Stack with a single visualization to be able to define proportion.
    layout.BeginStackedSection(70);
    layout.Add(largeVisual);
    layout.EndSection();

    // Second section containing all other visualizations stacked.
    layout.BeginStackedSection(30);
    for (const visual of visuals.filter(v => v !== largeVisual)) {
        layout.Add(visual);
    }
    layout.EndSection();

    layout.EndSection();

    // Apply the layout
    page.ApplyLayout(layout);

    /**
     * Gets the index of the visualization which was enlarged the last time this script ran.
     * @returns A property containing the index of the visualization which is currently enlarged.
     */
    function getIndexProperty(): Spotfire.Dxp.Data.DataProperty & { get Value(): number } {
        const propertyName = "script.layout.index";
        const property = OutParam.create(DataProperty);
        if (!document.Data.Properties.TryGetProperty(DataPropertyClass.Document, propertyName, property.out)) {
            // Hide the document property in the UI.
            const attributes = DataProperty.DefaultAttributes.Xor(DataPropertyAttributes.IsVisible);
            property.value = DataProperty.CreateCustomPrototype(propertyName, 0, DataType.Integer, attributes);
            document.Data.Properties.AddProperty(DataPropertyClass.Document, property);
        }

        const propertyValue = property.value;
        // Validate that the property is of the correct type.
        if (typeof propertyValue !== "number") {
            throw new Error(`Property ${propertyName} has unexpected type.`);
        }

        return propertyValue;
    }
}

RegisterEntryPoint(layoutAndRotate);

Switch cross table axis

Switches the axis expressions of a cross table visualization.

npx @spotfire/mods-sdk add-script switch-cross-table-axis --name "Switch cross table axis"

mod-manifest.json:

{
    "id": "switch-cross-table-axis",
    "name": "Switch cross table axis",
    "entryPoint": "switchCrossTableAxis",
    "file": "build/switch-cross-table-axis.js",
    "description": "Switches the axis expressions of a cross table visualization.",
    "parameters": [{ "name": "visual", "type": "Visualization", "description": "The cross table visualization." }]
}

src/scripts/switch-cross-table-axis.ts:

const { CrossTablePlot } = Spotfire.Dxp.Application.Visuals;

export function switchCrossTableAxis({ visual }: SwitchCrossTableAxisParameters) {
    const crossTable = visual.As(CrossTablePlot);

    // Throw an informative error if the user hasn't provided a visualization which is a cross table.
    if (crossTable == null) {
        throw new Error(
            `The visual provided is not a cross table, visualization type is: ${visual.TypeId.DisplayName}.`
        );
    }

    // Store the current expressions in local variables.
    const columnAxisExpression = crossTable.ColumnAxis.Expression;
    const rowAxisExpression = crossTable.RowAxis.Expression;

    // Swap them.
    crossTable.ColumnAxis.Expression = rowAxisExpression;
    crossTable.RowAxis.Expression = columnAxisExpression;
}

RegisterEntryPoint(switchCrossTableAxis);
Last modified June 5, 2024