Ulysses

Ulysses creates a narrative around geography by linking narrative steps to actions on a map. Each step is a feature in a GeoJSON feature collection, with a specific action defined as a property.

Actions are functions that change the map state -- panning, zooming, rotating, adding or removing layers -- anything you can do with the underlying map.

new Ulysses(map: mapboxgl.Map, steps: (Object | Array), actions: Object)
Parameters
map (mapboxgl.Map) The map we're controlling. For now, only mapbox is supported.
steps ((Object | Array)) A GeoJSON feature collection , or an array of features, where each feature is a step in our story.
actions (Object) An object containing action functions to be called for each feature. This gets merged into default actions ( flyTo , fitBounds , noop ) so you can override those defaults by adding new functions of the same name.
Instance Members
current
length
next()
previous()
step(n)
on(event, callback)
once(event, callback)
off(event, callback)
trigger(event, detail)
use(plugin)
destroy()

Examples

The biggest fires of 2018

Use the left and right arrow keys to move through the 10 largest wildfires for 2018.

Use numbers 0 through 9 to jump to a specific fire.

Every In-N-Out

Jump to a new In-N-Out location every few seconds. Hit the spacebar to stop and start.

Source: All the Places

The Odyssey

A scrollable story depicting the locations in Homer's Odyssey.

Source: ESRI Story Map

Events

Built-in events

A Ulysses object triggers events as you move through steps, and you can listen to these to keep other parts of your interface in sync. Some steps receive additional data, which a callback can use.

Note that both the next and previous events fire before any actions are triggered. Use step for callbacks that should fire after the step completes.

start: Fires before the first step. No additional data is included.

next (step, feature): Fires each time story.next() is called, before actually moving to the next step. The included event has two members:

  • step: the index of current step (before changing)
  • feature: the GeoJSON feature associated with the current step

previous (step, feature): Fires each time story.previous() is called, before actually moving to the previous step. The included event has two members:

  • step: the index of current step (before changing)
  • feature: the GeoJSON feature associated with the current step

step (step, feature): Fires each time story.step(n) is called, after the step and associated action have fired (though not necessarily after all map updates have completed). The included event has two members:

  • step: the index of the current step (after changing)
  • feature: the GeoJSON feature associated with the current step

end: Fires after the last step. No additional data is included.

destroy: Fires when story.destroy() method is called, just before removing event listers and running plugin cleanup functions. No additional data is included.

Custom events

It's possible to define custom events. For example, an action might trigger a custom event to signal that it's finished, and a callback can listen for it.

let success = false;

story.on("success", e => {
	success = e.success;
});

story.trigger("success", { success: true });

Built-in actions

These actions are included by default. Override them by creating functions of the same name on your actions object.

Built-in actions
Static Members
flyTo(map, feature)
fitBounds(map, feature)
noop()

Included plugins

A plugin is a function that adds functionality to a Ulysses instance. Add plugins in one of two ways:

// when creating a story, use the `plugins` array
const story = new Ulysses({ map, steps, plugins: [func, anotherFunc] });

// later, with the `use()` method
story.use(plugin);

In each case, the plugin callable takes a Ulysses instance as its only argument. For plugins that take configuration, you can use a function that returns a function. Finally, the plugin function may return a cleanup function, which will be called when a Ulysses instance is destroyed.

Here's an example plugin that adds keyboad controls:

export default function keys({ previous = "ArrowLeft", next = "ArrowRight" } = {}) {
	return story => {
		function keydown({ key }) {
			if (key === previous) {
				story.previous();
			}

			if (key === next) {
				story.next();
			}

			if (key.match(/\d{1}/)) {
				story.step(+e.key);
			}
		}

		window.addEventListener("keydown", keydown);

		// on destroy
		return () => {
			window.removeEventListener("keydown", keydown);
		};
	};
}

// later
story.use(keys());

// when we're done, this will remove the event listener
story.destroy();

The first function -- keys() -- is called to initialize the plugin and returns a function. That function is called with our Ulysses instance. When the story is eventually destroyed (for example, when routing to a new URL in a single-page application), the final cleanup function will remove event listers on the window.

Included plugins
Static Members
keys(options)
timer(options)
scroll(options)

Utilities

These might help when defining your own actions.

Utilities
Static Members
getAction(feature, actions)
extractOptions(properties, keys, cast)