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).
Related
I want to update time of my pc with the website in which use Lightstreamer for updating its time. In the debugging tab in chrome devtools, I found that the time value is updated in which line:
define("lscAZ", ["LoggerManager", "IllegalArgumentException", "lscAe"], function (d, f, b) {
function a(a, b, c, d, f) { this.Ay = b; this.zy = a; this.qu = d; this.ma = c; **this.Sd = f** } var c = d.getLoggerProxy(b.Ok); a.prototype = {
Bm: function () { return this.zy }, lj: function () { return this.Ay }, getValue: function (a) { a = this.ei(a); return (a = this.Sd[a]) && a.QC ? a.value : a }, Cr: function (a) { a = this.ei(a); return !this.Sd.Ao[a] }, py: function () { return this.qu }, forEachChangedField: function (a) {
for (var b = this.Sd.Mc, f = 0; f < b.length; f++) {
var k = this.ma.getName(b[f]),
h = this.Sd[b[f] + 1]; try { a(k, b[f], h) } catch (l) { c.logErrorExc(l, d.resolve(402)) }
}
}, Eq: function (a) { for (var b = 2; b < this.Sd.length; b++) { var f = b - 1, k = this.ma.getName(f), h = this.Sd[b]; try { a(k, f, h) } catch (l) { c.logErrorExc(l, d.resolve(403)) } } }, ei: function (a) { a = isNaN(a) ? this.ma.oe(a) : a; if (null == a) throw new f("the specified field does not exist"); if (0 >= a || a > this.ma.ym() + 1) throw new f("the specified field position is out of bounds"); return a + 1 }, cx: function () { return this.Sd.length - 2 }, Kw: function (a) { return this.ma.getName(a) }
};
a.prototype.getItemName = a.prototype.Bm; a.prototype.getItemPos = a.prototype.lj; a.prototype.getValue = a.prototype.getValue; a.prototype.isValueChanged = a.prototype.Cr; a.prototype.isSnapshot = a.prototype.py; a.prototype.forEachChangedField = a.prototype.forEachChangedField; a.prototype.forEachField = a.prototype.Eq; return a
});
In this code, the value Sd or f belongs to the website's time. the problem is that I don't know where can I find the calculation process of f and how to call the above code function to catch this value in console (I can't even recognize the name of function!). On the other hand, the clock updated by a function that use output of this function as updateInfo named construct and use it in the website.
these all are automatically and I need to do it manually with less intervals.
calculation of f is more useful for me because this value is formatted to string and doesn't contain milliseconds (formatted as hh:mm:ss).
configuration of Lightstreamer is as below
/*
* LIGHTSTREAMER - www.lightstreamer.com
* Lightstreamer Web Client
* Version 7.2.0 build 1777
* Copyright (c) Lightstreamer Srl. All Rights Reserved.
* Contains: LightstreamerClient, Subscription, ConnectionSharing, SimpleLoggerProvider
* ConsoleAppender, Promise
* Globals
*/
Please note that, since version 8.0.0, Lightstreamer Web Client SDK Library is open source and can be found at https://github.com/Lightstreamer/Lightstreamer-lib-client-javascript
This may be of help with your efforts.
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>
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/
I am having trouble understanding how to return information to the first function from the second when there are multiple arguments. Now I know the following code works.
function One() {
var newVal = 0;
newVal = Too(newVal);
console.log(newVal);
}
function Too(arg) {
++arg;
return arg;
}
But what if I try to complicate things by adding arguments and a setinterval.
function One() {
var newVal = 0;
var z = 3;
var y = 3;
var x = 1;
newVal = Too(newVal);
var StopAI2 = setInterval(function () {
Too(x, y, z, newVal)
}, 100);
}
function Too(Xarg, Yarg, Zarg, newValarg) {
Xarg*Xarg;
Yarg*Yarg;
Zarg*Zarg;
++newValarg;
return newValarg;
}
I'm not sure what to do with the newVal = line of code. I only want to return the newVal not x,y,z.
This is what I think you're trying to ask:
How can I operate on the 4th argument to a function when only one argument is passed?
The answer to that question is this:
If you want to operate on the 4th argument of a function, at least 4 arguments must be passed to the function.
There are a few ways you can approach your problem differently.
#1
If there's one argument that is always necessary, make sure it's the first argument:
function Too(mandatoryArg, optionalArg1, optionalArg2) {
alert(++mandatoryArg);
if (optionalArg1) {
alert(++optionalArg1);
}
}
#2
Pass placeholder values for all the undefined or unknown arguments.
You might use null, undefined, or ''.
alert(Too(null, null, 4));
function Too(optArg1, optArg2, mandatoryArg) {
alert(++mandatoryArg);
}
#3
Make a decision based on the number of arguments:
function Too(optArg1, optArg2, optArg3) {
var numArgs = arguments.length;
if (numArgs === 1) {
alert(++optArg1);
}
if (numArgs === 3) {
alert(++optArg3);
}
}
EDIT
"Will this update a variable in the first function?"
Let's use an actual example that demonstrates something:
function one() {
var a = 0;
var b = 25;
var c = 50;
var d = -1;
d = two(a, b, c);
alert("a: " + a);
alert("b: " + b);
alert("c: " + c);
alert("d: " + d);
}
function two(a, b, c) {
++a;
++b;
++c;
if (arguments.length === 1) {
return a;
}
if (arguments.length === 3) {
return c;
}
}
Invoking one() will cause the following alerts:
a: 0
b: 25
c: 50
d: 51
Only the value of d is modified in function one().
That's because d is assigned the return value of two().
The changes to a, b, and c, inside two() have no effect on the values of a, b, and c inside one().
This would be the case even if the arguments for two() were named a, b, and c.
Here's a fiddle with the code above.
EDIT #2
Here is one way you could create functions that move a game object:
var FORWARD = 0;
var BACK = 1;
var LEFT = 2;
var RIGHT = 3;
// use an object with three values to represent a position
var pos = {
x: 0,
y: 0,
z: 0
};
pos = moveObject(pos, FORWARD);
printPosition(pos);
pos = moveObject(pos, LEFT);
printPosition(pos);
pos = moveObject(pos, FORWARD);
printPosition(pos);
pos = moveObject(pos, LEFT);
printPosition(pos);
// invoking moveObject() with one argument
// will move the object forward
pos = moveObject(pos);
printPosition(pos);
function moveObject(position, direction) {
// assume FORWARD if no direction is specified
if (typeof direction === 'undefined') {
direction = FORWARD;
}
if (direction === FORWARD) {
++position.z;
}
if (direction === BACK) {
--position.z;
}
if (direction === LEFT) {
--position.x;
}
if (direction === RIGHT) {
++position.x;
}
return position;
}
function printPosition(pos) {
alert(pos.x + ", " + pos.y + ", " + pos.z);
}
Here's a fiddle that shows a working demo of another approach.
There are two concepts that are at play here.
1 . Variable number of function parameters (or optional parameters).
If you are going to call the same function with different number of parameters (this will eventually lead to a world of headache), you need to determine (inside the function) how this function was called. You can use arguments object available inside each function:
function Too() {
if (arguments.length == 4) {
arguments[0]*arguments[0];
arguments[1]*arguments[1];
arguments[2]*arguments[2];
return ++arguments[3];
} else if (arguments.length == 1) {
return ++arguments[0];
} else {
// you decide what to do here
}
}
2 . Asynchronous code execution.
Realize that Too which is called when interval expires, executes well after One completes and returns. If you want Too to affect newVal variable, and somehow get at this new value afterwards, - make newVal variable global.
When using Crossfilter (https://github.com/square/crossfilter), I specify functions to use when adding and removing data from a group. It's fairly trivial to keep track of a running average (using CoffeeScript):
reduceAdd = (p, v) ->
++p.count;
p.sum += v.digit;
p
reduceRemove = (p, v) ->
--p.count;
p.sum -= v.digit;
p
reduceInitial = ->
{
count: 0
sum: 0
average: ->
return 0 if this.count == 0
return this.sum / this.count
}
Is it possible to keep track of the max and min of each group? I can't figure out a way short of keeping all elements in a huge array and doing a d3.min / d3.max. It seems that adding/removing data would be extremely inefficient.
I also looked for a way to tell Crossfilter to completely rebuild the group from scratch, rather than removing items from an existing group. If a filter is applied, the group is reset and rebuilt. Nothing obvious.
You can use dimension.top(1) and dimension.bottom(1) to retrieve the current min and max. These methods respect any filters that may be active on the crossfilter.
The best solution I came up with, was to keep track of all values in an ordered list and to add elements with a simple quicksort-style insertion function (cp. how to insert a number into a sorted array) and to remove them using indexOf.
Common functions:
function insertElement(element, array) {
array.splice(locationOfElement(element, array) + 1, 0, element);
return array;
}
function removeElement(element, array) {
var index = array.indexOf(element);
if (index >= 0) array.splice(index, 1);
return array;
}
function locationOfElement(element, array, start, end) {
start = start || 0;
end = end || array.length;
var pivot = parseInt(start + (end - start) / 2, 10);
if (array[pivot] === element) return pivot;
if (end - start <= 1)
return array[pivot] > element ? pivot - 1 : pivot;
if (array[pivot] < element) {
return locationOfElement(element, array, pivot, end);
} else {
return locationOfElement(element, array, start, pivot);
}
}
function maxElement(array) {
return (array.length > 0) ?
array[array.length - 1] : null;
}
function minElement(array) {
return (array.length > 0) ?
array[0] : null;
}
Functions to use when adding and removing data from a group to track min / max:
minMaxDimension = cf.dimension(function (d) {
return d.key;
});
var reduceAdd = function(p, v) {
insertElement(v.value, p.elements);
return p;
};
var reduceRemove = function(p, v) {
removeElement(v.value, p.elements);
return p;
};
var reduceInitial = function() {
return {
elements: [],
max: function() { return maxElement(elements); },
min: function() { return minElement(elements); }
}
}
minMaxGroup = minMaxDimension
.group()
.reduce(reduceAdd, reduceRemove, reduceInitial)
.orderNatural()
.top(Infinity);
After playing around with this for a bit, you can rebuild the group by just calling the group method again.