ProofWidgets for Lean 4
repository·main·Indexed 42 days ago
https://github.com/leanprover-community/proofwidgets4A high-level user interface component library for Lean 4 designed for symbolic visualizations, data plotting, and interactive tactic interfaces. It extends Lean's native user widgets with abstractions for HTML-like syntax via a Jsx module, as well as specialized components such as ForceGraphDisplay for D3 force-directed simulations, GraphvizDisplay for DOT diagrams, and PenroseCanvas for interactive diagrams.
What's inside ProofWidgets
- ProofWidgets is a high-level component library built on top of the native user widgets mechanism in Lean 4. While Lean's core user widgets provide the minimal functionality required for custom interfaces, ProofWidgets provides higher-level abstractions, syntax sugar, and a library of reusable UI components for symbolic visualizations, data visualization, and tactic interfaces.
Build ProofWidgets from source
mainIf you are developing ProofWidgets or need to build it from source, you must have NPM installed. The build process compiles TypeScript code via NPM and then builds the Lean modules. The
lakefile.leanmanages this orchestration.- Full build:
lake build(compiles TypeScript then Lean) - TypeScript only:
lake build widgetJsAll - Development mode (TypeScript):
lake build widgetJsAllDev(makes widgets easier to inspect in developer tools)
Note: Due to a known Lake issue, modifying any single TypeScript source will trigger a rebuild of all widget sources.
# Build the entire package lake build # Build only the TypeScript components lake build widgetJsAll # Build TypeScript in development mode lake build widgetJsAllDev- Full build:
View ProofWidgets demos
mainTo view the built-in demos, clone the repository and build it using
lake. The demos are located inProofWidgets/Demos/.Tip: Use the pushpin icon in your editor to keep a widget in view while you live-code your own widgets.
git clone https://github.com/leanprover-community/ProofWidgets4 --depth 1 cd ProofWidgets4/ lake buildAdd ProofWidgets as a dependency in Lake
mainTo use ProofWidgets in your own Lean project, add it to your
lakefile.lean. It is recommended to reference a specific release tag rather than themainbranch to ensure compatibility with your Lean toolchain.By default, the repository includes pre-built JavaScript files in
widget/js/, so you and your users typically do not need to install NPM. If you want to prevent the build from attempting to compile TypeScript and instead force a failure with a custom error message, you can use thewithclause in your requirement.-- Standard usage: Replace v0.0.3 with a version published under Releases require "leanprover-community" / "proofwidgets" @ git "v0.0.3" -- Force failure with a custom error message instead of a full build require "leanprover-community" / "proofwidgets" @ git "v0.0.3" with NameMap.empty.insert `errorOnBuild "<my message>"Configure Graphviz interactivity and attributes
mainYou can extend the interactivity and visual styling of the rendered Graphviz diagram using the
attributerand event handler props. These are intended to be set by JavaScript callers rather than through the Lean interface.Customizing Attributes
Use the
attributerprop to modify how Graphviz attributes are applied to elements. This function is called byd3-graphvizduring the rendering process.Node Interactivity
The component supports three primary pointer events on nodes, edges, and clusters:
onClickNode: Triggered onclick.onPointerEnterNode: Triggered onpointerenter.onPointerLeaveNode: Triggered onpointerleave.
Warning: All three of these props must be memoized (e.g., using
useCallback) to avoid infinite re-render loops.Use JSX-like syntax for HTML in Lean
mainProofWidgets provides a
Jsxscoped module that allows you to write HTML-like syntax directly within Lean code using the#htmlmacro.import ProofWidgets.Component.HtmlDisplay open scoped ProofWidgets.Jsx -- click on the line below to see it in your infoview! #html <b>You can use HTML in Lean {.text s!"{1 + 3}"}!</b>Use the PenroseCanvas component to render diagrams
mainThe
PenroseCanvascomponent renders interactive Penrose diagrams. It supports embedding React nodes (like custom UI or math expressions) directly into the diagram at specific locations defined in the Penrose substance program.Requirements for Embedding Nodes
To successfully embed a React node using the
embedNodesprop:- Substance Program: The object must exist in your Penrose substance program.
- Style Program: The object must be assigned a
name.textBox : Rectanglefield in your Penrose style program. The component uses this field to determine the dimensions and position for the embedded React node. - Positioning: The component automatically calculates the offset of the named element and positions the React node absolutely over the
textBoxarea.
Props
trio: APenroseTrioobject containing thedsl,sty, andsubstrings.maxOptSteps: A number representing the maximum number of optimization steps allowed. This prevents the optimizer from running indefinitely if a diagram fails to converge.embedNodes: AMap<string, React.ReactNode>where the key is the name of the object in the Penrose program and the value is the React element to render at that location.
import { PenroseCanvas } from './path/to/penroseCanvas'; // Example usage <PenroseCanvas trio={{ dsl: 'domain code...', sty: 'style code with name.textBox : Rectangle...', sub: 'substance code...' }} maxOptSteps={100} embedNodes={new Map([ ['myObjectName', <div className="custom-widget">Hello World</div>] ])} />Use the ForceGraphDisplay component
mainThe
ForceGraphDisplaycomponent is a React component used to visualize a graph using a force-directed simulation (powered by D3). It supports interactive elements like dragging vertices, zooming, panning, and clicking elements to show details.To use it, provide a
Graphobject containingverticesandedges, along with configuration for the simulation forces.import ForceGraphDisplay from './forceGraphDisplay'; // Define your graph data const vertices = [ { id: 'v1', label: { text: 'Node 1' }, boundingShape: { circle: { radius: 10 } } }, { id: 'v2', label: { text: 'Node 2' }, boundingShape: { circle: { radius: 10 } } } ]; const edges = [ { source: 'v1', target: 'v2', attrs: [['stroke', 'black']] } ]; // Define simulation forces const forces = [ { center: { x: 0, y: 0, strength: 0.1 } }, { manyBody: { strength: -30 } }, { link: { distance: 50 } } ]; // Render the component <ForceGraphDisplay vertices={vertices} edges={edges} defaultEdgeAttrs={[['stroke', 'gray']]} forces={forces} showDetails={true} />Use the GraphvizDisplay component
mainThe
GraphvizDisplaycomponent is a React component used to render Graphviz diagrams usingd3-graphviz. It accepts DOT syntax strings and provides options for layout, sizing, and interactivity.Important Performance Note: To prevent unnecessary re-renders and layout thrashing, you must memoize the
options,attributer, and event handler props (onClickNode,onPointerEnterNode,onPointerLeaveNode). If these references change on every render, the component will re-render the entire graph.Props Reference
Prop Type Description dotstringThe DOT source string for the graph. optionsGraphvizOptionsConfiguration for d3-graphviz(e.g., width, height).renderDebounceMsnumberTime in milliseconds to debounce the rendering process. centerOnVertexstring(optional)The ID/key of a vertex to center the view on after rendering. attributerd3.ValueFn(optional)A function to customize graph attributes. Must be memoized. onClickNode(ev: PointerEvent, d: unknown) => void(optional)Callback triggered when a node, edge, or cluster is clicked. onPointerEnterNode(ev: PointerEvent, d: unknown) => void(optional)Callback triggered on pointer enter. onPointerLeaveNode(ev: PointerEvent, d: unknown) => void(optional)Callback triggered on pointer leave. styleReact.CSSPropertiesStandard React style props. import GraphvizDisplay from './GraphvizDisplay'; // Example usage with memoized props const options = React.useMemo(() => ({ width: 800, height: 600 }), []); const onClick = React.useCallback((ev, d) => console.log('Clicked:', d), []); function MyGraph() { return ( <GraphvizDisplay dot="digraph { A -> B }" options={options} renderDebounceMs={100} onClickNode={onClick} /> ); }Render HTML content with renderHtml
mainUse
renderHtmlto processHtmlobjects into a renderable format. The library also exportsHtmlDisplayas a default way to handle HTML-based displays.import { renderHtml, HtmlDisplay } from '@leanprover-community/proofwidgets4'; // Usage depends on the Html object structure provided by the library const rendered = renderHtml(htmlObject);Define Vertex and Edge data structures
mainWhen providing data to
ForceGraphDisplay, follow these interface structures:Vertex
interface Vertex { id: string label: Html // Uses the project's Html type for rendering boundingShape: | { circle: { radius: number } } | { rect: { width: number, height: number } } details?: Html // Content shown when clicked if showDetails is true }Edge
interface Edge { source: string // Vertex ID target: string // Vertex ID attrs: [string, any][] // Array of [attributeName, value] pairs (e.g., [['stroke', 'red']]) label?: Html details?: Html }Reference ForceParams for simulation configuration
mainThe
forcesprop accepts an array of objects, where each object defines a specific D3 force type. Supported force types include:center:{ x?: number, y?: number, strength?: number }- Positions the center of the simulation.collide:{ radius?: number, strength?: number, iterations?: number }- Prevents nodes from overlapping.link:{ distance?: number, strength?: number, iterations?: number }- Constrains edges to a specific distance.manyBody:{ strength?: number, theta?: number, distanceMin?: number, distanceMax?: number }- Simulates electrostatic forces (repulsion/attraction).x:{ x: number, strength?: number }- Forces nodes toward a specific X coordinate.y:{ y: number, strength?: number }- Forces nodes toward a specific Y coordinate.radial:{ radius: number, x?: number, y?: number, strength?: number }- Forces nodes into a radial pattern.
// Example of multiple forces const forces = [ { center: { x: 0, y: 0 } }, { manyBody: { strength: -100 } }, { collide: { radius: 20 } }, { link: { distance: 100 } } ];