Issue
I want to show the weight of an edge when the mouse hovers over it.
So I use an MouseEvent.MOUSE_MOVED
in my implemented MouseManager. With nodes, I just can call view.findGraphicElementAt(getManagedTypes(), event.getX(), event.getY())
to get the GraphicNode Object. Unfortunately, edges do not have one x and y value, and are thus not found by this method. Yes, I know getX()
and getY()
are implemented for a GraphicEdge but are just pointing at the center of the edge.
I need the Edge Object to get some further information stored at the edge (like weight). So how do I get the Edge Object using x,y or some other values I can retrieve from the received MouseEvent?
Solution
In fact, edge selection, mouseOver, and mouseLeft functions (which kind of includes hovering over edges) is already implemented in the MouseOverMouseManager
or FxMouseOverMouseManager
. This manager is automatically set when calling view.enableMouseOptions()
but I already implemented an individual MouseManager for some other reasons plus hovering over an edge is only detected when hovering over the center of the edge. So my solution was to copy the code from MouseOverMouseManager
into MyMousemanager and modify it.
Edit:
public class CompoundListNetworkMouseManager extends FxMouseManager {
private GraphicElement hoveredElement;
private long hoveredElementLastChanged;
private ReentrantLock hoverLock = new ReentrantLock();
private Timer hoverTimer = new Timer(true);
private HoverTimerTask latestHoverTimerTask;
/**
*(copied from the GraphsStream Library)
* The mouse needs to stay on an element for at least this amount of milliseconds,
* until the element gets the attribute "ui.mouseOver" assigned.
* A value smaller or equal to zero indicates, that the attribute is assigned without delay.
* */
private final long delayHover;
public CompoundListNetworkMouseManager(){
super(EnumSet.of(InteractiveElement.NODE, InteractiveElement.EDGE));
this.delayHover = 100;
}
@Override
public void init(GraphicGraph graph, View view) {
this.graph = graph;
this.view = view;
view.addListener(MouseEvent.MOUSE_MOVED, mouseMoved);
}
@Override
public void release() {
view.removeListener(MouseEvent.MOUSE_MOVED, mouseMoved);
}
@Override
public EnumSet<InteractiveElement> getManagedTypes() {
return super.getManagedTypes();
}
protected void mouseOverElement(GraphicElement element){
element.setAttribute("ui.mouseOver", true);
element.setAttribute("ui.class", "mouseOver"); //I defined a class/type for edges in the CSS styling sheet that is calles "mouseOver"
if(element instanceof GraphicEdge) {
mouseOverEdge((GraphicEdge) element);
}
else if(element instanceof GraphicNode){
mouseOverNode((GraphicNode) element);
}
protected void mouseOverEdge(GraphicEdge graphicEdge) {
view.freezeElement(graphicEdge, true);
Edge edge = graph.getEdge(graphicEdge.getId());
System.out.println("Mouse over edge " + edge.getId());
}
protected void mouseLeftElement(GraphicElement element) {
this.hoveredElement = null;
element.removeAttribute("ui.mouseOver");
element.removeAttribute("ui.class");
}
EventHandler<MouseEvent> mouseMoved = new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent event) {
try {
hoverLock.lockInterruptibly();
boolean stayedOnElement = false;
curElement = view.findGraphicElementAt(getManagedTypes(), event.getX(), event.getY());
//adjusted implementation of search for edges
if(curElement == null && getManagedTypes().contains(InteractiveElement.EDGE)){
curElement = (GraphicElement) findEdgeAt(event.getX(), event.getY());
}
if(hoveredElement != null) {
//check if mouse stayed on the same element to avoid the mouseOverEvent being processed multiple times
stayedOnElement = curElement == null ? false : curElement.equals(hoveredElement);
if (!stayedOnElement && hoveredElement.hasAttribute("ui.mouseOver")) {
mouseLeftElement(hoveredElement);
}
}
if (!stayedOnElement && curElement != null) {
if (delayHover <= 0) {
mouseOverElement(curElement);
} else {
hoveredElement = curElement;
hoveredElementLastChanged = Calendar.getInstance().getTimeInMillis();
if (latestHoverTimerTask != null) {
latestHoverTimerTask.cancel();
}
latestHoverTimerTask = new HoverTimerTask(hoveredElementLastChanged, hoveredElement);
hoverTimer.schedule(latestHoverTimerTask, delayHover);
}
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
hoverLock.unlock();
}
}
};
//copied from GraphStream Library
private final class HoverTimerTask extends TimerTask {
private final long lastChanged;
private final GraphicElement element;
public HoverTimerTask(long lastChanged, GraphicElement element) {
this.lastChanged = lastChanged;
this.element = element;
}
@Override
public void run() {
try {
hoverLock.lock();
if (hoveredElementLastChanged == lastChanged) {
mouseOverElement(element);
}
} catch (Exception ex) {
ex.printStackTrace();
} finally {
hoverLock.unlock();
}
}
}
//findGraphicElement could be used but I wanted to implement the edgeContains method myself
private Edge findEdgeAt(double x, double y){
Camera cam = view.getCamera();
GraphMetrics metrics = cam.getMetrics();
//transform x and y
double xT = x + metrics.viewport[0];
double yT = y + metrics.viewport[0];
Edge edgeFound = null;
if (getManagedTypes().contains(InteractiveElement.EDGE)) {
Optional<Edge> edge = graph.edges().filter(e -> edgeContains((GraphicEdge) e, xT, yT)).findFirst();
if (edge.isPresent()) {
if (cam.isVisible((GraphicElement) edge.get())) {
edgeFound = edge.get();
}
}
}
return edgeFound;
}
//new edgeContains() method that finds edge at hovering not only when hovered over edge center
private boolean edgeContains(GraphicEdge edge, double x, double y) {
Camera cam = view.getCamera();
GraphMetrics metrics = cam.getMetrics();
Values size = edge.getStyle().getSize();
double deviation = metrics.lengthToPx(size, 0);
Point3 edgeNode0 = cam.transformGuToPx(edge.from.x, edge.from.y, 0);
Point3 edgeNode1 = cam.transformGuToPx(edge.to.x, edge.to.y, 0);
//check of point x,y is between nodes of the edge
boolean edgeContains = false;
//check x,y range
if(x > Math.min(edgeNode0.x, edgeNode1.x) - deviation
&& x < Math.max(edgeNode0.x, edgeNode1.x) + deviation
&& y > Math.min(edgeNode0.y, edgeNode1.y) - deviation
&& y < Math.max(edgeNode0.y, edgeNode1.y) + deviation){
//check deviation from edge
Vector2 vectorNode0To1 = new Vector2(edgeNode0, edgeNode1);
Point2 point = new Point2(x, y);
Vector2 vectorNode0ToPoint = new Vector2(edgeNode0, point);
//cross product of vectorNode0ToPoint and vectorNode0to1
double crossProduct = vectorNode0ToPoint.x() * vectorNode0To1.y() - vectorNode0To1.x() * vectorNode0ToPoint.y();
//distance of point to the line extending the edge
double d = Math.abs(crossProduct) / vectorNode0To1.length();
if(d <= deviation){
edgeContains = true;
}
}
return edgeContains;
}
}
CSS Stylesheet:
...
edge{
fill-color: black;
size: 2px;
arrow-shape: none;
shape: line;
text-mode: hidden;
}
edge.mouseOver {
fill-color: red;
stroke-color: red;
text-mode: normal;
text-background-mode: plain; /*plain or none*/
text-background-color: rgba(255, 255, 255, 200);
text-alignment: along;
}
Answered By - Naddle
Answer Checked By - Gilberto Lyons (JavaFixing Admin)