scatter graph + line graph in ios-Charts - ios

Hi i need a graph as attached for ios. I am using ios-Chart library(swift alternative of MPAndroidChart) for swift .
I have managed to get these points on the graph using the scatter graph. But i couldn't figure out how will i connect the two vertical points. Any help or early response will be appreciate able.
my current code is :
func drawChart(dataPoints:[String] , value1 :[Double] , value2:[Double])
{
var dataEntries1:[ChartDataEntry] = []
for i in 0..<dataPoints.count {
let dataEntry = ChartDataEntry(value:value1[i] , xIndex : i)
dataEntries1.append(dataEntry)
}
var dataEntries2:[ChartDataEntry] = []
for i in 0..<dataPoints.count {
let dataEntry = ChartDataEntry(value:value2[i] , xIndex : i)
dataEntries2.append(dataEntry)
}
let dataSet1 = ScatterChartDataSet(yVals: dataEntries1, label: "Value1" )
dataSet1 .setColor(UIColor.blueColor())
let dataSet2 = ScatterChartDataSet(yVals: dataEntries2 ,label: "Value2")
dataSet2.setColor(UIColor.greenColor())
var bloodPressureDataSets = [ScatterChartDataSet]()
bloodPressureDataSets.append(dataSet1)
bloodPressureDataSets.append(dataSet2)
let barChartData = ScatterChartData(xVals: dataPoints, dataSets: bloodPressureDataSets)
bpChart.xAxis.labelPosition = .Bottom
bpChart.rightAxis.enabled=false
//barChart.legend.enabled=false
bpChart.descriptionText=""
bpChart.data = barChartData
}
Currently i can see this type of graph using the above code.
I want to join these two vertical points like the graph below,

take a look at scatter chart renderer, drawDataSet func. You can connect the dots there
UPDATE towards your comments:
first, go to ScatterChartRenderer and locate to
internal func drawDataSet(context context: CGContext, dataSet: ScatterChartDataSet)
This is where we calculate the position and draw the shape here
There is a main loop:
for (var j = 0, count = Int(min(ceil(CGFloat(entries.count) * _animator.phaseX), CGFloat(entries.count))); j < count; j++)
{
let e = entries[j]
point.x = CGFloat(e.xIndex)
point.y = CGFloat(e.value) * phaseY
point = CGPointApplyAffineTransform(point, valueToPixelMatrix);
...
}
Here's the iteration of the data entries your provide in the dataSet, we just get the xIndex and y value here and convert it to the coordinate on the screen.
So what you can do is to sub class this renderer, override this function to get what you want.
e.g. You want to connect the data entries(the dot) for the same xIndex, you should first iterate each data set to collect all the entries on same xIndex, use CGPointApplyAffineTransform(point, valueToPixelMatrix) to convert and use CoreGraphics APIs to draw the line. You don't need to worry about the math, the library already gives you the API to convert between data value and the screen coordinate value. You just focus on how to draw the chart.

Related

Swift Chart with multiple chart lines only displays the last line on the chart [duplicate]

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
}

highlightValue not working in chart with CombinedChartView :: Display marker programmatically is not working :: ChartIssue :: iOS Swift :: DanielGindi

Here is what i am doing!
chart.highlightValue(x: timeStampValue, dataSetIndex: totalCount)
==> In the above line,
timeStampValue is x axis value which i have set while filling up the array.
totalCount is total count of array of data which i am displaying in chart.
What i need to achieve is
When chart screen comes up, i need to display marker by default and for that, i am using "highlightValue" method of chart which is not working.
Please let me know the solution to show marker by default programatically.
NOTE: I am using marker whose UI is custom which is working fine when i tap manually at point in chart:
let marker = CustomMarkerView.viewFromXib()!
marker.chartView = chart
chart.marker = marker
chart.drawMarkers = true
Library used : https://github.com/danielgindi/Charts
Chart Data set :
let data = CombinedChartData()
data.lineData = LineChartData(dataSets:[viewModel.lineChartDataSet, viewModel.emptylineChartDataSet])
data.lineData.highlightEnabled = true
viewModel.lineChartDataSet.highlightColor = AssetsColor.highlightedColor.color
viewModel.lineChartDataSet.drawHorizontalHighlightIndicatorEnabled = false
viewModel.lineChartDataSet.highlightLineDashPhase = 2
viewModel.lineChartDataSet.highlightLineDashLengths = [5, 2.5]
you are using the wrong value for dataSetIndex param
based on your code, the datasets only contains 2 data
data.lineData = LineChartData(dataSets:[viewModel.lineChartDataSet, viewModel.emptylineChartDataSet])
dataSetIndex is not data count, in linechart dataset represent a line that has many data (x,y), so dataSetIndex is more like which line
so your code should be something like this
chart.highlightValue(x: timeStampValue, dataSetIndex: 0)
chart.highlightValue(x: timeStampValue, dataSetIndex: 0, dataIndex: 0)
When i added 1 more parameter which is dataIndex as 0 and it worked.
Here, dataSetIndex is set to 0 because it is CombinedChartView where i have merged 2 data set.

Why only first plot is not labeled with a shape, unlike the rest of plots in the graph?

I am trying to plot points on lineChartView class. I then plotted data on it, but I see a very funny thing. The second plot is labeled, but not the first one.
I am setting up LineChartView instance and named it lineChart:
var lineChart: LineChartView = {
var l = LineChartView()
l.translatesAutoresizingMaskIntoConstraints = false
l.backgroundColor = .white
return l
}()
lineChartDataPoints holds ChartDataEntry classes, which holds x and y values:
var lineChartDataPoints: [ChartDataEntry] = []
I loop over xData and append ChartDataEntry class to lineChartDataPoints. xData and yData variable hold x and y values: (Which is generated in other function and not really a point of this question)
for i in 0..<xData.count {
let data = ChartDataEntry(x: Double(i + 1), y: Double(yData[i])!)
lineChartDataPoints.append(data)
}
Then I add lineChartDataPoints to LineChartDataSet:
let lineDataSet = LineChartDataSet(values: lineChartDataPoints, label: "Values")
Then lineDataSet is added to lineData after setting parameters.
lineDataSet.colors = [UIColor.red]
lineDataSet.lineWidth = 5
lineDataSet.circleColors = [UIColor.blue]
lineDataSet.circleRadius = 5
lineData.addDataSet(lineDataSet)
And this is obviously not because there is only one point. Because I tried this:
print("data \(lineChartDataPoints)")
// data [ChartDataEntry, x: 1.0, y 1.0, ChartDataEntry, x: 6.0, y 1.0]
There definitely are two points supplied for the graph to display, but I am pretty stumped on why first point won't be labeled with blue dot like second point.
Before installing the new Charts framework (Version 3.2) for Swift 5, I did not have this issue. There were some tweaks to be made in Charts framework. Installing newest patch (3.3) have solved this problem. So update to the latest version of this framework to avoid this problem.

iOS-Charts null data point do not show on chart

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)
}

Cannot Assign a value of type CGPoint to a value of Type CGPoint

This is my code. I'm trying to make a small tile game and this is the function to randomise the tiles location. The problem i'm having here is to assign the randLocation to the imageview.
var imageViewCentersCopy : NSMutableArray = imageViewsCenters.mutableCopy() as! NSMutableArray
var randLocationIndex : Int
var randLocation : CGPoint
for imageView in self.imageViews {
randLocationIndex = Int(arc4random_uniform(UInt32(imageViewCentersCopy.count)))
randLocation = imageViewCentersCopy.objectAtIndex(randLocationIndex).CGPointValue()
println("\(self.imageViews)")
imageView.center = randLocation
}
Use the typecast to avoid that problem
(imageView as! UIImageView).center = randLocation
I think it is not very expedient to use the coordinates in your randomizing logic. These are really two separate problems. Try to think of a simpler model, then develop the placement logic.
For example, you could just randomize the order of the tiles in a flat array.
Then you simply iterate through the array, calculate the correct coordinates with some simple row and column algorithm (based on the index) and place the tiles in their correct position.
This, too, is MVC.
E.g. assuming 9 tiles for a 3x3 grid:
for var x = 0; x < randomTiles.count; x++ {
let row = x / 3
let column = x % 3
let point = CGPointMake(10 + column * 20, 10 + row * 20)
// assign the tile's center
}

Resources