This is a bare-bones example for using the d3-layout-narrative module. It's a cut-down version of the Episode IV chart in Star Wars: every scene from I-VI charted at ABC News.
View the annotated source for more on the API.
| license: mit | |
| scrolling: true | |
| height: 300 |
This is a bare-bones example for using the d3-layout-narrative module. It's a cut-down version of the Episode IV chart in Star Wars: every scene from I-VI charted at ABC News.
View the annotated source for more on the API.
| // Request the data | |
| d3.json('data.json', function(err, response){ | |
| var svg, scenes, charactersMap, width, height, sceneWidth; | |
| // Get the data in the format we need to feed to d3.layout.narrative().scenes | |
| scenes = wrangle(response); | |
| // Some defaults | |
| sceneWidth = 14; | |
| width = scenes.length * sceneWidth * 4; | |
| height = 300; | |
| labelSize = [110,15]; | |
| // The container element (this is the HTML fragment); | |
| svg = d3.select("body").append('svg') | |
| .attr('id', 'narrative-chart') | |
| .attr('width', width) | |
| .attr('height', height); | |
| // Calculate the actual width of every character label. | |
| scenes.forEach(function(scene){ | |
| scene.characters.forEach(function(character) { | |
| character.width = svg.append('text') | |
| .attr('opacity',0) | |
| .attr('class', 'temp') | |
| .text(character.name) | |
| .node().getComputedTextLength()+10; | |
| }); | |
| }); | |
| // Remove all the temporary labels. | |
| svg.selectAll('text.temp').remove(); | |
| // Do the layout | |
| narrative = d3.layout.narrative() | |
| .scenes(scenes) | |
| .size([width,height]) | |
| .pathSpace(10) | |
| .groupMargin(20) | |
| .labelSize([110,15]) | |
| .scenePadding([5,sceneWidth/2,5,sceneWidth/2]) | |
| .labelPosition('left') | |
| .layout(); | |
| // Get the extent so we can re-size the SVG appropriately. | |
| svg.attr('height', narrative.extent()[1]); | |
| // Draw the scenes | |
| svg.selectAll('.scene').data(narrative.scenes()).enter() | |
| .append('g').attr('class', 'scene') | |
| .attr('transform', function(d){ | |
| var x,y; | |
| x = Math.round(d.x)+0.5; | |
| y = Math.round(d.y)+0.5; | |
| return 'translate('+[x,y]+')'; | |
| }) | |
| .append('rect') | |
| .attr('width', sceneWidth) | |
| .attr('height', function(d){ | |
| return d.height; | |
| }) | |
| .attr('y', 0) | |
| .attr('x', 0) | |
| .attr('rx', 3) | |
| .attr('ry', 3); | |
| // Draw appearances | |
| svg.selectAll('.scene').selectAll('.appearance').data(function(d){ | |
| return d.appearances; | |
| }).enter().append('circle') | |
| .attr('cx', function(d){ | |
| return d.x; | |
| }) | |
| .attr('cy', function(d){ | |
| return d.y; | |
| }) | |
| .attr('r', function(){ | |
| return 2; | |
| }) | |
| .attr('class', function(d){ | |
| return 'appearance ' + d.character.affiliation; | |
| }); | |
| // Draw links | |
| svg.selectAll('.link').data(narrative.links()).enter() | |
| .append('path') | |
| .attr('class', function(d) { | |
| return 'link ' + d.character.affiliation.toLowerCase(); | |
| }) | |
| .attr('d', narrative.link()); | |
| // Draw intro nodes | |
| svg.selectAll('.intro').data(narrative.introductions()) | |
| .enter().call(function(s){ | |
| var g, text; | |
| g = s.append('g').attr('class', 'intro'); | |
| g.append('rect') | |
| .attr('y', -4) | |
| .attr('x', -4) | |
| .attr('width', 4) | |
| .attr('height', 8); | |
| text = g.append('g').attr('class','text'); | |
| // Apppend two actual 'text' nodes to fake an 'outside' outline. | |
| text.append('text'); | |
| text.append('text').attr('class', 'color'); | |
| g.attr('transform', function(d){ | |
| var x,y; | |
| x = Math.round(d.x); | |
| y = Math.round(d.y); | |
| return 'translate(' + [x,y] + ')'; | |
| }); | |
| g.selectAll('text') | |
| .attr('text-anchor', 'end') | |
| .attr('y', '4px') | |
| .attr('x', '-8px') | |
| .text(function(d){ return d.character.name; }); | |
| g.select('.color') | |
| .attr('class', function(d){ | |
| return 'color ' + d.character.affiliation; | |
| }); | |
| g.select('rect') | |
| .attr('class', function(d){ | |
| return d.character.affiliation; | |
| }); | |
| }); | |
| }); | |
| function wrangle(data) { | |
| var charactersMap = {}; | |
| return data.scenes.map(function(scene){ | |
| return {characters: scene.map(function(id){ | |
| return characterById(id); | |
| }).filter(function(d) { return (d); })}; | |
| }); | |
| // Helper to get characters by ID from the raw data | |
| function characterById(id) { | |
| charactersMap = charactersMap || {}; | |
| charactersMap[id] = charactersMap[id] || data.characters.find(function(character){ | |
| return character.id === id; | |
| }); | |
| return charactersMap[id]; | |
| } | |
| } |
| { | |
| "characters": [ | |
| { | |
| "id": "nm0001504", | |
| "name": "Johan Wengren", | |
| "affiliation": "light" | |
| }, | |
| { | |
| "id": "nm0574534", | |
| "name": "Mr. Wednesday", | |
| "affiliation": "other" | |
| }, | |
| { | |
| "id": "nm1340638", | |
| "name": "Shadow Moon", | |
| "affiliation": "other" | |
| }, | |
| { | |
| "id": "nm4523607", | |
| "name": "Sadie", | |
| "affiliation": "light" | |
| }, | |
| { | |
| "id": "nm0120905", | |
| "name": "Factory Supervisor", | |
| "affiliation": "light" | |
| }, | |
| { | |
| "id": "nm0115161", | |
| "name": "Laura Moon", | |
| "affiliation": "light" | |
| }, | |
| { | |
| "id": "nm1139455", | |
| "name": "Maman Brigitte", | |
| "affiliation": "light" | |
| }, | |
| { | |
| "id": "nm1032567", | |
| "name": "Mad Sweeney", | |
| "affiliation": "light" | |
| }, | |
| { | |
| "id": "nm0339991", | |
| "name": "Helen", | |
| "affiliation": "light" | |
| }, | |
| { | |
| "id": "nm7636242", | |
| "name": "Meme", | |
| "affiliation": "dark" | |
| }, | |
| { | |
| "id": "nm5897662", | |
| "name": "Trending", | |
| "affiliation": "dark" | |
| }, | |
| { | |
| "id": "nm0998489", | |
| "name": "Dr. Fapp", | |
| "affiliation": "dark" | |
| }, | |
| { | |
| "id": "nm3034163", | |
| "name": "Viral", | |
| "affiliation": "dark" | |
| }, | |
| { | |
| "id": "nm9500848", | |
| "name": "Ms. World", | |
| "affiliation": "dark" | |
| }, | |
| { | |
| "id": "nm6780817", | |
| "name": "Technical Boy", | |
| "affiliation": "dark" | |
| }, | |
| { | |
| "id": "nm0321047", | |
| "name": "Mr. Road", | |
| "affiliation": "dark" | |
| }, | |
| { | |
| "id": "nm1462340", | |
| "name": "Bilquis", | |
| "affiliation": "light" | |
| }, | |
| { | |
| "id": "nm0001295", | |
| "name": "Whiskey Jack", | |
| "affiliation": "light" | |
| }, | |
| { | |
| "id": "nm2612382", | |
| "name": "Cordelia", | |
| "affiliation": "light" | |
| }, | |
| { | |
| "id": "nm2144639", | |
| "name": "Bus Clerk", | |
| "affiliation": "light" | |
| }, | |
| { | |
| "id": "nm5257212", | |
| "name": "Sophie", | |
| "affiliation": "light" | |
| }, | |
| { | |
| "id": "nm10294995", | |
| "name": "Alison McGovern", | |
| "affiliation": "light" | |
| }, | |
| { | |
| "id": "nm0842140", | |
| "name": "Ann-Marie Hinzelmann", | |
| "affiliation": "light" | |
| }, | |
| { | |
| "id": "nm0425053", | |
| "name": "Chad Mulligan", | |
| "affiliation": "light" | |
| } | |
| ], | |
| "scenes": [ | |
| [ | |
| "nm0001504", | |
| "nm0574534" | |
| ], | |
| [ | |
| "nm1340638", | |
| "nm4523607", | |
| "nm0120905" | |
| ], | |
| [ | |
| "nm0115161", | |
| "nm1139455" | |
| ], | |
| [ | |
| "nm0115161", | |
| "nm1032567" | |
| ], | |
| [ | |
| "nm1340638", | |
| "nm4523607", | |
| "nm0339991", | |
| "nm0574534" | |
| ], | |
| [ | |
| "nm7636242", | |
| "nm5897662", | |
| "nm0998489", | |
| "nm3034163", | |
| "nm9500848" | |
| ], | |
| [ | |
| "nm1340638", | |
| "nm0574534" | |
| ], | |
| [ | |
| "nm6780817", | |
| "nm0998489", | |
| "nm3034163", | |
| "nm5897662", | |
| "nm7636242", | |
| "nm9500848" | |
| ], | |
| [ | |
| "nm0321047", | |
| "nm0574534", | |
| "nm1340638" | |
| ], | |
| [ | |
| "nm1462340", | |
| "nm6780817" | |
| ], | |
| [ | |
| "nm0574534", | |
| "nm1340638", | |
| "nm0001295" | |
| ], | |
| [ | |
| "nm0574534", | |
| "nm1340638", | |
| "nm2612382" | |
| ], | |
| [ | |
| "nm1340638", | |
| "nm2144639" | |
| ], | |
| [ | |
| "nm5257212", | |
| "nm10294995", | |
| "nm1340638" | |
| ], | |
| [ | |
| "nm0842140", | |
| "nm1340638", | |
| "nm0425053" | |
| ], | |
| [ | |
| "nm0425053", | |
| "nm1340638" | |
| ], | |
| [ | |
| "nm1340638" | |
| ] | |
| ] | |
| } |
| <!DOCTYPE html> | |
| <meta charset="utf-8"> | |
| <style> | |
| text { | |
| font-family: "ProximaNova",Helvetica,Arial,sans-serif; | |
| font-size: 12px; | |
| } | |
| rect { | |
| fill: none; | |
| stroke: #000; | |
| } | |
| path { | |
| fill: none; | |
| stroke-width: 2; | |
| stroke: #df2929; | |
| } | |
| path.light { | |
| stroke: #3c6da8; | |
| } | |
| path.dark { | |
| stroke: #333; | |
| } | |
| .intro text:first-child { | |
| fill: #fff; | |
| stroke: #f9f9f9; | |
| stroke-width: 3; | |
| } | |
| .intro text+text { | |
| fill: #df2929; | |
| } | |
| .intro text+text.dark { | |
| fill: #333; | |
| } | |
| .intro text+text.light { | |
| fill: #3c6da8; | |
| } | |
| </style> | |
| <body> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/es5-shim/4.5.7/es5-shim.js"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/es6-shim/0.35.0/es6-shim.js"></script> | |
| <script src="https://d3js.org/d3.v3.min.js"></script> | |
| <script src="https://cdn.rawgit.com/abcnews/d3-layout-narrative/1.0.0/narrative.js"></script> | |
| <script src="chart.js"></script> |