<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/rss2full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:creativeCommons="http://backend.userland.com/creativeCommonsRssModule" xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" version="2.0" xml:base="http://eagereyes.org">
<channel>
 <title>eagereyes</title>
 <link>http://eagereyes.org</link>
 <description />
 <language>en</language>
<atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" type="application/rss+xml" href="http://feeds.feedburner.com/EagerEyes" /><feedburner:info uri="eagereyes" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com/" /><creativeCommons:license>http://creativecommons.org/licenses/by-nc-sa/2.0/</creativeCommons:license><feedburner:emailServiceId>EagerEyes</feedburner:emailServiceId><feedburner:feedburnerHostname>http://feedburner.google.com</feedburner:feedburnerHostname><item>
 <title>Various VisWeek Workshops</title>
 <link>http://feedproxy.google.com/~r/EagerEyes/~3/DqeA4HWfu8A/various-visweek-workshops</link>
 <description>&lt;p&gt;I want to call your attention to three interesting workshops that will be held at &lt;a href="http://vis.computer.org/VisWeek2010/" target="_blank"&gt;VisWeek 2010&lt;/a&gt; in October in Salt Lake City. One is on storytelling in visualization, another on visual analytics in healthcare, and a third one on theory in infovis. The deadlines for two of them are coming up soon.
&lt;!--break--&gt;&lt;/p&gt;

&lt;h2&gt;Telling Stories With Data&lt;/h2&gt;

&lt;p&gt;If you haven't watched the &lt;a href="http://www.ted.com/index.php/talks/hans_rosling_shows_the_best_stats_you_ve_ever_seen.html"&gt;Hans Rosling video&lt;/a&gt; yet, you probably haven't realized that visualization isn't just there for data analysis, it's also a great tool for telling stories. Also, you really need to get out from under that rock.&lt;/p&gt;

&lt;p&gt;Anyway, the workshop &lt;em&gt;&lt;a href="http://thevcl.com/storytelling/" target="_blank"&gt;Telling Stories with Data&lt;/a&gt;&lt;/em&gt; aims to bring together people who are interested in this topic, and want to discuss ideas, approaches, etc. They also have a &lt;a href="http://www.facebook.com/pages/Telling-Stories-with-Data-A-VisWeek-2010-workshop/137941022892330" target="_blank"&gt;facebook page&lt;/a&gt; that you can subscribe to to get updates, even if you're only planning on attending without presenting.&lt;/p&gt;

&lt;p&gt;The deadline for this workshop is &lt;strong&gt;August 4&lt;/strong&gt;, they want a &lt;strong&gt;one-page abstract&lt;/strong&gt; with bio, and you'll get &lt;strong&gt;20 minutes&lt;/strong&gt; to present at the workshop.&lt;/p&gt;

&lt;h2&gt;Visual Analytics in Health Care&lt;/h2&gt;

&lt;p&gt;While medical applications are huge in scientific visualization, they haven't really made their way into infovis and visual analytics. This workshop on &lt;em&gt;&lt;a href="http://research.ihost.com/vahc2010/" target="_blank"&gt;Visual Analytics in Health Care&lt;/a&gt;&lt;/em&gt; is targeted at researchers working with medical records, on drug discovery, pandemic forecasting, etc.&lt;/p&gt;

&lt;p&gt;The deadline is &lt;strong&gt;August 27&lt;/strong&gt;, you can submit a paper with up to &lt;strong&gt;eight pages&lt;/strong&gt;. Accepted submissions will be presented as &lt;strong&gt;posters or in oral presentations&lt;/strong&gt;. Authors of selected papers will also be invited to submit to a special issue of the &lt;em&gt;Information Visualization&lt;/em&gt; journal.&lt;/p&gt;

&lt;h2&gt;The Role of Theory in Information Visualization&lt;/h2&gt;

&lt;p&gt;Last, but certainly not least, is the important topic of theory in information visualization. The workshop I am organizing together with T.J. Jankun-Kelly and Chris Weaver is titled &lt;a href="http://eagereyes.org/infovis-theory-workshop"&gt;&lt;em&gt;The Role of Theory in Information Visualization&lt;/em&gt;&lt;/a&gt;. We want to hear your thoughts on what constitutes theory in infovis, what questions we need to address, and how to do that.&lt;/p&gt;

&lt;p&gt;We also stole the idea of setting up &lt;a href="http://www.facebook.com/pages/VisWeek-2010-Workshop-The-Role-of-Theory-in-Information-Visualization/120203618025408" target="_blank"&gt;a facebook page&lt;/a&gt; from the storytelling workshop. Thanks for the idea, guys!&lt;/p&gt;

&lt;p&gt;The deadline for submissions for this workshop is &lt;strong&gt;August 2&lt;/strong&gt;, with a limit of &lt;strong&gt;two pages&lt;/strong&gt; for your abstract. We will select a number of submissions for &lt;strong&gt;three minute provocations&lt;/strong&gt;, not full presentations. Selected authors will also be invited to submit to a special issue of a journal later.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;These are just the three workshops I know of. Feel free to post pointers to others below.&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/EagerEyes/~4/DqeA4HWfu8A" height="1" width="1"/&gt;</description>
 <comments>http://eagereyes.org/blog/2010/various-visweek-workshops#comments</comments>
 <category domain="http://eagereyes.org/blog">blog</category>
 <pubDate>Tue, 27 Jul 2010 16:00:10 +0000</pubDate>
 <dc:creator>Robert Kosara</dc:creator>
 <guid isPermaLink="false">784 at http://eagereyes.org</guid>
<feedburner:origLink>http://eagereyes.org/blog/2010/various-visweek-workshops</feedburner:origLink></item>
<item>
 <title>A Protovis Primer, Part 3</title>
 <link>http://feedproxy.google.com/~r/EagerEyes/~3/GWG4KIv42xY/protovis-primer-part-3</link>
 <description>&lt;script src="http://eagereyes.org/media/protovis-r3.2.js" type="text/javascript"&gt;
&lt;/script&gt;

&lt;script src="http://eagereyes.org/media/2010/protovis-primer/presidents.js" type="text/javascript"&gt;
&lt;/script&gt;

&lt;p&gt;&lt;a href="http://eagereyes.org/tutorials/protovis-primer-part-3"&gt;&lt;img src="http://eagereyes.org/media/2010/protovis-primer/presidents-teaser.png" width="560" height="200"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After covering some &lt;a href="http://protovis.org/" target="_blank"&gt;Protovis&lt;/a&gt; &lt;a href="http://eagereyes.org/tutorials/protovis-primer-part-1"&gt;basics in part 1&lt;/a&gt;, and some &lt;a href="http://eagereyes.org/tutorials/protovis-primer-part-2"&gt;more advanced techniques plus data loading in part 2&lt;/a&gt;, this part is devoted mostly to using what we've already discussed in a more complex example. We'll also look at some basic interaction.
&lt;!--break--&gt;
While Protovis requires more work to set up charts, it pays off when you want to do something beyond the typical chart types, or when you want to add interaction. You are not limited to any fixed set of charts, but can create any combination you want, or even something entirely new.&lt;/p&gt;

&lt;p&gt;In this part of the tutorial, we will create &lt;a href="http://eagereyes.org/applications/PresidentialDemographicsII.html"&gt;a chart showing the lives and office terms of all U.S. presidents&lt;/a&gt;. The chart is a basic life lines "waterfall" chart, with a few small twists. A basic version of the chart can be created in your favorite spreadsheet, but making it look a particular way or being able to interact is a different matter.&lt;/p&gt;

&lt;p&gt;Here is a small part of it, showing only the last few presidents. Again, if you are reading this in Internet Explorer or a news reader, you will only see static images and not be able to interact. Go to &lt;a href="http://eagereyes.org/tutorials/protovis-primer-part-3"&gt;the tutorial website&lt;/a&gt; to get a better idea how it works.&lt;/p&gt;

&lt;!--[if !IE]&gt;&lt;!--&gt;

&lt;script type="text/javascript+protovis"&gt;&lt;!--

        // convertes a JavaScript Date object into a number representing the Julian Date
        // ignores time, time zones, and all those irrelevant details
        // http://en.wikipedia.org/wiki/Julian_day
        var toJulian = function(d) {
            var a = Math.floor((14 - d.getMonth()) / 12);
            var y = d.getFullYear() + 4800 - a;
            var m = d.getMonth() + 1 + 12 * a - 3;
            return d.getDay() + Math.floor((153 * m + 2) / 5) + 365 * y + Math.floor(y / 4) - Math.floor(y / 100) + Math.floor(y / 400) - 32045;
        }

        // converts Julian Date back to JS Date
        // this works for values before the epoch, too!
        var toJSDate = function(julianDate) {
            return new Date((julianDate - 2440587) * 24 * 3600 * 1000); // 2440587 is January 1st, 1970.
        }

        var today = toJulian(new Date());

        // chop off the lives and office terms of presidents who are still around (or in office)
        // at today's date
        presidents = presidents.map(function(d) {
            if (d.died &lt; 0)
                d.died = today;
            if (d.term_ended &lt; 0)
                d.term_ended = today;
            return d;
        });

        var h = 3;
        var pad = 12;

        presidents = presidents.slice(32);
        var width = 560;
        
        var height = (h + pad) * presidents.length;

        var dates = pv.Scale.linear(presidents[0].born, today).range(0, width - 10);
        
        var activePresident = 0;

        var dateFormat = pv.Format.date("%b %e, %Y");

        var makePresidentsPanel = function(interaction) {

            var vis = new pv.Panel()
                .width(width).height(height);
                
            if (interaction)
                vis.event("mousemove", pv.Behavior.point(Infinity).collapse("x"));

            // // tick marks at the bottom every 50 years from 1750 to 2000
            // vis.add(pv.Rule)
            //  .data([[2360235, "1750"], [2378497, "1800"], [2396759, "1850"], [2415021, "1900"], [2433283, "1950"], [2451545, "2000"]])
            //  .left(function(d) dates(d[0]))
            //  .bottom(10)
            //  .height(5)
            //  .strokeStyle("#ccc")
            // .anchor("bottom").add(pv.Label)
            //  .text(function(d) d[1]);

            if (interaction) {

                // the vertical rule marking the current president's birth
                vis.add(pv.Rule)
                    .left(function() dates(presidents[activePresident].born))
                    .bottom(0)
                    .strokeStyle("#aaa");

                // the vertical rule marking the current president's ascension
                vis.add(pv.Rule)
                    .left(function() dates(presidents[activePresident].term_began))
                    .bottom(0)
                    .strokeStyle("#aaa");

                // the vertical rule marking the end of the current president's office term
                vis.add(pv.Rule)
                    .left(function() dates(presidents[activePresident].term_ended))
                    .bottom(0)
                    .strokeStyle("#aaa")
                    .visible(function() presidents[activePresident].term_ended &lt; today);

                // the vertical rule marking the current president's death
                vis.add(pv.Rule)
                    .left(function() dates(presidents[activePresident].died))
                    .bottom(0)
                    .strokeStyle("#aaa")
                    .visible(function() presidents[activePresident].died &lt; today);
            }
            
            // the light gray bar showing each president's life, with name label
            vis.add(pv.Bar)
                .data(presidents)
                .left(function(d) dates(d.born))
                .width(function(d) dates(d.died) - dates(d.born))
                .height(h)
                .top(function() ((h + pad) * this.index) + pad)
                .fillStyle("#ddd")
                .event("point", function() {activePresident = this.index; return vis;})
            .anchor("left").add(pv.Label)
                .textAlign("left")
                .textBaseline("bottom")
                .text(function(d) d.name)
                .textStyle(function() (activePresident == this.index &amp;&amp; interaction) ? "#333" : "#ddd");

            // the little extension indicating a president who's still alive
            vis.add(pv.Bar)
                .data(presidents)
                .visible(function(d) d.died == today)
                .left(function(d) dates(d.died))
                .top(function() ((h + pad) * this.index) + pad + 1)
                .height(1)
                .width(10)
                .fillStyle(function() (this.index == presidents.length-1) ? "#333": "#ddd");

            // the dark gray bar showing each president's office term
            vis.add(pv.Bar)
                .data(presidents)
                .left(function(d) dates(d.term_began))
                .width(function(d) dates(d.term_ended) - dates(d.term_began))
                .height(3)
                .top(function() ((h + pad) * this.index) + pad)
                .fillStyle("#333");

            return vis;
        }

        var vis1 = makePresidentsPanel(true);

        vis1.render();
// --&gt;
&lt;/script&gt;

&lt;noscript&gt;
&lt;!--&lt;![endif]--&gt;
&lt;img src="http://eagereyes.org/media/2010/protovis-primer/presidents-interaction.png" width="560" height="180" alt="presidents chart" /&gt;
&lt;!--[if !IE]&gt;&lt;!--&gt;
&lt;/noscript&gt;

&lt;!--&lt;![endif]--&gt;

&lt;h2&gt;Data Preparation&lt;/h2&gt;

&lt;p&gt;The data used for this example requires some introduction and a bit of preparation. This is what it looks like:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;var presidents = 
[
    {
        "name": "George Washington",
        "born": 2353713,
        "died": 2378480,
        "term_began": 2374600,
        "term_ended": 2377465
    },
    ...
]
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The dates are encoded in single numbers, using the &lt;a href="http://en.wikipedia.org/wiki/Julian_day" target="_blank"&gt;Julian Date&lt;/a&gt;. This date measures the number of days between January 1st, 4713 BC and the given day. That might seem odd, but it's useful in many applications where a simple way of calculating the number of days between two dates is needed (e.g., in Astronomy). For our purposes, it is useful because it translates the rather unwieldy dates into a simple, linear numerical scale.&lt;/p&gt;

&lt;p&gt;A slight complication comes from the presidents who are still around. Their dates of death (and end of office term, for Barack Obama) are set to -1. A function at the top of the program inserts today's date for these dates. You will see below why that makes our lives a lot easier going forward (we won't have to handle special cases).&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;presidents = presidents.map(function(d) {
    if (d.died &amp;lt; 0)
        d.died = today;

    if (d.term_ended &amp;lt; 0)
        d.term_ended = today;

    return d;
});
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The &lt;code&gt;map&lt;/code&gt; function is one of those functional programming constructs that JavaScript, being a functional language, has. It applies the function given to it to all elements in the array it is called from, and returns a new array containing the results of calling the function. The resulting array has the same number of elements as the one the function was called with. Here, we're replacing the &lt;code&gt;presidents&lt;/code&gt; array with a new one which is tailored to the day when the function was called.&lt;/p&gt;

&lt;p&gt;There are two functions in the code that convert from and to the Julian date used. I won't list them here, because they're rather technical, but you can &lt;a href="http://github.com/eagereyes/Protovis-Primer" target="_blank"&gt;find them in the code&lt;/a&gt;. The value for the &lt;code&gt;today&lt;/code&gt; variable is generated using one of these functions.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;var today = toJulian(new Date());
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Setup&lt;/h2&gt;

&lt;p&gt;For the horizontal lines, we'll have to define a scale so the dates can be mapped to screen locations. We're defining a width of the overall chart, and then use a scale (see &lt;a href="http://eagereyes.org/tutorials/protovis-primer-part-2"&gt;part 2&lt;/a&gt;) to scale the dates from the first date we might encounter (the birth date of the first president) to the last one (today).&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;var width = 560;

var dates = pv.Scale.linear(presidents[0].born, today)
    .range(0, width - 10);
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;As you can see here, I took no chances and actually used the &lt;code&gt;.born&lt;/code&gt; field of the first element in the array. That makes it easier to subset the array, as I've done for the images in this article. No matter what part I choose, I don't have to update the scale manually (unless I start cutting at the end, so the end of the chart is not today).&lt;/p&gt;

&lt;p&gt;There's also a bit of setup for the vertical part of the chart, but that one's simpler. I'm specifying a variable &lt;code&gt;h&lt;/code&gt; for the height (thickness) of the bars, and &lt;code&gt;pad&lt;/code&gt; for the padding/spacing in between. The total height of each element is &lt;code&gt;h+pad&lt;/code&gt;, and when we multiply that by the number of objects in our array, we get the total height we need for the panel (plus a bit of space for a scale at the bottom).&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;var h = 3;
var pad = 12;

var height = (h + pad) * presidents.length + 16;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And as before, we'll create a panel to contain the visualization, using these variables to size it according to the content.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;var vis = new pv.Panel()
    .width(width).height(height);
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Life Lines&lt;/h2&gt;

&lt;p&gt;The lines representing both the lives and the office terms are fairly simple. I'm using the Bar mark here, but you could also use Rules if you wanted. If you followed the previous two installments, this should be fairly self-explanatory:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;vis.add(pv.Bar)
    .data(presidents)
    .left(function(d) dates(d.born))
    .width(function(d) dates(d.died) - dates(d.born))
    .height(h)
    .top(function() ((h + pad) * this.index) + pad)
    .fillStyle("#ddd")
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;We're telling the bars where the data comes from, and how to map it to its attributes. The &lt;code&gt;.left&lt;/code&gt; and &lt;code&gt;.width&lt;/code&gt; attributes use the dates scale we defined above to map the Julian dates stored in the data to screen locations. The y axis is handled differently, with an explicit calculation. We're using the &lt;code&gt;this.index&lt;/code&gt; variable again, like in the bar charts last time, to create a sequence of objects.&lt;/p&gt;

&lt;p&gt;The bars are ordered from the top down, so I'm using the &lt;code&gt;.top&lt;/code&gt; attribute instead of &lt;code&gt;.bottom&lt;/code&gt;. This not only means that I'm specifying a different point, it also changes the coordinate system: 0 is now at the top, not at the bottom (which it would be if I used &lt;code&gt;.bottom&lt;/code&gt;). This makes the definition a bit more elegant, but I could do the same with &lt;code&gt;.bottom&lt;/code&gt; by subtracting from &lt;code&gt;height&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You may have noticed that the semicolon at the end is missing. This is because we want to attach a label so we know who's who:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;.anchor("left").add(pv.Label)
    .textAlign("left")
    .textBaseline("bottom")
    .text(function(d) d.name)
    .textStyle("#333");
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Since the anchored label is passed the same data item the original object uses, we have to extract the name attribute. Everything else is just for proper alignment and styling.&lt;/p&gt;

&lt;p&gt;In addition to the lives, we care about the presidents' office terms. This works essentially the same way, it just uses a darker shade of gray and doesn't involve a label.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;vis.add(pv.Bar)
    .data(presidents)
    .left(function(d) dates(d.term_began))
    .width(function(d) dates(d.term_ended)
                        - dates(d.term_began))
    .height(3)
    .top(function() ((h + pad) * this.index) + pad)
    .fillStyle("#ddd");
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;So far, we've handled them all the same way, no matter if they are still alive or not. That works because we earlier set the values so that none of them was past &lt;code&gt;today&lt;/code&gt;, so none of the bars can go off the right edge. Now, let's add a little indicator to show which ones are still around. This is a thin line that we're adding to the right end of the life line. In the original version, I had a little triangle there that turned the bars into arrows, but that doesn't work very well with these thinner bars.&lt;/p&gt;

&lt;p&gt;The line is another bar, this one only a single pixel high. Other than that, the mapping is fairly straight-forward. The only thing to note is that we need a &lt;code&gt;.visible&lt;/code&gt; attribute to only show it for those few people who are still around. Also, the &lt;code&gt;.fillStyle&lt;/code&gt; has to be different for the president who is still in office. Since the office bar is darker, it only makes sense to also make the extension darker in this case. I will explain the &lt;code&gt;?&lt;/code&gt; operator a little further down, because we'll also use it for the interaction, but basically it's a conditional expression: if the first part is true, it returns the part right after the &lt;code&gt;?&lt;/code&gt;, otherwise it returns the part after the &lt;code&gt;:&lt;/code&gt;.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;vis.add(pv.Bar)
    .data(presidents)
    .visible(function(d) d.died == today)
    .left(function(d) dates(d.died))
    .top(function() ((h + pad) * this.index) + pad + 1)
    .height(1)
    .width(10)
    .fillStyle(function() (this.index == presidents.length-1)
                                    ? "#333": "#ddd");
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;So let's look at what we have so far:&lt;/p&gt;

&lt;!--[if !IE]&gt;&lt;!--&gt;

&lt;script type="text/javascript+protovis"&gt;&lt;!--

    var vis2 = makePresidentsPanel(false);
    vis2.render();
// --&gt;
&lt;/script&gt;

&lt;noscript&gt;
&lt;!--&lt;![endif]--&gt;
&lt;img src="http://eagereyes.org/media/2010/protovis-primer/presidents-basic.png" width="560" height="180" alt="presidents chart" /&gt;
&lt;!--[if !IE]&gt;&lt;!--&gt;
&lt;/noscript&gt;

&lt;!--&lt;![endif]--&gt;

&lt;h2&gt;Interaction Setup&lt;/h2&gt;

&lt;p&gt;There are several ways to create interactivity in Protovis. The general way so far has been to put a transparent object over the entire panel (or at least the parts that you want to interact with), catch mouse events, and figure out what to do with them. Protovis 3.2 introduces behaviors, which are much more elegant and specific, but also have their own pitfalls.&lt;/p&gt;

&lt;p&gt;The interaction we're going to build is a simple mouse-over that will show some lines to better compare different life lines, and a label showing actual dates. To do this, we'll need to capture mouse events when the mouse pointer is over an object. This is done by specifying a behavior for the entire panel:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;var vis = new pv.Panel()
    .width(width).height(height)
    .event("mousemove",
        pv.Behavior.point(Infinity).collapse("x"));
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Notice the second line that binds a &lt;code&gt;pv.Behavior&lt;/code&gt; to the &lt;code&gt;mousemove&lt;/code&gt; event. The definition of the behavior says that we are interested in pointing, and that we do not care if the closest object is too far away (i.e., we're specifying a circle with radius infinity to capture objects). Also, we do not care about the x position, only y, so we'll tell Protovis to ignore that part when looking for the closest match.&lt;/p&gt;

&lt;p&gt;This definition passes the events on to individual objects in the panel, which now have to handle them. In this case, because we are specifying a &lt;code&gt;point&lt;/code&gt; behavior, the events that our objects receive are called &lt;code&gt;point&lt;/code&gt; and &lt;code&gt;unpoint&lt;/code&gt;. We'll add a little function to our basic life lines bars that looks like this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;var activePresident = 0;

vis.add(pv.Bar)
    [definition like first one above]
    .event("point", function() {activePresident = this.index;
                                return vis;})
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The &lt;code&gt;activePresiedent&lt;/code&gt; variable contains the number of the president the user is pointing at. When the object receives a &lt;code&gt;point&lt;/code&gt; event, it calls the function we specify, which updates that variable. Whatever that function returns is then repainted. Since we don't want to worry about precise repainting here, we'll just return the entire panel to be updated. With more complex visualizations, it can make sense to only update parts that have changed.&lt;/p&gt;

&lt;p&gt;To keep this simple, we do not handle the &lt;code&gt;unpoint&lt;/code&gt; event. That means that there is always one active president, making things easier further down.&lt;/p&gt;

&lt;h2&gt;Reacting to Interaction&lt;/h2&gt;

&lt;p&gt;Now that we know which president's timeline the user is pointing at, we can do something to highlight it and add some information. This is what we'll do with the label to highlight it: use a darker color, so it stands out more. The only thing that differs from the definition above is the last line.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;.anchor("left").add(pv.Label)
    .textAlign("left")
    .textBaseline("bottom")
    .text(function(d) d.name)
    .textStyle(function() (activePresident == this.index)
                            ? "#333" : "#ddd");
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Because we are specifying a function, it gets re-evaluated every time the panel is redrawn. So whenever the &lt;code&gt;point&lt;/code&gt; event is triggered, it will check whether the label is for the "active" president or not.&lt;/p&gt;

&lt;p&gt;The operator used here is a shorter version of an &lt;code&gt;if&lt;/code&gt; statement. We could also write the last line above like this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;.textStyle(function() {
    if (activePresident == this.index)
        return "#333";
    else
        return "#ddd";
    });
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The first version is obviously more compact, and "more functional" (i.e., closer to the way &lt;code&gt;if&lt;/code&gt; works in some functional languages). If used sparingly, this syntax can actually make code more readable. But it easily gets overused, especially when you start nesting expressions; so use with caution.&lt;/p&gt;

&lt;p&gt;In addition to highlighting, we also want to have add some vertical lines that help the user compare the lives and office terms of different presidents. To do this, we'll add some Rules.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;vis.add(pv.Rule)
    .left(function() dates(presidents[activePresident].born))
    .bottom(0)
    .strokeStyle("#aaa");
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This very simply maps the current president's birth date to the x position using the &lt;code&gt;dates&lt;/code&gt; scale, and it is also updated every time the panel is redrawn. We'll also add one for the ascension, end of office term, and date of death. Only with the latter two, we want to be careful for those special cases. So we add a simple rule to hide the lines when they are not needed:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;.visible(function() presidents[activePresident].term_ended
                                                &amp;lt; today);
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The same is done for the &lt;code&gt;.died&lt;/code&gt; field. That means that we won't show the lines for those presidents still alive or in office.&lt;/p&gt;

&lt;h2&gt;Text Label&lt;/h2&gt;

&lt;p&gt;Now this is all good and well, but what about exact dates and such? Because the interaction essentially centers around one variable, it's very easy to add more things that react without creating additional dependencies. Adding a label that displays the active president's name is as easy as this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;vis.add(pv.Label)
    .text(function() presidents[activePresident].name)
    .left(0)
    .bottom(52)
    .font("bold 12pt sans-serif");
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Same idea as above, and the rest is styling. That's not yet terribly useful though, because we're already seeing the name on the life line. But we can just as easily add the life and office terms:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;vis.add(pv.Label)
    .text(function(d) {
        var s = "Life: "+dateFormat.format(
            toJSDate(presidents[activePresident].born))+" - ";
        if (presidents[activePresident].died &amp;lt; today)
            s += dateFormat.format(
                toJSDate(presidents[activePresident].died));
        return s;
    })
    .left(0)
    .bottom(40);
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The date here is printed using a date format function that is included in Protovis. To use that, we have to convert the date (which is a Julian date, remember) into a JavaScript object. The function &lt;code&gt;toJSDate()&lt;/code&gt; does that. If you want to see the specifics of the date format or the function, check out the source code.&lt;/p&gt;

&lt;p&gt;But since we are using the Julian date, there is one thing we can do very easily: figure out the age at the time of ascension.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;vis.add(pv.Label)
    .text(function(d) "Ascension Age: "+
        Math.floor((presidents[activePresident].term_began-
            presidents[activePresident].born)/365.25))
    .left(0)
    .bottom(20);
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The difference between the &lt;code&gt;term_began&lt;/code&gt; and &lt;code&gt;born&lt;/code&gt; fields is the number of days between them. By dividing by 365.25, we convert to years and even consider leap years (though it's unlikely to make a difference). Finally the value is rounded down to the next smaller integer using the &lt;code&gt;Math.floor()&lt;/code&gt; function.&lt;/p&gt;

&lt;p&gt;Here is the complete plot including the labels:&lt;/p&gt;

&lt;!--[if !IE]&gt;&lt;!--&gt;

&lt;script type="text/javascript+protovis"&gt;&lt;!--

    height += 75;

    var vis3 = makePresidentsPanel(true);
    
    vis3.add(pv.Label)
        .text(function() presidents[activePresident].name)
        .left(0)
        .bottom(52)
        .font("bold 12pt sans-serif");

    vis3.add(pv.Label)
        .text(function(d) {
            var s = "Life: "+dateFormat.format(toJSDate(presidents[activePresident].born))+" - ";
            if (presidents[activePresident].died &lt; today)
                s += dateFormat.format(toJSDate(presidents[activePresident].died));
            return s;
        })
        .left(0)
        .bottom(40);

    vis3.add(pv.Label)
        .text(function(d) {
            var s = "Office: "+dateFormat.format(toJSDate(presidents[activePresident].term_began))+" - ";
            if (presidents[activePresident].term_ended &lt; today)
                s += dateFormat.format(toJSDate(presidents[activePresident].term_ended));
            return s;
        })
        .left(0)
        .bottom(30);

    vis3.add(pv.Label)
        .text(function(d) "Ascension Age: "+Math.floor((presidents[activePresident].term_began-presidents[activePresident].born)/365.25))
        .left(0)
        .bottom(20);
        
    vis3.render();
// --&gt;
&lt;/script&gt;

&lt;noscript&gt;
&lt;!--&lt;![endif]--&gt;
&lt;img src="http://eagereyes.org/media/2010/protovis-primer/presidents-labels.png" width="560" height="240" alt="presidents chart" /&gt;
&lt;!--[if !IE]&gt;&lt;!--&gt;
&lt;/noscript&gt;

&lt;!--&lt;![endif]--&gt;

&lt;p&gt;And that's it! Interactive visualization in the browser! If you want to see the entire dataset, there is a &lt;a href="http://eagereyes.org/media/2010/protovis-primer/presidents.html" rel="lightframe[Presidential Demographics|width:1030px;height:695px;]"&gt;version of it here in a little virtual pop-up&lt;/a&gt;. Also check out the &lt;a href="http://github.com/eagereyes/Protovis-Primer" target="_blank"&gt;code on github&lt;/a&gt;, or &lt;a href="http://eagereyes.org/media/2010/protovis-primer/protovis-primer-part3.zip"&gt;download a ZIP file containing the full code and data file&lt;/a&gt;.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;Previous:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="http://eagereyes.org/tutorials/protovis-primer-part-1"&gt;A Protovis Primer, Part 1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://eagereyes.org/tutorials/protovis-primer-part-2"&gt;A Protovis Primer, Part 2&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;img src="http://feeds.feedburner.com/~r/EagerEyes/~4/GWG4KIv42xY" height="1" width="1"/&gt;</description>
 <comments>http://eagereyes.org/tutorials/protovis-primer-part-3#comments</comments>
 <category domain="http://eagereyes.org/tutorials">Tutorials</category>
 <pubDate>Mon, 26 Jul 2010 22:12:09 +0000</pubDate>
 <dc:creator>Robert Kosara</dc:creator>
 <guid isPermaLink="false">783 at http://eagereyes.org</guid>
<feedburner:origLink>http://eagereyes.org/tutorials/protovis-primer-part-3</feedburner:origLink></item>
<item>
 <title>Multi-touch Brushing for Parallel Coordinates</title>
 <link>http://feedproxy.google.com/~r/EagerEyes/~3/Hge2-tKlXqI/multi-touch-brushing-for-parallel-coordinates</link>
 <description>&lt;a href="http://eagereyes.org/blog/2010/multi-touch-brushing-for-parallel-coordinates"&gt;&lt;img src="http://eagereyes.org/media/2010/multitouch-brushing.png" width="560" height="232"&gt;&lt;/a&gt;

&lt;p&gt;Interaction in visualization is incredibly important, but often more tedious than it needs to be. I have developed a new way of brushing in parallel coordinates that uses the multi-touch trackpads on Apple's MacBook and MacBook Pro laptops for faster interaction. The video below demonstrates the technique, and the source code is available.&lt;/p&gt;
&lt;!--break--&gt;
&lt;p&gt;This is a little experiment that has gotten out of hand a bit. I recently submitted a paper to a conference that doesn't let me attach additional materials, so I figured I'd put the video up on vimeo and link to it in the paper. I also posted a link on my &lt;a href="http://blog.kosara.net/"&gt;secondary blog&lt;/a&gt;, which got posted on Twitter, and which ended up being blogged by some &lt;a href="http://www.theusrus.de/blog/why-do-we-do-it-–-cause-we-can/" target="_blank"&gt;other&lt;/a&gt; &lt;a href="http://interactivemultimediatechnology.blogspot.com/2010/07/multi-touch-parallel-coordinates-for.html" target="_blank"&gt;people&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you've used a MacBook or MacBook Pro, you know how simple and intuitive the two-finger scrolling is. Similarly, there are some interactions on the iPhone that are so obvious (like the pinch for zooming) that it's tough to remember how we did things before touch screens. I wanted to create a similar experience for brushing in parallel coordinates.&lt;/p&gt;

&lt;p&gt;The idea is to map the axes to locations on the trackpad, and use several fingers to indicate different kinds of brushing. A single finger selects an axis, two fingers let you brush on a single axis. With four fingers, you can brush on two axes at the same time, either on adjacent ones or ones that have other axes in between them. There is also a somewhat unusual brushing method called _angular brushing_, which is very tedious to use with a mouse. Using three fingers, it's very quick and intuitive, and great for finding outliers in the data. You can also flip and rearrange axes.&lt;/p&gt;

&lt;p&gt;There are a few details that make this a bit more complicated, because it's hard to maintain the horizontal position during interaction. So there is a certain "stickiness" to the first axis, which keeps it selected even when the fingers move somewhere else. The right mix of stability and flexibility is not easy to find, but I think I'm getting close.&lt;/p&gt;

&lt;p&gt;The trackpad is not just a good fit because it's a lot more common than large touch screens (also on some non-Apple laptops), but because it solves an interesting problem you have when interacting with stuff on a touch screen: your fingers get in the way. That isn't always a problem, but in the case of brushing, it is. You don't want to have to change the brush, lift your hands, look at it, put down the hands, adjust, lift again, etc. With the interaction happening on a different surface (and in a convenient location), you can focus on the program's response to what your hands are doing.&lt;/p&gt;

&lt;p&gt;The video below demonstrates the technique. It's still quite a different thing to actually try it out yourself, though. It allows for very fast exploration of data, unlike any interaction technique I'm aware of. Using your fingers is not very precise, but that's not the goal here, anyway: it's about getting a feeling for a new dataset.&lt;/p&gt;

&lt;p&gt;&lt;object width="560" height="315"&gt;&lt;param name="allowfullscreen" value="true" /&gt;&lt;param name="allowscriptaccess" value="always" /&gt;&lt;param name="movie" value="http://vimeo.com/moogaloop.swf?clip_id=13437693&amp;amp;server=vimeo.com&amp;amp;show_title=1&amp;amp;show_byline=1&amp;amp;show_portrait=0&amp;amp;color=00ADEF&amp;amp;fullscreen=1" /&gt;&lt;embed src="http://vimeo.com/moogaloop.swf?clip_id=13437693&amp;amp;server=vimeo.com&amp;amp;show_title=1&amp;amp;show_byline=1&amp;amp;show_portrait=0&amp;amp;color=00ADEF&amp;amp;fullscreen=1" type="application/x-shockwave-flash" allowfullscreen="true" allowscriptaccess="always" width="560" height="315"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;/p&gt;

&lt;p&gt;You can watch the &lt;a href="http://vimeo.com/13437693" target="_blank"&gt;video in HD&lt;/a&gt; on the vimeo site. The inset in the lower right is meant as a guide to show what the fingers are doing, not as a part of the actual user interface. People have suggested showing the location of the fingers as an overlay, but when you watch the video you will see why this would be a bad idea: the fingers don't have to line up with the axes, plus the floating dots would be distracting.&lt;/p&gt;

&lt;p&gt;I have also posted the &lt;a href="http://github.com/eagereyes/ParVisMT" target="_blank"&gt;source code&lt;/a&gt; and there's an executable under the downloads tab if you want to play with it. Be advised, though, that this is a pure research prototype that demonstrates the technique but doesn't do anything useful. The data is hardcoded and the program only lets you brush and rearrange axes, nothing else. The program requires Mac OS X 10.6 "Snow Leopard" and a recent MacBook or MacBook Pro.&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/EagerEyes/~4/Hge2-tKlXqI" height="1" width="1"/&gt;</description>
 <comments>http://eagereyes.org/blog/2010/multi-touch-brushing-for-parallel-coordinates#comments</comments>
 <category domain="http://eagereyes.org/blog">blog</category>
 <category domain="http://eagereyes.org/topics/Techniques">Techniques</category>
 <pubDate>Wed, 21 Jul 2010 01:49:55 +0000</pubDate>
 <dc:creator>Robert Kosara</dc:creator>
 <guid isPermaLink="false">781 at http://eagereyes.org</guid>
<feedburner:origLink>http://eagereyes.org/blog/2010/multi-touch-brushing-for-parallel-coordinates</feedburner:origLink></item>
<item>
 <title>Workshop: The Role of Theory in Information Visualization</title>
 <link>http://feedproxy.google.com/~r/EagerEyes/~3/z0Hr3ACn9ZI/infovis-theory-workshop</link>
 <description>&lt;p&gt;&lt;img src="http://eagereyes.org/media/2010/infovistheory-teaser.png" width="560" height="311" alt="InfoVisTheory Teaser"&gt;&lt;/p&gt;

&lt;p&gt;Information visualization is a very applied field that prides itself on its practical applications and real-world scenarios. Ignoring the theoretical side is dangerous, however, because it limits our ability to distill useful information about the foundations of the field from the practical work being done, and limits our understanding of how and why our own creations work. The goal of this workshop at &lt;a href="http://vis.computer.org/VisWeek2010/" target="_blank"&gt;VisWeek 2010&lt;/a&gt; is to bring together researchers interested in the theoretical aspects of visualization, define the field, discuss ideas and approaches, and get the word out about the importance of theoretical research in information visualization.
&lt;!--break--&gt;&lt;/p&gt;

&lt;h2&gt;Topics&lt;/h2&gt;

&lt;p&gt;There are many different views of and approaches to theory in visualization. We want to discuss them and come to an understanding of what encompasses theory. Topics that the organizers currently consider theory include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Perceptual research&lt;/li&gt;
&lt;li&gt;Design studies&lt;/li&gt;
&lt;li&gt;Ways of systematically exploring the visualization design space&lt;/li&gt;
&lt;li&gt;Methodological work: how do we do our work, and how can we improve our processes?&lt;/li&gt;
&lt;li&gt;Representation, structure, metaphors: how and why do we attach meaning to visual shapes?&lt;/li&gt;
&lt;li&gt;Reference datasets and tasks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Also, how do we make it easier to publish this kind of work? What makes for a good theory paper? Can a theory paper stand on its own, without an application or immediate use? Etc.&lt;/p&gt;

&lt;h2&gt;Structure&lt;/h2&gt;

&lt;p&gt;The workshop will be based mostly on discussions. We invite speakers to submit short (2-page) position statements. A small number of these will be invited to give very brief (3-minute) presentations at the workshop. The goal is not to present large bodies of work, but to have the presentations spark discussions.&lt;/p&gt;

&lt;p&gt;We are also planning a mini-panel, in which the panelists will give short (5-minute) presentations, with an extensive question-and-answer period afterwards.&lt;/p&gt;

&lt;p&gt;As an extension of the workshop, we are planning a special issue of a journal devoted to theoretical work in information visualization. More details on this will be coming soon.&lt;/p&gt;

&lt;h2&gt;Deadlines&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Submission: August 2, 2010&lt;/li&gt;
&lt;li&gt;Notification: August 6, 2010&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;Participation&lt;/h2&gt;

&lt;p&gt;Everybody at VisWeek 2010 will be welcome to participate in the discussions.&lt;/p&gt;

&lt;p&gt;We are sending out invitations to people in the field who we consider to be interested in theoretical work. But we want to especially encourage people who may not be well known yet, or who have had an interest in theoretical work but have not published in this area yet, to participate and submit statements.&lt;/p&gt;

&lt;h2&gt;Date and Time&lt;/h2&gt;

&lt;p&gt;The conference schedule is not available yet, but the workshop will take place some time during the conference week of October 24 to 29, 2010.&lt;/p&gt;

&lt;h2&gt;Format, Submission&lt;/h2&gt;

&lt;p&gt;Submissions have to be in the &lt;a href="http://www.cs.sfu.ca/~vis/Tasks/camera.html" target="_blank"&gt;summary format&lt;/a&gt; (same as for posters) and no longer than two pages. We are not looking for expositions of research results, but rather interesting and provocative ideas, position statements, and thoughts about the scope of theory in infovis.&lt;/p&gt;

&lt;p&gt;Accepted position papers will be made available online and be included in the VisWeek electronic proceedings.&lt;/p&gt;

&lt;p&gt;Submit your paper by emailing a PDF file to the following address:&lt;/p&gt;

&lt;p&gt;&lt;a href="mailto:infovistheoryws@me.com"&gt;infovistheoryws@me.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Feel free to post comments below if you have questions, or use the &lt;a href="http://eagereyes.org/contact"&gt;contact form&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;Organizers&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="http://kosara.net/" target="_blank"&gt;Robert Kosara&lt;/a&gt;, UNC Charlotte&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.cse.msstate.edu/~tjk/" target="_blank"&gt;T.J. Jankun-Kelly&lt;/a&gt;, Mississippi State University&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.cs.ou.edu/~weaver/academic/" target="_blank"&gt;Chris Weaver&lt;/a&gt;, University of Oklahoma&lt;/li&gt;
&lt;/ul&gt;

&lt;iframe src="http://www.facebook.com/plugins/likebox.php?id=120203618025408&amp;amp;width=292&amp;amp;connections=10&amp;amp;stream=false&amp;amp;header=true&amp;amp;height=287" scrolling="no" frameborder="0" style="border:none; overflow:hidden; width:292px; height:287px;margin-left:134px;margin-top:20px;" allowTransparency="true"&gt;&lt;/iframe&gt;&lt;img src="http://feeds.feedburner.com/~r/EagerEyes/~4/z0Hr3ACn9ZI" height="1" width="1"/&gt;</description>
 <comments>http://eagereyes.org/infovis-theory-workshop#comments</comments>
 <category domain="http://eagereyes.org/service">Service</category>
 <category domain="http://eagereyes.org/topics/Theory">Theory</category>
 <pubDate>Thu, 15 Jul 2010 03:51:21 +0000</pubDate>
 <dc:creator>Robert Kosara</dc:creator>
 <guid isPermaLink="false">780 at http://eagereyes.org</guid>
<feedburner:origLink>http://eagereyes.org/infovis-theory-workshop</feedburner:origLink></item>
<item>
 <title>InfoVis Discovery Exhibition 2010</title>
 <link>http://feedproxy.google.com/~r/EagerEyes/~3/KZ_mKILMLzM/infovis-discovery-exhibition-2010</link>
 <description>&lt;p&gt;Showing the impact of visualization work is not something we as a community are particularly good at. The &lt;a href="http://www.discoveryexhibition.org/"&gt;Discovery Exhibition&lt;/a&gt; at the Information Visualization conferences is a great venue for doing just that. If you have a tool that's used by real users for real work, submit a brief description by July 19.
&lt;!--break--&gt;
We (three readers of this website, my student Caroline Ziemkiewicz, and I) submitted &lt;a href="http://www.discoveryexhibition.org/pmwiki.php/Entries/Kosara2009" target="_blank"&gt;an entry&lt;/a&gt; on &lt;a href="/parallel-sets"&gt;Parallel Sets&lt;/a&gt;, and won the &lt;em&gt;Best Presentation Award&lt;/em&gt;. Petra Isenberg is writing a series of postings over at infosthetics on last year's entries, &lt;a href="http://infosthetics.com/archives/2010/07/visweek_discovery_exhibition_submit_story_about_impact_of_visualization.html" target="_blank"&gt;starting with ours&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This year, they've extended the limit for the abstracts to four pages, which makes writing things up a lot easier. Squeezing three short case studies, a brief description of the tool, an abstract, and a few screenshots into two pages was a bit of a challenge last year.&lt;/p&gt;

&lt;p&gt;Writing for the discovery exhibition is different than a paper or poster. You want to introduce your tool, but especially show that it has made a real impact. Perhaps the most effective way for this are case studies: show what real users have done with it. Doing this can be a challenge because many companies don't want to share their data or what they do with it. But there are ways to still do it, with sanitized data, cleaned-up screenshots, and general descriptions of what has been done.&lt;/p&gt;

&lt;p&gt;The point is that there needs to be some kind of evidence that the tool is actually useful to real people. Collecting that kind of data is very helpful for showing the value of visualization (beyond academia). So if you have done something useful, submit it!&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;Yes, more installments of the Protovis Primer are coming. Stay tuned.&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/EagerEyes/~4/KZ_mKILMLzM" height="1" width="1"/&gt;</description>
 <comments>http://eagereyes.org/blog/2010/infovis-discovery-exhibition-2010#comments</comments>
 <category domain="http://eagereyes.org/blog">blog</category>
 <pubDate>Fri, 02 Jul 2010 03:06:15 +0000</pubDate>
 <dc:creator>Robert Kosara</dc:creator>
 <guid isPermaLink="false">779 at http://eagereyes.org</guid>
<feedburner:origLink>http://eagereyes.org/blog/2010/infovis-discovery-exhibition-2010</feedburner:origLink></item>
<item>
 <title>The End of Verifiable.com</title>
 <link>http://feedproxy.google.com/~r/EagerEyes/~3/XoFQ2YANVDw/end-of-verifiable-com</link>
 <description>&lt;p&gt;&lt;a href="http://eagereyes.org/blog/2010/end-of-verifiable-com"&gt;&lt;img src="http://eagereyes.org/media/2010/verifiable.png" width="560" height="247"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On August 1, 2010, the visualization website &lt;a href="http://Verifiable.com/"&gt;Verifiable.com&lt;/a&gt; will close. If you don't know Verifiable, I recommend checking them out despite this, it's an interesting site that has explored an important niche of the online visualization (&lt;em&gt;visualization-as-a-service&lt;/em&gt;, if you will) world. Its demise can teach us a lot about how visualization for the masses works, and what we need to do to actually make it happen.
&lt;!--break--&gt;
Verifiable let you upload data and create visualizations. The interface was similar to &lt;a href="http://www.tableausoftware.com/" target="_blank"&gt;Tableau&lt;/a&gt;, though certainly not as powerful. But it provided a lot of ways of creating interesting visualizations, and was more analytical than &lt;a href="http://manyeyes.alphaworks.ibm.com/manyeyes/" target="_blank"&gt;Many Eyes&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;What I liked best about Verifiable, and what all other visualization websites I know are still missing, was that you did not have to pick the type of visualization first. Just like in Tableau, you could switch data dimensions and visual mappings around to figure out what would work best for your data.&lt;/p&gt;

&lt;p&gt;Like most online services, this one had a free tier, where your data and visualizations were public. With a paid account, everything was private, unless you chose to share it. It's kind of a typical setup where they tried to establish a popular service to get people interested, and convert some fraction of them into paying users.&lt;/p&gt;

&lt;h2&gt;Lessons Learned the Hard Way&lt;/h2&gt;

&lt;p&gt;Stuart Roseman, Verifiable's "soon to be Ex-President," &lt;a href="http://stuartroseman.com/post/619953720/out-with-the-old-business-in-with-the-new" target="_blank"&gt;presents his thoughts on what went wrong&lt;/a&gt; and what their main stumbling blocks were. These are valuable lessons worth discussing a bit further. According to Roseman, any startup must:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;serve a need actual people have RIGHT NOW&lt;/li&gt;
&lt;li&gt;implement in such a way so that users don't have to do ANY work or expend ANY energy except for paying&lt;/li&gt;
&lt;li&gt;avoid user interfaces like the plague.&lt;/li&gt;
&lt;li&gt;be able to field something really quickly: not years, not months, but weeks.&lt;/li&gt;
&lt;li&gt;get the product into users hands quickly and iterate on their daily needs often&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;As much as I hate to admit it, I think all visualization websites fail on point one. People don't feel a need to visualize data, we have to make them aware that visualization even exists, and that it can do something for them. The vast number of pretty but useless pictures on the web that are all called visualizations doesn't exactly help.&lt;/p&gt;

&lt;p&gt;The few people who do want to visualize their data and are looking for this kind of service are most likely not interested in visualization web sites the way they exist today. Those people are mostly dealing with very valuable proprietary data that they don't want to (or aren't allowed to, per corporate policy) upload to a third party's web service.&lt;/p&gt;

&lt;p&gt;The second point, I'm not sure I agree with. It may be true that casual users will abandon a site where they have to do work pretty quickly; but those corporate users I'm thinking of are very eager to play with their data.&lt;/p&gt;

&lt;p&gt;More general users are a different question, and I wonder if it even makes sense to mix the two. That may sound like a good idea to get lots of users that you can convert to paying customers, to raise awareness, etc. But ultimately, I think it makes it harder to focus on the power users that you'll make money from and eats up resources.&lt;/p&gt;

&lt;p&gt;Point number three, avoiding user interfaces, is an interesting one. Since Verifiable was quite similar to Tableau, a comparison is perhaps in order. While Tableau has one of the most effective user interfaces once you know it, it's by no means obvious. I use Tableau in my &lt;em&gt;Visual Analytics&lt;/em&gt; class, and I get complaints from students every year about how much time it takes them to figure out how to work with the program. It seems obvious when you see somebody else do it, but when you're working with it yourself for the first time, you easily get lost.&lt;/p&gt;

&lt;p&gt;The thing about Tableau, and what makes it so powerful, is that the user interface is not primarily about visualization, but about data. What you are seeing is a visualization that you're building, but you mostly control the data. That is a virtue, but it also creates a disconnect that users need to get over before they're able to use it effectively (and without frustration).&lt;/p&gt;

&lt;p&gt;Users who care about their data and what they can learn about it accept this very quickly, and actually appreciate it. But users who want to play with visualization settings don't like it, and will look for a different program. It may be that the same thing happened with Verifiable.com.&lt;/p&gt;

&lt;p&gt;The last two points (get the thing out the door quickly and iterate often) are typical agile development kinds of ideas. Those work with a focused user base that you can ask questions and get concrete ideas from, but not with casual users who want to play with baseball stats and global poverty data. For those users, you have to drive the tool forward, because you're presenting something new to them that they didn't realize was even possible. Power users will not (and shouldn't) tell you what to create, but at least they'll have a clear idea what they want to achieve with your tool.&lt;/p&gt;

&lt;h2&gt;Key Obstacles&lt;/h2&gt;

&lt;p&gt;Roseman also discusses some key issues that stood in the way of becoming a more successful website:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;how many edge cases there are in any complex user interface&lt;/li&gt;
&lt;li&gt;how little patience users have for complex user interfaces&lt;/li&gt;
&lt;li&gt;how hard it is to find clean data and upload it&lt;/li&gt;
&lt;li&gt;how little interest people have in finding clean data and uploading it&lt;/li&gt;
&lt;li&gt;how much time and skill it requires to find clean data and upload it&lt;/li&gt;
&lt;li&gt;how much time and skill it requires to make a decent, thoughtful chart from that data&lt;/li&gt;
&lt;li&gt;how impossible it is to test complex user interfaces (we even convinced the funfx guy to help out and we still failed to develop a decent overall easy to run test framework)&lt;/li&gt;
&lt;li&gt;how fragile a complex flex based user interface is. fix one thing break another.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;While the technical issues are certainly big practical problems, they equally apply to many other kinds of web services. The interesting point here, and the key idea behind Verifiable, is the difficulty of finding good, "clean" data that is also of interest to people. Despite the considerable amount of stuff now available on &lt;a href="http://www.data.gov/" target="_blank"&gt;data.gov&lt;/a&gt; and similar sites, there is no commensurate surge in web sites, visualizations, etc. Sure, there are exceptions, and some data makes big waves. But most of the data is probably never even looked at.&lt;/p&gt;

&lt;p&gt;The truth is that most data is just not very interesting. Digging through all this data is difficult, and visualization doesn't necessarily help. Are you going to spend an hour or two playing with some financial dataset, only to find out that there's nothing interesting in it? And if you've done that once, how many more times are you going to do it?&lt;/p&gt;

&lt;h2&gt;Visualization for the Masses – Done Right&lt;/h2&gt;

&lt;p&gt;I think the idea of visualization for the masses is a good one, but not if it's also done &lt;em&gt;by&lt;/em&gt; the masses. This kind of effort has to be curated, there has to be an editor who has the instinct to look for and find interesting data, knows how to analyze it, and uses visual tools to present it. That model is not really different from journalism, all the talk about "citizen journalism" notwithstanding.&lt;/p&gt;

&lt;p&gt;I think that what Verifiable's demise is showing is that visualization is too hard a sell right now to a public that has no idea what to do with it. A better way of making people see visualization's benefits is by presenting well-constructed, interesting examples that give people actual information. Over time, this will show them how visualization is more than just pretty pictures, and perhaps inspire them to try those tools themselves.&lt;/p&gt;

&lt;p&gt;We tend to forget that all those things that now get a "citizen" prefix (journalism, science, etc.) first had to be figured out and developed by people who spent their entire lives doing them. Only through the commoditization of this knowledge, and the tools that are needed, has it become possible for people outside that realm to try their hands on those same things. Visualization tools may be readily available (though not all are created equal), but the knowledge how to use them well is not. In fact, a large part of that does not even exist yet. Before we have experts who really know what they are doing when it comes to visualization (and can point to evidence that they do), there won't be effective visualization by the masses.&lt;/p&gt;

&lt;p&gt;Verifiable.com was the right idea, but it simply came too early. Give it another five to ten years, and the world will be ready for it.&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/EagerEyes/~4/XoFQ2YANVDw" height="1" width="1"/&gt;</description>
 <comments>http://eagereyes.org/blog/2010/end-of-verifiable-com#comments</comments>
 <category domain="http://eagereyes.org/blog">blog</category>
 <pubDate>Mon, 21 Jun 2010 02:26:22 +0000</pubDate>
 <dc:creator>Robert Kosara</dc:creator>
 <guid isPermaLink="false">778 at http://eagereyes.org</guid>
<feedburner:origLink>http://eagereyes.org/blog/2010/end-of-verifiable-com</feedburner:origLink></item>
<item>
 <title>Beautiful Visualization</title>
 <link>http://feedproxy.google.com/~r/EagerEyes/~3/8Tp1p7til5Y/beautiful-visualization</link>
 <description>&lt;p&gt;&lt;a href="http://eagereyes.org/blog/2010/beautiful-visualization"&gt;&lt;img src="http://eagereyes.org/media/2010/beautifulvis.jpg" width="560" height="220" alt="Beautiful Visualization"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="http://oreilly.com/catalog/0636920000617" target="_blank"&gt;&lt;em&gt;Beautiful Visualization&lt;/em&gt;&lt;/a&gt; is a collection of essays on a wide range of topics in visualization. Don't let the title mislead you: while it has its share of artistic visualization, there is also quite a bit of technical information in there. One of the chapters was written by yours truly.
&lt;!--break--&gt;
The chapters cover topics like Wordle, the New York Times News API, the use of color, visualizing Wikipedia, etc. Among the authors are people like Martin Wattenberg and Fernanda Viégas, Moritz Stefaner, Danyel Fisher, etc. All chapters are in the 10-25 page range, which makes for nice bite-sized chunks that can be read one after the other or as you find the time for them.&lt;/p&gt;

&lt;p&gt;Julie Steele and Noah Iliinsky, the editors, have done a great job to make the chapters read well and be easy to digest. Even the more in-depth stuff is illustrated nicely with figures and described in ways that provide enough detail without getting too technical.&lt;/p&gt;

&lt;p&gt;My chapter, &lt;a href="http://kosara.net/publications/Kosara_BeautifulVis_2010.html"&gt;&lt;em&gt;Turning a Table into a Tree: Growing Parallel Sets into a Purposeful Project&lt;/em&gt;&lt;/a&gt;, talks about some of the thinking behind the Parallel Sets redesign I did about two years ago. It goes into a bit of technical detail how &lt;a href="http://eagereyes.org/parallel-sets"&gt;Parallel Sets&lt;/a&gt; work internally, but also describes how thinking about the data model lead to a visual redesign, which in turn prompted more technical changes and a better understanding of our own technique. I also use my little soapbox to complain about the quality of academic prototypes and the lack of an open-source culture in visualization.&lt;/p&gt;

&lt;p&gt;O'Reilly offers the book in a physical version and as an ebook (as a completely annoyance-free PDF, and there's also epub and a few other formats). The chapters have also been released under a creative commons license, so you can find most of them posted on the web somewhere. If you buy the book, the proceeds go to charity.&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/EagerEyes/~4/8Tp1p7til5Y" height="1" width="1"/&gt;</description>
 <comments>http://eagereyes.org/blog/2010/beautiful-visualization#comments</comments>
 <category domain="http://eagereyes.org/blog">blog</category>
 <pubDate>Wed, 16 Jun 2010 03:51:39 +0000</pubDate>
 <dc:creator>Robert Kosara</dc:creator>
 <guid isPermaLink="false">777 at http://eagereyes.org</guid>
<feedburner:origLink>http://eagereyes.org/blog/2010/beautiful-visualization</feedburner:origLink></item>
<item>
 <title>A Protovis Primer, Part 2</title>
 <link>http://feedproxy.google.com/~r/EagerEyes/~3/A9TwrbFhzdA/protovis-primer-part-2</link>
 <description>&lt;script src="http://eagereyes.org/media/protovis-r3.2.js" type="text/javascript"&gt;
&lt;/script&gt;

&lt;p&gt;&lt;a href="http://eagereyes.org/tutorials/protovis-primer-part-2"&gt;&lt;img src="http://eagereyes.org/media/2010/protovis-primer/eq7.png" width="560" height="250" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The second installment of the &lt;a href="http://protovis.org/" target="_blank"&gt;Protovis&lt;/a&gt; tutorial introduces some more of its JavaScript-specific features like scales, shows how to anchor marks on other marks, and how to use rules. We also start using real data, and learn how to load that data from a remote source.
&lt;!--break--&gt;
Visualization is pointless if it doesn't represent interesting data. For this part of the tutorial, I have prepared a data file that contains all earthquakes of magnitude 1 or above over the last 24 hours. This data file is created by downloading a CSV file from &lt;a href="http://earthquake.usgs.gov/earthquakes/catalogs/" target="_blank"&gt;U.S. Geological Survey (USGS)&lt;/a&gt; and converting it to the JSON format. The data file (at &lt;a href="http://data.eagereyes.org/pv/earthquakes.json"&gt;this URL&lt;/a&gt;) is updated once every hour, so if you have a Protovis-capable browser, you are seeing current data in the examples below.&lt;/p&gt;

&lt;h1&gt;Data in Protovis&lt;/h1&gt;

&lt;p&gt;There are many ways to get data into Protovis, and several ways of organizing it. The easiest way is to load a file in the JSON (JavaScript Object Notation) format, which is essentially JavaScript syntax for expressing arrays/lists and objects/dictionaries. This is what the earthquake data looks like in the file:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;[
    {
        "Src": "ci",
         "Region": "Southern California",
         "Lon": "-116.8408",
         "Datetime": "Thursday, June 10, 2010 01:35:03 UTC",
         "Depth": "4.10",
         "Version": "1",
         "Lat": "34.3186",
         "Eqid": "14740876",
         "Magnitude": "2.0",
         "NST": "79"
    },
    ...
]
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The outer square brackets denote an array, which can contain any type of object (including other arrays, numbers, etc.). Curly braces delimit an object, which in other languages would be called a hash table or dictionary. An object maps keys, which have to be strings, to values, which can be of any type.&lt;/p&gt;

&lt;p&gt;The example here shows only strings, but that is the result of the conversion not caring about data types. The numbers could also be represented without quotation marks. JavaScript automatically converts strings to numbers as needed, so in practice it makes practically no difference how they are encoded.&lt;/p&gt;

&lt;p&gt;JSON data can be retrieved from many sources, instead of or in addition to XML. JSON has the advantage of being smaller than XML, easier to parse, and mapping directly to an object hierarchy in most programming languages. It is also possible to create JSON from CSV and other tabular data with the help of &lt;a href="http://www.cparker15.com/utilities/csv-to-json/" target="_blank"&gt;CSV to JSON converters&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;There are two ways to get this data into your Protovis visualization. One is like in the &lt;a href="http://vis.stanford.edu/protovis/ex/" target="_blank"&gt;Protovis examples&lt;/a&gt;, where the data is part of a variable declaration that is read in as a source file. That is fine for examples, but is a bad idea when getting data from remote sources: somebody could alter the file to contain any functionality to hijack your application, spread malware, etc.&lt;/p&gt;

&lt;p&gt;So the following way of loading data is more complicated, but also much more secure. It uses the &lt;a href="http://jquery.com/" target="_blank"&gt;jQuery library&lt;/a&gt; to load the data, and the browser's own JSON parser to securely parse the JSON data into a variable we call &lt;code&gt;earthquakes&lt;/code&gt;.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;var dataURL = "http://data.eagereyes.org/pv/earthquakes.json";

var JSONdata = $.ajax({ type: "GET", url: dataURL,
        async: false }).responseText;
var earthquakes = JSON.parse(JSONdata);
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;There are a lot more things jQuery can do, including asynchronous downloads of the data, etc., but this is all we'll use for now. Our &lt;code&gt;earthquakes&lt;/code&gt; variable now contains the array of earthquakes, with each entry in the array being an object that has fields such as &lt;code&gt;Magnitude&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To access this data, we need to set the variable as the &lt;em&gt;data&lt;/em&gt; property of the visualization we're building, and provide a function that accesses the data. The following code is very similar to what we ended up with in the &lt;a href="http://eagereyes.org/tutorials/protovis-primer-part-1"&gt;first part&lt;/a&gt;, with a few modifications. First, it defines a few variables for convenience, then it creates a simple bar chart from the magnitudes of the earthquakes.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;var width = 560;
var height = 245;

var barWidth = width/earthquakes.length;
var gap = 2;

new pv.Panel().width(width).height(height+5)
    .add(pv.Bar)
        .data(earthquakes)
        .bottom(0)
        .width(barWidth-gap)
        .height(function(d) d.Magnitude * (height/9))
        .left(function() this.index * barWidth)
    .root.render();
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;A little detail that will save us some work later on is the definition of the height. Notice that the panel is five pixels higher than the &lt;code&gt;height&lt;/code&gt; variable specifies. This gives us a little bit of headroom for later to add things close to the top without cutting them off. You can ignore this for now, though.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;barWidth&lt;/code&gt; variable adapts the visualization to the number of earthquakes to show. This number varies, since there can be more or fewer quakes over the course of a day, so we don't want to assume a particular number. An earthquake's magnitude is measured on the &lt;a href="http://en.wikipedia.org/wiki/Richter_magnitude_scale"&gt;Richter scale&lt;/a&gt;, which does not have an upper limit. For practical reasons, we'll limit the maximum to 9 here, which fortunately is rather uncommon.&lt;/p&gt;

&lt;p&gt;The key points for data access here are the following two lines. The first one tells Protovis to use our &lt;code&gt;earthquakes&lt;/code&gt; variable as the data source.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;        .data(earthquakes)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Protovis does not care what the variable contains, as long as it is an array. It simply walks through all the elements in the array and hands them back to functions we define. One such function determines the height of the bars in our bar chart.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;        .height(function(d) d.Magnitude * (height/9))
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This does two things: it accesses the &lt;code&gt;Magnitude&lt;/code&gt; field inside an object and scales that value to fit the height of the visualization. Since the value will be different for each bar, we have to define a function here. The function gets a single parameter, which we call &lt;code&gt;d&lt;/code&gt;. Since we've specified the data to be the &lt;code&gt;earthquakes&lt;/code&gt; variable, we know that the function will be passed one of the earthquake objects. So inside the function, we know that &lt;code&gt;d&lt;/code&gt; has all the fields shown in the JSON code snippet above. One of them is the earthquake's magnitude, in a field called Magnitude. The expression &lt;code&gt;d.Magnitude&lt;/code&gt; accesses the value of that field.&lt;/p&gt;

&lt;p&gt;The rest of the function scales the value from that field by multiplying it with one-ninth of the height of the panel. That means that if the bar will be the height of the panel if the magnitude is equal to 9, and smaller if it is less than that.&lt;/p&gt;

&lt;p&gt;And voilà, the earthquakes of the last 24 hours.&lt;/p&gt;

&lt;!--[if !IE]&gt;&lt;!--&gt;

&lt;script type="text/javascript+protovis"&gt;&lt;!--
    var dataURL = "http://eagereyes.org/media/2010/protovis-primer/earthquakes.json";
    
    var JSONdata = $.ajax({ type: "GET", url: dataURL, async: false }).responseText;
    var earthquakes = JSON.parse(JSONdata);
    
    var width = 560;
    var height = 245;
    
    var barWidth = width/earthquakes.length;
    var gap = 2;
    
    new pv.Panel().width(width).height(height+5)
        .add(pv.Bar)
            .data(earthquakes)
            .bottom(0)
            .width(barWidth-gap)
            .height(function(d) d.Magnitude * (height/9))
            .left(function() this.index * barWidth)
        .root.render();
// --&gt;
&lt;/script&gt;

&lt;noscript&gt;
&lt;!--&lt;![endif]--&gt;
&lt;img src="http://eagereyes.org/media/2010/protovis-primer/eq1.png" width="560" height="250" alt="bars" /&gt;
&lt;!--[if !IE]&gt;&lt;!--&gt;
&lt;/noscript&gt;

&lt;!--&lt;![endif]--&gt;

&lt;h2&gt;Anchors&lt;/h2&gt;

&lt;p&gt;Of course, this doesn't yet tell us a whole lot. How strong were those earthquakes? Were any of them serious? We can do this by attaching labels to the bars that tell us the actual numbers. In Protovis, this can be done in a very elegant fashion. At the end of the pv.Bar definition, before the line that says &lt;code&gt;.root.render()&lt;/code&gt;, we add these lines:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;.anchor("top").add(pv.Label)
    .text(function(d) d.Magnitude)
    .textBaseline("bottom")
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;What that does is attach one label to each bar, anchored at the top of the bar. The data is the same as what is used for the bar, which is one of our earthquake objects. So we need to again define a data accessor function that extracts the magnitude from the earthquake object (same as for the bar above). The additional &lt;em&gt;textBaseline&lt;/em&gt; definition is used to move the label up, otherwise it would overlap the bar. Labels can be aligned in a number of different ways, to be attached to different sides of objects without getting in the way.&lt;/p&gt;

&lt;p&gt;Labeling all the bars gives us this new variation of the chart.&lt;/p&gt;

&lt;!--[if !IE]&gt;&lt;!--&gt;

&lt;script type="text/javascript+protovis"&gt;&lt;!--
    new pv.Panel().width(width).height(height+5)
        .add(pv.Bar)
            .data(earthquakes)
            .bottom(0)
            .width(barWidth-gap)
            .height(function(d) d.Magnitude * (height/9))
            .left(function() this.index * barWidth)
        .anchor("top").add(pv.Label)
            .text(function(d) d.Magnitude)
            .textBaseline("bottom")
        .root.render();
// --&gt;
&lt;/script&gt;

&lt;noscript&gt;
&lt;!--&lt;![endif]--&gt;
&lt;img src="http://eagereyes.org/media/2010/protovis-primer/eq2.png" width="560" height="250" alt="bars" /&gt;
&lt;!--[if !IE]&gt;&lt;!--&gt;
&lt;/noscript&gt;

&lt;!--&lt;![endif]--&gt;

&lt;h1&gt;Hiding Marks&lt;/h1&gt;

&lt;p&gt;That's a start, but it's also very cluttered. Since higher magnitudes are less common, they stand out and are still readable, anyway. But it's clearly a better idea to get rid of all the clutter, and this is very easy:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;    .visible(function(d) d.Magnitude &amp;gt;= 4)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The &lt;em&gt;visible&lt;/em&gt; attribute determines whether a mark will show up or not. As before, we need to define a function here, because the value of the expression will change for each array entry. The rule here is to show the value for earthquakes of magnitude 4 and higher (i.e., earthquakes stronger than "minor"). The function works in a very similar way to the text and height definitions above, only its type is a boolean value: true or false. If the value is greater than or equal to four, the expression will evaluate to true, and the label will be visible. If the magnitude is less than four, the result is false, and the label is not shown.&lt;/p&gt;

&lt;!--[if !IE]&gt;&lt;!--&gt;

&lt;script type="text/javascript+protovis"&gt;&lt;!--
    new pv.Panel().width(width).height(height+5)
        .add(pv.Bar)
            .data(earthquakes)
            .bottom(0)
            .width(barWidth-gap)
            .height(function(d) d.Magnitude * (height/9))
            .left(function() this.index * barWidth)
        .anchor("top").add(pv.Label)
            .text(function(d) d.Magnitude)
            .visible(function(d) d.Magnitude &gt;= 4)
            .textBaseline("bottom")
        .root.render();
// --&gt;
&lt;/script&gt;

&lt;noscript&gt;
&lt;!--&lt;![endif]--&gt;
&lt;img src="http://eagereyes.org/media/2010/protovis-primer/eq3.png" width="560" height="250" alt="bars" /&gt;
&lt;!--[if !IE]&gt;&lt;!--&gt;
&lt;/noscript&gt;

&lt;!--&lt;![endif]--&gt;

&lt;p&gt;There is no guarantee that there won't be overlaps, but they are a lot less likely now. Also, it makes more sense to label the stronger earthquakes, because those are obviously of more interest.&lt;/p&gt;

&lt;h1&gt;Rules&lt;/h1&gt;

&lt;p&gt;Now in addition to numbers, let's also add a more visible scale. The mark to use for this is called a &lt;em&gt;rule&lt;/em&gt;. Rules are lines that are either horizontal or vertical, depending on what data is specified. By defining a &lt;em&gt;bottom&lt;/em&gt; function, we tell Protovis that we want horizontal lines. Rules are added directly to the panel, so let's call the panel we're creating &lt;code&gt;eq&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Just like with bars and other marks, rules need data to be drawn. To do that at regular intervals, we could specify an array that contains the values from 0 to 9, for example. But we can also use a Protovis utility function called &lt;code&gt;range()&lt;/code&gt; that creates that array for us. If you are familiar with the range function in languages like Python, this will look familiar: you can specify just the upper limit (which is not included in the array), or add the starting point and step size as well. Since we're starting at 0 and want a step size of 1 (which are the defaults), we can just specify the upper limit.&lt;/p&gt;

&lt;p&gt;Finally, the values need to be scaled just like for the bar height. So the formula for that looks exactly like above, only here we are directly getting the numerical values, rather than looking at an earthquake object.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;eq.add(pv.Rule)
    .data(pv.range(9))
    .bottom(function(d) d * (height/9))
    .strokeStyle("lightgray");
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The result looks like this. Defining this before the bars means that the bars will be painted on top of the lines. Since the bars are quite thin, it looks like the lines are in front, but they are not.&lt;/p&gt;

&lt;!--[if !IE]&gt;&lt;!--&gt;

&lt;script type="text/javascript+protovis"&gt;&lt;!--
    var eq = new pv.Panel().width(width).height(height+5);
    
    eq.add(pv.Rule)
        .data(pv.range(10))
        .bottom(function(d) d * (height/9))
        .strokeStyle("lightgray");
    
    eq.add(pv.Bar)
            .data(earthquakes)
            .bottom(0)
            .width(barWidth-gap)
            .height(function(d) d.Magnitude * (height/9))
            .left(function() this.index * barWidth)
        .anchor("top").add(pv.Label)
            .text(function(d) d.Magnitude)
            .visible(function(d) d.Magnitude &gt;= 4)
            .textBaseline("bottom");
                
    eq.render();
// --&gt;
&lt;/script&gt;

&lt;noscript&gt;
&lt;!--&lt;![endif]--&gt;
&lt;img src="http://eagereyes.org/media/2010/protovis-primer/eq4.png" width="560" height="250" alt="bars" /&gt;
&lt;!--[if !IE]&gt;&lt;!--&gt;
&lt;/noscript&gt;

&lt;!--&lt;![endif]--&gt;

&lt;h1&gt;Labeled Rules&lt;/h1&gt;

&lt;p&gt;Anchoring also works for rules, and it's extremely useful to label them. This specifies that we want to attach labels to all the rules, but we'll hide the one at 0. This is simply to avoid the 0 being cut off, which it would be given its location. There are other ways of doing this, like moving the baseline, but we'll keep this simple for now.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;.anchor("left").add(pv.Label)
    .visible(function(d) d &amp;gt; 0);
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Notice that we do not even have to specify the &lt;em&gt;text&lt;/em&gt; property here. Protovis automatically assumes that the label should show the numbers from the parent object if nothing is specified, and that is what we want here. (This, BTW, is where specifying the panel height as a bit higher than the chart comes in handy: the top-most label would otherwise be cut off. Having a bit of space around the panel is often a good idea, but makes examples a bit more confusing to read, so I'm keeping it to a minimum here.)&lt;/p&gt;

&lt;p&gt;As it is, the lines still start at the left edge of the panel, so the labels are actually pushed of the side. We need to move them over to the right a bit, like this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;eq.add(pv.Rule)
    .data(pv.range(10))
    .bottom(function(d) d * (height/9))
    .strokeStyle("lightgray")
    .left(10)
    .width(width-10)
.anchor("left").add(pv.Label)
    .visible(function(d) d &amp;gt; 0);
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Also, the bars need to be moved so they don't overlap with the labels. This is done by changing the &lt;code&gt;barWidth&lt;/code&gt; definition slightly.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;var barWidth = (width-10)/earthquakes.length;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Where that is used to specify the location of the bars, we also need to add the same amount so that the gap is to the left (if we don't add anything, the thinner bars will simply end up being shorter, with a gap on the right).&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;    .left(function() 10 + this.index * barWidth)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;That gives us a better view of the data, with a grid and labels.&lt;/p&gt;

&lt;!--[if !IE]&gt;&lt;!--&gt;

&lt;script type="text/javascript+protovis"&gt;&lt;!--
    barWidth = (width-10)/earthquakes.length;

    eq = new pv.Panel().width(width).height(height+5);
    
    eq.add(pv.Rule)
        .data(pv.range(10))
        .bottom(function(d) d * (height/9))
        .strokeStyle("lightgray")
        .left(10)
        .width(width-10)
    .anchor("left").add(pv.Label)
        .visible(function(d) d &gt; 0);
    
    eq.add(pv.Bar)
            .data(earthquakes)
            .bottom(0)
            .width(barWidth-gap)
            .height(function(d) d.Magnitude * (height/9))
            .left(function() 10 + this.index * barWidth)
        .anchor("top").add(pv.Label)
            .text(function(d) d.Magnitude)
            .visible(function(d) d.Magnitude &gt;= 4)
            .textBaseline("bottom");
                
    eq.render();
// --&gt;
&lt;/script&gt;

&lt;noscript&gt;
&lt;!--&lt;![endif]--&gt;
&lt;img src="http://eagereyes.org/media/2010/protovis-primer/eq5.png" width="560" height="250" alt="bars" /&gt;
&lt;!--[if !IE]&gt;&lt;!--&gt;
&lt;/noscript&gt;

&lt;!--&lt;![endif]--&gt;

&lt;h2&gt;Scales&lt;/h2&gt;

&lt;p&gt;Using the same function for the scale in the background is kind of ugly, especially if that means we need to copy code. That is why Protovis has a mechanism for reusable scales that are defined once and then used in several places. This not only makes the code more compact and elegant, it also makes it much easier to change it without running the risk of inconsistencies between the different marks.&lt;/p&gt;

&lt;p&gt;To define a scale, we use the &lt;code&gt;pv.Scale&lt;/code&gt; class, which has a number of convenience functions. Using the familiar function chaining style, it lets us create two scales, one for the x coordinate and one for the height.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;var xScale = pv.Scale.linear(0, earthquakes.length)
                .range(10, width);
var magnitude = pv.Scale.linear(0, 9).range(0, height);
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The code should be fairly self-explanatory: we are defining linear scales from 0 to the number of earthquakes, or from 0 to the maximum magnitude we want to show, respectively. The &lt;code&gt;range()&lt;/code&gt; function specifies what the output range will be, in our case it's simply the width or height of the panel.&lt;/p&gt;

&lt;p&gt;Now remember that this is Protovis, and it will not surprise you that scales are not objects, but functions. That means that you can call &lt;code&gt;xScale()&lt;/code&gt; and &lt;code&gt;magnitude()&lt;/code&gt; as functions to convert a number from the data domain to the screen domain. The way this is used is shown in the following, which specifies the height and location of the bars.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;        .height(function(d) magnitude(d.Magnitude))
        .left(function() xScale(this.index))
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If we had a simple array of numbers, we could even just pass it the function, but since we need to access a field for the height, and the index for the x coordinate, we need to define another function. But the rules are more straight-forward.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;eq.add(pv.Rule)
    .data(magnitude.ticks())
    .bottom(magnitude)
    .strokeStyle("lightgray");
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Notice the definition for &lt;em&gt;bottom&lt;/em&gt;, which simply passes the &lt;code&gt;magnitude&lt;/code&gt; function in without any modifications. Another trick here is using the &lt;code&gt;ticks()&lt;/code&gt; function on the scale, which creates equally-spaced numbers that are suited well for a scale (the exact numbers depend on the range of the scale and the optionally specified number of ticks). This actually means we're calling a function on a function! JavaScript is weird like that.&lt;/p&gt;

&lt;p&gt;The result of this looks no different, which is good. But it's now much more elegant and easier to change.&lt;/p&gt;

&lt;!--[if !IE]&gt;&lt;!--&gt;

&lt;script type="text/javascript+protovis"&gt;&lt;!--
    var xScale = pv.Scale.linear(0, earthquakes.length).range(10, width);
    var magnitude = pv.Scale.linear(0, 9).range(0, height);

    eq = new pv.Panel().width(width).height(height+5);

    eq.add(pv.Rule)
        .data(magnitude.ticks())
        .bottom(magnitude)
        .strokeStyle("lightgray")
        .left(10)
        .width(width-10)
    .anchor("left").add(pv.Label)
        .visible(function(d) d &gt; 0);

    eq.add(pv.Bar)
            .data(earthquakes)
            .bottom(0)
            .width(barWidth-gap)
            .height(function(d) magnitude(d.Magnitude))
            .left(function() xScale(this.index))
        .anchor("top").add(pv.Label)
            .text(function(d) d.Magnitude)
            .visible(function(d) d.Magnitude &gt;= 4)
            .textBaseline("bottom");
                
    eq.render();
// --&gt;
&lt;/script&gt;

&lt;noscript&gt;
&lt;!--&lt;![endif]--&gt;
&lt;img src="http://eagereyes.org/media/2010/protovis-primer/eq5.png" width="560" height="250" alt="bars" /&gt;
&lt;!--[if !IE]&gt;&lt;!--&gt;
&lt;/noscript&gt;

&lt;!--&lt;![endif]--&gt;

&lt;h2&gt;Color Scales&lt;/h2&gt;

&lt;p&gt;Scales can also be used to interpolate colors. Let's say we wanted to change the color of the bars depending on their value, we could create a color scale like this.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;var colors = pv.Scale.linear(1, 9).range("lightgray", "red");
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This assigns a light gray to a magnitude of 1, and turns the bar more and more red the closer we get to 9. In the definition of the bar, we can now use the new scale to specify the fill color.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;.fillStyle(function(d) colors(d.Magnitude));
&lt;/code&gt;&lt;/pre&gt;

&lt;!--[if !IE]&gt;&lt;!--&gt;

&lt;script type="text/javascript+protovis"&gt;&lt;!--

    var colors = pv.Scale.linear(1, 9).range("lightgray", "red");

    eq = new pv.Panel().width(width).height(height+5);
    
    eq.add(pv.Rule)
        .data(magnitude.ticks())
        .bottom(magnitude)
        .strokeStyle("lightgray")
        .left(10)
        .width(width-10)
    .anchor("left").add(pv.Label)
        .visible(function(d) d &gt; 0);

    eq.add(pv.Bar)
        .data(earthquakes)
        .bottom(0)
        .width(barWidth-gap)
        .height(function(d) magnitude(d.Magnitude))
        .left(function() xScale(this.index))
        .fillStyle(function(d) colors(d.Magnitude))
    .anchor("top").add(pv.Label)
        .text(function(d) d.Magnitude)
        .visible(function(d) d.Magnitude &gt;= 4)
        .textBaseline("bottom");
    
        
    eq.render();
// --&gt;
&lt;/script&gt;

&lt;noscript&gt;
&lt;!--&lt;![endif]--&gt;
&lt;img src="http://eagereyes.org/media/2010/protovis-primer/eq6.png" width="560" height="250" alt="bars" /&gt;
&lt;!--[if !IE]&gt;&lt;!--&gt;
&lt;/noscript&gt;

&lt;!--&lt;![endif]--&gt;

&lt;p&gt;At this point, this is a redundant mapping, because the magnitude is already shown as the height of the bar. So let's look at another attribute, like &lt;em&gt;Depth&lt;/em&gt; – perhaps there is some correlation between the depth at which a quake happens and its magnitude. To do this, we'll define a new scale, similar to the one above.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;var depthColors = pv.Scale.linear(0, 150)
                    .range("lightgray", "blue");
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And then use that one instead in the definition of the bars.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;    .fillStyle(function(d) depthColors(d.Depth));
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Done.&lt;/p&gt;

&lt;!--[if !IE]&gt;&lt;!--&gt;

&lt;script type="text/javascript+protovis"&gt;&lt;!--

    var depthColors = pv.Scale.linear(0, 150).range("lightgray", "blue");

    eq = new pv.Panel().width(width).height(height+5);
    
    eq.add(pv.Rule)
        .data(magnitude.ticks())
        .bottom(magnitude)
        .strokeStyle("lightgray")
        .left(10)
        .width(width-10)
    .anchor("left").add(pv.Label)
        .visible(function(d) d &gt; 0);

    eq.add(pv.Bar)
        .data(earthquakes)
        .bottom(0)
        .width(barWidth-gap)
        .height(function(d) magnitude(d.Magnitude))
        .left(function() xScale(this.index))
        .fillStyle(function(d) depthColors(d.Depth))
    .anchor("top").add(pv.Label)
        .text(function(d) d.Magnitude)
        .visible(function(d) d.Magnitude &gt;= 4)
        .textBaseline("bottom");
    
    eq.render();
// --&gt;
&lt;/script&gt;

&lt;noscript&gt;
&lt;!--&lt;![endif]--&gt;
&lt;img src="http://eagereyes.org/media/2010/protovis-primer/eq7.png" width="560" height="250" alt="bars" /&gt;
&lt;!--[if !IE]&gt;&lt;!--&gt;
&lt;/noscript&gt;

&lt;!--&lt;![endif]--&gt;

&lt;h2&gt;Putting It All Together&lt;/h2&gt;

&lt;p&gt;Here is the entire code for this, including the data loading part. This is also available as a file from the &lt;a href="http://github.com/eagereyes/Protovis-Primer" target="_blank"&gt;github repository&lt;/a&gt; and in &lt;a href="http://eagereyes.org/media/2010/protovis-primer/protovis-primer-part2.zip"&gt;a ZIP file together with sample data&lt;/a&gt;.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;var dataURL = "http://data.eagereyes.org/pv/earthquakes.json";

var JSONdata = $.ajax({ type: "GET", url: dataURL, async: false }).responseText;
var earthquakes = JSON.parse(JSONdata);

var width = 560;
var height = 245;

var barWidth = (width-10)/earthquakes.length;
var gap = 2;

var xScale = pv.Scale.linear(0, earthquakes.length).range(10, width);
var magnitude = pv.Scale.linear(0, 9).range(0, height);

var depthColors = pv.Scale.linear(0, 150).range("lightgray", "blue");

var eq = new pv.Panel().width(width).height(height+5);

eq.add(pv.Rule)
    .data(magnitude.ticks())
    .bottom(magnitude)
    .strokeStyle("lightgray")
    .left(10)
    .width(width-10)
.anchor("left").add(pv.Label)
    .visible(function(d) d &amp;gt; 0);

eq.add(pv.Bar)
    .data(earthquakes)
    .bottom(0)
    .width(barWidth-gap)
    .height(function(d) magnitude(d.Magnitude))
    .left(function() xScale(this.index))
    .fillStyle(function(d) depthColors(d.Depth))
.anchor("top").add(pv.Label)
    .text(function(d) d.Magnitude)
    .visible(function(d) d.Magnitude &amp;gt;= 4)
    .textBaseline("bottom");

eq.render();
&lt;/code&gt;&lt;/pre&gt;

&lt;hr /&gt;

&lt;p&gt;Previous: &lt;a href="http://eagereyes.org/tutorials/protovis-primer-part-1"&gt;A Protovis Primer, Part 1&lt;/a&gt;
Next: &lt;a href="http://eagereyes.org/tutorials/protovis-primer-part-3"&gt;A Protovis Primer, Part 3&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/EagerEyes/~4/A9TwrbFhzdA" height="1" width="1"/&gt;</description>
 <comments>http://eagereyes.org/tutorials/protovis-primer-part-2#comments</comments>
 <category domain="http://eagereyes.org/tutorials">Tutorials</category>
 <pubDate>Thu, 10 Jun 2010 12:57:52 +0000</pubDate>
 <dc:creator>Robert Kosara</dc:creator>
 <guid isPermaLink="false">776 at http://eagereyes.org</guid>
<feedburner:origLink>http://eagereyes.org/tutorials/protovis-primer-part-2</feedburner:origLink></item>
<item>
 <title>Conference Acceptance Rates</title>
 <link>http://feedproxy.google.com/~r/EagerEyes/~3/AOXQsQPElG8/conference-acceptance-rates</link>
 <description>&lt;p&gt;&lt;a href="http://eagereyes.org/blog/2010/conference-acceptance-rates"&gt;&lt;img src="http://eagereyes.org/media/2010/acceptance-rates.png" width="560" height="260" alt="acceptance rates over time" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Acceptance rates are one of the key ways of measuring the quality of conferences. I think it's time we collect that data for conferences relevant to visualization. I have put together &lt;a href="http://eagereyes.org/service/acceptance-rates"&gt;a page for this&lt;/a&gt;, and have found some of that data. But I need your help to fill in the gaps and suggest other conferences that would be of interest.
&lt;!--break--&gt;
I originally only wanted to include the VisWeek conferences, but then I figured it would make sense to extend the list a little bit. So currently, there is InfoVis, Vis, VAST, EuroVis, AVI, UIST, and CHI. I am open to suggestions for further conferences to include, but I want to keep focusing on visualization. So even though CHI and UIST are included, I don't want to expand into HCI much more.&lt;/p&gt;

&lt;p&gt;There is also missing data. Especially for InfoVis, IEEE Xplore doesn't have the prefaces for conferences before 2005. Some of the early Vis conferences and a few others are also missing. Please take a look at &lt;a href="http://eagereyes.org/service/acceptance-rates"&gt;the list&lt;/a&gt; and if you have access to proceedings for years where numbers are missing, &lt;a href="http://eagereyes.org/contact"&gt;send them to me&lt;/a&gt; or leave a comment below.&lt;/p&gt;

&lt;script src="http://eagereyes.org/media/protovis-r3.2.js" type="text/javascript"&gt;
&lt;/script&gt;

&lt;p&gt;Given that this is about visualization conferences, I figured it would make sense to also visualize the data. The first visualization shows acceptance rates from 1990 to 2010. Not all of the missing lines are actually missing data, because the different conferences started at different times.&lt;/p&gt;

&lt;!--[if gt IE 8]&gt;&lt;!--&gt;

&lt;script type="text/javascript+protovis"&gt;&lt;!--

    var dataURL = "http://eagereyes.org/media/conferences.json";

    var contentWidth = 460;
    var contentHeight = 235;

    var legendWidth = 60;
    var leftWidth = 30;

    var bottomHeight = 25;
    var topPadding = 10;

    var width = leftWidth+contentWidth+legendWidth;
    var height = contentHeight+bottomHeight+topPadding;
    var lastYear = 2010;
    var firstYear = lastYear-20;

    // load the data in a synchronous fashion. ASync would be neat but it's really not necessary
    // and just makes things more complicated.
    var JSONdata = $.ajax({ type: "GET", url: dataURL, async: false }).responseText;
    var conferences = JSON.parse(JSONdata);

    var numConferences = 0;

    // for each conference, remove all years before firstYear, adjust their firstYear value if
    // necessary, and calculate acceptance rates from accepted and submitted.
    for (var c in conferences) {
        if (conferences.hasOwnProperty(c)) {
            var conf = conferences[c];
            // cut off years that are too far back
            if (conf.firstYear &lt; firstYear) {
                conf.accepted = conf.accepted.slice(firstYear-conf.firstYear, conf.accepted.length);
                conf.submitted = conf.submitted.slice(firstYear-conf.firstYear, conf.submitted.length);
                conf.firstYear = firstYear;
            }

            // calculate acceptance rates
            var rates = [];
            for (var i = 0; i &lt; conf.submitted.length; i++) {
                if (conf.submitted[i] &gt; 0) {
                    rates[i] = conf.accepted[i]/conf.submitted[i];
                } else {
                    rates[i] = 0;
                }
            }
            conf.rates = rates;

            conf.colorIndex = numConferences;

            numConferences += 1;
        }
    }

    // yearsAligned and percentAligned align the coordinate to the full pixel+.5,
    // to avoid double-wide lines due to anti-aliasing
    var years = pv.Scale.linear(firstYear, lastYear).range(leftWidth, leftWidth+contentWidth);
    var yearsAligned = function(d) {
        return Math.round(years(d))+.5;
    }

    var percent = pv.Scale.linear(0, .5).range(bottomHeight, bottomHeight+contentHeight);
    var percentAligned = function(d) {
        return Math.round(percent(d))+.5;
    }

    var color = pv.Colors.category10(numConferences);

    // the year being pointed to by the mouse
    var activeYear = -1;

    // add a line for a conference to the chart. Also adds the indicator dots and value labels,
    // but they're invisible until triggered by the "point" pseudo-event
    var addLine = function(panel, conference, data, scale, percent) {
        panel.add(pv.Line)
            .data(data)
            .bottom(scale)
            .strokeStyle(color(conference.colorIndex))
            .lineWidth(2)
            .left(function() yearsAligned(conference.firstYear+this.index))
            .segmented(true)
            .visible(function(d) d &gt; 0)
            .event("point", function() { activeYear = conference.firstYear+this.index; return this.parent; })
            .event("unpoint", function() { activeYear = -1; return this.parent; })
        .anchor().add(pv.Dot)
            .size(2)
            .fillStyle(color(conference.colorIndex))
            .strokeStyle(color(conference.colorIndex))
            .visible(function() activeYear == conference.firstYear+this.index)
        .anchor("top").add(pv.Label)
            .textBaseline("bottom")
            .textAlign("center")
            .text(function(d) (percent)?Math.round(d * 100)+"%":d);
    }

    // ugly global variable to make it easier to order conferences in the legend in a particular
    // way by calling the function below in the right sequence.
    var legendIndex = 0;

    // add a conference to the legend. Order is from top down.
    var addLegend = function(panel, conference) {
        panel.add(pv.Rule)
            .strokeStyle(color(conference.colorIndex))
            .bottom(contentHeight-legendIndex*15)
            .left(contentWidth+leftWidth+5)
            .width(15)
            .lineWidth(2)
        .anchor("right").add(pv.Label)
            .text(conference.shortName)
            .textBaseline("middle");

        legendIndex += 1;
    }

    var makeLegend = function(panel) {
        addLegend(panel, conferences.infovis);
        addLegend(panel, conferences.vis);
        addLegend(panel, conferences.eurovis);
        addLegend(panel, conferences.vast);
        addLegend(panel, conferences.uist);
        addLegend(panel, conferences.chi);
    }

    var acceptanceRates = new pv.Panel()
        .width(width).height(height)
        .fillStyle("#fff")
        .event("mousemove", pv.Behavior.point(Infinity).collapse("y"));

    // year indicator for mouse-over
    acceptanceRates.add(pv.Rule)
        .left(function() yearsAligned(activeYear))
        .visible(function() activeYear != -1)
        .strokeStyle("#ddd")
        .height(contentHeight)
        .bottom(bottomHeight);

    // horizontal background grid with % labels
    acceptanceRates.add(pv.Rule)
        .data(percent.ticks(10))
        .strokeStyle("#eee")
        .bottom(percentAligned)
        .width(contentWidth+3)
        .left(leftWidth-3)
    .anchor("left").add(pv.Label)
        .textBaseline("middle")
        .visible(function() this.index &gt; 0 &amp;&amp; this.index % 2 == 0)
        .text(function(d) Math.round(d*100) + "%");

    // years and year ticks
    acceptanceRates.add(pv.Rule)
        .data(pv.range(firstYear, lastYear+1))
        .left(yearsAligned)
        .bottom(bottomHeight-4)
        .height(4)
    .anchor("bottom").add(pv.Label)
        .textAlign("center")
        .textBaseline("top")
        .visible(function(d) (d % 5) == 0);

    // lines in the chart
    addLine(acceptanceRates, conferences.vis, conferences.vis.rates, percentAligned, true);
    addLine(acceptanceRates, conferences.chi, conferences.chi.rates, percentAligned, true);
    addLine(acceptanceRates, conferences.uist, conferences.uist.rates, percentAligned, true);
    addLine(acceptanceRates, conferences.vast, conferences.vast.rates, percentAligned, true);
    addLine(acceptanceRates, conferences.eurovis, conferences.eurovis.rates, percentAligned, true);
    addLine(acceptanceRates, conferences.infovis, conferences.infovis.rates, percentAligned, true); 

    makeLegend(acceptanceRates);

    // done.
    acceptanceRates.render();
// --&gt;
&lt;/script&gt;

&lt;noscript&gt;
&lt;!--&lt;![endif]--&gt;
&lt;img src="http://eagereyes.org/media/2010/acceptance-rates.png" width="560" height="260" alt="acceptance rates over time" /&gt;
&lt;!--[if gt IE 8]&gt;&lt;!--&gt;
&lt;/noscript&gt;

&lt;!--&lt;![endif]--&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;The following shows numbers of submissions. CHI's increase in submissions is impressive; even more impressive is that they kept their acceptance rate nearly constant through this enormous growth.&lt;/p&gt;

&lt;!--[if gt IE 8]&gt;&lt;!--&gt;

&lt;script type="text/javascript+protovis"&gt;&lt;!--
    var submissions = new pv.Panel()
        .width(width).height(height)
        .fillStyle("#fff")
        .event("mousemove", pv.Behavior.point(Infinity).collapse("y"));

    var submissionsScale = pv.Scale.linear(0, 1400).range(bottomHeight, bottomHeight+contentHeight);
    var submissionsScaleAligned = function(d) {
        return Math.round(submissionsScale(d))+.5;
    }

    // year indicator for mouse-over
    submissions.add(pv.Rule)
        .left(function() yearsAligned(activeYear))
        .visible(function() activeYear != -1)
        .strokeStyle("#ddd")
        .height(contentHeight)
        .bottom(bottomHeight);

    // horizontal background grid with labels
    submissions.add(pv.Rule)
        .data(submissionsScale.ticks(6))
        .strokeStyle("#eee")
        .bottom(submissionsScaleAligned)
        .width(contentWidth+3)
        .left(leftWidth-3)
    .anchor("left").add(pv.Label)
        .textBaseline("middle")
        .visible(function(d) d &gt; 0);

    // years and year ticks
    submissions.add(pv.Rule)
        .data(pv.range(firstYear, lastYear+1))
        .left(yearsAligned)
        .bottom(bottomHeight-4)
        .height(4)
    .anchor("bottom").add(pv.Label)
        .textAlign("center")
        .textBaseline("top")
        .visible(function(d) (d % 5) == 0);

    // lines in the chart
    addLine(submissions, conferences.vis, conferences.vis.submitted, submissionsScaleAligned, false);
    addLine(submissions, conferences.chi, conferences.chi.submitted, submissionsScaleAligned, false);
    addLine(submissions, conferences.uist, conferences.uist.submitted, submissionsScaleAligned, false);
    addLine(submissions, conferences.vast, conferences.vast.submitted, submissionsScaleAligned, false);
    addLine(submissions, conferences.eurovis, conferences.eurovis.submitted, submissionsScaleAligned, false);
    addLine(submissions, conferences.infovis, conferences.infovis.submitted, submissionsScaleAligned, false);   

    makeLegend(submissions);

    // done.
    submissions.render();
// --&gt;
&lt;/script&gt;

&lt;noscript&gt;
&lt;!--&lt;![endif]--&gt;
&lt;img src="http://eagereyes.org/media/2010/submissions.png" width="560" height="260" alt="numbers of submissions over time" /&gt;
&lt;!--[if gt IE 8]&gt;&lt;!--&gt;
&lt;/noscript&gt;

&lt;!--&lt;![endif]--&gt;

&lt;p&gt;The next task is to look into impact factors for visualization journals. That information seems to be much harder to come by though, unfortunately. But it would be very worthwhile.&lt;/p&gt;

&lt;p&gt;Follow &lt;a href="http://eagereyes.org/service/acceptance-rates"&gt;this link to the acceptance rates data&lt;/a&gt;. The data is also available in CSV and JSON formats in &lt;a href="http://github.com/eagereyes/VisLitDB" target="_blank"&gt;a repository on github&lt;/a&gt;.&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/EagerEyes/~4/AOXQsQPElG8" height="1" width="1"/&gt;</description>
 <comments>http://eagereyes.org/blog/2010/conference-acceptance-rates#comments</comments>
 <category domain="http://eagereyes.org/blog">blog</category>
 <category domain="http://eagereyes.org/topics/Meta">Meta/Site News</category>
 <pubDate>Sun, 06 Jun 2010 21:20:04 +0000</pubDate>
 <dc:creator>Robert Kosara</dc:creator>
 <guid isPermaLink="false">775 at http://eagereyes.org</guid>
<feedburner:origLink>http://eagereyes.org/blog/2010/conference-acceptance-rates</feedburner:origLink></item>
<item>
 <title>A Protovis Primer, Part 1</title>
 <link>http://feedproxy.google.com/~r/EagerEyes/~3/SG8u5tLhAP4/protovis-primer-part-1</link>
 <description>&lt;script src="http://eagereyes.org/media/2009/protovis-r3.1.js" type="text/javascript"&gt;
&lt;/script&gt;

&lt;p&gt;&lt;a href="http://vis.stanford.edu/protovis/docs/" target="_blank"&gt;Protovis&lt;/a&gt; is a very powerful visualization toolkit. Part of what makes it special is that it is written in JavaScript and runs in the browser without the need for any plugins. Its clever use of JavaScript's language features makes it very elegant, but it can also be confusing to people who are not familiar with functional programming concepts and the finer points of JavaScript. This multi-part tutorial shows how to create a visualization (my &lt;a href="http://eagereyes.org/applications/PresidentialDemographicsII.html"&gt;interactive Presidents Chart&lt;/a&gt;) in Protovis, and explains the concepts that are involved along the way.
&lt;!--break--&gt;
This introduction is based on my experiences with using Protovis in my &lt;em&gt;Visualization and Visual Communication&lt;/em&gt; class earlier this spring. While the concepts involved are really not that difficult, they are rather foreign to students who have not been exposed to functional programming. And since that is also the case for a lot of hobbyists and people wanting to do visualization who do not have a computer science background, I imagine they run into the same problems.&lt;/p&gt;

&lt;p&gt;This has grown from being a single article into several parts (and is still expanding). Let me know if there are things that you don't understand or that you think need to be covered in more detail, so I can tailor the next parts accordingly.&lt;/p&gt;

&lt;p&gt;Protovis requires a modern browser, which means any recent version of Safari, Chrome, FireFox, or Opera. Internet Explorer does not work, because it does not support the HTML5 Canvas element. The visualizations in this article are all Protovis drawings (check out the source code!), with a fall-back to images for RSS readers and IE users. There is no real difference at this point, but once we get to interaction, you will want to read this in a supported browser.&lt;/p&gt;

&lt;h2&gt;A Simple Example&lt;/h2&gt;

&lt;p&gt;Let's start with a simple example, taken almost verbatim from the &lt;a href="http://vis.stanford.edu/protovis/protovis.pdf" target="_blank"&gt;Protovis paper presented at InfoVis 2009 (PDF)&lt;/a&gt;.&lt;/p&gt;

&lt;!--[if !IE]&gt;&lt;!--&gt;

&lt;script type="text/javascript+protovis"&gt;&lt;!--
    new pv.Panel().width(150).height(140)
        .add(pv.Bar)
            .data([1, 1.2, 1.7, 1.5, .7, .2])
            .bottom(0).width(20)
            .height(function(d) d * 80)
            .left(function() this.index * 25)
        .root.render();
// --&gt;
&lt;/script&gt;

&lt;noscript&gt;
&lt;!--&lt;![endif]--&gt;
&lt;img src="http://eagereyes.org/media/2010/protovis-primer/bars.png" width="150" height="140" alt="bars" /&gt;
&lt;!--[if !IE]&gt;&lt;!--&gt;
&lt;/noscript&gt;

&lt;!--&lt;![endif]--&gt;

&lt;p&gt;The following code creates this chart:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;var vis = new pv.Panel().width(150).height(140)
    .add(pv.Bar)
        .data([1, 1.2, 1.7, 1.5, .7, .2])
        .bottom(0).width(20)
        .height(function(d) d * 80)
        .left(function() this.index * 25)
    .root.render();
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Let's look at this line by line. The first line defines a new &lt;em&gt;Panel&lt;/em&gt;, the base element for any Protovis chart. The panel is the canvas on which everything is drawn (in fact, it becomes an actual &lt;code&gt;canvas&lt;/code&gt; element in the webpage). This line also defines its size by calling two functions that set its width and height.&lt;/p&gt;

&lt;h2&gt;Function Chaining&lt;/h2&gt;

&lt;p&gt;This brings us to the first bit of magic: Almost all functions in Protovis return the object they are called on. That makes it possible to chain function calls instead of having to repeat the variable name over and over. While this leads to more elegant, simpler code, it can make it a bit terse and more challenging to read. But consider the alternative:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;var vis = new pv.Panel();
vis.width(150);
vis.height(140);
var bar = vis.add(pv.Bar)
etc ...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Function chaining lets you treat functions like statements: I want a panel, and I want its width to be 150 and its height to be 140; this translates very directly into function calls. While this is not all there is to what Bostock and Heer call a "declarative style of programming," it certainly is a big part of it.&lt;/p&gt;

&lt;h2&gt;Marks&lt;/h2&gt;

&lt;p&gt;But back to our example. The second line adds something to the panel by calling the &lt;code&gt;add()&lt;/code&gt; function. What is added is called a &lt;em&gt;mark&lt;/em&gt;, a graphical element that can represent data. Let's briefly skip over the details of that and look at the last line: here, we need to call the render function of the panel to tell it to create the necessary infrastructure and start rendering. Since at this point, the functions being called are called on the bar mark that was added in the second line, we need a way to go back to the panel. This could be done by assigning the panel to a variable &lt;code&gt;vis&lt;/code&gt; and calling &lt;code&gt;vis.render()&lt;/code&gt;. The way it is done here is using the special variable &lt;code&gt;root&lt;/code&gt;, which exists in all marks. It returns the panel that the mark sits on, so we can add other marks to it or call its functions.&lt;/p&gt;

&lt;p&gt;The following code snippet is identical to the above. I just showed the other one first because it is the style that the Protovis examples are written in, so it makes sense to get familiar with it.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;var vis = new pv.Panel().width(150).height(140);
vis.add(pv.Bar)
    .data([1, 1.2, 1.7, 1.5, .7, .2])
    .bottom(0).width(20)
    .height(function(d) d * 80)
    .left(function() this.index * 25);
vis.render();
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Using variables is a good idea in general, especially when creating more complex visualizations. But the clever design of Protovis' functions makes it possible to all but eliminate them in many cases (at least as far as Protovis objects are concerned).&lt;/p&gt;

&lt;h2&gt;Mark Properties&lt;/h2&gt;

&lt;p&gt;Now let's look at the definition of the bars, which is the same between both alternatives. The first statement defines an array of numbers, which are the data to be shown on the chart. We will later see data in variables and arrays of objects, not just numbers. But this kind of inline definition of the data is also possible. The square brackets delimit an array in JavaScript (and lots of other programming languages), numbers are listed with commas in between.&lt;/p&gt;

&lt;p&gt;The line after the data statement specifies two important but constant parts of the layout. Since we're using bars, we have to specify at least their position (x and y), their width, and height. For a bar chart, it makes sense to have all the bars have the same width, so it is set to a number here. And since we want all bars to sit on the same baseline, we specify their y position as 0. The terminology here, &lt;em&gt;bottom&lt;/em&gt;, has to do with the way locations and sizes can be specified in Protovis. I'll cover more of that later.&lt;/p&gt;

&lt;h2&gt;More Fun With Functions&lt;/h2&gt;

&lt;p&gt;The next two lines is where it gets really interesting, and where a lot of confusion comes from. Remember that we are creating a bar chart here, but there is no loop to draw each of the bars. We specified the entire data array two lines earlier. Now how do we get Protovis to draw something?&lt;/p&gt;

&lt;p&gt;The way Protovis works is that a definition like this tells it to iterate over the array of data it is given, instantiate a mark for each entry in the array, and evaluate all the information it is given about what to do with the mark. In the case of the &lt;em&gt;width&lt;/em&gt; and &lt;em&gt;bottom&lt;/em&gt; attributes, this is simple: we specified constants, so it just uses these same numbers for all of the bars it creates.&lt;/p&gt;

&lt;p&gt;But we want the bars to represent the data by varying their height, how do we do that? The answer here is a function. Instead of specifying a number or variable, we assign a function to the height property. For every entry in the array, Protovis will call this function with the value, and use the result of the function as the height of the bar. The beauty of this approach is that JavaScript, unlike Java and most of the more common imperative languages, can use a function like a variable: you can pass it to another function, store it in a variable, and evaluate it by calling it.&lt;/p&gt;

&lt;p&gt;As with the function chaining before, the particular style in which this is done in Protovis makes this a bit more difficult to understand. Here is the definition of the height again:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;.height(function(d) d * 80)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And here is the same definition in a more verbose style that is actually correct JavaScript (Protovis does some magic with function parsing that allows the sloppy but more compact style above):&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;.height(function(d) {
    return d * 80;
})
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Both statements do the exact same thing: they specify a function without a name (called an &lt;em&gt;anonymous function&lt;/em&gt;) that takes one argument called &lt;code&gt;d&lt;/code&gt;. The function returns the result of a simple computation, multiplying the value of that argument by 80. The unit here is pixels, which means that our highest bar will be 1.7 * 80 pixels high (1.7 is the maximum value in the data array).&lt;/p&gt;

&lt;p&gt;Functions used in property definitions can do a lot more complex things. You can also assign functions to variables and reuse them in different definitions. But many functions used in visualization definitions in Protovis perform fairly straight-forward calculations or access data in more complex data structures (we'll get to that, too).&lt;/p&gt;

&lt;p&gt;The last line in the bar definition works the same way as the height definition, with a small difference. We want our bars to be next to each other, not drawn on top of each other. Even though the mark is called &lt;em&gt;bar&lt;/em&gt;, it is really more a general-purpose rectangle: it doesn't know anything about being a bar in a bar chart. That makes it very flexible and powerful, but it also means that you need to do a lot of things by hand that might seem obvious.&lt;/p&gt;

&lt;h2&gt;Bar Layout&lt;/h2&gt;

&lt;p&gt;To move the bars, we specify their &lt;em&gt;left&lt;/em&gt; property, which together with the &lt;em&gt;bottom&lt;/em&gt; means we're specifying the lower left corner. Since this value will have to be different between the bars, we need to again specify a function. We are specifying everything at once, so there is no obvious way to order the bars and use that order to space them. Protovis therefore has a special variable that is available when it evaluates functions, which is called &lt;code&gt;this.index&lt;/code&gt;. That variable's value is the index of the current value in the array, starting at 0 and going all the way to the size of the array minus 1.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;.left(function() this.index * 25);
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Remember that we set the width of the bars to 20. Putting the bars at multiples of 25 means that we're leaving five pixels of space between them. You can easily tweak any of these numbers to change spacing, scaling, etc. Just remember that when you increase the scale factor, you will have to also change the size of the panel, or your bars will be cut off!&lt;/p&gt;

&lt;h2&gt;Adding A Variable&lt;/h2&gt;

&lt;p&gt;The last step in this first part of this tutorial is simply to move the data into a variable. This is a simple but important step. To do this, we will define new variable &lt;code&gt;numbers&lt;/code&gt; that contains the exact same numbers as above. The only other thing that changes is the &lt;code&gt;data()&lt;/code&gt; function call that now uses the variable.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;var numbers = [1, 1.2, 1.7, 1.5, .7, .2];

new pv.Panel().width(150).height(140)
    .add(pv.Bar)
        .data(numbers)
        .bottom(0).width(20)
        .height(function(d) d * 80)
        .left(function() this.index * 25)
    .root.render();
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Running this does not actually make any difference, which is what we intended. But it opens the doors to some more flexibility. What if we wanted the bars to change size depending on how much data there is? Let's define another variable for the panel, &lt;code&gt;panelWidth&lt;/code&gt;, and calculate the width of the bars, &lt;code&gt;barWidth&lt;/code&gt;, from the size of the array.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;var panelWidth = 150;

var barWidth = panelWidth/numbers.length;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The barWidth is the total space a bar takes up, including its spacing to the next bar. If we simply use this for our definition:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;new pv.Panel().width(panelWidth).height(140)
    .add(pv.Bar)
        .data(numbers)
        .bottom(0).width(barWidth)
        .height(function(d) d * 80)
        .left(function() this.index * barWidth)
    .root.render();
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;We end up with something slightly unexpected:&lt;/p&gt;

&lt;!--[if !IE]&gt;&lt;!--&gt;

&lt;script type="text/javascript+protovis"&gt;&lt;!--
    var numbers = [1, 1.2, 1.7, 1.5, .7, .2];

    var panelWidth = 150;

    var barWidth = panelWidth/numbers.length;

    new pv.Panel().width(panelWidth).height(140)
        .add(pv.Bar)
            .data(numbers)
            .bottom(0).width(barWidth)
            .height(function(d) d * 80)
            .left(function() this.index * barWidth)
        .root.render();
// --&gt;
&lt;/script&gt;

&lt;noscript&gt;
&lt;!--&lt;![endif]--&gt;
&lt;img src="http://eagereyes.org/media/2010/protovis-primer/bars-toowide.png" width="150" height="140" alt="bars are too wide now" /&gt;
&lt;!--[if !IE]&gt;&lt;!--&gt;
&lt;/noscript&gt;

&lt;!--&lt;![endif]--&gt;

&lt;p&gt;We need to add some space between the bars again. Remember that we set the width to 20 earlier, but multiplied the index by 25. We need something similar here, so we'll introduce another variable, &lt;code&gt;barSpacing&lt;/code&gt;. This will be subtracted from the &lt;code&gt;barWidth&lt;/code&gt; in the width definition, so the bar does not use the entire width it got assigned.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;var barSpacing = 5;

new pv.Panel().width(panelWidth).height(140)
    .add(pv.Bar)
        .data(numbers)
        .bottom(0).width(barWidth-barSpacing)
        .height(function(d) d * 80)
        .left(function() this.index * barWidth)
    .root.render();
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The result is a readable chart that looks like before:&lt;/p&gt;

&lt;!--[if !IE]&gt;&lt;!--&gt;

&lt;script type="text/javascript+protovis"&gt;&lt;!--
    var barSpacing = 5;

    new pv.Panel().width(panelWidth).height(140)
        .add(pv.Bar)
            .data(numbers)
            .bottom(0).width(barWidth-barSpacing)
            .height(function(d) d * 80)
            .left(function() this.index * barWidth)
        .root.render();
// --&gt;
&lt;/script&gt;

&lt;noscript&gt;
&lt;!--&lt;![endif]--&gt;
&lt;img src="http://eagereyes.org/media/2010/protovis-primer/bars.png" width="150" height="140" alt="bars again" /&gt;
&lt;!--[if !IE]&gt;&lt;!--&gt;
&lt;/noscript&gt;

&lt;!--&lt;![endif]--&gt;

&lt;p&gt;This may not look very exciting, but it gives us a lot of flexibility. Now we can change the width of the chart by simply changing the &lt;code&gt;panelWidth&lt;/code&gt;, let's say to 250:&lt;/p&gt;

&lt;!--[if !IE]&gt;&lt;!--&gt;

&lt;script type="text/javascript+protovis"&gt;&lt;!--
    panelWidth = 250;

    barWidth = panelWidth/numbers.length;

    new pv.Panel().width(panelWidth).height(140)
        .add(pv.Bar)
            .data(numbers)
            .bottom(0).width(barWidth-barSpacing)
            .height(function(d) d * 80)
            .left(function() this.index * barWidth)
        .root.render();
// --&gt;
&lt;/script&gt;

&lt;noscript&gt;
&lt;!--&lt;![endif]--&gt;
&lt;img src="http://eagereyes.org/media/2010/protovis-primer/bars-wide.png" width="250" height="140" alt="wider bars" /&gt;
&lt;!--[if !IE]&gt;&lt;!--&gt;
&lt;/noscript&gt;

&lt;!--&lt;![endif]--&gt;

&lt;p&gt;As you can see, the bars have adapted to the new width. The spacing looks a bit odd because &lt;code&gt;barSpacing&lt;/code&gt; is still a constant, but that would be easy to change, too. But what happens if we add more data to our &lt;code&gt;numbers&lt;/code&gt; array?&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;var numbers = [1, 1.2, 1.7, 1.5, .7, .2, .5, .9];
&lt;/code&gt;&lt;/pre&gt;

&lt;!--[if !IE]&gt;&lt;!--&gt;

&lt;script type="text/javascript+protovis"&gt;&lt;!--
    numbers = [1, 1.2, 1.7, 1.5, .7, .2, .5, .9];

    barWidth = panelWidth/numbers.length;

    new pv.Panel().width(panelWidth).height(140)
        .add(pv.Bar)
            .data(numbers)
            .bottom(0).width(barWidth-barSpacing)
            .height(function(d) d * 80)
            .left(function() this.index * barWidth)
        .root.render();
// --&gt;
&lt;/script&gt;

&lt;noscript&gt;
&lt;!--&lt;![endif]--&gt;
&lt;img src="http://eagereyes.org/media/2010/protovis-primer/more-bars.png" width="250" height="140" alt="more bars" /&gt;
&lt;!--[if !IE]&gt;&lt;!--&gt;
&lt;/noscript&gt;

&lt;!--&lt;![endif]--&gt;

&lt;p&gt;The bars adapt to the number of values, because the barWidth depends on the number of values in the &lt;code&gt;numbers&lt;/code&gt; array, &lt;code&gt;numbers.length&lt;/code&gt;. This only works to a certain point, because our spacing is still constant; but it's a start.&lt;/p&gt;

&lt;p&gt;This concludes the first part of this tutorial. The next parts will cover more complex layouts, Protovis scales, other mark types, reading of complex data, labels, etc.&lt;/p&gt;

&lt;h2&gt;Start Playing!&lt;/h2&gt;

&lt;p&gt;If you want to play with Protovis, I am providing &lt;a href="http://eagereyes.org/media/2010/protovis-primer/protovis-primer-part1.zip"&gt;a ZIP file with two simple HTML files&lt;/a&gt;: one is the basic bar chart, the other one uses variables for the data and the widths.&lt;/p&gt;

&lt;p&gt;I have also started &lt;a href="http://github.com/eagereyes/Protovis-Primer" target="_blank"&gt;a repository on github&lt;/a&gt; for people familiar with git.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;Next:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="http://eagereyes.org/tutorials/protovis-primer-part-2"&gt;A Protovis Primer, Part 2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://eagereyes.org/tutorials/protovis-primer-part-3"&gt;A Protovis Primer, Part 3&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;img src="http://feeds.feedburner.com/~r/EagerEyes/~4/SG8u5tLhAP4" height="1" width="1"/&gt;</description>
 <comments>http://eagereyes.org/tutorials/protovis-primer-part-1#comments</comments>
 <category domain="http://eagereyes.org/tutorials">Tutorials</category>
 <pubDate>Thu, 03 Jun 2010 04:43:08 +0000</pubDate>
 <dc:creator>Robert Kosara</dc:creator>
 <guid isPermaLink="false">773 at http://eagereyes.org</guid>
<feedburner:origLink>http://eagereyes.org/tutorials/protovis-primer-part-1</feedburner:origLink></item>
</channel>
</rss>
