Published
Edited
May 1, 2021
1 fork
Insert cell
md`# Tatum Draft Draggable Graph with filtered degrees`
Insert cell
chart = {
const links = data.links.map(d => Object.create(d)); // read the data and use the links key to create links
const nodes = data.nodes.map(d => Object.create(d)); // read the data and use the nodes key to create nodes

const simulation = d3 // use d3 to simulate physical force
.forceSimulation(nodes) // use the force simulation function from d3 to model how nodes interact
.force("link", d3.forceLink(links).id(d => d.id)) // use the forceLink function to model how links interact
.force("charge", d3.forceManyBody().strength(-100)) // use the ManyBody function to simulate gravity between nodes
.force("center", d3.forceCenter(width / 2, height / 2)) // center the "gravitational pull" in the middle of the visualization
.force("collision", d3.forceCollide(d => d.betweenness * 200)); // nodes can't collide with each other and have a 2 pixel pad

const svg = d3 // use d3 to create the svg or scaleable vector graphics box to draw the chart on
.create("svg") // make the svg
.attr("height", height) // make the svg as tall as the height variable
.attr("width", width); // make the svg as wide as Observable's default width

const link = svg // draw the links on the svg
.append("g") // make all the links html elements called g
.attr("stroke", "#999") // define the color of the link lines
.attr("stroke-opacity", 0.6) // define the opacity of the link lines
.selectAll("line") // select all line objects
.data(links) // use the links entries from the data to draw the lines
.join("line") // adjust the lines when something moves, like if we drag a node or there's a collission
.attr("stroke-width", d => Math.sqrt(d.value)); // make the links as wide as the square root of the value column (this is a good way to scale things if you have a wide range in values).

const node = svg // draw the nodes on the svg
.append("g") // make all nodes html elements called g
.attr("stroke", "#fff") // define the default color of the nodes
.attr("stroke-width", 1.5) // draw a stroke 1.5px wide around each node
.selectAll("circle") // select all circle objects
.data(nodes) // use the nodes entries in the data to draw the circles
.join("circle") // adjust the nodes when something moves
.attr("r", d => (d.betweenness *200) + 2) // draw each node as a circle with the radius of its degree value plus 2px
.attr("fill", color) // use the color function defined below to assign a color to each modularity class
.on("mouseover", tooltip_in) // when the mouse hovers a node, call the tooltip_in function to create the tooltip
.on("mouseout", tooltip_out) // when the mouse stops hovering a node, call the tooltip_out function to get rid of the
.call(drag(simulation)); // use the drag function defined below to allow us to click and drag nodes. This can be commented out if you don't want to use the drag function below


simulation.on("tick", () => {
link
.attr("x1", d => d.source.x)
.attr("y1", d => d.source.y)
.attr("x2", d => d.target.x)
.attr("y2", d => d.target.y);

node.attr("cx", d => d.x).attr("cy", d => d.y);
});

return svg.node();
}
Insert cell
Insert cell
tooltip = d3
.select("body")
.append("div") // the tooltip always "exists" as its own html div, even when not visible
.style("position", "absolute") // the absolute position is necessary so that we can manually define its position later
.style("visibility", "hidden") // hide it from default at the start so it only appears on hover
.style("background-color", "white")
.attr("class", "tooltip")
Insert cell
tooltip_in = function(event, d) { // pass event and d to this function so that it can access d for our data
return tooltip
.html("<h4>" + d.full_name + "<p>" + d.type + "</p>" +"<p>" + d.gender + "<p>" + d.occupation + "</p>" + "<p>" + d.location +"<p>" + "Role in court system: " + d.court + "</p>" + "</p>"+ "</h4></br><p>" + d.transactions + "</p>") // add an html element with a header tag containing the name of the node. This line is where you would add additional information like: "<h4>" + d.name + "</h4></br><p>" + d.type + "</p>" Note the quote marks, pluses and </br>--these are necessary for javascript to put all the data and strings within quotes together properly. Any text needs to be all one line in .html() here
.style("visibility", "visible") // make the tooltip visible on hover
.style("top", event.pageY + "px") // position the tooltip with its top at the same pixel location as the mouse on the screen
.style("left", event.pageX + "px"); // position the tooltip just to the right of the mouse location
}
Insert cell
// name a tooltip_out function to call when the mouse stops hovering
tooltip_out = function() {
return tooltip
.transition()
.duration(50) // give the hide behavior a 50 milisecond delay so that it doesn't jump around as the network moves
.style("visibility", "hidden"); // hide the tooltip when the mouse stops hovering
}
Insert cell
md`## Get the data file

This is the graph.json file we created in the colabs lesson. Note that both nodes (individual people) and links (the relationships between them) are included in this file. Expand the triangle carets to see what attributes this data includes--are there other attributes like gender or betweeness it would make sense to color or size our nodes by? How would you handle sizing a node by betweenness if it had a betweenness of 0? (Hint: you can do math in your sizing function to give all your nodes a minimum size. Look at how nodes are sized by degree and given a minimum size of 2 in the chart above using d => d.degree + 2).`
Insert cell
data = FileAttachment("detailedfilteredgraph.json").json() // network data is almost always encoded in
// json format, so we tell Observable to read the data file
// as json
Insert cell
md`## Define height

Does what it says and defines the height in pixels of our visualization. We don't need to define the width on Observable because Observable makes all visualizations 100% width by default.`
Insert cell
height = 600
Insert cell
md`## Define the color scale

Below, we use d3 to define a categorical color scale like we did in the bar chart lesson, and then tell it [which color palette](https://github.com/d3/d3-scale-chromatic/blob/master/README.md#schemeCategory10) to use. Try changing schemeCategory10 to a different categorical color palette from the link--remember that capitalization matters!

After defining the color palette, we tell the function color to assign the color palette to the modularity categories and return those color values. Try editing this to color by gender instead.

What if you wanted to color your nodes by a numeric value like degree, birth_year, or betweenness? Numeric scales don't often work well with categorical color schemes--is there a different kind of color scale at the link above that would work better for a continuous scale of numbers?`
Insert cell
color = {
const scale = d3.scaleOrdinal(d3.schemeCategory10);
return d => scale(d.type);
}
Insert cell
md`## Handle dragging

Strictly speaking, you don't **need** a drag function to get the graph to work. This whole section below could be commented out, as long as every reference to the drag function in the chart function above was also commented out. This is a fairly standard drag function that doesn't change across force-directed graphs, so you don't need to change this; I usually just copy/paste the same function across all my network graphs.`
Insert cell
drag = simulation => {
function dragstarted(event) {
if (!event.active) simulation.alphaTarget(0.3).restart();
event.subject.fx = event.subject.x;
event.subject.fy = event.subject.y;
}

function dragged(event) {
event.subject.fx = event.x;
event.subject.fy = event.y;
}

function dragended(event) {
if (!event.active) simulation.alphaTarget(0);
event.subject.fx = null;
event.subject.fy = null;
}

return d3
.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended);
}
Insert cell
md`### Include D3`
Insert cell
d3 = require("d3@6")
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more