export default function ({ data, width, height, container }) { const stage = new PIXI.Container() const renderer = PIXI.autoDetectRenderer(width, height, { antialias: !0, transparent: !0, resolution: 1 }) const links = new PIXI.Graphics() stage.addChild(links) container.appendChild(renderer.view) const simulation = d3.forceSimulation() .force('link', d3.forceLink().id((d) => d.id)) .force('charge', d3.forceManyBody()) .force('center', d3.forceCenter(width / 2, height / 2)) data.nodes.forEach((node, i) => { node.gfx = new PIXI.Graphics() node.gfx.lineStyle(1.5, 0xFFFFFF) node.gfx.beginFill(colour(node.id)) node.gfx.drawCircle(0, 0, 5) stage.addChild(node.gfx) }) simulation .nodes(data.nodes) .on('tick', ticked) simulation.force('link') .links(data.links) function ticked () { data.nodes.forEach((node) => { let { x, y, gfx } = node gfx.position = new PIXI.Point(x, y) }) links.clear() links.alpha = 0.6 data.links.forEach((link) => { let { source, target } = link links.lineStyle(Math.sqrt(link.value), 0x999999) links.moveTo(source.x, source.y) links.lineTo(target.x, target.y) }) links.endFill() renderer.render(stage) } }
initialize( chart, data, opts ) { this.simulation = d3.forceSimulation() .force( 'link', d3.forceLink() .id( ( d ) => { return d.id; }) .distance( () => 120 ) ) .force( 'charge', d3.forceManyBody() .strength( () => -10 ) .distanceMax( () => 500 ) ) .force( 'center', d3.forceCenter( this.props.width / 2, this.props.height / 2 ) ); this.drawPlot( chart, data ); }
const layout = (graph, { height, width }) => { const simulation = d3 .forceSimulation() .alphaMin(1) .force( 'link', d3 .forceLink() .id(d => d.id) .distance(prop('value')) ) .force('charge', d3.forceManyBody()) .force('center', d3.forceCenter(width / 2, height / 2)) simulation.nodes(graph.nodes) simulation.force('link').links(graph.links) range(1, 200).forEach(() => { simulation.tick() }) return graph }
.attr("height", "100%") .attr("viewBox", "0 0 " + width + " " + height) .attr("preserveAspectRatio", "xMidYMid meet") .attr("pointer-events", "all") .call(d3.zoom().on("zoom", redraw)); let vis = svg .append('svg:g'); function redraw() { vis.attr("transform", "translate(" + d3.event.translate + ")" + " scale(" + d3.event.scale + ")"); } let graph = d3.forceSimulation() .force("link", d3.forceLink() .id(d => d.id) .distance(d => d.value / 50)) .force("centre", d3.forceCenter(w / 2, h / 2)) .force("size", d3.forceCollide() .radius(5) .strength(1)) .alphaDecay(0.001) d3.json('data/capital_distances.json', (e, data) => { if (e) throw e // let link = svg.append("g") // .attr("class", "links") // .selectAll("line") // .data(data.links)
, done : function(errors, window) { var body = window.document.querySelector('body') , listDiv = window.document.querySelector('#listDiv') , el = window.document.querySelector('#dataviz-container') //////////////// Original Trump World code: var margin = {top: 0, right: 0, bottom: 0, left: 0}, width = window.innerWidth - margin.left - margin.right, height = window.innerHeight - margin.top - margin.bottom; var voronoi = d3.voronoi() .x(function(d) { return d.x; }) .y(function(d) { return d.y; }) .extent([[-(width * 2), -(height * 3)], [width * 3 , height * 4]]); // how much of this stuff can be deleted because it's used only for the zoom function? var nominal_text_size = 60; var max_text_size = 14; var nominal_stroke = 1.25; var max_stroke = 3.5; var nominal_labelStroke = 1 var max_labelStroke = 20 var svg = d3.select(el).attr("id", "dataviz-container").append("svg") .attr("width", width) .attr("height", height + margin.top + margin.bottom); var gContainer = svg.append("g") .attr("id", "gContainer") .attr("transform", "translate(" + 300 + "," + margin.top + ")"); var simulation = d3.forceSimulation() .force("charge", d3.forceManyBody().strength(-200)) // .force("link", d3.forceLink(links).distance(20).strength(1).iterations(10)) .force("link", d3.forceLink().id(function(d) { return d.id; })) .force("x", d3.forceX(width / 2)) .force("y", d3.forceY(height / 2)); var color = d3.scaleOrdinal(d3.schemeCategory10).domain(["organization", "person", "federal agency"]); d3.queue() .defer(d3.csv, "https://raw.githubusercontent.com/BuzzFeedNews/trumpworld/master/data/trumpworld.csv") // .defer(d3.csv, "trumpworld.csv") .await(ready); function slug (id) { return id.replace(/,/g,"") .replace(/\./g,"") .replace(/'/g,"") .replace(/"/g,"") .replace(/&/g,"") .replace(/\(/g,"") .replace(/\)/g,"") .replace(/\//g,"") .replace(/ /g,"") .replace(/#/g,"") .replace(/—/g,""); }; function ready (error, trumpworld) { if (error) throw error; var orgAs = trumpworld .filter( function(d) { return d["Entity A Type"] === "Organization"}) .map(function(d) { return d["Entity A"]}); var peepAs = trumpworld .filter( function(d) { return d["Entity A Type"] === "Person"}) .map(function(d) { return d["Entity A"]}); var fedAs = trumpworld .filter( function(d) { return d["Entity A Type"] === "Federal Agency"}) .map(function(d) { return d["Entity A"]}); var orgBs = trumpworld .filter( function(d) { return d["Entity B Type"] === "Organization"}) .map(function(d) { return d["Entity B"]}); var peepBs = trumpworld .filter( function(d) { return d["Entity B Type"] === "Person"}) .map(function(d) { return d["Entity B"]}); var fedBs = trumpworld .filter( function(d) { return d["Entity B Type"] === "Federal Agency"}) .map(function(d) { return d["Entity B"]}); var orgs = orgAs.concat(orgBs); var peeps = peepAs.concat(peepBs); var feds = fedAs.concat(fedBs); var allOrgs = d3.set(orgs).values(); var allPeeps = d3.set(peeps).values(); var allFeds = d3.set(feds).values(); var nodes = [], links = [], listItems = []; // LINKS trumpworld.forEach(function(d) { links.push({ "source" : d["Entity A"], "target" : d["Entity B"], "connection" : d["Connection"], "reference" : d["Source(s)"] }) }); // NODES allOrgs.forEach(function(orgs) { nodes.push({ "id" : orgs, "type" : "organization" }) }); allPeeps.forEach(function(peeps) { nodes.push({ "id" : peeps, "type" : "person" }) }); allFeds.forEach(function(feds) { nodes.push({ "id" : feds, "type" : "federal agency" }) }); // get nodes' connection count var connectionCountList = {}; links.forEach(function(d) { if (!connectionCountList[d.source]) { connectionCountList[d.source] = 1 } else { connectionCountList[d.source] += 1 }; if (!connectionCountList[d.target]) { connectionCountList[d.target] = 1 } else { connectionCountList[d.target] += 1 }; }); // add count value to nodes nodes.forEach(function(d) { d.count = connectionCountList[d.id]; }); // GRAPH var graph = { "nodes": nodes, "links": links } // create json for use in trumpworld-interaction.js fs.writeFile('../public/trumpworld-graph.json', JSON.stringify(graph), function(err) { if(err) { console.log('error saving document', err) } else { console.log('trumpworld-graph.json was saved!') } } ); var link = gContainer.append("g") .attr("class", "links") .selectAll("line") .data(graph.links) .enter().append("line") .attr("class", "lines"); var node = gContainer.selectAll(".nodes") .data(graph.nodes) .enter().append("g") .attr("class", function(d) { return "nodes T" + slug(d.id); }) .on("mouseover", function() { this.parentNode.appendChild(this); }); var circle = node.append("circle") .attr("r", 7) .attr("class", "nodeCircle") .style("fill", function(d) { return color(d.type); }); var labelShadow = node.append("text") .attr("dy", ".35em") .attr("class", "labelShadow") .style("font-size", nominal_text_size + "px") .text(function(d) { return d.id; }); var label = node.append("text") .attr("dy", ".35em") .attr("class", "label") .style("font-size", nominal_text_size + "px") .text(function(d) { return d.id; }); /// simulation .nodes(graph.nodes); simulation.force("link") .links(graph.links); var clipPath = node.append("clipPath") .attr("class", "clip") .attr("id", function(d) { return "clip-" + slug(d.id); }) .append("path") .attr("class", "clip-path-circle"); // delete 'cause doesn't seem necessary // var clickHilightColor = "rgb(52, 255, 38)"; var ul = d3.select(listDiv).append("ul") .selectAll("li") .data(nodes) .enter().append("li") .attr("class", function(d) { return "T" + slug(d.id) }) .attr("id", function(d) { return "T" + slug(d.id) }) .html(function(d) { return d.id } ) .style("color", function(d) { return color(d.type) }) var circleCatcher = node.append("circle") .attr("r", 30) // .style("fill", function(d, i) { return colorScale(i); }) .attr("class", "circleCatcher") .style("fill-opacity", 0) .style("stroke", "none") .attr("clip-path", function(d) { return "url(#clip-" + slug(d.id) + ")"; }) // generate static simulation d3.timeout(function() { for (var i = 0, n = Math.ceil(Math.log(simulation.alphaMin()) / Math.log(1 - simulation.alphaDecay())); i < n; ++i) { simulation.tick(); } clipPath .data(voronoi.polygons(graph.nodes)) .attr("d", function(d) { return d ? "M" + d.join("L") : null; }); circleCatcher .attr("cx", function(d) { return d.x; }) .attr("cy", function(d) { return d.y; }); link .attr("x1", function(d) { return d.source.x; }) .attr("y1", function(d) { return d.source.y; }) .attr("x2", function(d) { return d.target.x; }) .attr("y2", function(d) { return d.target.y; }); node .attr("cx", function(d) { return d.x; }) .attr("cy", function(d) { return d.y; }); circle .attr("cx", function(d) { return d.x; }) .attr("cy", function(d) { return d.y; }); label .attr("x", function(d) { return d.x + 11; }) .attr("y", function(d) { return d.y; }); labelShadow .attr("x", function(d) { return d.x + 11; }) .attr("y", function(d) { return d.y; }); }); }; // ready function callback ///////////////// // Save result to an html file setTimeout(function() { fs.writeFile('../views/trumpworld.html', window.document.documentElement.innerHTML, function(err) { if(err) { console.log('error saving document', err) } else { console.log('trumpworld.html was saved!') } }) }, 2000); } // end jsDom done callback
export default (parentNode, data) => { const $svg = d3.select(parentNode).append('svg'); const $drawBox = $svg.append('g').attr('class', 'playground-d3'); const zoom = d3.zoom(); zoom.on('zoom.drawBox', () => { $drawBox.attr('transform', d3.event.transform); }); $svg.call(zoom).call(grid, zoom); zoom.translateBy($svg, window.innerWidth / 2, window.innerHeight / 2); const root = d3.hierarchy(data.node, (d) => d.childNodes); root.sum((d) => d.children ? d.children.length + 1 : 1); d3.tree().size([Math.PI * 2, 500])(root); root.descendants().forEach((d) => { const a = d.x; const r = d.y; d.x = Math.cos(a) * r; d.y = Math.sin(a) * r; d.f = d.parent ? (d.children ? d.children.length / d.parent.children.length : 0) : 1; }); const nodes = root.descendants(); const links = root.links(); const sim = d3.forceSimulation(nodes) .stop() .velocityDecay(.1) .alphaMin(.03) // .stop() .force('link', d3.forceLink(root.links(links)) .distance(10) .iterations(3) ) .force('charge', d3.forceManyBody() .theta(.2) .strength((d) => d.children ? d.children.length * -.1 : -10) // .strength((d) => d.children ? d.f * -.1 + d.value * -.01 : -10) // .strength((d) => (d.children ? -1 : -10) * .1) // .strength(-.1) // .strength(-1) ) .force('center', d3.forceCenter()) ; // let steps = 100; // while(steps--) { // sim.tick(10000); // if(!(steps % 10)) { // console.log(steps); // } // } const $nodes = $drawBox.append('g').selectAll('.node') .data(nodes) .enter() .append('circle') .attr('class', 'node') .attr('cx', (d) => d.x) .attr('cy', (d) => d.y) .attr('r', .6); $nodes.filter((d) => !d.depth).attr('r', 50).classed('root', true); const $links = $drawBox.append('g').selectAll('.link') .data(links) .enter() .append('path') .attr('class', 'link') .attr('vector-effect', 'non-scaling-stroke') .attr('d', (d) => { return 'M' + d.source.x + ',' + d.source.y + ' ' + 'L' + d.target.x + ',' + d.target.y; }); let ticks = 0; const update = () => { ticks++; $nodes .attr('cx', (d) => d.x) .attr('cy', (d) => d.y); $links .attr('d', (d) => { return 'M' + d.source.x + ',' + d.source.y + ' ' + 'L' + d.target.x + ',' + d.target.y; }); if(!(ticks % 10)) { console.log('Ticks: %d | Alpha: %f | Min alpha: ', ticks, sim.alpha(), sim.alphaMin()); } }; sim.on('tick', update); sim.restart(); // update(); }