CoffeeToCodette
CoffeeToCodette

Reputation: 93

Get GraphicEdge at Mouse hovering in GraphStream

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?

Upvotes: 0

Views: 252

Answers (1)

CoffeeToCodette
CoffeeToCodette

Reputation: 93

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;
}

Upvotes: 0

Related Questions