Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Linear chart line with different thickness of direction #3746

Closed
lyc2345 opened this issue Nov 14, 2018 · 2 comments
Closed

Linear chart line with different thickness of direction #3746

lyc2345 opened this issue Nov 14, 2018 · 2 comments

Comments

@lyc2345
Copy link

lyc2345 commented Nov 14, 2018

Hi, I have a question about linear chart and hope someone can give me a hint to do it.

I find out there is difference line width between horizontal direction line and others.

The horizontal line will thinner then others. Is something what I missed for chart configuration?

Thanks For this awesome tool. Appreciated!!!

What did you do?

ℹ set lineWidth = 2

What did you expect to happen?

ℹ Every part of linear line is same width

What happened instead?

ℹ Now the horizontal part of linear line will thinner than others, please refer to image

Charts Environment

Charts version/Branch/Commit Number: 3.1.1
Xcode version: 10.1
Swift version: 4
**Platform(s) running Charts: iOS **
**macOS version running Xcode: 10.14.1 **

Demo Project

ℹ Please link to or upload a project we can download that reproduces the issue.
I am the image, CLICK ME!


import Charts
import Hue

let LineChartDefaultFont = UIFont.systemFont(ofSize: 13)

struct LineChartTheme {
  var noDataTextColor: UIColor = .black
  var descriptionTextColor: UIColor = .black
  var gridColor: UIColor = .black
  var labelColor: UIColor = .black
  var valueColor: UIColor = .black
  var circleHoleColor: UIColor = .blue
  var gradientTopColor: UIColor = Configuration.Theme.blue
  var gradientBottomColor: UIColor = Configuration.Theme.blue
  var lineColor: UIColor = Configuration.Theme.blue
}

struct LineChartFont {
  var chartDescriptionFont: UIFont = LineChartDefaultFont
  var noDataFont: UIFont = LineChartDefaultFont
  var labelFont: UIFont = LineChartDefaultFont
  var valueFont: UIFont = LineChartDefaultFont
}

class LineChart: BarLineChartViewBase, ChartViewDelegate, LineChartDataProvider {
  
  // Conform LineChartDataProvider
  var lineData: LineChartData? { return self.data as? LineChartData }
  
  var subjects: [Int] = [] // 資料
  let animationInterval: TimeInterval = 0.0 // 設定動畫時間
  var highlightLastEnabled: Bool = false
  var fillEnabled: Bool = false
  var lineMode: LineChartDataSet.Mode = .linear
  var visibleXMaximum: Double {
    return Double(subjects.count - 1)
  }
  let visibleXMinimum: Double = 0
  var sourceDescription: String = "" {
    didSet {
      chartDescription?.text = sourceDescription // 右下角的表格說明
    }
  }
  
  var customTheme: LineChartTheme = LineChartTheme()
  var customFont: LineChartFont = LineChartFont()

  required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    setupInitial()
  }
  
  override init(frame: CGRect) {
    super.init(frame: frame)
    setupInitial()
  }
  
  init() {
    super.init(frame: .zero)
  }
  
  func setupInitial() {
    // needs to init a renderer like LineChartView
    //renderer = LineChartRenderer(dataProvider: self,
    //                             animator: chartAnimator,
    //                             viewPortHandler: viewPortHandler)
    renderer = SLineChartRenderer(dataProvider: self,
                                  animator: chartAnimator,
                                  viewPortHandler: viewPortHandler)
    
    self.delegate = self
    dragEnabled = false // 拖拉圖表
    setScaleEnabled(false) // 縮放
    pinchZoomEnabled = false // 兩隻手指放大
    drawGridBackgroundEnabled = false // 繪製網格
    drawBordersEnabled = false // 繪製框框
    dragDecelerationEnabled = false // 拖移後是否有慣性效果
    dragDecelerationFrictionCoef = 0 // 慣性摩擦係數(0~1), 數值越小,慣性越不明顯
    highlightPerTapEnabled = false
    clipValuesToContentEnabled = true // 要不要切掉 label content
    noDataText = "No Chart Data"
    noDataFont = customFont.noDataFont
    noDataTextColor = customTheme.noDataTextColor
    
    chartDescription?.text = sourceDescription // 右下角的表格說明
    chartDescription?.font = customFont.chartDescriptionFont // 表格說明的字型
    chartDescription?.textColor = customTheme.descriptionTextColor // 表格說明的文字顏色
    legend.enabled = false // 圖例說明
    
    minOffset = 0 // 上左下右的最小offset
    extraBottomOffset = 0 // 下方offset間距
    extraTopOffset = 0 // 上方offset間距
    extraLeftOffset = 0
    extraRightOffset = 0
    
    // Sets the size of the area (range on the x-axis) that should be maximum visible at once (no further zooming out allowed).
    setVisibleXRangeMaximum(visibleXMaximum) // 最大顯示
    setVisibleXRangeMinimum(visibleXMinimum) // 最小顯示
    
    setupLeftAxis() // 設定左右標線
    setupRightAxis() // 設定左右標線
    setupAxisBase() // 設定圖表
  }
  
  func setupAxisBase() {
    xAxis.gridColor = customTheme.gridColor
    xAxis.labelPosition = .bottom // 顯示曲線數值的位置
    xAxis.labelTextColor = customTheme.labelColor
    xAxis.labelFont = customFont.labelFont
    
    // 所有有關x軸的標線都不要
    xAxis.enabled = false
    
    // set this with axisLineWidth could make label shows but line
    xAxis.drawLabelsEnabled = true // 顯示xAxis單位label // label always shows with xAxis guide,
    xAxis.drawGridLinesEnabled = false // 顯示Axis中間的單位線grid
    xAxis.drawAxisLineEnabled = false // 顯示xAxis單位的格線(在label上方) _|_
    // if focing the y-label count is enabled. Default: false
    //xAxis.forceLabelsEnabled = false //  if true, 會消失幾個label 讓版面最前面跟最後面的label顯示出來
    //xAxis.granularityEnabled = true
    xAxis.granularity = 1 // 曲線間格為1
    
    // xAxis the first and last label entry in the char "clip" off the edge of that chart
    xAxis.avoidFirstLastClippingEnabled = true // 最前面跟最後面的label會不會切掉
    //xAxis.wordWrapEnabled = true
    xAxis.centerAxisLabelsEnabled = true
    
    // this will make line disappear
    //xAxis.axisLineWidth = 0
    
    //xAxis.axisMinimum = -3 // 最前面留空3格
    //xAxis.axisMaximum = Double(subjectsRange.rawValue) - 0.9 // 後面留空3格
  }
  
  func setupLeftAxis() {
    //leftAxis.removeAllLimitLines()
    //leftAxis.axisMaximum = 100 // 設定左邊單位格線最大值
    //leftAxis.axisMaximum = 0 // 設定左邊單位格線最小值
    //leftAxis.gridLineDashLengths = [0, 0]
    //leftAxis.drawZeroLineEnabled = true
    //leftAxis.drawLimitLinesBehindDataEnabled = false
    //leftAxis.labelTextColor = .white
    leftAxis.enabled = false // 繪製左邊單位所有
  }
  
  func setupRightAxis() {
    rightAxis.drawLimitLinesBehindDataEnabled = false
    rightAxis.enabled = false // 繪製右邊單位所有
  }
  
  private func setLineBase(line: LineChartDataSet) {
    line.drawIconsEnabled = false
    line.drawValuesEnabled = false // 顯示數值文字
    // error: CGContextSetLineDash: invalid dash array: at least one element must be non-zero.
    // https://stackoverflow.com/questions/34315551/warning-with-cgcontextsetlinedash
    /*
     You should post what are the argument values.
     As I can assume from the error message, your arguments has 0 and is not allowed. Read CGContextSetLineDash about the dash options.
     */
    //line.lineDashLengths = [0, 0]
    //line.setColor(.white)
    line.lineWidth = 2
    line.valueTextColor = customTheme.valueColor
    line.valueFont = customFont.valueFont
    line.mode = lineMode // 線圖使用曲線
  }
  
  // Not USE This, Now Draw custom Highlight indicator in HistoryMaker
  private func setLineHighlight(line: LineChartDataSet) {
    line.highlightEnabled = true // 是否開啟highlight(顯示十字線)
    line.drawVerticalHighlightIndicatorEnabled = false // 移動highlight顯示垂直線
    line.drawHorizontalHighlightIndicatorEnabled = false // 移動highlight顯示水平線
    line.highlightColor = Configuration.Theme.black
    line.highlightLineWidth = 1
    line.highlightLineDashLengths = [5, 5] // 讓highlight 虛線
    line.highlightLineDashPhase = 5
  }
  
  private func setLineCircle(line: LineChartDataSet) {
    // This also affect the highlight circle size and color
    line.drawCirclesEnabled = false // 繪製交叉點
    line.circleRadius = 9.0 // 交叉點半徑
    line.drawCircleHoleEnabled = false // 交叉點中空
    line.circleHoleRadius = 4.5 // 交叉中空心點半徑
    line.circleHoleColor = customTheme.circleHoleColor// 空心點顏色
    line.circleColors = [customTheme.circleHoleColor.alpha(0.2)]
  }
  
  private func setLineColor(line: LineChartDataSet) {
    let filledBackgroundGradientColors = [
      customTheme.gradientTopColor.alpha(0.25).cgColor,
      customTheme.gradientBottomColor.alpha(0.0).cgColor
    ]
    if let gradient = CGGradient(colorsSpace: CGColorSpaceCreateDeviceRGB(),
                                 colors: filledBackgroundGradientColors as CFArray,
                                 locations: nil) {
      line.drawFilledEnabled = true // 填滿顏色
      line.fillAlpha = 1.0 // 填滿的透明度
      //line.fill = Fill(radialGradient: gradient,
      //                 startOffsetPercent: self.center,
      //                 startRadiusPercent: 0.5,
      //                 endOffsetPercent: CGPoint(x: 150, y: 150),
      //                 endRadiusPercent: 0.2)
      //line.fill = Fill(CGColor: Configuration.Theme.mediumLightBlue.cgColor)
      line.fill = Fill(linearGradient: gradient, angle: 270)
      line.setColor(customTheme.lineColor)
    }
  }
  
  //private func setMarker() {
  //  // 使用自己的marker
  //  let marker = HistoryMarker(color: .clear,
  //                             font: Configuration.Font.numericFont,
  //                             xTextColor: Configuration.Theme.mediumGray,
  //                             yTextColor: Configuration.Theme.darkGray,
  //                             insets: UIEdgeInsets(top: 0, left: 5, bottom: 0, right: 5))
  //  // assign image depends chat value point
  //  marker.chartView = self // 指定marker用在哪裡
  //  self.marker = marker // assign local to global
  //}
  
  // 畫圖
  // range:
  func modifyChart(by subjects: [Int]) {
    
    let line: LineChartDataSet = LineChartDataSet() // 曲線 cubic
    var values: [ChartDataEntry] = []
    guard let min = subjects.min(), let max = subjects.max() else {
      return
    }
    for (index, subject) in subjects.enumerated() {
      let entry = ChartDataEntry(x: Double(index), y: Double(subject))
      values.append(entry)
    }
    // hotfix: use current subjects value to calculate the average
    // prevent line chart be cut off at the top
    // get min and max from data set
    // get avg
    //let avg = subjects.intAverage
    // To make minimum and maximum more smooth so that it won't exceed beyond top edge
    //leftAxis.axisMinimum = (min - (avg - min)).doubleValue // 設定左邊單位格線最小值
    //leftAxis.axisMaximum = (max + (max - avg)).doubleValue // 設定左邊單位格線最大值
    leftAxis.axisMinimum = min.doubleValue // 設定左邊單位格線最小值
    leftAxis.axisMaximum = max.doubleValue // 設定左邊單位格線最大值
    //let avg = subjects.decimalAverage
    //leftAxis.axisMinimum = (avg * 0.9).doubleValue // 設定左邊單位格線最小值
    //leftAxis.axisMaximum = (avg * 1.1).doubleValue // 設定左邊單位格線最大值
    // Value is ChartDataEntry array
    line.values = values
    setLineBase(line: line) // 設定曲線
    setLineHighlight(line: line) // 設定曲線點下去的highlight
    setLineCircle(line: line) // 設定曲線節點
    setLineColor(line: line) // 設定曲線顏色
    let data = LineChartData(dataSet: line)
    self.data = data // Charts.ChartData: Swift Chart 設定 chat 的進入點
    //xAxis.valueFormatter = IndexAxisValueFormatter(values: dates) // 設定下方文字
    //setMarker()
    highlightValue(nil) // Reload chart should disable previous selected highlighter
    
    if highlightLastEnabled {
      // Show Lastest highlight
      guard let lastSubject = subjects.last else { return }
      highlightValue(x: Double(self.subjects.count - 1),
                     y: Double(lastSubject),
                     dataSetIndex: 0,
                     callDelegate: false)
      moveViewToX(Double(self.subjects.count))
    }
  }
  
  // custom reload data like TableView
  func reloadData(completion: (() -> Void)? = nil) {
    modifyChart(by: subjects)
    
    // cancel reload animation
    //animate(yAxisDuration: animationInterval)
    completion?()
  }
  
  func clean() {
    self.subjects = []
    reloadData()
  }
  
  // custom reload Chart view 重整圖表的 UI
  func reloadChartViewProperties() {
    
    guard let dataSets = data?.dataSets as? [LineChartDataSet] else {
      return
    }
    for set in dataSets {
      set.mode = lineMode
    }
    setNeedsDisplay()
  }
  
  // MARK: ChartViewDelegate
  
  // Interaction with the Chart
  func chartValueSelected(_ chartView: ChartViewBase, entry: ChartDataEntry, highlight: Highlight) {
    // this is index and value, in this case
    // x: index
    // y: value
    //print("hight x: \(highlight.x), y: \(highlight.y)")
    // xpx, ypx could use to draw custom highlighter
    //print("hight x: \(highlight.xPx), y: \(highlight.yPx)")
    //self.moveViewToX(Double(highlight.xPx))
  }
  
  func chartValueNothingSelected(_ chartView: ChartViewBase) {
    //print("chart value nothing selected")
  }
  
  override func draw(_ rect: CGRect) {
    //    for i in 1 ..< (subjects.count - 1) {
    //      drawGrid(at: i)
    //    }
    super.draw(rect)
  }
  
  // custom draw grid
  func drawGrid(at index: Int, color: UIColor = Configuration.Theme.red, offset: CGFloat = 15) {
    guard let set = data?.dataSets.first,
      let entry = data?.dataSets.first?.entryForIndex(index),
      var startY = self.renderer?.viewPortHandler.contentBottom else {
        return
    }
    startY -= offset
    let endPoint = getTransformer(forAxis: set.axisDependency).pixelForValues(x: entry.x, y: entry.y)
    let bezierPath = UIBezierPath()
    bezierPath.lineWidth = 0
    bezierPath.move(to: CGPoint(x: endPoint.x, y: startY))
    bezierPath.addLine(to: CGPoint(x: endPoint.x, y: endPoint.y))
    color.setStroke()
    bezierPath.stroke()
  }
}

@liuxuan30
Copy link
Member

liuxuan30 commented Nov 15, 2018

it seems it got clipped off by your axisMinimum and max.
next time, ask on stack overflow.

@lyc2345
Copy link
Author

lyc2345 commented Nov 15, 2018

Thanks for reply

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants