Published
Edited
Jul 12, 2019
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
viz = {
generate
// Overall vars
const margin = 50
const height = 500 - margin * 2
const width = 1000 - margin * 2
const svgW = d3.select(DOM.svg(width + margin * 2, height + margin * 2))
const svg = svgW.append('g').attr('transform', `translate(${margin}, ${margin})`)
var x = d3.scaleLinear().domain([0,data.length]).range([0,width])
var y = d3.scaleLinear().domain(d3.extent(data)).range([height,0])
var results = []
for(var d of data){
var size = Math.random()*50
var start = [width*Math.random(), height*Math.random()]
var mid = [width*Math.random(), height*Math.random()]
var target = [width/2, height/2]
// Get the end as an offset from the target
var end = getAngledLineFromPoints(mid, target, size*2, target, false)[1]
results.push({
start: start,
mid: mid,
end: target,
x: start[0],
y: start[1],
size: size
})
}
results = results.sort((a,b) => b.size - a.size)
// results.forEach((d,i) => {
// if(i > 3){
// d.fx = width/2
// d.fy = height/2
// }
// })
var render = function(){
var simulation = d3.forceSimulation(results)
.force("x", d3.forceX(width / 2))
.force("y", d3.forceY(height / 2))
.force("collide", d3.forceCollide(function(d) {
return d.size + 2
}))
.stop()
for (var i = 0; i < 100; ++i) simulation.tick();
for (let d of results) {
// Get point that's half the width of the line going directly back from the new point to the initial point
// 1. Get the line width and half it
// 2. Get non-perp line from new end point to real end point
d.newMidpoint = getAngledLineFromPoints([d.x, d.y], [width/2, height/2], d.size*2, [d.x, d.y], false)[1]
var actualMidpoint = getMidpoint(d.start, d.newMidpoint)
var midMidpoint = getMidpoint(actualMidpoint, d.newMidpoint)
//getAngledLineFromPoints(getMidpoint(d.start, d.newMidpoint), d.end, halfDistance, getMidpoint(d.start, d.newMidpoint), true)
var halfDistance = getDistanceBetweenPoints(d.newMidpoint, d.end)/2
// d.newCenterpoint = d.end[1]
// d.newEndpointAnchor = getAngledLineFromPoints(d.newMidpoint, d.end, size, actualMidpoint, true)[1]
d.newCenterpoint = getAngledLineFromPoints(midMidpoint, d.newMidpoint, halfDistance, midMidpoint, true)
d.newCenterpoint = d.newMidpoint[1] > d.y ? d.newCenterpoint[1] : d.newCenterpoint[0]
d.initialX = d.x;
d.initialY = d.y
}
simulation
.force("x", d3.forceX(d => d.initialX).strength(0.2))
.force("y", d3.forceY(d => d.initialY).strength(0.2));
let nodeLevel1 = svg.selectAll('circle.l')
.data(results)
nodeLevel1.exit().remove();
let nodeLevel1Enter = nodeLevel1
.enter()
.append("circle")
.attr("cx", function(d) {
return width/2
})
.attr("cy", function(d) {
return height/2
})
.attr("r", function(d) {
return d.size
})
.attr('fill', 'blue')
.style("stroke", function(d) {
return 'green';
})
.transition().duration(1500)
.attr("cx", function(d) {
return d.x
})
.attr("cy", function(d) {
return d.y
})
let nodeLevel2 = svg.selectAll('circle.m')
.data(results)
.enter()
.append('circle')
.attr('r', 5)
.attr('fill', 'green')
.attr('cx', d => d.end[0])
.attr('cy', d => d.end[1])
.transition().duration(1500)
.attr("cx", function(d) {
return d.x
})
.attr("cy", function(d) {
return d.y
})
.transition().duration(1500)
.attr("cx", function(d) {
return d.newMidpoint[0]
})
.attr("cy", function(d) {
return d.newMidpoint[1]
})

let nodeLevel3 = svg.selectAll('circle.m')
.data(results)
.enter()
.append('circle')
.attr('r', 5)
.attr('fill', 'red')
.attr('cx', d => d.end[0])
.attr('cy', d => d.end[1])
.transition().duration(1500)
.attr("cx", function(d) {
return d.x
})
.attr("cy", function(d) {
return d.y
})
.transition().duration(1500)
.attr("cx", function(d) {
return d.newCenterpoint[0]
})
.attr("cy", function(d) {
return d.newCenterpoint[1]
})
svg.selectAll('path.cp').data(results).enter().append('path')
.attr('d', d => {
return `M ${d.start} L ${d.end}`
// return curvedPath([
// d.start,
// getMidpoint(d.start, d.end),
// d.end
// ], d.size)
})
.attr('stroke-width', 2)
.attr('stroke', 'orange')
.attr('fill', 'transparent')
.transition().duration(1500)
.attr('d', d => {
return `M ${d.start} Q ${getMidpoint(d.start, d.end)} ${[d.x, d.y]}`
// return curvedPath([
// d.start,
// getMidpoint(d.start, d.end),
// [d.x, d.y]
// ], d.size)
})
.transition().duration(1500)
.attr('d', d => {
return `M ${d.start} Q ${d.newCenterpoint} ${[d.x, d.y]}`
// return curvedPath([
// d.start,
// d.newCenterPoint,
// d.newMidpoint,
// [d.x, d.y],
// ], d.size)
})
const lineToMidpoint = svg.selectAll('line.l2m').data(results).enter()
.append('line')
.attr('stroke', 'black')
.attr('stroke-width', 1)
.attr('x1', d => d.end[0])
.attr('y1', d => d.end[1])
.attr('x2', d => d.end[0])
.attr('y2', d => d.end[1])
.transition().duration(1500)
.attr('x1', d => d.x)
.attr('y1', d => d.y)
.attr('x2', d => d.end[0])
.attr('y2', d => d.end[1])
.transition().duration(1500)
.attr('x1', d => d.newMidpoint[0])
.attr('y1', d => d.newMidpoint[1])
.attr('x2', d => d.end[0])
.attr('y2', d => d.end[1])
}
render()
svg.append('line')
.attr('x1', width/2-10)
.attr('y1', height/2-10)
.attr('x2', width/2+10)
.attr('y2', height/2+10)
.attr('stroke', 'black')
.attr('stroke-width', 2)
svg.append('line')
.attr('x1', width/2+10)
.attr('y1', height/2-10)
.attr('x2', width/2-10)
.attr('y2', height/2+10)
.attr('stroke', 'black')
.attr('stroke-width', 2)
return svgW.node()
}
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

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