ChartsOrg / Charts

Beautiful charts for iOS/tvOS/OSX! The Apple side of the crossplatform MPAndroidChart.
Apache License 2.0
27.58k stars 6k forks source link

Crash while selecting data point in zoomed state #3486

Open Saranjithpk opened 6 years ago

Saranjithpk commented 6 years ago

What did you do?

ℹ I zoomed the charts and made a selection to one of the data point.

What did you expect to happen?

ℹ The delegate method with selected coordinates should be called.

What happened instead?

ℹ Crash happed.

Crash log:

> * thread #1, queue = 'com.apple.main-thread', stop reason = Fatal error: Double value cannot be converted to Int because it is either infinite or NaN
    frame #0: 0x0000000116b4f900 libswiftCore.dylib`_swift_runtime_on_report
    frame #1: 0x0000000116b90181 libswiftCore.dylib`_swift_stdlib_reportFatalError + 113
    frame #2: 0x0000000116b2c38f libswiftCore.dylib`partial apply forwarder for closure #1 (Swift.UnsafeBufferPointer<Swift.UInt8>) -> () in closure #2 (Swift.UnsafeBufferPointer<Swift.UInt8>) -> () in Swift._fatalErrorMessage(Swift.StaticString, Swift.StaticString, file: Swift.StaticString, line: Swift.UInt, flags: Swift.UInt32) -> Swift.Never + 63
    frame #3: 0x00000001168571eb libswiftCore.dylib`function signature specialization <preserving fragile attribute, Arg[1] = [Closure Propagated : reabstraction thunk helper from @callee_owned (@unowned Swift.UnsafeBufferPointer<Swift.UInt8>) -> () to @callee_owned (@unowned Swift.UnsafeBufferPointer<Swift.UInt8>) -> (@out ()), Argument Types : [@callee_owned (@unowned Swift.UnsafeBufferPointer<Swift.UInt8>) -> ()]> of generic specialization <preserving fragile attribute, ()> of Swift.StaticString.withUTF8Buffer<A>((Swift.UnsafeBufferPointer<Swift.UInt8>) -> A) -> A + 187
    frame #4: 0x0000000116ae00f1 libswiftCore.dylib`partial apply forwarder for closure #2 (Swift.UnsafeBufferPointer<Swift.UInt8>) -> () in Swift._fatalErrorMessage(Swift.StaticString, Swift.StaticString, file: Swift.StaticString, line: Swift.UInt, flags: Swift.UInt32) -> Swift.Never + 113
    frame #5: 0x00000001168571eb libswiftCore.dylib`function signature specialization <preserving fragile attribute, Arg[1] = [Closure Propagated : reabstraction thunk helper from @callee_owned (@unowned Swift.UnsafeBufferPointer<Swift.UInt8>) -> () to @callee_owned (@unowned Swift.UnsafeBufferPointer<Swift.UInt8>) -> (@out ()), Argument Types : [@callee_owned (@unowned Swift.UnsafeBufferPointer<Swift.UInt8>) -> ()]> of generic specialization <preserving fragile attribute, ()> of Swift.StaticString.withUTF8Buffer<A>((Swift.UnsafeBufferPointer<Swift.UInt8>) -> A) -> A + 187
    frame #6: 0x0000000116a0b410 libswiftCore.dylib`function signature specialization <preserving fragile attribute, Arg[2] = Dead, Arg[3] = Dead> of Swift._fatalErrorMessage(Swift.StaticString, Swift.StaticString, file: Swift.StaticString, line: Swift.UInt, flags: Swift.UInt32) -> Swift.Never + 96
  * frame #7: 0x000000010ffccbea Charts`AxisRendererBase.computeAxisValues(min=NaN, max=NaN, self=0x0000600000e58630) at AxisRendererBase.swift:125
    frame #8: 0x0000000110177e05 Charts`XAxisRenderer.computeAxisValues(min=NaN, max=NaN, self=0x0000600000e58630) at XAxisRenderer.swift:62
    frame #9: 0x0000000110177d3e Charts`XAxisRenderer.computeAxis(min=0, max=2.02, inverted=false, self=0x0000600000e58630) at XAxisRenderer.swift:57
    frame #10: 0x000000010fffe704 Charts`BarLineChartViewBase.notifyDataSetChanged(self=0x00007fe4bb579560) at BarLineChartViewBase.swift:332
    frame #11: 0x000000011007618a Charts`ChartViewBase.data.setter(newValue=0x0000600000a900e0, self=0x00007fe4bb579560) at ChartViewBase.swift:269
    frame #12: 0x000000010e6fc1d2 CittaDoctor.ai`GraphCell.drawGraph(values=3 values, isBPGraph=false, xArray=3 elements, minValue=0, maxValue=150, values2=3 values, graphColor=0x00006000012763c0, normalRange=(unit = "", maximum = 0, minimum = 0), optionArrays=CittaDoctor_ai.Options @ 0x00007ffee162c6f8, self=0x00007fe4bb5d5700) at GraphCell.swift:457
    frame #13: 0x000000010e784423 CittaDoctor.ai`HistoryListingCV.collectionView(collectionView=0x00007fe4b6add000, indexPath=2 indices, self=0x00007fe4b6b16c00) at HistoryListingCV.swift:177
    frame #14: 0x000000010e784bbc CittaDoctor.ai`@objc HistoryListingCV.collectionView(_:cellForItemAt:) at HistoryListingCV.swift:0
    frame #15: 0x00000001127d48b6 UIKit`-[UICollectionView _createPreparedCellForItemAtIndexPath:withLayoutAttributes:applyAttributes:isFocused:notify:] + 290

Charts Environment

Charts version/Branch/Commit Number:3.0.4 Xcode version:9.2 Swift version:4 Platform(s) running Charts:iOS 11.3 macOS version running Xcode: 10.13.4

Demo Project

ℹ Please link to or upload a project we can download that reproduces the issue.

liuxuan30 commented 6 years ago

please take a look at

    frame #8: 0x0000000110177e05 Charts`XAxisRenderer.computeAxisValues(min=NaN, max=NaN, self=0x0000600000e58630) at XAxisRenderer.swift:62
    frame #9: 0x0000000110177d3e Charts`XAxisRenderer.computeAxis(min=0, max=2.02, inverted=false, self=0x0000600000e58630) at XAxisRenderer.swift:57

why computeAxis generate NaN. Something wrong with your matrix.

please debug and provide more debug trace so we know where goes off.

    open override func computeAxis(min: Double, max: Double, inverted: Bool)
    {
        var min = min, max = max

        if let transformer = self.transformer
        {
            // calculate the starting and entry point of the y-labels (depending on
            // zoom / contentrect bounds)
            if viewPortHandler.contentWidth > 10 && !viewPortHandler.isFullyZoomedOutX
            {
                let p1 = transformer.valueForTouchPoint(CGPoint(x: viewPortHandler.contentLeft, y: viewPortHandler.contentTop))
                let p2 = transformer.valueForTouchPoint(CGPoint(x: viewPortHandler.contentRight, y: viewPortHandler.contentTop))

                if inverted
                {
                    min = Double(p2.x)
                    max = Double(p1.x)
                }
                else
                {
                    min = Double(p1.x)
                    max = Double(p2.x)
                }
            }
        }

        computeAxisValues(min: min, max: max)
    }

please debug this function and print out all values that lead min/max to NaN

Saranjithpk commented 6 years ago

I just put your code in computeAxis Function. but I can't find any bool named inverted in the same. So I just removed the if loop and kept true block only. What I put is as below,

    @objc open func computeAxisValues(min: Double, max: Double)
    {
        guard let axis = self.axis else { return }
        var min = min, max = max

        if let transformer = self.transformer
       {
            // calculate the starting and entry point of the y-labels (depending on
            // zoom / contentrect bounds)
            if (viewPortHandler?.contentWidth)! > CGFloat(10.0) && !(viewPortHandler?.isFullyZoomedOutX)!
          {
               let p1 = transformer.valueForTouchPoint(CGPoint(x: (viewPortHandler?.contentLeft)!, y: (viewPortHandler?.contentTop)!))
               let p2 = transformer.valueForTouchPoint(CGPoint(x: (viewPortHandler?.contentRight)!, y: (viewPortHandler?.contentTop)!))

               min = Double(p2.x)
              max = Double(p1.x)
              print("p1.x:", p1.x,"p1.y:", p1.y,"p2.x:",p2.x,"p2.y:",p2.y,"Min:", min, "Max:", max)
        }

    // Rest of code here.
  }

My log before selection on selection and on crash as below,

p1.x: 0.609658677494641 p1.y: 110.0 p2.x: 3.43860364530313 p2.y: 110.0 Min: 3.43860364530313 Max: 0.609658677494641
p1.x: 0.630491120754639 p1.y: 110.0 p2.x: 3.41873694444591 p2.y: 110.0 Min: 3.41873694444591 Max: 0.630491120754639
p1.x: 0.630491120754639 p1.y: 110.0 p2.x: 3.41873694444591 p2.y: 110.0 Min: 3.41873694444591 Max: 0.630491120754639
p1.x: 0.651229249533855 p1.y: 110.0 p2.x: 3.398960185875 p2.y: 110.0 Min: 3.398960185875 Max: 0.651229249533855
p1.x: 0.651229249533855 p1.y: 110.0 p2.x: 3.398960185875 p2.y: 110.0 Min: 3.398960185875 Max: 0.651229249533855
p1.x: 0.667225140225756 p1.y: 110.0 p2.x: 3.38370582653968 p2.y: 110.0 Min: 3.38370582653968 Max: 0.667225140225756
p1.x: 0.667225140225756 p1.y: 110.0 p2.x: 3.38370582653968 p2.y: 110.0 Min: 3.38370582653968 Max: 0.667225140225756
p1.x: 0.696229955557689 p1.y: 110.0 p2.x: 3.35604560529598 p2.y: 110.0 Min: 3.35604560529598 Max: 0.696229955557689
p1.x: 0.696229955557689 p1.y: 110.0 p2.x: 3.35604560529598 p2.y: 110.0 Min: 3.35604560529598 Max: 0.696229955557689
p1.x: 0.71513346472276 p1.y: 110.0 p2.x: 3.33801841774783 p2.y: 110.0 Min: 3.33801841774783 Max: 0.71513346472276
p1.x: 0.71513346472276 p1.y: 110.0 p2.x: 3.33801841774783 p2.y: 110.0 Min: 3.33801841774783 Max: 0.71513346472276
p1.x: 0.730490532059137 p1.y: 110.0 p2.x: 3.32337326744029 p2.y: 110.0 Min: 3.32337326744029 Max: 0.730490532059137
p1.x: 0.730490532059137 p1.y: 110.0 p2.x: 3.32337326744029 p2.y: 110.0 Min: 3.32337326744029 Max: 0.730490532059137
p1.x: 0.751410107522615 p1.y: 110.0 p2.x: 3.30342347362082 p2.y: 110.0 Min: 3.30342347362082 Max: 0.751410107522615
p1.x: 0.751410107522615 p1.y: 110.0 p2.x: 3.30342347362082 p2.y: 110.0 Min: 3.30342347362082 Max: 0.751410107522615
p1.x: 0.751410107522615 p1.y: 110.0 p2.x: 3.30342347362082 p2.y: 110.0 Min: 3.30342347362082 Max: 0.751410107522615
p1.x: 0.751410107522615 p1.y: 110.0 p2.x: 3.30342347362082 p2.y: 110.0 Min: 3.30342347362082 Max: 0.751410107522615
Selected 3.0 90.0 Index) // Logged from selection delegate
p1.x: 0.751410107522615 p1.y: 110.0 p2.x: 3.30342347362082 p2.y: 110.0 Min: 3.30342347362082 Max: 0.751410107522615
p1.x: 0.751410107522615 p1.y: 101.0 p2.x: 3.30342347362082 p2.y: 101.0 Min: 3.30342347362082 Max: 0.751410107522615
p1.x: 0.751410107522615 p1.y: 110.0 p2.x: 3.30342347362082 p2.y: 110.0 Min: 3.30342347362082 Max: 0.751410107522615
p1.x: 0.751410107522615 p1.y: 1.61792382137608e+308 p2.x: 3.30342347362082 p2.y: 1.61792382137608e+308 Min: 3.30342347362082 Max: 0.751410107522615
2018-06-22 11:06:33.865541+0530 PersonalCare[72271:1394154] [Unknown process name] CGAffineTransformInvert: singular matrix.
2018-06-22 11:06:33.865712+0530 PersonalCare[72271:1394154] [Unknown process name] CGAffineTransformInvert: singular matrix.
p1.x: nan p1.y: nan p2.x: nan p2.y: nan Min: nan Max: nan
(lldb)

All variables in the scope during the crash

min Double  78
max Double  102
self    Charts.YAxisRenderer    0x000060400103a180
axis    Charts.YAxis    0x00007fdafdf6ddf0
Charts.AxisBase Charts.AxisBase 
drawBottomYLabelEntryEnabled    Bool    true    
drawTopYLabelEntryEnabled   Bool    true    
inverted    Bool    false   
drawZeroLineEnabled Bool    false   
zeroLineColor   NSUIColor?  0x0000600000443750
zeroLineWidth   CGFloat 1   
zeroLineDashPhase   CGFloat 0   
zeroLineDashLengths [CGFloat]?  nil none
spaceTop    CGFloat 0.10000000000000001 
native  CGFloat.NativeType  0.10000000000000001
spaceBottom CGFloat 0.10000000000000001 
native  CGFloat.NativeType  0.10000000000000001
labelPosition   Charts.YAxis.LabelPosition  outsideChart
_axisDependency Charts.YAxis.AxisDependency right
minWidth    CGFloat 0   
native  CGFloat.NativeType  0
maxWidth    CGFloat +Inf    
native  CGFloat.NativeType  +Inf
min Double  NaN
max Double  NaN
interval    Double  NaN
n   Int 140732830998736
yMin    Double  NaN
yMax    Double  NaN
labelCount  Int 6
range   Double  NaN
rawInterval Double  NaN
intervalMagnitude   Double  
intervalSigDigit    Int 

Please let me know if these information is enough.

Thanks for your support.

liuxuan30 commented 6 years ago

This function I post is within the library... what you mean by I just put your code in computeAxis Function.? If you really copy the code into your project, it won't matter anyway.

What you should do is to add some break points before and after transformer.valueForTouchPoint and figure out what lead to NaN.

Saranjithpk commented 6 years ago

Yeah. I got what u meant now. I have checked the same and seems issue fixed by adding these lines of code into AxisRenderBase.swift at line 125 Since interval is getting as nan.

  if !(interval > 0 || interval < 0) {
       print("Crash solved")
       return
 }

// Rest of code..
let intervalMagnitude = ChartUtils.roundToNextSignificant(number: pow(10.0, Double(Int(log10(interval)))))
        let intervalSigDigit = Int(interval / intervalMagnitude)
liuxuan30 commented 6 years ago

as I said, the point is to figure out how the NaN is created, not to avoid it after that.

Saranjithpk commented 6 years ago

I think nan is created after some calculations done inside the charts library. I can give all the datasets given to charts when crash occurs. It will be great if you can tell me what is the purpose of using this interval property, so that I can debug the same code better

Please let me know if need any further assistance to track issue. Thanks for your help and support.

liuxuan30 commented 6 years ago

hmm, interval is used to calculate the proper 'interval' to use.. I don't remember very clearly, but the code should tell. Will you be able to know what number causes to NaN? We didn't need all data sets values, just what calculation leads to NaN

philfi commented 6 years ago

Hi @Saranjithpk did you ever get anywhere with this?

The best I can tell atm is that the point values by transformer.valueForTouchPoint in the computeAxisValues of XAxisRenderer have x and y property values of .nan. That is what's leading to the downstream exception in AxisRendererBase computeAxisValues. Some quick logging shows that for some reason the tx and ty values of pixelToValueMatrix are nan:

pixelToValueMatrix: CGAffineTransform(a: 19.2000124087434, b: 0.0, c: -0.0, d: -0.344167172330097, tx: nan, ty: nan)

Will continue to dig into how it got into this state and update as I learn more but if you've already made it out of the rabbit hole would appreciate any assistance!

philfi commented 6 years ago

ok not sure if my case is the same as @Saranjithpk but I was able to trace it back to a mistake in my code that passed a duration of 0 into moveViewToAnimated(xValue: Double, yValue: Double, axis: YAxis.AxisDependency, duration: TimeInterval). This in turn led to the phase value to be set to nan in updateAnimationPhase and the rest of the dominoes fell from there.

Glad to have figured this out and would like to contribute something to hopefully keep others from falling into this same trap. @liuxuan30 given the info I've provided, I'm wondering if you think it makes more sense to enforce duration > 0 in AnimatedViewPortJob (though its default value is 0 too so would want to update that as well) or if something else should be done to protect the phase value from being set to nan i.e. if duration <= 0 { phase = 1.0 }?

Sega-Zero commented 6 years ago

having this issue too. pixelToValueMatrix contains nan in tx and ty.

figured out that _matrixValueToPx begin to contain nan values in prepareMatrixValuePx, because chartYMin == +Inf, which is because leftAxis.axisRange is inf.

This happens because leftAxis is disabled and never configured. Not sure if this code should even be calculated if the axis is disabled.

If i comment the line that disables leftAxis - no crash is occured. If I set the values to leftAxis with values for rightAxis + disable leftAxis, no crash is occured.

SURYAKANTSHARMA commented 3 years ago

@liuxuan30 Any update on this ?