核心图 x 轴标签不可见 - 边界矩形?

core-plot x-axis label not visible - bounding rects?

使用垂直条形图示例作为开始,我创建了一个图表,其 y 值为 0..1,x 值为 1..n,但为我的数据源的 CPTBarPlotFieldBarLocation 单元格提供的 x 标签索引没有出现;我不使用 y 轴标签。

我怀疑我在剪辑它们。有没有办法像在旧的 IB 中那样绘制各种图形区域的边界矩形?

x 轴绘图标签是示例中的非数字字符串。我打破了什么?这是我的设置

//
//  InfoBarView.m

#import "InfoBarView.h"

//  Item index to color mapping for legend symbols not item related
static NSUInteger fldMapping[] = { 5, 6, 7, 0 };

static const BOOL kUseHorizontalBars = NO;

static NSArray <__kindof NSString *> const* kTargets = nil;
static NSArray <__kindof NSString *> const* kGetters = nil;

@interface BarChart()
@property (nonatomic, readwrite, strong, nullable) CPTPlotSpaceAnnotation * symbolTextAnnotation;
@end

@implementation BarChart
@synthesize symbolTextAnnotation;
@synthesize plotData;

+ (void)initialize
{
    kTargets = [@[ @"grpA" , @"grpB", @"grpC", @"grpD" ] retain];
    kGetters = [@[ @"fldA", @"fldB", @"fldC", @"fldD" ] retain];
}

- (void)awakeFromNib
{
    //  Get our orig scaling
    [super awakeFromNib];
}

- (void)killGraph
{
    if ( self.graphs.count )
    {
        CPTGraph * graph = self.defaultGraph;

        CPTPlotSpaceAnnotation *annotation = self.symbolTextAnnotation;
        if ( annotation )
        {
            [graph.plotAreaFrame.plotArea removeAnnotation:annotation];
            self.symbolTextAnnotation = nil;
        }
    }

    [super killGraph];
}

- (void)generateData
{
    InfoBarView * barView = (id)[[self defaultLayerHostingView] superview];
    InfoController * controller = barView.controller;
    NSUInteger rndx = controller.barDataRanking;
    NSArray * rankings = @[ @"Rank", @"Runs" ];
    GroupItem * group = controller.group;
    CPTGraph * graph = [self defaultGraph];

    CPTPlotSpaceAnnotation * annotation = self.symbolTextAnnotation;
    if ( annotation )
    {
        if (graph)
        {
            [graph.plotAreaFrame.plotArea removeAnnotation:annotation];
            self.symbolTextAnnotation = nil;
        }
    }

    //  Update X title with new controller index
    if (graph)
    {
        graph.title = [NSString stringWithFormat:@"Past Performances by %@", rankings[rndx]];
    }

    //  Figure out our rank selector
    NSString * rankSelectorName = [NSString stringWithFormat:@"%@%@Compare:",
                                   kTargets[controller.barDataIndex],
                                   rankings[rndx]];
    SEL dataRanking = NSSelectorFromString(rankSelectorName);

    self.plotData = (id)[group.items sortedArrayUsingSelector:dataRanking];
}

- (void)renderInGraphHostingView:(nonnull CPTGraphHostingView *)hostingView
                       withTheme:(nullable CPTTheme *)theme
                        animated:(BOOL)animated
{
#if TARGET_OS_SIMULATOR || TARGET_OS_IPHONE
    CGRect bounds = hostingView.bounds;
#else
    CGRect bounds = NSRectToCGRect(hostingView.bounds);
#endif
    InfoController * controller = ((InfoBarView *)hostingView.superview).controller;
    CPTGraph * graph = [[[CPTXYGraph alloc] initWithFrame:bounds] autorelease];
    NSString * titles = @"fldA  fldB  fldC  fldD";
    GroupItem * group = controller.group;
    CPTBarPlot * barPlot;

    //  Initially we default by rank
    self.title = @"Past Performances by Rank";

    [self addGraph:graph toHostingView:hostingView];

    graph.plotAreaFrame.masksToBorder = NO;
    if ( kUseHorizontalBars )
    {
        graph.plotAreaFrame.paddingBottom += self.titleSize;
    }
    else
    {
        graph.plotAreaFrame.paddingLeft += self.titleSize;
    }

    //  Add plot space for bar charts
    CPTXYPlotSpace * barPlotSpace = (CPTXYPlotSpace *)graph.defaultPlotSpace;
    [barPlotSpace setScaleType:CPTScaleTypeCategory forCoordinate:CPTCoordinateX];
    if ( kUseHorizontalBars )
    {
        barPlotSpace.xRange = [CPTPlotRange plotRangeWithLocation:@(-10.0) length:@120.0];
        barPlotSpace.yRange = [CPTPlotRange plotRangeWithLocation:@(-1.0) length:@11.0];
    }
    else
    {
        barPlotSpace.xRange = [CPTPlotRange plotRangeWithLocation:@(-0.5) length:@(group.itemCount)];
        barPlotSpace.yRange = [CPTPlotRange plotRangeWithLocation:@(-0.05) length:@1.0];
    }
    barPlotSpace.allowsUserInteraction = YES;
    [graph addPlotSpace:barPlotSpace];
    barPlotSpace.delegate = self;

    // Create grid line styles
    CPTMutableLineStyle * majorGridLineStyle = [CPTMutableLineStyle lineStyle];
    majorGridLineStyle.lineWidth = 1.0;
    majorGridLineStyle.lineColor = [[CPTColor grayColor] colorWithAlphaComponent:0.75];

    CPTMutableLineStyle * minorGridLineStyle = [CPTMutableLineStyle lineStyle];
    minorGridLineStyle.lineWidth = 1.0;
    minorGridLineStyle.lineColor = [[CPTColor whiteColor] colorWithAlphaComponent:0.25];

    // Create axes
    CPTXYAxisSet * axisSet = (CPTXYAxisSet *)graph.axisSet;
    CPTXYAxis * x = axisSet.xAxis;
    {
        x.majorIntervalLength           = (kUseHorizontalBars ? @10.0 : @1.0);
        x.minorTicksPerInterval         = (kUseHorizontalBars ? 9 : 0);
        x.orthogonalPosition            = (kUseHorizontalBars ? @(-0.5) : @0.0);

        x.majorGridLineStyle            = majorGridLineStyle;
        x.minorGridLineStyle            = minorGridLineStyle;
        x.axisLineStyle                 = nil;
        x.majorTickLineStyle            = nil;
        x.minorTickLineStyle            = nil;

        x.labelOffset = self.titleSize * CPTFloat(2.5);
        if ( kUseHorizontalBars )
        {
            x.visibleRange   = [CPTPlotRange plotRangeWithLocation:@0.0 length:@10.0];
            x.gridLinesRange = [CPTPlotRange plotRangeWithLocation:@(-0.5) length:@(group.itemCount)];
        }
        else
        {
            x.visibleRange   = [CPTPlotRange plotRangeWithLocation:@(-0.5) length:@(group.itemCount)];
            x.gridLinesRange = [CPTPlotRange plotRangeWithLocation:@0.0 length:@(1.0)];
        }
        x.plotSpace                     = barPlotSpace;
    }

    CPTXYAxis * y = axisSet.yAxis;
    {
        y.majorIntervalLength           = (kUseHorizontalBars ? @1.0 : @10.0);
        y.minorTicksPerInterval         = (kUseHorizontalBars ? 0 : 9);
        y.orthogonalPosition            = (kUseHorizontalBars ? @0.0 : @(-0.5) );

        y.preferredNumberOfMajorTicks   = 8;
        y.majorGridLineStyle            = majorGridLineStyle;
        y.minorGridLineStyle            = minorGridLineStyle;
        y.axisLineStyle                 = nil;
        y.majorTickLineStyle            = nil;
        y.minorTickLineStyle            = nil;
        y.labelOffset                   = self.titleSize * CPTFloat(0.175);
        y.titleLocation                 = (kUseHorizontalBars ? @55.0 : @(group.itemCount-1));

        if ( kUseHorizontalBars )
        {
            y.visibleRange   = [CPTPlotRange plotRangeWithLocation:@(-0.5) length:@10.0];
            y.gridLinesRange = [CPTPlotRange plotRangeWithLocation:@0.0 length:@100.0];
        }
        else
        {
            y.visibleRange   = [CPTPlotRange plotRangeWithLocation:@(-0.5) length:@(group.itemCount)];
            y.gridLinesRange = [CPTPlotRange plotRangeWithLocation:@(-0.5) length:@(group.itemCount)];
        }
        y.title                         = titles;
        y.titleOffset                   = self.titleSize * CPTFloat(0.25);
        [barPlotSpace addCategory:title[seg] forCoordinate:CPTCoordinateY];
        y.plotSpace                     = barPlotSpace;
    }

    //  Set axes
    graph.axisSet.axes = @[x, y];

    //  Create a shared bar line style
    CPTMutableLineStyle * barLineStyle  = [[[CPTMutableLineStyle alloc] init] autorelease];
    barLineStyle.lineWidth = 1.0;
    barLineStyle.lineColor = [CPTColor whiteColor];

    //  Marshall Y axis into individual plot identifiers
    NSArray * title = [titles componentsSeparatedByString:@"  "];

    CPTMutableTextStyle * blackTextStyle = [CPTMutableTextStyle textStyle];
    blackTextStyle.color = [CPTColor blackColor];

    //  Create bar plot(s)
    for (int seg=0; seg<title.count; seg++)
    {
        barPlot = [[[CPTBarPlot alloc] init] autorelease];

        barPlot.lineStyle           = barLineStyle;
        barPlot.fill                = [CPTFill fillWithColor:[CPTColor colorAtIndex:seg]];
        barPlot.barBasesVary        = YES;
        barPlot.barCornerRadius     = 4.0;
        barPlot.barWidth            = @0.5;
        barPlot.barsAreHorizontal   = kUseHorizontalBars;
        barPlot.dataSource          = self;
        barPlot.delegate            = self;
        barPlot.identifier          = [NSString stringWithFormat:@"%@,%d",title[seg],seg],
        barPlot.labelTextStyle      = blackTextStyle;
        barPlot.labelOffset         = 0.0;
        [graph addPlot:barPlot toPlotSpace:barPlotSpace];
        NSLog(@"render:%@", barPlot.identifier);
    }

    //  Add X category per slot
    for (Item * item in plotData)
    {
        [barPlotSpace addCategory:item.numb forCoordinate:CPTCoordinateX];
    }
}

#pragma mark -
#pragma mark Plot Delegate

- (void)Plot:(nonnull CPTPlot *)plot dataLabelWasSelectedAtRecordIndex:(NSUInteger)index
{
//  NSString * barID = (NSString *)[[(id)plot.identifier componentsSeparatedByString:@","] lastObject];

    NSLog(@"Data label for '%@' was selected at record %d.", plot.identifier, (int)index);
}

- (CPTLayer *)dataLabelForPlot:(CPTPlot *)plot recordIndex:(NSUInteger)index;
{
    InfoBarView * barView = (id)[[self defaultLayerHostingView] superview];
    InfoController * controller = barView.controller;
    GroupItem * group = controller.group;

    int sndx = index % group.itemCount;
    Item * item = (Item *)[self.plotData objectAtIndex:sndx];

    CPTMutableTextStyle * textStyle = [CPTMutableTextStyle textStyle];
    textStyle.color = [CPTColor colorAtIndex:item.foreRGB];
    textStyle.textAlignment = CPTTextAlignmentCenter;
    textStyle.fontSize = 24;
    plot.labelOffset = 0;

    CPTMutableShadow * shadow = [CPTMutableShadow shadow];
    shadow.shadowColor = [CPTColor blackColor];//colorAtIndex:item.backRGB];
    shadow.shadowOffset = CGSizeMake(1,-1);
    shadow.shadowBlurRadius = 2.0f;
    plot.labelShadow = shadow;

    CPTTextLayer * textLayer = [[CPTTextLayer alloc] initWithText:item.numb.squish style:textStyle];

    return [textLayer autorelease];
}

- (void)barPlot:(CPTBarPlot *)plot barWasSelectedAtRecordIndex:(NSUInteger)index
{
    CGFloat floor = [[self valueForPlot:plot field:CPTBarPlotFieldBarBase recordIndex:index] doubleValue];
    CGFloat value = [[self valueForPlot:plot field:CPTBarPlotFieldBarTip recordIndex:index] doubleValue];
    CGFloat runs = 0;

    NSString * barID = (NSString *)[[(id)plot.identifier
                                     componentsSeparatedByString:@","] lastObject];
    InfoBarView * barView = (id)[[self defaultLayerHostingView] superview];
    InfoController * controller = barView.controller;
    GroupItem * group = controller.group;

    int fndx = [barID intValue];
    int sndx = index % group.itemCount;
    Item * item = (Item *)[self.plotData objectAtIndex:sndx];

    SEL dataItem = NSSelectorFromString(kTargets[controller.barDataIndex]);
    DataItem * data = [item performSelector:dataItem];
    runs = [[data fldD] doubleValue];

    NSLog(@"BarSelected %@ %@ record:%lu. base:%.2f value:%.2f",
          plot.identifier, data.name, index, floor, value);

    CPTGraph * graph = [self defaultGraph];

    CPTPlotSpaceAnnotation * annotation = self.symbolTextAnnotation;
    if ( annotation )
    {
        [graph.plotAreaFrame.plotArea removeAnnotation:annotation];
        self.symbolTextAnnotation = nil;
    }

    // Setup a style for the annotation
    CPTMutableTextStyle * hitAnnotationTextStyle = [CPTMutableTextStyle textStyle];
    hitAnnotationTextStyle.color    = [CPTColor colorAtIndex:fldMapping[fndx]];
    hitAnnotationTextStyle.fontSize = 16.0;
    hitAnnotationTextStyle.fontName = @"Helvetica-Bold";

    // Determine point of symbol in plot coordinates
    NSNumber * x = @(index);
    NSNumber * y = @(0.03 + (floor / runs));

    CPTNumberArray * anchorPoint = (kUseHorizontalBars ? @[y, x] : @[x, y]);

    // Add annotation
    // First make a string for the y value
    NSString * yString = [[NSNumberFormatter withFormat:@"#,###"] stringFromNumber:@(value)];

    // Now add the annotation to the plot area
    CPTPlotSpace * space = plot.plotSpace;
    if ( space )
    {
        CPTTextLayer * textLayer    = [[CPTTextLayer alloc] initWithText:yString style:hitAnnotationTextStyle];

        annotation                  = [[CPTPlotSpaceAnnotation alloc] initWithPlotSpace:space anchorPlotPoint:anchorPoint];
        annotation.contentLayer     = [textLayer autorelease];
        annotation.displacement     = CGPointMake(0.0, 0.0);
        self.symbolTextAnnotation   = annotation;

        [graph.plotAreaFrame.plotArea addAnnotation:[annotation autorelease]];
    }
}

#pragma mark Plot DataSource

- (NSUInteger)numberOfRecordsForPlot:(nonnull CPTPlot *)plot
{
    InfoBarView * barView = (id)[[self defaultLayerHostingView] superview];
    InfoController * controller = barView.controller;
    GroupItem * group = controller.group;

    return group.itemCount;
}

- (nullable id)numberForPlot:(nonnull CPTPlot *)plot field:(NSUInteger)fieldEnum recordIndex:(NSUInteger)index
{
    NSString * barID = (NSString *)[[(id)plot.identifier componentsSeparatedByString:@","] lastObject];
    InfoBarView * barView = (id)[[self defaultLayerHostingView] superview];
    InfoController * controller = barView.controller;
    GroupItem * group = controller.group;

    int fndx = [barID intValue];
    int sndx = index % group.itemCount;
    Item * item = (Item *)[self.plotData objectAtIndex:sndx];

    SEL dataItem = NSSelectorFromString(kTargets[controller.barDataIndex]);
    NSString * getName = kGetters[fndx];
    SEL getter = NSSelectorFromString(getName);
    SEL fldD = @selector(fldD);
    CGFloat runs, base=0, value=0;
    DataItem * data;
    id num=nil;

    //  First get which data data item we're after
    data = [item performSelector:dataItem];
    runs = [[data performSelector:fldD] floatValue];

    //  Now fetch its getter
    switch ( fieldEnum )
    {
        case CPTBarPlotFieldBarLocation:
            num = item.numb;
            break;

        case CPTBarPlotFieldBarTip:
            //  Build base for this tip
            for (int seg=0; seg < fndx; seg++)
            {
                value += [[data performSelector:NSSelectorFromString(kGetters[seg])] floatValue];
            }

            //  Add this 'tip' value unless it's the total
            if (fndx == 3)
                num = @(1.0f);
            else
            {
                value += [[data performSelector:getter] floatValue];
                num = @(value/runs);
            }
            break;

        case CPTBarPlotFieldBarBase:
            //  Calc base by sum prior fields
            for (int seg=0; seg < fndx; seg++)
            {
                base += [[data performSelector:NSSelectorFromString(kGetters[seg])] floatValue];
            }
            num = @(base/runs);
    }

    return num;
}

- (nullable id)valueForPlot:(nonnull CPTPlot *)plot field:(NSUInteger)fieldEnum recordIndex:(NSUInteger)index
{
    NSString * barID = (NSString *)[[(id)plot.identifier componentsSeparatedByString:@","] lastObject];
    InfoBarView * barView = (id)[[self defaultLayerHostingView] superview];
    InfoController * controller = barView.controller;
    GroupItem * group = controller.group;

    int fndx = [barID intValue];
    int sndx = index % group.itemCount;
    Item * item = (Item *)[self.plotData objectAtIndex:sndx];

    SEL dataItem = NSSelectorFromString(kTargets[controller.barDataIndex]);
    NSString * getName = kGetters[fndx];
    SEL getter = NSSelectorFromString(getName);
    SEL fldD = @selector(fldD);
    CGFloat runs, value=0;
    DataItem * data;
    id num=nil;

    //  First get which data block we're after
    data = [item performSelector:dataItem];
    runs = [[data performSelector:fldD] floatValue];

    //  Now fetch its getter
    switch ( fieldEnum )
    {
        case CPTBarPlotFieldBarLocation:
            num = item.numb.squish;
            break;

        case CPTBarPlotFieldBarTip:
            value = [[data performSelector:getter] floatValue];
            num = @(value);
            break;

        case CPTBarPlotFieldBarBase:
            //  Calc base by sum prior fields
            for (int seg=0; seg < fndx; seg++)
            {
                value += [[data performSelector:NSSelectorFromString(kGetters[seg])] floatValue];
            }
            num = @(value);
    }

    return num;
}

- (nullable NSString *)legendTitleForBarPlot:(nonnull CPTBarPlot *)plot recordIndex:(NSUInteger)index
{
    NSString * barID = (NSString *)[[(id)plot.identifier componentsSeparatedByString:@","] lastObject];
    int fndx = [barID intValue];

    return kGetters[fndx];
}

#pragma mark Plot Legend Delegate

- (void)legend:(CPTLegend *)legend legendEntryForPlot:(CPTPlot *)plot wasSelectedAtIndex:(NSUInteger)index
{
    NSLog(@"legend:%@ index:%lu", plot.identifier, index);
}

#pragma mark Plot Space Delegate

#pragma mark Plot Space Delegate

- (CPTPlotRange *)plotSpace:(CPTPlotSpace *)space
      willChangePlotRangeTo:(CPTPlotRange *)newRange
              forCoordinate:(CPTCoordinate)coordinate
{
    CGFloat newLocation = newRange.location.floatValue;
//  CGFloat newLength = newRange.length.floatValue;

    //  Range change to +2 point on data boundary
    if (CPTCoordinateX == coordinate)
    {
        if (newLocation < -0.5)
        {
            return [CPTPlotRange plotRangeWithLocation:@(-0.5) length:newRange.length];
        }
        else
        if (newLocation > (0.3))
        {
            return [CPTPlotRange plotRangeWithLocation:@(0.3) length:newRange.length];
        }
    }

    if (CPTCoordinateY == coordinate)
    {
        if (newLocation < (-0.1))
        {
            return [CPTPlotRange plotRangeWithLocation:@(-0.1) length:newRange.length];
        }
        else
        if (newLocation > (0.1))
        {
            return [CPTPlotRange plotRangeWithLocation:@(0.1) length:newRange.length];
        }
    }
//  CGRect chartBounds = self.defaultGraph.bounds;
//  NSLog(@"old:%@", NSStringFromRect(chartBounds));
//  NSLog(@"new:%@ %@", (coordinate == CPTCoordinateX ? @"X" : @"Y"),[newRange description]);
    return newRange;
}
@end

@implementation InfoBarView
@synthesize barChart;
@synthesize x_title;

- (void)awakeFromNib
{
    //  Get our orig scaling
    [super awakeFromNib];

    self.barChart = [[[BarChart alloc] init] autorelease];
}

- (void)drawRect:(NSRect)clip
{
    CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort];
    NSImage * webImage = controller.group.track.webImage;
    GroupItem * group = controller.group;
    NSRect rect = [self frame];

    //  Save state so we can restore later
    CGContextSaveGState(context);

    //  Draw track image as our view's background
    if (webImage)
    {
        [webImage drawInRect:rect fromRect:[webImage alignmentRect]
                   operation:NSCompositeSourceOver fraction:0.25];
    }

    if (group && !barChart.graphs.count)
    {
        [barChart renderInView:self withTheme:nil animated:YES];
    }

    CGContextRestoreGState(context);
}

@end

如果有可见的线索 - 绘制边界矩形,我想我们很多人都可以从中受益(我想念来自 IB circa v3 的那些)。

语句x.labelFormatter = nil;消除了自动轴标签。如果您提供标签格式化程序和标签文本样式,轴将根据标签策略自动创建刻度和标签。