SCRIPTING:

Chloe Jones
TRIGONOMETRIC PLOTTERS FOR THE WEB
Trigonometric analysis scripts, quite useful also for Dhtml: plotting segment lengths, angles, edges coordinates of a segment whose starting coordinates and length and inclination are known, and more.
May 2002
{ @ }

The model above is Chloe Jones
LOADS OF CHLOE JONES ON THE NET


INTRODUCTION
General utility of these scripts and a convention on angle measurements

These relatively (relatively) simple scripts can perform a variety of tasks of a consistent utility. Keep in mind that they have been conceived to be used on computer screen canvas; therefore a, say, negative value on the ordinate (vertical) axis (which means, in web document language, the top coordinate in a top/left pairs) does not mean, like in ordinary trigonometry, that the segment stretches downward, but... the opposite: it goes upward, for the lower the value of the top coordinates, the higher in the document position such coordinate lies. As far as the abscissa is concerned (horizontal axis) such difference with the classical trigonometry doesn't exist: but as far as the top coordinates of a screen locus is affected, this difference is mission critical. This is one more reason, arguably, which makes these trigonometrical plotters interesting.
Instances of the scripts:
  • Knowing the starting coordinates of a point and the ending coordinates of another point on the screen, plot out the length: although it appears simple (and actually you can find another analogous script, but with a more complex output, which does something similar at this UnitedScripters page whose name is distances), we don't find online many scripts that can do this, despite its utility (imagine knowing two spots coordinates and wanting to know the length in order to dynamically resize a layer whose width would fit that space...)
  • Knowing the starting point coordinates of a segment and its length and angle, plot its ending top and left coordinates.
    Note that the angle of a single segment is calculated considering its standard position being the one where the segment lies horizontally and eastward oriented on an ideal screen horizontal rule.
    Revolving around its starting coordinates (origin) in counterclockwise directions, any segment can describe a 0 to 360 degree round obviously: angles for a single segment are calculated in such way, for in order to plot an angle of a single segment we have to agree on a reference system abstract segment that the single actual segment has to be considered forming an angle with (you do need two segments to have an angle!)
    Without specifying an angle, there could be infinite segments (and therefore infinite possible endTop/endLeft coordinates) that segments of that given length could stretch towards from the origin coordinates!
    Therefore you have to pass an angle coordinate, and such angle must be determined imagining your segment revolving in such counterclockwise way starting from such initial position: so:

Angle convention for inclination of
revolving single segment
eastward 0 degrees
north-eastward 0-89 degrees
northward 90 degrees
north- westward 91-179 degrees
westward 180 degrees
south westward 181-269 degrees
southward 270 degrees
south-eastward 271-359 degrees
eastward 360=0 degrees

WARNING:
Bear also in mind that although in plain trigonometry the usual way to hand over position data are (x,y) namely first the abscissa (x, or horizontal) coordinate and then the ordinata (y, or vertical) coordinate, the web scripting tradition that have been developed over time is to reverse that, first the ordinata then the abscissa: [y,x]
.
This happens, in my opinion, for normally in a web document the vertical position has much greater an importance (talk about scrolling a page, which is a vertical process!) than the horizontal one which most of the times just fits the screen width, and therefore usually isn't introduced as the foremost in a coordinate pair.
 
Once you have your ending top-left coordinates, you could for instance take avail of the pathMaker() function which takes as arguments starting top-left and ending top-left coordinates in order to produce an array of all the intermediary steps such segment could be "dotted" by, which is definitely useful an array if you want later on move a layer on such yielded coordinates (let's remember that the pathMaker() wants as many as 6 arguments, whereas you have to set also the last one, named center as zero even if you don't mean to pass it: at times I have to find a trade off compromise between a small annoyance and a script that can cumulate a bundle of possibilities. The pathMaker() would require ULMA but if you pass all the required 6 arguments, among which centers passed as zero, then it would produce an array also without using ULMA).
Therefore, the more you interweave the scripts you find at Unitedscripters, the more you can find powerful combinations for purposes that are, probably, one step above the usual goals most script seekers search for.
Alternatively, I provide in this very same file a script named pathPlotter().


THE SCRIPTS
Boxes featuring the scripts purpose, the argument usage, and the codes

Here the scripts with boxes each with its own test form.
Keep in mind that we're considering a cartesian set of axis on a computer screen: if your segment startTop is, say 0 and its endTop is, say, -100, it means the segment is stretching UPWARD on the computer screen: since its starting position was zero and its next top coordinate is negative, which on a computer screen means... higher position than zero! These scripts are for computational puroposes meant for a web document canvas!

endPlotter()
This script returns one array of two entries carrying the [top,left] coordinates on the screen of a segment whose (arguments as follows) startTop, startLeft, length, angle are known. Accepts a 5th argument named angleIsRadians if the unit measure you're passing the angle is, for some reasons, radians instead of degrees: if so pass also such argument as number 1.
Note that an angle is mandatory, since from an origin given by startTop and startLeft coordinates there could be, stretching out at the given length, an infinite number of possible lines if no angle is known.
 
possible invocation:
foo=endPlotter(0,0,500,45)
then:
foo[0] is endTop coordinate,
foo[1] is endLeft coordinate.
function endPlotter(startTop, startLeft, length, angle, angleIsRadians){
//validate:
startTop=(startTop && !isNaN(parseFloat(startTop)))? parseFloat(startTop):0;
startLeft=(startLeft && !isNaN(parseFloat(startLeft)))? parseFloat(startLeft):0;
length=(length && !isNaN(parseFloat(length)))? parseFloat(length):0;
angle=(angle && !isNaN(parseFloat(angle)))? parseFloat(angle):0;
while(angle>360){angle-=360};
while(angle<0){angle+=360};
//initialize:
//Degree to Radians converter:
var DtoR=(!angleIsRadians)?
Math.round((Math.PI/180)*1000000)/1000000:
1;
//run:
  if(angle>=0 && angle<=90){
  startTop-=Math.sin(angle*DtoR)*length;
  startLeft+=Math.cos(angle*DtoR)*length;
  }
  else if(angle>90 && angle <=180){
  angle=180-angle;
  startTop-=Math.sin(angle*DtoR)*length;
  startLeft-=Math.cos(angle*DtoR)*length;
  }
  else if(angle>180 && angle <=270){
  angle-=180;
  startTop+=Math.sin(angle*DtoR)*length;
  startLeft-=Math.cos(angle*DtoR)*length;
  }
  else{/*271 to 360*/
  angle=360-angle;
  startTop+=Math.sin(angle*DtoR)*length;
  startLeft+=Math.cos(angle*DtoR)*length;
  };
return new Array( (Math.round(startTop*10)/10) , (Math.round(startLeft*10)/10) );
/*keep this comment to reuse freely
http://www.unitedscripters.com */}
endPlotter() TEST SNIPPET


(radians? )


lengthPlotter()
Knowing a set of coordinates on two screen points (or segment edges, so arguments as follows:) startTop, startLeft, endTop, endLeft whereas has no relevance which one you consider the starting pair and the ending one, this script returns a number which is the distance (length) of the segment.
A similar script but with more complex output and more geared to be used with ULMA is distances().
function lengthPlotter(startTop, startLeft, endTop, endLeft){
startTop=(startTop && !isNaN(parseFloat(startTop)))? parseFloat(startTop):0;
startLeft=(startLeft && !isNaN(parseFloat(startLeft)))? parseFloat(startLeft):0;
endTop=(endTop && !isNaN(parseFloat(endTop)))? parseFloat(endTop):0;
endLeft=(endLeft && !isNaN(parseFloat(endLeft)))? parseFloat(endLeft):0;
return Math.round( (Math.sqrt(
Math.pow(
  (Math.max(startTop, endTop)-Math.min(startTop, endTop))
,2)+
Math.pow(
  (Math.max(startLeft, endLeft)-Math.min(startLeft, endLeft))
,2)
))*100 )/100;
}
lengthPlotter() TEST SNIPPET





anglePlotter()
Given a single segment whose (arguments as follows) startTop, startLeft, endTop, endLeft set of coordinates are known, produces the angle such segment has, following the angle convention for single segments that was outlined above (which is, by the way, a mathematical standard).
5th argument named inRadians if set to 1 produces the result in radians, for the script defaults to degrees.
Obviously, if you want to reverse the result, you can subtract 360-returned output.
function anglePlotter(startTop, startLeft, endTop, endLeft, inRadians){
//FOR BROWSERS
//validate:
startTop=(startTop && !isNaN(parseFloat(startTop)))? parseFloat(startTop):0;
startLeft=(startLeft && !isNaN(parseFloat(startLeft)))? parseFloat(startLeft):0;
endTop=(endTop && !isNaN(parseFloat(endTop)))? parseFloat(endTop):0;
endLeft=(endLeft && !isNaN(parseFloat(endLeft)))? parseFloat(endLeft):0;
//initialize:
var supplement=0;
//Degree to Radians converter:
var DtoR=Math.round((Math.PI/180)*1000000)/1000000;
var length=Math.round( (Math.sqrt(
Math.pow(
  (Math.max(startTop, endTop)-Math.min(startTop, endTop))
,2)+
Math.pow(
  (Math.max(startLeft, endLeft)-Math.min(startLeft, endLeft))
,2)
))*100 )/100;
if(!length){return parseFloat(0)};
var sideY=(endTop<=startTop)?
startTop-endTop:endTop-startTop;
//run:
var ratio=sideY/length;
var arcsin=(Math.round( (Math.asin(ratio)/DtoR)*10 )/10);
  if((endTop==startTop && endLeft>startLeft)||
  (endTop<startTop && endLeft>startLeft)||
  (endTop<startTop && endLeft==startLeft)){
  supplement=arcsin;
  }
  else if((endTop<startTop && endLeft<startLeft) ||
  (endTop==startTop && endLeft<startLeft)){
  supplement=(!arcsin)?180:90+(90-arcsin);
  }
  else if((endTop>startTop && endLeft<startLeft)||
  (endTop>startTop && endLeft==startLeft)){
  supplement=arcsin+180;
  }
  else if(endTop>startTop && endLeft>startLeft){
  supplement=360-arcsin;
  }
return (!inRadians)?supplement:
Math.round( (supplement*DtoR) *100 )/100;
/*keep this comment to reuse freely
http://www.unitedscripters.com */}
anglePlotter() TEST SNIPPET


(radians? )
Please before considering script problems (in themselves always a chance), remember:
  • Unlike trigonometry, on browsers the lower the Y (top) values, the higher on the screen! Therefore the screen equivalent of X=1 is not Y=1 but:
    Y=-1
    !
  • Unlike trigonometry, you provide first Y (vertical), then X (horizontal).



anglePlotter2()
As the name suggests by the number 2, this script can return the angle formed by 2 segments.
The segments must have a common origin (that is, same startTop and startLeft coordinates, or positively no actual angle would exist between the two segments!): therefore startTop and startleft (called in the script originTop originLeft) coordinates are shared. It differs instead as far as the following coordinates (give a glimpse at the script now if you wish) endTop1 endLeft1 endTop2, endLeft2 for the two segments might arguably have different ending points.
The script assumes that the segment whose ending coordinates refer to endTop2 endLeft2 is the segment revolving around the other, therefore if the first is, say, horizontal, and the second ending coordinates are, say, southwest oriented, the angle would be between (see again the convention table if you prefer) 181 and 269. If you just want the opposite, that is imagining that the situation is inverted, just subtract 360-returned value!
 
Last argument inRadians to get (if set to 1) the result in radians instead than in degrees, as usual.
anglePlotter2() requires anglePlotter() being defined in the same script in order to work!
function anglePlotter2(originTop, originLeft, endTop1, endLeft1, endTop2, endLeft2, inRadians){
/*REQUIRES anglePlotter()*/
var _1=anglePlotter(originTop, originLeft, endTop1, endLeft1, inRadians);
var _2=anglePlotter(originTop, originLeft, endTop2, endLeft2, inRadians);
return ( Math.max(_1,_2)-Math.min(_1,_2) );
}
anglePlotter2() TEST SNIPPET



(radians? )


pathPlotter()
This script has been sort of half a nightmare: alike pathMaker but without having to meddle with ULMA at all, it can plot out all the intermediate steps that a segment is made up of, fragmenting it by steps whose default measurement is half a pixel (although you can set a lower or higher amount in order to fragment your segment in bigger or smaller bits of coordinates), returning an array whose each entry is an array of two entries representing the [top,left] coordinates of the step.
You pass the arguments as follows: startTop, startLeft, endTop, endLeft, unit, reversed, approx, whereas unit defaults to 0.5 but you can lower it: remember that if you lower it, the last meaningful unit you can fragment an object with on browsers and screen pixels is just 0.5 (that is, browsers can't actually do something with fragments of whatever whose incremental unit is lower than 0.5, like moving an object along such coordinate pairs); also, the lower the unit, the most likely rounding problems can be, like two subsequent fragments carrying the same coordinate: this may happen for two reasons: one is that in order to avoid the computer "wrong" rounding given to the numerical internal representation (you may know, that annoying problem that makes a computer represent a number 1.4 like 1.39999999999998 ...!) I round by one decimal (and if you want to override this, pass the approx argument as, say, 100 if you want to round by 2 floating digits, 1000 if you want to round by 3 floating digits, or just 1 if you do not want decimal fractions at all; by default it rounds by one floating digit).
 
The other reason you could get one set of coordinates (either top or left, but not both) whose length is unsuitable to be followed by a browser when, for instance, moving an object along such coordinates, is that either the vertical top coordinates or the horizontal left coordinates are calculated as a ratio from the other: so it may happen that if the ratio (it may happen in case of wide disproportion between the x ending coordinates and the y ending coordinates such as endTop=10 and endLeft=500) is too small and also the unit is small as well, then it is unavoidable that one set of coordinates would exhibit numbers whose increase is so small to be meaningless to a browser (say: 99.997, then 99.998, then 99.999... those numbers might even appear, as a consequence of the rounding by 10, as a set of consecutive 99.9).
If you do not pass any unit argument, the script will attempt to grab the first useful incremental ratio able to exhibit a browser meaningful increase on the affected axis: on the other hand, this might cause the opposite problem: one set of coordinates (say top) incrementing by the first meaningful unit (about 0.5), and the other set increasing dramatically. If you want you can test this by setting in the test form below:
  • UNIT: NONE
  • EndTop = 10
  • End Left = 500
and check the output array increments.
Conversely, whenever you do pass a unit argument, no such internal rearrangement will be performed, and the script will proceed using the unit amount you passed.

Argument reversed just reverses the output array if passed as 1.
Example of a simple invocation:
pathPlotter(0,0,400,-310)
Note that in themselves 5th argument unit (but with the cautions said above, sd maybe you want to consider a good idea passing it or not, depending upon the degree of pixel fragmentation you want) and 6th reverse and 7th approx are entirely optional.
function pathPlotter(startTop, startLeft, endTop, endLeft, unit, reversed, approx){
/* Thanks to Dirk Vdm on the sci.math newsgroup for pointing me out to the EXPLICIT
* line formula for the ordinate
* (and not the IMPLICIT formula I was using at first, with endless complications), namely:
*       y = y1 + M * (x-x1)
*/

//validate:
startTop=(startTop && !isNaN(parseFloat(startTop)))? parseFloat(startTop):0;
startLeft=(startLeft && !isNaN(parseFloat(startLeft)))? parseFloat(startLeft):0;
endTop=(endTop && !isNaN(parseFloat(endTop)))? parseFloat(endTop):0;
endLeft=(endLeft && !isNaN(parseFloat(endLeft)))? parseFloat(endLeft):0;
approx=(approx && !isNaN(parseFloat(approx)))? Math.abs(parseFloat(approx)):10;
//initialize:
var diffX=endLeft-startLeft;
var diffY=endTop-startTop;
var ratio=(diffX)?diffY/diffX:0;
if( ratio && (!unit || isNaN(parseFloat(unit))) ){
unit=0.5;
var increment=Math.abs(ratio)*unit;
  while(increment<0.5){
  unit+=0.5;
  increment=Math.abs(ratio)*unit;
  }
}
else{
unit=(unit && !isNaN(parseFloat(unit)))? parseFloat(unit):0.5;
};
var startAt=(diffX)?startLeft:startTop;//is x or y
var endAt=(diffX)?endLeft:endTop;//is x1 or y1
var y1=(diffX)?endTop:endLeft;
var type=(startAt<endAt)?-1:1;
unit=(unit<0)?Math.abs(unit):unit;
unit=(startAt<endAt)?unit:-unit;
var output=new Array(0);
var exit=0;
//run:
for(var i=startAt;; i+=unit){
  if(type<0 && i>=endAt){
  i=endAt;
  exit=1;
  }
  else if (type>0 && i<=endAt){
  i=endAt;
  exit=1;
  }
var y=Math.round( (y1 +ratio * (i-endAt)) *approx)/approx;
output[++output.length-1]=(diffX)?
new Array(y, Math.round(i*approx)/approx ):
new Array(Math.round(i*approx)/approx, y );
  if(exit){break};
};
return (reversed)?output.reverse():output;
/*keep this comment to reuse freely
http://www.unitedscripters.com */}
pathPlotter() TEST SNIPPET


* =
What's the ratio? In case you want to calculate it prior to passing your values, it is:
(endTop-startTop)/(endLeft-startLeft)
This applies only if (endLeft-startLeft) not equal to zero; if so, consider it as zero.



partyPlotter()
The intersection of two lines is given by a comparison by two line equation both explicited by the ordinate. This script does that, namely verify if two lines cross (only case they don't is if they are parallel, and in this case the script returns false, so always check whether the output is false before attempting to read the alternate output, namely an Array which would carry the coordinates of the intersection point).
Arguments require the starting and ending coordinates of two lines, therefore: startTop1, startLeft1, endTop1, endLeft1, startTop2, startLeft2, endTop2, endLeft2: be careful not to mess up them, and for each line doesn't matter which point you consider as starting and as ending as long as you keep the consistence: first 4 arguments belong to the first line (within whose each set each coordinate must belong to the same point), last 4 arguments belong to the second line.
 
This function requires anglePlotter() to be defined in your script to work for it has to use it.
 
Please do not pass negative numbers: actually you have no reason to do so for your screen, which is your playground, can be seen as a quadrant of only positive coordinates, and so the script would work; if you pass negative numbers (that is, segments that go beyond the screen limits: and honestly I can't see reasons you can't be able to avoid this, all you have to do is to pass just positive numbers) the script would still yield the trigonometric correct result, but since on browsers top coordinates with a negative number are higher upward (yes!), this would cause your output to be sort of a reversed mirror of the screen. Do not pass negative values for the top coordinates.
 
Last but not least, the script returns either false as I said, or an array whose entries are as follows:
[TOPCoord_ofCross, LEFTcoord_ofCross, aFLAG]
So first entry [0] of the output array is the top coordinate of the crossing point, second [1] entry is the left coordinates, the third [2] entry is interesting for if it is 1 it means the segments actually cross, if it is zero, means the segments would cross only if... prolonged beyond their edges! Note that this latest feature is browser specific and would not be reliable to plot out if such circumastance (that is, checking if actual interesction or intersection only if segments are prolonged) applies in a real trigonometric cross (namely not in a mirror-reversed one like on browsers); if you do not want it browser specific, pass a 9th argument (fullTrig... onometric) as number 1, and also the third output entry will be reliable on a standard trigonometric cross.
function partyPlotter(startTop1, startLeft1, endTop1, endLeft1, startTop2, startLeft2, endTop2, endLeft2, fullTrig){
//REQUIRES: anglePlotter()
/*Do NOT pass NEGATIVE numbers or won't produce browser fit results*/

//validate:
startTop1=(startTop1 && !isNaN(parseFloat(startTop1)))? parseFloat(startTop1):0;
startLeft1=(startLeft1 && !isNaN(parseFloat(startLeft1)))? parseFloat(startLeft1):0;
endTop1=(endTop1 && !isNaN(parseFloat(endTop1)))? parseFloat(endTop1):0;
endLeft1=(endLeft1 && !isNaN(parseFloat(endLeft1)))? parseFloat(endLeft1):0;
startTop2=(startTop2 && !isNaN(parseFloat(startTop2)))? parseFloat(startTop2):0;
startLeft2=(startLeft2 && !isNaN(parseFloat(startLeft2)))? parseFloat(startLeft2):0;
endTop2=(endTop2 && !isNaN(parseFloat(endTop2)))? parseFloat(endTop2):0;
endLeft2=(endLeft2 && !isNaN(parseFloat(endLeft2)))? parseFloat(endLeft2):0;
//initialize:
var diffX1=endLeft1-startLeft1;
var diffY1=endTop1-startTop1;
var diffX2=endLeft2-startLeft2;
var diffY2=endTop2-startTop2;
var m1=(diffX1)?diffY1/diffX1:0; //ratio
var m2=(diffX2)?diffY2/diffX2:0; //ratio
var y,x;
var skip=0;
//exceptions:
  if(m1==m2 || m1*m2==-1){
  //parallel or perpendicular:
  var d90=anglePlotter(startTop1, startLeft1, endTop1, endLeft1);
  var d90b=anglePlotter(startTop2, startLeft2, endTop2, endLeft2);
    if(m1*m2==-1 || d90==90 || d90b==90 || d90==270 || d90b==270){
    /* perpendicular, maybe also axis parallel */
    var one=(startTop1==endTop1)?endTop1:endLeft1;
    var two=(startTop2==endTop2)?endLeft2:endTop2;
    y=(one==endTop1)?endTop1:endTop2;
    x=(one==endLeft1)?endLeft1:endLeft2;
    skip=1
    }
    else if( (startTop1==endTop1 && startTop2==endTop2) ||
    (startLeft1==endLeft1 && startLeft2==endLeft2) ){
    //parallel:
    return false
    }
  };
//run:
if(!skip){
var b1=endTop1+m1*(0-endLeft1);
var b2=endTop2+m2*(0-endLeft2);
var firstHalf=b1-b2;
var secondHalf=m2-m1;
x=(secondHalf)? Math.round((firstHalf/secondHalf)*10)/10:0;
y=Math.round((b1+(x*m1))*10)/10;
};
/* they cross: is cross point within the segments' edges,
* or they'd cross only if PROLONGED? : */
var actualCross=( (y<startTop1 && y<endTop1) || (y<startTop2 && y<endTop2) || (y>startTop1 && y>endTop1) || (y>startTop2 && y>endTop2) )?0:1;
//for browsers:
  if(!fullTrig){actualCross=(y<0 || x<0)?0:actualCross;};
//actualCross=1, they do cross:
return new Array(y,x,actualCross);
/*keep this comment to reuse freely
http://www.unitedscripters.com */}
partyPlotter() TEST SNIPPET



(fullTrig? )


fragmentPlotter()
This is a script that finds the coordinates for a point which is somewhere inside a given line, providing the length level such point must be, or the ratio: that is, if you have a segment whose origin and end points coordinates you know, you may want to calculate what the top/left coordinates of a point in within such segment would be where its length is, say, one third or two fifth or whatever value you can imagine of the current length (actually if such value, passed as argument named fragment is not actually passed, the script defaults to one half: that is: 0.5 or, in other words, the coordinates of the mid point of the segment!). If it is a ratio (such as 1/3 is), you pass such value as the actual division: so if you search for the coordinates of the point located at three fifths of the given segment, you pass 3/5 namely: 0.6
As you are soon to see, you can also pass actual length values, and not uniquely ratios.
 
So arguments as follows: first the starting and ending segment coordinates as: startTop, startLeft, endTop, endLeft, then the fragment (as a ratio or actual length), then an optional 6th argument named isLength (if passed, it implies what you have passed as the previous just mentioned fragment argument isn't a ratio but a length itself: say you want to know the coordinates where the segment's length is say 20pixels: you can pass fragment as 20 and the isLength as number 1 to flag the previous fragment argument is not meant to be handled like a ratio but like an actual length).
7th argument named opposite would provide you, if passed as number 1, the coordinates of the point calculating it in the opposite direction.
On the whole, only the first 4 arguments are mandatory, and the script assumes the fragment value as 0.5 by default (halfway the segment, that is).
Note that passing as the fragment argument a value such as zero and meaning such value must be zero indeed, would be utterly meaningless: it would mean you want to know what are the coordinates of the segment when its length is zero: but you already know such coordinates: they're the starting ones, so you wouldn't need using this function at all, correct? Therefore the function defaults the fragment argument to a positive value (ratio: 0.5) also if you don't pass it or pass it as zero.
 
If you provide length values exceeding the current length of the segment, the script would behave ina way similar to the endPlotter() function and would provide you with the coordinates of the ending point where the given segment would end if it would stretch until it reaches the provided "exceeding" length or ratio.
If the coordinates of the two points (start and end edges) are identical (a nonsense actually: two edges which are identical do not describe a line but a point, so no inclination can be deduced), the script defaults to the behavior accordingly to which the hypothetical segment inclination is vertical: would stretch upward and vertically.
Also: keep in mind that the first point coordinates you provide (startTop, startLeft arguments, those are) are considered like the referring point from where the calculations are to be performed. This does not affect the outcome if, for instance, the start set of coordinates is at a level higher than the end set of coordinates: this is legitimate. Simply, the calculations assume as the reference point the first set of provided coordinates.
 
Example: for a segment in between: [top:0, left:0] stretching to [top:0, left:15] (perchance it is horizontal since it has identical starting and ending top coordinates, just in order to simplify our example), calculate point coords for length=8:
results may yield, depending on how you pass those parameters for the edges coordinates (and also depending on whether you pass or not the opposite argument):
  1. [0,0,0,15]=>[0, 8] /* horizontal line, left: 0+8=8 */
    /* if opposite=> [0, -8], for left is: 0-8=-8 */
  2. [0,15,0,0]=>[0, 23] /* horizontal line, left: 15+8=23 */
    /* if opposite=> [0, 7], for left is: 15-8=7 */
Therefore the only thing you must be aware is that if the coordinates of the first point (startTop, startLeft) rest at a higher level than the coordinates of the second point (endTop, endLeft), the result might be a point outside the line (see list point 2 above), as if it were stretching beyond its edges. In these cases either you make sure the first set of coordinates belongs to a point lower that the second, or you pass the opposite argument as 1.
Feel free to test with the test Form with some trivial values of your concept, it would make you understand how to effectively use this script.
 
The script returns an array of two entries, first entry is the top and second entry is the left screen coordinates for the point that is the ending point of the given subportion of the segment.
 
In the test Form remember to check the is length checkbox if the fragment value you pass is meant to be a length and not a ratio (this case applies especially if you use the Insert Fake button to populate the form: it won't check the checkbox by itself).
function fragmentPlotter(startTop, startLeft, endTop, endLeft, fragment, isLength, opposite){
//validate:
startTop=(startTop && !isNaN(parseFloat(startTop)))? parseFloat(startTop):0;
startLeft=(startLeft && !isNaN(parseFloat(startLeft)))? parseFloat(startLeft):0;
endTop=(endTop && !isNaN(parseFloat(endTop)))? parseFloat(endTop):0;
endLeft=(endLeft && !isNaN(parseFloat(endLeft)))? parseFloat(endLeft):0;
fragment=(fragment && !isNaN(parseFloat(fragment)))? parseFloat(fragment):0.5;
 /*again:*/ fragment=(fragment)? Math.abs(fragment):0.5;
//run:
var num=endTop-startTop;
var den=endLeft-startLeft;
var average=(den)?(num/den):0;
var fragmentLength=Math.round( (Math.sqrt(
Math.pow(
(Math.max(startTop, endTop)-Math.min(startTop, endTop))
,2)+
Math.pow(
(Math.max(startLeft, endLeft)-Math.min(startLeft, endLeft))
,2)
))*100 )/100;
var searchedLength=(!isLength)? (fragment*fragmentLength):fragment;
var unitIncreaseRatio=Math.sqrt(1+Math.pow(average,2));
var unitIncrease=(unitIncreaseRatio)? (searchedLength/unitIncreaseRatio):0; /*just safer checkin if unit is 0*/
var X,Y;
  if(den){
X=(!opposite)? (unitIncrease+startLeft): (startLeft-unitIncrease);
Y=(!opposite)? ((unitIncrease*average)+startTop): (startTop-(unitIncrease*average));
X=Math.round(X*100)/100;
Y=Math.round(Y*100)/100;
  }
  else{//is vertical
X=startLeft;
if(opposite){
Y=(endTop>startTop)? (startTop+searchedLength): (startTop-searchedLength);
}
else{
Y=(endTop>startTop)? (startTop-searchedLength): (startTop+searchedLength);
}
  }
return new Array(Y,X);
/*keep this comment to reuse freely
http://www.unitedscripters.com */}
fragmentPlotter() TEST SNIPPET



(fragment is length? )
(opposite? )



TOP OF PAGE