Android Open Source - SpunkyCharts Price By Volume Chart Fragment Adapter






From Project

Back to project page SpunkyCharts.

License

The source code is released under:

GNU General Public License

If you think the Android project SpunkyCharts listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

package com.jogden.spunkycharts.pricebyvolumechart;
/* /*w w w . j  ava 2  s . com*/
Copyright (C) 2014 Jonathon Ogden     < jeog.dev@gmail.com >

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see http://www.gnu.org/licenses.
*/
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.NavigableSet;
import java.util.UUID;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import android.annotation.SuppressLint;
import android.content.BroadcastReceiver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.database.Cursor;
import android.os.ConditionVariable;
import android.os.Handler;
import android.support.v4.content.LocalBroadcastManager;
import android.text.format.Time;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;

import com.jogden.spunkycharts.ApplicationPreferences;
import com.jogden.spunkycharts.MainApplication;
import com.jogden.spunkycharts.R;
import com.jogden.spunkycharts.data.DataClientLocalDebug;
import com.jogden.spunkycharts.data.DataContentService;
import com.jogden.spunkycharts.data.DataContentService.DataClientInterface;
import com.jogden.spunkycharts.data.DataContentService.DataConsumerInterface;
import com.jogden.spunkycharts.misc.OHLC;
import com.jogden.spunkycharts.misc.Pair;
import com.jogden.spunkycharts.traditionalchart.YAxisPriceLabel;
import com.jogden.spunkycharts.pricebyvolumechart.draw.DrawSemantics_SILO;

@SuppressLint("InflateParams")
public class PriceByVolumeChartFragmentAdapter 
    implements DataContentService.DataConsumerInterface       
{
    @SuppressWarnings("serial")
    public static class PriceByVolumeChartLogicError 
        extends RuntimeException {
            public PriceByVolumeChartLogicError(String msg){
                super(msg);
            }
        }
    /**/
    public final String MY_ID = UUID.randomUUID().toString();
    /**/
    private String _symbol = null;
    
    private final int MSPM = 60000;
    private final int GMS = 
        MSPM * DataClientInterface.TIME_GRANULARITY;
    
    /* the important child views */
    private final PriceByVolumeChartPanel _mainSgmnt;
    private final LinearLayout _priceAxis;
    private final LinearLayout _volumeAxis;  
    private final ViewGroup _myContainer;
    
    private final LayoutInflater _inflater;    
    private final Context _cntxt;
    private final Handler _guiThrdHndlr;    
     
    /* also chart specific settings but
     * currently changed for all charts of this type */
    private int _axisFontSz = 
        ApplicationPreferences.getAxisFontSize();
    private int _axisFontColor = 
        ApplicationPreferences.getAxisFontColor();
    private int _axisTimeout = 
        ApplicationPreferences.getLongTimeout();
    private int _axisTimeoutIncr =
        ApplicationPreferences.getTimeoutIncrement();
    
    private int _priceElemHght = 0;    
    private int _volSegHght = 0;     
    
    private volatile float _highPrice = 0;
    private volatile float _lowPrice = Float.MAX_VALUE;    
    private volatile int _highVolume = 0;
    
    private int _segCount = 0;
    private float _segSize = 0;
    
    private volatile Time _updateTime = new Time();    
        
    private Cursor _myCursor;   
    
    static private String[] columns = {
        "OPEN_COLUMN", "HIGH_COLUMN",
       "LOW_COLUMN", "CLOSE_COLUMN", "VOLUMN_COLUMN"    
       };
   
   @SuppressWarnings("serial")
   static private List<Pair<String,SQLType>> myColumnInfo = 
       new ArrayList<Pair<String,SQLType>>(){{       
           add(new Pair<String,SQLType>(
               columns[0],SQLType.FloatNotNull));
           add(new Pair<String,SQLType>(
               columns[1],SQLType.FloatNotNull));
           add(new Pair<String,SQLType>(
               columns[2],SQLType.FloatNotNull));
           add(new Pair<String,SQLType>(
               columns[3],SQLType.FloatNotNull));
           add(new Pair<String,SQLType>(
               columns[4],SQLType.IntNotNull));
       }};
 
    private AtomicBoolean _hasSegment = new AtomicBoolean(false);
    private AtomicBoolean _streamReady = new AtomicBoolean(false);
    private AtomicBoolean _updateReady = new AtomicBoolean(false);
    
    private volatile OHLC _lastGranularPriceSeg = new OHLC(0);  
    private volatile int _lastGranularVolSeg = 0;  
    private volatile Time _lastGranularSegTime = new Time();  
    
    private final Lock _volumeAxisLck = new ReentrantLock();
    private final Lock _priceAxisLck = new ReentrantLock();
        
    private volatile Thread _priceAxisThrd = null;    
    private final Object _priceAxisThrdMntr = new Object();
    
    private int _priceAxisPrmptCnt = 0;
    private final BlockingDeque<Thread> _priceAxisPrmptStck = 
        new LinkedBlockingDeque<Thread>();

    private final NavigableSet<Thread> _activeThreads = 
        new ConcurrentSkipListSet<Thread>(
                new Comparator<Thread>(){
                    @Override
                    public int compare(Thread lhs, Thread rhs) {
                        return (int)lhs.getId() - (int)rhs.getId();            
                    }});
    
    private LocalBroadcastManager _localBcastMngr;
    private static final IntentFilter _rangeFltr = new IntentFilter();    
    static{            
        _rangeFltr.addAction(HighLowChange.NEW_HIGH_PRICE);
        _rangeFltr.addAction(HighLowChange.NEW_LOW_PRICE);
        _rangeFltr.addAction(HighLowChange.NEW_HIGH_VOL);
    }   
    
    /*|--------->         begin PUBLIC interface         <--------|*/
    
    public PriceByVolumeChartFragmentAdapter(
            Context context, Handler handler,
            LocalBroadcastManager broadcastManager,
            PriceByVolumeChartPanel mainChartPanel,
            LinearLayout volumeAxisLayout, LinearLayout priceAxisLayout,          
            ViewGroup fragmentViewGroup, int volumeSegmentHeight,  
            Cursor cursor, String symbol, Boolean... bArgs                    
            ){
                this._cntxt = context;
                this._guiThrdHndlr = handler;    
                this._localBcastMngr = broadcastManager;
                this._mainSgmnt = mainChartPanel;
                this._volumeAxis = volumeAxisLayout;
                this._priceAxis = priceAxisLayout;
                this._myContainer = fragmentViewGroup;
                this._volSegHght = volumeSegmentHeight;                 
                this._myCursor = cursor;    
                this._symbol = symbol;
                this._localBcastMngr.registerReceiver( 
                    new HighLowChange(), _rangeFltr 
                    );        
                this._inflater = LayoutInflater.from(_cntxt);                
                this._setPricePanelDrawSemantics();   
                _mainSgmnt.init(
                    new PriceByVolumeChartPanel.OnNewHighVolume() {                        
                        @Override
                        public void onNewHighVolume(int v)
                        {
                            Intent iii = 
                                new Intent(HighLowChange.NEW_HIGH_VOL);
                            iii.putExtra(HighLowChange.EXTRA_VALUE, v);
                            iii.putExtra(HighLowChange.FRAGMENT_ID, MY_ID);
                            _localBcastMngr.sendBroadcast( iii    );                            
                        }
                    },
                    handler
                    );
                if (_myCursor != null) 
                    this._init();        
            }    
    
    @Override
    public boolean updateReady()
    {       
        return _updateReady.get();
    }

    @Override
    public Cursor swapCursor(Cursor cursor)
    {
        Cursor tmp = _myCursor;
        _myCursor = cursor;
        if (_myCursor != null)
            reset();
        return tmp; 
    }
      
    @Override
    public void update(
        DataClientInterface dataClient, 
        DataConsumerInterface.InsertCallback iCallback
    ){      

        Pair<OHLC,Time> pricePair = dataClient.getLast(
            _symbol, DataClientLocalDebug.DATA_PRICE_DEF
            );
        Pair<Integer,Time> volPair = dataClient.getLast(
            _symbol, DataClientLocalDebug.DATA_VOL_DEF
            );
        
        OHLC price = new OHLC(pricePair.first);
        int vol = volPair.first.intValue();
        Time time = new Time(pricePair.second); 
                       
        /* if we've rolled past TIME_GRANULARITY */    
        if( _hasSegment.get() && 
            _segHasRolled( _lastGranularSegTime, time, 
                DataClientInterface.TIME_GRANULARITY) 
            ){               
                iCallback.insertRow(
                    _createContentValues(
                        _lastGranularPriceSeg,
                        _lastGranularVolSeg, 
                        _lastGranularSegTime                      
                        ),
                    _symbol, (DataConsumerInterface)this
                    );      
                
                _lastGranularSegTime = 
                    _truncateTime(time,GMS,true,false);    /*
                Log.d("Data-Consumer-Update-DB", 
                    _lastGranularSegTime.format("%H:%M:%S")
                    );   */
            }       
        _lastGranularPriceSeg = new OHLC(price);
        _lastGranularVolSeg = vol;     
   

        if( !_streamReady.get() || _myCursor == null ){
            _updateTime.set(time);    
            return;
        }
           /* Log.d("Data-Consumer-Update-Current", 
            _lastPriceSeg.toString() + "    "
           + String.valueOf(_lastVolSeg) + "   " 
           +time.format("%H:%M:%S"));            */      
           
        if (price.high > _highPrice) {   
            _highPrice = price.high;
            Intent iii = 
                new Intent(HighLowChange.NEW_HIGH_PRICE);                                
            iii.putExtra(HighLowChange.EXTRA_VALUE, price.high); 
            iii.putExtra(HighLowChange.FRAGMENT_ID, MY_ID);
            _localBcastMngr.sendBroadcast( iii    );
        }
        if (price.low < _lowPrice) {
            _lowPrice = price.low;
            Intent iii = 
                new Intent(HighLowChange.NEW_LOW_PRICE);
            iii.putExtra(HighLowChange.EXTRA_VALUE, price.low);
            iii.putExtra(HighLowChange.FRAGMENT_ID, MY_ID);
            _localBcastMngr.sendBroadcast( iii    );                                
        }         
        
        try{ /* remember vol is cumm. vis-a-vis the granular seg */   
            _mainSgmnt.update( _getBucketIndex(price.close), vol );
        }catch(RuntimeException e){ //DEBUG
            Log.d("UPDATE EXCEPTION", "::BEGIN:: " + e.getMessage());
            e.printStackTrace();
            Log.d("UPDATE EXCEPTION", "::END:: " + e.getMessage());
        }
        
        /* high volume is checked in the panel, where it is agglomerated */
   
        _updateTime.set(time);    
    }    

    @Override
    public void bridge(
        DataClientInterface dataClient, 
        DataConsumerInterface.InsertCallback iCallback
    ){
        _updateReady.set(false);
        iCallback.clear(_symbol,(DataConsumerInterface)this);
        
        Pair<OHLC[],Time> pricePair = dataClient.getBulk(
            _symbol, null, DataClientLocalDebug.DATA_PRICE_DEF
            );
       Pair< Integer[],Time> volPair = dataClient.getBulk(
            _symbol, null, DataClientLocalDebug.DATA_VOL_DEF
            );    
       
        final OHLC[] prices = pricePair.first;
        final Integer[] vols = volPair.first;
        int len = vols.length;
        if(prices.length != len)
            throw new IllegalStateException(
                "price / volume getBulk size inconconsistency"
                );                    
      
        _updateTime.set(pricePair.second);
        --len;      
        long endMs = _updateTime.toMillis(true);      
        endMs /= GMS;
        endMs *= GMS;
        List<ContentValues> valsList = new
            ArrayList<ContentValues>();
        Time t = new Time();
        for(int i = 0; i < len; ++i){           
            t.set(endMs  -  ((len-i)*GMS));
            valsList.add( _createContentValues( prices[i],vols[i],t) ); 
            /*Log.d("Data-Consumer-Get-Bulk", 
               prices[i].toString() +"   " +
               String.valueOf(vols[i]) + "   " + 
               t.format("%H:%M:%S")); */
        }
        
        OHLC price = prices[len];  
        _lastGranularPriceSeg = new OHLC(price);
        _lastGranularVolSeg = vols[len];         
        _lastGranularSegTime.set(endMs);
        _updateReady.set(true);       
        
        iCallback.insertRows(
            valsList,_symbol,(DataConsumerInterface)this
            );   

    }

    @Override
    public List<Pair<String, SQLType>> getColumns()
    {
        return myColumnInfo;
    }
    
    public void reset(){
        cleanUpAndUpdate();
        _init();    
    }
    
    public void cleanUpAndUpdate() 
    {            
        _streamReady.set( false);          
        for( Thread t : _activeThreads )
            t.interrupt();        
        _clear(); /* leave display values */
        _highPrice = 0; 
        _lowPrice = Float.MAX_VALUE;
        _highVolume = 0;        
    }     
    
    public void axisRefresh()
    {
        try{           
            _guiThrdHndlr.post( 
                new Runnable(){ 
                    public void run(){                                                      
                        _volumeAxis.invalidate();                
                    }});
        }catch(IllegalStateException e){
            e.printStackTrace();
        }
    }  
    /* adjust vertical zoom 
     * NEED TO REDRAW EVERYTHING FOR THIS
    public void setSegmentHeight(int h)
    {
        if( h > MAX_SEG_HEIGHT )
            h = MAX_SEG_HEIGHT;
        else if( h < MIN_SEG_HEIGHT )
            h = MIN_SEG_HEIGHT;    
        _volSegHght = h;
        _mainSgmnt.setSegmentHeight(h);            
        _reset();
    }*/
    public int getSegmentHeight()
    {
        return _volSegHght;
    } 
    public void setAxisFontSize(int sp)
    {
        _axisFontSz = sp;        
        reset();
    }
    public void setAxisFontColor(int colorId)
    {
        _axisFontColor = colorId;        
        axisRefresh();
    }

    public void setAxisTimeout(int value)
    { /* should reset but cost not worth benefit*/
        _axisTimeout = value;
        axisRefresh();
    }
    public void setTimeoutIncrement(int value)
    {
         /* should reset but cost not worth benefit*/
        _axisTimeoutIncr = value;
        axisRefresh();
    }    
        
    /*|--------->         end PUBLIC interface         <--------|*/
    /*|--------->         begin PRIVATE methods         <--------|*/      

    // CONSIDER MAKING THESE GLOBAL UTILITIES
    private boolean _segHasRolled(Time t1, Time t2, int minutes)
    { /* this only checks up to hours, i.e. same time tomorrow 
        won't be treated as a roll (erroneously) */
        int rMin1 =  t1.minute / minutes;
        int rMin2 = t2.minute / minutes; 
        return ( rMin1 != rMin2 || t1.hour != t2.hour);        
    }
    private Time _truncateTime(
        Time t, long msInterval, 
        boolean ignoreDST, boolean inPlace
    ){
        long ms = t.toMillis(ignoreDST);
        ms /= msInterval;
        ms *= msInterval;
        if(inPlace){
            t.set(ms);
            return t;
        } else {
            Time nTime = new Time();
            nTime.set(ms);
            return nTime;
        }      
    }
    private void _createVolumeAxisX(){
        Thread thisThread =     
            new Thread() {          
                 public void run() {                                          
                     if( !_volumeAxisLck.tryLock())        
                         return;
                     try{        
                        final YAxisPriceLabel tv 
                            = (YAxisPriceLabel)_inflater.inflate( 
                                R.layout.y_axis_price_label, null
                                );                             
                        tv.setText( Integer.toString(_highVolume));
                        tv.setTextSize(_axisFontSz);
                        tv.setTextColor(_axisFontColor);
                        _guiThrdHndlr.post( 
                            new Runnable(){ 
                                public void run(){
                                    _volumeAxis.removeAllViewsInLayout();
                                    _volumeAxis.addView(tv);                                                            
                                }});
                        /*adjust volume axis to align*/
                        int tvWidth = 0;
                        int failCount = 0;                       
                        while( (tvWidth = tv.getWidth() / 2) == 0 ) {                                                     
                            Thread.sleep(_axisTimeoutIncr);
                            if( (failCount+=_axisTimeoutIncr) >= _axisTimeout )
                                return;  /* if timeout forget about align */      
                            }                      
                        _mainSgmnt.setHorizontalBuffers( 0, tvWidth );     
                        _guiThrdHndlr.post( 
                            new Runnable(){ 
                                public void run(){
                                    _mainSgmnt.forceDraw();                                                         
                                }});
                     }catch(InterruptedException e) {                    
                     }catch(RuntimeException e ){
                            throw e;   
                     }finally{                         
                         _activeThreads.remove( this );         
                         _volumeAxisLck.unlock();
                     }                             
                 }
             };
         _activeThreads.add(thisThread); 
         thisThread.start();           
        
    }
    
    private int _getBucketIndex(float p)
    {
        return (int) ((p - _lowPrice + (_segSize/2f)) / _segSize);    
    }    
    
    /* sync to avoid race with _agglomerate */
    private synchronized void _findPriceRange()
    {
        float tmpHigh, tmpLow;
        _myCursor.moveToPosition(-1);  
        while ( _myCursor.moveToNext() ) {  
            tmpHigh = _myCursor.getFloat(2);
            tmpLow = _myCursor.getFloat(3);            
            if(tmpHigh > _highPrice)
                _highPrice = tmpHigh;
            if(tmpLow < _lowPrice)
                _lowPrice = tmpLow;                                          
        }
        _myCursor.moveToPosition(-1);  
    }
    
    /// adjust high and low the assumed high and low from
    // end buckets/segs
    private synchronized void _agglomerate()
    {           
        // should we just pass these in vs global??
        if(_segCount <= 0 || _segSize <= 0){
            throw new IllegalStateException(
                "_segCount and _segSize can't be <= 0"
                );                      
        }                       
        final int[] buckets = new int[_segCount];
        Arrays.fill(buckets, 0);                 
        float mPrice;
        int gVol;
        _myCursor.moveToPosition(-1);            
        /* loop through and agglomerate the volumes by bucket */
        while ( _myCursor.moveToNext() ) {  
            mPrice = /* take the midpoint of the OHLC */
                ( _myCursor.getFloat(2) + _myCursor.getFloat(3) ) / 2;
            gVol = _myCursor.getInt(5);                           
            buckets[ _getBucketIndex(mPrice) ] += gVol;           
        }     
        _myCursor.moveToPosition(-1);       
        
        /* pass the buckets to the panel */
        _mainSgmnt.populate(buckets); //, true);
        _streamReady.set(true);   
    }
    
    private Thread _createPriceAxisAndMainPanel()
    {
        Thread thisThread =
            new Thread() {        
                public void run() {
                    ///////////////////////////////////
                    /*     START PREEMPTION LOGIC     */
                    /////////////////////////////////
                    boolean noWait;
                    final int MAX_PREEMPT_COUNT =
                        ApplicationPreferences.getMaxPreemptCount();
                    if( !(noWait = _priceAxisLck.tryLock()) )    {
                        synchronized(_priceAxisThrdMntr){
                            if( _priceAxisPrmptCnt++ > MAX_PREEMPT_COUNT )
                                return;
                            if( _priceAxisThrd != null)                                    
                                _priceAxisThrd.interrupt();
                        }
                        _priceAxisPrmptStck.offer(this); 
                        _priceAxisLck.lock();
                    }
                    try{ /* everything that follows should assume we own monitor */
                        if( !noWait) {
                            if( this != _priceAxisPrmptStck.peekLast())
                                return;
                            else    { /* don't assume stack hasn't grown since the peek */
                                Iterator<Thread> dIter =
                                    _priceAxisPrmptStck.iterator();                        
                                do{                                
                                    _priceAxisPrmptStck.poll();
                                }while(dIter.next() != this);
                            }                    
                        }
                        synchronized(_priceAxisThrdMntr){ 
                            _priceAxisThrd = this; 
                            }    
                        /////////////////////////////////
                        /*     END PREEMPTION LOGIC     */
                        //////////////////////////////                            
                        
                        float rangeDiff = _highPrice - _lowPrice;    
                        /* deal with problematic high/low parameters */    
                        if (rangeDiff <= 0 || _highPrice < 0 || _lowPrice < 0)         
                            if (rangeDiff == 0 && _highPrice > 0) {                 
                                _highPrice *= 1.001;
                                _lowPrice *= .999;
                                rangeDiff = _highPrice - _lowPrice;                            
                            } else
                                throw new IllegalStateException(
                                    "Invalid high and/or low price in the ChartAdapter"
                                    );        
                        /* if we haven't calculated the height of a price-label view */
                        if( _priceElemHght == 0) {/* can cached value doesn't go stale? */                        
                            final YAxisPriceLabel tv1 
                                = (YAxisPriceLabel)_inflater.inflate( 
                                    R.layout.y_axis_price_label, null
                                    );
                            tv1.setText("X"); 
                            tv1.setTextSize(_axisFontSz);
                            tv1.setVisibility(View.INVISIBLE);
                            final ConditionVariable cond = new ConditionVariable();            
                            _guiThrdHndlr.post( 
                                new Runnable(){ 
                                    public void run(){
                                        _priceAxis.removeAllViews();
                                        _priceAxis.addView(tv1);
                                        cond.open();
                                    }});                                
                            cond.block();                
                            cond.close();                
                            YAxisPriceLabel tv1b = 
                                (YAxisPriceLabel)_priceAxis.getChildAt(0);    
                            /* make sure a valid priceElemHeightt, or quit entirely */    
                            /* just spin, a new thread preempts us anyway */
                            while( (_priceElemHght = tv1b.getHeight()) == 0 )
                                Thread.sleep(_axisTimeoutIncr);                                                            
                        }
                        _guiThrdHndlr.post( 
                            new Runnable(){ 
                                public void run(){ 
                                    _priceAxis.removeAllViews(); 
                                }});                        
                        int totalHeight;                    
                        /* make sure a valid totalHeight, or quit entirely */    
                        /* just spin, a new thread preempts us anyway */
                        while( (totalHeight = _priceAxis.getHeight()) == 0 || 
                                        totalHeight > _myContainer.getHeight() )                                                                                    
                                            Thread.sleep(_axisTimeoutIncr);                  
                        float[] incrVals = new float[2];
                        try{          
                            int maxNodes = (int)(totalHeight / _priceElemHght);
                            if(rangeDiff < 0 || maxNodes < 0)
                                throw new PriceByVolumeChartLogicError(
                                    "rangeDiff and maxNodes can't be negative."
                                    );                            
                            incrVals = /* call down to our native sub to find increment values */
                                MainApplication.IncrementRegressNative( rangeDiff, maxNodes );
                            if( incrVals[0] < 0 ||  incrVals[1] <= 0)  
                                throw new PriceByVolumeChartLogicError(
                                    "IncrementRegressNative() sub-routine aborted. " + 
                                    "retVals[0]: " + incrVals[0] + 
                                    "retVals[1]: " + incrVals[1] +
                                    "adjRangeDiff: " + rangeDiff + 
                                    "maxNodes" + maxNodes
                                    );
                            _segCount = (int)incrVals[1];
                            _segSize = incrVals[0];
                        }catch(PriceByVolumeChartLogicError e){                            
                            Log.e("PriceByVolumeChartLogicError", e.getMessage());
                            return; /* just leave the axis empty*/
                        }                        
                      
                        /* adjust height to new increment values */
                        final int adjPriceElemHeight = (int)(totalHeight / _segCount);    
                        /* we'll need to account for any unused spaced in the axis */
                        final int vacantHeight = 
                            totalHeight - (int)(adjPriceElemHeight * _segCount);                                
                        double distFromIncr = Math.IEEEremainder( 
                                (double)_highPrice, (double)_segSize
                                ); /* distance from rounded incr value */
                        double adjTopNodeVal =  
                            (double)_highPrice - distFromIncr;
                        if(distFromIncr > 0) /* be sure to round to the upper incr */
                            adjTopNodeVal += _segSize;        
                        DecimalFormat df = new DecimalFormat("0.00");    
                        double lastNodeVal = adjTopNodeVal;                
                        int count = 0;
                        do{    /* loop through the increments */        
                            final YAxisPriceLabel tv 
                                = (YAxisPriceLabel)_inflater.inflate( 
                                    R.layout.y_axis_price_label, null
                                    );             
                            tv.setTextSize(_axisFontSz);
                            tv.setText( df.format( lastNodeVal ) );    
                            tv.setTextColor(_axisFontColor);
                            tv.setLayoutParams(
                                new LinearLayout.LayoutParams(
                                    LinearLayout.LayoutParams.WRAP_CONTENT,
                                    0, 1
                                    )
                                );                                 
                            _guiThrdHndlr.post( 
                                new Runnable() { 
                                    public void run() { 
                                        _priceAxis.addView(tv); 
                                    }});            /* for the loop-contingent side-effect */                                       
                        }while( ++count < (int)_segCount &&       
                                    (lastNodeVal-=_segSize) > 0);                 
                        final float halfVacantHeight = vacantHeight / 2; 
                        _mainSgmnt.setVerticalBuffers(
                            (int)halfVacantHeight, (int)halfVacantHeight
                            );
                        _guiThrdHndlr.post( 
                            new Runnable() { 
                                public void run() {                                
                                    _priceAxis.setPadding( /* center price-views */
                                        0, (int)halfVacantHeight,0,(int)halfVacantHeight
                                        );                                                                     
                                    _priceAxis.setClipChildren(false);
                                    _priceAxis.setClipToPadding(false);
                                    _priceAxis.invalidate();                          
                                }});                                 
                     
                        synchronized(_priceAxisThrdMntr) {                         
                            _priceAxisPrmptCnt = 0;
                            }
                    }catch(InterruptedException e){                        
                    }catch(IllegalStateException e){
                        Log.d(
                            "IllegalState(High, Low): ", 
                            "IllegalStateException caught in _createPriceAxisAndMainPanelY: " +
                            Float.toString(_highPrice) + " - " + 
                            Float.toString(_lowPrice)
                            );
                    }catch(RuntimeException e ){
                        e.printStackTrace();
                        throw e; 
                    }finally{           
                      
                        synchronized(_priceAxisThrdMntr) {     
                            _priceAxisThrd = null;                 
                            }
                        _activeThreads.remove(this);
                        _priceAxisLck.unlock();                        
                    }
                }
            };
        _activeThreads.add(thisThread);
        thisThread.start();         
        return thisThread;
    }

    
    private void _setPricePanelDrawSemantics()
    {
        _mainSgmnt.setDrawSemantics(
            DrawSemantics_SILO.class
            );
    }
    
    private ContentValues _createContentValues(
        OHLC price, int volume, Time t
    ){
        ContentValues tmp = new ContentValues();  
        tmp.put(
            DataConsumerInterface.primaryKey, t.toMillis(true)
            );
        tmp.put(columns[0], price.open);
        tmp.put(columns[1], price.high);
        tmp.put(columns[2], price.low);
        tmp.put(columns[3], price.close);
        tmp.put(columns[4], volume);    
        return tmp;       
    }

    private void _init()
    {
        if ( _myCursor != null ) {  
             final int INCR = 
                 ApplicationPreferences.getTimeoutIncrement();
             final int TOUT = 
                 ApplicationPreferences.getLongTimeout();
             int timeOutCount = 0;       
             /* stall for draw completion */
             while( _mainSgmnt.getMeasuredHeight() == 0 
                 && (timeOutCount+=INCR) < TOUT )
                     try {
                         Thread.sleep(INCR);
                     } catch (InterruptedException e) {               
                     }        
            try{
                _findPriceRange();
                _createVolumeAxisX();
                _createPriceAxisAndMainPanel().join();                     
            } catch (InterruptedException e) {                  
            }finally{
                _agglomerate();
                _mainSgmnt.forceDraw();
             }      
        }else
            throw new IllegalStateException(
                "ChartFragmentAdapter can not be initialized "
                + "without a valid Cursor"
                ); 
    }
    private void _clear()
    {
        _activeThreads.clear();
        _mainSgmnt.clear();        
        _priceAxis.removeAllViewsInLayout();        
        _volumeAxis.removeAllViewsInLayout();            
    }


    /*|--------->         end PRIVATE methods         <--------|*/    
    /*|--------->     begin NESTED OBJECT DEFS        <--------|*/    
    
    
    /* APPLICATION WIDE BROADCASTS: broadcast when a price
     * or volume update is at a new extreme. Currently these broadcasts
     * are only relevant within the adapter. */
    class HighLowChange extends BroadcastReceiver 
    {    
        public static final String NEW_HIGH_PRICE =
            "com.jogden.integratedtrader.NEW_HIGH_PRICE";
        public static final String NEW_LOW_PRICE = 
            "com.jogden.integratedtrader.NEW_LOW_PRICE";
        public static final String NEW_HIGH_VOL = 
            "com.jogden.integratedtrader.NEW_HIGH_VOL";
        public static final String EXTRA_VALUE = "EXTRA_VALUE";
        public static final String FRAGMENT_ID = "FRAGMENT_ID";
     
        @Override
        public void onReceive(Context c, Intent i)
        { /* if its for another chart exit quietly */
            if( !i.getStringExtra(FRAGMENT_ID).equals(MY_ID) )
                return;
            String action = i.getAction(); 
            float price;
            if (action.equals(HighLowChange.NEW_HIGH_PRICE)) {
                price = i.getFloatExtra(EXTRA_VALUE, _highPrice);
                if( price > _highPrice ) {        
                    _highPrice = price;
                    try {
                        _createPriceAxisAndMainPanel().join();
                    } catch (InterruptedException e) {                                    
                    } finally{
                        _agglomerate();
                    }
                }
            } else if(action.equals(HighLowChange.NEW_LOW_PRICE)) {
                price = i.getFloatExtra(EXTRA_VALUE, _lowPrice);
                if( price < _lowPrice ) {       
                    _lowPrice = price;
                    try {
                        _createPriceAxisAndMainPanel().join();
                    } catch (InterruptedException e) {                                    
                    } finally{
                        _agglomerate();
                    }
                }
            } else if(action.equals(HighLowChange.NEW_HIGH_VOL)) {
                 int vol = i.getIntExtra(EXTRA_VALUE, _highVolume);
                 if( vol > _highVolume )
                 {
                     _highVolume = vol;
                     _createVolumeAxisX();
                 }                                    
            }
        }        
    }
    
    
}




Java Source Code List

com.jogden.spunkycharts.ApplicationPreferences.java
com.jogden.spunkycharts.BaseChartFragmentA.java
com.jogden.spunkycharts.ChartPanelSurfaceView.java
com.jogden.spunkycharts.DockingPanelActivity.java
com.jogden.spunkycharts.GlobalChartPreferences.java
com.jogden.spunkycharts.InitActivity.java
com.jogden.spunkycharts.MainApplication.java
com.jogden.spunkycharts.OpeningView.java
com.jogden.spunkycharts.animations.BaseAnimationA.java
com.jogden.spunkycharts.animations.BaseEntExAnimationA.java
com.jogden.spunkycharts.animations.BaseSelectAnimationA.java
com.jogden.spunkycharts.animations.HorizontalBulgeAnimation.java
com.jogden.spunkycharts.animations.HorizontalShakeAnimation.java
com.jogden.spunkycharts.animations.VerticalBulgeAnimation.java
com.jogden.spunkycharts.animations.VerticalShakeAnimation.java
com.jogden.spunkycharts.animations.WiggleAnimation.java
com.jogden.spunkycharts.data.DataClientLocalDebug.java
com.jogden.spunkycharts.data.DataContentService.java
com.jogden.spunkycharts.misc.BorderOverlay.java
com.jogden.spunkycharts.misc.ColorPaletteDialog.java
com.jogden.spunkycharts.misc.HideHorizontalLeftOverflowWrapper.java
com.jogden.spunkycharts.misc.OHLC.java
com.jogden.spunkycharts.misc.Pair.java
com.jogden.spunkycharts.misc.TextInput.java
com.jogden.spunkycharts.misc.Triple.java
com.jogden.spunkycharts.pricebyvolumechart.PriceByVolumeChartFragmentAdapter.java
com.jogden.spunkycharts.pricebyvolumechart.PriceByVolumeChartFragment.java
com.jogden.spunkycharts.pricebyvolumechart.PriceByVolumeChartPanel.java
com.jogden.spunkycharts.pricebyvolumechart.PriceByVolumeChartPreferences.java
com.jogden.spunkycharts.pricebyvolumechart.draw.DrawSemanticsA.java
com.jogden.spunkycharts.pricebyvolumechart.draw.DrawSemantics_SILO.java
com.jogden.spunkycharts.traditionalchart.InnerXAxis.java
com.jogden.spunkycharts.traditionalchart.TraditionalChartFragmentAdapter.java
com.jogden.spunkycharts.traditionalchart.TraditionalChartFragment.java
com.jogden.spunkycharts.traditionalchart.TraditionalChartPanel.java
com.jogden.spunkycharts.traditionalchart.TraditionalChartPreferences.java
com.jogden.spunkycharts.traditionalchart.XAxisTimeLabel.java
com.jogden.spunkycharts.traditionalchart.YAxisPriceLabel.java
com.jogden.spunkycharts.traditionalchart.draw.DrawSemanticsA_C.java
com.jogden.spunkycharts.traditionalchart.draw.DrawSemanticsA.java
com.jogden.spunkycharts.traditionalchart.draw.DrawSemantics_CANDLE.java
com.jogden.spunkycharts.traditionalchart.draw.DrawSemantics_LINE.java
com.jogden.spunkycharts.traditionalchart.draw.DrawSemantics_OC.java
com.jogden.spunkycharts.traditionalchart.draw.DrawSemantics_OHLC.java
com.jogden.spunkycharts.traditionalchart.draw.DrawSemantics_POINT.java
com.jogden.spunkycharts.traditionalchart.draw.DrawSemantics_SILO.java