The `<perspective-viewer>` Custom Element, frontend for Perspective.js, providing interactive analytics and data visualization.
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
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>;
}