Skip to content

[FEATURE] Add a Mosaic Plotting Widget to Preswald #517

Open
@amrutha97

Description

@amrutha97

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 to frontend/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.

📘 References

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions