3

I want to be able to update / reload a ScatterPlot on iOS Devices during runtime. To be specific, I record Audio input, do some funny stuff and put the result as a simple NSNumber value into an array. Whenever that happens, reloadData is called on the Plot I want to update but sadly, exactely nothing happens. Basically, I do the same as described in the answer here: real time plotting on iPhone using core plot? . It just doesn't work, so I assume I made some stupid mistake.

This the relevant method:

-(NSNumber *) numberForPlot:(CPTPlot *)plot field:(NSUInteger)fieldEnum recordIndex:(NSUInteger)index {

    double val = (index/5.0) -10;

    if (fieldEnum == CPTScatterPlotFieldX) {
        return [NSNumber numberWithDouble:val];
    } else {
        if ([plot.identifier isEqual: @"X Squared Plot"]) {
            return [NSNumber numberWithDouble:10-(0.25*val*val)];
        } else if( [plot.identifier isEqual:@"LivePlot"]) {
            if (!_isMeasuring) {
                return [NSNumber numberWithFloat:0.0f];
            } else {
                printf("%f", [[_plotIndizesAndVolume objectAtIndex:index] floatValue]);
                return [NSNumber numberWithFloat:[[_plotIndizesAndVolume objectAtIndex:index] floatValue]];
            }
        } else {
            return 0;
        }
    }
}

The X Squared Plot is irrelevant to my needs for now, it's just a reference. The LivePlot is the one I need and want to update. The _plotIndizesAndVolume is the NSMutableArray I store my values in. It has 500 Elements, all of which are initialized with [NSNumber numberWithFloat:0.0f] at the beginning of my execution. The Plot has 500 indizes as well, therefor we don't get out of bounds or whatever.

The code executes fine, the method is being called, the printf statement works and correctly displays the floats, the Plot just doesn't update.

I also tried

if (index < [_plotIndizesAndVolume count]) {
    return [NSNumber numberWithFloat:[_plotIndizesAndVolume objectAtIndex:index]];
} else {
    return [NSNumber numberWithFloat:0.0f];
}

without having the 500 0.0fs in the array, so that I just access values differently from 0.0f. This of course is better, it just doesn't work either.

Any ideas?

Update 1:

  • If I fill the _plotIndizesAndVolume with just random numbers at the beginning of my application, the plot perfectly follows these numbers.

  • If I printf in the method I can perfectly read the updated values once reload has been called on the plot.

  • However, these updated values aren't shown in the plot. The plot remains the way it is after calling -reloadData on it, no matter which values changed in the _plotIndizesAndVolume-Array.

So, from what I can see: I can access the array properly, I can update and read the new values properly, the plot still stays the same and doesn't change. I'm confused. Probably I still made some random stupid mistake, I just don't see it.

Update2:

Well, it seems as if my problem isn't the reloadData itself, but from where I call it. Some more context: I'm using novocaine to measure decibel via the device microphone. This works fine. I then want to visualize part of the measured dB in a graph through corePlot (which I described above). I got a method called startMeasuring which I call somewhen and then - obviously - start measuring decibel. Whenever that happens I put the measured value int _plotIndicesAndVolume and call reloadData on the plot.

The method looks like this (and is largely copied directly from the novocain examples):

-(void) startMeasuring {

    if (_isMeasuring) {
        return;
    }

    __weak ViewController * wself = self;

    self.ringBuffer = new RingBuffer(32768, 2);
    self.audioManager = [Novocaine audioManager];

    // MEASURE SOME DECIBELS!
    // ==================================================
    __block float dbVal = 0.0;
    __block int count = 0;
    __block int limiter = 0;
    [self.audioManager setInputBlock:^(float *data, UInt32 numFrames, UInt32 numChannels) {

        vDSP_vsq(data, 1, data, 1, numFrames*numChannels);
        float meanVal = 0.0;
        vDSP_meanv(data, 1, &meanVal, numFrames*numChannels);

        float one = 1.0;
        vDSP_vdbcon(&meanVal, 1, &one, &meanVal, 1, 1, 0);
        dbVal = dbVal + 0.2*(meanVal - dbVal);

        float reducedVal = (dbVal + 66.0f)/3;

        if (!wself.measuringPaused && limiter > 10) {
            [wself.plotIndizesAndVolume replaceObjectAtIndex:count withObject:[NSNumber numberWithFloat:reducedVal]];
            [[wself.graph plotWithIdentifier:@"LivePlot"] reloadData];
            limiter = -1;
            ++count;
        }
        ++limiter;

    }];

    [self.audioManager play];

    _isMeasuring = true;

}

I call reloadData from within the measuring block. I don't know exactely much about using blocks, I just assumed I could do that. If I, however, try to change the data in the array and afterwards reload the plot manually from somewhere else, it works and the plot updates accordingly. Is the problem related to my call of the method from within the block?

Even then I'm still unsure why it doesn't work, as the printf statement works perfectly - so I'm under the assumption that the updateData method is correctly invoked and the values are correctly updated.

Update 3:

If I call reloadData from somewhere else during the measurement, the graph updates just fine and according to whatever is in _plotIndizesAndVolume. It simply doesn't update if I call it from inside the block (even if I call another method from within the block which in turn calles reloadData). However, I need the live update. I assume I could reload the plot once every x miliseconds as well (going to try that next), I'm still curious as to why it doesn't work in the first place.

Update 4: As assumed in Update 3, if I call reloadData from somewhere else through a NSTimer, it does exactely what I want it to do. Why can't I call it from within the block then?

Community
  • 1
  • 1
Aerius
  • 588
  • 5
  • 24
  • This isn't related to the plot not appearing, but you should return `[_plotIndizesAndVolume objectAtIndex:index]` directly instead of converting it to a float and right back to an `NSNumber`. It will run faster and make your code easier to read. – Eric Skroch Sep 25 '13 at 22:51
  • Are the `xRange` and `yRange` of the plot space set so the data will fall inside the ranges? For example, with 500 data points, the `xRange` should have a location of -10 and length 100 to fit all of the points. – Eric Skroch Sep 25 '13 at 22:57
  • @EricSkroch the NSNumber->float->NSNumber conversion was just me trying to find out if there's a mistake with the way the code handles the object of the array (as objectAtIndex: returns an id), which I couldn't imagine at all, but I wanted to try every possibility, even those I think of as stupid. About the xRange: shouldn't that just be significant for "showing" parts of the plot? It doesn't really matter which parts of the plot I'm showing, nothing changes at all. Or am I under a wrong assumption? – Aerius Sep 26 '13 at 08:12

2 Answers2

3

Call -reloadData (and any other Core Plot methods) from the main thread.

Eric Skroch
  • 27,381
  • 3
  • 29
  • 36
  • As I said in update 4, this is exactely what I did. Why is this the case though? – Aerius Sep 27 '13 at 07:32
  • The timer fires its target method on the main thread. Unless you can guarantee that the block will always execute on the main thread, you should make sure the call to `reloadData` in performed on the main thread. There are several ways to do this, including `-performSelectorOnMainThread:withObject:waitUntilDone:` and GCD. – Eric Skroch Sep 28 '13 at 15:55
2

Probably you need to update your plot space x-range/y-range in your timer

CPTXYPlotSpace *plotSpace = (CPTXYPlotSpace *)self.graph.defaultPlotSpace;
[plotSpace scaleToFitPlots:[self.graph allPlots]];

CPTPlot *thePlot   = [self.graph plotWithIdentifier:kIdentifier];
[thePlot reloadData];
Philip K. Adetiloye
  • 3,102
  • 4
  • 37
  • 63