Python/Flask Data Visualization & Interactive Maps – Hacker Noon

File Setup — — — — — — — — — — — — — — — — — -

The file structure that we’ll eventually have will probably look something like this:

To start off, we can create most of this using the command line. Some of the files, like the density.txt and densitydata.json will be created in the first portion of the project.

on the command line, do the following:

mkdir 
cd

Now, we’ll create all the initial files on the command line:

touch app.py .gitignore README.md requirements.txt runtime.txt
mkdir templates
mkdir static
cd templates
touch base.html
cd ..
cd static
touch main.css

Next, if we want to create a git repository and deploy on heroku, this is a good place to do that.

git init

Then, if we set up a heroku account, we can create a new heroku project by running:

heroku create 

Before we start installing dependencies and libraries, we definitely want to set up a virtual environment in Python. To do this, we can run the following command:

pyvenv-3.6 env (substitute your python version here)

next, we’ll activate that environment as follows:

source env/bin/activate

In the command line you’ll now see the (env) before your project folder to let you know your running a virtual environment. If you ever get an error from here on out that python can’t find a module you’ve installed, check to see if you are still using your virtual environment. If not, then run the last command again.

Next, since flask is the main dependency we’ll need, we can install that.

pip install flask

If we decide to run this on heroku, we’ll need to tell it which version of python we’re running and we can add that to our runtime.txt file:

echo "python-3.6.4" >> runtime.tx (or whatever your version is)

We’ll also want to install gunicorn if we’re using heroku:

pip install gunicorn

And then add the following text to our Procfile:

echo "web: gunicorn app:app" >> Procfile

Finally, we’ll run pip freeze in order to update our requirements.txt file:

pip freeze > requirements.txt

Cool, now that that’s all out of the way, most of the files in our above file tree should be there, and we’re ready to rock and roll.

Going back to our flowchart from earlier, here’s the area we’ll be working on first:

CSS — — — — — — — — — — — — — — — — — -

Based on the workflow we talked about earlier, the first thing we’ll need is an image of a map that we can overlay the grid of buttons on top of. To do that, we’ll just save a map that suits us, and use it as the background image for div or element. We’ll also set it to the appropriate dimensions. This will go in the main.css file we created earlier:

#body {
background-image: url('/static/states.png');
background-size: contain;
background-repeat: no-repeat;
background-size: 1150px 768px;
margin: 0px;
padding: 0px;
display: flex;
flex-flow: row wrap;
justify-content: flex-start;
align-items: flex-start;
width:1150px;
height:768px;
border:black solid 2px;
}

We’re also going to create two button classes here. When a user clicks, or an active zombie block infects another block, we’ll change the block from one css class to another. Here are the classes I created. ‘.btn’ class is the default button class, and ‘.active’ is the activated class:

.btn {
width: 4.5px;
height:4.8px;
background-color: transparent;
margin: 0px;
padding: 0px;
font-size: 1px;
}
.active {
width: 4.5px;
height:4.8px;
/* Permalink - use to edit and share this gradient: http://colorzilla.com/gradient-editor/#136325+0,305937+100&1+15,0+88 */
background: -moz-radial-gradient(center, ellipse cover, rgba(19,99,37,1) 0%, rgba(23,97,40,1) 15%, rgba(44,90,53,0) 88%, rgba(48,89,55,0) 100%); /* FF3.6-15 */
background: -webkit-radial-gradient(center, ellipse cover, rgba(19,99,37,1) 0%,rgba(23,97,40,1) 15%,rgba(44,90,53,0) 88%,rgba(48,89,55,0) 100%); /* Chrome10-25,Safari5.1-6 */
background: radial-gradient(ellipse at center, rgba(19,99,37,1) 0%,rgba(23,97,40,1) 15%,rgba(44,90,53,0) 88%,rgba(48,89,55,0) 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#136325', endColorstr='#00305937',GradientType=1 ); /* IE6-9 fallback on horizontal gradient */
margin: 0px;
padding: 0px;
font-size: 1px;
border-radius: 25%;

}
.btn:hover {
cursor: pointer;
}

HTML — — — — — — — — — — — — — — — — — -

Then, we can go ahead and create the actual HTML that is indicated in our CSS. This will go in the base.html file we created earlier:













JavaScript — — — — — — — — — — — — — — — — — -

Next, we’ll add some JavaScript. We’ll use a loop to create buttons for the length we need. If you used the same dimensions I did, then 40,000 buttons seems about right. Normally, you might have a separate JavaScript file. While that is probably best practice, I find that if I’m writing less than 300 lines of code, then including the JavaScript in a script tag is usually easier. If it looks like we’re going to have more than that, we can always change it later.


Next, I’ll create the mouseover event for all of the tags we just created:

var color1 = '#fff600';
var color2 = '#ffc32b';
var color3 = '#e8a600';
var color4 = '#e88300';
var color5 = '#ce7502';
var color6 = '#ce3b01';
var color7 = '#ad3201';
var color8 = '#7a2402';
var color9 = '#561a02';
var color10 = '#1e0800';

$('body').on('mouseover', '.btn', function () {
var currentVal = $(this).html();
console.log(currentVal);
var newVal = parseInt(currentVal) + 1;
if (newVal > 10){
newVal = 10 }

          $(this).html(newVal);
if ($(this).html() == 1){
$(this).css('background-color', color1);
}
else if ($(this).html() == 2){
$(this).css('background-color', color2);
}
else if ($(this).html() == 3){
$(this).css('background-color', color3);
}
else if ($(this).html() == 4){
$(this).css('background-color', color4);
}
else if ($(this).html() == 5){
$(this).css('background-color', color5);
}
else if ($(this).html() == 6){
$(this).css('background-color', color6);
}
else if ($(this).html() == 7){
$(this).css('background-color', color7);
}
else if ($(this).html() == 8){
$(this).css('background-color', color8);
}
else if ($(this).html() == 9){
$(this).css('background-color', color9);
}
else if ($(this).html() == 10){
$(this).css('background-color', color10);
}
});

In this previous example, each block or array element will have a value between 0 and 10. If we wanted to go back and edit this form and data, so that we could store data about the population and elevation on each block, we may want to increment the value differently, or store a true/false value instead. We can always change the input here, but have it write to a new file on the back end, depending on what data we want to store. We’ll talk more about this idea later, but just something to consider.

And lastly, I’ll create a form, which allows me to send this data from my flask front end to python on the back end. I can then manipulate the data and create the hash.

var inputArr = ''
$('#submitform').click(function(){
$(".btn").each(function(){
var input = $(this).html();
inputArr += ("," + input);
});
var form2 = `



`;
console.log(inputArr);
$('#formGoesHere').append(form2);
$('#myForm').submit();
});
});

Even though we’ve created the form and everything on the front end, it’s not actually connected to anything yet. In order to connect this data to python, we’ll need to modify our app.py file, and use Flask requests to receive the data from our base.html file. At this point, we’re now here in our flowchart:

Python — — — — — — — — — — — — — — — — — -

In app.py, let’s go ahead and import the things we’ll need to make this work:

from flask import Flask, request, render_template

next, we’ll define the app:

app = Flask(__name__)

Finally, we’ll define the route which will be associated with our base.html file, which will just be the main route, (“/”):

@app.route('/', methods=['GET', 'POST'])
def searchTopic():
return render_template('base.html')

This route will render the page, but we’ll also need to make a route for the form. If you go back to the html we wrote, the action for that form was “form1”, and that will be the route we’ll use for our form data.

@app.route('/form1', methods=['GET', 'POST'])
def getFormData():
data = request.form['t1']
f = open('file.txt', 'w')
f.write(data)
f.close()
return render_template('base.html')

Here, our route in app.py, “/form1” is associated with the action of the form in our html. And the input name in that form, “t1” is associated with the request.form[‘t1’] in app.py.

Last, we’ll add the following line to the bottom of our app.py file:

if __name__ == '__main__':
app.run()

Now, if we submit the form, that data will be saved as the variable ‘data’. Then, we’ll open a file called file.txt, and write our ‘data’ variable to that file. Now, we can iterate over the data in the file, and convert it to JSON, along with the other data we may collect later.

Back to HTML/JavaScript — — — — — — — — — — — — — — — — — -

If you set yours up like this, you can see that on the mousever, we’re changing the value of the html in each div, as well as the color. The color makes it easy to see what has been changed and what hasn’t. When I submit the form, I’m able to send the value of the divs as an array to python. The input process might look like this:

The console log there, is logging the value of each div, and you can see the area map as we create it. Once we have all the map data that we want, we can comment that code out of our JavaScript file, since we won’t need it to actually run the simulation. But it is handy to have if we want to make a new map of any new kind of data. In my case, I submitted one map which contained only the population data…another map which contained the elevation data, and so on…and then combined each list into a hash in python.

The data we’ve input via the front end, is being interpreted as an array, or comma separated values. The map data would basically be an array of 40000 elements ranging from 0 to 10, like so:

[0,0,0,0,0,0,1,1,2,3,4,0,0,0,0,0,0,0,5,6,6,7,7,7,4,1,0……..]

4 — Assembling the raw data into JSON — — — — — — — — — — — — — — — — — -

In python, each list will have the same number of indices, meaning, in list 1, the array at index 552 will correspond to the 552nd div in our grid. So I’ll loop through one list, but check the other list at the same time, without having to loop through both lists. In the example below, I have one list which contains the population data, and a second list which contains data about whether the current block is water or not.

import json
with open('population.txt') as f:
file1 = [line.rstrip() for line in f]
with open('file.txt') as f:
file2 = [line.rstrip() for line in f]
array = []
for idx, i in enumerate(file1):
arr = i.split(",")
waterArr = file2[idx].split(",")
x = 0
for index, a in enumerate(arr):
myObj = {
"population": 0,
"water": 0,
}
if int(a) > 9:
myObj['population'] = 8
elif int(a) is 9:
myObj['population'] = 7
elif int(a) is 8:
myObj['population'] = 6
elif int(a) is 7:
myObj['population'] = 5
elif int(a) is 6:
myObj['population'] = 4
elif int(a) is 5:
myObj['population'] = 3
elif int(a) is 4:
myObj['population'] = 2
elif int(a) is 3:
myObj['population'] = 1
elif int(a) < 3:
myObj['population'] = 0
if waterArr[index] == '1':
myObj['water'] = 1
array.append(myObj)
with open('density2.json', 'w') as outfile:
json.dump(array, outfile)

Then I convert the data into json, and save it as a json file. The revised list of json data looks like this:

...{"water": 1, "population": 0}, {"water": 0, "population": 0}, {"water": 1, "population": 0}, {"water": 0, "population": 0}, {"water": 0, "population": 0}, {"water": 0, "population": 0}, {"water": 0, "population": 0}, {"water": 0, "population": 0}, {"water": 0, "population": 1}, {"water": 0, "population": 1}, {"water": 0, "population": 0}, {"water": 0, "population": 0}, {"water": 0, "population": 1}, {"water": 0, "population": 1}, {"water": 0, "population": 5}, {"water": 0, "population": 0}, {"water": 0, "population": 0}, {"water": 0, "population": 0},...

Each element in the array contains data about both the population of that block, and whether or not the block is water (because we don’t want the zombies to wander into the ocean do we?)

END OF PORTION 1 — — — — — — — — — — — — — — — — — -

At this point, we’ve at least collected some data. We can always go back, using the file we’ve created, and enter new data about elevation, or population age, or whatever we think my be relevant. We’ll just need to give the new file a new name, so that we don’t overwrite a previous file. Then we can add that to our loop in python, and add the additional data to the JSON data.

One thing to consider before moving on, is that, since this is a fairly small project, I’m not going to make additional files for different functions. I’m simply going to comment out the sections I don’t need. Since we’re moving from the first portion to the second portion of this project, we can comment out all of the code that relates to gathering, organizing or collecting the data. This includes the previous lines where we turn the text files into a hash, as well as the JavaScript where we create and submit the form data. However, if it’s easier for you to keep track of to have separate files, then you can do that. Just keep in mind that the file structure you have will look different than mine.

read original article here