1.4.2
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.
(mapboxgl.Map)
The map we're controlling. For now, only mapbox is supported.
((Object | Array))
A
GeoJSON feature collection
,
or an array of features, where each feature is a step in our story.
(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.
Advance one step (unless we're at the end)
Go back a step (unless we're at the beginning)
Remove event listers and call plugin cleanup functions. Triggers a "destroy" event with no data.
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.
Jump to a new In-N-Out location every few seconds. Hit the spacebar to stop and start.
Source: All the Places
A scrollable story depicting the locations in Homer's Odyssey.
Source: ESRI Story Map
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 stepprevious (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 stepstep (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 stepend: 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.
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 });
These actions are included by default. Override them by creating functions of the same name on your actions object.
flyTo wraps Mapbox's Map#flyTo method, extracting arguments from feature properties and centering on this feature's geometry (which should be a point).
The following properties are extracted, converted to numbers and passed to Map#flyTo:
zoom
bearing
pitch
duration
(mapboxgl.Map)
The map attached to our current story
(Object)
The feature in view
Name | Description |
---|---|
feature.geometry any
|
|
feature.properties any
|
fitBounds finds a bounding box for a feature and fits the map to it, using Map#fitBounds. If the feature has a bbox attribute, that is used. Otherwise, we calculate a bounding box and use that.
The following properties are extracted, converted to numbers and passed to Map#fitBounds:
maxZoom
bearing
pitch
duration
padding
(single padding only)(mapboxgl.Map)
The map attached to our current story
(Object)
The feature in view
noop is the best action because it wants nothing and gives nothing in return
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
.
Add keyboard controls to a story. By default, this adds listeners for the left and right keys, stepping backward and forward, respectively. Using the numeric keys 0 - 9 will go to those steps.
Usage:
import keys from "ulysses-js/plugins/keys";
story.use(keys({ previous: "ArrowLeft", next: "ArrowRight" }))
See Every fire in 2018;
Add a timer that advances the map every duration
seconds.
Usage:
import timer from "ulysses-js/plugins/timer";
story.use(timer({ duration: 5000, toggle: "Space", start: true }));
This plugin adds additional methods to a Ulysses instance:
Ulysses#start()
- Start the timerUlysses#stop()
- Stop the timerUlysses#toggle()
- Stop or start, depending on current stateEach of these emits an event -- timer.start
, timer.stop
, timer.toggle
--
that can be used to coordinate other actions.
(Object
= {}
)
[{ duration, toggle = "Space" }={}]
Name | Description |
---|---|
options.duration Number
(default 5000 )
|
Time between steps, in milliseconds (default: 5000) |
options.toggle Space
(default "Space" )
|
Key to toggle starting and stopping the timer (default:
"Space"
)
|
options.start Boolean
(default true )
|
Start the timer immediately. If false, call
story.start()
to begin.
|
Add scroll interaction to a Ulysses story using scrollama. As each element scrolls into view, the story will trigger a step.
Any options given during initialization are passed through to scrollama. See scrollama's API documentation for a full list of available options.
Usage:
import scroll from "ulysses-js/plugins/scroll";
story.use(scroll({ step: ".step" }));
(scrollama.ScrollamaOptions
= {}
)
These might help when defining your own actions.
Get the action function based on feature properties
First, check for an action
property that matches a member of actions.
Next, try a default action.
fitBounds
flyTo
fitBounds
(Object
= {}
)
an object with defined actions to use. Each action is a function that takes
a map and the current feature in view.
function
:
with the signature
action(map, feature): void