or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

configuration.mdcore-element.mdevents.mdindex.mdplugin-development.mdwasm-init.md
tile.json

plugin-development.mddocs/

Plugin Development

Create custom visualization plugins for Perspective Viewer using the plugin interface and base class.

Capabilities

Plugin Interface

The core interface that all perspective-viewer plugins must implement.

/**
 * The IPerspectiveViewerPlugin interface defines the necessary API for a
 * perspective-viewer plugin, which must also be an HTMLElement
 */
interface IPerspectiveViewerPlugin {
  /**
   * The name for this plugin, used as unique key and display name
   */
  readonly name: string;

  /**
   * Select mode determines column add/remove button behavior
   * "select" mode exclusively selects added column, removing others
   * "toggle" mode toggles column on/off, leaving existing columns alone
   */
  readonly select_mode?: "select" | "toggle";

  /**
   * The minimum number of columns required for this plugin to operate
   * Affects drag/drop and column remove button behavior
   */
  readonly min_config_columns?: number;

  /**
   * Named column labels for drag/drop behavior
   * If provided, length must be >= min_config_columns
   */
  readonly config_column_names?: string[];

  /**
   * Load priority of the plugin (higher number = higher priority)
   * Plugin with highest priority loads by default
   */
  readonly priority?: number;

  /**
   * Determines whether to render column styles in settings sidebar
   */
  can_render_column_styles?(view_type: string, group: string): boolean;

  /**
   * Determines which column configuration controls are populated
   */
  column_style_config?(view_type: string, group: string): any;

  /**
   * Render this plugin using the provided View
   */
  draw(view: View): Promise<void>;

  /**
   * Update assuming ViewConfig unchanged but underlying data changed
   * Defaults to dispatch to draw()
   */
  update(view: View): Promise<void>;

  /**
   * Clear this plugin contents
   */
  clear(): Promise<void>;

  /**
   * Handle dimension changes when underlying data unchanged
   */
  resize(): Promise<void>;

  /**
   * Handle style environment changes
   */
  restyle(): Promise<void>;

  /**
   * Save plugin state to JSON-serializable value
   * Should work reciprocally with restore()
   */
  save(): Promise<any>;

  /**
   * Restore plugin to state previously returned by save()
   */
  restore(config: any): Promise<void>;

  /**
   * Free resources and prepare to be deleted
   */
  delete(): Promise<void>;
}

Plugin Base Class

The default plugin implementation that can be extended to create custom plugins.

/**
 * Base perspective plugin element with default implementations
 * Extend this class to create custom plugins
 */
class HTMLPerspectiveViewerPluginElement extends HTMLElement 
  implements IPerspectiveViewerPlugin {
  
  constructor();

  get name(): string;
  get select_mode(): "select" | "toggle";
  get min_config_columns(): number | undefined;
  get config_column_names(): string[] | undefined;
  get priority(): number;

  can_render_column_styles(): boolean;
  column_style_config(): any;

  draw(view: View): Promise<void>;
  update(view: View): Promise<void>;
  clear(): Promise<void>;
  resize(): Promise<void>;
  restyle(): Promise<void>;
  save(): Promise<any>;
  restore(config: any): Promise<void>;
  delete(): Promise<void>;
}

Plugin Registration

Register plugins globally for use with perspective-viewer instances.

/**
 * Register a plugin globally via its custom element name
 * Called automatically when importing plugin modules
 */
PerspectiveViewerElement.registerPlugin(name: string): Promise<void>;

Plugin Development Examples

Basic Plugin

import { HTMLPerspectiveViewerPluginElement } from "@finos/perspective-viewer";

class MyPlugin extends HTMLPerspectiveViewerPluginElement {
  get name() {
    return "My Plugin";
  }

  get priority() {
    return 1;
  }

  async draw(view) {
    const count = await view.num_rows();
    this.innerHTML = `<div>View has ${count} rows</div>`;
  }

  async clear() {
    this.innerHTML = "";
  }
}

// Register the plugin
customElements.define("my-plugin", MyPlugin);
const Viewer = customElements.get("perspective-viewer");
Viewer.registerPlugin("my-plugin");

Advanced Plugin with Configuration

import { HTMLPerspectiveViewerPluginElement } from "@finos/perspective-viewer";

class ChartPlugin extends HTMLPerspectiveViewerPluginElement {
  get name() {
    return "Chart Plugin";
  }

  get select_mode() {
    return "toggle";
  }

  get min_config_columns() {
    return 2;
  }

  get config_column_names() {
    return ["X Axis", "Y Axis"];
  }

  get priority() {
    return 5;
  }

  can_render_column_styles(view_type, group) {
    return group === "Y Axis";
  }

  column_style_config(view_type, group) {
    return {
      color: { type: "color" },
      style: { type: "select", options: ["solid", "dashed"] }
    };
  }

  async draw(view) {
    const data = await view.to_json();
    // Custom chart rendering logic
    this.renderChart(data);
  }

  async update(view) {
    // Optimized update for data changes
    const data = await view.to_json();
    this.updateChart(data);
  }

  async resize() {
    // Handle container size changes
    this.resizeChart();
  }

  async save() {
    return {
      chartType: this.chartType,
      colors: this.colors,
      // Other plugin-specific state
    };
  }

  async restore(config) {
    this.chartType = config.chartType;
    this.colors = config.colors;
    // Restore plugin-specific state
  }

  private renderChart(data) {
    // Chart rendering implementation
  }

  private updateChart(data) {
    // Chart update implementation
  }

  private resizeChart() {
    // Chart resize implementation
  }
}

customElements.define("chart-plugin", ChartPlugin);
const Viewer = customElements.get("perspective-viewer");
Viewer.registerPlugin("chart-plugin");

Plugin with Column Style Integration

class StyledPlugin extends HTMLPerspectiveViewerPluginElement {
  get name() {
    return "Styled Plugin";
  }

  can_render_column_styles(view_type, group) {
    // Enable column styling for specific groups
    return ["Value", "Category"].includes(group);
  }

  column_style_config(view_type, group) {
    if (group === "Value") {
      return {
        color: { type: "color", default: "#1f77b4" },
        format: { 
          type: "select", 
          options: ["number", "currency", "percentage"] 
        },
        precision: { type: "number", min: 0, max: 10 }
      };
    }
    
    if (group === "Category") {
      return {
        colorMap: { type: "colorMap" },
        showLabels: { type: "boolean", default: true }
      };
    }
  }

  async draw(view) {
    const data = await view.to_json();
    const config = this.getColumnStyleConfig();
    
    // Use styling configuration in rendering
    this.renderWithStyles(data, config);
  }

  private getColumnStyleConfig() {
    // Access column styling set by user
    // This is automatically managed by the viewer
    return this.parentElement?.getColumnConfig?.() || {};
  }

  private renderWithStyles(data, styles) {
    // Implement rendering that uses the style configuration
  }
}

Plugin Lifecycle

Registration Phase

  1. Define custom element class extending HTMLPerspectiveViewerPluginElement
  2. Register with customElements.define()
  3. Register plugin with perspective-viewer using registerPlugin()

Runtime Phase

  1. Plugin instantiated when selected by user or via configuration
  2. draw() called for initial render
  3. update() called for data changes
  4. resize() called for dimension changes
  5. restyle() called for style changes
  6. save()/restore() called for persistence
  7. delete() called for cleanup

Best Practices

  • Always call parent implementation for lifecycle methods if overriding
  • Handle errors gracefully in draw() and update()
  • Implement efficient update() that avoids full re-rendering
  • Store minimal state in save() - avoid large objects
  • Clean up resources properly in delete()
  • Use resize() for responsive behavior
  • Leverage column styling system for user customization

Plugin API Integration

Working with Views

async draw(view) {
  // Get data in various formats
  const json = await view.to_json();
  const csv = await view.to_csv(); 
  const arrow = await view.to_arrow();
  
  // Get metadata
  const schema = await view.schema();
  const config = await view.get_config();
  const numRows = await view.num_rows();
}

Event Handling

class InteractivePlugin extends HTMLPerspectiveViewerPluginElement {
  async draw(view) {
    // Render with click handlers
    this.innerHTML = `<div class="chart" onclick="this.handleClick(event)"></div>`;
  }

  handleClick(event) {
    // Dispatch custom events to parent viewer
    this.dispatchEvent(new CustomEvent("perspective-plugin-click", {
      detail: { /* event data */ },
      bubbles: true
    }));
  }
}

Types

interface View {
  to_json(): Promise<any[]>;
  to_csv(): Promise<string>;
  to_arrow(): Promise<ArrayBuffer>;
  schema(): Promise<Record<string, string>>;
  get_config(): Promise<ViewConfig>;
  num_rows(): Promise<number>;
  num_columns(): Promise<number>;
}

interface ViewConfig {
  group_by?: string[];
  split_by?: string[];
  columns?: string[];
  filter?: Filter[];
  sort?: Sort[];
  expressions?: Record<string, string>;
  aggregates?: Record<string, string>;
}