Skip to content

Instantly share code, notes, and snippets.

@JHawk
Last active July 29, 2020 04:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save JHawk/ade09a2ea62bb708cc0beab8c35609b0 to your computer and use it in GitHub Desktop.
Save JHawk/ade09a2ea62bb708cc0beab8c35609b0 to your computer and use it in GitHub Desktop.
Perspective NYC Citibike Example
license: apache-2.0
height: 1200

A real-time map of NYC Citi Bike stations colored by the number of bikes available at each updating once per second. A full blog post can be found here.

// Quick wrapper function for making a GET call.
function get(url) {
return new Promise(resolve => {
const xhr = new XMLHttpRequest();
xhr.open("GET", url, true);
xhr.responseType = "json";
xhr.onload = () => resolve(xhr.response);
xhr.send(null);
});
}
// Fetch feed data from NYC Citibike, if a callback is provided do it again every 1s asynchronously.
async function get_feed(feedname, callback) {
const url = `https://gbfs.citibikenyc.com/gbfs/en/${feedname}.json`;
const {data: {stations}, ttl} = await get(url);
if (typeof callback === "function") {
callback(stations);
setTimeout(() => get_feed(feedname, callback), ttl * 1000);
} else {
return stations;
}
}
// Create a new Perspective WebWorker instance.
const worker = perspective.worker();
// Use Perspective WebWorker's table to infer the feed's schema.
async function get_schema(feed) {
const table = worker.table(feed);
const schema = await table.schema();
table.delete();
return schema;
}
// Create a superset of the schemas defined by the feeds.
async function merge_schemas(feeds) {
const schemas = await Promise.all(feeds.map(get_schema));
return Object.assign({}, ...schemas);
}
async function main() {
const feednames = ["station_status", "station_information"];
const feeds = await Promise.all(feednames.map(get_feed));
const schema = await merge_schemas(feeds);
// Creating a table by joining feeds with an index
const table = worker.table(schema, {index: "station_id"});
// Load the `table` in the `<perspective-viewer>` DOM reference with the initial `feeds`.
for (let feed of feeds) {
table.update(feed);
}
// Start a recurring asyn call to `get_feed` and update the `table` with the response.
get_feed("station_status", table.update);
// Get `<perspective-viewer> elements from the DOM.
const viewers = document.getElementsByTagName("perspective-viewer");
// Load the `table` for each `<perspective-viewer>` DOM reference.
for (viewer of viewers) {
viewer.load(table);
}
}
main();
#grid {
display: flex;
max-width: 600px;
max-height: 1200px;
margin: auto;
flex-direction: column;
}
#grid perspective-viewer {
height: 600px;
width: 600px;
flex: 1;
display: block;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
<script src="https://unpkg.com/@finos/perspective/build/perspective.js"></script>
<script src="https://unpkg.com/@finos/perspective-viewer/build/perspective.view.js"></script>
<script src="https://unpkg.com/@finos/perspective-viewer-hypergrid/build/hypergrid.plugin.js"></script>
<script src="https://unpkg.com/@finos/perspective-viewer-d3fc/build/d3fc.plugin.js"></script>
<script src="citibike.js"></script>
<link rel='stylesheet' href="https://unpkg.com/@finos/perspective-viewer/build/material.css" is="custom-style">
<link rel='stylesheet' href="index.css">
</head>
<body>
<div id="grid">
<perspective-viewer
row-pivots='["name"]'
columns='["num_bikes_available"]'
sort='[["num_bikes_available","desc"]]'>
</perspective-viewer>
<perspective-viewer
view='d3_xy_scatter'
row-pivots='["name"]'
columns='["lon","lat","num_bikes_available"]'
sort='[["num_bikes_available","asc"]]'>
</perspective-viewer>
</div>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment