I have the following dataset
X Y
------------
9.2294 40
9.65712 60
10.0633 80
10.1865 90
10.2844 100
10.4122 120
10.5217 140
10.5776 160
10.5995 180
10.6237 200
10.563 250
and I want to plot a Profile (Y is the elevation) connecting the point on the basis of the Y order and not to the X order (it is a XY graph and not a time-series)... with dygraphs it seems to be not possible.
dygraphs requires that your data be sorted by the first column (the x-axis). You might be able to pull of what you're asking for using a custom plotter, but it's not a natural fit. You'll have an easier time using either D3 or a library with built-in support for plots like this.
I was able to achieve this with the use of a custom plotter function based on the link #danvk posted.
It works, but it's not pretty. I would love to hear suggestions on how to
avoid using a custom plotter
finding the canvas x/y positions without linear regression
reusing existing drawing code for better features support and avoiding code duplication
below is the code and a screenshot of the graphs it renders
plotter: function(e) {
// dygraph series
var seriesPoints = e.allSeriesPoints[e.seriesIndex];
// real series
var realData = table.lists.get(e.seriesIndex);
// build a linear regression to map from real values to canvas x/y based on a sample of 2 points
var pa, pb;
seriesPoints.forEach(p => {
if (!pa && p.xval != null && p.yval != null) pa = p;
else if (pa && !pb && p.xval != null && p.yval != null) pb = p;
});
if (!pa || !pb) {
return;
}
baseDataX = pa.xval;
baseDataXMul = (pb.canvasx - pa.canvasx) / (pb.xval - pa.xval);
baseDataY = pa.yval;
baseDataYMul = (pb.canvasy - pa.canvasy) / (pb.yval - pa.yval);
var getPoint = function(i) { return { canvasx: pa.canvasx + baseDataXMul * (realData[i].x - baseDataX), canvasy: pa.canvasy + baseDataYMul * (realData[i].y - baseDataY) }; }
// draw the line, iterating on the data points of the REAL series, each of which os properly sorted
var ctx = e.drawingContext;
ctx.beginPath();
var p0 = getPoint(0);
if (p0) ctx.moveTo(p0.canvasx, p0.canvasy);
for (var i = 1; i < realData.length; i++) {
var p = getPoint(i);
if (p) ctx.lineTo(p.canvasx, p.canvasy);
}
ctx.stroke(); }
Related
I have a path in paper.js witch describes a letterform. I got Path by loading a font whith opentype.js's function path.toSVG(); than I load it to paper.js by .importSVG();
Now I do some manipulations with paper.js and then want it back as a downloadable font file. So my question is, is there an easy way to get it back from paper.js to opentype.js?
Edit:
Okay I am asuming there is no easy way. So i tried to convert the path manually with js:
for(var i=0; i<fragments.children.length; i++) {
var glyphName = fragments.children[i].name;
if (glyphName.indexOf('0x') > -1) {
var unicode = String.fromCharCode(parseInt(glyphName, 16));
glyphName = 'uni' + glyphName.charCodeAt(0);
} else {
var unicode = glyphName.charCodeAt(0);
}
var w = 0;
var glyphPath = new opentype.Path();
//Going through the array with the segments of the paper.js Path
for(var i2 = 0; i2 < fragments.children[i].segments.length; i2++) {
// handle In
var x1 = fragments.children[i].segments[i2].handleIn.x;
var y1 = fragments.children[i].segments[i2].handleIn.y;
// handle Out
var x2 = fragments.children[i].segments[i2].handleOut.x;
var y2 = fragments.children[i].segments[i2].handleOut.y;
// Point
var x = fragments.children[i].segments[i2].point.x;
var y = fragments.children[i].segments[i2].point.y;
if (i2 === 0) {
// if its the first segment use move to
glyphPath.moveTo(x, y);
} else if(x1==0 && y1==0 && x2 == 0 && y2 == 0) {
// if there is no curve use line to
glyphPath.lineTo(x, y);
} else {
// use curve if its a curve
glyphPath.curveTo(x1+x,y1+y, x2+x,y2+y, x,y,);
}
if(i2+1 == fragments.children[i].segments.length) {
glyphPath.close();
}
w = Math.max(w, x);
}
var glyph = new opentype.Glyph({
name: fragments.children[i].name,
unicode: unicode,
advanceWidth: (w + 1),
path: glyphPath
});
}
This gets me an opentype font file to download. However, if i open the font in a Font Viewer the whole Form is upside down and the handles are wrong. The positions of them seem right but somehow the wrong handles are in the position where another one should be...they seem to be switched. I can't figure out what I am missing..
This is how it should look
This is how it looks..
How the problem was solved:
As mentioned in the comment I saw that there were no straight lines even in fragments which should have straight lines. So i checked the coordinates and realised that there were straight lines (paths with handle x1/y2 and x2/y2 all on coordinate 0/0) if i just push them one ahead.
For example:
x y x1 y1 x2 y2
1: 148.29 92.125 0 0 -1.25 -3.5
2: 140 85 3.93 1.084 0 0
3: 139.99 74.16 0 0 12.95 2.02
4: 159.55 92.1 -1.238 -8.283 0 0
So i had to change the for loop to actually get the handles of the last point mixed with the ones from the actual point.
So in this example nr. 1 and nr. 3 would get x1: 0, y1: 0 // x2: 0, y2: 0
In my for loop i take x1/y1 from the last segment:
// handle In
var x1 = fragmentPathObj.segments[i2-1].handleOut.x * letterScale;
var y1 = fragmentPathObj.segments[i2-1].handleOut.y*-1 * letterScale;
// handle Out
var x2 = fragmentPathObj.segments[i2].handleIn.x * letterScale;
var y2 = fragmentPathObj.segments[i2].handleIn.y*-1 * letterScale;
If i do this i can check for straight lines with:
if(x1==0 && y1==0 && x2 == 0 && y2 == 0) {
glyphTempPath.lineTo(x, y);
}
and draw the curves when there are handles:
var lastX = fragmentPathObj.segments[i2-1].point.x * letterScale - letterPosCorrectionX;
var lastY = fragmentPathObj.segments[i2-1].point.y*-1 * letterScale - letterPosCorrectionY;
glyphTempPath.curveTo(x1+lastX, y1+lastY, x2+x, y2+y, x,y);
lastX/lastY: Since x1/y1 is coming from the last segment, i need to calculate the position for the handles also with the x/y of the last point.
letter Scale: is used for the scaling of the letter and is calculated by dividing the glyph's advanceWidth by the scaledAdvanceWith
y*-1 : is used to solve the upside down problem.
letterPosCorrectionX and letterPosCorrectionY; are corrections for the position (so they are moved to the correct position in the font.)
Maybe this can help someone save some time :)
OK. Based on the images, you've got at least 2 problems.
The first is that the Y co-ordinates have inverted.
importSVG probably handled that for you correctly going in, but you'll need to handle it coming back out.
That will mean iterating the glyphPath a second time and reversing the Y values. Looks like you are already tracking the max Y (or X?), and you'll need that. And you will probably also need an offset value.
The second problem (which is a guess) is that curves are being turned into lines.
I'm afraid I can't really advise on that, other than it probably means your curve detection is incorrect.
Summary
I'm trying to read a .SCT file which is a custom file type that is created by a program named VRC. I use this file to plot lat long coordinates within my source area. The issue I am having is that most of the lat long coordinates are not being mapped correctly. It appears that most coordinates are mapping to some arbitrary point.
Background
Here is a sample of the code I'm currently using to convert and plot them into the SVG.js canvas.
CODE [index.js (at least the active part)]:
var coor = [];
let fixed = [];
var dms2dd = function(object) {
var deg = parseFloat(object.degrees),
min = parseFloat(object.minutes),
sec = parseFloat(object.seconds),
dir = object.dir == "N" || object.dir == "E" ? 1 : -1;
return dir*(deg+(min/60.0)+(sec/3600.0));
};
function llToXY(arr) {
mapWidth = 1000;
mapHeight = 1000;
// get x value
x = (arr[1]+180)*(mapWidth/360)
// convert from degrees to radians
latRad = arr[0]*Math.PI/180;
// get y value
mercN = Math.log(Math.tan((Math.PI/4)+(latRad/2)));
y = (mapHeight/2)-(mapWidth*mercN/(2*Math.PI));
return [x, y];
}
lineReader.eachLine('test.txt', function(line, last) {
let data = line.split(" ");
let coor_length = coor.length;
if (line[0] != " ") {
coor.push({
type: data[0],
coordinates: []
});
// Convert to DD
/*let direction = data[1].substr(0, 1);
let dms = data[1].split(".")
dms2dd({
})*/
data.splice(0, 1);
coor[coor.length - 1]["coordinates"].push(data.join(" "));
} else {
coor[coor_length - 1]["coordinates"].push(line.trim())
}
if (last) {
coor.forEach((data, index) => {
for (coordinate_pair in data["coordinates"]) {
let pair = data["coordinates"][coordinate_pair];
pair = pair.split(" ");
let x_data = pair[0].split("."),
y_data = pair[1].split(".");
let x = dms2dd({
degrees: x_data[0].substring(1),
minutes: parseFloat(x_data[1]),
seconds: parseFloat(`${x_data[2]}.${x_data[3]}`),
dir: x_data[0].substr(0,1)
});
let y = dms2dd({
degrees: y_data[0].substring(1),
minutes: parseFloat(y_data[1]),
seconds: parseFloat(`${y_data[2]}.${y_data[3]}`),
dir: y_data[0].substr(0,1)
});
console.log([x, y]);
coor[index]["coordinates"][coordinate_pair] = llToXY([x, y]);
}
})
return false;
}
});
Drawing Code
let draw = SVG("drawing").size(1000, 1000).panZoom();
let cp = <%- JSON.stringify(cp) %>;
//var line = draw.plot([32.737396,117.204284], [32.736862,117.204468], [32.737396,117.204284], [32.736862,117.204468]).stroke({ width: 1 })
// var line = draw.polyline().fill("none").stroke({width: 0.00005});
// line.transform({
// scale: 50000
// }).transform({rotation: 104.5});
cp.forEach((data)=> {
//draw.polyline(data.coordinates.join(" "))
//llToXY(data.coordinates);
draw.polyline(data.coordinates.join(" ")).fill("none").stroke({width: 0.00005}).transform({scale: 50000}).transform({rotation: -15.80})
});
This code is basically reading from a text file line-by-line and inserting the data into a variable named coor. Once the code reaches the last line, it will convert all coordinates i.e. coor values to decimal degrees.
Unfortunately, the library is not compatible with JSFiddle so I couldn't make a test scenario. I've attached all the necessary files, so you can run locally.
My main concern is that all coordinates are mapping to some arbitrary point.
Sources
This is what the image should look like
What it currently looks like:
VRC Sector File Documentation: http://www1.metacraft.com/VRC/docs/doc.php?page=appendix_g
StackOverflow Question Referenced: Convert latitude/longitude point to a pixels (x,y) on mercator projection
Library Used
svg.js: https://svgjs.dev/
svg.panzoom.js: https://github.com/svgdotjs/svg.panzoom.js
That is actually an issue with bad documentation for the SVG.js library. If you define a transformation as
element.transform({ scale: 30000 })
the result is not a transform attribute with the value scale(30000), which would mean an origin for the scaling at point (0, 0), but a transform matrix that is equivalent to a scaling around the center of the element bounding box.
In your code, each partial shape is drawn as a separate polyline, and is separately scaled around its individual center. The element, before the scaling, is extremely small, and all elements are as closely grouped together as to be virtually at one point. If they are scaled , the result looks like all elements have that point as one common center at their new size.
The most obvious solution is to scale the elements not around their individual center, but around one constant value:
const cx = ..., cy = ...
element.transform({ scale: 30000, cx, cy })
What that value is is not immediately clear. It would be a point that is in the center of the common bounding box of all polylines. How do you get at that? Let the library do the work for you.
If you add all polylines as childs of a <g> element, you can scale that group, and if you leave out values for the center, they will be computed for you:
let draw = SVG("drawing").size(1000, 1000).panZoom();
let g = draw.group();
let cp = <%- JSON.stringify(cp) %>;
cp.forEach((data) => {
group.polyline(data.coordinates.join(" "))
.fill("none")
.stroke({width: 0.00005});
});
group.transform({scale: 50000}).transform({rotation: -15.80});
The above solution is good if you want to get your resulting map at a defined size. If you want to find a scale value that actually lets the content fill your canvas from side to side, it is just as simple: you can get the bounding box of of the group, and set them as a viewbox on the <svg> element. The browser will then take care to scale that box to fit the canvas.
let draw = SVG("drawing").size(1000, 1000).panZoom();
let g = draw.group();
let cp = <%- JSON.stringify(cp) %>;
cp.forEach((data) => {
group.polyline(data.coordinates.join(" "))
.fill("none")
.stroke({width: 0.00005});
});
const { x, y, w, h } = group.bbox();
draw.viewbox(x, y w, h);
I wish to draw charts for large datasets using dimple.js. My code works absolutely fine. But the only problem is that the chart takes more than 45 seconds to come up. I am looking for some kind of an optimization in my code to reduce the time taken for the rendering of the chart. The following is the code for my area chart:
var dataset = [];
// The arrays xpoints and ypoints are populated dynamically
// with hundreds of thousands of points
var xpoints = chartData["xdata"];
var ypoints = chartData["ydata"];
var area1;
var svg = dimple.newSvg("#" + mychart, 700, 600);
var x, y;
for (var i = 0; i < xpoints.length; i++)
dataset.push({
x : xpoints[i],
y1 : parseFloat(ypoints[i])
});
var myChart = new dimple.chart(svg, dataset);
myChart.setBounds(75, 30, 480, 330);
y = myChart.addMeasureAxis("y", "y1");
x = myChart.addCategoryAxis("x", "x");
area1 = myChart.addSeries("First", dimple.plot.area, [ x, y ]);
var l = myChart.addLegend(65, 10, 510, 20, "right");
myChart.draw(1500);
Is there some way to optimize this code in either dimple.js itself or maybe using d3.js?
I'm afraid Dimple is not very performant for hundreds of thousands of points. It's drawing logic is built for flexibility and for cases like this you need to write specific d3 code (think of Dimple as a Swiss-Army Knife but here you need a scalpel). Even with raw d3 you might run into problems with a path containing that number of points. Certainly try raw d3 but you might need to write some more complex additional logic to average every n points together and then fill in detail on zoom. Also remember that even with perfect client code you will suffer a noticeable wait simply getting that volume of data from the server.
I found a solution!!. I was adamant on using dimple.js itself and not raw d3.
What I did was I aggregated the values first and then passed them to the chart.draw() function
The time taken to render the graph now is reduced from 40 seconds to 12 seconds, which is much better.
For now, my aggregation function just sums up the values for a particular category. Maybe the implementation in the draw() function is a little more complex and is therefore taking extra time. xpoints[] and ypoints[] are my arrays with lakhs of points.
Earlier, I just did this:
dataset.push({
x : xpoints[i],
y1 : parseFloat(ypoints[i])
});
Now, I first apply an aggregation as follows:
var isPresent = false;
for (var j = 0; j < unique_x.length; j++) {
if (xpoints[i] == unique_x[j]) {
y_val = parseFloat(ypoints[i]);
if (isNaN(y_val)) {
y_val = 0;
}
y_sum[j] = y_sum[j] + y_val;
isPresent = true;
break;
}
}
if (isPresent == false) {
unique_x.push(xpoints[i]);
y_sum.push(parseFloat(ypoints[i]));
}
Then, I do this:
for (var i = 0; i < unique_x.length; i++) {
dataset.push({
x : unique_x[i],
y1 : y_sum[i]
});
I'm using D3 to create an organization chart. I've got the data loading fine and have figured out how to make the canvas move by dragging the mouse as well a zoom with the mouse wheel.
My problem is that the org chart is rather large so when the document first loads the root node is out of the browser's view area and the zoom level is set fairly high.
I need to figure out how to set the viewable area of the canvas around the first node and set the initial zoom level to 100%.
I was able to create a solution thanks to #Lars Kotthoff.
I retrieved the root node's x value from it's translate attribute (i.e. translate(x,y)) and then took the browser's width / 2 - the x value. I applied this value to the parent group's translate attribute which centers the document around the root node.
var windowWidth = $(window).width();
var node0 = d3.select("#node-0");
var translate = parseTranslate(node0.attr("transform"));
var translateX = translate.x - (windowWidth / 2);
var svgGroup = d3.select("#svg_g");
svgGroup.attr("transform", "translate(-" + translateX + ",22) scale(1)"); // with 20 y padding
NOTE: Because I'm new to SVG and D3 I am still not sure how to get just the "x" value of a node's translate attribute so I created a function that parses the translate attribute with regex. I'm sure there is a better way of getting this value so if anyone wants to update my answer or add a comment for future readers that would increase the value of this question.
The function I created is:
function parseTranslate(str) {
var translate = {
x: 0,
y: 0,
scale: 0
}
var pattern = /\((.+?)\)/g;
var matches = [];
while (match = pattern.exec(str)) {
matches.push(match[1]);
}
if (matches.length) {
if (matches.length == 1) {
if (matches[0].indexOf(",") > -1) {
var p = matches[0].split(',');
translate.x = p[0];
translate.y = p[1];
} else {
translate.scale = matches[0];
}
} else if (matches.length == 2) {
var p = matches[0].split(',');
translate.x = p[0];
translate.y = p[1];
translate.scale = matches[1];
}
}
return translate;
}
I'm also using jQuery in my project to get the width of the browser (ex: $(window).width();)
I'm building a turn based HTML game based on a 2D square grid. Each grid square could take a variable number of movement points to cross (IE: 1 MP for roads, 1.5 MP for grasslands, 2 MP for forests, etc). When the user clicks on a unit I want to determine all possible movable spaces with said unit's allotted movement points so that I can highlight them and make them clickable.
Is there a free library available to do this? I've seen a few pathing algorithms but nothing about determining movable area. How do other game developers handle this problem? I'm open to both vanilla JS and JQuery solutions.
Well, I decided to try and attack this myself. I've never been great at these sorts of algorithms so I'm sure there's a more efficient way to handle it than what I've done. However, for my purposes it runs quickly enough and is very simple and easy to understand.
In case it's helpful to anyone else looking to do the same, I've included the code below. This is an updated version of my original answer, which I modified to also store the path taken so that you can show the units moving through the correct spaces. This answer uses JQuery in the lower examples, but only in a few places; you can easily enough replace them with vanilla JS. And the first block of code, containing the actual path/area finding functionality, is pure JS.
<script>
var possibleMovementAreaArray = new Array(); // This array will hold our allowable movement tiles. Your other functions can access this after running possibleMovementArea().
function possibleMovementArea(unitIndex) {
// I'm storing each unit in my game in an array. So I pass in the index of the unit I want to determine the movement area for.
var x = unitList[unitIndex][10]; // x coordinate on the playgrid
var y = unitList[unitIndex][11]; // y coordinate on the playgrid
var mp = unitList[unitIndex][15]; // number of movement points
possibleMovementAreaArray.length = 0; // Clear our array so previous runs don't interfere.
findPossibleMovement(x, y, mp);
}
function findPossibleMovement(x, y, mp, prevStepX, prevStepY) {
// This is a recursive function; something I'm not normally too good at.
for (var d=1; d<=4; d++) {
// We run through each of the four cardinal directions. Bump this to 8 and add 4 more cases to include corners.
if (d == 1) {
// Check Up
var newX = x;
var newY = y - 1;
} else if (d == 2) {
// Check Down
var newX = x;
var newY = y + 1;
} else if (d == 3) {
// Check Left
var newX = x - 1;
var newY = y;
} else if (d == 4) {
// Check Right
var newX = x + 1;
var newY = y;
}
// Check to see if this square is occupied by another unit. Two units cannot occupy the same space.
spaceOccupied = false;
for (var j=1; j<=numUnits; j++) {
if (unitList[j][10] == newX && unitList[j][11] == newY)
spaceOccupied = true;
}
if (!spaceOccupied) {
// Modify this for loop as needed for your usage. I have a 2D array called mainMap that holds the ID of a type of terrain for each tile.
// I then have an array called terList that holds all the details for each type of terrain, such as movement points needed to get past.
// This for loop is just looking up the ID of the terrain for use later. Sort of like a "SELECT * FROM terrainInfo WHERE ID=terrainOfCurrentTile".
for (var j=1; j<=numTerrains; j++) {
if (newX > 0 && newX <= mapWidth && newY > 0 && newY <= mapHeight && terList[j][1] == mainMap[newX][newY])
break; // After finding the index of terList break out of the loop so j represents the correct index.
}
if (j <= numTerrains) { // Run if an actual terrain is found. No terrain is found if the search runs off the sides of the map.
var newMp = mp - terList[j][7]; // Decrement the movement points for this particular path.
if (newMp >= 0) { // Only continue if there were enough movement points to move to this square.
// Check to see if this square is already logged. For both efficiency and simplicity we only want each square logged once.
var newIndex = possibleMovementAreaArray.length
var alreadyLogged = false
if (possibleMovementAreaArray.length > 0) {
for (var j=0; j<possibleMovementAreaArray.length; j++) {
if (possibleMovementAreaArray[j][1] == newX && possibleMovementAreaArray[j][2] == newY) {
alreadyLogged = true;
var alreadyLoggedIndex = j;
}
}
}
if (!alreadyLogged) {
// This adds a row to the array and records the x and y coordinates of this tile as movable
possibleMovementAreaArray[newIndex] = new Array(6);
possibleMovementAreaArray[newIndex][1] = newX;
possibleMovementAreaArray[newIndex][2] = newY;
possibleMovementAreaArray[newIndex][3] = prevStepX; // This tracks the x coords of the steps taken so far to get here.
possibleMovementAreaArray[newIndex][4] = prevStepY; // This tracks the y coords of the steps taken so far to get here.
possibleMovementAreaArray[newIndex][5] = newMp; // Records remaining MP after the previous steps have been taken.
}
if (alreadyLogged && newMp > possibleMovementAreaArray[alreadyLoggedIndex][5]) {
// If this tile was already logged, but there was less MP remaining on that attempt, then this one is more efficient. Update the old path with this one.
possibleMovementAreaArray[alreadyLoggedIndex][3] = prevStepX;
possibleMovementAreaArray[alreadyLoggedIndex][4] = prevStepY;
possibleMovementAreaArray[alreadyLoggedIndex][5] = newMp;
}
if (newMp > 0) {
// Now update the list of previous steps to include this tile. This list will be passed along to the next call of this function, thus building a path.
if (prevStepX == '') {
var newPrevStepX = [newX];
var newPrevStepY = [newY];
} else {
// This code is required to make a full copy of the array holding the existing list of steps. If you use a simple equals then you just create a reference and
// subsequent calls are all updating the same array which creates a chaotic mess. This way we store a separate array for each possible path.
var newPrevStepX = prevStepX.slice();
newPrevStepX.push(newX);
var newPrevStepY = prevStepY.slice();
newPrevStepY.push(newY);
}
// If there are still movement points remaining, check and see where we could move next.
findPossibleMovement(newX, newY, newMp, newPrevStepX, newPrevStepY);
}
}
}
}
}
}
</script>
After running the above, you can then loop through the array to find all usable tiles. Here is how I did it:
<script>
// Shows the movement area based on the currently selected unit.
function showMovement() {
var newHTML = "";
curAction = "move";
possibleMovementArea(curUnit); // See above code
for (x=0; x<possibleMovementAreaArray.length; x++) {
// Loop over the array and do something with each tile. In this case I'm creating an overlay that I'll fade in and out.
var tileLeft = (possibleMovementAreaArray[x][1] - 1) * mapTileSize; // Figure out where to absolutely position this tile.
var tileTop = (possibleMovementAreaArray[x][2] - 1) * mapTileSize; // Figure out where to absolutely position this tile.
newHTML = newHTML + "<img id='path_" + possibleMovementAreaArray[x][1] + "_" + possibleMovementAreaArray[x][2] + "' onClick='mapClk(" + possibleMovementAreaArray[x][1] + ", " + possibleMovementAreaArray[x][2] + ", 0);' src='imgs/path.png' class='mapTile' style='left:" + tileLeft + "px; top:" + tileTop + "px;'>";
}
$("#movementDiv").html(newHTML); // Add all those images into a preexisting div.
$("#movementDiv").css("opacity", "0.5"); // Fade the div to 50%
$("#movementDiv").show(); // Make the div visible.
startFading(); // Run a routine to fade the div in and out.
}
</script>
Since we determined the path, we can easily show movement as well by looping through the stored information:
<script>
for (j=0; j<possibleMovementAreaArray[areaIndex][3].length; j++) {
// This loop moves the unit img to each tile on its way to its destination. The final destination tile is not included.
var animSpeed = 150; // Time in ms that it takes to move each square.
var animEase = "linear"; // We want movement to remain a constant speed through each square in this case.
var targetLeft = (possibleMovementAreaArray[areaIndex][3][j]-1) * mapTileSize; // This looks at each step in the path array and multiplies it by tile size to determine the new horizonal position.
var targetTop = (possibleMovementAreaArray[areaIndex][4][j]-1) * mapTileSize; // This looks at each step in the path array and multiplies it by tile size to determine the new vertical position.
$("#char_"+curUnit).animate({"left":targetLeft, "top":targetTop}, animSpeed, animEase); // Do the animation. Subsequent animations get queued.
}
// Now we need to move to that last tile.
newLeft = (x-1) * mapTileSize;
newTop = (y-1) * mapTileSize;
$("#char_"+curUnit).animate({"left":newLeft, "top":newTop}, 400, "easeOutCubic"); // Slow unit at the end of journey for aesthetic purposes.
$("#char_"+curUnit).addClass("unitMoved", 250); // Turns the image grayscale so it can easily be seen that it has already moved.
</script>
Hopefully this is helpful to others.