[web2py] rpc from jquery

37 views
Skip to first unread message

MikeEllis

unread,
May 14, 2010, 4:32:30 PM5/14/10
to web2py-users
Hi,
I'm trying to create an application that displays live sparklines
(small inline graphs). A typical page might have a dozen or so of
these updating every few seconds, so it seems like either jsonrpc or
xlmrpc is the right thing to use.

As a test, I've currently got sparklines updating on the client side
using the jquery.sparkline and jquery.timers plugins. The code to
make it work is pretty straightforward. After including the
aforementioned plugins at the top of web2py_ajax.html, I can create a
sparkline that updates once a second with code at the bottom of
web2py_ajax.html like the following:

<script type="text/javascript">
/* <![CDATA[ */
$(function() {

$('.dynamicsparkline').everyTime(1000,function(i) {
var a = [10,9,8,7,6,5,4];
var b = [0,0,0,0,0,0,0]
var j = 2+i%5;
for(var k=0; k<b.length; k++) { b[k] = a[k]
%j;}
$(this).sparkline(b);
});
});
/* ]]> */
</script>

and in my view the following code:

<p>
Sparkline with dynamic data: <span class="dynamicsparkline">Loading..</
span>
</p>

displays a nice little wiggly graph that changes once a second. Just
the effect I'm looking for.

So my question is this: What's the most straightforward way to
rewrite this trivial little demo so the data calculation takes place
server side? Clearly the work would be done by a function that looks
something like the following:

@service.jsonrpc ## or is xmlrpc easier?
def datapoints(j):
return [n%j for n in [10,9,8,7,6,5,4]]

but what plugins, initializations and function calls are needed
client-side to replace the lines in my javascript example that fill
the 'b' array with the values to be plotted?


Thanks,
Mike


mdipierro

unread,
May 14, 2010, 7:53:49 PM5/14/10
to web2py-users
Try this: replace

$(function() {
$('.dynamicsparkline').everyTime(1000,function(i) {
var a = [10,9,8,7,6,5,4];
var b = [0,0,0,0,0,0,0]
var j = 2+i%5;
for(var k=0; k<b.length; k++) { b[k] = a[k]
%j;}
$(this).sparkline(b);
});
});

with

$(function() {
$('.dynamicsparkline').everyTime(1000,function(i) {
$.getJSON('{{=URL(r=request,f='call/json/
datapoints')}}/'+i, function(data) {
var b = [0,0,0,0,0,0,0]
var j = 2+i%5;
for(var k=0; k<b.length; k++) { b[k] = data.a[k]
%j;}
$(this).sparkline(b);
});
});
});

AND

@service.jsonrpc ## or is xmlrpc easier?
def datapoints(j):
return [n%j for n in [10,9,8,7,6,5,4]]

WITH

@service.json
def datapoints(j):
return dict(a=[10,9,8,7,6,5,4])

MikeEllis

unread,
May 14, 2010, 9:29:02 PM5/14/10
to web2py-users
Thanks, Massimo! I've said it before, but it's certainly worth
repeating. You are running the most responsive and helpful group on
the web.

So I've almost got it working with your suggestion, but I'm running
into what seems to be an issue with javascript's scoping rules.

My service function is definitely being called once per second, but
nothing gets drawn unless I put both the 'b' array and the call to $
(this).sparkline(b) outside the getJSON() call, but then 'b' is not
changed outside the getJSON() call. OTOH if I put the sparkline()
call inside the function(data) block, then it looks like $(this) is no
longer referring to the DIV element with class 'dynamicsparkline'.

I'm sure there's a way to sort it out. I think it's just that js is so
fugly compared to python that I have a hard time forcing myself to
really learn it.

Cheers,
Mike

mdipierro

unread,
May 14, 2010, 9:51:24 PM5/14/10
to web2py-users
aha try replace

$(function() {
$('.dynamicsparkline').everyTime(1000,function(i) {
$.getJSON('{{=URL(r=request,f='call/json/
datapoints')}}/'+i, function(data) {
var b = [0,0,0,0,0,0,0]
var j = 2+i%5;
for(var k=0; k<b.length; k++) { b[k] = data.a[k]
%j;}
$(this).sparkline(b);
});
});
});

with

$(function() {
$('.dynamicsparkline').each(function(index){
var obj=$(this);
obj.everyTime(1000,function(i) {
$.getJSON('{{=URL(r=request,f='call/json/
datapoints')}}/'+i, function(data) {
var b = [0,0,0,0,0,0,0]
var j = 2+i%5;
for(var k=0; k<b.length; k++) { b[k] = data.a[k] %j;}
obj.sparkline(b);
});
});
});
});

MikeEllis

unread,
May 15, 2010, 2:34:22 PM5/15/10
to web2py-users

After spending some time with Firebug to find and fix a couple of
typos, I've got it working now. There seems to be no way around
explicitly referring to the SPAN element (see below), but I can live
with that.

Now I need to tackle the next level, which is to expand the server-
side JSON function to return data for multiple sparklines on a page
and arrange to call sparkline() with the right data for each of the
corresponding elements.

Thanks again for the help!

Here's what's working correctly:

In the view ...

<script type="text/javascript">
/* <![CDATA[ */
$(function() {
$('.dynamicsparkline').everyTime(1000,function(i) {
var j = 2+i%5;
$.getJSON('{{=URL(r=request,f='call/json/
sparkdata')}}/'+j, function(data) {
var sparkdata = [10,9,8,7,6,5,4];
for(var k=0; k<sparkdata.length; k++) { sparkdata[k] =
data.a[k];}
console.log("sparkdata = " + sparkdata);
$('.dynamicsparkline').sparkline(sparkdata); // WORKS
// $(this).sparkline(sparkdata) // DOES NOT WORK!
});
});
});
/* ]]> */
</script>

<h1>This is the sparkline.html template</h1>
<p>
Sparkline with dynamic data: <span class="dynamicsparkline">Loading..</
span>
</
p>

and in the controller ...

@service.json
def sparkdata(j):
sys.stderr.write("\nsparkdata() called with j=%d\n"%int(j))
j = int(j)
return dict(a=[n%j for n in [10,9,8,7,6,5,4]])

Cheers,
Mike
Message has been deleted

Thadeus Burgess

unread,
May 15, 2010, 3:27:07 PM5/15/10
to web...@googlegroups.com
For multiple elements you might want to try and put it into a jQuery
plugin, allowing you to re-use the code for each sparkline.

$(".spark").makeSparkline({url:/path/to/call/sparkdata, args:etc});

--
Thadeus





On Sat, May 15, 2010 at 1:34 PM, MikeEllis <michael...@gmail.com> wrote:
>
> After spending some time with Firebug to find and fix a couple of
> typos, I've got it working now. There seems to be no way around
> explicitly referring to the SPAN element (see below),  but I can live
> with that.
>
> Now I need to tackle the next level, which is to expand the server-
> side JSON function to return data for multiple sparklines on a page
> and arrange to call sparkline() with the right data for each of the
> corresponding elements.
>
> Thanks again for the help!
>
> Here's what's working correctly:
>
> In the view ...
>
> <script type="text/javascript">
>    /* <![CDATA[ */
>    $(function() {
>        $('.dynamicsparkline').everyTime(1000,function(i) {
>            var j = 2+i%5;
>            $.getJSON('{{=URL(r=request,f='call/json/
> sparkdata')}}/'+j, function(data) {
>                var sparkdata =  [10,9,8,7,6,5,4];
>                for(var k=0; k<sparkdata.length; k++) { sparkdata[k] =
> data.a[k];}
>                console.log("sparkdata = " + sparkdata);
>                $('.dynamicsparkline').sparkline(sparkdata);  // WORKS
>                // $(this).sparkline(sparkdata)  // DOES NOT WORK!
>            });
>        });
>    });
>    /* ]]> */
> </script>
>
> <h1>This is the sparkline.html template</h1>
> <p>
> Sparkline with dynamic data: <span class="dynamicsparkline">Loading..</
> span>
> </
> p>
>
> and in the controller ...
>
> @service.json
> def sparkdata(j):
>    sys.stderr.write("\nsparkdata() called with j=%d\n"%int(j))
>    j = int(j)
>    return dict(a=[n%j for n in [10,9,8,7,6,5,4]])
>
> Cheers,
> Mike
>
>
> On May 14, 9:51 pm, mdipierro <mdipie...@cs.depaul.edu> wrote:

MikeEllis

unread,
May 15, 2010, 6:09:31 PM5/15/10
to web2py-users
Thanks, Thaddeus. That's a good suggestion. It turns out my case can
be simplified considerably by using explicit id's for the sparklines,
e.g.

$(function() {
$(this).everyTime(1000,function(i) {
$.getJSON('/peertool/sparkline/call/json/sparkdata/
13/0/20', function(data) {
$("#dynbar0").sparkline(data.dynbar0, {type: 'bar',
barColor: 'green'});
$("#dynbar1").sparkline(data.dynbar1, {type: 'bar',
barColor: 'green'});
// etc ...
});
});
});

This works because I know how many plots will be needed when the page
is rendered by the view, so it's fairly trivial to generate the
necessary id's server side. If I were doing something that required
consulting a different server to get the data for each plot, then I'd
certainly look seriously at writing a plugin.

In case it's useful to anyone else, here's a complete working example
that's kind of fun to see in action. When you browse to the index
page, it puts up between 5 and 25 bar graphs with random values
reverse sorted to emulate Pareto charts. The charts update once per
second with new data from the server.

(WARNING - a few minutes of this drives my MacBook's cpu usage up
because I don't have indexing turned off for wherever the json data is
going.)



1. web2py_ajax.html - add the sparkline and timer plugins

response.files.insert(4,URL(r=request,c='static',f='jquery.sparkline.js'))
response.files.insert(5,URL(r=request,c='static',f='jquery.timers-1.2.js'))

2. The controller

# coding: utf8
import sys
import random
def index():
ngraphs = random.choice(range(5,25))
return dict(message="hello from sparkline.py", ngraphs=ngraphs,
chartmin=0, chartmax=20)

def call():
session.forget()
return service()

@service.json
def sparkdata(ngraphs,chartmin,chartmax):
ngraphs = int(ngraphs)
chartmin = int(chartmin)
chartmax = int(chartmax)
sys.stderr.write("\nsparkdata() called with ngraphs=%d\n"%ngraphs)
d = dict()
for n in xrange(ngraphs):
id = "dynbar" + str(n)
## data for bar graph. 9 random ints between chartmax and
chartmin
data = [random.choice(range(chartmin,chartmax)) for i in
xrange(9)]
## simulate a Pareto plot
data.sort()
data.reverse()
d[id] = data
sys.stderr.write("\n%s : %s"%(id, str(d[id])))
return d

3. The view (index.html)

{{extend 'layout.html'}}
{{
chartoptions = "{type: 'bar', barColor: 'green', 'chartRangeMin':
'%d', 'chartRangeMax': '%d'}"%(chartmin,chartmax)
jsonurl = URL(r=request,f='call/json/sparkdata/%(ngraphs)d/%
(chartmin)d/%(chartmax)d'%locals())
}}
<script type="text/javascript">
/* <![CDATA[ */
$(function() {
$(this).everyTime(1000,function(i) {
$.getJSON('{{=jsonurl}}', function(data) {
{{for n in xrange(ngraphs):}}
$("#dynbar{{=n}}").sparkline(data.dynbar{{=n}},
{{=chartoptions}});
{{pass}}
});
});
});
/* ]]> */
</script>
<h1>This is the sparkline.html template</h1>
{{for n in xrange(ngraphs):}}
<p>
Bar chart with dynamic data: <span id="dynbar{{=n}}"
class="dynamicbar">Loading..</span>
</p>
{{pass}}
{{=BEAUTIFY(response._vars)}}


That's it. BTW, sparkline charts are really useful in applications
where you need to visually compare lots of similar data series. Here's
a link to a chapter in Edward Tufte's "Beautiful Evidence" with more
info

http://www.edwardtufte.com/bboard/q-and-a-fetch-msg?msg_id=0001OR

The JQuery Sparklines plug-in page is also useful.

http://omnipotent.net/jquery.sparkline/

Cheers,
Mike

On May 15, 3:27 pm, Thadeus Burgess <thade...@thadeusb.com> wrote:
> For multiple elements you might want to try and put it into a jQuery
> plugin, allowing you to re-use the code for each sparkline.
>
> $(".spark").makeSparkline({url:/path/to/call/sparkdata, args:etc});
>
> --
> Thadeus
>

Thadeus Burgess

unread,
May 15, 2010, 8:44:21 PM5/15/10
to web...@googlegroups.com
Would you make a web2pyslice of this with some screenshots? Looks like
great work.

MikeEllis

unread,
May 15, 2010, 11:50:32 PM5/15/10
to web2py-users
Done. See http://www.web2pyslices.com/main/slices/take_slice/79

On May 15, 8:44 pm, Thadeus Burgess <thade...@thadeusb.com> wrote:
> Would you make a web2pyslice of this with some screenshots? Looks like
> great work.
>
> --
> Thadeus
>

Thadeus Burgess

unread,
May 15, 2010, 11:53:55 PM5/15/10
to web...@googlegroups.com
Nice :)
Reply all
Reply to author
Forward
0 new messages