Animating a SVG Line in JavaScript - javascript

Hello
I'm creating and then animating SVG lines using this code, the first one doesen't work, but all of the rest does, what am I missing?
var newElement = document.createElementNS('http://www.w3.org/2000/svg','line');
newElement.setAttribute('class','travelPath');
newElement.setAttribute('x1',currentCity.x);
newElement.setAttribute('y1',currentCity.y);
newElement.setAttribute('x2',nextCity.x+10);
newElement.setAttribute('y2',nextCity.y+10);
newElement.style.stroke="#3541b1";
$("#theSVG").append(newElement);
var length = newElement.getTotalLength();
$(newElement).css({
'stroke-dasharray': length+1,
'stroke-dashoffset': length+1
});
$(newElement).animate({'stroke-dashoffset': 0}, 3000, mina.bounce);
The length variable comes back as 0 on the first console.log, but when I run it again at a later time, it comes back with the correct value, and animates in.
Almost as if the line hasen't been drawn before it tries to animate.

getTotalLength isn't well supported to my understanding on a 'line' element.
You could convert this over to a path. Eg...
var newElement = document.createElementNS('http://www.w3.org/2000/svg','path');
newElement.setAttribute('class','travelPath');
newElement.setAttribute('d', "M50,50L100,100")
newElement.style.stroke="#3541b1";
$("#theSVG").append(newElement);
var length = newElement.getTotalLength();
console.log( length )
jsfiddle
See if that helps at all.

As you have just a straight line in a Euclidean plane, you can just compute the length directly using:
let length = Math.sqrt(
Math.pow( newCity.x + 10 - currentCity.x, 2 ) +
Math.pow( newCity.y + 10 - currentCity.y, 2 )
);

Related

How do I plot an area chart in p5.js?

I am trying to recreate this visualization using p5.js. I have some trouble understanding how to create the coordinates for the new points and plot them on my canvas.
The data is a series of negative-positive values that need to be plotted below and above an X-axis respectively (from left to right). This is a sample:
"character","roll_value"
"Daphne Blake",0
"Daphne Blake",-1
"Daphne Blake",-1
"Daphne Blake",-5
"Daphne Blake",-3
"Daphne Blake",2
So I know that I have to map the values between a certain negative and positive height so I've demarcated those heights as follows:
let maxNegativeHeight = sketch.height - 120;
let maxPositiveHeight = sketch.height/4;
For mapping the input I thought of creating a new function called mapToGraph which takes in the roll_value, the old X position, max height and min height. This would map the old values to a new incremented X position and a vertical height:
const mapToGraph = (value, oldXPos, maxHeight, minHeight) => {
const newXPos = oldXPos + 10;
const newYPos = sketch.map(value, 0, maxHeight, minHeight, maxHeight);
return [newXPos, newYPos];
};
In my draw function, I am drawing the points as follows:
sketch.draw = () => {
for(let i = 0; i < data.getRowCount(); i++) {
let character = data.getString(i, "character");
if(character === 'Daphne Blake'){
console.log(character);
// Draw a horizontal line in the middle of the canvas
sketch.stroke('#F18F01');
sketch.line(0, sketch.height/2, sketch.width, sketch.height/2);
// Plot the data points
let value = data.getNum(i, "roll_value");
let [newX, newY] = mapToGraph(value, 0, maxNegativeHeight, maxPositiveHeight);
console.log(newX, newY);
sketch.strokeWeight(0.5);
sketch.point(newX, newY);
}
}
};
However, this does not plot any points. My console.log shows me that I am not processing the numbers correctly, since all of them look like this:
10 -3
cardThree.js:46 Daphne Blake
cardThree.js:55 10 -4
cardThree.js:46 Daphne Blake
cardThree.js:55 10 -4
cardThree.js:46 Daphne Blake
What am I doing wrong? How can I fix this and plot the points like the visualization I linked above?
Here is the full code of what I've tried (live link to editor sketch).
This is the full data
In your code newX is always 10 since you always pass 0 as the second argument to mapToGraph. Additionally the vertical displacement is always very small and often negative. Since you are using newY directly rather than relative to the middle of the screen many of the points are off screen.

Is there a JavaScript function to link ranges (top range 0----100 bottom range 55%----24%) known top input, want bottom value output

The following image will help explain what I am trying to achieve...
The top line (A) is a given calculated JavaScript value, lets call this the input.
The bottom line (B) is the output, so whatever input to (A) is given (will always be within the range) if a line (like the green one shown) were to be drawn I need the value of the output.
I have tried to search for phrases like "linked range", "parallel linked values" and similar but I think half of my problem is not knowing what this kind of calulation is called.
Usually I would be able to show what I have tried but on this one I really dont have a clue where to start.
Any help greatly appreciated.
So get the percentage in A
percentage = A[value] / ( A[max] - A[min] )
Use that to figure out the value in second
result = B[max] - percentage * (B[max] - B[min])
so basic JavaScript
var aMin = 0;
var aMax = 500;
var bMin = 24;
var bMax = 55;
var aValue = 100;
var percentage = aValue / ( aMax - aMin );
var result = bMax - percentage * (bMax - bMin);
console.log(result + "%");

Javascript. How to convert 2.5/3 to say 3/2

var oneX = eval(prompt ("What's the x variable of the first set of points"));
var oneY = eval(prompt ("What's the y variable of the first set of points"));
var twoX = eval(prompt ("What's the x variable of the second set of points"));
var twoY = eval(prompt ("What's the y variable of the second set of points"));
console.log(oneX);
console.log(oneY);
console.log(twoX);
console.log(twoY);
var yRes = twoY-oneY;
var xRes = twoX-oneX;
console.log(yRes);
console.log(xRes);
var slope = xRes/yRes;
console.log("The slope is " + yRes + "/" + xRes);
This is my code. This is what the output looks like:
0.9166666666666666
0.8
2.09375
0.5714285714285714
-0.22857142857142865
1.1770833333333335
The slope is -0.22857142857142865/1.1770833333333335
However, I don't want -.22/1.177 I want -33/7 (I don't know the exact answer) I've never posted on StackOverflow before, so if I did something stupid while typing, I apologize. I'm new to programming so don't be too harsh.
The easiest way would be rounding to a certain fraction, e.g:
Math.round( slope * 10 ) + "/10"
What you're talking about is simplifying a ratio. First you multiply each number by the least common multiple to get them both into whole numbers, then divide each number by the greatest common factor to reduce them.
A full explanation of the process with examples: https://www.tutorialspoint.com/ratios_and_unit_rates/simplifying_ratio_decimals.htm

Google Earth Engine: How can I perform image calculations with random values pixel by pixel?

I would like to perform some calculations on an ee.Image pixel by pixel, however, the result calculated must obey a probabilistic Gaussian distribution, so some pixels will be modified and others not. I already coded a probabilistic function but I don't know how to apply it to each pixel in GEE. Is there any way to do that?
I already tried something like the following, but the same random number is used for every pixel, and that is not what I want.
Trial 1)
var result = img.where(img.lte(32 + Math.floor((Math.random() * 10) + 1)),1);
Trial 2)
var result = img.expression(
'(B1 > 32 + P) ? 1', {
'P': Math.floor((Math.random() * 10) + 1),
'B1': img.select('B1')});
Trial 3)
var result = img.expression(
'P > 10 ? 1', {
'P': MyProbabilisticFunction(),
'B1': img.select('B1')});
I also tried to use the following to generate a "probabilistic" image to perform my calculations but, since I have several classes in the same raster, the probability of the "event" is not quite right.
Trial 4)
var rd = ee.FeatureCollection.randomPoints(geometry,1000,Math.floor((Math.random() * 10) + 1));
var prob = rd.draw('000000',5,0).select('vis-red');
var result = img.where(prob.eq(0),1);
You can generate a random value per pixel using ee.Image.random().
That said, the rest of Earth Engine doesn't work the way you're attempting to use it. All image functions are "vectorized"; you don't ever get to work "pixel by pixel".

SVG find rotation angle of a path

I've a problem with my SVG map.
I use jVectorMap to create a custom map and I need to write the name of every field in the center of the field.
The example is: JSFiddle Example (zoom in the right side to see the text)
I can find the center of every field with this function:
jvm.Map.prototype.getRegionCentroid = function(region){
if(typeof region == "string")
region = this.regions[region.toUpperCase()];
var bbox = region.element.shape.getBBox(),
xcoord = (bbox.x + bbox.width/2),
ycoord = (bbox.y + bbox.height/2);
return [xcoord, ycoord];
};
but my problem is that I want to rotate the text for align it with the top line of the relative field.
I've tried with getCTM() function but it give me always the same values for every field.
How can I find the right rotation angle of every field?
Thank you to all!
Looks like squeamish ossifrage has beaten me to this one, and what they've said would be exactly my approach too...
Solution
Essentially find the longest line segment in each region's path and then orient your text to align with that line segment whilst trying to ensure that the text doesn't end up upside-down(!)
Example
Here's a sample jsfiddle
In the $(document).ready() function of the fiddle I'm adding labels to all the regions but you will note that some of the regions have centroids that aren't within the area or non-straight edges that cause problems - Modifying your map slightly might be the easiest fix.
Explanation
Here are the 3 functions I've written to demonstrate the principles:
addOrientatedLabel(regionName) - adds a label to the named region of the map.
getAngleInDegreesFromRegion(regionName) - gets the angle of the longest edge of the region
getLengthSquared(startPt,endPt) - gets length squared of line seg (more efficient than getting length).
addOrientatedLabel() places the label at the centroid using a translate transform and rotates the text to the same angle as the longest line segment in the region. In SVG transforms are resolved right to left so:
transform="translate(x,y) rotate(45)"
is interpreted as rotate first, then translate. This ordering is important!
It also uses text-anchor="middle" and dominant-baseline="middle" as explained by squeamish ossifrage. Failing to do this will cause the text to be misaligned within its region.
getAngleInDegreesFromRegion() is where all the work is done. It gets the SVG path of the region with a selector, then loops through every point in the path. Whenever a point is found that is part of a line segment (rather than a Move-To or other instruction) it calculates the squared length of the line segment. If the squared length of the line segment is the longest so far it stores its details. I use squared length because that saves performing a square root operation (its only used for comparison purposes, so squared length is fine).
Note that I initialise the longestLine data to a horizontal one so that if the region has no line segments at all you'll at least get horizontal text.
Once we have the longest line, I calculate its angle relative to the x axis with Math.atan2, and convert it from radians to degrees for SVG with (angle / Math.PI) * 180. The final trick is to identify if the angle will rotate the text upside down, and if so, to rotate another 180 degrees.
Note
I've not used SVG before so my SVG code might not be optimal, but it's tested and it works on all regions that consist mostly of straight line segments - You will need to add error checking for a production application of course!
Code
function addOrientatedLabel(regionName) {
var angleInDegrees = getAngleInDegreesFromRegion(regionName);
var map = $('#world-map').vectorMap('get', 'mapObject');
var coords = map.getRegionCentroid(regionName);
var svg = document.getElementsByTagName('g')[0]; //Get svg element
var newText = document.createElementNS("http://www.w3.org/2000/svg","text");
newText.setAttribute("font-size","4");
newText.setAttribute("text-anchor","middle");
newText.setAttribute("dominant-baseline","middle");
newText.setAttribute('font-family', 'MyriadPro-It');
newText.setAttribute('transform', 'translate(' + coords[0] + ',' + coords[1] + ') rotate(' + angleInDegrees + ')');
var textNode = document.createTextNode(regionName);
newText.appendChild(textNode);
svg.appendChild(newText);
}
Here's my method to find the longest line segment in a given map region path:
function getAngleInDegreesFromRegion(regionName) {
var svgPath = document.getElementById(regionName);
/* longest edge will default to a horizontal line */
/* (in case the shape is degenerate): */
var longestLine = { startPt: {x:0, y:0}, endPt: {x:100,y:0}, lengthSquared : 0 };
/* loop through all the points looking for the longest line segment: */
for (var i = 0 ; i < svgPath.pathSegList.numberOfItems-1; i++) {
var pt0 = svgPath.pathSegList.getItem(i);
var pt1 = svgPath.pathSegList.getItem(i+1);
if (pt1.pathSegType == SVGPathSeg.PATHSEG_LINETO_ABS) {
var lengthSquared = getLengthSquared(pt0, pt1);
if( lengthSquared > longestLine.lengthSquared ) {
longestLine = { startPt:pt0, endPt:pt1, lengthSquared:lengthSquared};
}
}/* end if dealing with line segment */
}/* end loop through all pts in svg path */
/* determine angle of longest line segement relative to x axis */
var dY = longestLine.startPt.y - longestLine.endPt.y;
var dX = longestLine.startPt.x - longestLine.endPt.x;
var angleInDegrees = ( Math.atan2(dY,dX) / Math.PI * 180.0);
/* if text would be upside down, rotate through 180 degrees: */
if( (angleInDegrees > 90 && angleInDegrees < 270) || (angleInDegrees < -90 && angleInDegrees > -270)) {
angleInDegrees += 180;
angleInDegrees %= 360;
}
return angleInDegrees;
}
Note that my getAngleInDegreesFromRegion() method will only consider the longest straight line in a path if it is created with the PATHSEG_LINETO_ABS SVG command... You'll need more functionality to handle regions which don't consist of straight lines. You could approximate by treating curves as straight lines with:
if (pt1.pathSegType != SVGPathSeg.PATHSEG_MOVETO_ABS )
But there will be some corner cases, so modifying your map data might be the easiest approach.
And finally, here's the obligatory squared distance method for completeness:
function getLengthSquared(startPt, endPt ) {
return ((startPt.x - endPt.x) * (startPt.x - endPt.x)) + ((startPt.y - endPt.y) * (startPt.y - endPt.y));
}
Hope that is clear enough to help get you started.
Querying getCTM() won't help. All that gives you is a transformation matrix for the shape's coordinate system (which, as you discovered, is the same for every shape). To get a shape's vertex coordinates, you'll have to examine the contents of region.element.shape.pathSegList.
This can get messy. Although a lot of the shapes are drawn using simple "move-to" and "line-to" commands with absolute coordinates, some use relative coordinates and other types of line. I noticed at least one cubic curve. It might be worth looking for an SVG vertex manipulation library to make life easier.
But in general terms, what you need to do is fetch the list of coordinates for each shape (converting relative coordinates to absolute where necessary), and find the segment with the longest length. Be aware that this may be the segment between the two end points of the line. You can easily find the orientation of this segment from Math.atan2(y_end-y_start,x_end-x_start).
When rotating text, make life easy for yourself by using a <g> element with a transform=translate() attribute to move the coordinate origin to where the text needs to be. Then the text won't shoot off into the distance when you add a transform=rotate() attribute to it. Also, use text-anchor="middle" and dominant-baseline="middle" to centre the text where you want it.
Your code should end up looking something like this:
var svg = document.getElementsByTagName('g')[0]; //Get svg element
var shape_angle = get_orientation_of_longest_segment(svg.pathSegList); //Write this function
var newGroup = document.createElementNS("http://www.w3.org/2000/svg","g");
var newText = document.createElementNS("http://www.w3.org/2000/svg","text");
newGroup.setAttribute("transform", "translate("+coords[0]+","+coords[1]+")");
newText.setAttribute("font-size","4");
newText.setAttribute("text-anchor","middle");
newText.setAttribute("dominant-baseline","middle");
newText.setAttribute("transform","rotate("+shape_angle+")");
newText.setAttribute('font-family', 'MyriadPro-It');
var textNode = document.createTextNode("C1902");
newText.appendChild(textNode);
newGroup.appendChild(newText);
svg.appendChild(newGroup);

Categories