1

I'm using iOS Charts in my project, and I've been able to embed my charts in a tableview (every cell will contain a chart, up to maximum number of possible entries to keep under control the performance).

I would like to use the 'touchMatrix' feature in order zoom the x axis on one chart (in a cell) and have all of the other visible charts to follow along with the zoom scale (the x axis), possibly with a vertical axis showing the Y value on every visible chart for that x coordinate, but I'm kind of stuck, since even the delegate methods such as 'chartValueNothingSelected' are not working (I probably got it wrong when I tried to associate the delegate to the chartview, since the chart it's embedded in the cell, and it's not the tipical case of associating the delegate to a view controller). Assigning the delegate to the cell just doesn't seem to be working.

Any tips?

Thanks in advance.

Here is the code:

import UIKit import Charts

class CustomChartCell: UITableViewCell, ChartViewDelegate {

    @IBOutlet weak var lineChartView: LineChartView!
    var yValues     = [Double]()
    var xValues     = [Double]()
    var chartColor  = UIColor()
    var channelName = ""
    var yMin = 0.0
    var yMax = 0.0
    var yAvg = 0.0

    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code

        self.lineChartView.noDataFont = UIFont.systemFont(ofSize: 28.0)
        self.lineChartView.noDataText = NSLocalizedString("No chart data available", comment: "No Chart Data Available Title")
        self.lineChartView.xAxis.labelPosition = .bottom
        self.lineChartView.highlightPerTapEnabled = true
        self.lineChartView.autoScaleMinMaxEnabled = true

        self.lineChartView.chartDescription?.enabled = true
        self.lineChartView.dragEnabled = true
        self.lineChartView.setScaleEnabled(true)
        self.lineChartView.pinchZoomEnabled = false

        self.lineChartView.xAxis.gridLineDashLengths = [10, 10]
        self.lineChartView.xAxis.gridLineDashPhase = 0

        self.lineChartView.leftAxis.gridLineDashLengths = [5, 5]
        self.lineChartView.leftAxis.drawLimitLinesBehindDataEnabled = true

        self.lineChartView.rightAxis.enabled = false

        let marker = BalloonMarker(color: UIColor(white: 180/255, alpha: 1),
                                   font: .systemFont(ofSize: 12),
                                   textColor: .white,
                                   insets: UIEdgeInsets(top: 8, left: 8, bottom: 20, right: 8))
        marker.chartView = self.lineChartView
        marker.minimumSize = CGSize(width: 80, height: 40)
        self.lineChartView.marker = marker
        self.lineChartView.legend.form = .line

        self.lineChartView.delegate = self
    }

    func setChart(_ xValues: [Double], _ yValues: [Double]) {

        var dataEntries: [ChartDataEntry] = []

        for i in 0..<xValues.count {
            let dataEntry = ChartDataEntry(x: xValues[i], y: yValues[i])
            dataEntries.append(dataEntry)
        }

        let chartDataSet = LineChartDataSet(values: dataEntries, label: self.channelName)
        let chartData = LineChartData(dataSets: [chartDataSet])

        chartDataSet.setColor(self.chartColor.withAlphaComponent(0.5))
        chartDataSet.drawCirclesEnabled = false
        chartDataSet.lineWidth = 2
        chartDataSet.drawValuesEnabled = false
        chartDataSet.highlightEnabled = true
        chartDataSet.drawFilledEnabled = false

        lineChartView.data = chartData

        self.lineChartView!.leftAxis.axisMinimum = self.yValues.min()! - self.yValues.max()!*0.2
        self.lineChartView!.leftAxis.axisMaximum = self.yValues.max()! + self.yValues.max()!*0.2

        self.lineChartView!.chartDescription?.font = UIFont.systemFont(ofSize: 11.0)
        self.lineChartView!.chartDescription?.text = "y_min: \(self.yMin), y_max: \(self.yMax), y_avg: \(self.yAvg)"
    }

    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)

        // Configure the view for the selected state
    }

}


class ChartTableViewController: UITableViewController, ChartViewDelegate {

    var dataSet : [LineChartData] = []
    let data = LineChartData()
    var channelNames : [String] = []
    let channelColors = [UIColor.blue,   UIColor.green,  UIColor.red,
                         UIColor.orange, UIColor.purple, UIColor.darkGray]
    var averageValues : [Double] = []
    var minValues     : [Double] = []
    var maxValues     : [Double] = []
    var xValues       : [[Double]] = []
    var yValues       : [[Double]] = []


    @IBAction func chartZoomOut(_ sender: Any) {

        tableView?.visibleCells.forEach { cell in
            if let cell = cell as? CustomChartCell {
                cell.lineChartView.zoomOut()
            }
        }
    }

    @IBAction func chartZoom100(_ sender: Any) {
        tableView?.visibleCells.forEach { cell in
            if let cell = cell as? CustomChartCell {
                cell.lineChartView.zoomToCenter(scaleX: 0, scaleY: 0)
            }
        }
    }

    @IBAction func chartZoomIn(_ sender: Any) {
        tableView?.visibleCells.forEach { cell in
            if let cell = cell as? CustomChartCell {
                cell.lineChartView.zoomIn()
    }

    func chartValueNothingSelected(_ chartView: ChartViewBase) {
        chartView.chartDescription?.text = ""
    }


    func chartScaled(chartView: ChartViewBase, scaleX: CGFloat, scaleY: CGFloat) {
        print(scaleX)
        print(scaleY)
    }

    func chartValueSelected(_ chartView: ChartViewBase, entry: ChartDataEntry, highlight: Highlight) {

        chartView.chartDescription?.text = "x: \(entry.x), y: \(entry.y), y_min: \(minValues[0]), y_max: \(maxValues[0]), y_avg: \(averageValues[0])"

        chartView.reloadInputViews()
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        self.tableView.isEditing = false
        self.editButtonItem.title = NSLocalizedString("Edit", comment: "Edit Title")
        self.tableView.separatorColor = UIColor.clear

        self.navigationItem.rightBarButtonItem = self.editButtonItem


        self.loadFile()
        self.tableView.reloadData()
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        self.tableView.isEditing = false
        self.editButtonItem.title = NSLocalizedString("Edit", comment: "Edit Title")
    }



    // MARK: Load Chart File
    func loadFile() {
        // Try to load the file content
        do {
            // get the documents folder url
            let documentDirectoryURL = try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
            // create the destination url for the text file to be saved
            let fileDestinationUrl = documentDirectoryURL.appendingPathComponent(Constants.CHART_FILE_NAME)

            // reading from disk
            do {
                let file = try String(contentsOf: fileDestinationUrl)

                let newFile = file.replacingOccurrences(of: "\r", with: "") // Get rid of additional new lines

                let cleanChannels = self.generateCleanChannels(newFile)  // // a function used to retrieve the channels from the file (since the file it's not a plain CSV file but something different)
                channelNames.removeAll()
                dataSet.removeAll()
                channelNames  = self.getChannelNames(newFile)  // a function used to retrieve the channel names from the file (since the file it's not a plain CSV file but something different)

                var lineChartEntry = [ChartDataEntry]()


                data.clearValues()

                for (index,channel) in cleanChannels.enumerated() {
                    if (index <= Constants.MAX_CHART_CHANNELS) {
                        let csvImage = CSV(string: channel, delimiter: ";")

                        var y_max = 0.0
                        var firstXValueSampled : Bool = false
                        var firstXValue : Double = 0.0

                        var average = 0.0
                        var min     = 0.0
                        var max     = 0.0
                        var count   = 0

                        var new_xValues : [Double] = []
                        var new_yValues : [Double] = []

                        csvImage.enumerateAsDict { dict in
                            print(dict["X_Value"]!)

                            new_xValues.append(Double(dict["X_Value"]!)!)
                            new_yValues.append(Double(dict["Y_Value"]!)!)

                            if !firstXValueSampled {
                                firstXValueSampled = true
                                firstXValue = Double(dict["X_Value"]!)!
                            }

                            average = average + Double(dict["Y_Value"]!)!
                            count   = count + 1

                            let value = ChartDataEntry(x: (Double(dict["X_Value"]!)! - firstXValue), y: Double(dict["Y_Value"]!)! )
                            lineChartEntry.append(value)

                            if Double(dict["Y_Value"]!)! > max {
                                max = Double(dict["Y_Value"]!)!
                            }
                            if Double(dict["Y_Value"]!)! < min {
                                min = Double(dict["Y_Value"]!)!
                            }

                            if Double(dict["Y_Value"]!)! > y_max {
                                y_max = Double(dict["Y_Value"]!)!
                            }
                        }

                        xValues.append(new_xValues)
                        yValues.append(new_yValues)

                        average = average / Double(count)
                        averageValues.append(average)
                        minValues.append(min)
                        maxValues.append(max)

                        let line = LineChartDataSet(values: lineChartEntry, label: channelNames[index])

                        line.axisDependency = .left

                        line.colors = [channelColors[index]] // Set the color

                        line.setColor(channelColors[index].withAlphaComponent(0.5))
                        line.setCircleColor(channelColors[index])
                        line.lineWidth = 2.0
                        line.circleRadius = 3.0
                        line.fillAlpha = 65 / 255.0
                        line.fillColor = channelColors[index]
                        line.highlightColor = UIColor(red: 244/255, green: 117/255, blue: 117/255, alpha: 1)
                        line.drawCircleHoleEnabled = false
                        line.highlightLineWidth = 2.0
                        line.drawHorizontalHighlightIndicatorEnabled = true
                        data.setValueFont(.systemFont(ofSize: 9))

                        data.addDataSet(line)  // Add the line to the dataSet

                        let newData = LineChartData()
                        newData.setValueFont(.systemFont(ofSize: 9))
                        newData.addDataSet(line)
                        dataSet.append(newData)
                    }
                }

            } catch let error as NSError {
                print("error loading contentsOf url \(fileDestinationUrl)")
                print(error.localizedDescription)
            }

        } catch let error as NSError {
            print("error getting documentDirectoryURL")
            print(error.localizedDescription)
        }
    }


    // MARK: - Table view data source

    override func numberOfSections(in tableView: UITableView) -> Int {
        // #warning Incomplete implementation, return the number of sections
        return 1
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        // #warning Incomplete implementation, return the number of rows
        return dataSet.count
    }


    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        let cellIdentifier = "CustomChartCell"
        let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier,
                                                 for: indexPath) as! CustomChartCell
        cell.lineChartView.delegate = self

        if dataSet.count > 0 {
            cell.xValues     = xValues[indexPath.row]
            cell.yValues     = yValues[indexPath.row]
            cell.chartColor  = channelColors[indexPath.row]
            cell.channelName = channelNames[indexPath.row]
            cell.yMin        = minValues[indexPath.row]
            cell.yMax        = maxValues[indexPath.row]
            cell.yAvg        = averageValues[indexPath.row]

            cell.lineChartView.leftAxis.axisMaximum = cell.yValues.max()! + 1
            cell.lineChartView.leftAxis.axisMinimum = cell.yValues.min()! - 1
            cell.setChart(cell.xValues, cell.yValues)
        } else {
            cell.lineChartView.clearValues()
        }

        return cell
    }

    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        //tableView.deselectRow(at: indexPath, animated: true)

        let selectedCell = tableView.cellForRow(at: indexPath) as! CustomChartCell
        selectedCell.contentView.backgroundColor = UIColor.lightGray
        //let currentMatrix = selectedCell.lineChartView.viewPortHandler.touchMatrix

        //let selectedCell:UITableViewCell = tableView.cellForRow(at: indexPath)! as! CustomChartCell
        //selectedCell.contentView.backgroundColor = UIColor.lightGray
        //let currentMatrix = selectedCell.lineChartView.viewPortHandler.touchMatrix

        /*
         tableView.visibleCells.forEach { cell in
         if let cell = cell as? CustomChartCell {
         if cell.channelName != selectedCell.channelName {
         cell.lineChartView.viewPortHandler.refresh(newMatrix: currentMatrix, chart: cell.lineChartView, invalidate: true)
         }
         }
         }
         */
    }

    override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 250
    }

    override func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle {
        return .none
    }

    override func tableView(_ tableView: UITableView, shouldIndentWhileEditingRowAt indexPath: IndexPath) -> Bool {
        return false
    }

    override func setEditing (_ editing:Bool, animated:Bool)
    {
        super.setEditing(editing,animated:animated)
        if (self.isEditing) {
            //self.editButtonItem.title = "Editing"
            self.editButtonItem.title = NSLocalizedString("Done", comment: "Done Title")
        }
        else {
            self.editButtonItem.title = NSLocalizedString("Edit", comment: "Edit Title")
        }
    }

    override func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
        let movedObject = self.dataSet[sourceIndexPath.row]
        dataSet.remove(at: sourceIndexPath.row)
        dataSet.insert(movedObject, at: destinationIndexPath.row)
    }
}
Salva
  • 707
  • 2
  • 9
  • 18
  • 1
    Seems like you have set the chart delegate to be the cell, but the implementation of the delegate methods is in the view controller. You should implement the methods inside the delegate class, which in your case seems to be `CustomChartCell`. – gulyashki Nov 26 '18 at 12:23
  • Thank you Gulyashki, I made several tests (moving the the deletate reference inside and outside of the cell class), but I totally missed that point! It now seems to be working fine. – Salva Nov 27 '18 at 06:28

0 Answers0