I'm using D3 to draw some pieces of information on canvas. And I got my aim in version 3 but failed in version 4 (Of course, I had changed the updated functions in version 4 such as d3.geo.mercator() to d3.geoMercator()). I debugged some relative functions and found the projection function was different.
In Version 3:
function projection(point) {
point = projectRotate(point[0] * d3_radians, point[1] * d3_radians);
return [ point[0] * k + δx, δy - point[1] * k ];
}
In Version 4:
function projection(point) {
point = projectRotate(point[0] * radians, point[1] * radians);
return [point[0] * k + dx, dy - point[1] * k];
}
My code Snippets is following:
var d3Projection = d3.geoMercator().scale(1).translate([0, 0]);
// var d3Projection = d3.geo.mercator().scale(1).translate([0, 0]);
var d3Path = d3.geoPath().projection(d3Projection);
// var d3Path = d3.geo.path().projection(d3Projection);
var pixelBounds = d3Path.bounds(features);
var pixelBoundsWidth = pixelBounds[1][0] - pixelBounds[0][0];
var pixelBoundsHeight = pixelBounds[1][1] - pixelBounds[0][1];
I debugged there and found the value of 'δx' was different from 'dx', which is the same condition on 'δy(dy)'
Debugging in Version 4
Debugging in Version 3
Could you someone explain the differences in this condition, please?
And what's the meaning of dx(δx), dy(δy) ?
D3 V3 correct demo.
D3 V4 The center coordinate is wrong. wrong demo.
_______________________________________________________________________________
I found the different code which result into my wrong demo
This code was changed from v3.5.17, if I change this line code back to v3.5.16, everything will be OK. I will get the reason.
The projection function in vanilla D3v3 and v4 is identical, your problem is that you're using some modified D3v3 version. If you try running this snippet in your D3v3 environment (assiming the topojson is loaded in var topo):
var features = topojson.feature(topo, topo.objects.counties);
var d3Projection = d3.geo.mercator().scale(1).translate([0, 0]);
var d3Path = d3.geo.path().projection(d3Projection);
console.log(JSON.stringify(d3Path.bounds(features)));
you'll get
[[-3.141592653589793,-3.141592653589793],[3.141592653589793,3.141592653589793]]
If however you load current https://d3js.org/d3.v3.min.js, the same exact snippet will produce
[[2.110384445824674,-0.5558860193147427],[2.1337212032517625,-0.5298056661805025]]
which is the same as V4. So you either need to fix your code to work with current D3 (v3 and v4), or modify your D3v4 in the same way as D3v3.
Related
I have converted a line chart into a cumulative line chart and its y values are not displayed correctly. The range of the y axis should be 80.00 - 140.00 but instead I get -0.08 - 0.20. Has anyone managed to tweak their normalization code below to make it work with all kinds of ranges?
line.values = line.values.map(function(point, pointIndex) {
point.display = {
'y': (lines.y()(point, pointIndex) - v) / (1 + v)
};
return point;
})
Any help will be greatly appreciated.
I know that this question is somewhat old, but I am convinced that the normalization code for the cumulative line chart is not conceptually correct. Furthermore, the NVD3 cumulative line chart implementation is actually an index chart implementation (see Mike Bostock's example). A cumulative line chart would be more like this, I think. The cumulative chart can be easily achieved using the NVD3 line chart and some quick modifications to the underlying data.
If we take Bostock to be correct, and we really do wish to achieve an indexed line chart, then the indexify function in NVD3 should be changed to:
/* Normalize the data according to an index point. */
function indexify(idx, data) {
if (!indexifyYGetter) indexifyYGetter = lines.y();
return data.map(function(line, i) {
if (!line.values) {
return line;
}
var indexValue = line.values[idx];
if (indexValue == null) {
return line;
}
var v = indexifyYGetter(indexValue, idx);
// TODO: implement check below, and disable series if series
// causes a divide by 0 issue
if ((Math.abs(v) < 1e-6) && !noErrorCheck) {
// P.S. You may have to set a higher threshold (~1e-6?).
// I don't know I didn't run any tests...
line.tempDisabled = true;
return line;
}
line.tempDisabled = false;
line.values = line.values.map(function(point, pointIndex) {
point.display = {
'y': (indexifyYGetter(point, pointIndex) - v) / v
};
return point;
});
return line;
})
}
I asked a related question to the authors of NVD3 and plan to submit a pull request. Note that percentage change charts are really only meaningful when all of the underlying data is positive. When you start throwing negative values into the mix, percentage change loses all of its meaning.
What I found works is to insert another point with a y value of 0 at the beginning of the sequence of points.
Given a list of data points in the form [ [x1,y1], [x2,y2], ... [xn,yn]] ],
something like values.upshift([0,0]) works for me (the x value is arbitrary, but i just use 0 or values[0][0]) to insert to the front of the list.
(I'm getting the same thing with that chart. I'm still looking into it, but I hope this helped.)
not sure if I am going about this the right way but here goes...
So i have the this example see fiddle here
using lineplusbarchart and i am building on it from this question i posted here:
SO question
I have edited the lineplusbarchart to show the labels on the xaxis:
chart.xAxis.tickFormat(function(d) {
var dx = testdata[0].values[d] && testdata[0].values[d].x || 0;
return dx;
})
.showMaxMin(false);
but i am still having a couple of issues to get what i want...
1 -> how can i make the y1 and y2 axis be alligned? (ideally it would be good if there was only one axis)
2 -> how do i remove the y2 axis? (soution here but this does not work as I then want the 2 axis aligned)
3 -> how do i make the thickness of the barchart part for label1 and label5 to be the same thickness as the others(lable2,3 and 4)?
hope this helps:
you can use chart.lines.forceY() to set a range. To make it
work with dynamic values I'd suggest to find the overall max value of the attached data
and use it for the bar and the lines. Eg:
var maxValue = d3.max(d3.entries(testdata), function(d) {
return d3.max(d3.entries(d.value.values), function(e) {
return e.value.y;
});
}),
minValue = 0;
chart.bars.forceY([minValue, maxValue]);
chart.lines.forceY([minValue, maxValue]);
Your posted solution is exactly what I would do too.
Remove padData()
Allright, I know what machine precision is, but this, I can't understand...
Code:
console.log("meanX",meanX);
meanX2 = meanX * meanX; //squared
console.log("meanX2",meanX2);
Console output:
meanX 300.3
meanX2 28493.4400000000002
In case you are wondering, the correct value for meanX2 would be 90180.09
And this is only one of the many examples visible in the screenshot..
.toFixed(6) seems to fix this... But I have no idea why it doesn't work without it.
Edit
Ok, I don't want to post the whole program code here because in first place I'm not the only author, and second, I also wouldn't like this to be copied without our permission. But I'll gladly explain how I get this error and will post the whole method/function code here.
This code belongs, as you may have guessed from the window title, to a lane detection algorithm. We use Three.js/webgl to run some pre processing shaders on each frame of a video and then we analyze the resulting image. The method/function you see on the screenshot is a perpendicular line fitting algorithm and is part of the whole thing.
I can see the algorithm running nicely because I have the lane being drawn on top of the video, and It is well placed. Until suddenly the lane turns into an horizontal bar. This unexpected behavior happens exactly because of the phenomenon I described here, since it's from that moment that I start to see wrong math in the console.
Also, because the video and algorithm run at slightly different fps everytime, the problem doesn't always happen in the same moment of the video, and sometimes It doesn't happen at all.
Here is the code (it has some alterations because I was trying to isolate the issue):
this.perpendicularLineFit = function (points, slopeSign) {
var count = points.length;
var sumX = 0,
sumY = 0;
var sumX2 = 0,
sumY2 = 0,
sumXY = 0;
var meanX, meanY;
var i, lowp = {}, highp = {};
var B;
var slope;
var originY;
for (i = 0; i < count; i++) {
sumX += points[i].x;
sumY += points[i].y;
sumX2 += points[i].x * points[i].x;
sumY2 += points[i].y * points[i].y;
sumXY += points[i].y * points[i].x;
}
meanX = sumX / count;
meanY = sumY / count;
//If you uncoment this, problem reappears:
//var numeratorLeft = meanY * meanY;
console.log("meanX",meanX);
var meanX2 = meanX*meanX;
console.log("meanX2",meanX2);
var numerator = (sumY2 - count * (meanY * meanY)) - (sumX2 - count * meanX2);
var denominator = (count * meanX * meanY - sumXY);
B = 0.5 * (numerator / denominator);
slope = -B + slopeSign * Math.sqrt(B * B + 1);
originY = meanY - slope * meanX;
slope = isNaN(slope) ? slopeSign : slope;
originY = isNaN(originY) ? originY : originY;
lowp.y = this.lowY;
lowp.x = (this.lowY - originY) / slope;
highp.y = this.highY;
highp.x = (this.highY - originY) / slope;
return {
low: lowp,
high: highp
};
};
Now, I was trying to understand what was causing this, and the most bizarre thing is that it seems that when I place a statement of this form
var x = ... meanY * meanY ...;
before the meanX2 attribution, the issue happens. Otherwise it doesn't.
Also, I tried to catch this anomaly in the debugger but just when I enter the debugging tab, the problem disapears. And the values turn correct again.
I certainly don't believe in black magic, and I know that you are probably skeptic to this.
I would be too. But here is a link to a video showing it happening:
The video
Edit2:
I managed to reproduce this issue in another computer.. Both having ubuntu and using firefox (versions 20 and 21).
Edit3:
I'm sorry it took so much time! Here is a zip containing the issue. Just run it in any webserver. The code mentioned is in LaneDetection.js. Search for "HERE" in the file to find it.
https://docs.google.com/file/d/0B7y9wWiGlcYnYlo1S2pBelR1cHM/edit?usp=sharing
The problem might not happen in the first attempts. If that's the case refresh the page and try again. When the lines get horizontal you know it's there. As I said, I saw this problem happening in firefox versions 20 and 21 on ubuntu. In chrome it never happened.
By the way, I noticed that changing javascript.options.typeinference flag in firefox seems to stop the problem... I don't know exactly what that flag does, but maybe this optimization is not correctly implemented in firefox?
I can't say for sure that I actually have an answer but I think that I have confirmed that basilikum was correct to suggest a memory problem. Here's what I did: I took the first ten entries from your screenshot and calculated the correct answer. I then converted the correct answer and the wrong answer into the hexidecimal representation of the double-precision float. What I ended up with was the following:
292.416^2 = 85507.506 = 40F4E0381C71C71E
changed to 27583.373 = 40DAEFEB1C71C722
293.166^2 = 85946.694 = 40F4FBAB1C71C72A
changed to 27583.373 = 40DAEFEB1C71C722
295.818^2 = 87508.396 = 40F55D4658DC0876
changed to 28041.024 = 40DB62419637021F
294.500^2 = 86730.250 = 40F52CA400000000
changed to 27583.373 = 40DAEFEB1C71C722
297.000^2 = 88290.000 = 40F58E2000000000
changed to 28041.024 = 40DB62419637021F
221.750^2 = 49173.062 = 40E802A200000000
changed to 24964.000 = 40D8610000000000
300.300^2 = 90180.090 = 40F6044170A3D70A
changed to 28493.440 = 40DBD35C28F5C290
220.200^2 = 48488.040 = 40E7AD0147AE147B
changed to 25408.360 = 40D8D0170A3D70A4
300.600^2 = 90360.360 = 40F60F85C28F5C29
changed to 28493.440 = 40DBD35C28F5C290
213.000^2 = 45369.000 = 40E6272000000000
changed to 28032.326 = 40DB6014E5E0A72E
There's no persistent pattern to the change but there are a couple instances that are very telling of a memory issue. In the first two entries you can see that bytes 1, 2 and 3 were unchanged. In the 9th entry there's something even more odd. It would appear that bytes 0 - 3 were shifted left by exactly 4 bits! Upon considering the statement that the problem doesn't arise until after some time has passed and in light of these two anomalies, I'm fairly confident that you're encountering some sort of memory issue. Could it be, dare I say, a stack overflow?
I am working on a project that requires end users to be able draw in the browser much like svg-edit and send SVG data to the server for processing.
I've started playing with the Raphael framework and it seems promising.
Currently I am trying to implement a pencil or freeline type tool. Basically I am just drawing a new path based on percentage of mouse movement in the drawing area. However, in the end this is going to create massive amount of paths to deal with.
Is it possible to shorten an SVG path
by converting mouse movement to use
Curve and Line paths instead of line
segments?
Below is draft code I whipped up to do the job ...
// Drawing area size const
var SVG_WIDTH = 620;
var SVG_HEIGHT = 420;
// Compute movement required for new line
var xMove = Math.round(SVG_WIDTH * .01);
var yMove = Math.round(SVG_HEIGHT * .01);
// Min must be 1
var X_MOVE = xMove ? xMove : 1;
var Y_MOVE = yMove ? yMove : 1;
// Coords
var start, end, coords = null;
var paperOffset = null;
var mouseDown = false;
// Get drawing area coords
function toDrawCoords(coords) {
return {
x: coords.clientX - paperOffset.left,
y: coords.clientY - paperOffset.top
};
}
$(document).ready(function() {
// Get area offset
paperOffset = $("#paper").offset();
paperOffset.left = Math.round(paperOffset.left);
paperOffset.top = Math.round(paperOffset.top);
// Init area
var paper = Raphael("paper", 620, 420);
// Create draw area
var drawArea = paper.rect(0, 0, 619, 419, 10)
drawArea.attr({fill: "#666"});
// EVENTS
drawArea.mousedown(function (event) {
mouseDown = true;
start = toDrawCoords(event);
$("#startCoords").text("Start coords: " + $.dump(start));
});
drawArea.mouseup(function (event) {
mouseDown = false;
end = toDrawCoords(event);
$("#endCoords").text("End coords: " + $.dump(end));
buildJSON(paper);
});
drawArea.mousemove(function (event) {
coords = toDrawCoords(event);
$("#paperCoords").text("Paper coords: " + $.dump(coords));
// if down and we've at least moved min percentage requirments
if (mouseDown) {
var xMovement = Math.abs(start.x - coords.x);
var yMovement = Math.abs(start.y - coords.y);
if (xMovement > X_MOVE || yMovement > Y_MOVE) {
paper.path("M{0} {1}L{2} {3}", start.x, start.y, coords.x, coords.y);
start = coords;
}
}
});
});
Have a look at the Douglas-Peucker algorithm to simplify your line.
I don't know of any javascript implementation (though googling directed me to forums for google maps developers) but here's a tcl implementation that is easy enough to understand: http://wiki.tcl.tk/27610
And here's a wikipedia article explaining the algorithm (along with pseudocode): http://en.wikipedia.org/wiki/Ramer%E2%80%93Douglas%E2%80%93Peucker_algorithm
Here is a drawing tool which works with the iPhone or the mouse
http://irunmywebsite.com/raphael/drawtool2.php
However also look at Daves "game utility" #
http://irunmywebsite.com/raphael/raphaelsource.php which generates path data as you draw.
I'm working on something similar. I found a way to incrementally add path commands by a little bypass of the Raphael API as outlined in my answer here. In the modern browsers I tested on, this performs reasonably well but the degree to which your lines appear smooth depends on how fast the mousemove handler can work.
You might try my method for drawing paths using line segments and then perform smoothing after the initial jagged path is drawn (or as you go somehow), by pruning the coordinates using Ramer–Douglas–Peucker as slebetman suggested, and converting the remaining Ls to SVG curve commands.
I have a simillar problem , I draw using the mouse down and the M command. I then save that path to a database on the server. The issue I am having is to do with resolution. I have a background image where the users draw lines and shapes over parts of the image, but if the image is displayed on one resolution and the paths are created in that resolution then reopened on a different perhaps lower resolution, my paths get shifted and are not sized correctly. I guess what I am asking is : is there a way to draw a path over an image and make sure no matter the size of the underlying image the path remains proprtionally correct.
I have a bit of a problem. I am trying to do the following using Javascript & the Google Maps API v2:
I can draw individual circles just fine using formulas found all over the Internet. The problem I am facing is that the circles must:
A. Be concentric, and
B. Must have different radius for each "quadrant", i.e., NE, NW, SE & SW
I've searched almost everywhere I can think of on the Internet, and have come up with no way on how to do this. Clearly someone has done this before, and thus why I'm asking in a forum of programmers. :)
Thanks!
UPDATE: I have drawn out, using the following code, what I think the coordinates for each of the points would be. for the drawing below:
This was obtained using the following JS:
http://gist.github.com/181290
NOTE: This javascript is from (slightly modified) the following site, which may hold more answers in terms of what the algorithm may end up being: http://www.movable-type.co.uk/scripts/latlong.html
UPDATE 2: I was able to get this in Google Maps:
Created using the following code:
var NEQ = [0, 90];
var SEQ = [90, 180];
var SWQ = [180, 270];
var NWQ = [270, 0];
// var centrePoint = new LatLon(25.0, -83.1);
// pointsForWindQuadrant(NEQ, centrePoint, 50);
function pointsForWindQuadrant(quadrantDegrees, centrePoint, radius){
var points = [];
// Points must be pushed into the array in order
points.push(new google.maps.LatLng(centrePoint.lat, centrePoint.lon));
for(i = quadrantDegrees[0]; i <= quadrantDegrees[1]; i++){
var point = centrePoint.destPoint(i, radius * 1.85);
points.push(new google.maps.LatLng(point.lat, point.lon)); // Radius should be in nautical miles from NHC
}
points.push(new google.maps.LatLng(centrePoint.lat, centrePoint.lon));
return points;
}
UPDATE 3: I should probably also point out that this is for a geographic coordinate system (as this whole thing is for tropical cyclone wind radii), not the Cartesian coordinate system. Thanks!
Basically, calculate the circle as the x,y = (cos(a), sin(a)), and then multiple this (both terms) by a radius that's the appropriate function of the angle. I don't know Javascript well, or Google maps, so I'll do this in Python, hopefully it's clear enough from this.
from pylab import *
def Rscale(a):
if a>3*pi/2: # lower right, and then work CW around the circle
return 1.
elif a>pi: # lower left
return .9
elif a>pi/2: # upper left
return .8
else: # upper right
return 1.
def step_circle(R):
return array([(R*Rscale(a))*array([cos(a), sin(a)]) for a in arange(0, 2*pi, .001)])
for R in (.5, .7, .9): # make three concentric circles
c = step_circle(R)
plot(c[:,0], c[:,1])
show()
Which gives
I couldn't really follow your sketch, so I just guessed at the numbers. Also, I made the two rightmost quadrants to be the same since that's what your plot looked like, but that is, of course, optional.
I figured it out. Here is the final code. Maybe it can be refactored a bit?
// Returns points for a wind field for a cyclone. Requires
// a LatLon centre point, and an array of wind radii, starting
// from the northeast quadrant (NEQ), i.e., [200, 200, 150, 175]
//
// Returns points to be used in a GPolyline object.
function pointsForWindQuadrant(centrePoint, radii){
if(radii.length != 4){ return false; }
var points = [];
var angles = [0, 90, 180, 270];
// For each angle 0, 90, 180, 270...
for(a = 0; a < angles.length; a++){
// For each individual angle within the range, create a point...
for(i = angles[a]; i <= angles[a] + 90; i++){
var point = centrePoint.destPoint(i, radii[a] * 1.85); // Radius should be in nautical miles from NHC
points.push(new google.maps.LatLng(point.lat, point.lon));
}
}
// Add the first point again, to be able to close the GPolyline
var point = centrePoint.destPoint(0, radii[0] * 1.85);
points.push(new google.maps.LatLng(point.lat, point.lon));
return points;
}
This results in the following: