Updating domain values of multiline graph after line drag event - javascript

I am developing a simple multi-line graph with dual time axes and zooming/dragging features. Please take a look at my JSFiddle.
I am trying to implement the drag feature on the line graph, whereupon dragging a particular line will result in its respective axis also getting updated. Every time the drag is applied to the graph, I am trying to update the domain values of its respective axis, and redraw both the axis and the line graph.
Here is the logic that I implemented to update the domain values (referenced from a D3 Example):
var mousePoint = d3.mouse(this);
x1 = x1Scale.domain()[0],
x2 = x1Scale.domain()[1],
console.log("x1 = "+x1+", x2 = " +x2);
xextent = x1 - x2;
x1 += mousePoint[0];
x2 += mousePoint[0];
var newDomain = [x1, x2];
x1Scale.domain(newDomain);
When I implement this logic, I get a NaN error. Is this the correct way to update the domain values after the drag? If so, how do I solve the NaN error and achieve the desired functionality?

It is important to convert numbers into date objects, there was a typo in your code (data1[0].time instead of data1[0].date). Also you shouldn't multiply by 1000, since your data was already in milliseconds.
In your drag code, it is also important to convert your date objects back to numbers, in order that += will work on them. Of course you also need to convert them back to date when setting the domain again.
function draggedData1(d) {
console.log("dragging of data1 going on!!")
var mousePoint = d3.mouse(this);
var x1 = x1Scale.domain()[0].valueOf(); //date to number
var x2 = x1Scale.domain()[1].valueOf();
var xextent = x1 - x2;
x1 += mousePoint[0];
x2 += mousePoint[0];
var newDomain = [new Date(x1), new Date(x2)]; //number back to date
x1Scale.domain(newDomain);
redraw();
zoomBottom.x(x1Scale);
zoom.x(x2Scale);
}
I've created a fiddle with the full code and fixes here:
http://jsfiddle.net/pb3cod6q/2/

Related

Output/Generate a set of points from individual equations - Python, JS, Matlab

I began with a set of coordinates, which I then approximated a function to represent them (Fourier series). The function produced is a sum of sin and cos waves:
0.3sin(2x) + 1.7(sin5x) + 1.8(sin43x)...
I would like to take this new function that I generated and produce a new set of coordinates. How can I generate points for every [INTEGER X Value] say from 0-400?
Note: I have 2 complex (2D) functions.
GOAL: Take a function --> Generate Points from this function for every whole integer.
This uses a function handle and (:) to force a column vector ((:).' forces a row vector).
The code simply uses the given equation (summing sines and cosines) to calculate a corresponding y coordinate for each given x coordinate.
% MATLAB R2018b
X = 0:400; % x = 0, 1, 2, ..., 400
fh = #(x) 0.3*sin(2*x) + 1.7*sin(5*x) + 1.8*sin(43*x);
Y = fh(X);
P = [X(:) Y(:)];
Note that size(P) returns 401 x 2. You'll see Y takes on whatever size X is, which is a row vector. X can be declared as as column vector with X = (0:400).' using .' which performs a transpose.
Recommend taking a look at MATLAB's documentation, specifically the Getting Started and Language Fundamentals.
Relevant MATLAB functions: sin, cos.
Matlab Code
X = 0:400;
fh = #(x) 0.3*sin(2*x) + 1.7*sin(5*x) + 1.8*sin(43*x);
Y = fh(X);
P = [X, Y]

Is it possible to draw function like tangens wihout knowing its domain? [duplicate]

I am making a graphing program in C++ using the SFML library. So far I have been able to draw a function to the screen. I have run into two problems along the way.
The first is a line which seems to return to the origin of my the plane, starting from the end of my function.
You can see it in this image:
As you can see this "rogue" line seems to change colour as it nears the origin. My first question is what is this line and how may I eradicate it from my window?
The second problem which is slightly unrelated and more mathematical can be seen in this image:
As you can see the asymptotes which are points where the graph is undefined or non continuous are being drawn. This leads me to my second question: is there a way ( in code ) to identify an asymptote and not draw it to the window.
My code for anything drawn to the window is:
VertexArray axis(Lines, 4);
VertexArray curve(PrimitiveType::LinesStrip, 1000);
axis[0].position = Vector2f(100000, 0);
axis[1].position = Vector2f(-100000, 0);
axis[2].position = Vector2f(0, -100000);
axis[3].position = Vector2f(0, 100000);
float x;
for (x = -pi; x < pi; x += .0005f)
{
curve.append(Vertex(Vector2f(x, -tan(x)), Color::Green));
}
I would very much appreciate any input : )
Update:
Thanks to the input of numerous people this code seems to work fine in fixing the asymptote problem:
for (x = -30*pi; x < 30*pi; x += .0005f)
{
x0 = x1; y0 = y1;
x1 = x; y1 = -1/sin(x);
a = 0;
a = fabs(atan2(y1 - y0, x1 - x0));
if (a > .499f*pi)
{
curve.append(Vertex(Vector2f(x1, y1), Color::Transparent));
}
else
{
curve.append(Vertex(Vector2f(x1, y1), Color::Green));
}
}
Update 2:
The following code gets rid of the rogue line:
VertexArray curve(Lines, 1000);
float x,y;
for (x = -30 * pi; x < 30 * pi; x += .0005f)
{
y = -asin(x);
curve.append(Vertex(Vector2f(x, y)));
}
for (x = -30 * pi + .0005f; x < 30 * pi; x += .0005f)
{
y = -asin(x);
curve.append(Vertex(Vector2f(x, y)));
}
The first problem looks like a wrong polyline/curve handling. Don't know what API are you using for rendering but some like GDI need to start the pen position properly. For example if you draw like this:
Canvas->LineTo(x[0],y[0]);
Canvas->LineTo(x[1],y[1]);
Canvas->LineTo(x[2],y[2]);
Canvas->LineTo(x[3],y[3]);
...
Then you should do this instead:
Canvas->MoveTo(x[0],y[0]);
Canvas->LineTo(x[1],y[1]);
Canvas->LineTo(x[2],y[2]);
Canvas->LineTo(x[3],y[3]);
...
In case your API needs MoveTo command and you are not setting it then last position is used (or default (0,0)) which will connect start of your curve with straight line from last draw or default pen position.
Second problem
In continuous data you can threshold the asymptotes or discontinuities by checking the consequent y values. If your curve render looks like this:
Canvas->MoveTo(x[0],y[0]);
for (i=1;i<n;i++) Canvas->LineTo(x[i],y[i]);
Then you can change it to something like this:
y0=y[0]+2*threshold;
for (i=0;i<n;i++)
{
if (y[1]-y0>=threshold) Canvas->MoveTo(x[i],y[i]);
else Canvas->LineTo(x[i],y[i]);
y0=y[i];
}
The problem is selection of the threshold because it is dependent on x density of sampled points and on the first derivation of your y data by x (slope angles)
If you are stacking up more functions the curve append will create your unwanted line ... instead handle each data as separate draw or put MoveTo command in between them
[Edit1]
I see it like this (fake split):
double x0,y0,x1,y1,a;
for (e=1,x = -pi; x < pi; x += .0005f)
{
// last point
x0=x1; y0=y1;
// your actual point
x1=x; y1=-tan(x);
// test discontinuity
if (e) { a=0; e=0; } else a=fabs(atan2(y1-y0,x1-x0));
if (a>0.499*M_PI) curve.append(Vertex(Vector2f(x1,y1), Color::Black));
else curve.append(Vertex(Vector2f(x1,y1), Color::Green));
}
the 0.499*M_PI is you threshold the more closer is to 0.5*M_PIthe bigger jumps it detects... I faked the curve split by black color (background) it will create gaps on axis intersections (unless transparency is used) ... but no need for list of curves ...
Those artifacts are due to the way sf::PrimitiveType::LinesStrip works (or more specific lines strips in general).
In your second example, visualizing y = -tan(x), you're jumping from positive infinity to negative infinity, which is the line you're seeing. You can't get rid of this, unless you're using a different primitive type or splitting your rendering into multiple draw calls.
Imagine a line strip as one long thread you're pinning with pushpins (representing your vertices). There's no (safe) way to go from positive infinity to negative infinity without those artifacts. Of course you could move outside the visible area, but then again that's really specific to this one function.

dimple tickFormat ignores grouping comma

I have a dimple chart displaying some large numbers. I want the precise values to appear in the toolbox (not truncated to 14k) so I changed the tickFormat. However, dimple/d3 seems to ignore the grouping comma, which makes large numbers hard to process.
// this prints "12,345" as expected
var f = d3.format(",.0f");
console.log(f(12345.123));
var data = [{"date":"2016-01-18","completed":1234123.100},{"date":"2016-01-19","completed":1345123.0},{"date":"2016-01-20","completed":2123123.1}]
var svg = dimple.newSvg("#foo", "100%", "100%");
var myChart = new dimple.chart(svg, data);
var x = myChart.addTimeAxis("x", "date", "%Y-%m-%d", "%d/%m");
var y = myChart.addMeasureAxis("y", "completed");
// this does not work
// tooltips and y axis values appear without grouping comma
y.tickFormat = d3.format(",.0f");
myChart.setMargins("60px", "30px", "30px", "70px");
series = myChart.addSeries(null, dimple.plot.line);
myChart.draw();
According to the API documentation the property dimple.axis.tickFormat will accept the format specifier string (i.e. ",.0f"). as it is passed to d3.format(). You don't have to call d3.format() yourself; this is done internally in function _getFormat(). Thus your code becomes
y.tickFormat = ",.0f";

dygraphs - limiting the change of y axis range when zooming

When I am zooming the chart then the Y scale is changing automatically to select the range fitting to the values limited by x.
I know valueRange param where I can fix the range, but I am searching for an option to define some maximal range.
So that the Y scale is changing when zooming , but the scale is limited by some parameter
In the interest of clarity and brevity, I have omitted some things - such as the creation process.
You would be able to do something similar to limit the minimum range.
g = new Dygraph(
... your parameters ...
);
window.intervalId = setInterval(
function(){
var extents = g.yAxisRange();
var lowerExtent = extents[0];
var upperExtent = extents[1];
if(upperExtent > MY_MAX_DESIRED){
var range = [lowerExtent, MY_MAX_DESIRED];
g.updateOptions('valueRange': range);
}
},
500
);
Using the above solution, you would notice a half-second 'glitch' on zooming when the range it set. As an alternative, you could also use the zoomCallBack, specified when you create the graph object:
g = new Dygraph(
document.getElementById("div_g"),
[[0,0]],
{
zoomCallBack: function(minDate, maxDate, yRanges){
var extents = yRanges;
var lowerExtent = extents[0];
var upperExtent = extents[1];
if(upperExtent > MY_MAX_DESIRED){
var range = [lowerExtent, MY_MAX_DESIRED];
g.updateOptions('valueRange': range);
}
}
}
);
One caveat, I haven't tested the above solutions. I just believe they should work. Good luck!
thank you!
I have tried to use your suggestion
I have some issues:
"http://jsfiddle.net/stojak/zL6es924/4/"
my goal: e.g when y values in data are in between 10 and 11 then I want to zoom on Y to 0 (10-10) to 21 (11+10)
the code is nearly working, however I do not know how to let say reset my valueRange.
the result is that after each one zoom action the range increase.

HTML Canvas game: 2D collision detection

I need to program a very simple 2D HTML canvas game with a character and a few walls. The map (top view) is a multidimensional array (1=walls)
map = [
[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
[1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0],
[1,0,0,1,0,0,0,0,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,1],
[1,0,0,1,0,0,0,0,1,0,1,1,1,1,1,1,0,0,0,0,0,0,0,1],
[1,0,0,1,0,0,0,0,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0,1],
[1,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
[1,0,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,1],
[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,1,1],
[1,1,1,0,1,1,1,1,1,1,0,1,1,1,1,1,1,0,0,1,1,0,1,1],
[1,1,1,0,1,1,1,1,1,1,0,1,1,1,1,0,0,0,0,1,1,0,1,1],
[0,0,0,0,1,1,1,1,1,1,0,1,1,1,1,0,0,1,1,1,1,0,1,1],
[1,1,1,0,1,1,1,1,0,0,0,1,1,1,0,0,0,1,1,1,1,0,1,1],
[1,1,1,0,0,0,0,0,0,0,0,1,1,0,0,1,1,1,0,0,0,0,1,1],
[1,1,1,1,1,1,1,1,1,1,0,1,1,0,0,0,0,0,0,1,0,0,1,1],
[1,1,1,1,1,1,1,1,1,1,0,0,0,0,1,1,1,1,1,1,1,1,1,1],
[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]
];
The character shouldn't be able to walk over the walls... so he should only walk on the "0"s. I already got the map rendering and the walking part of the character working just fine, but I can't quite figure out how to check for collisions yet.
A very simple version can be found on JSBin. You can either use the arrow keys or WASD to move around (black square).
I already tried to do a very simple collision detection by using something like this:
function checkCollision( x, y ) {
if ( map[ Math.round( x ) ][ Math.round( y ) ] !== 0 ) {
return true; // Collision
}
return false;
}
But this doesn't quite work (see JSBin). With Math.round the character and wall overlap... if I use Math.ceil or Math.floor it's even worse.
Is there any way that I can improve this "collision detection", so that the character can't walk over the red walls?
There are a few problems:
First you should avoid using 0.1 as coordinate step because it's a "bad number" in floating point (it's periodic when expressed in binary). Much better is 0.125 (1/8). Adding/substracting 1/8 will guarantee your numbers will always remain exact multiples of 1/8, not accumulating any error.
You should define an "ok(x, y)" function checking if the (possibly fractional) point (x, y) is valid or inside a wall... a simple implementation could be:
return map[y|0][x|0] == 0; // <expr>|0 is used to convert to integer
Finally you should compute new_charX and new_charY and only accept moving from charX, charY to the new position if all four points:
(new_charX+s, new_charY+s)
(new_charX+1-s, new_charY+s)
(new_charX+s, new_charY+1-s)
(new_charX+1-s, new_charY+1-s)
are valid with s = 1/16 (i.e. half of the moving step).
Example: http://jsbin.com/wipimidije/edit?js,output
Your player can potentially be overlapping four squares in the map at any time. So you need to check for collision in all four squares (corresponding to the top-left, top-right, bottom-left, bottom-right corners of the character). To allow it to 'squeeze' through corridors (given that the character is the same size as a tile) you may also need to adjust this by one or two pixels (hence the 1 / config.tileSize in the following).
function checkCollision( x, y ) {
var x1 = Math.floor(x + 1 / config.tileSize),
y1 = Math.floor(y + 1 / config.tileSize),
x2 = Math.floor(x + 1 - 1 / config.tileSize),
y2 = Math.floor(y + 1 - 1 / config.tileSize);
if (map[y1][x1] !== 0 || map[y2][x1] !== 0 || map[y1][x2] !== 0 ||
map[y2][x2] !== 0) {
return true; // Collision
}
return false;
}
See this version of the JSBin
My answer is a little different. Instead of having a 2D array I'd use a simple array and calculate the rows (if you even need them). Now you only have to check the map array at the one index that is the position of the actor:
var map = [0,0,0,1,1,0,0,1,1,0,0,...];
//heres how to calculate the x/y if you need it for something else
var pos_x = position % NUM_OF_ROWS;
var pos_y = Math.floor(position / NUM_OF_ROWS);
//for collisions now you just check the value of the array at that index
leftKey.addEventListener('keypress', function() {
test(position - 1);
});
rightKey.addEventListener('keypress', function() {
test(position + 1);
});
upKey.addEventListener('keypress', function() {
test(position + NUM_OF_ROWS);
});
downKey.addEventListener('keypress', function() {
test(position - NUM_OF_ROWS);
});
function test(n) {
if (map[n] === 0) {
//if there's no collision, update the position.
position = n;
} else {
console.log('Collided!');
}
}
You need to consider two aspects: the first is collision detection, the second is response. Let's start with the detection. You are checking a single point, but in reality you have a tile size, so there is thickness which you must consider. The coordinate of the character, and the coordinate of your tiles is the top-left corner. It is not sufficient to compare the top-left corners, you must also check the other corners. The right-hand side of the player's square for example is at charX + config.tileSize.
The second aspect is the collision response. The simplest mechanism you can use here is to check the next position of the character for collisions, and only move the character if there are none. You should preferably check the two axes separately to allow the character to "slide" along walls (otherwise it till get stuck in walls in moving diagonally into the wall).
First of all I would change the tiles "value" if you change the character to walk in 1's but in 0's you can check if a tile is walkable by typing
If(tile[x][y])...
Then, I would, first calculate the next position and then make the move if the player is able to...
Var nextpos = new position;
If(KEYLEFT){
Nextpos.x = currpos - 1;
}
If(nextpos > 0 && nextpos < mapsize && tile[nextpos.x][nextpos.y])
Player.pos = nextpos;

Categories

Resources