block by dhoboy 962c18ab12f1f73e2351

Infinite Jest

Full Screen

Chord Diagram of the relationships in David Foster Wallace’s Infinite Jest. Family connections are shown in orange. Non-family connections shown in green. Hover over a character name to focus on their specific relationships, or hover around the blue perimeter of the circle to cycle through relationships. There is a lot of overlap in this diagram. Not all labels line up exactly with their chords.

Thanks to Syntagmatic for his help with this block.

Thanks to Mark Overstreet for his help in creating the square matrix for this diagram, IJ.csv.

IJ.csv adapted from this. For the most part, I deferred to this diagram. A couple of things I don’t quite agree with. E.g. There is no line directly connecting Gately with Pat Montesian, and the line connecting Michael and Matty Pemulis is a regular line, not a family line. But whatever. I couldn’t have made this without Sam’s incredible work.

index.html

<!DOCTYPE html>
<meta charset="utf-8">
<style>
#circle circle {
  fill: none;
  pointer-events: all;
}
.group path { 
  fill-opacity: 1; 
}
.label { 
  font: 8px sans-serif; 
}
.fade { 
  opacity: 0; 
}
.family { 
  fill: tomato; 
}
.attention {
  font-weight: bold;
  fill: red;
  stroke: #000;
  stroke-width: .25px;
}
#circle:hover path.fade { 
  display: none; 
}
</style>
<body>
<script src="//d3js.org/d3.v3.js"></script>
<script>

var width = 620,
	height = 620,
	padding = 650,
	outerRadius = Math.min(width, height) / 2 - 10,
	innerRadius = outerRadius - 24;

/* the 3 pieces needed for Chord Diagram */
// arc generator
var arc = d3.svg.arc()
	.innerRadius(innerRadius)
	.outerRadius(outerRadius);

// chord layout
var layout = d3.layout.chord()
	.padding(.04)
	.sortSubgroups(d3.ascending) 
	.sortChords(d3.ascending) 

// chord generator 
var path = d3.svg.chord()
	.radius(innerRadius);

var svg = d3.select("body").append("svg")
	.attr("width", width + padding)
	.attr("height", height + padding)
  .append("g")
    .attr("id", "circle") // #circle
    .attr("transform", "translate(" + (width + padding) / 2 + "," + (height + padding) / 2 + ")");

svg.append("circle")
	.attr("r", outerRadius);

var key = ["Sharyn Vaught","Caryn Vaught","Miles Penn","Jeffrey J. Penn","Ann Kittenplan","Esteban Reyes",
           "Guglielmo Redondo","\"Sleepy TP\" Peterson","John \"N.R.\" Wayne","Kieran McKenna",
           "Eliot Kornspan","Jennie Bash","Erica Siress","Cisne","Brian van Vleck","Tunnel Club",
           "\"Axhandle\" Axford","Lamont Chu","Amy Wingo","Shoshana Abrams","Lori Clow",
           "Audern Tallat-Kelpsa","Keith B. \"The Viking\" Freer","Philip Traub","Carol Spodek",
           "Frances L. Unwin","Diane Prins","Josh Gopnik","Younger Players","Tina Echt","Gretchen Holt",
           "Bernadette Longley","Carl \"Mobes\" Whale","Jolene Criess","Virgilio","Kyle D. Coyle",
           "Felicity Zweig","Evan Ingersoll","Peter Beak","Zoltan Csikzentmihalyi","Bernard Makulic",
           "Idris \"Id\" Arslanian","Jeff Wax","Stephen Wagenknecht","Chip Sweeny","Graham \"Yard-Guard\" Rader",
           "Petropolis Kahn","Ortho \"The Darkness\" Stice","Todd \"Postal Weight\" Postalthwaite",
           "\"U.S.S.\" Milicent Kent","Kent Blott","Anton \"Booger\" Doucette","Otis P. Lord","Players",
           "Dymphna","Univ. of Arizona","Coach Kirk White","Dean of Admissions","Gerhardt Schtitt",
           "Mario","Dir. of Composition","Lyle","The Moms","Militant Grammarians of Mass.",
           "Dean of Academic Affairs","Dean of Athletic Affairs","Theirry Poutrincourt","Mary Esther Thode",
           "Ruth","Barry Loach","\"Lateral\" Alice Moore","Elizabeth Tavis","Therese Loach","Aubrey deLint",
           "Rik Dunkel","Charles Tavis","Ken N. Johnson","Tony Nwangi","E.T.A","Prorectors","Corbett Thorp",
           "Cantrell","Mrs. Clarke","Dr. E. Zegarelli","Neil Hartigan","Tex Watson","Donnie Stott",
           "Veach","Dolores Rusk","Syrian Satellite Pro","Slodoban","Miriam Prickett","Meniscus Films, Ltd.",
           "Teachers","Flottman","Fentress","Poor Yorick Entertainment Unltd.","Latrodectus Mactans Prod.",
           "Heliotrope Films, Ltd.","Disney R. Leith","Urguhart Oglivie Jr.","Lingley","Pettijohn",
           "Soma Richardson-Levy-O'Bryne Chawaf","Ibn-Said Chawaf","Molly Cantrell Notkin","Melinda",
           "Vogelsong","Notkin's Party","Rutherford Keck","Crosby Baum","Reeves Mainwaring","Iaccarrino",
           "Kenkle","Calvin Thrust","Otto Brandt","Dave \"Fall Down Very\" Harde","ETA Physical Plant & Maintenance",
           "WETA","Fully Functional Phil","Fond du Lac NoCoat Inc","LipoVac Unltd.","Nunhagen Aspirin",
           "Tenuate","Am. Council of Disseminators of Cable","Viney and Veals","Bernard Wayne","James Struck Jr.",
           "Jim Troeltsch","Gloeckner","Stockhausen","P. Tom Veals","Carl E. \"Buster\" Yee","Van Slack",
           "Hugh Pemberton","Tall Paul Shaw","Marueen Hooley","Interlace","Noreen Lace-Forche","Ted Schacht",
           "Seniors (18s)","Michael Pemulis","Albertan Mogul","Glad","Canadian P.M","Mexican President","Johhny Gentle, Famous Crooner",
           "O.N.A.N","Acme Inc.","Neil in Spin","Harv","Clean U.S. Party","Dick Desai","Inner Infant Group","Jim",
           "Bridget C. Boone","Thomas M. Flatto","Input/Output","Rodney Tine Sr.","Rodney Tine Jr.",
           "O.U.S","Kevin Bane","Bain's Sister","Dick Willis","Vienna VA Szech. Steakhouse",
           "Saprogenic Greetings","Marlon K. Bain","Luria Perec","Henri F. Hoyne","at ETA","Hal","Smothergill",
           "Miriam Hoyne","Fortier","Dwight Flechette","Helen/Hugh Steeply","Broullime","Other Members","Balbalis",
           "Steeply's father","Nickerson","B.U.","Orin","Mo Cheery","Muminsky","R. Ossowiecke","Joubet","Desjardins",
           "Tassigny","Beausoleil","Moment Mag.","\"Jethro Bodine\"","Eric Clipperton","Bain Parents","Marathe",
           "Marathe's father","Heart in Purse","Gertraud","Ross Reat","Himself","James I. Sr.","St. Abt. L'Ist, QC",
           "Mario Sr.","engineer","\"Dark Star\" McNair","The Filmography","Miss Diagnosis","The Entertainment",
           "Dr. Wo","60 Minutes +/-","WYYY","MIT","A.Y. Rickey","U.H.I.D","Joelle van Dyne","Joe Lon van Dyne",
           "Medical Attache's Wife","Saudi Minister of Home Entertainment","Medical Attache","\"Uncle\" Lum Riney","\"Madame Psychosis\"",
           "Lady Delphina","Pamela Hoffman-Jeep","Stavros Lobokulas","Sir Osis of Thuliver","Gately","Gately's Mom",
           "former Navy M.P.","Doshka Bulat","Vinnie Nucci","Mrs. Waite","St. Elizabeth's Hospital","\"Fax\" Fackelmann",
           "\"Quo Vadis\" Kite","White Flag","\"Ferocious\" Francis Gehaney","Revere ADA","The wraith","Dr. Pendleton",
           "Dr. Pressburger or Prissburger","Cathy or Kathy","Tooty","Crocodiles","T.S.B.Y.S.C.D","Robert F. \"Bob Death\"",
           "Whitey Sorkin","Gwendine O'Shay","Eighties Bill","Dr. Robert \"Sixties Bob\" Monroe","John L.","Louise B.",
           "Tamara N.","Dickey N.","Jack J.","Sven S.","Bud O.","Glenn K.","Danielle Steenbok","Didi Neaves","Clenette Henderson",
           "Clenette's momma","Dolores Epps","Columbus Epps","Clenette's father/Roy Tony's brother","Reginald","Reginald's momma",
           "Wardine","Antitois","Wade McDade","Alfonso Parias-Carbo","Dwayne R. \"Doony\" Glynn","\"The Madame\"","Roy Tony",
           "William","Shantell","Roy","Wardine's momma","Calgarian Pro-Canada Phalanx","Jeu Du Prochain Train","Le Bloc QC",
           "Les Fils de Montcalm","La Culte de Baiser Sans Fin","Brandeis","Sepratisteurs Quebecois","Les Fils de Papineau",
           "Le Parti Quebecois","Guillaume Duplessis","Quebec Liberation Front","Wheelchair Assassins","Test Subjects",
           "Sheraton Commander","Da","Matty Pemulis","Lucien","Bertraund","Antitoi Entertainment","Susan T. Cheese",
           "Bridget Tenderhole","Lolasister","Equus Reese","Kate Gompert","Ken Erdedy","\"Poor Tony\" Krause","Ruth Van Cleve",
           "Dr. Garton","Ernest Feaster","Burt F. Smith","Emil \"yrstrly\" Minty","Bobby C.","DesMont(e)s","Purpleboy","Randy Lenz",
           "\"Wild Conceits\"","Mrs. Lenz","Tommy Doocey","Kely Vinoy","Yolanda Willis","2 Chinese Ladies","Residents",
           "Geoffrey T. Day","Bruce Green Sr.","Harriet Bonk Green","Mildred Bonk Green","Bruce Green","Ennet House",
           "Guy w/o First Name","Ennet House Staff","Johnette Marie Foltz","Eugenio Martinez","Ennet House Admin.",
           "Ennet House Unit 6","Ennet House Unit 5 \"The Shed\"","Tree Lady","Mrs. Lopate","Mars Montesian",
           "Pat Montesian","Annie Parrot","Janice","Ennet House Counselors","Enfield Marine Public Health Hospital Complex",
           "Maureen N.","Pointgrave","Enfield Marine Public Health Hospital Complex Unit 4","Retired Air Force Nurse Who Asks For Help",
           "Charlotte Treat","Tiny Ewell","\"Franklin W. Dixon\"","Money Stealers Club","Gavin Diehl","Nell Gunther",
           "Glenn K. Kubitz","Hester Thrale","Selwyn","Jennifer Belbin","Morris Hanley","Chandler Foss",
           "David Krane","April Cortelyu","Skull","Amy Johnson","Tingley"];

d3.csv("IJ.csv", form_the_data, function(error, data) {
  var matrix = data;
  
  // compute the chord layout
  layout.matrix(matrix);

  // add a group per character
  // each group is a row in the matrix
    
  var group = svg.selectAll(".group")
	  .data(layout.groups)
    .enter()
      .append("g")
      .attr("class", "group")
      .on("mouseover", mouseover)
      .on("mouseout", mouseout);
	
  // add the group arc
  var groupPath = group.append("path")
    .attr("id", function(d, i) {
      return "group" + i; 
	})
	.attr("d", arc)
	.style("fill", "#2171b5");
		
  // add a text label
  var x; // offset variable
  var labels = svg.selectAll(".label")
    .data(layout.groups);
	
  labels.enter()
    .append("g")
	.attr("class", "label")
	.attr("transform", function(d) {
	  return "rotate(" + (d.startAngle * 180 / Math.PI - 90) + ")"
		+ "translate(" + outerRadius + ",0)";
	})
	.append("text")
	.attr("x", function(d, i){ // stagger the labels on both sides of circle
	  if (d.startAngle > Math.PI) { // left side of circle
		  x = [8, -70, -148, -226];
	  } else if (d.startAngle < Math.PI) { // right side of circle
		  x = [8, 86, 164, 242];
	  }            
	  return x[(i % 4)];
	})
	.attr("dy", function(d, i){ 
		if (d.startAngle > Math.PI) { // left side of circle
		  return (i % 2 == 0) ? "-.35em" : ".35em";
	  } else if (d.startAngle < Math.PI) { // right side of circle
		  return (i % 2 == 0) ? ".35em" : "-.35em";
	  }
	})
	.attr("transform", function(d) { return d.startAngle > Math.PI ? "rotate(180)translate(-16)" : null; })
	.style("text-anchor", function(d) { return d.startAngle > Math.PI ? "end" : null; })
	.text(function(d, i){ return key[i]; })
	.on("mouseover", mouseover)
    .on("mouseout", mouseout);
	

	/* fix specific overlaps */     
    // right side of circle
    x = [8, 86, 164, 242];
    
    labels.selectAll("text").filter(function(p) { return p.index == 62; }) // the moms
      .attr("x", x[0]); // first row
    
    labels.selectAll("text").filter(function(p) { return p.index == 59; }) // mario
      .attr("x", x[1]); // second row
    
    labels.selectAll("text").filter(function(p) { return p.index == 80; }) // corbett thorp
      .attr("x", x[2]); // third row
    
    labels.selectAll("text").filter(function(p) { return p.index == 73; }) // aubrey delint
      .attr("x", x[3]); // fourth row
    
    labels.selectAll("text").filter(function(p) { return p.index == 97; }) // lactrodectus mactans prod.
      .attr("x", x[2]);
    
    labels.selectAll("text").filter(function(p) { return p.index == 117; }) // eta physical plant & maint.
      .attr("x", x[2]);
    
    labels.selectAll("text").filter(function(p) { return p.index == 164; }) // vienna va steakhouse
      .attr("x", x[1]);
    
    labels.selectAll("text").filter(function(p) { return p.index == 173; }) // fortier
      .attr("x", x[0]);
    
    labels.selectAll("text").filter(function(p) { return p.index == 163; }) // dick willis
      .attr("x", x[1])
      .attr("dy", ".1em");
    
    labels.selectAll("text").filter(function(p) { return p.index == 161; }) // kevin bane
      .attr("x", x[2]);
    
    labels.selectAll("text").filter(function(p) { return p.index == 158; }) // rodney tine sr
      .attr("x", x[1]);
    
    labels.selectAll("text").filter(function(p) { return p.index == 103; }) // soma richardson-....
      .attr("x", x[2]);
    
    labels.selectAll("text").filter(function(p) { return p.index == 64; }) // dean of academic affairs
      .attr("dy", "-.1em"); 
  
    labels.selectAll("text").filter(function(p) { return p.index == 128; }) // jim troeltsch
      .attr("dy", "-.1em"); 
   
    labels.selectAll("text").filter(function(p) { return p.index == 146; }) // johnny gentle, f.c.
      .attr("x", x[1]);
    
    // left side of circle
    x = [8, -70, -148, -226];
    
    labels.selectAll("text").filter(function(p) { return p.index == 174; }) // dwight flechtte
      .attr("x", x[3]);
    
    labels.selectAll("text").filter(function(p) { return p.index == 178; }) // balbalis
      .attr("x", x[3])
      .attr("dy", "-.5em");
    
	labels.selectAll("text").filter(function(p) { return p.index == 209; }) // 60 min +/-
      .attr("x", x[2]);
    
	labels.selectAll("text").filter(function(p) { return p.index == 206; }) // miss diagnosis
      .attr("x", x[3]);
    
	labels.selectAll("text").filter(function(p) { return p.index == 203; }) // engineer
      .attr("x", x[0]);
    
	labels.selectAll("text").filter(function(p) { return p.index == 200; }) // james I. sr.
      .attr("x", x[3])
      .attr("dy", ".5em");
    
	labels.selectAll("text").filter(function(p) { return p.index == 197; }) // gertraud
      .attr("dy", "-.5em");
    
	labels.selectAll("text").filter(function(p) { return p.index == 204; }) // dark star mcnair
      .attr("dy", "-.7em");
    
    labels.selectAll("text").filter(function(p) { return p.index == 216; }) // medical attache's wife
      .attr("x", x[3]);
    
    labels.selectAll("text").filter(function(p) { return p.index == 211; }) // MIT
      .attr("dy", "-.3em");
    
    labels.selectAll("text").filter(function(p) { return p.index == 225; }) // gately
      .attr("x", x[2]);
    
    labels.selectAll("text").filter(function(p) { return p.index == 170; }) // hal
      .attr("dy", "-.5em");
    
    labels.selectAll("text").filter(function(p) { return p.index == 181; }) // b.u.
      .attr("dy", ".5em");
	
	labels.selectAll("text").filter(function(p) { return p.index == 226; }) // gately's mom
      .attr("dy", ".1em");
    
	labels.selectAll("text").filter(function(p) { return p.index == 242; }) // crocodiles
      .attr("dy", ".1em");
    
	labels.selectAll("text").filter(function(p) { return p.index == 249; }) // john l.
      .attr("x", x[2]);
    
	labels.selectAll("text").filter(function(p) { return p.index == 263; }) // clenette's father/roy tony's brother
      .attr("x", x[0]);
      
    labels.selectAll("text").filter(function(p) { return p.index == 265; }) // reginald's momma
      .attr("x", x[2]);
    
    labels.selectAll("text").filter(function(p) { return p.index == 264; }) // reginald
	  .attr("dy", "-1em");
    
	labels.selectAll("text").filter(function(p) { return p.index == 278; }) // prochain train
	  .attr("dy", "-1em");
	
	labels.selectAll("text").filter(function(p) { return p.index == 289; }) // test subjects
	  .attr("x", x[2]);
	
	labels.selectAll("text").filter(function(p) { return p.index == 292; }) // matty pemulis
	  .attr("dy", ".3em");
	
	labels.selectAll("text").filter(function(p) { return p.index == 303; }) // ruth van cleve
	  .attr("x", x[2])
	  .attr("dy", ".1em");
	
	labels.selectAll("text").filter(function(p) { return p.index == 301; }) // ken erdedy
	  .attr("dy", "-.1em");
    
    labels.selectAll("text").filter(function(p) { return p.index == 296; }) // susan t. cheese
	  .attr("dy", "-.5em");
	
	labels.selectAll("text").filter(function(p) { return p.index == 306; }) // burt f. smith
	  .attr("dy", "-.5em");
	
	labels.selectAll("text").filter(function(p) { return p.index == 312; }) // wild conceits
	  .attr("dy", ".4em");
	
	labels.selectAll("text").filter(function(p) { return p.index == 325; }) // guy w/o first name
	  .attr("x", x[2]);
	
	labels.selectAll("text").filter(function(p) { return p.index == 313; }) // mrs. lenz
	  .attr("dy", ".7em");
	
	labels.selectAll("text").filter(function(p) { return p.index == 322; }) // mildred bonk green
	  .attr("x", x[1]);
	
	labels.selectAll("text").filter(function(p) { return p.index == 314; }) // tommy doocey
	  .attr("dy", ".1em");
	
	labels.selectAll("text").filter(function(p) { return p.index == 327; }) // johnette marie foltz
	  .attr("x", x[0]);
    
    labels.selectAll("text").filter(function(p) { return p.index == 329; }) // ennet house admin.
	  .attr("x", x[2]);
	  
	labels.selectAll("text").filter(function(p) { return p.index == 328; }) // eugenio martinez
	  .attr("x", x[1])
	  .attr("dy", "-.1em");
	
	labels.selectAll("text").filter(function(p) { return p.index == 339; }) // ennet marine public health...
	  .attr("x", x[2])
    
    labels.selectAll("text").filter(function(p) { return p.index == 338; }) // ennet house counselors
	  .attr("x", x[1])
	  .attr("dy", "-1em");
	
	labels.selectAll("text").filter(function(p) { return p.index == 343; }) // retired air force nurse...
	  .attr("x", x[0])
	
    labels.selectAll("text").filter(function(p) { return p.index == 360; }) // tingley
	  .attr("dy", ".5em")
	
  // add the chords
  var chord = svg.selectAll(".chord")
      .data(layout.chords)
    .enter()
	  .append("path")
	  .attr("class", "chord")
	  .style("fill", function(d, i) { 
	    if ((d.source.value == 2) && (d.target.value == 2)) { // family
		  return "tomato"; 
		} else {
		  return "#1a9850";
		}
	  })
	  .attr("d", path);
		
  function mouseover(d, i) {  
    var active_chords = d3.selectAll(".chord")
      .filter(function(p) {
        return p.source.index == d.index ||
               p.source.subindex == d.index;
      })
	
    // all the indices of labels the active chords touch
    var active_labels = [];
    active_chords.data().forEach(function(s) {
  	  active_labels.push(s.source.index);
  	  active_labels.push(s.target.index);
    })

    // destination labels the active chords touch
    var dest_labels = d3.selectAll(".label")
  	  .filter(function(p) {
  	    return active_labels.indexOf(p.index) != -1; 
  	  })
  
    // family labels 
	var family_labels_indices = [];
    active_chords.data().forEach(function(s) {
	  if ((s.source.value == 2) && (s.target.value == 2)) { // family    
 	    family_labels_indices.push(s.source.index);
 	    family_labels_indices.push(s.target.index);
	  }
	})
	
	var family_labels = d3.selectAll(".label")
  	  .filter(function(p) {
  	    return family_labels_indices.indexOf(p.index) != -1; 
  	  })
   
    // fade everything
    chord.classed("fade", true)
    labels.classed("fade", true)
    
    // show this label, active chords, and destination labels.
    active_chords.classed("fade", false)
    dest_labels.classed("fade", false)  
   
    // specify family labels (hard to see what orange lines point to exactly in diagram)
    family_labels.classed("family", true);

    // make this label red & bold so you can tell which one is showing
    svg.selectAll(".label")
      .filter(function(p) {
        return p.index == d.index;
      })
      .classed("attention", true);        
  }

  function mouseout(d, i) {
    chord.classed("fade", false)
    labels.classed("fade", false)
    
    svg.selectAll(".label")
      .filter(function(p) {
        return p.index == d.index;
      })
      .classed("attention", false);
    
    labels.classed("family", false);
  }
  
});

function form_the_data(d) {
  var i = 0;
  for (i = 0; i <= 360; i++){
	d[i] = +d[i];
  }
  return d;
}
d3.select(self.frameElement).style("height", (height + padding) + "px").style("width", (width + padding) + "px");	
</script>