From 5527d64e7bbbcdb6c66e81cd51369da5be7c9100 Mon Sep 17 00:00:00 2001
From: BREgitLEEhub2222 <78449263+Brelee2222@users.noreply.github.com>
Date: Thu, 12 Sep 2024 14:17:36 -0700
Subject: [PATCH] JitterFixes
---
src/views/dash/circlepacker.ts | 165 ++++++++++++++++++++++++++++++---
src/views/dash/index.ts | 15 +++
2 files changed, 165 insertions(+), 15 deletions(-)
diff --git a/src/views/dash/circlepacker.ts b/src/views/dash/circlepacker.ts
index ba992c3..f4ab504 100644
--- a/src/views/dash/circlepacker.ts
+++ b/src/views/dash/circlepacker.ts
@@ -1,96 +1,120 @@
import { busSigns, getArrivals } from './trimet'
+
const membersDiv = document.getElementById('members')!
+const DELTA_AVG = 0.25
+
export class Vector2D {
x: number
y: number
+
constructor(x = 0, y = 0) {
this.x = x
this.y = y
}
+
getDistanceFrom(vector: Vector2D) {
return Math.sqrt((this.x - vector.x) ** 2 + (this.y - vector.y) ** 2)
}
+
normalized() {
const divisor = new Vector2D().getDistanceFrom(this)
+
return new Vector2D(this.x / divisor, this.y / divisor)
}
+
scaled(scalar: number) {
return new Vector2D(this.x * scalar, this.y * scalar)
}
+
added(vector: Vector2D) {
return new Vector2D(this.x + vector.x, this.y + vector.y)
}
}
+
export abstract class Circle {
position: Vector2D
velocity: Vector2D
acceleration: Vector2D
+
r: number
+
readonly element: HTMLElement
+
constructor(r: number) {
this.element = membersDiv.appendChild(document.createElement('name'))
+
this.r = r
this.position = new Vector2D()
this.acceleration = new Vector2D()
this.velocity = new Vector2D()
}
+
get mass() {
return Math.PI * this.r ** 2
// return 200;
}
+
get charge() {
return this.r ** 2 / 5
}
+
destroy() {
this.element.remove()
}
+
abstract updateSize(): void
}
+
export class MemberCircle extends Circle {
loginTime: number
name: string
email: string
+
constructor(loginTime: number, email: string, name: string, imgurl: string) {
super(Math.sqrt(0.2) * 10)
+
this.loginTime = loginTime
this.email = email
this.element.id = email
this.element.style.backgroundImage = `url(${imgurl})`
this.element.className = 'memberCircle'
+
const nameBubble = this.element.appendChild(document.createElement('name'))
this.name = nameBubble.innerHTML = name
nameBubble.className = 'bubblename'
nameBubble.style.backgroundColor = BUBBLE_COLORS[Math.floor(Math.random() * BUBBLE_COLORS.length)]
// name.style.fontSize = `${Math.min(30*multiplier, 20)}px`;
- nameBubble.style.fontSize = '25px'
+ nameBubble.style.fontSize = '2.5vh'
}
+
updateSize() {
this.r = Math.sqrt((Date.now() - this.loginTime) / 360000 + 0.2) * 20
}
}
+
export class ClockCircle extends Circle {
constructor() {
super(Math.sqrt(0.2) * 10)
@@ -98,13 +122,13 @@ export class ClockCircle extends Circle {
this.element.innerHTML = `
@@ -116,12 +140,14 @@ export class ClockCircle extends Circle {
-- min
-- min
+
Beaverton
-- min
-- min
+
`
@@ -148,6 +174,7 @@ export class ClockCircle extends Circle {
timetext.classList.remove('timeSmallerText')
}
+
await updateBusElements([...document.querySelectorAll('.bustime.east')], busSigns.east)
await updateBusElements([...document.querySelectorAll('.bustime.west')], busSigns.west)
}
@@ -155,19 +182,24 @@ export class ClockCircle extends Circle {
setInterval(updateBusTimes, 15000)
}
+
updateSize() {
this.r = 20
- this.r = Math.max(...placedCircles.map((circle) => circle.r * 1.2))
+ this.r = Math.max(...placedCircles.map((circle) => circle.r * 1.5))
}
}
+
export let placedCircles: Circle[] = []
+
const BUBBLE_COLORS = ['rgba(35,132,198,.5)', 'rgba(255,214,0,.5)', 'rgba(241,93,34,.5)', 'rgba(108,157,204,.5)']
const FORCE_MULTIPLIER = 0.1
const FRICTION = 0.8
const TIME_SCALE = 1
-const MARGIN = 1
+const MARGIN = 0.5
+const SNAP_DISTANCE = 20
+
export function getBounds() {
if (placedCircles.length == 0) {
@@ -179,6 +211,7 @@ export function getBounds() {
}
}
+
const bounds = {
minX: Infinity,
maxX: -Infinity,
@@ -186,6 +219,7 @@ export function getBounds() {
maxY: -Infinity
}
+
for (const circle of placedCircles) {
bounds.maxX = Math.max(circle.position.x + circle.r, bounds.maxX)
bounds.minX = Math.min(circle.position.x - circle.r, bounds.minX)
@@ -193,25 +227,32 @@ export function getBounds() {
bounds.minY = Math.min(circle.position.y - circle.r, bounds.minY)
}
+
return bounds
}
+
let aspectRatio = 1
function updateAspectRatio() {
aspectRatio = membersDiv.clientWidth / membersDiv.clientHeight
}
+
const BOUNDARY_FIELD = 0.005
+
export function applyBoundaryForce(circle: Circle) {
const acceleration = circle.position.scaled((-circle.charge / circle.mass) * BOUNDARY_FIELD * FORCE_MULTIPLIER)
+
acceleration.y *= aspectRatio
+
circle.acceleration = acceleration.scaled(1 / (3 + 2 ** acceleration.getDistanceFrom(new Vector2D())))
// .scaled(1/Math.sqrt(acceleration.getDistanceFrom(new Vector2D())));
}
+
export function updateCircleList(loggedIn: Record): string[] {
const filled: Record = {}
placedCircles = placedCircles.filter((circle) => {
@@ -225,38 +266,60 @@ export function updateCircleList(loggedIn: Record): string[] {
return true
})
+
return Object.keys(loggedIn).filter((email) => !filled[email])
}
+
export function placeCircles(circles: Circle[]) {
circles = circles.sort((circleA, circleB) => circleB.r - circleA.r)
+
const bounds = getBounds()
+
const newPlacedCircles = []
let maxNewBoundSpace = Math.max(...circles.map((circle) => circle.r))
+
for (const circle of circles) {
const offsetX = Math.random() * maxNewBoundSpace + circle.r
const offsetY = Math.random() * maxNewBoundSpace + circle.r
+
circle.position.x = Math.random() > 0.5 ? offsetX + bounds.maxX : bounds.minX - offsetX
circle.position.y = Math.random() > 0.5 ? offsetY + bounds.maxY : bounds.minY - offsetY
+
newPlacedCircles.push(circle)
maxNewBoundSpace += circle.r
}
+
placedCircles.push(...newPlacedCircles)
}
+
export function updateCircles(time: number) {
- if (time > 100) return
+ if (time > 1000) return
+
placedCircles.forEach((circle) => circle.updateSize())
+
+ const sortedCircles = placedCircles.sort((a, b) => a.r - b.r)
+
+
+ let sizeSum = 0
+
+
+ for(const circle of sortedCircles)
+ circle.r -= (circle.r - (sizeSum += circle.r)/(sortedCircles.length+1)) * DELTA_AVG
+
+
time *= TIME_SCALE
+
const center = new Vector2D(
placedCircles.map((circle) => circle.position.x).reduce((sum, r) => sum + r, 0) / placedCircles.length,
placedCircles.map((circle) => circle.position.y).reduce((sum, r) => sum + r, 0) / placedCircles.length
@@ -270,23 +333,31 @@ export function updateCircles(time: number) {
.added(circle.acceleration.scaled((FRICTION * time ** 2) / 2))
.added(center)
+
circle.velocity = circle.velocity.added(circle.acceleration.scaled(time)).scaled(FRICTION)
+
applyBoundaryForce(circle)
})
+
for (let circleIndex = 0; circleIndex < placedCircles.length; circleIndex++) {
const circle = placedCircles[circleIndex]
+
for (let secondaryIndex = circleIndex + 1; secondaryIndex < placedCircles.length; secondaryIndex++) {
const otherCircle = placedCircles[secondaryIndex]
+
const distance = circle.position.getDistanceFrom(otherCircle.position)
+
const force = ((circle.charge * otherCircle.charge) / distance ** 2) * FORCE_MULTIPLIER
+
const forceDirection = new Vector2D(otherCircle.position.x - circle.position.x, otherCircle.position.y - circle.position.y).normalized()
+
circle.acceleration.x -= (force * forceDirection.x) / circle.mass
circle.acceleration.y -= (force * forceDirection.y) / circle.mass
otherCircle.acceleration.x -= (force * -forceDirection.x) / otherCircle.mass
@@ -295,28 +366,92 @@ export function updateCircles(time: number) {
}
}
-export function sizeCircles() {
- const { maxX, maxY, minX, minY } = getBounds()
+
+let renderedCircles: { circle: Circle; top: any; left: any; dia: any }[] = [];
+
+
+export async function sizeCircles() {
+ const sizedCircles = [];
+ for(const circle of placedCircles) {
+ let minScale = Infinity;
+
+
+ placedCircles.forEach(otherCircle => {
+ if(otherCircle == circle)
+ return Infinity;
+ minScale = Math.min(circle.position.getDistanceFrom(otherCircle.position) / (circle.r + otherCircle.r), minScale);
+ });
+
+
+ sizedCircles.push({
+ circle,
+ radius : circle.r * (minScale == Infinity ? 1 : minScale)
+ });
+ }
+
+
+ let maxX = 0, maxY = 0, minX = 0, minY = 0;
+
+
+ sizedCircles.forEach(circle => {
+ maxX = Math.max(circle.circle.position.x + circle.radius, maxX);
+ minX = Math.min(circle.circle.position.x - circle.radius, minX);
+ maxY = Math.max(circle.circle.position.y + circle.radius, maxY);
+ minY = Math.min(circle.circle.position.y - circle.radius, minY);
+ });
+
+
const widthMult = membersDiv.clientWidth / membersDiv.clientHeight
+
const lengthYX = (maxY - minY) * widthMult
const lengthX = maxX - minX
+
const vwWidth = (membersDiv.clientWidth / window.innerWidth) * 100
+
const multiplier = vwWidth / (lengthYX > lengthX ? lengthYX : lengthX)
+
const offsetX = (vwWidth - lengthX * multiplier) / 2
const offsetY = (vwWidth - lengthYX * multiplier) / 2
- for (const circle of placedCircles) {
- const elem = circle.element
- const radius = circle.r * 2 * multiplier - MARGIN
- elem.style.width = elem.style.height = `${radius}vw`
- elem.style.left = `${(circle.position.x - minX) * multiplier + offsetX - radius / 2}vw`
- elem.style.top = `${(circle.position.y - minY) * multiplier + offsetY - radius / 2}vw`
+ let snapCircles = false;
+ for(const circle of sizedCircles) {
+ const elem = circle.circle.element;
- if (circle instanceof ClockCircle) circle.element.style.fontSize = Math.min(radius) + 'vw'
+
+ let diameter = circle.radius * multiplier * 2 - MARGIN;
+
+
+ elem.style.width = elem.style.height = `${diameter}vw`;
+
+
+ elem.style.left = `${(circle.circle.position.x - minX) * multiplier + offsetX - diameter/2}vw`;
+ elem.style.top = `${(circle.circle.position.y - minY) * multiplier + offsetY - diameter/2}vw`;
+
+ let rendered = renderedCircles.find(renderedCircle => circle.circle == renderedCircle.circle);
+ const computedStyle = elem.computedStyleMap();
+ if(rendered == undefined) {
+ renderedCircles.push({
+ circle : circle.circle,
+ // @ts-ignore
+ top : computedStyle.get("top").value,
+ // @ts-ignore
+ left : computedStyle.get("left").value,
+ // @ts-ignore
+ dia : computedStyle.get("width").value
+ })
+ } else {
+ // @ts-ignore
+ if(!(Math.abs(computedStyle.get("top").value - rendered.top) < SNAP_DISTANCE && Math.abs(computedStyle.get("left").value - rendered.left) < SNAP_DISTANCE)) {
+ snapCircles = true;
+ }
+ }
}
}
+
+
+
diff --git a/src/views/dash/index.ts b/src/views/dash/index.ts
index 289ee43..70d5dc5 100644
--- a/src/views/dash/index.ts
+++ b/src/views/dash/index.ts
@@ -5,22 +5,28 @@ import { getLoggedIn, getMemberList } from '~views/grid/clockapi'
import { APIMember, WSCluckChange } from '~types'
import socket_io from 'socket.io-client'
+
let members: Record
let loggedInCache: Record = {}
+
window['openFullscreen'] = openFullscreen
+
setTimeout(cyclePanel)
setInterval(cyclePanel, 1000 * 60) // chnage panel every 1 minutes
setInterval(populateCircles, 50) // refresh circles at 20Hz
+
let prevTime = Date.now()
+
function populateCircles() {
const membersToAdd = updateCircleList(loggedInCache)
const circlesToAdd = membersToAdd.map((entry) => {
const member = members[entry]
+
return new MemberCircle(
loggedInCache[entry].getTime(), // 1000 / 60 / 60
member.email,
@@ -30,12 +36,14 @@ function populateCircles() {
})
placeCircles(circlesToAdd)
+
const now = Date.now()
updateCircles(now - prevTime)
sizeCircles()
prevTime = now
}
+
async function update() {
try {
const loggedIn = await getLoggedIn()
@@ -49,6 +57,7 @@ async function update() {
}
}
+
async function start() {
members = {}
const memberlist = await getMemberList()
@@ -65,6 +74,7 @@ async function start() {
})
}
+
const socket = socket_io({ path: '/ws' })
socket.on('cluck_change', (data: WSCluckChange) => {
if (data.logging_in) {
@@ -74,6 +84,7 @@ socket.on('cluck_change', (data: WSCluckChange) => {
}
})
+
socket.on('disconnect', () => {
document.getElementById('logo')!.style.display = 'none'
document.body.style.backgroundColor = 'red'
@@ -83,4 +94,8 @@ socket.on('connect', () => {
document.body.style.backgroundColor = 'black'
})
+
setTimeout(start)
+
+
+