Zoom chart in mouse focus - javafx

I have a custom chart which I scale using the following code:
final double SCALE_DELTA = 1.1;
treePane.setOnScroll(new EventHandler<ScrollEvent>()
{
#Override
public void handle(ScrollEvent event)
{
event.consume();
if (event.getDeltaY() == 0)
{
return;
}
double scaleFactor = (event.getDeltaY() > 0) ? SCALE_DELTA : 1 / SCALE_DELTA;
treePane.setScaleX(treePane.getScaleX() * scaleFactor);
treePane.setScaleY(treePane.getScaleY() * scaleFactor);
}
});
I noticed that when I scroll the chart with mouse wheel I cannot zoom the chart where my mouse points. Instead of this the chart is zoomed to left or right for example.
I would like when I zoom with the mouse wheel to scale the chart where my cursor points. Is there any solution?

I haven't worked with javafx, but I will try to answer anyway. From my experience with Win32Api/GDI and XNA you should definitely be able to read mouse coordinates. Anything you display is displayed at coordinates x,y. When you want to scale and zoom to the cursor's location, you will redraw with this in mind: (assume zoom factor = z, mouseX and mouseY the mouse coordinates X and Y the new coordinates)
your cursor will have to point at the same pixel, so if the image is z times bigger now, so will the mouse coordinates be, relative to the source of the picture. To these coordinates, essentially the distance from the top and left of the image, you have to add the coordinates of the source itself, which should remain unchanged relative to the window's top-left corner. This eventually means that mouseX - X = (mouseX - x)*z, which means X = mouseX - (mouseX - x)*z
Same goes for the Y coordinate. In theory this should work perfectly, i have not tried it with code but it seems right on paper. You will notice X and Y seem to be getting smaller, essentially they might even turn out negative since by scaling and keeping the mouse on the same pixel, you push/stretch the top-left corner further up and left, possibly out of the screen.
You will probably work with the scrollbars though, same philosophy on their displacement.
Play around with the variables a bit to make them fit the drawing mode, I do not know if the libraries you are working with start from the screen's (0,0), or the window's, or the working areas, so you will have to adapt it to the rest of the code, but this is the math/geometry behind it.

Related

Rotation of coordinate system with rectangle leads to parallelogram

In my Flutter app, I have drawn a rectangle in one coordinate system on a Google Map and I want to rotate this coordinate system around the coordinate (0,0). How this can be done is described here: Rotation matrix. I have implemented the rotation of coordinates like this in my app:
class Coordinate {
Coordinate(this.x, this.y);
double x;
double y;
void rotate(double angle) {
// angle is in degrees, convert to radians
final double angleInRadians = angle * (math.pi / 180);
final double oldX = x;
final double oldY = y;
final double newX = oldX * math.cos(angleInRadians) - oldY * math.sin(angleInRadians);
final double newY = oldX * math.sin(angleInRadians) + oldY * math.cos(angleInRadians);
x = newX;
y = newY;
}
}
Using this to rotate four corners of a rectangle works fine for some places in the world, like Australia and a place near (0,0). In this picture, you can see the rotation of the red rectangle on the top around the point (0,0) (bottom left) to the rotated red rectangle on the bottom right:
Please ignore all the markers and non-red lines. The next to images show a rectangle like the one in the picture above which I again rotate around (0,0) using the code above, but as you can see in the second image, the red rectangle is not a rectangle anymore, but a parallelogram instead.
I cannot find an explanation for this. The questions I ask myself are: Why does the code work for some places but not for others? Why does the rectangle transform to a perfect parallelogram instead of whatever other shape with the points possibly all around the world? As it is working for some locations, the problem cannot be that I have mixed up some of the coordinates or something like that, it has to do something with the calculation of the edges of the rotated rectangle. But I cannot find out what I did wrong here.
REMINDER: IGNORE ALL MARKERS AND NON-RED LINES

Get visible elements from AnchorPane

I'm developing a JavaFX application where the user is able to zoom and drag the elements (all contained in a AnchorPane). Some of those elements are simple lines and I need to have something like a ruler that has different parent to stick on the screen on the same position even if the user zooms or drags the mentioned AnchorPane. I got almost everything working but I have one problem, I need to know which lines from the AnchorPane are visible to the user (as if the user zooms and drags the AnchorPane, some of the lines are not visible anymore). Here's what I tried (not working...)
private List<Double> getVisibleVerticalLinesXCoordonate() {
List<Double> xCoordonatesOfVisibleVerticalLines = new ArrayList<>();
List<Node> visibleNodes = new ArrayList<>();
Bounds bounds = rulerParent.getBoundsInLocal();
Bounds paneBounds = rulerParent.localToScene(bounds);
for (Node n : gridVerticalLines) {
Bounds nodeBounds = n.getBoundsInParent();
if (paneBounds.intersects(nodeBounds)) {
visibleNodes.add(n);
}
}
for (Node node : visibleNodes) {
Bounds newBounds = getRelative(node);
xCoordonatesOfVisibleVerticalLines.add(newBounds.getMinX());
}
System.out.println(Arrays.asList(xCoordonatesOfVisibleVerticalLines));
return xCoordonatesOfVisibleVerticalLines;
}
private Bounds getRelative(Node node) {
return rulerParent.sceneToLocal(node.localToScene(node.getBoundsInLocal()));
}
So, the rulerParent is what is fixed on the screen (is not zooming or dragging at all). After I have the visible lines from the AnchorPane I get the x coordinates of the lines relative to rulerParent - so I can align the ruler lines with the lines in the AnchorPane.
The problem is that this is not returning the actual visible lines...
I don't need to be able to see the whole line to consider it visible, that's why I'm using intersect...if any part of a line is visible, I need it.
It's about how you handle the zoom and dragging actions try to use ViewPort instead of scaling as with ViewPort you can know the translation X and Y coordinates of the ViewPort which is the visible X and Y coordinates of the AnchorPane

How to align to the a rectangle to the left of a QGraphicsView without scaling the scene?

Here is what I want to do.
I need to draw in a QGraphicsView a series of rectangles that are aligned left and right. By this I mean that if rectangle i has posion (0,y) rectangle i+1 needs to have position (0,max) where max is such that the right side of the rectangle "touches" the right side of the QGraphicsView.
When the window is resized I need to recalculate the value of max such that the rectangle is always touching the right side of the screen.
Here is how I add my scene (this references a class that inherits QGraphicsView)
scene = new QGraphicsScene(this);
this->setScene(scene);
this->setAlignment(Qt::AlignTop|Qt::AlignLeft);
To add a rectangle that touches the left border I add it a (0,yvalue,width,height).
How do I calculate the value of X so that that the rectangle will touch the right border?
Ok. So this should go in the resize event of the QGraphicsView. But this does what I wanted:
void AView::resized(){
scene->clear();
QSize newsize = this->size();
qreal w = newsize.width()*.99;
qreal h = newsize.height()*.99;
this->setSceneRect(0,0,w,h);
scene->setSceneRect(0,0,w,h);
qreal rectwidth = 100;
qreal newx = w - rectwidth;
left = new QGraphicsRectItem(0,0,rectwidth,100);
left->setBrush(QBrush(Qt::red));
right = new QGraphicsRectItem(newx,100,rectwidth,100);
right->setBrush(QColor(Qt::blue));
scene->addItem(left);
scene->addItem(right);
}
In this way the blue rectangle is pretty much always at the border. Aslo there is no stretching of the image. There is a gap between the right borders which increases due to the window resizing, but it seems that the size returned by this->size() is slightly larger than the "white area" that you see on screen. Adding the .99 gives a much better results from my experiments.
This little example should calculate the shift and move all items from a selection or list of items, based on alignment you desire (I showed right, but let, center, top, bottom would be the same).
QRectF refRect = scene()->sceneRect();
QList<QGraphicsItem*> sel = allItemsYouWantAligned; // scene()->selectedItems(); for example
foreach(QGraphicsItem* selItem, sel)
{
qreal dx = 0, dy = 0;
QRectF itemRect = selItem->mapToScene(selItem->boundingRect()).boundingRect();
if(align_right)
dx = refRect.right() - itemRect.right();
else
.. calculate either dx or dy on how you want to align
selItem->moveBy(dx, dy);
}
Re-reading the question - I see you don't really need to move rectangle, but create new larger and larger rectangles.
Your solution is simple enough. If you want to resize instead of move -
you would have to setRect() on your items by dx increment:
QRectF r = selItem->rect();
r.setWidth(r.width + dx);
selItem->setRect(r);

How to create Vector3 for all resolutions

I'm trying to fit my interface for all resolutions. I have this code
private void OnEnable() {
GameObject back = Instantiate(Background) as GameObject;
BoxCollider2D back_bc2d = back.GetComponent<BoxCollider2D>();
float x = ((Screen.currentResolution.width) / back_bc2d.size.x) / 2;
float y = (Screen.currentResolution.height / back_bc2d.size.y);
back.transform.localScale = new Vector3(x, y, back.transform.localScale.z);
Destroy(back_bc2d);
}
As you can see, I'm trying to create something like GUI.DrawTexture (I don't want to use GUI.DrawTexture because of some reasons)
My texture have to draw on a half screen.
But on different resolutions it doesn't work. What I'm doing wrong?
Screen resolution does not mean anything in world space, only the aspect ratio is important there. The in game geometry size is the same in every resolution, only the viewport may be wider showing more of it - this is how the camera works.
Also, you should not derive localScale used in world space from screen space coordinates - they don't work this way.
If you want to position some geometry in worldspace relative to the camera, use http://docs.unity3d.com/ScriptReference/Camera.ScreenToWorldPoint.html

Zooming in/out on a mouser point ?

As seen in the pictures.
I have QWidget inside a QScrollArea.
QWidget act as a render widget for cell image and some vector based contour data.
User can performe zoom in/out and what simply happens is, it changes the QPainters scale and change the size of QWidget size accordinly.
Now I want to perform the zooming in/out on the point under the mouse. (like zooming action in GIMP).
How to calculate the new positions of the scrollbars according to the zoom level ?
Is it better to implement this using transformations without using a scrollarea?
One solution could be to derive a new class from QScrollArea and reimplementing wheelEvent for example so that zooming is performed with the mouse wheel and at the current mouse cursor position.
This method works by adjusting scroll bar positions accordingly to reflect the new zoom level. This means as long as there is no visible scroll bar, zooming does not take place under mouse cursor position. This is the behavior of most image viewer applications.
void wheelEvent(QWheelEvent* e) {
double OldScale = ... // Get old scale factor
double NewScale = ... // Set new scale, use QWheelEvent...
QPointF ScrollbarPos = QPointF(horizontalScrollBar()->value(), verticalScrollBar()->value());
QPointF DeltaToPos = e->posF() / OldScale - widget()->pos() / OldScale;
QPointF Delta = DeltaToPos * NewScale - DeltaToPos * OldScale;
widget()->resize(/* Resize according to new scale factor */);
horizontalScrollBar()->setValue(ScrollbarPos.x() + Delta.x());
verticalScrollBar()->setValue(ScrollbarPos.y() + Delta.y());
}
Will void QScrollArea::ensureVisible(int x, int y, int xmargin = 50, int ymargin = 50) do what you need?
You need to pick up the wheelEvent() on the QWidget, get the event.pos() and pass it into the QscrollArea.ensureVisible(), right after scaling your QWidget.
def wheelEvent(self, event):
self.setFixedSize(newWidth, newHeight)
self.parent().ensureVisible(event.pos())
That should more or less produce what you want.

Resources