MPAndroidChart icon indicating copy to clipboard operation
MPAndroidChart copied to clipboard

PieChart touch area for rotation is outside of chart

Open estrnod opened this issue 3 years ago • 3 comments

Summary

I have made 3 concentric pie charts - the inside one (no hole) responds to touches on the chart. The middle and outer ones (with successively larger holes to expose the inner one) have successively further offsets for touch - touching on them directly does not rotate them; I have to trial-and-error to find the place outside of them that will respond to touch.)

Expected Behavior

I would expect the pie charts with holes to respond to touch on the slices themselves.

Possible Solution Is it possible that the hole is creating a corresponding offset for the touch area? Is there some way to rein this "slop" back in? Some setting that affects the offset of the touch area?

Device (please complete the following information):

System Information:  
 manufacturer: onn 
 model: 100026191 
 system version: 30 
 system versionRelease: 11

and

System Information:  
 manufacturer: KYOCERA 
 model: E6820TM 
 system version: 23 
 system versionRelease: 6.0.1

Using library version implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0'

Additional Context

My code for this is not a small test-case to post publicly, but I have downloaded another's example of concentric pie charts, compiled and run it with the same result. His is here: https://github.com/elatoskinas/AndroidChartTests (using the "Pie Charts" example, you will note that even a single pie chart with a hole exhibits this offset touch area behavior.)

ADD A REWARD using Speed to SOLVE this issue QUICKLY and SUPPORT this project.

estrnod avatar Oct 25 '22 16:10 estrnod

I have a fix for this. It prevents touch or swipe, etc. events from being handled by the wrong pie chart, or outside of the pie chart the touch was on, with nested pie charts. I am a newbie with doing anything to this project and it turned out that offsets had no bearing; the touch event coordinates were already scoped to the chart (so a lot of comments etc. are here in case more debugging is needed or can be removed.) It would be great if it could be incorporated into a release sometime. (In case it does not, I am adding another comment after this one to show how I overrode this listener in my code to the same effect.)

So, I added this method to PieRadarChartTouchListener:

    public boolean isFingerInPie(View view, MotionEvent event) {
        try {
            //int[] absoluteLocation =  new int[2];
            //view.getLocationOnScreen(absoluteLocation);
            //float xOffset = absoluteLocation[0];
            //float yOffset = absoluteLocation[1];
            float eventX = event.getX();
            float eventY = event.getY();
            //System.out.println("Offsets: " + xOffset + ", " + yOffset + "; event: " + event.getX() + ", " + event.getY());
            if (mChart instanceof PieChart) {
                MPPointF middlepoint = ((PieChart) mChart).getCenterCircleBox();
                float middleX = middlepoint.getX();
                float middleY = middlepoint.getY();
                double touchDistanceFromCenter =
                        Math.sqrt(Math.pow((eventX - middleX), 2) + Math.pow((eventY - middleY), 2));
                double outerRadius = Math.min(((PieChart)mChart).getCircleBox().width(), ((PieChart)mChart).getCircleBox().height()) / 2;
                //System.out.println("Outer radius: " + outerRadius);
                // if drawing hole is enabled and point is in hole, return false
                if (((PieChart) mChart).isDrawHoleEnabled()) {
                    double holeRadius = (.01 * ((PieChart) mChart).getHoleRadius()) * outerRadius;
                    //System.out.println("Touch distance from center: " + touchDistanceFromCenter + "; Hole radius: " + holeRadius + " based on percentage: " + ((PieChart) mChart).getHoleRadius());
                    if (touchDistanceFromCenter < holeRadius) {
                        //System.out.println("event " + eventX+", " + eventY + " point is inside hole with center " + middleX + ", " + middleY);
                        return false;
                    }
                }
                // see if point is outside of drawn pie chart
                if (touchDistanceFromCenter > outerRadius) {
                    //System.out.println("event " + eventX +", " + eventY + " point is outside of chart with radius " + outerRadius);
                    return false;
                }
                MPPointF.recycleInstance(middlepoint);
            }
        } catch (Exception e) {
           e.printStackTrace();
        }
        return true;
    }

And added this invocation immediately at the beginning of PieRadarChartTouchListener.onTouch(View v, MotionEvent event) :

        if (!isFingerInPie(v, event)) {
            return false;
        }

estrnod avatar Nov 12 '22 17:11 estrnod

In case this helps other users, I was able to override and set the listener in each of my nested pie charts in my application. This works with the current release version implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0' .

Added inner class in my Activity class (or make it an external class file, if used for more than one set of nested charts) to extend the MPAndroidCharts PieRadarChartTouchListener:

    static class NestedPieChartListener extends PieRadarChartTouchListener {

        public NestedPieChartListener(PieRadarChartBase<?> chart) {
            super(chart);
        }

        public boolean isFingerInPie(View view, MotionEvent event) {
            try {
                //int[] absoluteLocation =  new int[2];
                //view.getLocationOnScreen(absoluteLocation);
                //float xOffset = absoluteLocation[0];
                //float yOffset = absoluteLocation[1];
                float eventX = event.getX();
                float eventY = event.getY();
                //System.out.println("Offsets: " + xOffset + ", " + yOffset + "; event: " + event.getX() + ", " + event.getY());
                if (mChart instanceof PieChart) {
                    MPPointF middlepoint = ((PieChart) mChart).getCenterCircleBox();
                    float middleX = middlepoint.getX();
                    float middleY = middlepoint.getY();
                    double touchDistanceFromCenter =
                            Math.sqrt(Math.pow((eventX - middleX), 2) + Math.pow((eventY - middleY), 2));
                    double outerRadius = Math.min(((PieChart)mChart).getCircleBox().width(), ((PieChart)mChart).getCircleBox().height()) / 2;
                    //System.out.println("Outer radius: " + outerRadius);
                    // if drawing hole is enabled and point is in hole, return false
                    if (((PieChart) mChart).isDrawHoleEnabled()) {
                        double holeRadius = (.01 * ((PieChart) mChart).getHoleRadius()) * outerRadius;
                        //System.out.println("Touch distance from center: " + touchDistanceFromCenter + "; Hole radius: " + holeRadius + " based on percentage: " + ((PieChart) mChart).getHoleRadius());
                        if (touchDistanceFromCenter < holeRadius) {
                            //System.out.println("event " + eventX+", " + eventY + " point is inside hole with center " + middleX + ", " + middleY);
                            return false;
                        }
                    }
                    // see if point is outside of drawn pie chart
                    if (touchDistanceFromCenter > outerRadius) {
                        //System.out.println("event " + eventX +", " + eventY + " point is outside of chart with radius " + outerRadius);
                        return false;
                    }
                    MPPointF.recycleInstance(middlepoint);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            return true;
        }

        @SuppressLint("ClickableViewAccessibility")
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            // Also check if the pie itself is being touched, rather than the entire chart area
            if (!isFingerInPie(v, event)) {
                return false;
            }
            return super.onTouch(v, event);
        }
    }

... and set it as the onTouchListener for each of the nested charts in the onCreate() method:


        // inner chart
        holdingsCostChart = findViewById(R.id.sector_holdings_cost_chart);
        holdingsCostChart.setOnTouchListener(new NestedPieChartListener(holdingsCostChart));
        holdingsCostChart.setDrawHoleEnabled(false);
        holdingsCostChart.animate();
...
        // middle chart
        holdingsValueChart = findViewById(R.id.sector_holdings_value_chart);
        holdingsValueChart.setOnTouchListener(new NestedPieChartListener(holdingsValueChart));
        holdingsValueChart.setHoleRadius(80.0f);
        holdingsValueChart.setTransparentCircleAlpha(0);
        holdingsValueChart.setTransparentCircleRadius(80.0f);
        holdingsValueChart.animate();

...
        // outer chart
        percentageEarningsChart = findViewById(R.id.sector_percentage_earnings_chart);
        percentageEarningsChart.setOnTouchListener(new NestedPieChartListener(percentageEarningsChart));
        percentageEarningsChart.setHoleRadius(80.0f);
        percentageEarningsChart.setTransparentCircleRadius(80.0f);
        percentageEarningsChart.setTransparentCircleAlpha(0);
        percentageEarningsChart.animate();

estrnod avatar Nov 12 '22 18:11 estrnod

I had the same requirement and patched the library to support it: https://github.com/mtotschnig/MPAndroidChart/commit/42e15b323961f1d5e4dedba6f0fd81cef16cc77c

mtotschnig avatar Jun 05 '24 06:06 mtotschnig