Published
Edited
Apr 26, 2021
4 stars
Insert cell
Insert cell
Insert cell
Insert cell
FileAttachment("tumblr_mgu56brrH81s3xom1o1_1280 copy.gif").image()
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
d3 = require("d3@5")
Insert cell
graphData = FileAttachment("Barr_avantgarde@9.json").json()
Insert cell
allNodesNames = graphData.nodes.map(function(d){return d.movement_name});
Insert cell
allNodesNamesYear = graphData.nodes.map(function(d){return d.year});
Insert cell
allNodesNamesLocation = graphData.nodes.map(function(d){return d.location});
Insert cell
graphData["nodes"]
Insert cell
allNodesText = graphData.nodes.map(function(d){return d.key_artist});
Insert cell
idToNode =
{
let dict = {};
graphData.nodes.forEach(function(n) {
dict[n.id] = n;
});
return dict;
}
Insert cell
idToTargetNodes =
{
let dict = {};
graphData.nodes.forEach(function (n) {
dict[n.id] = [];
graphData.links.forEach(function (l) {
if (l.source === n.id) {
dict[n.id].push(l.target);
}
});
});
return dict;
}
Insert cell
import {swatches, legend} from "@d3/color-legend"
Insert cell
md `## Create SVG`
Insert cell
margin = ({top: 20, bottom: 60, left: 120, right: 20});
Insert cell
height = 1200 - margin.top - margin.bottom;
Insert cell
width = 3000
Insert cell
md`### Create x and y scale function`
Insert cell
xScale = d3.scalePoint()
.domain(allNodesNames)
.range([0,width])
Insert cell
yScale = d3.scalePoint()
.domain(allNodesNames)
.range([0,height])
Insert cell
md`### Assign colors based on
Adapted from https://observablehq.com/@sjengle/interactivity`
Insert cell
md`### Assign colors based on location`
Insert cell
location_movement = d3.set(graphData["nodes"], d => d.location);
Insert cell
color = {
return d3.scaleOrdinal(d3.schemeSet1).domain(location_movement.values());
}
Insert cell
md`### Assign colors based on relationship`
Insert cell
// assign relationships to different colors
types_relationship = d3.set(graphData["links"], d => d.relationship);
Insert cell
color2 = {
return d3.scaleOrdinal(d3.schemeSet2).domain(types_relationship.values());
}
Insert cell
md`### Assign xxx based on year`
Insert cell
// assign relationships to different colors
types_years = d3.set(graphData["nodes"], d => d.year);
Insert cell
color3 = {
return d3.scaleOrdinal(d3.schemeSet3).domain(types_years.values());
}
Insert cell
swatches({
color: color2
});
Insert cell
//swatches({
//color: color3
//});
Insert cell
Insert cell
Insert cell
justNodesAndLabels = {
const radius = 10
// create a container for the svg; use standard margin notation
const container = d3.select(DOM.svg(width+margin.left+margin.right,
height+margin.top+margin.bottom))
const arcGroup = container
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")

const nodes = arcGroup.selectAll("nodes")
.data(graphData.nodes)
.enter().append("circle")
.attr("cx", margin.left+150)
.attr("cy", d => yScale(d.movement_name))
.attr("r", radius)
.attr("fill", d => color(d.location))
.attr("id", d => d.id)

const nodeLabels = arcGroup.selectAll("nodeLabels")
.data(graphData.nodes)
.enter().append("text")
.attr("x",margin.left+120)
.attr("y", d => yScale(d.movement_name))
.attr("fill", d => color(d.location))
// change the font and size for improved readability
.style("font", "16px helvetica neue bold")
.style("text-anchor","end")
.style("alignment-baseline","central")
.text(d => d.movement_name)
const artist = arcGroup.selectAll("key_artist")
.data(graphData.nodes)
.enter().append("text")
.attr("x",margin.left+300)
.attr("y", d => yScale(d.movement_name)+20)
.attr("fill","white")
.style("font", "16px helvetica neue")
.style("text-anchor","start")
.style("alignment-baseline","central")
.text(d => d.key_artist)

const year = arcGroup.selectAll("year")
.data(graphData.nodes)
.enter().append("text")
.attr("x",margin.left-190)
.attr("y", d => yScale(d.movement_name)+20)
.attr("fill","grey")
.style("font", "16px helvetica neue bold")
.style("text-anchor","start")
.style("alignment-baseline","central")
.text(d => d.year)
// This code builds up the SVG path element; see nodesAndArcs for details
function buildArc(d) {
let start = yScale(idToNode[d.source].movement_name);
let end = yScale(idToNode[d.target].movement_name);
const arcPath = ['M', margin.left+150, start, 'A', Math.abs(start - end)/2, ',', Math.abs(start-end)/2, 0,0,",",
start < end ? 1: 0, margin.left+150, end].join(' ');
return arcPath;
}

// create the arcs
const arcs = arcGroup.selectAll("arcs")
.data(graphData.links)
.enter().append("path")
.style("fill", "none")
.attr("stroke", "grey")
.attr("d", d => buildArc(d))

// When the user mouses over a node,
// add interactive highlighting to see connections between nodes
nodes.on('mouseover', function(d, i) {
// add animation on mouseover with expanding bigger nodes
// highlight only the selected node
d3.select(this).style("fill", "grey")
.transition()
.attr("r", radius*1.3);
arcs
// the arc color and thickness stays as the default unless connected to the selected node d
// notice how embedding the reference to arcs within nodes.on() allows the code to connect d to arcd
// this code iterates through all the arcs so we can compare each to the selected node d
.style('stroke', function (arcd) {
return arcd.source === d.id || arcd.target === d.id ? d => color2(d.relationship) : 'none';})
.style('stroke-width', function (arcd) {
return arcd.source === d.id || arcd.target === d.id ? 3 : 1;})
.attr("stroke-dasharray", function () {return this.getTotalLength()})
.attr("stroke-dashoffset", function () {return this.getTotalLength()})
// reveal the arcs
.transition()
// add duration and animation to highlight connection
.duration(1200)
.attr("stroke-dashoffset", 0)
year
.style('fill', function (yeard) {
return yeard.movement_name === d.movement_name ? 'grey' : 'none';});
artist
.style('fill', function (artistd) {
return artistd.movement_name === d.movement_name ? 'grey' : 'none';});

});
// remove highlighting when user mouse moves out of node by restoring default colors and thickness
nodes.on('mouseout', function (d, i) {
nodes.style("fill", d => color(d.location))
// add the animation
.transition()
.attr("r", radius);
arcs.style('stroke', 'grey');
arcs.style('stroke-width', 1);
artist.style("fill", "white");
// have year appear as default timeline for overview
year.style("fill", "grey");
});
return container.node();
}
Insert cell
Insert cell
Insert cell
Insert cell
FileAttachment("tumblr_mgu56brrH81s3xom1o1_1280 copy.gif").image()
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