AppPear / ChartView

ChartView made in SwiftUI
MIT License
5.34k stars 653 forks source link

Can't get legend or x and y labels #243

Open dadixon opened 2 years ago

dadixon commented 2 years ago

v2 ticket

Ticket description:

I'm having trouble getting the legend and labels to show in the ChartGrid with a LineChart. I'm passing in the tuple for the data but only the data is displayed

var demoData: [(String, Double)] = [
      ("M", 8),
      ("T", 2),
      ("W", 4),
      ("T", 6),
      ("F", 12),
      ("S", 9),
      ("S", 2)
]

var body: some View {

   ChartGrid {
       LineChart()
       ChartLabel("")
          .labelStyle(.automatic)
   }
       .data(demoData)
       .chartStyle(ChartStyle(backgroundColor: .white,
                                         foregroundColor: ColorGradient(.blue, .purple)))
}

Screen Shot 2022-06-02 at 10 30 16 AM

JakobStadlhuber commented 2 years ago

Same

app4g commented 2 years ago

The data is already being parsed but it's not implemented in the code for some reason. You can try editing it like below. (this is specifically for BarChart)

Look for the code comment "// BarChartLabel"

import SwiftUI

/// A single row of data, a view in a `BarChart`
public struct BarChartRow: View {
    @EnvironmentObject var chartValue: ChartValue
    @ObservedObject var chartData: ChartData
    @State private var touchLocation: CGFloat = -1.0

    var style: ChartStyle

    var maxValue: Double {
        guard let max = chartData.points.max() else {
            return 1
        }
        return max != 0 ? max : 1
    }

    /// The content and behavior of the `BarChartRow`.
    ///
    /// Shows each `BarChartCell` in an `HStack`; may be scaled up if it's the one currently being touched.
    /// Not using a drawing group for optimizing animation.
    /// As touched (dragged) the `touchLocation` is updated and the current value is highlighted.
    public var body: some View {
        GeometryReader { geometry in
            HStack(alignment: .bottom,
                   spacing: geometry.frame(in: .local).width / CGFloat(chartData.data.count * 3)) {
                    ForEach(0..<chartData.data.count, id: \.self) { index in
                        BarChartCell(value: chartData.normalisedPoints[index],
                                     index: index,
                                     gradientColor: self.style.foregroundColor.rotate(for: index),
                                     touchLocation: self.touchLocation,
                                     label: self.chartData.values[index]) // BarChartLabel
                            .scaleEffect(self.getScaleSize(touchLocation: self.touchLocation, index: index), anchor: .bottom)
                            .animation(Animation.easeIn(duration: 0.2))
                    }
//                    .drawingGroup()
            }
            .frame(maxHeight: chartData.isInNegativeDomain ? geometry.size.height / 2 : geometry.size.height)
            .gesture(DragGesture()
                .onChanged({ value in
                    let width = geometry.frame(in: .local).width
                    self.touchLocation = value.location.x/width
                    if let currentValue = self.getCurrentValue(width: width) {
                        self.chartValue.currentValue = currentValue
                        self.chartValue.interactionInProgress = true
                    }
                })
                .onEnded({ value in
                    self.chartValue.interactionInProgress = false
                    self.touchLocation = -1
                })
            )
        }
    }

    /// Size to scale the touch indicator
    /// - Parameters:
    ///   - touchLocation: fraction of width where touch is happening
    ///   - index: index into data array
    /// - Returns: a scale larger than 1.0 if in bounds; 1.0 (unscaled) if not in bounds
    func getScaleSize(touchLocation: CGFloat, index: Int) -> CGSize {
        if touchLocation > CGFloat(index)/CGFloat(chartData.data.count) &&
           touchLocation < CGFloat(index+1)/CGFloat(chartData.data.count) {
            return CGSize(width: 1.4, height: 1.1)
        }
        return CGSize(width: 1, height: 1)
    }

    /// Get data value where touch happened
    /// - Parameter width: width of chart
    /// - Returns: value as `Double` if chart has data
    func getCurrentValue(width: CGFloat) -> Double? {
        guard self.chartData.data.count > 0 else { return nil}
            let index = max(0,min(self.chartData.data.count-1,Int(floor((self.touchLocation*width)/(width/CGFloat(self.chartData.data.count))))))
            return self.chartData.points[index]
        }
}

struct BarChartRow_Previews: PreviewProvider {
     static let chartData = ChartData([6, 2, 5, 6, 7, 8, 9])
    static let chartData2 = ChartData([("M",6), ("T",2), ("W",5), ("T",6), ("F",7), ("S",8), ("S",9)])
    static let chartStyle = ChartStyle(backgroundColor: .white, foregroundColor: .orangeBright)
    static var previews: some View {
      VStack{
        BarChartRow(chartData: chartData, style: chartStyle)
          .border(.red)
          .frame(width: 100, height: 100)
        HStack (alignment: .lastTextBaseline){
          VStack {
              Text("Load")
              Text("400")
          }

          Spacer()
          BarChartRow(chartData: chartData2, style: chartStyle)
            .border(.red)
            .frame(width: 150, height: 50)

        }
      }
      .padding([.leading, .trailing],10)

    }
}

BarChartCell

import SwiftUI

/// A single vertical bar in a `BarChart`
public struct BarChartCell: View {
  var value: Double
  var index: Int = 0
  var gradientColor: ColorGradient
  var touchLocation: CGFloat
  var label: String = "" // BarChartLabel

  @State private var didCellAppear: Bool = false

  public init( value: Double,
               index: Int = 0,
               gradientColor: ColorGradient,
               touchLocation: CGFloat,
               label: String = "") { // BarChartLabel
    self.value = value
    self.index = index
    self.gradientColor = gradientColor
    self.touchLocation = touchLocation
    self.label = label     // BarChartLabel
  }

  /// The content and behavior of the `BarChartCell`.
  ///
  /// Animated when first displayed, using the `firstDisplay` variable, with an increasing delay through the data set.
  public var body: some View {
    VStack{ // BarChartLabel - Embed it into a Stack
      BarChartCellShape(value: didCellAppear ? value : 0.0)
        .fill(gradientColor.linearGradient(from: .bottom, to: .top))
        .onAppear {
          self.didCellAppear = true
        }
        .onDisappear {
          self.didCellAppear = false
        }
        .transition(.slide)
        .animation(Animation.spring().delay(self.touchLocation < 0 || !didCellAppear ? Double(self.index) * 0.04 : 0))
      Text(String(label)) // BarChartLabel
        .font(.caption).    // BarChartLabel
    }
  }
}

struct BarChartCell_Previews: PreviewProvider {
    static var previews: some View {
        Group {
            Group {
                BarChartCell(value: 0, gradientColor: ColorGradient.greenRed, touchLocation: CGFloat(), label: "POPO")
                BarChartCell(value: 0.5, gradientColor: ColorGradient.greenRed, touchLocation: CGFloat())
                BarChartCell(value: 0.75, gradientColor: ColorGradient.whiteBlack, touchLocation: CGFloat())
                BarChartCell(value: 1, gradientColor: ColorGradient(.purple), touchLocation: CGFloat())
            }

            Group {
                BarChartCell(value: 1, gradientColor: ColorGradient.greenRed, touchLocation: CGFloat())
                BarChartCell(value: 1, gradientColor: ColorGradient.whiteBlack, touchLocation: CGFloat())
                BarChartCell(value: 1, gradientColor: ColorGradient(.purple), touchLocation: CGFloat())
            }.environment(\.colorScheme, .dark)
        }
    }
}
Screenshot 2022-07-27 at 10 07 59 AM
app4g commented 2 years ago
struct ContentView: View {
//  var demoData: [String, Double] = [("M",1.0),("T",2.0),("W",4.0), ("T", 8.0), ("F",10.0), ("S",9.0),("S", 2.0)]
  var demoData: [(String, Double)] = ([("M",9), ("T",2), ("W",5), ("T",6), ("F",7), ("S",8), ("S",9)])
  var demoData2: [(String, Double)] = ([("M",1), ("T",11), ("W",12), ("T",6), ("F",11), ("S",0), ("S",7)])
  var demoData3: [(String, Double)] = ([("M",1.55), ("T",2.1), ("W",1.6), ("T",0.3), ("F",0.9), ("S",1.3), ("S",2.0)])

    var body: some View {
    VStack {
      Text("Hello, world!")

      HStack{
        CardView(showShadow: true) {
          Text("Load")
            .padding([.leading, .top],10)
            .font(.caption)
          ChartLabel("400", type: .custom(size: 10, padding: EdgeInsets(top: 0.0, leading: 10.0, bottom: 0.0, trailing: 10.0), color: Color(UIColor.secondaryLabel)), format: "%0.0f")
          BarChart()
            .padding(.horizontal,10)
            .padding(.bottom,5)
        }
        .data(demoData)
        .chartStyle(ChartStyle(backgroundColor: .white,
                               foregroundColor: [ColorGradient(.blue, .purple)]
                              ))

        CardView(showShadow: true) {
          Text("Distance")
            .padding([.leading, .top],10)
            .font(.caption)
          ChartLabel("400km", type: .custom(size: 10, padding: EdgeInsets(top: 0.0, leading: 10.0, bottom: 0.0, trailing: 10.0), color: Color(UIColor.secondaryLabel)), format: "%0.0f")
          BarChart()
            .padding(.horizontal,10)
            .padding(.bottom,5)
        }
        .data(demoData2)
        .chartStyle(ChartStyle(backgroundColor: .white,
                               foregroundColor: [ColorGradient(.blue, .purple)]
                              ))

        CardView(showShadow: true) {
          Text("Time")
            .padding([.leading, .top],10)
            .font(.caption)
          ChartLabel("10h 50m", type: .custom(size: 10, padding: EdgeInsets(top: 0.0, leading: 10.0, bottom: 0.0, trailing: 10.0), color: Color(UIColor.secondaryLabel)), format: "%0.1f")
          BarChart()
            .padding(.horizontal,10)
            .padding(.bottom,5)
        }
        .data(demoData3)
        .chartStyle(ChartStyle(backgroundColor: .white,
                               foregroundColor: [ColorGradient(.blue, .purple)]
                              ))

      }
      .frame(width: .infinity, height: 150, alignment: .center)

    }
  }
}
Screenshot 2022-07-27 at 11 32 44 AM
JakobStadlhuber commented 2 years ago

@app4g that looks nice, you forked already ChartView but without changes, do you have plans to implement the above shown changes? Would be nice :)

app4g commented 2 years ago

@JakobStadlhuber I just made the changes locally and am just still playing around / testing it. eg: it doesn't honour dark mode in cardview

and trying to get this to work. In the interim (screenshot) shows just changing it from .white to .lightgray jus to see if it works.

Screenshot 2022-07-27 at 8 00 00 PM

Yeah.. I've forked It but have yet to sync it. Possibly over the next few days as I play more w/ it.

JakobStadlhuber commented 2 years ago

Sounds good @app4g I will follow your repo. Do you think it is possible to get a y axis too?

JakobStadlhuber commented 2 years ago

@app4g some news with the forked repo?

app4g commented 2 years ago

Forked Repo : https://github.com/app4g/ChartView

Here is the Demo Project or rather the code I was working on to see /make the changes https://github.com/app4g/ChartViewV2-Demo-Project