-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.py
285 lines (215 loc) · 10.2 KB
/
index.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
from bokeh.io import show, output_notebook, output_file
from bokeh.models import (
GeoJSONDataSource,
HoverTool,
LinearColorMapper
)
from bokeh.plotting import figure
from bokeh.palettes import Viridis6
from bokeh.tile_providers import CARTODBPOSITRON
from bokeh.models.widgets import CheckboxGroup, Slider, RangeSlider, Tabs
import json
import collections
import bokeh
from bokeh.io import show, output_notebook, push_notebook
from bokeh.plotting import figure
from bokeh.models import CategoricalColorMapper, HoverTool, ColumnDataSource, Panel
from bokeh.models.widgets import CheckboxGroup, Slider, RangeSlider, Tabs
from bokeh.layouts import column, row, WidgetBox
from bokeh.palettes import Category20_16
from bokeh.application.handlers import FunctionHandler
from bokeh.application import Application
import pandas as pd
import numpy as np
from numpy.random import random
from bokeh.core.properties import Instance, Dict, JSON, Any
from bokeh import events
from bokeh.models import (Select, Column, Row, ColumnDataSource, HoverTool,
Range1d, LinearAxis, GeoJSONDataSource, ColumnarDataSource)
from bokeh.plotting import figure
from bokeh.io import curdoc
# import os
# import datetime
from collections import OrderedDict
from bokeh.models.widgets import Select
from numpy.random import random, normal, lognormal
from bokeh import events
from bokeh.models import (Select, Column, Row, ColumnDataSource, HoverTool,
Range1d, LinearAxis, GeoJSONDataSource, ColumnarDataSource)
from bokeh.models import CheckboxGroup, RadioGroup, Toggle
from bokeh.palettes import Blues9, OrRd9
from bokeh.models import (
BasicTicker, ColumnDataSource, ColorBar,
ColorMapper, CustomJS, Div,
HBar, HoverTool, LinearColorMapper,
LogColorMapper, MultiSelect, PrintfTickFormatter,
Select
)
from bokeh.themes import Theme
import yaml
from bokeh.tile_providers import get_provider, Vendors
from bokeh.io import curdoc
#
# initialize the default view of hood2vec.
#
with open('chi-zip-code-tabulation-areas-2012.geojson','rt') as json_file: # load geojson files as the background
chi_json = json.load(json_file)
zipcodes = collections.defaultdict()
# record zipcodes in geojson into zipcodes list
for zip_idx in range(len(chi_json['features'])):
zipcodes[chi_json['features'][zip_idx]['properties']['ZIP']] = zip_idx
chi_zipcode_to_dist = pd.read_pickle('chi_zipcode_to_dist_'+ 'midday') # load parameters for default view
# chi_zipcode_to_dist stores the content of hood2vec statistical file. It is a dictionary, where keys are zip codes and values are closest zip codes with corresponding distances to key.
# pick up zip codes existing in both geojson file and hood2vec statistical file
shared_zips = collections.defaultdict(int)
for zipcode in chi_zipcode_to_dist:
shared_zips[zipcode] +=1
for zipcode in zipcodes:
shared_zips[zipcode] +=1
zipcodes_sorted = []
for zipcode in shared_zips:
if shared_zips[zipcode] == 2:
zipcodes_sorted.append(zipcode)
# sort the shared zip codes for users to select easily (left part of visualization)
zipcodes_sorted.sort()
initial_zipcodes = zipcodes_sorted[0]
initial_num = 3
initial_period = 'midday'
zipcodes_to_plot = initial_zipcodes
num_neighbor = initial_num
period_str = initial_period
with open('chi-zip-code-tabulation-areas-2012.geojson','rt') as json_file: # load geojson files as the background
chi_json = json.load(json_file)
chi_json['features'][zipcodes[zipcodes_to_plot]]['properties']['color_type'] = 2 # mark the target area a dark color "2"
chi_zipcode_to_dist = pd.read_pickle('chi_zipcode_to_dist_'+ period_str)
# if a zip code exists in both geojson file and hood2vec statistical file, then "YES", we can visualize it. Otherwise, "NO"
for zipcode in zipcodes:
if shared_zips[zipcode] == 2:
chi_json['features'][zipcodes[zipcode]]['properties']['has_data'] = 'YES'
else:
chi_json['features'][zipcodes[zipcode]]['properties']['has_data'] = 'NO'
# if closest zip codes are also in geojson file, we can visualize it. We set them to a lighter color "1"
distances = chi_zipcode_to_dist[zipcodes_to_plot]
num_neighbor_count = 0
for neighbor_idx in range(len(distances)):
if shared_zips[distances[neighbor_idx][0]] == 2:
num_neighbor_count +=1
chi_json['features'][zipcodes[distances[neighbor_idx][0]]]['properties']['color_type'] = 1
if num_neighbor_count == num_neighbor:
break
# convert json to string
geojson = json.dumps(chi_json)
geo_source = GeoJSONDataSource(geojson = geojson)
#
# interactive view of hood2vec.
#
def make_map(zipcodes_to_plot = '60608', num_neighbor = 3, period_str = 'midday', cate= []):
"""
input: updated parameters selected by users
output: updated visualization of nearby neighbors
"""
with open('chi-zip-code-tabulation-areas-2012.geojson','rt') as json_file:
chi_json = json.load(json_file)
chi_json['features'][zipcodes[zipcodes_to_plot]]['properties']['color_type'] = 2
chi_zipcode_to_dist = pd.read_pickle('chi_zipcode_to_dist_'+ period_str)
# for an updated period, check the shared zip codes with geojson
shared_zips = collections.defaultdict(int)
for zipcode in chi_zipcode_to_dist:
shared_zips[zipcode] +=1
for zipcode in zipcodes:
shared_zips[zipcode] +=1
# if a zip code exists in both geojson file and hood2vec statistical file, then "YES", we can visualize it. Otherwise, "NO"
for zipcode in zipcodes:
if shared_zips[zipcode] == 2:
chi_json['features'][zipcodes[zipcode]]['properties']['has_data'] = 'YES'
else:
chi_json['features'][zipcodes[zipcode]]['properties']['has_data'] = 'NO'
# if user selects to visualize neighbors in venue type representation
if cate == [0]:
period_str = 'category'
chi_zipcode_to_dist = pd.read_pickle('chi_zipcode_to_dist_'+ period_str)
# if closest zip codes are also in geojson file, we can visualize it. We set them to a lighter color "1"
distances = chi_zipcode_to_dist[zipcodes_to_plot]
num_neighbor_count = 0
for neighbor_idx in range(len(distances)):
if shared_zips[distances[neighbor_idx][0]] == 2:
num_neighbor_count +=1
chi_json['features'][zipcodes[distances[neighbor_idx][0]]]['properties']['color_type'] = 1
if num_neighbor_count == num_neighbor:
break
geojson = json.dumps(chi_json)
geo_source = GeoJSONDataSource(geojson = geojson)
color_mapper = LinearColorMapper(palette=Viridis6)
p = figure(title="Chicago",
# tools=TOOLS,\
# x_axis_location=None, y_axis_location=None,\
# x_range=(-9780000, -9745000), y_range=(5130000, 5160000),
x_axis_type="mercator", y_axis_type="mercator",\
)
p.axis.visible = False
p.grid.grid_line_color = None
# https://bokeh.pydata.org/en/latest/docs/reference/tile_providers.html
# p.add_tile(get_provider(Vendors.STAMEN_TERRAIN))
# p.add_tile(CARTODBPOSITRON)
p.grid.grid_line_color = None
p.patches('xs', 'ys', fill_alpha=0.7, fill_color={'field': 'color_type', 'transform': color_mapper},
source=geo_source,
)
# information to hover
hover = HoverTool(tooltips=[('Zip Code', '@ZIP'),
('Has Data', '@has_data')
])
p.add_tools(hover)
# return the handler p to pass updated values to visualization
return p
zipcode_selection = Select(options=zipcodes_sorted, value = initial_zipcodes)
num_selection = Select(options=['3','4','5'], value = str(initial_num))
period_selection = Select(options=['overnight',\
'morning',\
'midday',\
'afternoon',\
'night'], value = initial_period)
category_selection = CheckboxGroup(labels=[' category'], active = [])
# if active = [0], it means there are a total of 1 option, and it is checked;
# if active = [0,1], it means there are a total of 2 options, and they are both checked;
# webpage modules
div_title = Div(text=\
"""<h1><tt>hood2vec</tt> app</h1>"""+\
"You can access <a href=\"https://github.com/xinliupitt/hood2vec\">source code</a> "+\
"and <a href=\"https://arxiv.org/abs/1907.11951\">backend data generation</a> of this app. "+\
"The whole project won Foursquare Data Challenge, 2019."
)
div_zipcode = Div(text=\
"<br>Move cursor on the map for the data availability of a zip code. "+\
"""For a zip code with "Has Data: Yes", you can select that <b>zip code</b> from this menu: """
)
div_num = Div(text=\
"<br>For the selected zip code, you can select the <b>number</b> of nearest neighbors "+\
"to be visualized under <tt>hood2vec</tt> metric:"
)
div_period = Div(text=\
"<br>You can select a <b>period</b> "+\
"to visualize period-dependent nearest neighbors of selected zip code:"
)
div_cate = Div(text=\
"<br>You can <b>tick</b> to visualize nearest neighbors by "+\
"venue categories; "+\
"You can <b>untick</b> to visualize nearest neighbors by "+\
"<tt>hood2vec</tt> metric: "
)
# pass user's selected parameters to backend
def update_plot(attr, old, new):
layout.children[1] = make_map(zipcodes_to_plot = zipcode_selection.value, \
num_neighbor = int(num_selection.value), \
period_str = period_selection.value,\
cate = category_selection.active)
# user's actions to update parameters
zipcode_selection.on_change('value', update_plot)
num_selection.on_change('value', update_plot)
period_selection.on_change('value', update_plot)
category_selection.on_change('active', update_plot)
# webpage layout
layout = row(Column(div_title,div_zipcode,zipcode_selection,div_num,num_selection,\
div_period,period_selection, div_cate, category_selection), make_map())
# doc.add_root(layout)
curdoc().add_root(layout)