Create custom visualization plugins for Perspective Viewer using the plugin interface and base class.
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>;
}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>;
}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>;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");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");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
}
}HTMLPerspectiveViewerPluginElementcustomElements.define()registerPlugin()draw() called for initial renderupdate() called for data changesresize() called for dimension changesrestyle() called for style changessave()/restore() called for persistencedelete() called for cleanupdraw() and update()update() that avoids full re-renderingsave() - avoid large objectsdelete()resize() for responsive behaviorasync 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();
}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
}));
}
}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>;
}