Insert cell
Insert cell
chart = {
const links = data.links.map(d => Object.create(d));
const nodes = data.nodes.map(d => Object.create(d));
const forceY = d3.forceY((d, i) => {
//var yPosition;
var yScale;
var iLargest = 0;
if (d.color == 1) {
iLargest = (i>iLargest) ? i : iLargest;
/* if (i<=50)
yPosition=-500;
else if (i>50 && i<=100)
yPosition=-400;
else if (i>100 && i<=150)
yPosition=-200;
else if (i>150 && i<=200)
yPosition=0;
else if (i>200 && i<=250)
yPosition=200;
else
yPosition=300; */
}
//else { yPosition = 500; }
yScale = d3.scaleLinear()
.domain([0,200])
.range([300-height/2, 500]);
//const position = d.color == 1 ? 200 : 800;
//console.log('yPosition', yPosition);
var yPosition = (d.color == 1) ? yScale(i) : 800;
console.log ('yPosition = ', yPosition, i);
return yPosition;
}).strength(0.2)
//const forceX = d3.forceX(width/2).strength(0.015)
var xScale = d3.scalePoint()
.domain([3, 4, 5, 6, 7, 8, 9])
.range([200-width/2, width/2 - 200]);
const forceX = d3.forceX((d) => d.color == 1 ? 0 : xScale(d.color)).strength(0.15);

const simulation = d3.forceSimulation(nodes)
.force("link", d3.forceLink(links).id(d => d.id).distance(0).strength(0))
.force("x", forceX)
.force("y", forceY)
.force("collide", d3.forceCollide().radius(radius))
.force("charge", d3.forceManyBody().strength(5))
.force("center", d3.forceCenter(0, 500));
simulation.on("tick", ticked);

const svg = d3.select(DOM.svg(width, height))
.attr("viewBox", [-width/2, -height/2, width, height]);
//.attr("viewBox", [-width / 2, -height, width, height*2]);
const link = svg.append("g")
.selectAll(".line")
.data(links)
.enter()
.append("line")
.attr("stroke", "#000")
.attr("stroke-opacity", 0.6)
// .attr("stroke-width", 1.5);
.attr("stroke-width", d => Math.sqrt(d.value))
.attr('fill','none');

const node = svg.selectAll('n') // from https://stackoverflow.com/questions/42250305/d3-v4-missing-node
.data(nodes)
.enter().append('g')
.attr('transform', ({ x, y }) => `translate(${x}, ${y})`);
var maxradius = 0;
node.each(function (d, i) {
console.log('d in each function is: ', d);
const _this = d3.select(this)
//var c = "svgGradient";
const circle = _this.append('circle')
.attr('r', radius)
.attr('fill', color)
.attr("stroke", "#fff")
.classed("poet", function (d) { (d.color < 2); })
if( d.color < 2 ) {
d.fixed = true;
d.x = Math.floor(i/12);
d.y = i % 12;
var r1=(d.id).split("-")[0].length;
maxradius=(r1>maxradius) ? r1 : maxradius;
const label = _this.append('foreignObject')
.attr('x',(0-r1*10/2))
.attr('y',-10)
// .attr('width',(poetradius-5)*2)
.attr('height',(poetradius-5)*2)
.attr('width',r1*10+5)
.append('xhtml:div')
.style('line-height','100%')
.style('font-family', "'CapitaFont', Helvetica, Arial, sans-serif")
.style('text-align','center')
.style('font-style','bold')
.style('text-transform', 'capitalize')
.style('color', '#fff')
.style('cursor', 'default')
.style('font-size', '1.7rem')
// .style('flex-shrink', '0')
.attr('text-anchor', 'middle')
.attr('title', d.title)
.html((d.id).split("-")[0])
}
});
//.call(drag(simulation)); //will removed this - not needed for fpp
console.log(maxradius);
//node.attr('r', maxradius*10);
node.append("title")
.text(d => d.title);
var nodeClicked = false;

/* from Hover Effect Autism Douban example */
node.transition()
.duration(300)
.ease(d3.easeLinear);
link.transition()
.duration(300)
.ease(d3.easeLinear);
function focus(d) {
if (!nodeClicked) { // if poet node has not been clicked
var nodeIndex=[];
var linkIndex=[];

var isLink = d.hasOwnProperty('source')
if(isLink){
nodeIndex.push(d.source.index)
nodeIndex.push(d.target.index)
linkIndex.push(d.index)
}else{
nodeIndex.push(d.index)
links.forEach(function(e,i){
if(e.source.index == d.index || e.target.index == d.index){
linkIndex.push(i)
nodeIndex.push(e.source.index)
nodeIndex.push(e.target.index)
}
})
}
node.transition()
.duration(300)
.ease(d3.easeLinear)
.style("opacity", function(o){
// node.transition(t).style("opacity", function(o){
// return nodeIndex.includes(o.index)?"block":"none";
return nodeIndex.includes(o.index)? 1:0.0; //originally was 0.1
})
link.transition()
.duration(300)
.ease(d3.easeLinear)
.style("opacity", function(o){
// link.transition(t).style("opacity", function(o){
// return linkIndex.includes(o.index)?"block":"none";
return linkIndex.includes(o.index)? 1:0.0;
})
}
}

function unfocus() {
if (!nodeClicked) { // if poet node has not been clicked
// node.attr("display", "block");
node.transition()
.duration(750)
.ease(d3.easeLinear)
.style("opacity", 1);
// link.attr("display", "block");
link.transition()
.duration(750)
.ease(d3.easeLinear)
.style("opacity", 1);
}
}
function gotourl(d) {
if (d.color > 2) { // only nonpoet and nonpoem nodes are clickable
window.open( d.links_to, "_blank");
}
else {
nodeClicked = !nodeClicked; // toggle nodeClicked
unfocus(d);
}
}
function checkFocus(d) {
if (nodeClicked) {
nodeClicked = false;
unfocus(d);
}
}
node.on("mouseover", focus)
.on("mouseout", unfocus)
.on("click", gotourl);
link.on("mouseover",focus)
.on("mouseout",unfocus);
//svg.on("click", checkFocus);
// node.transition(t)
// link.transition(t)
function ticked() {
// node
// .attr( "transform", d => `translate(${d.x}, ${d.y})`);
/* node
.attr( "transform", function (d,i) {
console.log('in ticked d: ', d);
console.log('in ticked i: ', i);
if (d.color > 1) {
return 'translate(${Math.floor(i/12)}, ${i%12})';
}
else {
return d => `translate(${d.x}, ${d.y})`;
}
} ); */
// }
// else {
node
.attr( "transform", d => `translate(${d.x}, ${d.y})`);
//.attr('transform', ({ x, y }) => `translate(${x}, ${y})`);
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)
//.attr("d",positionLink); no shape to the path so this is not needed.
}

// Quote from https://beta.observablehq.com/@bumbeishvili/d3-dag-topological
function positionLink(d) {
var offset = 110;

var midpoint_x = (d.source.x + d.target.x) / 2;
var midpoint_y = (d.source.y + d.target.y) / 2;

var dx = (d.target.x - d.source.x);
var dy = (d.target.y - d.source.y);

var normalise = Math.sqrt((dx * dx) + (dy * dy));

var offSetX = midpoint_x + offset*(dy/normalise);
var offSetY = midpoint_y - offset*(dx/normalise);

return "M" + d.source.x + "," + d.source.y +
"S" + offSetX + "," + offSetY +
" " + d.target.x + "," + d.target.y;
}
/* end Hover Effect Autism Douban example */
invalidation.then(() => simulation.stop());

return svg.node();
}
Insert cell
function forceSimulation(nodes, links) { // not used: I'm using d3.forceSimulation instead
console.log("************* IN FORCESIMULATION FUNCTION ************************");
var svg = d3.select("svg")
var radius = height/2;
//var radius = height/2.8;
return d3.forceSimulation(nodes)
.force("link", d3.forceLink(links)
.id(d => d.id)
.strength(0)
) //With id accessor, you can use named sources and targets:
.force("charge", d3.forceManyBody()
.strength( d => -2*scaleLog_radius(d.value) )
)
.force("x",d3.forceRadial(radius))
.force("y",d3.forceRadial(radius))
}
Insert cell
data = FileAttachment("fppjsonNoPoems-fpp@1.json").json()
Insert cell
height=width*1.5;
Insert cell
poetradius = 50;
Insert cell
radius = {
const list = [50,20,10,10,10,10,10,10,10]
return d => list[d.color - 1];
}
Insert cell
color = {
const list = ['#000000','#009988','#332288','#ee3377','#ee7733','#33bbee','#cc3311','#ddaa33', '#999999']
// const scale = d3.scaleOrdinal(d3.schemeCategory10);
return d => list[d.color - 1];
}
Insert cell
scaleLog_radius= {
var scale = d3.scaleLog()
.domain([10,200])
.range([20,70])
.base(10)
return d=>scale(d)
}
Insert cell
drag = simulation => {
function dragstarted(d) {
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
}
function dragended(d) {
if (!d3.event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
return d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended);
}
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