text: // https://github.com/CoderWQYao/WQCharts-iOS
  //
  // BizChartVC.m
  // WQCharts
  //
  // Created by WQ.Yao on 2020/01/02.
  // Copyright (c) 2020年 WQ.Yao All rights reserved.
  //
  
  #import "BizChartVC.h"
  
  @interface BizChartData: NSObject
  
  @property (nonatomic) CGFloat lineValue;
  @property (nonatomic) CGFloat lineValue2;
  @property (nonatomic) CGFloat barValue;
  
  @end
  
  @implementation BizChartData
  
  @end
  
  
  @interface BizChartItem: NSObject
  
  @property (nonatomic, strong) NSArray<id<WQChart>>* charts;
  @property (nonatomic, strong) NSArray<BizChartData*>* datas;
  
  @end
  
  @implementation BizChartItem
  
  - (instancetype)initWithCharts:(NSArray<id<WQChart>>*)charts datas:(NSArray<BizChartData*>*)datas
  {
      self = [super init];
      if (self) {
          self.charts = charts;
          self.datas = datas;
      }
      return self;
  }
  
  @end
  
  @interface BizChartVC () <WQChartAnimatable>
  
  @property (nonatomic, strong) WQBizChartView* chartView;
  
  @property (nonatomic) CGFloat barWidth;
  @property (nonatomic) CGFloat barWidthHalf;
  @property (nonatomic) NSInteger dataCount;
  @property (nonatomic) CGFloat maxDataValue;
  @property (nonatomic) UIEdgeInsets clipToRectInset;
  
  @property (nonatomic, strong) NSMutableArray<BizChartItem*>* items;
  @property (nonatomic, strong) NSMutableArray<WQBarGraphic*>* barGraphics;
  @property (nonatomic, strong) NSValue* touchLocation;
  
  @property (nonatomic, strong) NSValue* clipRect;
  @property (nonatomic, strong) WQChartCGRectTween* clipRectTween;
  
  @end
  
  @implementation BizChartVC
  
  - (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
      if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
          self.barWidth = 5;
          self.barWidthHalf = self.barWidth / 2;
          self.dataCount = 1000;
          self.maxDataValue = 1000;
          self.clipToRectInset = UIEdgeInsetsMake(0, -self.barWidthHalf, 0, -self.barWidthHalf);
      }
      return self;
  }
  
  - (void)viewWillLayoutSubviews {
      [super viewWillLayoutSubviews];
      self.chartView.frame = self.chartContainer.bounds;
  }
  
  - (UIView *)createChartView {
      _chartView = [[WQBizChartView alloc] init];
      _chartView.adapter = self;
      _chartView.separatorWidth = 10;
      [_chartView addGestureRecognizer:[[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPressGesture:)]];
      return _chartView;
  }
  
  - (void)configChartOptions {
      [super configChartOptions];
      
      __weak typeof(self) weakSelf = self;
      
      [self.optionsView addItem:RadioCell.new
       .setTitle(@"Padding")
       .setOptions(@[@"OFF",@"ON"])
       .setSelection(1)
       .setOnSelectionChange(^(RadioCell* cell, NSInteger selection) {
          WQBizChartView* chartView = weakSelf.chartView;
          if(selection) {
              chartView.padding = UIEdgeInsetsMake(20, 15, 20, 15);
          } else {
              chartView.padding = UIEdgeInsetsZero;
          }
      })];
      
      // DistributionMode see also bizChartView:rowAtIndex
      [self.optionsView addItem:RadioCell.new
       .setTitle(@"DistributionMode")
       .setOptions(@[@"0",@"1"])
       .setSelection(0)
       .setOnSelectionChange(^(RadioCell* cell, NSInteger selection) {
          [weakSelf.chartView reloadData];
      })];
      
      // 固定BoundsX，开启可以展示出更平滑的滚动效果
      // 如果不开启，适合FixedVisiableCountDistributionRow使用
      [self.optionsView addItem:RadioCell.new
       .setTitle(@"FixedBoundsX")
       .setOptions(@[@"OFF",@"ON"])
       .setSelection(1)
       .setOnSelectionChange(^(RadioCell* cell, NSInteger selection) {
          [weakSelf.chartView redraw];
      })];
      
      [self.optionsView addItem:RadioCell.new
       .setTitle(@"FixedBoundsY")
       .setOptions(@[@"OFF",@"ON"])
       .setSelection(1)
       .setOnSelectionChange(^(RadioCell* cell, NSInteger selection) {
          [weakSelf.chartView redraw];
      })];
      
      [self.optionsView addItem:RadioCell.new
       .setTitle(@"ClipToRect")
       .setOptions(@[@"OFF",@"ON"])
       .setSelection(1)
       .setOnSelectionChange(^(RadioCell* cell, NSInteger selection) {
          [weakSelf.chartView redraw];
      })];
      
  }
  
  #pragma mark - Items
  
  - (void)configChartItemsOptions {
      [super configChartItemsOptions];
  }
  
  - (NSMutableArray<BizChartItem *> *)items {
      if(!_items) {
          NSMutableArray<BizChartItem*>* items = [NSMutableArray array];
          for (NSInteger i=0; i<1; i++) {
              BizChartItem* item = [self createItemAtIndex:i];
              [items addObject:item];
          }
          _items = items;
      }
      return _items;
  }
  
  - (NSString*)itemsOptionTitle {
      return @"Rows";
  }
  
  - (BizChartItem*)createItemAtIndex:(NSInteger)index {
      NSMutableArray<id<WQChart>>* charts = [NSMutableArray array];
      
      [charts addObject:[[WQAxisChart alloc] init]];
      [charts addObject:[[WQBarChart alloc] init]];
      
      WQLineChart* lineChart = [[WQLineChart alloc] init];
      lineChart.paint.color = Color_Red;
      [charts addObject:lineChart];
      
      WQLineChart* lineChart2 = [[WQLineChart alloc] init];
      lineChart2.paint.color = Color_Orange;
      [charts addObject:lineChart2];
      
      return [[BizChartItem alloc] initWithCharts:charts datas:[self createDatas]];
  }
  
  - (NSMutableArray<BizChartData*>*)createDatas {
      NSMutableArray<BizChartData*>* datas = [NSMutableArray array];
      for (NSInteger i=0; i<self.dataCount; i++) {
          BizChartData* data = [[BizChartData alloc] init];
          data.barValue = [NSNumber randomCGFloatFrom:0 to:self.maxDataValue];
          // 让线段不顶边
          NSInteger lineMinValue = self.maxDataValue * 0.2;
          NSInteger lineMaxValue = self.maxDataValue * 0.8;
          data.lineValue = [NSNumber randomCGFloatFrom:lineMinValue to:lineMaxValue];
          data.lineValue2 =[NSNumber randomCGFloatFrom:lineMinValue to:lineMaxValue];
          [datas addObject:data];
      }
      return datas;
  }
  
  - (SectionCell*)createItemCellWithItem:(BizChartItem*)item atIndex:(NSInteger)index {
      __weak typeof(self) weakSelf = self;
      return SectionCell.new
      .setObject(item)
      .setTitle([NSString stringWithFormat:@"Row%ld",index])
      .setOnReload(^(SectionCell* cell) {
          BizChartItem* item = (BizChartItem*)cell.objcect;
          item.datas = [weakSelf createDatas];
          [weakSelf.chartView redraw];
      });
  }
  
  - (void)itemsDidChange:(NSMutableArray*)items {
      [self.chartView reloadData];
  }
  
  #pragma mark - BizChartViewAdapter
  
  - (NSInteger)numberOfRowsInBizChartView:(WQBizChartView *)bizChartView {
      return self.items.count;
  }
  
  - (WQBizChartViewRow *)bizChartView:(WQBizChartView *)bizChartView rowAtIndex:(NSInteger)index {
      CGFloat rowWidth = MIN(bizChartView.bounds.size.height, bizChartView.bounds.size.width) / 3;
      if([self radioCellSelectionForKey:@"DistributionMode"] != 0) {
          // The items are distributed evenly along the row in visible area
          NSInteger visiableCount = (bizChartView.bounds.size.width - bizChartView.padding.left - bizChartView.padding.right) / (self.barWidth + 2);
          return [[WQFixedVisiableCountDistributionRow alloc] initWithWidth:rowWidth visiableCount:visiableCount itemCount:self.dataCount];
      } else {
          // The items are distributed at regular intervals
          return [[WQFixedItemSpacingDistributionRow alloc] initWithWidth:rowWidth itemSpacing:self.barWidth + 2 itemCount:self.dataCount];
      }
  }
  
  - (void)bizChartView:(WQBizChartView *)bizChartView distributeRowForDistributionPath:(WQDistributionPath *)distributionPath atIndex:(NSInteger)index {
      BizChartItem* item = self.items[index];
      WQAxisChart* axisChart = (WQAxisChart*)item.charts[0];
      WQBarChart* barChart = (WQBarChart*)item.charts[1];
      WQLineChart* lineChart = (WQLineChart*)item.charts[2];
      WQLineChart* lineChart2 = (WQLineChart*)item.charts[3];
      
      NSArray<WQDistributionPathItem*>* distributionPathItems = distributionPath.items;
      NSInteger capacity = distributionPathItems.count;
      
      // Build Items
      CGFloat minValue = 0;
      CGFloat maxValue = 0;
      NSMutableArray<WQBarChartItem*>* barChartItems = [NSMutableArray arrayWithCapacity:capacity];
      NSMutableArray<WQLineChartItem*>* lineChartItems = [NSMutableArray arrayWithCapacity:capacity];
      NSMutableArray<WQLineChartItem*>* lineChartItems2 = [NSMutableArray arrayWithCapacity:capacity];
      for (NSInteger i=0; i<capacity; i++) {
          WQDistributionPathItem* distributionPathItem = distributionPathItems[i];
          BizChartData* data = item.datas[distributionPathItem.index];
          NSInteger location = distributionPathItem.location;
          CGFloat barValue = data.barValue;
          CGFloat lineValue = data.lineValue;
          CGFloat lineValue2 = data.lineValue2;
          
          WQBarChartItem* barChartItem = [[WQBarChartItem alloc] initWithX:location endY:barValue];
          barChartItem.barWidth = self.barWidth;
          barChartItem.paint.fill.color = Color_Gray;
          barChartItem.paint.stroke = nil;
          [barChartItems addObject:barChartItem];
          
          [lineChartItems addObject:[[WQLineChartItem alloc] initWithValue:CGPointMake(location, lineValue)]];
          [lineChartItems2 addObject:[[WQLineChartItem alloc] initWithValue:CGPointMake(location, lineValue2)]];
          
          CGFloat itemMinValue = MIN(MIN(barValue, lineValue),lineValue2);
          CGFloat itemMaxValue = MAX(MAX(barValue, lineValue),lineValue2);
          if(i==0) {
              minValue = itemMinValue;
              maxValue = itemMaxValue;
          } else {
              minValue = MIN(minValue, itemMinValue);
              maxValue = MAX(maxValue, itemMaxValue);
          }
      }
      
      barChart.items = barChartItems;
      lineChart.items = lineChartItems;
      lineChart2.items = lineChartItems2;
      
      // Fix Bounds
      BOOL fixedBoundsX = [self radioCellSelectionForKey:@"FixedBoundsX"] != 0;
      BOOL fixedBoundsY = [self radioCellSelectionForKey:@"FixedBoundsY"] != 0;
      for (NSInteger i=1; i<item.charts.count; i++) {
          WQCoordinateChart* chart = (WQCoordinateChart*)item.charts[i];
          if(fixedBoundsX) {
              chart.fixedMinX = @(distributionPath.lowerBound);
              chart.fixedMaxX = @(distributionPath.upperBound);
          } else {
              chart.fixedMinX = nil;
              chart.fixedMaxX = nil;
          }
          
          if(fixedBoundsY) {
              chart.fixedMinY = @(0);
              chart.fixedMaxY = @(self.maxDataValue);
          } else {
              chart.fixedMinY = @(minValue);
              chart.fixedMaxY = @(maxValue);
          }
      }
      
      axisChart.items = [self createAxisChartItemsWithLowerBound:barChart.fixedMinY.doubleValue upperBound:barChart.fixedMaxY.doubleValue];
  }
  
  // 绘制的分布内容的Charts.Rect不建议改动，因为Items是按照Row.length来分布的，Rect则按照Row.width、Row.length、BizChartView.contentOffset算出。
  // 需要显示上的调整修改BizChartView.padding、Axis.rect、Context.clip等即可
  - (void)bizChartView:(WQBizChartView *)bizChartView drawRowAtIndex:(NSInteger)index inContext:(CGContextRef)context {
      WQBizChartViewRow* row = bizChartView.rows[index];
      CGRect rect = row.rect;
      
      BizChartItem* item = self.items[index];
      WQAxisChart* axisChart = (WQAxisChart*)item.charts[0];
      WQBarChart* barChart = (WQBarChart*)item.charts[1];
      WQLineChart* lineChart = (WQLineChart*)item.charts[2];
      WQLineChart* lineChart2 = (WQLineChart*)item.charts[3];
      
      WQAxisGraphic* axisGraphic = [axisChart drawGraphicInRect:UIEdgeInsetsInsetRect(rect, UIEdgeInsetsMake(-0.5, -self.barWidthHalf - 0.5, -0.5, -self.barWidthHalf - 0.5))  context:context];
      
      BOOL clipToRect = [self radioCellSelectionForKey:@"ClipToRect"] != 0;
      if(clipToRect) {
          CGContextClipToRect(context, UIEdgeInsetsInsetRect(rect, self.clipToRectInset));
      }
      
      if (self.clipRect) {
          CGContextClipToRect(context, self.clipRect.CGRectValue);
      }
      
      WQBarGraphic* barGraphic = [barChart drawGraphicInRect:rect context:context];
      [self.barGraphics addObject:barGraphic];
      
      [lineChart drawGraphicInRect:rect context:context];
      [lineChart2 drawGraphicInRect:rect context:context];
      
      if(clipToRect || self.clipRect) {
          CGContextResetClip(context);
      }
      
      [axisChart drawTextForGraphic:axisGraphic inContext:context];
  }
  
  - (void)bizChartViewWillDraw:(WQBizChartView *)bizChartView inContext:(CGContextRef)context {
      self.barGraphics = [NSMutableArray arrayWithCapacity:bizChartView.rows.count];
  }
  
  - (void)bizChartViewDidDraw:(WQBizChartView *)bizChartView inContext:(CGContextRef)context {
      [self drawTouchFocusInContext:context];
  }
  
  - (NSArray<WQAxisChartItem*>*)createAxisChartItemsWithLowerBound:(CGFloat)lowerBound upperBound:(CGFloat)upperBound {
      NSMutableArray<WQAxisChartItem*>* items = [NSMutableArray array];
      
      // Horizontal Axis
      NSInteger horizontalCount = 5;
      CGFloat horizontalStep = (upperBound - lowerBound) / (horizontalCount - 1);
      for (NSInteger i=0; i<horizontalCount; i++) {
          WQAxisChartItem* item = [[WQAxisChartItem alloc] initWithStart:CGPointMake(0, i) end:CGPointMake(1, i)];
          item.paint.color = Color_White;
          WQChartText* text = [[WQChartText alloc] init];
          text.color = Color_White;
          text.font = [UIFont systemFontOfSize:9];
          
          WQChartTextBlocks* textBlocks = WQChartTextBlocks.new;
          textBlocks.offsetByAngle = ^CGFloat(WQChartText * _Nonnull chartText, CGSize size, CGFloat angle) {
              return -(size.width / 2) - 3;
          };
          text.string = [NSString stringWithFormat:@"%ld",(NSInteger)(round(i * horizontalStep) + lowerBound)];
          if(i==0) {
              textBlocks.offset = ^CGPoint(WQChartText * _Nonnull chartText, CGSize size, NSNumber * _Nullable angle) {
                  return CGPointMake(0, -size.height / 2);
              };
          } else if(i==horizontalCount - 1) {
              textBlocks.offset = ^CGPoint(WQChartText * _Nonnull chartText, CGSize size, NSNumber * _Nullable angle) {
                  return CGPointMake(0, size.height / 2);
              };
          } else {
              item.paint.dashLengths = @[@4,@2];
          }
          text.delegateUsingBlocks = textBlocks;
          item.headerText = text;
          
          [items addObject:item];
      }
      
      // Vertical Axis
      {
          WQAxisChartItem* item = [[WQAxisChartItem alloc] initWithStart:CGPointMake(0, 0) end:CGPointMake(0, horizontalCount - 1)];
          item.paint.color = Color_White;
          [items addObject:item];
          
          item = [[WQAxisChartItem alloc] initWithStart:CGPointMake(1, 0) end:CGPointMake(1, horizontalCount - 1)];
          item.paint.color = Color_White;
          [items addObject:item];
      }
      
      return items;
  }
  
  - (void)drawTouchFocusInContext:(CGContextRef)context {
      if(self.touchLocation==nil || self.barGraphics.count==0) {
          return;
      }
      
      CGPoint touchLocation = self.touchLocation.CGPointValue;
      WQBarGraphic* insideBarGraphic = nil;
      for (WQBarGraphic* barGraphic in self.barGraphics) {
          if(CGRectContainsPoint(barGraphic.rect, CGPointMake(CGRectGetMidX(barGraphic.rect), touchLocation.y))) {
              insideBarGraphic = barGraphic;
              break;
          }
      }
      if(insideBarGraphic == nil) {
          return;
      }
      
      WQBarGraphic* firstBarGraphic = self.barGraphics.firstObject;
      WQBarGraphicItem* nearestBarGraphicItem = [firstBarGraphic findNearestItemAtPoint:touchLocation inRect:UIEdgeInsetsInsetRect(insideBarGraphic.rect, self.clipToRectInset)];
      WQBarChartItem* nearestBarItem = (WQBarChartItem*)nearestBarGraphicItem.builder;
      if(nearestBarGraphicItem==nil) {
          return;
      }
      
      CGContextSaveGState(context);
      
      CGRect bounds = self.chartView.bounds;
      CGFloat stringX = nearestBarGraphicItem.stringStart.x;
      
      CGContextMoveToPoint(context, stringX, CGRectGetMinY(bounds));
      CGContextAddLineToPoint(context, stringX, CGRectGetMaxY(bounds));
      CGContextMoveToPoint(context, CGRectGetMinX(bounds), touchLocation.y);
      CGContextAddLineToPoint(context, CGRectGetMaxX(bounds), touchLocation.y);
      CGContextSetLineWidth(context, 1);
      CGContextSetStrokeColorWithColor(context, Color_White.CGColor);
      CGContextDrawPath(context, kCGPathStroke);
      
      NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
      paragraphStyle.alignment = NSTextAlignmentCenter;
      NSDictionary<NSAttributedStringKey, id> * stringAttributes = @{NSFontAttributeName:[UIFont systemFontOfSize:9],NSForegroundColorAttributeName:Color_White,NSParagraphStyleAttributeName:paragraphStyle};
      
      NSString* horizontalString = [NSString stringWithFormat:@"X:%.2f",nearestBarItem.x];
      CGSize horizontalStringSize = [horizontalString sizeWithAttributes:stringAttributes];
      CGRect horizontalStringRect = CGRectMake(stringX - horizontalStringSize.width / 2 - 5, CGRectGetMinY(bounds), horizontalStringSize.width + 10, horizontalStringSize.height);
      if(CGRectGetMinX(horizontalStringRect) < CGRectGetMinX(bounds)) {
          horizontalStringRect.origin.x = CGRectGetMinX(bounds);
      } else if(CGRectGetMaxX(horizontalStringRect) > CGRectGetMaxX(bounds)) {
          horizontalStringRect.origin.x = CGRectGetMaxX(bounds) - horizontalStringRect.size.width;
      }
      CGContextSetFillColorWithColor(context, Color_Tint.CGColor);
      CGContextFillRect(context, horizontalStringRect);
      [horizontalString drawInRect:horizontalStringRect withAttributes:stringAttributes];
      
      if(insideBarGraphic) {
          CGPoint verticalBoundsPoint = [insideBarGraphic convertRectPointToBounds:touchLocation];
          NSString* verticalString = [NSString stringWithFormat:@"Y:%.2f",verticalBoundsPoint.y];
          CGSize verticalStringSize = [verticalString sizeWithAttributes:stringAttributes];
          CGRect verticalStringRect = CGRectMake(CGRectGetMinX(bounds), touchLocation.y - verticalStringSize.height / 2, verticalStringSize.width + 10, verticalStringSize.height);
          if(CGRectGetMinY(verticalStringRect) < CGRectGetMinY(bounds)) {
              verticalStringRect.origin.y = CGRectGetMinY(bounds);
          } else if(CGRectGetMaxY(verticalStringRect) > CGRectGetMaxY(bounds)) {
              verticalStringRect.origin.y = CGRectGetMaxY(bounds) - verticalStringRect.size.height;
          }
          CGContextSetFillColorWithColor(context, Color_Tint.CGColor);
          CGContextFillRect(context, verticalStringRect);
          [verticalString drawInRect:verticalStringRect withAttributes:stringAttributes];
      }
      
      CGContextRestoreGState(context);
  }
  
  #pragma mark - Animation
  
  - (void)appendAnimationKeys:(NSMutableArray<NSString *> *)animationKeys {
      [super appendAnimationKeys:animationKeys];
      [animationKeys addObject:@"Padding"];
      [animationKeys addObject:@"Clip"];
  }
  
  - (void)prepareAnimationOfChartViewForKeys:(NSArray<NSString*>*)keys {
      [super prepareAnimationOfChartViewForKeys:keys];
      
      WQBizChartView* chartView = self.chartView;
      
      if ([keys containsObject:@"Padding"]) {
          RadioCell* paddingCell = [self findRadioCellForKey:@"Padding"];
          UIEdgeInsets padding = paddingCell.selection == 0 ? UIEdgeInsetsMake(20, 15, 20, 15) : UIEdgeInsetsZero;
          chartView.paddingTween = [[WQChartUIEdgeInsetsTween alloc] initWithFrom:chartView.padding to:padding];
          paddingCell.selection = paddingCell.selection == 0 ? 1 : 0;
      }
      
  }
  
  - (void)appendAnimationsInArray:(NSMutableArray<WQChartAnimation *> *)array forKeys:(NSArray<NSString *> *)keys {
      [super appendAnimationsInArray:array forKeys:keys];
      
      WQBizChartView* chartView = self.chartView;
      
      if ([keys containsObject:@"Clip"]) {
          CGRect rect = chartView.bounds;
          BOOL isReversed = [NSNumber randomBOOL];
          if (isReversed) {
              self.clipRectTween = [[WQChartCGRectTween alloc] initWithFrom:
                                        CGRectMake(CGRectGetMaxX(rect), CGRectGetMinY(rect), 0, CGRectGetHeight(rect))
                                                                            to:
                                        CGRectMake(CGRectGetMinX(rect), CGRectGetMinY(rect), CGRectGetWidth(rect), CGRectGetHeight(rect))];
          } else {
              self.clipRectTween = [[WQChartCGRectTween alloc] initWithFrom:
                                        CGRectMake(CGRectGetMinX(rect), CGRectGetMinY(rect), 0, CGRectGetHeight(rect))
                                                                            to:
                                        CGRectMake(CGRectGetMinX(rect), CGRectGetMinY(rect), CGRectGetWidth(rect), CGRectGetHeight(rect))];
          }
          [array addObject:[[WQChartAnimation alloc] initWithAnimatable:self duration:self.animationDuration interpolator:self.animationInterpolator]];
      }
      
  }
  
  #pragma mark - Animatable
  
  - (void)transform:(CGFloat)progress {
      WQChartCGRectTween* clipRectTween = self.clipRectTween;
      if (clipRectTween) {
          self.clipRect = [NSValue valueWithCGRect:[clipRectTween lerp:progress]];
      }
  }
  
  - (void)clearAnimationElements {
      self.clipRectTween = nil;
      self.clipRect = nil;
  }
  
  #pragma mark - Action
  
  - (void)handleLongPressGesture:(UILongPressGestureRecognizer*)gestureRecognizer  {
      UIGestureRecognizerState state = gestureRecognizer.state;
      if (state == UIGestureRecognizerStateBegan || state == UIGestureRecognizerStateChanged) {
          self.touchLocation = [NSValue valueWithCGPoint:[gestureRecognizer locationInView:gestureRecognizer.view]];
      } else {
          self.touchLocation = nil;
      }
      [self.chartView redraw];
  }
  
  
  
  @end
  