jQuery Animate Increase Number Effect Slow Down Near End - javascript

I want change animation speed after x seconds, from fast to slow until end. However, this one doesn't works.
Please help me.
$('.holder').each(function() {
var speed = 15000;
function change() {
speed = 2000;
}
setTimeout(change, 2000);
$(this).prop('Counter', 0).animate({
Counter: $(this).data('number')
}, {
duration: speed,
easing: 'swing',
step: function(now) {
$(this).text(Math.ceil(now).toLocaleString('en'));
}
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<h3 class="holder" data-number="1000000"></h3>

As per my comment, your code does not work because the original speed value has been used to instantiate the jQuery animation queue, and you cannot modify it and assume jQuery animation will "watch" for updated settings. There also seems to be no API for jQuery .animate() method to allow for changes in animation settings when it is already running.
The best solution will be to actually author your own easing function. Since it can be mathematically complicated, there are actually third-party plugins such as bez that you can use. The plugin returns a cubic-bezier function that allows you to set how the values should be tweened/interpolated.
If you want your values to change quickly at the start and slower towards the end, an example cubic bezier curve you can use is cubic-bezier(.20, 1, .20, 1):
p/s: You can use this site to play around with different parameters to get the interpolation you want: http://cubic-bezier.com
As mentioned above, if you're using the $.bez plugin to create an easing function, it is as simple as calling: $.bez([0.2,1,0.2,1]). Simply provide this as the easing parameter in your .animate method:
$('.holder').each(function() {
setTimeout(change, 2000);
$(this).prop('Counter', 0).animate({
Counter: $(this).data('number')
}, {
duration: 15000,
easing: $.bez([0.2,1,0.2,1]),
step: function(now) {
$(this).text(Math.ceil(now).toLocaleString('en'));
}
});
});
See proof-of-concept below:
/*!
* Bez #VERSION
* http://github.com/rdallasgray/bez
*
* A plugin to convert CSS3 cubic-bezier co-ordinates to jQuery-compatible easing functions
*
* With thanks to Nikolay Nemshilov for clarification on the cubic-bezier maths
* See http://st-on-it.blogspot.com/2011/05/calculating-cubic-bezier-function.html
*
* Copyright #YEAR Robert Dallas Gray. All rights reserved.
* Provided under the FreeBSD license: https://github.com/rdallasgray/bez/blob/master/LICENSE.txt
*/
(function(factory) {
if (typeof exports === "object") {
factory(require("jquery"));
} else if (typeof define === "function" && define.amd) {
define(["jquery"], factory);
} else {
factory(jQuery);
}
}(function($) {
$.extend({ bez: function(encodedFuncName, coOrdArray) {
if ($.isArray(encodedFuncName)) {
coOrdArray = encodedFuncName;
encodedFuncName = 'bez_' + coOrdArray.join('_').replace(/\./g, 'p');
}
if (typeof $.easing[encodedFuncName] !== "function") {
var polyBez = function(p1, p2) {
var A = [null, null], B = [null, null], C = [null, null],
bezCoOrd = function(t, ax) {
C[ax] = 3 * p1[ax], B[ax] = 3 * (p2[ax] - p1[ax]) - C[ax], A[ax] = 1 - C[ax] - B[ax];
return t * (C[ax] + t * (B[ax] + t * A[ax]));
},
xDeriv = function(t) {
return C[0] + t * (2 * B[0] + 3 * A[0] * t);
},
xForT = function(t) {
var x = t, i = 0, z;
while (++i < 14) {
z = bezCoOrd(x, 0) - t;
if (Math.abs(z) < 1e-3) break;
x -= z / xDeriv(x);
}
return x;
};
return function(t) {
return bezCoOrd(xForT(t), 1);
}
};
$.easing[encodedFuncName] = function(x, t, b, c, d) {
return c * polyBez([coOrdArray[0], coOrdArray[1]], [coOrdArray[2], coOrdArray[3]])(t/d) + b;
}
}
return encodedFuncName;
}});
}));
$('.holder').each(function() {
$(this).prop('Counter', 0).animate({
Counter: $(this).data('number')
}, {
duration: 15000,
easing: $.bez([0.2,1,0.2,1]),
step: function(now) {
$(this).text(Math.ceil(now).toLocaleString('en'));
}
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<h3 class="holder" data-number="1000000"></h3>

Related

CLI-Progress package - How to hide the progress bar on start?

I'm using the CLI-Progress package from:
https://www.npmjs.com/package/cli-progress.
This is my implementation according to the documentation example:
https://github.com/npkgz/cli-progress/blob/master/examples/example-visual.js)
const b1 = new progress.Bar({
format: colors.cyan('[{bar}]') + ' {percentage}% || {value}/{total} Chunks || Speed: {speed}',
barCompleteChar: '\u2588',
barIncompleteChar: '\u2591',
hideCursor: true,
});
b1.start(200, 0, {
speed: "N/A"
});
let value = 0;
const speedData: number[] = [];
const timer = setInterval(() => {
value++;
speedData.push(Math.random() * 2 + 5);
const currentSpeedData = speedData.splice(-10);
b1.update(value, {
speed: (currentSpeedData.reduce((a, b) => {
return a + b;
}, 0) / currentSpeedData.length).toFixed(2) + "Mb/s"
});
if (value >= b1.getTotal()) {
clearInterval(timer);
b1.stop();
}
}, 20);
Which renders :
I have two questions about this :
Why is there two bars (I would like to get rid of the first one) ?
Why does it work since the timer function is never called (it is called recursively but there is no first call) ?
Thank you.

useAnimationOnDidUpdate react hook implementation

So I want to use requestAnimationFrame to animate something using react hooks.
I want something small so react-spring/animated/react-motion is a bad choice for me.
I implemented useAnimationOnDidUpdate but it is working incorrectly, here is reproduction with details.
What's wrong here: on second click multiplier for animation starts with 1, but should always start with 0 (simple interpolation from 0 to 1).
So I'm trying to understand why the hook saved previous value though I started a new animation loop already.
Here is a full code listing for hook:
import { useState, useEffect, useRef } from 'react';
export function useAnimationOnDidUpdate(
easingName = 'linear',
duration = 500,
delay = 0,
deps = []
) {
const elapsed = useAnimationTimerOnDidUpdate(duration, delay, deps);
const n = Math.min(1, elapsed / duration);
return easing[easingName](n);
}
// https://github.com/streamich/ts-easing/blob/master/src/index.ts
const easing = {
linear: n => n,
elastic: n =>
n * (33 * n * n * n * n - 106 * n * n * n + 126 * n * n - 67 * n + 15),
inExpo: n => Math.pow(2, 10 * (n - 1)),
inOutCubic: (t) => t <.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1,
};
export function useAnimationTimerOnDidUpdate(duration = 1000, delay = 0, deps = []) {
const [elapsed, setTime] = useState(0);
const mountedRef = useRef(false);
useEffect(
() => {
let animationFrame, timerStop, start, timerDelay;
function onFrame() {
const newElapsed = Date.now() - start;
setTime(newElapsed);
if (newElapsed >= duration) {
console.log('>>>> end with time', newElapsed)
return;
}
loop();
}
function loop() {
animationFrame = requestAnimationFrame(onFrame);
}
function onStart() {
console.log('>>>> start with time', elapsed)
start = Date.now();
loop();
}
if (mountedRef.current) {
timerDelay = delay > 0 ? setTimeout(onStart, delay) : onStart();
} else {
mountedRef.current = true;
}
return () => {
clearTimeout(timerStop);
clearTimeout(timerDelay);
cancelAnimationFrame(animationFrame);
};
},
[duration, delay, ...deps]
);
return elapsed;
}
This problem with this hook is that it doesn't clean up the elapsedTime upon completion.
You can resolve this by adding setTime(0) to you onFrame function when the animation is expected to stop.
Like this:
function onFrame() {
const newElapsed = Date.now() - start;
if (newElapsed >= duration) {
console.log('>>>> end with time', newElapsed)
setTime(0)
return;
}
setTime(newElapsed);
loop();
}
I know it may seem weird that it doesn't reset itself. But bear in mind that your animation is making use of the same hook instance for both easing in and out. Therefore that cleanup is necessary.
Note: I've also move the setTime(newElapsed) line so that it's after the if statement since this isn't necessary if the if statement is true.
UPDATE:
To further improve how this works, you could move the setTime(0) to the return cleanup.
This would mean that you're onFrame function changes to:
function onFrame() {
const newElapsed = Date.now() - start;
if (newElapsed >= duration) {
console.log('>>>> end with time', newElapsed)
setTime(0)
return;
}
setTime(newElapsed);
loop();
}
And then update your return cleanup for useAnimationTimerOnDidUpdate to:
return () => {
clearTimeout(timerStop);
clearTimeout(timerDelay);
cancelAnimationFrame(animationFrame);
setTime(0);
};
I'm assuming that the reason your animation "isn't working properly" was because the component would flash. As far as my testing goes, this update fixes that.

Percentage Change in dc.js/crossfilter

I just started with dc.js and was looking at the NASDAQ example on the main site: https://dc-js.github.io/dc.js/
I created a Fiddle with some sample dummy data and just the two relevant charts for this question.
Similar to the NASDAQ example, I want to have a bubble chart with the Y-Axis being the % Change in value over a timespan controlled by a brush in a different chart. The code for the NASDAQ example does the following:
var yearlyPerformanceGroup = yearlyDimension.group().reduce(
/* callback for when data is added to the current filter results */
function (p, v) {
++p.count;
p.absGain += v.close - v.open;
p.fluctuation += Math.abs(v.close - v.open);
p.sumIndex += (v.open + v.close) / 2;
p.avgIndex = p.sumIndex / p.count;
p.percentageGain = p.avgIndex ? (p.absGain / p.avgIndex) * 100 : 0;
p.fluctuationPercentage = p.avgIndex ? (p.fluctuation / p.avgIndex) * 100 : 0;
return p;
},
/* callback for when data is removed from the current filter results */
function (p, v) {
--p.count;
p.absGain -= v.close - v.open;
p.fluctuation -= Math.abs(v.close - v.open);
p.sumIndex -= (v.open + v.close) / 2;
p.avgIndex = p.count ? p.sumIndex / p.count : 0;
p.percentageGain = p.avgIndex ? (p.absGain / p.avgIndex) * 100 : 0;
p.fluctuationPercentage = p.avgIndex ? (p.fluctuation / p.avgIndex) * 100 : 0;
return p;
},
/* initialize p */
function () {
return {
count: 0,
absGain: 0,
fluctuation: 0,
fluctuationPercentage: 0,
sumIndex: 0,
avgIndex: 0,
percentageGain: 0
};
}
);
which I currently interpret as summing(close-open) across all data and dividing by the average of the average daily index. But this is not a percent change formula I am familiar with. (e.g. (new-old)/old x 100)
While it seems to work for the NASDAQ example, my data would be more like the following:
country_id,received_week,product_type,my_quantity,my_revenue,country_other_quantity
3,2017-04-02,1,1,361,93881
1,2017-04-02,4,45,140,93881
2,2017-04-02,4,2,30,93881
3,2017-04-02,3,1,462,93881
2,2017-04-02,3,48,497,93881
etc.. over many months and product_types.
Let's say I was interested in computing the percent change for a particular Country. How do I get the start and end quantities for a given country so I can compute change as end-start/start * 100?
I was thinking of something such as the following (assuming I set up the proper dimensions and everything)
var country_dim = ndx.dimension(function (d) { return d['country_id']; })
var received_day_dim = ndx.dimension(function (d) { return d['received_day']; })
var date_min = received_day_dim.bottom(1)[0]['received_day']
var date_max = received_day_dim.top(1)[0]['received_day']
Then in my custom reduce function currently in the vein of the example (wrong):
var statsByCountry = country_dim.group().reduce(
function (p, v) {
++p.count;
p.units += +v["my_units"];
p.example_rate = +v['my_units']/(v['quantity_unpacked']*90) //place holder for total units per day per country
p.sumRate += p.opp_buy_rate;
p.avgRate = p.opp_buy_rate/p.count;
p.percentageGain = p.avgRate ? (p.opp_buy_rate / p.avgRate) * 100 : 0;
p.dollars += +v["quantity_unpacked"]/2;
// p.max_date = v['received_week'].max();
// p.min_date
//dateDimension.top(Infinity)[dateDimension.top(Infinity).length - 1]['distance'] - dateDimension.top(Infinity)[0]['distance']
return p;
},
function (p, v) {
--p.count;
if (v.region_id > 2) {
p.test -= 100;
}
p.units -= +v["quantity_unpacked"];
p.opp_buy_rate = +v['quantity_unpacked']/(v['quantity_unpacked']*90) //place holder for total units per day per country
p.sumRate -= p.opp_buy_rate;
p.avgRate = p.count ? p.opp_buy_rate/p.count : 0;
p.percentageGain = p.avgRate ? (p.opp_buy_rate / p.avgRate) * 100 : 0;
p.dollars -= +v["quantity_unpacked"]/2;
// p.max_date = v['received_week'].max();
return p;
},
function () {
return {quantity_unpacked: 0,
count: 0,
units: 0,
opp_buy_rate: 0,
sumRate: 0,
avgRate: 0,
percentageGain: 0,
dollars: 0,
test: 0
};//, dollars: 0}
}
);
and my chart:
country_bubble
.width(990)
.height(250)
.margins({top:10, right: 50, bottom: 30, left:80})
.dimension(country_dim)
.group(statsByCountry)
.keyAccessor(function (p) {
return p.value.units;
})
.valueAccessor(function (p) { //y alue
return p.value.percentageGain;
})
.radiusValueAccessor(function (p) { //radius
return p.value.dollars/10000000;
})
.maxBubbleRelativeSize(0.05)
.elasticX(true)
.elasticY(true)
.elasticRadius(true)
.x(d3.scale.linear())
.y(d3.scale.linear())
// .x(d3.scale.linear().domain([0, 1.2*bubble_xmax]))
// .y(d3.scale.linear().domain([0, 10000000]))
.r(d3.scale.linear().domain([0, 10]))
.yAxisPadding('25%')
.xAxisPadding('15%')
.renderHorizontalGridLines(true)
.renderVerticalGridLines(true)
.on('renderlet', function(chart, filter){
chart.svg().select(".chart-body").attr("clip-path",null);
});
Originally thought of having something similar to the following in statsbycountry:
if (v.received_day == date_min) {
p.start_value += v.my_quantity;
}
if (v.received_day == date_max) {
p.end_value += v.my_quantity;
}
This seems a bit clumsy? But if I do this, I don't think this will continually update as other filters change (say time or product)? Ethan suggested I use fake groups, but I'm a bit lost.
With the working fiddle, we can demonstrate one way to do this. I don't really think this is the best way to go about it, but it is the Crossfilter way.
First you need to maintain an ordered array of all data in a group as part of the group using your custom reduce function:
var statsByCountry = country_dim.group().reduce(
function(p, v) {
++p.count;
p.units += +v["my_quantity"];
p.country_rate = p.units / (1.0 * v['country_other_quantity']) //hopefully total sum of my_quantity divided by the fixed country_other_quantity for that week
p.percent_change = 50 //placeholder for now, ideally this would be the change in units over the timespan brush on the bottom chart
p.dollars += +v["my_revenue"];
i = bisect(p.data, v, 0, p.data.length);
p.data.splice(i, 0, v);
return p;
},
function(p, v) {
--p.count;
p.units -= +v["my_quantity"];
p.country_rate = p.units / (1.0 * v['country_other_quantity']) //hopefully total sum of my_quantity divided by the fixed country_other_quantity for that week
p.percent_change = 50 //placeholder for now, ideally this would be the change in units over the timespan brush on the bottom chart
p.dollars -= +v["my_revenue"];
i = bisect(p.data, v, 0, p.data.length);
p.data.splice(i, 1)
return p;
},
function() {
return {
data: [],
count: 0,
units: 0,
country_rate: 0,
dollars: 0,
percent_change: 0
}; //, dollars: 0}
}
);
Above, I've updated your reduce function to maintain this ordered array (ordered by received_week) under the .data property. It uses Crossfilter's bisect function to maintain order efficiently.
Then in your valueAccessor you want to actually calculate your change in value based on this data:
.valueAccessor(function(p) { //y alue
// Calculate change in units/day from first day to last day.
var firstDay = p.value.data[p.value.data.length-1].received_week.toString();
var lastDay = p.value.data[0].received_week.toString();
var firstDayUnits = d3.sum(p.value.data, function(d) { return d.received_week.toString() === firstDay ? d.my_quantity : 0 })
var lastDayUnits = d3.sum(p.value.data, function(d) { return d.received_week.toString() === lastDay ? d.my_quantity : 0 })
return lastDayUnits - firstDayUnits;
})
You do this in the value accessor because it only runs once per filter change, whereas the reduce functions run once per record added or removed, which can be thousands of times per filter.
If you want to calculate % change, you can do this here as well, but the key question for % calculations is always "% of what?" and the answer to that question wasn't clear to me from your question.
It's worth noting that with this approach your group structure is going to get really big as you are storing your entire data set in the groups. If you are having performance problems while filtering, I would still recommend moving away from this approach and towards one based on a fake group.
Working updated fiddle: https://jsfiddle.net/vysbxd1h/1/

Easing animations in Canvas

Trying to create a function that lets you animate any number of numerical properties with a given easing function, but it doesn't quite work... calling it doesn't result in any motion. Everything is set up correctly as when I change what the values change to, it does show, so that means it's the equation that's the problem here. It's either not giving the right value, or not getting the right ones.
function animate(obj, props, options) {
var start = Date.now(),
total = start + options.duration,
diff = total - start,
vals = {},
id;
for (var v in props) {
vals[v] = props[v];
}
(function update() {
var curr = Date.now(),
progress = Math.min((options.duration - (total - curr)) / options.duration, 1);
for (var p in props) {
console.log(obj[p] = options.equation(curr, vals[p], obj[p] - vals[p], total));
}
if (progress < 1) {
id = requestAnimationFrame(update);
} else {
id = cancelAnimationFrame(id);
if (typeof options.callback === 'function') {
options.callback();
}
}
}());
}
animate(rect, {
x: map.width / 2,
y: map.height / 2
}, {
duration: 2000,
equation: function(t, b, c, d) {
return c * (t /= d) * t + b;
},
callback: function() {
console.log('Whoa... it works.'); // ...yeah, nope. ;(
}
});
t = time, b = beginning value, c = change in value, d = duration.
Am I giving it the wrong arguments? How would I make this work?
Your time & duration arguments should not be summed with Date.now().
If you want your easing to take 2000ms then send 2000 (d) into the easing equation.
The time to send into the easing equation is the elapsed time, so send Date.now()-startTime (t) into the easing equation.
I assume you have properly set the beginning value (b) and net change in value (c).

optionals skipping of fx in mootools

Is there a simple way to skip all the fx, while still setting the values and calling the events.
I figured out to set the fx duration options globally to 0 by doing
Fx.prototype.options.duration = 0
but this still doesn't solve my problem because it sill takes some minimal time which ends up in a lot of displaying errors.
what would be nice is something like
Fx.ENGINE = 'on' / 'off'
Fx.SPEED_MULTIPLYER = 1 ... 10
Well after a litte hacking I've found a solution myself...
$extend(Fx.Durations, { skip: 0 });
$extend(Fx.prototype.options, { skip: false, multiplier: 1 });
Fx.implement({
step: function() {
var time = $time();
if ((time < this.time + (this.options.duration / this.options.multiplier)) && !this.options.skip){
var delta = this.transition((time - this.time) / (this.options.duration / this.options.multiplier));
this.set(this.compute(this.from, this.to, delta));
} else {
this.set(this.compute(this.from, this.to, 1));
this.complete();
}
},
startTimer: function(){
if (this.timer) return false;
this.time = $time() - this.time;
this.step();
this.timer = this.step.periodical(Math.round(1000 / this.options.fps), this);
return true;
}
});
There is now a skip options which allows you to skip the effect and a multiplier option to globally speed up / slow down the effect.
jim

Categories

Resources