Search code examples
iosobjective-ccashapelayercgpath

SHGraphLineView remove a plot


I'm using this class called SHLineGraphView and it's working good, but I have a problem that they didn't implemented a methods for removing a plot. Now I have to do it, but I can't quite figure the right way to do.

This is how they drawing the plot:

- (void)addPlot:(SHPlot *)newPlot;
{
  if(nil == newPlot) {
    return;
  }

  if(_plots == nil){
    _plots = [NSMutableArray array];
  }
  [_plots addObject:newPlot];
}

- (void)drawPlot:(SHPlot *)plot {

  NSDictionary *theme = plot.plotThemeAttributes;

  //
  CAShapeLayer *backgroundLayer = [CAShapeLayer layer];
  backgroundLayer.frame = self.bounds;
  backgroundLayer.fillColor = ((UIColor *)theme[kPlotFillColorKey]).CGColor;
  backgroundLayer.backgroundColor = [UIColor clearColor].CGColor;
  [backgroundLayer setStrokeColor:[UIColor clearColor].CGColor];
  [backgroundLayer setLineWidth:((NSNumber *)theme[kPlotStrokeWidthKey]).intValue];

  CGMutablePathRef backgroundPath = CGPathCreateMutable();

  //
  CAShapeLayer *circleLayer = [CAShapeLayer layer];
  circleLayer.frame = self.bounds;
  circleLayer.fillColor = ((UIColor *)theme[kPlotPointFillColorKey]).CGColor;
  circleLayer.backgroundColor = [UIColor clearColor].CGColor;
  [circleLayer setStrokeColor:((UIColor *)theme[kPlotPointFillColorKey]).CGColor];
  [circleLayer setLineWidth:((NSNumber *)theme[kPlotStrokeWidthKey]).intValue];

  CGMutablePathRef circlePath = CGPathCreateMutable();

  //
  CAShapeLayer *graphLayer = [CAShapeLayer layer];
  graphLayer.frame = self.bounds;
  graphLayer.fillColor = [UIColor clearColor].CGColor;
  graphLayer.backgroundColor = [UIColor clearColor].CGColor;
  [graphLayer setStrokeColor:((UIColor *)theme[kPlotStrokeColorKey]).CGColor];
  [graphLayer setLineWidth:((NSNumber *)theme[kPlotStrokeWidthKey]).intValue];

  CGMutablePathRef graphPath = CGPathCreateMutable();

  double yRange = [_yAxisRange doubleValue]; // this value will be in dollars
  double yIntervalValue = yRange / INTERVAL_COUNT;

  //logic to fill the graph path, ciricle path, background path.
  [plot.plottingValues enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
    NSDictionary *dic = (NSDictionary *)obj;

    __block NSNumber *_key = nil;
    __block NSNumber *_value = nil;

    [dic enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
      _key = (NSNumber *)key;
      _value = (NSNumber *)obj;
    }];

    int xIndex = [self getIndexForValue:_key forPlot:plot];

    //x value
    double height = self.bounds.size.height - BOTTOM_MARGIN_TO_LEAVE;
    double y = height - ((height / ([_yAxisRange doubleValue] + yIntervalValue)) * [_value doubleValue]);
    (plot.xPoints[xIndex]).x = ceil((plot.xPoints[xIndex]).x);
    (plot.xPoints[xIndex]).y = ceil(y);
  }];

  //move to initial point for path and background.
  CGPathMoveToPoint(graphPath, NULL, _leftMarginToLeave, plot.xPoints[0].y);
  CGPathMoveToPoint(backgroundPath, NULL, _leftMarginToLeave, plot.xPoints[0].y);

  int count = _xAxisValues.count;
  for(int i=0; i< count; i++){
    CGPoint point = plot.xPoints[i];
    CGPathAddLineToPoint(graphPath, NULL, point.x, point.y);
    CGPathAddLineToPoint(backgroundPath, NULL, point.x, point.y);
    CGPathAddEllipseInRect(circlePath, NULL, CGRectMake(point.x - 2.5, point.y - 2.5, 5, 5));
  }

  //move to initial point for path and background.
  CGPathAddLineToPoint(graphPath, NULL, _leftMarginToLeave + PLOT_WIDTH, plot.xPoints[count -1].y);
  CGPathAddLineToPoint(backgroundPath, NULL, _leftMarginToLeave + PLOT_WIDTH, plot.xPoints[count - 1].y);

  //additional points for background.
  CGPathAddLineToPoint(backgroundPath, NULL, _leftMarginToLeave + PLOT_WIDTH, self.bounds.size.height - BOTTOM_MARGIN_TO_LEAVE);
  CGPathAddLineToPoint(backgroundPath, NULL, _leftMarginToLeave, self.bounds.size.height - BOTTOM_MARGIN_TO_LEAVE);
  CGPathCloseSubpath(backgroundPath);

  backgroundLayer.path = backgroundPath;
  graphLayer.path = graphPath;
  circleLayer.path = circlePath;

  //animation
  CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
  animation.duration = 1;
  animation.fromValue = @(0.0);
  animation.toValue = @(1.0);
  [graphLayer addAnimation:animation forKey:@"strokeEnd"];

  backgroundLayer.zPosition = 0;
  graphLayer.zPosition = 1;
  circleLayer.zPosition = 2;

  [self.layer addSublayer:graphLayer];
  [self.layer addSublayer:circleLayer];
  [self.layer addSublayer:backgroundLayer];

    NSUInteger count2 = _xAxisValues.count;
    for(int i=0; i< count2; i++){
        CGPoint point = plot.xPoints[i];
        UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
        btn.backgroundColor = [UIColor clearColor];
        btn.tag = i;
        btn.frame = CGRectMake(point.x - 20, point.y - 20, 40, 40);
        [btn addTarget:self action:@selector(clicked:) forControlEvents:UIControlEventTouchUpInside];
        objc_setAssociatedObject(btn, kAssociatedPlotObject, plot, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

    [self addSubview:btn];
    }
}

And this is what I've been added, but just can't figure out how to actually remove the plot:

- (void)removePlot:(SHPlot *)plot
{
    if (nil == plot) {
        return;
    }

    if (_plots == nil) {
        return;
    }

    [_plots removeObject:plot];
    [self removePlotFromView:plot];
}

Solution

  • So, by @Tejvansh Singh Chhabra's answer I managed to find a solution:

    First of all, I've added these 2 new methods to SHGraphLineView.h:

    /**
     *  this methods will remove a plot from the graph.
     *
     *  @param plot the Plot that you want to remove from the Graph.
     */
    - (void)removePlot:(SHPlot *)plot;
    
    
    /**
     *  this methods will draw a new plot on the graph.
     *
     *  @param plot the Plot that you want to draw on the Graph.
     */
    - (void)drawNewPlot:(SHPlot*)plot;
    

    And implement them on the main file:

    - (void)removePlot:(SHPlot *)plot
    {
        if (plot == nil) {
            return;
        }
        else if (_plots == nil) {
            return;
        }
    
        for (CALayer *layer in [self.layer.sublayers copy])
        {
            if ([layer valueForKey:@"PLTag"] != nil)
            {
                NSString *tag = [layer valueForKey:@"PLTag"];
                NSString *plotIndex = [NSString stringWithFormat:@"%ld", (long)plot.plotIndex];
                if ([tag isEqualToString:plotIndex])
                {
                    [layer removeFromSuperlayer];
                }
            }
        }
    
        [_plots removeObject:plot];
    }
    
    - (void)drawNewPlot:(SHPlot*)plot
    {
        if (nil == plot) {
            return;
        }
    
        if (_plots == nil) {
            _plots = [NSMutableArray array];
        }
    
        [_plots addObject:plot];
        [self xPointWithoutDrawingForPlot:plot];
        [self drawPlot:plot];
    }
    

    Now there is another method that create the x point for the plot, but without the labels and all the other this that are drawing with it when you first build the graph:

    - (void)xPointWithoutDrawingForPlot:(SHPlot*)plot
    {
        int xIntervalCount = _xAxisValues.count;
        double xIntervalInPx = PLOT_WIDTH / _xAxisValues.count;
    
        //initialize actual x points values where the circle will be
        plot.xPoints = calloc(sizeof(CGPoint), xIntervalCount);
    
        for(int i=0; i < xIntervalCount; i++)
        {
            CGPoint currentLabelPoint = CGPointMake((xIntervalInPx * i) + _leftMarginToLeave, self.bounds.size.height - BOTTOM_MARGIN_TO_LEAVE);
            CGRect xLabelFrame = CGRectMake(currentLabelPoint.x , currentLabelPoint.y, xIntervalInPx, BOTTOM_MARGIN_TO_LEAVE);
            plot.xPoints[i] = CGPointMake((int) xLabelFrame.origin.x + (xLabelFrame.size.width /2) , (int) 0);
        }
    }
    

    Another important thing is to add a valueForKey to the CAShapeLayer objects of the plot at first draw, we need to add to this method:

    - (void)drawPlot:(SHPlot *)plot;
    

    These 3 lines:

    [graphLayer setValue:[NSString stringWithFormat:@"%ld", (long)plot.plotIndex]  forKey:@"PLTag"];
    [circleLayer setValue:[NSString stringWithFormat:@"%ld", (long)plot.plotIndex] forKey:@"PLTag"];
    [backgroundLayer setValue:[NSString stringWithFormat:@"%ld", (long)plot.plotIndex] forKey:@"PLTag"];
    

    One last thing, to SHPlot class we need to add an NSInteger that will indicate the index of this specific plot, and you need to set it when you first build the plot

    SHPlot.h

    /**
     *  index for plot for identification
     */
    @property (assign, nonatomic) NSInteger plotIndex;
    

    Enjoy! :)