protovis

Protovis
Stable release 3.3.1 / September 17, 2010; 16 months ago (2010-09-17)
Development status Protovis is no longer under active development.
Written in JavaScript
License BSD
Website http://protovis.org/

Protovis is a visualization toolkit for JavaScript that takes a graphical approach to data visualization by allowing the user to specify how the data should be encoded in the marks representing it on the screen. The project is led by Mike Bostock and Jeff Heer of the Stanford Visualization Group, with help from Vadim Ogievetsky. Protovis uses SVG image for rendering, allowing visualizations to be seamlessly inserted into pages.

Contents

Use

Protovis is a single JavaScript file, containing all its declarations and functions. It can be included within a web page using the following mark-up:

<script type="text/javascript" src="protovis-r3.2.js"></script>

The Protovis code to build the visualization is added to the body of the page using a script tag.

<script type="text/javascript+protovis">
  // Protovis code goes here...
</script>

Due to its declarative style, Protovis code typically makes very extensive use of small anonymous functions; to allow for more concise code and increase readability Protovis provides the javascript+protovis script tag type which adds the shorthand function declaration:

function(x) 5*x

which is equivalent to writing:

function(x) { return 5*x; }

Network diagrams

The following example demonstrates the ability to create three different network visualizations using the same dataset. The visualizations show content co-creation trends in a fictitious organization.

Force-directed layout

var w = document.body.clientWidth,
    h = document.body.clientHeight,
    colors = pv.Colors.category20();
 
var vis = new pv.Panel()
    .width(w)
    .height(h)
    .fillStyle("white")
    .event("mousedown", pv.Behavior.pan())
    .event("mousewheel", pv.Behavior.zoom(3));
 
var force = vis.add(pv.Layout.Force)
    .nodes(departments.nodes)
    .links(departments.links);
 
force.link.add(pv.Line);
 
force.node.add(pv.Dot)
    .size(function(d) (d.linkDegree + 104) * Math.pow(this.scale, -1.5))
    .fillStyle(function(d) d.fix ? "red" : colors(d.group))
    .strokeStyle(function() this.fillStyle().darker())
    .lineWidth(0)
    .title(function(d) d.nodeName)
    .event("mousedown", pv.Behavior.drag())
    .event("drag", force);
 
force.label.add(pv.Label)
 
vis.render();

Arc Diagram

var vis = new pv.Panel()
    .width(880)
    .height(410)
    .bottom(90);
 
var colors = pv.Colors.category20();
 
var arc = vis.add(pv.Layout.Arc)
    .nodes(departments.nodes)
    .links(departments.links)
    .sort(function(a, b) a.group == b.group
        ? b.linkDegree - a.linkDegree
        : b.group - a.group);
 
arc.link.add(pv.Line);
 
 
arc.node.add(pv.Dot)
    .size(function(d) (d.linkDegree + 104) * Math.pow(this.scale, -1.5))
    .fillStyle(function(d) d.fix ? "brown" : colors(d.group))
    .strokeStyle(function() this.fillStyle().darker());
 
arc.label.add(pv.Label)
 
vis.render();

Matrix Diagram

var color = pv.Colors.category20().by(function(d) d.group);
 
var vis = new pv.Panel()
    .width(400)
    .height(400)
    .top(90)
    .left(90);
 
var layout = vis.add(pv.Layout.Matrix)
    .nodes(departments.nodes)
    .links(departments.links)
    .sort(function(a, b) b.group - a.group);
 
 
layout.link.add(pv.Bar)
    .fillStyle(function(l) l.linkValue
        ? ((l.targetNode.group == l.sourceNode.group)
        ? color(l.sourceNode) 
             : "rgb("+ (80-(l.linkValue)) + "%, " + (80-(l.linkValue)) + "%, "+ (80-(l.linkValue)) +"%)") 
                 : "#EEEEEE")
    .antialias(false)
    .lineWidth(1)
    .anchor("center").add(pv.Label).text(function(l) l.linkValue)
          .textStyle("#FFFFFF")
          .font(function(l) l.linkValue + "px sans-serif")
 
layout.label.add(pv.Label)
    .textStyle(color);
 
vis.render();

Dataset

var departments = {
  nodes:[
    {nodeName:"Marketing", group:0},
    {nodeName:"IT", group:1},
    {nodeName:"Engineering", group:2},
    {nodeName:"Quality", group:3},
    {nodeName:"BI", group:4},
    {nodeName:"RND", group:5},
    {nodeName:"Administrative", group:6},
    {nodeName:"Sales", group:7},
    {nodeName:"Legal", group:8},
 
  ],
  links:[
    {source:1, target:0, value:4},
    {source:1, target:2, value:3},
    {source:1, target:3, value:5},
    {source:1, target:4, value:15},
    {source:1, target:6, value:2},
    {source:1, target:7, value:13},
    {source:6, target:0, value:1},
    {source:6, target:2, value:1},
    {source:6, target:3, value:1},
    {source:6, target:4, value:1},
    {source:6, target:7, value:1},
    {source:0, target:7, value:17},
    {source:2, target:3, value:15},
    {source:2, target:4, value:9},
    {source:2, target:5, value:1},
    {source:3, target:5, value:1},
    {source:8, target:0, value:9},
    {source:8, target:1, value:2},
    {source:8, target:7, value:1},
 
  ]
};

Bar chart

Here is an example of code needed to draw a bar chart in Protovis:

// Create the root panel and set the visualization's size to 150x150
var vis = new pv.Panel()
    .width(150)
    .height(150);
 
// Add the horizontal rules (grid lines), we add them first so they go in the back.
vis.add(pv.Rule)
    .data(pv.range(0, 2, .5))
    .bottom(function(d) d * 80 + 1)
  .add(pv.Label);
 
// Add the bars with the height corresponding to the values in the data property
vis.add(pv.Bar)
    .data([1, 1.2, 1.7, 1.5, .7])
    .width(20)
    .height(function(d) 80 * d)
    .bottom(0)
    .left(function() this.index * 25 + 25) // this.index is the position of the datum in the array
  .anchor("bottom").add(pv.Label); // Add a label to the bottom of each bar
 
// Render everything.
vis.render();

Protovis makes extensive use of property chaining allowing the example above to be written in four statements.

External links