Getting mouse coordinates on heatmap canvas

Custom indicators, trading strategies, data export and recording and more...
leyris
Posts: 4
Joined: Mon Nov 29, 2021 8:34 pm

Getting mouse coordinates on heatmap canvas

Post by leyris » Tue May 10, 2022 6:55 am

Hi,

I am currently developing an add-on. In short, the heatmap coordinates as time and price pointed by the mouse needs to be retrieved so the add-on can add graphic objects on the heatmap canvas. I have a pretty good idea on how to draw them since there are the SSP canvas examples available which does exactly that (Layer1OrdersOverlayDemo). I am not familiar with java too much, I am indeed familiar with OO programming. Here is where I struggle:

1/ On which properties/object should I add the addMouseListener to get the mousePressed, mouseDragged and all of is its kind in the add-on ? I have tried on the heatmap canvas object but no luck.
2/ How to convert those coordinates into time and price on the heatmap if they are not in canvas coordinates? I believe if they are in canvas coordinates I can just follow the Layer1OrdersOverlayDemo example to convert them by using heatmapTimeLeft and heatmapActiveTimeWidth.

Thanks in advance !
 

AndreyR
Posts: 458
Joined: Mon Jul 09, 2018 11:18 am
Has thanked: 18 times
Been thanked: 71 times

Re: Getting mouse coordinates on heatmap canvas

Post by AndreyR » Fri May 13, 2022 12:03 pm

Acquiring the mouseListener is not implemented at the moment. We will register/update a feature request regarding this feature.

Now, there may be a workaround and it will be ugly.
You need to traverse the components tree and find the canvas on the chart. This canvas will be the Bookmap openGL image. The canvas will be inside a JPanel and a JTable will be somewhere near it. The JTable will have some headers. Take a look at the headers' width. Now, when you know the widths and the canvas size you will know the canvas configuration. You will know what columns are there and maybe understand the columns' roles (by headers names. THe column without a name will be the heatmap. This is not a 100% solution but currently the best one).
So now you know the heatmap coordinates on the screen. You can add a mouse listener to the canvas and catch events. The timeline coordinates depend on canvas size.

leyris
Posts: 4
Joined: Mon Nov 29, 2021 8:34 pm

Re: Getting mouse coordinates on heatmap canvas

Post by leyris » Mon May 16, 2022 9:34 pm

Thanks Andrey for the response. I get the overall idea. Quick follow-up question: do you happen to know the name of the top level instance of the component tree you are referring to ? Can I access it via the api var of:

Code: Select all

public void initialize(String alias, InstrumentInfo info, Api api, InitialState initialState)
?

AndreyR
Posts: 458
Joined: Mon Jul 09, 2018 11:18 am
Has thanked: 18 times
Been thanked: 71 times

Re: Getting mouse coordinates on heatmap canvas

Post by AndreyR » Tue May 17, 2022 9:21 am

Hi, this is just an example, perhaps not the best one.

Code: Select all

@Layer1SimpleAttachable
@Layer1StrategyName("Find Canvas")
@Layer1ApiVersion(Layer1ApiVersionValue.VERSION2)
public class FindCanvas implements CustomModule {

    @Override
    public void initialize(String arg0, InstrumentInfo arg1, Api arg2, InitialState arg3) {
        Window mainWindow = getMainWindow();
        Canvas canvas = findCanvasRecursively(mainWindow);
    }

    @Override
    public void stop() {
    }
    
    private Window getMainWindow() {
        Window[] windows = Window.getWindows();
        
        for (Window w : windows) {
            if (w.getName().contains("main-window")){
                return w;      
            }
        }
        return null;
    }

    private Canvas findCanvasRecursively(Container container) {
        Component[] components = container.getComponents();
        for (Component component : components) {
            if (component instanceof Canvas) {
                return (Canvas) component;
            } else if (component instanceof Container) {
                Canvas t = findCanvasRecursively((Container) component);
                if (t != null) {
                    return t;
                }
            }
        }
        return null;
    }
}

leyris
Posts: 4
Joined: Mon Nov 29, 2021 8:34 pm

Re: Getting mouse coordinates on heatmap canvas

Post by leyris » Tue May 17, 2022 6:02 pm

Thanks for that! I was not expecting that much :) I will give a try later this week and keep you posted

leyris
Posts: 4
Joined: Mon Nov 29, 2021 8:34 pm

Re: Getting mouse coordinates on heatmap canvas

Post by leyris » Sat May 21, 2022 6:44 am

I made good progress thanks to your code. I managed to get the time at which the mouse is clicked. After carefully rereading the examples, I have found that there are some helpers than can convert pixel coordinates into time coordinate directly (in SSP demos), so maybe I should have started with this and will take a slightly different approach for my add-on...

Anyway, below is a minimal code for those interested. There are still issues though as I see that the constructor is called twice apparently once when loading module and once when activating. I can see that because one of my ssp log says pointer is null (probably the one coming from module load) and the other is not. Since my mouse listener is in the constructor I then end up catching the mouse events twice (and the add-on jar file is locked, so it makes it difficult to iterate on code/build/deploy). So I am not sure calling constructor twice is expected behavior in Bookmap and if it is, I probably need to put the addMouseListener somewhere else. Do you have any leads on this ?

Thanks again !

EDIT ...and this example is probably not thread safe since maybe the vars from SSP are in a different thread. I need to do some more digging.

Code: Select all

// package velox.api.layer1.simpledemo.screenspacepainter;
package com.bookmap.api.simple.simplified;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.util.HashMap;
import java.util.Map;

import velox.api.layer1.Layer1ApiAdminAdapter;
import velox.api.layer1.Layer1ApiFinishable;
import velox.api.layer1.Layer1ApiProvider;
import velox.api.layer1.annotations.Layer1ApiVersion;
import velox.api.layer1.annotations.Layer1ApiVersionValue;
import velox.api.layer1.annotations.Layer1Attachable;
import velox.api.layer1.annotations.Layer1StrategyName;
import velox.api.layer1.common.ListenableHelper;
import velox.api.layer1.common.Log;
import velox.api.layer1.layers.strategies.interfaces.ScreenSpaceCanvas;
import velox.api.layer1.layers.strategies.interfaces.ScreenSpaceCanvas.CanvasIcon;
import velox.api.layer1.layers.strategies.interfaces.ScreenSpaceCanvas.CompositeCoordinateBase;
import velox.api.layer1.layers.strategies.interfaces.ScreenSpaceCanvas.CompositeHorizontalCoordinate;
import velox.api.layer1.layers.strategies.interfaces.ScreenSpaceCanvas.CompositeVerticalCoordinate;
import velox.api.layer1.layers.strategies.interfaces.ScreenSpaceCanvas.PreparedImage;
import velox.api.layer1.layers.strategies.interfaces.ScreenSpaceCanvasFactory;
import velox.api.layer1.layers.strategies.interfaces.ScreenSpaceCanvasFactory.ScreenSpaceCanvasType;
import velox.api.layer1.layers.strategies.interfaces.ScreenSpacePainter;
import velox.api.layer1.layers.strategies.interfaces.ScreenSpacePainterAdapter;
import velox.api.layer1.layers.strategies.interfaces.ScreenSpacePainterFactory;
import velox.api.layer1.messages.UserMessageLayersChainCreatedTargeted;
import velox.api.layer1.messages.indicators.Layer1ApiUserMessageModifyScreenSpacePainter;

import java.awt.Canvas;
import java.awt.Component;
import java.awt.Container;
import java.awt.Window;
import javax.swing.JPanel;
import javax.swing.JTable;
import javax.swing.table.TableColumn;
import java.awt.event.*;
import java.sql.Timestamp;
import java.util.Date;

/** This demo shows you locations corresponding to each canvas type visually */
@Layer1Attachable
@Layer1StrategyName("Get clicked time")
@Layer1ApiVersion(Layer1ApiVersionValue.VERSION2)
public class TimeGetter implements
    Layer1ApiFinishable,
    Layer1ApiAdminAdapter,
    ScreenSpacePainterFactory,
    MouseListener
{
 
    private static final String INDICATOR_NAME = "Screen space canvas demo";
    
    private Layer1ApiProvider provider;

    private Map<String, String> indicatorsFullNameToUserName = new HashMap<>();
    private Map<String, String> indicatorsUserNameToFullName = new HashMap<>();
    private CustomSSP sspAdapter = null;
    private Canvas canvas = null;
    private JTable jTable = null;
    int canvasOffset;

    public TimeGetter(Layer1ApiProvider provider) {
        this.provider = provider;
        ListenableHelper.addListeners(provider, this);
        Window mainWindow = getMainWindow();
        findCanvasAndTableRecursively(mainWindow);
        showTableInfo(jTable);
        canvas.addMouseListener(this);
    }

    private void showTableInfo(JTable table) {
        Log.info("Row count " + table.getRowCount());
        Log.info("Col count " + table.getColumnCount());
        for (int i = 0; i < table.getColumnCount(); i++) {
            TableColumn c = table.getColumnModel().getColumn(i);
            Log.info("Col " + i + " name " + c.getHeaderValue() + " width " + c.getWidth());
        }
    }

    public class CustomSSP implements ScreenSpacePainterAdapter {
            int heatmapFullPixelsWidth;
            int heatmapPixelsHeight;
            int rightOfTimelineWidth;
            int x, y;
            long heatmapTimeLeft;
            long heatmapActiveTimeWidth;
            long heatmapFullTimeWidth;
                        
            CanvasIcon heatmapIcon;
            CanvasIcon rightOfTimelineIcon;
            
            ScreenSpaceCanvas heatmapCanvas;
            ScreenSpaceCanvas timelineCanvas;

            public long getTimeLeft() {
                return heatmapTimeLeft;
            }

            public long getActiveTimeWidth() {
                return heatmapActiveTimeWidth;
            }

            public long getFullTimeWidth() {
                return heatmapFullTimeWidth;
            }

            public double getPixelToTimeScale() {
                return (double)heatmapFullTimeWidth / (double)heatmapFullPixelsWidth;
            }

            public CustomSSP(ScreenSpaceCanvas heatmapCanvas, ScreenSpaceCanvas timelineCanvas) {
                super();
                this.heatmapCanvas = heatmapCanvas;
                this.timelineCanvas = timelineCanvas;
            }
            
            @Override
            public void finalize() {
                Log.info("SSP Destroyed");
            }

            @Override
            public void onHeatmapTimeLeft(long heatmapTimeLeft) {
                this.heatmapTimeLeft = heatmapTimeLeft;
                Log.info("Heatmap Left " + heatmapTimeLeft);
            }

            @Override
            public void onHeatmapActiveTimeWidth(long heatmapActiveTimeWidth) {
                this.heatmapActiveTimeWidth = heatmapActiveTimeWidth;
                Log.info("Heatmap active time width " + heatmapActiveTimeWidth);
            }

            @Override
            public void onHeatmapFullTimeWidth(long heatmapFullTimeWidth) {
                this.heatmapFullTimeWidth = heatmapFullTimeWidth;
                Log.info("Heatmap full time width " + heatmapFullTimeWidth);
            }

            @Override
            public void onHeatmapFullPixelsWidth(int heatmapFullPixelsWidth) {
                this.heatmapFullPixelsWidth = heatmapFullPixelsWidth;
                Log.info("Heatmap pixel width " + heatmapFullPixelsWidth);
            }
            
            @Override
            public void onHeatmapPixelsHeight(int heatmapPixelsHeight) {
                this.heatmapPixelsHeight = heatmapPixelsHeight;
            }

            @Override
            public void onRightOfTimelineWidth(int rightOfTimelineWidth) {
                this.rightOfTimelineWidth = rightOfTimelineWidth;
                Log.info("Right of time of line " + rightOfTimelineWidth);
            }

            @Override
            public void onMoveEnd() {

            }
 
            @Override
            public void dispose() {
                heatmapCanvas.dispose();
                timelineCanvas.dispose();
            }
    }
    @Override
    public void finish() {
        canvas.removeMouseListener(this);
        synchronized (indicatorsFullNameToUserName) {
            for (String userName: indicatorsFullNameToUserName.values()) {
                Layer1ApiUserMessageModifyScreenSpacePainter message = Layer1ApiUserMessageModifyScreenSpacePainter
                        .builder(TimeGetter.class, userName).setIsAdd(false).build();
                provider.sendUserMessage(message);
            }
        }
    }
    
    private Layer1ApiUserMessageModifyScreenSpacePainter getUserMessageAdd(String userName) {
        return Layer1ApiUserMessageModifyScreenSpacePainter.builder(TimeGetter.class, userName)
                .setIsAdd(true)
                .setScreenSpacePainterFactory(this)
                .build();
    }
    
    @Override
    public void onUserMessage(Object data) {
        if (data.getClass() == UserMessageLayersChainCreatedTargeted.class) {
            UserMessageLayersChainCreatedTargeted message = (UserMessageLayersChainCreatedTargeted) data;
            if (message.targetClass == getClass()) {
                addIndicator();
            }
        }
    }

    public void addIndicator() {
        Layer1ApiUserMessageModifyScreenSpacePainter message = getUserMessageAdd(INDICATOR_NAME);
        
        synchronized (indicatorsFullNameToUserName) {
            indicatorsFullNameToUserName.put(message.fullName, message.userName);
            indicatorsUserNameToFullName.put(message.userName, message.fullName);
        }
        provider.sendUserMessage(message);
    }

    @Override
    public ScreenSpacePainter createScreenSpacePainter(String indicatorName, String indicatorAlias,
            ScreenSpaceCanvasFactory screenSpaceCanvasFactory) {

        ScreenSpaceCanvas heatmapCanvas = screenSpaceCanvasFactory.createCanvas(ScreenSpaceCanvasType.HEATMAP);
        ScreenSpaceCanvas timelineCanvas = screenSpaceCanvasFactory.createCanvas(ScreenSpaceCanvasType.RIGHT_OF_TIMELINE);
        sspAdapter = new CustomSSP(heatmapCanvas, timelineCanvas);
        Log.info("SSP adapter is " + sspAdapter);
        return sspAdapter;
    }

    private Window getMainWindow() {
        Window[] windows = Window.getWindows();
        for (Window w : windows) {
            if (w.getName().contains("main-window")) {
                return w;      
            }
        }
        return null;
    }

    private void retrieveJTable(JTable someTable) {
        if (someTable.getRowCount() == 0) {
            jTable = someTable;
        }
    }

    private void findCanvasAndTableRecursively(Container container) {
        Component[] components = container.getComponents();
        for (Component component : components) {
            if (component instanceof JTable) {
                retrieveJTable((JTable) component);
            } else if (component instanceof Canvas) {
                canvas = (Canvas) component;
            } else if (component instanceof Container) {
                findCanvasAndTableRecursively((Container) component);
            }
        }
    }

    private void updateCanvasOffsetAndSize() {
        if (jTable == null) {
            return;
        }
        int offset = 0;
        int width = 0;
        int canvasWidth = 0;
        for (int i = 0; i < jTable.getColumnCount(); i++) {
            TableColumn c = jTable.getColumnModel().getColumn(i);
            if (c.getHeaderValue() == "") {
                canvasWidth = c.getWidth();
                break;
            }
            offset += c.getWidth();
        }
        canvasOffset = offset;
        Log.info("Offset is " + canvasOffset);
        Log.info("Width  is " + canvasWidth);
    }

    private static Date getDate(long t) {
        Timestamp ts = new Timestamp(t / 1000000);
        Date date = new Date(ts.getTime());
        return date;
    }

    @Override
    public void mouseClicked(MouseEvent mouseEvent) {
        Log.info("Clicked " + mouseEvent.getX() + " " + mouseEvent.getY());
        // Log.info("SSP adapter is " + sspAdapter);
        updateCanvasOffsetAndSize();
        if (sspAdapter == null) {
            Log.info("Clicked: drop event");
            return;
        }
        long timeLeft = sspAdapter.getTimeLeft();
        long timeWidth = sspAdapter.getFullTimeWidth();
        double scale = sspAdapter.getPixelToTimeScale();
        int canvasXCoord = mouseEvent.getX() - canvasOffset;
        int canvasYCoord = mouseEvent.getY();
        long timeRight = timeLeft + timeWidth;
        Log.info("Time left " + timeLeft + " (" + getDate(timeLeft) + ") ");
        Log.info("Time right " + timeRight + " (" + getDate(timeRight) + ") ");
        long clickedTime = (long)(scale * (double)canvasXCoord + timeLeft);
        Log.info("Time clicked " + clickedTime + " (" + getDate(clickedTime) + ") ");
    }
        
    @Override
    public void mousePressed(MouseEvent mouseEvent) {
    }

    @Override
    public void mouseReleased(MouseEvent mouseEvent)  {
    }
        
    @Override
    public void mouseEntered(MouseEvent mouseEvent)  {
    }

    @Override
    public void mouseExited(MouseEvent mouseEvent)  {
    }
}


 

Post Reply