Creating svg paths with javascript(shape morphing) - javascript

So I have this class which is used for shape morphing:
class ShapeOverlays {
constructor(elm) {
this.elm = elm;
this.path = elm.querySelectorAll('path');
this.numPoints = 18;
this.duration = 600;
this.delayPointsArray = [];
this.delayPointsMax = 300;
this.delayPerPath = 100;
this.timeStart = Date.now();
this.isOpened = false;
this.isAnimating = false;
}
toggle() {
this.isAnimating = true;
const range = 4 * Math.random() + 6;
for (var i = 0; i < this.numPoints; i++) {
const radian = i / (this.numPoints - 1) * Math.PI;
this.delayPointsArray[i] = (Math.sin(-radian) + Math.sin(-radian * range) + 2) / 4 * this.delayPointsMax;
}
if (this.isOpened === false) {
this.open();
} else {
this.close();
}
}
open() {
this.isOpened = true;
this.elm.classList.add('is-opened');
this.timeStart = Date.now();
this.renderLoop();
}
close() {
this.isOpened = false;
this.elm.classList.remove('is-opened');
this.timeStart = Date.now();
this.renderLoop();
}
updatePath(time) {
const points = [];
for (var i = 0; i < this.numPoints + 1; i++) {
points[i] = ease.cubicInOut(Math.min(Math.max(time - this.delayPointsArray[i], 0) / this.duration, 1)) * 100
}
let str = '';
str += (this.isOpened) ? `M 0 0 V ${points[0]} ` : `M 0 ${points[0]} `;
for (var i = 0; i < this.numPoints - 1; i++) {
const p = (i + 1) / (this.numPoints - 1) * 100;
const cp = p - (1 / (this.numPoints - 1) * 100) / 2;
str += `C ${cp} ${points[i]} ${cp} ${points[i + 1]} ${p} ${points[i + 1]} `;
}
str += (this.isOpened) ? `V 0 H 0` : `V 100 H 0`;
return str;
}
render() {
if (this.isOpened) {
for (var i = 0; i < this.path.length; i++) {
this.path[i].setAttribute('d', this.updatePath(Date.now() - (this.timeStart + this.delayPerPath * i)));
}
} else {
for (var i = 0; i < this.path.length; i++) {
this.path[i].setAttribute('d', this.updatePath(Date.now() - (this.timeStart + this.delayPerPath * (this.path.length - i - 1))));
}
}
}
renderLoop() {
this.render();
if (Date.now() - this.timeStart < this.duration + this.delayPerPath * (this.path.length - 1) + this.delayPointsMax) {
requestAnimationFrame(() => {
this.renderLoop();
});
}
else {
this.isAnimating = false;
}
}
}
(function() {
const elmHamburger = document.querySelector('.hamburger');
const gNavItems = document.querySelectorAll('.global-menu__item');
const elmOverlay = document.querySelector('.shape-overlays');
const overlay = new ShapeOverlays(elmOverlay);
elmHamburger.addEventListener('click', () => {
if (overlay.isAnimating) {
return false;
}
overlay.toggle();
if (overlay.isOpened === true) {
elmHamburger.classList.add('is-opened-navi');
for (var i = 0; i < gNavItems.length; i++) {
gNavItems[i].classList.add('is-opened');
}
} else {
elmHamburger.classList.remove('is-opened-navi');
for (var i = 0; i < gNavItems.length; i++) {
gNavItems[i].classList.remove('is-opened');
}
}
});
}());
Can some one please explain this code? I don't really get how the paths are created using time,how the points are placed and how could I modify it.What is range used for? Why are trigonometral functions used for the delayPointsArray?
Basically it's this part that I don't get:
updatePath(time) {
const points = [];
for (var i = 0; i < this.numPoints + 1; i++) {
points[i] = ease.cubicInOut(Math.min(Math.max(time - this.delayPointsArray[i], 0) / this.duration, 1)) * 100
}
let str = '';
str += (this.isOpened) ? `M 0 0 V ${points[0]} ` : `M 0 ${points[0]} `;
for (var i = 0; i < this.numPoints - 1; i++) {
const p = (i + 1) / (this.numPoints - 1) * 100;
const cp = p - (1 / (this.numPoints - 1) * 100) / 2;
str += `C ${cp} ${points[i]} ${cp} ${points[i + 1]} ${p} ${points[i + 1]} `;
}
str += (this.isOpened) ? `V 0 H 0` : `V 100 H 0`;
return str;
}
render() {
if (this.isOpened) {
for (var i = 0; i < this.path.length; i++) {
this.path[i].setAttribute('d', this.updatePath(Date.now() - (this.timeStart + this.delayPerPath * i)));
}
} else {
for (var i = 0; i < this.path.length; i++) {
this.path[i].setAttribute('d', this.updatePath(Date.now() - (this.timeStart + this.delayPerPath * (this.path.length - i - 1))));
}
}
}
Why is time being used? What is the purpose of this:
points[i] = ease.cubicInOut(Math.min(Math.max(time - this.delayPointsArray[i], 0) / this.duration, 1)) * 100

If you look at how updatePath() is being called, it's like this:
this.updatePath(Date.now() - (this.timeStart + this.delayPerPath * i))
So the time value passed in is the difference between the current time, and the start time of the path we are working with.
So what then is the line of code you are interested in, doing?
points[i] = ease.cubicInOut(Math.min(Math.max(time - this.delayPointsArray[i], 0) / this.duration, 1)) * 100
I'm going to ignore delayPointsArray. It is modifying the start time slightly based on angle. Without seeing the full demo, I'm not sure of the reason for that.
The purpose of this line of code is to calculate how far through the current path's animation we are. The result is in the form of a coordinate value from 0 to 100.
It's doing a lot in that one line of code. So let's break down the individual steps.
Firstly, we are clamping the elapsed time to minimum of 0.
Math.max(time, 0)
In other words, anything before the animation start time becomes zero.
Then we divide by the animation's duration.
Math.max(time, 0) / duration
This will result in a value from 0, representing the start of the animation, to 1, representing the end of the animation. However, the value might also be greater than 1 if the elapsed time is after the end of the animation. Hence the next step.
Now clamp this value to a maximum of 1.
Math.min( Math.max(time, 0) / duration, 1)
We now have a value >= 0 and <= 1 whichdescribes where in the course of the animation, the path is supposed to be. 0 if we should be at the animations start position. 1 if we should be at the animations end position. And somewhere in between if the animation is in progress.
However this value is strictly linear, corresponding with the progression of time. And usually linear movement is not what you want. It is unnatural. Objects accelarate when the start moving and decelerate when the come to a stop. That will be what the easeInOut() function will be doing. If you are not familiar with easing curves, take a look at the diagram below.
Source: Google: The Basics of Easing
So we pass in a linear time value from 0..1 (horizontal axis). It will return a modified value that takes into account acceleration and deceleration.
The final step is to multiply by 100, to convert to a final coordinate value (0..100).
Hope this helps.

Related

O(1) JavaScript Circular Buffer With GetMax and GetMin

So I've been scouring the web to find a way to do this but nothing I've found fits the exact solution we are looking for.
I have an app that stores float numbers in a circular buffer. The circular buffer class can be found here https://www.npmjs.com/package/circular-buffer.
In my app new numbers are coming in every few miliseconds and the buffer holds around 60K values.
For the purpose of this question though I created a circular buffer of 10 with a stream of 100 randomly generated numbers to simulate the incoming data. This is the line that generates the simulated stream:
for (let i = 0; i < valuesToEnqueueForDemoPurposes.length; i++) {
With my current setup, it is taking the cpu too much time to convert the circular buffer to an array and then calculate its min, max, min position / index number, max position / index number and standard deviations (but the stddev is not the focus of this question).
Here is my current code:
stats = require("stats-lite");
var CircularBuffer = require("circular-buffer");
var circularBuffer10 = new CircularBuffer(10);
var valuesToEnqueueForDemoPurposes = Array.from(Array(100)).map(x=>Math.random() * 1000)
for (let i = 0; i < valuesToEnqueueForDemoPurposes.length; i++) {
var newValue = valuesToEnqueueForDemoPurposes[i];
circularBuffer10.enq(newValue);
let valuesArray = circularBuffer10.toarray();
var maxIndex = valuesArray.reduce((iMax, x, i, arr) => x > arr[iMax] ? i : iMax, 0);
var minIndex = valuesArray.reduce((iMin, x, i, arr) => x < arr[iMin] ? i : iMin, 0);
var max = valuesArray[maxIndex];
var min = valuesArray[minIndex];
var standardDeviation = stats.stdev(valuesArray);
console.log(maxIndex);
console.log(max);
console.log(minIndex);
console.log(min);
console.log(standardDeviation + "\n\n");
}
So I was wondering if it was possible to optimize this code with different data structures.
The closest answer I've found to solve this issue is from this SO answer: https://stackoverflow.com/a/48610455
It uses:
a queue of N items
a Min / Max Heap to track the min / max item.
A hash map to track the frequency of each item.
But the problem with this solution is that the heap is always growing and with the amount of differing incoming data I receive, this would cause a serious problem. And it also only calculates the maximum.
Also found this c++ solution but it is only for a normal queue, a max (not min) and I wasn't able to reproduce in javascript:
https://www.geeksforgeeks.org/design-a-queue-data-structure-to-get-minimum-or-maximum-in-o1-time/
Does anyone know if it would be possible, using whatever combination of data structures, to find the Max or Min in O(1) for this type of scenario (with or without circular buffers)?
Thanks to #thomas's advice I was able to alter the circular buffer class I was using so that the sum, mean,max, min and standard deviations are calculated on average at O(1) but at the worst O(N). Worked like a charm for the performance I needed.
Please note that for my purposes I have only altered the "unshift" method of the CBuffer circular buffer class . So the other methods won't be updating the max, min and standard deviations correctly. Here is the link to the jsfiddle:
https://jsfiddle.net/29f4an7s/
And here is my testing code:
// A standard deviation object constructor. Running deviation (avoid growing arrays) which
// is round-off error resistant. Based on an algorithm found in a Knuth book.
class StandardDeviation {
constructor() {
this.v = 0;
this.w = 0;
this.S = 0;
this.count = 0;
}
// Add a measurement. Also calculates updates to stepwise parameters which are later used
// to determine sigma.
add(measurement) {
this.count += 1;
this.w = this.v;
this.v = this.v + (measurement - this.v) / this.count;
this.S = this.S + (measurement - this.w) * (measurement - this.v);
}
// Performs the final step needed to get the standard deviation and returns it.
get() {
if (this.count < 2) {
// There are less measurements accumulated than necessary to perform computation
return 0.0;
} else {
return Math.sqrt(this.S / (this.count));
}
}
// Replaces the value x currently present in this sample with the
// new value y. In a sliding window, x is the value that
// drops out and y is the new value entering the window. The sample
// count remains constant with this operation.
replace(x, y) {
const deltaYX = y - x;
const deltaX = x - this.v;
const deltaY = y - this.v;
this.v = this.v + deltaYX / this.count;
const deltaYp = y - this.v;
const countMinus1 = this.count - 1;
this.S = this.S - this.count / countMinus1 * (deltaX * deltaX - deltaY * deltaYp) - deltaYX * deltaYp / countMinus1;
}
// Remove a measurement. Also calculates updates to stepwise parameters which are later used
// to determine sigma.
remove(x) {
this.w = (this.count * this.v - x) / (this.count - 1);
this.S -= (x - this.v) * (x - this.w);
this.v = this.w;
this.count -= 1;
}
}
function CBuffer() {
// handle cases where "new" keyword wasn't used
if (!(this instanceof CBuffer)) {
// multiple conditions need to be checked to properly emulate Array
if (arguments.length > 1 || typeof arguments[0] !== 'number') {
return CBuffer.apply(new CBuffer(arguments.length), arguments);
} else {
return new CBuffer(arguments[0]);
}
}
// if no arguments, then nothing needs to be set
if (arguments.length === 0)
throw new Error('Missing Argument: You must pass a valid buffer size');
// this is the same in either scenario
this.length = this.start = 0;
// set to callback fn if data is about to be overwritten
this.overflow = null;
// set to callback fn if data is about to be overwritten
this.maxIndex = null;
this.minIndex = null;
this.max = null;
this.min = null;
this.sum = null;
this.mean = null;
this.standardDeviation = new StandardDeviation();
// emulate Array based on passed arguments
if (arguments.length > 1 || typeof arguments[0] !== 'number') {
this.data = new Array(arguments.length);
this.end = (this.size = arguments.length) - 1;
this.push.apply(this, arguments);
} else {
this.data = new Array(arguments[0]);
this.end = (this.size = arguments[0]) - 1;
}
// need to `return this` so `return CBuffer.apply` works
return this;
}
function defaultComparitor(a, b) {
return a == b ? 0 : a > b ? 1 : -1;
}
function mod(n, m) {
return ((n % m) + m) % m;
}
CBuffer.prototype = {
// properly set constructor
constructor : CBuffer,
/* mutator methods */
// pop last item
pop : function () {
var item;
if (this.length === 0) return;
item = this.data[this.end];
// remove the reference to the object so it can be garbage collected
delete this.data[this.end];
this.end = (this.end - 1 + this.size) % this.size;
this.length--;
return item;
},
// push item to the end
push : function () {
var i = 0;
var returnOverflow = false;
// check if overflow is set, and if data is about to be overwritten
if (this.overflow && this.length + arguments.length > this.size) {
// call overflow function and send data that's about to be overwritten
for (; i < this.length + arguments.length - this.size; i++) {
returnOverflow = this.data[(this.end + i + 1) % this.size];
// this.overflow(this.data[(this.end + i + 1) % this.size], this);
}
}
// push items to the end, wrapping and erasing existing items
// using arguments variable directly to reduce gc footprint
for (i = 0; i < arguments.length; i++) {
this.data[(this.end + i + 1) % this.size] = arguments[i];
}
// recalculate length
if (this.length < this.size) {
if (this.length + i > this.size) this.length = this.size;
else this.length += i;
}
// recalculate end
this.end = (this.end + i) % this.size;
// recalculate start
this.start = (this.size + this.end - this.length + 1) % this.size;
// return number current number of items in CBuffer
return returnOverflow;
},
// reverse order of the buffer
reverse : function () {
var i = 0,
tmp;
for (; i < ~~(this.length / 2); i++) {
tmp = this.data[(this.start + i) % this.size];
this.data[(this.start + i) % this.size] = this.data[(this.start + (this.length - i - 1)) % this.size];
this.data[(this.start + (this.length - i - 1)) % this.size] = tmp;
}
return this;
},
// rotate buffer to the left by cntr, or by 1
rotateLeft : function (cntr) {
if (typeof cntr === 'undefined') cntr = 1;
if (typeof cntr !== 'number') throw new Error("Argument must be a number");
while (--cntr >= 0) {
this.push(this.shift());
}
return this;
},
// rotate buffer to the right by cntr, or by 1
rotateRight : function (cntr) {
if (typeof cntr === 'undefined') cntr = 1;
if (typeof cntr !== 'number') throw new Error("Argument must be a number");
while (--cntr >= 0) {
this.unshift(this.pop());
}
return this;
},
// remove and return first item
shift : function () {
var item;
// check if there are any items in CBuff
if (this.length === 0) return;
// store first item for return
item = this.data[this.start];
// recalculate start of CBuffer
this.start = (this.start + 1) % this.size;
// decrement length
this.length--;
return item;
},
// sort items
sort : function (fn) {
this.data.sort(fn || defaultComparitor);
this.start = 0;
this.end = this.length - 1;
return this;
},
// add item to beginning of buffer
unshift : function () {
var i = 0;
var returnOverflow = false;
if (this.length == this.size) {
returnOverflow = this.last();
}
for (i = 0; i < arguments.length; i++) {
this.data[(this.size + this.start - (i % this.size) - 1) % this.size] = arguments[i];
}
if (this.size - this.length - i < 0) {
this.end += this.size - this.length - i;
if (this.end < 0) this.end = this.size + (this.end % this.size);
}
if (this.length < this.size) {
if (this.length + i > this.size) this.length = this.size;
else this.length += i;
}
this.start -= arguments.length;
if (this.start < 0) this.start = this.size + (this.start % this.size);
this.recalculateMaxMin(arguments[0], returnOverflow);
this.sum += arguments[0];
if (returnOverflow) {
this.sum -= returnOverflow;
this.standardDeviation.replace(returnOverflow, arguments[0])
}
else {
this.standardDeviation.add(arguments[0]);
}
this.mean = this.sum / this.length;
return returnOverflow;
},
/* accessor methods */
// return index of first matched element
indexOf : function (arg, idx) {
if (!idx) idx = 0;
for (; idx < this.length; idx++) {
if (this.data[(this.start + idx) % this.size] === arg) return idx;
}
return -1;
},
// return last index of the first match
lastIndexOf : function (arg, idx) {
if (!idx) idx = this.length - 1;
for (; idx >= 0; idx--) {
if (this.data[(this.start + idx) % this.size] === arg) return idx;
}
return -1;
},
// return the index an item would be inserted to if this
// is a sorted circular buffer
sortedIndex : function(value, comparitor, context) {
comparitor = comparitor || defaultComparitor;
var isFull = this.length === this.size,
low = this.start,
high = isFull ? this.length - 1 : this.length;
// Tricky part is finding if its before or after the pivot
// we can get this info by checking if the target is less than
// the last item. After that it's just a typical binary search.
if (low && comparitor.call(context, value, this.data[high]) > 0) {
low = 0, high = this.end;
}
while (low < high) {
var mid = (low + high) >>> 1;
if (comparitor.call(context, value, this.data[mid]) > 0) low = mid + 1;
else high = mid;
}
return !isFull ? low :
// http://stackoverflow.com/a/18618273/1517919
(((low - this.start) % this.length) + this.length) % this.length;
},
/* iteration methods */
// check every item in the array against a test
every : function (callback, context) {
var i = 0;
for (; i < this.length; i++) {
if (!callback.call(context, this.data[(this.start + i) % this.size], i, this))
return false;
}
return true;
},
// loop through each item in buffer
// TODO: figure out how to emulate Array use better
forEach : function (callback, context) {
var i = 0;
for (; i < this.length; i++) {
callback.call(context, this.data[(this.start + i) % this.size], i, this);
}
},
// construct new CBuffer of same length, apply map function, and return new CBuffer
map : function (callback, context) {
var outCBuffer = new CBuffer(this.size);
for (var i = 0; i < this.length; i++) {
var n = (this.start + i) % this.size;
outCBuffer.push(callback.call(context, this.data[n], i, this));
}
return outCBuffer;
},
// check items agains test until one returns true
// TODO: figure out how to emulate Array use better
some : function (callback, context) {
var i = 0;
for (; i < this.length; i++) {
if (callback.call(context, this.data[(this.start + i) % this.size], i, this))
return true;
}
return false;
},
// calculate the average value of a circular buffer
avg : function () {
return this.length == 0 ? 0 : (this.sum() / this.length);
},
// loop through each item in buffer and calculate sum
sum : function () {
var index = this.length;
var s = 0;
while (index--) s += this.data[index];
return s;
},
// loop through each item in buffer and calculate sum
getMaxPosition : function () {
// return 0
return (this.start + this.start + this.maxIndex) % this.size;
},
// loop through each item in buffer and calculate sum
getStandardDeviation : function () {
// return 0
return this.standardDeviation.get();
},
// loop through each item in buffer and calculate sum
getMinPosition : function () {
// return 0
return (this.start + this.start + this.minIndex) % this.size;
},
recalculateMaxMin : function (newValue, returnOverflow) {
if (this.length == 1) {
this.max = newValue;
this.maxIndex = this.start;
this.min = newValue;
this.minIndex = this.start;
return;
}
// Max / Mins
if (newValue > this.max) {
this.max = newValue;
this.maxIndex = this.start;
}
if (newValue < this.min) {
this.min = newValue;
this.minIndex = this.start;
}
// If overflow max or min recalculate
if (
returnOverflow && (returnOverflow >= this.max || returnOverflow <= this.min)
) {
this.maxIndex = 0;
this.minIndex = 0;
this.max = this.data[0];
this.min = this.data[0];
for (let i = 0; i < this.length; i++) {
if (this.data[i] > this.max) {
this.maxIndex = i;
this.max = this.data[i];
}
if (this.data[i] < this.min) {
this.minIndex = i;
this.min = this.data[i];
}
}
}
},
// loop through each item in buffer and calculate median
median : function () {
if (this.length === 0)
return 0;
var values = this.slice().sort(defaultComparitor);
var half = Math.floor(values.length / 2);
if(values.length % 2)
return values[half];
else
return (values[half-1] + values[half]) / 2.0;
},
/* utility methods */
// reset pointers to buffer with zero items
// note: this will not remove values in cbuffer, so if for security values
// need to be overwritten, run `.fill(null).empty()`
empty : function () {
var i = 0;
this.length = this.start = 0;
this.end = this.size - 1;
return this;
},
// fill all places with passed value or function
fill : function (arg) {
var i = 0;
if (typeof arg === 'function') {
while(this.data[i] = arg(), ++i < this.size);
} else {
while(this.data[i] = arg, ++i < this.size);
}
// reposition start/end
this.start = 0;
this.end = this.size - 1;
this.length = this.size;
return this;
},
// return first item in buffer
first : function () {
return this.data[this.start];
},
// return last item in buffer
last : function () {
return this.data[this.end];
},
// return specific index in buffer
get : function (arg) {
return this.data[mod(this.start + arg, this.size)];
},
isFull : function (arg) {
return this.size === this.length;
},
// set value at specified index
set : function (idx, arg) {
return this.data[(this.start + idx) % this.size] = arg;
},
// return clean array of values
toArray : function () {
return this.slice();
},
// return a string based on the array
join : function(separator) {
if (!separator) separator = ',';
var outString = new String(this.data[0]);
for (var i = 1; i < this.length; i++) {
var n = (this.start + i) % this.size;
outString = outString.concat(separator, this.data[i]);
}
return outString;
},
// slice the buffer to an arraay
slice : function (start, end) {
var size = this.length;
start = +start || 0;
if (start < 0) {
if (start >= end)
return [];
start = (-start > size) ? 0 : size + start;
}
if (end == null || end > size)
end = size;
else if (end < 0)
end += size;
else
end = +end || 0;
size = start < end ? end - start : 0;
var result = Array(size);
for (var index = 0; index < size; index++) {
result[index] = this.data[(this.start + start + index) % this.size];
}
return result;
}
};
var bufferLength = 3;
var numbersToGenerate = 10;
var circularBufferN = new CBuffer(bufferLength);
var valuesToEnqueueForDemoPurposes = Array.from(Array(numbersToGenerate)).map(x=>Math.random() * 1000)
for (let i = 0; i < valuesToEnqueueForDemoPurposes.length; i++) {
var newValue = valuesToEnqueueForDemoPurposes[i];
console.log("\n\nNEW VALUE****************************************************************:");
console.log(newValue);
console.log("STARTING UNSHIFT:");
console.log(circularBufferN.unshift(newValue));
let valuesArray = circularBufferN.data;
var maxIndex = circularBufferN.maxIndex;
var minIndex = circularBufferN.minIndex;
var max = valuesArray[maxIndex];
var min = valuesArray[minIndex];
console.log("Max Index");
console.log(maxIndex);
console.log("Max:");
console.log(max);
console.log("Min Index:");
console.log(minIndex);
console.log("Min:");
console.log(min);
console.log("Start:");
console.log(circularBufferN.start);
console.log("ORDERED ARRAY:");
console.log(circularBufferN.toArray());
console.log("Max Position:");
console.log(circularBufferN.getMaxPosition());
console.log("Min Position:");
console.log(circularBufferN.getMinPosition());
console.log('Sum:');
console.log(circularBufferN.sum);
console.log("mean:");
console.log(circularBufferN.mean);
console.log("Derived Standard Deviation");
console.log(circularBufferN.getStandardDeviation());
}

Calculate the nearest value on a circular variable

I have an problem where i have 3 times of the 24 hour day. To keep it simple i can use the decimal representation:
a) 23:45 (23.75)
b) 11:30 (11.50)
c) 00:15 (00.25)
I'd like to know , for each time, which other time is closest.
var closestTime = 24
var closestActualTime = 0;
for (var i = 0; i < times.length; i++) {
if (times[i].time == this.time) continue;
var temp = Math.abs(this.time - times[i].time)
if (temp < closestTime) {
closestTime = temp;
closestActualTime = times[i].time;
}
}
My issue is that 23:45 and 00:25 are actually really close but i don't know how process a variable with a modulo type
I suggest to build a list with the pairs and then calculate the difference.
The difference is the third element in the pairs array.
Basically you need to check the delta and if it greater than 12 hours, take the difference from 24 and delta.
delta = Math.abs(aa - bb);
if (delta > 12) {
delta = 24 - delta;
}
function combination(array, size) {
function c(part, start) {
var i, l, p;
for (i = start, l = array.length + part.length + 1 - size; i < l; i++) {
p = part.slice();
p.push(array[i]);
p.length < size ? c(p, i + 1) : result.push(p);
}
}
var result = [];
c([], 0);
return result;
}
function timeDelta(a, b) {
function decimalTime(s) {
var p = s.split(':');
return +p[0] + p[1] / 60;
}
function padZero(v) {
return (v < 10) ? '0' + v : String(v);
}
var aa = decimalTime(a),
bb = decimalTime(b),
delta = Math.abs(aa - bb);
if (delta > 12) {
delta = 24 - delta;
}
return padZero(Math.floor(delta)) + ':' + padZero(Math.round(60 * (delta - Math.floor(delta))));
}
var times = ['23:45', '11:30', '00:15'],
pairs = combination(times, 2);
pairs.forEach(function (a, i, aa) {
aa[i][2] = timeDelta(a[0], a[1]);
});
console.log(pairs);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Loop over times.
Try combinations of delta time, offset by 24 hours.
Pick smallest delta time.
var times = [23.75, 11.50, 3, 6, 7];
/**
* timeClosestTo
*
* #param {number} time
* #returns {number}
*/
function timeClosestTo(time) {
//Distance variable to compare against
var distance = 100;
//Hours in a day
var day = 24;
//Current best
var best = null;
//Test against all times
for (var i = 0; i < times.length; i++) {
//Find best score based upon day
var d = Math.min(Math.abs((times[i]) - (time)), Math.abs((times[i] + day) - time), Math.abs((times[i]) - (time + day)), Math.abs((times[i] + day) - (time + day)));
//If best found distance yet, set best to current
if (d < distance) {
best = times[i];
distance = d;
}
}
//Return best
return best;
}
console.log("times to deal with:",times.join(", "));
console.log("closest to 1:", timeClosestTo(1), "closest to 11:", timeClosestTo(11), "closest to 5:", timeClosestTo(5));
Quite functionally i would do this job as follows
var times = [23.75,11.50,0.25],
diffs = times.reduce((d,t1,i,a) => a[i+1] ? d.concat(a.slice(i+1)
.map(t2 => [t1,t2,[Math.min(Math.abs(t1-t2),24-Math.abs(t1-t2))]
.map(n => ~~n + ":" + (n%1)*60)[0]]))
: d,[]);
console.log(diffs);

Ease in and out curve in for loop

I have a for loop like this:
var speed = 100;
var curve = [];
for (var i = 0; i < 5; i++) {
curve.push(i*speed);
}
So for the last loop its 400, the question is how do i implement ease in and out in the for loop? roughly in the end the result should be like this? [0,52,200,348,400]
EDIT:
var defaultSpin = 24;
var totalSlices = 12;
for (var i = 0; i < defaultSpin; i++) {
highlight(divs[i%totalSlices], i*100, 100);
}
function highlight(el, delay, duration) {
setTimeout(function() {
el.className += ' active';
setTimeout(function() {
el.className = 'pie';
}, duration)
}, delay)
}
It is a spin wheel with highlight instead of actually spinning it. I'm calling the above function with the loop. for now it only has constant speed because each loop difference is only 100 so the 1st hightlight delay is 0 and it start immediately. 2nd is 100, 3rd is 200 and so on.
Lots of common easing functions are shown here:
http://gizma.com/easing/
Here is an example of how to use one:
// from http://gizma.com/easing/
var easeInOutQuad = function (t, b, c, d) {
t /= d/2;
if (t < 1) return c/2*t*t + b;
t--;
return -c/2 * (t*(t-2) - 1) + b;
};
var steps = 4
var speed = 100
var curve = []
for (var i = 0; i < steps+1; i++) {
var stepValue = easeInOutQuad(i, 0, speed*steps, steps);
curve.push(stepValue);
}
console.log(curve); // [0, 50, 200, 350, 400]
Hey take a note of this snippet
/*\
* Raphael.easing_formulas
[ property ]
**
* Object that contains easing formulas for animation. You could extend it with your own. By default it has following list of easing:
# <ul>
# <li>“linear”</li>
# <li>“<” or “easeIn” or “ease-in”</li>
# <li>“>” or “easeOut” or “ease-out”</li>
# <li>“<>” or “easeInOut” or “ease-in-out”</li>
# <li>“backIn” or “back-in”</li>
# <li>“backOut” or “back-out”</li>
# <li>“elastic”</li>
# <li>“bounce”</li>
# </ul>
# <p>See also Easing demo.</p>
\*/
var ef = R.easing_formulas = {
linear: function (n) {
return n;
},
"<": function (n) {
return pow(n, 1.7);
},
">": function (n) {
return pow(n, .48);
},
"<>": function (n) {
var q = .48 - n / 1.04,
Q = math.sqrt(.1734 + q * q),
x = Q - q,
X = pow(abs(x), 1 / 3) * (x < 0 ? -1 : 1),
y = -Q - q,
Y = pow(abs(y), 1 / 3) * (y < 0 ? -1 : 1),
t = X + Y + .5;
return (1 - t) * 3 * t * t + t * t * t;
},
backIn: function (n) {
var s = 1.70158;
return n * n * ((s + 1) * n - s);
},
backOut: function (n) {
n = n - 1;
var s = 1.70158;
return n * n * ((s + 1) * n + s) + 1;
},
elastic: function (n) {
if (n == !!n) {
return n;
}
return pow(2, -10 * n) * math.sin((n - .075) * (2 * PI) / .3) + 1;
},
bounce: function (n) {
var s = 7.5625,
p = 2.75,
l;
if (n < (1 / p)) {
l = s * n * n;
} else {
if (n < (2 / p)) {
n -= (1.5 / p);
l = s * n * n + .75;
} else {
if (n < (2.5 / p)) {
n -= (2.25 / p);
l = s * n * n + .9375;
} else {
n -= (2.625 / p);
l = s * n * n + .984375;
}
}
}
return l;
}
};
ef.easeIn = ef["ease-in"] = ef["<"];
ef.easeOut = ef["ease-out"] = ef[">"];
ef.easeInOut = ef["ease-in-out"] = ef["<>"];
ef["back-in"] = ef.backIn;
ef["back-out"] = ef.backOut;
This is a snippet from Raphael. Here you see you have a list of animation ease-in formulas.
Lets try one of them, e.g. ease-in
var pow = Math.pow;
function easeIn(n) {
return pow(n, 1.7);
}
function easeOut(n) {
return pow(n, .48);
}
function process(min, max, intervals, fN) {
var diff = 1 / intervals,
difference = max - min,
curve = [];
for (i = diff; i <= 1; i += diff) {
curve.push(min + (difference * fN(i)));
}
return curve;
}
console.log('easeIn: \n', process(0, 400, 5, easeIn));
console.log('easeOut: \n', process(0, 400, 5, easeOut));
This might not be in sync with the output you have expected. But these are the formulas a renowned JS SVG library like Rapahel uses. You would love this demo

Write patterns with javascript

i'm doing a "Steve Reich - Clapping Music" kinda thing with "xoxoxoxx" with x as the clapping. but i want it to write the pattern while it keep going to the right. so you'd have this kinda writing:
xoxoxoxxxoxoxoxxxoxoxoxxxoxoxoxx
xoxoxoxxoxoxoxxxoxoxxoxoxxxoxoxo
so it prints the X or O and then goes a bit to the right and prints again. I hope this is clear, english isn't my first language, so i'm sorry if it's hard to understand. here is the full code for 2 lines because i'm bad at explaining:
var noise, env;
var seq = "o x o x o x o x o x o x o x x x";
var steps = seq.split(" ");
var speed = 8;
var count = 0;
var count2 = 0;
var count3=0;
var shift = 0;
var repeat = 1;
var sf, sf2, sf3;
var f;
function preload() {
sf = loadSound("./files/clap.wav");
sf2 = sf;
sf3 = sf;
}
function setup() {
createCanvas(400, 400);
env = new p5.Env(0.01, 1, 0.2, 0.1);
env2 = new p5.Env(0.1, 0.8, 0.01, 0.1);
env3 = new p5.Env(0.05, 0.9, 0.1, 0.1);
}
function hitMeSteve(when, env, loc) {
if (when == 'x' && frameCount % speed == 0) {
env.play();
}
}
function draw() {
if (frameCount % speed == 0) {
count++;
}
if (frameCount % (steps.length * speed * repeat) == 0) {
shift++;
count2=count2+2;
count3=count3+4;
}
if(shift==4){
shift=0;
count2=0;
count3=0;
}
shift = shift % steps.length;
shift2 = shift + 2;
var now = steps[count % steps.length];
hitMeSteve(now, sf, 10);
var canon = steps[(count + shift) % steps.length];
hitMeSteve(canon, sf2, width / 2 + 10);
var canon2 = steps[(count + shift+count2) % steps.length];
hitMeSteve(canon2, sf3, width / 2 + 20);
textSize(30);
//1
for (var i = 0; i < steps.length; i++) {
if (i == count % steps.length) {
fill(255, 180, 0);
} else {
fill(0);
}
text(steps[i],10+ ( + i) * 15,20);
//text(steps[i], 110 + (shift / 2 + i) * 15, height / 2);
}
//2
for (var i = 0; i < steps.length; i++) {
if (i == (count + shift) % steps.length) {
fill(255, 180, 0);
} else {
fill(0);
}
text(steps[i],10+( + i)*15,40);
//text(steps[i], 110 + (-shift / 2 + i) * 15, height / 2 + 20);
}
}
Just a proposal with setInterval, maybe this works for you.
var content = "oxoxoxoxoxoxoxxx",
target = document.getElementById('ticker'),
i = 0,
timer = setInterval(addChar, 800);
function addChar() {
if (i < content.length) {
target.innerHTML += ' ' + content[i];
i++;
} else {
target.innerHTML = '';
i=0;
}
}
<div id="ticker"></div>

setInterval javascript memory leak

I can't figure out why the memory is increasing and it stays there each time I run this code:
easingFunction = function (t, b, c, d) {
if ((t /= d / 2) < 1) return c / 2 * t * t * t * t * t + b;
return c / 2 * ((t -= 2) * t * t * t * t + 2) + b;
}
processFrame = function () {
for (var i = 0; i < tiles.length; i++) {
var tile = tiles[i];
tile.percent += 4;
if (tile.percent > 0) {
var TH = Math.max(0, Math.min(TILE_HEIGHT, targetObj.height - tile.imageY));
var TW = Math.max(0, Math.min(TILE_WIDTH, targetObj.width - tile.imageX));
var SW, SH, SX, SY, amount;
draw.save();
draw.translate(tile.imageX, tile.imageY);
if (direction == "tb" || direction == "bt") {
amount = easingFunction(tile.percent, 0, TW, 100);
SW = Math.min(TW, amount);
SH = TH;
SX = 0;
SY = 0;
} else {
amount = easingFunction(tile.percent, 0, TH, 100);
SW = TW;
SH = Math.min(TH, amount);
SX = 0;
SY = 0;
}
draw.drawImage(copycanvas, tile.imageX, tile.imageY, SW, SH, SX, SY, SW, SH);
draw.restore();
}
}
var ok = true;
for (i = 0; i < tiles.length; i++) {
if (tiles[i].percent < 100) {
ok = false;
break;
}
}
if (ok) {
clearInterval(interval);
showComplete();
}
};
this.show = function (target, hideTarget) {
createTiles();
for (var i = 0; i < tiles.length; i++) {
var tile = tiles[i];
tile.percent = 0 - i * 10;
}
}
var intervalDelay = (config.duration * 1000) / (tiles.length * 3 + 25);
interval = setInterval(function () {
processFrame();
}, intervalDelay);
};
function Tile() {
this.imageX = 0;
this.imageY = 0;
this.percent = 0;
};
};
I left out some unimportant code. The ideea is that I call externally the show() function. The setInterval is initialized and runs processFrame() about 100 times.
I've tried to leave some code outside from processFrame, and I got to :
processFrame = function () {
for (var i = 0; i < tiles.length; i++) {
var tile = tiles[i];
tile.percent += 4;
}
var ok = true;
for (i = 0; i < tiles.length; i++) {
if (tiles[i].percent < 100) {
ok = false;
break;
}
}
if (ok) {
clearInterval(interval);
showComplete();
}
};
But the memory still increases.
Try validating your code with JSLint. http://www.jslint.com/
Right now your adding easingFunction & processFrame to the Global object (which isn't a good thing). Not that this is the cause of the problem, but I've found that mismanagement of my objects is the usual cause of memory leaks.
You'll want to do something like:
var MyObject = {};
MyObject.easingFunction = function(){};
MyObject.processFrame = function(){};
In short make sure you declare all objects with var before using them.
I found the problem. I was continuously redrawing the canvas. To resolve this problem I had to erase the canvas each time before modifying it.

Categories

Resources