Time to shave away stuff we don't need. Open up
melissa_is_a_babe/templates/index.html
(which is a
genshi
template) and scoop out its guts so it looks like:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:py="http://genshi.edgewall.org/"
xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include href="master.html" />
<meta content="text/html; charset=UTF-8" http-equiv="content-type" py:replace="''"/>
<title>Welcome to TurboGears 2.0, standing on the
shoulders of giants, since 2007</title>
</head>
</body>
</html>
Since we told paster
to launch our app with the
--reload
option, it should automatically notice we changed
something and be ready for us. Return to your browser, reload the page and
behold:
Not very exciting now, is it?
Our first tw2.protovis chart
Open up melissa_is_a_babe/controllers/root.py
. There you'll
find a class called RootController with a number of methods. When you visit a
particular location, say, http://localhost:8080/foobar
, your TG2.1
BaseController will redirect the flow of control to your RootController's
foobar
method (if it has one).
We don't need any of the methods other than index
so you can go
ahead and remove them.
Inside the index
method, we're going to configure an instance of
tw2.protovis.custom.BubbleChart
and return it to the WSGI context.
At the top root.py
add the appropriate import statements to bring
in the random
module and the BubbleChart
class from
the tw2.protovis.custom
module. As for the index
method, we'll need to do three things: 1) initialize some bogus data to
visualize, 2) fill in the parameters for BubbleChart
, and 3) return
our configured widget from the controller method.
As for our bogus data, the p_data
parameter requires a list of
python dictionaries (we'll create 40 of them), each with four keys: a
'name'
that provides the alt-text for the bubbles, a
'value'
that provides the size of each, a 'text'
that
provides the label, and a 'group'
which is the basis on which to
colorize them.
Mangle melissa_is_a_babe/controllers/root.py
to look like
this:
# -*- coding: utf-8 -*-
"""Main Controller"""
from tg import expose, flash, require, url, request, redirect
from pylons.i18n import ugettext as _, lazy_ugettext as l_
from melissa_is_a_babe.lib.base import BaseController
from melissa_is_a_babe.model import DBSession, metadata
from melissa_is_a_babe.controllers.error import ErrorController
__all__ = ['RootController']
from tw2.protovis.custom import BubbleChart
import random
class RootController(BaseController):
The root controller for the barcamp.roc.fall.2010 application.
All the other controllers and WSGI applications should be mounted on this
controller. For example::
panel = ControlPanelController()
another_app = AnotherWSGIApplication()
Keep in mind that WSGI applications shouldn't be mounted directly: They
must be wrapped around with :class:`tg.controllers.WSGIAppController`.
error = ErrorController()
@expose('melissa_is_a_babe.templates.index')
def index(self):
"""Handle the front-page."""
data = [
'name' : random.random(),
'value' : random.random(),
'text' : random.random(),
'group' : random.random(),
} for i in range(40) ]
chart = BubbleChart(
id='a-chart-for-my-friends',
p_width=750,
p_height=750,
p_data=data
return dict(page='index', widget=chart)
Now that our bubble chart of random data is available to our templates, we'll
need to make mention of it in
melissa_is_a_babe/templates/index.html
. Just add the following
single line inside the body
tag.
${widget.display()}
</body>
Again, paster
should have noticed we changed something and
restarted itself. Revisit http://localhost:8080
and you should see
something like:
Querying Google's Ajax Search API
Cool? Cool. Well, sort of. Random numbers are unfortunately not as
interesting as we might like them to be so let's do something a little less
entropic. Google has a sweet
ajax search API we can use. Add the following to the top of
melissa_is_a_babe/controllers/root.py
import itertools
import urllib
import simplejson
import itertools
import math
base_url = "http://ajax.googleapis.com/ajax/services/search/web?v=1.0"
def make_entry(combo):
phrase = '"%s"' % " ".join(combo)
print "Querying for %s." % phrase
query = urllib.urlencode({ 'q': phrase })
url = "%s&%s" % (base_url, query)
results = urllib.urlopen(url)
json = simplejson.loads(results.read())
if 'estimatedResultCount' in json['responseData']['cursor']:
count = int(json['responseData']['cursor']['estimatedResultCount'])
else:
count = len(json['responseData']['results'])
value = math.log(count+1.000000001)
return {
'name' : "%s : %i " % (phrase, count),
'value' : value,
'text' : phrase[:10],
'group' : len(combo),
The above function will, given a list of words (combo), perform a google
search on that exact phrase and return a python dictionary ready for BubbleChart
the value of which is the estimatedResultCount of the search term. We use the
urllib module to prepare and make our query and the simplejson module to convert
the json (javascript object notation) that google hands us into a python
dictionary.
We'll need to make use of the make_entry
function in our
controller.
Keyword arguments passed to our app via the URL's query string arrive as
keyword arguments to controller methods. Let's add one to the
index
method called sentence
.
Furthermore, we'll take that sentence, break it up into its constituent words
and, making use of the itertools module, prepare a list of every unique
combination of words in that sentence.
With our make_entry
function and such a list of word
combinations, its trivial to map our list to a map of BubbleChart-ready
dict
objects.
Modify melissa_is_a_babe/controllers/root.py
as follows:
@expose('melissa_is_a_babe.templates.index')
def index(self, sentence="word to your moms"):
"""Handle the front-page."""
words = str(sentence).split()
combos = []
for i in range(len(words)):
combos += list(itertools.combinations(words, i+1))
data = map(make_entry, combos)
chart = BubbleChart(
id='a-chart-for-my-friends',
p_width=750,
p_height=750,
p_data=data
return dict(page='index', widget=chart)
Since we made sentence
a keyword argument to our
index
method, we should also be able to visit
http://localhost:8080/index?sentence=the+quick+brown+fox+jumps+over+the+lazy+dog
and see a pretty dope chart.
Speeding things up with multiprocessing
Wow.. that was cool but it took forever to make all those queries.
What exactly is it that's taking so much time? It seems to be all the
handshaking and waiting for a response before making the next request. We can
likely speed this up greatly by making use of python's
multiprocessing
module.Just below your definition of the make_entity
function, add the
following lines:
return {
'name' : "%s : %i " % (phrase, count),
'value' : value,
'text' : phrase[:10],
'group' : len(combo),
from multiprocessing import Pool
pool = Pool(processes=150)
class RootController(BaseController):
The Pool class has a map method that performs the same operation as the
builtin map, but distributes the workload among all available processes. Inside
the index
method, make use of the thread pool when mapping
make_entity
onto the list of word combinations. Change the
highlighted line as follows:
for i in range(len(words)):
combos += list(itertools.combinations(words, i+1))
data = pool.map(make_entry, combos)
chart = BubbleChart(
id='a-chart-for-my-friends',
Repoint your browser at
http://localhost:8080/index?sentence=the+quick+brown+fox+jumps+over+the+lazy+dog
and it should fly!
Sorting our data gives different results
One last thing: BubbleChart places the bubbles in the order they are given
and tries to pack each subsequent entry as tightly as it can. We can achieve
differently flavored charts by reordering our list of dicts. In
melissa_is_a_babe/controllers/root.py
try adding the following
line:
for i in range(len(words)):
combos += list(itertools.combinations(words, i+1))
data = pool.map(make_entry, combos)
data.sort(lambda x, y: cmp(y['value'], x['value']))
chart = BubbleChart(
id='a-chart-for-my-friends',
account
Live demonstrations of all my widgets are available at http://tw2-demos.threebean.org/
Cheers!