Skip to content

Instantly share code, notes, and snippets.

@alansmithy
Last active August 29, 2015 14:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save alansmithy/b28db498cf830f61e8eb to your computer and use it in GitHub Desktop.
Save alansmithy/b28db498cf830f61e8eb to your computer and use it in GitHub Desktop.
Using Pym.js to create a dynamic, responsive graphic

Pym.js

Pym.js by NPR is a really useful tool for deploying responsive SVG graphics.

Pym.js works by creating dynamic iframes on a (parent) page which can transmit window-size changes to an embedded (child) page. The child can also send changes back to the parent if interaction causes the embedded item to change in height. Crucially, the child page can be set to redraw its content when a window is resized, allowing different content to be generated depending on the size of the iframe.

This playful (and artistically inept) example illustrates the concept by using d3 to draw different images (a 'landscape' landscape and a 'portrait' portrait) depending on the window width. To see it properly using bl.ocks.org, you'll need to launch into the new window view - then just resize the window by dragging the window in and out (the switch happens at 600px).

You can read more about using pym.js on the NPR Blog

You can see data visualisation examples using this approach on Visual.ONS

<!DOCTYPE html>
<html>
<head>
<title>Using Pym.js for responsive d3</title>
</head>
<body>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/pym/0.4.1/pym.min.js"></script>
<div id="embed-1"></div>
<script type="text/javascript">
new pym.Parent("embed-1", "svgimage.html");
</script>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<title>embedded page</title>
</head>
<body>
<style>
svg{width:100%;}
text{fill:#111; font-family: sans-serif; font-size: 1.2em;}
circle.eyes {stroke:blue;stroke-width:8px;fill:lightblue;}
.landscape{fill:#7ec0ee;}
.portrait{fill:#dedede;}
</style>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/pym/0.4.1/pym.min.js"></script>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script type="text/javascript">
new pym.Child();
pym.Child({ renderCallback: createChart });
var svgDoc;
function createChart(container_width) {
var mobSwitch=600;
//if svg already exists, clear out contents, else create svg element
if (typeof svgDoc !='undefined') {
var myNode = document.getElementById("svgDoc");
while (myNode.firstChild) {
myNode.removeChild(myNode.firstChild);
}
} else {
svgDoc = d3.select("body").append("svg").attr("id","svgDoc");
}
//we now have a nice empty svg element to work with
var defs = svgDoc.append("defs")
//set the height of the graphic according to the width
if (container_width>mobSwitch) {
//symbol definition for a tree
svgDoc.attr("height",container_width*0.5);
var tree = defs.append("g")
.attr("id","iconTree");
tree.append("polygon")
.attr("points","21.1,50 16,43 16,0 4,0 4,43 -1.1,50 ")
.attr("fill","#998675");
tree.append("path")
.attr("d","M38.2-9.4c0-5.5-4.5-10-10-10c-0.4,0-0.8,0.1-1.3,0.1c0.1-0.4,0.1-0.8,0.1-1.3c0-5.5-4.5-10-10-10c-3.5,0-6.5,1.8-8.3,4.5c-1.8-2.7-4.8-4.5-8.3-4.5c-4,0-7.4,2.4-9,5.8c-0.7-0.1-1.4-0.2-2.1-0.2c-5.5,0-10,4.5-10,10c0,2.1,0.6,4,1.7,5.5c-1.1,1.6-1.7,3.5-1.7,5.5c0,4.2,2.6,7.8,6.3,9.2c1.5,3.7,5,6.3,9.2,6.3c3.5,0,6.5-1.8,8.3-4.5c1.8,2.7,4.8,4.5,8.3,4.5c2.1,0,4-0.6,5.5-1.7c1.6,1.1,3.5,1.7,5.5,1.7c5.5,0,10-4.5,10-10c0-0.7-0.1-1.4-0.2-2.1C35.8-2,38.2-5.4,38.2-9.4z")
.attr("fill","#39B54A");
} else {
svgDoc.attr("height",container_width*1.5);
}
//create group - and append rect which shows the height of the svg graphic
var graph = svgDoc.append("g");
var bg=graph.append("rect")
.attr("width","100%")
.attr("height","100%")
if (container_width>mobSwitch) {
graph.append("text")
.attr("x",20)
.attr("y",20)
.text("Larger view (width = "+container_width+" pixels)");
//pretty landscape picture...
//foreground
graph.append("rect")
.attr("width","100%")
.attr("height","37%")
.attr("y","63%")
.attr("fill","green")
//sun
graph.append("circle")
.attr("cx","85%")
.attr("cy","14%")
.attr("r","7%")
.attr("fill","yellow")
//place some trees
numTrees=7;
treeSpacing = container_width/numTrees;
treeArray=d3.range(numTrees);
graph.selectAll("use")
.data(treeArray)
.enter()
.append("use")
.attr("xlink:href","#iconTree")
.attr("id",function(d,i){
return "tree"+i
})
.attr("x",function(d,i){
return 20+(i*treeSpacing)
})
.attr("y","60%");
bg.attr("class","landscape");
} else {
graph.append("text")
.attr("x",20)
.attr("y",20)
.text("Smaller view (width = "+container_width+" pixels)")
//woeful depiction of a person in portrait - but you get the idea
graph.append("ellipse")
.attr("cx",container_width/2)
.attr("cy",555)
.attr("rx",function(){return container_width/2})
.attr("ry","40%")
.attr("fill","#72512d")
graph.append("ellipse")
.attr("cx",container_width/2)
.attr("cy",300)
.attr("rx",function(){return container_width/4})
.attr("ry",175)
.attr("fill","pink")
graph.append("circle")
.attr("cx",function(){return (container_width/2)-30})
.attr("cy",270)
.attr("r",12)
.attr("class","eyes")
graph.append("circle")
.attr("cx",function(){return (container_width/2)+30})
.attr("cy",270)
.attr("r",12)
.attr("class","eyes")
graph.append("ellipse")
.attr("cx",container_width/2)
.attr("cy",400)
.attr("rx",15)
.attr("ry",25)
.attr("fill","black")
bg.attr("class","portrait");
}
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment