ArcGIS Maps SDK for JavaScript

Jumpstart your visualization: build on top of an existing custom 2D layer

Before we started shipping FlowRenderer as part of the ArcGIS API for JavaScript (JS API), we published a blog article and an open-source TypeScript demo (repo animated-flow-ts on GitHub) that implemented a simplified version of the same visualization as a custom WebGL layer. This effort was well received by our readers, who started experimenting with the code to adapt it to their use cases. Today, most common flow visualization use cases are handled by the official FlowRenderer, but the original open-source repository animated-flow-ts is still a valuable starting point if you need to satisfy a specific use case. In this article, we explain how to leverage the existing code and custom WebGL layers to create novel visualizations.

What we will build

The goal of this article is to explain how the jumpstart.ts app (live demo) in the open-source repository works. This app packs 3 different custom WebGL layers and enables toggling between them using a LayerList widget.

This article assumes familiarity with WebGL. Having experience with BaseLayerViewGL2D can be useful for gaining a deeper understanding of the codebase, but it is not a prerequisite; the code that we are going to write today does not import BaseLayerViewGL2D and is completely unaware of the fact that, behind the scenes, it is the entry point for all custom 2D WebGL rendering operations.

A refresher on custom layers

This section briefly covers how to create custom layers from scratch; we included it for completeness and for providing context, but it is not required for understanding the rest of the article. Feel free to skip to the next section if your focus today is on the rapid creation of new visualizations.

At Esri, we believe that you deserve the best tools for your projects. That is why the JS API ships with many predefined layers, ready to use and backed by our cloud offering.

But we also know that you and your team may have unique requirements, calling for a custom integrated solution. For this reason, the 4x JS API has shipped with support for 3D externalRenderers since its inception in 2016, and custom 2D layers using BaseLayerView2D and BaseLayerViewGL2D since versions 4.8 and 4.11 respectively. This article focuses on custom WebGL layers, the ones that rely on BaseLayerViewGL2D for 2D MapView rendering.

There is an official SDK sample/tutorial that explains how to create custom WebGL layers. In a nutshell, the following steps are involved.

The following sequence diagram shows the runtime interactions between the app and the other objects involved in 2D rendering.

This is the extension experience that we have been supporting for over 10 releases of the API. It could very well be that this is all you need. If that’s the case, great! We have plenty of SDK samples that cover the standard extension workflow in detail, both for BaseLayerViewGL2D and BaseLayerView2D, its Canvas2D counterpart.

But, if you are a WebGL developer and you are looking for ways to boost your productivity, please keep reading!

An overview of the animated-flow-ts repository

Let’s take a look at the code in the animated-flow-ts repo and see how it can help your project.

Start by cloning the repository, cd into it, npm install, and npm start it.

These commands start the TypeScript compiler in --watch mode, so that every time that a .ts file is modified, it is automatically recompiled. In parallel, a test web server starts listening at http://localhost:3000. A browser window should automatically open with a list of demo apps for you to try. The app that we will be using today is called jumpstart. If the browser does not open for some reason, launch your browser of choice, and point it to http://localhost:3000/demos/jumpstart.html.

The repository uses TypeScript and AMD modules.

The two top-level directories are demos and src.

The TypeScript files are organized into 4 packages.

The core package

The core package contains a few modules; 3 of them are relevant for implementing new visualizations.

The VisualizationLayerView2D abstract base class

The abstract class VisualizationLayerView2D subclasses BaseLayerViewGL2D and offers an easier, albeit more constrained, extension workflow. To take advantage of it, subclass VisualizationLayerView2D and implement method createVisualizationStyle() to return your style object.

You must also flag the custom layer view as animated or not by implementing the animate flag. An animated layer view will redraw continuously without the need to explicitly invoke requestRender().

The Resources interface

The Resources interface is defined in core/types and describes an object that supports attach(gl) and detach(gl) methods. Such objects are instantiated by the visualization style in an unattached state. The VisualizationLayerView2D is responsible for attaching them when they are first rendered, and detaching them when they are not needed anymore. Methods attach(gl) and detach(gl) act as a constructor/destructor pair and receive the current WebGL rendering context as a parameter.

These objects are meant as containers of resources needed by the rendering algorithm, including non-garbage-collectible references such as WebGL objects and WebSockets.

At any given time, a visualization relies on a set of global resources and a set of local resources. As such, you need to implement the Resources interface twice.

The VisualizationStyle abstract base class

Next, you will need to subclass VisualizationStyle. A visualization style implements the logic that loads and renders resources.

You will need to override 3 methods. These methods are automatically invoked by the layer view when needed.

The VisualizationRenderParams interface

An instance of this type is delivered every frame to the VisualizationStyle.renderVisualization() method. It tells the visualization style where to render the visualization. Here is a list of its most useful properties.

Creating the custom layer class

The core package does not prescribe any particular way to implement the custom layer. As such, the most common way to go about it is to subclass esri/Layer or some other suitable layer type and override createLayerView() to return an instance of the custom layer view.

An alternate extension mechanism

Let’s briefly recap the steps needed to implement a custom WebGL layer by extending VisualizationLayerView2D and the other classes in the core package of the animated-flow-ts repository. Please compare and contrast with the steps taken when extending BaseLayerViewGL2D.

As you can see, usage of the core package imposes a lot of structure on the extending code; developers have to implement certain things in a certain way; the standard BaseLayerViewGL2D extension experience, on the other hand, offers much more freedom.

However, there are two benefits to choosing to extend the core package; these cover two aspects of custom layer development that are otherwise pretty difficult to get right.

Benefit #1: Automatic resource life cycle management

Your custom layer view inherits from VisualizationLayerView2D a complete resource management system. You will not have to load, queue, attach, detach, listen, watch, debounce, anything. You just have to split the rendering resources between global and local, implement attach(gl) and detach(gl), and the base classes in core will do the rest.

This automatic behavior is exemplified by the sequence diagram below.

Benefit #2: Simplified view state management

When the view becomes stationary and VisualizationStyle.loadLocalResources() is called, it receives as parameters the extent of the data to load, and the target size in pixels. You can use this information to query the data at the appropriate LOD and return the ideal set of local resources for it. When this happens, the translation and scale passed to VisualizationStyle.renderVisualization() are reset.

The diagram below illustrates the behavior for the case of zooming in and subsequent data reload at a higher LOD.

What we just described is a very important inherited behavior; it implements data refreshing and accurate screen positioning; it is a fundamental component of any real-world large-scale data visualization. Crucially, it enables your WebGL code to support multiple LODs while at the same time keeping your attributes and uniforms as small in magnitude as possible; remember that WebGL shaders use single-precision floating-point (or worse) for all computations, and when attributes or uniforms get too large, positioning tends to get very imprecise.

Please take some time to watch the video below; it illustrates the mechanism in action. See how the idle values for translation and scale are reset every time that loadLocalResources() completes.

The idle values for translation are negative because the origin of the visualization is North-West of the upper left corner of the current extent; this happens because VisualizationLayerView2D tells the visualization style to load an extent that is 15% larger than what is needed to cover the screen. We do this to have some headroom and keep border artifacts away from the area of interest.

Our overview of the repository is complete. Now it’s time to look at 3 brand-new custom visualizations and their implementations! You can find the new custom layers, together with their layer views and visualization styles, in the src/jumpstart folder.

There is an app (.ts, .html) that adds the 3 custom layers to the MapView and we recommend that you keep it open in your browser as you experiment with the code. If you run npm start, a live version of the app should open automatically at http://localhost:3000/demos/jumpstart.html. A live version is also available online.

For each visualization, check out the layer.ts file, which contains the custom layer, and the view.ts file, which contains the custom layer view. See how the layer overrides createLayerView() and how the layer view inherits from VisualizationLayerView2D and overrides createVisualizationStyle().

Then check the rendering.ts file which defines the global resources, the local resources, and the visualization style. All WebGL rendering code resides here. Please note that visualization styles do not require a layer view, and can be instantiated and used independently from the MapView; this can be useful for testing and for generating thumbnails and galleries.

One thing that all the visualization styles in the repo have in common, is the way the transform matrices are handled. For starters, they all use two matrices named u_ScreenFromLocal and u_ClipFromScreen, their uniform locations are always kept in the global resources object, and the actual mat4 instances are always kept in the local resources object. In addition to this, their computation is always done in the same way.

Here is how u_ScreenFromLocal is computed.

And here is how u_ClipFromScreen is computed.

Code sample #1: The view state “test pattern”

This visualization displays the extent passed to loadLocalResources() as a translucent rectangle with a dashed border. It also displays the bounds of the extent in map units.

Some notes about the implementation.

Code sample #2: The fancy animated raster

This visualization loads elevation data from an imagery tiled service and animates the opacity of each pixel using a formula that combines the current time, the elevation of the pixel, and a sine function.

Some notes about the implementation.

Code sample #3: The shimmering power plants

This visualization loads points from a feature layer and renders them as shimmering luminous dots. The color of the dot is attribute-driven, and the falloff of the opacity is animated using a sine function.

Some notes about the implementation.


We hope that you enjoyed this deep dive into the world of custom WebGL layers and that you will find the animated-flow-ts repository useful for your project.

Clone it! Study it! Mod it! Ship it!

Go build something amazing and share your experience on the animated-flow-ts thread on!

Happy coding!

About the author

Hello, my name is Dario! I majored in CS with a focus on formal verification methods at the University of Florence, Italy. I live in Redlands with my wife and two sons and work as a rendering engineer at Esri.

Notify of
Inline Feedbacks
View all comments

Next Article

ArcGIS StoryMaps briefings: the essentials

Read this article