Monodroid and MouseEvents

Jan 1, 2013 at 12:45 PM

Hi,

if I see it right, there is no Support for MouseEvents right now. Is it planned to get them implemented?

How would you suggest to get s Zoom/Pan working right now?

Jan 12, 2013 at 6:30 PM

I am currently thinking about how to solve http://oxyplot.codeplex.com/workitem/9625, and I would like to use some cross-platform code to solve this..

I think it should be possible to implement the mouse events on Mono for Android in the same way as for the other platforms, have a look at the code for the WPF control! (Source\OxyPlot.Wpf\Plot.cs)

Jan 13, 2013 at 5:16 AM

In my Monodroid project, I made a custom PlotView implements IPlotControl, and use ScaleGestureDetector for zoom gesture, GestureDector for pan gesture.

A cross platform solution would be great. Emulate mouse events might be better than reply on platform specific  code eg. GestureDector of android api.

Btw, just found out Oxyplot recently, it's such a great plot solution, thanks objo! (and other contributors)

Jan 15, 2013 at 7:26 AM

codenutter: it would be interesting to see your implementation of ScaleGestureDetector, can you create a fork or post the code here?

I am new to the touch events, and need to find an abstraction that can be implemented on both the Windows (Winforms, WPF, SL), iOS and Android platforms.

Jan 15, 2013 at 7:58 AM

I Implemented it in the AndroidView class. The only issue is the scaling according to the real size of the axis (in Screen space) Also I did not implement the Translation in x- and y- direction with this code...

I have no knowledge about how the scaling and Transformation issues are woring inside the lib but maybe you could start with my code here :)

Some snippets from my implementation:

 

private void Initialize()
        {
 
            mScaleDetector = new ScaleGestureDetector(Contextthis);
        }
 
        private readonly object renderingLock = new object();
        private readonly object modelLock = new object();
        private ScaleGestureDetector mScaleDetector;
 
        public override bool OnTouchEvent(MotionEvent e)
        {
            return mScaleDetector.OnTouchEvent(e);
            return base.OnTouchEvent(e);
        }
public bool OnScale(ScaleGestureDetector detector)
        {
            Debug.WriteLine("Scale" + (1-detector.ScaleFactor));
            var y=Model.Axes[1];
            y.Minimum -= (1-detector.ScaleFactor)*20;
            y.Maximum += (1-detector.ScaleFactor)*20;
            Model.Update(true);
            Invalidate();
            return true;
        }
 
        public bool OnScaleBegin(ScaleGestureDetector detector)
        {
            return true;
        }
 
        public void OnScaleEnd(ScaleGestureDetector detector)
        {
 
        }
Jan 22, 2013 at 9:35 AM

Thank you! I will look at this soon. I also noticed there were some build errors with the latest Mono for Android version.

I am also trying to get a Mono for Android build running on the build machine.

Jan 23, 2013 at 12:51 PM

Hi objo,

Here're the TouchablePlotView for supporting scale and pan gesture. actually it's much simpler than I thought thanks to the IPlotControl. 

I'm quite new to Andorid (only starting weeks ago), so I just use the built-in ScaleGestureDetector and GestureDetector. It might be better to implement my own gesture support by using OnTouchEvent. But for my current project, built-in gesture class seems sufficient.

 

public class TouchablePlotView : View , IPlotControl, ScaleGestureDetector.IOnScaleGestureListener, GestureDetector.IOnGestureListener
    {
        private ScaleGestureDetector _scaleDetector;
        private GestureDetector _gestureDetector;

        public override bool OnTouchEvent(MotionEvent e) {
            bool isConsumed = _gestureDetector.OnTouchEvent(e);
            if (!isConsumed)
                isConsumed = _scaleDetector.OnTouchEvent(e);
            return isConsumed;
        }

        private PlotModel model;

        public PlotModel Model
        {
            get
            {
                return this.model;
            }
            set
            {
                if (this.model == value)
                {
                    return;
                }

                this.model = value;
                this.OnModelChanged();
            }
        }

        private void OnModelChanged()
        {
            this.InvalidatePlot(true);
        }

        private readonly object invalidateLock = new object();
        private bool isModelInvalidated;
        private bool updateDataFlag = true;

        
        protected override void OnAttachedToWindow()
        {
            base.OnAttachedToWindow();
            _scaleDetector = new ScaleGestureDetector(this.Context, this);
            _gestureDetector = new GestureDetector(this.Context, this);
        }
        public PlotModel ActualModel {
            get { return Model; }
        }

        public void GetAxesFromPoint(ScreenPoint pt, out Axis xaxis, out Axis yaxis) {
            if (this.ActualModel != null) {
                this.ActualModel.GetAxesFromPoint(pt, out xaxis, out yaxis);
            } else {
                xaxis = null;
                yaxis = null;
            }
        }

        public Series GetSeriesFromPoint(ScreenPoint pt, double limit = 100) {
            return this.ActualModel != null ? this.ActualModel.GetSeriesFromPoint(pt, limit) : null;
        }

        public void HideTracker() {
            //
        }

        public void HideZoomRectangle() {
            //
        }

        public void InvalidatePlot(bool updateData)
        {
            lock (this.invalidateLock)
            {
                this.isModelInvalidated = true;
                this.updateDataFlag = this.updateDataFlag || updateData;
            }

            this.Invalidate();
        }

        public void Pan(Axis axis, ScreenPoint ppt, ScreenPoint cpt) {
            axis.Pan(ppt, cpt);
            this.InvalidatePlot(false);
        }

        public void RefreshPlot(bool updateData = true) {
            UpdateModelAndVisuals(updateData);
        }

        public void Reset(Axis axis) {
            axis.Reset();
        }

        public void SetCursorType(CursorType cursorType) {
            /*
            switch (cursorType)
            {
                case CursorType.Pan:
                    this.Cursor = this.PanCursor;
                    break;
                case CursorType.ZoomRectangle:
                    this.Cursor = this.ZoomRectangleCursor;
                    break;
                case CursorType.ZoomHorizontal:
                    this.Cursor = this.ZoomHorizontalCursor;
                    break;
                case CursorType.ZoomVertical:
                    this.Cursor = this.ZoomVerticalCursor;
                    break;
                default:
                    this.Cursor = Cursors.Arrow;
                    break;
            }
            */
        }

        public void ShowTracker(TrackerHitResult trackerHitResult) {
            //throw new System.NotImplementedException();
        }

        public void ShowZoomRectangle(OxyRect r) {
            //throw new System.NotImplementedException();
        }

        public void Zoom(Axis axis, double p1, double p2) {
            axis.Zoom(p1, p2);
            this.RefreshPlot(false);
        }

        public void ZoomAt(Axis axis, double factor, double x) {
            if (double.IsNaN(x))
            {
                double sx = (axis.Transform(axis.ActualMaximum) + axis.Transform(axis.ActualMinimum)) * 0.5;
                x = axis.InverseTransform(sx);
            }

            axis.ZoomAt(factor, x);
        }

        private void UpdateModelAndVisuals(bool updateData = true) {
            this.UpdateModel(updateData);
            this.UpdateVisuals();
        }

        private void UpdateModel(bool updateData) {
            this.ActualModel.Update(updateData);
        }

        private void UpdateVisuals() {
            this.Invalidate();
        }

        public TouchablePlotView(Context context, IAttributeSet attrs) :
            base(context, attrs)
        {
            Initialize();
        }

        public TouchablePlotView(Context context, IAttributeSet attrs, int defStyle) :
            base(context, attrs, defStyle)
        {
            Initialize();
        }

        private void Initialize()
        {
        }

        private readonly object renderingLock = new object();
        private readonly object modelLock = new object();

        protected override void OnDraw(Canvas canvas)
        {
            base.OnDraw(canvas);
            canvas.DrawColor(Color.White);

            lock (this.invalidateLock)
            {
                if (this.isModelInvalidated)
                {
                    if (this.model != null)
                    {
                        this.model.Update(this.updateDataFlag);
                        this.updateDataFlag = false;
                    }

                    this.isModelInvalidated = false;
                }
            }

            lock (this.renderingLock)
            {
                if (this.model != null)
                {
                    var rc = new CanvasRenderContext(canvas);
                    this.model.Render(rc);
                }
            }
            /*
            canvas.DrawColor(Color.White);

            using (var paint = new Paint())
            {
                paint.AntiAlias = true;
                paint.Color = new Color(0, 0, 0, 220);
                paint.StrokeWidth = 2;
                canvas.DrawLine(10f, 10f, canvas.Width - 10, h - 10, paint);
                canvas.DrawLine(canvas.Width - 10, 10f, 10, h - 10, paint);
                paint.TextSize = 24;
                paint.TextAlign = Paint.Align.Left;
                var textbounds = new Rect();
                paint.GetTextBounds("ABC", 0, 1, textbounds);
                var tw = paint.MeasureText("ABC");
                // canvas.DrawText(this.Text, 40, 200, paint);
                canvas.Translate(40, 40);
                canvas.DrawText("ABC=" + tw + "/" + textbounds, 0, 0, paint);
                canvas.Rotate(20);
                canvas.DrawText("ABC=" + tw, 0, 0, paint);
            }*/
        }

        #region Implementation of IOnScaleGestureListener

        public bool OnScale(ScaleGestureDetector detector) {
            float scale = detector.ScaleFactor;
            if (OnPlotZoomed != null)
                OnPlotZoomed(this, new PlotScaleEventArgs(scale));
            return false;
        }

        public class PlotScaleEventArgs : System.EventArgs {
            public float Scale { get; set; }
            public PlotScaleEventArgs(float scale) {
                Scale = scale;
            }
        }

        public event EventHandler<PlotScaleEventArgs> OnPlotZoomed;
       
        public bool OnScaleBegin(ScaleGestureDetector detector) {
            return true;
        }

        public void OnScaleEnd(ScaleGestureDetector detector) {}

        #endregion

        #region Implementation of IOnGestureListener

        public bool OnDown(MotionEvent e) {
            return false;
        }

        public bool OnFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
            return false;
        }

        public void OnLongPress(MotionEvent e) {
            //return false;
        }
        public class PlotScrollEventArgs : EventArgs {
            public float DistanceX { get; set; }
            public float DistanceY { get; set; }
            public PlotScrollEventArgs(float distanceX, float distanceY) {
                DistanceX = distanceX;
                DistanceY = distanceY;
            }
        }

        public event EventHandler<PlotScrollEventArgs> OnPlotScroll;
        public bool OnScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
            if (OnPlotScroll!=null)
                OnPlotScroll(this, new PlotScrollEventArgs(distanceX, distanceY));
            return false;
        }

        public void OnShowPress(MotionEvent e) {
            //
        }

        public bool OnSingleTapUp(MotionEvent e) {
            return false;
        }

        #endregion
    }

 

Jan 24, 2013 at 9:21 AM

That Looks damn good.

But how do I use that?

How can I enable the paning and Zooming? I see that the corresponding OnScale etc. are correctly called but they just Forward it to an Event handler which I now have to implement right?

 

Jan 24, 2013 at 11:49 AM

HI Tamlocar,

For Zooming, you add event handler for TouchablePlotView.OnPlotZoomed;  For Panning, you add event handler for TouchablePlotView.OnPlotScroll

eg, for panning:

protected override void OnCreate(Bundle bundle)
{
    _plotView = FindViewById(Resource.Id.plotview);
    _plotView.OnPlotScroll += (sender, args) => {
                var deltaX = -1*args.DistanceX;
                PanPlot(deltaX);
            };
}

private void PanPlot(float deltaX) {
            var axe = _plotView.Model.Axes[0];
            axe.Pan(deltaX);
            _plotView.Model.RefreshPlot(false);
}

Jan 24, 2013 at 12:12 PM

Yep, thx. I got the panning working.

But sooming is not working as expected. If I release the pinc-to-zoom gesture and zoom again the scale will be reset every time. Somehow the zoom function does not Keep the latest value.

        private void ZoomPlot(float delta)
        {
            var axeX = plotView.Model.Axes[0];            
            axeX.Zoom(delta);
            var axeY = plotView.Model.Axes[1];
            axeY.Zoom(delta);
            //plotView.Model.PlotControl = plotView;
            plotView.RefreshPlot(false);
        }

 

Jan 24, 2013 at 12:20 PM
Edited Jan 24, 2013 at 12:21 PM

Sorry, missed something in my last code snippet when formatting. It should be

protected override void OnCreate(Bundle bundle)
{
    _plotView = FindViewById<TouchablePlotView>(Resource.Id.plotview);
    _plotView.Model = ... // Setup your model here
    _plotView.Model.AttachPlotControl(_plotView); 
    _plotView.OnPlotScroll += (sender, args) => {
                var deltaX = -1*args.DistanceX;
                PanPlot(deltaX);
            };
}
Jan 24, 2013 at 12:49 PM

Yes, otherwiese it is crashing. I just called the RefreshPlot function directly on the view instead of the model.

Still could not get the zooming stuff to work. Adding or multiplying with the Axis.Scale does not work as it seems.

Jan 24, 2013 at 2:17 PM

I only use DateTimeAxis, and the zooming works for me. But I suppose the LinearAxis should works too.

May I suggest you try  Zoom(double x0, double x1) instead of Zoom(double newScale). My guess is there're some problem with Zoom(double newScale) method, but have not look deep inside.

Jan 25, 2013 at 11:38 AM

Hm, that does not seem to work eighter. As I have only the scaling factor available. If I put that one in into x0 and x1 it is acting very strange :)

Jan 29, 2013 at 2:17 AM

On vacation, dont have the code around. As I can recall, for using the Zoom(double x0, double x1), you should fetch the actualMinimum and actualMaximum from existing axis first, then use the zoom factor to calculate out your new minimum and maximum, then you use Zoom(newMimum, newMaxium) to get your desiring zooming. ( And you might want to clamp the zoom factor to prevent out of scale error )

Feb 24, 2013 at 7:29 AM
Tried that approach too, but with no luck either. The scaling seems to be extremely erratic then.
Apr 10, 2013 at 12:15 PM
I tried your code to pan but my chart juste move a bit and then do'nt move ever again...
Have your a solution for that?
Thanks.