Skip to main content

Views and Projections

The same data can be drawn differently to screen based on what projection method is used. deck.gl's view system defines how one or more cameras should be set up to look at your data objects. The default view used in deck.gl is the MapView, which implements the Web Mercator projection. The view system is designed to be flexible and composable and can handle many different configurations such as side-by-side views, overlapping views etc. If you plan to work with non-geospatial data, or show more than a single standard viewport, it may be worth spending some time to get familiar with the View API.

View classes enable applications to specify one or more rectangular viewports and control what should be rendered inside each view.

A "minimap" app, implemented as two overlapping, partially synchronized MapViews

A vehicle log rendered from the driver's perspective, implemented with FirstPersonView

A graph, implemented with OrthographicView

View, View State and Viewport

View

A View instance defines the following information:

  • A unique id.
  • The position and extent of the view on the canvas: x, y, width, and height.
  • Certain camera parameters specifying how your data should be projected into this view, e.g. field of view, near/far planes, perspective vs. orthographic, etc.
  • The controller to be used for this view. A controller listens to pointer events and touch gestures, and translates user input into changes in the view state. If enabled, the camera becomes interactive.

To summarize, a View instance wraps the "hard configuration" of a camera. Once defined, it does not need to change frequently.

deck.gl allows multiple views to be specified, allowing the application to divide the screen into multiple similar or different views. These views can be synchronized or separately controlled by the user or the application.

View State

A View instance must be used in combination with a viewState object. As the name suggests, the object describes the state of a View instance. The view state object defines the temporary properties of a view at runtime, like the camera position, orientation, zoom, etc. If the view is interactive, every time the user pans/rotates/zooms, the view state will be updated to reflect the change.

To summarize, a viewState object describes the "real-time properties" of a camera. It may be updated continuously during interaction and/or transition.

Viewport

A Viewport instance is the camera itself. It is "resolved" from a View instance and its viewState. It handles the mathematical operations such as coordinate projection/unprojection, the calculation of projection matrices, and other GLSL uniforms needed by the shaders.

Whenever viewState updates, the view creates a new viewport under the hood. Typically, the deck.gl user does not need to work with viewports directly. In certain use cases, the JavaScript functions offered by a Viewport instance can be handy for projecting and unprojecting coordinates.

If you are using the Deck canvas as an overlay on a base map rendered by another library, you may need to update the viewport using the API provided by that library rather than by deck.gl.

Types of Views

deck.gl offers a set of View classes that package the camera and controller logic that you need to visualize and interact with your data. You may choose one or multiple View classes based on the type of data (e.g. geospatial, 2D chart) and the desired perspective (top down, first-person, etc).

Note that the set of view state parameters that will be used varies between Views. Consult each view class' documentation for a full list of parameters supported.

View ClassUse CaseStatusDescription
ViewThe base view has to be supplied with raw view and projection matrices. It is typically only instantiated directly if the application needs to work with views that have been supplied from external sources, such as the WebVR API.
MapView (default)geospatialfull supportThis view renders data using the Web Mercator projection and is designed to match an external base map library such as Mapbox or Google Maps.
GlobeViewgeospatialexperimentalThis view renders data as a 3D globe.
FirstPersonViewgeospatialfull supportThe camera is positioned in a provided geolocation and looks in a provided direction, similar to that of a first-person game.
OrthographicViewinfo-vis (2D)full supportThe camera looks at a target point from top-down. Does not rotate.
OrbitViewinfo-vis (3D)full supportThe camera looks at a target point from a provided direction. Rotates around the target.

Examples

Using a View

If the views prop of Deck is not specified, deck.gl will automatically create a MapView that fills the whole canvas, so basic geospatial applications often do not have to specify any Views.

If using non-geospatial data, you will need to manually create a view that is appropriate for info-vis, e.g.:

import {Deck, OrthographicView} from '@deck.gl/core';

const deck = new Deck({
// ...
views: new OrthographicView()
});

Using a View with View State

If initialViewState is provided, deck.gl automatically tracks the view states of interactive views (used as a "stateful" component):

import {Deck} from '@deck.gl/core';

const INITIAL_VIEW_STATE = {
longitude: -122.4,
latitude: 37.8,
zoom: 12,
pitch: 0,
bearing: 0
};

const deckInstance = new Deck({
initialViewState: INITIAL_VIEW_STATE,
controller: true
});

If you need to manage and manipulate the view state outside of deck.gl, you may do so by providing the viewState prop (used as a "stateless" component). In this case, you also need to listen to the onViewStateChange callback and update the viewState object yourself:

import {Deck, OrthographicView} from '@deck.gl/core';

const INITIAL_VIEW_STATE = {
target: [0, 0, 0],
zoom: 1
};

const deckInstance = new Deck({
viewState: INITIAL_VIEW_STATE,
controller: true,
onViewStateChange: e => {
deckInstance.setProps({
viewState: e.viewState
});
}
});

document.getElementById('reset-btn').onclick = () => {
deckInstance.setProps({
viewState: INITIAL_VIEW_STATE
});
}

Using Multiple Views

deck.gl also supports multiple views by taking a views prop that is a list of View instances.

Views allow the application to specify the position and extent of the viewport (i.e. the target rendering area on the screen) with x (left), y (top), width and height. These can be specified in either numbers or CSS-like percentage strings (e.g. width: '50%'), which is evaluated at runtime when the canvas resizes.

Common examples in 3D applications that render a 3D scene multiple times with different "cameras":

  • To show views from multiple viewpoints (cameras), e.g. in a split screen setup.
  • To show a detail view (e.g, first person), and an overlaid, smaller "map" view (e.g. third person or top down, zoomed out to show where the primary viewpoint is).
  • To support stereoscopic rendering (e.g. VR), where left and right views are needed, providing the necessary parallax between left and right eye.
  • For rendering into offscreen framebuffers, which can then be used for e.g. advanced visual effects, screen shot solutions, overlays onto DOM elements outside of the primary deck.gl canvas (e.g. a video).

Example of displaying two maps side-by-side, and any camera change in one map is synchronized to another:

import {Deck, MapView} from '@deck.gl/core';

const deckInstance = new Deck({
views: [
new MapView({id: 'left', x: 0, width: '50%', controller: true}),
new MapView({id: 'right', x: '50%', width: '50%', controller: true})
],
viewState: {
longitude: -122.4,
latitude: 37.8,
zoom: 12
},
onViewStateChange: ({viewState}) => {
deckInstance.setProps({viewState});
}
});

Using Multiple Views with View States

When using multiple views, each View can either have its own independent view state, or share the same view state as other views. To define the view state of a specific view, add a key to the viewState object that matches its view id.

The following example displays a "minimap" in the corner that synchronizes with the main view, but provides a different perspective:

import {Deck, MapView} from '@deck.gl/core';

let currentViewState = {
main: {
longitude: -122.4,
latitude: 37.8,
pitch: 30,
zoom: 12,
},
minimap: {
longitude: -122.4,
latitude: 37.8,
zoom: 8
}
};

function onViewStateChange({viewId, viewState}) {
if (viewId === 'main') {
// When user moves the camera in the first-person view, the minimap should follow
currentViewState = {
main: viewState,
minimap: {
...currentViewStates.minimap,
longitude: viewState.longitude,
latitude: viewState.latitude
}
};
} else {
// Only allow the user to change the zoom in the minimap
currentViewState = {
main: currentViewStates.main,
minimap: {
...currentViewStates.minimap,
zoom: viewState.zoom
}
};
}
// Apply the new view state
deckInstance.setProps({viewState: currentViewState});
};

const deckInstance = new Deck({
views: [
new MapView({id: 'main', controller: true}),
new MapView({id: 'minimap', x: 10, y: 10, width: 300, height: 200, controller: true})
],
viewState: currentViewState
onViewStateChange
});

Rendering Layers in Multiple Views

By default, all visible layers are rendered into all the views. This may not be the case if certain layers are designed to go into one particular view.

The Deck class has a layerFilter prop that can be used to determine which layers to draw in which view. In the following example, two views are rendered to follow a car moving on a map: a first-person perspective from the car's dash, and a top-down perspective of the city block that it's in. We only want to render the 3D car model in the minimap because it would block the camera in the first-person view.

import {Deck, FirstPersonView, MapView} from '@deck.gl/core';
import {SimpleMeshLayer} from '@deck.gl/mesh-layers';
import {MVTLayer} from '@deck.gl/geo-layers';

const deckInstance = new Deck({
views: [
new FirstPersonView({id: 'first-person'}),
new MapView({id: 'minimap', x: 10, y: 10, width: '20%', height: '20%'})
],
layerFilter: ({layer, viewport}) => {
if (viewport.id === 'first-person' && layer.id === 'car') {
// Do not draw the car layer in the first person view
return false;
}
return true;
}
});

/** Called periodically to update the map with the car's latest position */
function updateCar(carPose) {
deckInstance.setProps({
layers: [
new MVTLayer({
id: 'base-map',
// ...
}),
new SimpleMeshLayer({
id: 'car',
mesh: '/path/to/model.obj',
data: [carPose],
getPosition: d => [d.longitude, d.latitude, 0],
getOrientation: d => [0, -d.heading * Math.PI / 180, 0]
})
],
viewState: {
'first-person': {
longitude: carPos.longitude,
latitude: carPos.latitude,
bearing: carPos.heading,
position: [0, 0, 2]
},
minimap: {
longitude: carPos.longitude,
latitude: carPos.latitude,
zoom: 10
}
}
});
}

Some layers, including TileLayer, MVTLayer, HeatmapLayer and ScreenGridLayer, perform expensive operations (data fetching and/or aggregation) on viewport change. Therefore, it is generally NOT recommended to render them into multiple views. If you do need to show e.g. tiled base map in multiple views, create one layer instance for each view and limit their rendering with layerFilter:

import {Deck, MapView} from '@deck.gl/core';
import {MVTLayer} from '@deck.gl/geo-layers';

const deck = new Deck({
// ...
views: [
new MapView({id: 'main', controller: true}),
new MapView({id: 'minimap', x: 10, y: 10, width: 300, height: 200})
],
layers: [
new MVTLayer({
id: 'tiles-for-main',
// ...
}),
new MVTLayer({
id: 'tiles-for-minimap',
// ...
})
],
layerFilter: ({layer, viewport}) => {
return layer.id === `tiles-for-${viewport.id}`;
}
});

Starting with v8.5, Tile3DLayer supports rendering in multiple views with a single tile cache.

Picking in Multiple Views

deck.gl's built-in picking support extends naturally to multiple viewports. The picking process renders all viewports.

Note that the pickInfo object does not contain a viewport reference, so you will not be able to tell which viewport was used to pick an object.

In the above example, you may also control which layer is pickable by view in layerFilter:

const layerFilter = ({layer, viewport, isPicking}) => {
if (viewport.id === 'first-person' && layer.id === 'car') {
// Do not draw the car layer in the first person view
return false;
}
if (isPicking && viewport.id === 'minimap') {
// Do not pick anything in the minimap
return false;
}
return true;
};

Auto-Positioning UI Components Behind Views

When the deck.gl project first started, one of our major use cases was to build complex Web apps with perfectly synchronized WebGL visualizations and other UI components (HTML markers, charts, lists, etc.). Given the scale of these applications, some reactive, virtual-DOM framework is expected to be in use. At the moment, these features are only implemented for React. Visit the DeckGL React component docs for examples.

Performance Notes

This section discusses how views and viewState impacts performance (frame rate).

Between rerenders, the views and viewState props are deep compared to determine if anything changed. There is very little performance concern even if new view instances are constructed each render, as long as they are deemed equivalent with the previous values.

If views/viewState do change, new viewports will be constructed. At this point, two things will happen:

  • Layers are given a chance to recompute their state and create additional GPU resources (by calling the shouldUpdateState lifecycle method), with the UpdateParameters argument containing changeFlags.viewportChanged: true. By default, most layers ignore viewport changes, so updateState does not get called as long as nothing else changes. However, some layers do need to update when viewport changes (e.g. the TileLayer and HeatmapLayer). During interaction and transition, this may happen many times a second, so such layers may contribute to significant performance overhead.
  • Afterwards, all layers are redrawn to the updated viewport. This is a relatively cheap step as GPU is doing the heavy-lifting. All that CPU has to do is to supply the GPU with the new viewport parameters.

Read more about this topic in Layer Lifecycles.