在 GraphStream 中鼠标悬停时获取 GraphicEdge

Get GraphicEdge at Mouse hovering in GraphStream

我想在鼠标悬停在边上时显示边的权重。

所以我在实现的 MouseManager 中使用了 MouseEvent.MOUSE_MOVED。对于节点,我只需调用 view.findGraphicElementAt(getManagedTypes(), event.getX(), event.getY()) 即可获取 GraphicNode 对象。不幸的是,边没有 one x 和 y 值,因此无法通过此方法找到。是的,我知道 getX()getY() 是为 GraphicEdge 实现的,但只是指向边缘的中心。

我需要边缘对象来获取存储在边缘的更多信息(例如重量)。那么如何使用 x、y 或我可以从接收到的 MouseEvent 中检索的其他一些值来获取边缘对象?

实际上,MouseOverMouseManagerFxMouseOverMouseManager 中已经实现了边缘选择、mouseOver 和 mouseLeft 功能(其中包括悬停在边缘上)。调用 view.enableMouseOptions() 时会自动设置此管理器,但由于某些其他原因我已经实现了一个单独的 MouseManager 并且仅当将鼠标悬停在边缘中心时才检测到悬停在边缘上。所以我的解决方案是将代码从 MouseOverMouseManager 复制到 MyMousemanager 并修改它。

编辑:

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 样式表:

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