chart = {
let resetState = true
const SCREEN_WIDTH = width;
const SCREEN_HEIGHT = width;
const RESOLUTION = window.devicePixelRatio;
const NODE_RADIUS = 5;
const NODE_HIT_WIDTH = 5;
const NODE_HIT_RADIUS = NODE_RADIUS + NODE_HIT_WIDTH;
const TEXTURE_COLOR = 0xffffff;
const CIRCLE = 'CIRCLE';
let tick = 0
let color = ["0xf7524f", "0x4561cc", "0xf8bc2e", "0x17c3b2", "0x9336fd", "0xff928b"]
let IDs = ["1", "2", "3", "4", "5", "6"]
let config = {}
let attrs = {}
IDs.map((d,i)=>{
config[d] = null
attrs[d] = {idx: i, color: color[i], scale: i>=5 ? 2 : 1, offset: i===0 ? 110 : 90}
})
const container = html`<div style="position: relative"></div>`;
const positions = runLayout(nodes)
nodes.forEach((nodeData,i) => {
const position = positions[i][1];
let randX = getRandomArbitrary(-10, 10)
let randY = getRandomArbitrary(-10, 10)
nodeData.id = i
nodeData.x1 = position.x + getRandomArbitrary(-1000, 1000) + randX;
nodeData.y1 = position.y + getRandomArbitrary(-1000, 1000) + randY;
nodeData.x = position.x + randX
nodeData.y = position.y + randY
nodeData.offset = 0
});
const minNodeX = Math.min(...nodes.map(nodeData => nodeData.x));
const maxNodeX = Math.max(...nodes.map(nodeData => nodeData.x));
const minNodeY = Math.min(...nodes.map(nodeData => nodeData.y));
const maxNodeY = Math.max(...nodes.map(nodeData => nodeData.y));
const graphWidth = Math.abs(maxNodeX - minNodeX);
const graphHeight = Math.abs(maxNodeY - minNodeY);
const WORLD_WIDTH = Math.max(SCREEN_WIDTH * 2, graphWidth * 1.1);
const WORLD_HEIGHT = Math.max(SCREEN_HEIGHT * 2, graphHeight * 1.1);
nodes.forEach(nodeData => {
nodeData.x = nodeData.x - minNodeX - graphWidth / 2 + WORLD_WIDTH / 2;
nodeData.y = nodeData.y - minNodeY - graphHeight / 2 + WORLD_HEIGHT / 2;
nodeData.x1 = nodeData.x1 - minNodeX - graphWidth / 2 + WORLD_WIDTH / 2;
nodeData.y1 = nodeData.y1 - minNodeY - graphHeight / 2 + WORLD_HEIGHT / 2;
});
const app = new PIXI.Application({
width: SCREEN_WIDTH,
height: SCREEN_HEIGHT,
resolution: RESOLUTION,
transparent: true,
antialias: true,
autoDensity: true,
autoStart: false
});
const viewport = new Viewport({
screenWidth: SCREEN_WIDTH,
screenHeight: SCREEN_HEIGHT,
worldWidth: WORLD_WIDTH,
worldHeight: WORLD_HEIGHT,
backgroundColor : 0x061639,
interaction: app.renderer.plugins.interaction,
});
const resetViewport = () => {
viewport.center = new PIXI.Point(WORLD_WIDTH / 2, WORLD_HEIGHT / 2);
viewport.fitWorld(true);
};
app.stage.addChild(viewport);
viewport.drag().pinch().wheel().decelerate().clampZoom({ minWidth: SCREEN_WIDTH, minHeight: SCREEN_HEIGHT });
const nodesLayer = new PIXI.Container();
viewport.addChild(nodesLayer);
const circleGraphics = new PIXI.Graphics();
circleGraphics.beginFill(TEXTURE_COLOR);
circleGraphics.drawCircle(NODE_RADIUS, NODE_RADIUS, NODE_RADIUS);
const circleTexture = app.renderer.generateTexture(circleGraphics, PIXI.settings.SCALE_MODE, RESOLUTION);
const nodeDataGfxPairs = nodes.map(nodeData => {
const nodeGfx = new PIXI.Container();
nodeGfx.x = nodeData.x;
nodeGfx.y = nodeData.y;
nodeGfx.interactive = true;
nodeGfx.buttonMode = true;
nodeGfx.hitArea = new PIXI.Circle(0, 0, NODE_HIT_RADIUS);
const circle = new PIXI.Sprite(circleTexture);
circle.name = CIRCLE;
circle.x = -circle.width / 2;
circle.y = -circle.height / 2;
circle.tint = '#000';
circle.alpha = 0.5;
nodeGfx.addChild(circle);
nodesLayer.addChild(nodeGfx);
return [nodeData, nodeGfx];
});
let nodeDataToNodeGfx = new WeakMap(nodeDataGfxPairs.map(([nodeData, nodeGfx]) => [nodeData, nodeGfx]));
let nodeGfxToNodeData = new WeakMap(nodeDataGfxPairs.map(([nodeData, nodeGfx]) => [nodeGfx, nodeData]));
let nodesF = nodes.slice()
let nodesF_previous = nodesF
updatePositions();
resetViewport();
app.ticker.start()
container.appendChild(app.view);
app.view.addEventListener('wheel', event => { event.preventDefault(); });
function updatePositions() {
nodes.forEach(nodeData => {
const nodeGfx = nodeDataToNodeGfx.get(nodeData);
nodeGfx.x = nodeData.x1;
nodeGfx.y = nodeData.y1;
});
let T = new PIXI.Ticker()
T.add((delta) => animate(delta, nodes, T, {idx: -1, color: "0x000000", scale: 1, offset: 0}, 'initial'));
T.start()
};
function animate(delta, data, ticker, attrs, reverse) {
data.forEach((d,i)=>{
move(d.id)
})
tick += 1
function angle(cx, cy, ex, ey) {
var dy = ey - cy;
var dx = ex - cx;
var theta = Math.atan2(dy, dx);
return theta;
}
function move(id){
let n = nodes.find(d=>d.id === id)
let N = nodeDataToNodeGfx.get(n)
let angRad = angle(WORLD_WIDTH/2, WORLD_HEIGHT/2, nodes[id].x, nodes[id].y)
let xDist, yDist
xDist = attrs.offset * Math.cos(angRad)
yDist = attrs.offset * Math.sin(angRad)
let xDistR, yDistR
xDistR = n.offset * Math.cos(angRad)
yDistR = n.offset * Math.sin(angRad)
let steps = reverse === 'initial' ? 80 : 20
let scaleTo = attrs.scale
if(tick <= steps) {
if(reverse === 'initial'){
let xDistR = (n.x1-n.x)
let yDistR = (n.y1-n.y)
N.position.x -= xDistR/steps
N.position.y -= yDistR/steps
} else if(reverse === 'reverse'){
if(n.offset){
N.position.x += xDistR/steps
N.position.y += yDistR/steps
if(n.scale === 2){
N.scale.set((1/steps*tick),(1/steps*tick));
}
}
} else {
N.position.x -= xDist/steps
N.position.y -= yDist/steps
}
if(scaleTo !== 1){
N.scale.set((scaleTo/steps*tick),(scaleTo/steps*tick));
}
const circle = N.getChildByName(CIRCLE);
circle.tint = attrs.color;
} else {
ticker.stop()
if(reverse === 'reverse'){
nodes.forEach(d=>{
d.offset = 0
})
nodesF = nodes
}
}
}
}
function run(group){
resetState = false
let index = IDs.indexOf(group)
let groupInt = parseInt(group)
config[groupInt] = group
nodesF_previous = nodesF
nodesF = nodesF.filter(d=>(d[groupInt] === group))
if(nodesF_previous.length !== nodesF.length){
nodesF.forEach((d,i)=>{
let idx = nodes.find(el=>el.id === d.id).id
nodes[idx].offset += attrs[groupInt].offset
nodes[idx].scale = index >= 5 ? 2 : 1
})
startTicker(groupInt, nodesF, new PIXI.Ticker(), attrs[groupInt])
}
}
function startTicker(id, nodes, ticker, attrs, reverse){
if(config[id] !== null){
tick = 0
if(nodes.length !== 0){
ticker.add((delta) => animate(delta, nodes, ticker, attrs, reverse));
ticker.start()
}
}
}
function loop(){
if(resetState === false) return
let counter = 0
let timer = setInterval(function(){
run(IDs[counter])
counter += 1
resetState = false
if(IDs[counter] === undefined) {
clearInterval(timer)
};
}, 1000);
}
function reset(){
if(resetState) return
resetState = true
startTicker(0, nodes, new PIXI.Ticker(), {idx: 0, color: '0x000000', scale: 1, offset: null}, 'reverse')
IDs.map((d,i)=>{
config[d] = null
})
}
const animateButton = html`<button>Animate</button>`;
animateButton.addEventListener('click', () => loop());
const resetButton = html`<button>Reset</button>`;
resetButton.addEventListener('click', () => reset());
const toolbar = html`<div style="position: absolute; top: 5px; left: 10px"></div>`;
toolbar.appendChild(animateButton);
toolbar.appendChild(resetButton);
container.appendChild(toolbar);
yield container
}