Trying to show the percentages right on the donut charts in dimple.plot.pie
Here is some code that kind of works but places the labels right onto the slices.
Having trouble making the labels show up outside the pie.
rings = chart.addSeries("series", dimple.plot.pie);
rings.afterDraw = function(shape, data) {
var bbox, ctm;
ctm = shape.getCTM();
bbox = shape.getBBox();
return this.chart.svg.append("text")
.attr("x", ctm.e + bbox.x + bbox.width/2)
.attr("y", ctm.f + bbox.y + bbox.height/2)
.text(Math.round(1000*data.piePct)/10 + "%");;
};
Here's the best I can do..
I'd like to build this into the dimple library, but for the time being, here is the method I use in one of my own projects:
function getCentroid(data, plot) {
var centerX = plot.x + plot.width / 2,
centerY = plot.y + plot.height / 2,
angle = (data.startAngle + (data.endAngle - data.startAngle) / 2),
hyp = (data.innerRadius + (data.outerRadius - data.innerRadius) / 2),
opp = Math.sin(angle) * hyp,
adj = Math.cos(angle) * hyp;
return [centerX + opp, centerY - adj];
}
series.afterDraw = function (shape, data) {
var ctd = getCentroid(data, plotSize),
s = d3.select(shape),
degrees = ((data.startAngle + (data.endAngle - data.startAngle) / 2) * 180) / Math.PI;
if (degrees < 180) {
degrees -= 90;
} else {
degrees += 90;
}
if (Math.abs(data.startAngle - data.endAngle) > 0.1) {
chart._group.append("text")
.attr("transform", "rotate(" + degrees + ", " + ctd[0] + ", " + ctd[1] + 4 + ")")
.attr("dy", "0.35em")
.attr("x", ctd[0])
.attr("y", ctd[1])
.style("text-anchor", "middle")
.style("pointer-events", "none")
.text(format(data.pValue));
}
};
I took this direct from my own code so it relies on a few variables in scope but hopefully they are fairly self-explanatory.
2 years later and still no solution that works out-of-the-box?
The solution below is a working compilation of all I could find on the topic. Updates are welcome :-)
var pies = chart.addSeries("series", dimple.plot.pie);
pies.radius = 60;
pies.innerRadius = "50%";
pies.afterDraw = function (shape, data) {
var arc = d3.arc()
.outerRadius(radius)
.innerRadius(radius/2);
var ctm = shape.getCTM();
chart.svg.append("text")
// Position text in the centre of the shape
.attr("x", arc.centroid(data)[0])
.attr("y", arc.centroid(data)[1])
.attr("transform", function () {
return "translate("+ctm.e+","+ctm.f+")";
})
// Centre align and nicer display
.style("text-anchor", "middle")
.style("font-size", "10px")
.style("font-family", "sans-serif")
.style("opacity", 0.8)
// Prevent text cursor on hover and allow tooltips
.style("pointer-events", "none")
// Display text
.text((data.piePct*100).toFixed(2) + "%)");
}
Related
I am passing the array of co-ordinates to the polygon and want to find the area of the polygon through that co-ordinates. I have checked the documentation of the react-native-maps but there is no function provided.
Is there is any way to calculate the area.
Thanks in advance.
Library will not give you this functionality.
Try this
function calcArea(locations) {
if (!locations.length) {
return 0;
}
if (locations.length < 3) {
return 0;
}
let radius = 6371000;
const diameter = radius * 2;
const circumference = diameter * Math.PI;
const listY = [];
const listX = [];
const listArea = [];
// calculate segment x and y in degrees for each point
const latitudeRef = locations[0].latitude;
const longitudeRef = locations[0].longitude;
for (let i = 1; i < locations.length; i++) {
let latitude = locations[i].latitude;
let longitude = locations[i].longitude;
listY.push(this.calculateYSegment(latitudeRef, latitude, circumference));
listX.push(this.calculateXSegment(longitudeRef, longitude, latitude, circumference));
}
// calculate areas for each triangle segment
for (let i = 1; i < listX.length; i++) {
let x1 = listX[i - 1];
let y1 = listY[i - 1];
let x2 = listX[i];
let y2 = listY[i];
listArea.push(this.calculateAreaInSquareMeters(x1, x2, y1, y2));
}
// sum areas of all triangle segments
let areasSum = 0;
listArea.forEach(area => areasSum = areasSum + area)
// get abolute value of area, it can't be negative
let areaCalc = Math.abs(areasSum);// Math.sqrt(areasSum * areasSum);
return areaCalc;
}
function calculateAreaInSquareMeters(x1, x2, y1, y2) {
return (y1 * x2 - x1 * y2) / 2;
}
function calculateYSegment(latitudeRef, latitude, circumference) {
return (latitude - latitudeRef) * circumference / 360.0;
}
function calculateXSegment(longitudeRef, longitude, latitude, circumference) {
return (longitude - longitudeRef) * circumference * Math.cos((latitude * (Math.PI / 180))) / 360.0;
}
Reference
I am planning to build an antenna tracker. I need to get bearing and tilt from GPS point A with altitude and GPS point B with altitude.
This is the example points:
latA = 39.099912
lonA = -94.581213
altA = 273.543
latB = 38.627089
lonB = -90.200203
altB = 1380.245
I've already got the formula for horizontal bearing and it gives me 97.89138167122422
This is the code:
function toRadian(num) {
return num * (Math.PI / 180);
}
function toDegree(num) {
return num * (180 / Math.PI);
}
function getHorizontalBearing(fromLat, fromLon, toLat, toLon) {
fromLat = toRadian(fromLat);
fromLon = toRadian(fromLon);
toLat = toRadian(toLat);
toLon = toRadian(toLon);
let dLon = toLon - fromLon;
let x = Math.tan(toLat / 2 + Math.PI / 4);
let y = Math.tan(fromLat / 2 + Math.PI / 4);
let dPhi = Math.log(x / y);
if (Math.abs(dLon) > Math.PI) {
if (dLon > 0.0) {
dLon = -(2 * Math.PI - dLon);
} else {
dLon = (2 * Math.PI + dLon);
}
}
return (toDegree(Math.atan2(dLon, dPhi)) + 360) % 360;
}
let n = getHorizontalBearing(39.099912, -94.581213, 38.627089, -90.200203);
console.info(n);
But I don't know how to find the tilt angle. Anyone could help me?
I think I got the answer after searching around.
This is the complete code, if you think this is wrong, feel free to correct me.
function toRadian(num) {
return num * (Math.PI / 180);
}
function toDegree(num) {
return num * (180 / Math.PI);
}
// North is 0 degree, South is 180 degree
function getHorizontalBearing(fromLat, fromLon, toLat, toLon, currentBearing) {
fromLat = toRadian(fromLat);
fromLon = toRadian(fromLon);
toLat = toRadian(toLat);
toLon = toRadian(toLon);
let dLon = toLon - fromLon;
let x = Math.tan(toLat / 2 + Math.PI / 4);
let y = Math.tan(fromLat / 2 + Math.PI / 4);
let dPhi = Math.log(x / y);
if (Math.abs(dLon) > Math.PI) {
if (dLon > 0.0) {
dLon = -(2 * Math.PI - dLon);
} else {
dLon = (2 * Math.PI + dLon);
}
}
let targetBearing = (toDegree(Math.atan2(dLon, dPhi)) + 360) % 360;
return targetBearing - currentBearing;
}
// Horizon is 0 degree, Up is 90 degree
function getVerticalBearing(fromLat, fromLon, fromAlt, toLat, toLon, toAlt, currentElevation) {
fromLat = toRadian(fromLat);
fromLon = toRadian(fromLon);
toLat = toRadian(toLat);
toLon = toRadian(toLon);
let fromECEF = getECEF(fromLat, fromLon, fromAlt);
let toECEF = getECEF(toLat, toLon, toAlt);
let deltaECEF = getDeltaECEF(fromECEF, toECEF);
let d = (fromECEF[0] * deltaECEF[0] + fromECEF[1] * deltaECEF[1] + fromECEF[2] * deltaECEF[2]);
let a = ((fromECEF[0] * fromECEF[0]) + (fromECEF[1] * fromECEF[1]) + (fromECEF[2] * fromECEF[2]));
let b = ((deltaECEF[0] * deltaECEF[0]) + (deltaECEF[2] * deltaECEF[2]) + (deltaECEF[2] * deltaECEF[2]));
let elevation = toDegree(Math.acos(d / Math.sqrt(a * b)));
elevation = 90 - elevation;
return elevation - currentElevation;
}
function getDeltaECEF(from, to) {
let X = to[0] - from[0];
let Y = to[1] - from[1];
let Z = to[2] - from[2];
return [X, Y, Z];
}
function getECEF(lat, lon, alt) {
let radius = 6378137;
let flatteningDenom = 298.257223563;
let flattening = 0.003352811;
let polarRadius = 6356752.312106893;
let asqr = radius * radius;
let bsqr = polarRadius * polarRadius;
let e = Math.sqrt((asqr-bsqr)/asqr);
// let eprime = Math.sqrt((asqr-bsqr)/bsqr);
let N = getN(radius, e, lat);
let ratio = (bsqr / asqr);
let X = (N + alt) * Math.cos(lat) * Math.cos(lon);
let Y = (N + alt) * Math.cos(lat) * Math.sin(lon);
let Z = (ratio * N + alt) * Math.sin(lat);
return [X, Y, Z];
}
function getN(a, e, latitude) {
let sinlatitude = Math.sin(latitude);
let denom = Math.sqrt(1 - e * e * sinlatitude * sinlatitude);
return a / denom;
}
let n = getHorizontalBearing(39.099912, -94.581213, 39.099912, -94.588032, 0.00);
console.info("Horizontal bearing:\t", n);
let m = getVerticalBearing(39.099912, -94.581213, 273.543, 39.099912, -94.588032, 873.543, 0.0);
console.info("Vertical bearing:\t", m);
Don Cross's javascript code produces good results. It takes into consideration the curvature of the earth plus the fact that the earth is oblate.
Example:
var elDegrees = calculateElevationAngleCosineKitty(
{latitude: 35.346257, longitude: -97.863801, altitudeMetres: 10},
{latitude: 34.450545, longitude: -96.500167, altitudeMetres: 9873}
);
console.log("El: " + elDegrees);
/***********************************
Code by Don Cross at cosinekitty.com
http://cosinekitty.com/compass.html
************************************/
function calculateElevationAngleCosineKitty(source, target)
{
var oblate = true;
var a = {'lat':source.latitude, 'lon':source.longitude, 'elv':source.altitudeMetres};
var b = {'lat':target.latitude, 'lon':target.longitude, 'elv':target.altitudeMetres};
var ap = LocationToPoint(a, oblate);
var bp = LocationToPoint(b, oblate);
var bma = NormalizeVectorDiff(bp, ap);
var elevation = 90.0 - (180.0 / Math.PI)*Math.acos(bma.x*ap.nx + bma.y*ap.ny + bma.z*ap.nz);
return elevation;
}
function NormalizeVectorDiff(b, a)
{
// Calculate norm(b-a), where norm divides a vector by its length to produce a unit vector.
var dx = b.x - a.x;
var dy = b.y - a.y;
var dz = b.z - a.z;
var dist2 = dx*dx + dy*dy + dz*dz;
if (dist2 == 0) {
return null;
}
var dist = Math.sqrt(dist2);
return { 'x':(dx/dist), 'y':(dy/dist), 'z':(dz/dist), 'radius':1.0 };
}
function EarthRadiusInMeters (latitudeRadians) // latitude is geodetic, i.e. that reported by GPS
{
// http://en.wikipedia.org/wiki/Earth_radius
var a = 6378137.0; // equatorial radius in meters
var b = 6356752.3; // polar radius in meters
var cos = Math.cos (latitudeRadians);
var sin = Math.sin (latitudeRadians);
var t1 = a * a * cos;
var t2 = b * b * sin;
var t3 = a * cos;
var t4 = b * sin;
return Math.sqrt ((t1*t1 + t2*t2) / (t3*t3 + t4*t4));
}
function GeocentricLatitude(lat)
{
// Convert geodetic latitude 'lat' to a geocentric latitude 'clat'.
// Geodetic latitude is the latitude as given by GPS.
// Geocentric latitude is the angle measured from center of Earth between a point and the equator.
// https://en.wikipedia.org/wiki/Latitude#Geocentric_latitude
var e2 = 0.00669437999014;
var clat = Math.atan((1.0 - e2) * Math.tan(lat));
return clat;
}
function LocationToPoint(c, oblate)
{
// Convert (lat, lon, elv) to (x, y, z).
var lat = c.lat * Math.PI / 180.0;
var lon = c.lon * Math.PI / 180.0;
var radius = oblate ? EarthRadiusInMeters(lat) : 6371009;
var clat = oblate ? GeocentricLatitude(lat) : lat;
var cosLon = Math.cos(lon);
var sinLon = Math.sin(lon);
var cosLat = Math.cos(clat);
var sinLat = Math.sin(clat);
var x = radius * cosLon * cosLat;
var y = radius * sinLon * cosLat;
var z = radius * sinLat;
// We used geocentric latitude to calculate (x,y,z) on the Earth's ellipsoid.
// Now we use geodetic latitude to calculate normal vector from the surface, to correct for elevation.
var cosGlat = Math.cos(lat);
var sinGlat = Math.sin(lat);
var nx = cosGlat * cosLon;
var ny = cosGlat * sinLon;
var nz = sinGlat;
x += c.elv * nx;
y += c.elv * ny;
z += c.elv * nz;
return {'x':x, 'y':y, 'z':z, 'radius':radius, 'nx':nx, 'ny':ny, 'nz':nz};
}
/***********************
END cosinekitty.com code
************************/
I'm using an answer from Raphael from this post (https://stackoverflow.com/a/10401734/3321095) to convert lat/long to xy coordinates plotted on a mercator map. Raphael's example uses an area in Hamburg, Germany. I tested it and it does work. I then changed it to find a point within the United States but the coordinates are always beyond the size of the image. Can someone help?
<script type="text/javascript">
var mapWidth = 749; //1500;
var mapHeight = 462; //1577;
var mapLonLeft = 125; //9.8;
var mapLonRight = 65 //10.2;
var mapLonDelta = mapLonRight - mapLonLeft;
var mapLatBottom = 25 //53.45;
var mapLatBottomDegree = mapLatBottom * Math.PI / 180;
function convertGeoToPixel(lat, lon)
{
var position = new Array(2);
var x = (lon - mapLonLeft) * (mapWidth / mapLonDelta);
var lat = lat * Math.PI / 180;
var worldMapWidth = ((mapWidth / mapLonDelta) * 360) / (2 * Math.PI);
var mapOffsetY = (worldMapWidth / 2 * Math.log((1 + Math.sin(mapLatBottomDegree)) / (1 - Math.sin(mapLatBottomDegree))));
var y = mapHeight - ((worldMapWidth / 2 * Math.log((1 + Math.sin(lat)) / (1 - Math.sin(lat)))) - mapOffsetY);
position[0] = x;
position[1] = y;
return position;
}
var coordinates = convertGeoToPixel(30.274333164300643, -97.74064064025879); //convertGeoToPixel(53.7, 9.95);
alert("x: " + coordinates[0] + " y: " + coordinates[1]);
</script>
Hope you figured this out in the last year. Your code helped me with a similar project. Your code is missing a minus sign and should look like this:
var mapLonLeft = -125; //9.8;
var mapLonRight = -65 //10.2;
Longitude is negative in the USA.
I have a plane/board, with a grid, that is about 1100 x 1100. I have panning, zooming, and rotating working except for the fact that the board moves back to the center of the screen after it has been panned. So, if I don't pan the board at all then everything works. After I pan the board it moves back to the center of the screen when I try to rotate it. I cannot figure out how to change the origin of the camera so that it rotates around the center of the board. It seems like it rotates around the center of the camera.
var radius = 1500, theta = 45 * 0.5, onMouseDownTheta = 45 * 0.5;
var fov = 45;
var mouse2D = new THREE.Vector3(0, 10000, 0.5);
cameraX = radius * Math.sin(THREE.Math.degToRad(theta));
cameraY = 1000;
cameraZ = radius * Math.cos(THREE.Math.degToRad(theta));
camera = new THREE.PerspectiveCamera(fov, window.innerWidth / window.innerHeight, 1, 10000);
camera.position.set(cameraX, cameraY, cameraZ);
scene = new THREE.Scene();
camera.lookAt(scene.position);
render();
ThreeBoard.prototype.render = function() {
mouse2D.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse2D.y = - (event.clientY / window.innerHeight) * 2 + 1;
// rotate
if (isMouseDown && isShiftPressed && !isCtrlPressed) {
theta = ((event.pageX - mouse2D.x) * 0.5) + onMouseDownTheta;
cameraX = radius * Math.sin(THREE.Math.degToRad(theta));
cameraZ = radius * Math.cos(THREE.Math.degToRad(theta));
camera.position.set(cameraX, cameraY, cameraZ);
camera.lookAt(scene.position);
}
// pan
if (isMouseDown && isShiftPressed && isCtrlPressed) {
theta = ((event.pageX - mouse2D.x) * 0.5) + onMouseDownTheta;
cameraX += 10 * mouse2D.x;
// cameraY += 10;
cameraZ -= 10 * mouse2D.y;
camera.position.set(cameraX, cameraY, cameraZ);
// camera.lookAt(scene.position);
}
renderer.render(scene, camera);
};
I am trying to populate a circumference with points located at equal intervals. Here is the code (it uses some Processing, but it is not crucial for understanding):
class Circle (x: Float, y: Float, subdivisions: Int, radius: Float) extends WorldObject(x, y) {
def subs = subdivisions
def r = radius
val d = r + r
def makePoints() : List[Glyph] = {
val step = PConstants.TWO_PI / subdivisions
val points = List.make(subdivisions, new Glyph())
for(i <- 0 to subdivisions - 1) {
points(i) position (PApplet.cos(step * i) * r + xPos, PApplet.sin(step * i) * r + yPos)
}
points
}
val points: List[Glyph] = makePoints()
override def draw() {
applet fill 0
applet stroke 255
applet ellipse(x, y, d, d)
applet fill 255
points map(_.update())
}
}
class Glyph(x: Float, y: Float) extends WorldObject(x, y){
def this() = this(0, 0)
override def draw() {
applet ellipse(xPos, yPos, 10, 10)
}
}
object WorldObject {
}
abstract class WorldObject(var xPos: Float, var yPos: Float) {
def this() = this(0, 0)
def x = xPos
def y = yPos
def update() {
draw()
}
def draw()
def position(x: Float, y: Float) {
xPos = x
yPos = y
}
def move(dx: Float, dy: Float) {
xPos += dx
yPos += dy
}
}
The strange result that I get is that all the points are located at a single place. I have experimented with println checks... the checks in the makePoints() method shows everything ok, but checks in the Circle.draw() or even right after the makePoints() show the result as I see it on the screen - all points are located in a single place, right where the last of them is generated, namely x=430.9017 y=204.89435 for a circle positioned at x=400 y=300 and subdivided to 5 points. So somehow they all get collected into the place where the last of them sits.
Why is there such a behavior? What am I doing wrong?
UPD: We have been able to locate the reason, see below:
Answering the question, user unknown changed the code to use the fill method instead of make. The main relevant difference between them is that make pre-computes it's arguments and fill does not. Thus make fills the list with totally identical items. However, fill repeats the computation on each addition. Here are the source codes of these methods from Scala sources:
/** Create a list containing several copies of an element.
*
* #param n the length of the resulting list
* #param elem the element composing the resulting list
* #return a list composed of n elements all equal to elem
*/
#deprecated("use `fill' instead", "2.8.0")
def make[A](n: Int, elem: A): List[A] = {
val b = new ListBuffer[A]
var i = 0
while (i < n) {
b += elem
i += 1
}
b.toList
}
And the fill method:
/** Produces a $coll containing the results of some element computation a number of times.
* #param n the number of elements contained in the $coll.
* #param elem the element computation
* #return A $coll that contains the results of `n` evaluations of `elem`.
*/
def fill[A](n: Int)(elem: => A): CC[A] = {
val b = newBuilder[A]
b.sizeHint(n)
var i = 0
while (i < n) {
b += elem
i += 1
}
b.result
}
I changed a lot of variables forth and back (def x = ... => def x () = , x/ this.x and x/xPos and so on) added println statements and removed (P)applet-stuff, which made the compiler complain.
Providing a compilable, runnable, standalone demo would be beneficial. Here it is:
class Circle (x: Float, y: Float, subdivisions: Int, radius: Float)
extends WorldObject (x, y) {
def subs = subdivisions
def r = radius
val d = r + r
def makePoints() : List[Glyph] = {
// val step = PConstants.TWO_PI / subdivisions
val step = 6.283F / subdivisions
val points = List.fill (subdivisions) (new Glyph ())
for (i <- 0 to subdivisions - 1) {
// points (i) position (PApplet.cos (step * i) * r + xPos,
// PApplet.sin (step * i) * r + yPos)
val xx = (math.cos (step * i) * r).toFloat + xPos
val yy = (math.sin (step * i) * r).toFloat + yPos
println (xx + ": " + yy)
points (i) position (xx, yy)
}
points
}
val points: List [Glyph] = makePoints ()
override def draw () {
/*
applet fill 0
applet stroke 255
applet ellipse(x, y, d, d)
applet fill 255
*/
// println ("Circle:draw () upd-> " + super.x () + "\t" + y () + "\t" + d);
points map (_.update ())
println ("Circle:draw () <-upd " + x + "\t" + y + "\t" + d);
}
}
class Glyph (x: Float, y: Float) extends WorldObject (x, y) {
def this () = this (0, 0)
override def draw() {
// applet ellipse (xPos, yPos, 10, 10)
println ("Glyph:draw (): " + xPos + "\t" + yPos + "\t" + 10);
}
}
object Circle {
def main (as: Array [String]) : Unit = {
val c = new Circle (400, 300, 5, 100)
c.draw ()
}
}
object WorldObject {
}
abstract class WorldObject (var xPos: Float, var yPos: Float) {
def this () = this (0, 0)
def x = xPos
def y = yPos
def update () {
draw ()
}
def draw ()
def position (x: Float, y: Float) {
xPos = x
yPos = y
// println (x + " ?= " + xPos + " ?= " + (this.x ()))
}
def move (dx: Float, dy: Float) {
xPos += dx
yPos += dy
}
}
My result is:
500.0: 300.0
430.9052: 395.1045
319.10266: 358.78452
319.09177: 241.23045
430.8876: 204.88977
Glyph:draw (): 500.0 300.0 10
Glyph:draw (): 430.9052 395.1045 10
Glyph:draw (): 319.10266 358.78452 10
Glyph:draw (): 319.09177 241.23045 10
Glyph:draw (): 430.8876 204.88977 10
Circle:draw () <-upd 400.0 300.0 200.0
Can you spot the difference?
You should create a copy of your code, and stepwise remove code, which isn't necessary to reproduce the error, checking, whether the error is still present. Then you should reach a much smaller problem, or find the error yourself.