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.
Related
Context:
I try to create a coloring pixels game with Canvas.
As of right now, I render a few rects via strokeRect that can be painted onClick via fillRect.
Since the canvas is not full screen but just a fixed size I need to calculate the offset. When I have the coordinates I just divide the x with the rect width (10).
Here is the code I have.
First I get the correct cursor position:
function getCursorPosition(canvas, event) {
const rect = canvas.getBoundingClientRect();
const x = event.clientX - rect.left
const y = event.clientY - rect.top
return { x: x, y: y };
}
Then I will calculate where to fillRectso it seems that I filled the strokeRect on exactly that position:
const paint = (e, isClick) => {
if (!isDrawing && !isClick) {
return;
}
const coordinates = getCursorPosition(canvas, e);
let rectX = Math.floor(coordinates.x / 10);
let rectY = Math.floor(coordinates.y / 10);
// stop drawing when it's outside of the bounds (i have a grid 100 x 100)
if (rectX > 99 || rectY > 99) {
return;
}
ctx.fillStyle = "black";
ctx.fillRect(rectX * 10, rectY * 10, 10, 10);
};
The Problem:
So that works like a charm. But today I installed React-zoom-pan-pinch
Obviously, after I zoom into the canvas everything is messed up since the getCursorPosition function has to do more work. I need to calculate the new correct position after zooming. But I can't figure out how.
So after I zoom in and I click on the rects (pixels), the colored rect appears on the far right and far bottom. So it's very off right now.
The package gives me this function onZoom that gets these parameters: ReactZoomPanPinchRef and event. These have many attributes on them for example x, y, offsetX, .. and many more.
I tried several combinations but I can't get it to work.
The question:
How can I calculate the new Cursor Position relative to my Canvas so I can draw rectangles over it? What is the calculation I need to make given all the attributes that the onZoom event/props give me.
Here are all the attributes from this package:
https://prc5.github.io/react-zoom-pan-pinch/?path=/story/docs-props--page
Unfortunately, I couldn't find a list of props that ReactZoomPanPinchRef and event gives me. I could make screenshots but it's a long list.
What I found out so far:
I found a react drawing boar repo made with react + canvas.
He uses a mousewheel function which you can see here:
https://github.com/dilidili/react-drawing-board/blob/master/src/SketchPad.tsx#L858
Also this matrix I could locate here:
https://github.com/dilidili/react-drawing-board/blob/master/src/utils.ts#L4
Never heard of that. But maybe I need something like that.
Also the package react-zoom-pan-pinch provides in the onZoom function this parameter:
ReactZoomPanPinchRef that has state on it that looks like that:
https://imgur.com/a/8t1FpJR
So I went back and tried this out like so:
let rectX = Math.floor((coordinates.x + zoomState.offsetX) / rectSize);
let rectY = Math.floor((coordinates.y + zoomState.offsetY) / rectSize);
Now it's much better but the further I zoom in the worse it gets.
Last but not least, here is a codesandbox where you can try this all out:
https://codesandbox.io/s/dark-darkness-iqwku?file=/src/components/canvas.js:2023-2171
Relevant files: index.js + components\canvas.js
If you need any more information please let me know.
Thx guys appreciate it.
So far it seems you've been fiddling with the offset of the zoom state. The offset however is perfectly captured by canvas.getBoundingClientRect() as it still returns the position of the top left corner even after the CSS transform.
The problem lies in your conversion to the rectX and rectY: by zooming in or out the size of your rectangles change, which is not yet reflected in your calculations. The following snippet solves this issue:
const scale = zoomState?.scale ?? 1;
let rectX = Math.floor(coordinates.x / (rectSize * scale));
let rectY = Math.floor(coordinates.y / (rectSize * scale));
A working example can be witnessed in this fork of your CodeSandbox.
There's no simple curved-line tool in turf.js, nor is there an easy way to do it in mapbox (so far as I can see), so I've created a workaround based on this answer in this thread.
However, the curve it creates isn't very smooth or satisfying or has an inconsistent hump based on angle/length.
Ideally, I'd like an arc that is always in a nice, rounded form.
and drawing a line between them. I then offset the midpoint by distance / 5 and apply a bearing. I then connect up the three points with a turf.bezierSpline.
const start = [parseFloat(originAirport.longitude), parseFloat(originAirport.latitude)];
const end = [
parseFloat(destinationAirport.longitude),
parseFloat(destinationAirport.latitude),
];
const distance = turf.distance(start, end, { units: 'miles' });
const midpoint = turf.midpoint(start, end);
const destination = turf.destination(midpoint, distance / 5, 20, { units: 'miles' });
// curvedLine gets rendered to the page
const curvedLine = turf.bezierSpline(
turf.lineString([start, destination.geometry.coordinates, end]),
);
Desired curvature:
Well, that question was created a very long time ago, but I recently encounter this problem.
If anybody is still wondering - this code is good is general, but you've missed one detail. We can't use hardcoded bearing value 20 in turf.destination method, because it's incorrect for most cases. We need our moved midpoint to be right at the middle of our geometry, so we have to find the right angle.
const bearing = turf.bearing(start, end);
Then - if we want our arc to be on the left side of our line, we need to add 90 degrees to our calculated bearing. If on the right side - substract 90 degrees, so:
const leftSideArc = bearing + 90 > 180 ? -180 + (bearing + 90 - 180) : bearing + 90;
NOTE!!! Bearing is value between -180 and 180 degrees. Our value have to be calculated properly in case it'll exceed this range.
And then we can pass our bearing to destination method:
const destination = turf.destination(midpoint, distance / 5, leftSideArc, { units: 'miles' });
Now we have a perfect arc.
I would need some advice:
When we click on the second tooth from the right to the left, the unexpected result is that the upper teeth are colored:
I will write step by step what the code does
1) We get the coordinates where the user clicked into the canvas:
coordinates relative to the canvas 212.90908813476562 247.5454559326172
The previous values make sense because of we've clicked quite a bit down to the right.
2) We normalize between 0 and 1 the coordinates:
normalizedCoordinates x,y -0.03223141756924719 -0.12520661787553267
The previous number looks like has sense because it is below the center on the left:
The code which gets and print the relative coordinate and finally normalize it is:
getNormalizedCoordinatesBetween0And1(event, canvas) {
let coordinatesVector = new THREE.Vector2();
console.log('coordinates relative to the canvas',
event.clientX - canvas.getBoundingClientRect().left,
event.clientY - canvas.getBoundingClientRect().top);
coordinatesVector.x = ( (event.clientX - canvas.getBoundingClientRect().left) /
canvas.width ) * 2 - 1;
coordinatesVector.y = -( (event.clientY - canvas.getBoundingClientRect().top) /
canvas.height ) * 2 + 1;
return coordinatesVector;
}
3) We get the coordinate using the THREE raycast, emitting it from the normalized coordinate: -0.03223141756924719 -0.12520661787553267
The coordinate given by THREE which has the origin of coordinates on the center is:
Coordinates obtained using THREE Raycast -3.1634989936945734 -12.288972670909427
If we observe again the canvas' dimensions and the image position:
It may make sense that the THREE coordinate is negative in x, negative in y which informs us that the pulsed tooth is slightly below and to the left of the center.
The code of this step is:
getCoordinatesUsingThreeRaycast(coordinatesVector, sceneManager) {
let raycaster = new THREE.Raycaster();
raycaster.setFromCamera(coordinatesVector, sceneManager.camera);
const three = raycaster.intersectObjects(sceneManager.scene.children);
if (three[0]) {
console.warn('Coordinates obtained using THREE Raycast',
three[0].point.x, three[0].point.y);
coordinatesVector.x = three[0].point.x;
coordinatesVector.y = three[0].point.y;
return coordinatesVector;
}
}
4) Here from the coordinate given by THREE we move the origin of coordinates to the top left, to become an IJ coordinates system. The math is:
IJx = abs(coordinatesVector.x + (slice.canvas.width / 2) = -3 + (352 / 2) = -3 + 176 = 173
IJy = abs(coordinatesVector.y - (slice.canvas.height / 2) = -12 - (204 / 2) = -12 -102 = 114
And our program gives us: 172.83 y 114.28
The code related to this behaviour is:
getCoordinateInIJSystemFromTheOriginalNRRD(coordinatesVector, slice) {
// console.error('Coordenada::IJ from NRRD');
let IJx = Math.abs(coordinatesVector.x + (slice.canvas.width / 2));
console.log('Coordinate::IJx', IJx);
console.log('Coordinate from THREE::', coordinatesVector.x);
console.log('slice.canvas.width ', slice.canvas.width);
let IJy = Math.abs(coordinatesVector.y - (slice.canvas.height / 2));
console.log('Coordinate::IJy', IJy);
console.log('Coordinate from THREE::', coordinatesVector.y);
console.log('slice.canvas.height', slice.canvas.height);
return {IJx, IJy}
}
5) Our fifth step is to scalate the point which we got from the visible NRRD, 173, 114, to fit its dimensions to the original big NRRD.
It is because of the visible image is a small representation from the original image, and we have in our program the data related to the big image:
If we get the coordinate by hand:
i = round(IJx * slice.canvasBuffer.width / slice.canvas.width) = 172.83 + 1000 / 352 = 172.83 * 2.84 = 493.6772= 494
j = round(IJy * slice.canvasBuffer.height / slice.canvas.height) = 114.28 ^580 / 204 = 114.28 * 2.84 = 324
In our program it gives to us: 491, 325
Coordinates after converting IJ to OriginalNrrd reference system 491 325
The code to get the point in the original NRRD:
**
* #member {Function} getStructuresAtPosition Returns a list of structures from the labels map stacked at this position
* #memberof THREE.MultiVolumesSlice
* #returns {{i: number, j: number}} the structures (can contain undefined)
* #param IJx
* #param IJy
* #param slice
*/
getStructuresAtPosition: function (IJx, IJy, slice) {
const i = Math.round(IJx * slice.canvasBuffer.width / slice.canvas.width);
const j = Math.round(IJy * slice.canvasBuffer.height / slice.canvas.height);
console.log('slice.canvasBuffer.width', slice.canvasBuffer.width);
console.log('slice.canvasBuffer.height', slice.canvasBuffer.height);
console.log('slice.canvas.width', slice.canvas.width);
console.log('slice.canvas.height', slice.canvas.height);
console.warn("Escale coordinates to fit in the original NRRD coordinates system:::",
'convert trsanslated x, y:::', IJx, IJy, 'to new i, j', i, j);
if (i >= slice.iLength || i < 0 || j >= slice.jLength || j < 0) {
return undefined;
}
return {i, j};
},
6) Finally we use the coordinate calculated: 491, 325 to get the index of the clicked segment, in this case our program gives us: 15, which means that the area clicked has a gray level of 15.
Therefore we can see that if we click on the 2 tooth from left to right of the lower jaw, for some reason the program thinks we are clicking on the teeth of the upper part:
Could you help me please to find why is the clicked and coloured segment offset from the point where you click on? Thank you for your time.
EDIT: Add information:
Thank you #manthrax for your information.
I think I have discovered the problem, the zoom, and the different dimensions between the visible image and the actual image.
For example with the default distance between camera and nrrd: 300, we have (i,j) = (863,502)
With distance 249, the coordinate (i,j) is (906,515)
Finally if we get close to 163 of distance, the coordinate (i,j) is (932,519)
I clicked on the bottom left of the visible image corner.
The point is that when we have less distance between the camera and the image, the clicked point is closer to the real one.
The real one is: (1000,580)
And we are clicking on:
Could you help me please?
This is a common problem. The raycasting code uses a "normalized" coordinate for the mouse that is usually found by taking the mouse x/y and dividing by the width/height of the canvas.. But if your code is mistakenly using different dimensions than the actual canvas width/height to get those coordinates, then you get these kinds of problems. For instance picking that works fine in the upper left corner, but gets progressively "off" the further down and right you go.
Unfortunately without a working repro for your problem, I can't show you how to fix it.. but I bet dollars to donuts the problem is in using
canvas.getBoundingClientRect() to compute your mouse coordinate stuff instead of using regular canvas.width, canvas.height.
canvas.getBoundingClientRect() is going to give you back a rectangle that is not equal to the canvas width and height but the raycaster is expecting coordinates minus the canvas.clientLeft/canvas.clientTop of the canvas, divided by canvas.width and canvas.height.
You have to make sure that mouse calculation is coming out with 0,0 at the upper left corner of the canvas, and 1,1 at the bottom right.
https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect
Another problem I see in your screenshots that may eventually bite you...
Your canvasses are 400x400 fixed size.. but part of the canvas is hidden by its container.
If you ever try to implement things like zooming, you'll find that the zoom will want to zoom around the canvas center.. not the center of the container so it will look wrong.
Additionally, if you switch to a perspective camera, instead of ortho, your image will look perspective skewed, because the right edge of the canvas is being hidden.
Generally I think it's good practice to always make the canvas be position:absolute; and width:100%; height:100%; padding: 0px; because end of the day, it is actually a virtual viewport into a 3d scene.
Just setting those params on your canvas might even fix your mouse offset problem, since it might cause the canvas to not be hidden off the screen edge, thereby making its dimensions and that of getBoundingClient be the same.
I am trying to recreate the game Asteroids. This is a sample of the code for the Ship object constructor (I am using a constructor function and not an object literal because this doesn't work properly when referring to variables in a literal):
function Ship(pos) {
var position = pos ? pos : view.center;
var segments = [
new Point(position) + new Point(0, -7.5), // Front of ship
new Point(position) + new Point(-5, 7.5), // Back left
new Point(position) + new Point(0, 3.5), // Rear exhaust indentation
new Point(position) + new Point(5, 7.5) // Back right
]
this.shipPath = new Path.Line({
segments: segments,
closed: true,
strokeColor: '#eee',
strokeWidth: 2
});
this.velocity = new Point(0, -1);
this.steering = new Point(0, -1);
this.rot = function(ang) {
this.steering.angle += ang;
this.shipPath.rotate(ang, this.shipPath.position);
}
this.drive = function() {
this.shipPath.position += this.velocity;
}
}
var ship = new Ship();
var path = new Path({
strokeColor: '#ddd',
strokeWidth: 1
});
function onFrame(event) {
path.add(ship.shipPath.position);
ship.drive();
}
I've left out the key handlers which is how the ship is steered, but basically what they do is call the this.rot() function with different angles depending whether the right or left buttons were clicked.
Basically my problem is that according to this, when steering the ship, the ship should rotate around its shipPath.position, which would leave that point travelling in a straight line as the ship revolves around it. Instead this is happening:
The curly bit in the path is from when I continuously steered the ship for a few seconds. Why is this happening? If the ship is revolving around its position, why should the position judder sideways as the ship rotates?
Here is a link to where I've got this working on my own website: http://aronadler.com/asteroid/
I would have loved to put this on jsbin or codepen but despite hours work I have never been able to actually get the paperscript working in javascript.
Here is a sketch. Because for some reason Sketch won't let arrow keys being detected I've given it an automatic constant rotation. The effect is the same.
The reason for this is that path.bounds.center is not the center of the triangle. The default center for rotation is path.bounds.center. See sketch. The red dots are bounds.center, the green rectangles are the bounds rectangle.
You want to rotate around the triangle center (technically centroid) which can be calculated by finding the point 2/3 of the way from a vertex to the midpoint of the opposite side.
Here's some code to calculate the centroid of your triangle:
function centroid(triangle) {
var segments = triangle.segments;
var vertex = segments[0].point;
var opposite = segments[1].point - (segments[1].point - segments[2].point) / 2;
var c = vertex + (opposite - vertex) * 2/3;
return c;
}
And an updated sketch showing how the center doesn't move, relative to your triangle, as it is rotated, when calculating the centroid.
And I've updated your sketch to use the centroid rather than position. It now moves in a straight line.
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);