CorePlot x axis labels - core-plot

OK, my final issue with getting my bar chart setup is how to print the X axis labels. I tried this:
if let axis = graph.axisSet as? CPTXYAxisSet, xAxis = axis.xAxis {
let dateLabels = self!.dates!.map {
CPTAxisLabel(text: NSDateFormatter.localizedStringFromDate($0, dateStyle: .ShortStyle, timeStyle: .NoStyle), textStyle: nil)
}
xAxis.axisLabels = Set(dateLabels)
}
I'm getting nothing displayed though. I looked at DatePlot sample but I don't want to do what it's doing as it incorrectly assumes that a day is 86,400 seconds long, and that will break multiple times. Also, my date offsets are in months, so that makes it even worse. Can't I just somehow provide the already formatted date string?
Seems strange to me that "axisLabels" would be a set, since a set is not ordered.

Each axis label has a tickLocation. Set the tick location to the bar location for that label in the same units used in the plot space and datasource. Since the label tick location is not related to its position in the array, we can use an unordered collection like a set.

Related

Facing error in swift UI “Invalid frame dimension (negative or non-finite)” while drawing negative value in chart

In widget iOS14, I want to show values in graphical form using bar chart.
I have used this library "https://github.com/dawigr/BarChart" to draw bar chart.
But in Xcode12+, it's not showing negative values and considering negative value as 0 and throwing warning as shown in screen shot.
"[SwiftUI] Invalid frame dimension (negative or non-finite)"
You could try to normalise your input Values to prevent getting errors like this.
e.g.: if your data set contains values from -10 to 100, your min normalised value would be 0 and your max normalised value 1. This only works if your numbers are CGFloat, Double or something like this, numbers in Int format would be rounded up.
This could be done by using an extension like this:
extension Array where Element == Double {
var noramlized: [Double] {
if let min = self.min(), let max = self.max() {
return self.map{ ($0 - min) / (max - min) }
}
return []
}
}
I don't no how you get your values for the frame exactly, but I think you did something like this:
// normalise your data set:
let data : [Double] = [Double]()
youChart(inputData: data.noramlized)
// get values for the frame
let height = data.noramlized.max()
// if your normalised value is too small for your purpose (your max will always be 1.0 but in relation to the rest it might fit), you can scale it up like height * 20.
// the width might be a non changing value that you will enter manually or it will append on the count of bars in your chart.

SciCharts - ISCIAxisCore visible min and max index

I am using the SciChart API for 2D Charts from SciCharts, I wanted to know if it is possible to know the min and max visible index/indices from ISCIAxisCore.
I need to get this information from the callback made to SCIVisibleRangeChangeListener when visible range as changed. So I can calculate some extended information, all the data is stored in a few arrays and the graph is only showing a section of it and I need to show some average values based on the visible range of the graph.
I know I could use Swift API to get the index out of the array but this seems to me like the most inefficient way of getting the visible min and max index of the data set, as it will need to search in a data set that can span more than 5k records.
I suspect, you are looking for one of the following:
search index range directly on the DataSeries, using -getIndicesXRange:xCoordinateCalculator:
search index in the underlying ISCIList, via the -findIndexOf:searchMode:isSorted: on the dataSeries.xValues
I added the following listener with prints into our Line Chart Example, which showcases how to use getIndicesXRange:
xAxis.visibleRangeChangeListener = { (axis, oldRange, newRange, animated) in
guard animated == false else { return }
if let axis = axis, let min = newRange?.minAsDouble, let max = newRange?.maxAsDouble {
let indicesRange = SCIIndexRange()
dataSeries.getIndicesXRange(indicesRange, xCoordinateCalculator: axis.currentCoordinateCalculator)
print("Values: \(min) : \(max)")
print("Min: \(indicesRange.min), Max \(indicesRange.max)")
}
}
Hope that helps.

Records are slight shifted on date plot created using coreplot library

Here is a necessary code snippets,
X axis label formatter,
NSDateFormatter dateFormatter = new NSDateFormatter();
dateFormatter.DateFormat = "dd/MM";
var timeFormatter = new CPTTimeFormatter(dateFormatter);
timeFormatter.ReferenceDate = NSDate.FromTimeIntervalSinceNow(0);
x.LabelFormatter = timeFormatter;
Delegate method of getting records,
public override NSNumber NumberForPlot(CPTPlot plot, CPTPlotField forFieldEnum, nuint index)
{
if (forFieldEnum == CPTPlotField.ScatterPlotFieldX)
return new NSNumber((index + 1) * 86400);
Debug.WriteLine("{0}", Data[(int)index].Rate);
return Data[(int)index].Rate;
}
See attached screenshot for result looks like. You can see that markers are not aligned to X axis. First data point should display on “01/01” but it is displaying just before it. Same for all other points.
Let me know if anybody wish to look at any other part of code. I just need direction or clue what could lead to this record shifting. I have already looked at sample code provided in coreplot but didn't get any clue on this.
Edit:
Ranges are as below,
plotSpace.XRange = new CPTPlotRange(NSNumber.FromDouble(0).NSDecimalValue, NSNumber.FromDouble(86400 * 9).NSDecimalValue);
plotSpace.YRange = new CPTPlotRange(NSNumber.FromDouble(-1).NSDecimalValue, NSNumber.FromDouble(9).NSDecimalValue);
Also tried,
var space = graph.DefaultPlotSpace as CPTXYPlotSpace;
space.ScaleToFitPlots(new CPTPlot [] { dataSourceLinePlot });
Edit: Graph setup code
The problem is with the ReferenceDate for the time formatter. It is being initialized with the current date and time, so the offset will vary throughout the day depending on when the setup code runs. There are several ways to make an NSDate object that corresponds to a fixed time of day. The most straightforward is by using NSDateComponents.
Several of the Core Plot example apps, including the "Date Plot" demo in the Plot Gallery app, use this technique to generate reference dates.
Also, the automatic axis labeling policy doesn't work well with dates. It's picking tick locations that fall on "nice" numbers of seconds between ticks, but that doesn't correspond to even numbers of days. You should use the fixed interval policy (the default) or one of the ones that let you provide the tick locations directly.

UI Slider Time of Day Beyond Midnight?

So this is a unique situation. I have a double slider I made using swift to make a time range picker like the one Kayak has. Unfortunately mine needs to be a time range between 00:00 all the way to 04:00 the following morning.
It was easy to get it from 00:00 to 23:59 using a scale of 86340 seconds for my slider control. I simply plug that into this little function and out pops the correct range on both ends/knobs:
func getTimeStringFromSeconds(seconds: Double) -> String {
let dcFormatter = NSDateComponentsFormatter()
dcFormatter.zeroFormattingBehavior = NSDateComponentsFormatterZeroFormattingBehavior.Pad
dcFormatter.allowedUnits = [NSCalendarUnit.Hour, NSCalendarUnit.Minute]
dcFormatter.unitsStyle = NSDateComponentsFormatterUnitsStyle.Positional
return dcFormatter.stringFromTimeInterval(seconds)!
}
As you can see in the screenshot above though I have 100740 seconds instead as the scale. How can I get it to go beyond 23:59, then reset to 00:00 and go into the next day? I can make it go to 28:00 as seen above which would technically be 4am but I want it to start over and show 04:00, not 28:00. What's a good solution for this?
Just as a note, my only solution so far was a sort of hack to make the label say 0:00 by resetting the seconds to 0.00 once it goes over 86340. The scale is still 100740 but there's some math like the following to make the label say otherwise without messing with the value of the knob (upperValue):
if upperKnobLayer.highlighted {
upperValue += valueDelta
upperValue = max(min(maximumValue, upperValue), lowerValue)
var upperDouble = Double(round(upperValue))
if upperDouble > 86340.00 {
let newValue = upperDouble - 86340
upperDouble = 0.00 + newValue
}
upperTime = getTimeStringFromSeconds(upperDouble)
}
It would help to post the implementation of your picker but generally you want the sliders value type to be NSTimeIntervals. Then you can have a reference date which will be midnight 00:00 of today. You can use NSDate(timeIntervalSince:referenceDate) to then get an NSDate representing any date since that reference date which can be formatted accordingly and display to the screen. The added benefit of this way is that the actual day of the date will be correct if your timeInterval makes it spill over to the next day
How about normalizing to a day's worth of seconds.
return dcFormatter.stringFromTimeInterval(seconds % 86340)!

LineChart plot data not filling its UIView

So I'm using the library iOS-Charts for creating a calendar. The calendar shows plots of each day for each month. So for 1 year I have 12 UIViews plotted with data from each corresponding month. So for January there are 31 points in the line chart (since it's 31 days in January), in February I have 28 plots and so on. However, the UIView does not get completely filled with the LineChart.
So for February it looks like 3 points are missing, since the plot is not stretching the entire UIView. I've tried everything I can think of, setting autoresizingMask for the UIView, or the lineChart (which is a LineChartView), as well as setting the UIView.contentMode for both. Nothing helps.
I set the xRange of the LineChartView to be the amount of points I want, which depends on the number of days in the month. I then proceed to create the pageView (which are UIView's used to plot the LineChart on). Anyone know what I'm missing here? Below are my settings for each individual LineCharts.
// Create and set LineChart
var lineChart = LineChartView()
lineChart.descriptionText = ""
lineChart.delegate = self
lineChart.noDataTextDescription = "You need to provide data for the chart."
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
lineChart.autoresizingMask = UIViewAutoresizing.FlexibleWidth
From your screenshot, I am guessing the xIndex count in your LineChartData is the same. Your last data point 5496 on Feb view is aligned perfectly with 2457 on March view, which indicates the share the same x values count on xAxis.
Based on your description, you should have 12 LineChartData objects, and each line chart data has its own xValues and dataSets, which means 12 xValues and 12 dataSets. You need to debug on xAxis renderer to see if I am correct.
If not resolved, then you need to provide your chart data code. I don't think it's a bug, it's some kinds of mistakes you made while creating chart data.

Resources