Pannellum

Interactive 360° panorama viewer with tour mode, hotspots, and video support

Full documentation: https://pannellum.2plot.dev — the dedicated dash-pannellum documentation site, with the complete API reference and deeper examples. This page is the quick-start overview.

dash-pannellum is a Dash component library that integrates the Pannellum panorama viewer into your Dash applications. It allows you to display interactive 360° panoramas, including equirectangular images, cube maps, and 360° videos. The component features tour mode with multiple scenes and hotspots, customizable camera controls, multi-resolution panorama support, and keyboard navigation for an immersive viewing experience.

Since 0.2.0 the viewer is also fully imperative: the lookAt prop pans and zooms the live camera, loadScene switches tour scenes, and callbackHotspots moves markers in real time — all without rebuilding the viewer. As of 0.4.0, the orientation prop turns on gyroscope look-around on mobile devices, so the device itself becomes the camera.

Installation

Visit GitHub Repo

⭐️ Star this component on GitHub! Stay up to date on new releases and browse the codebase.

pip install dash-pannellum

Note: dash-pannellum 0.1.0+ requires Dash 4.2+ and is built against React 18. The Pannellum/video.js runtime loads from CDN at first render, so the browser needs internet access.


Quick Start

Display an interactive 360° equirectangular panorama with camera controls and adjustable viewing parameters.

# File: docs/dash_pannellum/simple.py

from dash import *
import dash_mantine_components as dmc
from dash_pannellum import DashPannellum


component = dmc.SimpleGrid(
    cols={"base": 1, "sm": 1, "lg": 4},
    children=[
        dmc.Paper(dmc.Stack([
            html.Div(id="view-pannellum"),
            html.Label(id="pannellum-output"),
            ]),
            id="intro-wrapper-dem",
            style={"gridColumn": "1 / 4"},
        ),
        dmc.Stack(
            [
                dmc.TextInput(
                    id='pannellum-panorama-url',
                    value='/assets/images/art_museum.jpg',
                    label='Panorama URL',
                    placeholder='Enter URL',
                ),
                dmc.Select(
                        label="Select Panorama Type",
                        placeholder="Select one",
                        id="panorama-type",
                        value="equirectangular",
                        data=[
                            {"value": "equirectangular", "label": "Equirectangular"},
                            {"value": "tour", "label": "Tour"},
                            {"value": "multires", "label": "MultiRes"},
                            {"value": "video", "label": "Video"},
                        ],
                    ),
                dmc.NumberInput(
                            id='pannellum-haov', label="haov prop", hideControls=True, mb=10, value=360, min=0, max=360,
                        ),
                dmc.NumberInput(
                            id='pannellum-vaov', label="vaov prop", hideControls=True, mb=10, value=180, min=0, max=180,
                        ),
                dmc.NumberInput(
                            id='pannellum-vOffset', label="vOffset prop", hideControls=True, mb=10, value=1, min=-90, max=90,
                        ),
                    # dynamicWidth
                # dmc.Checkbox(
                #     id="pannellum-custom-controls", label="customControls", checked=False, mb=10
                # ),
                dmc.Checkbox(
                    id="pannellum-show-center-dot", label="showCenterDot", checked=True, mb=10
                ),
                dmc.Checkbox(
                    id="pannellum-autoload", label="autoLoad", checked=False, mb=10
                ),

            ],
            style={'overflow-y': 'auto', 'max-height': '500px'},
        ),
        dcc.Interval(id='interval-component', interval=100, n_intervals=0)
    ],
    spacing="2rem",
)

@callback(
    Output("view-pannellum", "children"),
    Input("pannellum-panorama-url", "value"),
    Input("panorama-type", "value"),
    Input("pannellum-haov", "value"),
    Input("pannellum-vaov", "value"),
    Input("pannellum-vOffset", "value"),
    # Input("pannellum-custom-controls", "checked"),
    Input("pannellum-show-center-dot", "checked"),
    Input("pannellum-autoload", "checked"),
)
def update_pannellum_output(url, panorama_type, haov, vaov, vOffset, showCenterDot, autoload):
    print('selected props')
    print(url, panorama_type, haov, vaov, vOffset, showCenterDot, autoload)
    if not url:
        return html.Div("No URL provided")
    elif panorama_type == "equirectangular":
        config = {
            "type": "equirectangular",
            "panorama": f"{url}",
            "haov": float(haov),
            "vaov": float(vaov),
            "vOffset": float(vOffset),
        }

        return DashPannellum(
            id=f"pannellum-example",
            tour={"default": {"firstScene": "scene1"}, "scenes": {"scene1": config}},
            showCenterDot=showCenterDot,
            autoLoad=autoload,
            width='100%',
            height='400px',
        )
    elif panorama_type == 'tour':
        tour_config = {
            "default": {
                "firstScene": "circle",
                "author": "Pip Install Python",
                "sceneFadeDuration": 1000,
            },
            "scenes": {
                "circle": {
                    "title": "Dash Pannellum",
                    "hfov": 110,
                    "pitch": -3,
                    "yaw": 117,
                    "type": "equirectangular",
                    "panorama": f"{url}",
                    "hotSpots": [
                        {
                            "pitch": -2.1,
                            "yaw": 132.9,
                            "type": "scene",
                            "text": "Spring House or Dairy",
                            "sceneId": "house"
                        }
                    ]
                },
                "house": {
                    "title": "Spring House or Dairy",
                    "hfov": 110,
                    "yaw": 5,
                    "type": "equirectangular",
                    "panorama": "https://pannellum.org/images/bma-0.jpg",
                    "hotSpots": [
                        {
                            "pitch": -0.6,
                            "yaw": 37.1,
                            "type": "scene",
                            "text": "Mason Circle",
                            "sceneId": "circle",
                            "targetYaw": -23,
                            "targetPitch": 2
                        }
                    ]
                }
            }
        }

        return DashPannellum(
            id='pannellum-example',
            tour=tour_config,
            showCenterDot=showCenterDot,
            width='100%',
            height='400px',
            autoLoad=autoload
        )
    elif panorama_type == 'multires':
        multiRes_config = {
            "basePath": "https://pannellum.org/images/multires/library",
            "path": "/%l/%s%y_%x",
            "fallbackPath": "/fallback/%s",
            "extension": "jpg",
            "tileResolution": 512,
            "maxLevel": 6,
            "cubeResolution": 8432,
        }

        return DashPannellum(
            id='pannellum-example',
            multiRes=multiRes_config,
            showCenterDot=showCenterDot,
            width='100%',
            height='400px',
            autoLoad=autoload
        )
    elif panorama_type == 'video':
        video_config = {
            "sources": [
                {"src": "https://bitmovin-a.akamaihd.net/content/playhouse-vr/progressive.mp4", "type": "video/mp4"},
            ],
            "poster": "https://bitmovin-a.akamaihd.net/content/playhouse-vr/poster.jpg"
        }

        return DashPannellum(
            id='pannellum-example',
            video=video_config,
            showCenterDot=showCenterDot,
            width='100%',
            height='400px',
            autoLoad=autoload
        )
    return html.Div("Something went wrong")


@callback(
    Output('pannellum-output', 'children'),
    Input('pannellum-example', 'pitch'),
    Input('pannellum-example', 'yaw'),
    Input('interval-component', 'n_intervals'),
    prevent_initial_call=True
)
def update_video_output(pitch, yaw, n):
    if pitch is not None and yaw is not None:
        return f'Camera Position - Pitch: {pitch:.2f}, Yaw: {yaw:.2f}'
    return 'Camera Position - Pitch: 0.00, Yaw: 0.00'

Basic Panorama

Create a simple panorama viewer with basic configuration.

# File: docs/dash_pannellum/basic.py

from dash import html
import dash_pannellum

component = html.Div([
    dash_pannellum.DashPannellum(
        id='panorama',
        tour={
            "default": {
                "firstScene": "scene1",
                "sceneFadeDuration": 1000
            },
            "scenes": {
                "scene1": {
                    "title": "Example Panorama",
                    "hfov": 110,
                    "pitch": -3,
                    "yaw": 117,
                    "type": "equirectangular",
                    "panorama": "/assets/images/landscape.jpg"
                }
            }
        },
        autoLoad=True,
        width='100%',
        height='400px',
    )
])

Tour Mode

Tour mode enables navigation between multiple connected panorama scenes with interactive hotspots. Users can click hotspots to jump between different viewpoints, creating an immersive multi-scene experience.

# File: docs/dash_pannellum/tours.py

from dash import html
import dash_pannellum

component = html.Div([
    dash_pannellum.DashPannellum(
        id='panorama',
        tour={
                "default": {
                    "firstScene": "circle",
                    "author": "Pip Install Python",
                    "sceneFadeDuration": 1000,
                },
                "scenes": {
                    "circle": {
                        "title": "Dash Pannellum",
                        "hfov": 110,
                        "pitch": -3,
                        "yaw": 117,
                        "type": "equirectangular",
                        "panorama": "https://pannellum.org/images/alma.jpg",
                        "hotSpots": [
                            {
                                "pitch": -2.1,
                                "yaw": 132.9,
                                "type": "scene",
                                "text": "Spring House or Dairy",
                                "sceneId": "house"
                            }
                        ]
                    },
                    "house": {
                        "title": "Spring House or Dairy",
                        "hfov": 110,
                        "yaw": 5,
                        "type": "equirectangular",
                        "panorama": "https://pannellum.org/images/bma-0.jpg",
                        "hotSpots": [
                            {
                                "pitch": -0.6,
                                "yaw": 37.1,
                                "type": "scene",
                                "text": "Mason Circle",
                                "sceneId": "circle",
                                "targetYaw": -23,
                                "targetPitch": 2
                            }
                        ]
                    }
                }
            },
        autoLoad=True,
        width='100%',
        height='400px',
    )
])

Tour Configuration:

Each tour consists of a default object and a scenes dictionary:

Hotspot Configuration:


Partial Panorama

Display panoramas that don't cover the full 360° horizontally or 180° vertically by specifying viewing extents using horizontal angle of view (haov), vertical angle of view (vaov), and vertical offset (vOffset).

# File: docs/dash_pannellum/partial_panorama.py

from dash import html
import dash_pannellum

component = html.Div([
    dash_pannellum.DashPannellum(
        id='partial-panorama-component',
        tour={"default": {"firstScene": "scene1"}, "scenes": {"scene1":
                    {
                        "type": "equirectangular",
                        "panorama": "https://archive.org/download/SalinaKansas1916Postcard/Salina%20Kansas%2C%201916%20Postcard%2C%20Front.jpg",
                        "haov": 149.87,
                        "vaov": 54.15,
                        "vOffset": 1.17
                    }
                }
              },
        width='100%',
        height='400px',
        autoLoad=True
    )
])

Viewing Parameters:

These parameters allow precise control over which portion of the panorama is visible and how it's framed within the viewer.


360° Video Panorama

Display interactive 360° video content with standard video controls.

# File: docs/dash_pannellum/video.py

from dash import html
import dash_pannellum

component = html.Div([
    dash_pannellum.DashPannellum(
        id='panorama',
        video={
            "sources": [
                {"src": "https://cdn.bitmovin.com/content/assets/playhouse-vr/progressive.mp4", "type": "video/mp4"},
            ],
            "poster": "https://cdn.bitmovin.com/content/assets/playhouse-vr/poster.jpg"
        },
        autoLoad=True,
        width='100%',
        height='400px',
    )
])

Video Configuration:

The component supports standard HTML5 video formats. Users can interact with the video using typical video controls while maintaining the ability to pan around the 360° view.


Using Callbacks

Track the viewer's current state (camera position, zoom, loaded status, active scene) using Dash callbacks. The component provides read-only properties that update as the user interacts with the panorama. View-state updates are throttled to 4 per second and change-detected, so idle viewers fire nothing.

from dash import callback, Input, Output

@callback(
    Output('output-div', 'children'),
    Input('panorama', 'loaded'),
    Input('panorama', 'pitch'),
    Input('panorama', 'yaw'),
    Input('panorama', 'hfov'),
    Input('panorama', 'currentScene')
)
def update_output(loaded, pitch, yaw, hfov, current_scene):
    """Display current panorama state"""
    if loaded and pitch is not None and yaw is not None:
        return f'Scene: {current_scene}, Pitch: {pitch:.2f}°, Yaw: {yaw:.2f}°, Zoom: {hfov:.1f}°'
    return 'Loading panorama...'

Available Read-Only Callback Properties:

Warning: These properties are outputs only — use them as callback Inputs, never as Outputs. Writing to pitch/yaw/hfov does not move the camera. To drive the camera, write the lookAt prop instead; set the initial orientation in the scene config.


Driving the Camera with lookAt

The lookAt prop (added in 0.2.0) is an imperative camera write: set {pitch, yaw, hfov, animated} from any callback and the live viewer pans and zooms in place — no rebuild, no flash, no camera reset. Omitted fields keep their current value, and animated is the transition duration in milliseconds. Combined with the read-only pitch/yaw/hfov outputs, this gives you a full "fly-to" pattern from buttons, clicked hotspots, or any external event.

# File: docs/dash_pannellum/camera_control.py

import dash_mantine_components as dmc
from dash import callback, ctx, Input, Output
from dash_pannellum import DashPannellum

# Named camera targets. Omitted fields keep their current value,
# so "Zoom In" only touches hfov and leaves pitch/yaw alone.
TARGETS = {
    "pannellum-lookat-btn-house": {"pitch": -2.1, "yaw": 132.9, "hfov": 60},
    "pannellum-lookat-btn-sky": {"pitch": 55, "hfov": 100},
    "pannellum-lookat-btn-zoom": {"hfov": 50},
    "pannellum-lookat-btn-reset": {"pitch": -3, "yaw": 117, "hfov": 110},
}

component = dmc.Stack(
    [
        DashPannellum(
            id="pannellum-lookat-viewer",
            tour={
                "default": {"firstScene": "circle"},
                "scenes": {
                    "circle": {
                        "type": "equirectangular",
                        "panorama": "https://pannellum.org/images/alma.jpg",
                        "hfov": 110,
                        "pitch": -3,
                        "yaw": 117,
                    }
                },
            },
            autoLoad=True,
            width="100%",
            height="400px",
        ),
        dmc.Group(
            [
                dmc.Button("Spring House", id="pannellum-lookat-btn-house", size="xs"),
                dmc.Button("Look Up", id="pannellum-lookat-btn-sky", size="xs"),
                dmc.Button("Zoom In", id="pannellum-lookat-btn-zoom", size="xs"),
                dmc.Button(
                    "Reset View",
                    id="pannellum-lookat-btn-reset",
                    size="xs",
                    variant="outline",
                ),
            ]
        ),
        dmc.Text(id="pannellum-lookat-readout", size="sm", c="dimmed"),
    ],
    gap="sm",
)


@callback(
    Output("pannellum-lookat-viewer", "lookAt"),
    Input("pannellum-lookat-btn-house", "n_clicks"),
    Input("pannellum-lookat-btn-sky", "n_clicks"),
    Input("pannellum-lookat-btn-zoom", "n_clicks"),
    Input("pannellum-lookat-btn-reset", "n_clicks"),
    prevent_initial_call=True,
)
def fly_to(*_):
    """Write lookAt — the live viewer pans/zooms in place, no rebuild."""
    return {**TARGETS[ctx.triggered_id], "animated": 1000}


@callback(
    Output("pannellum-lookat-readout", "children"),
    Input("pannellum-lookat-viewer", "pitch"),
    Input("pannellum-lookat-viewer", "yaw"),
    Input("pannellum-lookat-viewer", "hfov"),
    prevent_initial_call=True,
)
def readout(pitch, yaw, hfov):
    """pitch / yaw / hfov are read-only Inputs that report the camera back."""
    if pitch is None or yaw is None:
        return "Camera: waiting for viewer..."
    hfov_text = f", hfov {hfov:.1f}°" if hfov is not None else ""
    return f"Camera: pitch {pitch:.1f}°, yaw {yaw:.1f}°{hfov_text}"

lookAt keys:

For high-frequency steering (a joystick, keyboard, or game loop), skip the server round-trip and write from a clientside callback or plain JS:

window.dash_clientside.set_props('panorama', {lookAt: {yaw: bearing, animated: 220}});

lookAt works in image panorama (tour) mode; it does not reach multiRes or video viewers. A lookAt write that lands while the viewer is still booting is dropped — initial orientation belongs in the scene config.

Related imperative props — the same no-rebuild philosophy applies to:


Callback Hotspots

callbackHotspots adds hotspots that report clicks back to Python — separate from the tour's own hotSpots. Keys are scene IDs; each entry's name is written to the read-only lastClickedHotspot prop on click:

from dash import callback, Input, Output
from dash_pannellum import DashPannellum

DashPannellum(
    id='panorama',
    tour=tour,
    callbackHotspots={
        "lobby": [
            {"pitch": -1.2, "yaw": 122.0, "type": "info",
             "text": "Reception desk", "name": "reception"},
        ],
    },
)

@callback(
    Output('info-panel', 'children'),
    Input('panorama', 'lastClickedHotspot'),
    prevent_initial_call=True,
)
def on_hotspot(name):
    return INFO[name]

Live updates with per-name diffing (0.3.1): outputting a new callbackHotspots dict from a callback never rebuilds the viewer, and the update is diffed per name:

This means markers can drift every tick as real DOM hotspots:

@callback(Output('panorama', 'callbackHotspots'), Input('tick', 'n_intervals'))
def drift(n):
    return {"main": [project_to_yaw_pitch(e) for e in world.entities]}

Keep name stable per entity — it's the diff key. If you encode a tick counter into name, every update becomes a remove-and-re-add again.

Tip — authoring hotspot coordinates: set showCenterDot=True (a crosshair at screen center), stream pitch/yaw into a readout callback, aim the dot at the target, and copy the values into your hotspot config. Remove the dot for production.


Gyroscope Look-Around

New in 0.4.0. The orientation prop requests gyroscope steering — point the phone around and the panorama follows. It's imperative (no rebuild): set it to True to engage, False to release. Two read-only props report the truth: orientationSupported (the device/browser can gyro-steer at all) and orientationActive (the gyro is steering right now — False if permission was denied, and Pannellum pauses it while the user drags the panorama).

# File: docs/dash_pannellum/gyro.py

import dash_mantine_components as dmc
from dash import callback, clientside_callback, Input, Output
from dash_pannellum import DashPannellum

component = dmc.Stack(
    [
        DashPannellum(
            id="pannellum-gyro-viewer",
            tour={
                "default": {"firstScene": "house"},
                "scenes": {
                    "house": {
                        "type": "equirectangular",
                        "panorama": "https://pannellum.org/images/bma-0.jpg",
                        "hfov": 110,
                    }
                },
            },
            autoLoad=True,
            width="100%",
            height="400px",
        ),
        dmc.Group(
            [
                dmc.Switch(
                    id="pannellum-gyro-switch",
                    label="Gyroscope look-around",
                    checked=False,
                ),
                dmc.Badge(
                    "Checking sensors...",
                    id="pannellum-gyro-supported",
                    color="gray",
                    variant="light",
                ),
                dmc.Badge(
                    "Gyro idle",
                    id="pannellum-gyro-active",
                    color="gray",
                    variant="light",
                ),
            ]
        ),
        dmc.Text(
            "Gyro steering needs a mobile device with motion sensors, served over "
            "HTTPS. On desktop the switch is a no-op and the panorama stays "
            "drag-to-look — nothing breaks.",
            size="sm",
            c="dimmed",
        ),
    ],
    gap="sm",
)

# The orientation write MUST be clientside: iOS 13+ only shows the
# motion-permission prompt inside a user-gesture window, so the prop has
# to be set synchronously on the tap — a server round-trip is too late.
clientside_callback(
    "function(on) { return Boolean(on); }",
    Output("pannellum-gyro-viewer", "orientation"),
    Input("pannellum-gyro-switch", "checked"),
    prevent_initial_call=True,
)


@callback(
    Output("pannellum-gyro-supported", "children"),
    Output("pannellum-gyro-supported", "color"),
    Output("pannellum-gyro-active", "children"),
    Output("pannellum-gyro-active", "color"),
    Input("pannellum-gyro-viewer", "orientationSupported"),
    Input("pannellum-gyro-viewer", "orientationActive"),
)
def gyro_status(supported, active):
    """orientationSupported / orientationActive are read-only truth props."""
    if supported:
        supported_badge = ("Gyro supported", "green")
    else:
        supported_badge = ("Not supported on this device", "gray")
    if active:
        active_badge = ("Gyro steering", "teal")
    else:
        active_badge = ("Gyro idle", "gray")
    return (*supported_badge, *active_badge)

iOS requires a clientside callback on a direct tap. iOS 13+ only shows the motion-permission prompt inside a user-gesture window, so the orientation write must happen synchronously on the tap — a server callback round-trip is too late:

from dash import clientside_callback, Input, Output

clientside_callback(
    "function(on) { return Boolean(on); }",
    Output('panorama', 'orientation'),
    Input('gyro-switch', 'checked'),
    prevent_initial_call=True,
)

Gyro support constraints — Pannellum gates orientation on all three of:

  1. DeviceOrientationEvent being available (motion sensors)
  2. A mobile user agent
  3. Literal https: — Pannellum checks location.protocol, so plain-HTTP localhost reports unsupported even though browsers would allow the sensor there. Use a TLS dev cert or a tunnel to test on a phone.

On desktop the feature degrades gracefully: orientationSupported stays False, the switch is a no-op, and the panorama remains drag-to-look. Turn orientation off before running directed lookAt sequences — a camera fighting the gyroscope feels broken.


Keyboard Controls

The component supports keyboard navigation for improved user experience:


Component Properties

PropertyTypeDefaultDescription
idstringRequiredUnique identifier for the component used in Dash callbacks.
widthstring'600px'Width of the panorama viewer (any CSS size, e.g., '100%', '800px').
heightstring'400px'Height of the panorama viewer (any CSS size, e.g., '400px', '100vh').
tourdictNoneTour mode config: {default: {firstScene, ...}, scenes: {sceneId: {...}}}. A single panorama is a tour with one scene. A scene may set panoramaCanvasId (DOM id of a <canvas>) instead of panorama for dynamic canvas mode. Live — changing it rebuilds the viewer.
multiResdictNoneMulti-resolution (tiled) panorama config: basePath, path, fallbackPath, extension, tileResolution, maxLevel, cubeResolution. Live.
videodictNone360° video config: {sources: [{src, type}], poster}. Rendered through video.js. Live.
autoLoadboolTrueIf true, the panorama loads automatically. If false, user must click to load.
compassboolFalseIf true, displays a compass heading indicator in the viewer.
northOffsetnumber0Offset, in degrees, of the center of the panorama from North.
customControlsboolFalseIf true, hides the built-in zoom/fullscreen controls so you can build your own with Dash components.
showCenterDotboolFalseIf true, displays a center dot — useful as a crosshair when authoring hotspot positions.
useHttpStreamingboolFalseIf true, loads the video.js HTTP streaming plugin so HLS/DASH sources (e.g. live streams) can play as 360° video.
callbackHotspotsdict{}Hotspots that report clicks to Dash: {sceneId: [{pitch, yaw, type, text, name}]}. Diffed per-name in place — no rebuild (0.3.1).
lookAtdictNoneImperative camera write: {pitch, yaw, hfov, animated} pans/zooms the live viewer with no rebuild. Omitted keys keep their value; animated is ms (default 1000). Image panorama modes only.
loadScenestringNoneImperative tour scene switch by scene ID — no rebuild. Unknown IDs and the active scene are ignored.
preloadScenesboolTruePrefetch the other scenes' panoramas once the viewer is up, so tour jumps don't show a loading box.
hideLoadingSpinnerboolFalseSuppress Pannellum's "Loading..." box for this viewer.
dynamicUpdateboolFalseRe-upload the panorama texture every frame — required for live panoramaCanvasId canvas scenes. Leave false for static panoramas.
orientationboolFalseRequest gyroscope look-around (0.4.0). Mobile + HTTPS only; on iOS, set from a clientside callback on a direct user tap.
orientationSupportedbool(read-only)True when the device/browser can drive the camera from the gyroscope (motion sensors + mobile browser + literal https:).
orientationActivebool(read-only)True while gyro look-around is actively steering (false if permission was denied or while the user drags).
loadedbool(read-only)Indicates whether the panorama has finished loading.
pitchnumber(read-only)Current vertical camera angle in degrees (-90 to 90). Throttled to 4 updates/s.
yawnumber(read-only)Current horizontal camera angle in degrees (-180 to 180). Throttled to 4 updates/s.
hfovnumber(read-only)Current horizontal field of view (zoom) in degrees. Throttled to 4 updates/s.
currentScenestring(read-only)ID of the currently active scene in tour mode.
lastClickedHotspotstring(read-only)name of the last clicked callback hotspot.

Read-only props are callback Inputs — never write to them from an Output. Use lookAt, loadScene and callbackHotspots to act on a running viewer; changing tour/multiRes/video (and other "live" config props) tears down and re-initializes it.

Multi-Resolution Configuration:

When using multiRes for high-quality panoramas, the configuration requires:


Contributing

Contributions to dash-pannellum are welcome! Please refer to the project's issues on GitHub for any feature requests or bug reports.

License

This project is licensed under the MIT License.

Note for AI agents: This is the static, prerendered view of an interactive Dash application served because we detected a non-JS user agent. Full prose docs: