Improving speed and data efficiency with javascript calculator. - javascript

I'm building a javascript calculator (with jquery mobile) for simplifying routine calculations in microscopy. I'm looking to create more efficient code and would love any input... I don't expect anyone to dig through the whole thing, but here is the link to the program for reference: http://www.iscopecalc.com
(the javascript for the calculator is at http://www.iscopecalc.com/js/calc.js )
The calculator basically consists of about 12 inputs that the user can set. Using the values received from these inputs, the calculator generates values for about 15 different parameters and outputs the results in the display. Currently, whenever the state of an input changes, I bind that change event to a quick function that writes the value for that input to a cookie. But the meat of the program comes with the "updateCalc()" function which reads the values from all of the inputs (from stored cookies) and then recalculates every one of the parameters to be displayed and outputs them. I've coped just that function here for ease of access:
function updateCalc(){
readValues(); //load current calculator state into cookies
var data = $.cookie(); //puts all cookie data into object
var fluorData = fluoroTable[data['fluorophore']]; //fluorophore data taken from table at the end of the file depending on chosen fluorophore
var fluorem = fluorData['fluorem'];
var fluorex = fluorData['fluorex'];
var cameraData = cameraTable[data['camera']]; //camera data taken from table at the end of the file depending on chosen camera
var campix = cameraData['campix'];
var chipWidth = cameraData['chipWidth'];
var chipHeight = cameraData['chipHeight'];
var chipHpix = cameraData['chipHpix'];
var chipVpix = cameraData['chipVpix'];
var RefInd = data['media']; //simple variables taken directly from calculator inputs
var NA = data['NAslider'];
var obj = data['objective'];
var cammag = data['cameraRelay'];
var CSUmag = data['CSUrelay'];
var bin = data['binning'];
var pinholeRad;
var FOVlimit;
var mode;
if (data['modality']=='widefield'){ //FOVlimit, pinholeRad, and CSU mag will all depend on which modality is chosen
FOVlimit = 28;
pinholeRad = NaN;
mode = 'Widefield';
CSUmag = 1;
}
else if (data['modality']=='confocal'){
if (data['CSUmodel']=='X1'){
pinholeRad = 25;
if(data['borealis']=='true'){
mode = "Borealis CSU-X1";
FOVlimit = 9;
}
else {
mode = "Yokogawa CSU-X1";
FOVlimit = 7;
CSUmag = 1;
}
}
else if (data['CSUmodel']=='W1'){
mode = "Yokogawa CSU-W1";
FOVlimit = 16;
pinholeRad = data['W1-disk']/2;
CSUmag = 1;
}
}
//These are main outputs and they depend on the input variables above
var latRes = 0.61 * fluorem / NA;
var axRes = 1.4 * fluorem * RefInd / (NA*NA);
var BPpinhole = 1000 * pinholeRad / (obj * CSUmag);
var AU = BPpinhole / latRes;
var totalMag = obj * cammag * CSUmag;
var BPpixel = 1000 * campix * bin / totalMag;
var samples = latRes / BPpixel;
var pixperpin = BPpinhole * 2 / BPpixel;
var sampLit = 1000 * FOVlimit / (obj * CSUmag);
var coverage = FOVlimit * cammag / chipHeight;
if (coverage < 1) {
chipUsed = coverage;
FOV = sampLit;
}
else {
chipUsed = 1;
FOV = sampLit * chipHeight / (FOVlimit * cammag);
}
var sampWaste = 1 - FOV / sampLit;
var imgpix = 1000 * FOV / (chipVpix / bin);
//function goes on to update display with calculated values...
}
It works ok and I'm generally pleased with the results but here's what I'd like advice on:
Each of the input variables only really affects a handful of the outputs (for instance, a change in input #3 would only really change the calculation for a few of the outputs... not all 15), however, my function recalculates ALL outputs everytime ANY of the inputs are changed, regardless of relevance... I've considered making a giant If-Then function that would selectively update only the outputs that would have changed based on the input that was changed. This would obviously take a larger amount of code, but I'm wondering if (once loaded) the code would be faster when using the calculator, of if it would just be a waste of my time and clutter up my code.
I'm also wondering if storing inputs in cookies and reading values back from cookies is a reasonable way to do things and if I should maybe make a global variable instead that stores the state of the calculator. (the cookies have the added benefit of storing the users calculator state for later visits).
I'm pretty new at this stuff, so any and all comments on how I might improve the efficiency of my code would be greatly appreciated (feel free to just link to a page that I should read, or a method I should be using for instance...)
if you've made it this far, thanks for your time!!

Related

Using REPT() with Decimals

I was wondering about being able to use/make a function like REPT() to display partial repetitions with decimal numbers. My formula, as it stands, works fine for integers. If I wanted to give 3 stars, I just use =REPT(CHAR(9733), 3) and that prints 3 black stars.
Let's say I wanted to give something 4.2 stars. Is there a way to do something like this? I've been trying to figure out a way to do it with App Script, but I'm not sure how to proceed. Everything I've researched online is geared towards making a clickable rating system with HTML/CSS/JavaScript. But I'd be looking more for something like an average rating on Amazon or something.
This is what I have with App Script so far:
function starRating() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('test');
var cell = sheet.getRange(2,1); // Sets a test cell
// Create a concatenated string of 5 blank stars
var blankStars = "";
for (let i = 0; i < 5; i++) {
blankStars = blankStars.concat(String.fromCharCode(9734));
}
// Create a concatenated string of 5 black stars
var blackStars = "";
for (let i = 0; i < 5; i++) {
blackStars = blackStars.concat(String.fromCharCode(9733));
}
cell.setValue(blankStars);
cell.setHorizontalAlignment("center")
cell.setFontSize(18);
var rating = sheet.getRange(1,1).getValue(); // Raw Rating (e.g. 4.3)
var amount = Math.max(0, (Math.min(5, rating))) * 20; // Gets percent out of 100, also ensures rating is from 0-5
/*
Maybe find a way to overlay the blackStar on top of blankStar?
Use amount as a way of only showing percent of blackStar?
*/
}
As I put in the comment, my thought was to overlay a percent of the blackStar string on top of the blankStar string, but 1. I don't know how to do that and 2. I don't know how that could be put into the cell.
This is fairly close and a lot easier
function stars(n = 9) {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName("Sheet0");
const style = SpreadsheetApp.newTextStyle().setForegroundColor("#000000").setFontSize(16).build();
let s = "";
Array.from(new Array(10).keys()).forEach(x => s = s.concat(String.fromCharCode(x < n ? 9733 : 9734)));
sh.getRange(3, 1).setValue(s).setTextStyle(style);
}

How to get the value from a range slider so that it can be used in a calculation and display a result?

I'm a complete beginner trying to build a solar power calculator (why did I do this to myself) I've tried everything but I'm still stuck. The idea is that users use a range slider to add their monthly electricity bill to a calculator. I want to then use what they entered in a calculation to convert their monthly bill to kwh but Console log shows "undefined" I have some constants that works with the value entered by the user. The calculation works if used without user input for the monthly electricity bill - a constant (I tested it) but I can't figure out how to use the user input in the calculation. Here's a codepen of what I did. The function I got there is probably a total mess but I don't know how to fix it. Please help, my brain have turned to mush and this is due tomorrow. I started like a month ago on this having to restart all the time because there is so much that needs to go in here and I don't know what I'm doing.
<h2>Solar Power Calculator</h2>
Monthly Electricity Bill:
<input type="range" min="100" max="10000" value="0" step="1" onchange="getSliderValues(this.value)" style="width:400px;">
<span id="monthlyBillval">0%</span>
<h2>Monthly Bill to KWH <span id="monthlyBill"></span></h2>
//initial variables
var phases = 1;
var systemCost = 247765.00;
var sizeOfPanelArray = 8.19;
var batteryBankCapacity = 22.20;
var payBackPeriod = 5.3;
var monthlyEstimatedSavings = 3864.00
// var amortData = [];
// Constants
var singlePhaseBaseCost = 41000;
var threePhaseBaseCost = 69000;
var panelCost = 2639;
var fiveKwSinglePhaseInverter = 17371;
var eightKwSinglePhaseInverter = 28344;
var twelveKwThreePhaseInverter = 43783;
var batteryCost = 29160;
var panelPeakPower = 455;
var avgDailySunHrs = 5.33;
var avgDaysPerMonth = 30.5;
var batteryCapacity = 7.4;
var maxCeilingDiscountedBracketInRands = 1440;
var lowBracketKwh = 600;
var discountedTarrif = 2.400395;
var higherTarrif = 3.312575;
function getSliderValues (billSlider){
var monthlyBill = document.getElementById('monthlyBill');
var monthlyBillval = document.getElementById('monthlyBillval');
monthlyBillval.innerHTML = billSlider;}
var monthlyBillToKwhConversion;
if (monthlyBillval < maxCeilingDiscountedBracketInRands) {
monthlyBillToKwhConversion = monthlyBillval / higherTarrif
}
else if (monthlyBillval > maxCeilingDiscountedBracketInRands) {
monthlyBillToKwhConversion = Math.round (((monthlyBillval - maxCeilingDiscountedBracketInRands)/ higherTarrif) + (maxCeilingDiscountedBracketInRands/discountedTarrif))
}
console.log("monthly bill to kWh conversion:" + monthlyBillToKwhConversion);
You're trying to access the monthlyBillval variable from outside the scope of the function where it's defined. That's not possible in Javascript as each variable is accessible only inside the scope of the function that defined it.
So all your if checks yield false, hence monthlyBillToKwhConversion remains undefined
Consider to return something from your function if you need outside it.
Moreover, in modern Javascript it's considered a best practice to avoid the var declaration because could lead to unexpected results and mess up the global scope. You should use let and const to define your variables.
Your code has some scope issues, also :
var monthlyBillval = document.getElementById('monthlyBillval');
monthlyBillval is an element not value, you can't do this:
monthlyBillToKwhConversion = monthlyBillval / higherTarrif
you can see the working code on codeopen

Rounding output to 2 decimals

I've coded a small compound interest calculator. The last output-field has to show the amount calculated with the inputs above. But it should be rounded to two decimals and I didn't get it to work with various code-snippets I found on the web and even stackoverflow.
var $button9 = $('.increment-btn5'); // button to call the function below
var $counter1 = $('.counter1'); // First input
var $counter2 = $('.counter2'); // obsolete var at the moment
var $counter3 = $('.counter3'); // Second input
var $counter4 = $('.counter4'); // Third input
var $counter5 = $('.counter5'); // Ouput
$button9.click(function(){
$counter5.val( parseInt($counter1.val()) * ((parseInt($counter4.val()) / 100) + 1) ** parseInt($counter3.val() ) ); // Calculates amount and loads it into .counter5
Useful ideas would be highly appreciated, thanks.
Have you tried using Number.toFixed yet?
var $button9 = $('.increment-btn5'); // button to call the function below
var $counter1 = $('.counter1'); // First input
var $counter2 = $('.counter2'); // obsolete var at the moment
var $counter3 = $('.counter3'); // Second input
var $counter4 = $('.counter4'); // Third input
var $counter5 = $('.counter5'); // Ouput
$button9.click(function(){
$counter5.val(
( // begin calculation
parseInt($counter1.val()) *
((parseInt($counter4.val()) / 100) + 1) **
parseInt($counter3.val() )
) // end calculation
.toFixed(2) // round it to two decimals
); // pass it to counter5
});

How can I count form fields with the same id?

What I want should be very simple I think, but I end up with too complex situations if I search here, or on Google.
<script language="javascript">
// putten tellen
$(document).ready(function () {
$("input[type='number']").keyup(function () {
$.fn.myFunction();
});
$.fn.myFunction = function () {
var minute_value = $("#minute").val();
var second_value = $("#second").val();
if ((minute_value != '')) {
var productiesec_value = (1 / (parseInt(minute_value) * 60 + parseInt(second_value)));
var productiemin_value = productiesec_value * 60;
var productieuur_value = productiesec_value * 3600;
var productiedag_value = productiesec_value * 86400;
var productieweek_value = productiesec_value * 604800;
var productiemaand_value = productiesec_value * 2629700;
var productiejaar_value = productiesec_value * 31556952;
productiesec_value = (productiesec_value).toFixed(5);
productiemin_value = (productiemin_value).toFixed(2);
productieuur_value = (productieuur_value).toFixed(2);
productiedag_value = (productiedag_value).toFixed(0);
productieweek_value = (productieweek_value).toFixed(0);
productiemaand_value = (productiemaand_value).toFixed(0);
productiejaar_value = (productiejaar_value).toFixed(0);
$("#productiesec").val(productiesec_value.toString());
$("#productiemin").val(productiemin_value.toString());
$("#productieuur").val(productieuur_value.toString());
$("#productiedag").val(productiedag_value.toString());
$("#productieweek").val(productieweek_value.toString());
$("#productiemaand").val(productiemaand_value.toString());
$("#productiejaar").val(productiejaar_value.toString());
}
};
});
</script>
The thing I'd like to accomplish is:
Calculate the production time of a gem in multi-types of time (seconds, minutes, hours etc.) - (Done)
Calculate the production of gems by multiple pits.
Preview: http://hielke.net/projecten/productie/edelsteenput.htm
The idea is that you fill in the minutes in the first field and the seconds in the second field. Then the script should count the production in seconds, minutes, hours etc. on the right side.
After that it must be possible to fill in the second row of minutes and seconds and then counts the total production time. The same for the rest of the rows.
Welcome to SO!
A caveat about your setup: whenever possible, avoid having elements share IDs on your page. IDs are generally for elements which only occur once on your page; otherwise use a class. This practice is why document.getElementById() returns a single element, while document.getElementsByClassName() returns an array, which makes the answer to your question as easy as getting that array's .length.
This being said -- counting the number of elements with the same ID in Javascript is generally considered invalid, as getElementById() will only return one element, and (as far as I know) there isn't a way to iterate over instances of the same ID on a page.
Try changing those IDs to class names if you can, the run a document.getElementsByClassName().length on them to get the count.

Is Javascript multiplying these values wrong?

Allright, I know what machine precision is, but this, I can't understand...
Code:
console.log("meanX",meanX);
meanX2 = meanX * meanX; //squared
console.log("meanX2",meanX2);
Console output:
meanX 300.3
meanX2 28493.4400000000002
In case you are wondering, the correct value for meanX2 would be 90180.09
And this is only one of the many examples visible in the screenshot..
.toFixed(6) seems to fix this... But I have no idea why it doesn't work without it.
Edit
Ok, I don't want to post the whole program code here because in first place I'm not the only author, and second, I also wouldn't like this to be copied without our permission. But I'll gladly explain how I get this error and will post the whole method/function code here.
This code belongs, as you may have guessed from the window title, to a lane detection algorithm. We use Three.js/webgl to run some pre processing shaders on each frame of a video and then we analyze the resulting image. The method/function you see on the screenshot is a perpendicular line fitting algorithm and is part of the whole thing.
I can see the algorithm running nicely because I have the lane being drawn on top of the video, and It is well placed. Until suddenly the lane turns into an horizontal bar. This unexpected behavior happens exactly because of the phenomenon I described here, since it's from that moment that I start to see wrong math in the console.
Also, because the video and algorithm run at slightly different fps everytime, the problem doesn't always happen in the same moment of the video, and sometimes It doesn't happen at all.
Here is the code (it has some alterations because I was trying to isolate the issue):
this.perpendicularLineFit = function (points, slopeSign) {
var count = points.length;
var sumX = 0,
sumY = 0;
var sumX2 = 0,
sumY2 = 0,
sumXY = 0;
var meanX, meanY;
var i, lowp = {}, highp = {};
var B;
var slope;
var originY;
for (i = 0; i < count; i++) {
sumX += points[i].x;
sumY += points[i].y;
sumX2 += points[i].x * points[i].x;
sumY2 += points[i].y * points[i].y;
sumXY += points[i].y * points[i].x;
}
meanX = sumX / count;
meanY = sumY / count;
//If you uncoment this, problem reappears:
//var numeratorLeft = meanY * meanY;
console.log("meanX",meanX);
var meanX2 = meanX*meanX;
console.log("meanX2",meanX2);
var numerator = (sumY2 - count * (meanY * meanY)) - (sumX2 - count * meanX2);
var denominator = (count * meanX * meanY - sumXY);
B = 0.5 * (numerator / denominator);
slope = -B + slopeSign * Math.sqrt(B * B + 1);
originY = meanY - slope * meanX;
slope = isNaN(slope) ? slopeSign : slope;
originY = isNaN(originY) ? originY : originY;
lowp.y = this.lowY;
lowp.x = (this.lowY - originY) / slope;
highp.y = this.highY;
highp.x = (this.highY - originY) / slope;
return {
low: lowp,
high: highp
};
};
Now, I was trying to understand what was causing this, and the most bizarre thing is that it seems that when I place a statement of this form
var x = ... meanY * meanY ...;
before the meanX2 attribution, the issue happens. Otherwise it doesn't.
Also, I tried to catch this anomaly in the debugger but just when I enter the debugging tab, the problem disapears. And the values turn correct again.
I certainly don't believe in black magic, and I know that you are probably skeptic to this.
I would be too. But here is a link to a video showing it happening:
The video
Edit2:
I managed to reproduce this issue in another computer.. Both having ubuntu and using firefox (versions 20 and 21).
Edit3:
I'm sorry it took so much time! Here is a zip containing the issue. Just run it in any webserver. The code mentioned is in LaneDetection.js. Search for "HERE" in the file to find it.
https://docs.google.com/file/d/0B7y9wWiGlcYnYlo1S2pBelR1cHM/edit?usp=sharing
The problem might not happen in the first attempts. If that's the case refresh the page and try again. When the lines get horizontal you know it's there. As I said, I saw this problem happening in firefox versions 20 and 21 on ubuntu. In chrome it never happened.
By the way, I noticed that changing javascript.options.typeinference flag in firefox seems to stop the problem... I don't know exactly what that flag does, but maybe this optimization is not correctly implemented in firefox?
I can't say for sure that I actually have an answer but I think that I have confirmed that basilikum was correct to suggest a memory problem. Here's what I did: I took the first ten entries from your screenshot and calculated the correct answer. I then converted the correct answer and the wrong answer into the hexidecimal representation of the double-precision float. What I ended up with was the following:
292.416^2 = 85507.506 = 40F4E0381C71C71E
changed to 27583.373 = 40DAEFEB1C71C722
293.166^2 = 85946.694 = 40F4FBAB1C71C72A
changed to 27583.373 = 40DAEFEB1C71C722
295.818^2 = 87508.396 = 40F55D4658DC0876
changed to 28041.024 = 40DB62419637021F
294.500^2 = 86730.250 = 40F52CA400000000
changed to 27583.373 = 40DAEFEB1C71C722
297.000^2 = 88290.000 = 40F58E2000000000
changed to 28041.024 = 40DB62419637021F
221.750^2 = 49173.062 = 40E802A200000000
changed to 24964.000 = 40D8610000000000
300.300^2 = 90180.090 = 40F6044170A3D70A
changed to 28493.440 = 40DBD35C28F5C290
220.200^2 = 48488.040 = 40E7AD0147AE147B
changed to 25408.360 = 40D8D0170A3D70A4
300.600^2 = 90360.360 = 40F60F85C28F5C29
changed to 28493.440 = 40DBD35C28F5C290
213.000^2 = 45369.000 = 40E6272000000000
changed to 28032.326 = 40DB6014E5E0A72E
There's no persistent pattern to the change but there are a couple instances that are very telling of a memory issue. In the first two entries you can see that bytes 1, 2 and 3 were unchanged. In the 9th entry there's something even more odd. It would appear that bytes 0 - 3 were shifted left by exactly 4 bits! Upon considering the statement that the problem doesn't arise until after some time has passed and in light of these two anomalies, I'm fairly confident that you're encountering some sort of memory issue. Could it be, dare I say, a stack overflow?

Categories