I have built a small application to measure Heart Rate (HR) and currently trying to implement a chart using iOS Charts. It is working as intended (getting HR from watch and displaying it), but I have a small problem with the design of the chart.
On the image below you can see that I have overlapping numbers above the data line (70, and the later 90). I do not know how to remove them.
Chart Image
Here is my setup for the chart and its' update counter function:
//chart set up
self.chtChart.delegate = self as? ChartViewDelegate
let set_a: LineChartDataSet = LineChartDataSet(entries:[ChartDataEntry(x: Double(0), y: self.valueHR)], label: "HR")
set_a.drawCirclesEnabled = false
set_a.setColor(UIColor.systemPink)
self.chtChart.xAxis.drawGridLinesEnabled = false
self.chtChart.rightAxis.drawLabelsEnabled = false
self.chtChart.xAxis.drawLabelsEnabled = false
self.chtChart.data = LineChartData(dataSets: [set_a])
// update counter
var i = 1
#objc func updateCounter() {
self.chtChart.data?.addEntry(ChartDataEntry(x: Double(i), y: valueHR), dataSetIndex: 0)
self.chtChart.setVisibleXRange(minXRange: Double(0), maxXRange: Double(1000))
self.chtChart.notifyDataSetChanged()
self.chtChart.moveViewToX(Double(i))
i = i + 1
}
I know that there is a duplicate question, but the solution with the formatter did not help me: nothing was changed after the implementation of the solution.
formatter solution
Related
When creating a line chart from more than one data sets, the line chart only shows one of the data sets and when zooming or panning the chart it crashes with Fatal error: Can't form Range with upperBound < lowerBound.
If I create the line chart from one data set it works as expected.
This problem only occurs when the two datasets have completely different ranges of X values.
The code below should draw a chart with x ranging from 0 to 19 (i.e. 2 datasets). But it only draws the second dataset. The chart crashes if you pan or zoom it.
If I edit the code, replacing for x in (10..<20) with for x in (0..<10), both datasets are correctly drawn and the chart does not crash.
To summarise: when adding two dataSets that have entries with different ranges of X coordinates the chart draws incorrectly and will crash.
Is there an iOS_charts API call needed to prevent this? How can I draw two datasets that do not have overlapping X-coordinates?
I've been able to produce the same crash when running code using this demo code if I modify it to create multiple datasets that have non-overlapping x-coordinates.
class ElevationChartViewController: UIViewController {
#IBOutlet var chartView: LineChartView!
override func viewDidLoad() {
super.viewDidLoad()
chartView.backgroundColor = .white
chartView.legend.enabled = false
chartView.maxVisibleCount = 20000
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let dataSets = createChartDataSets()
chartView.data = LineChartData(dataSets: dataSets)
}
}
func createChartDataSets() -> [LineChartDataSet] {
var dataSets = [LineChartDataSet]()
var entriesOne = [ChartDataEntry]()
var entriesTwo = [ChartDataEntry]()
var y = 0.0
for x in (0..<10) {
entriesOne.append( ChartDataEntry(x: Double(x), y: y))
y = y + 10
if y > 60 {y = 0.0}
}
dataSets.append(LineChartDataSet(entriesOne))
for x in (10..<20) {
entriesTwo.append( ChartDataEntry(x: Double(x), y: y))
y = y + 10
if y > 50 {y = 0.0}
}
dataSets.append(LineChartDataSet(entriesTwo))
return dataSets
}
Swift version: 5.4
Xcode 12.4
Observed running on a real iPhone 12 sw version 14.4
Charts v4.0.1
I have been facing a similar issue and this solution worked for me so far. Not sure about potential side effects that could arise from this. I have not tested with panning or zooming.
Subclass LineChartDataSet and override entryIndex(x xValue:closestToY yValue:rounding) copy and paste the super implementation, but remove the guard statement at the top of the function
var closest = partitioningIndex { $0.x >= xValue }
guard closest < endIndex else { return -1 }
and replace with
var closest = partitioningIndex { $0.x >= xValue }
if closest >= endIndex {
closest = endIndex - 1
}
I'm very new dealing charts in swift. Using LineChartView to plot my data on graph. On zooming,values on Y-axis min and max change accordingly based on values of viewport min and max. For that implemented the following delegate.
func chartScaled(_ chartView: ChartViewBase, scaleX: CGFloat, scaleY: CGFloat) {
self.mapView.leftAxis.axisMaximum = axisY[Int(self.mapView.highestVisibleX)]
self.mapView.leftAxis.axisMinimum = axisY[Int(self.mapView.lowestVisibleX)]
}
Sometimes working fine, but after scaling the graph,the labels are not visible and graph is not scaling to normal position also.
Snippet for leftaxis
self.mapView!.leftAxis.enabled = true
self.mapView!.leftAxis.drawAxisLineEnabled = false
self.mapView!.leftAxis.spaceTop = 0.5
self.mapView!.leftAxis.spaceBottom = 0.4
self.mapView.leftAxis.granularity = 5.0
self.mapView.leftAxis.granularityEnabled = true
Please help me out where am doing wrong.
Thanks in advance.
I'm using ios-chart to present a calendar I've built. I'm currently using LineChart to plot my data, and I plot 1 point for each day of the year in one chart. So I have 365 points plotted in one chart. And it takes like 1 second to draw it. This isn't a huge issue, except that I have my calendar as a TableViewCell, which will result in a very hacky scroll once the TableViewCell is scrolled outside the ContentView and then scrolled back again (so the cell gets redrawn). It feels weird that it takes so long to draw around 400 points, even on an iPhone 6. I might be doing something wrong here?
My setup code for the chart:
lineChart.descriptionText = ""
lineChart.drawGridBackgroundEnabled = false
lineChart.userInteractionEnabled = false
lineChart.xAxis.drawAxisLineEnabled = false
lineChart.xAxis.drawGridLinesEnabled = false
lineChart.xAxis.drawLabelsEnabled = false
lineChart.drawBordersEnabled = false
lineChart.leftAxis.enabled = false
lineChart.rightAxis.enabled = false
lineChart.legend.enabled = false
lineChart.contentMode = .ScaleAspectFill
var xVals = [String]()
var dataSet = LineChartDataSet(yVals: [ChartDataEntry]())
for (index, value) in enumerate(plotData){
dataSet.addEntry(ChartDataEntry(value: Float(value), xIndex: index))
xVals.append("\(index)")
}
dataSet.setColor(Colors.whiteColor())
dataSet.lineWidth = 1.0
dataSet.circleRadius = 0.0
dataSet.drawCirclesEnabled = false
dataSet.drawValuesEnabled = false
dataSet.drawFilledEnabled = true
dataSet.fillColor = Colors.whiteColor()
dataSet.fillAlpha = 0.1
dataSet.valueTextColor = Colors.whiteColor()
lineChart.data = LineChartData(xVals: xVals, dataSet: dataSet)
The code above is done each time a cell is created (or reused). Any ideas?
The issue was in data that was being setup each time I reused the cell. Data creation should obviously not be in a cell, but somewhere else. Should be solved once I move my data initiation somewhere else.
Like the Health app on iOS 8 where null/empty data points are not displayed while X axis labels are still there. Using iOS-Charts as the chart library for my project is it possible to achieve the same?
You shouldn't pass a nil, it does not a accept a nil and Xcode will warn about it. What you should do, is just not pass it at all.
You do not have to pass a y value for every x index. You just have to make sure that the y values are ordered according to the x indices.
It can be achieved using iOS-Charts. I am currently making a project which requires various multiple line charts, multiple bar charts and line charts. Even if I have null values X axis labels are still there. You can ask me if you have any doubts.
Here's a function I wrote based on daniel.gindi's answer (I pass NaNs for the entries in the values array that are empty) :
func populateLineChartView(lineChartView: LineChartView, labels: [String], values: [Float]) {
var dataEntries: [ChartDataEntry] = []
for i in 0..<labels.count {
if !values[i].isNaN {
let dataEntry = ChartDataEntry(value: Double(values[i]), xIndex: i)
dataEntries.append(dataEntry)
}
}
let lineChartDataSet = LineChartDataSet(yVals: dataEntries, label: "Label")
let lineChartData = LineChartData(xVals: labels, dataSet: lineChartDataSet)
lineChartView.data = lineChartData
}
You can use the axis Maximum, so even if you don't have ChartDataEntry for that events it will still show
chartView.xAxis.axisMaximum = 60
chartView.xAxis.axisMaximum = 90
Simplest answer as Daniel mentioned is to just not pass the y value. Initially I was also confused as all functions needed x and y. This approach is how I did it
var dataEntries = [ChartDataEntry]()
for (iterating a source array){
let entry = ChartDataEntry()
if (some condition to check yValue exists) {
entry.x = xValue
entry.y = yValue
}
else
{
entry.x = xValue
}
dataEntries.append(entry)
}
I have created a CPTXYGraph using Core Plot in Swift. The graph works just fine. Now I am trying to add a legend. I have looked at several examples and come up with the following to create the legend:
var theLegend=CPTLegend(graph: graph)
var legendFill=CPTFill(color: CPTColor.blueColor())
theLegend.fill = legendFill
var legendLineStyle = CPTMutableLineStyle()
legendLineStyle.lineColor = CPTColor.whiteColor()
theLegend.borderLineStyle = legendLineStyle
theLegend.cornerRadius = 2.0
theLegend.swatchSize = CGSizeMake(15.0, 15.0)
graph.legend = theLegend
graph.legendAnchor = CPTRectAnchor.TopLeft
graph.legendDisplacement = CGPointMake(100.0, -100.0)
When I run this, I get a blue square with slightly rounded corners that appears to be about 10 X 10 pixels.
I tried setting the frame just to see if it would make a difference:
theLegend.frame = CGRectMake(0, 0, 200, 200)
This did not change anything.
Has anyone had any success with this in Swift?
Make sure all of your plots are already in the graph before using that legend initializer. You can also initialize a legend with an array of individual plots.
I found a solution here : Problems with legend in Core-Plot (pieChart)
You need to add this method
func legendTitleForPieChart(pieChart:CPTPieChart,recordIndex index:Int) -> NSString{
return "the title"
}
You need 3 things:
this delegate:
CPTPieChartDelegate
You need set the delegate to self on piechart:
pieChart.delegate = self
and feed the legend:
func legendTitleForPieChart(pieChart: CPTPieChart!, recordIndex idx: UInt) -> String! {
return "titulo"
}