I know there is a chart.leftAxis.startAtZeroEnabled = false option but it's not working for me.
I'm making a line graph to track Instagram Followers over time and I have data points that vary very little so I need the y-axis adjusted so that the changes are enabled, but it
Here is the code I have for the graph:
let chart = LineChartView(frame: chart1View.bounds)
let dayTrackerUnits = trackerCrawler.thisWeeksPoints()
var xVals : [Int] = []
var yVals : [ChartDataEntry] = []
var minimum = 1000000000
var maximum = 0
for i in 0..<dayTrackerUnits.count {
xVals.append(i)
yVals.append(ChartDataEntry(value: Double(dayTrackerUnits[i].followerCount), xIndex: i))
if dayTrackerUnits[i].followerCount < minimum {
minimum = dayTrackerUnits[i].followerCount
}
if dayTrackerUnits[i].followerCount > maximum {
maximum = dayTrackerUnits[i].followerCount
}
}
print(yVals)
let chartDataSet = LineChartDataSet(yVals: yVals, label: "Followers")
chartDataSet.setColor(GlobalStyles.sharedStyles.instagramBlue())
chartDataSet.lineWidth = 2.0
chartDataSet.circleRadius = 5.0
chartDataSet.circleColors = [GlobalStyles.sharedStyles.instagramBlue()]
chartDataSet.drawValuesEnabled = false
chartDataSet.drawVerticalHighlightIndicatorEnabled = true
let data = LineChartData(xVals: xVals, dataSet: chartDataSet)
chart.data = data
chart.delegate = self
chart.leftAxis.startAtZeroEnabled = false
chart.rightAxis.startAtZeroEnabled = false
print("min: \(minimum), max: \(maximum)")
chart.rightAxis.enabled = false
chart1View.addSubview(chart)
left1Label.text = "Followers"
I'd like to set the yAxisMinimum to minimum and the yAxisMaximum to maximum but the graph won't display how I want.
Any suggestions?
You can just use customAxisMin/Max to limit its range.
BTW, you have to make sure your data values does not contains 0. It seems like your data has 0, that's why it always start from 0. It has a small algorithm to calculate the axis range in computeAxisValues in y axis renderer.
Related
I am getting this as an orange error - not sure what I have done wrong or whether this error is the reason that the code doesn't plot. I am brand new to Pine-script and any assistance would be invaluable. Many thanks in advance Alastair
//#version=5
indicator("PCY TEST4")
//Get User Inputs
fast_length = input(title="Fast Length", defval=12)
slow_length = input(title="Slow Length", defval=26)
src = input(title="Source", defval=close)
signal_length = input.int(title="Signal Smoothing", minval = 1, maxval = 50, defval = 9)
sma_source = input.string(title="Oscillator MA Type", defval="EMA", options=["SMA", "EMA"])
sma_signal = input.string(title="Signal Line MA Type", defval="EMA", options=["SMA", "EMA"])
//Variables
a = 0.33
PCY = 0.0
PCYi = 0.0
// Calculating
fast_ma = sma_source == "SMA" ? ta.sma(src, fast_length) : ta.ema(src, fast_length)
slow_ma = sma_source == "SMA" ? ta.sma(src, slow_length) : ta.ema(src, slow_length)
macd = fast_ma - slow_ma
minMacd=0.0
for i = 0 to (signal_length[1])
minMacd := ta.min(macd)
maxMacd=0.0
for i = 0 to (signal_length[1])
maxMacd := ta.max(macd)
//maxMacd = ta.min(macd)
//minMacd = ta.max(macd)
//signal = sma_signal == "SMA" ? ta.sma(macd, signal_length) : ta.ema(macd, signal_length)
//hist = macd - signal
delta = maxMacd-(fast_ma - slow_ma)/(maxMacd -minMacd)
for i = 0 to (signal_length)
PCYi := a*((delta - PCYi[1])+PCYi[1])
PCYi
//Plotting
plot(PCYi)
I think it's because you're calculating the min/max in a for loop
Those functions should (not mandatory but strongly recommended) be calculated at every candle instead
for i = 0 to (signal_length[1])
minMacd := ta.min(macd)
maxMacd=0.0
for i = 0 to (signal_length[1])
maxMacd := ta.max(macd)
If you want to calculate the min/max over a X period of time, maybe use the ta.highest/ta.lowest functions instead :)
And of course, those 2 should be called at every candle (i.e. not in a if, for,... statement)
I am struggling to find out how to set gridlines on my chart to specific points on the x axis and to centre labels on the lines corresponding to the time of day. I have a simple chart, the data represents a day with about 280 data points (one for every 5 minute period). I want to show gridlines at times of the day equal to 03:00, 06:00, 12:00, 15:00, 18:00 and 21:00 and show the corresponding labels centred below the gridlines. I have have also tried LimitLines, which works for placing the vertical lines at the correct points, but there does not seem to be the ability to align the time labels with the centre of the limit lines.
I know this can be done because I have seen screenshots on-line showing time labels centred on gridlines (or limitlines) for specific periods on the day, but I've spent hours looking for how to do this an I'm drawing a blank.
My code below (note the commented out failed attempt to get limitlines to work).
let tideHeights = LineChartDataSet(entries: dataEntries)
tideHeights.drawCirclesEnabled = false
tideHeights.colors = [NSUIColor.black]
//let timings = ["00:00", "03:00", "06:00", "12:00", "15:00", "18:00", "21:00"]
let data = LineChartData(dataSet: tideHeights)
lineChartView.xAxis.valueFormatter = IndexAxisValueFormatter(values: dataPoints)
//lineChartView.xAxis.drawGridLinesEnabled = false
//let gridLine = values.count / timings.count
//var xPos = 0
//for label in timings {
//let limitLine = ChartLimitLine(limit: Double(xPos), label: label)
//limitLine.labelPosition = .bottomLeft
//limitLine.lineWidth = 0.5
//limitLine.lineColor = .black
//limitLine.valueTextColor = .black
//lineChartView.xAxis.addLimitLine(limitLine)
//xPos += gridLine
//}
lineChartView.xAxis.labelCount = 7
lineChartView.xAxis.labelPosition = .bottom
lineChartView.xAxis.granularity = 1
lineChartView.xAxis.avoidFirstLastClippingEnabled = true
lineChartView.legend.enabled = false
lineChartView.data = data
The output is below, the gridlines are at system set positions, how can I control where they are placed?
Update:
After applying the very helpful suggestion by Stephan Schlecht, I am still getting strange results. With lineChartView.xAxis.setLabelCount(9, force: true) I get:
and with lineChartView.xAxis.setLabelCount(4, force: true) I get:
It seems for some reason that the 1st gridline after the Axis is ignored.
Code is:
lineChartView.xAxis.valueFormatter = IndexAxisValueFormatter(values: dataPoints)
let marker = ChartMarker()
marker.setMinuteInterval(interval: 5)
marker.chartView = lineChartView
lineChartView.marker = marker
lineChartView.xAxis.setLabelCount(4, force: true)
lineChartView.xAxis.avoidFirstLastClippingEnabled = true
lineChartView.xAxis.labelPosition = .bottom
lineChartView.leftAxis.labelPosition = .insideChart
lineChartView.rightAxis.drawLabelsEnabled = false
lineChartView.legend.enabled = false
lineChartView.data = data
lineChartView.data?.highlightEnabled = true
Update 2
To reproduce, I have the following class which creates the data (btw, it is a stub class, eventually it will contain a paid api, so for now I generate the data for test purposes:
class TideHeights {
var tideHeights = [Double]()
var tideTimes = [Date]()
var strTimes = [String]()
var intervalMins = 5
init (intervalMins: Int) {
self.intervalMins = intervalMins
let slots = 24 * 60.0 / Double(intervalMins)
let seconds = 24 * 60 * 60.0 / slots
let radians = 2 * Double.pi / slots
let endDate = Date().endOfDay
var date = Date().startOfDay
var radianOffset = 0.0
repeat {
tideHeights.append(sin(radianOffset) + 1)
tideTimes.append(date)
let timeFormatter = DateFormatter()
timeFormatter.dateFormat = "HH:mm"
strTimes.append(timeFormatter.string(from: date))
date = date.advanced(by: seconds)
radianOffset += radians
} while date <= endDate
}
}
this class is instantiated and used to draw the graph in the following two functions in ViewController.
func drawChart() -> Void {
let tideData = TideHeights(intervalMins: 5)
lineChartView.dragEnabled = true
lineChartView.setScaleEnabled(false)
lineChartView.pinchZoomEnabled = false
setChartValues(dataPoints: tideData.strTimes, values: tideData.tideHeights)
}
func setChartValues(dataPoints: [String], values: [Double]) -> Void {
//initialise and load array of chart data entries for drawing
var dataEntries: [ChartDataEntry] = []
for i in 0..<values.count {
let dataEntry = ChartDataEntry(x: Double(i), y: values[i])
dataEntries.append(dataEntry)
}
let tideHeights = LineChartDataSet(entries: dataEntries)
tideHeights.drawCirclesEnabled = false
tideHeights.colors = [NSUIColor.black]
let gradientColors = [ChartColorTemplates.colorFromString("#72E700").cgColor,
ChartColorTemplates.colorFromString("#724BEB").cgColor]
let gradient = CGGradient(colorsSpace: nil, colors: gradientColors as CFArray, locations: nil)!
tideHeights.fillAlpha = 0.7
tideHeights.fill = Fill(linearGradient: gradient, angle: 90)
tideHeights.drawFilledEnabled = true
let data = LineChartData(dataSet: tideHeights)
lineChartView.xAxis.valueFormatter = IndexAxisValueFormatter(values: dataPoints)
let marker = ChartMarker()
marker.setMinuteInterval(interval: 5)
marker.chartView = lineChartView
lineChartView.marker = marker
lineChartView.xAxis.setLabelCount(4, force: true)
lineChartView.xAxis.avoidFirstLastClippingEnabled = true
lineChartView.xAxis.labelPosition = .bottom
lineChartView.leftAxis.labelPosition = .insideChart
lineChartView.rightAxis.drawLabelsEnabled = false
lineChartView.legend.enabled = false
lineChartView.data = data
lineChartView.data?.highlightEnabled = true
}
The documentation says:
setLabelCount(int count, boolean force): Sets the number of labels for
the y-axis. Be aware that this number is not fixed (if force == false)
and can only be approximated. If force is enabled (true), then the
exact specified label-count is drawn – this can lead to uneven numbers
on the axis.
see https://weeklycoding.com/mpandroidchart-documentation/axis-general/
Remark: although the y-axis is mentioned in the documentation of the function, it can actually be applied to both axes.
So instead of
lineChartView.xAxis.labelCount = 7
use
lineChartView.xAxis.setLabelCount(9, force: true)
Note: you need 9 label counts not 7.
Test
If one combines a sine wave of 288 points with your code above
for i in 0...288 {
let val = sin(Double(i) * 2.0 * Double.pi / 288.0) + 1
dataEntries.append(ChartDataEntry(x: Double(i), y: val))
}
and forces the label count = 9 as shown, then it looks like this:
Stephan was correct in his answer to this question. To force gridlines and alignment, use setLabelCount(x, force: true) the additional complexity related to my specific project and the data contained in the labels.
I would like to know how should I detect overlapping nodes while enumerating them? Or how should I make that every random generated position in Y axis is at least some points higher or lower.
This is what I do:
1 - Generate random number between -400 and 400
2 - Add those into array
3 - Enumerate and add nodes to scene with generated positions like this:
var leftPositions = [CGPoint]()
for _ in 0..<randRange(lower: 1, upper: 5){
leftPositions.append(CGPoint(x: -295, y: Helper().randomBetweenTwoNumbers(firstNumber: leftSparkMinimumY, secondNumber: leftSparkMaximumY)))
}
leftPositions.enumerated().forEach { (index, point) in
let leftSparkNode = SKNode()
leftSparkNode.position = point
leftSparkNode.name = "LeftSparks"
let leftSparkTexture = SKTexture(imageNamed: "LeftSpark")
LeftSpark = SKSpriteNode(texture: leftSparkTexture)
LeftSpark.name = "LeftSparks"
LeftSpark.physicsBody = SKPhysicsBody(texture: leftSparkTexture, size: LeftSpark.size)
LeftSpark.physicsBody?.categoryBitMask = PhysicsCatagory.LeftSpark
LeftSpark.physicsBody?.collisionBitMask = PhysicsCatagory.Bird
LeftSpark.physicsBody?.contactTestBitMask = PhysicsCatagory.Bird
LeftSpark.physicsBody?.isDynamic = false
LeftSpark.physicsBody?.affectedByGravity = false
leftSparkNode.addChild(LeftSpark)
addChild(leftSparkNode)
}
But like this sometimes they overlap each other because the generated CGPoint is too close to the previous one.
I am trying to add some amount of triangles to the wall and those triangles are rotated by 90°
To describe in image what I want to achieve:
And I want to avoid thing like this:
Your approach to this is not the best, i would suggest only storing the Y values in your position array and check against those values to make sure your nodes will not overlap. The following will insure no two sparks are within 100 points of each other. You may want to change that value depending on your node's actual height or use case.
Now, obviously if you end up adding too many sparks within an 800 point range, this just will not work and cause an endless loop.
var leftPositions = [Int]()
var yWouldOverlap = false
for _ in 0..<randRange(lower: 1, upper: 5){
//Moved the random number generator to a function
var newY = Int(randY())
//Start a loop based on the yWouldOverlap Bool
repeat{
yWouldOverlap = false
//Nested loop to range from +- 100 from the randomly generated Y
for p in newY - 100...newY + 100{
//If array already contains one of those values
if leftPosition.contains(p){
//Set the loop Bool to true, get a new random value, and break the nested for.
yWouldOverlap = true
newY = Int(randY())
break
}
}
}while(yWouldOverlap)
//If we're here, the array does not contain the new value +- 100, so add it and move on.
leftPositions.append(newY)
}
func randY() -> CGFloat{
return Helper().randomBetweenTwoNumbers(firstNumber: leftSparkMinimumY, secondNumber: leftSparkMaximumY)
}
And here is a different version of your following code.
for (index,y) in leftPositions.enumerated() {
let leftSparkNode = SKNode()
leftSparkNode.position = CGPoint(x:-295,y:CGFloat(y))
leftSparkNode.name = "LeftSparks\(index)" //All node names should be unique
let leftSparkTexture = SKTexture(imageNamed: "LeftSpark")
LeftSpark = SKSpriteNode(texture: leftSparkTexture)
LeftSpark.name = "LeftSparks"
LeftSpark.physicsBody = SKPhysicsBody(texture: leftSparkTexture, size: LeftSpark.size)
LeftSpark.physicsBody?.categoryBitMask = PhysicsCatagory.LeftSpark
LeftSpark.physicsBody?.collisionBitMask = PhysicsCatagory.Bird
LeftSpark.physicsBody?.contactTestBitMask = PhysicsCatagory.Bird
LeftSpark.physicsBody?.isDynamic = false
LeftSpark.physicsBody?.affectedByGravity = false
leftSparkNode.addChild(LeftSpark)
addChild(leftSparkNode)
}
I need to round stocks, indices and futures prices to the nearest tick. The first step is to look if the price is a multiple of the tick. Apple docs says "Unlike the remainder operator in C and Objective-C, Swift’s remainder operator can also operate on floating-point numbers".
If I write the following code in a playground or in a console app and I run it, I expect 0 as result but I get a remainder value equals to 0.00999999999999775:
var stringPrice = "17.66"
var price = Double(stringPrice)
var tickSize: Double = 0.01
let remainder = price! % ticksize
This problem breaks my rounding function when using values such 17.66 as aPrice and 0.01 as aTickSize:
func roundPriceToNearestTick(Price aPrice: Double, TickSize a TickSize: Double)-> Double{
let remainder = aPrice % aTickSize
let shouldRoundUp = remainder >= aTickSize/2 ? true : false
let multiple = floor(aPrice/aTickSize)
let returnPrice = !shouldRoundUp ? aTickSize*multiple : aTickSize*multiple + aTickSize
return returnPrice
}
What is the best way to fix this?
Following the comments about the broken floating point math and the need to avoid floats and doubles for all the operations concerning money I changed my code to perform the remainder operation using NSDecimalNumbers. This seems to solve the precision problem.
var stringPrice = "17.66"
var tickSizeDouble : Double = 0.01
var tickSizeDecimalNumber: NSDecimalNumber = 0.01
func decimalNumberRemainder(Dividend aDividend: NSDecimalNumber, Divisor aDivisor: NSDecimalNumber)->NSDecimalNumber{
let behaviour = NSDecimalNumberHandler(roundingMode: NSRoundingMode.RoundDown,
scale: 0,
raiseOnExactness: false ,
raiseOnOverflow: false,
raiseOnUnderflow: false,
raiseOnDivideByZero: false )
let quotient = aDividend.decimalNumberByDividingBy(aDivisor, withBehavior: behaviour)
let subtractAmount = quotient.decimalNumberByMultiplyingBy(aDivisor)
let remainder = aDividend.decimalNumberBySubtracting(subtractAmount)
return remainder
}
let doubleRemainder = Double(stringPrice)! % tickSizeDouble
let decimalRemainder = decimalNumberRemainder(Dividend: NSDecimalNumber(string: stringPrice), Divisor:tickSizeDecimalNumber)
print("Using Double: \(doubleRemainder)")
print("Using NSDecimalNumber: \(decimalRemainder)")
I'm using vDSP_conv to perform autocorrelation. Mostly it works just fine but every so often it's filling the output array with NaNs.
The code:
func corr_test() {
var pass = 0
var x = [Float]()
for i in 0..<2000 {
x.append(Float(i))
}
while true {
print("pass \(pass)")
let corr = autocorr(x)
if corr[1].isNaN {
print("!!!")
}
pass += 1
}
}
func autocorr(a: [Float]) -> [Float] {
let resultLen = a.count * 2 + 1
let padding = [Float].init(count: a.count, repeatedValue: 0.0)
let a_pad = padding + a + padding
var result = [Float].init(count: resultLen, repeatedValue: 0.0)
vDSP_conv(a_pad, 1, a_pad, 1, &result, 1, UInt(resultLen), UInt(a_pad.count))
return result
}
The output:
pass ...
pass 169
pass 170
pass 171
(lldb) p corr
([Float]) $R0 = 4001 values {
[0] = 2.66466637E+9
[1] = NaN
[2] = NaN
[3] = NaN
[4] = NaN
...
I'm not sure what's going on here. I think I'm handling the 0 padding correctly since if I weren't I don't think I'd be getting correct results 99% of the time.
Ideas? Gracias.
Figured it out. The key was this comment from https://developer.apple.com/library/mac/samplecode/vDSPExamples/Listings/DemonstrateConvolution_c.html :
// “The signal length is padded a bit. This length is not actually passed to the vDSP_conv routine; it is the number of elements
// that the signal array must contain. The SignalLength defined below is used to allocate space, and it is the filter length
// rounded up to a multiple of four elements and added to the result length. The extra elements give the vDSP_conv routine
// leeway to perform vector-load instructions, which load multiple elements even if they are not all used. If the caller did not
// guarantee that memory beyond the values used in the signal array were accessible, a memory access violation might result.”
“Padded a bit.” Thanks for being so specific. Anyway here's the final working product:
func autocorr(a: [Float]) -> [Float] {
let filterLen = a.count
let resultLen = filterLen * 2 - 1
let signalLen = ((filterLen + 3) & 0xFFFFFFFC) + resultLen
let padding1 = [Float].init(count: a.count - 1, repeatedValue: 0.0)
let padding2 = [Float].init(count: (signalLen - padding1.count - a.count), repeatedValue: 0.0)
let signal = padding1 + a + padding2
var result = [Float].init(count: resultLen, repeatedValue: 0.0)
vDSP_conv(signal, 1, a, 1, &result, 1, UInt(resultLen), UInt(filterLen))
// Remove the first n-1 values which are just mirrored from the end so that [0] always has the autocorrelation.
result.removeFirst(filterLen - 1)
return result
}
Note that the results here aren't normalized.