Kepler.gl is a data-agnostic, high-performance web-based application for visual exploration of large-scale geolocation data sets. Built on top of Mapbox GL and deck.gl, kepler.gl can render millions of points representing thousands of trips and perform spatial aggregations on the fly.
Kepler.gl is also a React component that uses Redux to manage its state and data flow. It can be embedded into other React-Redux applications and is highly customizable. For information on how to embed kepler.gl in your app take a look at this step-by-step tutorial on vis.academy.
- Website
- Demo
- Examples
- Get Started
- App User Guide
- Tutorial
- Stack Overflow
- Contribution Guidelines
- Api Reference
- Roadmap
Use Node v6 and above, older node versions have not been tested.
For best results, use nvm nvm install
.
Install node (> 6
), yarn, and project dependencies
npm install --save kepler.gl
// or
yarn add kepler.gl
kepler.gl is built upon mapbox. You will need a Mapbox Access Token to use it.
If you don't use a module bundler, it's also fine. Kepler.gl npm package includes precompiled production UMD builds in the (umd folder)[https://unpkg.com/kepler.gl/umd]. You can add the script tag to your html file as it follows:
<script src="https://unpkg.com/kepler.gl/umd/keplergl.min.js"></script>
or if you would like, you can load a specific version
<script src="https://unpkg.com/kepler.gl@0.2.2/umd/keplergl.min.js"></script>
Take a look at the development guide to develop kepler.gl locally.
Here are the basic steps to import kepler.gl into your app. You also take a look at the examples folder. Each example in the folder can be installed and run locally.
Kepler.gl uses Redux to manage its internal state, along with react-palm middleware to handle side effects.
You need to add taskMiddleware
of react-palm
to your store too. We are actively working on a solution where
react-palm
will not be required, however it is still a very lightweight side effects management tool that is easier to test than react-thunk.
import keplerGlReducer from 'kepler.gl/reducers';
import {createStore, combineReducers, applyMiddleware, compose} from 'redux';
import {taskMiddleware} from 'react-palm/tasks';
const initialState = {};
const reducers = combineReducers({
// <-- mount kepler.gl reducer in your app
keplerGl: keplerGlReducer,
// Your other reducers here
app: appReducer
});
// using createStore
export default createStore(reducer, initialState, applyMiddleware(taskMiddleware));
Or if use enhancer:
// using enhancers
const initialState = {};
const middlewares = [taskMiddleware];
const enhancers = [
applyMiddleware(...middlewares)
];
export default createStore(reducer, initialState, compose(...enhancers));
If you mount kepler.gl reducer in another address instead of keplerGl
, or the kepler.gl reducer is not
mounted at root of your state, you will need to specify the path to it when you mount the component
with the getState
prop.
Read more about Reducers.
import KeplerGl from 'kepler.gl';
const Map = props => (
<KeplerGl
id="foo"
width={width}
mapboxApiAccessToken={token}
height={height}/>
);
- Default:
map
The id of this KeplerGl instance. id
is required if you have multiple
KeplerGl instances in your app. It defines the prop name of the KeplerGl state that is
stored in the KeplerGl reducer. For example, the state of the KeplerGl component with id foo
is
stored in state.keplerGl.foo
.
In case you create multiple kepler.gl instances using the same id, the kepler.gl state defined by the entry will be overridden by the latest instance and reset to a blank state.
- Default:
undefined
You can create a free account at mapbox and create a token at www.mapbox.com/account/access-tokens
- Default:
state => state.keplerGl
The path to the root keplerGl state in your reducer.
- Default:
800
Width of the KeplerGl UI.
- Default:
800
- Default:
Kepler.Gl
App name displayed in side panel header
- Default:
v1.0
version displayed in side panel header
- Default:
() => {}
Action called when click Save Map Url in side panel header.
- Default:
{}
Actions creators to replace default kepler.gl action creator. Only use custom action when you want to modify action payload.
- Default:
true
Whether to load a fresh empty state when component is mounted. when parse mint: true
kepler.gl component will always load a fresh state when re-mount the same component, state inside this component will be destroyed once its unmounted.
By Parsing mint: false
kepler.gl will keep the component state in the store even when it is unmounted, and use it as initial state when re-mounted again. This is useful when mounting kepler.gl in a modal, and keep the same map when re-open.
Read more about Components.
One advantage of using the reducer over React component state to handle keplerGl state is the flexibility
to customize its behavior. If you only have one KeplerGl
instance in your app or never intend to dispatch actions to KeplerGl from outside the component itself,
you don’t need to worry about forwarding dispatch and can move on to the next section. But life is full of customizations, and we want to make yours as enjoyable as possible.
There are multiple ways to dispatch actions to a specific KeplerGl
instance.
- In the root reducer, with reducer updaters.
Each action is mapped to a reducer updater in kepler.gl. You can import the reducer updater corresponding to a specific action, and call it with the previous state and action payload to get the updated state.
e.g. updateVisDataUpdater
is the updater for ActionTypes.UPDATE_VIS_DATA
(take a look at each reducer reducers/vis-state.js
for action to updater mapping).
Here is an example how you can listen to an app action QUERY_SUCCESS
and call updateVisDataUpdater
to load data into Kepler.Gl.
import keplerGlReducer, {visStateUpdaters} from 'kepler.gl/reducers';
// Root Reducer
const reducers = combineReducers({
keplerGl: keplerGlReducer,
app: appReducer
});
const composedReducer = (state, action) => {
switch (action.type) {
case 'QUERY_SUCCESS':
return {
...state,
keplerGl: {
...state.keplerGl,
// 'map' is the id of the keplerGl instance
map: {
...state.keplerGl.map,
visState: visStateUpdaters.updateVisDataUpdater(
state.keplerGl.map.visState, {datasets: action.payload})
}
}
};
}
return reducers(state, action);
};
export default composedReducer;
Read more about using updaters to modify kepler.gl state
- Using redux
connect
You can add a dispatch function to your component that dispatches actions to a specific keplerGl
component,
using connect.
// component
import KeplerGl from 'kepler.gl';
// action and forward dispatcher
import {toggleFullScreen, forwardTo} from 'kepler.gl/actions';
import {connect} from 'react-redux';
const MapContainer = props => (
<div>
<button onClick={() => props.keplerGlDispatch(toggleFullScreen())}/>
<KeplerGl
id="foo"
/>
</div>
)
const mapStateToProps = state => state
const mapDispatchToProps = (dispatch, props) => ({
dispatch,
keplerGlDispatch: forwardTo(‘foo’, dispatch)
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(MapContainer);
- Wrap action payload
You can also simply wrap an action into a forward action with the wrapTo
helper
// component
import KeplerGl from 'kepler.gl';
// action and forward dispatcher
import {toggleFullScreen, wrapTo} from 'kepler.gl/actions';
// create a function to wrapper action payload to 'foo'
const wrapToMap = wrapTo('foo');
const MapContainer = ({dispatch}) => (
<div>
<button onClick={() => dispatch(wrapToMap(toggleFullScreen())} />
<KeplerGl
id="foo"
/>
</div>
);
Read more about forward dispatching actions
Everyone wants the flexibility to render custom kepler.gl components. Kepler.gl has a dependency injection system that allow you to inject
components to KeplerGl replacing existing ones. All you need to do is to create a component factory for the one you want to replace, import the original component factory
and call injectComponents
at the root component of your app where KeplerGl
is mounted.
Take a look at examples/demo-app/src/app.js
and see how it renders a custom side panel header in kepler.gl
import {injectComponents, PanelHeaderFactory} from 'kepler.gl/components';
// define custom header
const CustomHeader = () => (<div>My kepler.gl app</div>);
const myCustomHeaderFactory = () => CustomHeader;
// Inject custom header into Kepler.gl, replacing default
const KeplerGl = injectComponents([
[PanelHeaderFactory, myCustomHeaderFactory]
]);
// render KeplerGl, it will render your custom header instead of the default
const MapContainer = () => (
<div>
<KeplerGl
id="foo"
/>
</div>
);
Using withState
helper to add reducer state and actions to customized component as additional props.
import {withState, injectComponents, PanelHeaderFactory} from 'kepler.gl/components';
import {visStateLens} from 'kepler.gl/reducers';
// custom action wrap to mounted instance
const addTodo = (text) => wrapTo('map', {
type: 'ADD_TODO',
text
});
// define custom header
const CustomHeader = ({visState, addTodo}) => (
<div onClick={() => addTodo('hello')}>{`${Object.keys(visState.datasets).length} dataset loaded`}</div>
);
// now CustomHeader will receive `visState` and `addTodo` as additional props.
const myCustomHeaderFactory = () => withState(
// keplerGl state lenses
[visStateLens],
// customMapStateToProps
headerStateToProps,
// actions
{addTodo}
)(CustomHeader);
Read more about replacing UI component
To interact with a kepler.gl instance and add new data to it, you can dispatch addDataToMap
action from anywhere inside your app. It adds a dataset or multiple datasets to kepler.gl instance and update the full configuration (mapState, mapStyle, visState).
datasets
(Array<Object> | Object) *required datasets can be a dataset or an array of datasets Each dataset object needs to haveinfo
anddata
property.options
Objectconfig
Object this object will contain the full kepler.gl instance configuration {mapState, mapStyle, visState}
Kepler.gl provides an easy API KeplerGlSchema.getConfigToSave
to generate a json blob of the current kepler instance configuration.
// app.js
import {addDataToMap} from 'kepler.gl/actions';
const sampleTripData = {
fields: [
{name: 'tpep_pickup_datetime', format: 'YYYY-M-D H:m:s', type: 'timestamp'},
{name: 'pickup_longitude', format: '', type: 'real'},
{name: 'pickup_latitude', format: '', type: 'real'}
],
rows: [
['2015-01-15 19:05:39 +00:00', -73.99389648, 40.75011063],
['2015-01-15 19:05:39 +00:00', -73.97642517, 40.73981094],
['2015-01-15 19:05:40 +00:00', -73.96870422, 40.75424576],
]
};
const sampleConfig = {
visState: {
filters: [
{
id: 'me',
dataId: 'test_trip_data',
name: 'tpep_pickup_datetime',
type: 'timeRange',
enlarged: true
}
]
}
}
this.props.dispatch(
addDataToMap({
datasets: {
info: {
label: 'Sample Taxi Trips in New York City',
id: 'test_trip_data'
},
data: sampleTripData
},
option: {
centerMap: true,
readOnly: false
},
config: sampleConfig
})
);
Read more about addDataToMap and Saving and loading maps with schema manager.