Description
Summary:
Introduce a new plotting widget that renders visualizations using [Mosaic](https://github.com/uwdata/mosaic), a high-performance declarative visualization grammar. This will expand Preswald’s charting capabilities beyond Plotly, allowing for more expressive, scalable, and Vega-Lite-compatible visualizations.
📌 Motivation
Preswald currently supports plotly()
for visualizations. While excellent for general-purpose charts, Plotly lacks native support for declarative grammar like Vega-Lite and high-dimensional faceting. Mosaic (from UW IDL) fills this gap, enabling developers to declaratively define richly interactive and faceted plots.
Adding Mosaic support would:
- Unlock new visualization types (e.g., repeat/facet, grid layout, split axes).
- Align with Vega/Vega-Lite standards.
- Improve performance for large datasets via GPU/WebGL rendering.
- Attract data scientists and researchers familiar with Observable and Vega-Lite.
🧱 Proposed Implementation
1. Backend API (preswald/interfaces/components.py
)
Add a new interface function:
def mosaic(
label: str,
spec: dict,
size: float = 1.0,
) -> str:
"""Render a Mosaic visualization based on a Vega-Lite-compatible spec"""
from preswald.service import PreswaldService
import hashlib
service = PreswaldService.get_instance()
component_id = f"mosaic-{hashlib.md5(label.encode()).hexdigest()[:8]}"
component = {
"type": "mosaic",
"id": component_id,
"label": label,
"spec": spec,
"size": size,
}
service.append_component(component)
return f"Mosaic: {label}"
2. Component Exposure (preswald/interfaces/__init__.py
)
from .components import mosaic
3. Frontend Widget (frontend/src/components/widgets/MosaicWidget.jsx
)
import React, { useEffect, useRef } from 'react';
import { Card } from '@/components/ui/card';
import { cn } from '@/lib/utils';
import { mosaic } from '@uwdata/mosaic-vega';
const MosaicWidget = ({ _label, spec, className, _size }) => {
const containerRef = useRef(null);
useEffect(() => {
if (containerRef.current && spec) {
mosaic(containerRef.current, spec);
}
}, [spec]);
return (
<Card className={cn('w-full overflow-x-auto', className)}>
<div ref={containerRef}></div>
</Card>
);
};
export default MosaicWidget;
Note: Add
@uwdata/mosaic-vega
tofrontend/package.json
.
4. Dynamic Registration (frontend/src/components/DynamicComponents.jsx
)
import MosaicWidget from '@/components/widgets/MosaicWidget';
case 'mosaic':
return (
<MosaicWidget
{...commonProps}
className={component.className}
_size={component.size || 'default'}
spec={component.spec}
/>
);
5. Test Usage (e.g., examples/iris/mosaic_test.py
)
from preswald import text, mosaic
text("# Mosaic Example")
mosaic(
label="Iris Mosaic Plot",
spec={
"$schema": "https://vega.github.io/schema/vega-lite/v5.json",
"data": {"url": "https://vega.github.io/vega-lite/data/iris.json"},
"mark": "point",
"encoding": {
"x": {"field": "sepalLength", "type": "quantitative"},
"y": {"field": "sepalWidth", "type": "quantitative"},
"color": {"field": "species", "type": "nominal"}
}
}
)
Run locally:
cd examples/iris
preswald run
📦 Dependencies
Update frontend/package.json
:
"dependencies": {
"@uwdata/mosaic-vega": "^0.2.0"
}
✅ Acceptance Criteria
- Mosaic widget renders Vega-Lite-style JSON specs.
- Users can pass
spec
via Python and render rich charts. - Compatible with browser and server execution.
- Shipped with an Iris example in
/examples/iris/mosaic_test.py
.