Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Embedded analytics in a web application

Embedded analytics means giving your application’s users a way to explore their data inside your product — not a static chart you designed, and not a link out to a separate BI tool. <perspective-viewer> is a component built for that job.

  • A Web Component, not a platform. It is one Custom Element with no framework dependency. It works in plain HTML and in React (via @perspective-dev/react), Vue, Svelte and Angular through standard DOM APIs. There is no server to deploy unless you want one, and no iframe.
  • Self-service by default. Users group, pivot, filter, sort, write computed columns and switch between data grid, charts and maps themselves.
  • State is JSON. save() and restore() round-trip the entire configuration, so “saved views”, shareable links and per-user defaults are a database column, not a feature to build.
  • Multi-panel dashboards. One element can hold a tabbed, split layout of many panels with cross-panel global filters, saved and restored with saveWorkspace() and restoreWorkspace().
  • Your brand. Themes are CSS custom properties; several light and dark themes are included.
  • Your data path. Load data in the browser, replicate it from your server, virtualize it server-side, or point it at your database.
  • Apache-2.0. No per-seat or per-deployment licensing, and no feature tier: pivoting, charts and server-side virtualization are all open source.

React

import * as React from "react";
import perspective from "@perspective-dev/client";
import { PerspectiveViewer } from "@perspective-dev/react";

const worker = await perspective.worker();
const table = worker.table(
    fetch("/api/orders.arrow").then((resp) => resp.arrayBuffer()),
);

export function OrdersReport({ saved, onChange }) {
    return (
        <PerspectiveViewer
            client={table}
            config={saved ?? { plugin: "Datagrid", group_by: ["Region"] }}
            onConfigUpdate={onChange}
        />
    );
}

WebAssembly initialization for your bundler is covered in Importing with or without a bundler; with Next.js, load the component client-side only (ssr: false).

Plain JavaScript

const viewer = document.querySelector("perspective-viewer");
await viewer.load(table);
await viewer.restore(await loadSavedViewFor(user));

viewer.addEventListener("perspective-config-update", async () => {
    await persistSavedViewFor(user, await viewer.save());
});

Constraining what users can do

restore() sets the starting point; users can change anything from there. To lock an embedded report down, hide the configuration UI with the settings config field and drive the element only from your own controls. Row-level security belongs on the server: host a filtered View, or a virtual server bound to a restricted database role, rather than relying on a client-side filter.

An assistant in the box

<perspective-viewer> includes an opt-in LLM agent which lets users ask for a view in plain language.