Published
Edited
Jul 26, 2020
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
chart = {
/*
****************
Extracts links and nodes from data
****************
*/
const links = data.links.map(d => Object.create(d));
const nodes = data.nodes.map(d => Object.create(d));

/*
****************
Simulation Parameters. Check out D3 force-direct graph documentation for more info
****************
*/
const simulation = d3.forceSimulation(nodes)
.force("link", d3.forceLink(links).id(d => d.id).distance(50))
.force("center", d3.forceCenter(width / 2, height / 2))
.force("charge", d3.forceManyBody().strength(attraction)) //this variable controlled by slider above
.force("collide", d3.forceCollide().radius(radius*distance))
.force("y", d3.forceY().strength(.01));

/*
****************
Create SVG that holds image. 'width' is automatically detected by Observable. Height is defined below
****************
*/
const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height])


/*
************
Create Markers. Code adapted from:
https://observablehq.com/@xianwu/force-directed-graph-network-graph-with-arrowheads-and-lab
**************
*/
//appending little triangles, path object, as arrowhead
//The <defs> element is used to store graphical objects that will be used at a later time
//The <marker> element defines the graphic that is to be used for drawing arrowheads or polymarkers on a given <path>, <line>, <polyline> or <polygon> element.
/*
svg.append('defs').append('marker')
.attr("id",'arrowhead')
.attr('viewBox','-0 -5 10 10') //the bound of the SVG viewport for the current SVG fragment. defines a coordinate system 10 wide and 10 high starting on (0,-5)
.attr('refX',radius*2) // x coordinate for the reference point of the marker. If circle is bigger, this need to be bigger.
.attr('refY',0)
.attr('orient','auto')
.attr('markerWidth',3)
.attr('markerHeight',3)
.attr('xoverflow','visible')
.append('svg:path')
.attr('d', 'M 0,-5 L 10 ,0 L 0,5')
.attr('fill', 'black')
.style('stroke','none')
.style("opacity", .6);

*/
// black marker - normal mode
svg.append("defs").selectAll("marker")
.data(nodes)
.join("marker")
.attr("id", 'arrowhead')
.attr("viewBox", "0 -5 10 10")
.attr("refX", 10)// where arrow is starting
.attr("refY", 0)
.attr("markerWidth", 3)
.attr("markerHeight", 3)
.attr("orient", "auto")
.append("path")
.attr("fill", "black")
.attr("d", "M0,-5L10,0L0,5");
//Blue marker - mouseover
svg.append("defs").selectAll("marker")
.data(nodes)
.join("marker")
.attr("id", 'arrowhead-blue')
.attr("viewBox", "0 -5 10 10")
.attr("refX", 10)// where arrow is starting
.attr("refY", 0)
.attr("markerWidth", 3)
.attr("markerHeight", 3)
.attr("orient", "auto")
.append("path")
.attr("fill", "blue")
.attr("d", "M0,-5L10,0L0,5");
//red marker - selected and changed
svg.append("defs").selectAll("marker")
.data(nodes)
.join("marker")
.attr("id", 'arrowhead-red')
.attr("viewBox", "0 -5 10 10")
.attr("refX", 10)// where arrow is starting
.attr("refY", 0)
.attr("markerWidth", 3)
.attr("markerHeight", 3)
.attr("orient", "auto")
.append("path")
.attr("fill", "red")
.attr("d", "M0,-5L10,0L0,5");
//Magenta marker: changed
svg.append("defs").selectAll("marker")
.data(nodes)
.join("marker")
.attr("id", 'arrowhead-magenta')
.attr("viewBox", "0 -5 10 10")
.attr("refX", 10)// where arrow is starting
.attr("refY", 0)
.attr("markerWidth", 3)
.attr("markerHeight", 3)
.attr("orient", "auto")
.append("path")
.attr("fill", "magenta")
.attr("d", "M0,-5L10,0L0,5");
/*
************
Initialize Links
**************
*/


const link = svg.append("g")
.attr("fill", "none")

.attr("stroke-opacity", 0.4)

.selectAll("path")
.data(links)
.join("path")
.attr("stroke-width", link_thickness )
.attr('marker-end',function(d){
if( d.changed_edge ==1){
return 'url(#arrowhead-magenta)';
}
else{
return 'url(#arrowhead)'
}
})
.attr("stroke", color_link);
link.style('stroke', function(d){
if (d.changed_edge == 1){
return 'black';
};
});
// Prior function for changing edges before adding communities
/*function(d){
if( d.changed_edge == 1){
return "magenta";
}
else{
return color_link // Add the switch to community-based coloring here?
}}); */


link.append("title")
.text(d => d.weight);


/*
const link = svg.append("g")
.selectAll("line")
.data(data.links)
.attr("class", "link")
.style("stroke-width", function (d) {
return Math.sqrt(d.value);
})
.attr("x1", function (d) {
return d.source.x;
})
.attr("y1", function (d) {
return d.source.y;
})
.attr("x2", lineX2)
.attr("y2", lineY2)
.attr("marker-end", "url(#arrowGray)");
*/

/*
************
Initialize Nodes
**************
*/



const node = svg.append("g")
.attr("stroke", "#fff")
.attr("stroke-width", radius/20)
.selectAll("circle")
.data(nodes)
.join("circle")
.attr("r", radius) //This variable corresponds to slider above
.attr("fill", color)
.attr("opacity", 1)
.call(drag(simulation));

/*
************
Initialize Text
**************
*/
var texts = svg.selectAll(".texts")
.data(nodes)
.enter()
.append("text")
.attr("font-family", "sans-serif")
.attr("dx", -0.8*radius)
.attr("dy", "0.12em")
.attr("font-size", radius*.9)
.attr("fill", "white")
.text( d => d.pitch )
.call(drag(simulation))
.on("click", clickedtext);

node.append("title")
.text(d => d.id);
/*
************
Animation: Play note and animate when text is clicked
**************
*/
function clickedtext(d) {
if (d3.event.defaultPrevented) return; // dragged
// play(d.data.node.note);
d3.select(this).transition()
.attr("fill", "black")
.attr("r", radius * 2)
.transition()
.attr("r", radius)
.attr("fill", "white");
if(d.id != "start" && d.id != "end"){
play(d.pitch);
}
}
/*
**************
Link MouseClick Functionality.
-Allows user to choose link and change weight
**************
*/
link.on('click',function (d) {
d3.select(this)
.attr('stroke', 'red')
.attr("stroke-opacity", .4)
.attr('marker-end','url(#arrowhead-red)');
mutable highlighted_edge = `{${d.source.id} , ${d.target.id}}`
mutable weight = `${d.weight}`

});
/*
**************
Link Mouseover Functionality.
-should turn selected link blue (only works before mousing over node?)
-show tool tip in top right corner with link name
**************
*/
link.on('mouseover',function (d) {
d3.select(this)
.attr('stroke', 'blue')
.attr("stroke-opacity", .4)
.attr('marker-end','url(#arrowhead-blue)')
.attr("stroke-width", 5);
mutable highlighted_edge = `{${d.source.id} , ${d.target.id}}`
});
link.on('mouseout',function (d) {
d3.select(this)
//.attr('stroke', '#999')
.attr("stroke-opacity", 0.4)
.attr("stroke-width", link_thickness )
.attr('marker-end',function(d){
if( d.changed_edge ==1){
return 'url(#arrowhead-magenta)';
}
else{
return 'url(#arrowhead)'
}
})
.attr("stroke", color_link);
link.style('stroke', function(d){
if (d.changed_edge == 1){
return 'black';
}});
/*
.style("stroke", function(d){
if( d.changed_edge == 1){
return "magenta";
}
else{
return color_link
}});
*/
d3.select("#tooltip").remove();
});

link.on('click',function (d) {
d3.select(this)
.attr('stroke', 'red')
.attr("stroke-opacity", .7)
.attr('marker-end','url(#arrowhead-red)');
mutable chosen_source = `${d.source.id}`;
mutable chosen_target = `${d.target.id}`;
mutable weight = `${d.weight}`

});
/*
**************
Describes Simulation
**************
*/
simulation.on("tick", () => {
link.attr("d", linkArc);
node.attr("transform", d => `translate(${d.x},${d.y})`);
texts.attr("transform", d => `translate(${d.x},${d.y})`);
});



/*
simulation.on("tick", function () {
link
.attr("x1", d => d.source.x)
.attr("y1", d => d.source.y)
.attr("x2", lineX2)
.attr("y2", lineY2)
.attr("r", d=>Math.hypot(d.target.x - d.source.x, d.target.y - d.source.y));

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

/*
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);
node
.attr("cx", function(d) { return d.x = Math.max(radius, Math.min(width - radius, d.x)); })
.attr("cy", function(d) { return d.y = Math.max(radius, Math.min(height - radius, d.y)); });
texts
.attr("x", function(d) { return d.x = Math.max(radius, Math.min(width - radius, d.x)); })
.attr("y", function(d) { return d.y = Math.max(radius, Math.min(height - radius, d.y)); });
});


*/
/*
**************
Not 100% sure what this does
**************
*/

invalidation.then(() => simulation.stop());

/*
**************
Return function - houses update for random walk
-only activated when 'pnt' changes
**************
*/

return Object.assign(svg.node(), {
update({nodes, links}, c) {

// Make a shallow copy to protect against mutation, while
// recycling old nodes to preserve position and velocity.
const old = new Map(node.data().map(d => [d.id, d]));
nodes = nodes.map(d => Object.assign(old.get(d.id) || {}, d));
links = links.map(d => Object.assign({}, d));

//new variable
const pnt = random_walk[c];
node = node
.data(nodes, d => d.id)
.join(enter => enter.append("circle")
.attr("fill", d => color(color)))
.attr("r", function (n) {
if(n.id === pnt.id){
return radius*1.5;
}
else{
return radius;
}
});




link = link
.data(links, d => [d.source, d.target])
.join("line");

simulation.nodes(nodes);
simulation.force("link").links(links);
simulation.alpha(1).restart();

}
});
}
Insert cell
update = chart.update(data, counter)
Insert cell
md`# Input Parameters`
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
md`# Data and Random Walk`
Insert cell
Insert cell
Insert cell
md` # Other Parameters`
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
function linkArc(d) {
const r = Math.hypot(d.target.x - d.source.x, d.target.y - d.source.y);
return `
M${d.source.x},${d.source.y}
A${r},${r} 0 0,1 ${lineX2(d)},${lineY2(d)}
`;
}
Insert cell
md ` Section for defining and testing Synthesizer. Code adapted from https://observablehq.com/@tmcw/playing-with-tone-js`
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
md `Imports`
Insert cell
d3 = require("d3@5")
Insert cell
import {slider} from "@jashkenas/inputs"
Insert cell
Insert cell
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