Custom events dispatched by the perspective-viewer for user interactions and configuration changes.
Register event listeners for perspective-viewer custom events.
/**
* Add event listener for configuration updates
*/
addEventListener(
name: "perspective-config-update",
cb: (e: CustomEvent<ViewerConfigUpdate>) => void,
options?: { signal: AbortSignal }
): void;
/**
* Add event listener for data selection changes
*/
addEventListener(
name: "perspective-select",
cb: (e: CustomEvent<PerspectiveSelectEventDetail>) => void,
options?: { signal: AbortSignal }
): void;
/**
* Add event listener for settings panel toggle
*/
addEventListener(
name: "perspective-toggle-settings",
cb: (e: CustomEvent<boolean>) => void,
options?: { signal: AbortSignal }
): void;
/**
* Add event listener for column settings toggle
*/
addEventListener(
name: "perspective-toggle-column-settings",
cb: (e: CustomEvent<{open: boolean, column_name?: string}>) => void,
options?: { signal: AbortSignal }
): void;
/**
* Add event listener for user click events on data
*/
addEventListener(
name: "perspective-click",
cb: (e: CustomEvent<PerspectiveClickEventDetail>) => void,
options?: { signal: AbortSignal }
): void;
/**
* Remove event listeners
*/
removeEventListener(name: "perspective-config-update", cb: any): void;
removeEventListener(name: "perspective-select", cb: any): void;
removeEventListener(name: "perspective-toggle-settings", cb: any): void;
removeEventListener(name: "perspective-toggle-column-settings", cb: any): void;
removeEventListener(name: "perspective-click", cb: any): void;Fired when the viewer configuration changes through user interaction or programmatic updates.
/**
* Event detail for configuration updates
* Contains the complete ViewerConfigUpdate object
*/
interface PerspectiveConfigUpdateEvent extends CustomEvent {
detail: ViewerConfigUpdate;
}Usage:
import "@finos/perspective-viewer";
const viewer = document.createElement("perspective-viewer");
viewer.addEventListener("perspective-config-update", (event) => {
const config = event.detail;
console.log("Configuration changed:", config);
// Save configuration automatically
localStorage.setItem("viewerConfig", JSON.stringify(config));
// React to specific changes
if (config.plugin) {
console.log(`Plugin changed to: ${config.plugin}`);
}
if (config.filter) {
console.log(`Filters applied: ${config.filter.length}`);
}
});Fired when user selects data in the viewer or selection is changed programmatically.
/**
* Event detail for selection changes
*/
interface PerspectiveSelectEventDetail {
view_window: ViewWindow;
}
interface ViewWindow {
start_row: number;
end_row: number;
start_col: number;
end_col: number;
}Usage:
viewer.addEventListener("perspective-select", (event) => {
const { view_window } = event.detail;
console.log(`Selected rows: ${view_window.start_row} to ${view_window.end_row}`);
console.log(`Selected columns: ${view_window.start_col} to ${view_window.end_col}`);
// Get selected data
viewer.getView().then(async (view) => {
const selectedData = await view.to_json({
start_row: view_window.start_row,
end_row: view_window.end_row,
start_col: view_window.start_col,
end_col: view_window.end_col
});
console.log("Selected data:", selectedData);
});
});
// Clear selection
viewer.addEventListener("perspective-select", (event) => {
if (!event.detail.view_window) {
console.log("Selection cleared");
}
});Fired when user clicks on data cells or visualization elements.
/**
* Event detail for click events
* Contains clicked row data and configuration context
*/
interface PerspectiveClickEvent extends CustomEvent {
detail: PerspectiveClickEventDetail;
}Usage:
viewer.addEventListener("perspective-click", (event) => {
const { config, row, column_names } = event.detail;
console.log("Data clicked:", row);
console.log("Column names:", column_names);
console.log("Current config:", config);
// Example: Show details in a popup
showDetailsPopup(row, column_names);
// Example: Filter other viewers based on clicked data
filterOtherViewers(row);
// Example: Navigate to detail page
const id = row.id;
if (id) {
window.location.href = `/details/${id}`;
}
});
function showDetailsPopup(row: Record<string, any>, columns: Array<string | Array<string>>) {
const popup = document.createElement("div");
popup.className = "data-popup";
const content = Object.entries(row)
.map(([key, value]) => `<div><strong>${key}:</strong> ${value}</div>`)
.join("");
popup.innerHTML = `
<h3>Row Details</h3>
${content}
<button onclick="this.parentElement.remove()">Close</button>
`;
document.body.appendChild(popup);
}Fired when the settings panel is opened or closed.
/**
* Event detail for settings panel toggle
* Boolean indicates whether panel is open (true) or closed (false)
*/
interface PerspectiveToggleSettingsEvent extends CustomEvent {
detail: boolean;
}Usage:
viewer.addEventListener("perspective-toggle-settings", (event) => {
const isOpen = event.detail;
if (isOpen) {
console.log("Settings panel opened");
// Maybe adjust layout or show help text
} else {
console.log("Settings panel closed");
// Maybe hide help text or expand chart area
}
});Fired when column-specific settings panel is opened or closed.
/**
* Event detail for column settings toggle
*/
interface PerspectiveToggleColumnSettingsEvent extends CustomEvent {
detail: {
open: boolean;
column_name?: string;
};
}Usage:
viewer.addEventListener("perspective-toggle-column-settings", (event) => {
const { open, column_name } = event.detail;
if (open && column_name) {
console.log(`Column settings opened for: ${column_name}`);
// Show column-specific help or information
} else {
console.log("Column settings closed");
}
});import "@finos/perspective-viewer";
class DashboardManager {
private viewers: HTMLPerspectiveViewerElement[] = [];
constructor() {
this.setupEventHandlers();
}
addViewer(viewer: HTMLPerspectiveViewerElement) {
this.viewers.push(viewer);
this.setupViewerEvents(viewer);
}
private setupViewerEvents(viewer: HTMLPerspectiveViewerElement) {
// Sync theme changes across all viewers
viewer.addEventListener("perspective-config-update", (event) => {
const config = event.detail;
if (config.theme) {
this.synchronizeTheme(config.theme, viewer);
}
});
// Handle selection for cross-filtering
viewer.addEventListener("perspective-select", (event) => {
this.handleCrossFilter(event.detail.view_window, viewer);
});
}
private synchronizeTheme(theme: string, sourceViewer: HTMLPerspectiveViewerElement) {
this.viewers.forEach(async (viewer) => {
if (viewer !== sourceViewer) {
const config = await viewer.save();
await viewer.restore({ ...config, theme });
}
});
}
private async handleCrossFilter(selection: ViewWindow, sourceViewer: HTMLPerspectiveViewerElement) {
// Get selected data from source viewer
const sourceView = await sourceViewer.getView();
const selectedData = await sourceView.to_json({
start_row: selection.start_row,
end_row: selection.end_row
});
// Apply filters to other viewers based on selection
// (Implementation depends on your cross-filtering logic)
}
}class AutoSaveManager {
private saveTimeout: number | null = null;
private readonly SAVE_DELAY = 1000; // 1 second debounce
constructor(private viewer: HTMLPerspectiveViewerElement, private storageKey: string) {
this.setupAutoSave();
}
private setupAutoSave() {
this.viewer.addEventListener("perspective-config-update", (event) => {
// Debounce saves to avoid excessive storage writes
if (this.saveTimeout) {
clearTimeout(this.saveTimeout);
}
this.saveTimeout = window.setTimeout(() => {
this.saveConfiguration(event.detail);
}, this.SAVE_DELAY);
});
}
private saveConfiguration(config: ViewerConfigUpdate) {
try {
localStorage.setItem(this.storageKey, JSON.stringify(config));
console.log("Configuration auto-saved");
} catch (error) {
console.error("Failed to save configuration:", error);
}
}
async loadConfiguration(): Promise<boolean> {
try {
const saved = localStorage.getItem(this.storageKey);
if (saved) {
const config = JSON.parse(saved);
await this.viewer.restore(config);
return true;
}
} catch (error) {
console.error("Failed to load configuration:", error);
}
return false;
}
}
// Usage
const viewer = document.createElement("perspective-viewer");
const autoSave = new AutoSaveManager(viewer, "dashboard-config");
// Load saved configuration on startup
autoSave.loadConfiguration();class SelectionHandler {
constructor(private viewer: HTMLPerspectiveViewerElement) {
this.setupSelectionHandler();
}
private setupSelectionHandler() {
this.viewer.addEventListener("perspective-select", async (event) => {
const { view_window } = event.detail;
if (view_window) {
await this.handleSelection(view_window);
} else {
this.clearSelection();
}
});
}
private async handleSelection(selection: ViewWindow) {
const view = await this.viewer.getView();
// Get selected data
const selectedData = await view.to_json({
start_row: selection.start_row,
end_row: selection.end_row,
start_col: selection.start_col,
end_col: selection.end_col
});
// Show selection details
this.showSelectionDetails(selectedData, selection);
// Enable selection-based actions
this.enableSelectionActions(selectedData);
}
private showSelectionDetails(data: any[], selection: ViewWindow) {
const rowCount = selection.end_row - selection.start_row + 1;
const colCount = selection.end_col - selection.start_col + 1;
console.log(`Selected ${rowCount} rows and ${colCount} columns`);
console.log("Selected data:", data);
// Update UI to show selection info
document.getElementById("selection-info")!.textContent =
`${rowCount} rows × ${colCount} columns selected`;
}
private enableSelectionActions(data: any[]) {
// Enable export selected data button
const exportBtn = document.getElementById("export-selection") as HTMLButtonElement;
exportBtn.disabled = false;
exportBtn.onclick = () => this.exportSelection(data);
// Enable other selection-based actions
this.setupDetailView(data);
}
private clearSelection() {
document.getElementById("selection-info")!.textContent = "No selection";
(document.getElementById("export-selection") as HTMLButtonElement).disabled = true;
}
private exportSelection(data: any[]) {
const csv = this.convertToCSV(data);
this.downloadCSV(csv, "selection.csv");
}
private convertToCSV(data: any[]): string {
if (data.length === 0) return "";
const headers = Object.keys(data[0]);
const csvContent = [
headers.join(","),
...data.map(row => headers.map(header => `"${row[header]}"`).join(","))
].join("\n");
return csvContent;
}
private downloadCSV(content: string, filename: string) {
const blob = new Blob([content], { type: "text/csv" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = filename;
a.click();
URL.revokeObjectURL(url);
}
private setupDetailView(data: any[]) {
// Create detailed view of selected data
// (Implementation depends on your UI framework)
}
}class EventManager {
private abortController = new AbortController();
constructor(private viewer: HTMLPerspectiveViewerElement) {
this.setupEvents();
}
private setupEvents() {
const { signal } = this.abortController;
// All event listeners will be automatically removed when aborted
this.viewer.addEventListener("perspective-config-update", (event) => {
this.handleConfigUpdate(event.detail);
}, { signal });
this.viewer.addEventListener("perspective-select", (event) => {
this.handleSelection(event.detail);
}, { signal });
this.viewer.addEventListener("perspective-toggle-settings", (event) => {
this.handleSettingsToggle(event.detail);
}, { signal });
}
private handleConfigUpdate(config: ViewerConfigUpdate) {
// Handle configuration updates
}
private handleSelection(detail: PerspectiveSelectEventDetail) {
// Handle selection changes
}
private handleSettingsToggle(isOpen: boolean) {
// Handle settings panel toggle
}
// Clean up all event listeners
destroy() {
this.abortController.abort();
}
}
// Usage
const viewer = document.createElement("perspective-viewer");
const eventManager = new EventManager(viewer);
// Clean up when component unmounts
window.addEventListener("beforeunload", () => {
eventManager.destroy();
});// All event detail types
type PerspectiveConfigUpdateEvent = CustomEvent<ViewerConfigUpdate>;
type PerspectiveSelectEvent = CustomEvent<PerspectiveSelectEventDetail>;
type PerspectiveClickEvent = CustomEvent<PerspectiveClickEventDetail>;
type PerspectiveToggleSettingsEvent = CustomEvent<boolean>;
type PerspectiveToggleColumnSettingsEvent = CustomEvent<{
open: boolean;
column_name?: string;
}>;
// Event detail interfaces
interface PerspectiveClickEventDetail {
config: ViewerConfigUpdate;
row: Record<string, any>;
column_names: Array<string | Array<string>>;
}
// Event listener type helpers
type ConfigUpdateListener = (event: PerspectiveConfigUpdateEvent) => void;
type SelectListener = (event: PerspectiveSelectEvent) => void;
type ClickListener = (event: PerspectiveClickEvent) => void;
type SettingsToggleListener = (event: PerspectiveToggleSettingsEvent) => void;
type ColumnSettingsToggleListener = (event: PerspectiveToggleColumnSettingsEvent) => void;