Skip to content

Instantly share code, notes, and snippets.

@vlaurin
Last active April 22, 2018 06:49
Show Gist options
  • Save vlaurin/34685a2fc8159ba95f2831aef637c6ef to your computer and use it in GitHub Desktop.
Save vlaurin/34685a2fc8159ba95f2831aef637c6ef to your computer and use it in GitHub Desktop.
Dazzle widget - TflStopPointArrivals

Dazzle widget - TflStopPointArrivals

A Dazzle widget showing predictions for next arrivals at a TfL stop point, powered by Transport for London's Unified API.

alt text

Use

  1. Copy TflStopPointArrivals.js to your dashboard project.
  2. Add to your widgets and configure. For example:
{
    widgets: {
        ...,
        YourWidgetKey: {
            type: TflStopPointArrivals,
            title: 'Next bus',
            props: {
                stopPointId: '490011348E',
                refreshInterval: 10000,
                lineIds: [
                    '131',
                    '152',
                ],
                limit: 3,
            },
        },
    },
    layout: {...}
}

Props

Name Type Required Default Description
stopPointId string Yes N/A Unique stop point ID, as provided by TfL's API.
refreshInterval number No 10,000ms Interval for refreshing the widget, in milli-seconds.
lineIds string[] No All lines When provided, filter out lines for which the id is not specified.
limit number No 3 Maximum number of arrivals to display.

Styling

Styling is achieved using Bootstrap v4 Card component and a custom WidgetFrame.

See dazzle-bootstrap-seed to achieve similar results. Or change the render() method and make it your own!

Notes

Sorting

Arrivals are displayed by shortest waiting time first.

Limit

The number of predictions returned by TfL may vary, as such using a limit only guaranties that the number of results displayed will be between 0 and the limit, included. Filtering by line IDs may further reduce the number of results displayed.

Transport modes

I only tested for bus stops, but stop points for other modes of transport should work seamlessly.

Extensions

More informations are returned by TfL's API and could be displayed, for example the destinationName. See Swagger for the full contract: https://api.tfl.gov.uk/swagger/ui/index.html?url=/swagger/docs/v1#!/StopPoint/StopPoint_Arrivals

import React from 'react';
import PropTypes from 'prop-types';
class TflStopPointArrivals extends React.Component {
constructor() {
super();
this.state = {
arrivals: [],
loading: false,
};
}
componentDidMount() {
const {
lineIds,
limit,
} = this.props;
this.filter = filterArrivals(lineIds);
this.limit = limitArrivals(limit);
this.loadArrivals();
const {
refreshInterval,
} = this.props;
const refreshIntervalId = setInterval(() => this.loadArrivals(), refreshInterval);
this.setState(Object.assign({}, this.state, { refreshIntervalId }));
}
componentWillUnmount() {
clearInterval(this.state.refreshIntervalId);
}
loadArrivals () {
if (this.state.loading) {
return; // Skip
}
const {
stopPointId,
} = this.props;
this.setState(Object.assign({}, this.state, { loading: true }));
fetch(`https://api.tfl.gov.uk/StopPoint/${stopPointId}/arrivals`)
.then(res => res.ok ? res : Promise.reject(res.statusText))
.then(res => res.json())
.then(this.filter)
.then(sortArrivals)
.then(this.limit)
.then(arrivals => {
if (arrivals) {
this.setState({
arrivals,
});
}
})
.catch(error => console.error(`Failed to retrieve arrivals for stop ${stopPointId}:`, error))
.then(() => {
this.setState({
loading: false,
});
});
}
render() {
return (
<ul className="list-group list-group-flush">
{this.state.arrivals.map((arrival, index) => (
<li key={arrival.id}
className="list-group-item"
style={!index ? style.nextArrival : style.arrival}>
<strong>{arrival.lineName}</strong>
{formatTimeToStation(arrival)}
</li>
))}
</ul>
);
}
}
TflStopPointArrivals.propTypes = {
stopPointId: PropTypes.string.isRequired,
refreshInterval: PropTypes.number,
lineIds: PropTypes.arrayOf(PropTypes.string),
limit: PropTypes.number,
};
TflStopPointArrivals.defaultProps = {
refreshInterval: 10000,
lineIds: null,
limit: 3
};
const style = {
arrival: {
fontSize: '1.5rem',
fontWeight: '400',
lineHeight: '1',
},
nextArrival: {
fontSize: '3rem',
fontWeight: '600',
lineHeight: '1.2',
},
};
const filterArrivals = lineIds => arrivals => arrivals.filter(arrival => !lineIds || lineIds.includes(arrival.lineId));
const sortArrivals = arrivals => arrivals.sort((a, b) => a.timeToStation - b.timeToStation);
const limitArrivals = limit => arrivals => arrivals.slice(0, limit);
const formatTimeToStation = arrival => {
const minutesLeft = Math.round(arrival.timeToStation / 60);
return (
<span className="float-right">{displayTime(minutesLeft)}</span>
);
};
const displayTime = minutesLeft => {
switch (minutesLeft) {
case 0:
return 'Due';
case 1:
return '1 min';
default:
return `${minutesLeft} mins`
}
};
export default TflStopPointArrivals;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment