*
padding 0
margin 0
body, html
height 100%
width 100%
overflow hidden
background #697ba7
background radial-gradient(ellipse at center, #001B36 0%, #000 100%)
h1
font-weight 300
font-size 3em
line
fill none
stroke steelblue
stroke-width 1.5
circle
fill #fff
stroke #000
stroke-width .5
View Compiled
(function(env){
const PI2 = 2 * Math.PI;
const radius = (r) => 1 + Math.sqrt(16 * r);
const isNaN = (v) => Number.isNaN(Number(v));
let lastEvent;
class Canvas {
constructor(width, height) {
this.canvas = document.createElement("canvas");
this.canvas.width = width;
this.canvas.height = height;
this.ctx = this.canvas.getContext("2d");
document.body.appendChild(this.canvas);
let zoom = this.zoom = d3.behavior.zoom()
.size([width, height])
.scaleExtent([.2, 1])
.on("zoom", () => {
lastEvent = d3.event;
});
this.d3canvas = d3.select(this.canvas).call(zoom)
}
get width() {
return this.canvas.width
}
get height() {
return this.canvas.height
}
resize(width, height) {
if (this.height == height && this.width == width)
return;
this.canvas.width = +width;
this.canvas.height = +height;
this.zoom.size([+width, +height]);
}
}
class Point {
constructor({
x,
y,
color,
radius
}) {
this.x = x;
this.y = y;
this.color = color;
this.radius = radius
}
}
class Catalog extends Point {
constructor({
x,
y,
radius,
name,
color,
bgcolor,
parent
}) {
super({
x,
y,
color,
radius
});
this.name = name;
this.bgcolor = bgcolor;
this.parent = parent;
this.weight = 1;
}
render(ctx) {
let r = this.radius;
ctx.fillStyle = this.bgcolor;
ctx.strokeStyle = this.color;
ctx.beginPath();
r = radius(r);
ctx.arc(this.x, this.y, r, 0, PI2);
ctx.closePath();
ctx.stroke();
ctx.fill();
}
}
class Link {
constructor({source, target, color}) {
this.source = source;
this.target = target;
this.color = color
}
render(ctx) {
ctx.fillStyle = "rgba(255, 255, 255, 0)";
ctx.strokeStyle = this.color;
ctx.beginPath();
ctx.moveTo(this.source.x, this.source.y);
ctx.lineTo(this.target.x, this.target.y);
ctx.closePath();
ctx.stroke();
}
}
class Force {
constructor(width, height, nodes, links) {
let collide5, cluster;
this.force = d3.layout.force()
.charge(function(d) {
var l = radius(d.radius) + radius(d.parent && d.parent.radius || 0);
return -l * 20//100;
})
// .friction(.9)
//.gravity(-.001)
.linkDistance(60)
.nodes(nodes)
.links(links)
.size([width, height])
}
size(width, height) {
this.force.size([width, height]);
return this;
}
get nodes() {
return this.force.nodes()
}
set nodes(value) {
this.force.nodes(value)
}
tick(value) {
if (!arguments.length)
return this.force.on("tick");
this.force.on("tick", value);
return this;
}
start() {
this.force.start();
return this;
}
stop() {
this.force.stop();
return this;
}
}
let w = window.innerWidth
, h = window.innerHeight
, p = 10
, max = 60
, nodes = [new Catalog({
x: w / 2,
y: h / 2,
radius: 0,
color: 'hsla(10,80%,75%,.8)',
bgcolor: "rgba(255, 255, 255, 1)"
})]
, links = []
;
let force = (new Force(w, h, nodes, links))
.tick(updatePositions)
.start()
;
let c = new Canvas(w, h);
d3.timer(function anim() {
if (w != env.innerWidth || h != env.innerHeight)
force.size(env.innerWidth, env.innerHeight);
w = env.innerWidth;
h = env.innerHeight;
c.resize(w, h);
let ctx = c.ctx;
ctx.save();
ctx.globalCompositeOperation = "destination-out";
ctx.fillStyle = "rgba(0, 0, 0, 1)";
ctx.fillRect(0, 0, w, h);
if (lastEvent) {
let tr = c.zoom.translate();
ctx.translate(tr[0], tr[1]);
let sc = c.zoom.scale();
ctx.scale(sc, sc);
}
// ctx.globalCompositeOperation = 'lighter';
ctx.globalCompositeOperation = 'source-over';
let l = links.length, item;
while(l--){
item = links[l];
item.render(ctx);
}
// ctx.globalCompositeOperation = 'source-over';
l = nodes.length;
let maxX = -Infinity, minX = Infinity;
let maxY = -Infinity, minY = Infinity;
while(l--){
item = nodes[l];
maxX = Math.max(maxX, item.x + radius(item.radius));
minX = Math.min(minX, item.x - radius(item.radius));
maxY = Math.max(maxY, item.y + radius(item.radius));
minY = Math.min(minY, item.y - radius(item.radius));
item.render(ctx);
}
let z = c.zoom.scale()
, tr = c.zoom.translate()
, zh = z, zw = z
;
if (false) {
ctx.beginPath();
ctx.strokeStyle = "red";
ctx.lineWidth = 5;
ctx.moveTo(minX, nodes[0].y);
ctx.lineTo(maxX, nodes[0].y);
ctx.stroke();
ctx.closePath();
ctx.beginPath();
ctx.strokeStyle = "blue";
ctx.lineWidth = 5;
ctx.moveTo(nodes[0].x, minY);
ctx.lineTo(nodes[0].x, maxY);
ctx.stroke();
ctx.closePath();
ctx.beginPath();
ctx.strokeStyle = "green";
ctx.lineWidth = 5;
ctx.strokeRect(-tr[0]/z + 1, -tr[1]/z + 1, w/z - 1, h/z - 1);
ctx.stroke();
ctx.closePath();
ctx.beginPath();
ctx.strokeStyle = "yellow";
ctx.lineWidth = 5;
ctx.strokeRect(0, 0, w*z - 1, h*z - 1);
ctx.stroke();
ctx.closePath();
}
if (maxX > w || minX < 0) {
maxX = maxX > w ? maxX : w;
minX = minX < 0 ? minX : 0;
zw = w / (2 * Math.max(maxX - nodes[0].x, nodes[0].x - minX));
}
if (maxY > h || minY < 0) {
maxY = maxY > h ? maxY : h;
minY = minY < 0 ? minY : 0;
zh = h / (2 * Math.max(maxY - nodes[0].y, nodes[0].y - minY));
}
z = d3.min([z, zh, zw]);
let minZ = c.zoom.scaleExtent()[0];
z = z < minZ ? minZ : z;
let w4 = (w/2 - nodes[0].x*z)
, h4 = (h/2 - nodes[0].y*z)
;
c.d3canvas.call(c.zoom.scale(z).translate([w4, h4]).event);
ctx.restore();
});
let t = 0;
d3.timer(function calc() {
if (t++ % 20)
return false;
let source = nodes[~~(Math.random() * nodes.length)],
bud = new Catalog({
x: source.x + Math.random() - .5,
y: source.y + Math.random() - .5,
parent: source,
radius: ~~(Math.random() * 20),
color: 'hsla(150, 80%, 100%, 1)',
bgcolor: "rgba(255, 255, 255, 1)"
});
inflate(bud);
links.push(new Link({
source: source,
target: bud,
color: 'rgba(0, 255, 0, 1)'//'hsla(10,80%,75%,.8)'
}));
nodes.push(bud);
force.start();
return nodes.length >= max
});
function inflate(d) {
while (d = d.parent) d.weight += 50;
}
function updatePositions(e) {
force.force.alpha(.1)//.resume()
}
})(window)
View Compiled
This Pen doesn't use any external CSS resources.