reptile7's JavaScript blog
Wednesday, April 20, 2016
 
Did We Really Count to Five Hundred?
Blog Entry #365

In today's post we'll go through the Calendars, Clocks, and Calculators (CCC) sector's "Count It!" script. Crafted by "Tigger" in March 1998, the Count It! script counts up from 0 to an inputted positive integer via a running display of integers in a text box. The javagoodies.com/countit.txt document holding the Count It! script code is still live as of this writing.

The script demo at the "Tigger's Counting Script" countit.html page works in a sense but not satisfactorily so. If you input, say, 25 in the Count to: field and then click the button, your 25 will instantly appear in the below-the-button field and you won't see any counting at all, that is, the 024 intermediate numbers zoom by much too fast for you to see them. We'll slow things down below.

Control HTML

The user enters the target number into a name="number" text input; the counting takes place in a name="output" text input. A name="form" form contains the number field, a value=" Count! " push button, and the output field. The display is horizontally centered on the page by a center element.

<center>
<form name="form"><br><br><br>
Count to:<input type="text" name="number" length="3">
<input type="button" value=" Count! " onclick="go(document.form.number.value);">
<input type="text" name="output">
</form></center>


Neither the input element nor any other HTML element has a length attribute; as the target number is capped at 500 (vide infra), I suspect Tigger meant maxlength rather than length.
• FWIW, the starting space character and the ending space character of the " Count! " button label are rendered by all of the browsers on my computer except Firefox.

Let's go( )

Clicking the button calls a go( ) function and passes thereto the number field's value, which is given a number identifier.

<script language="JavaScript">
function go(number) { ... }
</script>
</head>


The go( ) function body first tests if number is greater than 500; if the test returns true, then
(a) a "That's pretty high, so I'll set it to 500" alert( ) pops up and
(b-c) number and the number field's value are lowered to 500.

if (number > 500) { window.alert("That's pretty high, so I'll set it to 500."); number = 500; document.form.number.value = "500"; }

number initially has a string data type and is converted to a number for the number > 500 comparison.

This is as much validation as we get from the go( ) function: there are no checks for non-numeric strings, floating-point numbers, or negative numbers.

With number capped at 500, the for statement below increments an n counter from 0 to number and sequentially loads the n values into the output field.

for (n = 0; n <= number; n++) { document.form.output.value = n; window.setTimeout("", 500); }

A setTimeout( ) command whose expression parameter is set to an empty string and whose msec parameter is set to 500 is meant to serve as a delaying tactic that allows us to see discrete ns as the loop runs. It doesn't work in practice, and you wouldn't expect it to work in practice, because...
window.setTimeout( ) does not stall the script. The script continues immediately (not waiting for the timeout to expire). The call simply schedules a future event.
So, we schedule "" to happen in 500 milliseconds and long before we get to that "" n will have reached number and the latter is all we see. Did the delaying work once upon a time when processors were more primitive than what we have today? Maybe, but that was then and this is now.

More generally, we shouldn't be using a loop to count n in the first place: loops are meant to iteratively execute one or more statements as quickly as possible vis-à-vis in a controlled, gradual manner. However, this doesn't mean that we have to start from scratch to sort out the situation; we can achieve the desired effect if we
(1) have the setTimeout( ) command recursively call go( ),
(2) externalize the n initialization, and
(3) deploy the remaining parts of the loop in a corresponding if conditional.

var n = 0, countID; function go(number) { ...number-vetting code... if (n <= number) { document.form.output.value = n; n++; countID = window.setTimeout(function ( ) { go(number) }, 500); } else n = 0; }

Validation

Let's go back to the original script for a moment. If we leave the number field blank and click the button, then our empty string 'input' is converted to 0 for the n <= number loop condition; in this case, the loop runs for one iteration and document.form.output.value = 0 is as high as we go.

All other non-numeric inputs convert to NaN for the n <= number condition. The 0 <= NaN comparison returns false and therefore the loop doesn't run for any iterations (the condition is evaluated a single time and that's all that happens) and no counting occurs. FYI: According to Netscape, NaN is not equal to anything, including NaN.

For a negative number input, the n <= number condition again returns false from the get-go and no counting occurs. (But perhaps you might like to count down to a negative number - we go in reverse here.)

For a floating-point number input, go( ) counts to Math.floor(Number(number)), which is OK, but I'd rather it count to Math.round(Number(number)).

All things considered, here's the number-vetting code I'd use:

if (Number(number) < 0 || 500 < Number(number) || isNaN(number) || number === "") { window.alert("Please enter a positive integer in the range:\n0 \u2264 n \u2264 500"); document.form.number.value = ""; document.form.number.focus( ); return; } if (number.toString( ).indexOf(".") != -1) { number = Math.round(Number(number)); document.form.number.value = number; window.alert("We're going to round your input to: " + Math.round(number)); }

• Upon round( )ing a floating-point number input in a first go( ) iteration it is necessary to toString( ) number for the indexOf( ) test in subsequent go( ) iterations. Alternatively, if we deparameterize go( ) and (re-)type number as a string at the beginning of the go( ) body via

var number;
function go( ) { number = document.form.number.value; ... countID = window.setTimeout(go, 500); ... }

<input type="button" value=" Count! " onclick="go( );">


then the toString( ) conversion is unnecessary.

\u2264 is the Unicode escape sequence for a ≤ character.

Bells and whistles

Stop it

You type 500 in the number field and click the button. When n hits 100 you think, "All right, I get the idea, let's pull the plug on this." For stopping the count in midstream we can add:

function stopCount( ) { window.clearTimeout(countID); }

<input type="button" value="Stop the Count" onclick="stopCount( );">


Start over

We naturally want to add a reset capability to the display. A complete reset requires us to not only blank the number and output fields but also send n back to 0.

If we hold onto the form form, then adding our reset is no more complicated than appending an <input type="reset" value="Reset" onclick="n=0;"> child to the form.

In the demo below, I replace the form and its <center> parent with a <div style="text-align:center;"> container, give the number field an id="numberInput", and exchange the output field for an id="outputSamp" samp placeholder, and I accordingly clear the decks with:

function resetCount( ) { n = 0; document.getElementById("numberInput").value = ""; document.getElementById("outputSamp").textContent = ""; }

Count to: <input id="numberInput" name="number">
The current count is: <samp id="outputSamp"></samp>
<button type="button" onclick="resetCount( );">Reset</button>


Color it

My demo randomly colors the n numbers with code borrowed from the HTML Goodies JavaScript Script Tips #81-83 script; the colors are accentuated by beefing up the n font-size to 48px.

#outputSamp { font-size: 48px; }

var defClrsArray, colorIndex;
defClrsArray = ["red", "purple", "aqua", "green", "blue", "fuchsia", "orange", "brown"];

function go( ) { ... if (n <= number) { colorIndex = Math.floor(Math.random( ) * defClrsArray.length); document.getElementById("outputSamp").style.color = defClrsArray[colorIndex]; document.getElementById("outputSamp").textContent = n; n++; countID = window.setTimeout(go, 500); } ... }

Demo



The current count is:


The financial countdown

On the CCC portal page, Joe comments that the Count It! script is useless - but fun. Hmmm... I think of the downward counts in the "Fast Money" part of Family Feud. If you won $20,000 by giving the right answers to questions during those counts, that wouldn't be so useless, would it? So let's put 20 seconds on the clock, which will start after I read the first question and you click the button below...
The current count is: 00:20
(I trust you can write out the code for this, yes?)
We'll briefly discuss some more geometry-related scripts and then perhaps start work on a calculator script in the following entry.

Saturday, April 09, 2016
 
Leaving Mr. Hypotenuse Out of the Loop
Blog Entry #364

We make today a brief, basic foray into trigonometry via the next CCC script, "Tangent". Written by T. Jay Tipps in March 1998, the Tangent script
(I) asks the user to input the lengths of the sides opposite (opp) and adjacent (adj) to an acute angle (θ) in a right triangle and then
(O) calculates and outputs the opp/adj tangent ratio for the angle.

A right triangle

The tangent.html page features a mostly functioning script demo (the script HTML contains a very small mistake that we'll correct below). The javagoodies.com/tangent.txt document holding the script code is gone but the JavaScript Goodies copy of the document is still live.

Deconstruction

The Tangent script is reasonably short and straightforward so let me give it to you in one go:

<script language="javascript"> function tan( ) { var opp = window.prompt("Enter the measurement of the side opposite of the angle you are trying to find:"); var adj = window.prompt("Enter the measurement of the side adjacent to the angle you are trying to find:"); var tan = opp / adj; var a = window.alert("The answer is " + tan + ". This was solved using the formula Tan A=Opposite/Adjacent."); } function touse( ) { window.alert("For Tan enter the sides opposite and adjacent of the angle you are trying to find."); } </script> <form> <input type="button" value="The Tangent Ratio" onclick="tan( );"> <inpu type="button" value="To Use The Tangent Ratio" onclick="touse( );"> <!-- There's no </form> tag in the source, but there should be. -->

Regarding the next-to-the-last line, <inpu is the aforementioned HTML mistake - it's in both the tangent.txt document and the tangent.html source - and you don't see a button on the tangent.html page: appending a t thereto brings the button to life, as you would expect. Clicking the button calls a touse( ) function that pops up a help message of sorts on an alert( ) box.

The preceding button is where the action is. Clicking the button calls a tan( ) function that
(1-2) pops up a first prompt( ) box that asks for an opp length and then a second prompt( ) box that asks for an adj length,
(3) divides opp by adj, and
(4) displays the quotient (tan) plus some explanatory text on an alert( ) box.

• That opp and adj are strings poses no problem for the var tan = opp / adj; division.

• No validation is provided for the user's inputs; tan values that run many places past the decimal point are not truncated.

• The tan( ) prompt( ) messages and the touse( ) alert( ) message imply that the script is going to determine the angle whose tangent is opp/adj. The script does not actually do that: it just calculates the opp/adj ratio. We'll get the angle momentarily, however.

• Unlike a prompt( ) command, an alert( ) command doesn't have a return value (more precisely, the alert( ) return is undefined) and therefore the tan alert( )'s var a = assignment is unnecessary/serves no purpose.

Looking for a certain angle

As for the angle you are trying to find, finding it is a cinch:

var angle = Math.atan(tan) * 180 / Math.PI;

The JavaScript Math object has an atan(x) method that gets the arctangent, in radians, of its x argument; the Math.atan(x) return can be converted to an angle in degrees via a (180°/π radians) conversion factor.

The Math object has a second method for getting an arctangent, atan2(y, x), that connects us to a Cartesian coordinate system - more here.

Pre-demo

The demo below incorporates coding techniques that were presented in the A grounded I/O section of Blog Entry #360 and the Demo considerations section of Blog Entry #362. Here's the skinny:

• Re the original HTML, I've traded in the <form> for a <div id="tangentDiv"> and recast the <input type="button">s as <button type="button">s.

• The opp and adj lengths are entered into <input type="text">s vis-à-vis prompt( ) boxes: we access those <input>s via a getElementsByTagName("input") operation.

• User inputs are restricted to numbers in the range -100 < x < 100 and with no more than two post-decimal point digits.

tan and angle values having three or more post-decimal point digits are toFixed(2) at the hundredths place.

• The formatted tan and angle values are displayed in <samp>s below a button.

• Clicking the button calls a resetTangent( ) function that clears the <input> values and the <samp> textContents by setting them to empty strings.

Division by 0

Suppose the opp length is 5 and the adj length is 0. In this case, the var tan = opp / adj; division gives Infinity, which is fair enough as f(θ) = tan(θ) does in fact approach infinity as θ approaches 90° (π/2 rad). If both opp and adj are 0, however, then the tan return is NaN, which isn't very helpful, is it? One way to forestall the latter possibility is:

var tangentInputs, opp, adj, tan, angle, tangentSamps; tangentInputs = document.getElementById("tangentDiv").getElementsByTagName("input"); opp = tangentInputs[0].value; adj = tangentInputs[1].value; if (opp === "0" && adj === "0") { window.alert("Please enter at least one nonzero value."); for (var i = 0; i < tangentInputs.length; i++) tangentInputs[i].value = ""; tangentInputs[0].focus( ); return; } else { /* ...Remaining vetting code... */ }

If the if condition is true, then a troubleshooting alert( ) pops up, the opp and adj input fields are cleared, focus( ) is given to the opp input field, and the tan( ) function exits.

Negative number inputs

The opp and adj lengths are necessarily positive if we stay within the right triangle context, but we don't have to do that, now do we? Via the unit circle and the periodic definition of the tangent function (cf. the aforelinked tangent-graph.gif image if you didn't look at it earlier) we can effectively extend the Tangent script to any real-valued angle and thereby accept negative number inputs that correspond to Quadrant II-IV measurements in a Cartesian plane.

The Math.atan(x) return spans a -π/2 (-90°) → π/2 (90°) range. Given the trigonometric identities tan(π - θ) = -tan(θ), tan(θ + π) = +tan(θ), and tan(-θ) = -tan(θ), we can put the angle value for an obtuse, straight, or reflex angle in the 90° < θ < 360° range via:

var tan = opp / adj;
angle = Math.atan(tan) * 180 / Math.PI;
if (Number(adj) < 0) angle += 180;
else if (Number(opp) < 0) angle += 360;
if (/\.\d{3,}$/.test(angle)) angle = angle.toFixed(2);
document.getElementById("angleSamp").textContent = angle + "\u00b0";


• The Number( ) operations are there for the purpose of readability: it's not necessary to explicitly numberify opp and adj as the JavaScript engine will convert them to numbers automatically for the < 0 comparisons.
\u00b0 is the Unicode escape sequence for a ° character.

We rotate angle counterclockwise
(a) by 180° for 90° < θ < 270° and
(b) by 360° for 270° ≤ θ < 360°.
For example, if we input opp = -5 and adj = 5 - a negative rise and a positive run puts us in Quadrant IV - then the initial angle, -45, is twirled around to 315. You could just leave the angle at -45, of course, but I prefer to express it as a reflex angle.

As per the tan(θ + 2π) = +tan(θ) trigonometric identity, 360° < θ angles have the same tangent values that their θ < 360° coterminal counterparts do; I will limit angle to the 0° ≤ θ < 360° range in the name of simplicity.

Arctangent alternative

We can also directly plug opp as a y coordinate and adj as an x coordinate into a Math.atan2(y, x) command so as to get the arctangent, in radians, of the opp/adj quotient. The Math.atan2(y, x) method covers all four Cartesian Quadrants: its return spans a 0π range for Quadrants I and II (and their x-axis boundary) and a 0 range for Quadrants III and IV (and their x-axis boundary). The code below positions angle in the 0° ≤ θ < 360° range:

angle = Math.atan2(opp, adj) * 180 / Math.PI;
if (Number(opp) < 0) angle += 360;


Having said this, some of Mozilla's atan2(y, x) Examples involving signed 0 and Infinity arguments are confusing and they incline me to stick with Math.atan(x) arctangents.

Speaking of signed 0s - in the name of completeness - I would use a regular expressions-based

if (/^(\+|-)?0$/.test(opp) && /^(\+|-)?0$/.test(adj))

condition to fend off an opp = +0|-0 AND adj = +0|-0 combination of inputs, which would otherwise take us back to the 0/0 = NaN situation.

Demo

A right triangle





Your opposite/adjacent tangent ratio is:
The angle in question is:

More CCC trig scripts from T. Jay

The Tangent script is followed by a corresponding "Sine" script that determines an opposite/hypotenuse sine ratio (get the code here) and a corresponding "Cosine" script that determines an adjacent/hypotenuse cosine ratio (get the code here). There's no need to discuss these scripts, so we won't; at least their buttons are OK.
We'll do some counting with a "Count It!" script in the following entry.


Powered by Blogger

Actually, reptile7's JavaScript blog is powered by Café La Llave. ;-)