diff options
Diffstat (limited to 'static')
| -rw-r--r-- | static/css/style.css | 107 | ||||
| -rw-r--r-- | static/js/map.js | 35 | ||||
| -rw-r--r-- | static/js/prediction.js | 25 | ||||
| -rw-r--r-- | static/js/search.js | 5 | ||||
| -rw-r--r-- | static/js/timeline-slider.js | 315 |
5 files changed, 475 insertions, 12 deletions
diff --git a/static/css/style.css b/static/css/style.css index 79584dd..db964d1 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -1,3 +1,110 @@ #map { height: 100%; } + +.range { + position: relative; + width: 550px; + height: 5px; +} + +.range input { + width: 100%; + position: absolute; + top: 2px; + height: 0; + -webkit-appearance: none; +} +.range input::-webkit-slider-thumb { + -webkit-appearance: none; + width: 18px; + height: 18px; + margin: -8px 0 0; + border-radius: 50%; + background: #37adbf; + cursor: pointer; + border: 0 !important; +} +.range input::-moz-range-thumb { + width: 18px; + height: 18px; + margin: -8px 0 0; + border-radius: 50%; + background: #37adbf; + cursor: pointer; + border: 0 !important; +} +.range input::-ms-thumb { + width: 18px; + height: 18px; + margin: -8px 0 0; + border-radius: 50%; + background: #37adbf; + cursor: pointer; + border: 0 !important; +} +.range input::-webkit-slider-runnable-track { + width: 100%; + height: 2px; + cursor: pointer; + background: #b2b2b2; +} +.range input::-moz-range-track { + width: 100%; + height: 2px; + cursor: pointer; + background: #b2b2b2; +} +.range input::-ms-track { + width: 100%; + height: 2px; + cursor: pointer; + background: #b2b2b2; +} +.range input:focus { + background: none; + outline: none; +} +.range input::-ms-track { + width: 100%; + cursor: pointer; + background: transparent; + border-color: transparent; + color: transparent; +} + +.range-labels { + margin: 18px -41px 0; + padding: 0; + list-style: none; +} +.range-labels li { + position: relative; + float: left; + width: 90.25px; + text-align: center; + color: #b2b2b2; + font-size: 14px; + cursor: pointer; +} +.range-labels li::before { + position: absolute; + top: -25px; + right: 0; + left: 0; + content: ""; + margin: 0 auto; + width: 9px; + height: 9px; + background: #b2b2b2; + border-radius: 50%; +} +.range-labels .active { + color: #37adbf; +} +.range-labels .selected::before { + background: #37adbf; +} +.range-labels .active.selected::before { + display: none; +} diff --git a/static/js/map.js b/static/js/map.js index a197fdb..843449b 100644 --- a/static/js/map.js +++ b/static/js/map.js @@ -10,13 +10,13 @@ function InitializeMap(city) { displayMap(map, city); } -function reRenderMap(city) { +function reRenderMap(city, style={}) { map.remove(); map = L.map('map').setView([city.latitude, city.longitude], 12); - displayMap(map, city); + displayMap(map, city, style); } -function displayMap(map, city) { +function displayMap(map, city, style = {}) { L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { accessToken: 'pk.eyJ1IjoibHVjaWZlcmNyIiwiYSI6ImNrNGx0amIzejJkaHIzZm8yODB2dGx2cXYifQ.sopB-tKzpX_qXc30bv_puQ' }).addTo(map); @@ -40,8 +40,14 @@ function displayMap(map, city) { type: 'FeatureCollection', features: lines }; - + var fg = L.featureGroup().addTo(map); var gs = L.geoJSON(geoJSON).addTo(map); + + if (style.fillColor) { + fg.clearLayers(); + gs.setStyle(style); + } + try { map.fitBounds(gs.getBounds()); } catch (e) { @@ -76,4 +82,23 @@ function getBoundaries(city) { }).catch(function () { return false; }); -}
\ No newline at end of file +} + +function getColors(temperature) { + // Generate relevant colors from temperature values for the map and return them + // Set hex values to 0.3 opacity + return temperature > 40 ? '#ff0000' : temperature > 30 ? '#ff4000' : temperature > 20 ? '#ff8000' : temperature > 10 ? '#ffbf00' : temperature > 0 ? '#ffff00' : temperature > -10 ? '#bfff00' : temperature > -20 ? '#80ff00' : temperature > -30 ? '#40ff00' : temperature > -40 ? '#00ff00' : '#00ff40'; +} + +// Function to change color of the map geoJSON based on provided temperature input +function changeColor(city, temperature) { + var color = getColors(temperature); + var style = { + fillColor: color, + fillOpacity: 0.3, + color: color, + weight: 1 + }; + reRenderMap(city, style); +} + diff --git a/static/js/prediction.js b/static/js/prediction.js index 499cba9..7a8e2bf 100644 --- a/static/js/prediction.js +++ b/static/js/prediction.js @@ -1,5 +1,5 @@ var displayDataButton = document.getElementById('displayData'); - +var temperatureData = []; var today = new Date(); var dd = today.getDate(); var mm = today.getMonth() + 1; //January is 0! @@ -38,10 +38,14 @@ displayDataButton.addEventListener('click', e => { e.preventDefault(); var cityId = document.getElementById('citySearch').getAttribute('data-city-id'); var cityName = document.getElementById('citySearch').value; - predictData(startDate, endDate, cityId, cityName); + var latitude = document.getElementById('citySearch').getAttribute('latitude'); + var longitude = document.getElementById('citySearch').getAttribute('longitude'); + predictData(startDate, endDate, cityId, cityName, latitude, longitude); }) -function predictData(startDate, endDate, cityId, cityName) { +function predictData(startDate, endDate, cityId, cityName, latitude, longitude) { + latitude ? latitude : latitude = 42.8864; + longitude ? longitude : longitude = -78.8784; var startDateValue = startDate; var endDateValue = endDate; if (startDateValue == '' || endDateValue == '') { @@ -57,13 +61,13 @@ function predictData(startDate, endDate, cityId, cityName) { end_date: endDateValue, city_id: cityId }, data => { - console.log(data); var x = data.x_data; var y = data.y_data.map(function (item) { return Math.round(item * 100) / 100; }); + temperatureData = y; var upperBound = data.upper_bound.map(function (item) { return Math.round(item * 100) / 100; @@ -79,7 +83,7 @@ function predictData(startDate, endDate, cityId, cityName) { mode: "lines", name: "Lower Bound", type: "scatter", - line: { color: "rgb(0,100,80)" } + line: { color: "#ffbf00" } }; var trace2 = { @@ -89,7 +93,7 @@ function predictData(startDate, endDate, cityId, cityName) { fillcolor: "rgba(231,107,243,0.0)", mode: "lines", fill: "tozerox", - line: { color: "rgb(31, 119, 180)" }, + line: { color: "#ff8000" }, name: "Prediction" }; @@ -100,7 +104,7 @@ function predictData(startDate, endDate, cityId, cityName) { mode: "lines", name: "Upper Bound", type: "scatter", - line: { color: "rgb(0,176,246)" } + line: { color: "#ff4000" } }; var data = [trace1, trace2, trace3]; @@ -120,6 +124,13 @@ function predictData(startDate, endDate, cityId, cityName) { document.getElementById("processingButton").classList.add('hidden'); displayDataButton.classList.remove('hidden'); + + document.getElementById('rangeContainer').classList.remove('hidden'); + changeColor({ + city: cityName, + latitude: latitude, + longitude: longitude + }, temperatureData[0]) }); } }
\ No newline at end of file diff --git a/static/js/search.js b/static/js/search.js index c96e003..28dbd15 100644 --- a/static/js/search.js +++ b/static/js/search.js @@ -28,6 +28,8 @@ citySearch.addEventListener('keyup', (e) => { citySearch.removeAttribute('data-city-id'); citySearch.value = e.target.innerText; citySearch.setAttribute('data-city-id', e.target.getAttribute('cityId')); + citySearch.setAttribute('data-latitude', e.target.getAttribute('latitude')); + citySearch.setAttribute('data-longitude', e.target.getAttribute('longitude')); resultContainer.classList.add('hidden'); resultContainer.innerHTML = ""; city = { @@ -36,6 +38,9 @@ citySearch.addEventListener('keyup', (e) => { longitude: e.target.getAttribute('longitude') }; reRenderMap(city); + document.getElementById('rangeContainer').classList.add('hidden'); + $rangeInput = $('.range input'); + $rangeInput.val(1).trigger('input'); }); document.getElementById('results').appendChild(listElement); } diff --git a/static/js/timeline-slider.js b/static/js/timeline-slider.js new file mode 100644 index 0000000..eb1f710 --- /dev/null +++ b/static/js/timeline-slider.js @@ -0,0 +1,315 @@ +// TODO: parameterize timeline colors, overall length, and length between points (css styles) +L.Control.TimeLineSlider = L.Control.extend({ + + options: { + position: 'bottomright', + timelineItems: ["Today", "Tomorrow", "The Next Day"], + + changeMap: function({label, value, map}) { + console.log("You are not using the value or label from the timeline to change the map."); + }, + extraChangeMapParams: {}, + initializeChange: true, + + thumbHeight: "4.5px", + labelWidth: "80px", + betweenLabelAndRangeSpace: "20px", + + labelFontSize: "14px", + activeColor: "#37adbf", + inactiveColor: "#8e8e8e", + + backgroundOpacity: 0.75, + backgroundColor: "#ffffff", + + topBgPadding: "10px", + bottomBgPadding: "0px", + rightBgPadding: "30px", + leftBgPadding: "30px", + + }, + + initialize: function (options) { + if (typeof options.changeMap != "function") { + options.changeMap = function ({label, value, map}) { + console.log("You are not using the value or label from the timeline to change the map."); + }; + } + + if (parseFloat(options.thumbHeight) <= 2) { + console.log("The nodes on the timeline will not appear properly if its radius is less than 2px.") + } + + L.setOptions(this, options); + }, + onAdd: function(map) { + this.map = map; + this.sheet = document.createElement('style'); + document.body.appendChild(this.sheet); + + this.container = L.DomUtil.create('div', 'control_container'); + + /* Prevent click events propagation to map */ + L.DomEvent.disableClickPropagation(this.container); + + /* Prevent right click event propagation to map */ + L.DomEvent.on(this.container, 'control_container', function (ev) + { + L.DomEvent.stopPropagation(ev); + }); + + /* Prevent scroll events propagation to map when cursor on the div */ + L.DomEvent.disableScrollPropagation(this.container); + + /* Create html elements for input and labels */ + this.slider = L.DomUtil.create('div', 'range', this.container); + this.slider.innerHTML = `<input id="rangeinputslide" type="range" min="1" max="${this.options.timelineItems.length}" steps="1" value="1"></input>` + + this.rangeLabels = L.DomUtil.create('ul', 'range-labels', this.container); + this.rangeLabels.innerHTML = this.options.timelineItems.map((item) => { return "<li>" + item + "</li>" }).join(''); + + this.rangeInput = L.DomUtil.get(this.slider).children[0]; + this.rangeLabelArray = Array.from(this.rangeLabels.getElementsByTagName('li')); + this.sliderLength = this.rangeLabelArray.length; + + this.thumbSize = parseFloat(this.options.thumbHeight) * 2; + // double the thumb size when its active + this.activeThumbSize = this.thumbSize * 2; + + // make the width of the range div holding the input the number of intervals * the label width and add the thumb size on either end of the range + this.rangeWidthCSS = parseFloat(this.options.labelWidth) * (this.options.timelineItems.length-1) + (this.thumbSize*2); + + // move labels over to the left so they line up; move half the width of the label and adjust for thumb radius + this.rlLabelMargin = parseFloat(this.options.labelWidth)/2 - (parseFloat(this.options.thumbHeight)/2); + + // 2.5 because that is half the height of the range input + this.topLabelMargin = parseFloat(this.options.betweenLabelAndRangeSpace) - parseFloat(this.options.thumbHeight) - 2.5; + + this.backgroundRGBA = this.hexToRGBA(this.options.backgroundColor, this.options.backgroundOpacity); + this.coverBackgroundRGBA = this.hexToRGBA(this.options.backgroundColor, 0); + + that = this; + + this.sheet.textContent = this.setupStartStyles(); + + /* When input gets changed change styles on slider and trigger user's changeMap function */ + L.DomEvent.on(this.rangeInput, "input", function() { + + curValue = this.value; + + that.sheet.textContent += that.getTrackStyle(this, that.sliderLength); + var curLabel = that.rangeLabelArray[curValue-1].innerHTML; + + // Change map according to either current label or value chosen + mapParams = {value: curValue, label: curLabel, map: map} + allChangeMapParameters = {...mapParams, ...that.options.extraChangeMapParams}; + that.options.changeMap(allChangeMapParameters); + }); + + // Add click event to each label so it triggers input change for corresponding value + for (li of this.rangeLabelArray) { + L.DomEvent.on(li, "click", function (e) { + var targetli = e.target; + var index = that.rangeLabelArray.indexOf(targetli); + that.rangeInput.value = index + 1; + + var inputEvent = new Event('input'); + that.rangeInput.dispatchEvent(inputEvent); + + }); + }; + + // Initialize input change at start + if (this.options.initializeChange) { + var inputEvent = new Event('input'); + this.rangeInput.dispatchEvent(inputEvent); + } + + return this.container; + + }, + + onRemove: function() { + // remove control html element + L.DomUtil.remove(this.container); + }, + + hexToRGBA: function(hex, opacity){ + // from https://stackoverflow.com/questions/21646738/convert-hex-to-rgba + var c; + if(/^#([A-Fa-f0-9]{3}){1,2}$/.test(hex)){ + c= hex.substring(1).split(''); + if(c.length== 3){ + c= [c[0], c[0], c[1], c[1], c[2], c[2]]; + } + c= '0x'+c.join(''); + return 'rgba('+[(c>>16)&255, (c>>8)&255, c&255].join(',')+','+opacity+')'; + } + throw new Error('Bad Hex'); + }, + + setupStartStyles: function() { + style = ` + .control_container { + background-color: ${that.backgroundRGBA}; + padding: ${that.options.topBgPadding} ${that.options.rightBgPadding} ${that.options.bottomBgPadding} ${that.options.leftBgPadding}; + } + + .range { + position: relative; + left: -${that.thumbSize}px; + height: 5px; + width: ${that.rangeWidthCSS}px; + } + + .range input { + width: 100%; + position: absolute; + height: 0; + -webkit-appearance: none; + } + + /* -1 because the height is 2 (half the height) */ + .range input::-webkit-slider-thumb { + background: ${that.options.activeColor}; + margin: -${that.thumbSize - 1}px 0 0; + width: ${that.activeThumbSize}px; + height: ${that.activeThumbSize}px; + -webkit-appearance: none; + border-radius: 50%; + cursor: pointer; + border: 0 !important; + } + .range input::-moz-range-thumb { + background: ${that.options.activeColor}; + margin: -${that.thumbSize - 1}px 0 0; + width: ${that.activeThumbSize}px; + height: ${that.activeThumbSize}px; + border-radius: 50%; + cursor: pointer; + border: 0 !important; + } + .range input::-ms-thumb { + background: ${that.options.activeColor}; + margin: -${that.thumbSize - 1}px 0 0; + width: ${that.activeThumbSize}px; + height: ${that.activeThumbSize}px; + border-radius: 50%; + cursor: pointer; + border: 0 !important; + } + + + .range input::-webkit-slider-runnable-track { + background: ${that.options.backgroundColor}; + width: 100%; + height: 2px; + cursor: pointer; + } + .range input::-moz-range-track { + background: ${that.options.backgroundColor}; + width: 100%; + height: 2px; + cursor: pointer; + } + .range input::-ms-track { + background: ${that.options.backgroundColor}; + width: 100%; + height: 2px; + cursor: pointer; + background: transparent; + border-color: transparent; + color: transparent; + } + + .range input:focus { + background: none; + outline: none; + } + + . range input[type=range]::-moz-focus-outer { + border: 0; + } + + .range-labels { + margin: ${that.topLabelMargin}px -${that.rlLabelMargin}px 0; + padding: 0; + list-style: none; + } + + .range-labels li { + color: ${that.options.inactiveColor}; + width: ${that.options.labelWidth}; + font-size: ${that.options.labelFontSize}; + position: relative; + float: left; + text-align: center; + cursor: pointer; + } + .range-labels li::before { + background: ${that.options.inactiveColor}; + width: ${that.thumbSize}px; + height: ${that.thumbSize}px; + position: absolute; + top: -${that.options.betweenLabelAndRangeSpace}; + right: 0; + left: 0; + content: ""; + margin: 0 auto; + border-radius: 50%; + } + .range-labels .active { + color: ${that.options.activeColor}; + } + .range-labels .selected::before { + background: ${that.options.activeColor}; + } + .range-labels .active.selected::before { + display: none; + } + `; + + + return style; + + }, + + getTrackStyle: function (el, sliderLength) { + prefs = ['webkit-slider-runnable-track', 'moz-range-track', 'ms-track']; + + var curVal = el.value, + labelIndex = curVal - 1, + val = (labelIndex) * (100/(sliderLength-1)), + coverVal = (parseFloat(that.thumbSize)/that.rangeWidthCSS) * 100; + style = ''; + + // Remove active and selected classes from all labels + for (li of that.rangeLabelArray) { + L.DomUtil.removeClass(li, 'active'); + L.DomUtil.removeClass(li, 'selected'); + } + + // Find label that should be active and give it appropriate classes + var curLabel = that.rangeLabelArray[labelIndex]; + L.DomUtil.addClass(curLabel, 'active'); + L.DomUtil.addClass(curLabel, 'selected'); + + // For labels before active label, add selected class + for (i = 0; i < curVal; i++) { + L.DomUtil.addClass(that.rangeLabelArray[i], 'selected'); + } + + // Change background gradient + for (var i = 0; i < prefs.length; i++) { + style += `.range {background: linear-gradient(to right, ${that.coverBackgroundRGBA} 0%, ${that.coverBackgroundRGBA} ${coverVal}%, ${that.options.activeColor} ${coverVal}%, ${that.options.activeColor} ${val}%, ${that.coverBackgroundRGBA} 0%, ${that.coverBackgroundRGBA} 100%)}`; + style += '.range input::-' + prefs[i] + `{background: linear-gradient(to right, ${that.coverBackgroundRGBA} 0%, ${that.coverBackgroundRGBA} ${coverVal}%, ${that.options.activeColor} 0%, ${that.options.activeColor} ${val}%, ${that.options.inactiveColor} ${val}%, ${that.options.inactiveColor} ${100-coverVal}%, ${that.coverBackgroundRGBA} ${100-coverVal}%, ${that.coverBackgroundRGBA} 100%)}`; + } + + return style; + } + +}) + +L.control.timelineSlider = function(options) { + return new L.Control.TimeLineSlider(options); +}
\ No newline at end of file |
