/************************* * * * * * * * * * * * * *************************** Copyright (c) 1999-2005 Ryan Bobko ryan@ostrich-emulators.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 2 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, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ************************* * * * * * * * * * * * * ***************************/ #include "piegraph.h" #include "qhacc.h" #include "qhaccutils.h" #include "qhacctable.h" #include "guiconstants.h" #include "qhaccsegmenter.h" #include #include // plugin factory calls extern "C" { QHaccPlugin * create(){ return new PieGraph; } void destroy( PieGraph * p ){ delete p; } } const uint PieGraph::TITLE= 0; const uint PieGraph::TOTAL= 1; const uint PieGraph::MERGED= 2; const uint PieGraph::MERGEROWS=12; PieGraph::PieGraph() : GraphBase(){ refColors=new QColor[0]; } PieGraph::~PieGraph(){ delete [] refColors; if( data ) delete data; } void PieGraph::setup( QHacc * eng ){ if( data ) delete data; GraphBase::setup( eng ); conv.reset( new MonCon( engine ) ); ColType types[]={ CTSTRING, CTFLOAT, CTBOOL }; data=new QHaccTable( 3, types ); data->setPK( TITLE ); // need these ref colors because non-24bit colors will change slightly refColors=new QColor[12]; for ( int i=0; i<12; i++ ){ QPixmap m( 1, 1 ); QPainter p( &m ); p.fillRect( 0, 0, 1, 1, colors[i] ); QImage im=m.convertToImage(); refColors[i]=im.pixel( 0, 0 ); } } void PieGraph::isetData(){ bool de=engine->getBP( "DOUBLEENTRY" ); bool payees=engine->getBP( "GRAPHPIEPAYEES" ); data->clear(); float datatot=0; const int ACCTID=0, PAYEE=1, SUM=2, TID=3, COLS=4; const int GRABCOLS[]={ QC::XSACCTID, QC::XTPAYEE, QC::XSSUM, QC::XTID }; QHaccTable trans( COLS ); QHaccTableIndex * idx=0; const MonCon& conv=engine->converter(); for( uint i=0; irows(); i++ ){ const Account& account=accounts->at( i ); // for each account, grab the transactions between the cutoff dates // and not void, and in the right journal. We need to build the criteria vector criteria; criteria.push_back( TableSelect( QC::XTDATE, start, TableSelect::GE ) ); criteria.push_back( TableSelect( QC::XTDATE, end, TableSelect::LT ) ); criteria.push_back( TableSelect( QC::XTVOID, false ) ); if( journalid!=0 ) criteria.push_back( TableSelect( QC::XTLID, TableCol( journalid ) ) ); // actually grab the data uint rr=0; vector GCOLS; for( int i=0; i atrans=engine->getXTForA( account, GCOLS, criteria, rr ); bool incc=engine->getBP( "GRAPHPIECREDITS" ); bool incd=engine->getBP( "GRAPHPIEDEBITS" ); if( incc && !incd ){ // if we're only including debits or credits, // we need to prune a little more auto_ptr temp( new QHaccResultSet( COLS ) ); if( incc ){ for( uint i=0; iat( i ) ); QString tsum=tr[SUM].gets(); tr.set( SUM, tsum ); if( conv.converti( tsum )>=0 ) temp->add( tr ); } } atrans=temp; rr=atrans->rows(); } else if( !incc && incd ){ auto_ptr temp( new QHaccResultSet( COLS ) ); for( uint i=0; iat( i ); QString tsum=tr[SUM].gets(); tr.set( SUM, tsum ); if( conv.converti( tsum )<0 ) temp->add( tr ); } atrans=temp; rr=atrans->rows(); } // at this point, atrans has all the transactions from the specified // accounts that match the dates, void, and journal criteria if( de && !payees ){ // we don't care about this account's splits, we want splits // who share a transaction with this account for( uint j=0; jat( j ); // get splits that have the same tid as tr, but aren't // from tr's account vector ts; ts.push_back( TableSelect( QC::STID, tr[TID] ) ); ts.push_back( TableSelect( QC::SACCTID, tr[ACCTID], TableSelect::NE ) ); vector cols; cols.push_back( QC::SSUM ); cols.push_back( QC::SACCTID ); uint rr2=0; auto_ptr rslt=engine->getWhere( SPLITS, TableGet( cols ), ts, rr2 ); for( uint k=0; kat( k ); TableCol cols[]={ s[1], tr[PAYEE], s[0], tr[TID] }; trans+=TableRow( cols, COLS ); } } } else trans+=*atrans; } // accounts loop // if de, we want to graph based on split trans' TACCTID // else we want to graph based on trans' TPAYEE if( de && !payees ) idx=new QHaccTableIndex( &trans, ACCTID, CTUINT ); else idx=new QHaccTableIndex( &trans, PAYEE, CTSTRING ); /* cout<<"pie graph working on "<sorts()<<")"<at( i )].toString()<setName( "junkball" ); data->startLoad( sz-1 ); for( uint i=0; iat( FI )]; TableCol name; // figure out the partition heading if( de && !payees ){ const Account& account=engine->getA( t[ACCTID].getu() ); name=account[QC::ANAME]; } else name=t[PAYEE]; // figure out the associated value int sum=0; for( uint j=FI; jat( j )].gets( SUM ) ); } if( sum<0 ) sum=0-sum; // we only want positive values to graph //cout<<"segment ["<add( newrow ); } delete [] pos; data->stopLoad(); //cout<<"data for graph:"<rows(); i++ ) cout<<" "<at( i ).toString()<rows(); i++ ){ const TableRow& row=data->at( i ); int sum=row.geti( TOTAL ); TableCol nsum( ( float )sum/( float )datatot ); data->updateWhere( TableSelect( TITLE, row[TITLE] ), TableUpdate( TOTAL, nsum ) ); } // PRUNING: we want to prune datapoints if their percentage // is below MERGEPCT or there are more rows than MERGEROWS summer=new QHaccTableIndex( data, TOTAL, CTFLOAT ); TableCol total( 0 ); TableCol d[]={ TableCol( QT_TR_NOOP( "Other" ) ), total, TableCol( true ) }; TableRow other( d, 3 ); const float MERGEPCT=engine->getFP( "MERGEPCT" )/100; // figure out if any datapoints are too small to graph if( !data->isEmpty() ){ float perc=0; while( percat( summer->at( 0 ) ); perc=row.getf( 1 ); if( percdeleteWhere( TableSelect( TITLE, row[TITLE] ) ); summer->reindex(); } } } // if we still have too many account, so merge the smallest ones if( data->rows()>MERGEROWS-1 ){ // yep, we're pruning float sum=0; // the index sorts ascendingly, so remove rows from the front uint lim=data->rows()-MERGEROWS; for( uint i=0; i<=lim; i++ ){ const TableRow& row=data->at( summer->at( 0 ) ); sum+=row.getf( 1 ); data->deleteWhere( TableSelect( TITLE, row[TITLE] ) ); } other.set( 1, TableCol( sum ) ); } if( other.getf( 1 )>0 ) data->add( TableRow( other ) ); summer->reindex(); } void PieGraph::paintBase( QPainter * p, const QRect& size ){ // cout<<"piepainter base"<fontMetrics(); int fmh=fm.height(); bool showTots=engine->getBP( "GRAPHSHOWTOTALS" ); uint rows=data->rows(); int baseh=5*fmh; // allow at most 5 rows for the legend zeroline=-1; baseline=size.height()-baseh; int wspot=size.left(), hspot=baseline; // width per account--each account get's 1/3 of the screen space for its name int wpa=size.width()/3; for( uint i=0; iat( rows-1-i ); const TableRow& row=data->at( idx ); if( i%3==0 ){ wspot=0; // carriage return hspot+=fmh; // new line } else wspot+=wpa; QString perc=temp.setNum( row.getf( TOTAL )*100, 'f', 0 ); int pw=fm.width( perc ); if( pw<15 ) pw=15; int pw2=pw+2; // draw the colored square for the legend p->fillRect( wspot, hspot, pw, 15, colors[idx] ); // draw the appropriate name p->drawText( wspot+pw2, hspot, wpa-pw2, fmh, p->AlignLeft, row[TITLE].gets() ); // draw the totals in the box, if necessary if( showTots ) p->drawText( wspot, hspot, pw, fmh, p->AlignCenter, perc ); } } void PieGraph::paintMain( QPainter * p, const QRect& size ){ //cout<<"piepainter main"<isEmpty() ){ p->drawText( size.left(), size.top(), size.width(), size.height(), p->AlignCenter, EMPTYSTR ); return; } int h=baseline; int w=size.width(); int oldh=size.top(); uint rows=data->rows(); for ( uint i=0; iat( rows-1-i ); p->setBrush( colors[idx] ); const TableRow& row=data->at( idx ); //cout<<"brush color is "<drawPie( 10, 10, w-20, h-20, oldh, hspot ); oldh+=hspot; } } void PieGraph::painted( const QPixmap& pix ){ image=pix.convertToImage(); } TableSelect PieGraph::mouseSel( const QPoint& p ) const { // the colors returned by image.pixel are slightly different from the // colors in the colors array. Thus, we can't figure out accounts yet. // go through the colors, and see if we can find the right account bool de=engine->getBP( "DOUBLEENTRY" ); bool payees=engine->getBP( "GRAPHPIEPAYEES" ); TableSelect ret; QColor testcolor( image.pixel( p.x(), p.y() ) ); //cout<<"("<rows(); i++ ){ //cout<<" against("<at( i ).toString()<at( i ); if( de && !payees ){ const Account& accter=engine->getA( row[TITLE].gets() ); if( !accter.isNull() ){ ret=TableSelect( QC::XSACCTID, accter[QC::AID] ); } } else ret=TableSelect( QC::XTPAYEE, row[TITLE] ); } } return ret; } const PieInfo PieGraph::pinfo; const PluginInfo& PieGraph::info() const { return pinfo; }