xxxxxxxxxx
/******************
Code by Vamoss
Original code link:
https://openprocessing.org/sketch/2107128
Author links:
http://vamoss.com.br
http://twitter.com/vamoss
http://github.com/vamoss
******************/
const SIZE = Math.min(innerWidth, innerHeight);
const QUALITY = 1;//pixelDensity
const minRadius = 30;
const maxRadius = 100;
const gravityConstant = 1.1;
const forceConstant = 2000;
var motionVel = 10;
var center;
var ctx;
const types = ["Variáveis", "Cores", "Estrutura", "Parâmetros", "Funções", "Condições", "Aleatoriedade", "Tamanho", "Movimento", "Forma", "Padrões", "Repetição", "Posição"];
let typeFilter = -1;
//pan
var transX = 0,
transY = 0,
draggingMap = false;
var boundBox;
const STATES = { FREE: 0, OVER: 1, PRESSED: 2 };
const meatballs =
({ windowWidth, windowHeight, data, selectHandler }) =>
(sketch) => {
let colors = {};
colors["Variáveis"] = sketch.color(255, 99, 71);
colors["Cores"] = sketch.color(50, 205, 50);
colors["Estrutura"] = sketch.color(70, 130, 180);
colors["Parâmetros"] = sketch.color(255, 215, 0);
colors["Funções"] = sketch.color(138, 43, 226);
colors["Condições"] = sketch.color(255, 69, 0);
colors["Aleatoriedade"] = sketch.color(32, 178, 170);
colors["Tamanho"] = sketch.color(218, 112, 214);
colors["Movimento"] = sketch.color(34, 139, 34);
colors["Forma"] = sketch.color(255, 165, 0);
colors["Padrões"] = sketch.color(123, 104, 238);
colors["Repetição"] = sketch.color(0, 191, 255);
colors["Posição"] = sketch.color(240, 128, 128);
// as funções do nativas que o processing pega de hook tem
// que ser adicionadas ao objecto sketch passado como argumento
sketch.setup = () => {
var canvas = sketch.createCanvas(SIZE, SIZE);
sketch.pixelDensity(QUALITY);
ctx = canvas.drawingContext;
center = sketch.createVector(sketch.width/2, sketch.height/2);
/*
//generate random points
//descending from the original
var total = 600;
for (var i = 0; i < total; i++) {
data.push({
id: data.length,
parentId: i==0?null:0, //floor(random(data.length-1))
});
}
//descending from others
total = 3000;
for (var i = 0; i < total; i++) {
data.push({
id: data.length,
parentId: sketch.floor(sketch.random(data.length - 1)),
});
}
/**/
//add positions, state, color, total children and parent reference
data.forEach((d) => {
d.children = data.filter((d2) => d.id === d2.parentId);
d.parent = data.find((p) => p.id === d.parentId);
if (d.parentId !== null) {
if(d.x && d.y){
//for saved data
d.pos = sketch.createVector(
d.x * sketch.width,
d.y * sketch.height
);
d.color = sketch.color(d.r * 255, d.g * 255, d.b * 255);
}else{
//for new content (generated content enter this condition)
// d.pos = findSpot(d);
d.pos = sketch.createVector(
d.parent.pos.x + sketch.random(-20, 20),
d.parent.pos.y + sketch.random(-20, 20)
);
d.color = sketch.color(
sketch.constrain(d.parent.color._getRed() + sketch.random(-30, 30), 0, 255),
sketch.constrain(d.parent.color._getGreen() + sketch.random(-30, 30), 0, 255),
sketch.constrain(d.parent.color._getBlue() + sketch.random(-30, 30), 0, 255)
);
}
} else {
d.pos = sketch.createVector(sketch.width / 2, sketch.height / 2);
d.parent = null;
d.color = sketch.color(sketch.random(255), sketch.random(255), sketch.random(255));
if (d.id === 'initial') {
d.pos = sketch.createVector(
d.x * sketch.width,
d.y * sketch.height
);
d.color = sketch.color(d.r * 255, d.g * 255, d.b * 255);
}
}
d.tags = [];
d.tagsType = [];
if(d.analysis){
const analysis = JSON.parse(d.analysis);
d.label = analysis.analysis;
analysis.tags.forEach(t => {
colors[t].setAlpha(200);
d.tags.push(colors[t].toString());
d.tagsType.push(types.indexOf(t));
});
}
d.overColor = sketch.color(
sketch.constrain(d.color._getRed() + 60, 0, 255),
sketch.constrain(d.color._getGreen() + 60, 0, 255),
sketch.constrain(d.color._getBlue() + 60, 0, 255)
);
d.pressColor = sketch.color(
sketch.constrain(d.color._getRed() - 30, 0, 255),
sketch.constrain(d.color._getGreen() - 30, 0, 255),
sketch.constrain(d.color._getBlue() - 30, 0, 255)
);
d.colorString = d.color.toString();
d.overColorString = d.overColor.toString();
d.pressColorString = d.pressColor.toString();
d.virtualPos = {x:0, y:0}
d.force = {x:0, y:0}
d.grow = 0;
d.growVel = 0;
d.state = STATES.FREE;
});
//calculate size
var minValue = Math.min.apply(
Math,
data.map((d) => d.children.length)
);
var maxValue = Math.max.apply(
Math,
data.map((d) => d.children.length)
);
data.forEach(
(d) =>
(d.radius = d.originalRadius =
sketch.map(
d.children.length,
minValue,
maxValue,
minRadius,
maxRadius
))
);
};
sketch.draw = () => {
if(motionVel > 20)
motionVel -= 5
// apply force towards centre
for(let i = 0; i < data.length; i++){
let d = data[i];
d.virtualPos.x = d.pos.x - center.x
d.virtualPos.y = d.pos.y - center.y
//gravity
d.force.x = d.virtualPos.x * -gravityConstant
d.force.y = d.virtualPos.y * -gravityConstant
}
// apply repulsive force between nodes
for(let i = 0; i < data.length; i++){
const d = data[i];
for(let j = i+1; j < data.length; j++){
const d2 = data[j];
const dirX = d2.virtualPos.x - d.virtualPos.x
const dirY = d2.virtualPos.y - d.virtualPos.y
var magnitude = magSq(dirX, dirY);
if(magnitude == 0) magnitude = 0.001;
const forceX = (dirX / magnitude) * forceConstant
const forceY = (dirY / magnitude) * forceConstant
d.force.x -= forceX * (d2.originalRadius)/minRadius
d.force.y -= forceY * (d2.originalRadius)/minRadius
d2.force.x += forceX * (d.originalRadius)/minRadius
d2.force.y += forceY * (d.originalRadius)/minRadius
}
}
// apply forces applied by connections
for(let i = 0; i < data.length; i++){
const d = data[i]
if (d.parent !== null) {
let d2 = d.parent
const dirX = (d.virtualPos.x - d2.virtualPos.x)/((d2.children.length+10)/10)
const dirY = (d.virtualPos.y - d2.virtualPos.y)/((d2.children.length+10)/10)
d.force.x -= dirX
d.force.y -= dirY
d2.force.x += dirX
d2.force.y += dirY
}
}
//update
for(let i = 0; i < data.length; i++){
const d = data[i]
if (d.state === STATES.FREE){
d.virtualPos.x += d.force.x / motionVel
d.virtualPos.y += d.force.y / motionVel
d.pos.x = d.virtualPos.x + center.x
d.pos.y = d.virtualPos.y + center.y
}
}
/*
if(sketch.frameCount < 30)
return
else
sketch.noLoop();
*/
calculateBoundbox();
sketch.background(255);
//*
sketch.translate(sketch.width/2, sketch.height/2);
const w = boundBox.bottom-boundBox.top;
sketch.scale(SIZE/w);
sketch.translate(-sketch.width/2, -sketch.height/2);
/**/
sketch.translate(transX, transY);
//draw connections
ctx.lineWidth = 8;
data.forEach((d) => {
if(!sketch.isVisible(d))
return
if (d.parent !== null) {
if(d.tags.length>0){
const a = Math.atan2(d.pos.y - d.parent.pos.y, d.pos.x - d.parent.pos.x) + sketch.PI;
for(var z = 0; z < d.tags.length; z++){
if(typeFilter != -1 && d.tagsType[z] != typeFilter) continue;
let aa = a - (z * (z%2==0 ? 1 : -1) * 0.5);
//let aa = z / d.tags.length * sketch.TWO_PI;
// let aa = a+(z-d.tags.length/2)*Math.PI;
let xx = sketch.cos(aa) * d.radius * 1 + d.pos.x;
let yy = sketch.sin(aa) * d.radius * 1 + d.pos.y;
let vertices = [
{x: d.parent.pos.x, y: d.parent.pos.y},
{x: xx, y: yy},
{x: d.pos.x, y: d.pos.y},
];
var di = magSq(d.pos.x - d.parent.pos.x, d.pos.y - d.parent.pos.y);
var tension = sketch.map(di, 40*40, 1300*1300, 1, 10);
//createHobbyBezier by @arnoson https://github.com/arnoson/hobby-curve
var hobbyPoints = createHobbyBezier(vertices, { tension: tension, cyclic: false });
ctx.strokeStyle = d.tags[z];
ctx.beginPath();
ctx.setLineDash([]);
ctx.moveTo(vertices[0].x, vertices[0].y);
hobbyPoints.forEach(({ startControl, endControl, point }) => {
ctx.bezierCurveTo(startControl.x, startControl.y, endControl.x, endControl.y, point.x, point.y);
});
ctx.stroke();
/*
ctx.strokeStyle = d.tags[z];
ctx.beginPath();
ctx.moveTo(d.parent.pos.x, d.parent.pos.y);
ctx.lineTo(xx, yy);
ctx.lineTo(d.pos.x, d.pos.y);
ctx.stroke();
*/
}
}else{
ctx.strokeStyle = "grey";//d.colorString;
ctx.beginPath();
ctx.setLineDash([5, 15]);
ctx.moveTo(d.parent.pos.x, d.parent.pos.y);
ctx.lineTo(d.pos.x, d.pos.y);
ctx.stroke();
}
}
});
ctx.setLineDash([]);
//draw circles
ctx.lineWidth = 8;
data.forEach((d, i) => {
if(!sketch.isVisible(d))
return
ctx.strokeStyle = "black";
if (d.state === STATES.FREE) ctx.fillStyle = "rgb(255, 255, 255)";
else if (d.state === STATES.OVER) ctx.fillStyle = d.overColorString;
else if (d.state === STATES.PRESSED) ctx.fillStyle = d.pressColorString;
var pulse = 0;
if(i === data.length-1){
pulse = (Math.sin(sketch.frameCount/10)+1)/2 * 30 + 20;
}
d.grow = sketch.constrain(d.grow + d.growVel, 0, 1);
var ease = quarticInOut; //d.growVel > 0 ? elasticOut : elasticIn;
d.radius = d.originalRadius + ease(d.grow) * 20 + pulse;
ctx.beginPath();
ctx.ellipse(d.pos.x, d.pos.y, d.radius * 0.5 - 4, d.radius * 0.5 - 4, 0, 0, Math.PI * 2);
ctx.stroke();
ctx.fill();
});
/*
//draw texts
sketch.noStroke();
sketch.fill(0);
sketch.textAlign(sketch.CENTER, sketch.CENTER);
data.forEach(d => {
var size = d.radius * 0.8;
sketch.textSize(size);
// sketch.text(d.id, d.pos.x, d.pos.y + size/10);
sketch.text(d.children.length, d.pos.x, d.pos.y + size/10);
})
/**/
};
sketch.isVisible = d => {
return true;
const border = -50;
return d.pos.x > border-transX && d.pos.x < sketch.width-border-transX
&& d.pos.y > border-transY && d.pos.y < sketch.height-border-transY
}
sketch.mouseMoved = () => {
if(!ctx) return;
var found = false;
data.forEach((d) => {
var distance = sketch.dist(
d.pos.x,
d.pos.y,
sketch.mouseX - transX,
sketch.mouseY - transY
);
if (distance < d.radius / 2 && !found) {
d.state = STATES.OVER;
d.growVel = 0.025;
found = true;
sketch.cursor(sketch.HAND);
} else {
d.state = STATES.FREE;
d.growVel = -0.05;
}
});
if (!found) {
sketch.cursor(sketch.ARROW);
}
};
sketch.keyPressed = () => {
typeFilter++;
console.log(types[typeFilter]);
}
/*
sketch.mouseDragged = () => {
if (draggingMap) {
transX += sketch.mouseX - sketch.pmouseX;
transY += sketch.mouseY - sketch.pmouseY;
//limit to boundBox
transX = sketch.min(transX, -boundBox.left);
transX = sketch.max(transX, sketch.width - boundBox.right);
transY = sketch.min(transY, -boundBox.top);
transY = sketch.max(transY, sketch.height - boundBox.bottom);
} else {
data.forEach((d) => {
if (d.state === STATES.PRESSED) {
d.dragged = true;
d.pos.x += sketch.mouseX - sketch.pmouseX;
d.pos.y += sketch.mouseY - sketch.pmouseY;
}
});
}
};
sketch.mousePressed = () => {
var notFound = data.every((d) => {
var distance = sketch.dist(
d.pos.x,
d.pos.y,
sketch.mouseX - transX,
sketch.mouseY - transY
);
if (distance < d.radius / 2) {
d.state = STATES.PRESSED;
d.dragged = false;
return false;
}
return true;
});
draggingMap = notFound;
};
sketch.mouseReleased = () => {
draggingMap = false;
data.forEach((d) => {
if (d.state === STATES.PRESSED && !d.dragged) {
if (typeof selectHandler === "function") selectHandler(d);
}
});
};
*/
function calculateBoundbox() {
var minX = sketch.width / 2;
var maxX = sketch.width / 2;
var minY = sketch.height / 2;
var maxY = sketch.height / 2;
data.forEach((d) => {
minX = sketch.min(minX, d.pos.x);
maxX = sketch.max(maxX, d.pos.x);
minY = sketch.min(minY, d.pos.y);
maxY = sketch.max(maxY, d.pos.y);
});
var margin = 100;
boundBox = {
left: sketch.min(minX - margin, 0),
top: sketch.min(minY - margin, 0),
right: sketch.max(maxX + margin, sketch.width),
bottom: sketch.max(maxY + margin, sketch.height),
};
}
// https://idmnyu.github.io/p5.js-func/
function quarticInOut(x) {
if (x < 0.5) {
return 8 * x * x * x * x;
} else {
var v = x - 1;
return -8 * v * v * v * v + 1;
}
}
function mag(x, y){
return Math.sqrt(x * x + y * y);
}
function magSq(x, y){
return x * x + y * y;
}
};
fetch('data.json')
.then((response) => response.json())
.then((json) => {
var sketch = new p5(
meatballs({
windowWidth: window.innerWidth,
windowHeight: window.innerHeight,
data: json
})
);
});