Core Plot: How to present popover from a bar selec

2020-03-06 03:22发布

问题:

What I would like to accomplish

I am using Core Plot (1.1) to draw a bar chart and I would like to present a popover with further details below the bar which has been selected (tapped) by the user.

Code

My code looks like this:

- (void)barPlot:(CPTBarPlot *)plot barWasSelectedAtRecordIndex:(NSUInteger)idx {

    NSNumber *yValue = self.values[idx];
    NSLog(@"barWasSelectedAtRecordIndex x: %i, y: %@",idx,yValue);

    NSDecimal plotPoint[2];
    NSNumber *plotXvalue = [self numberForPlot:plot
                                         field:CPTScatterPlotFieldX
                                   recordIndex:idx];
    plotPoint[CPTCoordinateX] =
        CPTDecimalFromFloat(plotXvalue.floatValue);

    NSNumber *plotYvalue = [self numberForPlot:plot
                                         field:CPTScatterPlotFieldY
                                   recordIndex:idx];

    plotPoint[CPTCoordinateY] =
        CPTDecimalFromFloat(plotYvalue.floatValue); 

    CPTXYPlotSpace *plotSpace =
        (CPTXYPlotSpace *)graph.defaultPlotSpace;
    CGPoint dataPoint =
        [plotSpace plotAreaViewPointForPlotPoint:plotPoint];
    NSLog(@"datapoint (CGPoint) coordinates tapped: %@",
          NSStringFromCGPoint(dataPoint));

    GrowthChartInfoTableViewController *infoViewController =
        [self.storyboard instantiateViewControllerWithIdentifier:
         @"GrowthChartInfo"];

    self.popover = [[UIPopoverController alloc]
                    initWithContentViewController:infoViewController];
    self.popover.popoverContentSize = CGSizeMake(320, 300);
    self.popover.delegate = self;
    CGRect popoverAnchor =
            CGRectMake(dataPoint.x + graph.paddingLeft,
                       dataPoint.y - graph.paddingTop + graph.paddingBottom,
                       (CGFloat)1.0f, (CGFloat)1.0f);

    [self.popover presentPopoverFromRect:popoverAnchor
                                  inView:self.view
                permittedArrowDirections:UIPopoverArrowDirectionAny
                                animated:YES];
}

My problem

The y-position of the popover view controller is incorrect, it is different for each bar and for the first bar, which has a negative y-value, it is presented at the upper limit of the bar instead the lower limit and hides the bar. The x-position seems to be correct.

Please help

Any ideas on why my code does not work as expected?

Thank you!

Edit

Based on Eric's answer I have updated my code as follows:

- (void)barPlot:(CPTBarPlot *)plot barWasSelectedAtRecordIndex:(NSUInteger)idx {

    NSNumber *plotXvalue = [self numberForPlot:plot
                                         field:CPTScatterPlotFieldX
                                   recordIndex:idx];
    NSNumber *plotYvalue = [self numberForPlot:plot
                                         field:CPTScatterPlotFieldY
                                   recordIndex:idx];

    CPTXYPlotSpace *plotSpace = (CPTXYPlotSpace *)graph.defaultPlotSpace;

    CGPoint cgPlotPoint =
        CGPointMake(plotXvalue.floatValue,
                    plotYvalue.floatValue);
    CGPoint cgPlotAreaPoint =
        [graph convertPoint:cgPlotPoint toLayer:graph.plotAreaFrame.plotArea];

    NSDecimal plotAreaPoint[2];
    plotAreaPoint[CPTCoordinateX] =
        CPTDecimalFromFloat(cgPlotAreaPoint.x);
    plotAreaPoint[CPTCoordinateY] =
        CPTDecimalFromFloat(cgPlotAreaPoint.y);

    CGPoint dataPoint = [plotSpace


           plotAreaViewPointForPlotPoint:plotAreaPoint];
        NSLog(@"datapoint (CGPoint) coordinates tapped: %@",NSStringFromCGPoint(dataPoint));

GrowthChartInfoTableViewController *infoViewController = [self.storyboard 
   instantiateViewControllerWithIdentifier:@"GrowthChartInfo"];



    self.popover = nil;
        self.popover = [[UIPopoverController alloc]


      initWithContentViewController:infoViewController];
    self.popover.popoverContentSize = CGSizeMake(250, 200);
    self.popover.delegate = self;
    CGRect popoverAnchor =
        CGRectMake(dataPoint.x + graph.paddingLeft,
                   dataPoint.y - graph.paddingTop + graph.paddingBottom,
                   (CGFloat)1.0f, (CGFloat)1.0f);

    [self.popover presentPopoverFromRect:popoverAnchor
        inView:self.view
        permittedArrowDirections:UIPopoverArrowDirectionUp
        animated:YES];
}

However, the results I am getting are more erroneous than before. I believe I must have misunderstand something. Here are some of the results of the NSLog commands:

barWasSelectedAtRecordIndex x: 0, y: -0.03920931548773198848
datapoint (CGPoint) coordinates tapped: {-6388.88, -67024.8}

barWasSelectedAtRecordIndex x: 2, y: 0.174494288286651392
datapoint (CGPoint) coordinates tapped: {-6073.38, -66790}

barWasSelectedAtRecordIndex x: 2, y: 0.174494288286651392
datapoint (CGPoint) coordinates tapped: {-6073.38, -66790}

barWasSelectedAtRecordIndex x: 0, y: -0.03920931548773198848
datapoint (CGPoint) coordinates tapped: {-6388.88, -67024.8}

It seems that the conversion from the bar coordinates to the view coordinates is completely wrong in my code.

Edit 2

Thank you again for your code sample, Eric!

I have updated my code and tested it with the following options (y-value = 0: plotPoint[CPTCoordinateY] = CPTDecimalFromFloat(0.0f), which results in an anchor point for the popover whose y-value seems to be incorrect (please refer to the following screenshot). The anchor point's y-value should be equivalent to the position of 0 on the y-axis.

Then I tried to set the y-value of the anchor point to the current y-value of the data. (plotPoint[CPTCoordinateY] = plotYvalue.decimalValue). This results in an anchor point which is close to the y-axis's 0 value instead of being close to the top of the bar. However, the y-value changes a little bit for each company (x-value) indicating that there might be a problem with the shift of the y-values:

I also tried the additional conversion which Eric suggested dataPoint = [self.view convertPoint:dataPoint fromView:graph.hostingView], but this did not change the results. In addition, I tried to add - graph.paddingTop + graph.paddingBottom to the final y-values, without success.

I would appreciate any ideas on what my error might be.

Thank you!

回答1:

The -plotAreaViewPointForPlotPoint: method returns a point in the plot area coordinate system. You need to convert that to the graph's coordinate system:

[graph convertPoint:dataPoint fromLayer:graph.plotAreaFrame.plotArea];

The graph and its hosting view share a coordinate system. If self.view is not the hosting view, you will need to convert the coordinates to its coordinate system from the hosting view.


A more complete code sample:

NSDecimal plotPoint[2];
NSNumber *plotXvalue = [self numberForPlot:plot
                                     field:CPTScatterPlotFieldX
                               recordIndex:idx];
plotPoint[CPTCoordinateX] = plotXvalue.decimalValue;

NSNumber *plotYvalue = [self numberForPlot:plot
                                     field:CPTScatterPlotFieldY
                               recordIndex:idx];
plotPoint[CPTCoordinateY] = plotYvalue.decimalValue; 

CPTXYPlotSpace *plotSpace = (CPTXYPlotSpace *)graph.defaultPlotSpace;

// convert from data coordinates to plot area coordinates
CGPoint dataPoint = [plotSpace plotAreaViewPointForPlotPoint:plotPoint];

// convert from plot area coordinates to graph (and hosting view) coordinates
dataPoint = [graph convertPoint:dataPoint fromLayer:graph.plotAreaFrame.plotArea];

// convert from hosting view coordinates to self.view coordinates (if needed)
dataPoint = [self.view convertPoint:dataPoint fromView:hostingView];

NSLog(@"barWasSelectedAtRecordIndex x: %@, y: %@", plotXvalue, plotYvalue);
NSLog(@"datapoint coordinates tapped: %@", NSStringFromCGPoint(dataPoint));