In angular updating one variable inexplicably updates another - javascript

I am using angular and plotly to plot either the raw data or a moving average. I have the moving average working but I am running into an issue with assigning variables. I retrieve an array of user objects which each have an x and y key with arrays associated with them.
$scope.init=function(){
$rootScope.page='companyResults';
$scope.isPlotlyDone = false;
$scope.moving = false;
var refresh = function () {
incidentService.dayWiseTripsByUser(...).then(function (plotArray){
$scope.unaffectedPlot = plotArray;
$scope.movingAveragePlot = allMoving(plotArray);
console.log($scope.unaffectedPlot[0].y);
console.log($scope.movingAveragePlot[0].y);
});
};
refresh();
}
Im that code block, I would expect that $scope.unaffectedPlot[0].y and $scope.movingAveragePlot[0].y would have different arrays since I ran the latter through the following set of functions. The curious thing is that both $scope variables are synced, so if I run the second through allMoving the unaffectedPlot variable also gets smoothed and neither get synced obviously if I don't call allMoving. What am I missing about Angular? What is a good way to have a moving average work with a toggle? My plan is to show one variable or the other depending on if a button is clicked.
var d3_numeric = function(x) {
return !isNaN(x);
}
var d3sum = function(array, f) {
var s = 0,
n = array.length,
a,
i = -1;
if (arguments.length === 1) {
// zero and null are equivalent
while (++i < n) if (d3_numeric(a = +array[i])) s += a;
} else {
while (++i < n) if (d3_numeric(a = +f.call(array, array[i], i))) s += a;
}
return s;
};
var movingWindowAvg = function (arr, step) {
return arr.map(function (_, idx) {
var wnd = arr.slice(idx - step, idx + step + 1);
var result = d3sum(wnd) / wnd.length; if (isNaN(result)) { result = _; }
return result;
});
};
var allMoving = function(pltArray) {
var movingArray = [];
pltArray.forEach(function(plot){
var oneMoving = plot;
oneMoving.y = movingWindowAvg(plot.y, 5);
movingArray.push(oneMoving);
});
return movingArray;
}

This actually isn't an angular issue. I had to test it some since I didn't see what was going on either.
When you wrote
oneMoving.y = blah
you were actually altering the contents of plot for each element and in turn altering the contents of plotArray unintentionally (since plot is an object)
So you are only creating a reference variable when you say 'var onMoving = plot' )
To outright solve your problem you can clone plot but that isn't so clean of a process
One easy yet dirty way is
JSON.parse(JSON.stringify(obj))
from this thread
I threw together a shotty example that captures what was going wrong for you
var array = [{one:1, two:2},{one:1, two:2},{one:1, two:2}],
copyArray = array,
newArr = doStuff(array)
function doStuff(a) {
var otherNewArr = []
a.forEach(function(ae) {
var aVar = ae
aVar.one = 5
otherNewArr.push(aVar)
})
return otherNewArr
}
console.log(copyArray,newArr)
And to fix it just replace
var aVar = ae
with
var aVar = JSON.parse(JSON.stringify(ae))

Related

Getting Incorrect range height for seemingly no reason?

I am writing a script to copy and paste a range from one sheet to another. The pasted range size should be reduced by using two functions : one to delete rows with specific values and the other is an aggregate function.
I started getting this error after I introduced the aggregate function The function is basically reducing the array size using the reduce JS function.
I have replicated my problem here and the code is accessible in the script editor.
When I run the script I am getting the following error :
Incorrect range height was 28 but should be 7 (line 36, file "test")
I have no idea why am I getting this error. My aggregate function returns a properly formatted array with the right length.
function append_range(){
var origin_sheet = SpreadsheetApp.openById('1-2ZheMz1p01qwtwY3ghbNjJedYfGXeylnLEjDMCLpMw');//open the file
origin_sheet = origin_sheet.getSheetByName('test');
var rangeStart = 2;
var range = origin_sheet.getRange('A'+ (rangeStart.toString())+':T'+ (origin_sheet.getLastRow()).toString());
var dataFromRange = range.getValues();
var dataFromRangeLength = dataFromRange.length;
var destination_sheet = SpreadsheetApp.openById('1-2ZheMz1p01qwtwY3ghbNjJedYfGXeylnLEjDMCLpMw');
destination_sheet = destination_sheet.getSheetByName('append');
var rowLast = destination_sheet.getLastRow()+1;
Logger.log("row last" + rowLast);
var formattedRange = deleteRows(dataFromRange);
var groups = aggregate(formattedRange);
var aggregates = [];
for(var group in groups)
{
aggregates.push(groups[group]);
}
Logger.log(aggregates);
var formattedRangeLength = aggregates.length;
Logger.log("formattedRangeLength" + formattedRangeLength);
destination_sheet.getRange(rowLast,1,formattedRangeLength, 20).setValues(deleteRows(dataFromRange));
function isDate(sDate) {
if (isValidDate(sDate)) {
sDate = Utilities.formatDate(new Date(sDate), "PST", "yyyy-MM-dd");
}
return sDate;
}
function isValidDate(d) {
if ( Object.prototype.toString.call(d) !== "[object Date]" )
return false;
return !isNaN(d.getTime());
}
//
function deleteRows(dataRange){//just pass the range in an array and this method will return another array with filtered range
var formatted = dataRange.filter(function(e) {
return e[8]||e[9]||e[10]||e[11]||e[12]||e[13]||e[14]||e[15]||e[16]||e[17]||e[18]||e[19];
});
return formatted;
}
function aggregate(data)
{
var groups = data.reduce(
function(accumulator, previous){
{
var key = previous[1] + previous[3] + previous[5] + previous[6];
var group = accumulator[key];
if(group == null || typeof group == 'undefined')
{
accumulator[key] = previous;
}
else {
var startIndex = 8;
for(var i = startIndex; i < previous.length;i++)
{
group[i] += previous[i];
}
}
return accumulator;
}},
{});
return groups;
}
}
The .setValues() is not setting your aggregates array it is trying to set deleteRows(dataFromRange)
// Change the setValues() to your reduced array
destination_sheet.getRange(rowLast,1,formattedRangeLength, 20).setValues(aggregates);
I think this might work:
var output=deleteRows(dataFromRange));
destination_sheet.getRange(rowLast,1,output.length, output[0].length).setValues(deleteRows(output));
This assumes a non jagged array.

Sequence from Class OO to Object Delegation pattern

My intention is to use function logFive2 to iterate over
a sequence object like ArraySeq2 or RangeSeq2 although I want to create the RangeSeq2 using Object delegation pattern and stay away from Class like way(ArraySeq2). What I am doing wrong with RangeSeq2?
My code doesn't work because logFive2 does not iterate over RangeSeq2 and I cannot see why. If you have any idea about what goes wrong please let me see. Thank you.
function logFive2(sequence){
for(var i = 0; i < 5 && sequence != null; i++){
console.log(sequence.head());
sequence = sequence.rest();
}
}
function ArraySeq2(array,offset){
this.array = array;
this.offset = offset;
}
ArraySeq2.prototype.rest = function(){
console.log("to follow " + this.offset);
return ArraySeq2.make(this.array,this.offset + 1);
};
ArraySeq2.prototype.head = function(){
return this.array[this.offset];
};
ArraySeq2.make = function(array,offset){
if(offset == null) offset = 0;
if(offset >= array.length)
return null;
else return new ArraySeq2(array,offset);
}
logFive2(ArraySeq2.make([1, 2,5,6,9,11]));
// → 1
// → 2
The part above works fine ______________ RangeSeq2 object it is my problem
var RangeSeq2 = {
init: function(from,to){
this.from = from;
this.to = to;
},
rest: function(){
if (from > to)
return null;
else
return this.init(this.from + 1,this.to);
},
head: function(){
return this.from;
}
};
var RangeTT = Object.create(RangeSeq2);
RangeTT.init(100,1000);
logFive2(RangeTT.init(100,1000));
function logFive2(sequence){
for(var i = 0; i < 5 ; i++){
console.log(sequence.head());
sequence.rest();
}
}
var RangeSeq2 = {
rest: function(){
if (this.from > this.to) {
return null;
}
else
return this.from += 1,this.to;
},
head: function(){
return this.from;
}
};
var RangeTT = Object.create(RangeSeq2);
RangeTT.from = 100;
RangeTT.to = 1000;
logFive2(RangeTT);
//100
//101
//102
//103
//104
Sorted out! the problem was so much simpler than I thought will be.
My problem was trying to do an unhealthy mixture of classical inheritance and instantiation over the Object delegation because I didn't understood how it works.
Soon as I managed to understand how "this" works and soon I understood Object.create (which is very powerful ) , the __proto__ and knew the difference it has compared to function Object.prototype I could find a solution.
1.My first mistake I think was trying to create state in the object by calling the method init() without having a property to hold the values in the object.
2.The rest() method would query on variables which would not exist on the object.
I have to mention that I had to change the iterator function LogFive2() to be suitable for the object delegation design in my case.

indexOf() : is there a better way to implement this?

EDIT
Thank you guys, and i apologize for not being more specific in my question.
This code was written to check if a characters in the second string is in the first string. If so, it'll return true, otherwise a false.
So my code works, I know that much, but I am positive there's gotta be a better way to implement this.
Keep in mind this is a coding challenge from Freecodecamp's Javascript tree.
Here's my code:
function mutation(arr) {
var stringOne = arr[0].toLowerCase();
var stringTwo = arr[1].toLowerCase().split("");
var i = 0;
var truthyFalsy = true;
while (i < arr[1].length && truthyFalsy) {
truthyFalsy = stringOne.indexOf(stringTwo[i]) > -1;
i++
}
console.log(truthyFalsy);
}
mutation(["hello", "hey"]);
//mutation(["hello", "yep"]);
THere's gotta be a better way to do this. I recently learned about the map function, but not sure how to use that to implement this, and also just recently learned of an Array.prototype.every() function, which I am going to read tonight.
Suggestions? Thoughts?
the question is very vague. however what i understood from the code is that you need to check for string match between two strings.
Since you know its two strings, i'd just pass them as two parameters. additionally i'd change the while into a for statement and add a break/continue to avoid using variable get and set.
Notice that in the worst case its almost the same, but in the best case its half computation time.
mutation bestCase 14.84499999999997
mutation worstCase 7.694999999999993
bestCase: 5.595000000000027
worstCase: 7.199999999999989
// your function (to check performance difference)
function mutation(arr) {
var stringOne = arr[0].toLowerCase();
var stringTwo = arr[1].toLowerCase().split("");
var i = 0;
var truthyFalsy = true;
while (i < arr[1].length && truthyFalsy) {
truthyFalsy = stringOne.indexOf(stringTwo[i]) > -1;
i++
}
return truthyFalsy;
}
function hasMatch(base, check) {
var strOne = base.toLowerCase();
var strTwo = check.toLowerCase().split("");
var truthyFalsy = false;
// define both variables (i and l) before the loop condition in order to avoid getting the length property of the string multiple times.
for (var i = 0, l = strTwo.length; i < l; i++) {
var hasChar = strOne.indexOf(strTwo[i]) > -1;
if (hasChar) {
//if has Char, set true and break;
truthyFalsy = true;
break;
}
}
return truthyFalsy;
}
var baseCase = "hello";
var bestCaseStr = "hey";
var worstCaseStr = "yap";
//bestCase find match in first iteration
var bestCase = hasMatch("hello", bestCaseStr);
console.log(bestCase);
//worstCase loop over all of them.
var worstCase = hasMatch("hello", worstCaseStr);
console.log(worstCase);
// on your function
console.log('mutation bestCase', checkPerf(mutation, [baseCase, bestCaseStr]));
console.log('mutation worstCase', checkPerf(mutation, [baseCase, worstCaseStr]));
// simple performance check
console.log('bestCase:', checkPerf(hasMatch, baseCase, bestCaseStr));
console.log('worstCase:', checkPerf(hasMatch, baseCase, worstCaseStr));
function checkPerf(fn) {
var t1 = performance.now();
for (var i = 0; i < 10000; i++) {
fn(arguments[1], arguments[2]);
}
var t2 = performance.now();
return t2 - t1;
}

Equalizing Element Heights with JavaScript

I am writing Javascript that will take an element with the class of "eqlizm", get the .offsetHeight of all the .children, determine the max value, and then set the heights of all the .children to that value.
My problem is that while I can get it to echo the values out, it is only setting them for the first child.
I have the code setup on CodePen.io but here is the script, complete with my diagnostic console.logs:
var eqlizmContainers = document.querySelectorAll(".eqlizm");
function getArrMax(numArray) {
return Math.max.apply(null, numArray);
}
function findMaxHeight(targetContainer) {
var eqlizNodes = targetContainer.children;
var childHeights = [];
for (c = 0; c < eqlizNodes.length; c++) {
childHeights.push(eqlizNodes[c].offsetHeight);
}
return getArrMax(childHeights);
}
function eqlizHeight(targetContainer) {
var eqlizNodes = targetContainer.children;
//console.log(eqlizNodes);
for (c = 0; c < eqlizNodes.length; c++) {
//console.log(targetContainer + " " + c);
//console.log(eqlizNodes[c]);
eqlizNodes[c].style.height = findMaxHeight(targetContainer) + "px";
}
}
for (i = 0; i < eqlizmContainers.length; i++) {
//console.log("Tier " + (i+1) + ": " + findMaxHeight(eqlizmContainers[i]) + "px");
eqlizHeight(eqlizmContainers[i]);
}
After I get this function working, I will be adding event listeners for the resizing of the browser window.
I have looked high and low for a solution but all of the ones I could find involve jQuery which I want to avoid using that.
The biggest problem with the code you posted is that you're misunderstanding variable scoping in JavaScript.
All variables are function-scoped, regardless of the block in which they're initialized.
Your c = 0 is being saved in the global scope.
window.c; //0
Thus, inside of eqlizHeight, you're setting c to 0, and then for each call to findMaxHeight (which looks like a pretty big de-op, that would be better suited to run outside of the loop, and be stored once per parent), you're running the value of c up to the length of the children of the first parent.
The quick and dirty solution is to add var c = 0; to your variable declarations in the functions.
Then your for constructs can look like for (; c < ...; c++).
I prefer working in a functional fashion, without explicit looping, so I had a quick run at a second solution:
// turns an array-like into an array (el.children, document.querySelectorAll(...), etc)
function slice (arrlike, start, end) {
return [].slice.call(arrlike, start, end);
}
// takes a property name and returns a reusable function
// var getName = pluck("name");
// getName({ name: "Bob" }); // Bob
// [{ name: "Bob" }, { name: "Doug" }].map(getName); // ["Bob", "Doug"]
function pluck (key) {
// returns a function, which will pull the same property from any object it gets
return function (obj) { return obj[key]; };
}
// it's just faster calling query(selector); than document.querySelector(selector);
function query (selector) {
return document.querySelector(selector);
}
// same as above, but I also turn the resulting HTMLCollection into an Array
function queryAll (selector) {
return slice(document.querySelectorAll(selector));
}
// take an array of numbers and return the max
function max (numbers) {
numbers = Array.isArray(numbers) ? numbers : slice(arguments);
return Math.max.apply(Math, numbers);
}
// take an array of numbers and return the min
function min (numbers) {
numbers = Array.isArray(numbers) ? numbers : slice(arguments);
return Math.min.apply(Math, numbers);
}
// returns an object with top, right, bottom, left, width, height in pixels
// (measured from the visible part of the page, so scrolling affects the numbers)
function getBoundingClientRect (el) {
return el.getBoundingClientRect();
}
// takes a style-name, and value, and returns a reusable function
// var setHeightTo100 = setStyle("height", "100px");
// setHeightTo100(el_1);
// setHeightTo100(el_2);
function setStyle (key, value) {
// sets the same el.style[key] = value; for every element it's passed
return function (el) { el.style[key] = value; };
}
// takes an array of elements
function resizeChildren (nodes) {
nodes = Array.isArray(nodes) ? nodes : slice(arguments);
// for each element ("container")
nodes.forEach(function (container) {
// get the container's children as an array
var children = slice(container.children);
// get the max( ) of the .height properties of the boundingClientRects of the children
var maxHeight = max(children.map(getBoundingClientRect).map(pluck("height")));
// set style.height = maxHeight + "px" on each child
children.forEach(setStyle("height", maxHeight + "px"));
});
}
// for a list of all ".eqlizm" children, resizeChildren
resizeChildren(queryAll(".eqlizm"));
If you paste that into your pen, I think you'll find that it works just fine.
Edit: illustrating the quick & dirty fix to the preexisting code
var eqlizmContainers = document.querySelectorAll(".eqlizm");
var i = 0;
function getArrMax(numArray) {
return Math.max.apply(null, numArray);
}
function findMaxHeight(targetContainer) {
var eqlizNodes = targetContainer.children;
var childHeights = [];
var c = 0;
for (c = 0; c < eqlizNodes.length; c++) {
childHeights.push(eqlizNodes[c].offsetHeight);
}
return getArrMax(childHeights);
}
function eqlizHeight(targetContainer) {
var eqlizNodes = targetContainer.children;
var c = 0;
for (c = 0; c < eqlizNodes.length; c++) {
eqlizNodes[c].style.height = findMaxHeight(targetContainer) + "px";
}
}
for (i = 0; i < eqlizmContainers.length; i++) {
eqlizHeight(eqlizmContainers[i]);
}
Other than removing the comments, all I've done is added two var c; lines.
Whether you initialize var c to a value or not doesn't matter; all that matters is that you use var to announce it.
Otherwise, it gets saved in the global scope.
Sure, you could just keep using a, b, d, e, f, g, h, j, ..., but that means that any time two pieces of code on the same page use x, one will overwrite the other...
var c; inside of the function tells JavaScript that c's home is there, and not to look any higher up.
Assign them all targets a similar class and loop through them.
$(document).ready(function(){
var height = 0;
$(".homepage-product").each(function(){
if ($(this).height() > height){
height = $(this).height()
}
});
$(".homepage-product").each(function(){
$(this).height(height);
});
});
JSFIDDLE example

Searching an array of objects using jquery

I have a log analysis script to populate a complex visualisation.
Picture an array (called, rather unoriginally, 'log') of Activity objects, each of which is in the form:
{
name:foo,
activities:[
{time:t, action:a},
{time:t, action:a},
{time:t, action:a},
...
]
}
There will be up to 75 activity objects in the array, each containing an array of 400-600 actions (one timeslot every 5 minutes since midnight the previous day).
Given a known activity name (foo above) and a time that will already exist in the activities array, I need to update the associated action.
Each name will be unique and each time is in ascending order in the array in exact 5 minutes increments.
As I have to do this a little over 1000 times every time the graph is updated (so an average of 1000 values to update and 1000*500*60 points to plot), performance is a fairly key issue...
Looping in jq is far more efficient than anything I could write so, at the moment, I have
n = "foo";
t = new Date(y,mm,d,h,m).toLocaleString() // matches a time stamp in the log
$.grep($.grep(log, function(n, i)
{
return (n.name == n)
}
)[0].activities, function(n, i)
{
return (n.time == t)
}
)[0].action = "bar";
That seems to be working, but it's taken me so long and I've had so many arguments with myself that I'm not confident.
Am I missing a better way?
I wont give you a better loop method for your problem, as any loop you come up with will relatively be no better than the last.
If you truly want a solution that will enhance performance, you should think about rearranging your object entirely. If every name of each log and time of each activities array is unique, you can change your object setup to have those values as the key of each subobject.
Using this method, you'll just be doing a key look up, no loop needed.
New LOG Object
var log =
{
unique_name : {
"activities" : {
time_1 : action_1,
time_2 : action_2,
time_3 : action_3,
etc...
}
},
unique_name_2 : {
"activities" : {
etc...
}
}
}
Now with var u_name = "foo"; and var t = "some time"; you can simply do...
log[u_name][t] = "some action";
Hope this helps!
Seems like you want the first matched activity of the first matched log.
In that case, you should break the loop after the first match is found. You can do this with .some().
n = "foo";
t = new Date(y,mm,d,h,m).toLocaleString() // matches a time stamp in the log
log.some(function(ob, i) {
if (ob.name == n) {
ob.activities.some(function(ob2, i) {
if (ob2.time == t) {
ob2.action = "bar";
return true;
}
});
return true;
}
});
Also, your n parameter was shadowing your n variable, so I changed the param to ob.
But for loops will generally be quite a bit faster than functional methods.
n = "foo";
t = new Date(y,mm,d,h,m).toLocaleString() // matches a time stamp in the log
for (var i = 0; i < log.length; i++) {
var ob = log[i];
if (ob.name == n) {
for (var j = 0; j < ob.activities.length; j++) {
var ob2 = ob.activities[j];
if (ob2.time == t) {
ob2.action = "bar";
break;
}
}
break;
}
}
If you decide that you should keep the outer loop going if there's no match found on the inner loop, change the code to one of these:
n = "foo";
t = new Date(y,mm,d,h,m).toLocaleString() // matches a time stamp in the log
log.some(function(ob, i) {
if (ob.name == n) {
return ob.activities.some(function(ob2, i) {
if (ob2.time == t) {
ob2.action = "bar";
return true;
}
});
}
});
n = "foo";
t = new Date(y,mm,d,h,m).toLocaleString() // matches a time stamp in the log
OUTER:
for (var i = 0; i < log.length; i++) {
var ob = log[i];
if (ob.name == n) {
for (var j = 0; j < ob.activities.length; j++) {
var ob2 = ob.activities[j];
if (ob2.time == t) {
ob2.action = "bar";
break OUTER;
}
}
}
}

Categories

Resources