diff --git a/package.json b/package.json
index e73fd9b..2e58551 100644
--- a/package.json
+++ b/package.json
@@ -25,6 +25,7 @@
"jquery": "^3.3.1",
"mapbox-gl": "^0.44.0",
"react": "^16.2.0",
- "react-dom": "^16.2.0"
+ "react-dom": "^16.2.0",
+ "prop-types": "^15.6.0"
}
}
diff --git a/src/css/stylish.css b/src/css/stylish.css
index 2353ab7..ae091de 100644
--- a/src/css/stylish.css
+++ b/src/css/stylish.css
@@ -97,3 +97,21 @@ button {
.vex-content h1 {
font-size: 32px;
}
+
+/* Mapbox GL Stuff */
+
+.marker {
+ background-image: url('../img/marker.png');
+ background-size: contain;
+ width: 30px;
+ height: 77px;
+ cursor: pointer;
+}
+
+.mapboxgl-popup {
+ max-width: 200px;
+}
+
+.mapboxgl-popup-content {
+ font-family: 'Open Sans', sans-serif;
+}
diff --git a/src/img/marker.png b/src/img/marker.png
new file mode 100644
index 0000000..4c87bec
Binary files /dev/null and b/src/img/marker.png differ
diff --git a/src/js/app.jsx b/src/js/app.jsx
index 8e7f4e4..61d6c09 100644
--- a/src/js/app.jsx
+++ b/src/js/app.jsx
@@ -1,25 +1,36 @@
import React, { Component } from 'react';
import { render } from 'react-dom';
-import Parser from './components/parser';
-import Util from './components/util';
-import ShowMap from './components/map';
+import Parser from './components/Parser';
+import Dates from './components/dates/Dates';
+import ShowMap from './components/Map';
import '../css/stylish.css';
class Application extends Component {
- static plot() {
+ static prepare() {
const parsed = new Parser().parseData();
+ // keys: organized, dates
parsed.then((data) => {
- Util.populateDates(data.dates);
+ // Pop modal?
+ // TODO
+
+ // Add the dates
+ const dateEl = document.getElementById('date-selector-container');
+ render(, dateEl);
+
+ // Add the shows
+ const mapEl = document.getElementById('app');
+ render(, mapEl);
+ console.log(data.geojson)
});
}
render() {
- Application.plot();
+ Application.prepare();
return (
-
+
);
}
}
diff --git a/src/js/components/Plotter.js b/src/js/components/Plotter.js
new file mode 100644
index 0000000..d2ba40f
--- /dev/null
+++ b/src/js/components/Plotter.js
@@ -0,0 +1,69 @@
+export default class Plotter {
+ static plotShows(organized) {
+ return organized;
+ }
+}
+
+/*
+function plotShows(geojson) {
+ // update function for coordinates infobox
+ window.onmove = function onmove() {
+ // Get the map bounds - the top-left and bottom-right locations.
+ const inBounds = [],
+ bounds = map.getBounds();
+ clusterGroup.eachLayer((marker) => {
+ // For each marker, consider whether it is currently visible by comparing
+ // with the current map bounds.
+ if (bounds.contains(marker.getLatLng()) && selectedDatesList.indexOf(marker.feature.properties.date) !== -1) {
+ const feature = marker.feature;
+ const coordsTemplate = L.mapbox.template('{{properties.date}} - {{properties.venue}} |{{#properties.bands}} {{.}} |{{/properties.bands}}{{properties.details}}', feature);
+ inBounds.push(coordsTemplate);
+ }
+ });
+ // Display a list of markers.
+ inBounds.reverse();
+ document.getElementById('coordinates').innerHTML = inBounds.join('\n');
+ };
+
+ // attach data
+ const myLayer = L.mapbox.featureLayer(geojson);
+
+ // make clustergroup
+ const clusterGroup = ModifiedClusterGroup();
+ // add features
+ clusterGroup.addLayer(myLayer);
+ overlays = L.layerGroup().addTo(map);
+ // add cluster layer
+ // overlays are multiple layers
+ // add in showShows()
+ showShows();
+
+ // for each layer in feature layer
+ myLayer.eachLayer((e) => {
+ const marker = e;
+ const feature = e.feature;
+
+ // Create custom popup content
+ const popupContent = L.mapbox.template(' {{properties.venue}}
{{properties.date}}
{{#properties.bands}} - {{.}}
{{/properties.bands}}
{{properties.details}}
', feature);
+
+ marker.bindPopup(popupContent, {
+ closeButton: true,
+ minWidth: 320,
+ });
+ });
+
+
+ map.on('move', onmove);
+ // call onmove off the bat so that the list is populated.
+ // otherwise, there will be no markers listed until the map is moved.
+ window.onmove();
+}
+
+function ModifiedClusterGroup() {
+ return new L.MarkerClusterGroup({
+ spiderfyOnMaxZoom: true,
+ maxClusterRadius: 1,
+ spiderfyDistanceMultiplier: 3,
+ });
+}
+*/
diff --git a/src/js/components/dates/DateSelector.jsx b/src/js/components/dates/DateSelector.jsx
new file mode 100644
index 0000000..59b7cc2
--- /dev/null
+++ b/src/js/components/dates/DateSelector.jsx
@@ -0,0 +1,24 @@
+import React, { Component } from 'react';
+import PropTypes from 'prop-types';
+
+export default class DateSelector extends Component {
+ render() {
+ return (
+
+ { this.props.date }
+
+ );
+ }
+}
+
+DateSelector.propTypes = {
+ date: PropTypes.string.isRequired,
+ showShows: PropTypes.func.isRequired,
+};
+
diff --git a/src/js/components/dates/Dates.jsx b/src/js/components/dates/Dates.jsx
new file mode 100644
index 0000000..23c5422
--- /dev/null
+++ b/src/js/components/dates/Dates.jsx
@@ -0,0 +1,33 @@
+import React, { Component } from 'react';
+import PropTypes from 'prop-types';
+
+import DateSelector from './DateSelector';
+
+export default class Dates extends Component {
+ constructor(props) {
+ super(props);
+ this.makeDateSelectors = this.makeDateSelectors.bind(this);
+ this.showShows = this.showShows.bind(this);
+ }
+
+ // eslint-disable-next-line
+ showShows(){}
+
+ makeDateSelectors(dates) {
+ const selectors = [];
+ for (let d = 0; d < dates.length; d += 1) {
+ const selector = ();
+ selectors.push(selector);
+ }
+ return selectors;
+ }
+ render() { return this.makeDateSelectors(this.props.dates); }
+}
+
+Dates.propTypes = {
+ dates: PropTypes.array.isRequired,
+};
diff --git a/src/js/components/map.jsx b/src/js/components/map.jsx
index 794167d..426deec 100644
--- a/src/js/components/map.jsx
+++ b/src/js/components/map.jsx
@@ -1,13 +1,16 @@
import React, { Component } from 'react';
+import PropTypes from 'prop-types';
import mapboxgl from 'mapbox-gl';
import 'mapbox-gl/dist/mapbox-gl.css';
+import Venues from '../../data/venues.json';
+
mapboxgl.accessToken = 'pk.eyJ1IjoibWV0YXN5biIsImEiOiIwN2FmMDNhNTRhOWQ3NDExODI1MTllMDk1ODc3NTllZiJ9.Bye80QJ4r0RJsKj4Sre6KQ';
class ShowMap extends Component {
- constructor(Props) {
- super(Props);
+ constructor(props) {
+ super(props);
// Centered on SF
this.state = {
@@ -15,6 +18,8 @@ class ShowMap extends Component {
lat: 37.76,
zoom: 13,
};
+
+ this.bindMap = this.bindMap.bind(this);
}
componentDidMount() {
@@ -27,6 +32,7 @@ class ShowMap extends Component {
zoom,
});
+ // Add locator control
map.addControl(new mapboxgl.GeolocateControl({
positionOpionts: {
enableHighAccuracy: true,
@@ -34,26 +40,64 @@ class ShowMap extends Component {
trackUserLocation: true,
}));
+ // Add the actual shows
+ map.on('load', () => {
+ map.addSource('shows', {
+ type: 'geojson',
+ data: this.props.geojson,
+ });
+ });
+
map.on('move', () => {
- const { lng, lat } = map.getCenter();
+ const center = map.getCenter();
this.setState({
- lng: lng.toFixed(4),
- lat: lat.toFixed(4),
+ lng: center.lng.toFixed(4),
+ lat: center.lat.toFixed(4),
zoom: map.getZoom().toFixed(2),
});
});
+
+ ShowMap.plotMarkers(this.props.geojson, map);
}
- render() {
- const { lng, lat, zoom } = this.state;
+ bindMap(el) {
+ this.mapContainer = el;
+ }
+ static plotMarkers(geojson, map) {
+ geojson.features.forEach((marker) => {
+ const el = document.createElement('div');
+ el.className = 'marker';
+
+ const lngLat = Venues[marker.geometry.coordinates];
+ const popup = new mapboxgl.Popup({ offset: 25 }).setHTML(marker.properties.showHTML)
+
+ if (lngLat) {
+ try {
+ new mapboxgl.Marker(el)
+ .setLngLat(lngLat)
+ .setPopup(popup)
+ .addTo(map);
+ } catch (e) {
+ console.log(e)
+ }
+ }
+ });
+ }
+
+
+ render() {
return (
-
this.mapContainer = el} className="absolute top right left bottom" />
+
);
}
}
+ShowMap.propTypes = {
+ geojson: PropTypes.object.isRequired,
+};
+
export default ShowMap;
diff --git a/src/js/components/parser.js b/src/js/components/parser.js
index 95eb3b6..bf7e730 100644
--- a/src/js/components/parser.js
+++ b/src/js/components/parser.js
@@ -1,5 +1,8 @@
import $ from 'jquery';
+import Venues from '../../data/venues.json';
+import getEditDistance from './Util';
+
export default class Parser {
constructor() {
this.yql = Parser.makeYQL();
@@ -11,7 +14,8 @@ export default class Parser {
const results = Parser.parseHTMLtoDOM(success);
const dates = Parser.getDates(results);
const organized = Parser.sortByDate(results, dates);
- return { organized, dates };
+ const geojson = Parser.geojsonify(organized);
+ return { organized, geojson, dates };
})
.catch(e => Error((e)));
}
@@ -68,4 +72,64 @@ export default class Parser {
return organized;
}
+
+ static geojsonify(data) {
+ const features = [];
+ const dateKeys = Object.keys(data);
+
+ // loop through dates
+ for (let i = 0; i < dateKeys.length; i += 1) {
+ // loop through shows
+ for (let j = 0; j < data[dateKeys[i]].length; j += 1) {
+ const showData = data[dateKeys[i]][j];
+ const venueList = Object.keys(Venues);
+
+ // check for misspellings
+ if (!Venues[showData.venue]) {
+ try {
+ for (let v = 0; v < venueList.length; v += 1) {
+ const misspelled = showData.venue.replace(/\W/g, '');
+ const spelledCorrect = venueList[v].replace(/\W/g, '');
+ const editDistance = getEditDistance(misspelled, spelledCorrect);
+ if (editDistance <= 3) {
+ console.log(`"${showData.venue}" has been replaced with "${venueList[v]}"`);
+ showData.venue = venueList[v];
+ }
+ }
+ } catch (e) {
+ console.log('Missing Venue?', e);
+ }
+ }
+
+ const showString = `${dateKeys[i]} - ${showData.venue} | ${showData.bands.join(' |')} | ${showData.details}`;
+ const showHTML = `
${showData.venue}
${dateKeys[i]}
${showData.bands.join(' |')}
${showData.details}`;
+
+ const show = {
+ type: 'Feature',
+ geometry: {
+ type: 'Point',
+ coordinates: [showData.venue] || [-122.422960, 37.826524],
+ },
+ properties: {
+ date: dateKeys[i],
+ venue: showData.venue,
+ bands: showData.bands,
+ details: showData.details.replace(/ ,/g, ''), // fucking commas
+ showString,
+ showHTML,
+ },
+ };
+
+ // add show to features array
+ features.push(show);
+ }
+ }
+
+ // format for valid geojson
+ const geojson = {
+ type: 'FeatureCollection',
+ features,
+ };
+ return geojson;
+ }
}
diff --git a/src/js/components/util.js b/src/js/components/util.js
index 2cf1c3c..9dc180c 100644
--- a/src/js/components/util.js
+++ b/src/js/components/util.js
@@ -1,13 +1,38 @@
-export default class Util {
- static populateDates(dates) {
- const form = document.getElementById('date-selector');
- form.innerHTML = '
';
- for (let d = 0; d < dates.length; d += 1) {
- const radio = ` ${dates[d]}`;
- form.innerHTML += radio;
+// Compute the edit distance between the two given strings
+export default function getEditDistance(a, b) {
+ if (a.length === 0) return b.length;
+ if (b.length === 0) return a.length;
+
+ const matrix = [];
+
+ // increment along the first column of each row
+ let i;
+ for (i = 0; i <= b.length; i += 1) {
+ matrix[i] = [i];
+ }
+
+ // increment each column in the first row
+ let j;
+ for (j = 0; j <= a.length; j += 1) {
+ matrix[0][j] = j;
+ }
+
+ // Fill in the rest of the matrix
+ for (i = 1; i <= b.length; i += 1) {
+ for (j = 1; j <= a.length; j += 1) {
+ if (b.charAt(i - 1) === a.charAt(j - 1)) {
+ matrix[i][j] = matrix[i - 1][j - 1];
+ } else {
+ matrix[i][j] = Math.min(
+ matrix[i - 1][j - 1] + 1, // substitution
+ Math.min(
+ matrix[i][j - 1] + 1, // insertion
+ matrix[i - 1][j] + 1,
+ ),
+ ); // deletion
+ }
}
- form.innerHTML += '
';
- // TODO: handle filters
- // filters = document.getElementById('date-selector').filters;
}
+
+ return matrix[b.length][a.length];
}