Skip to content

Instantly share code, notes, and snippets.

@texodus
Forked from JHawk/.block
Last active August 10, 2022 19:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save texodus/bc8d7e6f72e09c9dbd7424b4332cacad to your computer and use it in GitHub Desktop.
Save texodus/bc8d7e6f72e09c9dbd7424b4332cacad to your computer and use it in GitHub Desktop.
Perspective NYC Citibike Example
license: apache-2.0

Demo of Perspective.

A real-time map of NYC Citi Bike stations colored by the number of bikes available at each updating once per second.

// 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 = await 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 get_layout() {
const req = await fetch("layout.json");
const json = await req.json();
return json;
}
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 = await 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);
window.workspace.tables.set("citibike", Promise.resolve(table));
const layout = await get_layout();
window.workspace.restore(layout);
}
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;
}
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,minimum-scale=1,user-scalable=no"/>
<link rel="stylesheet" crossorigin="anonymous" href="https://cdn.jsdelivr.net/npm/@finos/perspective-workspace/dist/css/material.css" />
<link rel="stylesheet" href="index.css" />
<script src="https://cdn.jsdelivr.net/npm/@finos/perspective-workspace@latest"></script>
<script src="https://cdn.jsdelivr.net/npm/@finos/perspective-viewer-datagrid@latest"></script>
<script src="https://cdn.jsdelivr.net/npm/@finos/perspective-viewer-d3fc@latest"></script>
<script src="https://cdn.jsdelivr.net/npm/@finos/perspective-viewer-openlayers/dist/umd/perspective-viewer-openlayers.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@finos/perspective@latest"></script>
<script src="citibike.js"></script>
<style>
body {
display: flex;
flex-direction: column;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin: 0;
padding: 0;
overflow: hidden;
}
</style>
</head>
<body>
<perspective-workspace id="workspace"></perspective-workspace>
</body>
</html>
{
"sizes": [
1
],
"detail": {
"main": {
"type": "split-area",
"orientation": "horizontal",
"children": [
{
"type": "tab-area",
"widgets": [
"PERSPECTIVE_GENERATED_ID_0"
],
"currentIndex": 0
},
{
"type": "tab-area",
"widgets": [
"PERSPECTIVE_GENERATED_ID_1"
],
"currentIndex": 0
}
],
"sizes": [
0.6,
0.4
]
}
},
"mode": "globalFilters",
"viewers": {
"PERSPECTIVE_GENERATED_ID_0": {
"plugin": "X/Y Scatter",
"columns": [
"lon",
"lat",
"num_bikes_available"
],
"sort": [
[
"num_bikes_available",
"asc"
]
],
"plugin_config": {
"realValues": [
"lon",
"lat",
"num_bikes_available"
]
},
"master": false,
"table": "citibike",
"linked": false,
"name": "Map"
},
"PERSPECTIVE_GENERATED_ID_1": {
"plugin": "datagrid",
"columns": [
"capacity",
"num_bikes_available",
"name"
],
"sort": [["last_reported", "desc"]],
"master": false,
"table": "citibike",
"linked": false,
"name": "Recently updated"
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment